├── .commitlintrc.js ├── .editorconfig ├── .env.development ├── .env.pre ├── .env.production ├── .env.test ├── .gitignore ├── .idea ├── .gitignore ├── elegant-admin.iml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml └── vcs.xml ├── .lintstagedrc ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── deploy.sh ├── eslint.config.js ├── index.html ├── package.json ├── plop-templates ├── component │ ├── index.hbs │ └── prompt.js ├── mock │ ├── mock.hbs │ └── prompt.js ├── page │ ├── index.hbs │ └── prompt.js └── store │ ├── index.hbs │ └── prompt.js ├── plopfile.js ├── postcss.config.js ├── public ├── browser_upgrade │ ├── chrome.png │ └── edge.png ├── favicon.svg ├── icons │ ├── ant-design-raw.json │ ├── ep-raw.json │ ├── fa-solid-raw.json │ ├── mdi-raw.json │ ├── ri-raw.json │ └── streamline-raw.json └── tinymce │ ├── langs │ └── zh-Hans.js │ └── skins │ ├── content │ ├── dark │ │ ├── content.css │ │ └── content.min.css │ ├── default │ │ ├── content.css │ │ └── content.min.css │ ├── document │ │ ├── content.css │ │ └── content.min.css │ ├── tinymce-5-dark │ │ ├── content.css │ │ └── content.min.css │ ├── tinymce-5 │ │ ├── content.css │ │ └── content.min.css │ └── writer │ │ ├── content.css │ │ └── content.min.css │ └── ui │ ├── oxide-dark │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css │ ├── oxide │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css │ ├── tinymce-5-dark │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css │ └── tinymce-5 │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css ├── scripts └── generate.icons.ts ├── src ├── App.vue ├── api │ ├── config │ │ ├── serviceLoading.ts │ │ └── servicePort.ts │ ├── helper │ │ ├── axiosCancel.ts │ │ └── checkStatus.ts │ ├── index.ts │ ├── interface │ │ ├── index.ts │ │ └── modules │ │ │ ├── auth.ts │ │ │ ├── login.ts │ │ │ └── upload.ts │ └── modules │ │ ├── auth.ts │ │ ├── common.ts │ │ ├── login.ts │ │ └── upload.ts ├── assets │ ├── icons │ │ ├── 403.svg │ │ ├── 404.svg │ │ ├── example-crown.svg │ │ ├── example-emotion-laugh-line.svg │ │ ├── example-emotion-line.svg │ │ ├── example-emotion-unhappy-line.svg │ │ ├── example-star.svg │ │ ├── example-vip.svg │ │ ├── image-load-fail.svg │ │ └── toolbar-collapse.svg │ ├── images │ │ ├── avatar.gif │ │ ├── login-banner.png │ │ ├── login-bg.png │ │ ├── logo.png │ │ └── notData.png │ └── styles │ │ ├── el-themes.scss │ │ ├── element.scss │ │ ├── globals.scss │ │ ├── nprogress.scss │ │ └── resources │ │ ├── utils.scss │ │ └── variables.scss ├── components │ ├── Auth │ │ └── index.vue │ ├── AuthAll │ │ └── index.vue │ ├── CountTo │ │ └── index.vue │ ├── DragBall │ │ └── index.vue │ ├── FileUpload │ │ └── index.vue │ ├── FixedActionBar │ │ └── index.vue │ ├── FormDesign │ │ ├── core │ │ │ ├── components │ │ │ │ ├── asyncLoader │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ ├── builder │ │ │ │ │ ├── hooks │ │ │ │ │ │ └── useBuilder.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── src │ │ │ │ │ │ ├── builder.vue │ │ │ │ │ │ └── types.ts │ │ │ │ ├── designer │ │ │ │ │ ├── index.ts │ │ │ │ │ └── src │ │ │ │ │ │ ├── designer.vue │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ ├── modules │ │ │ │ │ │ ├── actionBar │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── attributeView │ │ │ │ │ │ │ ├── attributeView.vue │ │ │ │ │ │ │ ├── eventView.vue │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── styleView.vue │ │ │ │ │ │ ├── componentView │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── editContainer │ │ │ │ │ │ │ ├── editNodeItem.vue │ │ │ │ │ │ │ ├── editScreenContainer.vue │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ ├── index.vue │ │ │ │ │ │ │ ├── nodeItem.vue │ │ │ │ │ │ │ ├── previewJson.vue │ │ │ │ │ │ │ ├── previewWidgets.vue │ │ │ │ │ │ │ └── toolbar.vue │ │ │ │ │ │ ├── header │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── outline │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── outline.vue │ │ │ │ │ │ ├── preview │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── rightSidebar │ │ │ │ │ │ │ ├── breadcrumb.vue │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ └── sourceCode │ │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ └── types.ts │ │ │ │ ├── icon │ │ │ │ │ ├── index.ts │ │ │ │ │ └── src │ │ │ │ │ │ └── icon.vue │ │ │ │ ├── node │ │ │ │ │ ├── index.ts │ │ │ │ │ └── src │ │ │ │ │ │ ├── dynamicFormItem.vue │ │ │ │ │ │ └── node.vue │ │ │ │ └── tree │ │ │ │ │ ├── index.ts │ │ │ │ │ └── src │ │ │ │ │ ├── index.scss │ │ │ │ │ ├── tree.vue │ │ │ │ │ ├── treeNodeItem.vue │ │ │ │ │ └── treeNodes.vue │ │ │ ├── extensions │ │ │ │ ├── EActionEditor │ │ │ │ │ ├── index.scss │ │ │ │ │ ├── index.vue │ │ │ │ │ └── src │ │ │ │ │ │ ├── EActionEditorItem.vue │ │ │ │ │ │ ├── EActionModal.vue │ │ │ │ │ │ ├── EArgsEditor.vue │ │ │ │ │ │ └── EScriptEdit.vue │ │ │ │ ├── EColEditor │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ ├── EInputSize │ │ │ │ │ └── index.vue │ │ │ │ ├── EOptionsEditor │ │ │ │ │ ├── index.vue │ │ │ │ │ └── optionItem.vue │ │ │ │ ├── ERuleEditor │ │ │ │ │ ├── ERuleItem.vue │ │ │ │ │ ├── data.ts │ │ │ │ │ ├── index.scss │ │ │ │ │ ├── index.vue │ │ │ │ │ └── types.ts │ │ │ │ ├── MonacoEditor │ │ │ │ │ ├── index.ts │ │ │ │ │ └── index.vue │ │ │ │ ├── Page │ │ │ │ │ ├── index.ts │ │ │ │ │ └── index.vue │ │ │ │ └── index.ts │ │ │ ├── index.scss │ │ │ ├── index.ts │ │ │ ├── static │ │ │ │ ├── icons │ │ │ │ │ └── iconify.css │ │ │ │ └── logo.png │ │ │ ├── theme │ │ │ │ └── var.scss │ │ │ └── types │ │ │ │ └── elegant-designer.ts │ │ ├── hooks │ │ │ ├── common │ │ │ │ ├── element.ts │ │ │ │ ├── index.ts │ │ │ │ └── theme.ts │ │ │ ├── index.ts │ │ │ └── store │ │ │ │ └── index.ts │ │ ├── ui │ │ │ ├── elementPlus │ │ │ │ ├── button │ │ │ │ │ ├── button.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── card │ │ │ │ │ ├── card.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── cascader │ │ │ │ │ └── index.ts │ │ │ │ ├── checkbox │ │ │ │ │ ├── checkbox.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── col │ │ │ │ │ ├── col.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── collapse-item │ │ │ │ │ ├── collapseItem.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── collapse │ │ │ │ │ ├── collapse.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── color-picker │ │ │ │ │ └── index.ts │ │ │ │ ├── date-picker │ │ │ │ │ └── index.ts │ │ │ │ ├── form │ │ │ │ │ ├── form.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── formItem │ │ │ │ │ ├── formItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── index.scss │ │ │ │ ├── index.ts │ │ │ │ ├── input-number │ │ │ │ │ └── index.ts │ │ │ │ ├── input │ │ │ │ │ └── index.ts │ │ │ │ ├── modal │ │ │ │ │ ├── index.ts │ │ │ │ │ └── modal.ts │ │ │ │ ├── radio │ │ │ │ │ ├── index.ts │ │ │ │ │ └── radio.ts │ │ │ │ ├── row │ │ │ │ │ ├── index.ts │ │ │ │ │ └── row.ts │ │ │ │ ├── select │ │ │ │ │ ├── index.ts │ │ │ │ │ └── select.ts │ │ │ │ ├── slider │ │ │ │ │ └── index.ts │ │ │ │ ├── switch │ │ │ │ │ └── index.ts │ │ │ │ ├── textarea │ │ │ │ │ └── index.ts │ │ │ │ ├── upload-file │ │ │ │ │ ├── index.ts │ │ │ │ │ └── uploadFile.ts │ │ │ │ └── upload-image │ │ │ │ │ ├── index.ts │ │ │ │ │ └── uploadImage.ts │ │ │ └── index.ts │ │ └── utils │ │ │ ├── common │ │ │ ├── common.ts │ │ │ ├── component.ts │ │ │ ├── data.ts │ │ │ ├── index.ts │ │ │ └── string.ts │ │ │ ├── index.ts │ │ │ └── manager │ │ │ ├── index.ts │ │ │ ├── pageManager.ts │ │ │ ├── pluginManager.ts │ │ │ └── revoke.ts │ ├── FormTable │ │ └── index.vue │ ├── Grid │ │ ├── components │ │ │ └── GridItem.vue │ │ ├── index.vue │ │ └── interface │ │ │ └── index.ts │ ├── IconSelect │ │ ├── data.ts │ │ ├── index.vue │ │ └── select.ts │ ├── ImagePreview │ │ └── index.vue │ ├── ImageUpload │ │ └── index.vue │ ├── ImagesUpload │ │ └── index.vue │ ├── ImgVerify │ │ └── index.vue │ ├── ImportExcel │ │ └── index.vue │ ├── NotAllowed │ │ └── index.vue │ ├── PageHeader │ │ └── index.vue │ ├── PageMain │ │ └── index.vue │ ├── PcasCascader │ │ ├── index.vue │ │ └── pcas-code.json │ ├── ProFlicker │ │ ├── index.css │ │ └── index.ts │ ├── ProTable │ │ ├── components │ │ │ ├── ColSetting.vue │ │ │ ├── Pagination.vue │ │ │ └── TableColumn.vue │ │ ├── index.vue │ │ └── interface │ │ │ └── index.ts │ ├── ProTitle │ │ └── index.vue │ ├── SearchForm │ │ ├── components │ │ │ └── SearchFormItem.vue │ │ ├── index.ts │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── SystemInfo │ │ └── index.vue │ ├── TableSelect │ │ └── index.vue │ ├── Tinymce │ │ └── index.vue │ ├── Trend │ │ └── index.vue │ ├── Typeit │ │ └── index.vue │ └── VideoPlayer │ │ └── index.vue ├── directives │ ├── index.ts │ └── modules │ │ ├── auth.ts │ │ ├── authAll.ts │ │ ├── copy.ts │ │ ├── debounce.ts │ │ ├── draggable.ts │ │ ├── resizeObserver.ts │ │ ├── throttle.ts │ │ └── waterMarker.ts ├── enums │ ├── common.ts │ └── httpEnum.ts ├── hooks │ ├── interface │ │ └── index.ts │ ├── useAuth.ts │ ├── useDownload.ts │ ├── useEcharts.ts │ ├── useExportExcel.ts │ ├── useGlobalProperties.ts │ ├── useHandleData.ts │ ├── useMainPage.ts │ ├── useMenu.ts │ ├── useSelection.ts │ ├── useTabbar.ts │ ├── useTable.ts │ ├── useVerify.ts │ └── useViewTransition.ts ├── iconify │ ├── data.json │ ├── index.json │ └── index.ts ├── layouts │ ├── index.vue │ ├── modules │ │ ├── AppSetting │ │ │ └── index.vue │ │ ├── BackTop │ │ │ └── index.vue │ │ ├── Breadcrumb │ │ │ ├── index.vue │ │ │ └── item.vue │ │ ├── BuyIt │ │ │ └── index.vue │ │ ├── Copyright │ │ │ └── index.vue │ │ ├── Header │ │ │ └── index.vue │ │ ├── HeaderMenu │ │ │ └── index.vue │ │ ├── HotkeysIntro │ │ │ └── index.vue │ │ ├── Logo │ │ │ └── index.vue │ │ ├── MainSidebar │ │ │ └── index.vue │ │ ├── Menu │ │ │ ├── index.vue │ │ │ ├── item.vue │ │ │ ├── sub.vue │ │ │ └── types.ts │ │ ├── Search │ │ │ └── index.vue │ │ ├── SubSidebar │ │ │ └── index.vue │ │ ├── Topbar │ │ │ ├── Tabbar │ │ │ │ └── index.vue │ │ │ ├── Toolbar │ │ │ │ ├── Breadcrumb │ │ │ │ │ └── index.vue │ │ │ │ ├── ColorScheme │ │ │ │ │ └── index.vue │ │ │ │ ├── Fullscreen │ │ │ │ │ └── index.vue │ │ │ │ ├── Language │ │ │ │ │ └── index.vue │ │ │ │ ├── NavSearch │ │ │ │ │ └── index.vue │ │ │ │ ├── PageReload │ │ │ │ │ └── index.vue │ │ │ │ ├── index.vue │ │ │ │ ├── leftSide.vue │ │ │ │ └── rightSide.vue │ │ │ └── index.vue │ │ └── views │ │ │ ├── iframe.vue │ │ │ └── link.vue │ └── ui-kit-components │ │ ├── HButton.vue │ │ ├── HCheckList.vue │ │ ├── HDialog.vue │ │ ├── HDropdown.vue │ │ ├── HDropdownMenu.vue │ │ ├── HInput.vue │ │ ├── HKbd.vue │ │ ├── HSelect.vue │ │ ├── HSlideover.vue │ │ ├── HTabList.vue │ │ ├── HToggle.vue │ │ └── HTooltip.vue ├── locales │ ├── index.ts │ └── lang │ │ ├── en.ts │ │ └── zh.ts ├── main.ts ├── mock │ ├── app.ts │ ├── monitor_login_logs.ts │ ├── monitor_online_user.ts │ ├── monitor_operation_logs.ts │ ├── sys_menu.ts │ ├── sys_resource.ts │ ├── sys_role.ts │ ├── sys_user.ts │ └── user.ts ├── plugins │ └── loading.ts ├── router │ ├── index.ts │ ├── modules │ │ ├── mock_demo.ts │ │ ├── permission_demo.ts │ │ ├── plugin_demo.ts │ │ └── sys_setting.ts │ └── routes.ts ├── setting │ └── config.ts ├── settings.default.ts ├── settings.ts ├── store │ ├── index.ts │ └── modules │ │ ├── keepAlive.ts │ │ ├── menu.ts │ │ ├── route.ts │ │ ├── settings.ts │ │ ├── tabbar.ts │ │ └── user.ts ├── theme │ └── index.ts ├── types │ ├── auto-imports.d.ts │ ├── components.d.ts │ ├── global.d.ts │ └── shims.d.ts ├── ui-provider │ ├── index.ts │ ├── index.vue │ └── useElementTheme.ts ├── utils │ ├── crypto │ │ └── index.ts │ ├── dayjs.ts │ ├── eroorHandler.ts │ ├── eventBus.ts │ ├── index.ts │ ├── injectionKeys.ts │ ├── storage │ │ ├── index.ts │ │ ├── local.ts │ │ └── session.ts │ ├── system.copyright.ts │ ├── theme │ │ └── index.ts │ ├── uuid.ts │ └── validate │ │ ├── regExp.ts │ │ └── validate.ts └── views │ ├── [...all].vue │ ├── breadcrumb_demo │ ├── detail1.vue │ ├── detail2.vue │ ├── list1.vue │ └── list2.vue │ ├── components_demo │ ├── svg_icon │ │ ├── icon_select.vue │ │ ├── iconify.vue │ │ └── svg_icon.vue │ ├── table_select │ │ └── index.vue │ ├── time_line │ │ └── index.vue │ └── upload │ │ └── index.vue │ ├── data_screen_demo │ ├── assets │ │ ├── alarmList.Json │ │ ├── china.json │ │ └── ranking-icon.ts │ ├── components │ │ ├── AgeRatioChart.vue │ │ ├── AnnualUseChart.vue │ │ ├── ChinaMapChart.vue │ │ ├── HotPlateChart.vue │ │ ├── MaleFemaleRatioChart.vue │ │ ├── OverNext30Chart.vue │ │ ├── PlatformSourceChart.vue │ │ └── RealTimeAccessChart.vue │ ├── images │ │ ├── bg.png │ │ ├── contrast-bg.png │ │ ├── dataScreen-alarm.png │ │ ├── dataScreen-header-btn-bg-l.png │ │ ├── dataScreen-header-btn-bg-r.png │ │ ├── dataScreen-header-center-bg.png │ │ ├── dataScreen-header-left-bg.png │ │ ├── dataScreen-header-right-bg.png │ │ ├── dataScreen-header-warn-bg.png │ │ ├── dataScreen-main-cb.png │ │ ├── dataScreen-main-lb.png │ │ ├── dataScreen-main-lc.png │ │ ├── dataScreen-main-lt.png │ │ ├── dataScreen-main-rb.png │ │ ├── dataScreen-main-rc.png │ │ ├── dataScreen-main-rt.png │ │ ├── dataScreen-title.png │ │ ├── dataScreen-warn-bg.png │ │ ├── line-bg.png │ │ ├── man-bg.png │ │ ├── man.png │ │ ├── map-title-bg.png │ │ ├── rankingChart-bg.png │ │ ├── total.png │ │ ├── woman-bg.png │ │ └── woman.png │ ├── index.scss │ └── index.vue │ ├── flow_design_demo │ └── index.vue │ ├── form_design_demo │ └── index.vue │ ├── keep_alive_demo │ ├── detail.vue │ ├── nested │ │ ├── nested.vue │ │ └── nested │ │ │ ├── nested.vue │ │ │ └── nested │ │ │ ├── index.vue │ │ │ └── index2.vue │ └── page.vue │ ├── login │ └── index.vue │ ├── mock_demo │ └── index.vue │ ├── monitor │ ├── http │ │ └── monitor.ts │ ├── interface │ │ └── monitor.ts │ ├── login_logs │ │ └── index.vue │ ├── online_user │ │ └── index.vue │ └── operation_logs │ │ └── index.vue │ ├── multilevel_menu_demo │ ├── level2 │ │ ├── level3 │ │ │ ├── page1.vue │ │ │ └── page2.vue │ │ └── page.vue │ └── page.vue │ ├── permission_demo │ ├── index.vue │ └── test.vue │ ├── personal │ ├── edit.password.vue │ └── setting.vue │ ├── plugin_demo │ ├── echart │ │ ├── data.ts │ │ └── index.vue │ ├── esign │ │ └── index.vue │ ├── markdown │ │ └── index.vue │ ├── modules │ │ └── alert.vue │ ├── print │ │ └── index.vue │ ├── qrcode │ │ └── index.vue │ ├── splitpanes │ │ └── index.vue │ ├── swiper │ │ └── index.vue │ ├── tinymce │ │ └── index.vue │ ├── typeit │ │ └── index.vue │ └── xgplayer_video │ │ └── index.vue │ ├── reload │ └── index.vue │ ├── sys_setting │ ├── http │ │ ├── sys_menu.ts │ │ ├── sys_resource.ts │ │ ├── sys_role.ts │ │ └── sys_user.ts │ ├── interface │ │ ├── sys_menu.ts │ │ ├── sys_resource.ts │ │ ├── sys_role.ts │ │ └── sys_user.ts │ ├── sys_menu │ │ ├── index.vue │ │ └── modules │ │ │ └── menuDetail.vue │ ├── sys_resource │ │ ├── index.vue │ │ └── modules │ │ │ └── resourceDrawer.vue │ ├── sys_role │ │ ├── index.vue │ │ └── modules │ │ │ ├── roleAuthDrawer.vue │ │ │ └── roleDrawer.vue │ └── sys_user │ │ ├── index.vue │ │ └── modules │ │ └── userDrawer.vue │ └── welcome │ ├── index.vue │ └── modules │ ├── card_data.vue │ ├── header_banner.vue │ ├── line_chart.vue │ ├── pie_chart.vue │ ├── project_news.vue │ └── table_statistics.vue ├── stylelint.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── uno.config.ts ├── vite.config.ts └── vite ├── plugins ├── app-info.ts ├── archiver.ts ├── auto-import.ts ├── banner.ts ├── components.ts ├── compression.ts ├── console.ts ├── devtools.ts ├── index.ts ├── layouts.ts ├── mock.ts ├── svg-icon.ts ├── unocss.ts ├── version-update.ts └── visualizer.ts ├── proxy.ts └── utils.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 应用配置面板 2 | VITE_APP_SETTING = true 3 | #开发环境变量 4 | VITE_APP_MODE = dev 5 | # 页面标题 6 | VITE_APP_TITLE = Elegant-admin 管理系统 7 | # 接口请求地址,会设置到 axios 的 baseURL 参数上 8 | VITE_APP_API_BASEURL = / 9 | # 调试工具,可设置 vconsole,如果不需要开启则留空 10 | VITE_APP_DEBUG_TOOL = 11 | # 跨域代理,您可以配置多个 ,请注意,没有换行符 12 | VITE_PROXY = [["/mock","http://localhost:8080"]] 13 | # 是否开启代理 14 | VITE_OPEN_PROXY = false 15 | # 是否开启开发者工具 16 | VITE_OPEN_DEVTOOLS = false 17 | -------------------------------------------------------------------------------- /.env.pre: -------------------------------------------------------------------------------- 1 | VITE_NODE_ENV = production 2 | #预发环境 3 | VITE_APP_MODE = pre 4 | # 应用配置面板 5 | VITE_APP_SETTING = true 6 | # 页面标题 7 | VITE_APP_TITLE = Elegant-admin 管理系统 8 | # 接口请求地址,会设置到 axios 的 baseURL 参数上 9 | VITE_APP_API_BASEURL = / 10 | # 调试工具,可设置 vconsole,如果不需要开启则留空 11 | VITE_APP_DEBUG_TOOL = 12 | 13 | # 是否在打包时启用 Mock 14 | VITE_BUILD_MOCK = true 15 | # 是否在打包时生成 sourcemap 16 | VITE_BUILD_SOURCEMAP = false 17 | # 是否在打包时开启压缩,支持 gzip 和 brotli 18 | VITE_BUILD_COMPRESS = gzip,brotli 19 | # 是否在打包后生成存档,支持 zip 和 tar 20 | VITE_BUILD_ARCHIVE = 21 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VITE_NODE_ENV = production 2 | #预发环境 3 | VITE_APP_MODE = pre 4 | # 应用配置面板 5 | VITE_APP_SETTING = true 6 | # 页面标题 7 | VITE_APP_TITLE = Elegant-admin 管理系统 8 | # 接口请求地址,会设置到 axios 的 baseURL 参数上 9 | VITE_APP_API_BASEURL = / 10 | # 调试工具,可设置 vconsole,如果不需要开启则留空 11 | VITE_APP_DEBUG_TOOL = 12 | 13 | # 是否在打包时启用 Mock 14 | VITE_BUILD_MOCK = true 15 | # 是否在打包时生成 sourcemap 16 | VITE_BUILD_SOURCEMAP = false 17 | # 是否在打包时开启压缩,支持 gzip 和 brotli 18 | VITE_BUILD_COMPRESS = gzip,brotli 19 | # 是否在打包后生成存档,支持 zip 和 tar 20 | VITE_BUILD_ARCHIVE = 21 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | VITE_NODE_ENV = production 2 | #测试环境 3 | VITE_APP_MODE = test 4 | # 应用配置面板 5 | VITE_APP_SETTING = true 6 | # 页面标题 7 | VITE_APP_TITLE = Elegant-admin 管理系统 8 | # 接口请求地址,会设置到 axios 的 baseURL 参数上 9 | VITE_APP_API_BASEURL = / 10 | # 调试工具,可设置 vconsole,如果不需要开启则留空 11 | VITE_APP_DEBUG_TOOL = 12 | 13 | # 是否在打包时启用 Mock 14 | VITE_BUILD_MOCK = true 15 | # 是否在打包时生成 sourcemap 16 | VITE_BUILD_SOURCEMAP = false 17 | # 是否在打包时开启压缩,支持 gzip 和 brotli 18 | VITE_BUILD_COMPRESS = gzip,brotli 19 | # 是否在打包后生成存档,支持 zip 和 tar 20 | VITE_BUILD_ARCHIVE = 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist* 4 | dist-ssr 5 | *.local 6 | *.idea 7 | .eslintcache 8 | .stylelintcache 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | /package-lock.json 26 | /pnpm-lock.yaml 27 | /tsconfig.tsbuildinfo 28 | /src/types/auto-imports.d.ts 29 | /src/types/components.d.ts 30 | /public/version.json 31 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/elegant-admin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,tsx,vue}": "eslint --cache --fix", 3 | "*.{css,scss,vue}": "stylelint --cache --fix" 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | engine-strict=true 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.EditorConfig", 4 | "mikestead.dotenv", 5 | "dbaeumer.vscode-eslint", 6 | "stylelint.vscode-stylelint", 7 | "Vue.volar", 8 | "antfu.unocss" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.experimental.useFlatConfig": true, 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit", 7 | "source.fixAll.stylelint": "explicit", 8 | "source.organizeImports": "never" 9 | }, 10 | "stylelint.validate": [ 11 | "css", 12 | "scss", 13 | "vue" 14 | ], 15 | "eslint.validate": [ 16 | "javascript", 17 | "javascriptreact", 18 | "typescript", 19 | "typescriptreact", 20 | "vue", 21 | "html", 22 | "markdown", 23 | "json", 24 | "jsonc", 25 | "yaml" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present, elegant-admin 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 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 当发生错误时中止脚本 4 | set -e 5 | 6 | # 构建 7 | pnpm run build:test 8 | 9 | # cd 到构建输出的目录下 10 | cd dist 11 | 12 | # 部署到自定义域域名 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m 'deploy' 18 | 19 | # 部署到 https://.github.io/ 20 | git push -f git@github.com:zhangyao1990/elegant-admin.git main:gh-pages 21 | 22 | cd - 23 | -------------------------------------------------------------------------------- /plop-templates/component/index.hbs: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /plop-templates/mock/prompt.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import fs from 'node:fs' 3 | 4 | function getFolder(path) { 5 | const components = [] 6 | const files = fs.readdirSync(path) 7 | files.forEach((item) => { 8 | const stat = fs.lstatSync(`${path}/${item}`) 9 | if (stat.isDirectory() === true && item !== 'components') { 10 | components.push(`${path}/${item}`) 11 | components.push(...getFolder(`${path}/${item}`)) 12 | } 13 | }) 14 | return components 15 | } 16 | 17 | export default { 18 | description: '创建标准模块 Mock', 19 | prompts: [ 20 | { 21 | type: 'list', 22 | name: 'path', 23 | message: '请选择模块目录', 24 | choices: getFolder('src/views'), 25 | }, 26 | ], 27 | actions: (data) => { 28 | const pathArr = path.relative('src/views', data.path).split('\\') 29 | const moduleName = pathArr.pop() 30 | const relativePath = pathArr.join('/') 31 | const actions = [] 32 | actions.push({ 33 | type: 'add', 34 | path: pathArr.length === 0 ? 'src/mock/{{moduleName}}.ts' : `src/mock/${pathArr.join('.')}.{{moduleName}}.ts`, 35 | templateFile: 'plop-templates/mock/mock.hbs', 36 | data: { 37 | relativePath, 38 | moduleName, 39 | }, 40 | }) 41 | return actions 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /plop-templates/page/index.hbs: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /plop-templates/page/prompt.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import fs from 'node:fs' 3 | 4 | function getFolder(path) { 5 | const components = [] 6 | const files = fs.readdirSync(path) 7 | files.forEach((item) => { 8 | const stat = fs.lstatSync(`${path}/${item}`) 9 | if (stat.isDirectory() === true && item !== 'components') { 10 | components.push(`${path}/${item}`) 11 | components.push(...getFolder(`${path}/${item}`)) 12 | } 13 | }) 14 | return components 15 | } 16 | 17 | export default { 18 | description: '创建页面', 19 | prompts: [ 20 | { 21 | type: 'list', 22 | name: 'path', 23 | message: '请选择页面创建目录', 24 | choices: getFolder('src/views'), 25 | }, 26 | { 27 | type: 'input', 28 | name: 'name', 29 | message: '请输入文件名', 30 | validate: (v) => { 31 | if (!v || v.trim === '') { 32 | return '文件名不能为空' 33 | } 34 | else { 35 | return true 36 | } 37 | }, 38 | }, 39 | ], 40 | actions: (data) => { 41 | const relativePath = path.relative('src/views', data.path) 42 | const actions = [ 43 | { 44 | type: 'add', 45 | path: `${data.path}/{{dotCase name}}.vue`, 46 | templateFile: 'plop-templates/page/index.hbs', 47 | data: { 48 | componentName: `${relativePath} ${data.name}`, 49 | }, 50 | }, 51 | ] 52 | return actions 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /plop-templates/store/index.hbs: -------------------------------------------------------------------------------- 1 | const use{{ properCase name }}Store = defineStore( 2 | // 唯一ID 3 | '{{ camelCase name }}', 4 | () => { 5 | const someThing = ref(0) 6 | 7 | return { 8 | someThing, 9 | } 10 | }, 11 | ) 12 | 13 | export default use{{ properCase name }}Store 14 | -------------------------------------------------------------------------------- /plop-templates/store/prompt.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: '创建全局状态', 3 | prompts: [ 4 | { 5 | type: 'input', 6 | name: 'name', 7 | message: '请输入模块名称', 8 | validate: (v) => { 9 | if (!v || v.trim === '') { 10 | return '模块名称不能为空' 11 | } 12 | else { 13 | return true 14 | } 15 | }, 16 | }, 17 | ], 18 | actions: () => { 19 | const actions = [ 20 | { 21 | type: 'add', 22 | path: 'src/store/modules/{{camelCase name}}.ts', 23 | templateFile: 'plop-templates/store/index.hbs', 24 | }, 25 | ] 26 | return actions 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /plopfile.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'node:fs' 2 | 3 | export default async function (plop) { 4 | plop.setWelcomeMessage('请选择需要创建的模式:') 5 | const items = await fs.readdir('./plop-templates') 6 | for (const item of items) { 7 | const stat = await fs.lstat(`./plop-templates/${item}`) 8 | if (stat.isDirectory()) { 9 | const prompt = await import(`./plop-templates/${item}/prompt.js`) 10 | plop.setGenerator(item, prompt.default) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /public/browser_upgrade/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/public/browser_upgrade/chrome.png -------------------------------------------------------------------------------- /public/browser_upgrade/edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/public/browser_upgrade/edge.png -------------------------------------------------------------------------------- /public/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; } 2 | a { color: #4099ff; } 3 | table { border-collapse: collapse; } 4 | 5 | table:not([cellpadding]) td, 6 | table:not([cellpadding]) th { padding: 0.4rem; } 7 | 8 | table[border]:not([border="0"], [style*="border-width"]) td, 9 | table[border]:not([border="0"], [style*="border-width"]) th { border-width: 1px; } 10 | 11 | table[border]:not([border="0"], [style*="border-style"]) td, 12 | table[border]:not([border="0"], [style*="border-style"]) th { border-style: solid; } 13 | 14 | table[border]:not([border="0"], [style*="border-color"]) td, 15 | table[border]:not([border="0"], [style*="border-color"]) th { border-color: #6d737b; } 16 | figure { display: table; margin: 1rem auto; } 17 | figure figcaption { color: #8a8f97; display: block; margin-top: 0.25rem; text-align: center; } 18 | hr { border-color: #6d737b; border-style: solid; border-width: 1px 0 0; } 19 | code { background-color: #6d737b; border-radius: 3px; padding: 0.1rem 0.2rem; } 20 | .mce-content-body:not([dir="rtl"]) blockquote { border-left: 2px solid #6d737b; margin-left: 1.5rem; padding-left: 1rem; } 21 | .mce-content-body[dir="rtl"] blockquote { border-right: 2px solid #6d737b; margin-right: 1.5rem; padding-right: 1rem; } 22 | -------------------------------------------------------------------------------- /public/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; } 2 | table { border-collapse: collapse; } 3 | 4 | table:not([cellpadding]) td, 5 | table:not([cellpadding]) th { padding: 0.4rem; } 6 | 7 | table[border]:not([border="0"], [style*="border-width"]) td, 8 | table[border]:not([border="0"], [style*="border-width"]) th { border-width: 1px; } 9 | 10 | table[border]:not([border="0"], [style*="border-style"]) td, 11 | table[border]:not([border="0"], [style*="border-style"]) th { border-style: solid; } 12 | 13 | table[border]:not([border="0"], [style*="border-color"]) td, 14 | table[border]:not([border="0"], [style*="border-color"]) th { border-color: #ccc; } 15 | figure { display: table; margin: 1rem auto; } 16 | figure figcaption { color: #999; display: block; margin-top: 0.25rem; text-align: center; } 17 | hr { border-color: #ccc; border-style: solid; border-width: 1px 0 0; } 18 | code { background-color: #e8e8e8; border-radius: 3px; padding: 0.1rem 0.2rem; } 19 | .mce-content-body:not([dir="rtl"]) blockquote { border-left: 2px solid #ccc; margin-left: 1.5rem; padding-left: 1rem; } 20 | .mce-content-body[dir="rtl"] blockquote { border-right: 2px solid #ccc; margin-right: 1.5rem; padding-right: 1rem; } 21 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/document/content.min.css: -------------------------------------------------------------------------------- 1 | @media screen { html { background: #f4f4f4; min-height: 100%; } } 2 | body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } 3 | 4 | @media screen { body { background-color: #fff; box-shadow: 0 0 4px rgb(0 0 0 / 15%); box-sizing: border-box; margin: 1rem auto 0; max-width: 820px; min-height: calc(100vh - 1rem); padding: 4rem 6rem 6rem; } } 5 | table { border-collapse: collapse; } 6 | 7 | table:not([cellpadding]) td, 8 | table:not([cellpadding]) th { padding: 0.4rem; } 9 | 10 | table[border]:not([border="0"], [style*="border-width"]) td, 11 | table[border]:not([border="0"], [style*="border-width"]) th { border-width: 1px; } 12 | 13 | table[border]:not([border="0"], [style*="border-style"]) td, 14 | table[border]:not([border="0"], [style*="border-style"]) th { border-style: solid; } 15 | 16 | table[border]:not([border="0"], [style*="border-color"]) td, 17 | table[border]:not([border="0"], [style*="border-color"]) th { border-color: #ccc; } 18 | figure figcaption { color: #999; margin-top: 0.25rem; text-align: center; } 19 | hr { border-color: #ccc; border-style: solid; border-width: 1px 0 0; } 20 | .mce-content-body:not([dir="rtl"]) blockquote { border-left: 2px solid #ccc; margin-left: 1.5rem; padding-left: 1rem; } 21 | .mce-content-body[dir="rtl"] blockquote { border-right: 2px solid #ccc; margin-right: 1.5rem; padding-right: 1rem; } 22 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/tinymce-5-dark/content.min.css: -------------------------------------------------------------------------------- 1 | body { background-color: #2f3742; color: #dfe0e4; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; line-height: 1.4; margin: 1rem; } 2 | a { color: #4099ff; } 3 | table { border-collapse: collapse; } 4 | 5 | table:not([cellpadding]) td, 6 | table:not([cellpadding]) th { padding: 0.4rem; } 7 | 8 | table[border]:not([border="0"], [style*="border-width"]) td, 9 | table[border]:not([border="0"], [style*="border-width"]) th { border-width: 1px; } 10 | 11 | table[border]:not([border="0"], [style*="border-style"]) td, 12 | table[border]:not([border="0"], [style*="border-style"]) th { border-style: solid; } 13 | 14 | table[border]:not([border="0"], [style*="border-color"]) td, 15 | table[border]:not([border="0"], [style*="border-color"]) th { border-color: #6d737b; } 16 | figure { display: table; margin: 1rem auto; } 17 | figure figcaption { color: #8a8f97; display: block; margin-top: 0.25rem; text-align: center; } 18 | hr { border-color: #6d737b; border-style: solid; border-width: 1px 0 0; } 19 | code { background-color: #6d737b; border-radius: 3px; padding: 0.1rem 0.2rem; } 20 | .mce-content-body:not([dir="rtl"]) blockquote { border-left: 2px solid #6d737b; margin-left: 1.5rem; padding-left: 1rem; } 21 | .mce-content-body[dir="rtl"] blockquote { border-right: 2px solid #6d737b; margin-right: 1.5rem; padding-right: 1rem; } 22 | -------------------------------------------------------------------------------- /public/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; } 2 | table { border-collapse: collapse; } 3 | 4 | table:not([cellpadding]) td, 5 | table:not([cellpadding]) th { padding: 0.4rem; } 6 | 7 | table[border]:not([border="0"], [style*="border-width"]) td, 8 | table[border]:not([border="0"], [style*="border-width"]) th { border-width: 1px; } 9 | 10 | table[border]:not([border="0"], [style*="border-style"]) td, 11 | table[border]:not([border="0"], [style*="border-style"]) th { border-style: solid; } 12 | 13 | table[border]:not([border="0"], [style*="border-color"]) td, 14 | table[border]:not([border="0"], [style*="border-color"]) th { border-color: #ccc; } 15 | figure { display: table; margin: 1rem auto; } 16 | figure figcaption { color: #999; display: block; margin-top: 0.25rem; text-align: center; } 17 | hr { border-color: #ccc; border-style: solid; border-width: 1px 0 0; } 18 | code { background-color: #e8e8e8; border-radius: 3px; padding: 0.1rem 0.2rem; } 19 | .mce-content-body:not([dir="rtl"]) blockquote { border-left: 2px solid #ccc; margin-left: 1.5rem; padding-left: 1rem; } 20 | .mce-content-body[dir="rtl"] blockquote { border-right: 2px solid #ccc; margin-right: 1.5rem; padding-right: 1rem; } 21 | -------------------------------------------------------------------------------- /public/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; } 2 | table { border-collapse: collapse; } 3 | 4 | table:not([cellpadding]) td, 5 | table:not([cellpadding]) th { padding: 0.4rem; } 6 | 7 | table[border]:not([border="0"], [style*="border-width"]) td, 8 | table[border]:not([border="0"], [style*="border-width"]) th { border-width: 1px; } 9 | 10 | table[border]:not([border="0"], [style*="border-style"]) td, 11 | table[border]:not([border="0"], [style*="border-style"]) th { border-style: solid; } 12 | 13 | table[border]:not([border="0"], [style*="border-color"]) td, 14 | table[border]:not([border="0"], [style*="border-color"]) th { border-color: #ccc; } 15 | figure { display: table; margin: 1rem auto; } 16 | figure figcaption { color: #999; display: block; margin-top: 0.25rem; text-align: center; } 17 | hr { border-color: #ccc; border-style: solid; border-width: 1px 0 0; } 18 | code { background-color: #e8e8e8; border-radius: 3px; padding: 0.1rem 0.2rem; } 19 | .mce-content-body:not([dir="rtl"]) blockquote { border-left: 2px solid #ccc; margin-left: 1.5rem; padding-left: 1rem; } 20 | .mce-content-body[dir="rtl"] blockquote { border-right: 2px solid #ccc; margin-right: 1.5rem; padding-right: 1rem; } 21 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | 5 | .tox-fullscreen { 6 | border: 0; 7 | height: 100%; 8 | margin: 0; 9 | overflow: hidden; 10 | overscroll-behavior: none; 11 | padding: 0; 12 | touch-action: pinch-zoom; 13 | width: 100%; 14 | } 15 | 16 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 17 | display: none; 18 | } 19 | 20 | .tox.tox-tinymce.tox-fullscreen, 21 | .tox-shadowhost.tox-fullscreen { 22 | left: 0; 23 | position: fixed; 24 | top: 0; 25 | z-index: 1200; 26 | } 27 | 28 | .tox.tox-tinymce.tox-fullscreen { 29 | background-color: transparent; 30 | } 31 | 32 | .tox-fullscreen .tox.tox-tinymce-aux, 33 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 34 | z-index: 1201; 35 | } 36 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { overflow: hidden; } 2 | .tox-fullscreen { border: 0; height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; padding: 0; touch-action: pinch-zoom; width: 100%; } 3 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { display: none; } 4 | 5 | .tox-shadowhost.tox-fullscreen, 6 | .tox.tox-tinymce.tox-fullscreen { left: 0; position: fixed; top: 0; z-index: 1200; } 7 | .tox.tox-tinymce.tox-fullscreen { background-color: transparent; } 8 | 9 | .tox-fullscreen .tox.tox-tinymce-aux, 10 | .tox-fullscreen ~ .tox.tox-tinymce-aux { z-index: 1201; } 11 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | 5 | .tox-fullscreen { 6 | border: 0; 7 | height: 100%; 8 | margin: 0; 9 | overflow: hidden; 10 | overscroll-behavior: none; 11 | padding: 0; 12 | touch-action: pinch-zoom; 13 | width: 100%; 14 | } 15 | 16 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 17 | display: none; 18 | } 19 | 20 | .tox.tox-tinymce.tox-fullscreen, 21 | .tox-shadowhost.tox-fullscreen { 22 | left: 0; 23 | position: fixed; 24 | top: 0; 25 | z-index: 1200; 26 | } 27 | 28 | .tox.tox-tinymce.tox-fullscreen { 29 | background-color: transparent; 30 | } 31 | 32 | .tox-fullscreen .tox.tox-tinymce-aux, 33 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 34 | z-index: 1201; 35 | } 36 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { overflow: hidden; } 2 | .tox-fullscreen { border: 0; height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; padding: 0; touch-action: pinch-zoom; width: 100%; } 3 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { display: none; } 4 | 5 | .tox-shadowhost.tox-fullscreen, 6 | .tox.tox-tinymce.tox-fullscreen { left: 0; position: fixed; top: 0; z-index: 1200; } 7 | .tox.tox-tinymce.tox-fullscreen { background-color: transparent; } 8 | 9 | .tox-fullscreen .tox.tox-tinymce-aux, 10 | .tox-fullscreen ~ .tox.tox-tinymce-aux { z-index: 1201; } 11 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | 5 | .tox-fullscreen { 6 | border: 0; 7 | height: 100%; 8 | margin: 0; 9 | overflow: hidden; 10 | overscroll-behavior: none; 11 | padding: 0; 12 | touch-action: pinch-zoom; 13 | width: 100%; 14 | } 15 | 16 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 17 | display: none; 18 | } 19 | 20 | .tox.tox-tinymce.tox-fullscreen, 21 | .tox-shadowhost.tox-fullscreen { 22 | left: 0; 23 | position: fixed; 24 | top: 0; 25 | z-index: 1200; 26 | } 27 | 28 | .tox.tox-tinymce.tox-fullscreen { 29 | background-color: transparent; 30 | } 31 | 32 | .tox-fullscreen .tox.tox-tinymce-aux, 33 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 34 | z-index: 1201; 35 | } 36 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { overflow: hidden; } 2 | .tox-fullscreen { border: 0; height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; padding: 0; touch-action: pinch-zoom; width: 100%; } 3 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { display: none; } 4 | 5 | .tox-shadowhost.tox-fullscreen, 6 | .tox.tox-tinymce.tox-fullscreen { left: 0; position: fixed; top: 0; z-index: 1200; } 7 | .tox.tox-tinymce.tox-fullscreen { background-color: transparent; } 8 | 9 | .tox-fullscreen .tox.tox-tinymce-aux, 10 | .tox-fullscreen ~ .tox.tox-tinymce-aux { z-index: 1201; } 11 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | 5 | .tox-fullscreen { 6 | border: 0; 7 | height: 100%; 8 | margin: 0; 9 | overflow: hidden; 10 | overscroll-behavior: none; 11 | padding: 0; 12 | touch-action: pinch-zoom; 13 | width: 100%; 14 | } 15 | 16 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 17 | display: none; 18 | } 19 | 20 | .tox.tox-tinymce.tox-fullscreen, 21 | .tox-shadowhost.tox-fullscreen { 22 | left: 0; 23 | position: fixed; 24 | top: 0; 25 | z-index: 1200; 26 | } 27 | 28 | .tox.tox-tinymce.tox-fullscreen { 29 | background-color: transparent; 30 | } 31 | 32 | .tox-fullscreen .tox.tox-tinymce-aux, 33 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 34 | z-index: 1201; 35 | } 36 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { overflow: hidden; } 2 | .tox-fullscreen { border: 0; height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; padding: 0; touch-action: pinch-zoom; width: 100%; } 3 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { display: none; } 4 | 5 | .tox-shadowhost.tox-fullscreen, 6 | .tox.tox-tinymce.tox-fullscreen { left: 0; position: fixed; top: 0; z-index: 1200; } 7 | .tox.tox-tinymce.tox-fullscreen { background-color: transparent; } 8 | 9 | .tox-fullscreen .tox.tox-tinymce-aux, 10 | .tox-fullscreen ~ .tox.tox-tinymce-aux { z-index: 1201; } 11 | -------------------------------------------------------------------------------- /src/api/config/serviceLoading.ts: -------------------------------------------------------------------------------- 1 | import { ElLoading } from 'element-plus' 2 | 3 | /* 全局 loading(服务方式调用) */ 4 | let loadingInstance: ReturnType 5 | 6 | function startLoading() { 7 | loadingInstance = ElLoading.service({ 8 | fullscreen: true, 9 | lock: true, 10 | text: '数据加载中,请稍候...', 11 | background: 'rgba(0, 0, 0, 0.5)', 12 | }) 13 | } 14 | function endLoading() { 15 | loadingInstance.close() 16 | } 17 | 18 | // 那么 showFullScreenLoading() tryHideFullScreenLoading() 要做的事就是将同一时刻的请求合并。 19 | // 声明一个变量 needLoadingRequestCount,每次调用showFullScreenLoading方法 needLoadingRequestCount + 1。 20 | // 调用tryHideFullScreenLoading()方法,needLoadingRequestCount - 1。needLoadingRequestCount为 0 时,结束 loading。 21 | let needLoadingRequestCount = 0 22 | export function showFullScreenLoading() { 23 | if (needLoadingRequestCount === 0) { 24 | startLoading() 25 | } 26 | needLoadingRequestCount++ 27 | } 28 | 29 | export function tryHideFullScreenLoading() { 30 | if (needLoadingRequestCount <= 0) { 31 | return 32 | } 33 | needLoadingRequestCount-- 34 | if (needLoadingRequestCount === 0) { 35 | endLoading() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/api/config/servicePort.ts: -------------------------------------------------------------------------------- 1 | // * 后端接口请求地址1服务 2 | const BASEURL1: any = { 3 | dev: '/mock', 4 | test: 'http://127.0.0.1:8080/mock', 5 | sit: '', 6 | prod: '', 7 | } 8 | // * 后端接口请求地址2服务(暂未使用) 9 | const BASEURL2: any = { 10 | dev: '/mock', 11 | test: '', 12 | sit: '', 13 | prod: '', 14 | } 15 | export const PORT1 = BASEURL1[import.meta.env.VITE_APP_MODE] 16 | export const PORT2 = BASEURL2[import.meta.env.VITE_APP_MODE] 17 | -------------------------------------------------------------------------------- /src/api/interface/index.ts: -------------------------------------------------------------------------------- 1 | // * 请求响应参数(不包含data) 2 | export interface Result { 3 | code: string 4 | msg: string 5 | } 6 | 7 | // * 请求响应参数(包含data) 8 | export interface ResultData extends Result { 9 | data: T 10 | } 11 | 12 | // * 分页响应参数 13 | export interface ResPage { 14 | records: T[] 15 | pageNum: number 16 | pageSize: number 17 | total: number 18 | } 19 | export interface ResList { 20 | list: T[] 21 | total: number 22 | } 23 | 24 | // * 分页请求参数 25 | export interface ReqPage { 26 | pageNum: number 27 | pageSize: number 28 | } 29 | -------------------------------------------------------------------------------- /src/api/interface/modules/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 登录账号用户信息模块 3 | */ 4 | 5 | export interface ResUserInfo { 6 | id: string 7 | image?: string 8 | name: string 9 | phone: string 10 | role: [] 11 | sysDept?: object 12 | thirdChannel?: string 13 | createTime?: string 14 | thirdChannelList?: [] 15 | } 16 | 17 | export interface ResAuthButtons { 18 | [key: string]: { 19 | [key: string]: boolean 20 | } 21 | } 22 | export interface ResAuthPermission { 23 | permissions: string[] 24 | } 25 | export interface ReqPasswordForm { 26 | password: string 27 | newpassword: string 28 | } 29 | -------------------------------------------------------------------------------- /src/api/interface/modules/login.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 登录模块 3 | */ 4 | 5 | export interface ReqLoginForm { 6 | account: string 7 | password: string 8 | id?: string 9 | captcha?: string 10 | verifyCode?: string 11 | remember?: boolean 12 | } 13 | export interface ReqImageCaptchaForm { 14 | id: string 15 | } 16 | export interface ResLogin { 17 | account: string 18 | token: string 19 | avatar: string 20 | } 21 | -------------------------------------------------------------------------------- /src/api/interface/modules/upload.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 文件上传模块 3 | */ 4 | export interface ResOssCredentials { 5 | securityToken: string 6 | accessKeySecret: string 7 | accessKeyId: string 8 | expiration: string 9 | bucket: string 10 | region: string 11 | } 12 | -------------------------------------------------------------------------------- /src/api/modules/auth.ts: -------------------------------------------------------------------------------- 1 | import type { ResultData } from '../interface' 2 | import type { ReqPasswordForm, ResAuthPermission, ResUserInfo } from '@/api/interface/modules/auth' 3 | import type { Menu } from '#/global' 4 | import { PORT1 } from '@/api/config/servicePort' 5 | 6 | // import qs from 'qs'; 7 | import http from '@/api' 8 | 9 | /** 10 | * @name 登录模块 11 | */ 12 | 13 | // * 获取用户信息 14 | export function getUserInfoApi() { 15 | return http.get(`${PORT1}/admin/sysUser/detail/current`, {}, { headers: { noLoading: true } }) 16 | } 17 | 18 | // * 获取按钮权限 19 | 20 | export function getAuthPermissionListApi() { 21 | return http.get(`${PORT1}/user/permission`, {}, { headers: { noLoading: true } }) 22 | } 23 | // * 修改密码 24 | 25 | export function editPasswordApi(params: ReqPasswordForm) { 26 | return http.post(`${PORT1}/user/permission`, params, { headers: { noLoading: true } }) 27 | } 28 | // * 获取菜单列表 29 | 30 | export function getAuthMenuListApi() { 31 | return http.get(`${PORT1}/app/route/list`, {}, { headers: { noLoading: true } }) 32 | // 如果想让菜单变为本地数据,注释上一行代码,并引入本地 dynamicRouter.json 数据 33 | // return new Promise(resolve => { 34 | // resolve(DynamicRouter); 35 | // }); 36 | } 37 | -------------------------------------------------------------------------------- /src/api/modules/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 公共模块 3 | */ 4 | -------------------------------------------------------------------------------- /src/api/modules/login.ts: -------------------------------------------------------------------------------- 1 | import type { ResultData } from '../interface' 2 | import type { ReqImageCaptchaForm, ReqLoginForm, ResLogin } from '@/api/interface/modules/login' 3 | import { PORT1 } from '@/api/config/servicePort' 4 | 5 | // import qs from 'qs'; 6 | import http from '@/api' 7 | 8 | /** 9 | * @name 登录模块 10 | */ 11 | // * 用户登录接口 12 | export function loginApi(params: ReqLoginForm) { 13 | return http.post(`${PORT1}/user/login`, params, { headers: { noLoading: true } }) // 正常 post json 请求 ==> application/json 14 | // return http.post(PORT1 + `/admin/login`, {}, { params }); // post 请求携带 query 参数 ==> ?username=admin&password=123456 15 | // return http.post(PORT1 + `/admin/login`, qs.stringify(params)); // post 请求携带 表单 参数 ==> application/x-www-form-urlencoded 16 | // return http.post(PORT1 + `/admin/login`, params, { headers: { noLoading: true } }); // 控制当前请求不显示 loading 17 | } 18 | 19 | // * 获取图形验证码 20 | export function getImageCaptchaApi(params: ReqImageCaptchaForm) { 21 | return http.get(`${PORT1}/user/captcha`, params, { headers: { noLoading: true } }) 22 | } 23 | -------------------------------------------------------------------------------- /src/api/modules/upload.ts: -------------------------------------------------------------------------------- 1 | import type { ResOssCredentials } from '../interface/modules/upload' 2 | import { PORT1 } from '@/api/config/servicePort' 3 | 4 | import http from '@/api' 5 | 6 | /** 7 | * @name 文件上传模块 8 | */ 9 | // * 获取临时授权token 10 | export function getOssToken(params: any) { 11 | return http.post(`${PORT1}/admin/oss/upload/stsToken`, params) 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/icons/example-crown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/example-emotion-laugh-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/example-emotion-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/example-emotion-unhappy-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/example-star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/example-vip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/image-load-fail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/toolbar-collapse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/avatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/assets/images/avatar.gif -------------------------------------------------------------------------------- /src/assets/images/login-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/assets/images/login-banner.png -------------------------------------------------------------------------------- /src/assets/images/login-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/assets/images/login-bg.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/notData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/assets/images/notData.png -------------------------------------------------------------------------------- /src/assets/styles/nprogress.scss: -------------------------------------------------------------------------------- 1 | #nprogress { 2 | pointer-events: none; 3 | 4 | .bar { 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | z-index: 2000; 9 | width: 100%; 10 | height: 2px; 11 | background: var(--g-ui-primary); 12 | } 13 | 14 | .peg { 15 | position: absolute; 16 | right: 0; 17 | display: block; 18 | width: 100px; 19 | height: 100%; 20 | box-shadow: 0 0 10px var(--g-ui-primary), 0 0 5px var(--g-ui-primary); 21 | opacity: 1; 22 | transform: rotate(3deg) translate(0, -4px); 23 | } 24 | 25 | .spinner { 26 | position: fixed; 27 | top: 11px; 28 | right: 14px; 29 | z-index: 2000; 30 | display: block; 31 | 32 | .spinner-icon { 33 | box-sizing: border-box; 34 | width: 18px; 35 | height: 18px; 36 | border: solid 2px transparent; 37 | border-top-color: var(--g-ui-primary); 38 | border-left-color: var(--g-ui-primary); 39 | border-radius: 50%; 40 | animation: nprogress-spinner 400ms linear infinite; 41 | } 42 | } 43 | } 44 | 45 | .nprogress-custom-parent { 46 | position: relative; 47 | overflow: hidden; 48 | 49 | #nprogress .spinner, 50 | #nprogress .bar { 51 | position: absolute; 52 | } 53 | } 54 | 55 | @keyframes nprogress-spinner { 56 | 0% { transform: rotate(0deg); } 57 | 100% { transform: rotate(360deg); } 58 | } 59 | 60 | @keyframes nprogress-spinner { 61 | 0% { transform: rotate(0deg); } 62 | 100% { transform: rotate(360deg); } 63 | } 64 | -------------------------------------------------------------------------------- /src/assets/styles/resources/utils.scss: -------------------------------------------------------------------------------- 1 | // 定位居中,默认水平居中,可选择垂直居中,或者水平垂直都居中 2 | @mixin position-center($type: x) { 3 | position: absolute; 4 | 5 | @if $type == x { 6 | left: 50%; 7 | transform: translateX(-50%); 8 | } 9 | 10 | @if $type == y { 11 | top: 50%; 12 | transform: translateY(-50%); 13 | } 14 | 15 | @if $type == xy { 16 | top: 50%; 17 | left: 50%; 18 | transform: translateX(-50%) translateY(-50%); 19 | } 20 | } 21 | 22 | @mixin scroll-bar { 23 | &::-webkit-scrollbar-track-piece { 24 | background: #fff; 25 | } 26 | 27 | &::-webkit-scrollbar { 28 | width: 0.75em; 29 | height: 0.75em; 30 | } 31 | 32 | &::-webkit-scrollbar-thumb { 33 | background: #90939957; 34 | border-radius: 10px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/styles/resources/variables.scss: -------------------------------------------------------------------------------- 1 | // 全局变量 2 | :root { 3 | --el-fill-color-light: #f1faff; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Auth/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/AuthAll/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/FixedActionBar/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/asyncLoader/index.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/builder/index.ts: -------------------------------------------------------------------------------- 1 | import EBuilder from './src/builder.vue' 2 | 3 | export default EBuilder 4 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/builder/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface DesignerProps { 2 | disabledZoom?: boolean 3 | hiddenHeader?: boolean 4 | } 5 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/index.ts: -------------------------------------------------------------------------------- 1 | import EDesigner from './src/designer.vue' 2 | 3 | export default EDesigner 4 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/attributeView/index.scss: -------------------------------------------------------------------------------- 1 | // 属性编辑 2 | .elegant-attribute-view, 3 | .elegant-style-view { 4 | padding: 16px; 5 | 6 | .elegant-attr-item { 7 | display: flex; 8 | // align-items : center; 9 | margin-bottom: 12px; 10 | 11 | &.vertical { 12 | display: block; 13 | } 14 | 15 | // &:hover{ 16 | // background-color: var(--elegant-primary-color); 17 | // } 18 | .elegant-attr-label { 19 | width: 80px; 20 | overflow: hidden; 21 | font-size: var(--elegant-text-sm); 22 | line-height: 32px; 23 | text-overflow: ellipsis; 24 | white-space: nowrap; 25 | } 26 | 27 | .elegant-attr-input { 28 | display: flex; 29 | flex: 1; 30 | align-items: center; 31 | } 32 | } 33 | } 34 | 35 | .elegant-designer-main .el-collapse .el-collapse-item__header { 36 | box-sizing: border-box; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/componentView/index.scss: -------------------------------------------------------------------------------- 1 | // 组件选择面板 2 | .elegant-component-view { 3 | height: 100%; 4 | 5 | .elegant-component-item { 6 | padding: 8px 10px; 7 | cursor: pointer; 8 | background-color: var(--elegant-primary-hover-color); 9 | border-radius: 4px; 10 | 11 | .iconfont { 12 | margin-right: 5px; 13 | font-size: 16px; 14 | color: var(--elegant-primary-color); 15 | } 16 | 17 | &:hover { 18 | color: var(--elegant-primary-color); 19 | background-color: var(--elegant-primary-hover-color); 20 | 21 | .iconfont { 22 | color: var(--elegant-primary-color); 23 | } 24 | } 25 | } 26 | 27 | .elegant-tabs-box { 28 | box-sizing: border-box; 29 | width: 62px; 30 | height: 100%; 31 | padding: 12px 6px; 32 | background: var(--elegant-compoent-tabs-color); 33 | 34 | .elegant-tab { 35 | height: 28px; 36 | margin-bottom: 10px; 37 | font-size: var(--elegant-text-sm); 38 | font-weight: 500; 39 | line-height: 28px; 40 | color: var(--elegant-text-main); 41 | text-align: center; 42 | letter-spacing: 0; 43 | border-radius: 4px; 44 | 45 | &.checked { 46 | color: var(--elegant-primary-color); 47 | background-color: var(--elegant-primary-hover-color); 48 | } 49 | } 50 | } 51 | 52 | .elegant-search-box { 53 | border: none; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/editContainer/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/header/index.scss: -------------------------------------------------------------------------------- 1 | // 头部 2 | .elegant-header-container { 3 | border: 1px solid var(--elegant-border-color); 4 | border-bottom: 0; 5 | } 6 | 7 | .elegant-header, 8 | .elegant-header a { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | height: 42px; 13 | padding: 0 12px; 14 | color: var(--elegant-text-main); 15 | background-color: var(--elegant-header-color); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/outline/index.scss: -------------------------------------------------------------------------------- 1 | .elegant-outline { 2 | box-sizing: border-box; 3 | height: 100%; 4 | overflow: auto; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/preview/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/components/FormDesign/core/components/designer/src/modules/preview/index.scss -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/rightSidebar/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | ... 21 | 22 | 26 | {{ pluginManager.getComponentConfingByType(item?.type 27 | ?? '')?.defaultSchema.label }} 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/sourceCode/index.scss: -------------------------------------------------------------------------------- 1 | .elegant-sound-code { 2 | height: 100%; 3 | } 4 | 5 | .elegant-editor { 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/modules/sourceCode/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/designer/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { PageSchema } from '../../../../core' 2 | 3 | export interface DesignerProps { 4 | disabledZoom?: boolean 5 | hiddenHeader?: boolean 6 | lockDefaultSchemaEdit?: boolean 7 | formMode?: boolean 8 | title?: string 9 | defaultSchema?: PageSchema 10 | sourceCodeReadOnly?: boolean 11 | } 12 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/icon/index.ts: -------------------------------------------------------------------------------- 1 | import EIcon from './src/icon.vue' 2 | 3 | export default EIcon 4 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/icon/src/icon.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/node/index.ts: -------------------------------------------------------------------------------- 1 | import ENode from './src/node.vue' 2 | 3 | export default ENode 4 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/node/src/dynamicFormItem.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/components/tree/index.ts: -------------------------------------------------------------------------------- 1 | import ETree from './src/tree.vue' 2 | 3 | export default ETree 4 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/EActionEditor/src/EScriptEdit.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 自定义函数编辑 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/EColEditor/index.scss: -------------------------------------------------------------------------------- 1 | .EColEditor-item { 2 | display: flex; 3 | align-items: center; 4 | padding-top: 6px; 5 | 6 | .elegant-del-btn { 7 | width: 50px; 8 | height: 100%; 9 | text-align: center; 10 | cursor: pointer; 11 | 12 | &:hover { 13 | color: #f22; 14 | } 15 | } 16 | } 17 | 18 | .add-btn { 19 | margin-top: 6px; 20 | color: var(--elegant-primary-color); 21 | cursor: pointer; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/EOptionsEditor/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | 暂无选项 39 | 40 | 48 | 49 | 50 | 添加选项 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/ERuleEditor/data.ts: -------------------------------------------------------------------------------- 1 | export const typeOptions = [ 2 | { label: 'string', value: 'string' }, 3 | { label: 'number', value: 'number' }, 4 | { label: 'boolean', value: 'boolean' }, 5 | { label: 'method', value: 'method' }, 6 | { label: 'regexp', value: 'regexp' }, 7 | { label: 'integer', value: 'integer' }, 8 | { label: 'float', value: 'float' }, 9 | { label: 'array', value: 'array' }, 10 | { label: 'object', value: 'object' }, 11 | // { label: 'enum', value: 'enum' }, 12 | { label: 'date', value: 'date' }, 13 | { label: 'url', value: 'url' }, 14 | { label: 'hex', value: 'hex' }, 15 | { label: 'email', value: 'email' }, 16 | { label: 'any', value: 'any' }, 17 | ] 18 | 19 | export const triggerOptions = [ 20 | { label: 'change', value: 'change' }, 21 | { label: 'blur', value: 'blur' }, 22 | ] 23 | 24 | export const lenTypeOptions = ['string', 'number', 'url', 'array', 'email'] 25 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/ERuleEditor/index.scss: -------------------------------------------------------------------------------- 1 | .rule-item-main { 2 | background-color: var(--elegant-view-color); 3 | border-color: var(--elegant-border-color); 4 | 5 | &:hover { 6 | border-color: var(--elegant-primary-color); 7 | } 8 | 9 | .rule-btn-delete { 10 | pointer-events: none; 11 | opacity: 0; 12 | } 13 | 14 | &:hover > .rule-btn-delete { 15 | pointer-events: all; 16 | background-color: var(--elegant-primary-color); 17 | opacity: 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/ERuleEditor/types.ts: -------------------------------------------------------------------------------- 1 | export interface RuleItem { 2 | required?: boolean 3 | type?: string 4 | pattern?: RegExp | string 5 | min?: number 6 | max?: number 7 | len?: number 8 | enum?: Array 9 | whitespace?: boolean 10 | validator?: string 11 | isValidator?: boolean 12 | message?: string | ((a?: string) => string) 13 | } 14 | export interface FormItemRule extends RuleItem { 15 | trigger?: string | string[] 16 | [model: string]: any 17 | } 18 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/MonacoEditor/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: async () => await import('./index.vue'), 5 | defaultSchema: { 6 | label: '代码编辑器', 7 | type: 'monacoEditor', 8 | field: 'monacoEditor', 9 | icon: 'elegant-icon-write', 10 | input: true, 11 | }, 12 | config: { 13 | attribute: [ 14 | { 15 | label: '字段名', 16 | type: 'input', 17 | field: 'field', 18 | }, 19 | { 20 | label: '标题', 21 | type: 'input', 22 | field: 'label', 23 | }, 24 | { 25 | label: '默认值', 26 | type: 'monacoEditor', 27 | field: 'componentProps.defaultValue', 28 | }, 29 | ], 30 | }, 31 | bindModel: 'model-value', 32 | } as ComponentConfigModel 33 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/Page/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: async () => await import('./index.vue'), 5 | defaultSchema: { 6 | label: '页面', 7 | type: 'page', 8 | componentProps: {}, 9 | children: [], 10 | }, 11 | config: { 12 | attribute: [ 13 | { 14 | label: '页面名称', 15 | type: 'input', 16 | componentProps: { 17 | placeholder: '请输入', 18 | }, 19 | field: 'componentProps.name', 20 | }, 21 | ], 22 | }, 23 | } as ComponentConfigModel 24 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/extensions/Page/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 18 | 19 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/index.scss: -------------------------------------------------------------------------------- 1 | @import "./static/icons/iconify.css"; 2 | @import "./theme/var"; 3 | @import "./components/tree/src/index"; 4 | @import "./components/designer/src/index"; 5 | @import "./extensions/EActionEditor/index"; 6 | @import "./extensions/ERuleEditor/index"; 7 | @import "./extensions/EColEditor/index"; 8 | @import "./components/asyncLoader/index"; 9 | 10 | // 基础组件适配样式 11 | @import "../ui/elementPlus/index"; 12 | 13 | // iconfont 图标样式 14 | .iconfont { 15 | font-family: elegant-iconfont, iconfont !important; 16 | font-size: 1em; 17 | font-style: normal; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-osx-font-smoothing: grayscale; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/index.ts: -------------------------------------------------------------------------------- 1 | import 'virtual:uno.css' 2 | import { type PageManager, type PluginManager, pluginManager, usePageManager } from '../utils' 3 | import EBuilder from './components/builder/' 4 | import EDesigner from './components/designer/' 5 | import ENode from './components/node/' 6 | import { setupComponent } from './extensions' 7 | import './index.scss' 8 | export type * from './types/elegant-designer' 9 | // 初始化设计器 10 | setupComponent(pluginManager) 11 | 12 | // const components = [EBuilder, EDesigner] 13 | 14 | // 注册全局组件 15 | // const EDesignr = { 16 | // install (app: App) { 17 | // components.forEach((comp) => { 18 | // app.component(comp.__name ?? '', comp) 19 | // }) 20 | // }, 21 | // pluginManager, 22 | // usePageManager 23 | // } 24 | 25 | export { 26 | EBuilder, 27 | EDesigner, 28 | ENode, 29 | pluginManager, 30 | usePageManager, 31 | type PluginManager, 32 | type PageManager, 33 | } 34 | // export default EDesignr 35 | -------------------------------------------------------------------------------- /src/components/FormDesign/core/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/components/FormDesign/core/static/logo.png -------------------------------------------------------------------------------- /src/components/FormDesign/hooks/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element' 2 | export * from './theme' 3 | -------------------------------------------------------------------------------- /src/components/FormDesign/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common' 2 | export * from './store' 3 | -------------------------------------------------------------------------------- /src/components/FormDesign/hooks/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createSharedComposable } from '@vueuse/core' 2 | import { ref } from 'vue' 3 | import { useKeyPress } from '../common/element' 4 | import { useDark } from '../common/theme' 5 | 6 | /** 7 | * 初始化共享存储 8 | */ 9 | export function initStore() { 10 | // 设计区域画布缩放 11 | const canvasScale = ref(1) 12 | // 画布缩放启用状态 13 | const disabledZoom = ref(false) 14 | 15 | const { isDark } = useDark() 16 | 17 | // 获取键盘状态 18 | const { pressSpace, pressShift, pressCtrl } = useKeyPress() 19 | return { 20 | canvasScale, 21 | pressSpace, 22 | pressShift, 23 | pressCtrl, 24 | disabledZoom, 25 | isDark, 26 | } 27 | } 28 | 29 | // 使用共享状态 30 | export const useStore = createSharedComposable(initStore) 31 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/button/button.ts: -------------------------------------------------------------------------------- 1 | import { type PropType, defineComponent, h, renderSlot } from 'vue' 2 | import { ElButton } from 'element-plus' 3 | import type { ComponentSchema } from '../../../core' 4 | 5 | // 二次封装组件 6 | export default defineComponent({ 7 | props: { 8 | componentSchema: { 9 | type: Object as PropType, 10 | default: () => ({}), 11 | }, 12 | }, 13 | setup(props, { slots }) { 14 | return () => { 15 | const componentProps: Record = { 16 | ...props.componentSchema?.componentProps, 17 | } 18 | 19 | return h(ElButton, componentProps, { 20 | default: () => 21 | renderSlot(slots, 'default', {}, () => [props.componentSchema?.label]), 22 | }) 23 | } 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/card/card.ts: -------------------------------------------------------------------------------- 1 | import { type PropType, defineComponent, h, renderSlot } from 'vue' 2 | import { ElCard } from 'element-plus' 3 | import type { ComponentSchema } from '../../../core' 4 | 5 | export default defineComponent({ 6 | props: { 7 | componentSchema: { 8 | type: Object as PropType, 9 | required: true, 10 | default: () => ({}), 11 | }, 12 | }, 13 | setup(props, { slots }) { 14 | return () => { 15 | const componentSchema = { 16 | ...props.componentSchema, 17 | header: props.componentSchema?.label ?? '', 18 | } as ComponentSchema 19 | const children = componentSchema.children ?? [] 20 | delete componentSchema.children 21 | 22 | let vNodeClildren: any = null 23 | if (children.length) { 24 | vNodeClildren = () => 25 | children.map((node: ComponentSchema) => 26 | renderSlot(slots, 'node', { componentSchema: node }), 27 | ) 28 | } 29 | else { 30 | vNodeClildren = () => [renderSlot(slots, 'default')] 31 | } 32 | return h(ElCard, componentSchema, { 33 | default: () => renderSlot(slots, 'edit-node', {}, vNodeClildren), 34 | header: () => renderSlot(slots, 'header'), 35 | }) 36 | } 37 | }, 38 | }) 39 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/card/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: () => import('./card'), 5 | groupName: '布局', 6 | icon: 'icon--elegant--wysiwyg-rounded', 7 | sort: 700, 8 | defaultSchema: { 9 | label: '卡片布局', 10 | type: 'card', 11 | children: [], 12 | componentProps: { 13 | }, 14 | }, 15 | config: { 16 | attribute: [ 17 | { 18 | label: '标题', 19 | type: 'input', 20 | field: 'label', 21 | }, 22 | { 23 | label: '阴影时机', 24 | type: 'select', 25 | componentProps: { 26 | options: [ 27 | { 28 | label: 'always', 29 | value: 'always', 30 | }, 31 | { 32 | label: 'hover', 33 | value: 'hover', 34 | }, 35 | { 36 | label: 'never', 37 | value: 'never', 38 | }, 39 | ], 40 | placeholder: '请选择', 41 | clearable: true, 42 | }, 43 | field: 'componentProps.shadow', 44 | }, 45 | { 46 | label: '隐藏', 47 | type: 'switch', 48 | field: 'componentProps.hidden', 49 | }, 50 | ], 51 | }, 52 | } as ComponentConfigModel 53 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/checkbox/checkbox.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h } from 'vue' 2 | import 'element-plus/es/components/select/style/css' 3 | import { ElCheckbox, ElCheckboxButton, ElCheckboxGroup } from 'element-plus' 4 | 5 | // 二次封装组件 6 | export default defineComponent({ 7 | emits: ['update:modelValue'], 8 | setup(_, { emit, attrs }) { 9 | function handleUpdate(e = null): void { 10 | emit('update:modelValue', e) 11 | } 12 | return () => { 13 | const props: Record = { 14 | ...attrs, 15 | 'onUpdate:modelValue': handleUpdate, 16 | } 17 | return h(ElCheckboxGroup, props, { 18 | default: () => [ 19 | props?.radioButton 20 | ? props.options?.map((option: any) => 21 | h(ElCheckboxButton, { label: option.label, value: option.value }), 22 | ) 23 | : props.options?.map((option: any) => 24 | h(ElCheckbox, { label: option.label, value: option.value }), 25 | ), 26 | ], 27 | }) 28 | } 29 | }, 30 | }) 31 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/col/col.ts: -------------------------------------------------------------------------------- 1 | import { type PropType, defineComponent, h, renderSlot } from 'vue' 2 | import { ElCol } from 'element-plus' 3 | import type { ComponentSchema } from '../../../core' 4 | 5 | export default defineComponent({ 6 | props: { 7 | componentSchema: { 8 | type: Object as PropType, 9 | required: true, 10 | default: () => ({}), 11 | }, 12 | }, 13 | setup(props, { _attrs, slots }) { 14 | return () => { 15 | const componentSchema = { 16 | ...props.componentSchema, 17 | title: props.componentSchema?.label, 18 | } as ComponentSchema 19 | const children = componentSchema.children ?? [] 20 | delete componentSchema.children 21 | 22 | return h(ElCol, componentSchema, { 23 | default: () => 24 | renderSlot(slots, 'edit-node', {}, () => 25 | children.map((node: ComponentSchema) => 26 | renderSlot(slots, 'node', { componentSchema: node }), 27 | ), 28 | ), 29 | }) 30 | } 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/col/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: () => import('./col'), 5 | defaultSchema: { 6 | label: '栅格布局-列', 7 | type: 'col', 8 | children: [], 9 | componentProps: { 10 | span: 6, 11 | }, 12 | }, 13 | config: { 14 | attribute: [ 15 | { 16 | label: '占位格数', 17 | type: 'number', 18 | field: 'componentProps.span', 19 | }, 20 | ], 21 | }, 22 | } as ComponentConfigModel 23 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/collapse-item/collapseItem.ts: -------------------------------------------------------------------------------- 1 | import { type PropType, defineComponent, h, renderSlot } from 'vue' 2 | import { ElCollapseItem } from 'element-plus' 3 | import type { ComponentSchema } from '../../../core' 4 | 5 | export default defineComponent({ 6 | props: { 7 | componentSchema: { 8 | type: Object as PropType, 9 | require: true, 10 | default: () => ({}), 11 | }, 12 | }, 13 | setup(props, { slots }) { 14 | return () => { 15 | const componentSchema = { 16 | ...props.componentSchema, 17 | title: props.componentSchema?.label ?? '', 18 | } as any 19 | const children = componentSchema.children 20 | delete componentSchema.children 21 | 22 | return h(ElCollapseItem, componentSchema, { 23 | default: () => 24 | renderSlot(slots, 'edit-node', {}, () => 25 | children.map((subcomponentSchema: ComponentSchema) => 26 | renderSlot(slots, 'node', { componentSchema: subcomponentSchema }), 27 | ), 28 | ), 29 | }) 30 | } 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/collapse-item/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: () => import('./collapseItem'), 5 | defaultSchema: { 6 | label: '折叠项', 7 | type: 'collapse-item', 8 | children: [], 9 | }, 10 | config: { 11 | attribute: [], 12 | }, 13 | } as ComponentConfigModel 14 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/collapse/collapse.ts: -------------------------------------------------------------------------------- 1 | import { type PropType, defineComponent, h, renderSlot } from 'vue' 2 | import { ElCollapse } from 'element-plus' 3 | import type { ComponentSchema } from '../../../core' 4 | 5 | export default defineComponent({ 6 | props: { 7 | componentSchema: { 8 | type: Object as PropType, 9 | required: true, 10 | default: () => ({}), 11 | }, 12 | }, 13 | setup(props, { _attrs, slots }) { 14 | return () => { 15 | const componentSchema = { 16 | ...props.componentSchema, 17 | title: props.componentSchema?.label ?? '', 18 | } as any 19 | const children = componentSchema.children 20 | delete componentSchema.children 21 | 22 | return h(ElCollapse, componentSchema, { 23 | default: () => [ 24 | renderSlot(slots, 'edit-node', {}, () => 25 | children.map((subcomponentSchema: ComponentSchema) => 26 | renderSlot(slots, 'node', { componentSchema: subcomponentSchema }), 27 | ), 28 | ), 29 | ], 30 | }) 31 | } 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/collapse/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: () => import('./collapse'), 5 | icon: 'elegant-icon-xiala', 6 | defaultSchema: { 7 | label: '折叠面板', 8 | type: 'collapse', 9 | children: [ 10 | { 11 | type: 'collapse-item', 12 | children: [], 13 | componentProps: { 14 | span: 12, 15 | }, 16 | id: 'g062zikd2jk001', 17 | }, 18 | { 19 | type: 'collapse-item', 20 | children: [], 21 | componentProps: { 22 | span: 12, 23 | }, 24 | id: 'gy5z9jtfb3s001', 25 | }, 26 | ], 27 | }, 28 | config: { 29 | attribute: [ 30 | { 31 | label: '折叠项管理', 32 | type: 'EColEditor', 33 | field: 'children', 34 | }, 35 | { 36 | label: '隐藏', 37 | type: 'switch', 38 | field: 'componentProps.hidden', 39 | }, 40 | ], 41 | }, 42 | } as ComponentConfigModel 43 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/formItem/formItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/formItem/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: () => import('./formItem.vue'), 5 | defaultSchema: { 6 | label: '表单项', 7 | type: 'form-item', 8 | }, 9 | config: { 10 | }, 11 | } as ComponentConfigModel 12 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/modal/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentConfigModel } from '../../../utils' 2 | 3 | export default { 4 | component: () => import('./modal'), 5 | defaultSchema: { 6 | label: '模态框', 7 | type: 'modal', 8 | children: [], 9 | }, 10 | config: { 11 | attribute: [ 12 | { 13 | label: '标题', 14 | type: 'input', 15 | field: 'title', 16 | }, 17 | ], 18 | }, 19 | } as ComponentConfigModel 20 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/radio/radio.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h } from 'vue' 2 | import 'element-plus/es/components/select/style/css' 3 | import { ElRadio, ElRadioButton, ElRadioGroup } from 'element-plus' 4 | 5 | // 二次封装组件 6 | export default defineComponent({ 7 | emits: ['update:modelValue'], 8 | setup(_, { emit, attrs }) { 9 | function handleUpdate(e = null): void { 10 | emit('update:modelValue', e) 11 | } 12 | 13 | return () => { 14 | const props: Record = { 15 | ...attrs, 16 | 'onUpdate:modelValue': handleUpdate, 17 | } 18 | return h(ElRadioGroup, props, { 19 | default: () => [ 20 | props?.radioButton 21 | ? props.options?.map((option: any) => 22 | h(ElRadioButton, { value: option.value }, { default: () => option.label }), 23 | ) 24 | : props.options?.map((option: any) => 25 | h(ElRadio, { value: option.value }, { default: () => option.label }), 26 | ), 27 | ], 28 | }) 29 | } 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/row/row.ts: -------------------------------------------------------------------------------- 1 | import { type PropType, defineComponent, h, renderSlot } from 'vue' 2 | import { ElRow } from 'element-plus' 3 | import type { ComponentSchema } from '../../../core' 4 | 5 | export default defineComponent({ 6 | props: { 7 | componentSchema: { 8 | type: Object as PropType, 9 | require: true, 10 | default: () => ({}), 11 | }, 12 | }, 13 | setup(props, { slots }) { 14 | return () => { 15 | const componentSchema = { 16 | ...props.componentSchema, 17 | title: props.componentSchema?.label ?? '', 18 | } as any 19 | const children = componentSchema.children 20 | delete componentSchema.children 21 | 22 | return h(ElRow, componentSchema, { 23 | default: () => [ 24 | renderSlot(slots, 'edit-node', {}, () => 25 | children.map((subcomponentSchema: ComponentSchema) => 26 | renderSlot(slots, 'node', { componentSchema: subcomponentSchema }), 27 | ), 28 | ), 29 | ], 30 | }) 31 | } 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/elementPlus/select/select.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h } from 'vue' 2 | import 'element-plus/es/components/select/style/css' 3 | import { ElOption, ElSelect } from 'element-plus' 4 | 5 | // 二次封装组件 6 | export default defineComponent({ 7 | emits: ['update:modelValue'], 8 | setup(_, { emit, attrs }) { 9 | function handleUpdate(e = null): void { 10 | emit('update:modelValue', e) 11 | } 12 | 13 | return () => { 14 | const props: Record = { 15 | ...attrs, 16 | 'key': String(attrs.multiple), 17 | 'onUpdate:modelValue': handleUpdate, 18 | } 19 | 20 | // watch 21 | 22 | return h(ElSelect, props, { 23 | default: () => [ 24 | props.options?.map((option: any) => 25 | h(ElOption, { label: option.label, value: option.value }), 26 | ), 27 | ], 28 | }) 29 | } 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /src/components/FormDesign/ui/index.ts: -------------------------------------------------------------------------------- 1 | import { pluginManager } from '../utils' 2 | 3 | export function setupElementPlus() { 4 | import('./elementPlus').then(({ setupElementPlus }) => { 5 | setupElementPlus.bind(null, pluginManager)() 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /src/components/FormDesign/utils/common/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 防抖函数,用于限制处理函数的执行频率。 3 | * @param handler - 要防抖的函数。 4 | * @param delay - 防抖延迟时间,单位为毫秒。 5 | * @returns 返回一个防抖处理后的函数。 6 | */ 7 | export function debounce void>(handler: T, delay: number): (...args: Parameters) => void { 8 | let timer: NodeJS.Timeout | null = null 9 | 10 | return (...args: Parameters): void => { 11 | if (timer) { 12 | clearTimeout(timer) 13 | } 14 | timer = setTimeout(() => { 15 | handler(...args) 16 | }, delay) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/FormDesign/utils/common/component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type AsyncComponentLoader, 3 | type Component, 4 | defineAsyncComponent, 5 | } from 'vue' 6 | 7 | /* 8 | * 异步加载组件 9 | * @param loader 10 | * @param loadingComponent 11 | * @returns 12 | */ 13 | export function loadAsyncComponent(loader: AsyncComponentLoader, 14 | loadingComponent?: Component): any { 15 | return defineAsyncComponent({ 16 | loader, 17 | loadingComponent, 18 | delay: 80, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/components/FormDesign/utils/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component' 2 | export * from './data' 3 | export * from './string' 4 | export * from './common' 5 | -------------------------------------------------------------------------------- /src/components/FormDesign/utils/common/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成一个用不重复的ID 3 | * @param randomLength 随机id长度 0 - 11 4 | */ 5 | export function getUUID(randomLength = 10): string { 6 | const characters = 'abcdefghijklmnopqrstuvwxyz0123456789' 7 | let uuid = '' 8 | 9 | for (let i = 0; i < randomLength; i++) { 10 | const randomIndex = Math.floor(Math.random() * characters.length) 11 | uuid += characters[randomIndex] 12 | } 13 | 14 | return uuid 15 | } 16 | 17 | /** 18 | 将字符串的首字母大写 19 | @param str 待处理字符串 20 | @returns string 首字母大写后的字符串 21 | */ 22 | export function capitalizeFirstLetter(str: string): string { 23 | return str.charAt(0).toUpperCase() + str.slice(1) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/FormDesign/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './manager' 2 | export * from './common' 3 | -------------------------------------------------------------------------------- /src/components/FormDesign/utils/manager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pageManager' 2 | export * from './pluginManager' 3 | export * from './revoke' 4 | -------------------------------------------------------------------------------- /src/components/Grid/interface/index.ts: -------------------------------------------------------------------------------- 1 | export type BreakPoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' 2 | 3 | export interface Responsive { 4 | span?: number 5 | offset?: number 6 | } 7 | -------------------------------------------------------------------------------- /src/components/IconSelect/select.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash-es' 2 | import { IconJson } from './data' 3 | 4 | export function getIconList() { 5 | let iconList: any = cloneDeep(IconJson) 6 | let files: any = import.meta.glob('../../assets/icons/*.svg') 7 | const svgNames: any = [] 8 | 9 | for (const path in files) { 10 | const fileNameWithExt: any = path.split('/').pop() // 获取带有后缀的文件名 11 | const fileName = fileNameWithExt?.split('.').slice(0, -1).join('.') // 去除后缀部分 12 | if (fileName) { 13 | svgNames.push(fileName) 14 | } 15 | } 16 | iconList['local:'] = svgNames 17 | return iconList 18 | } 19 | -------------------------------------------------------------------------------- /src/components/ImagePreview/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 64 | -------------------------------------------------------------------------------- /src/components/ImgVerify/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/NotAllowed/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 403 41 | 42 | 43 | 抱歉,你无权访问该页面 44 | 45 | 46 | 47 | {{ data.countdown }} 秒后,返回首页 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{ title }} 20 | 21 | 22 | 23 | 24 | {{ content }} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/PageMain/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | 41 | 42 | 43 | {{ title }} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/components/ProFlicker/index.css: -------------------------------------------------------------------------------- 1 | .point { 2 | position: relative; 3 | width: var(--point-width); 4 | height: var(--point-height); 5 | background: var(--point-background); 6 | border-radius: var(--point-border-radius); 7 | } 8 | 9 | .point-flicker::after { 10 | background: var(--point-background); 11 | } 12 | 13 | .point-flicker::before, 14 | .point-flicker::after { 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | content: ""; 21 | border-radius: var(--point-border-radius); 22 | animation: flicker 1.2s ease-out infinite; 23 | } 24 | 25 | @keyframes flicker { 26 | 0% { 27 | opacity: 1; 28 | transform: scale(0.5); 29 | } 30 | 31 | 30% { 32 | opacity: 1; 33 | } 34 | 35 | 100% { 36 | opacity: 0; 37 | transform: scale(var(--point-scale)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ProFlicker/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | import { type Component, defineComponent, h } from 'vue' 3 | 4 | export interface attrsType { 5 | width?: string 6 | height?: string 7 | borderRadius?: number | string 8 | background?: string 9 | scale?: number | string 10 | } 11 | 12 | /** 13 | * 圆点、方形闪烁动画组件 14 | * @returns Component 15 | * @param attrs 16 | */ 17 | export function useRenderFlicker(attrs?: attrsType): Component { 18 | return defineComponent({ 19 | name: 'ReFlicker', 20 | render() { 21 | return h( 22 | 'div', 23 | { 24 | class: 'point point-flicker', 25 | style: { 26 | '--point-width': attrs?.width ?? '12px', 27 | '--point-height': attrs?.height ?? '12px', 28 | '--point-background': 29 | attrs?.background ?? 'var(--el-color-primary)', 30 | '--point-border-radius': attrs?.borderRadius ?? '50%', 31 | '--point-scale': attrs?.scale ?? '2', 32 | }, 33 | }, 34 | { 35 | default: () => [], 36 | }, 37 | ) 38 | }, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/components/ProTable/components/ColSetting.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 暂无可配置列 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /src/components/ProTable/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/ProTitle/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 23 | 24 | 25 | 26 | 27 | 28 | {{ text }} 29 | 30 | 31 | 32 | 33 | 34 | 42 | -------------------------------------------------------------------------------- /src/components/Trend/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | 33 | {{ prefix }} 34 | {{ value }} 35 | {{ suffix }} 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/directives/modules/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-auth 3 | * 按钮权限指令 4 | */ 5 | import type { Directive } from 'vue' 6 | 7 | const auth: Directive = { 8 | mounted: (el, binding) => { 9 | if (!useAuth().auth(binding.value)) { 10 | el.remove() 11 | } 12 | }, 13 | } 14 | export default auth 15 | -------------------------------------------------------------------------------- /src/directives/modules/authAll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-auth-all 3 | * 按钮权限指令 4 | */ 5 | import type { Directive } from 'vue' 6 | 7 | const authAll: Directive = { 8 | mounted: (el, binding) => { 9 | if (!useAuth().authAll(binding.value)) { 10 | el.remove() 11 | } 12 | }, 13 | } 14 | export default authAll 15 | -------------------------------------------------------------------------------- /src/directives/modules/copy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-copy 3 | * 复制某个值至剪贴板 4 | * 接收参数:string类型/Ref类型/Reactive类型 5 | */ 6 | import type { Directive, DirectiveBinding } from 'vue' 7 | import { ElMessage } from 'element-plus' 8 | 9 | interface ElType extends HTMLElement { 10 | copyData: string | number 11 | __handleClick__: any 12 | } 13 | const copy: Directive = { 14 | mounted(el: ElType, binding: DirectiveBinding) { 15 | el.copyData = binding.value 16 | el.addEventListener('click', handleClick) 17 | }, 18 | updated(el: ElType, binding: DirectiveBinding) { 19 | el.copyData = binding.value 20 | }, 21 | beforeUnmount(el: ElType) { 22 | el.removeEventListener('click', el.__handleClick__) 23 | }, 24 | } 25 | 26 | function handleClick(this: any) { 27 | const input = document.createElement('input') 28 | input.value = this.copyData.toLocaleString() 29 | document.body.appendChild(input) 30 | input.select() 31 | document.execCommand('Copy') 32 | document.body.removeChild(input) 33 | ElMessage({ 34 | type: 'success', 35 | message: '复制成功', 36 | }) 37 | } 38 | 39 | export default copy 40 | -------------------------------------------------------------------------------- /src/directives/modules/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-debounce 3 | * 按钮防抖指令,可自行扩展至input 4 | * 接收参数:function类型 5 | */ 6 | import type { Directive, DirectiveBinding } from 'vue' 7 | 8 | interface ElType extends HTMLElement { 9 | __handleClick__: () => any 10 | } 11 | const debounce: Directive = { 12 | mounted(el: ElType, binding: DirectiveBinding) { 13 | if (typeof binding.value !== 'function') { 14 | throw new TypeError('callback must be a function') 15 | } 16 | let timer: NodeJS.Timeout | null = null 17 | el.__handleClick__ = function () { 18 | if (timer) { 19 | clearInterval(timer) 20 | } 21 | timer = setTimeout(() => { 22 | binding.value() 23 | }, 500) 24 | } 25 | el.addEventListener('click', el.__handleClick__) 26 | }, 27 | beforeUnmount(el: ElType) { 28 | el.removeEventListener('click', el.__handleClick__) 29 | }, 30 | } 31 | 32 | export default debounce 33 | -------------------------------------------------------------------------------- /src/directives/modules/draggable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。 3 | * 思路: 4 | * 1、设置需要拖拽的元素为absolute,其父元素为relative。 5 | * 2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。 6 | * 3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值 7 | * 4、鼠标松开(onmouseup)时完成一次拖拽 8 | *使用:在 Dom 上加上 v-draggable 即可 9 | * 10 | */ 11 | 12 | import type { Directive } from 'vue' 13 | 14 | interface ElType extends HTMLElement { 15 | parentNode: any 16 | } 17 | const draggable: Directive = { 18 | mounted(el: ElType) { 19 | el.style.cursor = 'move' 20 | el.style.position = 'absolute' 21 | el.onmousedown = function (e) { 22 | const disX = e.pageX - el.offsetLeft 23 | const disY = e.pageY - el.offsetTop 24 | document.onmousemove = function (e) { 25 | let x = e.pageX - disX 26 | let y = e.pageY - disY 27 | const maxX = el.parentNode.offsetWidth - el.offsetWidth 28 | const maxY = el.parentNode.offsetHeight - el.offsetHeight 29 | if (x < 0) { 30 | x = 0 31 | } 32 | else if (x > maxX) { 33 | x = maxX 34 | } 35 | 36 | if (y < 0) { 37 | y = 0 38 | } 39 | else if (y > maxY) { 40 | y = maxY 41 | } 42 | el.style.left = `${x}px` 43 | el.style.top = `${y}px` 44 | } 45 | document.onmouseup = function () { 46 | document.onmousemove = document.onmouseup = null 47 | } 48 | } 49 | }, 50 | } 51 | export default draggable 52 | -------------------------------------------------------------------------------- /src/directives/modules/resizeObserver.ts: -------------------------------------------------------------------------------- 1 | // 监听元素大小变化的指令 2 | import type { Directive } from 'vue' 3 | 4 | const map = new WeakMap() 5 | const ob = new ResizeObserver((entries) => { 6 | for (const entry of entries) { 7 | // 获取dom元素的回调 8 | const handler = map.get(entry.target) 9 | // 存在回调函数 10 | if (handler) { 11 | // 将监听的值给回调函数 12 | handler({ 13 | width: entry.borderBoxSize[0].inlineSize, 14 | height: entry.borderBoxSize[0].blockSize, 15 | }) 16 | } 17 | } 18 | }) 19 | const resize: Directive = { 20 | mounted(el: any, binding: any) { 21 | // 将dom与回调的关系塞入map 22 | map.set(el, binding.value) 23 | // 监听el元素的变化 24 | ob.observe(el) 25 | }, 26 | unmounted(el: any) { 27 | // 取消监听 28 | ob.unobserve(el) 29 | }, 30 | } 31 | 32 | export default resize 33 | -------------------------------------------------------------------------------- /src/directives/modules/throttle.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。 3 | 4 | 思路: 5 | 1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮 6 | 2、将需要触发的方法绑定在指令上 7 | 8 | 使用:给 Dom 加上 v-throttle 及回调函数即可 9 | 节流提交 10 | */ 11 | import type { Directive, DirectiveBinding } from 'vue' 12 | 13 | interface ElType extends HTMLElement { 14 | __handleClick__: () => any 15 | disabled: boolean 16 | } 17 | const throttle: Directive = { 18 | mounted(el: ElType, binding: DirectiveBinding) { 19 | if (typeof binding.value !== 'function') { 20 | throw new TypeError('callback must be a function') 21 | } 22 | let timer: NodeJS.Timeout | null = null 23 | el.__handleClick__ = function () { 24 | if (timer) { 25 | clearTimeout(timer) 26 | } 27 | if (!el.disabled) { 28 | el.disabled = true 29 | binding.value() 30 | timer = setTimeout(() => { 31 | el.disabled = false 32 | }, 1000) 33 | } 34 | } 35 | el.addEventListener('click', el.__handleClick__) 36 | }, 37 | beforeUnmount(el: ElType) { 38 | el.removeEventListener('click', el.__handleClick__) 39 | }, 40 | } 41 | 42 | export default throttle 43 | -------------------------------------------------------------------------------- /src/directives/modules/waterMarker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:给整个页面添加背景水印。 3 | 4 | 思路: 5 | 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。 6 | 2、将其设置为背景图片,从而实现页面或组件水印效果 7 | 8 | 使用:设置水印文案,颜色,字体大小即可 9 | 10 | */ 11 | 12 | import type { Directive, DirectiveBinding } from 'vue' 13 | const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => { 14 | // 水印文字,父元素,字体,文字颜色 15 | const can: HTMLCanvasElement = document.createElement('canvas') 16 | parentNode.appendChild(can) 17 | can.width = 210 18 | can.height = 150 19 | can.style.display = 'none' 20 | const cans = can.getContext('2d') as CanvasRenderingContext2D 21 | cans.rotate((-20 * Math.PI) / 180) 22 | cans.font = font || '16px Microsoft JhengHei' 23 | cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)' 24 | cans.textAlign = 'left' 25 | cans.textBaseline = 'Middle' as CanvasTextBaseline 26 | cans.fillText(str, can.width / 10, can.height / 2) 27 | parentNode.style.backgroundImage = `url(${can.toDataURL('image/png')})` 28 | } 29 | 30 | const waterMarker = { 31 | mounted(el: DirectiveBinding, binding: DirectiveBinding) { 32 | addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor) 33 | }, 34 | } 35 | 36 | export default waterMarker 37 | -------------------------------------------------------------------------------- /src/enums/common.ts: -------------------------------------------------------------------------------- 1 | /** 缓存的key */ 2 | export enum EnumStorageKey { 3 | /** 用户token */ 4 | 'token' = '__TOKEN__', 5 | /** 用户刷新token */ 6 | 'refresh-token' = '__REFRESH_TOKEN__', 7 | /** 用户信息 */ 8 | 'user-info' = '__USER_INFO__', 9 | } 10 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | // * 请求枚举配置 2 | /** 3 | * @description:请求配置 4 | */ 5 | export enum ResultEnum { 6 | SUCCESS = 200, 7 | ERROR = 500, 8 | OVERDUE = 104, // 根据自己后端返回定义 9 | TIMEOUT = 500000, 10 | TYPE = 'success', 11 | } 12 | 13 | /** 14 | * @description:请求方法 15 | */ 16 | export enum RequestEnum { 17 | GET = 'GET', 18 | POST = 'POST', 19 | PATCH = 'PATCH', 20 | PUT = 'PUT', 21 | DELETE = 'DELETE', 22 | } 23 | 24 | /** 25 | * @description:常用的contentTyp类型 26 | */ 27 | export enum ContentTypeEnum { 28 | // json 29 | JSON = 'application/json;charset=UTF-8', 30 | // text 31 | TEXT = 'text/plain;charset=UTF-8', 32 | // form-data 一般配合qs 33 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 34 | // form-data 上传 35 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/interface/index.ts: -------------------------------------------------------------------------------- 1 | export interface Pageable { 2 | pageNum: number 3 | pageSize: number 4 | total: number 5 | } 6 | 7 | export interface StateProps { 8 | tableData: any[] 9 | pageable: Pageable 10 | searchParam: { 11 | [key: string]: any 12 | } 13 | searchInitParam: { 14 | [key: string]: any 15 | } 16 | totalParam: { 17 | [key: string]: any 18 | } 19 | icon?: { 20 | [key: string]: any 21 | } 22 | } 23 | export type MessageType = '' | 'success' | 'warning' | 'info' | 'error' 24 | export type ThemeType = 'light' | 'inverted' | 'dark' 25 | export type GreyOrWeakType = 'grey' | 'weak' 26 | -------------------------------------------------------------------------------- /src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import useSettingsStore from '@/store/modules/settings' 2 | import useUserStore from '@/store/modules/user' 3 | 4 | export default function useAuth() { 5 | function hasPermission(permission: string) { 6 | const settingsStore = useSettingsStore() 7 | const userStore = useUserStore() 8 | if (settingsStore.settings.app.enablePermission) { 9 | return userStore.permissions.includes(permission) 10 | } 11 | else { 12 | return true 13 | } 14 | } 15 | 16 | function auth(value: string | string[]) { 17 | let auth 18 | if (typeof value === 'string') { 19 | auth = value !== '' ? hasPermission(value) : true 20 | } 21 | else { 22 | auth = value.length > 0 ? value.some(item => hasPermission(item)) : true 23 | } 24 | return auth 25 | } 26 | 27 | function authAll(value: string[]) { 28 | return value.length > 0 ? value.every(item => hasPermission(item)) : true 29 | } 30 | 31 | return { 32 | auth, 33 | authAll, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/useGlobalProperties.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentInternalInstance } from 'vue' 2 | 3 | export default function useGlobalProperties() { 4 | const { appContext } = getCurrentInstance() as ComponentInternalInstance 5 | return appContext.config.globalProperties 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/useHandleData.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage, ElMessageBox } from 'element-plus' 2 | import type { MessageType } from './interface' 3 | 4 | /** 5 | * @description 操作单条数据信息 (二次确认【删除、禁用、启用、重置密码】) 6 | * @param {Function} api 操作数据接口的api方法 (必传) 7 | * @param {object} params 携带的操作数据参数 {id,params} (必传) 8 | * @param {string} message 提示信息 (必传) 9 | * @param {string} confirmType icon类型 (不必传,默认为 warning) 10 | * @returns {Promise} 返回Promise 11 | */ 12 | export function useHandleData(api: (params: any) => Promise, 13 | params: any = {}, 14 | message: string, 15 | confirmType: MessageType = 'warning') { 16 | return new Promise((resolve, reject) => { 17 | ElMessageBox.confirm(`是否${message}?`, '温馨提示', { 18 | confirmButtonText: '确定', 19 | cancelButtonText: '取消', 20 | type: confirmType, 21 | draggable: true, 22 | }).then(async () => { 23 | const res = await api(params) 24 | if (!res) { 25 | return reject(new Error('Something went wrong')) 26 | } 27 | ElMessage({ 28 | type: 'success', 29 | message: `${message}成功!`, 30 | }) 31 | resolve(true) 32 | }) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/useMainPage.ts: -------------------------------------------------------------------------------- 1 | export default function useMainPage() { 2 | const router = useRouter() 3 | 4 | function reload() { 5 | router.push({ 6 | name: 'reload', 7 | }) 8 | } 9 | 10 | return { 11 | reload, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useMenu.ts: -------------------------------------------------------------------------------- 1 | import useSettingsStore from '@/store/modules/settings' 2 | import useMenuStore from '@/store/modules/menu' 3 | 4 | export default function useMenu() { 5 | const router = useRouter() 6 | 7 | const settingsStore = useSettingsStore() 8 | const menuStore = useMenuStore() 9 | 10 | function switchTo(index: number | string) { 11 | menuStore.setActived(index) 12 | if (settingsStore.settings.menu.switchMainMenuAndPageJump) { 13 | router.push(menuStore.sidebarMenusFirstDeepestPath) 14 | } 15 | } 16 | 17 | return { 18 | switchTo, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/useSelection.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue' 2 | 3 | /** 4 | * @description 表格多选数据操作 5 | * @param {string} rowKey 当表格可以多选时,所指定的 id 6 | */ 7 | export function useSelection(rowKey: string = 'id') { 8 | const isSelected = ref(false) 9 | const selectedList = ref<{ [key: string]: any }[]>([]) 10 | 11 | // 当前选中的所有 ids 数组 12 | const selectedListIds = computed((): string[] => { 13 | let ids: string[] = [] 14 | selectedList.value.forEach(item => ids.push(item[rowKey])) 15 | return ids 16 | }) 17 | 18 | /** 19 | * @description 多选操作 20 | * @param {Array} rowArr 当前选择的所有数据 21 | * @return void 22 | */ 23 | const selectionChange = (rowArr: { [key: string]: any }[]) => { 24 | rowArr.length ? (isSelected.value = true) : (isSelected.value = false) 25 | selectedList.value = rowArr 26 | } 27 | return { 28 | isSelected, 29 | selectedList, 30 | selectedListIds, 31 | selectionChange, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/hooks/useViewTransition.ts: -------------------------------------------------------------------------------- 1 | export default function useViewTransition(callback: () => void) { 2 | function startViewTransition() { 3 | // @ts-expect-error: View Transition API 4 | if (!document.startViewTransition || window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 5 | callback() 6 | return 7 | } 8 | // @ts-expect-error: View Transition API 9 | return document.startViewTransition(async () => { 10 | await Promise.resolve(callback()) 11 | }) 12 | } 13 | 14 | return { 15 | startViewTransition, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/iconify/index.json: -------------------------------------------------------------------------------- 1 | { "collections": ["ant-design", "ep", "fa-solid", "mdi", "ri", "streamline"], "isOfflineUse": true } 2 | -------------------------------------------------------------------------------- /src/iconify/index.ts: -------------------------------------------------------------------------------- 1 | import { addCollection } from '@iconify/vue' 2 | import data from './data.json' 3 | 4 | export async function downloadAndInstall(name: string) { 5 | const data = Object.freeze(await fetch(`./icons/${name}-raw.json`).then(r => r.json())) 6 | addCollection(data) 7 | } 8 | 9 | export const icons = data.sort((a: any, b: any) => a.info.name.localeCompare(b.info.name)) 10 | -------------------------------------------------------------------------------- /src/layouts/modules/BackTop/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/layouts/modules/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 22 | -------------------------------------------------------------------------------- /src/layouts/modules/Breadcrumb/item.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | {{ separator }} 28 | 29 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/layouts/modules/Copyright/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 29 | 30 | 31 | 45 | -------------------------------------------------------------------------------- /src/layouts/modules/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ title }} 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/layouts/modules/Menu/types.ts: -------------------------------------------------------------------------------- 1 | import { createInjectionKey } from '@/utils/injectionKeys' 2 | import type { Menu } from '#/global' 3 | 4 | export interface MenuItem { 5 | index: string 6 | indexPath: string[] 7 | active?: boolean 8 | } 9 | 10 | export interface MenuProps { 11 | menu: Menu.recordRaw[] 12 | value: string 13 | accordion?: boolean 14 | defaultOpeneds?: string[] 15 | mode?: 'horizontal' | 'vertical' 16 | collapse?: boolean 17 | showCollapseName?: boolean 18 | } 19 | 20 | export interface MenuInjection { 21 | props: MenuProps 22 | items: Record 23 | subMenus: Record 24 | activeIndex: MenuProps['value'] 25 | openedMenus: string[] 26 | mouseInMenu: string[] 27 | isMenuPopup: boolean 28 | openMenu: (index: string, indexPath: string[]) => void 29 | closeMenu: (index: string | string[]) => void 30 | handleMenuItemClick: (index: string) => void 31 | handleSubMenuClick: (index: string, indexPath: string[]) => void 32 | } 33 | 34 | export const rootMenuInjectionKey = createInjectionKey('rootMenu') 35 | 36 | export interface SubMenuProps { 37 | uniqueKey: string[] 38 | menu: Menu.recordRaw 39 | level?: number 40 | } 41 | 42 | export interface SubMenuItemProps { 43 | uniqueKey: string[] 44 | item: Menu.recordRaw 45 | level?: number 46 | subMenu?: boolean 47 | expand?: boolean 48 | } 49 | -------------------------------------------------------------------------------- /src/layouts/modules/Topbar/Toolbar/Fullscreen/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/layouts/modules/Topbar/Toolbar/Language/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/layouts/modules/Topbar/Toolbar/NavSearch/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{ $t('head.search') }} 18 | {{ settingsStore.os === 'mac' ? '⌥' : 'Alt' }} S 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/layouts/modules/Topbar/Toolbar/PageReload/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/layouts/modules/Topbar/Toolbar/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | -------------------------------------------------------------------------------- /src/layouts/modules/Topbar/Toolbar/leftSide.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HButton.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HCheckList.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ option.label }} 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HDropdown.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HDropdownMenu.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{ v.label }} 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HInput.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HKbd.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HToggle.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/layouts/ui-kit-components/HTooltip.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{ text }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | 3 | import zh from './lang/zh' 4 | import en from './lang/en' 5 | import { getLocal } from '@/utils/storage' 6 | const i18n = createI18n({ 7 | // Use Composition API, Set to false 8 | allowComposition: true, 9 | legacy: false, 10 | locale: getLocal('lang') || 'zh', 11 | messages: { 12 | zh, 13 | en, 14 | }, 15 | }) 16 | 17 | export default i18n 18 | -------------------------------------------------------------------------------- /src/locales/lang/en.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | greeting: 'Good morning, {userName}, today is another day full of vitality!', 4 | weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!', 5 | }, 6 | head: { 7 | search: 'search', 8 | }, 9 | table: { 10 | action: 'Action', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/locales/lang/zh.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | greeting: '早安,{userName}, 今天又是充满活力的一天!', 4 | weatherDesc: '今日多云转晴,20℃ - 25℃!', 5 | }, 6 | head: { 7 | search: '搜索', 8 | }, 9 | table: { 10 | action: '操作', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/mock/monitor_login_logs.ts: -------------------------------------------------------------------------------- 1 | import { defineFakeRoute } from 'vite-plugin-fake-server/client' 2 | import Mock from 'mockjs' 3 | 4 | export default defineFakeRoute([ 5 | { 6 | url: '/mock/admin/monitorLoginLogs/page', 7 | method: 'post', 8 | response: () => { 9 | return { 10 | code: 200, 11 | message: 'success', 12 | ok: true, 13 | data: Mock.mock({ 14 | 'total': 100, 15 | 'records|10': [ 16 | { 17 | 'id|+1': 1, 18 | 'name|1': ['SU', 'Admin', 'User', 'Edit', 'Guest'], 19 | 'ip|1': ['139.141.123.81', '206.71.134.225'], 20 | 'address|1': ['中国浙江省杭州市', '中国湖南长沙市'], 21 | 'system|1': ['macOS', 'Windows'], 22 | 'browser|1': ['Chrome', 'Firefox'], 23 | 'behavior|1': ['账号登录', '第三方登录'], 24 | 'status|1': ['SUCCESS', 'FAIL'], 25 | 'loginTime': '@date(\'yy-MM-dd hh:mm:ss\')', 26 | 'sort|1-100': 1, 27 | }, 28 | ], 29 | }), 30 | } 31 | }, 32 | }, 33 | ]) 34 | -------------------------------------------------------------------------------- /src/mock/monitor_online_user.ts: -------------------------------------------------------------------------------- 1 | import { defineFakeRoute } from 'vite-plugin-fake-server/client' 2 | import Mock from 'mockjs' 3 | 4 | export default defineFakeRoute([ 5 | { 6 | url: '/mock/admin/monitorOnlineUser/page', 7 | method: 'post', 8 | response: () => { 9 | return { 10 | code: 200, 11 | message: 'success', 12 | ok: true, 13 | data: Mock.mock({ 14 | 'total': 100, 15 | 'records|10': [ 16 | { 17 | 'id|+1': 1, 18 | 'name|1': ['SU', 'Admin', 'User', 'Edit', 'Guest'], 19 | 'ip|1': ['139.141.123.81', '206.71.134.225'], 20 | 'address|1': ['中国浙江省杭州市', '中国湖南长沙市'], 21 | 'system|1': ['macOS', 'Windows'], 22 | 'browser|1': ['Chrome', 'Firefox'], 23 | 'loginTime': '@date(\'yy-MM-dd hh:mm:ss\')', 24 | 'sort|1-100': 1, 25 | }, 26 | ], 27 | }), 28 | } 29 | }, 30 | }, 31 | { 32 | url: '/mock/admin/monitorOnlineUser/forceOut', 33 | method: 'post', 34 | response: () => { 35 | return { 36 | code: 200, 37 | message: 'success', 38 | ok: true, 39 | data: Mock.Random.id(), 40 | } 41 | }, 42 | }, 43 | ]) 44 | -------------------------------------------------------------------------------- /src/mock/monitor_operation_logs.ts: -------------------------------------------------------------------------------- 1 | import { defineFakeRoute } from 'vite-plugin-fake-server/client' 2 | import Mock from 'mockjs' 3 | 4 | export default defineFakeRoute([ 5 | { 6 | url: '/mock/admin/monitorOperationLogs/page', 7 | method: 'post', 8 | response: () => { 9 | return { 10 | code: 200, 11 | message: 'success', 12 | ok: true, 13 | data: Mock.mock({ 14 | 'total': 100, 15 | 'records|10': [ 16 | { 17 | 'id|+1': 1, 18 | 'name|1': ['SU', 'Admin', 'User', 'Edit', 'Guest'], 19 | 'module|1': ['系统管理', '菜单管理', '在线用户'], 20 | 'summary|1': ['菜单管理-添加菜单', '列表分页查询'], 21 | 'ip|1': ['139.141.123.81', '206.71.134.225'], 22 | 'address|1': ['中国浙江省杭州市', '中国湖南长沙市'], 23 | 'system|1': ['macOS', 'Windows'], 24 | 'browser|1': ['Chrome', 'Firefox'], 25 | 'status|1': ['SUCCESS', 'FAIL'], 26 | 'operatingTime': '@date(\'yy-MM-dd hh:mm:ss\')', 27 | 'sort|1-100': 1, 28 | }, 29 | ], 30 | }), 31 | } 32 | }, 33 | }, 34 | ]) 35 | -------------------------------------------------------------------------------- /src/router/modules/mock_demo.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | function Layout() { 4 | return import('@/layouts/index.vue') 5 | } 6 | 7 | const routes: RouteRecordRaw = { 8 | path: '/mock_demo', 9 | component: Layout, 10 | redirect: '/mock_demo/index', 11 | name: 'mockDemo', 12 | meta: { 13 | title: 'Mock', 14 | icon: 'ri:database-2-line', 15 | }, 16 | children: [ 17 | { 18 | path: 'index', 19 | name: 'mockDemoIndex', 20 | component: () => import('@/views/mock_demo/index.vue'), 21 | meta: { 22 | title: 'Mock', 23 | menu: false, 24 | breadcrumb: false, 25 | activeMenu: '/mock_demo', 26 | }, 27 | }, 28 | ], 29 | } 30 | 31 | export default routes 32 | -------------------------------------------------------------------------------- /src/router/modules/permission_demo.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | function Layout() { 4 | return import('@/layouts/index.vue') 5 | } 6 | 7 | const routes: RouteRecordRaw = { 8 | path: '/permission_demo', 9 | component: Layout, 10 | redirect: '/permission_demo/index', 11 | name: 'permissionDemo', 12 | meta: { 13 | title: '权限验证', 14 | icon: 'ri:shield-keyhole-line', 15 | }, 16 | children: [ 17 | { 18 | path: 'index', 19 | name: 'permissionDemoIndex', 20 | component: () => import('@/views/permission_demo/index.vue'), 21 | meta: { 22 | title: '权限验证', 23 | menu: false, 24 | breadcrumb: false, 25 | activeMenu: '/permission_demo', 26 | }, 27 | }, 28 | { 29 | path: 'test', 30 | name: 'permissionDemoTest', 31 | component: () => import('@/views/permission_demo/test.vue'), 32 | meta: { 33 | title: '测试页面', 34 | auth: ['permission.browse'], 35 | menu: false, 36 | breadcrumb: false, 37 | activeMenu: '/permission_demo', 38 | }, 39 | }, 40 | ], 41 | } 42 | 43 | export default routes 44 | -------------------------------------------------------------------------------- /src/router/modules/sys_setting.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: zhangyao 4 | * @Date: 2024-06-27 14:38:40 5 | * @LastEditTime: 2024-07-02 11:01:57 6 | * @LastEditors: zhangyao 7 | */ 8 | import type { RouteRecordRaw } from 'vue-router' 9 | 10 | function Layout() { 11 | return import('@/layouts/index.vue') 12 | } 13 | 14 | const routes: RouteRecordRaw = { 15 | path: '/sys_setting', 16 | component: Layout, 17 | redirect: '/sys_setting/sys_user', 18 | name: 'systemSetting', 19 | meta: { 20 | title: '系统设置', 21 | icon: 'fluent:laptop-settings-20-regular', 22 | }, 23 | children: [ 24 | { 25 | path: 'sys_user', 26 | name: 'SysUser', 27 | component: () => import('@/views/sys_setting/sys_user/index.vue'), 28 | meta: { 29 | title: '账号管理', 30 | }, 31 | }, 32 | 33 | { 34 | path: 'sys_role', 35 | name: 'SysRole', 36 | component: () => import('@/views/sys_setting/sys_role/index.vue'), 37 | meta: { 38 | title: '角色管理', 39 | }, 40 | }, 41 | { 42 | path: 'sys_menu', 43 | name: 'SysMenu', 44 | component: () => import('@/views/sys_setting/sys_menu/index.vue'), 45 | meta: { 46 | title: '菜单管理', 47 | }, 48 | }, { 49 | path: 'sys_resource', 50 | name: 'SysResource', 51 | component: () => import('@/views/sys_setting/sys_resource/index.vue'), 52 | meta: { 53 | title: '资源管理', 54 | }, 55 | }, 56 | ], 57 | } 58 | 59 | export default routes 60 | -------------------------------------------------------------------------------- /src/setting/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: zhangyao 4 | * @Date: 2024-07-01 14:47:51 5 | * @LastEditTime: 2024-07-31 09:41:32 6 | * @LastEditors: zhangyao 7 | */ 8 | export const ossPath = 'https://isdm.oss-cn-hangzhou.aliyuncs.com/' 9 | // 获取文件路径 10 | export function getSysFilePath(path: string): string { 11 | if (!path) { 12 | // 处理path为空字符串、null或undefined的情况 13 | throw new Error('Invalid path') 14 | } 15 | 16 | const pattern = /^https:\/\// 17 | if (pattern.test(path)) { 18 | // 如果path已经是https开头,则直接返回 19 | return path 20 | } 21 | else { 22 | // 否则,将http转换为https,或拼接ossPath(需确保ossPath已定义) 23 | return path.startsWith('http') ? path.replace('http', 'https') : ossPath + path 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | const pinia = createPinia() 2 | 3 | export default pinia 4 | -------------------------------------------------------------------------------- /src/store/modules/keepAlive.ts: -------------------------------------------------------------------------------- 1 | const useKeepAliveStore = defineStore( 2 | // 唯一ID 3 | 'keepAlive', 4 | () => { 5 | const list = ref([]) 6 | 7 | function add(name: string | string[]) { 8 | if (typeof name === 'string') { 9 | !list.value.includes(name) && list.value.push(name) 10 | } 11 | else { 12 | name.forEach((v) => { 13 | v && !list.value.includes(v) && list.value.push(v) 14 | }) 15 | } 16 | } 17 | function remove(name: string | string[]) { 18 | if (typeof name === 'string') { 19 | list.value = list.value.filter((v) => { 20 | return v !== name 21 | }) 22 | } 23 | else { 24 | list.value = list.value.filter((v) => { 25 | return !name.includes(v) 26 | }) 27 | } 28 | } 29 | function clean() { 30 | list.value = [] 31 | } 32 | 33 | return { 34 | list, 35 | add, 36 | remove, 37 | clean, 38 | } 39 | }, 40 | ) 41 | 42 | export default useKeepAliveStore 43 | -------------------------------------------------------------------------------- /src/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { hex2rgba } from '@unocss/preset-mini/utils' 2 | 3 | export const lightTheme = { 4 | 'color-scheme': 'light', 5 | // 内置 UI 6 | '--ui-text': hex2rgba('#fcfcfc')!.join(' '), 7 | } 8 | 9 | export const darkTheme = { 10 | 'color-scheme': 'dark', 11 | // 内置 UI 12 | '--ui-text': hex2rgba('#fcfcfc')!.join(' '), 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/types/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Window { 2 | webkitDevicePixelRatio: any 3 | mozDevicePixelRatio: any 4 | } 5 | 6 | declare const __SYSTEM_INFO__: { 7 | pkg: { 8 | dependencies: Recordable 9 | devDependencies: Recordable 10 | } 11 | lastBuildTime: string 12 | } 13 | 14 | declare module 'vue-esign' 15 | declare module 'sortablejs' 16 | declare module '@bytemd/plugin-gfm/lib/locales/zh_Hans.json' 17 | -------------------------------------------------------------------------------- /src/ui-provider/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: zhangyao 4 | * @Date: 2024-03-01 09:03:25 5 | * @LastEditTime: 2024-06-06 17:26:19 6 | * @LastEditors: zhangyao 7 | */ 8 | import type { App } from 'vue' 9 | import ElementPlus from 'element-plus' 10 | import 'element-plus/dist/index.css' 11 | import 'element-plus/theme-chalk/dark/css-vars.css' 12 | 13 | // 注册表单设计器 14 | import { setupElementPlus } from '@/components/FormDesign/ui/index.ts' 15 | 16 | async function install(app: App) { 17 | app.use(ElementPlus) 18 | // 注册Element UI 19 | setupElementPlus() 20 | } 21 | 22 | export default { install } 23 | -------------------------------------------------------------------------------- /src/ui-provider/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/ui-provider/useElementTheme.ts: -------------------------------------------------------------------------------- 1 | import { onBeforeMount } from 'vue' 2 | import useSettingsStore from '@/store/modules/settings' 3 | import { getDarkColor, getLightColor } from '@/utils/theme' 4 | import { getLocal } from '@/utils/storage' 5 | 6 | /** 7 | * @description element-plus切换主题 8 | */ 9 | export function useTheme() { 10 | const settingsStore = useSettingsStore() 11 | 12 | // 修改主题颜色 13 | const changePrimary = (val: string | null) => { 14 | if (!val) { 15 | val = getLocal('themeColor') || settingsStore.settings.app.themeColor 16 | } 17 | settingsStore.setThemeColor(val) 18 | // 颜色加深 19 | document.documentElement.style.setProperty('--el-color-primary-dark-2', `${getDarkColor(settingsStore.settings.app.themeColor, 0.5)}`) 20 | document.documentElement.style.setProperty('--el-color-primary', settingsStore.settings.app.themeColor) 21 | // 颜色变浅 22 | for (let i = 1; i <= 9; i++) { 23 | document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(settingsStore.settings.app.themeColor, i / 10)}`, 24 | ) 25 | } 26 | } 27 | 28 | onBeforeMount(() => { 29 | changePrimary(settingsStore.settings.app.themeColor) 30 | }) 31 | 32 | return { 33 | changePrimary, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/dayjs.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import 'dayjs/locale/zh-cn' 3 | 4 | dayjs.locale('zh-cn') 5 | 6 | export default dayjs 7 | -------------------------------------------------------------------------------- /src/utils/eroorHandler.ts: -------------------------------------------------------------------------------- 1 | import Message from 'vue-m-message' 2 | 3 | /** 4 | * @description 全局代码错误捕捉 5 | */ 6 | function errorHandler(error: any) { 7 | // 过滤 HTTP 请求错误 8 | if (error.status || error.status == 0) { 9 | return false 10 | } 11 | let errorMap: { [key: string]: string } = { 12 | InternalError: 'Javascript引擎内部错误', 13 | ReferenceError: '未找到对象', 14 | TypeError: '使用了错误的类型或对象', 15 | RangeError: '使用内置对象时,参数超范围', 16 | SyntaxError: '语法错误', 17 | EvalError: '错误的使用了Eval', 18 | URIError: 'URI错误', 19 | } 20 | let errorName = errorMap[error.name] || '未知错误' 21 | Message.error(error, { 22 | title: errorName, 23 | duration: 4000, 24 | closable: true, 25 | zIndex: 2000, 26 | position: 'top-right', 27 | }) 28 | } 29 | 30 | export default errorHandler 31 | -------------------------------------------------------------------------------- /src/utils/eventBus.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt' 2 | 3 | interface MittTypes { 4 | [key: string | symbol]: any 5 | 'global-search-toggle'?: 'menu' | 'tab' 6 | } 7 | 8 | export default mitt() 9 | -------------------------------------------------------------------------------- /src/utils/injectionKeys.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue' 2 | 3 | export function createInjectionKey(key: string): InjectionKey { 4 | return key as any 5 | } 6 | 7 | export const rootMenuInjectionKey = createInjectionKey('rootMenu') 8 | -------------------------------------------------------------------------------- /src/utils/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './local' 2 | export * from './session' 3 | -------------------------------------------------------------------------------- /src/utils/storage/session.ts: -------------------------------------------------------------------------------- 1 | import { decrypto, encrypto } from '../crypto' 2 | 3 | /** 4 | * 将给定的值加密后存储到会话存储(sessionStorage)中。 5 | * @param key 用于在存储中标识数据的键名。 6 | * @param value 需要存储的数据,类型未知,将被加密。 7 | */ 8 | export function setSession(key: string, value: unknown) { 9 | // 对值进行加密处理 10 | const json = encrypto(value) 11 | // 将加密后的数据存入sessionStorage 12 | sessionStorage.setItem(key, json) 13 | } 14 | 15 | /** 16 | * 从会话存储中获取经过加密的会话数据。 17 | * @param key 用于在会话存储中标识数据的键名。 18 | * @returns 返回解密后的数据,如果不存在或解密失败则返回 null。 19 | */ 20 | export function getSession(key: string) { 21 | // 从会话存储中获取指定键名的值(已加密的数据) 22 | const json = sessionStorage.getItem(key) 23 | let data: T | null = null 24 | if (json) { 25 | try { 26 | // 尝试解密获取到的数据 27 | data = decrypto(json) 28 | } 29 | catch { 30 | // 如果解密过程失败,则保持 data 为 null,防止使用错误的数据 31 | // 这里不详细记录错误,因为可能由于多种原因导致失败,且此处不需要对失败情况做特殊处理 32 | } 33 | } 34 | return data 35 | } 36 | 37 | /** 38 | * 从会话存储中移除指定的项。 39 | * @param key 要移除的项的键名。 40 | */ 41 | export function removeSession(key: string) { 42 | window.sessionStorage.removeItem(key) // 从sessionStorage中移除指定键名的项 43 | } 44 | 45 | /** 46 | * 清除当前会话的sessionStorage中的所有数据。 47 | * 无参数。 48 | * 无返回值。 49 | */ 50 | export function clearSession() { 51 | window.sessionStorage.clear() 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/system.copyright.ts: -------------------------------------------------------------------------------- 1 | if (import.meta.env.PROD) { 2 | const copyright_common_style = 'font-size: 14px; margin-bottom: 2px; padding: 6px 8px; color: #fff;' 3 | const copyright_main_style = `${copyright_common_style} background: #e24329;` 4 | const copyright_sub_style = `${copyright_common_style} background: #707070;` 5 | if ((navigator.language).toLowerCase() === 'zh-cn') { 6 | console.info('%c由%cElegant-admin%c驱动', copyright_sub_style, copyright_main_style, copyright_sub_style, '\nhttps://github.com/zhangyao1990/elegant-admin') 7 | } 8 | else { 9 | console.info('%cPowered by%cElegant-admin', copyright_sub_style, copyright_main_style, '\nhttps://github.com/zhangyao1990/elegant-admin') 10 | } 11 | } 12 | 13 | export {} 14 | -------------------------------------------------------------------------------- /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 | } 12 | else if (i === 15) { 13 | uuid += 4 14 | } 15 | else if (i === 20) { 16 | uuid += hexList[(Math.random() * 4) | 8] 17 | } 18 | else { 19 | uuid += hexList[(Math.random() * 16) | 0] 20 | } 21 | } 22 | return uuid.replace(/-/g, '') 23 | } 24 | 25 | let unique = 0 26 | export function buildShortUUID(prefix = ''): string { 27 | const time = Date.now() 28 | const random = Math.floor(Math.random() * 1000000000) 29 | unique++ 30 | return `${prefix}_${random}${unique}${String(time)}` 31 | } 32 | -------------------------------------------------------------------------------- /src/views/[...all].vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 404 38 | 39 | 40 | 抱歉,你访问的页面不存在 41 | 42 | 43 | 44 | {{ data.countdown }} 秒后,返回首页 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/breadcrumb_demo/detail1.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 注意看面包屑导航的变化 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/breadcrumb_demo/detail2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 注意看面包屑导航的变化 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/breadcrumb_demo/list1.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 查看详情页 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/breadcrumb_demo/list2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 查看详情页 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/components_demo/svg_icon/icon_select.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/components_demo/svg_icon/svg_icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | SVG Icon 10 | 11 | 12 | 13 | 14 | 15 | 翻转: 16 | 17 | 18 | 水平翻转 19 | 20 | 21 | 垂直翻转 22 | 23 | 24 | 水平垂直翻转 25 | 26 | 27 | 28 | 使用方法: 29 | 30 | 上 Iconfont 下载需要的 svg 图标 31 | 将 svg 文件放入 ./src/assets/icons 目录下,文件名即为 name 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/views/data_screen_demo/assets/alarmList.Json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "warnMsg": "2022-04-25 14:09-23:09 预约人数 1,006 人次,已达到最大承载量的 99 %", 5 | "label": "峨眉山" 6 | }, 7 | { 8 | "id": 2, 9 | "warnMsg": "2022-04-12 19:30-20:30 预约人数 66,666 人次,已达到最大承载量的 25 %", 10 | "label": "稻城亚丁" 11 | }, 12 | { 13 | "id": 3, 14 | "warnMsg": "2022-04-09 14:09-23:09 预约人数 5,813 人次,已达到最大承载量的 3 %", 15 | "label": "九寨沟" 16 | }, 17 | { 18 | "id": 4, 19 | "warnMsg": "2022-04-07 22:39-23:39 预约人数 999 人次,已达到最大承载量的 13 %", 20 | "label": "万里长城" 21 | }, 22 | { 23 | "id": 5, 24 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 123,368 人次,已达到最大承载量的 46 %", 25 | "label": "北京故宫" 26 | }, 27 | { 28 | "id": 6, 29 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 869 人次,已达到最大承载量的 95 %", 30 | "label": "阆中古城" 31 | }, 32 | { 33 | "id": 7, 34 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 6,985 人次,已达到最大承载量的 80 %", 35 | "label": "乐山大佛" 36 | }, 37 | { 38 | "id": 8, 39 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 25,696 人次,已达到最大承载量的 70 %", 40 | "label": "阿坝州黄龙" 41 | }, 42 | { 43 | "id": 9, 44 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 45,987 人次,已达到最大承载量的 55 %", 45 | "label": "青城山" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/contrast-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/contrast-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-alarm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-alarm.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-header-btn-bg-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-header-btn-bg-l.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-header-btn-bg-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-header-btn-bg-r.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-header-center-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-header-center-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-header-left-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-header-left-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-header-right-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-header-right-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-header-warn-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-header-warn-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-cb.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-lb.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-lc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-lc.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-lt.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-rb.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-rc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-rc.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-main-rt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-main-rt.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-title.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/dataScreen-warn-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/dataScreen-warn-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/line-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/line-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/man-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/man-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/man.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/map-title-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/map-title-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/rankingChart-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/rankingChart-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/total.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/total.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/woman-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/woman-bg.png -------------------------------------------------------------------------------- /src/views/data_screen_demo/images/woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyao1990/elegant-admin/332a725cf8400fe514cf4bc74b315ec280ea1cb5/src/views/data_screen_demo/images/woman.png -------------------------------------------------------------------------------- /src/views/flow_design_demo/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 进行中 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/keep_alive_demo/detail.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 返回 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/keep_alive_demo/nested/nested.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 层级:1 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/keep_alive_demo/nested/nested/nested.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 层级:1-1 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/keep_alive_demo/nested/nested/nested/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 层级 1-1-1 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/views/keep_alive_demo/nested/nested/nested/index2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 层级 1-1-2 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/views/mock_demo/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Mock.js 官网 28 | 29 | 30 | 31 | 32 | 测试:获取用户权限 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/views/monitor/http/monitor.ts: -------------------------------------------------------------------------------- 1 | import type { ReqLoginLogsListParams, ReqOnlineUserListParams, ReqOperationLogsListParams, ResLoginLogsList, ResOnlineUserList, ResOperationLogsList } from '../interface/monitor' 2 | import type { ResList } from '@/api/interface' 3 | import { PORT1 } from '@/api/config/servicePort' 4 | 5 | import http from '@/api' 6 | 7 | /** ******** 在线用户 */ 8 | // * 获取在线登录列表 9 | export function getMonitorOnlineUserListApi(params: ReqOnlineUserListParams) { 10 | return http.post>(`${PORT1}/admin/monitorOnlineUser/page`, params) 11 | } 12 | // * 在线用户强制退出 13 | export function operateMonitorOnlineUserForceoutApi(params: ReqOnlineUserListParams) { 14 | return http.post>(`${PORT1}/admin/monitorOnlineUser/forceOut`, params) 15 | } 16 | /** ******** 登录日志 */ 17 | // * 获取在线登录列表 18 | export function getMonitorLoginLogsListApi(params: ReqLoginLogsListParams) { 19 | return http.post>(`${PORT1}/admin/monitorLoginLogs/page`, params) 20 | } 21 | /** ******** 操作日志 */ 22 | // * 获取操作日志列表 23 | export function getMonitorOperationLogsListApi(params: ReqOperationLogsListParams) { 24 | return http.post>(`${PORT1}/admin/monitorOperationLogs/page`, params) 25 | } 26 | -------------------------------------------------------------------------------- /src/views/monitor/interface/monitor.ts: -------------------------------------------------------------------------------- 1 | import type { ReqPage } from '@/api/interface/index' 2 | 3 | /** 4 | * *** 5 | @name 在线用户 6 | */ 7 | 8 | export interface ReqOnlineUserListParams extends ReqPage { 9 | name: string 10 | } 11 | 12 | export interface ResOnlineUserList { 13 | id: string 14 | name: string 15 | ip: string 16 | address: string 17 | system: string 18 | browser: string 19 | loginTime: string 20 | sort: number 21 | openStatus: string 22 | } 23 | /** 24 | * *** 25 | * @name 登录日志 26 | */ 27 | 28 | export interface ReqLoginLogsListParams extends ReqPage { 29 | name: string 30 | status: string 31 | loginTime: string 32 | } 33 | 34 | export interface ResLoginLogsList { 35 | id: string 36 | name: string 37 | ip: string 38 | address: string 39 | system: string 40 | browser: string 41 | behavior: string 42 | loginTime: string 43 | sort: number 44 | status: string 45 | } 46 | /** 47 | * *** 48 | * @name 操作日志 49 | */ 50 | 51 | export interface ReqOperationLogsListParams extends ReqPage { 52 | name: string 53 | status: string 54 | operationTime: string 55 | } 56 | 57 | export interface ResOperationLogsList { 58 | id: string 59 | name: string 60 | module: string 61 | summary: string 62 | ip: string 63 | address: string 64 | system: string 65 | browser: string 66 | operatingTime: string 67 | sort: number 68 | status: string 69 | } 70 | -------------------------------------------------------------------------------- /src/views/multilevel_menu_demo/level2/level3/page1.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多级导航2-2-1 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/multilevel_menu_demo/level2/level3/page2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多级导航2-2-2 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/multilevel_menu_demo/level2/page.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多级导航2-1 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/multilevel_menu_demo/page.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多级导航1 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/permission_demo/test.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 你能看到这个页面,说明你有访问权限。 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/plugin_demo/markdown/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 安装命令:pnpm add bytemd @bytemd/vue-next @bytemd/plugin-gfm 33 | 34 | 35 | 36 | 37 | 38 | 39 | 访问 bytemd 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | -------------------------------------------------------------------------------- /src/views/plugin_demo/modules/alert.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/views/plugin_demo/tinymce/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 安装命令:pnpm add tinymce @tinymce/tinymce-vue 18 | 19 | 20 | 安装成功后,在框架 /public 目录下创建 tinymce 文件夹,并将 /node_modules/tinymce 目录下的 langs 和 skins 文件夹复制到 /public/tinymce 目录下。 21 | 22 | 23 | 24 | 25 | 26 | 27 | 访问 TinyMCE 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/views/plugin_demo/typeit/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 安装命令:pnpm add typeit 23 | 24 | 25 | 26 | 27 | 28 | 29 | 访问 Typeit 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/views/plugin_demo/xgplayer_video/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 安装命令:npm install xgplayer 20 | 21 | 22 | 23 | 24 | 25 | 26 | 访问 xgplayer 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/views/reload/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/sys_setting/http/sys_menu.ts: -------------------------------------------------------------------------------- 1 | import type { ResMenuList } from '../interface/sys_menu' 2 | import type { ResList, ResultData } from '@/api/interface' 3 | import { PORT1 } from '@/api/config/servicePort' 4 | 5 | import http from '@/api' 6 | 7 | /** ******** 菜单管理模块 */ 8 | // * 获取菜单列表 9 | export function getSysMenuListApi(params: any) { 10 | return http.post>(`${PORT1}/admin/sysMenu/list`, params) 11 | } 12 | 13 | // * 新增菜单 14 | export function addSysMenuApi(params: any) { 15 | return http.post(`${PORT1}/admin/sysMenu/save`, params) 16 | } 17 | 18 | // * 编辑菜单 19 | export function editSysMenuApi(params: any) { 20 | return http.put(`${PORT1}/admin/sysMenu/update`, params) 21 | } 22 | 23 | // * 删除菜单 24 | export function delSysMenuApi(params: { id: string }) { 25 | return http.delete(`${PORT1}/admin/sysMenu/delete`, params) 26 | } 27 | -------------------------------------------------------------------------------- /src/views/sys_setting/http/sys_resource.ts: -------------------------------------------------------------------------------- 1 | import type { ReqResourceListParams, ResResourceList } from '../interface/sys_resource' 2 | import type { ResPage, ResultData } from '@/api/interface' 3 | import { PORT1 } from '@/api/config/servicePort' 4 | 5 | import http from '@/api' 6 | 7 | /** ******** 资源管理模块 */ 8 | // * 获取资源列表 9 | export function getSysResourceListApi(params: ReqResourceListParams) { 10 | return http.post>(`${PORT1}/admin/sysResource/page`, params) 11 | } 12 | 13 | // * 新增资源 14 | export function addResourceApi(params: any) { 15 | return http.post(`${PORT1}/admin/sysResource/save`, params) 16 | } 17 | 18 | // * 编辑资源 19 | export function editResourceApi(params: any) { 20 | return http.put(`${PORT1}/admin/sysResource/update`, params) 21 | } 22 | 23 | // * 删除资源 24 | export function delSysResourceApi(params: { id: string }) { 25 | return http.delete(`${PORT1}/admin/sysResource/delete`, params) 26 | } 27 | -------------------------------------------------------------------------------- /src/views/sys_setting/http/sys_role.ts: -------------------------------------------------------------------------------- 1 | import type { ReqRoleListParams, ResRoleList } from '../interface/sys_role' 2 | import type { ResPage, ResultData } from '@/api/interface' 3 | import { PORT1 } from '@/api/config/servicePort' 4 | 5 | import http from '@/api' 6 | 7 | /** ******** 角色管理模块 */ 8 | // * 获取角色列表 9 | export function getSysRoleListApi(params: ReqRoleListParams) { 10 | return http.post>(`${PORT1}/admin/sysRole/page`, params) 11 | } 12 | 13 | // * 新增角色 14 | export function addRoleApi(params: any) { 15 | return http.post(`${PORT1}/admin/sysRole/save`, params) 16 | } 17 | 18 | // * 编辑角色 19 | export function editRoleApi(params: any) { 20 | return http.put(`${PORT1}/admin/sysRole/update`, params) 21 | } 22 | 23 | // * 删除角色 24 | export function delSysRoleApi(params: { id: string }) { 25 | return http.delete(`${PORT1}/admin/sysRole/delete`, params) 26 | } 27 | // * 设置权限 28 | export function settingSysRoleAuthApi(params: { id: string }) { 29 | return http.post(`${PORT1}/admin/sysRole/authSetting`, params) 30 | } 31 | // * 获取角色权限 32 | export function getSysRoleAuthApi(params: { id: string }) { 33 | return http.get(`${PORT1}/admin/sysRole/authList`, params) 34 | } 35 | -------------------------------------------------------------------------------- /src/views/sys_setting/http/sys_user.ts: -------------------------------------------------------------------------------- 1 | import type { ReqUserListParams, ResUserList } from '../interface/sys_user' 2 | import type { ResPage, ResultData } from '@/api/interface' 3 | import { PORT1 } from '@/api/config/servicePort' 4 | 5 | import http from '@/api' 6 | 7 | /** ******** 用户管理模块 */ 8 | // * 获取用户列表 9 | export function getSysUserListApi(params: ReqUserListParams) { 10 | return http.post>(`${PORT1}/admin/sysUser/page`, params) 11 | } 12 | 13 | // * 新增用户 14 | export function addUserApi(params: ReqUserListParams) { 15 | return http.post(`${PORT1}/admin/sysUser/save`, params) 16 | } 17 | 18 | // * 编辑用户 19 | export function editUserApi(params: ReqUserListParams) { 20 | return http.put(`${PORT1}/admin/sysUser/update`, params) 21 | } 22 | // * 用户详情 23 | export function getSysUserDetailApi(params: any) { 24 | return http.get(`${PORT1}/admin/sysUser/${params.id}`, params) 25 | } 26 | // * 删除用户 27 | export function delSysUserApi(params: { id: string }) { 28 | return http.delete(`${PORT1}/admin/sysUser/delete`, params) 29 | } 30 | -------------------------------------------------------------------------------- /src/views/sys_setting/interface/sys_menu.ts: -------------------------------------------------------------------------------- 1 | export interface ResMenuList { 2 | id: string 3 | pid: string 4 | name: string 5 | sort: number 6 | path: string 7 | redirect: string 8 | component: string 9 | meta: { 10 | title: string 11 | type: string 12 | icon: string 13 | activeMenu: string 14 | menu: boolean 15 | breadcrumb: boolean 16 | cache: boolean 17 | threeMenu: any 18 | iframe: boolean 19 | link: string | null 20 | } 21 | api: any 22 | apiPermissions: any 23 | } 24 | -------------------------------------------------------------------------------- /src/views/sys_setting/interface/sys_resource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 资源管理模块 3 | */ 4 | import type { ReqPage } from '@/api/interface/index' 5 | 6 | export interface ReqResourceListParams extends ReqPage { 7 | name: string 8 | openStatus: string 9 | } 10 | 11 | export interface ResResourceList { 12 | id: string 13 | name: string 14 | sort: number 15 | openStatus: string 16 | } 17 | -------------------------------------------------------------------------------- /src/views/sys_setting/interface/sys_role.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 角色管理模块 3 | */ 4 | import type { ReqPage } from '@/api/interface/index' 5 | 6 | export interface ReqRoleListParams extends ReqPage { 7 | roleName: string 8 | openStatus: string 9 | } 10 | 11 | export interface ResRoleList { 12 | id: string 13 | roleName: string 14 | note: string 15 | alias: string 16 | sort: number 17 | openStatus: string 18 | } 19 | -------------------------------------------------------------------------------- /src/views/sys_setting/interface/sys_user.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 用户管理模块 3 | */ 4 | import type { ReqPage } from '@/api/interface/index' 5 | 6 | export interface ReqUserListParams extends ReqPage { 7 | username: string 8 | gender: number 9 | idCard: string 10 | email: string 11 | address: string 12 | createTime: string[] 13 | status: number 14 | } 15 | export interface ResUserList { 16 | id: string 17 | username: string 18 | gender: string 19 | age: number 20 | idCard: string 21 | email: string 22 | address: string 23 | createTime: string 24 | status: number 25 | avatar: string 26 | children?: ResUserList[] 27 | } 28 | -------------------------------------------------------------------------------- /src/views/welcome/modules/header_banner.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{ $t('home.greeting', { userName: userStore.account }) }} 20 | 21 | {{ $t('home.weatherDesc') }} 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: [ 3 | 'stylelint-config-standard-scss', 4 | 'stylelint-config-standard-vue/scss', 5 | 'stylelint-config-recess-order', 6 | '@stylistic/stylelint-config', 7 | ], 8 | plugins: [ 9 | 'stylelint-scss', 10 | ], 11 | rules: { 12 | 'at-rule-no-unknown': null, 13 | 'no-descending-specificity': null, 14 | 'property-no-unknown': null, 15 | 'font-family-no-missing-generic-family-keyword': null, 16 | 'selector-class-pattern': null, 17 | 'no-empty-source': null, 18 | 'function-no-unknown': [ 19 | true, 20 | { 21 | ignoreFunctions: [ 22 | 'v-bind', 23 | 'map-get', 24 | 'lighten', 25 | 'darken', 26 | ], 27 | }, 28 | ], 29 | 'selector-pseudo-element-no-unknown': [ 30 | true, 31 | { 32 | ignorePseudoElements: [ 33 | '/^view-transition/', 34 | ], 35 | }, 36 | ], 37 | 'scss/double-slash-comment-empty-line-before': null, 38 | 'scss/no-global-function-names': null, 39 | '@stylistic/max-line-length': null, 40 | '@stylistic/block-closing-brace-newline-after': [ 41 | 'always', 42 | { 43 | ignoreAtRules: ['if', 'else'], 44 | }, 45 | ], 46 | }, 47 | allowEmptyInput: true, 48 | ignoreFiles: [ 49 | 'node_modules/**/*', 50 | 'dist*/**/*', 51 | 'public/tinymce/**/*', 52 | ], 53 | } 54 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "lib": [ 6 | "ESNext", 7 | "DOM", 8 | "DOM.Iterable" 9 | ], 10 | "moduleDetection": "force", 11 | "useDefineForClassFields": true, 12 | "baseUrl": "./", 13 | "module": "ESNext", 14 | "moduleResolution": "Bundler", 15 | "paths": { 16 | "@/*": [ 17 | "src/*" 18 | ], 19 | "#/*": [ 20 | "src/types/*" 21 | ] 22 | }, 23 | "resolveJsonModule": true, 24 | "types": [ 25 | "vite/client", 26 | "vite-plugin-pages/client", 27 | "vite-plugin-vue-meta-layouts/client", 28 | "vite-plugin-app-loading/client", 29 | "element-plus/global" 30 | ], 31 | "allowImportingTsExtensions": true, 32 | "allowJs": false, 33 | "strict": true, 34 | "noFallthroughCasesInSwitch": true, 35 | "noUnusedLocals": true, 36 | "noUnusedParameters": true, 37 | "noEmit": true, 38 | "sourceMap": true, 39 | "esModuleInterop": true, 40 | "isolatedModules": true, 41 | "skipLibCheck": true 42 | }, 43 | "include": [ 44 | "src/**/*.ts", 45 | "src/**/*.d.ts", 46 | "src/**/*.tsx", 47 | "src/**/*.vue" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "lib": [ 6 | "ESNext", 7 | "DOM", 8 | "DOM.Iterable" 9 | ], 10 | "moduleDetection": "force", 11 | "useDefineForClassFields": true, 12 | "baseUrl": "./", 13 | "module": "ESNext", 14 | "moduleResolution": "Bundler", 15 | "paths": { 16 | "@/*": [ 17 | "src/*" 18 | ], 19 | "#/*": [ 20 | "src/types/*" 21 | ] 22 | }, 23 | "resolveJsonModule": true, 24 | "types": [ 25 | "vite/client", 26 | "vite-plugin-pages/client", 27 | "vite-plugin-vue-meta-layouts/client", 28 | "element-plus/global" 29 | ], 30 | "allowImportingTsExtensions": true, 31 | "allowJs": false, 32 | "strict": true, 33 | "noFallthroughCasesInSwitch": true, 34 | "noUnusedLocals": true, 35 | "noUnusedParameters": true, 36 | "noEmit": true, 37 | "sourceMap": true, 38 | "esModuleInterop": true, 39 | "isolatedModules": true, 40 | "skipLibCheck": true 41 | }, 42 | "include": [ 43 | "src/**/*.ts", 44 | "src/**/*.d.ts", 45 | "src/**/*.tsx", 46 | "src/**/*.vue" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "moduleDetection": "force", 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler", 8 | "allowImportingTsExtensions": true, 9 | "strict": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noEmit": true, 14 | "isolatedModules": true, 15 | "skipLibCheck": true 16 | }, 17 | "include": [ 18 | "package.json", 19 | "vite.config.ts", 20 | "vite/**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /vite/plugins/app-info.ts: -------------------------------------------------------------------------------- 1 | import boxen from 'boxen' 2 | import picocolors from 'picocolors' 3 | import type { Plugin } from 'vite' 4 | 5 | export default function appInfo(): Plugin { 6 | return { 7 | name: 'appInfo', 8 | apply: 'serve', 9 | async buildStart() { 10 | const { bold, green, bgGreen, underline } = picocolors 11 | console.log( 12 | boxen( 13 | `${bold(green(`由 ${bgGreen('Elegant-admin')} 驱动`))}\n\n${underline('https://github.com/zhangyao1990/elegant-admin')}`, 14 | { 15 | padding: 1, 16 | margin: 1, 17 | borderStyle: 'double', 18 | textAlignment: 'center', 19 | }, 20 | ), 21 | ) 22 | }, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /vite/plugins/archiver.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import dayjs from 'dayjs' 3 | import archiver from 'archiver' 4 | import type { Plugin } from 'vite' 5 | 6 | function sleep(ms) { 7 | return new Promise(resolve => setTimeout(resolve, ms)) 8 | } 9 | 10 | export default function createArchiver(env): Plugin { 11 | const { VITE_BUILD_ARCHIVE } = env 12 | let outDir: string 13 | return { 14 | name: 'vite-plugin-archiver', 15 | apply: 'build', 16 | configResolved(resolvedConfig) { 17 | outDir = resolvedConfig.build.outDir 18 | }, 19 | async closeBundle() { 20 | if (['zip', 'tar'].includes(VITE_BUILD_ARCHIVE)) { 21 | await sleep(1000) 22 | const archive = archiver(VITE_BUILD_ARCHIVE, { 23 | ...(VITE_BUILD_ARCHIVE === 'zip' && { zlib: { level: 9 } }), 24 | ...(VITE_BUILD_ARCHIVE === 'tar' && { gzip: true, gzipOptions: { level: 9 } }), 25 | }) 26 | const output = fs.createWriteStream(`${outDir}.${dayjs().format('YYYY-MM-DD-HH-mm-ss')}.${VITE_BUILD_ARCHIVE === 'zip' ? 'zip' : 'tar.gz'}`) 27 | archive.pipe(output) 28 | archive.directory(outDir, false) 29 | archive.finalize() 30 | } 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vite/plugins/auto-import.ts: -------------------------------------------------------------------------------- 1 | import autoImport from 'unplugin-auto-import/vite' 2 | 3 | export default function createAutoImport() { 4 | return autoImport({ 5 | imports: [ 6 | 'vue', 7 | 'vue-router', 8 | 'pinia', 9 | 'vue-i18n', 10 | ], 11 | dts: './src/types/auto-imports.d.ts', 12 | dirs: [ 13 | './src/hooks/**', 14 | ], 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /vite/plugins/banner.ts: -------------------------------------------------------------------------------- 1 | import banner from 'vite-plugin-banner' 2 | 3 | export default function createBanner() { 4 | return banner(` 5 | /** 6 | * 由 MrZhang 提供技术支持 7 | * Powered by elegant-admin 8 | * Github https://github.com/zhangyao1990/elegant-admin 9 | */ 10 | `) 11 | } 12 | -------------------------------------------------------------------------------- /vite/plugins/components.ts: -------------------------------------------------------------------------------- 1 | import components from 'unplugin-vue-components/vite' 2 | 3 | export default function createComponents() { 4 | return components({ 5 | dirs: [ 6 | 'src/components', 7 | 'src/layouts/ui-kit-components', 8 | ], 9 | include: [/\.vue$/, /\.vue\?vue/, /\.tsx$/], 10 | dts: './src/types/components.d.ts', 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /vite/plugins/compression.ts: -------------------------------------------------------------------------------- 1 | import { compression } from 'vite-plugin-compression2' 2 | import type { PluginOption } from 'vite' 3 | 4 | export default function createCompression(env, isBuild) { 5 | const plugin: (PluginOption | PluginOption[])[] = [] 6 | if (isBuild) { 7 | const { VITE_BUILD_COMPRESS } = env 8 | const compressList = VITE_BUILD_COMPRESS.split(',') 9 | if (compressList.includes('gzip')) { 10 | plugin.push( 11 | compression(), 12 | ) 13 | } 14 | if (compressList.includes('brotli')) { 15 | plugin.push( 16 | compression({ 17 | exclude: [/\.(br)$/, /\.(gz)$/], 18 | algorithm: 'brotliCompress', 19 | }), 20 | ) 21 | } 22 | } 23 | return plugin 24 | } 25 | -------------------------------------------------------------------------------- /vite/plugins/console.ts: -------------------------------------------------------------------------------- 1 | import TurboConsole from 'unplugin-turbo-console/vite' 2 | 3 | export default function createConsole() { 4 | return TurboConsole() 5 | } 6 | -------------------------------------------------------------------------------- /vite/plugins/devtools.ts: -------------------------------------------------------------------------------- 1 | import VueDevTools from 'vite-plugin-vue-devtools' 2 | 3 | export default function createDevtools(env) { 4 | const { VITE_OPEN_DEVTOOLS } = env 5 | return VITE_OPEN_DEVTOOLS === 'true' && VueDevTools() 6 | } 7 | -------------------------------------------------------------------------------- /vite/plugins/layouts.ts: -------------------------------------------------------------------------------- 1 | import Layouts from 'vite-plugin-vue-meta-layouts' 2 | 3 | export default function createLayouts() { 4 | return Layouts({ 5 | defaultLayout: 'index', 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /vite/plugins/mock.ts: -------------------------------------------------------------------------------- 1 | import { vitePluginFakeServer } from 'vite-plugin-fake-server' 2 | 3 | export default function createMock(env, isBuild) { 4 | const { VITE_BUILD_MOCK } = env 5 | return vitePluginFakeServer({ 6 | logger: !isBuild, 7 | include: 'src/mock', 8 | infixName: false, 9 | enableProd: isBuild && VITE_BUILD_MOCK === 'true', 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /vite/plugins/svg-icon.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import process from 'node:process' 3 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 4 | 5 | export default function createSvgIcon(isBuild) { 6 | return createSvgIconsPlugin({ 7 | iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/')], 8 | symbolId: 'icon-[dir]-[name]', 9 | svgoOptions: isBuild, 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /vite/plugins/unocss.ts: -------------------------------------------------------------------------------- 1 | import Unocss from 'unocss/vite' 2 | 3 | export default function createUnocss() { 4 | return Unocss() 5 | } 6 | -------------------------------------------------------------------------------- /vite/plugins/version-update.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import fs from 'node:fs' 3 | 4 | // 将文本内容写入指定文件中 5 | async function writeVersion(versionFile, content) { 6 | fs.writeFile(versionFile, content, (err) => { 7 | if (err) { 8 | throw err 9 | } 10 | }) 11 | } 12 | 13 | export default (options) => { 14 | // 声明配置文件路径 15 | let configPath 16 | return { 17 | name: 'refreshVersion', 18 | configResolved(resolvedConfig) { 19 | // 保存配置文件的路径,后用 20 | configPath = resolvedConfig.publicDir 21 | }, 22 | async buildStart() { 23 | // 生成版本信息文件路径 24 | const file = `${configPath + path.sep}version.json` 25 | // 采用编译的当前时间作为每个版本的标识 26 | const content = JSON.stringify({ version: options.version }) 27 | if (fs.existsSync(configPath)) { 28 | // 如果文件路径已存在,直接写入文件 29 | writeVersion(file, content) 30 | } 31 | else { 32 | // 如果文件路径不存在,先创建文件夹,然后再写入文件 33 | fs.mkdir(configPath, (err) => { 34 | if (err) { 35 | throw err 36 | } 37 | writeVersion(file, content) 38 | }) 39 | } 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /vite/plugins/visualizer.ts: -------------------------------------------------------------------------------- 1 | import { visualizer } from 'rollup-plugin-visualizer' 2 | 3 | // 依赖分析插件 4 | export default function createVisualizer() { 5 | return visualizer({ 6 | emitFile: true, 7 | filename: 'stats.html', // 分析图生成的文件名 8 | open: true, // 如果存在本地服务端口,将在打包后自动展示 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /vite/proxy.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: zhangyao 4 | * @Date: 2024-06-18 19:18:26 5 | * @LastEditTime: 2024-06-26 20:26:50 6 | * @LastEditors: zhangyao 7 | */ 8 | import type { ProxyOptions } from 'vite' 9 | 10 | type ProxyItem = [string, string] 11 | 12 | type ProxyList = ProxyItem[] 13 | 14 | type ProxyTargetList = Record 15 | 16 | const httpsRE = /^https:\/\// 17 | 18 | /** 19 | * 创建代理配置对象 20 | * @param list 代理列表,是一个键为前缀、值为目标地址的键值对数组,默认为空数组 21 | * @param VITE_OPEN_PROXY 环境变量,用于控制是否开启代理,字符串值,默认为字符串形式的 'true' 或其他非 'true' 值 22 | * @returns 返回一个对象,键为前缀,值为代理配置对象 23 | */ 24 | export function createProxy(list: ProxyList = [], VITE_OPEN_PROXY: boolean) { 25 | const ret: ProxyTargetList = {} 26 | for (const [prefix, target] of list) { 27 | const isHttps = httpsRE.test(target) 28 | // 根据代理目标地址配置代理选项 29 | ret[prefix] = { 30 | target, 31 | changeOrigin: VITE_OPEN_PROXY, 32 | // 如果环境变量 VITE_OPEN_PROXY 为 'true',则重写路径,否则移除路径中的 '/proxy' 33 | rewrite: path => VITE_OPEN_PROXY ? path.replace(new RegExp(`^${prefix}`), `${prefix}`) : path.replace(/\/proxy/, ''), 34 | // 如果目标地址是 HTTPS,则设置 secure 为 false 35 | ...(isHttps ? { secure: false } : {}), 36 | } 37 | } 38 | console.log('ret', ret) 39 | return ret 40 | } 41 | -------------------------------------------------------------------------------- /vite/utils.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | 3 | /** 4 | * 将环境配置信息包装并注入到进程环境变量中。 5 | * @param envConf 环境配置对象,键为环境变量名,值为环境变量的字符串值。 6 | * @returns 返回一个对象,其中包含经过处理的环境变量配置。 7 | */ 8 | export function wrapperEnv(envConf: any) { 9 | const ret: any = {} 10 | 11 | // 遍历环境配置对象,对每个环境变量进行处理 12 | for (const envName of Object.keys(envConf)) { 13 | // 替换环境变量值中的换行符,并处理布尔值字符串 14 | let realName = envConf[envName].replace(/\\n/g, '\n') 15 | realName = realName === 'true' ? true : realName === 'false' ? false : realName 16 | 17 | // 如果环境变量名为'VITE_PROXY'且其值不为空,则尝试将其解析为JSON 18 | if (envName === 'VITE_PROXY' && realName) { 19 | try { 20 | realName = JSON.parse(realName.replace(/'/g, '"')) 21 | } 22 | catch (error) { 23 | realName = '' 24 | } 25 | } 26 | 27 | // 将处理后的环境变量值存入返回对象,并根据值的类型注入到进程环境变量中 28 | ret[envName] = realName 29 | if (typeof realName === 'string') { 30 | process.env[envName] = realName 31 | } 32 | else if (typeof realName === 'object') { 33 | process.env[envName] = JSON.stringify(realName) 34 | } 35 | } 36 | return ret 37 | } 38 | --------------------------------------------------------------------------------
SVG Icon
翻转:
使用方法:
32 | 安装命令:pnpm add bytemd @bytemd/vue-next @bytemd/plugin-gfm 33 |
17 | 安装命令:pnpm add tinymce @tinymce/tinymce-vue 18 |
20 | 安装成功后,在框架 /public 目录下创建 tinymce 文件夹,并将 /node_modules/tinymce 目录下的 langs 和 skins 文件夹复制到 /public/tinymce 目录下。 21 |
22 | 安装命令:pnpm add typeit 23 |
19 | 安装命令:npm install xgplayer 20 |