From df60c8c91567a9805bd287aa3b29d57d502b809d Mon Sep 17 00:00:00 2001 From: zhouhaibin Date: Mon, 6 May 2024 17:03:42 +0800 Subject: [PATCH] first commit --- .env.base | 21 + .env.dev | 23 + .env.gitee | 23 + .env.pro | 23 + .env.test | 23 + .eslintignore | 8 + .eslintrc.js | 70 + .gitignore | 8 + .prettierignore | 10 + .stylelintignore | 6 + CHANGELOG.md | 802 ++++++++ LICENSE | 21 + README.md | 132 ++ README.zh-CN.md | 132 ++ commitlint.config.js | 28 + index.html | 142 ++ mock/_createProductionServer.ts | 18 + mock/analysis/index.ts | 89 + mock/dict/index.ts | 63 + mock/role/index.ts | 534 +++++ mock/table/index.ts | 137 ++ mock/user/index.ts | 93 + mock/workplace/index.ts | 172 ++ package.json | 135 ++ plop/component/component.hbs | 11 + plop/component/index.hbs | 3 + plop/component/prompt.js | 38 + plop/view/prompt.js | 37 + plop/view/view.hbs | 7 + plopfile.js | 7 + postcss.config.js | 5 + prettier.config.js | 19 + public/favicon.ico | Bin 0 -> 4286 bytes public/images/add-close.png | Bin 0 -> 466 bytes public/images/add-close1.png | Bin 0 -> 223 bytes public/images/cancel.png | Bin 0 -> 549 bytes public/images/check_box.png | Bin 0 -> 325 bytes public/images/icon_file.png | Bin 0 -> 209 bytes public/images/icon_people.png | Bin 0 -> 1185 bytes public/images/icon_role.png | Bin 0 -> 2390 bytes public/images/jiaojiao.png | Bin 0 -> 1097 bytes public/images/list_search.png | Bin 0 -> 1131 bytes public/images/loading.gif | Bin 0 -> 82505 bytes public/images/next_level.png | Bin 0 -> 245 bytes public/images/next_level_active.png | Bin 0 -> 266 bytes public/logo.png | Bin 0 -> 9193 bytes src/App.vue | 103 + src/api/common/index.ts | 11 + src/api/dashboard/analysis/index.ts | 23 + src/api/dashboard/analysis/types.ts | 22 + src/api/dashboard/workplace/index.ts | 22 + src/api/dashboard/workplace/types.ts | 30 + src/api/index.ts | 19 + src/api/login/index.ts | 34 + src/api/login/types.ts | 12 + src/api/table/index.ts | 18 + src/api/table/types.ts | 9 + src/assets/images/add-close.png | Bin 0 -> 466 bytes src/assets/images/add-close1.png | Bin 0 -> 223 bytes src/assets/images/cancel.png | Bin 0 -> 549 bytes src/assets/images/check_box.png | Bin 0 -> 325 bytes src/assets/images/icon_file.png | Bin 0 -> 209 bytes src/assets/images/icon_people.png | Bin 0 -> 1185 bytes src/assets/images/icon_role.png | Bin 0 -> 2390 bytes src/assets/images/jiaojiao.png | Bin 0 -> 1097 bytes src/assets/images/list_search.png | Bin 0 -> 1131 bytes src/assets/images/loading.gif | Bin 0 -> 82505 bytes src/assets/images/next_level.png | Bin 0 -> 245 bytes src/assets/images/next_level_active.png | Bin 0 -> 266 bytes src/assets/imgs/avatar.jpg | Bin 0 -> 6264 bytes src/assets/imgs/logo.png | Bin 0 -> 9193 bytes src/assets/svgs/403.svg | 1 + src/assets/svgs/404.svg | 1 + src/assets/svgs/500.svg | 1 + src/assets/svgs/icon.svg | 1 + src/assets/svgs/login-bg.svg | 1 + src/assets/svgs/login-box-bg.svg | 1 + src/assets/svgs/message.svg | 1 + src/assets/svgs/money.svg | 1 + src/assets/svgs/peoples.svg | 1 + src/assets/svgs/shopping.svg | 1 + src/components/Backtop/index.ts | 3 + src/components/Backtop/src/Backtop.vue | 15 + src/components/Breadcrumb/index.ts | 3 + src/components/Breadcrumb/src/Breadcrumb.vue | 127 ++ src/components/Breadcrumb/src/helper.ts | 31 + src/components/Collapse/index.ts | 3 + src/components/Collapse/src/Collapse.vue | 35 + src/components/ConfigGlobal/index.ts | 3 + .../ConfigGlobal/src/ConfigGlobal.vue | 63 + src/components/ContentDetailWrap/index.ts | 3 + .../src/ContentDetailWrap.vue | 59 + src/components/ContentWrap/index.ts | 3 + .../ContentWrap/src/ContentWrap.vue | 33 + src/components/ContextMenu/index.ts | 10 + .../ContextMenu/src/ContextMenu.vue | 72 + src/components/CountTo/index.ts | 3 + src/components/CountTo/src/CountTo.vue | 180 ++ src/components/Descriptions/index.ts | 3 + .../Descriptions/src/Descriptions.vue | 151 ++ src/components/Dialog/index.ts | 3 + src/components/Dialog/src/Dialog.vue | 119 ++ src/components/Echart/index.ts | 3 + src/components/Echart/src/Echart.vue | 113 ++ src/components/Editor/index.ts | 8 + src/components/Editor/src/Editor.vue | 138 ++ src/components/Error/index.ts | 3 + src/components/Error/src/Error.vue | 58 + src/components/Footer/index.ts | 3 + src/components/Footer/src/Footer.vue | 22 + src/components/Form/index.ts | 15 + src/components/Form/src/Form.vue | 301 +++ src/components/Form/src/componentMap.ts | 49 + .../Form/src/components/useRenderCheckbox.tsx | 26 + .../Form/src/components/useRenderRadio.tsx | 26 + .../Form/src/components/useRenderSelect.tsx | 57 + src/components/Form/src/helper.ts | 150 ++ src/components/Form/src/types.ts | 17 + src/components/Highlight/index.ts | 3 + src/components/Highlight/src/Highlight.vue | 65 + src/components/Icon/index.ts | 3 + src/components/Icon/src/Icon.vue | 78 + src/components/ImageViewer/index.ts | 33 + .../ImageViewer/src/ImageViewer.vue | 34 + src/components/ImageViewer/src/types.ts | 9 + src/components/Infotip/index.ts | 3 + src/components/Infotip/src/Infotip.vue | 53 + src/components/InputPassword/index.ts | 3 + .../InputPassword/src/InputPassword.vue | 152 ++ src/components/LocaleDropdown/index.ts | 3 + .../LocaleDropdown/src/LocaleDropdown.vue | 52 + src/components/Logo/index.ts | 3 + src/components/Logo/src/Logo.vue | 87 + src/components/Menu/index.ts | 3 + src/components/Menu/src/Menu.vue | 299 +++ .../Menu/src/components/useRenderMenuItem.tsx | 59 + .../src/components/useRenderMenuTitle.tsx | 37 + src/components/Menu/src/helper.ts | 55 + src/components/Qrcode/index.ts | 3 + src/components/Qrcode/src/Qrcode.vue | 252 +++ src/components/Screenfull/index.ts | 3 + src/components/Screenfull/src/Screenfull.vue | 30 + src/components/Search/index.ts | 3 + src/components/Search/src/Search.vue | 146 ++ src/components/Setting/index.ts | 3 + src/components/Setting/src/Setting.vue | 301 +++ .../src/components/ColorRadioPicker.vue | 65 + .../src/components/InterfaceDisplay.vue | 201 ++ .../src/components/LayoutRadioPicker.vue | 171 ++ src/components/SizeDropdown/index.ts | 3 + .../SizeDropdown/src/SizeDropdown.vue | 40 + src/components/Sticky/index.ts | 3 + src/components/Sticky/src/Sticky.vue | 141 ++ src/components/TabMenu/index.ts | 3 + src/components/TabMenu/src/TabMenu.vue | 253 +++ src/components/TabMenu/src/helper.ts | 52 + src/components/Table/index.ts | 12 + src/components/Table/src/Table.vue | 298 +++ src/components/Table/src/helper.ts | 8 + src/components/Table/src/types.ts | 26 + src/components/TagsView/index.ts | 3 + src/components/TagsView/src/TagsView.vue | 586 ++++++ src/components/TagsView/src/helper.ts | 21 + src/components/ThemeSwitch/index.ts | 3 + .../ThemeSwitch/src/ThemeSwitch.vue | 47 + src/components/UserInfo/index.ts | 3 + src/components/UserInfo/src/UserInfo.vue | 93 + src/components/abc/ChangePassword/index.vue | 82 + src/components/abc/CollapseTab/index.vue | 49 + .../abc/ColumnsController/index.vue | 110 ++ src/components/abc/ContentWrap/index.ts | 3 + .../abc/ContentWrap/src/ContentWrap.vue | 33 + .../abc/CronExpression/components/day.vue | 223 +++ .../abc/CronExpression/components/hour.vue | 197 ++ .../abc/CronExpression/components/month.vue | 194 ++ .../components/secondAndMinute.vue | 195 ++ .../abc/CronExpression/components/week.vue | 233 +++ .../abc/CronExpression/components/year.vue | 150 ++ src/components/abc/CronExpression/index.vue | 206 ++ src/components/abc/Dialog/index.ts | 3 + src/components/abc/Dialog/src/Dialog.vue | 91 + .../DictionaryCheckboxGroup.vue | 133 ++ .../DictionarySelect/DictionaryRadioGroup.vue | 59 + .../abc/DictionarySelect/DictionarySelect.vue | 115 ++ src/components/abc/Editor/index.ts | 8 + src/components/abc/Editor/src/Editor.vue | 220 +++ src/components/abc/EntityReference/index.vue | 82 + .../FormCreateComponent/AttachmentManager.js | 25 + .../AttachmentManagerAndUploader.js | 25 + .../FormCreateComponent/AttachmentUploader.js | 25 + .../FormCreateComponent/AttachmentViewer.js | 25 + .../DictionaryRadioGroup.js | 28 + .../FormCreateComponent/DictionarySelect.js | 29 + .../abc/FormCreateComponent/IconPicker.js | 25 + .../OrganizationMultipleSelect.js | 25 + .../OrganizationSingleSelect.js | 25 + .../abc/FormCreateComponent/RichText.js | 26 + .../FormCreateComponent/UserSingleSelect.js | 25 + src/components/abc/IconDisplay/index.vue | 12 + src/components/abc/IconPicker/index.vue | 100 + src/components/abc/ListPager/index.vue | 43 + src/components/abc/Notification/index.vue | 39 + src/components/abc/PortletContainer/index.vue | 115 ++ src/components/abc/QueryButton/index.vue | 44 + src/components/abc/QueryText/index.vue | 75 + src/components/index.ts | 6 + src/config/axios/config.ts | 46 + src/config/axios/index.ts | 112 ++ src/config/axios/service.ts | 113 ++ src/constant/common.ts | 75 + src/constant/index.ts | 24 + src/directives/index.ts | 10 + src/directives/permission/hasPermi.ts | 47 + src/hooks/event/useScrollTo.ts | 62 + src/hooks/useGlobal.js | 14 + src/hooks/web/useCache.ts | 17 + src/hooks/web/useConfigGlobal.ts | 10 + src/hooks/web/useCrudSchemas.ts | 253 +++ src/hooks/web/useDesign.ts | 18 + src/hooks/web/useEmitt.ts | 23 + src/hooks/web/useForm.ts | 95 + src/hooks/web/useI18n.ts | 52 + src/hooks/web/useIcon.ts | 8 + src/hooks/web/useIntro.ts | 47 + src/hooks/web/useLocale.ts | 35 + src/hooks/web/useNProgress.ts | 34 + src/hooks/web/usePageLoading.ts | 18 + src/hooks/web/useTable.ts | 196 ++ src/hooks/web/useTimeAgo.ts | 50 + src/hooks/web/useTitle.ts | 25 + src/hooks/web/useValidator.ts | 64 + src/hooks/web/useWatermark.ts | 55 + src/layout/Layout.vue | 78 + src/layout/components/AppView.vue | 87 + src/layout/components/ToolHeader.vue | 85 + src/layout/components/useRenderLayout.tsx | 276 +++ src/locales/en.ts | 443 +++++ src/locales/zh-CN.ts | 438 +++++ src/main.ts | 172 ++ src/mixin/addForAdvanceConfigMixin.js | 89 + src/mixin/addMixin.js | 102 + src/mixin/flowMixin.js | 236 +++ src/mixin/listMixin.js | 355 ++++ src/mixin/modifyForAdvanceConfigMixin.js | 91 + src/mixin/modifyMixin.js | 102 + src/mixin/referenceMixin.js | 233 +++ src/mixin/treeListReferenceMixin.js | 229 +++ src/mixin/treeMixin.js | 73 + src/mixin/treeMultipleDirectReferenceMixin.js | 91 + src/mixin/treeMultipleReferenceMixin.js | 117 ++ src/mixin/treeMultipleSelectMixin.js | 120 ++ src/mixin/treeReferenceMixin.js | 136 ++ src/mixin/treeSingleSelectMixin.js | 77 + src/mixin/treelistMixin.js | 23 + src/mixin/viewForAdvanceConfigMixin.js | 54 + src/mixin/viewMixin.js | 58 + src/modules/businessflow/api/index.ts | 26 + src/modules/businessflow/constant/index.ts | 1 + .../businessflow/view/dictionary/list.js | 2 + src/modules/businessflow/view/leave/add.vue | 113 ++ .../businessflow/view/leave/detail.vue | 174 ++ src/modules/businessflow/view/leave/list.vue | 193 ++ .../businessflow/view/leave/modify.vue | 113 ++ src/modules/businessflow/view/leave/view.vue | 104 + .../businessflow/view/navigate/index.vue | 57 + src/modules/cip/api/index.ts | 112 ++ src/modules/cip/constant/index.ts | 1 + src/modules/cip/view/activeMessage/list.vue | 295 +++ src/modules/cip/view/activeMessage/view.vue | 99 + src/modules/cip/view/apiService/add.vue | 70 + src/modules/cip/view/apiService/list.vue | 204 ++ src/modules/cip/view/apiService/modify.vue | 70 + src/modules/cip/view/apiService/reference.vue | 127 ++ src/modules/cip/view/apiService/view.vue | 61 + src/modules/cip/view/apiServiceLog/list.vue | 210 ++ src/modules/cip/view/apiServiceLog/view.vue | 94 + src/modules/cip/view/app/add.vue | 68 + .../cip/view/app/apiServicePermission.vue | 234 +++ src/modules/cip/view/app/list.vue | 243 +++ .../cip/view/app/messagePermission.vue | 238 +++ src/modules/cip/view/app/modify.vue | 68 + src/modules/cip/view/app/reference.vue | 122 ++ src/modules/cip/view/app/view.vue | 58 + .../cip/view/appDataPermission/add.vue | 71 + .../cip/view/appDataPermission/list.vue | 167 ++ .../cip/view/appDataPermission/modify.vue | 71 + .../cip/view/appDataPermission/view.vue | 63 + src/modules/cip/view/messageLog/list.vue | 267 +++ src/modules/cip/view/messageLog/view.vue | 96 + .../cip/view/messageSubscription/list.vue | 235 +++ src/modules/cip/view/messageTopic/add.vue | 75 + src/modules/cip/view/messageTopic/list.vue | 209 ++ src/modules/cip/view/messageTopic/modify.vue | 75 + .../cip/view/messageTopic/reference.vue | 124 ++ src/modules/cip/view/messageTopic/view.vue | 67 + src/modules/echart/monthlySales.vue | 77 + src/modules/echart/stageSpeedGauge.vue | 91 + src/modules/echart/userSource.vue | 61 + src/modules/echart/weeklyUserActivity.vue | 40 + src/modules/entityconfig/api/index.ts | 177 ++ src/modules/entityconfig/constant/index.ts | 60 + src/modules/entityconfig/util/util.js | 59 + src/modules/entityconfig/view/entity/add.vue | 72 + .../entityconfig/view/entity/config.vue | 75 + src/modules/entityconfig/view/entity/list.vue | 221 +++ .../entityconfig/view/entity/modify.vue | 72 + .../entityconfig/view/entity/reference.vue | 146 ++ src/modules/entityconfig/view/entity/view.vue | 63 + .../entityconfig/view/entityModel/add.vue | 80 + .../entityconfig/view/entityModel/list.vue | 178 ++ .../entityconfig/view/entityModel/modify.vue | 76 + .../view/entityModel/reference.vue | 133 ++ .../view/entityModel/singleSelect.vue | 126 ++ .../entityconfig/view/entityModel/view.vue | 65 + .../view/entityModelProperty/add.vue | 257 +++ .../view/entityModelProperty/list.vue | 248 +++ .../view/entityModelProperty/modify.vue | 267 +++ .../view/entityModelProperty/reference.vue | 122 ++ .../view/entityModelProperty/view.vue | 196 ++ .../entityconfig/view/entityView/add.vue | 163 ++ .../entityconfig/view/entityView/list.vue | 247 +++ .../entityconfig/view/entityView/modify.vue | 162 ++ .../entityconfig/view/entityView/view.vue | 144 ++ .../entityViewConfig/advanceView/index.vue | 317 +++ .../advanceView/indexBack.vue | 53 + .../advanceView/indexBack2.vue | 194 ++ .../entityViewConfig/buttonArea/index.vue | 163 ++ .../entityViewConfig/detailView/index.vue | 55 + .../detailView/propertyArea/add.vue | 179 ++ .../detailView/propertyArea/index.vue | 145 ++ .../detailView/propertyArea/modify.vue | 184 ++ .../detailView/propertyList/index.vue | 102 + .../view/entityViewConfig/listView/index.vue | 76 + .../listView/propertyList/index.vue | 122 ++ .../listView/queryArea/add.vue | 181 ++ .../listView/queryArea/index.vue | 148 ++ .../listView/queryArea/modify.vue | 177 ++ .../listView/resultArea/add.vue | 95 + .../listView/resultArea/index.vue | 148 ++ .../listView/resultArea/modify.vue | 92 + .../entityconfig/view/template/add.vue | 502 +++++ .../entityconfig/view/template/list.vue | 214 ++ .../entityconfig/view/template/modify.vue | 502 +++++ .../entityconfig/view/template/reference.vue | 192 ++ .../entityconfig/view/template/view.vue | 502 +++++ .../entityconfig/view/viewButton/add.vue | 117 ++ .../entityconfig/view/viewButton/modify.vue | 114 ++ .../entityconfig/view/viewButton/select.vue | 59 + .../view/viewButtonTemplate/add.vue | 109 ++ .../view/viewButtonTemplate/list.vue | 175 ++ .../view/viewButtonTemplate/modify.vue | 110 ++ .../view/viewButtonTemplate/view.vue | 81 + src/modules/notification/api/index.ts | 19 + src/modules/notification/constant/index.ts | 1 + .../notification/view/systemMessage/list.vue | 154 ++ .../notification/view/systemMessage/view.vue | 70 + .../view/systemMessage/webSocket.js | 183 ++ src/modules/scheduler/api/index.ts | 38 + src/modules/scheduler/constant/index.ts | 1 + src/modules/scheduler/view/job/add.vue | 63 + src/modules/scheduler/view/job/list.vue | 192 ++ src/modules/scheduler/view/job/modify.vue | 63 + src/modules/scheduler/view/job/reference.vue | 114 ++ src/modules/scheduler/view/job/view.vue | 55 + src/modules/scheduler/view/jobParam/add.vue | 72 + src/modules/scheduler/view/jobParam/list.vue | 168 ++ .../scheduler/view/jobParam/modify.vue | 68 + src/modules/scheduler/view/jobParam/view.vue | 60 + .../scheduler/view/schedulerJob/add.vue | 72 + .../scheduler/view/schedulerJob/list.vue | 277 +++ .../scheduler/view/schedulerJob/modify.vue | 72 + .../scheduler/view/schedulerJob/reference.vue | 125 ++ .../scheduler/view/schedulerJob/view.vue | 63 + .../scheduler/view/schedulerJobParam/add.vue | 75 + .../scheduler/view/schedulerJobParam/list.vue | 168 ++ .../view/schedulerJobParam/modify.vue | 71 + .../scheduler/view/schedulerJobParam/view.vue | 63 + src/modules/support/api/index.ts | 98 + src/modules/support/constant/index.ts | 1 + .../view/attachment/attachmentManager.vue | 88 + .../attachmentManagerAndUploader.vue | 43 + .../view/attachment/attachmentUploader.vue | 219 +++ .../view/attachment/attachmentViewer.vue | 62 + .../support/view/contentTemplate/add.vue | 80 + .../support/view/contentTemplate/list.vue | 162 ++ .../support/view/contentTemplate/modify.vue | 80 + .../support/view/contentTemplate/view.vue | 71 + .../support/view/desktopTemplate/add.vue | 68 + .../view/desktopTemplate/configDesktop.vue | 371 ++++ .../support/view/desktopTemplate/desktop.vue | 76 + .../support/view/desktopTemplate/list.vue | 172 ++ .../support/view/desktopTemplate/modify.vue | 68 + .../view/desktopTemplate/reference.vue | 114 ++ .../support/view/desktopTemplate/view.vue | 59 + src/modules/support/view/notice/add.vue | 86 + src/modules/support/view/notice/list.vue | 259 +++ src/modules/support/view/notice/modify.vue | 86 + .../support/view/notice/noticeView.vue | 75 + .../support/view/notice/noticeViewList.vue | 182 ++ src/modules/support/view/notice/view.vue | 82 + src/modules/support/view/portlet/add.vue | 75 + src/modules/support/view/portlet/list.vue | 209 ++ src/modules/support/view/portlet/modify.vue | 75 + .../support/view/portlet/reference.vue | 127 ++ src/modules/support/view/portlet/view.vue | 64 + .../support/view/portletList/favorite.vue | 195 ++ .../support/view/portletList/notice.vue | 147 ++ src/modules/support/view/portletParam/add.vue | 75 + .../support/view/portletParam/list.vue | 163 ++ .../support/view/portletParam/modify.vue | 71 + .../support/view/portletParam/view.vue | 63 + src/modules/support/view/serialNo/add.vue | 89 + src/modules/support/view/serialNo/list.vue | 191 ++ src/modules/support/view/serialNo/modify.vue | 89 + .../support/view/serialNo/reference.vue | 166 ++ src/modules/support/view/serialNo/view.vue | 78 + src/modules/system/api/index.ts | 233 +++ src/modules/system/constant/index.ts | 1 + .../system/view/dictionaryItem/add.vue | 76 + .../system/view/dictionaryItem/list.vue | 185 ++ .../system/view/dictionaryItem/modify.vue | 72 + .../system/view/dictionaryItem/treeList.vue | 41 + .../system/view/dictionaryItem/view.vue | 63 + .../system/view/dictionaryType/add.vue | 72 + .../system/view/dictionaryType/list.vue | 173 ++ .../system/view/dictionaryType/modify.vue | 68 + .../system/view/dictionaryType/tree.vue | 34 + .../system/view/dictionaryType/treeList.vue | 41 + .../view/dictionaryType/treeReference.vue | 51 + .../dictionaryType/treeReferenceUseCode.vue | 100 + .../system/view/dictionaryType/view.vue | 60 + src/modules/system/view/log/list.vue | 199 ++ src/modules/system/view/log/view.vue | 86 + src/modules/system/view/module/add.vue | 71 + src/modules/system/view/module/list.vue | 163 ++ src/modules/system/view/module/modify.vue | 72 + src/modules/system/view/module/reference.vue | 122 ++ src/modules/system/view/module/view.vue | 61 + src/modules/system/view/organization/add.vue | 74 + src/modules/system/view/organization/list.vue | 260 +++ .../system/view/organization/modify.vue | 215 ++ .../system/view/organization/modifyBack.vue | 78 + src/modules/system/view/organization/tree.vue | 34 + .../system/view/organization/treeList.vue | 41 + .../organization/treeMultipleReference.vue | 47 + .../view/organization/treeMultipleSelect.vue | 49 + .../view/organization/treeReference.vue | 53 + src/modules/system/view/organization/view.vue | 61 + src/modules/system/view/param/add.vue | 59 + src/modules/system/view/param/list.vue | 151 ++ src/modules/system/view/param/modify.vue | 60 + src/modules/system/view/param/view.vue | 52 + .../system/view/permissionItem/add.vue | 100 + .../system/view/permissionItem/list.vue | 232 +++ .../system/view/permissionItem/modify.vue | 97 + .../system/view/permissionItem/tree.vue | 34 + .../system/view/permissionItem/treeList.vue | 41 + .../permissionItem/treeMultipleReference.vue | 40 + .../view/permissionItem/treeReference.vue | 51 + .../system/view/permissionItem/view.vue | 82 + src/modules/system/view/systemManage/list.vue | 22 + src/modules/system/view/user/add.vue | 105 + src/modules/system/view/user/list.vue | 362 ++++ src/modules/system/view/user/modify.vue | 101 + src/modules/system/view/user/reference.vue | 125 ++ src/modules/system/view/user/treeList.vue | 41 + .../system/view/user/treeListReference.vue | 137 ++ src/modules/system/view/user/view.vue | 88 + src/modules/system/view/userGroup/add.vue | 75 + src/modules/system/view/userGroup/list.vue | 229 +++ src/modules/system/view/userGroup/modify.vue | 71 + src/modules/system/view/userGroup/tree.vue | 34 + .../system/view/userGroup/treeList.vue | 41 + .../userGroup/treeMultipleDirectReference.vue | 40 + .../system/view/userGroup/treeReference.vue | 51 + .../view/userGroup/treeSingleSelect.vue | 38 + .../system/view/userGroup/user/index.vue | 52 + .../system/view/userGroup/user/list.vue | 271 +++ src/modules/system/view/userGroup/view.vue | 63 + .../system/view/userProfile/modify.vue | 68 + src/modules/template/api/index.ts | 12 + src/modules/template/constant/index.ts | 1 + src/modules/workflow/api/index.ts | 185 ++ src/modules/workflow/constant/index.ts | 1 + src/modules/workflow/view/apply/list.vue | 167 ++ src/modules/workflow/view/done/list.vue | 165 ++ src/modules/workflow/view/flow/create.vue | 67 + src/modules/workflow/view/flow/view.vue | 74 + .../workflow/view/portletList/apply.vue | 187 ++ .../workflow/view/portletList/todo.vue | 179 ++ src/modules/workflow/view/task/basicInfo.vue | 53 + src/modules/workflow/view/task/commit.vue | 209 ++ src/modules/workflow/view/task/delegate.vue | 82 + src/modules/workflow/view/task/handle.vue | 158 ++ .../workflow/view/task/historyStep.vue | 45 + src/modules/workflow/view/task/jump.vue | 222 +++ src/modules/workflow/view/task/reject.vue | 222 +++ src/modules/workflow/view/task/transfer.vue | 82 + src/modules/workflow/view/todo/list.vue | 159 ++ .../workflow/view/workflowListener/add.vue | 68 + .../workflow/view/workflowListener/list.vue | 193 ++ .../workflow/view/workflowListener/modify.vue | 68 + .../view/workflowListener/reference.vue | 141 ++ .../workflow/view/workflowListener/view.vue | 58 + .../workflow/view/workflowTemplate/add.vue | 84 + .../workflow/view/workflowTemplate/list.vue | 287 +++ .../model/components/ViewDiagramButton.vue | 44 + .../model/components/addNode.vue | 206 ++ .../components/dialog/FlowListenerSelect.vue | 79 + .../components/dialog/FlowStepSelect.vue | 81 + .../model/components/dialog/ImportDialog.vue | 38 + .../model/components/dialog/ViewDialog.vue | 54 + .../model/components/dialog/common.js | 52 + .../model/components/dialog/errorDialog.vue | 77 + .../components/drawer/ConditionNodeConfig.vue | 73 + .../components/drawer/HandleNodeConfig.vue | 307 +++ .../components/drawer/RootNodeConfig.vue | 163 ++ .../model/components/flowDesign.vue | 184 ++ .../model/components/flowPreview.vue | 88 + .../model/components/nodeWrap.vue | 354 ++++ .../workflowTemplate/model/css/dialog.css | 34 + .../workflowTemplate/model/css/workflow.css | 1722 +++++++++++++++++ .../workflowTemplate/model/stores/index.js | 53 + .../workflowTemplate/model/utils/index.js | 193 ++ .../workflow/view/workflowTemplate/modify.vue | 85 + .../workflow/view/workflowTemplate/view.vue | 76 + src/permission.ts | 62 + src/plugins/animate.css/index.ts | 1 + src/plugins/echarts/index.ts | 41 + src/plugins/elementPlus/index.ts | 18 + src/plugins/svgIcon/index.ts | 3 + src/plugins/vueI18n/helper.ts | 3 + src/plugins/vueI18n/index.ts | 42 + src/plugins/windi.css/index.ts | 3 + src/router/index.ts | 586 ++++++ src/store/index.ts | 10 + src/store/modules/app.ts | 267 +++ src/store/modules/dict.ts | 34 + src/store/modules/locale.ts | 59 + src/store/modules/notification.ts | 35 + src/store/modules/permission.ts | 96 + src/store/modules/tagsView.ts | 149 ++ src/store/modules/user.ts | 65 + src/styles/index.less | 7 + src/styles/theme.less | 6 + src/styles/var.css | 67 + src/styles/variables.module.less | 10 + src/types/components.d.ts | 52 + src/types/configGlobal.d.ts | 4 + src/types/contextMenu.d.ts | 7 + src/types/descriptions.d.ts | 11 + src/types/elementPlus.d.ts | 3 + src/types/form.d.ts | 45 + src/types/icon.d.ts | 5 + src/types/infoTip.d.ts | 4 + src/types/layout.d.ts | 1 + src/types/localeDropdown.d.ts | 10 + src/types/qrcode.d.ts | 9 + src/types/table.d.ts | 36 + src/types/theme.d.ts | 16 + src/utils/TableColumnFormatter.js | 17 + src/utils/auth.ts | 18 + src/utils/color.ts | 153 ++ src/utils/dateFormatter.js | 48 + src/utils/domUtils.ts | 289 +++ src/utils/index.ts | 158 ++ src/utils/is.ts | 105 + src/utils/propTypes.ts | 29 + src/utils/routerHelper.ts | 193 ++ src/utils/stringUtil.js | 10 + src/utils/tree.ts | 207 ++ src/utils/tsxHelper.ts | 16 + src/views/Authorization/Role.vue | 89 + src/views/Authorization/User.vue | 89 + src/views/Components/CommonSelect.vue | 19 + src/views/Components/CountTo.vue | 100 + src/views/Components/Descriptions.vue | 143 ++ src/views/Components/Dialog.vue | 148 ++ src/views/Components/Echart.vue | 36 + src/views/Components/Editor/Editor.vue | 32 + src/views/Components/Form/DefaultForm.vue | 1136 +++++++++++ src/views/Components/Form/RefForm.vue | 270 +++ src/views/Components/Form/UseFormDemo.vue | 280 +++ src/views/Components/Highlight.vue | 20 + src/views/Components/Icon.vue | 62 + src/views/Components/ImageViewer.vue | 30 + src/views/Components/Infotip.vue | 33 + src/views/Components/InputPassword.vue | 21 + src/views/Components/MyEchart.vue | 38 + src/views/Components/Qrcode.vue | 108 ++ src/views/Components/Search.vue | 227 +++ src/views/Components/Sticky.vue | 62 + src/views/Components/Table/DefaultTable.vue | 107 + src/views/Components/Table/RefTable.vue | 182 ++ src/views/Components/Table/UseTableDemo.vue | 223 +++ src/views/Dashboard/Analysis.vue | 127 ++ src/views/Dashboard/Workplace.vue | 291 +++ src/views/Dashboard/components/PanelGroup.vue | 200 ++ src/views/Dashboard/echarts-data.ts | 309 +++ src/views/Error/403.vue | 17 + src/views/Error/404.vue | 17 + src/views/Error/500.vue | 17 + src/views/Example/Dialog/ExampleDialog.vue | 282 +++ .../Example/Dialog/components/Detail.vue | 41 + src/views/Example/Dialog/components/Write.vue | 56 + src/views/Example/Page/ExampleAdd.vue | 52 + src/views/Example/Page/ExampleDetail.vue | 32 + src/views/Example/Page/ExampleEdit.vue | 64 + src/views/Example/Page/ExamplePage.vue | 165 ++ src/views/Example/Page/components/Detail.vue | 66 + src/views/Example/Page/components/Write.vue | 147 ++ src/views/Guide/Guide.vue | 20 + src/views/Level/Menu111.vue | 20 + src/views/Level/Menu12.vue | 20 + src/views/Level/Menu2.vue | 20 + src/views/Login/Login.vue | 94 + src/views/Login/components/LoginForm.vue | 171 ++ src/views/Login/components/RegisterForm.vue | 145 ++ src/views/Login/components/index.ts | 3 + src/views/Redirect/Redirect.vue | 30 + src/views/hooks/useCrudSchemas.vue | 223 +++ src/views/hooks/useWatermark.vue | 32 + stylelint.config.js | 231 +++ tsconfig.json | 39 + types/components.d.ts | 7 + types/custom-types.d.ts | 27 + types/env.d.ts | 26 + types/global.d.ts | 39 + types/router.d.ts | 74 + vite.config.ts | 149 ++ windi.config.ts | 76 + 631 files changed, 60386 insertions(+) create mode 100644 .env.base create mode 100644 .env.dev create mode 100644 .env.gitee create mode 100644 .env.pro create mode 100644 .env.test create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .stylelintignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README.zh-CN.md create mode 100644 commitlint.config.js create mode 100644 index.html create mode 100644 mock/_createProductionServer.ts create mode 100644 mock/analysis/index.ts create mode 100644 mock/dict/index.ts create mode 100644 mock/role/index.ts create mode 100644 mock/table/index.ts create mode 100644 mock/user/index.ts create mode 100644 mock/workplace/index.ts create mode 100644 package.json create mode 100644 plop/component/component.hbs create mode 100644 plop/component/index.hbs create mode 100644 plop/component/prompt.js create mode 100644 plop/view/prompt.js create mode 100644 plop/view/view.hbs create mode 100644 plopfile.js create mode 100644 postcss.config.js create mode 100644 prettier.config.js create mode 100644 public/favicon.ico create mode 100644 public/images/add-close.png create mode 100644 public/images/add-close1.png create mode 100644 public/images/cancel.png create mode 100644 public/images/check_box.png create mode 100644 public/images/icon_file.png create mode 100644 public/images/icon_people.png create mode 100644 public/images/icon_role.png create mode 100644 public/images/jiaojiao.png create mode 100644 public/images/list_search.png create mode 100644 public/images/loading.gif create mode 100644 public/images/next_level.png create mode 100644 public/images/next_level_active.png create mode 100644 public/logo.png create mode 100644 src/App.vue create mode 100644 src/api/common/index.ts create mode 100644 src/api/dashboard/analysis/index.ts create mode 100644 src/api/dashboard/analysis/types.ts create mode 100644 src/api/dashboard/workplace/index.ts create mode 100644 src/api/dashboard/workplace/types.ts create mode 100644 src/api/index.ts create mode 100644 src/api/login/index.ts create mode 100644 src/api/login/types.ts create mode 100644 src/api/table/index.ts create mode 100644 src/api/table/types.ts create mode 100644 src/assets/images/add-close.png create mode 100644 src/assets/images/add-close1.png create mode 100644 src/assets/images/cancel.png create mode 100644 src/assets/images/check_box.png create mode 100644 src/assets/images/icon_file.png create mode 100644 src/assets/images/icon_people.png create mode 100644 src/assets/images/icon_role.png create mode 100644 src/assets/images/jiaojiao.png create mode 100644 src/assets/images/list_search.png create mode 100644 src/assets/images/loading.gif create mode 100644 src/assets/images/next_level.png create mode 100644 src/assets/images/next_level_active.png create mode 100644 src/assets/imgs/avatar.jpg create mode 100644 src/assets/imgs/logo.png create mode 100644 src/assets/svgs/403.svg create mode 100644 src/assets/svgs/404.svg create mode 100644 src/assets/svgs/500.svg create mode 100644 src/assets/svgs/icon.svg create mode 100644 src/assets/svgs/login-bg.svg create mode 100644 src/assets/svgs/login-box-bg.svg create mode 100644 src/assets/svgs/message.svg create mode 100644 src/assets/svgs/money.svg create mode 100644 src/assets/svgs/peoples.svg create mode 100644 src/assets/svgs/shopping.svg create mode 100644 src/components/Backtop/index.ts create mode 100644 src/components/Backtop/src/Backtop.vue create mode 100644 src/components/Breadcrumb/index.ts create mode 100644 src/components/Breadcrumb/src/Breadcrumb.vue create mode 100644 src/components/Breadcrumb/src/helper.ts create mode 100644 src/components/Collapse/index.ts create mode 100644 src/components/Collapse/src/Collapse.vue create mode 100644 src/components/ConfigGlobal/index.ts create mode 100644 src/components/ConfigGlobal/src/ConfigGlobal.vue create mode 100644 src/components/ContentDetailWrap/index.ts create mode 100644 src/components/ContentDetailWrap/src/ContentDetailWrap.vue create mode 100644 src/components/ContentWrap/index.ts create mode 100644 src/components/ContentWrap/src/ContentWrap.vue create mode 100644 src/components/ContextMenu/index.ts create mode 100644 src/components/ContextMenu/src/ContextMenu.vue create mode 100644 src/components/CountTo/index.ts create mode 100644 src/components/CountTo/src/CountTo.vue create mode 100644 src/components/Descriptions/index.ts create mode 100644 src/components/Descriptions/src/Descriptions.vue create mode 100644 src/components/Dialog/index.ts create mode 100644 src/components/Dialog/src/Dialog.vue create mode 100644 src/components/Echart/index.ts create mode 100644 src/components/Echart/src/Echart.vue create mode 100644 src/components/Editor/index.ts create mode 100644 src/components/Editor/src/Editor.vue create mode 100644 src/components/Error/index.ts create mode 100644 src/components/Error/src/Error.vue create mode 100644 src/components/Footer/index.ts create mode 100644 src/components/Footer/src/Footer.vue create mode 100644 src/components/Form/index.ts create mode 100644 src/components/Form/src/Form.vue create mode 100644 src/components/Form/src/componentMap.ts create mode 100644 src/components/Form/src/components/useRenderCheckbox.tsx create mode 100644 src/components/Form/src/components/useRenderRadio.tsx create mode 100644 src/components/Form/src/components/useRenderSelect.tsx create mode 100644 src/components/Form/src/helper.ts create mode 100644 src/components/Form/src/types.ts create mode 100644 src/components/Highlight/index.ts create mode 100644 src/components/Highlight/src/Highlight.vue create mode 100644 src/components/Icon/index.ts create mode 100644 src/components/Icon/src/Icon.vue create mode 100644 src/components/ImageViewer/index.ts create mode 100644 src/components/ImageViewer/src/ImageViewer.vue create mode 100644 src/components/ImageViewer/src/types.ts create mode 100644 src/components/Infotip/index.ts create mode 100644 src/components/Infotip/src/Infotip.vue create mode 100644 src/components/InputPassword/index.ts create mode 100644 src/components/InputPassword/src/InputPassword.vue create mode 100644 src/components/LocaleDropdown/index.ts create mode 100644 src/components/LocaleDropdown/src/LocaleDropdown.vue create mode 100644 src/components/Logo/index.ts create mode 100644 src/components/Logo/src/Logo.vue create mode 100644 src/components/Menu/index.ts create mode 100644 src/components/Menu/src/Menu.vue create mode 100644 src/components/Menu/src/components/useRenderMenuItem.tsx create mode 100644 src/components/Menu/src/components/useRenderMenuTitle.tsx create mode 100644 src/components/Menu/src/helper.ts create mode 100644 src/components/Qrcode/index.ts create mode 100644 src/components/Qrcode/src/Qrcode.vue create mode 100644 src/components/Screenfull/index.ts create mode 100644 src/components/Screenfull/src/Screenfull.vue create mode 100644 src/components/Search/index.ts create mode 100644 src/components/Search/src/Search.vue create mode 100644 src/components/Setting/index.ts create mode 100644 src/components/Setting/src/Setting.vue create mode 100644 src/components/Setting/src/components/ColorRadioPicker.vue create mode 100644 src/components/Setting/src/components/InterfaceDisplay.vue create mode 100644 src/components/Setting/src/components/LayoutRadioPicker.vue create mode 100644 src/components/SizeDropdown/index.ts create mode 100644 src/components/SizeDropdown/src/SizeDropdown.vue create mode 100644 src/components/Sticky/index.ts create mode 100644 src/components/Sticky/src/Sticky.vue create mode 100644 src/components/TabMenu/index.ts create mode 100644 src/components/TabMenu/src/TabMenu.vue create mode 100644 src/components/TabMenu/src/helper.ts create mode 100644 src/components/Table/index.ts create mode 100644 src/components/Table/src/Table.vue create mode 100644 src/components/Table/src/helper.ts create mode 100644 src/components/Table/src/types.ts create mode 100644 src/components/TagsView/index.ts create mode 100644 src/components/TagsView/src/TagsView.vue create mode 100644 src/components/TagsView/src/helper.ts create mode 100644 src/components/ThemeSwitch/index.ts create mode 100644 src/components/ThemeSwitch/src/ThemeSwitch.vue create mode 100644 src/components/UserInfo/index.ts create mode 100644 src/components/UserInfo/src/UserInfo.vue create mode 100644 src/components/abc/ChangePassword/index.vue create mode 100644 src/components/abc/CollapseTab/index.vue create mode 100644 src/components/abc/ColumnsController/index.vue create mode 100644 src/components/abc/ContentWrap/index.ts create mode 100644 src/components/abc/ContentWrap/src/ContentWrap.vue create mode 100644 src/components/abc/CronExpression/components/day.vue create mode 100644 src/components/abc/CronExpression/components/hour.vue create mode 100644 src/components/abc/CronExpression/components/month.vue create mode 100644 src/components/abc/CronExpression/components/secondAndMinute.vue create mode 100644 src/components/abc/CronExpression/components/week.vue create mode 100644 src/components/abc/CronExpression/components/year.vue create mode 100644 src/components/abc/CronExpression/index.vue create mode 100644 src/components/abc/Dialog/index.ts create mode 100644 src/components/abc/Dialog/src/Dialog.vue create mode 100644 src/components/abc/DictionarySelect/DictionaryCheckboxGroup.vue create mode 100644 src/components/abc/DictionarySelect/DictionaryRadioGroup.vue create mode 100644 src/components/abc/DictionarySelect/DictionarySelect.vue create mode 100644 src/components/abc/Editor/index.ts create mode 100644 src/components/abc/Editor/src/Editor.vue create mode 100644 src/components/abc/EntityReference/index.vue create mode 100644 src/components/abc/FormCreateComponent/AttachmentManager.js create mode 100644 src/components/abc/FormCreateComponent/AttachmentManagerAndUploader.js create mode 100644 src/components/abc/FormCreateComponent/AttachmentUploader.js create mode 100644 src/components/abc/FormCreateComponent/AttachmentViewer.js create mode 100644 src/components/abc/FormCreateComponent/DictionaryRadioGroup.js create mode 100644 src/components/abc/FormCreateComponent/DictionarySelect.js create mode 100644 src/components/abc/FormCreateComponent/IconPicker.js create mode 100644 src/components/abc/FormCreateComponent/OrganizationMultipleSelect.js create mode 100644 src/components/abc/FormCreateComponent/OrganizationSingleSelect.js create mode 100644 src/components/abc/FormCreateComponent/RichText.js create mode 100644 src/components/abc/FormCreateComponent/UserSingleSelect.js create mode 100644 src/components/abc/IconDisplay/index.vue create mode 100644 src/components/abc/IconPicker/index.vue create mode 100644 src/components/abc/ListPager/index.vue create mode 100644 src/components/abc/Notification/index.vue create mode 100644 src/components/abc/PortletContainer/index.vue create mode 100644 src/components/abc/QueryButton/index.vue create mode 100644 src/components/abc/QueryText/index.vue create mode 100644 src/components/index.ts create mode 100644 src/config/axios/config.ts create mode 100644 src/config/axios/index.ts create mode 100644 src/config/axios/service.ts create mode 100644 src/constant/common.ts create mode 100644 src/constant/index.ts create mode 100644 src/directives/index.ts create mode 100644 src/directives/permission/hasPermi.ts create mode 100644 src/hooks/event/useScrollTo.ts create mode 100644 src/hooks/useGlobal.js create mode 100644 src/hooks/web/useCache.ts create mode 100644 src/hooks/web/useConfigGlobal.ts create mode 100644 src/hooks/web/useCrudSchemas.ts create mode 100644 src/hooks/web/useDesign.ts create mode 100644 src/hooks/web/useEmitt.ts create mode 100644 src/hooks/web/useForm.ts create mode 100644 src/hooks/web/useI18n.ts create mode 100644 src/hooks/web/useIcon.ts create mode 100644 src/hooks/web/useIntro.ts create mode 100644 src/hooks/web/useLocale.ts create mode 100644 src/hooks/web/useNProgress.ts create mode 100644 src/hooks/web/usePageLoading.ts create mode 100644 src/hooks/web/useTable.ts create mode 100644 src/hooks/web/useTimeAgo.ts create mode 100644 src/hooks/web/useTitle.ts create mode 100644 src/hooks/web/useValidator.ts create mode 100644 src/hooks/web/useWatermark.ts create mode 100644 src/layout/Layout.vue create mode 100644 src/layout/components/AppView.vue create mode 100644 src/layout/components/ToolHeader.vue create mode 100644 src/layout/components/useRenderLayout.tsx create mode 100644 src/locales/en.ts create mode 100644 src/locales/zh-CN.ts create mode 100644 src/main.ts create mode 100644 src/mixin/addForAdvanceConfigMixin.js create mode 100644 src/mixin/addMixin.js create mode 100644 src/mixin/flowMixin.js create mode 100644 src/mixin/listMixin.js create mode 100644 src/mixin/modifyForAdvanceConfigMixin.js create mode 100644 src/mixin/modifyMixin.js create mode 100644 src/mixin/referenceMixin.js create mode 100644 src/mixin/treeListReferenceMixin.js create mode 100644 src/mixin/treeMixin.js create mode 100644 src/mixin/treeMultipleDirectReferenceMixin.js create mode 100644 src/mixin/treeMultipleReferenceMixin.js create mode 100644 src/mixin/treeMultipleSelectMixin.js create mode 100644 src/mixin/treeReferenceMixin.js create mode 100644 src/mixin/treeSingleSelectMixin.js create mode 100644 src/mixin/treelistMixin.js create mode 100644 src/mixin/viewForAdvanceConfigMixin.js create mode 100644 src/mixin/viewMixin.js create mode 100644 src/modules/businessflow/api/index.ts create mode 100644 src/modules/businessflow/constant/index.ts create mode 100644 src/modules/businessflow/view/dictionary/list.js create mode 100644 src/modules/businessflow/view/leave/add.vue create mode 100644 src/modules/businessflow/view/leave/detail.vue create mode 100644 src/modules/businessflow/view/leave/list.vue create mode 100644 src/modules/businessflow/view/leave/modify.vue create mode 100644 src/modules/businessflow/view/leave/view.vue create mode 100644 src/modules/businessflow/view/navigate/index.vue create mode 100644 src/modules/cip/api/index.ts create mode 100644 src/modules/cip/constant/index.ts create mode 100644 src/modules/cip/view/activeMessage/list.vue create mode 100644 src/modules/cip/view/activeMessage/view.vue create mode 100644 src/modules/cip/view/apiService/add.vue create mode 100644 src/modules/cip/view/apiService/list.vue create mode 100644 src/modules/cip/view/apiService/modify.vue create mode 100644 src/modules/cip/view/apiService/reference.vue create mode 100644 src/modules/cip/view/apiService/view.vue create mode 100644 src/modules/cip/view/apiServiceLog/list.vue create mode 100644 src/modules/cip/view/apiServiceLog/view.vue create mode 100644 src/modules/cip/view/app/add.vue create mode 100644 src/modules/cip/view/app/apiServicePermission.vue create mode 100644 src/modules/cip/view/app/list.vue create mode 100644 src/modules/cip/view/app/messagePermission.vue create mode 100644 src/modules/cip/view/app/modify.vue create mode 100644 src/modules/cip/view/app/reference.vue create mode 100644 src/modules/cip/view/app/view.vue create mode 100644 src/modules/cip/view/appDataPermission/add.vue create mode 100644 src/modules/cip/view/appDataPermission/list.vue create mode 100644 src/modules/cip/view/appDataPermission/modify.vue create mode 100644 src/modules/cip/view/appDataPermission/view.vue create mode 100644 src/modules/cip/view/messageLog/list.vue create mode 100644 src/modules/cip/view/messageLog/view.vue create mode 100644 src/modules/cip/view/messageSubscription/list.vue create mode 100644 src/modules/cip/view/messageTopic/add.vue create mode 100644 src/modules/cip/view/messageTopic/list.vue create mode 100644 src/modules/cip/view/messageTopic/modify.vue create mode 100644 src/modules/cip/view/messageTopic/reference.vue create mode 100644 src/modules/cip/view/messageTopic/view.vue create mode 100644 src/modules/echart/monthlySales.vue create mode 100644 src/modules/echart/stageSpeedGauge.vue create mode 100644 src/modules/echart/userSource.vue create mode 100644 src/modules/echart/weeklyUserActivity.vue create mode 100644 src/modules/entityconfig/api/index.ts create mode 100644 src/modules/entityconfig/constant/index.ts create mode 100644 src/modules/entityconfig/util/util.js create mode 100644 src/modules/entityconfig/view/entity/add.vue create mode 100644 src/modules/entityconfig/view/entity/config.vue create mode 100644 src/modules/entityconfig/view/entity/list.vue create mode 100644 src/modules/entityconfig/view/entity/modify.vue create mode 100644 src/modules/entityconfig/view/entity/reference.vue create mode 100644 src/modules/entityconfig/view/entity/view.vue create mode 100644 src/modules/entityconfig/view/entityModel/add.vue create mode 100644 src/modules/entityconfig/view/entityModel/list.vue create mode 100644 src/modules/entityconfig/view/entityModel/modify.vue create mode 100644 src/modules/entityconfig/view/entityModel/reference.vue create mode 100644 src/modules/entityconfig/view/entityModel/singleSelect.vue create mode 100644 src/modules/entityconfig/view/entityModel/view.vue create mode 100644 src/modules/entityconfig/view/entityModelProperty/add.vue create mode 100644 src/modules/entityconfig/view/entityModelProperty/list.vue create mode 100644 src/modules/entityconfig/view/entityModelProperty/modify.vue create mode 100644 src/modules/entityconfig/view/entityModelProperty/reference.vue create mode 100644 src/modules/entityconfig/view/entityModelProperty/view.vue create mode 100644 src/modules/entityconfig/view/entityView/add.vue create mode 100644 src/modules/entityconfig/view/entityView/list.vue create mode 100644 src/modules/entityconfig/view/entityView/modify.vue create mode 100644 src/modules/entityconfig/view/entityView/view.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/advanceView/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack2.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/buttonArea/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/detailView/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/add.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/modify.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/detailView/propertyList/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/propertyList/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/queryArea/add.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/queryArea/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/queryArea/modify.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/resultArea/add.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/resultArea/index.vue create mode 100644 src/modules/entityconfig/view/entityViewConfig/listView/resultArea/modify.vue create mode 100644 src/modules/entityconfig/view/template/add.vue create mode 100644 src/modules/entityconfig/view/template/list.vue create mode 100644 src/modules/entityconfig/view/template/modify.vue create mode 100644 src/modules/entityconfig/view/template/reference.vue create mode 100644 src/modules/entityconfig/view/template/view.vue create mode 100644 src/modules/entityconfig/view/viewButton/add.vue create mode 100644 src/modules/entityconfig/view/viewButton/modify.vue create mode 100644 src/modules/entityconfig/view/viewButton/select.vue create mode 100644 src/modules/entityconfig/view/viewButtonTemplate/add.vue create mode 100644 src/modules/entityconfig/view/viewButtonTemplate/list.vue create mode 100644 src/modules/entityconfig/view/viewButtonTemplate/modify.vue create mode 100644 src/modules/entityconfig/view/viewButtonTemplate/view.vue create mode 100644 src/modules/notification/api/index.ts create mode 100644 src/modules/notification/constant/index.ts create mode 100644 src/modules/notification/view/systemMessage/list.vue create mode 100644 src/modules/notification/view/systemMessage/view.vue create mode 100644 src/modules/notification/view/systemMessage/webSocket.js create mode 100644 src/modules/scheduler/api/index.ts create mode 100644 src/modules/scheduler/constant/index.ts create mode 100644 src/modules/scheduler/view/job/add.vue create mode 100644 src/modules/scheduler/view/job/list.vue create mode 100644 src/modules/scheduler/view/job/modify.vue create mode 100644 src/modules/scheduler/view/job/reference.vue create mode 100644 src/modules/scheduler/view/job/view.vue create mode 100644 src/modules/scheduler/view/jobParam/add.vue create mode 100644 src/modules/scheduler/view/jobParam/list.vue create mode 100644 src/modules/scheduler/view/jobParam/modify.vue create mode 100644 src/modules/scheduler/view/jobParam/view.vue create mode 100644 src/modules/scheduler/view/schedulerJob/add.vue create mode 100644 src/modules/scheduler/view/schedulerJob/list.vue create mode 100644 src/modules/scheduler/view/schedulerJob/modify.vue create mode 100644 src/modules/scheduler/view/schedulerJob/reference.vue create mode 100644 src/modules/scheduler/view/schedulerJob/view.vue create mode 100644 src/modules/scheduler/view/schedulerJobParam/add.vue create mode 100644 src/modules/scheduler/view/schedulerJobParam/list.vue create mode 100644 src/modules/scheduler/view/schedulerJobParam/modify.vue create mode 100644 src/modules/scheduler/view/schedulerJobParam/view.vue create mode 100644 src/modules/support/api/index.ts create mode 100644 src/modules/support/constant/index.ts create mode 100644 src/modules/support/view/attachment/attachmentManager.vue create mode 100644 src/modules/support/view/attachment/attachmentManagerAndUploader.vue create mode 100644 src/modules/support/view/attachment/attachmentUploader.vue create mode 100644 src/modules/support/view/attachment/attachmentViewer.vue create mode 100644 src/modules/support/view/contentTemplate/add.vue create mode 100644 src/modules/support/view/contentTemplate/list.vue create mode 100644 src/modules/support/view/contentTemplate/modify.vue create mode 100644 src/modules/support/view/contentTemplate/view.vue create mode 100644 src/modules/support/view/desktopTemplate/add.vue create mode 100644 src/modules/support/view/desktopTemplate/configDesktop.vue create mode 100644 src/modules/support/view/desktopTemplate/desktop.vue create mode 100644 src/modules/support/view/desktopTemplate/list.vue create mode 100644 src/modules/support/view/desktopTemplate/modify.vue create mode 100644 src/modules/support/view/desktopTemplate/reference.vue create mode 100644 src/modules/support/view/desktopTemplate/view.vue create mode 100644 src/modules/support/view/notice/add.vue create mode 100644 src/modules/support/view/notice/list.vue create mode 100644 src/modules/support/view/notice/modify.vue create mode 100644 src/modules/support/view/notice/noticeView.vue create mode 100644 src/modules/support/view/notice/noticeViewList.vue create mode 100644 src/modules/support/view/notice/view.vue create mode 100644 src/modules/support/view/portlet/add.vue create mode 100644 src/modules/support/view/portlet/list.vue create mode 100644 src/modules/support/view/portlet/modify.vue create mode 100644 src/modules/support/view/portlet/reference.vue create mode 100644 src/modules/support/view/portlet/view.vue create mode 100644 src/modules/support/view/portletList/favorite.vue create mode 100644 src/modules/support/view/portletList/notice.vue create mode 100644 src/modules/support/view/portletParam/add.vue create mode 100644 src/modules/support/view/portletParam/list.vue create mode 100644 src/modules/support/view/portletParam/modify.vue create mode 100644 src/modules/support/view/portletParam/view.vue create mode 100644 src/modules/support/view/serialNo/add.vue create mode 100644 src/modules/support/view/serialNo/list.vue create mode 100644 src/modules/support/view/serialNo/modify.vue create mode 100644 src/modules/support/view/serialNo/reference.vue create mode 100644 src/modules/support/view/serialNo/view.vue create mode 100644 src/modules/system/api/index.ts create mode 100644 src/modules/system/constant/index.ts create mode 100644 src/modules/system/view/dictionaryItem/add.vue create mode 100644 src/modules/system/view/dictionaryItem/list.vue create mode 100644 src/modules/system/view/dictionaryItem/modify.vue create mode 100644 src/modules/system/view/dictionaryItem/treeList.vue create mode 100644 src/modules/system/view/dictionaryItem/view.vue create mode 100644 src/modules/system/view/dictionaryType/add.vue create mode 100644 src/modules/system/view/dictionaryType/list.vue create mode 100644 src/modules/system/view/dictionaryType/modify.vue create mode 100644 src/modules/system/view/dictionaryType/tree.vue create mode 100644 src/modules/system/view/dictionaryType/treeList.vue create mode 100644 src/modules/system/view/dictionaryType/treeReference.vue create mode 100644 src/modules/system/view/dictionaryType/treeReferenceUseCode.vue create mode 100644 src/modules/system/view/dictionaryType/view.vue create mode 100644 src/modules/system/view/log/list.vue create mode 100644 src/modules/system/view/log/view.vue create mode 100644 src/modules/system/view/module/add.vue create mode 100644 src/modules/system/view/module/list.vue create mode 100644 src/modules/system/view/module/modify.vue create mode 100644 src/modules/system/view/module/reference.vue create mode 100644 src/modules/system/view/module/view.vue create mode 100644 src/modules/system/view/organization/add.vue create mode 100644 src/modules/system/view/organization/list.vue create mode 100644 src/modules/system/view/organization/modify.vue create mode 100644 src/modules/system/view/organization/modifyBack.vue create mode 100644 src/modules/system/view/organization/tree.vue create mode 100644 src/modules/system/view/organization/treeList.vue create mode 100644 src/modules/system/view/organization/treeMultipleReference.vue create mode 100644 src/modules/system/view/organization/treeMultipleSelect.vue create mode 100644 src/modules/system/view/organization/treeReference.vue create mode 100644 src/modules/system/view/organization/view.vue create mode 100644 src/modules/system/view/param/add.vue create mode 100644 src/modules/system/view/param/list.vue create mode 100644 src/modules/system/view/param/modify.vue create mode 100644 src/modules/system/view/param/view.vue create mode 100644 src/modules/system/view/permissionItem/add.vue create mode 100644 src/modules/system/view/permissionItem/list.vue create mode 100644 src/modules/system/view/permissionItem/modify.vue create mode 100644 src/modules/system/view/permissionItem/tree.vue create mode 100644 src/modules/system/view/permissionItem/treeList.vue create mode 100644 src/modules/system/view/permissionItem/treeMultipleReference.vue create mode 100644 src/modules/system/view/permissionItem/treeReference.vue create mode 100644 src/modules/system/view/permissionItem/view.vue create mode 100644 src/modules/system/view/systemManage/list.vue create mode 100644 src/modules/system/view/user/add.vue create mode 100644 src/modules/system/view/user/list.vue create mode 100644 src/modules/system/view/user/modify.vue create mode 100644 src/modules/system/view/user/reference.vue create mode 100644 src/modules/system/view/user/treeList.vue create mode 100644 src/modules/system/view/user/treeListReference.vue create mode 100644 src/modules/system/view/user/view.vue create mode 100644 src/modules/system/view/userGroup/add.vue create mode 100644 src/modules/system/view/userGroup/list.vue create mode 100644 src/modules/system/view/userGroup/modify.vue create mode 100644 src/modules/system/view/userGroup/tree.vue create mode 100644 src/modules/system/view/userGroup/treeList.vue create mode 100644 src/modules/system/view/userGroup/treeMultipleDirectReference.vue create mode 100644 src/modules/system/view/userGroup/treeReference.vue create mode 100644 src/modules/system/view/userGroup/treeSingleSelect.vue create mode 100644 src/modules/system/view/userGroup/user/index.vue create mode 100644 src/modules/system/view/userGroup/user/list.vue create mode 100644 src/modules/system/view/userGroup/view.vue create mode 100644 src/modules/system/view/userProfile/modify.vue create mode 100644 src/modules/template/api/index.ts create mode 100644 src/modules/template/constant/index.ts create mode 100644 src/modules/workflow/api/index.ts create mode 100644 src/modules/workflow/constant/index.ts create mode 100644 src/modules/workflow/view/apply/list.vue create mode 100644 src/modules/workflow/view/done/list.vue create mode 100644 src/modules/workflow/view/flow/create.vue create mode 100644 src/modules/workflow/view/flow/view.vue create mode 100644 src/modules/workflow/view/portletList/apply.vue create mode 100644 src/modules/workflow/view/portletList/todo.vue create mode 100644 src/modules/workflow/view/task/basicInfo.vue create mode 100644 src/modules/workflow/view/task/commit.vue create mode 100644 src/modules/workflow/view/task/delegate.vue create mode 100644 src/modules/workflow/view/task/handle.vue create mode 100644 src/modules/workflow/view/task/historyStep.vue create mode 100644 src/modules/workflow/view/task/jump.vue create mode 100644 src/modules/workflow/view/task/reject.vue create mode 100644 src/modules/workflow/view/task/transfer.vue create mode 100644 src/modules/workflow/view/todo/list.vue create mode 100644 src/modules/workflow/view/workflowListener/add.vue create mode 100644 src/modules/workflow/view/workflowListener/list.vue create mode 100644 src/modules/workflow/view/workflowListener/modify.vue create mode 100644 src/modules/workflow/view/workflowListener/reference.vue create mode 100644 src/modules/workflow/view/workflowListener/view.vue create mode 100644 src/modules/workflow/view/workflowTemplate/add.vue create mode 100644 src/modules/workflow/view/workflowTemplate/list.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/ViewDiagramButton.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/addNode.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowListenerSelect.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowStepSelect.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/dialog/ImportDialog.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/dialog/ViewDialog.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/dialog/common.js create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/dialog/errorDialog.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/drawer/ConditionNodeConfig.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/drawer/HandleNodeConfig.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/drawer/RootNodeConfig.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/flowDesign.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/flowPreview.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/components/nodeWrap.vue create mode 100644 src/modules/workflow/view/workflowTemplate/model/css/dialog.css create mode 100644 src/modules/workflow/view/workflowTemplate/model/css/workflow.css create mode 100644 src/modules/workflow/view/workflowTemplate/model/stores/index.js create mode 100644 src/modules/workflow/view/workflowTemplate/model/utils/index.js create mode 100644 src/modules/workflow/view/workflowTemplate/modify.vue create mode 100644 src/modules/workflow/view/workflowTemplate/view.vue create mode 100644 src/permission.ts create mode 100644 src/plugins/animate.css/index.ts create mode 100644 src/plugins/echarts/index.ts create mode 100644 src/plugins/elementPlus/index.ts create mode 100644 src/plugins/svgIcon/index.ts create mode 100644 src/plugins/vueI18n/helper.ts create mode 100644 src/plugins/vueI18n/index.ts create mode 100644 src/plugins/windi.css/index.ts create mode 100644 src/router/index.ts create mode 100644 src/store/index.ts create mode 100644 src/store/modules/app.ts create mode 100644 src/store/modules/dict.ts create mode 100644 src/store/modules/locale.ts create mode 100644 src/store/modules/notification.ts create mode 100644 src/store/modules/permission.ts create mode 100644 src/store/modules/tagsView.ts create mode 100644 src/store/modules/user.ts create mode 100644 src/styles/index.less create mode 100644 src/styles/theme.less create mode 100644 src/styles/var.css create mode 100644 src/styles/variables.module.less create mode 100644 src/types/components.d.ts create mode 100644 src/types/configGlobal.d.ts create mode 100644 src/types/contextMenu.d.ts create mode 100644 src/types/descriptions.d.ts create mode 100644 src/types/elementPlus.d.ts create mode 100644 src/types/form.d.ts create mode 100644 src/types/icon.d.ts create mode 100644 src/types/infoTip.d.ts create mode 100644 src/types/layout.d.ts create mode 100644 src/types/localeDropdown.d.ts create mode 100644 src/types/qrcode.d.ts create mode 100644 src/types/table.d.ts create mode 100644 src/types/theme.d.ts create mode 100644 src/utils/TableColumnFormatter.js create mode 100644 src/utils/auth.ts create mode 100644 src/utils/color.ts create mode 100644 src/utils/dateFormatter.js create mode 100644 src/utils/domUtils.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/is.ts create mode 100644 src/utils/propTypes.ts create mode 100644 src/utils/routerHelper.ts create mode 100644 src/utils/stringUtil.js create mode 100644 src/utils/tree.ts create mode 100644 src/utils/tsxHelper.ts create mode 100644 src/views/Authorization/Role.vue create mode 100644 src/views/Authorization/User.vue create mode 100644 src/views/Components/CommonSelect.vue create mode 100644 src/views/Components/CountTo.vue create mode 100644 src/views/Components/Descriptions.vue create mode 100644 src/views/Components/Dialog.vue create mode 100644 src/views/Components/Echart.vue create mode 100644 src/views/Components/Editor/Editor.vue create mode 100644 src/views/Components/Form/DefaultForm.vue create mode 100644 src/views/Components/Form/RefForm.vue create mode 100644 src/views/Components/Form/UseFormDemo.vue create mode 100644 src/views/Components/Highlight.vue create mode 100644 src/views/Components/Icon.vue create mode 100644 src/views/Components/ImageViewer.vue create mode 100644 src/views/Components/Infotip.vue create mode 100644 src/views/Components/InputPassword.vue create mode 100644 src/views/Components/MyEchart.vue create mode 100644 src/views/Components/Qrcode.vue create mode 100644 src/views/Components/Search.vue create mode 100644 src/views/Components/Sticky.vue create mode 100644 src/views/Components/Table/DefaultTable.vue create mode 100644 src/views/Components/Table/RefTable.vue create mode 100644 src/views/Components/Table/UseTableDemo.vue create mode 100644 src/views/Dashboard/Analysis.vue create mode 100644 src/views/Dashboard/Workplace.vue create mode 100644 src/views/Dashboard/components/PanelGroup.vue create mode 100644 src/views/Dashboard/echarts-data.ts create mode 100644 src/views/Error/403.vue create mode 100644 src/views/Error/404.vue create mode 100644 src/views/Error/500.vue create mode 100644 src/views/Example/Dialog/ExampleDialog.vue create mode 100644 src/views/Example/Dialog/components/Detail.vue create mode 100644 src/views/Example/Dialog/components/Write.vue create mode 100644 src/views/Example/Page/ExampleAdd.vue create mode 100644 src/views/Example/Page/ExampleDetail.vue create mode 100644 src/views/Example/Page/ExampleEdit.vue create mode 100644 src/views/Example/Page/ExamplePage.vue create mode 100644 src/views/Example/Page/components/Detail.vue create mode 100644 src/views/Example/Page/components/Write.vue create mode 100644 src/views/Guide/Guide.vue create mode 100644 src/views/Level/Menu111.vue create mode 100644 src/views/Level/Menu12.vue create mode 100644 src/views/Level/Menu2.vue create mode 100644 src/views/Login/Login.vue create mode 100644 src/views/Login/components/LoginForm.vue create mode 100644 src/views/Login/components/RegisterForm.vue create mode 100644 src/views/Login/components/index.ts create mode 100644 src/views/Redirect/Redirect.vue create mode 100644 src/views/hooks/useCrudSchemas.vue create mode 100644 src/views/hooks/useWatermark.vue create mode 100644 stylelint.config.js create mode 100644 tsconfig.json create mode 100644 types/components.d.ts create mode 100644 types/custom-types.d.ts create mode 100644 types/env.d.ts create mode 100644 types/global.d.ts create mode 100644 types/router.d.ts create mode 100644 vite.config.ts create mode 100644 windi.config.ts diff --git a/.env.base b/.env.base new file mode 100644 index 0000000..5de1f0b --- /dev/null +++ b/.env.base @@ -0,0 +1,21 @@ +# 环境 +NODE_ENV=development + +# 请求路径 +VITE_BASE_URL='http://localhost:8080' + +# 通知服务路径 +VITE_NOTIFICATION_SERVER_URL='ws://localhost:9997/webSocket' + +# 接口前缀 +VITE_API_BASEPATH=base + +# 打包路径 +VITE_BASE_PATH=/ + +# 标题 +VITE_APP_TITLE=一二三应用开发平台 + + +# 文档预览地址 +VITE_DOCUMENT_PREVIEW_URL= 'http://127.0.0.1:8012/onlinePreview?url=' diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..f0735a8 --- /dev/null +++ b/.env.dev @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=development + +# 接口前缀 +VITE_API_BASEPATH=dev + +# 打包路径 +VITE_BASE_PATH=/dist-dev/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 输出路径 +VITE_OUT_DIR=dist-dev + +# 标题 +VITE_APP_TITLE=一二三应用开发平台 diff --git a/.env.gitee b/.env.gitee new file mode 100644 index 0000000..9181525 --- /dev/null +++ b/.env.gitee @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=pro + +# 打包路径 +VITE_BASE_PATH=/vue-element-plus-admin/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 输出路径 +VITE_OUT_DIR=dist-pro + +# 标题 +VITE_APP_TITLE=一二三应用开发平台 diff --git a/.env.pro b/.env.pro new file mode 100644 index 0000000..8c3564f --- /dev/null +++ b/.env.pro @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=pro + +# 打包路径 +VITE_BASE_PATH=/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 输出路径 +VITE_OUT_DIR=dist-pro + +# 标题 +VITE_APP_TITLE=一二三应用开发平台 diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..ea2e44d --- /dev/null +++ b/.env.test @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=test + +# 打包路径 +VITE_BASE_PATH=/dist-test/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 输出路径 +VITE_OUT_DIR=dist-test + +# 标题 +VITE_APP_TITLE=一二三应用开发平台 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..1e85c0f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +/build/ +/config/ +/dist/ +/*.js +/test/unit/coverage/ +/node_modules/* +/dist* +/src/main.ts diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..98bba99 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,70 @@ +// @ts-check +const { defineConfig } = require('eslint-define-config') +module.exports = defineConfig({ + root: true, + env: { + browser: true, + node: true, + es6: true + }, + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + jsxPragma: 'React', + ecmaFeatures: { + jsx: true + } + }, + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:prettier/recommended' + ], + rules: { + 'vue/script-setup-uses-vars': 'error', + 'vue/no-reserved-component-names': 'off', + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'vue/custom-event-name-casing': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-unused-vars': 'off', + "vue/no-unused-components": "off", + 'space-before-function-paren': 'off', + + 'vue/attributes-order': 'off', + 'vue/one-component-per-file': 'off', + 'vue/html-closing-bracket-newline': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/attribute-hyphenation': 'off', + 'vue/require-default-prop': 'off', + 'vue/require-explicit-emits': 'off', + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'never', + component: 'always' + }, + svg: 'always', + math: 'always' + } + ], + 'vue/multi-word-component-names': 'off', + 'vue/no-v-html': 'off' + } +}) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b05e6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +/dist* +*-lock.* +pnpm-debug diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..15cf071 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +/node_modules/** +/dist/ +/dist* +/public/* +/docs/* +/vite.config.ts +/src/types/env.d.ts +/docs/**/* +/plop/**/* +CHANGELOG diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..aa605b4 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,6 @@ +/dist/* +/public/* +public/* +/dist* +/src/types/env.d.ts +/docs/**/* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b4696bf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,802 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [1.9.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.9.3...v1.9.4) (2023-03-03) + + +### Bug Fixes + +* 修复已知BUG ([782b8e2](https://github.com/kailong321200875/vue-element-plus-admin/commit/782b8e2f94c867c3ee282287c37c888fff93fc55)) + +## [1.9.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.9.2...v1.9.3) (2023-03-01) + + +### Types + +* 修复类型错误 ([297b2c6](https://github.com/kailong321200875/vue-element-plus-admin/commit/297b2c69a239b487126c3b9316645a1b5f06bb7c)) + + +### Styling + +* 抽屉弹出样式问题 ([16a3eef](https://github.com/kailong321200875/vue-element-plus-admin/commit/16a3eef85a1ffb296bd44f67d89a911ecaf1c25e)) +* 调整主题切换样式 ([6e6beef](https://github.com/kailong321200875/vue-element-plus-admin/commit/6e6beefc3c380f7297985adcabcf966fbd2c5197)) + +## [1.9.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.9.1...v1.9.2) (2023-01-16) + + +### Bug Fixes + +* 修复TS类型错误 ([1c06a27](https://github.com/kailong321200875/vue-element-plus-admin/commit/1c06a27b900a891cd0b47098062cebc984ff6505)) + +## [1.9.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.9.0...v1.9.1) (2023-01-11) + + +### Bug Fixes + +* dark mode toggle ([bbc7646](https://github.com/kailong321200875/vue-element-plus-admin/commit/bbc764601ec864c2fdbe3ad78c083c5ae80615e0)) + +## [1.9.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.7...v1.9.0) (2022-12-28) + + +### Features + +* 添加打包进度条 ([354e87f](https://github.com/kailong321200875/vue-element-plus-admin/commit/354e87f7c533ad8e93ef484b47d0fe16f17048c9)) + + +### Bug Fixes + +* husky ([6fe5b2e](https://github.com/kailong321200875/vue-element-plus-admin/commit/6fe5b2e6c781b251bff5f0ac936c04dcfe5ef95f)) + +## [1.8.7](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.6...v1.8.7) (2022-12-05) + + +### Bug Fixes + +* 解决iframe无法正常工作 ([4fcc46f](https://github.com/kailong321200875/vue-element-plus-admin/commit/4fcc46fccf747b47909e2079c4f2abc5dbfb1a0c)) + +## [1.8.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.5...v1.8.6) (2022-11-21) + + +### Bug Fixes + +* 修复Search组件无法默认值 ([3368fda](https://github.com/kailong321200875/vue-element-plus-admin/commit/3368fda251bd3ff5a8e0059b3b33f9c0339d236b)) + +## [1.8.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.4...v1.8.5) (2022-11-17) + + +### Bug Fixes + +* 修复Form赋值问题 ([f37cc1b](https://github.com/kailong321200875/vue-element-plus-admin/commit/f37cc1b5801add3ada168dbbcf4cd2c340f0e30d)) + +## [1.8.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.3...v1.8.4) (2022-11-07) + + +### Bug Fixes + +* 修复option禁用属性无效 ([0b671e9](https://github.com/kailong321200875/vue-element-plus-admin/commit/0b671e914e396c7666ad5e34768a6e29f7dfbd33)) + + +### Styling + +* input默认宽度与select对齐 ([0b671e9](https://github.com/kailong321200875/vue-element-plus-admin/commit/0b671e914e396c7666ad5e34768a6e29f7dfbd33)) + +## [1.8.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.2...v1.8.3) (2022-10-28) + + +### Performance Improvements + +* 优化描述组件 ([73ecc98](https://github.com/kailong321200875/vue-element-plus-admin/commit/73ecc98671d430013920246d98ce9ab1752e56eb)) + +## [1.8.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.1...v1.8.2) (2022-10-18) + + +### Bug Fixes + +* Correct spelling of words(aciton →action) ([eb405b2](https://github.com/kailong321200875/vue-element-plus-admin/commit/eb405b2a9041ca0ad4455db79bf617ec910dc485)) +* Correct spelling of words(tigger →trigger) ([c2ca2d7](https://github.com/kailong321200875/vue-element-plus-admin/commit/c2ca2d736c92e02380923a6741450844acb41a38)) + +## [1.8.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.0...v1.8.1) (2022-10-11) + + +### Bug Fixes + +* 修复cutMenu收起时 ([993af6b](https://github.com/kailong321200875/vue-element-plus-admin/commit/993af6bb6576249e66e0c0ea592ebf851f65ab8c)) + + +### Styling + +* cutMenu层级样式 ([32d2408](https://github.com/kailong321200875/vue-element-plus-admin/commit/32d2408588c487cff2cf73e3cc132e5105ff4459)) + +## [1.8.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.7.1...v1.8.0) (2022-10-10) + + +### Features + +* types优化 ([3351155](https://github.com/kailong321200875/vue-element-plus-admin/commit/33511553cd9055b036b2d7491f9c2eda123f8b22)) + + +### Styling + +* 优化第四种布局 ([122fa62](https://github.com/kailong321200875/vue-element-plus-admin/commit/122fa62d859413d16175e0d97c7bf13f232dbb3a)) + +## [1.7.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.7.0...v1.7.1) (2022-10-10) + + +### Bug Fixes + +* 修正types提示错误 ([ef3e006](https://github.com/kailong321200875/vue-element-plus-admin/commit/ef3e006859dcd8b93ffb7cffcaeae24cbb330f2a)) + +## [1.7.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.6...v1.7.0) (2022-10-09) + + +### Features + +* type抽离 ([8b4fa1a](https://github.com/kailong321200875/vue-element-plus-admin/commit/8b4fa1aa21aa2c1379288315ccd64a6f3375be51)) + +## [1.6.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.5...v1.6.6) (2022-10-09) + + +### Bug Fixes + +* table search params ([a62929a](https://github.com/kailong321200875/vue-element-plus-admin/commit/a62929a8dac21028d3dd1cddf98189492c33b093)) + +## [1.6.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.4...v1.6.5) (2022-10-08) + + +### Bug Fixes + +* The attribute of option does not work ([d946920](https://github.com/kailong321200875/vue-element-plus-admin/commit/d946920e61ed81beacf9f1f8be7ee1f50505f64d)) + + +### Performance Improvements + +* perf store ([d416178](https://github.com/kailong321200875/vue-element-plus-admin/commit/d416178d69ca6100be4b635922b1a22d27629f08)) +* token test ([b320e65](https://github.com/kailong321200875/vue-element-plus-admin/commit/b320e658d1a559a6eaebdf374d63649c223c2ecd)) + +## [1.6.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.3...v1.6.4) (2022-09-21) + + +### Bug Fixes + +* fix bug ([da39f3b](https://github.com/kailong321200875/vue-element-plus-admin/commit/da39f3bc904ca2d80f432a31709725f9a57deb19)) + +## [1.6.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.2...v1.6.3) (2022-08-20) + + +### Bug Fixes + +* 修复重定向错误 ([89d03fd](https://github.com/kailong321200875/vue-element-plus-admin/commit/89d03fd067e7aca565ceb84ea9276f340bbfcb60)) + + +### Styling + +* 调整样式 ([d29e151](https://github.com/kailong321200875/vue-element-plus-admin/commit/d29e151f8a660031a685f6ef9f789532b1b7b58b)) + +## [1.6.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.1...v1.6.2) (2022-08-13) + + +### Styling + +* Misspelling ([c43e833](https://github.com/kailong321200875/vue-element-plus-admin/commit/c43e833582e4f14ac78b0683f1eb3bdeb9fb4821)) +* perfect tableDemo ([c589edd](https://github.com/kailong321200875/vue-element-plus-admin/commit/c589edd960b23ad0c8b56d2c7880b61014114d45)) + + +### Code Refactoring + +* refactor axios ([0980640](https://github.com/kailong321200875/vue-element-plus-admin/commit/0980640f65fc80e3e58ba49e98db10b7b1b80077)) + +## [1.6.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.0...v1.6.1) (2022-07-30) + + +### Bug Fixes + +* fix menu active bug ([ff59fc7](https://github.com/kailong321200875/vue-element-plus-admin/commit/ff59fc7e133202945360a7e210f9cdf3a4a89dd7)) +* Pie chart data not updated ([55d4ce7](https://github.com/kailong321200875/vue-element-plus-admin/commit/55d4ce7e35ff9a0c5590bc3589160cfd304d3ae5)) + + +### Performance Improvements + +* add static router ([55522b0](https://github.com/kailong321200875/vue-element-plus-admin/commit/55522b0661a8df3ad3c8afafcc9f8fcb162c5a00)) + + +### Styling + +* tagviews styles update ([bff7d93](https://github.com/kailong321200875/vue-element-plus-admin/commit/bff7d9370db7a9c171828721bb99643dac2f235d)) + +## [1.6.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.4...v1.6.0) (2022-07-18) + + +### Features + +* 添加按钮权限 ([7bef662](https://github.com/kailong321200875/vue-element-plus-admin/commit/7bef662db1e91aa8164e9f7a92de3fe4480a3c3b)) + + +### Performance Improvements + +* 移除md5依赖 ([a123097](https://github.com/kailong321200875/vue-element-plus-admin/commit/a123097f1f38eac45e945c7d3fdccafc16ea9b69)) + + +### Styling + +* 压缩图片尺寸 ([ae3c565](https://github.com/kailong321200875/vue-element-plus-admin/commit/ae3c5657b604ffbdae5ce3ce3603626ad4acc5e5)) + + +### Docs + +* vite2 to vite3 ([b3918b9](https://github.com/kailong321200875/vue-element-plus-admin/commit/b3918b9c3c5de4b48811ec95967851cfb3c231e1)) +* vite2 to vite3 ([aaf07de](https://github.com/kailong321200875/vue-element-plus-admin/commit/aaf07de77aa600332880a894faa35757f787c012)) + +## [1.6.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.4...v1.6.0) (2022-07-18) + + +### Features + +* 添加按钮权限 ([7bef662](https://github.com/kailong321200875/vue-element-plus-admin/commit/7bef662db1e91aa8164e9f7a92de3fe4480a3c3b)) + + +### Performance Improvements + +* 移除md5依赖 ([a123097](https://github.com/kailong321200875/vue-element-plus-admin/commit/a123097f1f38eac45e945c7d3fdccafc16ea9b69)) + + +### Styling + +* 压缩图片尺寸 ([ae3c565](https://github.com/kailong321200875/vue-element-plus-admin/commit/ae3c5657b604ffbdae5ce3ce3603626ad4acc5e5)) + + +### Docs + +* vite2 to vite3 ([b3918b9](https://github.com/kailong321200875/vue-element-plus-admin/commit/b3918b9c3c5de4b48811ec95967851cfb3c231e1)) +* vite2 to vite3 ([aaf07de](https://github.com/kailong321200875/vue-element-plus-admin/commit/aaf07de77aa600332880a894faa35757f787c012)) + +## [1.5.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.3...v1.5.4) (2022-07-16) + + +### Bug Fixes + +* fix build:test error ([14530cf](https://github.com/kailong321200875/vue-element-plus-admin/commit/14530cf790bfbbe37c72fa831f0376bbb4209e9d)) + +## [1.5.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.2...v1.5.3) (2022-07-01) + + +### Bug Fixes + +* fix useCrudSchemas not work ([0a855b9](https://github.com/kailong321200875/vue-element-plus-admin/commit/0a855b93e282dfe7863b3fce31dde5d7e0d3e2b6)) + +## [1.5.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.1...v1.5.2) (2022-07-01) + + +### Performance Improvements + +* add useCrudSchemas demo ([ca3ce54](https://github.com/kailong321200875/vue-element-plus-admin/commit/ca3ce54630b723d87415b14c642440d6734876ff)) + +## [1.5.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.0...v1.5.1) (2022-07-01) + + +### Bug Fixes + +* change showMainRoute to canTo ([5e292f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/5e292f8a2b2ded7297a2a76893e113ac81517d23)) + + +### Performance Improvements + +* dialog combine with form ([34aefb6](https://github.com/kailong321200875/vue-element-plus-admin/commit/34aefb64ab9237521a1225925264818eebff9ad3)) +* dynamic options demo ([1acb4d7](https://github.com/kailong321200875/vue-element-plus-admin/commit/1acb4d7e8f449ba342699f1b4387ac2404a4c1fb)) +* dynamic options demo ([9a3b617](https://github.com/kailong321200875/vue-element-plus-admin/commit/9a3b6177aa0fbc99c86c5073a1c6c696e1eaf890)) +* useCrudSchemas cutom label ([7864d83](https://github.com/kailong321200875/vue-element-plus-admin/commit/7864d830e2134d814609e722b7bad1754ea9460e)) + +## [1.5.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.5...v1.5.0) (2022-06-25) + + +### Features + +* refactoring API ([37b7583](https://github.com/kailong321200875/vue-element-plus-admin/commit/37b75839a591648b145065432efb1dc8c7a3b917)) + + +### Bug Fixes + +* 修复axios已知问题 ([537af57](https://github.com/kailong321200875/vue-element-plus-admin/commit/537af57a0aaa24c88ebe75acf52dc0403a58b04b)) + + +### Performance Improvements + +* perf axios config ([39edd84](https://github.com/kailong321200875/vue-element-plus-admin/commit/39edd84023109a84683c21ea33e41bd024756520)) + +## [1.4.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.4...v1.4.5) (2022-06-09) + + +### Bug Fixes + +* fix tagsview not work ([d88e051](https://github.com/kailong321200875/vue-element-plus-admin/commit/d88e0514349e877f1c5280a48f9b1bd2bfd622bf)) +* fix tagsview not work ([1bf2d4c](https://github.com/kailong321200875/vue-element-plus-admin/commit/1bf2d4c77287fdca7ed1cb8c9998a53f1375dc6f)) + + +### Types + +* fix types error ([586486a](https://github.com/kailong321200875/vue-element-plus-admin/commit/586486a68d4bf2a024e50a79945b4007324f642d)) + +## [1.4.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.3...v1.4.4) (2022-06-06) + + +### Types + +* fix type error ([d66f12e](https://github.com/kailong321200875/vue-element-plus-admin/commit/d66f12e0e77f6acf485bae06509d9ea4abcd1eaa)) + +### [1.4.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.2...v1.4.3) (2022-06-01) + + +### Bug Fixes + +* multiple requests when pageSize change and currentPage isn't 1 ([f71a250](https://github.com/kailong321200875/vue-element-plus-admin/commit/f71a2503bc521c01e7102feecf4ec39a5224a6bb)) + +### [1.4.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.1...v1.4.2) (2022-05-15) + + +### Styling + +* fix dark mode bug ([2f9fd5d](https://github.com/kailong321200875/vue-element-plus-admin/commit/2f9fd5d21550d771ec12ae3540b975e2eebcd25b)) + +### [1.4.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.0...v1.4.1) (2022-05-12) + + +### Bug Fixes + +* the warning of VSCode extensions ([a368c21](https://github.com/kailong321200875/vue-element-plus-admin/commit/a368c21fb9c41f98f31f51586a2023076a8a9132)) + +## [1.4.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.3.2...v1.4.0) (2022-05-10) + + +### Features + +* add dark mode ([0758a6a](https://github.com/kailong321200875/vue-element-plus-admin/commit/0758a6a9d83170e53d45d39c3313e52ff5885746)) + +### [1.3.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.3.1...v1.3.2) (2022-05-07) + + +### Bug Fixes + +* fix the problem that the page is stuck in top mode ([8d01f48](https://github.com/kailong321200875/vue-element-plus-admin/commit/8d01f48d5098195b10c03b3cb3a0f485ebc9e018)) + +### [1.3.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.3.0...v1.3.1) (2022-05-06) + + +### Bug Fixes + +* spelling 'useRenderChcekbox' ([ee92f03](https://github.com/kailong321200875/vue-element-plus-admin/commit/ee92f039bea4307ccfb819728d3e2ed04fa00e03)) + +## [1.3.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.13...v1.3.0) (2022-04-26) + + +### Features + +* add useCrudSchemas hook ([00d947e](https://github.com/kailong321200875/vue-element-plus-admin/commit/00d947e2f81105194b0622d33768f999e37ad28a)) + + +### Bug Fixes + +* fix Table slot warning ([0eac05d](https://github.com/kailong321200875/vue-element-plus-admin/commit/0eac05d4f973ff7b15e00973f6e96595a3cd6d43)) + + +### Code Refactoring + +* refactor useAxios ([185f1e6](https://github.com/kailong321200875/vue-element-plus-admin/commit/185f1e6e210ecaac28ebfdee4198b7ca2eaa0933)) + + +### Build System + +* add url ([ab0f59a](https://github.com/kailong321200875/vue-element-plus-admin/commit/ab0f59ac91a077cf060923fa76e6d57e05d0b21b)) +* update plugins ([c475a61](https://github.com/kailong321200875/vue-element-plus-admin/commit/c475a610c19094034306f2dc665e240c7c117f87)) +* update plugins ([dfedbc7](https://github.com/kailong321200875/vue-element-plus-admin/commit/dfedbc74fdb2c819a96b6263849bdaab59b9e337)) + +### [1.2.13](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.12...v1.2.13) (2022-04-18) + + +### Performance Improvements + +* Editor component support v-model ([d77f8e3](https://github.com/kailong321200875/vue-element-plus-admin/commit/d77f8e334d77ee43c9ee0f411733f7397b278bc0)) + +### [1.2.12](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.11...v1.2.12) (2022-04-17) + + +### Bug Fixes + +* fixed spelling 'ElememtPlusSzie' ([5dbbc60](https://github.com/kailong321200875/vue-element-plus-admin/commit/5dbbc608640d93fe68fec6f58fdb30a43e02aada)) + +### [1.2.11](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.10...v1.2.11) (2022-04-14) + + +### Bug Fixes + +* fix the error reported by the Editor component ([7dc6d8a](https://github.com/kailong321200875/vue-element-plus-admin/commit/7dc6d8a9d7289bfaf27f972e9ca1773c0a1ddd7d)) +* fix the error reported by the Editor component ([90ef985](https://github.com/kailong321200875/vue-element-plus-admin/commit/90ef9856a0885fa812339cb7047ecc98b86c7b73)) + + +### Performance Improvements + +* add tagsViewIcon setting ([d395f03](https://github.com/kailong321200875/vue-element-plus-admin/commit/d395f03a57a9265f1d39b3220fc7c9b983efee30)) +* add uniqueopened setting ([b060319](https://github.com/kailong321200875/vue-element-plus-admin/commit/b0603199a5ae0ee923483dad449f49220d36f444)) + + +### Build System + +* update plugins ([2ee4954](https://github.com/kailong321200875/vue-element-plus-admin/commit/2ee49549e7b601af26ef5204f7648d271f3348f2)) + + +### Styling + +* add layout background color ([9b614fe](https://github.com/kailong321200875/vue-element-plus-admin/commit/9b614fe89288538197c50f164586aeed7836b7a8)) + +### [1.2.10](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.9...v1.2.10) (2022-04-12) + + +### Bug Fixes + +* fix bug ([327522f](https://github.com/kailong321200875/vue-element-plus-admin/commit/327522f2b73ae0e11f8ebbc39394b06029ce0b65)) + + +### Styling + +* modify the commitlint package manager ([ba7e722](https://github.com/kailong321200875/vue-element-plus-admin/commit/ba7e7224ab58612548519415f5429c32827a61de)) + +### [1.2.9](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.8...v1.2.9) (2022-04-12) + + +### Docs + +* update changlog ([e37273d](https://github.com/kailong321200875/vue-element-plus-admin/commit/e37273d95d29a3bb752604658d550264aacdc979)) + +### [1.2.8](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.7...v1.2.8) (2022-04-11) + +### Build System + +- update plugins ([00a573a](https://github.com/kailong321200875/vue-element-plus-admin/commit/00a573af3f455395b4ee2ab99a03f3103d466e9c)) + +### Docs + +- update changlog ([62fc183](https://github.com/kailong321200875/vue-element-plus-admin/commit/62fc1839fdff3a4d06a7db4cf3f8ce2cb9aee681)) + +### [1.2.7](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.6...v1.2.7) (2022-04-10) + +### Build System + +- update plugins ([f13a91d](https://github.com/kailong321200875/vue-element-plus-admin/commit/f13a91dd460b1dcdbd17aef723ab3ca2b01c34f0)) + +### Styling + +- .bhs code formatting ([57b2707](https://github.com/kailong321200875/vue-element-plus-admin/commit/57b27071e9a33423c46542a5d0e5d5c2e9a3b718)) + +### [1.2.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.5...v1.2.6) (2022-04-08) + +### Build System + +- update plugins ([d645892](https://github.com/kailong321200875/vue-element-plus-admin/commit/d645892cde2f7f43215a2ba1776ee94a322437bf)) + +### [1.2.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.4...v1.2.5) (2022-04-08) + +### Performance Improvements + +- add plop ([fa54a17](https://github.com/kailong321200875/vue-element-plus-admin/commit/fa54a1704ffd93f7b42dbeb1229bc4868d2d3a6a)) + +### Build System + +- update plugins ([18c6bd8](https://github.com/kailong321200875/vue-element-plus-admin/commit/18c6bd868622d954b51ea34e37516361ad4eb540)) + +### Styling + +- fix padding and background color ([f8c9d54](https://github.com/kailong321200875/vue-element-plus-admin/commit/f8c9d54687edafd92f5b61bf5288bb1188c73f01)) + +### [1.2.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.3...v1.2.4) (2022-04-06) + +### Bug Fixes + +- add Sticky props comment ([46133b3](https://github.com/kailong321200875/vue-element-plus-admin/commit/46133b3ff39d48d11cbcaa1f20a271118f48eb29)) +- fix bug ([179ca06](https://github.com/kailong321200875/vue-element-plus-admin/commit/179ca064ba8adbb3b063d9798ec1930ccc68e459)) +- fix remove unnecessary variables ([ca01cbf](https://github.com/kailong321200875/vue-element-plus-admin/commit/ca01cbfd98b63a0d76190fe8d43097fdc9df74e6)) +- fix style ([17c8fea](https://github.com/kailong321200875/vue-element-plus-admin/commit/17c8fea93811d9d9b708808484f5c907d761fcf1)) +- remove ContentDetailWrap style ([4ceaa9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/4ceaa9d7816369d0dcaf3e18e4cdbbd6165cef88)) + +### [1.2.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.2...v1.2.3) (2022-03-31) + +### Bug Fixes + +- fix refresh with query ([e94020f](https://github.com/kailong321200875/vue-element-plus-admin/commit/e94020ff541a061599486c0003258f1dbf13aba8)) + +### [1.2.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.1...v1.2.2) (2022-03-30) + +### Bug Fixes + +- fix avatar height bug ([cd4ab76](https://github.com/kailong321200875/vue-element-plus-admin/commit/cd4ab767018941777174d7837045f5259d1cc403)) +- fix parmas to params ([2c7211c](https://github.com/kailong321200875/vue-element-plus-admin/commit/2c7211c89d7299ffc0a36bef8999b3c201dbaf4a)) + +### [1.2.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.0...v1.2.1) (2022-03-29) + +### Bug Fixes + +- fix invalid paging ([ad184ee](https://github.com/kailong321200875/vue-element-plus-admin/commit/ad184ee9c0619da36f1ca3f26e67f18f88488523)) + +### Build System + +- update plugins ([0c7276f](https://github.com/kailong321200875/vue-element-plus-admin/commit/0c7276feadaedef83e6a4ad9d457e26d408698a8)) + +## [1.2.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.14...v1.2.0) (2022-03-27) + +### Features + +- add hooks demo ([c43f39e](https://github.com/kailong321200875/vue-element-plus-admin/commit/c43f39efef296266c64cc24690717d07fa0bcb85)) +- add inputPassword demo ([8f8b126](https://github.com/kailong321200875/vue-element-plus-admin/commit/8f8b1260e75df6998ebea617f62d4ab6be81d721)) + +### Docs + +- update LICENSE ([69d3dcc](https://github.com/kailong321200875/vue-element-plus-admin/commit/69d3dcc7edf69e9b4e3042ddb11faa85ec7d39e2)) + +### Styling + +- modify the function name to make it more semantic ([046ae51](https://github.com/kailong321200875/vue-element-plus-admin/commit/046ae512f02df2d3f08134949b9376a061c1eef3)) +- update Footer component presentation ([d4a9ba3](https://github.com/kailong321200875/vue-element-plus-admin/commit/d4a9ba3aa6758b8aac18b30e1a6b9501baff826c)) +- update Icon demo ([8597122](https://github.com/kailong321200875/vue-element-plus-admin/commit/85971227cd3055ea280cf493c7c42b250c1515da)) + +### Tests + +- test push first commit ([a67bb48](https://github.com/kailong321200875/vue-element-plus-admin/commit/a67bb48f269651a2dcd01b9e33d10f20c42d76ee)) + +### Build System + +- update plugins ([9c13d92](https://github.com/kailong321200875/vue-element-plus-admin/commit/9c13d92b36a2a7c95b9edb7821367fc8f0ac6658)) +- update server port ([d2be8c1](https://github.com/kailong321200875/vue-element-plus-admin/commit/d2be8c1a307a3c5daf363bd7f1d21e574598de5c)) + +### [1.1.14](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.13...v1.1.14) (2022-03-22) + +### Bug Fixes + +- fix the bug that the form search function is invalid of the example-dialog page ([9ec30e7](https://github.com/kailong321200875/vue-element-plus-admin/commit/9ec30e719f89865497dbb1321be1df906f59f14e)) + +### [1.1.13](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.12...v1.1.13) (2022-03-17) + +### Build System + +- update plugins ([a2d0313](https://github.com/kailong321200875/vue-element-plus-admin/commit/a2d03137899f9b16fc1d4a09a23576cd74e7950e)) + +### [1.1.12](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.11...v1.1.12) (2022-03-15) + +### Build System + +- update plugins ([fee2252](https://github.com/kailong321200875/vue-element-plus-admin/commit/fee2252930b05b709d0c012e809568c4ed32bd89)) + +### [1.1.11](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.10...v1.1.11) (2022-03-15) + +### Bug Fixes + +- fix the problem of blank bar when toggle the TagsView component ([b1d9771](https://github.com/kailong321200875/vue-element-plus-admin/commit/b1d9771c750709fe45061d13299a85dbbd6ead25)) +- fix the problem that no reaction when copy setting config in http page ([61e0e33](https://github.com/kailong321200875/vue-element-plus-admin/commit/61e0e33c64d6a889fe6ed80d27a10cf8b201d21a)) + +### [1.1.10](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.9...v1.1.10) (2022-03-13) + +### Build System + +- update plugins ([0b525c8](https://github.com/kailong321200875/vue-element-plus-admin/commit/0b525c875075a28288e92243b205b337f85ab550)) + +### [1.1.9](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.8...v1.1.9) (2022-03-07) + +### Build System + +- update plugins ([1456fd4](https://github.com/kailong321200875/vue-element-plus-admin/commit/1456fd49ec9abbfe1f25aeadfe5fed54fec07394)) + +### [1.1.8](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.7...v1.1.8) (2022-03-07) + +### Docs + +- update changelog ([bf09441](https://github.com/kailong321200875/vue-element-plus-admin/commit/bf09441852e59b0d07d4949a33de75958696817f)) + +### [1.1.7](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.6...v1.1.7) (2022-03-06) + +### Styling + +- add labelMessage attribute to Form component ([8c42790](https://github.com/kailong321200875/vue-element-plus-admin/commit/8c427907843ccb2dfd882d27c1e8a894c5616487)) + +### [1.1.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.5...v1.1.6) (2022-03-04) + +### Bug Fixes + +- fix the problem that the tree data of Table component cannot be displayed ([bf83d3e](https://github.com/kailong321200875/vue-element-plus-admin/commit/bf83d3efbad9097f245c32cc07d1178580cec4e3)) + +### [1.1.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.4...v1.1.5) (2022-03-02) + +### Bug Fixes + +- fix the problem of tagsview error when loginout ([835d76a](https://github.com/kailong321200875/vue-element-plus-admin/commit/835d76ae87b950106f957976ebc8f6f2e8842ddf)) + +### Build System + +- update plugins ([de34bb1](https://github.com/kailong321200875/vue-element-plus-admin/commit/de34bb193d6c844dbc1cec38db5f61b3f95e19f2)) + +### Styling + +- fix tabMenu z-index bug ([8b3be02](https://github.com/kailong321200875/vue-element-plus-admin/commit/8b3be02368a1bddb7dc78f18adbea7f4ebfe75d6)) +- fix tags-view style bug ([ebff817](https://github.com/kailong321200875/vue-element-plus-admin/commit/ebff81777b9c0b839256b83e321ecbdbff25fc73)) + +### [1.1.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.3...v1.1.4) (2022-03-01) + +### CI + +- update workflow ([0490d18](https://github.com/kailong321200875/vue-element-plus-admin/commit/0490d18145cb0d9c4b066ab01a2c10cb527e38ba)) +- update workflow ([51f7bca](https://github.com/kailong321200875/vue-element-plus-admin/commit/51f7bca6034902b251d081ee383b0d796782d434)) + +### [1.1.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.2...v1.1.3) (2022-03-01) + +### CI + +- update workflow ([91cc5c5](https://github.com/kailong321200875/vue-element-plus-admin/commit/91cc5c595cadc5695d8f54bdc4922d8f04439f24)) + +### [1.1.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.1...v1.1.2) (2022-03-01) + +### Workflows + +- update workflow ([d9708aa](https://github.com/kailong321200875/vue-element-plus-admin/commit/d9708aae5bc0cb795bb0fbf8d17df753cc88ba1d)) + +### [1.1.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.0...v1.1.1) (2022-03-01) + +### Workflows + +- update workflow ([085328a](https://github.com/kailong321200875/vue-element-plus-admin/commit/085328aba8c4f356bf7915a6bbdc1ec4f46ceeda)) + +## [1.1.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.0.3...v1.1.0) (2022-03-01) + +### Features + +- 🎸 layout 三种布局重构完成 ([429e428](https://github.com/kailong321200875/vue-element-plus-admin/commit/429e42809cef33a33662e41ad50297217d128b8c)) +- 🎸 layout 布局重构 �[bd24b92](https://github.com/kailong321200875/vue-element-plus-admin/commit/bd24b92acb279343dbaf83b74f1ed2a3f57f1003)) +- 🎸 Table 组件重构完成并给出相应示 �[35879f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/35879f8ecc0ffa76122a336e2eaa93ecfb408c1d)) +- 🎸 v0.0.4 发布 ([a58dc1b](https://github.com/kailong321200875/vue-element-plus-admin/commit/a58dc1b1c2774974782ef6d116b8805975b82b1c)) +- 🎸 初始化项 �[26d4c7c](https://github.com/kailong321200875/vue-element-plus-admin/commit/26d4c7c56894cf2031b3a7cce08d53c37f4a49e3)) +- 🎸 初版完成 ([5bfe4d2](https://github.com/kailong321200875/vue-element-plus-admin/commit/5bfe4d236fd9c2841da100f34c980b4572b67b20)) +- 🎸 新增 Detail 详情组件并给出相应示 �[e77a931](https://github.com/kailong321200875/vue-element-plus-admin/commit/e77a931ef2d2967a9717e27b187d68512c01284f)) +- 🎸 新增二维码组 �[85555ee](https://github.com/kailong321200875/vue-element-plus-admin/commit/85555eef7dc7d72cb701bdd81044ba8fb8e72acc)) +- 🎸 新增全局配置 ([f8405a6](https://github.com/kailong321200875/vue-element-plus-admin/commit/f8405a63c9b1288fbe95bae235b65a08e8fae8d2)) +- 🎸 新增固定 � 级菜单配 �[4c4903e](https://github.com/kailong321200875/vue-element-plus-admin/commit/4c4903e806c8818e320108cc3e5279d728061c29)) +- 🎸 新增权限管理及相关示例文 �[32b6583](https://github.com/kailong321200875/vue-element-plus-admin/commit/32b6583099646b2ee622ac7b35388468769b91b8)) +- 🎸 显示更多组建 � 发中 ([fa9f24d](https://github.com/kailong321200875/vue-element-plus-admin/commit/fa9f24d5da8d2e40d7c3661eabacb8f0474a7bf2)) +- 🎸 权限管理 � 发中 ([38f5211](https://github.com/kailong321200875/vue-element-plus-admin/commit/38f521174ba9eba750fee4516141d7a267f1c4ce)) +- 🎸 权限管理 � 发中 ([6d7ea66](https://github.com/kailong321200875/vue-element-plus-admin/commit/6d7ea6694d8299332018a6689bcd82502a9a552c)) +- 🎸 综合实例重构 �[5142e6e](https://github.com/kailong321200875/vue-element-plus-admin/commit/5142e6e323cb20c89a97398bf41d32c93ce42cad)) +- 🎸 重构 layout ([7ede021](https://github.com/kailong321200875/vue-element-plus-admin/commit/7ede02141e258ab4c88e9b4daad966513d4dbe68)) +- 🎸 重构 layout-classic 布局 ([29d9c98](https://github.com/kailong321200875/vue-element-plus-admin/commit/29d9c988605b822195900268da6bc3f3b0b9c770)) +- 🎸 重构 sider 组件 �[51313d7](https://github.com/kailong321200875/vue-element-plus-admin/commit/51313d7116c7ab2ded7e3a65514ea9ac413edecd)) +- Add analysis api ([83327ea](https://github.com/kailong321200875/vue-element-plus-admin/commit/83327ea763ebb233bb540513276ffa288fbcb4a1)) +- Add analysis demo ([cd06934](https://github.com/kailong321200875/vue-element-plus-admin/commit/cd069340fc5157535fdc82e792c6b6dce7d7a97e)) +- Add count-to demo ([d3fbd3a](https://github.com/kailong321200875/vue-element-plus-admin/commit/d3fbd3a06c3b802fc863b4dc8013122c14bd16f2)) +- Add Descriptions component and add Descriptions demo ([7ad46f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/7ad46f828d626a87699cd4d3a959a5634577d580)) +- Add Dialog component and add dailog demo ([a18ad8f](https://github.com/kailong321200875/vue-element-plus-admin/commit/a18ad8f4a89b78c73e57d8d2543494243f656d05)) +- add doucment link ([53201ae](https://github.com/kailong321200875/vue-element-plus-admin/commit/53201ae97a425714871d99e8847a3672ba0d389f)) +- Add dynamic route ([9d926b2](https://github.com/kailong321200875/vue-element-plus-admin/commit/9d926b2760b75e1d8e71a68dc7ff6c5026223a43)) +- Add Editor component and add editor demo ([3fb3e8d](https://github.com/kailong321200875/vue-element-plus-admin/commit/3fb3e8da39d816bcf4aedb65d40c7052bdb6d8bf)) +- Add Error component ([7411dbc](https://github.com/kailong321200875/vue-element-plus-admin/commit/7411dbc9fd8f122187c86a11523b49c88cc71a8c)) +- Add example-dialog demo ([262f421](https://github.com/kailong321200875/vue-element-plus-admin/commit/262f4211cf53aef30a32f4b88e88fb1b9246ffcb)) +- Add example-page demo ([1492f91](https://github.com/kailong321200875/vue-element-plus-admin/commit/1492f9119aa2960cc05956218e6d151c8b316875)) +- Add form demo ([472f574](https://github.com/kailong321200875/vue-element-plus-admin/commit/472f574f42f8f31c4e6047043ac755ba5fb35b7b)) +- Add form demo ([e6f9580](https://github.com/kailong321200875/vue-element-plus-admin/commit/e6f95803316bb5df2d1060285c1d591a79340721)) +- Add form demo ([543156f](https://github.com/kailong321200875/vue-element-plus-admin/commit/543156f328350bd12e71a41c872e547e41cda7fe)) +- Add form demo ([7795d2a](https://github.com/kailong321200875/vue-element-plus-admin/commit/7795d2a4fe3dbc9849ddc7c1d3e2d9215dc66f56)) +- Add guide demo ([0832194](https://github.com/kailong321200875/vue-element-plus-admin/commit/0832194e6131051416edff7c2eac6b0a016ffd80)) +- Add highlight demo ([eb206b0](https://github.com/kailong321200875/vue-element-plus-admin/commit/eb206b0cc31ac7da3dfd8b3d4b874061c5c91d53)) +- Add Icon demo ([e4b7a76](https://github.com/kailong321200875/vue-element-plus-admin/commit/e4b7a769126d6f0fca424007c294ff229eefcb35)) +- Add ImageViewer component and add ImageViewer demo ([af9fc0a](https://github.com/kailong321200875/vue-element-plus-admin/commit/af9fc0a4aded3ec08746ddeaeabac4c3cfa9463d)) +- Add Infotip component ([e4b7a76](https://github.com/kailong321200875/vue-element-plus-admin/commit/e4b7a769126d6f0fca424007c294ff229eefcb35)) +- Add infotip demo ([dbf3b0f](https://github.com/kailong321200875/vue-element-plus-admin/commit/dbf3b0f5a333ccef524bbac825035b0c6dc78ec9)) +- Add Qrcode component and add qrcode demo ([535a31b](https://github.com/kailong321200875/vue-element-plus-admin/commit/535a31b35eb6a76589f602fd96dcf91f46f349b0)) +- Add Search component and add search demo ([33eca8a](https://github.com/kailong321200875/vue-element-plus-admin/commit/33eca8a97d59f5cc453e1a60ee81b1519527d0f1)) +- Add Table component and add useTable hook ([17e8e7c](https://github.com/kailong321200875/vue-element-plus-admin/commit/17e8e7cda9a009818f11cfa0429ce0f9adc00be5)) +- Add useScrollTo hook ([7d7fd9e](https://github.com/kailong321200875/vue-element-plus-admin/commit/7d7fd9ed646d2b68cec0547ad8e65b0404bb95bb)) +- Add useWatermark hook and add useWatermark demo ([d3fbd3a](https://github.com/kailong321200875/vue-element-plus-admin/commit/d3fbd3a06c3b802fc863b4dc8013122c14bd16f2)) +- Add workplace api ([cb558f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/cb558f8af9dfef2ba2879f021db395ee79e8c8d4)) +- **Animate:** Add animate.css ([1436543](https://github.com/kailong321200875/vue-element-plus-admin/commit/1436543a5c599f651ed7805165ea83b9ebcddef5)) +- **Breadcrumbe:** Add Breadcrumb component ([4612e55](https://github.com/kailong321200875/vue-element-plus-admin/commit/4612e5544bcd626d686972e5cb874d0aa4af08b3)) +- **component:** Add CountTo component and Echart component ([e20fa76](https://github.com/kailong321200875/vue-element-plus-admin/commit/e20fa76cad0894a69fd04c81c2108faabf392684)) +- **component:** Add Footer component ([dad7330](https://github.com/kailong321200875/vue-element-plus-admin/commit/dad733063413c79eca61c6cb5ff671b35933a85f)) +- **component:** Add Footer component ([f81e996](https://github.com/kailong321200875/vue-element-plus-admin/commit/f81e996a426538aeaa2aa37a540395dcf360a09c)) +- **Component:** Add Highlight component ([c53fa56](https://github.com/kailong321200875/vue-element-plus-admin/commit/c53fa562e540447df082e35c7f26e56f2426e430)) +- **component:** Add namespace of class ([d847ccb](https://github.com/kailong321200875/vue-element-plus-admin/commit/d847ccb098edc72fe55c1f8459bf149453a3b73d)) +- **Component:** Setting component add copy button ([e496096](https://github.com/kailong321200875/vue-element-plus-admin/commit/e496096539e6a56b0761a625c9d59210facc5432)) +- **ContextMenu:** Add ContextMenu component ([349ac9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/349ac9d3989d77e5246cecf0006dd8d83c489990)) +- Detail 组件重构完成 ([7f5ef99](https://github.com/kailong321200875/vue-element-plus-admin/commit/7f5ef99ccc32b03f7be21f70c333bb8e679c7d93)) +- Highlight 组件重构 ([34221f3](https://github.com/kailong321200875/vue-element-plus-admin/commit/34221f387f5e15a08cdc21edd76ce8d8c5c20fbc)) +- **hooks:** Add useIntro hook ([0832194](https://github.com/kailong321200875/vue-element-plus-admin/commit/0832194e6131051416edff7c2eac6b0a016ffd80)) +- **hooks:** Add useTimeAgo hook ([c53fa56](https://github.com/kailong321200875/vue-element-plus-admin/commit/c53fa562e540447df082e35c7f26e56f2426e430)) +- **I18n:** Add Ii8n ([3810b8c](https://github.com/kailong321200875/vue-element-plus-admin/commit/3810b8c3b26f86c27aa7db479dfb7b0d283d970f)) +- **Layout:** Add classic layout ([839b601](https://github.com/kailong321200875/vue-element-plus-admin/commit/839b6015b8e31bf70e6f0bf0608fa729b028729b)) +- **Layout:** Add cutMenu layout ([ff4dd3a](https://github.com/kailong321200875/vue-element-plus-admin/commit/ff4dd3afbf5c0c7a439c71b0c494b81e0f2c70d4)) +- **Layout:** Add topLeft layout ([71b1c5e](https://github.com/kailong321200875/vue-element-plus-admin/commit/71b1c5e10cade8d1c018d0c5f63c98ba9357bab8)) +- **LocaleDropdown:** Add LocaleDropdown Component ([3810b8c](https://github.com/kailong321200875/vue-element-plus-admin/commit/3810b8c3b26f86c27aa7db479dfb7b0d283d970f)) +- **Logo:** Add Logo component ([958edef](https://github.com/kailong321200875/vue-element-plus-admin/commit/958edefe7bc2bf3ae77520a5d885a9d47e8a37b9)) +- **mock:** Add mock ([3fc7d4d](https://github.com/kailong321200875/vue-element-plus-admin/commit/3fc7d4d39a72056fcf419fe19a9d41d90f945bad)) +- **router:** Add dynamic routing ([b218ccc](https://github.com/kailong321200875/vue-element-plus-admin/commit/b218ccc9cce2ce1363c4a21d22b4d69c43c7b2dc)) +- Search component add expand attribute and expandField attribute ([9b4b317](https://github.com/kailong321200875/vue-element-plus-admin/commit/9b4b31781765d31dec50acc40e2eed91401502d4)) +- **store:** Add localeStore ([3810b8c](https://github.com/kailong321200875/vue-element-plus-admin/commit/3810b8c3b26f86c27aa7db479dfb7b0d283d970f)) +- **store:** Add tagsView store ([349ac9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/349ac9d3989d77e5246cecf0006dd8d83c489990)) +- Table 组件重构 ([07adefb](https://github.com/kailong321200875/vue-element-plus-admin/commit/07adefb89b7555280e6217e09cf81ba7aa5b93c2)) +- **TagsView:** Add TagsView component ([349ac9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/349ac9d3989d77e5246cecf0006dd8d83c489990)) +- **useForm:** Add useForm ([357fc44](https://github.com/kailong321200875/vue-element-plus-admin/commit/357fc44e519c5829567c17f611fcaadee3f9f933)) +- **useNProgress:** Add useNProgress ([c5ab359](https://github.com/kailong321200875/vue-element-plus-admin/commit/c5ab3599c8ea001ff7831b72fefc9e274163fbbb)) +- **useTitle:** Add useTitle ([c5ab359](https://github.com/kailong321200875/vue-element-plus-admin/commit/c5ab3599c8ea001ff7831b72fefc9e274163fbbb)) +- **utils:** Add color utils ([71dfba2](https://github.com/kailong321200875/vue-element-plus-admin/commit/71dfba21c5bc0276689b5aecf0d75e53efdda09f)) +- **VForm:** Add VForm component ([448ac52](https://github.com/kailong321200875/vue-element-plus-admin/commit/448ac5293e48a03840df2bb0b399a8f02aae666e)) +- **VInputPassword:** Add VInputPassword Component ([a1bf7e9](https://github.com/kailong321200875/vue-element-plus-admin/commit/a1bf7e9b552f75d3b87c64904ac9e7c99fc936a5)) +- **Workplace:** Add wrokplace demo ([c53fa56](https://github.com/kailong321200875/vue-element-plus-admin/commit/c53fa562e540447df082e35c7f26e56f2426e430)) +- 综合实例、权限管理重 �[a4bd206](https://github.com/kailong321200875/vue-element-plus-admin/commit/a4bd2068a5d40d146b5b45cb3727ced990147b68)) +- 部分组件重构完成 ([3d96229](https://github.com/kailong321200875/vue-element-plus-admin/commit/3d9622978dc234ef12dbce63e18caf3440563aa0)) + +### Bug Fixes + +- 🐛 删除 Editor 双向绑定,改 �props 传参 ([c395e27](https://github.com/kailong321200875/vue-element-plus-admin/commit/c395e27f67af9f60b151a5484ab5a3c90c4c1d1e)) +- fix Form component setProps not work bug ([48ffc52](https://github.com/kailong321200875/vue-element-plus-admin/commit/48ffc52ca8fa26d8e6a5fa4b8b3001701a1f0732)) +- fix useScrollTo not work bug ([53201ae](https://github.com/kailong321200875/vue-element-plus-admin/commit/53201ae97a425714871d99e8847a3672ba0d389f)) +- 修复 tagsView 无动画效 �[0e3eb4b](https://github.com/kailong321200875/vue-element-plus-admin/commit/0e3eb4ba8b1503e1d221dfda59a3a0001dbdcb56)) + +### Performance Improvements + +- update useForm hook ([8a958cd](https://github.com/kailong321200875/vue-element-plus-admin/commit/8a958cd71d9afbd32b243aac0814bfa3281477cd)) + +### Code Refactoring + +- 💡 综合实例查看详情重构 ([9c26edd](https://github.com/kailong321200875/vue-element-plus-admin/commit/9c26edd5d599b5fb5a832fb547e3d95b6bfa9a98)) + +### Build System + +- Add conventional-changelog-cli plugin ([384485f](https://github.com/kailong321200875/vue-element-plus-admin/commit/384485f6994c6ac33abee506508ab9d35fe658a9)) +- Add conventional-github-releaser plugin ([3cd5c71](https://github.com/kailong321200875/vue-element-plus-admin/commit/3cd5c71899dde3ac3910aef0180d8b39fad51f1b)) +- Add standard-version plugin ([110ce25](https://github.com/kailong321200875/vue-element-plus-admin/commit/110ce257841648e29b247a0338624a188694b6e9)) +- Add vite-plugin-html plugin ([d5b6e2a](https://github.com/kailong321200875/vue-element-plus-admin/commit/d5b6e2a7770eb59aa32839f69da5be37397e3538)) +- delete useless plugin ([c756761](https://github.com/kailong321200875/vue-element-plus-admin/commit/c756761dfc3200156acb228474d3539197ef413b)) +- **pinia:** Add pinia ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- **types:** Add vue-types ([2c41826](https://github.com/kailong321200875/vue-element-plus-admin/commit/2c41826c572268b74a663a6966c548628ac7e280)) +- **unplugin-auto-import:** Delete unplugin-auto-import ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- **unplugin-vue-components:** Delete unplugin-vue-components ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- update plugin ([8d08bc6](https://github.com/kailong321200875/vue-element-plus-admin/commit/8d08bc6fc92258674abdd12834eaa4530ec276dd)) +- update plugins ([3c58042](https://github.com/kailong321200875/vue-element-plus-admin/commit/3c580420a20121845f02c0dd3caca5a74f06a89d)) +- **vite-plugin-style-import:** Add vite-plugin-style-import ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- **vite-plugin-vue-setup-extend:** Delete vite-plugin-vue-setup-extend ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- 修改 vite 配置 ([9991fb4](https://github.com/kailong321200875/vue-element-plus-admin/commit/9991fb4e5c46b9e4016beaade7232e28dc272797)) +- 设置多语 �([45e879e](https://github.com/kailong321200875/vue-element-plus-admin/commit/45e879edeef677b6aa1d2cfe4dd8dc5b76c83c59)) +- 配置代码格式 �[ffdb556](https://github.com/kailong321200875/vue-element-plus-admin/commit/ffdb556a096db247306eae8eecc1b85718314cdd)) +- 集成基础配置 ([ced99de](https://github.com/kailong321200875/vue-element-plus-admin/commit/ced99de9b113a01d9d0b190f6d2c6adc983a3102)) +- 集成基础配置 ([5dbcf23](https://github.com/kailong321200875/vue-element-plus-admin/commit/5dbcf2397ccdec80c695c113f49e8aa9bb6d012c)) + +### Styling + +- 💄 优化 layout 样式 ([37ec378](https://github.com/kailong321200875/vue-element-plus-admin/commit/37ec378f0b2bf83d73ddf0e472aada6aab248f09)) +- 💄 微调样式 ([612b486](https://github.com/kailong321200875/vue-element-plus-admin/commit/612b48673c3389779ccfdd161e2ca80b21d265b2)) +- Add elNamespace ([d847ccb](https://github.com/kailong321200875/vue-element-plus-admin/commit/d847ccb098edc72fe55c1f8459bf149453a3b73d)) +- **appStore:** code style ([641ed68](https://github.com/kailong321200875/vue-element-plus-admin/commit/641ed684fefeb52e2f91e8baab7b610fc74c8d88)) +- **Breadcrumb:** fix Breadcrumb style bug ([8755c86](https://github.com/kailong321200875/vue-element-plus-admin/commit/8755c862b837d90a25b27c01fabe64abf81fc4a2)) +- **breadcrumb:** update disabled text color ([1522e92](https://github.com/kailong321200875/vue-element-plus-admin/commit/1522e925bae37cb9df4de2252d81f717788f4537)) +- change function to arrow function ([4612e55](https://github.com/kailong321200875/vue-element-plus-admin/commit/4612e5544bcd626d686972e5cb874d0aa4af08b3)) +- delete console.log ([49a6bfe](https://github.com/kailong321200875/vue-element-plus-admin/commit/49a6bfe9d81a40e2f5f15b68d7289e1787e89b54)) +- **Icon:** delete default color ([95a2bd8](https://github.com/kailong321200875/vue-element-plus-admin/commit/95a2bd884dd9846a56cda7c4c3ee4a41ce631b7c)) +- level demo style beautification ([dbf3b0f](https://github.com/kailong321200875/vue-element-plus-admin/commit/dbf3b0f5a333ccef524bbac825035b0c6dc78ec9)) +- lint code style ([b292419](https://github.com/kailong321200875/vue-element-plus-admin/commit/b2924190b8996e8208f951e3fadbcb09baddb8df)) +- **Login:** update login styles ([eb68f1d](https://github.com/kailong321200875/vue-element-plus-admin/commit/eb68f1d919e13c07b7d200e9aec53804b2a6dc7b)) +- modify menu z-index attribute ([0d7a778](https://github.com/kailong321200875/vue-element-plus-admin/commit/0d7a7781ce0b5e39f01355d3acdb3f364cabf76d)) +- **TagView:** Vertical center tag ([41281c4](https://github.com/kailong321200875/vue-element-plus-admin/commit/41281c4d541a2744e5df5dff2764cc85465b6a4c)) + +### Types + +- add ImportMetaEnv ([38e0257](https://github.com/kailong321200875/vue-element-plus-admin/commit/38e0257487e4138a74ad1bb4ee4ba004abcfaa12)) +- Adding BfFrom Component types ([8e036f5](https://github.com/kailong321200875/vue-element-plus-admin/commit/8e036f54b56ce8521eb8ec4b7ca21aa9c24f43f2)) +- **BfForm:** Adding BfForm types ([bc9195b](https://github.com/kailong321200875/vue-element-plus-admin/commit/bc9195b21eeb79629a82a04d90e2ac5aa6592928)) +- **BfForm:** Adding BfForm types ([184b468](https://github.com/kailong321200875/vue-element-plus-admin/commit/184b468cd41dcd1cdae11477b9ee2d6e17de1481)) +- **BfForm:** Adding BfForm types ([58cb24d](https://github.com/kailong321200875/vue-element-plus-admin/commit/58cb24d9f8a50be80b5ea793387d582a77a59137)) +- delete useless types ([3fc79c0](https://github.com/kailong321200875/vue-element-plus-admin/commit/3fc79c0ae7acd0929f47e33f96c8d45a90d8f762)) +- **VForm:** Adding VForm types ([7528fe6](https://github.com/kailong321200875/vue-element-plus-admin/commit/7528fe6da60368213d28d9f1b6310d02d3d53282)) + +### Docs + +- ✏️ 修改 readme ([8edb2a3](https://github.com/kailong321200875/vue-element-plus-admin/commit/8edb2a3493dca975036859b5d2c52afaa91f5dbb)) +- ✏️ 更新 readme ([62eeb55](https://github.com/kailong321200875/vue-element-plus-admin/commit/62eeb55330dd4af2a46801c7a19f38a3ef312bbf)) +- Add README.md ([21dcf88](https://github.com/kailong321200875/vue-element-plus-admin/commit/21dcf88ba31957bbdb50c6207d010650daab70fc)) +- Error modifying readme name ([25d5c84](https://github.com/kailong321200875/vue-element-plus-admin/commit/25d5c84e92e68aa72362a14f55aacd946fa5b1b2)) +- update description ([be6ff98](https://github.com/kailong321200875/vue-element-plus-admin/commit/be6ff9899b25cc00519210950d27ee56ac5112e6)) +- update description ([c15aa87](https://github.com/kailong321200875/vue-element-plus-admin/commit/c15aa8755c9c937512c7380a6d03c4d877ef4d87)) +- update README ([27979dc](https://github.com/kailong321200875/vue-element-plus-admin/commit/27979dc6def7d9d8cea62a08d49a6c828be2258b)) +- update README.md ([53201ae](https://github.com/kailong321200875/vue-element-plus-admin/commit/53201ae97a425714871d99e8847a3672ba0d389f)) +- update README.md ([c11823a](https://github.com/kailong321200875/vue-element-plus-admin/commit/c11823abd8a033e14b4c20d17ac941195d39bcfe)) +- 修改 README ([b79a567](https://github.com/kailong321200875/vue-element-plus-admin/commit/b79a56753df55976e749c4494266df052d315416)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9861118 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-present Archer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddc3778 --- /dev/null +++ b/README.md @@ -0,0 +1,132 @@ +


+ +[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) + +

vue-element-plus-admin

+
+ +**English** | [中文](./README.zh-CN.md) + +## Introduction + +vue-element-plus-admin is a free and open source middle and background template based on `element-plus`. Developed using the latest mainstream technologies such as `vue3`, `vite4` and `typescript`, the out of the box middle and background front-end solution can be used as the starting template of the project and learning reference. And always pay attention to the latest technological trends and update them as soon as possible. + +vue-element-plus-admin is positioned as a background integration scheme, which is not suitable for secondary development as a basic template. Because it integrates many functions that you may not use, it will cause a lot of code redundancy. If your project doesn't pay attention to this problem, you can also directly carry out secondary development based on it. + +If you need a basic template, please switch to the `tempalte` branch. `Tempalte` simply integrates some common layout functions such as layout and dynamic menu, which is more suitable for developers to carry out secondary development. + +## Feature + +- **State of The Art Development**:Use front-end front-end technology development such as Vue3/vite4 +- **TypeScript**: Application-level JavaScript language +- **Theming**: Configurable themes +- **International**:Built-in complete internationalization program +- **Mock Server** Built-in mock data scheme +- **Authority** Built-in complete dynamic routing permission generation scheme. +- **Component** Multiple commonly used components are encapsulated twice +- **Examples** Built-in rich examples + +## Preview + +- [vue-element-plus-admin](https://element-plus-admin.cn/) - Full version of the github site +- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - Full version of the gitee site + +account: **admin/admin test/test** + +`admin` account is used to simulate the control permission of the server, and render whatever the server returns + +`test` account is used to simulate the front-end control authority. The server only returns the menu key to be displayed, and the front-end performs matching rendering + +## Documentation + +[Document Github](https://element-plus-admin-doc.cn/) + +[Document Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc) + +## Preparation + +- [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment +- [Vite4](https://vitejs.dev/) - Familiar with vite features +- [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax +- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript` +- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax +- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router +- [Element-Plus](https://element-plus.org/) - Familiar with the basic use of element-plus +- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax + +## Install and use + +- Get the project code + +```bash +git clone https://github.com/kailong321200875/vue-element-plus-admin.git +``` + +- Installation dependencies + +```bash +cd vue-element-plus-admin + +pnpm install + +``` + +- run + +```bash +pnpm run dev +``` + +- build + +```bash +pnpm run build:pro +``` + +## Change Log + +[CHANGELOG](./CHANGELOG.md) + +## How to contribute + +You can [Raise an issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) Or submit a Pull Request. + +**Pull Request:** + +1. Fork code +2. Create your own branch: `git checkout -b feat/xxxx` +3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` +4. Push your branch: `git push origin feat/xxxx` +5. submit `pull request` + +## Git Contribution submission specification + +- `feat` New features +- `fix` Fix bugs +- `docs` document +- `style` Format and style (changes that do not affect code operation) +- `refactor` Refactor +- `perf` Optimize related, such as improving performance and experience +- `test` Add test +- `build` Compilation related modifications, changes to project construction or dependencies +- `ci` Continuous integration modification +- `chore` Changes in the construction process or auxiliary tools +- `revert` Rollback to previous version +- `workflow` Workflow improvement +- `mod` Uncertain modification classification +- `wip` Under development +- `types` type + +## Browser support + +The `Chrome 80+` browser is recommended for local development + +Support modern browsers, not IE + +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | + +## License + +[MIT](./LICENSE) diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..e7b24ad --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,132 @@ +


+ +[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) + +

vue-element-plus-admin

+
+ +[English](./README.md) | **中文** + +## 介绍 + +vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3`,`vite4`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。 + +vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。 + +如需要基础模版,请切换到 `tempalte` 分支,`tempalte` 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。 + +## 特性 + +- **最新技术栈**:使用 Vue3/vite4 等前端前沿技术开发 +- **TypeScript**: 应用程序级 JavaScript 的语言 +- **主题**: 可配置的主题 +- **国际化**:内置完善的国际化方案 +- **自定义数据** 内置 Mock 数据方案 +- **权限** 内置完善的动态路由权限生成方案 +- **组件** 二次封装了多个常用的组件 +- **示例** 内置丰富的示例 + +## 预览 + +- [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点 +- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点 + +帐号:**admin/admin test/test** + +`admin` 帐号用于模拟服务端控制权限,服务端返回什么就渲染什么 + +`test` 帐号用于模拟前端控制权限,服务端只返回需要显示的菜单 key,前端进行匹配渲染 + +## 文档 + +[文档地址 Github](https://element-plus-admin-doc.cn/) + +[文档地址 Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc) + +## 前序准备 + +- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境 +- [Vite4](https://vitejs.dev/) - 熟悉 vite 特性 +- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法 +- [TypeScript](https://www.typescriptlang.org/) - 熟悉 `TypeScript` 基本语法 +- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法 +- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用 +- [Element-Plus](https://element-plus.org/) - element-plus 基本使用 +- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法 + +## 安装和使用 + +- 获取代码 + +```bash +git clone https://github.com/kailong321200875/vue-element-plus-admin.git +``` + +- 安装依赖 + +```bash +cd vue-element-plus-admin + +pnpm install + +``` + +- 运行 + +```bash +pnpm run dev +``` + +- 打包 + +```bash +pnpm run build:pro +``` + +## 更新日志 + +[更新日志](./CHANGELOG.md) + +## 如何贡献 + +你可以[提一个 issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) 或者提交一个 Pull Request。 + +**Pull Request:** + +1. Fork 代码 +2. 创建自己的分支: `git checkout -b feat/xxxx` +3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'` +4. 推送您的分支: `git push origin feat/xxxx` +5. 提交 `pull request` + +## Git 贡献提交规范 + +- `feat` 新功能 +- `fix` 修补 bug +- `docs` 文档 +- `style` 格式、样式(不影响代码运行的变动) +- `refactor` 重构(即不是新增功能,也不是修改 BUG 的代码) +- `perf` 优化相关,比如提升性能、体验 +- `test` 添加测试 +- `build` 编译相关的修改,对项目构建或者依赖的改动 +- `ci` 持续集成修改 +- `chore` 构建过程或辅助工具的变动 +- `revert` 回滚到上一个版本 +- `workflow` 工作流改进 +- `mod` 不确定分类的修改 +- `wip` 开发中 +- `types` 类型 + +## 浏览器支持 + +本地开发推荐使用 `Chrome 80+` 浏览器 + +支持现代浏览器, 不支持 IE + +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | + +## 许可证 + +[MIT](./LICENSE) diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..78d145d --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,28 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新功能(feature) + 'fix', // 修补bug + 'docs', // 文档(documentation) + 'style', // 格式、样式(不影响代码运行的变动) + 'refactor', // 重构(即不是新增功能,也不是修改BUG的代码) + 'perf', // 优化相关,比如提升性能、体验 + 'test', // 添加测试 + 'ci', // 持续集成修改 + 'chore', // 构建过程或辅助工具的变动 + 'revert', // 回滚到上一个版本 + 'workflow', // 工作流改进 + 'mod', // 不确定分类的修改 + 'wip', // 开发中 + 'types', // 类型修改 + 'release' // 版本发布 + ] + ], + 'subject-full-stop': [0, 'never'], + 'subject-case': [0, 'never'] + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..507d616 --- /dev/null +++ b/index.html @@ -0,0 +1,142 @@ + + + + + + + <%= title %> + + +
+ +
+
+
+ +
<%= title %>
+
+
+
+
+
+
+
+
+ + + diff --git a/mock/_createProductionServer.ts b/mock/_createProductionServer.ts new file mode 100644 index 0000000..caa26bf --- /dev/null +++ b/mock/_createProductionServer.ts @@ -0,0 +1,18 @@ +import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' + +const modules = import.meta.glob('./**/*.ts', { + import: 'default', + eager: true +}) + +const mockModules: any[] = [] +Object.keys(modules).forEach(async (key) => { + if (key.includes('_')) { + return + } + mockModules.push(...(modules[key] as any)) +}) + +export function setupProdMockServer() { + createProdMockServer(mockModules) +} diff --git a/mock/analysis/index.ts b/mock/analysis/index.ts new file mode 100644 index 0000000..d624219 --- /dev/null +++ b/mock/analysis/index.ts @@ -0,0 +1,89 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +export default [ + // 分析页统计接口 + { + url: '/analysis/total', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: { + users: 102400, + messages: 81212, + moneys: 9280, + shoppings: 13600 + } + } + } + }, + // 用户来源 + { + url: '/analysis/userAccessSource', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { value: 1000, name: 'analysis.directAccess' }, + { value: 310, name: 'analysis.mailMarketing' }, + { value: 234, name: 'analysis.allianceAdvertising' }, + { value: 135, name: 'analysis.videoAdvertising' }, + { value: 1548, name: 'analysis.searchEngines' } + ] + } + } + }, + // 每周用户活跃量 + { + url: '/analysis/weeklyUserActivity', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { value: 13253, name: 'analysis.monday' }, + { value: 34235, name: 'analysis.tuesday' }, + { value: 26321, name: 'analysis.wednesday' }, + { value: 12340, name: 'analysis.thursday' }, + { value: 24643, name: 'analysis.friday' }, + { value: 1322, name: 'analysis.saturday' }, + { value: 1324, name: 'analysis.sunday' } + ] + } + } + }, + // 每月销售额 + { + url: '/analysis/monthlySales', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { estimate: 100, actual: 120, name: 'analysis.january' }, + { estimate: 120, actual: 82, name: 'analysis.february' }, + { estimate: 161, actual: 91, name: 'analysis.march' }, + { estimate: 134, actual: 154, name: 'analysis.april' }, + { estimate: 105, actual: 162, name: 'analysis.may' }, + { estimate: 160, actual: 140, name: 'analysis.june' }, + { estimate: 165, actual: 145, name: 'analysis.july' }, + { estimate: 114, actual: 250, name: 'analysis.august' }, + { estimate: 163, actual: 134, name: 'analysis.september' }, + { estimate: 185, actual: 56, name: 'analysis.october' }, + { estimate: 118, actual: 99, name: 'analysis.november' }, + { estimate: 123, actual: 123, name: 'analysis.december' } + ] + } + } + } +] as MockMethod[] diff --git a/mock/dict/index.ts b/mock/dict/index.ts new file mode 100644 index 0000000..b27f3b3 --- /dev/null +++ b/mock/dict/index.ts @@ -0,0 +1,63 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const dictObj: Recordable = { + importance: [ + { + value: 0, + label: 'tableDemo.commonly' + }, + { + value: 1, + label: 'tableDemo.good' + }, + { + value: 2, + label: 'tableDemo.important' + } + ] +} + +export default [ + // 字典接口 + { + url: '/dict/list', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: dictObj + } + } + }, + // 获取某个字典 + { + url: '/dict/one', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + label: 'test1', + value: 0 + }, + { + label: 'test2', + value: 1 + }, + { + label: 'test3', + value: 2 + } + ] + } + } + } +] as MockMethod[] diff --git a/mock/role/index.ts b/mock/role/index.ts new file mode 100644 index 0000000..aa43000 --- /dev/null +++ b/mock/role/index.ts @@ -0,0 +1,534 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const adminList = [ + { + path: '/dashboard', + component: '#', + redirect: '/dashboard/analysis', + name: 'Dashboard', + meta: { + title: 'router.dashboard', + icon: 'ant-design:dashboard-filled', + alwaysShow: true + }, + children: [ + { + path: 'analysis', + component: 'views/Dashboard/Analysis', + name: 'Analysis', + meta: { + title: 'router.analysis', + noCache: true + } + }, + { + path: 'workplace', + component: 'views/Dashboard/Workplace', + name: 'Workplace', + meta: { + title: 'router.workplace', + noCache: true + } + } + ] + }, + { + path: '/external-link', + component: '#', + meta: {}, + name: 'ExternalLink', + children: [ + { + path: 'https://element-plus-admin-doc.cn/', + name: 'DocumentLink', + meta: { + title: 'router.document', + icon: 'clarity:document-solid' + } + } + ] + }, + { + path: '/guide', + component: '#', + name: 'Guide', + meta: {}, + children: [ + { + path: 'index', + component: 'views/Guide/Guide', + name: 'GuideDemo', + meta: { + title: 'router.guide', + icon: 'cib:telegram-plane' + } + } + ] + }, + { + path: '/components', + component: '#', + redirect: '/components/form/default-form', + name: 'ComponentsDemo', + meta: { + title: 'router.component', + icon: 'bx:bxs-component', + alwaysShow: true + }, + children: [ + { + path: 'form', + component: '##', + name: 'Form', + meta: { + title: 'router.form', + alwaysShow: true + }, + children: [ + { + path: 'default-form', + component: 'views/Components/Form/DefaultForm', + name: 'DefaultForm', + meta: { + title: 'router.defaultForm' + } + }, + { + path: 'use-form', + component: 'views/Components/Form/UseFormDemo', + name: 'UseForm', + meta: { + title: 'UseForm' + } + }, + { + path: 'ref-form', + component: 'views/Components/Form/RefForm', + name: 'RefForm', + meta: { + title: 'RefForm' + } + } + ] + }, + { + path: 'table', + component: '##', + redirect: '/components/table/default-table', + name: 'TableDemo', + meta: { + title: 'router.table', + alwaysShow: true + }, + children: [ + { + path: 'default-table', + component: 'views/Components/Table/DefaultTable', + name: 'DefaultTable', + meta: { + title: 'router.defaultTable' + } + }, + { + path: 'use-table', + component: 'views/Components/Table/UseTableDemo', + name: 'UseTable', + meta: { + title: 'UseTable' + } + }, + { + path: 'ref-table', + component: 'views/Components/Table/RefTable', + name: 'RefTable', + meta: { + title: 'RefTable' + } + } + ] + }, + { + path: 'editor-demo', + component: '##', + redirect: '/components/editor-demo/editor', + name: 'EditorDemo', + meta: { + title: 'router.editor', + alwaysShow: true + }, + children: [ + { + path: 'editor', + component: 'views/Components/Editor/Editor', + name: 'Editor', + meta: { + title: 'router.richText' + } + } + ] + }, + { + path: 'search', + component: 'views/Components/Search', + name: 'Search', + meta: { + title: 'router.search' + } + }, + { + path: 'descriptions', + component: 'views/Components/Descriptions', + name: 'Descriptions', + meta: { + title: 'router.descriptions' + } + }, + { + path: 'image-viewer', + component: 'views/Components/ImageViewer', + name: 'ImageViewer', + meta: { + title: 'router.imageViewer' + } + }, + { + path: 'dialog', + component: 'views/Components/Dialog', + name: 'Dialog', + meta: { + title: 'router.dialog' + } + }, + { + path: 'icon', + component: 'views/Components/Icon', + name: 'Icon', + meta: { + title: 'router.icon' + } + }, + { + path: 'echart', + component: 'views/Components/Echart', + name: 'Echart', + meta: { + title: 'router.echart' + } + }, + { + path: 'count-to', + component: 'views/Components/CountTo', + name: 'CountTo', + meta: { + title: 'router.countTo' + } + }, + { + path: 'qrcode', + component: 'views/Components/Qrcode', + name: 'Qrcode', + meta: { + title: 'router.qrcode' + } + }, + { + path: 'highlight', + component: 'views/Components/Highlight', + name: 'Highlight', + meta: { + title: 'router.highlight' + } + }, + { + path: 'infotip', + component: 'views/Components/Infotip', + name: 'Infotip', + meta: { + title: 'router.infotip' + } + }, + { + path: 'input-password', + component: 'views/Components/InputPassword', + name: 'InputPassword', + meta: { + title: 'router.inputPassword' + } + }, + { + path: 'sticky', + component: 'views/Components/Sticky', + name: 'Sticky', + meta: { + title: 'router.sticky' + } + } + ] + }, + { + path: '/hooks', + component: '#', + redirect: '/hooks/useWatermark', + name: 'Hooks', + meta: { + title: 'hooks', + icon: 'ic:outline-webhook', + alwaysShow: true + }, + children: [ + { + path: 'useWatermark', + component: 'views/hooks/useWatermark', + name: 'UseWatermark', + meta: { + title: 'useWatermark' + } + }, + { + path: 'useCrudSchemas', + component: 'views/hooks/useCrudSchemas', + name: 'UseCrudSchemas', + meta: { + title: 'useCrudSchemas' + } + } + ] + }, + { + path: '/level', + component: '#', + redirect: '/level/menu1/menu1-1/menu1-1-1', + name: 'Level', + meta: { + title: 'router.level', + icon: 'carbon:skill-level-advanced' + }, + children: [ + { + path: 'menu1', + name: 'Menu1', + component: '##', + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: 'router.menu1' + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11', + component: '##', + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: 'router.menu11', + alwaysShow: true + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111', + component: 'views/Level/Menu111', + meta: { + title: 'router.menu111' + } + } + ] + }, + { + path: 'menu1-2', + name: 'Menu12', + component: 'views/Level/Menu12', + meta: { + title: 'router.menu12' + } + } + ] + }, + { + path: 'menu2', + name: 'Menu2Demo', + component: 'views/Level/Menu2', + meta: { + title: 'router.menu2' + } + } + ] + }, + { + path: '/example', + component: '#', + redirect: '/example/example-dialog', + name: 'Example', + meta: { + title: 'router.example', + icon: 'ep:management', + alwaysShow: true + }, + children: [ + { + path: 'example-dialog', + component: 'views/Example/Dialog/ExampleDialog', + name: 'ExampleDialog', + meta: { + title: 'router.exampleDialog' + } + }, + { + path: 'example-page', + component: 'views/Example/Page/ExamplePage', + name: 'ExamplePage', + meta: { + title: 'router.examplePage' + } + }, + { + path: 'example-add', + component: 'views/Example/Page/ExampleAdd', + name: 'ExampleAdd', + meta: { + title: 'router.exampleAdd', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-edit', + component: 'views/Example/Page/ExampleEdit', + name: 'ExampleEdit', + meta: { + title: 'router.exampleEdit', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-detail', + component: 'views/Example/Page/ExampleDetail', + name: 'ExampleDetail', + meta: { + title: 'router.exampleDetail', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example/example-page' + } + } + ] + }, + { + path: '/error', + component: '#', + redirect: '/error/404', + name: 'Error', + meta: { + title: 'router.errorPage', + icon: 'ci:error', + alwaysShow: true + }, + children: [ + { + path: '404-demo', + component: 'views/Error/404', + name: '404Demo', + meta: { + title: '404' + } + }, + { + path: '403-demo', + component: 'views/Error/403', + name: '403Demo', + meta: { + title: '403' + } + }, + { + path: '500-demo', + component: 'views/Error/500', + name: '500Demo', + meta: { + title: '500' + } + } + ] + } +] + +const testList: string[] = [ + '/dashboard', + '/dashboard/analysis', + '/dashboard/workplace', + 'external-link', + 'https://element-plus-admin-doc.cn/', + '/guide', + '/guide/index', + '/components', + '/components/form', + '/components/form/default-form', + '/components/form/use-form', + '/components/form/ref-form', + '/components/table', + '/components/table/default-table', + '/components/table/use-table', + '/components/table/ref-table', + '/components/editor-demo', + '/components/editor-demo/editor', + '/components/search', + '/components/descriptions', + '/components/image-viewer', + '/components/dialog', + '/components/icon', + '/components/echart', + '/components/count-to', + '/components/qrcode', + '/components/highlight', + '/components/infotip', + '/Components/InputPassword', + '/Components/Sticky', + '/hooks', + '/hooks/useWatermark', + '/hooks/useCrudSchemas', + '/level', + '/level/menu1', + '/level/menu1/menu1-1', + '/level/menu1/menu1-1/menu1-1-1', + '/level/menu1/menu1-2', + '/level/menu2', + '/example', + '/example/example-dialog', + '/example/example-page', + '/example/example-add', + '/example/example-edit', + '/example/example-detail', + '/error', + '/error/404-demo', + '/error/403-demo', + '/error/500-demo' +] + +export default [ + // 列表接口 + { + url: '/role/list', + method: 'get', + timeout, + response: ({ query }) => { + const { roleName } = query + return { + code: result_code, + data: roleName === 'admin' ? adminList : testList + } + } + } +] as MockMethod[] diff --git a/mock/table/index.ts b/mock/table/index.ts new file mode 100644 index 0000000..8caee83 --- /dev/null +++ b/mock/table/index.ts @@ -0,0 +1,137 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' +import { toAnyString } from '@/utils' +import Mock from 'mockjs' + +const { result_code } = config + +const timeout = 1000 + +const count = 100 + +const baseContent = + '

I am testing data, I am testing data.

' + +let List: { + id: string + author: string + title: string + content: string + importance: number + display_time: string + pageviews: number +}[] = [] + +for (let i = 0; i < count; i++) { + List.push( + Mock.mock({ + id: toAnyString(), + // timestamp: +Mock.Random.date('T'), + author: '@first', + title: '@title(5, 10)', + content: baseContent, + importance: '@integer(1, 3)', + display_time: '@datetime', + pageviews: '@integer(300, 5000)' + // image_uri + }) + ) +} + +export default [ + // 列表接口 + { + url: '/example/list', + method: 'get', + timeout, + response: ({ query }) => { + const { title, pageIndex, pageSize } = query + const mockList = List.filter((item) => { + if (title && item.title.indexOf(title) < 0) return false + return true + }) + const pageList = mockList.filter( + (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1) + ) + return { + code: result_code, + data: { + total: mockList.length, + list: pageList + } + } + } + }, + // 保存接口 + { + url: '/example/save', + method: 'post', + timeout, + response: ({ body }) => { + if (!body.id) { + List = [ + Object.assign(body, { + id: toAnyString() + }) + ].concat(List) + return { + code: result_code, + data: 'success' + } + } else { + List.map((item) => { + if (item.id === body.id) { + for (const key in item) { + item[key] = body[key] + } + } + }) + return { + code: result_code, + data: 'success' + } + } + } + }, + // 详情接口 + { + url: '/example/detail', + method: 'get', + response: ({ query }) => { + const { id } = query + for (const example of List) { + if (example.id === id) { + return { + code: result_code, + data: example + } + } + } + } + }, + // 删除接口 + { + url: '/example/delete', + method: 'post', + response: ({ body }) => { + const ids = body.ids + if (!ids) { + return { + code: '500', + message: '请选择需要删除的数据' + } + } else { + let i = List.length + while (i--) { + if (ids.indexOf(List[i].id) !== -1) { + List.splice(i, 1) + } + } + return { + code: result_code, + data: 'success' + } + } + } + } +] as MockMethod[] diff --git a/mock/user/index.ts b/mock/user/index.ts new file mode 100644 index 0000000..caa91bd --- /dev/null +++ b/mock/user/index.ts @@ -0,0 +1,93 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const List: { + username: string + password: string + role: string + roleId: string + permissions: string | string[] +}[] = [ + { + username: 'admin', + password: 'admin', + role: 'admin', + roleId: '1', + permissions: ['*.*.*'] + }, + { + username: 'test', + password: 'test', + role: 'test', + roleId: '2', + permissions: ['example:dialog:create', 'example:dialog:delete'] + } + ] + +export default [ + // 列表接口 + { + url: '/user/list', + method: 'get', + response: ({ query }) => { + const { username, pageIndex, pageSize } = query + + const mockList = List.filter((item) => { + if (username && item.username.indexOf(username) < 0) return false + return true + }) + const pageList = mockList.filter( + (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1) + ) + + return { + code: result_code, + data: { + total: mockList.length, + list: pageList + } + } + } + }, + // 登录接口 + { + url: '/user/login', + method: 'post', + timeout, + response: ({ body }) => { + const data = body + let hasUser = false + for (const user of List) { + if (user.username === data.username && user.password === data.password) { + hasUser = true + return { + code: result_code, + data: user + } + } + } + if (!hasUser) { + return { + code: '500', + message: '账号或密码错误' + } + } + } + }, + // 退出接口 + { + url: '/user/loginOut', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: null + } + } + } +] as MockMethod[] diff --git a/mock/workplace/index.ts b/mock/workplace/index.ts new file mode 100644 index 0000000..3274a4c --- /dev/null +++ b/mock/workplace/index.ts @@ -0,0 +1,172 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +export default [ + // 获取统计 + { + url: '/workplace/total', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: { + project: 40, + access: 2340, + todo: 10 + } + } + } + }, + // 获取项目 + { + url: '/workplace/project', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + name: 'Github', + icon: 'akar-icons:github-fill', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Vue', + icon: 'logos:vue', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Angular', + icon: 'logos:angular-icon', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'React', + icon: 'logos:react', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Webpack', + icon: 'logos:webpack', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Vite', + icon: 'vscode-icons:file-type-vite', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + } + ] + } + } + }, + // 获取动态 + { + url: '/workplace/dynamic', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + } + ] + } + } + }, + // 获取团队信息 + { + url: '/workplace/team', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + name: 'Github', + icon: 'akar-icons:github-fill' + }, + { + name: 'Vue', + icon: 'logos:vue' + }, + { + name: 'Angular', + icon: 'logos:angular-icon' + }, + { + name: 'React', + icon: 'logos:react' + }, + { + name: 'Webpack', + icon: 'logos:webpack' + }, + { + name: 'Vite', + icon: 'vscode-icons:file-type-vite' + } + ] + } + } + }, + // 获取指数 + { + url: '/workplace/radar', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { name: 'workplace.quote', max: 65, personal: 42, team: 50 }, + { name: 'workplace.contribution', max: 160, personal: 30, team: 140 }, + { name: 'workplace.hot', max: 300, personal: 20, team: 28 }, + { name: 'workplace.yield', max: 130, personal: 35, team: 35 }, + { name: 'workplace.follow', max: 100, personal: 80, team: 90 } + ] + } + } + } +] as MockMethod[] diff --git a/package.json b/package.json new file mode 100644 index 0000000..faca1f6 --- /dev/null +++ b/package.json @@ -0,0 +1,135 @@ +{ + "name": "vue-element-plus-admin", + "version": "1.9.4", + "description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。", + "author": "Archer <502431556@qq.com>", + "private": false, + "scripts": { + "i": "pnpm install", + "dev": "vite --mode base", + "ts:check": "vue-tsc --noEmit", + "build:pro": "vite build --mode pro", + "build:gitee": "vite build --mode gitee", + "build:dev": "npm run ts:check && vite build --mode dev", + "build:test": "npm run ts:check && vite build --mode test", + "serve:pro": "vite preview --mode pro", + "serve:dev": "vite preview --mode dev", + "serve:test": "vite preview --mode test", + "npm:check": "npx npm-check-updates", + "clean": "npx rimraf node_modules", + "clean:cache": "npx rimraf node_modules/.cache", + "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src", + "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"", + "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", + "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", + "prepare": "husky install", + "p": "plop", + "analysis": "windicss-analysis" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.1.0", + "@form-create/designer": "^3.1.3", + "@form-create/element-ui": "^3.1.24", + "@iconify/iconify": "^3.1.0", + "@iconify/vue": "^4.1.1", + "@vueuse/core": "^9.13.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.10", + "@zxcvbn-ts/core": "^2.2.1", + "animate.css": "^4.1.1", + "axios": "^1.3.4", + "clipboard": "^2.0.11", + "echarts": "^5.4.2", + "echarts-wordcloud": "^2.1.0", + "element-plus": "2.2.32", + "intro.js": "^6.0.0", + "js-base64": "^3.7.6", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "mitt": "^3.0.0", + "mockjs": "^1.1.0", + "moment": "^2.29.4", + "nprogress": "^0.2.0", + "pinia": "2.0.35", + "qrcode": "^1.5.1", + "qs": "^6.11.0", + "shortid": "^2.2.16", + "url": "^0.11.0", + "vue": "3.2.47", + "vue-echarts": "^6.6.0", + "vue-grid-layout": "3.0.0-beta1", + "vue-i18n": "9.2.2", + "vue-router": "^4.1.6", + "vue-simple-uploader": "^1.0.1", + "vue-types": "^5.0.2", + "vuedraggable": "^4.1.0", + "web-storage-cache": "^1.1.1" + }, + "devDependencies": { + "@commitlint/cli": "^17.4.4", + "@commitlint/config-conventional": "^17.4.4", + "@iconify/json": "^2.2.29", + "@intlify/unplugin-vue-i18n": "^0.8.2", + "@purge-icons/generated": "^0.9.0", + "@types/intro.js": "^5.1.1", + "@types/lodash-es": "^4.17.6", + "@types/node": "^18.14.2", + "@types/nprogress": "^0.2.0", + "@types/qrcode": "^1.5.0", + "@types/qs": "^6.9.7", + "@typescript-eslint/eslint-plugin": "^5.54.0", + "@typescript-eslint/parser": "^5.54.0", + "@vitejs/plugin-legacy": "^4.0.1", + "@vitejs/plugin-vue": "^4.0.0", + "@vitejs/plugin-vue-jsx": "^3.0.0", + "autoprefixer": "^10.4.13", + "consola": "^2.15.3", + "eslint": "^8.35.0", + "eslint-config-prettier": "^8.6.0", + "eslint-define-config": "^1.15.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.9.0", + "less": "^4.1.3", + "lint-staged": "^13.1.2", + "plop": "^3.1.2", + "postcss": "^8.4.21", + "postcss-html": "^1.5.0", + "postcss-less": "^6.0.0", + "prettier": "^2.8.4", + "rimraf": "^4.1.2", + "rollup": "^3.17.3", + "stylelint": "^15.2.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recommended": "^10.0.1", + "stylelint-config-standard": "^30.0.1", + "stylelint-order": "^6.0.2", + "terser": "^5.16.5", + "typescript": "4.9.5", + "unplugin-vue-define-options": "^1.2.3", + "vite": "4.1.4", + "vite-plugin-ejs": "^1.6.4", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-mock": "^2.9.6", + "vite-plugin-progress": "^0.0.6", + "vite-plugin-purge-icons": "^0.9.2", + "vite-plugin-style-import": "2.0.0", + "vite-plugin-svg-icons": "^2.0.1", + "vite-plugin-windicss": "^1.8.10", + "vue-tsc": "^1.2.0", + "windicss": "^3.5.6", + "windicss-analysis": "^0.3.5" + }, + "engines": { + "node": ">= 14.18.0" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kailong321200875/vue-element-plus-admin.git" + }, + "bugs": { + "url": "https://github.com/kailong321200875/vue-element-plus-admin/issues" + }, + "homepage": "https://github.com/kailong321200875/vue-element-plus-admin" +} diff --git a/plop/component/component.hbs b/plop/component/component.hbs new file mode 100644 index 0000000..21e5653 --- /dev/null +++ b/plop/component/component.hbs @@ -0,0 +1,11 @@ + + + diff --git a/plop/component/index.hbs b/plop/component/index.hbs new file mode 100644 index 0000000..aca6f0a --- /dev/null +++ b/plop/component/index.hbs @@ -0,0 +1,3 @@ +import {{ upperFirstName }} from './src/{{ upperFirstName }}.vue' + +export { {{ upperFirstName }} } diff --git a/plop/component/prompt.js b/plop/component/prompt.js new file mode 100644 index 0000000..98a852f --- /dev/null +++ b/plop/component/prompt.js @@ -0,0 +1,38 @@ +const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1) + +module.exports = { + description: 'Create vue component', + prompts: [ + { + type: 'input', + name: 'name', + message: '请输入组件名称(Please enter the component name)' + } + ], + actions: (data) => { + const { name } = data + const upperFirstName = toUpperCase(name) + + const actions = [] + if (name) { + actions.push({ + type: 'add', + path: `./src/components/${upperFirstName}/src/${upperFirstName}.vue`, + templateFile: './plop/component/component.hbs', + data: { + name, + upperFirstName + } + }, { + type: 'add', + path: `./src/components/${upperFirstName}/index.ts`, + templateFile: './plop/component/index.hbs', + data: { + upperFirstName + } + }) + } + + return actions + } +} diff --git a/plop/view/prompt.js b/plop/view/prompt.js new file mode 100644 index 0000000..dbf324f --- /dev/null +++ b/plop/view/prompt.js @@ -0,0 +1,37 @@ +const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1) + +module.exports = { + description: 'Create vue view', + prompts: [ + { + type: 'input', + name: 'path', + message: '请输入路径(Please enter a path)', + default: 'views' + }, + { + type: 'input', + name: 'name', + message: '请输入模块名称(Please enter module name)' + } + ], + actions: (data) => { + const { name, path } = data + const upperFirstName = toUpperCase(name) + + const actions = [] + if (name) { + actions.push({ + type: 'add', + path: `./src/${path}/${upperFirstName}.vue`, + templateFile: './plop/view/view.hbs', + data: { + name, + upperFirstName + } + }) + } + + return actions + } +} diff --git a/plop/view/view.hbs b/plop/view/view.hbs new file mode 100644 index 0000000..0ae9c15 --- /dev/null +++ b/plop/view/view.hbs @@ -0,0 +1,7 @@ + + + diff --git a/plopfile.js b/plopfile.js new file mode 100644 index 0000000..72e6af2 --- /dev/null +++ b/plopfile.js @@ -0,0 +1,7 @@ +const viewGenerator = require('./plop/view/prompt.js') +const componentGenerator = require('./plop/component/prompt.js') + +module.exports = function (plop) { + plop.setGenerator('view', viewGenerator) + plop.setGenerator('component', componentGenerator) +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..961986e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {} + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..b46239f --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,19 @@ +module.exports = { + printWidth: 100, + tabWidth: 2, + useTabs: false, + semi: false, + vueIndentScriptAndStyle: false, + singleQuote: true, + quoteProps: 'as-needed', + bracketSpacing: true, + trailingComma: 'none', + jsxSingleQuote: false, + arrowParens: 'always', + insertPragma: false, + requirePragma: false, + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + endOfLine: 'auto', + rangeStart: 0 +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5a7de08267811feb034bb0c54ab38ca417731aab GIT binary patch literal 4286 zcmcJTdr*|u8OGm?f3Tad7j|KHxq~xpVq;A@HfgF7QY~O)Go9%q6O&2POq*#slbPnf zwzf$$rcG^ZYDE+@N@~3q7I49+L1DRyKt#F7{U!nn%U;+^j39aYobU7VmKqZ!%ltTJ zSayHU`<%1G3`F1^k7e(&;Z`VJu(?k;=T7X%N)jqMq!dMyZeDC3iF!z6rw+67kxV&jDKh*=XMMH7BzC%0ZZgW^F*GR;m`LJ8Nxh;MnV_2 zo#Aeu7~L8}V>!_RD+rBki)NV^cA^7!L1e&FY)JDI!(TGDXW-mnR@`bh?a0EnGI=P9 zy>>$Z2m|CjGIt$Z+!8}0*)asCbu>FBggr}a!3yv^PwX`@<_>sE4e5ZqN9V>rmTRUF zbU(T+S`C+7@m92s-#UA`Fo4_`PSqdlNi_8Le1D!YO<=@vm4O z$%$c4iIKfW(ZR8ZbU?mO?%glC2lvBQG7OL4g2E>3CE|D(x!lD@#>|2OxWk0scT#t^ zS6+JEa8?X~2srB%*M&`SJoL{S_(0yf@}t;S27g&Z0Hpg)z+Z89e{|ZcvsAWtlnyU! zrooK~1ov;@oE;J_FsSek>;)U+*tyTdzLW6F6!^;ZVSuI_-@Jq7m6vJu4@{*G56tUW zpGZTSVx!<}kX-N$2;dyp7%$QPemsxlS=d(&zn~%_Fjb*XpK`!o{mnXPeP))mj8zZ^lWb$NF4{~3AH+q%ce-ErK%W3!iN7WAAnu?S-NPqStq)-K z^r;$jQ1|uDpfTeToz0w~uIE_Ym=?0w^$yVAEIfqUV+#(N3e zk2~QhGcbJDsny&3bvhLwfBl_1IF%8kGpjCBvvt~9zrr8d9#JR!m$M(Jd}05>Eb4nd zJ`&DTz3>To1b>z3MIY{C28N5L>h#j;Kcx$R41YcR4PV_s^-rhhbVi8kGeYX7r!U!S zQoWp`s}X+Qa=*iY4)hJLtt9w=1?Lsv?f#1(Q9@*K9WgR|GWA#mr*$@egH|Q{flqJW zfn&Lc>e2!ND{_P?Q{22wTP^vKVHzF$V&C_+Q16C#BfNFp?VUHBP>oj2B zhz=UjK_mQU{=dKMNf%Wv_mX3|SB)O*b?E`FSmwIXFFL5s)z(Cu`t>S;|CO%wi8Gz> zcD{3+@uDaWG)hk4Wq*?l=C;AD68_*>&6hq4$5NbB{u8&riVPuS;Bxqt{>2%98FcW< z-UZpz{a%6r-kQ#JN!+2ZHi^e>j@Xfb z(FH3R_9nI5>KFImbdI(Z5j@?YYO33 z34gFFoDTl5j|!g{5?GNTRI+4*l?*4In&hHK`i1Yt`Ne(~9lZ9R?`G5acM|n*SGN8s znFXUbf8oK55?7tV%W!hALlXom0foB5=@9%tUGVsTz3@jvj0}t(u4OpB%*i{yd6`D*|GZP1pkYzYvxb4teMYa#Zj_Lo>g|rUJiD`FX%!C-Qg-g2Nw4TGLa#a|M&na z8A`8a_#|~g>R_v;>eddvKnJhy{!TWvy`7|kyQ1Zf^VxYMQp~8#D(_{d@N%#l^n?dP zJ>eOEc0bbbz}_Ep9p1mVhmnEN1LG{D9z=$tSMNjqf-V*rZoG@=pcx%l%Ab3d&aIih z8eE{+mp&i+1G+xk}WiODJh<6N)JDmfKzAKZd|2|w0ncYeRMI&V=sBLj;JI14`X znA}4Vday6K$fZk1`Dluh#aSF*>g2+Oy}Ud24Cm@=8E1fdP!-j*CXKqY;$HSP>1S?o zig|DLDt@t+^XG}oKAp^97_-1JPiEbH%mN~Xwb-w{LX;}_oG4M?Bhm>5iNXX9qIrUK L_}7DgzT)vecywrn literal 0 HcmV?d00001 diff --git a/public/images/add-close.png b/public/images/add-close.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7e4d66ff9ec304f0389788c8e41dd5c92e71d5 GIT binary patch literal 466 zcmV;@0WJQCP)Px$j7da6R45gFkugidKorOCE*Bj-7eR0mItZ2uZO#skeg+2zI|xluY%J*F;Nm7I zR+|qKjaglnN^1A~=eIgA|vfIo|~XD%1ye@A3Hm-rai-8<~v7IiKGw z7O&AiwxFtESC*FvMo1+X?*ll|OTVb=11_cK$n)A#!?s^oJbr-)IY=tXUBj}Te}Xwx z9qbZPhurFhY3@l7;AX4!sxVe3oL3lQk4?*Z5|CjSLmcBeM`$H)niY|QANfbb$k4P1 zlB83NvEg$5KoL3v@lw7}IP?W@{Q^WXr|ZK|wFV&tZ-DqjFtgy$~*LQA87ohg?KKV9woAT3_F>Hom|?s{h>so zN0`!WmPmA(wtXugAINIj7@@T4b-OcLrP9@RHEgxpon$Ol1*iz4cL2JJCjbBd07*qo IM6N<$f}N_v%m4rY literal 0 HcmV?d00001 diff --git a/public/images/add-close1.png b/public/images/add-close1.png new file mode 100644 index 0000000000000000000000000000000000000000..27e1ebb5598414b7102b741762eea9c270269954 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJgXH*YQ9B=X0yv4V@2WwF9~f!-IZ<>npmD`F1ZxcM8~y(8QAah?>5K4WgZ zdDETteh#nwC*?EJLt6_?DpVU5>a8|VQ8qYiYOLGh#60O>!-SR?-{tP+^=U2dufM%5 z?^9)P+v&-JyN{e=cS;=XW{Fv)ac%eKj$I|Pu_6cgH-;VcnZ5oN->%^6a}O+<`)}hU W7yGG)+(UqlWbkzLb6Mw<&;$UL{Z_{S literal 0 HcmV?d00001 diff --git a/public/images/cancel.png b/public/images/cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..2dbe531c314c741f80a59b0c6d1f64b4cfc49c7b GIT binary patch literal 549 zcmV+=0^0qFP)Px$-$_J4R45g7Qaxx>Q4~JszSjgxT8e{EyVXt#;@T=Dw$AAo9USVAbN>L<`u~aMw5nQ@Bh-grfcker%b6;+7s22|R-0$a}b1oMF06FZc z*XuLG#5@N|OAIe%DD?r=+x(RGlKEd_DJYlJYPGfDNS^}Gyg{fKJ-TXxbHRiYjnhjt z6o3HVAkf1oijE!m?;eKR>!_&hG|1&=hBlYuz!VS)a1a5KFRg!As zwpWGYg_)N6if8Efs2*iF-=r`~M&>@Z_d&wwX@ n(H8>f{QU8Kr`2k?PfmXU;(OjyN90xK00000NkvXXu0mjf=Dh^8 literal 0 HcmV?d00001 diff --git a/public/images/check_box.png b/public/images/check_box.png new file mode 100644 index 0000000000000000000000000000000000000000..fb89fe2d17a2ba91d6383fb98ecfb6a23b070f92 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{SNcITw zWnidMV_;}#VPN_c z&H|6fVg?3oArNM~bhqvgP;jNEi(`ny<=XQLy$%O5w0^vQb>WU8$DRBe(jz2igh+Ix zI`X(ZytY)exq8CxO==n2GULDT9x>P?l zlBZv|7S6UhD*Vm*sDDqi-)j5kvKf|pA8_6Oap;NtmX|L}b-pGAf9E!xpli)|I#~(i NBTrX9mvv4FO#r=Idw&1` literal 0 HcmV?d00001 diff --git a/public/images/icon_file.png b/public/images/icon_file.png new file mode 100644 index 0000000000000000000000000000000000000000..857d35b4de37fd6ff712a3a580390057db4b6186 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^{2(?58;~rMGjRk`jKx9jP7LeL$-D$|x;EB_$>J{v;=N@`|O_^y#oB8?USW^xLq_ZHCT_v>GOp z6eecj$KE>(9`BECoRqV?VOIU$cJ4`s5|Y-WD7o5p-KzgT`NZ`z2Tr&)rm?jJp4BLH zoWaava4f!MYGUxm|LWWU#Zt&0^}RUG+eY-qHWk>Puu(#==9Sq*{CV(@hJb6Mw< G&;$T3$4$Th literal 0 HcmV?d00001 diff --git a/public/images/icon_people.png b/public/images/icon_people.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4e7af25e01b872f3ebd0cda003d43719ed8698 GIT binary patch literal 1185 zcmV;S1YY}zP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv00000008+zyMF)x010qNS#tmY3labT3lag+-G2N400ad|L_t(o!@ZbKY#T)s z#(z6&Z`#-)QT{j)36PW?aDhavBAg1^q#z_hDh@;y5LrU1gb=dG33}iV1l-ya6idK? z1F`}VDwon234ut`LpQ3ZlF~YdhBQT1o5YElI*zCtdpUTWuAQ|#OaFY*Xm{Sc{pQ;@ zGjDcQ!2trnun%|@=mnku+JQ&R=3Bs3;4fet_(hL|Zpd;gk^&9@$ADJ=mn3%vhyus; zNNDn&gam@&UBDN>fRo7C;0!RVM?!x)77_@C-vYkkZs_k7w}AKbNNCiqQ7s{21o#rD zx=$i7xdz)h2aNd4*my-DfnfL};IsQEpcseRItMo6Gh+qwreJ8y;TUy9`~NYdM?&9k z6A}o9JAi4R<{=450$q9}G!Gyw4JRL>2+Y1t7DOZv48H(WTwZxVo|<#lQ1 z#Oj74;euSMl|t-FLYml#FOU?#ysY*O;A4kW8h?W(~4ggJder>|BDgQ`ZoR^t8)k zTsp=38a#A7R)^Q4ZQ1hcLMgfjSWl&y|L11e=$Y6ATJyW_Y2dJ(SCyvn>{HEKo167z z4I@`K%BIN5Q7rC9zlyW((M?$aVqj20#NNvpydiJ$arOBQkw&T&}8tUtIqF;!! zmi*Vw<%Bho1cKq8u^by}YpUq&X-9LbwnJbyB!-2sI7oNyWEekpm9^w;tNDx`2_1&Q zFSPeDbBd;^^zCoQQ|-p>7HDpP+a(I`Ep^Q;aJkGjccDFBJk@Uc_P3*HTi@$RU^rjd zvc_k|Znkv}EC7SgcRx-`lNVKpyG1R!H|xS;6V|#-*-_=f>(N+vFoL8lW$3mPnU<^3%X79dn% z;4`C>|55V1!CO9*>^SCp2AnBU(FOzfj z-sd}e@3Z$l=Ul=yYTlB%?jRaOKu-pG0_aXaJ`?4Ek!OfOxJb+w88{E1l7IsY902pq zKyhy6wGfpu#iaW(_T;&>(O~UcL>LZ6=d^ecjxwVhB)@r4kvCP&rC?h0Vhna15a05U zmvzhlV?4lZGa)oU)gY`T2`~C*<^-JN;TRyk5@iUJ{u2=02WH3U#+@t?gLx~l^fQ04 zdxsOiq%C;8Z&(Ikq!R+AL1PP;=cemxQU;{gTF%Km6Iuwud;qy=OvK5dbznSO*FNXx zAC0F_Duh!4WMCKFmK%WIk~LF&!w0-K3fLj6_R9IunnN=V)UB z;#*etMRt+D8SEyBP>BA24XNz&&&@q(HP8|eBj`G-yw`N&47(Ua_a}c{JNIC#;LQ>c zZ%J@52#eBX+oZ=co(dFu7B_+_`iJ`5WPcM1$1{a%9hmz@=RS=z#9JEN+yoM}Oo0$h zcT1qiGb;SY2tevn_2t}gk&(vdE7&bQvZgDT^hcU5fu4?E2|8G3I8iab|K7gquCEoU$5 z%5JBNJFRknKaO*uQ=9mqjHWY+H>vpG_%#NFh)KeLhRWMjK)hw4--3B&GLYKo^GJb& zsa{u7LypzxC|sjLtHx2%<)qU! zP`3nZo8m^ECt-$gbrK8SRm_8up)dT8pUqg6i%l3fBg= z{$*+*vG!;C(8eDzs1u=NwA&WhzaG>u|830}v#QXA-W?)MP57JdZ&2~k8D~LZlE+od zcC9x7DGmOCfXTKbPond5uY{t4Og~9t!EJZ>@!c}US*&m(LIul1P<3E*PYF+b!y1s46&jZ9G)>E~#@vIv zrNK!6Ry#&Cj8M^6B~16(Gnrb2G2~^_WbVI1!jgNe$L~0z6$CY) zPY1%v!q_p)?2QWEcRtKNOy08488ElY6d*UafxkTHM!OplYsu30RIJ`>uKJ2GF7)eY zo&S7#fidP4#nCv#U`lkByrrQ!fIXQ4M9}?OXTs7^E=Uv`lszYP%y`4hVP6^Fb26gc?w<=Km@d24(*#a|^8rir9m&Xn1V!*6h>p+&>+6 zruC2rKr(DH;hOi0Z_2p0d*fZ>+pmOh{DRqYL%UibW}4)T$6pUp9OFV1R^K1Fg*^Jn_qfyaXJ|=(Za!^p|fYyl}q@ ztt?6be?P2Y$vYaVYXMAODh;-L@v}ZFVgP>PUv`Xx0Z?O+PaK|?mcb-rg>%D z(@nB;q_I@~io$sPLmjL3XgF63h$52+;RUEIb0UGMIH}`>of`I@az!kx-{EEj-~&? z+-D4sF}Z(Z+fPTx)0Y?`oZoYbje2KsfsCo%#yi#CQ#xjCRE;`H%1xyIVsN?zB2ig? zexwV&TQhS>K+zbB|EA)tO4Bsv4UsXSZ(~q?7f?~ME5-b#p+F=Uq+3E4(qujSj!MQ7dw_$hhh-=r#$r zM1|Suf^jLt#`(*p0~*S!*l92j_c}zqmZWuICO;SGnoqd3HK9XGAU~Io=K)%{0FR5{ zlFS571VAFH5UMV=)LI3&q5w5@jL&L;Qo=>PM@T^TvvSZ`^vSG%K>A>m<3_VT}#`84CVG7Zyl?0HB7!RGvCCRs4V zToam151C`YntkdX1DNc` z5Mlw7{al7MGT9HRB>6eR2mtTM)Gh|KtvsF9E2&UZ33gA6*{d0KH0e+yDRo07*qo IM6N<$f{aUdwEzGB literal 0 HcmV?d00001 diff --git a/public/images/jiaojiao.png b/public/images/jiaojiao.png new file mode 100644 index 0000000000000000000000000000000000000000..4e3c7fe68499b8a171a7fdfc7b8538bab52aabf2 GIT binary patch literal 1097 zcmbVLO=#0l91pFVb7L@M;^xJW&FN%mKKhy1Zfl#>71lYr!n%X7GFzO?J~FoqaN-ba4F|M&0V-0bzD^ zNBPW*s6gE(yBMFNo0v@y(FwF6tlEjlr(X;;L1_nM)|CIl_Vs8&$<%p94779fMZAg!=m zfD8*hF31HU+!)EToS$L+4CkjgUStK43zCNqg}qs7QOruILtpq5qsj=GBEwWF6<@{g zgI0;*1VLa}p5b{KBWQcwK(a#{_E6h`1Z>69Or$}BbS=sSxQ1dBj9=B5oB$%X>jzBSi(iyJ?YD<&-~Dxb_=K}3+iOE#G-bb`+rKk#aI8gbPNuPap&Z( zxoKbFVRW}gD!_*$9RUN2$HFR^dGYl&R+y2N;yLG2AzEz=diw^vjm6HuyHoY(60Z_-07)V>3L%5?r&J` BT>1b2 literal 0 HcmV?d00001 diff --git a/public/images/list_search.png b/public/images/list_search.png new file mode 100644 index 0000000000000000000000000000000000000000..01c2f9717242bcc5de8fb88f349050f3951324ba GIT binary patch literal 1131 zcmV-x1eE)UP)`)=)p%QbZrD5~;pOMS|;{yV;k)ikaD6 zv%x67C@94|6l{$OO3}m*h^VEa_yvN6T0}%@8*A3x?7c@$xd~}=vm5QU^SE>7{O+7N zAO9sZSlMiLg;J`Qh_(Xg0dNI?i`}mqO7&{`DRX+y05iK;C6T zu~?M1X~_!1P&2b7q8F^SPo{xS0$346QKnQX{oWEP0Il^70DA!Jcg}531J3|h=KKC~ zV@$18;9M?uPaMbZ0C>+icUKzt7=V>CGc#+3hK7D=6*vfjoka8sfW6MS2hzX?0c?ol z__kuP_;IViVHiHk%y|ITIVZ#Bv_)F$B7iIrIcsg$DzMgC5_LTh^;&BWq=9b+@F_DN z?&#>~HpbM?7M}F|?*L5szP~ChcgPr{DwT>%lyv|ech0?hwqULGegNBu=tXPo6RDj` ze$-lv!gve7A5j$bluD(;{}&zv!F@!u7r@CViqh)`ndE30K1%XFD-Uv{ye@=`nAkc06!_EwhRsq{@4sQyPw?jwFG)(dV0EhWMrh; z6f6co5L}+fo{a!xh>sAFZFKu2k)D%i$xr|w;-MYDfm*G$Z80&hKy&r?_xo*aZ6g34 z0iXa#H=7_LQItoC=!CRarBoL)OGmmP0gBGbDWxXjI2O5dDG`m=>-8pJ4z1002ovPDHLkV1il$8?gWY literal 0 HcmV?d00001 diff --git a/public/images/loading.gif b/public/images/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..979d52d83dd6a27a3fb8536352db1bbb07c6a395 GIT binary patch literal 82505 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 Ay#N3J literal 0 HcmV?d00001 diff --git a/public/images/next_level.png b/public/images/next_level.png new file mode 100644 index 0000000000000000000000000000000000000000..53d2837c9b3c6890c67e4f670fda2642c3a685d1 GIT binary patch literal 245 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 X5_)J=A58xJn+`(LK;w&=?YsX2HuGA3 literal 0 HcmV?d00001 diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..74a8b74 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/api/common/index.ts b/src/api/common/index.ts new file mode 100644 index 0000000..3f78a52 --- /dev/null +++ b/src/api/common/index.ts @@ -0,0 +1,11 @@ +import request from '@/config/axios' + +// 获取所有字典 +export const getDictApi = (): Promise => { + return request.get({ url: '/dict/list' }) +} + +// 模拟获取某个字典 +export const getDictOneApi = async (): Promise => { + return request.get({ url: '/dict/one' }) +} diff --git a/src/api/dashboard/analysis/index.ts b/src/api/dashboard/analysis/index.ts new file mode 100644 index 0000000..1f00ed0 --- /dev/null +++ b/src/api/dashboard/analysis/index.ts @@ -0,0 +1,23 @@ +import request from '@/config/axios' +import type { + AnalysisTotalTypes, + UserAccessSource, + WeeklyUserActivity, + MonthlySales +} from './types' + +export const getCountApi = (): Promise> => { + return request.get({ url: '/analysis/total' }) +} + +export const getUserAccessSourceApi = (): Promise> => { + return request.get({ url: '/analysis/userAccessSource' }) +} + +export const getWeeklyUserActivityApi = (): Promise> => { + return request.get({ url: '/analysis/weeklyUserActivity' }) +} + +export const getMonthlySalesApi = (): Promise> => { + return request.get({ url: '/analysis/monthlySales' }) +} diff --git a/src/api/dashboard/analysis/types.ts b/src/api/dashboard/analysis/types.ts new file mode 100644 index 0000000..4a239f5 --- /dev/null +++ b/src/api/dashboard/analysis/types.ts @@ -0,0 +1,22 @@ +export type AnalysisTotalTypes = { + users: number + messages: number + moneys: number + shoppings: number +} + +export type UserAccessSource = { + value: number + name: string +} + +export type WeeklyUserActivity = { + value: number + name: string +} + +export type MonthlySales = { + name: string + estimate: number + actual: number +} diff --git a/src/api/dashboard/workplace/index.ts b/src/api/dashboard/workplace/index.ts new file mode 100644 index 0000000..0880640 --- /dev/null +++ b/src/api/dashboard/workplace/index.ts @@ -0,0 +1,22 @@ +import request from '@/config/axios' +import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types' + +export const getCountApi = (): Promise> => { + return request.get({ url: '/workplace/total' }) +} + +export const getProjectApi = (): Promise> => { + return request.get({ url: '/workplace/project' }) +} + +export const getDynamicApi = (): Promise> => { + return request.get({ url: '/workplace/dynamic' }) +} + +export const getTeamApi = (): Promise> => { + return request.get({ url: '/workplace/team' }) +} + +export const getRadarApi = (): Promise> => { + return request.get({ url: '/workplace/radar' }) +} diff --git a/src/api/dashboard/workplace/types.ts b/src/api/dashboard/workplace/types.ts new file mode 100644 index 0000000..da23e74 --- /dev/null +++ b/src/api/dashboard/workplace/types.ts @@ -0,0 +1,30 @@ +export type WorkplaceTotal = { + project: number + access: number + todo: number +} + +export type Project = { + name: string + icon: string + message: string + personal: string + time: Date | number | string +} + +export type Dynamic = { + keys: string[] + time: Date | number | string +} + +export type Team = { + name: string + icon: string +} + +export type RadarData = { + personal: number + team: number + max: number + name: string +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..610a22a --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,19 @@ +import * as system from '@/modules/system/api' +import * as support from '@/modules/support/api' +import * as entityconfig from '@/modules/entityconfig/api' +import * as cip from '@/modules/cip/api' +import * as scheduler from '@/modules/scheduler/api' +import * as notification from '@/modules/notification/api' +import * as workflow from '@/modules/workflow/api' +import * as businessflow from '@/modules/businessflow/api' + +export default { + system, + support, + entityconfig, + cip, + scheduler, + notification, + workflow, + businessflow +} diff --git a/src/api/login/index.ts b/src/api/login/index.ts new file mode 100644 index 0000000..5c8c1de --- /dev/null +++ b/src/api/login/index.ts @@ -0,0 +1,34 @@ +import request from '@/config/axios' +import type { UserType } from './types' + +interface RoleParams { + roleName: string +} + +export const loginApi = (data: UserType) => { + return request.post({ + url: '/system/user/login?username=' + data.username + '&password=' + data.password, + data + }) +} + +export const loginOutApi = (): Promise => { + return request.get({ url: '/system/user/logout' }) +} + +export const getUserListApi = ({ params }: AxiosConfig) => { + return request.get<{ + total: number + list: UserType[] + }>({ url: '/user/list', params }) +} + +export const getAdminRoleApi = ( + params: RoleParams +): Promise> => { + return request.get({ url: '/role/list', params }) +} + +export const getTestRoleApi = (params: RoleParams): Promise> => { + return request.get({ url: '/role/list', params }) +} diff --git a/src/api/login/types.ts b/src/api/login/types.ts new file mode 100644 index 0000000..446a023 --- /dev/null +++ b/src/api/login/types.ts @@ -0,0 +1,12 @@ +export type UserLoginType = { + username: string + password: string +} + +export type UserType = { + username: string + password: string + role: string + roleId: string + permissions: string | string[] +} diff --git a/src/api/table/index.ts b/src/api/table/index.ts new file mode 100644 index 0000000..bd4f0b9 --- /dev/null +++ b/src/api/table/index.ts @@ -0,0 +1,18 @@ +import request from '@/config/axios' +import type { TableData } from './types' + +export const getTableListApi = (params: any): Promise => { + return request.get({ url: '/example/list', params }) +} + +export const saveTableApi = (data: Partial): Promise => { + return request.post({ url: '/example/save', data }) +} + +export const getTableDetApi = (id: string): Promise> => { + return request.get({ url: '/example/detail', params: { id } }) +} + +export const delTableListApi = (ids: string[] | number[]): Promise => { + return request.post({ url: '/example/delete', data: { ids } }) +} diff --git a/src/api/table/types.ts b/src/api/table/types.ts new file mode 100644 index 0000000..876c111 --- /dev/null +++ b/src/api/table/types.ts @@ -0,0 +1,9 @@ +export type TableData = { + id: string + author: string + title: string + content: string + importance: number + display_time: string + pageviews: number +} diff --git a/src/assets/images/add-close.png b/src/assets/images/add-close.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7e4d66ff9ec304f0389788c8e41dd5c92e71d5 GIT binary patch literal 466 zcmV;@0WJQCP)Px$j7da6R45gFkugidKorOCE*Bj-7eR0mItZ2uZO#skeg+2zI|xluY%J*F;Nm7I zR+|qKjaglnN^1A~=eIgA|vfIo|~XD%1ye@A3Hm-rai-8<~v7IiKGw z7O&AiwxFtESC*FvMo1+X?*ll|OTVb=11_cK$n)A#!?s^oJbr-)IY=tXUBj}Te}Xwx z9qbZPhurFhY3@l7;AX4!sxVe3oL3lQk4?*Z5|CjSLmcBeM`$H)niY|QANfbb$k4P1 zlB83NvEg$5KoL3v@lw7}IP?W@{Q^WXr|ZK|wFV&tZ-DqjFtgy$~*LQA87ohg?KKV9woAT3_F>Hom|?s{h>so zN0`!WmPmA(wtXugAINIj7@@T4b-OcLrP9@RHEgxpon$Ol1*iz4cL2JJCjbBd07*qo IM6N<$f}N_v%m4rY literal 0 HcmV?d00001 diff --git a/src/assets/images/add-close1.png b/src/assets/images/add-close1.png new file mode 100644 index 0000000000000000000000000000000000000000..27e1ebb5598414b7102b741762eea9c270269954 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJgXH*YQ9B=X0yv4V@2WwF9~f!-IZ<>npmD`F1ZxcM8~y(8QAah?>5K4WgZ zdDETteh#nwC*?EJLt6_?DpVU5>a8|VQ8qYiYOLGh#60O>!-SR?-{tP+^=U2dufM%5 z?^9)P+v&-JyN{e=cS;=XW{Fv)ac%eKj$I|Pu_6cgH-;VcnZ5oN->%^6a}O+<`)}hU W7yGG)+(UqlWbkzLb6Mw<&;$UL{Z_{S literal 0 HcmV?d00001 diff --git a/src/assets/images/cancel.png b/src/assets/images/cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..2dbe531c314c741f80a59b0c6d1f64b4cfc49c7b GIT binary patch literal 549 zcmV+=0^0qFP)Px$-$_J4R45g7Qaxx>Q4~JszSjgxT8e{EyVXt#;@T=Dw$AAo9USVAbN>L<`u~aMw5nQ@Bh-grfcker%b6;+7s22|R-0$a}b1oMF06FZc z*XuLG#5@N|OAIe%DD?r=+x(RGlKEd_DJYlJYPGfDNS^}Gyg{fKJ-TXxbHRiYjnhjt z6o3HVAkf1oijE!m?;eKR>!_&hG|1&=hBlYuz!VS)a1a5KFRg!As zwpWGYg_)N6if8Efs2*iF-=r`~M&>@Z_d&wwX@ n(H8>f{QU8Kr`2k?PfmXU;(OjyN90xK00000NkvXXu0mjf=Dh^8 literal 0 HcmV?d00001 diff --git a/src/assets/images/check_box.png b/src/assets/images/check_box.png new file mode 100644 index 0000000000000000000000000000000000000000..fb89fe2d17a2ba91d6383fb98ecfb6a23b070f92 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{SNcITw zWnidMV_;}#VPN_c z&H|6fVg?3oArNM~bhqvgP;jNEi(`ny<=XQLy$%O5w0^vQb>WU8$DRBe(jz2igh+Ix zI`X(ZytY)exq8CxO==n2GULDT9x>P?l zlBZv|7S6UhD*Vm*sDDqi-)j5kvKf|pA8_6Oap;NtmX|L}b-pGAf9E!xpli)|I#~(i NBTrX9mvv4FO#r=Idw&1` literal 0 HcmV?d00001 diff --git a/src/assets/images/icon_file.png b/src/assets/images/icon_file.png new file mode 100644 index 0000000000000000000000000000000000000000..857d35b4de37fd6ff712a3a580390057db4b6186 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^{2(?58;~rMGjRk`jKx9jP7LeL$-D$|x;EB_$>J{v;=N@`|O_^y#oB8?USW^xLq_ZHCT_v>GOp z6eecj$KE>(9`BECoRqV?VOIU$cJ4`s5|Y-WD7o5p-KzgT`NZ`z2Tr&)rm?jJp4BLH zoWaava4f!MYGUxm|LWWU#Zt&0^}RUG+eY-qHWk>Puu(#==9Sq*{CV(@hJb6Mw< G&;$T3$4$Th literal 0 HcmV?d00001 diff --git a/src/assets/images/icon_people.png b/src/assets/images/icon_people.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4e7af25e01b872f3ebd0cda003d43719ed8698 GIT binary patch literal 1185 zcmV;S1YY}zP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 IM6N<$f{aUdwEzGB literal 0 HcmV?d00001 diff --git a/src/assets/images/jiaojiao.png b/src/assets/images/jiaojiao.png new file mode 100644 index 0000000000000000000000000000000000000000..4e3c7fe68499b8a171a7fdfc7b8538bab52aabf2 GIT binary patch literal 1097 zcmbVLO=#0l91pFVb7L@M;^xJW&FN%mKKhy1Zfl#>71lYr!n%X7GFzO?J~FoqaN-ba4F|M&0V-0bzD^ zNBPW*s6gE(yBMFNo0v@y(FwF6tlEjlr(X;;L1_nM)|CIl_Vs8&$<%p94779fMZAg!=m zfD8*hF31HU+!)EToS$L+4CkjgUStK43zCNqg}qs7QOruILtpq5qsj=GBEwWF6<@{g zgI0;*1VLa}p5b{KBWQcwK(a#{_E6h`1Z>69Or$}BbS=sSxQ1dBj9=B5oB$%X>jzBSi(iyJ?YD<&-~Dxb_=K}3+iOE#G-bb`+rKk#aI8gbPNuPap&Z( zxoKbFVRW}gD!_*$9RUN2$HFR^dGYl&R+y2N;yLG2AzEz=diw^vjm6HuyHoY(60Z_-07)V>3L%5?r&J` BT>1b2 literal 0 HcmV?d00001 diff --git a/src/assets/images/list_search.png b/src/assets/images/list_search.png new file mode 100644 index 0000000000000000000000000000000000000000..01c2f9717242bcc5de8fb88f349050f3951324ba GIT binary patch literal 1131 zcmV-x1eE)UP)`)=)p%QbZrD5~;pOMS|;{yV;k)ikaD6 zv%x67C@94|6l{$OO3}m*h^VEa_yvN6T0}%@8*A3x?7c@$xd~}=vm5QU^SE>7{O+7N zAO9sZSlMiLg;J`Qh_(Xg0dNI?i`}mqO7&{`DRX+y05iK;C6T zu~?M1X~_!1P&2b7q8F^SPo{xS0$346QKnQX{oWEP0Il^70DA!Jcg}531J3|h=KKC~ zV@$18;9M?uPaMbZ0C>+icUKzt7=V>CGc#+3hK7D=6*vfjoka8sfW6MS2hzX?0c?ol z__kuP_;IViVHiHk%y|ITIVZ#Bv_)F$B7iIrIcsg$DzMgC5_LTh^;&BWq=9b+@F_DN z?&#>~HpbM?7M}F|?*L5szP~ChcgPr{DwT>%lyv|ech0?hwqULGegNBu=tXPo6RDj` ze$-lv!gve7A5j$bluD(;{}&zv!F@!u7r@CViqh)`ndE30K1%XFD-Uv{ye@=`nAkc06!_EwhRsq{@4sQyPw?jwFG)(dV0EhWMrh; z6f6co5L}+fo{a!xh>sAFZFKu2k)D%i$xr|w;-MYDfm*G$Z80&hKy&r?_xo*aZ6g34 z0iXa#H=7_LQItoC=!CRarBoL)OGmmP0gBGbDWxXjI2O5dDG`m=>-8pJ4z1002ovPDHLkV1il$8?gWY literal 0 HcmV?d00001 diff --git a/src/assets/images/loading.gif b/src/assets/images/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..979d52d83dd6a27a3fb8536352db1bbb07c6a395 GIT binary patch literal 82505 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 Ay#N3J literal 0 HcmV?d00001 diff --git a/src/assets/images/next_level.png b/src/assets/images/next_level.png new file mode 100644 index 0000000000000000000000000000000000000000..53d2837c9b3c6890c67e4f670fda2642c3a685d1 GIT binary patch literal 245 zcmV$wf6(|LR~3;&5)b%A^7t~Z$kNlq}E8AN1RO8GXkqpE)N-}Qo#aTtbxhaS57X@a%Z7`+Tg@(f_!baaxJfU{aF va?ZOZqAlqeFC(jYM)4I&{S zWpMHN-*xZ0ukYUH)p>E&=dAr*`<(r~UASEXAX@60>Hr7?0HC`BZkK^5fZ#3&2?&Xa z3CSR&5Hd=Nd-o_PS*U66l0}G_gZXX=!NmA^_{CtlauO19x)$0GwJnhL_Q-_DgqD_5 z(Es;<+g<>Q58MDZU=SOC1qFejpxb_c9ssbgz;}@U8z3wkFg7mU?Gk|h_X+@TAqs#X zia80nZ-z*uFcD zP)Y!!L(=9e+rcumPh+2YXE(J-QejRVpO0I!gpo~}BLMlcHz=;JP-D~&6C7Ho@}#Q6+@<03;Kb8wmX_-jgx(e>OVud(P*XF^Vl16sN3 zL9VSB`M_g%o1P)BrQX9$svj~GgHrVO^-|)7rKENg&rc-;oC!k)8e~5)HYqtZ2DASDJ8VEC2{_$OcaQB$S8L5D^GQ? z+s$*j6EqD70RKm}|D$LS78v_qHDf{U_Uv61dt8A`-ZyYSRaJk{$HGGBBjlXX+YC8V zKwC6zGPAqf`cyz$pIyrm(T+CDOVQ!GC`}aSwxBW1ad%G@#B25L_H>uJj_SdwyELe? z%Fe*gzZu@k_u1M3aj&S$SqNz`-S2$GASYKgNd~J_^|UX_DG1h-kMkwvwcS#nMa04V7ZUszQk&4N$!hAgCTqpAQhQTk8ON*f0ofdV2=AQy@IIWQN=Pws^DCr*gw;EVeJmQO)u{x?i}GaVh_sL#~;X+7GB*sV+YkAKOH z1g^{+L!LhAjVPHKFZ{%Zl7;fP!+=RU0=KZEB1ma_T2b`3fZ#yb9jF2X0D-ZvvGBpT z*tl5WJ1!7#2MWcx$0`I-!X;-DHZZh%$`YQ&E-0d0{DFeQJ6%-8$i8tZqKVR{uM@jzyiT0o z7c1-kPHi_&7&CHY^{doMimEv}v39dY#&Lgi=@(n{fNjE>B(JpcOwknSb2+tX++3cg z32t~n^3u%`AtMKa_^FzDw{tyaj}mT_SWwV&0ibLN*!>>|Owa%LbKvT4stf0)h_`1JuTz zwF(^utK=XF|Ct&@jJM8u8IOb7jQc*V9HTkqI4CC2!JgUU0{6MLFg9yAHpz3dDZx!@ z%o`8A+h+QIWx*?}R6xxZ68zM4BDx4){0`?SOX0o-f_=pGF+*6D2_s#3Xv5In#4PWP zm^HIklb(YU7j;~7_apAK8Th|gPZ1_+9E4lGMe)LS?_tSUeYcu}830qZ-I%ywSJLBQCSZ*o5 zCo0nC;jboG#KEqAdOUJ=+8r_QQkfjYgT#Y^(;!X6g@~ z_AifVbfl{4(J4zV*gr{36(ao(#RMe|fy1^js-iSUeI6-0np|F+Dw&E=9G_ecKU^vA zWN}f?U8B~?+h<|4H%<+0JW1%?Uve+}TEc~Fp?(8cl~Ve?ztZD{ z{~Twy!MMdpLAL%yl|zp!)gkn3(ZRQbSoH%#jIxSu*UBhKF_KY*!St}JmGuYDa}dV@ z_oh&%3MK5Gv1|}-V*SUam&DxL^F$R6P_Yf^%0k_9LI(d(DHl zPM&QTmtZpFK!M%pmuP3^N0zjRnhP%sU!^J~xher`D*M--%wWo?s)K_BH}(j((H1w? z7Z*GPd{)eY%@U4bUG2<~q*Uc((k`5%BAC@?DR(L0zLv)#<0>7~;bCt|0bO7%Ianpn zDn!%zLBCUU+O+z^aS_=Pt+MKzoWSkmc^{qLsu%`6;^r1aV{6ZqkAcYR77qj>|qEPS9${R0!P!M8cHKDuKPmO5kT`Ds^pNeal!XZCTLU=7b z{t^W-SpZzaE=Oecj8B7_iQm%=1)|lV(V(*nM^=yN!W*P~prKXZr*1bYLCYGHz^*?%h;PzP7OMlZ4Qx z{1%wAwuzq9MLM?!Mov+J?XDQ(a>oiy-+x zA5mNxTAM+pK^nSlVjC!DnfL{tR3swA>jL(F3!ReXLiLOu(mk)1Ad}H`k zDvyCLitK~}eGA+p|MqP!|JQHE(p$h(&Y~nosuYpdE>*XXA_t3$HF`8MP?;7g$AtFT z>28_cZ~Zc6)KBIYJ-A!6ezqZLJ0&`AJUW~N-q~jDs>2y!A+bVs>7Em=xlUaQHOtgA z)_5}rgeQT{prumS(l!aZ9s5J4+i-OWokXFjcY=$+q2NBgNtz7!HKOs$6>q+a$j}x8 z0|<95@%!M{&R^n|q_h5kYKUA9Y7M*SMm(G6<~Ls}3dg@m+*lS}DJj;lW>r*&VtU8t}8EzuB{!;QTLJr)#bnPeko9@_>%zJS|VkP zq(@GhDofDSCE1DSSyHNOve6_Tsc)O;jy!WK4{C5AfYY#(MCD_wj;l7OhwS&Owz;OQ z+Av;ow)s^N=-F9Ggp)!MI8FhVfZd9(Xa_%RD3J~3ahc1ri>JYDXehfQdP2SdhX6zX z06!G{F*%?|ff9Uo`jcl9VbGXN<*o?c9%Qcl!`HD!-*kV}U_C0F#y85{Nsrl#h%Ny0 zGhdHH-|}^l3G5c=HPMhdy0rPtsXd8UuT=QQhu-{%>ElrAU^o9n`5$7#;`4Lwj$|$k ze>h$|+(tcL=ESL?%JG zB%dfqN0c6Y{fo;V%L4S2J|Xw6vghC7DJdXikfoOoh5Z4nW!a5_)5XG&Jjql|qKKS|h#jfwPynLJ0 zBOycm3`flAZyQCqTfhJlF+<%mk4!n4g5%jj9OIo>Cr?ftGlPeo#N|c#7JhEmd z`*~xLs-WF2tr3RF9Z4y)J;@b>0iOk8DKtUaiRQ|+r4*)Ynp((I%| z6GaeKR9BXVY(8(q^c;0<6%VonVwDK@?IMeXA8xq@Gcn9G;@$#KYDGYlNZ?Z9+#(rJ z{`sQUY@6Y51Lq*;;-wYd(C_F0lm<*`M(e~&Octe=J*S6=E>mCK)cZ4l6~1}49`nAc zqVi>Keg?C8G^{SIKfT!5yNCo`Ap&)g_1B0rKJ7Focn>aKTVsHkx`_JMX{3tj$u>at zI0SBi$RkQF5%Pnj@VTx}*IL*ReCd#}IepcO#$zDEDdF=sdQab9>X{W|0q|qoCly&* zkrz3ljI^UWbrCq$Sal5;xgXQ{kL=wJWiAMvHHi4wlFVzedxwrHzxLmKU{l?0foUp! zxs?pYcFW}F(iw0GI%Y^X`%^z1YcYW+WS4A;aeK0y4$9x`n(=-AWoz<%9~9WzM3?tf zj8V%)_)lfghSRHxQx>3V&6H;~!$G~2k*)vyL+9h* zF6tK8_*R@WV+r44X30-hd~o-uDVBfaW4`imz^iwyWd5xC*;sP8E_^wJiksZA2vHjN z(CcFN<1gh-98!wbr?ZBtJBdz6GcqjloM9Rz&?okNLYl{Jqr7DcD~r*66j?E82c+Dv zRP0|fKL;#R`73fS-iECsC9$!Z+WMf)OGS(!6lwWBLZ5D;DR17`1pgDTscFN>6RZMW zIDEKJt(k8`A*6gX<>`7&e+#%B$D}~!7w)&!#?;>e4TY6BG8#Lqa1JL=OEVb(lY&hgHWmo}y^l9rQ@Ia0(-S;V1<%xVLlB}X8-sNxJojf$ zmiR_6G!Fs-?|u#=7(|?!Wprj1WZMv^t^#Mo{+YX41afAvNKDTy4qiJ#XO=Dwsu-*` z4Cq|_=(+NizBhTbH|&-)wGpvEpEMElu6=K6YU(XqYU(#nZ&KMM2{@=jDWN}zMHThr zGM5myaXMubt-3?=qw2uD`RkyMKOPa+ysoID^4sW=lUbRI15*91B2 zo6JX@O}Y1=6{<}TV}obhs!^(S1I1>71e{dFL_8$L8>!SV6_|+{nL|xI|rrLlSfjLJ4vr?#bx-ADLXnY zgilc@@O6GI!CNlI4s3moX%|TAUTsLKDDDg-)F{@VF+QmbF@Gg0^hGGjNuP~Jbxdt+ zd__@chTr1@HQmc4p09MYbO zez$-+ME&*W8Y&h_W&^6ePb80sWE(HDAh^ zi4~Lruc+lyf@zd+`#r0mg>i9E1YaGvm^(YcAKZ}u!yy2)i(^i{M->%1R<$YCVFrlXzFW1YXmrAb z)KGK^FX!mv^4#>o<|N&HW%_2uUyNKAuMMnSznr=&F${X=!u$>Fe=V55CBE}-!H_!> zMv2A14qNysIh$cP4!ekQ+J6kBzZGs_q2k1)JRjdyJZfZsyvu>XDZ}BE5g+WSz)jH! z{;YURbaD4rY^gw4mRqpF4Skk_XHWHujU|&Mcx!hSuHl)9T@*@G0!lNbZELl9Eukai zbh-8=6Z2KyBTXZ1-}`fraU%Ux)$%yF+9gRskBo9GIqpgGF<5AS>jg8XM;hx+BK&r! zE;rJg*Y;rabcZV?9Sw-Hcu8E@-LpjYEWy&GXL~VkR^a(MJgtdG{ojJ?4>5}t-j+P^+(<;A4L=tRZB~w^*Ys`;%i8B)xY+a}hH{7crjc+B=^6+Oo zH*Y&-{z2Uk)}URh-CaU3r=g>zo-K#=y=+}n#QG_*Ta{ap?J75@#D9$knHK|BvqUI^ z_46Fh)wAfUx!fAAsP2QLMmc}Qr)mFM_MdRY_n8)#DArG~RSr>3R@QnToH%m3_+J|q BLq7lj literal 0 HcmV?d00001 diff --git a/src/assets/imgs/logo.png b/src/assets/imgs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b76c71b6ba9a0f73203fbf745fe93d240a3c7802 GIT binary patch literal 9193 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 X5_)J=A58xJn+`(LK;w&=?YsX2HuGA3 literal 0 HcmV?d00001 diff --git a/src/assets/svgs/403.svg b/src/assets/svgs/403.svg new file mode 100644 index 0000000..4500596 --- /dev/null +++ b/src/assets/svgs/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/404.svg b/src/assets/svgs/404.svg new file mode 100644 index 0000000..5244d8d --- /dev/null +++ b/src/assets/svgs/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/500.svg b/src/assets/svgs/500.svg new file mode 100644 index 0000000..9c02092 --- /dev/null +++ b/src/assets/svgs/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/icon.svg b/src/assets/svgs/icon.svg new file mode 100644 index 0000000..7024bec --- /dev/null +++ b/src/assets/svgs/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/login-bg.svg b/src/assets/svgs/login-bg.svg new file mode 100644 index 0000000..bbe06c1 --- /dev/null +++ b/src/assets/svgs/login-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/login-box-bg.svg b/src/assets/svgs/login-box-bg.svg new file mode 100644 index 0000000..ab10040 --- /dev/null +++ b/src/assets/svgs/login-box-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/message.svg b/src/assets/svgs/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/src/assets/svgs/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/money.svg b/src/assets/svgs/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/src/assets/svgs/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/peoples.svg b/src/assets/svgs/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/src/assets/svgs/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/shopping.svg b/src/assets/svgs/shopping.svg new file mode 100644 index 0000000..f395bc7 --- /dev/null +++ b/src/assets/svgs/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Backtop/index.ts b/src/components/Backtop/index.ts new file mode 100644 index 0000000..96de88d --- /dev/null +++ b/src/components/Backtop/index.ts @@ -0,0 +1,3 @@ +import Backtop from './src/Backtop.vue' + +export { Backtop } diff --git a/src/components/Backtop/src/Backtop.vue b/src/components/Backtop/src/Backtop.vue new file mode 100644 index 0000000..f5c2fc4 --- /dev/null +++ b/src/components/Backtop/src/Backtop.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/Breadcrumb/index.ts b/src/components/Breadcrumb/index.ts new file mode 100644 index 0000000..93ffe70 --- /dev/null +++ b/src/components/Breadcrumb/index.ts @@ -0,0 +1,3 @@ +import Breadcrumb from './src/Breadcrumb.vue' + +export { Breadcrumb } diff --git a/src/components/Breadcrumb/src/Breadcrumb.vue b/src/components/Breadcrumb/src/Breadcrumb.vue new file mode 100644 index 0000000..bbbbc19 --- /dev/null +++ b/src/components/Breadcrumb/src/Breadcrumb.vue @@ -0,0 +1,127 @@ + + + diff --git a/src/components/Breadcrumb/src/helper.ts b/src/components/Breadcrumb/src/helper.ts new file mode 100644 index 0000000..fb3ec19 --- /dev/null +++ b/src/components/Breadcrumb/src/helper.ts @@ -0,0 +1,31 @@ +import { pathResolve } from '@/utils/routerHelper' +import type { RouteMeta } from 'vue-router' + +export const filterBreadcrumb = ( + routes: AppRouteRecordRaw[], + parentPath = '' +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const meta = route?.meta as RouteMeta + if (meta.hidden && !meta.canTo) { + continue + } + + const data: AppRouteRecordRaw = + !meta.alwaysShow && route.children?.length === 1 + ? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) } + : { ...route } + + data.path = pathResolve(parentPath, data.path) + + if (data.children) { + data.children = filterBreadcrumb(data.children, data.path) + } + if (data) { + res.push(data) + } + } + return res +} diff --git a/src/components/Collapse/index.ts b/src/components/Collapse/index.ts new file mode 100644 index 0000000..73f65a3 --- /dev/null +++ b/src/components/Collapse/index.ts @@ -0,0 +1,3 @@ +import Collapse from './src/Collapse.vue' + +export { Collapse } diff --git a/src/components/Collapse/src/Collapse.vue b/src/components/Collapse/src/Collapse.vue new file mode 100644 index 0000000..f397af5 --- /dev/null +++ b/src/components/Collapse/src/Collapse.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/components/ConfigGlobal/index.ts b/src/components/ConfigGlobal/index.ts new file mode 100644 index 0000000..dda2462 --- /dev/null +++ b/src/components/ConfigGlobal/index.ts @@ -0,0 +1,3 @@ +import ConfigGlobal from './src/ConfigGlobal.vue' + +export { ConfigGlobal } diff --git a/src/components/ConfigGlobal/src/ConfigGlobal.vue b/src/components/ConfigGlobal/src/ConfigGlobal.vue new file mode 100644 index 0000000..0a55f15 --- /dev/null +++ b/src/components/ConfigGlobal/src/ConfigGlobal.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/components/ContentDetailWrap/index.ts b/src/components/ContentDetailWrap/index.ts new file mode 100644 index 0000000..1871cac --- /dev/null +++ b/src/components/ContentDetailWrap/index.ts @@ -0,0 +1,3 @@ +import ContentDetailWrap from './src/ContentDetailWrap.vue' + +export { ContentDetailWrap } diff --git a/src/components/ContentDetailWrap/src/ContentDetailWrap.vue b/src/components/ContentDetailWrap/src/ContentDetailWrap.vue new file mode 100644 index 0000000..22aeb83 --- /dev/null +++ b/src/components/ContentDetailWrap/src/ContentDetailWrap.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/ContentWrap/index.ts b/src/components/ContentWrap/index.ts new file mode 100644 index 0000000..8c22cc8 --- /dev/null +++ b/src/components/ContentWrap/index.ts @@ -0,0 +1,3 @@ +import ContentWrap from './src/ContentWrap.vue' + +export { ContentWrap } diff --git a/src/components/ContentWrap/src/ContentWrap.vue b/src/components/ContentWrap/src/ContentWrap.vue new file mode 100644 index 0000000..4538fc0 --- /dev/null +++ b/src/components/ContentWrap/src/ContentWrap.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/ContextMenu/index.ts b/src/components/ContextMenu/index.ts new file mode 100644 index 0000000..2a7c1f0 --- /dev/null +++ b/src/components/ContextMenu/index.ts @@ -0,0 +1,10 @@ +import ContextMenu from './src/ContextMenu.vue' +import { ElDropdown } from 'element-plus' +import type { RouteLocationNormalizedLoaded } from 'vue-router' + +export interface ContextMenuExpose { + elDropdownMenuRef: ComponentRef + tagItem: RouteLocationNormalizedLoaded +} + +export { ContextMenu } diff --git a/src/components/ContextMenu/src/ContextMenu.vue b/src/components/ContextMenu/src/ContextMenu.vue new file mode 100644 index 0000000..5e77307 --- /dev/null +++ b/src/components/ContextMenu/src/ContextMenu.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/components/CountTo/index.ts b/src/components/CountTo/index.ts new file mode 100644 index 0000000..2119f02 --- /dev/null +++ b/src/components/CountTo/index.ts @@ -0,0 +1,3 @@ +import CountTo from './src/CountTo.vue' + +export { CountTo } diff --git a/src/components/CountTo/src/CountTo.vue b/src/components/CountTo/src/CountTo.vue new file mode 100644 index 0000000..cab8ea9 --- /dev/null +++ b/src/components/CountTo/src/CountTo.vue @@ -0,0 +1,180 @@ + + + diff --git a/src/components/Descriptions/index.ts b/src/components/Descriptions/index.ts new file mode 100644 index 0000000..91b0eb4 --- /dev/null +++ b/src/components/Descriptions/index.ts @@ -0,0 +1,3 @@ +import Descriptions from './src/Descriptions.vue' + +export { Descriptions } diff --git a/src/components/Descriptions/src/Descriptions.vue b/src/components/Descriptions/src/Descriptions.vue new file mode 100644 index 0000000..4b70992 --- /dev/null +++ b/src/components/Descriptions/src/Descriptions.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/components/Dialog/index.ts b/src/components/Dialog/index.ts new file mode 100644 index 0000000..1655dad --- /dev/null +++ b/src/components/Dialog/index.ts @@ -0,0 +1,3 @@ +import Dialog from './src/Dialog.vue' + +export { Dialog } diff --git a/src/components/Dialog/src/Dialog.vue b/src/components/Dialog/src/Dialog.vue new file mode 100644 index 0000000..db1315f --- /dev/null +++ b/src/components/Dialog/src/Dialog.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/Echart/index.ts b/src/components/Echart/index.ts new file mode 100644 index 0000000..4822092 --- /dev/null +++ b/src/components/Echart/index.ts @@ -0,0 +1,3 @@ +import Echart from './src/Echart.vue' + +export { Echart } diff --git a/src/components/Echart/src/Echart.vue b/src/components/Echart/src/Echart.vue new file mode 100644 index 0000000..84f23da --- /dev/null +++ b/src/components/Echart/src/Echart.vue @@ -0,0 +1,113 @@ + + + diff --git a/src/components/Editor/index.ts b/src/components/Editor/index.ts new file mode 100644 index 0000000..3fbf0a9 --- /dev/null +++ b/src/components/Editor/index.ts @@ -0,0 +1,8 @@ +import Editor from './src/Editor.vue' +import { IDomEditor } from '@wangeditor/editor' + +export interface EditorExpose { + getEditorRef: () => Promise +} + +export { Editor } diff --git a/src/components/Editor/src/Editor.vue b/src/components/Editor/src/Editor.vue new file mode 100644 index 0000000..f45b096 --- /dev/null +++ b/src/components/Editor/src/Editor.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/components/Error/index.ts b/src/components/Error/index.ts new file mode 100644 index 0000000..a52c6f9 --- /dev/null +++ b/src/components/Error/index.ts @@ -0,0 +1,3 @@ +import Error from './src/Error.vue' + +export { Error } diff --git a/src/components/Error/src/Error.vue b/src/components/Error/src/Error.vue new file mode 100644 index 0000000..8676a81 --- /dev/null +++ b/src/components/Error/src/Error.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/components/Footer/index.ts b/src/components/Footer/index.ts new file mode 100644 index 0000000..bd052e0 --- /dev/null +++ b/src/components/Footer/index.ts @@ -0,0 +1,3 @@ +import Footer from './src/Footer.vue' + +export { Footer } diff --git a/src/components/Footer/src/Footer.vue b/src/components/Footer/src/Footer.vue new file mode 100644 index 0000000..80f175f --- /dev/null +++ b/src/components/Footer/src/Footer.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/Form/index.ts b/src/components/Form/index.ts new file mode 100644 index 0000000..484c7a2 --- /dev/null +++ b/src/components/Form/index.ts @@ -0,0 +1,15 @@ +import Form from './src/Form.vue' +import { ElForm } from 'element-plus' +import { FormSchema, FormSetPropsType } from '@/types/form' + +export interface FormExpose { + setValues: (data: Recordable) => void + setProps: (props: Recordable) => void + delSchema: (field: string) => void + addSchema: (formSchema: FormSchema, index?: number) => void + setSchema: (schemaProps: FormSetPropsType[]) => void + formModel: Recordable + getElFormRef: () => ComponentRef +} + +export { Form } diff --git a/src/components/Form/src/Form.vue b/src/components/Form/src/Form.vue new file mode 100644 index 0000000..f914f03 --- /dev/null +++ b/src/components/Form/src/Form.vue @@ -0,0 +1,301 @@ + + + diff --git a/src/components/Form/src/componentMap.ts b/src/components/Form/src/componentMap.ts new file mode 100644 index 0000000..9fdb1db --- /dev/null +++ b/src/components/Form/src/componentMap.ts @@ -0,0 +1,49 @@ +import type { Component } from 'vue' +import { + ElCascader, + ElCheckboxGroup, + ElColorPicker, + ElDatePicker, + ElInput, + ElInputNumber, + ElRadioGroup, + ElRate, + ElSelect, + ElSelectV2, + ElSlider, + ElSwitch, + ElTimePicker, + ElTimeSelect, + ElTransfer, + ElAutocomplete, + ElDivider +} from 'element-plus' +import { InputPassword } from '@/components/InputPassword' +import { Editor } from '@/components/Editor' +import { ComponentName } from '@/types/components' + +const componentMap: Recordable = { + Radio: ElRadioGroup, + Checkbox: ElCheckboxGroup, + CheckboxButton: ElCheckboxGroup, + Input: ElInput, + Autocomplete: ElAutocomplete, + InputNumber: ElInputNumber, + Select: ElSelect, + Cascader: ElCascader, + Switch: ElSwitch, + Slider: ElSlider, + TimePicker: ElTimePicker, + DatePicker: ElDatePicker, + Rate: ElRate, + ColorPicker: ElColorPicker, + Transfer: ElTransfer, + Divider: ElDivider, + TimeSelect: ElTimeSelect, + SelectV2: ElSelectV2, + RadioButton: ElRadioGroup, + InputPassword: InputPassword, + Editor: Editor +} + +export { componentMap } diff --git a/src/components/Form/src/components/useRenderCheckbox.tsx b/src/components/Form/src/components/useRenderCheckbox.tsx new file mode 100644 index 0000000..16adecf --- /dev/null +++ b/src/components/Form/src/components/useRenderCheckbox.tsx @@ -0,0 +1,26 @@ +import { FormSchema } from '@/types/form' +import { ElCheckbox, ElCheckboxButton } from 'element-plus' +import { defineComponent } from 'vue' + +export const useRenderCheckbox = () => { + const renderCheckboxOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + const valueAlias = item?.componentProps?.optionsAlias?.valueField + const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType< + typeof defineComponent + > + return item?.componentProps?.options?.map((option) => { + const { value, ...other } = option + return ( + + {option[labelAlias || 'label']} + + ) + }) + } + + return { + renderCheckboxOptions + } +} diff --git a/src/components/Form/src/components/useRenderRadio.tsx b/src/components/Form/src/components/useRenderRadio.tsx new file mode 100644 index 0000000..45b65bf --- /dev/null +++ b/src/components/Form/src/components/useRenderRadio.tsx @@ -0,0 +1,26 @@ +import { FormSchema } from '@/types/form' +import { ElRadio, ElRadioButton } from 'element-plus' +import { defineComponent } from 'vue' + +export const useRenderRadio = () => { + const renderRadioOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + const valueAlias = item?.componentProps?.optionsAlias?.valueField + const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType< + typeof defineComponent + > + return item?.componentProps?.options?.map((option) => { + const { value, ...other } = option + return ( + + {option[labelAlias || 'label']} + + ) + }) + } + + return { + renderRadioOptions + } +} diff --git a/src/components/Form/src/components/useRenderSelect.tsx b/src/components/Form/src/components/useRenderSelect.tsx new file mode 100644 index 0000000..e4b5a26 --- /dev/null +++ b/src/components/Form/src/components/useRenderSelect.tsx @@ -0,0 +1,57 @@ +import { ElOption, ElOptionGroup } from 'element-plus' +import { getSlot } from '@/utils/tsxHelper' +import { Slots } from 'vue' +import { FormSchema } from '@/types/form' +import { ComponentOptions } from '@/types/components' + +export const useRenderSelect = (slots: Slots) => { + // 渲染 select options + const renderSelectOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + return item?.componentProps?.options?.map((option) => { + if (option?.options?.length) { + return ( + + {() => { + return option?.options?.map((v) => { + return renderSelectOptionItem(item, v) + }) + }} + + ) + } else { + return renderSelectOptionItem(item, option) + } + }) + } + + // 渲染 select option item + const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + const valueAlias = item?.componentProps?.optionsAlias?.valueField + + const { label, value, ...other } = option + + return ( + + {{ + default: () => + // option 插槽名规则,{field}-option + item?.componentProps?.optionsSlot + ? getSlot(slots, `${item.field}-option`, { item: option }) + : undefined + }} + + ) + } + + return { + renderSelectOptions + } +} diff --git a/src/components/Form/src/helper.ts b/src/components/Form/src/helper.ts new file mode 100644 index 0000000..a802d46 --- /dev/null +++ b/src/components/Form/src/helper.ts @@ -0,0 +1,150 @@ +import { useI18n } from '@/hooks/web/useI18n' +import type { Slots } from 'vue' +import { getSlot } from '@/utils/tsxHelper' +import { PlaceholderMoel } from './types' +import { FormSchema } from '@/types/form' +import { ColProps } from '@/types/components' + +const { t } = useI18n() + +/** + * + * @param schema 对应组件数据 + * @returns 返回提示信息对象 + * @description 用于自动设置placeholder + */ +export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => { + const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword'] + const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect'] + if (textMap.includes(schema?.component as string)) { + return { + placeholder: t('common.inputText') + } + } + if (selectMap.includes(schema?.component as string)) { + // 一些范围选择器 + const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange'] + if ( + twoTextMap.includes( + (schema?.componentProps?.type || schema?.componentProps?.isRange) as string + ) + ) { + return { + startPlaceholder: t('common.startTimeText'), + endPlaceholder: t('common.endTimeText'), + rangeSeparator: '-' + } + } else { + return { + placeholder: t('common.selectText') + } + } + } + return {} +} + +/** + * + * @param col 内置栅格 + * @returns 返回栅格属性 + * @description 合并传入进来的栅格属性 + */ +export const setGridProp = (col: ColProps = {}): ColProps => { + const colProps: ColProps = { + // 如果有span,代表用户优先级更高,所以不需要默认栅格 + ...(col.span + ? {} + : { + xs: 24, + sm: 12, + md: 12, + lg: 12, + xl: 12 + }), + ...col + } + return colProps +} + +/** + * + * @param item 传入的组件属性 + * @returns 默认添加 clearable 属性 + */ +export const setComponentProps = (item: FormSchema): Recordable => { + const notNeedClearable = ['ColorPicker'] + const componentProps: Recordable = notNeedClearable.includes(item.component as string) + ? { ...item.componentProps } + : { + clearable: true, + ...item.componentProps + } + // 需要删除额外的属性 + delete componentProps?.slots + return componentProps +} + +/** + * + * @param slots 插槽 + * @param slotsProps 插槽属性 + * @param field 字段名 + */ +export const setItemComponentSlots = ( + slots: Slots, + slotsProps: Recordable = {}, + field: string +): Recordable => { + const slotObj: Recordable = {} + for (const key in slotsProps) { + if (slotsProps[key]) { + // 由于组件有可能重复,需要有一个唯一的前缀 + slotObj[key] = (data: Recordable) => { + return getSlot(slots, `${field}-${key}`, data) + } + } + } + return slotObj +} + +/** + * + * @param schema Form表单结构化数组 + * @param formModel FormMoel + * @returns FormMoel + * @description 生成对应的formModel + */ +export const initModel = (schema: FormSchema[], formModel: Recordable) => { + const model: Recordable = { ...formModel } + schema.map((v) => { + // 如果是hidden,就删除对应的值 + if (v.hidden) { + delete model[v.field] + } else if (v.component && v.component !== 'Divider') { + const hasField = Reflect.has(model, v.field) + // 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值 + model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : '' + } + }) + return model +} + +/** + * @param slots 插槽 + * @param field 字段名 + * @returns 返回FormIiem插槽 + */ +export const setFormItemSlots = (slots: Slots, field: string): Recordable => { + const slotObj: Recordable = {} + if (slots[`${field}-error`]) { + slotObj['error'] = (data: Recordable) => { + return getSlot(slots, `${field}-error`, data) + } + } + if (slots[`${field}-label`]) { + slotObj['label'] = (data: Recordable) => { + return getSlot(slots, `${field}-label`, data) + } + } + return slotObj +} diff --git a/src/components/Form/src/types.ts b/src/components/Form/src/types.ts new file mode 100644 index 0000000..92a49d8 --- /dev/null +++ b/src/components/Form/src/types.ts @@ -0,0 +1,17 @@ +import { FormSchema } from '@/types/form' + +export interface PlaceholderMoel { + placeholder?: string + startPlaceholder?: string + endPlaceholder?: string + rangeSeparator?: string +} + +export type FormProps = { + schema?: FormSchema[] + isCol?: boolean + model?: Recordable + autoSetPlaceholder?: boolean + isCustom?: boolean + labelWidth?: string | number +} & Recordable diff --git a/src/components/Highlight/index.ts b/src/components/Highlight/index.ts new file mode 100644 index 0000000..3e2d9ed --- /dev/null +++ b/src/components/Highlight/index.ts @@ -0,0 +1,3 @@ +import Highlight from './src/Highlight.vue' + +export { Highlight } diff --git a/src/components/Highlight/src/Highlight.vue b/src/components/Highlight/src/Highlight.vue new file mode 100644 index 0000000..ef923a9 --- /dev/null +++ b/src/components/Highlight/src/Highlight.vue @@ -0,0 +1,65 @@ + diff --git a/src/components/Icon/index.ts b/src/components/Icon/index.ts new file mode 100644 index 0000000..cf92784 --- /dev/null +++ b/src/components/Icon/index.ts @@ -0,0 +1,3 @@ +import Icon from './src/Icon.vue' + +export { Icon } diff --git a/src/components/Icon/src/Icon.vue b/src/components/Icon/src/Icon.vue new file mode 100644 index 0000000..8c5dded --- /dev/null +++ b/src/components/Icon/src/Icon.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/components/ImageViewer/index.ts b/src/components/ImageViewer/index.ts new file mode 100644 index 0000000..3868135 --- /dev/null +++ b/src/components/ImageViewer/index.ts @@ -0,0 +1,33 @@ +import ImageViewer from './src/ImageViewer.vue' +import { isClient } from '@/utils/is' +import { createVNode, render, VNode } from 'vue' +import { ImageViewerProps } from './src/types' + +let instance: Nullable = null + +export function createImageViewer(options: ImageViewerProps) { + if (!isClient) return + const { + urlList, + initialIndex = 0, + infinite = true, + hideOnClickModal = false, + appendToBody = false, + zIndex = 2000, + show = true + } = options + + const propsData: Partial = {} + const container = document.createElement('div') + propsData.urlList = urlList + propsData.initialIndex = initialIndex + propsData.infinite = infinite + propsData.hideOnClickModal = hideOnClickModal + propsData.appendToBody = appendToBody + propsData.zIndex = zIndex + propsData.show = show + + document.body.appendChild(container) + instance = createVNode(ImageViewer, propsData) + render(instance, container) +} diff --git a/src/components/ImageViewer/src/ImageViewer.vue b/src/components/ImageViewer/src/ImageViewer.vue new file mode 100644 index 0000000..02fdb00 --- /dev/null +++ b/src/components/ImageViewer/src/ImageViewer.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/components/ImageViewer/src/types.ts b/src/components/ImageViewer/src/types.ts new file mode 100644 index 0000000..1932d74 --- /dev/null +++ b/src/components/ImageViewer/src/types.ts @@ -0,0 +1,9 @@ +export interface ImageViewerProps { + urlList?: string[] + zIndex?: number + initialIndex?: number + infinite?: boolean + hideOnClickModal?: boolean + appendToBody?: boolean + show?: boolean +} diff --git a/src/components/Infotip/index.ts b/src/components/Infotip/index.ts new file mode 100644 index 0000000..413fa5f --- /dev/null +++ b/src/components/Infotip/index.ts @@ -0,0 +1,3 @@ +import Infotip from './src/Infotip.vue' + +export { Infotip } diff --git a/src/components/Infotip/src/Infotip.vue b/src/components/Infotip/src/Infotip.vue new file mode 100644 index 0000000..3d88fe9 --- /dev/null +++ b/src/components/Infotip/src/Infotip.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/components/InputPassword/index.ts b/src/components/InputPassword/index.ts new file mode 100644 index 0000000..1dcc38e --- /dev/null +++ b/src/components/InputPassword/index.ts @@ -0,0 +1,3 @@ +import InputPassword from './src/InputPassword.vue' + +export { InputPassword } diff --git a/src/components/InputPassword/src/InputPassword.vue b/src/components/InputPassword/src/InputPassword.vue new file mode 100644 index 0000000..c678c9f --- /dev/null +++ b/src/components/InputPassword/src/InputPassword.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/src/components/LocaleDropdown/index.ts b/src/components/LocaleDropdown/index.ts new file mode 100644 index 0000000..d02e640 --- /dev/null +++ b/src/components/LocaleDropdown/index.ts @@ -0,0 +1,3 @@ +import LocaleDropdown from './src/LocaleDropdown.vue' + +export { LocaleDropdown } diff --git a/src/components/LocaleDropdown/src/LocaleDropdown.vue b/src/components/LocaleDropdown/src/LocaleDropdown.vue new file mode 100644 index 0000000..9040908 --- /dev/null +++ b/src/components/LocaleDropdown/src/LocaleDropdown.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/components/Logo/index.ts b/src/components/Logo/index.ts new file mode 100644 index 0000000..1c4224c --- /dev/null +++ b/src/components/Logo/index.ts @@ -0,0 +1,3 @@ +import Logo from './src/Logo.vue' + +export { Logo } diff --git a/src/components/Logo/src/Logo.vue b/src/components/Logo/src/Logo.vue new file mode 100644 index 0000000..bd7f9ab --- /dev/null +++ b/src/components/Logo/src/Logo.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/components/Menu/index.ts b/src/components/Menu/index.ts new file mode 100644 index 0000000..a6ec696 --- /dev/null +++ b/src/components/Menu/index.ts @@ -0,0 +1,3 @@ +import Menu from './src/Menu.vue' + +export { Menu } diff --git a/src/components/Menu/src/Menu.vue b/src/components/Menu/src/Menu.vue new file mode 100644 index 0000000..cd50db2 --- /dev/null +++ b/src/components/Menu/src/Menu.vue @@ -0,0 +1,299 @@ + + + + + diff --git a/src/components/Menu/src/components/useRenderMenuItem.tsx b/src/components/Menu/src/components/useRenderMenuItem.tsx new file mode 100644 index 0000000..17a520a --- /dev/null +++ b/src/components/Menu/src/components/useRenderMenuItem.tsx @@ -0,0 +1,59 @@ +import { ElSubMenu, ElMenuItem } from 'element-plus' +import type { RouteMeta } from 'vue-router' +import { hasOneShowingChild } from '../helper' +import { isUrl } from '@/utils/is' +import { useRenderMenuTitle } from './useRenderMenuTitle' +import { useDesign } from '@/hooks/web/useDesign' +import { pathResolve } from '@/utils/routerHelper' + +export const useRenderMenuItem = ( + // allRouters: AppRouteRecordRaw[] = [], + menuMode: 'vertical' | 'horizontal' +) => { + const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => { + return routers.map((v) => { + const meta = (v.meta ?? {}) as RouteMeta + if (!meta.hidden) { + const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v) + const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath(allRouters, v.path).join('/') + + const { renderMenuTitle } = useRenderMenuTitle() + + if ( + oneShowingChild && + (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) && + !meta?.alwaysShow + ) { + return ( + + {{ + default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta) + }} + + ) + } else { + const { getPrefixCls } = useDesign() + + const preFixCls = getPrefixCls('menu-popper') + return ( + + {{ + title: () => renderMenuTitle(meta), + default: () => renderMenuItem(v.children!, fullPath) + }} + + ) + } + } + }) + } + + return { + renderMenuItem + } +} diff --git a/src/components/Menu/src/components/useRenderMenuTitle.tsx b/src/components/Menu/src/components/useRenderMenuTitle.tsx new file mode 100644 index 0000000..5d266c7 --- /dev/null +++ b/src/components/Menu/src/components/useRenderMenuTitle.tsx @@ -0,0 +1,37 @@ +import type { RouteMeta } from 'vue-router' + +import { useI18n } from '@/hooks/web/useI18n' +import IconDisplay from '@/components/abc/IconDisplay/index.vue' +export const useRenderMenuTitle = () => { + const renderMenuTitle = (meta: RouteMeta) => { + const { t } = useI18n() + const { title = 'Please set title', icon } = meta + if (icon) { + return ( + <> + + + + {t(title as string)} + + ) + } else { + return {t(title as string)} + } + + // return icon ? ( + // <> + // + // + // + // {t(title as string)} + // + // ) : ( + // {t(title as string)} + // ) + } + + return { + renderMenuTitle + } +} diff --git a/src/components/Menu/src/helper.ts b/src/components/Menu/src/helper.ts new file mode 100644 index 0000000..b483881 --- /dev/null +++ b/src/components/Menu/src/helper.ts @@ -0,0 +1,55 @@ +import type { RouteMeta } from 'vue-router' +import { ref, unref } from 'vue' +import { findPath } from '@/utils/tree' + +type OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean } + +interface HasOneShowingChild { + oneShowingChild?: boolean + onlyOneChild?: OnlyOneChildType +} + +export const getAllParentPath = (treeData: T[], path: string) => { + const menuList = findPath(treeData, (n) => n.path === path) as AppRouteRecordRaw[] + return (menuList || []).map((item) => item.path) +} + +export const hasOneShowingChild = ( + children: AppRouteRecordRaw[] = [], + parent: AppRouteRecordRaw +): HasOneShowingChild => { + const onlyOneChild = ref() + + const showingChildren = children.filter((v) => { + const meta = (v.meta ?? {}) as RouteMeta + if (meta.hidden) { + return false + } else { + // Temp set(will be used if only has one showing child) + onlyOneChild.value = v + return true + } + }) + + // When there is only one child router, the child router is displayed by default + if (showingChildren.length === 1) { + return { + oneShowingChild: true, + onlyOneChild: unref(onlyOneChild) + } + } + + // Show parent if there are no child router to display + if (!showingChildren.length) { + onlyOneChild.value = { ...parent, path: '', noShowingChildren: true } + return { + oneShowingChild: true, + onlyOneChild: unref(onlyOneChild) + } + } + + return { + oneShowingChild: false, + onlyOneChild: unref(onlyOneChild) + } +} diff --git a/src/components/Qrcode/index.ts b/src/components/Qrcode/index.ts new file mode 100644 index 0000000..ce46161 --- /dev/null +++ b/src/components/Qrcode/index.ts @@ -0,0 +1,3 @@ +import Qrcode from './src/Qrcode.vue' + +export { Qrcode } diff --git a/src/components/Qrcode/src/Qrcode.vue b/src/components/Qrcode/src/Qrcode.vue new file mode 100644 index 0000000..6af4bed --- /dev/null +++ b/src/components/Qrcode/src/Qrcode.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/src/components/Screenfull/index.ts b/src/components/Screenfull/index.ts new file mode 100644 index 0000000..faec2d8 --- /dev/null +++ b/src/components/Screenfull/index.ts @@ -0,0 +1,3 @@ +import Screenfull from './src/Screenfull.vue' + +export { Screenfull } diff --git a/src/components/Screenfull/src/Screenfull.vue b/src/components/Screenfull/src/Screenfull.vue new file mode 100644 index 0000000..32a12e7 --- /dev/null +++ b/src/components/Screenfull/src/Screenfull.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/components/Search/index.ts b/src/components/Search/index.ts new file mode 100644 index 0000000..fcc6f16 --- /dev/null +++ b/src/components/Search/index.ts @@ -0,0 +1,3 @@ +import Search from './src/Search.vue' + +export { Search } diff --git a/src/components/Search/src/Search.vue b/src/components/Search/src/Search.vue new file mode 100644 index 0000000..3ff06ef --- /dev/null +++ b/src/components/Search/src/Search.vue @@ -0,0 +1,146 @@ + + + diff --git a/src/components/Setting/index.ts b/src/components/Setting/index.ts new file mode 100644 index 0000000..b64c9ad --- /dev/null +++ b/src/components/Setting/index.ts @@ -0,0 +1,3 @@ +import Setting from './src/Setting.vue' + +export { Setting } diff --git a/src/components/Setting/src/Setting.vue b/src/components/Setting/src/Setting.vue new file mode 100644 index 0000000..913833e --- /dev/null +++ b/src/components/Setting/src/Setting.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/src/components/Setting/src/components/ColorRadioPicker.vue b/src/components/Setting/src/components/ColorRadioPicker.vue new file mode 100644 index 0000000..bc40e10 --- /dev/null +++ b/src/components/Setting/src/components/ColorRadioPicker.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/components/Setting/src/components/InterfaceDisplay.vue b/src/components/Setting/src/components/InterfaceDisplay.vue new file mode 100644 index 0000000..e7f8876 --- /dev/null +++ b/src/components/Setting/src/components/InterfaceDisplay.vue @@ -0,0 +1,201 @@ + + + diff --git a/src/components/Setting/src/components/LayoutRadioPicker.vue b/src/components/Setting/src/components/LayoutRadioPicker.vue new file mode 100644 index 0000000..de6e80f --- /dev/null +++ b/src/components/Setting/src/components/LayoutRadioPicker.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/src/components/SizeDropdown/index.ts b/src/components/SizeDropdown/index.ts new file mode 100644 index 0000000..516488d --- /dev/null +++ b/src/components/SizeDropdown/index.ts @@ -0,0 +1,3 @@ +import SizeDropdown from './src/SizeDropdown.vue' + +export { SizeDropdown } diff --git a/src/components/SizeDropdown/src/SizeDropdown.vue b/src/components/SizeDropdown/src/SizeDropdown.vue new file mode 100644 index 0000000..cb8d9f8 --- /dev/null +++ b/src/components/SizeDropdown/src/SizeDropdown.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/components/Sticky/index.ts b/src/components/Sticky/index.ts new file mode 100644 index 0000000..5e1de45 --- /dev/null +++ b/src/components/Sticky/index.ts @@ -0,0 +1,3 @@ +import Sticky from './src/Sticky.vue' + +export { Sticky } diff --git a/src/components/Sticky/src/Sticky.vue b/src/components/Sticky/src/Sticky.vue new file mode 100644 index 0000000..6906fbc --- /dev/null +++ b/src/components/Sticky/src/Sticky.vue @@ -0,0 +1,141 @@ + + diff --git a/src/components/TabMenu/index.ts b/src/components/TabMenu/index.ts new file mode 100644 index 0000000..b5fd71c --- /dev/null +++ b/src/components/TabMenu/index.ts @@ -0,0 +1,3 @@ +import TabMenu from './src/TabMenu.vue' + +export { TabMenu } diff --git a/src/components/TabMenu/src/TabMenu.vue b/src/components/TabMenu/src/TabMenu.vue new file mode 100644 index 0000000..26540fc --- /dev/null +++ b/src/components/TabMenu/src/TabMenu.vue @@ -0,0 +1,253 @@ + + + diff --git a/src/components/TabMenu/src/helper.ts b/src/components/TabMenu/src/helper.ts new file mode 100644 index 0000000..2213133 --- /dev/null +++ b/src/components/TabMenu/src/helper.ts @@ -0,0 +1,52 @@ +import { getAllParentPath } from '@/components/Menu/src/helper' +import type { RouteMeta } from 'vue-router' +import { isUrl } from '@/utils/is' +import { cloneDeep } from 'lodash-es' +import { reactive } from 'vue' + +export type TabMapTypes = { + [key: string]: string[] +} + +export const tabPathMap = reactive({}) + +export const initTabMap = (routes: AppRouteRecordRaw[]) => { + for (const v of routes) { + const meta = (v.meta ?? {}) as RouteMeta + if (!meta?.hidden) { + tabPathMap[v.path] = [] + } + } +} + +export const filterMenusPath = ( + routes: AppRouteRecordRaw[], + allRoutes: AppRouteRecordRaw[] +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + for (const v of routes) { + let data: Nullable = null + const meta = (v.meta ?? {}) as RouteMeta + if (!meta.hidden || meta.canTo) { + const allParentPath = getAllParentPath(allRoutes, v.path) + + const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/') + + data = cloneDeep(v) + data.path = fullPath + if (v.children && data) { + data.children = filterMenusPath(v.children, allRoutes) + } + + if (data) { + res.push(data) + } + + if (allParentPath.length && Reflect.has(tabPathMap, allParentPath[0])) { + tabPathMap[allParentPath[0]].push(fullPath) + } + } + } + + return res +} diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts new file mode 100644 index 0000000..689f64a --- /dev/null +++ b/src/components/Table/index.ts @@ -0,0 +1,12 @@ +import Table from './src/Table.vue' +import { ElTable } from 'element-plus' +import { TableSetPropsType } from '@/types/table' + +export interface TableExpose { + setProps: (props: Recordable) => void + setColumn: (columnProps: TableSetPropsType[]) => void + selections: Recordable[] + elTableRef: ComponentRef +} + +export { Table } diff --git a/src/components/Table/src/Table.vue b/src/components/Table/src/Table.vue new file mode 100644 index 0000000..9ec10c9 --- /dev/null +++ b/src/components/Table/src/Table.vue @@ -0,0 +1,298 @@ + diff --git a/src/components/Table/src/helper.ts b/src/components/Table/src/helper.ts new file mode 100644 index 0000000..d8b34a8 --- /dev/null +++ b/src/components/Table/src/helper.ts @@ -0,0 +1,8 @@ +export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => { + const newIndex = index + 1 + if (reserveIndex) { + return size * (current - 1) + newIndex + } else { + return newIndex + } +} diff --git a/src/components/Table/src/types.ts b/src/components/Table/src/types.ts new file mode 100644 index 0000000..1c7ff76 --- /dev/null +++ b/src/components/Table/src/types.ts @@ -0,0 +1,26 @@ +import { Pagination, TableColumn } from '@/types/table' + +export type TableProps = { + pageSize?: number + currentPage?: number + // 是否多选 + selection?: boolean + // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip, + showOverflowTooltip?: boolean + // 表头 + columns?: TableColumn[] + // 是否展示分页 + pagination?: Pagination | undefined + // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key) + reserveSelection?: boolean + // 加载状态 + loading?: boolean + // 是否叠加索引 + reserveIndex?: boolean + // 对齐方式 + align?: 'left' | 'center' | 'right' + // 表头对齐方式 + headerAlign?: 'left' | 'center' | 'right' + data?: Recordable + expand?: boolean +} & Recordable diff --git a/src/components/TagsView/index.ts b/src/components/TagsView/index.ts new file mode 100644 index 0000000..30e604a --- /dev/null +++ b/src/components/TagsView/index.ts @@ -0,0 +1,3 @@ +import TagsView from './src/TagsView.vue' + +export { TagsView } diff --git a/src/components/TagsView/src/TagsView.vue b/src/components/TagsView/src/TagsView.vue new file mode 100644 index 0000000..345d902 --- /dev/null +++ b/src/components/TagsView/src/TagsView.vue @@ -0,0 +1,586 @@ + + + + + diff --git a/src/components/TagsView/src/helper.ts b/src/components/TagsView/src/helper.ts new file mode 100644 index 0000000..22f6a50 --- /dev/null +++ b/src/components/TagsView/src/helper.ts @@ -0,0 +1,21 @@ +import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router' +import { pathResolve } from '@/utils/routerHelper' + +export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => { + let tags: RouteLocationNormalizedLoaded[] = [] + routes.forEach((route) => { + const meta = route.meta as RouteMeta + const tagPath = pathResolve(parentPath, route.path) + if (meta?.affix) { + tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded) + } + if (route.children) { + const tempTags: RouteLocationNormalizedLoaded[] = filterAffixTags(route.children, tagPath) + if (tempTags.length >= 1) { + tags = [...tags, ...tempTags] + } + } + }) + + return tags +} diff --git a/src/components/ThemeSwitch/index.ts b/src/components/ThemeSwitch/index.ts new file mode 100644 index 0000000..823a276 --- /dev/null +++ b/src/components/ThemeSwitch/index.ts @@ -0,0 +1,3 @@ +import ThemeSwitch from './src/ThemeSwitch.vue' + +export { ThemeSwitch } diff --git a/src/components/ThemeSwitch/src/ThemeSwitch.vue b/src/components/ThemeSwitch/src/ThemeSwitch.vue new file mode 100644 index 0000000..71d8052 --- /dev/null +++ b/src/components/ThemeSwitch/src/ThemeSwitch.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/components/UserInfo/index.ts b/src/components/UserInfo/index.ts new file mode 100644 index 0000000..c3a34ab --- /dev/null +++ b/src/components/UserInfo/index.ts @@ -0,0 +1,3 @@ +import UserInfo from './src/UserInfo.vue' + +export { UserInfo } diff --git a/src/components/UserInfo/src/UserInfo.vue b/src/components/UserInfo/src/UserInfo.vue new file mode 100644 index 0000000..3bbbf34 --- /dev/null +++ b/src/components/UserInfo/src/UserInfo.vue @@ -0,0 +1,93 @@ + + + diff --git a/src/components/abc/ChangePassword/index.vue b/src/components/abc/ChangePassword/index.vue new file mode 100644 index 0000000..f4bee11 --- /dev/null +++ b/src/components/abc/ChangePassword/index.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/components/abc/CollapseTab/index.vue b/src/components/abc/CollapseTab/index.vue new file mode 100644 index 0000000..06eb7ed --- /dev/null +++ b/src/components/abc/CollapseTab/index.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/components/abc/ColumnsController/index.vue b/src/components/abc/ColumnsController/index.vue new file mode 100644 index 0000000..92b3aea --- /dev/null +++ b/src/components/abc/ColumnsController/index.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/components/abc/ContentWrap/index.ts b/src/components/abc/ContentWrap/index.ts new file mode 100644 index 0000000..8c22cc8 --- /dev/null +++ b/src/components/abc/ContentWrap/index.ts @@ -0,0 +1,3 @@ +import ContentWrap from './src/ContentWrap.vue' + +export { ContentWrap } diff --git a/src/components/abc/ContentWrap/src/ContentWrap.vue b/src/components/abc/ContentWrap/src/ContentWrap.vue new file mode 100644 index 0000000..4538fc0 --- /dev/null +++ b/src/components/abc/ContentWrap/src/ContentWrap.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/abc/CronExpression/components/day.vue b/src/components/abc/CronExpression/components/day.vue new file mode 100644 index 0000000..a582313 --- /dev/null +++ b/src/components/abc/CronExpression/components/day.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/components/abc/CronExpression/components/hour.vue b/src/components/abc/CronExpression/components/hour.vue new file mode 100644 index 0000000..6401118 --- /dev/null +++ b/src/components/abc/CronExpression/components/hour.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/components/abc/CronExpression/components/month.vue b/src/components/abc/CronExpression/components/month.vue new file mode 100644 index 0000000..3d7613f --- /dev/null +++ b/src/components/abc/CronExpression/components/month.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/src/components/abc/CronExpression/components/secondAndMinute.vue b/src/components/abc/CronExpression/components/secondAndMinute.vue new file mode 100644 index 0000000..0be81c7 --- /dev/null +++ b/src/components/abc/CronExpression/components/secondAndMinute.vue @@ -0,0 +1,195 @@ + + + + + + diff --git a/src/components/abc/CronExpression/components/week.vue b/src/components/abc/CronExpression/components/week.vue new file mode 100644 index 0000000..c83e672 --- /dev/null +++ b/src/components/abc/CronExpression/components/week.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/src/components/abc/CronExpression/components/year.vue b/src/components/abc/CronExpression/components/year.vue new file mode 100644 index 0000000..73b4063 --- /dev/null +++ b/src/components/abc/CronExpression/components/year.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/components/abc/CronExpression/index.vue b/src/components/abc/CronExpression/index.vue new file mode 100644 index 0000000..55af73e --- /dev/null +++ b/src/components/abc/CronExpression/index.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/src/components/abc/Dialog/index.ts b/src/components/abc/Dialog/index.ts new file mode 100644 index 0000000..1655dad --- /dev/null +++ b/src/components/abc/Dialog/index.ts @@ -0,0 +1,3 @@ +import Dialog from './src/Dialog.vue' + +export { Dialog } diff --git a/src/components/abc/Dialog/src/Dialog.vue b/src/components/abc/Dialog/src/Dialog.vue new file mode 100644 index 0000000..87bde1c --- /dev/null +++ b/src/components/abc/Dialog/src/Dialog.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/components/abc/DictionarySelect/DictionaryCheckboxGroup.vue b/src/components/abc/DictionarySelect/DictionaryCheckboxGroup.vue new file mode 100644 index 0000000..8ae10ca --- /dev/null +++ b/src/components/abc/DictionarySelect/DictionaryCheckboxGroup.vue @@ -0,0 +1,133 @@ + + + + diff --git a/src/components/abc/DictionarySelect/DictionaryRadioGroup.vue b/src/components/abc/DictionarySelect/DictionaryRadioGroup.vue new file mode 100644 index 0000000..80af051 --- /dev/null +++ b/src/components/abc/DictionarySelect/DictionaryRadioGroup.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/abc/DictionarySelect/DictionarySelect.vue b/src/components/abc/DictionarySelect/DictionarySelect.vue new file mode 100644 index 0000000..e57d829 --- /dev/null +++ b/src/components/abc/DictionarySelect/DictionarySelect.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/components/abc/Editor/index.ts b/src/components/abc/Editor/index.ts new file mode 100644 index 0000000..3fbf0a9 --- /dev/null +++ b/src/components/abc/Editor/index.ts @@ -0,0 +1,8 @@ +import Editor from './src/Editor.vue' +import { IDomEditor } from '@wangeditor/editor' + +export interface EditorExpose { + getEditorRef: () => Promise +} + +export { Editor } diff --git a/src/components/abc/Editor/src/Editor.vue b/src/components/abc/Editor/src/Editor.vue new file mode 100644 index 0000000..9dbf6fa --- /dev/null +++ b/src/components/abc/Editor/src/Editor.vue @@ -0,0 +1,220 @@ + + + + + + + + diff --git a/src/components/abc/EntityReference/index.vue b/src/components/abc/EntityReference/index.vue new file mode 100644 index 0000000..5cca106 --- /dev/null +++ b/src/components/abc/EntityReference/index.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/components/abc/FormCreateComponent/AttachmentManager.js b/src/components/abc/FormCreateComponent/AttachmentManager.js new file mode 100644 index 0000000..1242a9e --- /dev/null +++ b/src/components/abc/FormCreateComponent/AttachmentManager.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/support/view/attachment/attachmentManager.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/AttachmentManagerAndUploader.js b/src/components/abc/FormCreateComponent/AttachmentManagerAndUploader.js new file mode 100644 index 0000000..be52f2e --- /dev/null +++ b/src/components/abc/FormCreateComponent/AttachmentManagerAndUploader.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/support/view/attachment/attachmentManagerAndUploader.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/AttachmentUploader.js b/src/components/abc/FormCreateComponent/AttachmentUploader.js new file mode 100644 index 0000000..20a7e81 --- /dev/null +++ b/src/components/abc/FormCreateComponent/AttachmentUploader.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/support/view/attachment/attachmentUploader.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/AttachmentViewer.js b/src/components/abc/FormCreateComponent/AttachmentViewer.js new file mode 100644 index 0000000..c437406 --- /dev/null +++ b/src/components/abc/FormCreateComponent/AttachmentViewer.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/support/view/attachment/attachmentViewer.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/DictionaryRadioGroup.js b/src/components/abc/FormCreateComponent/DictionaryRadioGroup.js new file mode 100644 index 0000000..a348ccb --- /dev/null +++ b/src/components/abc/FormCreateComponent/DictionaryRadioGroup.js @@ -0,0 +1,28 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [ + //生成`checkbox`组件的`options`配置规则 + { type: 'input', field: 'code', title: '字典类型编码' } + ] + } +} diff --git a/src/components/abc/FormCreateComponent/DictionarySelect.js b/src/components/abc/FormCreateComponent/DictionarySelect.js new file mode 100644 index 0000000..d48deb0 --- /dev/null +++ b/src/components/abc/FormCreateComponent/DictionarySelect.js @@ -0,0 +1,29 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/components/abc/DictionarySelect/DictionarySelect.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [ + //生成`checkbox`组件的`options`配置规则 + // FcDesigner.makeOptionsRule('options'), + { type: 'input', field: 'code', title: '字典类型编码' } + ] + } +} diff --git a/src/components/abc/FormCreateComponent/IconPicker.js b/src/components/abc/FormCreateComponent/IconPicker.js new file mode 100644 index 0000000..719e730 --- /dev/null +++ b/src/components/abc/FormCreateComponent/IconPicker.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/components/abc/IconPicker/index.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/OrganizationMultipleSelect.js b/src/components/abc/FormCreateComponent/OrganizationMultipleSelect.js new file mode 100644 index 0000000..d1d5fcf --- /dev/null +++ b/src/components/abc/FormCreateComponent/OrganizationMultipleSelect.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/system/view/organization/treeMultipleSelect.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/OrganizationSingleSelect.js b/src/components/abc/FormCreateComponent/OrganizationSingleSelect.js new file mode 100644 index 0000000..24d68a8 --- /dev/null +++ b/src/components/abc/FormCreateComponent/OrganizationSingleSelect.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/system/view/organization/treeReference.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/RichText.js b/src/components/abc/FormCreateComponent/RichText.js new file mode 100644 index 0000000..eea8793 --- /dev/null +++ b/src/components/abc/FormCreateComponent/RichText.js @@ -0,0 +1,26 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import { Editor } from '@/components/abc/Editor' + +const label = Editor.label +const name = Editor.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(Editor), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/FormCreateComponent/UserSingleSelect.js b/src/components/abc/FormCreateComponent/UserSingleSelect.js new file mode 100644 index 0000000..33e7361 --- /dev/null +++ b/src/components/abc/FormCreateComponent/UserSingleSelect.js @@ -0,0 +1,25 @@ +import FcDesigner from '@form-create/designer' +import { markRaw } from 'vue' +import CustomComponent from '@/modules/system/view/user/treeListReference.vue' +const label = CustomComponent.label +const name = CustomComponent.name +let i = 1 +const uniqueId = () => `uni${i++}` +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + component: markRaw(CustomComponent), + field: uniqueId(), + title: label, + info: '', + props: {} + } + }, + props() { + return [] + } +} diff --git a/src/components/abc/IconDisplay/index.vue b/src/components/abc/IconDisplay/index.vue new file mode 100644 index 0000000..56df390 --- /dev/null +++ b/src/components/abc/IconDisplay/index.vue @@ -0,0 +1,12 @@ + + + + + diff --git a/src/components/abc/IconPicker/index.vue b/src/components/abc/IconPicker/index.vue new file mode 100644 index 0000000..22af250 --- /dev/null +++ b/src/components/abc/IconPicker/index.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/components/abc/ListPager/index.vue b/src/components/abc/ListPager/index.vue new file mode 100644 index 0000000..521cf0f --- /dev/null +++ b/src/components/abc/ListPager/index.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/abc/Notification/index.vue b/src/components/abc/Notification/index.vue new file mode 100644 index 0000000..7524918 --- /dev/null +++ b/src/components/abc/Notification/index.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/components/abc/PortletContainer/index.vue b/src/components/abc/PortletContainer/index.vue new file mode 100644 index 0000000..3f50881 --- /dev/null +++ b/src/components/abc/PortletContainer/index.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/components/abc/QueryButton/index.vue b/src/components/abc/QueryButton/index.vue new file mode 100644 index 0000000..21e449d --- /dev/null +++ b/src/components/abc/QueryButton/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/components/abc/QueryText/index.vue b/src/components/abc/QueryText/index.vue new file mode 100644 index 0000000..ad17f4e --- /dev/null +++ b/src/components/abc/QueryText/index.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..4d030c3 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,6 @@ +import type { App } from 'vue' +import { Icon } from './Icon' + +export const setupGlobCom = (app: App): void => { + app.component('Icon', Icon) +} diff --git a/src/config/axios/config.ts b/src/config/axios/config.ts new file mode 100644 index 0000000..3c193d5 --- /dev/null +++ b/src/config/axios/config.ts @@ -0,0 +1,46 @@ +const config: { + base_url: { + base: string + dev: string + pro: string + test: string + } + result_code: number | string + default_headers: AxiosHeaders + request_timeout: number +} = { + /** + * api请求基础路径 + */ + base_url: { + // 开发环境接口前缀 + base: 'base', + + // 打包开发环境接口前缀 + dev: '', + + // 打包生产环境接口前缀 + pro: '', + + // 打包测试环境接口前缀 + test: '' + }, + + /** + * 接口成功返回状态码 + */ + result_code: 200, + + /** + * 接口请求超时时间 + */ + request_timeout: 30000, + + /** + * 默认接口请求类型 + * 可选值:application/x-www-form-urlencoded multipart/form-data + */ + default_headers: 'application/json' +} + +export { config } diff --git a/src/config/axios/index.ts b/src/config/axios/index.ts new file mode 100644 index 0000000..61336e2 --- /dev/null +++ b/src/config/axios/index.ts @@ -0,0 +1,112 @@ +import { service } from './service' + +import { config } from './config' +import { ElMessage } from 'element-plus' +import { getToken } from '@/utils/auth' +const { default_headers } = config + +const request = (option: any) => { + const { url, method, params, data, headersType, responseType } = option + return service({ + url: url, + method, + params, + data, + responseType: responseType, + headers: { + 'Content-Type': headersType || default_headers + } + }) +} +// 处理显示信息 +const processShowInfo = (option, res) => { + // 未定义时,默认显示提示;明确设置为false时,不显示提示 + if ( + option.params === undefined || + option.params.showInfo === undefined || + option.params.showInfo === true + ) { + ElMessage.info(res.data.message) + } +} +export default { + get: (option: any) => { + return new Promise((resolve, reject) => { + request({ method: 'get', ...option }) + .then((res) => { + // 明确设置为true时,显示提示 + if (option.params && option.params.showInfo === true) { + ElMessage.info(res.data.message) + } + resolve(res.data) + }) + .catch((err) => { + reject(err) + }) + }) + }, + post: (option: any) => { + return new Promise((resolve, reject) => { + request({ method: 'post', ...option }) + .then((res) => { + processShowInfo(option, res) + resolve(res.data) + }) + .catch((err) => { + reject(err) + }) + }) + }, + delete: (option: any) => { + return new Promise((resolve, reject) => { + request({ method: 'delete', ...option }) + .then((res) => { + processShowInfo(option, res) + resolve(res.data) + }) + .catch((err) => { + reject(err) + }) + }) + }, + put: (option: any) => { + return new Promise((resolve, reject) => { + request({ method: 'put', ...option }) + .then((res) => { + processShowInfo(option, res) + resolve(res.data) + }) + .catch((err) => { + reject(err) + }) + }) + }, + // 下载 + download: (option: any) => { + const link = document.createElement('a') + link.href = import.meta.env.VITE_BASE_URL + option.url + if (link.href.indexOf('?') != -1) { + link.href += '&X-Token=' + getToken() + } else { + link.href += '?X-Token=' + getToken() + } + + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + }, + // 上传 + upload: (option: any) => { + return new Promise((resolve, reject) => { + option.headersType = 'multipart/form-data' + request({ method: 'post', ...option }) + .then((res) => { + processShowInfo(option, res) + resolve(res.data) + }) + .catch((err) => { + reject(err) + }) + }) + } +} diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts new file mode 100644 index 0000000..2bc2593 --- /dev/null +++ b/src/config/axios/service.ts @@ -0,0 +1,113 @@ +import axios, { + AxiosError, + AxiosInstance, + AxiosRequestHeaders, + AxiosResponse, + InternalAxiosRequestConfig +} from 'axios' + +import qs from 'qs' + +import { config } from './config' + +import { ElMessage } from 'element-plus' + +import { METHOD_NOT_ALLOWED, NOT_FOUND, REQUEST_SUCCESS, UNAUTHORIZED } from '@/constant/common' +import { getToken } from '@/utils/auth' +import { useCache } from '@/hooks/web/useCache' + +const { base_url } = config + +const { wsCache } = useCache() + +export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] + +// 创建axios实例 +const service: AxiosInstance = axios.create({ + baseURL: PATH_URL, // api 的 base_url + timeout: config.request_timeout // 请求超时时间 +}) + +// request拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // 读取token + const token = getToken() + if (token) { + // 若不为空,则将token放入header属性 + config.headers['X-Token'] = token + } + const urlencoded = 'application/x-www-form-urlencoded' + if ( + config.method === 'post' && + (config.headers as AxiosRequestHeaders)['Content-Type'] === urlencoded + ) { + config.data = qs.stringify(config.data) + } + // get参数编码 + if (config.method === 'get' && config.params) { + let url = config.url as string + url += '?' + const keys = Object.keys(config.params) + for (const key of keys) { + if (config.params[key] !== void 0 && config.params[key] !== null) { + url += `${key}=${encodeURIComponent(config.params[key])}&` + } + } + url = url.substring(0, url.length - 1) + config.params = {} + config.url = url + } + return config + }, + (error: AxiosError) => { + // Do something with request error + console.log(error) // for debug + Promise.reject(error) + } +) + +// response 拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + if (response.config.responseType === 'blob') { + // 如果是文件流,直接过 + return response + } else if (response.status === REQUEST_SUCCESS) { + return new Promise((resolve) => { + // 若为成功请求,直接返回业务数据 + if (response.status === REQUEST_SUCCESS) { + resolve(response) + } + }) + return response.data + } else { + ElMessage.error(response.data.message) + } + }, + (error: AxiosError) => { + if (error.response) { + if (error.response.status === UNAUTHORIZED) { + // 收到401响应时,给出友好提示 + ElMessage.warning('未登录或会话超时,请重新登录') + // 清空浏览器缓存 + wsCache.clear() + // 执行页面刷新 + setTimeout(function () { + location.reload() + }, 2000) + } else if (error.response.status === NOT_FOUND) { + ElMessage.error('未找到服务,请确认') + } else if (error.response.status === METHOD_NOT_ALLOWED) { + ElMessage.error('请求的方法不支持,请确认') + } else { + ElMessage.error(error.response.data.message) + } + return Promise.reject(error) + } else { + ElMessage.error('请求远程服务器失败') + } + } +) + +export { service } diff --git a/src/constant/common.ts b/src/constant/common.ts new file mode 100644 index 0000000..f23c5fa --- /dev/null +++ b/src/constant/common.ts @@ -0,0 +1,75 @@ +// 网络请求状态码 +// 请求成功 +export const REQUEST_SUCCESS = 200 +// 请求参数错误 +export const BAD_REQUEST = 400 +// 未授权状态码 +export const UNAUTHORIZED = 401 +// 无权访问 +export const FORBIDDEN = 403 +// 未找到资源 +export const NOT_FOUND = 404 +// 请求方法不支持 +export const METHOD_NOT_ALLOWED = 405 +// 服务器内部错误 +export const INTERNAL_SERVER_ERROR = 500 + +// token键值 +export const TOKEN_KEY = 'AbcPlatformToken' +// 用户键值 +export const USER_KEY = 'AbcPlatformUser' + +// 默认的当前页码 +export const DEFAULT_PAGE_NUM = 1 +// 默认的分页显示行数 +export const DEFAULT_PAGE_SIZE = 10 +// 分页功能默认的可选的page sizes +export const DEFAULT_PAGE_SIZE_ARR = [10, 20, 50, 100] +// 没有数据项目被选中时的标志 +export const NO_ITEM_SELECTED = '-1' + +// api公用方法 + +import request from '@/config/axios' + +export const COMMON_METHOD = { + serveUrl: '', + init() { + return request.get({ url: this.serveUrl + 'init' }) + }, + get(id) { + return request.get({ url: this.serveUrl + id }) + }, + add(params) { + return request.post({ url: this.serveUrl, data: params }) + }, + modify(params) { + return request.put({ url: this.serveUrl, data: params }) + }, + remove(id) { + return request.delete({ url: this.serveUrl + id }) + }, + page(params) { + return request.get({ url: this.serveUrl + 'page', params }) + }, + list(params) { + return request.get({ url: this.serveUrl + 'list', params }) + }, + // 批量复制新增 + addByCopy(ids) { + return request.post({ url: this.serveUrl + ids }) + }, + // 单条复制新增 + addSingleByCopy(id) { + return request.post({ url: this.serveUrl + id + '/addSingleByCopy' }) + } +} + +// 数据字典常量 +// 是/否 +export const YES_OR_NO_CODE = 'YesOrNo' +export const YES = 'YES' +export const NO = 'NO' + +// 日期时间格式 +export const DATETIME_FORMAT = 'DatetimeFormat' diff --git a/src/constant/index.ts b/src/constant/index.ts new file mode 100644 index 0000000..b1d7561 --- /dev/null +++ b/src/constant/index.ts @@ -0,0 +1,24 @@ +/** + * 将模块中的常量汇集到这里, 使用方式保持不变 this.constant.XXX + */ +import * as common from './common' +import * as system from '@/modules/system/constant' +import * as support from '@/modules/support/constant' +import * as entityconfig from '@/modules/entityconfig/constant' +import * as cip from '@/modules/cip/constant' +import * as scheduler from '@/modules/scheduler/constant' +import * as notification from '@/modules/notification/constant' +import * as workflow from '@/modules/workflow/constant' +import * as businessflow from '@/modules/businessflow/constant' + +export default { + ...common, + ...system, + ...support, + ...entityconfig, + ...cip, + ...scheduler, + ...notification, + ...workflow, + ...businessflow +} diff --git a/src/directives/index.ts b/src/directives/index.ts new file mode 100644 index 0000000..11b1da8 --- /dev/null +++ b/src/directives/index.ts @@ -0,0 +1,10 @@ +import type { App } from 'vue' +import { setupPermissionDirective } from './permission/hasPermi' + +/** + * 导出指令:v-xxx + * @methods hasPermi 按钮权限,用法: v-hasPermi + */ +export const setupPermission = (app: App) => { + setupPermissionDirective(app) +} diff --git a/src/directives/permission/hasPermi.ts b/src/directives/permission/hasPermi.ts new file mode 100644 index 0000000..0efaf28 --- /dev/null +++ b/src/directives/permission/hasPermi.ts @@ -0,0 +1,47 @@ +import type { App, Directive, DirectiveBinding } from 'vue' +import { useI18n } from '@/hooks/web/useI18n' +import { useCache } from '@/hooks/web/useCache' +import { intersection } from 'lodash-es' +import { isArray } from '@/utils/is' +import { useAppStoreWithOut } from '@/store/modules/app' + +const { t } = useI18n() +const { wsCache } = useCache() +const appStore = useAppStoreWithOut() + +// 全部权限 +const all_permission = ['*.*.*'] +const hasPermission = (value: string | string[]): boolean => { + const permissions = wsCache.get(appStore.getUserInfo).buttonPermission as string[] + if (!value) { + throw new Error(t('permission.hasPermission')) + } + if (!isArray(value)) { + return permissions?.includes(value as string) + } + if (all_permission[0] === permissions[0]) { + return true + } + return (intersection(value, permissions) as string[]).length > 0 +} +function hasPermi(el: Element, binding: DirectiveBinding) { + const value = binding.value + + const flag = hasPermission(value) + if (!flag) { + el.parentNode?.removeChild(el) + } +} +const mounted = (el: Element, binding: DirectiveBinding) => { + hasPermi(el, binding) +} + +const permiDirective: Directive = { + mounted +} + +export const setupPermissionDirective = (app: App) => { + app.directive('permission', permiDirective) +} + +export default permiDirective diff --git a/src/hooks/event/useScrollTo.ts b/src/hooks/event/useScrollTo.ts new file mode 100644 index 0000000..74fd673 --- /dev/null +++ b/src/hooks/event/useScrollTo.ts @@ -0,0 +1,62 @@ +import { ref, unref } from 'vue' + +export interface ScrollToParams { + el: HTMLElement + to: number + position: string + duration?: number + callback?: () => void +} + +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2 + if (t < 1) { + return (c / 2) * t * t + b + } + t-- + return (-c / 2) * (t * (t - 2) - 1) + b +} +const move = (el: HTMLElement, position: string, amount: number) => { + el[position] = amount +} + +export function useScrollTo({ + el, + position = 'scrollLeft', + to, + duration = 500, + callback +}: ScrollToParams) { + const isActiveRef = ref(false) + const start = el[position] + const change = to - start + const increment = 20 + let currentTime = 0 + + function animateScroll() { + if (!unref(isActiveRef)) { + return + } + currentTime += increment + const val = easeInOutQuad(currentTime, start, change, duration) + move(el, position, val) + if (currentTime < duration && unref(isActiveRef)) { + requestAnimationFrame(animateScroll) + } else { + if (callback) { + callback() + } + } + } + + function run() { + isActiveRef.value = true + animateScroll() + } + + function stop() { + isActiveRef.value = false + } + + return { start: run, stop } +} diff --git a/src/hooks/useGlobal.js b/src/hooks/useGlobal.js new file mode 100644 index 0000000..e479bcf --- /dev/null +++ b/src/hooks/useGlobal.js @@ -0,0 +1,14 @@ +import { getCurrentInstance } from 'vue' + +export default function useGetGlobalProperties() { + const { + emit, + appContext: { + app: { + config: { globalProperties } + } + } + } = getCurrentInstance() + + return { ...globalProperties } +} diff --git a/src/hooks/web/useCache.ts b/src/hooks/web/useCache.ts new file mode 100644 index 0000000..b405ef3 --- /dev/null +++ b/src/hooks/web/useCache.ts @@ -0,0 +1,17 @@ +/** + * 配置浏览器本地存储的方式,可直接存储对象数组。 + */ + +import WebStorageCache from 'web-storage-cache' + +type CacheType = 'sessionStorage' | 'localStorage' + +export const useCache = (type: CacheType = 'sessionStorage') => { + const wsCache: WebStorageCache = new WebStorageCache({ + storage: type + }) + + return { + wsCache + } +} diff --git a/src/hooks/web/useConfigGlobal.ts b/src/hooks/web/useConfigGlobal.ts new file mode 100644 index 0000000..bde9d78 --- /dev/null +++ b/src/hooks/web/useConfigGlobal.ts @@ -0,0 +1,10 @@ +import { ConfigGlobalTypes } from '@/types/configGlobal' +import { inject } from 'vue' + +export const useConfigGlobal = () => { + const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes + + return { + configGlobal + } +} diff --git a/src/hooks/web/useCrudSchemas.ts b/src/hooks/web/useCrudSchemas.ts new file mode 100644 index 0000000..5acf078 --- /dev/null +++ b/src/hooks/web/useCrudSchemas.ts @@ -0,0 +1,253 @@ +import { reactive } from 'vue' +import { eachTree, treeMap, filter } from '@/utils/tree' +import { findIndex } from '@/utils' +import { useDictStoreWithOut } from '@/store/modules/dict' +import { useI18n } from '@/hooks/web/useI18n' +import type { AxiosPromise } from 'axios' +import { FormSchema } from '@/types/form' +import { TableColumn } from '@/types/table' +import { DescriptionsSchema } from '@/types/descriptions' + +export type CrudSchema = Omit & { + search?: CrudSearchParams + table?: CrudTableParams + form?: CrudFormParams + detail?: CrudDescriptionsParams + children?: CrudSchema[] +} + +type CrudSearchParams = { + // 是否显示在查询项 + show?: boolean + // 字典名称,会去取全局的字典 + dictName?: string + // 接口 + api?: () => Promise +} & Omit + +type CrudTableParams = { + // 是否显示表头 + show?: boolean +} & Omit + +type CrudFormParams = { + // 字典名称,会去取全局的字典 + dictName?: string + // 接口 + api?: () => Promise + // 是否显示表单项 + show?: boolean +} & Omit + +type CrudDescriptionsParams = { + // 是否显示表单项 + show?: boolean +} & Omit + +const dictStore = useDictStoreWithOut() + +const { t } = useI18n() + +interface AllSchemas { + searchSchema: FormSchema[] + tableColumns: TableColumn[] + formSchema: FormSchema[] + detailSchema: DescriptionsSchema[] +} + +// 过滤所有结构 +export const useCrudSchemas = ( + crudSchema: CrudSchema[] +): { + allSchemas: AllSchemas +} => { + // 所有结构数据 + const allSchemas = reactive({ + searchSchema: [], + tableColumns: [], + formSchema: [], + detailSchema: [] + }) + + const searchSchema = filterSearchSchema(crudSchema, allSchemas) + allSchemas.searchSchema = searchSchema || [] + + const tableColumns = filterTableSchema(crudSchema) + allSchemas.tableColumns = tableColumns || [] + + const formSchema = filterFormSchema(crudSchema, allSchemas) + allSchemas.formSchema = formSchema + + const detailSchema = filterDescriptionsSchema(crudSchema) + allSchemas.detailSchema = detailSchema + + return { + allSchemas + } +} + +// 过滤 Search 结构 +const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const searchSchema: FormSchema[] = [] + + // 获取字典列表队列 + const searchRequestTask: Array<() => Promise> = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.search?.show) { + const searchSchemaItem = { + // 默认为 input + component: schemaItem.search.component || 'Input', + componentProps: {}, + ...schemaItem.search, + field: schemaItem.field, + label: schemaItem.search?.label || schemaItem.label + } + + if (searchSchemaItem.dictName) { + // 如果有 dictName 则证明是从字典中获取数据 + const dictArr = dictStore.getDictObj[searchSchemaItem.dictName] + searchSchemaItem.componentProps!.options = filterOptions(dictArr) + } else if (searchSchemaItem.api) { + searchRequestTask.push(async () => { + const res = await (searchSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => { + return v.field === searchSchemaItem.field + }) + if (index !== -1) { + allSchemas.searchSchema[index]!.componentProps!.options = filterOptions( + res, + searchSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + + // 删除不必要的字段 + delete searchSchemaItem.show + delete searchSchemaItem.dictName + + searchSchema.push(searchSchemaItem) + } + }) + + for (const task of searchRequestTask) { + task() + } + + return searchSchema +} + +// 过滤 table 结构 +const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => { + const tableColumns = treeMap(crudSchema, { + conversion: (schema: CrudSchema) => { + if (schema?.table?.show !== false) { + return { + ...schema.table, + ...schema + } + } + } + }) + + // 第一次过滤会有 undefined 所以需要二次过滤 + return filter(tableColumns as TableColumn[], (data) => { + if (data.children === void 0) { + delete data.children + } + return !!data.field + }) +} + +// 过滤 form 结构 +const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const formSchema: FormSchema[] = [] + + // 获取字典列表队列 + const formRequestTask: Array<() => Promise> = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.form?.show !== false) { + const formSchemaItem = { + // 默认为 input + component: schemaItem?.form?.component || 'Input', + componentProps: {}, + ...schemaItem.form, + field: schemaItem.field, + label: schemaItem.search?.label || schemaItem.label + } + + if (formSchemaItem.dictName) { + // 如果有 dictName 则证明是从字典中获取数据 + const dictArr = dictStore.getDictObj[formSchemaItem.dictName] + formSchemaItem.componentProps!.options = filterOptions(dictArr) + } else if (formSchemaItem.api) { + formRequestTask.push(async () => { + const res = await (formSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.formSchema, (v: FormSchema) => { + return v.field === formSchemaItem.field + }) + if (index !== -1) { + allSchemas.formSchema[index]!.componentProps!.options = filterOptions( + res, + formSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + + // 删除不必要的字段 + delete formSchemaItem.show + delete formSchemaItem.dictName + + formSchema.push(formSchemaItem) + } + }) + + for (const task of formRequestTask) { + task() + } + return formSchema +} + +// 过滤 descriptions 结构 +const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => { + const descriptionsSchema: FormSchema[] = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.detail?.show !== false) { + const descriptionsSchemaItem = { + ...schemaItem.detail, + field: schemaItem.field, + label: schemaItem.detail?.label || schemaItem.label + } + + // 删除不必要的字段 + delete descriptionsSchemaItem.show + + descriptionsSchema.push(descriptionsSchemaItem) + } + }) + + return descriptionsSchema +} + +// 给options添加国际化 +const filterOptions = (options: Recordable, labelField?: string) => { + return options?.map((v: Recordable) => { + if (labelField) { + v['labelField'] = t(v.labelField) + } else { + v['label'] = t(v.label) + } + return v + }) +} diff --git a/src/hooks/web/useDesign.ts b/src/hooks/web/useDesign.ts new file mode 100644 index 0000000..1ec349f --- /dev/null +++ b/src/hooks/web/useDesign.ts @@ -0,0 +1,18 @@ +import variables from '@/styles/variables.module.less' + +export const useDesign = () => { + const lessVariables = variables + + /** + * @param scope 类名 + * @returns 返回空间名-类名 + */ + const getPrefixCls = (scope: string) => { + return `${lessVariables.namespace}-${scope}` + } + + return { + variables: lessVariables, + getPrefixCls + } +} diff --git a/src/hooks/web/useEmitt.ts b/src/hooks/web/useEmitt.ts new file mode 100644 index 0000000..26b5d62 --- /dev/null +++ b/src/hooks/web/useEmitt.ts @@ -0,0 +1,23 @@ +import mitt from 'mitt' +import { onBeforeUnmount } from 'vue' + +interface Option { + name: string // 事件名称 + callback: Fn // 回调 +} + +const emitter = mitt() + +export const useEmitt = (option?: Option) => { + if (option) { + emitter.on(option.name, option.callback) + + onBeforeUnmount(() => { + emitter.off(option.name) + }) + } + + return { + emitter + } +} diff --git a/src/hooks/web/useForm.ts b/src/hooks/web/useForm.ts new file mode 100644 index 0000000..356ee8f --- /dev/null +++ b/src/hooks/web/useForm.ts @@ -0,0 +1,95 @@ +import type { Form, FormExpose } from '@/components/Form' +import type { ElForm } from 'element-plus' +import { ref, unref, nextTick } from 'vue' +import type { FormProps } from '@/components/Form/src/types' +import { FormSchema, FormSetPropsType } from '@/types/form' + +export const useForm = (props?: FormProps) => { + // From实例 + const formRef = ref() + + // ElForm实例 + const elFormRef = ref>() + + /** + * @param ref Form实例 + * @param elRef ElForm实例 + */ + const register = (ref: typeof Form & FormExpose, elRef: ComponentRef) => { + formRef.value = ref + elFormRef.value = elRef + } + + const getForm = async () => { + await nextTick() + const form = unref(formRef) + if (!form) { + console.error('The form is not registered. Please use the register method to register') + } + return form + } + + // 一些内置的方法 + const methods: { + setProps: (props: Recordable) => void + setValues: (data: Recordable) => void + getFormData: () => Promise + setSchema: (schemaProps: FormSetPropsType[]) => void + addSchema: (formSchema: FormSchema, index?: number) => void + delSchema: (field: string) => void + } = { + setProps: async (props: FormProps = {}) => { + const form = await getForm() + form?.setProps(props) + if (props.model) { + form?.setValues(props.model) + } + }, + + setValues: async (data: Recordable) => { + const form = await getForm() + form?.setValues(data) + }, + + /** + * @param schemaProps 需要设置的schemaProps + */ + setSchema: async (schemaProps: FormSetPropsType[]) => { + const form = await getForm() + form?.setSchema(schemaProps) + }, + + /** + * @param formSchema 需要新增数据 + * @param index 在哪里新增 + */ + addSchema: async (formSchema: FormSchema, index?: number) => { + const form = await getForm() + form?.addSchema(formSchema, index) + }, + + /** + * @param field 删除哪个数据 + */ + delSchema: async (field: string) => { + const form = await getForm() + form?.delSchema(field) + }, + + /** + * @returns form data + */ + getFormData: async (): Promise => { + const form = await getForm() + return form?.formModel as T + } + } + + props && methods.setProps(props) + + return { + register, + elFormRef, + methods + } +} diff --git a/src/hooks/web/useI18n.ts b/src/hooks/web/useI18n.ts new file mode 100644 index 0000000..f1e0e7b --- /dev/null +++ b/src/hooks/web/useI18n.ts @@ -0,0 +1,52 @@ +import { i18n } from '@/plugins/vueI18n' + +type I18nGlobalTranslation = { + (key: string): string + (key: string, locale: string): string + (key: string, locale: string, list: unknown[]): string + (key: string, locale: string, named: Record): string + (key: string, list: unknown[]): string + (key: string, named: Record): string +} + +type I18nTranslationRestParameters = [string, any] + +const getKey = (namespace: string | undefined, key: string) => { + if (!namespace) { + return key + } + if (key.startsWith(namespace)) { + return key + } + return `${namespace}.${key}` +} + +export const useI18n = ( + namespace?: string +): { + t: I18nGlobalTranslation +} => { + const normalFn = { + t: (key: string) => { + return getKey(namespace, key) + } + } + + if (!i18n) { + return normalFn + } + + const { t, ...methods } = i18n.global + + const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => { + if (!key) return '' + if (!key.includes('.') && !namespace) return key + return (t as any)(getKey(namespace, key), ...(arg as I18nTranslationRestParameters)) + } + return { + ...methods, + t: tFn + } +} + +export const t = (key: string) => key diff --git a/src/hooks/web/useIcon.ts b/src/hooks/web/useIcon.ts new file mode 100644 index 0000000..3500204 --- /dev/null +++ b/src/hooks/web/useIcon.ts @@ -0,0 +1,8 @@ +import { h } from 'vue' +import type { VNode } from 'vue' +import { Icon } from '@/components/Icon' +import { IconTypes } from '@/types/icon' + +export const useIcon = (props: IconTypes): VNode => { + return h(Icon, props) +} diff --git a/src/hooks/web/useIntro.ts b/src/hooks/web/useIntro.ts new file mode 100644 index 0000000..85604df --- /dev/null +++ b/src/hooks/web/useIntro.ts @@ -0,0 +1,47 @@ +import introJs from 'intro.js' +import { IntroJs, Step, Options } from 'intro.js' +import 'intro.js/introjs.css' +import { useI18n } from '@/hooks/web/useI18n' +import { useDesign } from '@/hooks/web/useDesign' + +export const useIntro = (setps?: Step[], options?: Options) => { + const { t } = useI18n() + + const { variables } = useDesign() + + const defaultSetps: Step[] = setps || [ + { + element: `#${variables.namespace}-menu`, + title: t('common.menu'), + intro: t('common.menuDes'), + position: 'right' + }, + { + element: `#${variables.namespace}-tool-header`, + title: t('common.tool'), + intro: t('common.toolDes'), + position: 'left' + }, + { + element: `#${variables.namespace}-tags-view`, + title: t('common.tagsView'), + intro: t('common.tagsViewDes'), + position: 'bottom' + } + ] + + const defaultOptions: Options = options || { + prevLabel: t('common.prevLabel'), + nextLabel: t('common.nextLabel'), + skipLabel: t('common.skipLabel'), + doneLabel: t('common.doneLabel') + } + + const introRef: IntroJs = introJs() + + introRef.addSteps(defaultSetps).setOptions(defaultOptions) + + return { + introRef + } +} diff --git a/src/hooks/web/useLocale.ts b/src/hooks/web/useLocale.ts new file mode 100644 index 0000000..c65070e --- /dev/null +++ b/src/hooks/web/useLocale.ts @@ -0,0 +1,35 @@ +import { i18n } from '@/plugins/vueI18n' +import { useLocaleStoreWithOut } from '@/store/modules/locale' +import { setHtmlPageLang } from '@/plugins/vueI18n/helper' + +const setI18nLanguage = (locale: LocaleType) => { + const localeStore = useLocaleStoreWithOut() + + if (i18n.mode === 'legacy') { + i18n.global.locale = locale + } else { + ;(i18n.global.locale as any).value = locale + } + localeStore.setCurrentLocale({ + lang: locale + }) + setHtmlPageLang(locale) +} + +export const useLocale = () => { + // Switching the language will change the locale of useI18n + // And submit to configuration modification + const changeLocale = async (locale: LocaleType) => { + const globalI18n = i18n.global + + const langModule = await import(`../../locales/${locale}.ts`) + + globalI18n.setLocaleMessage(locale, langModule.default) + + setI18nLanguage(locale) + } + + return { + changeLocale + } +} diff --git a/src/hooks/web/useNProgress.ts b/src/hooks/web/useNProgress.ts new file mode 100644 index 0000000..28990e7 --- /dev/null +++ b/src/hooks/web/useNProgress.ts @@ -0,0 +1,34 @@ +import { nextTick, unref } from 'vue' +import type { NProgressOptions } from 'nprogress' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import { useCssVar } from '@vueuse/core' + +const primaryColor = useCssVar('--el-color-primary', document.documentElement) + +export const useNProgress = () => { + NProgress.configure({ showSpinner: false } as NProgressOptions) + + const initColor = async () => { + await nextTick() + const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef + if (bar) { + bar.style.background = unref(primaryColor.value) + } + } + + initColor() + + const start = () => { + NProgress.start() + } + + const done = () => { + NProgress.done() + } + + return { + start, + done + } +} diff --git a/src/hooks/web/usePageLoading.ts b/src/hooks/web/usePageLoading.ts new file mode 100644 index 0000000..bb89457 --- /dev/null +++ b/src/hooks/web/usePageLoading.ts @@ -0,0 +1,18 @@ +import { useAppStoreWithOut } from '@/store/modules/app' + +const appStore = useAppStoreWithOut() + +export const usePageLoading = () => { + const loadStart = () => { + appStore.setPageLoading(true) + } + + const loadDone = () => { + appStore.setPageLoading(false) + } + + return { + loadStart, + loadDone + } +} diff --git a/src/hooks/web/useTable.ts b/src/hooks/web/useTable.ts new file mode 100644 index 0000000..5d111ee --- /dev/null +++ b/src/hooks/web/useTable.ts @@ -0,0 +1,196 @@ +import { Table, TableExpose } from '@/components/Table' +import { ElTable, ElMessageBox, ElMessage } from 'element-plus' +import { ref, reactive, watch, computed, unref, nextTick } from 'vue' +import { get } from 'lodash-es' +import type { TableProps } from '@/components/Table/src/types' +import { useI18n } from '@/hooks/web/useI18n' +import { TableSetPropsType } from '@/types/table' + +const { t } = useI18n() + +interface TableResponse { + total: number + list: T[] + pageNumber: number + pageSize: number +} + +interface UseTableConfig { + getListApi: (option: any) => Promise>> + delListApi?: (option: any) => Promise + // 返回数据格式配置 + response: { + list: string + total?: string + } + // 默认传递的参数 + defaultParams?: Recordable + props?: TableProps +} + +interface TableObject { + pageSize: number + currentPage: number + total: number + tableList: T[] + params: any + loading: boolean + currentRow: Nullable +} + +export const useTable = (config?: UseTableConfig) => { + const tableObject = reactive>({ + // 页数 + pageSize: 10, + // 当前页 + currentPage: 1, + // 总条数 + total: 10, + // 表格数据 + tableList: [], + // AxiosConfig 配置 + params: { + ...(config?.defaultParams || {}) + }, + // 加载中 + loading: true, + // 当前行的数据 + currentRow: null + }) + + const paramsObj = computed(() => { + return { + ...tableObject.params, + pageSize: tableObject.pageSize, + pageIndex: tableObject.currentPage + } + }) + + watch( + () => tableObject.currentPage, + () => { + methods.getList() + } + ) + + watch( + () => tableObject.pageSize, + () => { + // 当前页不为1时,修改页数后会导致多次调用getList方法 + if (tableObject.currentPage === 1) { + methods.getList() + } else { + tableObject.currentPage = 1 + methods.getList() + } + } + ) + + // Table实例 + const tableRef = ref() + + // ElTable实例 + const elTableRef = ref>() + + const register = (ref: typeof Table & TableExpose, elRef: ComponentRef) => { + tableRef.value = ref + elTableRef.value = unref(elRef) + } + + const getTable = async () => { + await nextTick() + const table = unref(tableRef) + if (!table) { + console.error('The table is not registered. Please use the register method to register') + } + return table + } + + const delData = async (ids: string[] | number[]) => { + const res = await (config?.delListApi && config?.delListApi(ids)) + if (res) { + ElMessage.success(t('common.delSuccess')) + + // 计算出临界点 + const currentPage = + tableObject.total % tableObject.pageSize === ids.length || tableObject.pageSize === 1 + ? tableObject.currentPage > 1 + ? tableObject.currentPage - 1 + : tableObject.currentPage + : tableObject.currentPage + + tableObject.currentPage = currentPage + methods.getList() + } + } + + const methods = { + getList: async () => { + tableObject.loading = true + const res = await config?.getListApi(unref(paramsObj)).finally(() => { + tableObject.loading = false + }) + if (res) { + tableObject.tableList = get(res.data || {}, config?.response.list as string) + tableObject.total = get(res.data || {}, config?.response?.total as string) || 0 + } + }, + setProps: async (props: TableProps = {}) => { + const table = await getTable() + table?.setProps(props) + }, + setColumn: async (columnProps: TableSetPropsType[]) => { + const table = await getTable() + table?.setColumn(columnProps) + }, + getSelections: async () => { + const table = await getTable() + return (table?.selections || []) as T[] + }, + // 与Search组件结合 + setSearchParams: (data: Recordable) => { + tableObject.currentPage = 1 + tableObject.params = Object.assign(tableObject.params, { + pageSize: tableObject.pageSize, + pageIndex: tableObject.currentPage, + ...data + }) + methods.getList() + }, + // 删除数据 + delList: async (ids: string[] | number[], multiple: boolean, message = true) => { + const tableRef = await getTable() + if (multiple) { + if (!tableRef?.selections.length) { + ElMessage.warning(t('common.delNoData')) + return + } + } else { + if (!tableObject.currentRow) { + ElMessage.warning(t('common.delNoData')) + return + } + } + if (message) { + ElMessageBox.confirm(t('common.delMessage'), t('common.delWarning'), { + confirmButtonText: t('common.delOk'), + cancelButtonText: t('common.delCancel'), + type: 'warning' + }).then(async () => { + await delData(ids) + }) + } else { + await delData(ids) + } + } + } + + config?.props && methods.setProps(config.props) + + return { + register, + elTableRef, + tableObject, + methods + } +} diff --git a/src/hooks/web/useTimeAgo.ts b/src/hooks/web/useTimeAgo.ts new file mode 100644 index 0000000..5675e5e --- /dev/null +++ b/src/hooks/web/useTimeAgo.ts @@ -0,0 +1,50 @@ +import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core' +import { computed, unref } from 'vue' +import { useLocaleStoreWithOut } from '@/store/modules/locale' + +const TIME_AGO_MESSAGE_MAP: { + 'zh-CN': UseTimeAgoMessages + en: UseTimeAgoMessages +} = { + 'zh-CN': { + justNow: '刚刚', + invalid: '无效时间', + past: (n) => (n.match(/\d/) ? `${n}前` : n), + future: (n) => (n.match(/\d/) ? `${n}后` : n), + month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`), + year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`), + day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`), + week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`), + hour: (n) => `${n} 小时`, + minute: (n) => `${n} 分钟`, + second: (n) => `${n} 秒` + }, + en: { + justNow: '刚刚', + invalid: 'Invalid Date', + past: (n) => (n.match(/\d/) ? `${n} ago` : n), + future: (n) => (n.match(/\d/) ? `in ${n}` : n), + month: (n, past) => + n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`, + year: (n, past) => + n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`, + day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`), + week: (n, past) => + n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`, + hour: (n) => `${n} hour${n > 1 ? 's' : ''}`, + minute: (n) => `${n} minute${n > 1 ? 's' : ''}`, + second: (n) => `${n} second${n > 1 ? 's' : ''}` + } +} + +export const useTimeAgo = (time: Date | number | string) => { + const localeStore = useLocaleStoreWithOut() + + const currentLocale = computed(() => localeStore.getCurrentLocale) + + const timeAgo = useTimeAgoCore(time, { + messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang] + }) + + return timeAgo +} diff --git a/src/hooks/web/useTitle.ts b/src/hooks/web/useTitle.ts new file mode 100644 index 0000000..d0eb188 --- /dev/null +++ b/src/hooks/web/useTitle.ts @@ -0,0 +1,25 @@ +import { watch, ref } from 'vue' +import { isString } from '@/utils/is' +import { useAppStoreWithOut } from '@/store/modules/app' +import { useI18n } from '@/hooks/web/useI18n' + +const appStore = useAppStoreWithOut() + +export const useTitle = (newTitle?: string) => { + const { t } = useI18n() + const title = ref( + newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle + ) + + watch( + title, + (n, o) => { + if (isString(n) && n !== o && document) { + document.title = n + } + }, + { immediate: true } + ) + + return title +} diff --git a/src/hooks/web/useValidator.ts b/src/hooks/web/useValidator.ts new file mode 100644 index 0000000..a0d36c3 --- /dev/null +++ b/src/hooks/web/useValidator.ts @@ -0,0 +1,64 @@ +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +type Callback = (error?: string | Error | undefined) => void + +interface LengthRange { + min: number + max: number + message: string +} + +export const useValidator = () => { + const required = (message?: string) => { + return { + required: true, + message: message || t('common.required') + } + } + + const lengthRange = (val: any, callback: Callback, options: LengthRange) => { + const { min, max, message } = options + if (val.length < min || val.length > max) { + callback(new Error(message)) + } else { + callback() + } + } + + const notSpace = (val: any, callback: Callback, message: string) => { + // 用户名不能有空格 + if (val.indexOf(' ') !== -1) { + callback(new Error(message)) + } else { + callback() + } + } + + const notSpecialCharacters = (val: any, callback: Callback, message: string) => { + // 密码不能是特殊字符 + if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) { + callback(new Error(message)) + } else { + callback() + } + } + + // 两个字符串是否想等 + const isEqual = (val1: string, val2: string, callback: Callback, message: string) => { + if (val1 === val2) { + callback() + } else { + callback(new Error(message)) + } + } + + return { + required, + lengthRange, + notSpace, + notSpecialCharacters, + isEqual + } +} diff --git a/src/hooks/web/useWatermark.ts b/src/hooks/web/useWatermark.ts new file mode 100644 index 0000000..4a31359 --- /dev/null +++ b/src/hooks/web/useWatermark.ts @@ -0,0 +1,55 @@ +const domSymbol = Symbol('watermark-dom') + +export function useWatermark(appendEl: HTMLElement | null = document.body) { + let func: Fn = () => {} + const id = domSymbol.toString() + const clear = () => { + const domId = document.getElementById(id) + if (domId) { + const el = appendEl + el && el.removeChild(domId) + } + window.removeEventListener('resize', func) + } + const createWatermark = (str: string) => { + clear() + + const can = document.createElement('canvas') + can.width = 300 + can.height = 240 + + const cans = can.getContext('2d') + if (cans) { + cans.rotate((-20 * Math.PI) / 120) + cans.font = '15px Vedana' + cans.fillStyle = 'rgba(0, 0, 0, 0.15)' + cans.textAlign = 'left' + cans.textBaseline = 'middle' + cans.fillText(str, can.width / 20, can.height) + } + + const div = document.createElement('div') + div.id = id + div.style.pointerEvents = 'none' + div.style.top = '0px' + div.style.left = '0px' + div.style.position = 'absolute' + div.style.zIndex = '100000000' + div.style.width = document.documentElement.clientWidth + 'px' + div.style.height = document.documentElement.clientHeight + 'px' + div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat' + const el = appendEl + el && el.appendChild(div) + return id + } + + function setWatermark(str: string) { + createWatermark(str) + func = () => { + createWatermark(str) + } + window.addEventListener('resize', func) + } + + return { setWatermark, clear } +} diff --git a/src/layout/Layout.vue b/src/layout/Layout.vue new file mode 100644 index 0000000..324da7e --- /dev/null +++ b/src/layout/Layout.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/layout/components/AppView.vue b/src/layout/components/AppView.vue new file mode 100644 index 0000000..c781344 --- /dev/null +++ b/src/layout/components/AppView.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/layout/components/ToolHeader.vue b/src/layout/components/ToolHeader.vue new file mode 100644 index 0000000..ad06d85 --- /dev/null +++ b/src/layout/components/ToolHeader.vue @@ -0,0 +1,85 @@ + + + diff --git a/src/layout/components/useRenderLayout.tsx b/src/layout/components/useRenderLayout.tsx new file mode 100644 index 0000000..d388d5f --- /dev/null +++ b/src/layout/components/useRenderLayout.tsx @@ -0,0 +1,276 @@ +import { computed } from 'vue' +import { useAppStore } from '@/store/modules/app' +import { Menu } from '@/components/Menu' +import { TabMenu } from '@/components/TabMenu' +import { TagsView } from '@/components/TagsView' +import { Logo } from '@/components/Logo' +import AppView from './AppView.vue' +import ToolHeader from './ToolHeader.vue' +import { ElScrollbar } from 'element-plus' +import { useDesign } from '@/hooks/web/useDesign' + +const { getPrefixCls } = useDesign() + +const prefixCls = getPrefixCls('layout') + +const appStore = useAppStore() + +const pageLoading = computed(() => appStore.getPageLoading) + +// 标签页 +const tagsView = computed(() => appStore.getTagsView) + +// 菜单折叠 +const collapse = computed(() => appStore.getCollapse) + +// logo +const logo = computed(() => appStore.logo) + +// 固定头部 +const fixedHeader = computed(() => appStore.getFixedHeader) + +// 是否是移动端 +const mobile = computed(() => appStore.getMobile) + +// 固定菜单 +const fixedMenu = computed(() => appStore.getFixedMenu) + +export const useRenderLayout = () => { + const renderClassic = () => { + return ( + <> +
+ {logo.value ? ( + + ) : undefined} + +
+
+ +
+ + + {tagsView.value ? ( + + ) : undefined} +
+ + +
+
+ + ) + } + + const renderTopLeft = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ +
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+
+ + ) + } + + const renderTop = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+ + ) + } + + const renderCutMenu = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ +
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+
+ + ) + } + + return { + renderClassic, + renderTopLeft, + renderTop, + renderCutMenu + } +} diff --git a/src/locales/en.ts b/src/locales/en.ts new file mode 100644 index 0000000..3934b3c --- /dev/null +++ b/src/locales/en.ts @@ -0,0 +1,443 @@ +export default { + common: { + inputText: 'Please input', + selectText: 'Please select', + startTimeText: 'Start time', + endTimeText: 'End time', + login: 'Login', + required: 'This is required', + loginOut: 'Login out', + document: 'Document', + changePassword: 'Change password', + reminder: 'Reminder', + loginOutMessage: 'Exit the system?', + back: 'Back', + ok: 'OK', + cancel: 'Cancel', + reload: 'Reload current', + closeTab: 'Close current', + closeTheLeftTab: 'Close left', + closeTheRightTab: 'Close right', + closeOther: 'Close other', + closeAll: 'Close all', + prevLabel: 'Prev', + nextLabel: 'Next', + skipLabel: 'Jump', + doneLabel: 'End', + menu: 'Menu', + menuDes: 'Menu bar rendered in routed structure', + collapse: 'Collapse', + collapseDes: 'Expand and zoom the menu bar', + tagsView: 'Tags view', + tagsViewDes: 'Used to record routing history', + tool: 'Tool', + toolDes: 'Used to set up custom systems', + query: 'Query', + reset: 'Reset', + shrink: 'Put away', + expand: 'Expand', + delMessage: 'Delete the selected data?', + delWarning: 'Warning', + delOk: 'OK', + delCancel: 'Cancel', + delNoData: 'Please select the data to delete', + delSuccess: 'Deleted successfully' + }, + error: { + noPermission: `Sorry, you don't have permission to access this page.`, + pageError: 'Sorry, the page you visited does not exist.', + networkError: 'Sorry, the server reported an error.', + returnToHome: 'Return to home' + }, + setting: { + projectSetting: 'Project setting', + theme: 'Theme', + layout: 'Layout', + systemTheme: 'System theme', + menuTheme: 'Menu theme', + interfaceDisplay: 'Interface display', + breadcrumb: 'Breadcrumb', + breadcrumbIcon: 'Breadcrumb icon', + collapseMenu: 'Collapse menu', + hamburgerIcon: 'Hamburger icon', + screenfullIcon: 'Screenfull icon', + sizeIcon: 'Size icon', + localeIcon: 'Locale icon', + tagsView: 'Tags view', + logo: 'Logo', + greyMode: 'Grey mode', + fixedHeader: 'Fixed header', + headerTheme: 'Header theme', + cutMenu: 'Cut Menu', + copy: 'Copy', + clearAndReset: 'Clear cache and reset', + copySuccess: 'Copy success', + copyFailed: 'Copy failed', + footer: 'Footer', + uniqueOpened: 'Unique opened', + tagsViewIcon: 'Tags view icon', + + reExperienced: 'Please exit the login experience again', + fixedMenu: 'Fixed menu' + }, + size: { + default: 'Default', + large: 'Large', + small: 'Small' + }, + login: { + welcome: 'Welcome to the system', + message: 'Enterprise-level general low-code development platform', + username: 'Username', + password: 'Password', + register: 'Register', + checkPassword: 'Confirm password', + login: 'Sign in', + otherLogin: 'Sign in with', + remember: 'Remember me', + hasUser: 'Existing account? Go to login', + forgetPassword: 'Forget password', + usernamePlaceholder: 'Please input username', + passwordPlaceholder: 'Please input password', + code: 'Verification code', + codePlaceholder: 'Please input verification code' + }, + router: { + login: 'Login', + level: 'Multi level menu', + menu: 'Menu', + menu1: 'Menu1', + menu11: 'Menu1-1', + menu111: 'Menu1-1-1', + menu12: 'Menu1-2', + menu2: 'Menu2', + dashboard: 'Dashboard', + analysis: 'Analysis', + workplace: 'Workplace', + guide: 'Guide', + component: 'Component', + icon: 'Icon', + echart: 'Echart', + countTo: 'Count to', + watermark: 'Watermark', + qrcode: 'Qrcode', + highlight: 'Highlight', + infotip: 'Infotip', + form: 'Form', + defaultForm: 'All examples', + search: 'Search', + table: 'Table', + defaultTable: 'Basic example', + editor: 'Editor', + richText: 'Rich text', + dialog: 'Dialog', + imageViewer: 'Image viewer', + descriptions: 'Descriptions', + example: 'Example', + exampleDialog: 'Example dialog', + examplePage: 'Example page', + exampleAdd: 'Example page - add', + exampleEdit: 'Example page - edit', + exampleDetail: 'Example page - detail', + errorPage: 'Error page', + authorization: 'Authorization', + user: 'User management', + role: 'Role management', + document: 'Document', + inputPassword: 'InputPassword', + sticky: 'Sticky' + }, + permission: { + hasPermission: 'Please set the operation permission value' + }, + analysis: { + newUser: 'New user', + unreadInformation: 'Unread information', + transactionAmount: 'Transaction amount', + totalShopping: 'Total Shopping', + monthlySales: 'Monthly sales', + userAccessSource: 'User access source', + january: 'January', + february: 'February', + march: 'March', + april: 'April', + may: 'May', + june: 'June', + july: 'July', + august: 'August', + september: 'September', + october: 'October', + november: 'November', + december: 'December', + estimate: 'Estimate', + actual: 'Actual', + directAccess: 'Airect access', + mailMarketing: 'Mail marketing', + allianceAdvertising: 'Alliance advertising', + videoAdvertising: 'Video advertising', + searchEngines: 'Search engines', + weeklyUserActivity: 'Weekly user activity', + activeQuantity: 'Active quantity', + monday: 'Monday', + tuesday: 'Tuesday', + wednesday: 'Wednesday', + thursday: 'Thursday', + friday: 'Friday', + saturday: 'Saturday', + sunday: 'Sunday' + }, + workplace: { + goodMorning: 'Good morning', + happyDay: 'Wish you happy every day!', + toady: `It's sunny today`, + project: 'Project', + access: 'Project access', + toDo: 'To do', + introduction: 'A serious introduction', + more: 'More', + shortcutOperation: 'Shortcut operation', + operation: 'Operation', + index: 'Index', + personal: 'Personal', + team: 'Team', + quote: 'Quote', + contribution: 'Contribution', + hot: 'Hot', + yield: 'Yield', + dynamic: 'Dynamic', + push: 'push', + pushCode: 'Archer push code to Github', + follow: 'Follow' + }, + formDemo: { + input: 'Input', + inputNumber: 'InputNumber', + default: 'Default', + icon: 'Icon', + mixed: 'Mixed', + textarea: 'Textarea', + slot: 'Slot', + position: 'Position', + autocomplete: 'Autocomplete', + select: 'Select', + selectGroup: 'Select Group', + selectV2: 'SelectV2', + cascader: 'Cascader', + switch: 'Switch', + rate: 'Rate', + colorPicker: 'Color Picker', + transfer: 'Transfer', + render: 'Render', + radio: 'Radio', + button: 'Button', + checkbox: 'Checkbox', + slider: 'Slider', + datePicker: 'Date Picker', + shortcuts: 'Shortcuts', + today: 'Today', + yesterday: 'Yesterday', + aWeekAgo: 'A week ago', + week: 'Week', + year: 'Year', + month: 'Month', + dates: 'Dates', + daterange: 'Date Range', + monthrange: 'Month Range', + dateTimePicker: 'DateTimePicker', + dateTimerange: 'Datetime Range', + timePicker: 'Time Picker', + timeSelect: 'Time Select', + inputPassword: 'input Password', + passwordStrength: 'Password Strength', + defaultForm: 'All examples', + formDes: + 'The secondary encapsulation of form components based on ElementPlus realizes data-driven and supports all Form parameters', + example: 'example', + operate: 'operate', + change: 'Change', + restore: 'Restore', + disabled: 'Disabled', + disablement: 'Disablement', + delete: 'Delete', + add: 'Add', + setValue: 'Set value', + resetValue: 'Reset value', + set: 'Set', + subitem: 'Subitem', + formValidation: 'Form validation', + verifyReset: 'Verify reset' + }, + guideDemo: { + guide: 'Guide', + start: 'Start', + message: + 'The guide page is very useful for some people who enter the project for the first time. You can briefly introduce the functions of the project. The boot page is based on intro js' + }, + iconDemo: { + icon: 'Icon', + localIcon: 'Local Icon', + iconify: 'Iconify component', + recommendedUse: 'Recommended use', + recommendeDes: + 'Iconify component basically contains all icons. You can query any icon you want. And packaging will only package the icons used.', + accessAddress: 'Access address' + }, + echartDemo: { + echart: 'Echart', + echartDes: + 'Based on the secondary packaging components of eckarts, the width is adaptive. The corresponding chart can be displayed by passing in the options and height attributes.' + }, + countToDemo: { + countTo: 'CountTo', + countToDes: + 'The transformation is based on vue-count-to and supports all vue-count-to parameters.', + suffix: 'Suffix', + prefix: 'Prefix', + separator: 'Separator', + duration: 'Duration', + endVal: 'End val', + startVal: 'Start val', + start: 'Start', + pause: 'Pause', + resume: 'Resume' + }, + watermarkDemo: { + watermark: 'Watermark', + createdWatermark: 'Created watermark', + clearWatermark: 'Clear watermark', + resetWatermark: 'Reset watermark' + }, + qrcodeDemo: { + qrcode: 'Qrcode', + qrcodeDes: 'Secondary packaging based on qrcode', + basicUsage: 'Basic usage', + imgTag: 'Img tag', + style: 'Style config', + click: 'Click event', + asynchronousContent: 'Asynchronous content', + invalid: 'Invalid', + logoConfig: 'Logo config', + logoStyle: 'Logo style', + size: 'size config' + }, + highlightDemo: { + highlight: 'Highlight', + message: 'The best time to plant a tree is ten years ago, followed by now.', + keys1: 'ten years ago', + keys2: 'now' + }, + infotipDemo: { + infotip: 'Infotip', + infotipDes: 'Secondary packaging of components based on Highlight', + title: 'matters needing attention' + }, + levelDemo: { + menu: 'Multi level menu cache' + }, + searchDemo: { + search: 'Search', + searchDes: + 'Based on the secondary encapsulation of form components, the functions of query and reset are realized', + operate: 'operate', + change: 'Change', + grid: 'grid', + button: 'Button', + restore: 'Restore', + inline: 'inline', + bottom: 'Bottom', + position: 'position', + left: 'left', + center: 'center', + right: 'right', + dynamicOptions: 'Dynamic options' + }, + stickyDemo: { + sticky: 'Sticky' + }, + tableDemo: { + table: 'Table', + tableDes: 'Secondary packaging of Table components based on ElementPlus', + index: 'Index', + title: 'Title', + author: 'Author', + displayTime: 'Display time', + importance: 'Importance', + pageviews: 'Pageviews', + action: 'Action', + important: 'Important', + good: 'Good', + commonly: 'Commonly', + operate: 'operate', + example: 'example', + show: 'Show', + hidden: 'Hidden', + pagination: 'pagination', + reserveIndex: 'Reserve index', + restoreIndex: 'Restore index', + showSelections: 'Show selections', + hiddenSelections: 'Restore selections', + showExpandedRows: 'Show expanded rows', + hiddenExpandedRows: 'Hidden expanded rows', + changeTitle: 'Change title', + header: 'Header', + selectAllNone: 'Select all / none' + }, + richText: { + richText: 'Rich text', + richTextDes: 'Secondary packaging based on wangeditor' + }, + dialogDemo: { + dialog: 'Dialog', + dialogDes: 'Secondary packaging of Dialog components based on ElementPlus', + open: 'Open', + close: 'Close', + combineWithForm: 'Combine with form', + submit: 'Submit' + }, + imageViewerDemo: { + open: 'Open', + imageViewer: 'Image viewer', + imageViewerDes: 'Secondary packaging of ImageViewer components based on ElementPlus' + }, + descriptionsDemo: { + descriptions: 'Descriptions', + descriptionsDes: 'Secondary packaging of Descriptions components based on ElementPlus', + username: 'Username', + nickName: 'NickName', + phone: 'Phone', + email: 'Email', + addr: 'Address', + form: 'Combined with Form component' + }, + exampleDemo: { + title: 'Title', + add: 'Add', + del: 'Delete', + edit: 'Edit', + author: 'Author', + displayTime: 'Display time', + importance: 'Importance', + pageviews: 'Pageviews', + important: 'Important', + content: 'Content', + save: 'Save', + detail: 'Detail' + }, + userDemo: { + title: 'User management', + message: + 'Because it is simulated data, only two accounts with different permissions are provided, which can be modified and combined by developers according to the actual situation.', + index: 'Index', + action: 'Action', + username: 'Username', + password: 'Password', + role: 'Role', + remark: 'Remark', + remarkMessage1: 'Back end control routing permission', + remarkMessage2: 'Front end control routing permission' + }, + inputPasswordDemo: { + title: 'InputPassword', + inputPasswordDes: 'Secondary packaging of Input components based on ElementPlus' + } +} diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts new file mode 100644 index 0000000..5b1c27a --- /dev/null +++ b/src/locales/zh-CN.ts @@ -0,0 +1,438 @@ +export default { + common: { + inputText: '请输入', + selectText: '请选择', + startTimeText: '开始时间', + endTimeText: '结束时间', + login: '登录', + required: '该项为必填项', + loginOut: '退出系统', + document: '项目文档', + changePassword: '修改密码', + reminder: '温馨提示', + loginOutMessage: '是否退出本系统?', + back: '返回', + ok: '确定', + cancel: '取消', + reload: '重新加载', + closeTab: '关闭标签页', + closeTheLeftTab: '关闭左侧标签页', + closeTheRightTab: '关闭右侧标签页', + closeOther: '关闭其他标签页', + closeAll: '关闭全部标签页', + prevLabel: '上一步', + nextLabel: '下一步', + skipLabel: '跳过', + doneLabel: '结束', + menu: '菜单', + menuDes: '以路由的结构渲染的菜单栏', + collapse: '展开缩收', + collapseDes: '展开和缩放菜单栏', + tagsView: '标签页', + tagsViewDes: '用于记录路由历史记录', + tool: '工具', + toolDes: '用于设置定制系统', + query: '查询', + reset: '重置', + shrink: '收起', + expand: '展开', + delMessage: '是否删除所选中数据?', + delWarning: '提示', + delOk: '确定', + delCancel: '取消', + delNoData: '请选择需要删除的数据', + delSuccess: '删除成功' + }, + error: { + noPermission: `抱歉,您无权访问此页面。`, + pageError: '抱歉,您访问的页面不存在。', + networkError: '抱歉,服务器报告错误。', + returnToHome: '返回首页' + }, + setting: { + projectSetting: '项目配置', + theme: '主题', + layout: '布局', + systemTheme: '系统主题', + menuTheme: '菜单主题', + interfaceDisplay: '界面显示', + breadcrumb: '面包屑', + breadcrumbIcon: '面包屑图标', + collapseMenu: '折叠菜单', + hamburgerIcon: '折叠图标', + screenfullIcon: '全屏图标', + sizeIcon: '尺寸图标', + localeIcon: '多语言图标', + tagsView: '标签页', + logo: '标志', + greyMode: '灰色模式', + fixedHeader: '固定头部', + headerTheme: '头部主题', + cutMenu: '切割菜单', + copy: '拷贝', + clearAndReset: '清除缓存并且重置', + copySuccess: '拷贝成功', + copyFailed: '拷贝失败', + footer: '页脚', + uniqueOpened: '菜单手风琴', + tagsViewIcon: '标签页图标', + reExperienced: '请重新退出登录体验', + fixedMenu: '固定菜单' + }, + size: { + default: '默认', + large: '大', + small: '小' + }, + login: { + welcome: '欢迎使用', + message: '企业级通用低代码开发平台', + username: '用户名', + password: '密码', + register: '注册', + checkPassword: '确认密码', + login: '登录', + otherLogin: '其他登录方式', + remember: '记住我', + hasUser: '已有账号?去登录', + forgetPassword: '忘记密码', + usernamePlaceholder: '请输入用户名', + passwordPlaceholder: '请输入密码', + code: '验证码', + codePlaceholder: '请输入验证码' + }, + router: { + login: '登录', + level: '多级菜单', + menu: '菜单', + menu1: '菜单1', + menu11: '菜单1-1', + menu111: '菜单1-1-1', + menu12: '菜单1-2', + menu2: '菜单2', + dashboard: '首页', + analysis: '分析页', + workplace: '工作台', + guide: '引导', + component: '组件', + icon: '图标', + echart: '图表', + countTo: '数字动画', + watermark: '水印', + qrcode: '二维码', + highlight: '高亮', + infotip: '信息提示', + form: '表单', + defaultForm: '全部示例', + search: '查询', + table: '表格', + defaultTable: '基础示例', + editor: '编辑器', + richText: '富文本', + dialog: '弹窗', + imageViewer: '图片预览', + descriptions: '描述', + example: '综合示例', + exampleDialog: '综合示例 - 弹窗', + examplePage: '综合示例 - 页面', + exampleAdd: '综合示例 - 新增', + exampleEdit: '综合示例 - 编辑', + exampleDetail: '综合示例 - 详情', + errorPage: '错误页面', + authorization: '权限管理', + user: '用户管理', + role: '角色管理', + document: '文档', + inputPassword: '密码输入框', + sticky: '黏性' + }, + permission: { + hasPermission: '请设置操作权限值' + }, + analysis: { + newUser: '新增用户', + unreadInformation: '未读消息', + transactionAmount: '成交金额', + totalShopping: '购物总量', + monthlySales: '每月销售额', + userAccessSource: '用户访问来源', + january: '一月', + february: '二月', + march: '三月', + april: '四月', + may: '五月', + june: '六月', + july: '七月', + august: '八月', + september: '九月', + october: '十月', + november: '十一月', + december: '十二月', + estimate: '预计', + actual: '实际', + directAccess: '直接访问', + mailMarketing: '邮件营销', + allianceAdvertising: '联盟广告', + videoAdvertising: '视频广告', + searchEngines: '搜索引擎', + weeklyUserActivity: '每周用户活跃量', + activeQuantity: '活跃量', + monday: '周一', + tuesday: '周二', + wednesday: '周三', + thursday: '周四', + friday: '周五', + saturday: '周六', + sunday: '周日' + }, + workplace: { + goodMorning: '早安', + happyDay: '祝你开心每一天!', + toady: '今日晴', + project: '项目数', + access: '项目访问', + toDo: '待办', + introduction: '一个正经的简介', + more: '更多', + shortcutOperation: '快捷操作', + operation: '操作', + index: '指数', + personal: '个人', + team: '团队', + quote: '引用', + contribution: '贡献', + hot: '热度', + yield: '产量', + dynamic: '动态', + push: '推送', + pushCode: 'Archer 推送 代码到 Github', + follow: '关注' + }, + formDemo: { + input: '输入框', + inputNumber: '数字输入框', + default: '默认', + icon: '图标', + mixed: '复合型', + textarea: '多行文本', + slot: '插槽', + position: '位置', + autocomplete: '自动补全', + select: '选择器', + selectGroup: '选项分组', + selectV2: '虚拟列表选择器', + cascader: '级联选择器', + switch: '开关', + rate: '评分', + colorPicker: '颜色选择器', + transfer: '穿梭框', + render: '渲染器', + radio: '单选框', + button: '按钮', + checkbox: '多选框', + slider: '滑块', + datePicker: '日期选择器', + shortcuts: '快捷选项', + today: '今天', + yesterday: '昨天', + aWeekAgo: '一周前', + week: '周', + year: '年', + month: '月', + dates: '日期', + daterange: '日期范围', + monthrange: '月份范围', + dateTimePicker: '日期时间选择器', + dateTimerange: '日期时间范围', + timePicker: '时间选择器', + timeSelect: '时间选择', + inputPassword: '密码输入框', + passwordStrength: '密码强度', + defaultForm: '全部示例', + formDes: '基于 ElementPlus 的 Form 组件二次封装,实现数据驱动,支持所有 Form 参数', + example: '示例', + operate: '操作', + change: '更改', + restore: '还原', + disabled: '禁用', + disablement: '解除禁用', + delete: '删除', + add: '添加', + setValue: '设置值', + resetValue: '重置值', + set: '设置', + subitem: '子项', + formValidation: '表单验证', + verifyReset: '验证重置' + }, + guideDemo: { + guide: '引导页', + start: '开始', + message: + '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 intro.js' + }, + iconDemo: { + icon: '图标', + localIcon: '本地图标', + iconify: 'Iconify组件', + recommendedUse: '推荐使用', + recommendeDes: + 'Iconify组件基本包含所有的图标,你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。', + accessAddress: '访问地址' + }, + echartDemo: { + echart: '图表', + echartDes: + '基于 echarts 二次封装组件,自适应宽度,只需传入 options 与 height 属性即可展示对应的图表。' + }, + countToDemo: { + countTo: '数字动画', + countToDes: '基于 vue-count-to 进行改造,支持所有 vue-count-to 参数。', + suffix: '后缀', + prefix: '前缀', + separator: '分割符号', + duration: '持续时间', + endVal: '结束值', + startVal: '开始值', + start: '开始', + pause: '暂停', + resume: '继续' + }, + watermarkDemo: { + watermark: '水印', + createdWatermark: '创建水印', + clearWatermark: '清除水印', + resetWatermark: '重置水印' + }, + qrcodeDemo: { + qrcode: '二维码', + qrcodeDes: '基于 qrcode 二次封装', + basicUsage: '基础用法', + imgTag: 'img标签', + style: '样式配置', + click: '点击事件', + asynchronousContent: '异步内容', + invalid: '失效', + logoConfig: 'logo配置', + logoStyle: 'logo样式', + size: '大小配置' + }, + highlightDemo: { + highlight: '高亮', + message: '种一棵树最好的时间是十年前,其次就是现在。', + keys1: '十年前', + keys2: '现在' + }, + infotipDemo: { + infotip: '信息提示', + infotipDes: '基于 Highlight 组件二次封装', + title: '注意事项' + }, + levelDemo: { + menu: '多级菜单缓存' + }, + searchDemo: { + search: '查询', + searchDes: '基于 Form 组件二次封装,实现查询、重置功能', + operate: '操作', + change: '更改', + grid: '栅格', + button: '按钮', + restore: '还原', + inline: '内联', + bottom: '底部', + position: '位置', + left: '左', + center: '中', + right: '右', + dynamicOptions: '动态选项' + }, + stickyDemo: { + sticky: '黏性' + }, + tableDemo: { + table: '表格', + tableDes: '基于 ElementPlus 的 Table 组件二次封装', + index: '序号', + title: '标题', + author: '作者', + displayTime: '创建时间', + importance: '重要性', + pageviews: '阅读数', + action: '操作', + important: '重要', + good: '良好', + commonly: '一般', + operate: '操作', + example: '示例', + show: '显示', + hidden: '隐藏', + pagination: '分页', + reserveIndex: '叠加序号', + restoreIndex: '还原序号', + showSelections: '显示多选', + hiddenSelections: '隐藏多选', + showExpandedRows: '显示展开行', + hiddenExpandedRows: '隐藏展开行', + changeTitle: '修改标题', + header: '头部', + selectAllNone: '全选/全不选' + }, + richText: { + richText: '富文本', + richTextDes: '基于 wangeditor 二次封装' + }, + dialogDemo: { + dialog: '弹窗', + dialogDes: '基于 ElementPlus 的 Dialog 组件二次封装', + open: '打开', + close: '关闭', + combineWithForm: '与表单结合', + submit: '提交' + }, + imageViewerDemo: { + open: '打开', + imageViewer: '图片预览', + imageViewerDes: '基于 ElementPlus 的 ImageViewer 组件二次封装' + }, + descriptionsDemo: { + descriptions: '描述', + descriptionsDes: '基于 ElementPlus 的 Descriptions 组件二次封装', + username: '用户名', + nickName: '昵称', + phone: '联系电话', + email: '邮箱', + addr: '地址', + form: '与 Form 组件组合' + }, + exampleDemo: { + title: '标题', + add: '新增', + del: '删除', + edit: '编辑', + author: '作者', + displayTime: '创建时间', + importance: '重要性', + pageviews: '阅读数', + important: '重要', + content: '内容', + save: '保存', + detail: '详情' + }, + userDemo: { + title: '用户管理', + message: '由于是模拟数据,所以只提供了两种不同权限的帐号,开发者可根据实际情况自行改造结合。', + index: '序号', + action: '操作', + username: '用户名', + password: '密码', + role: '角色', + remark: '备注', + remarkMessage1: '后端控制路由权限', + remarkMessage2: '前端控制路由权限' + }, + inputPasswordDemo: { + title: '密码输入框', + inputPasswordDes: '基于 ElementPlus 的 Input 组件二次封装' + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..4812884 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,172 @@ +// 引入windi css +import '@/plugins/windi.css' + +// 导入全局的svg图标 +import '@/plugins/svgIcon' + +// 初始化多语言 +import { setupI18n } from '@/plugins/vueI18n' + +// 引入状态管理 +import { setupStore } from '@/store' + +// 全局组件 +import { setupGlobCom } from '@/components' + + + +// 引入全局样式 +import '@/styles/index.less' + +// 引入动画 +import '@/plugins/animate.css' + +// 路由 +import { setupRouter } from './router' + +// 权限 +import { setupPermission } from './directives' + +import { createApp } from 'vue' + +import App from './App.vue' + +import './permission' + +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +import constant from '@/constant/index' +import api from '@/api/index' + +import dateFormatter from '@/utils/dateFormatter.js' + +import StringUtil from '@/utils/stringUtil.js' + +import uploader from 'vue-simple-uploader' + +import 'vue-simple-uploader/dist/style.css' + +// web socket +import webSocket from '@/modules/notification/view/systemMessage/webSocket.js' + + +// 可自配置的网格布局 +import VueGridLayout from 'vue-grid-layout' + +// echart图表 +import "echarts" +import ECharts from "vue-echarts" + +// 表单构建器 +import FormCreate from '@form-create/element-ui' +import FcDesigner from '@form-create/designer' + + +import { Base64 } from 'js-base64' + +// 自定义组件 + +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' +import { Editor } from '@/components/abc/Editor' +import AttachmentUploader from '@/modules/support/view/attachment/attachmentUploader.vue' +import AttachmentManager from '@/modules/support/view/attachment/attachmentManager.vue' +import AttachmentViewer from '@/modules/support/view/attachment/attachmentViewer.vue' +import AttachmentManagerAndUploader from '@/modules/support/view/attachment/attachmentManagerAndUploader.vue' + +FormCreate.component(DictionarySelect) +FormCreate.component(DictionaryRadioGroup) +FormCreate.component(OrganizationSingleSelect) +FormCreate.component(OrganizationMultipleSelect) +FormCreate.component(UserSingleSelect) +FormCreate.component(IconPicker) +FormCreate.component(Editor) +FormCreate.component(AttachmentUploader) +FormCreate.component(AttachmentManager) +FormCreate.component(AttachmentViewer) +FormCreate.component(AttachmentManagerAndUploader) + + +// 创建实例 +const setupAll = async () => { + const app = createApp(App) + + await setupI18n(app) + + setupStore(app) + + setupGlobCom(app) + + setupRouter(app) + + setupPermission(app) + + // 全量注册element-plus组件 + app.use(ElementPlus) + + // 自定义表单 + app.use(FormCreate) + app.use(FcDesigner) + + // 全量注册element-plus图标 + app.config.globalProperties.$icons = [] + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) + // 将图标键值放入全局变量,用于图标选择器 + app.config.globalProperties.$icons.push(key) + } + + // 挂载全局变量,兼容2.0习惯写法 + app.config.globalProperties.$constant = constant + + app.config.globalProperties.$api = api + + // 日期函数 + app.config.globalProperties.$dateFormatter = dateFormatter + + // 字符串工具函数 + app.config.globalProperties.$StringUtil = StringUtil + + // web socket + app.config.globalProperties.$webSocket = webSocket + + // base64工具类 + app.config.globalProperties.$base64Util = Base64 + + // 文件上传 + app.use(uploader) + + // 可自配置的网格布局 + app.use(VueGridLayout) + + // echart图表 + app.component('v-chart', ECharts) + + app.mount('#app') + + app.directive('focus', { + mounted(el) { + el.focus() + } + }) + + app.directive('enterNumber', { + mounted(el, { value = 100 }, vnode) { + el = el.nodeName == "INPUT" ? el : el.children[0] + var RegStr = value == 0 ? `^[\\+\\-]?\\d+\\d{0,0}` : `^[\\+\\-]?\\d+\\.?\\d{0,${value}}` + el.addEventListener('input', function () { + el.value = el.value.match(new RegExp(RegStr, 'g')); + el.dispatchEvent(new Event('input')) + }) + } + }) + +} + +setupAll() diff --git a/src/mixin/addForAdvanceConfigMixin.js b/src/mixin/addForAdvanceConfigMixin.js new file mode 100644 index 0000000..3d472c8 --- /dev/null +++ b/src/mixin/addForAdvanceConfigMixin.js @@ -0,0 +1,89 @@ +/** + * 新增页面混入 + */ + +import { Dialog } from '@/components/abc/Dialog' + +export const addMixin = { + components: { + Dialog + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false, + fApi: {}, + entityId: '', + formValue: {} + } + }, + methods: { + // 带参初始化,参数可为空 + init(param) { + if (this.beforeInit) { + this.beforeInit(param) + } + this.api.init().then((res) => { + this.entityData = res.data + this.formValue = res.data + this.entityId = res.data.id + this.updateData() + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + //更新标签值 + updateData() { + const ruleString = JSON.stringify(this.rule) + .replaceAll('@EntityId@', this.entityId, 'g') + .replaceAll('@EntityType@', this.entityType, 'g') + .replaceAll('@ModuleCode@', this.moduleCode, 'g') + this.rule = JSON.parse(ruleString) + }, + // 保存 + save() { + if (this.beforeSave) { + this.beforeSave() + } + this.fApi.validate((valid, fail) => { + if (valid === true) { + if (this.validateData) { + // 数据验证通过后才执行保存操作 + if (this.validateData()) { + this.saveData() + } + } else { + // 无需数据验证,直接执行 + this.saveData() + } + } + }) + }, + // 关闭 + close() { + this.visible = false + }, + // 保存 + saveData() { + const data = Object.assign(this.entityData, this.formValue) + this.api + .add(data) + .then((res) => { + this.entityData = res.data + this.formValue = res.data + if (this.afterSave) { + this.afterSave() + } + this.$emit('refresh', this.entityData) + this.close() + }) + .finally(() => { + this.loading = false + }) + } + } +} diff --git a/src/mixin/addMixin.js b/src/mixin/addMixin.js new file mode 100644 index 0000000..1ce1d65 --- /dev/null +++ b/src/mixin/addMixin.js @@ -0,0 +1,102 @@ +/** + * 新增页面混入 + */ + +import { Dialog } from '@/components/abc/Dialog' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DataDictionarySelect from '@/modules/system/view/dictionaryType/treeReferenceUseCode.vue' +import { Editor } from '@/components/abc/Editor' +import CronExpression from '@/components/abc/CronExpression/index.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' +import AttachmentManager from '@/modules/support/view/attachment/attachmentManager.vue' +import AttachmentUploader from '@/modules/support/view/attachment/attachmentUploader.vue' +import AttachmentViewer from '@/modules/support/view/attachment/attachmentViewer.vue' +import AttachmentManagerAndUploader from '@/modules/support/view/attachment/attachmentManagerAndUploader.vue' +export const addMixin = { + components: { + Dialog, + DictionaryRadioGroup, + DictionarySelect, + DataDictionarySelect, + Editor, + CronExpression, + OrganizationSingleSelect, + OrganizationMultipleSelect, + UserSingleSelect, + IconPicker, + AttachmentManager, + AttachmentUploader, + AttachmentViewer, + AttachmentManagerAndUploader + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false + } + }, + methods: { + // 带参初始化,参数可为空 + init(param) { + if (this.beforeInit) { + this.beforeInit(param) + } + this.api.init().then((res) => { + this.entityData = res.data + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + // 保存 + save() { + if (this.beforeSave) { + this.beforeSave() + } + this.$refs.form.validate((valid) => { + if (valid) { + if (this.validateData) { + // 数据验证通过后才执行保存操作 + if (this.validateData()) { + this.saveData() + } + } else { + // 无需数据验证,直接执行 + this.saveData() + } + } + }) + }, + // 关闭 + close() { + this.visible = false + }, + // 保存 + saveData() { + this.api + .add(this.entityData) + .then((res) => { + this.entityData = res.data + if (this.afterSave) { + this.afterSave() + } + this.$emit('refresh', this.entityData) + this.close() + }) + .finally(() => { + this.loading = false + }) + }, + // 附件上传完成,刷新管理组件 + fileComplete() { + this.$refs.attachmentManager.list() + } + } +} diff --git a/src/mixin/flowMixin.js b/src/mixin/flowMixin.js new file mode 100644 index 0000000..7d2841a --- /dev/null +++ b/src/mixin/flowMixin.js @@ -0,0 +1,236 @@ +/** + * 流程页面混入 + */ + +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DataDictionarySelect from '@/modules/system/view/dictionaryType/treeReferenceUseCode.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import { closeCurrentTab } from '@/utils' +import BasicInfo from '@/modules/workflow/view/task/basicInfo.vue' +import FlowPreview from '@/modules/workflow/view/workflowTemplate/model/components/flowPreview.vue' +export const flowMixin = { + components: { + DictionarySelect, + DictionaryRadioGroup, + OrganizationSingleSelect, + OrganizationMultipleSelect, + DataDictionarySelect, + BasicInfo, + FlowPreview + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false, + // 模式 + mode: '', + // 只读 + readonly: false, + // // 流程实例标识 + // flowInstanceId: '', + // // 任务标识 + // taskId: '', + // 任务数据 + taskData: { + // 任务标识 + taskId: '', + // 环节名称 + stepName: '', + // 流程实例标识 + processInstanceId: '', + // 办理意见 + comment: '发起申请', + // 流程模板标识 + processDefinitionId: '', + // 流程模板类型 + processDefinitionKey: '', + // 流程模板名称 + processDefinitionName: '', + // 是否已保存 + isSaved: false, + // 处理人 + assignee: '' + }, + // 权限配置 + permissionConfigData: {} + } + }, + computed: {}, + methods: { + // 新增 + add(processDefinitionId) { + if (this.beforeAdd) { + this.beforeAdd() + } + this.mode = 'add' + + this.api.init().then((res) => { + this.entityData = res.data + // 获取发起环节权限设置 + this.getNodePermissionConfig(processDefinitionId, 'root') + + // TODO:为测试方便,填充值,需过后删除 + this.entityData = Object.assign({}, this.entityData, { + leaveType: 'PERSONAL', + startTime: '2020-10-06 00:00:00', + endTime: '2020-10-07 00:00:00', + total: 3, + reason: '家中有事处理' + }) + }) + }, + // 修改 + modify(billNo, processDefinitionId, taskDefinitionKey) { + if (this.beforeModify) { + this.beforeModify() + } + this.mode = 'modify' + this.api.getByBillNo(billNo).then((res) => { + this.entityData = res.data + this.getNodePermissionConfig(processDefinitionId, taskDefinitionKey) + }) + }, + // 查看 + view(billNo, processDefinitionId) { + this.mode = 'view' + this.api.getByBillNo(billNo).then((res) => { + this.entityData = res.data + // 获取环节权限设置 + this.$api.workflow.workflowNodePermissionConfig + .getNodePermissionConfigForView(processDefinitionId) + .then((res) => { + this.permissionConfigData = {} + res.data.forEach((item) => { + this.permissionConfigData[item.areaCode] = item.permission + }) + }) + if (this.afterView) { + this.afterView() + } + }) + this.readonly = true + this.visible = true + }, + getNodePermissionConfig(processDefinitionId, nodeId) { + // 获取发起环节权限设置 + this.$api.workflow.workflowNodePermissionConfig + .getNodePermissionConfig(processDefinitionId, nodeId) + .then((res) => { + this.permissionConfigData = {} + res.data.forEach((item) => { + this.permissionConfigData[item.areaCode] = item.permission + }) + }) + }, + // 获取流程类型 + getProcessType() { + return this.entityType + }, + // 获取单号 + getBillNo() { + return this.entityData.billNo + }, + // 获取流程实例标识 + getProcessInstanceId() { + return this.entityData.flowInstanceId + }, + // 获取保存状态 + getSaveStatus() { + return this.taskData.isSaved + }, + // 提交 + commit() { + if (this.taskData.taskId === '') { + // 流程创建时保存后,无任务标识,通过流程标识获取任务标识 + this.$api.workflow.processInstance + .getTaskId(this.taskData.processInstanceId) + .then((res) => { + this.taskData.taskId = res.data + this.$refs.commit.show(this.taskData.processInstanceId, this.taskData.taskId) + }) + } else { + this.$refs.commit.show( + this.taskData.processInstanceId, + this.taskData.taskId, + this.taskData.delegation + ) + } + }, + // 驳回 + reject() { + this.$refs.reject.show(this.taskData.processInstanceId, this.taskData.taskId) + }, + // 跳转 + jump() { + if (this.taskData.taskId === '') { + // 流程创建时保存后,无任务标识,通过流程标识获取任务标识 + this.$api.workflow.processInstance + .getTaskId(this.taskData.processInstanceId) + .then((res) => { + this.taskData.taskId = res.data + this.$refs.jump.show(this.taskData.processInstanceId, this.taskData.taskId) + }) + } else { + this.$refs.jump.show(this.taskData.processInstanceId, this.taskData.taskId) + } + }, + // 保存 + save() { + this.$refs.flowComponent.saveBusinessData() + }, + // 转办 + transfer() { + this.$refs.transfer.show() + }, + // 委派 + delegate() { + this.$refs.delegate.show() + }, + // 关闭tab页 + close() { + closeCurrentTab() + }, + // 关闭对话框 + closeDialog() { + this.visible = false + }, + // 保存业务数据 + saveBusinessData() { + this.$refs.form.validate((valid) => { + if (valid) { + if (this.mode === 'add') { + this.api + .add(this.entityData) + .then((res) => { + this.entityData = res.data + if (this.afterAdd) { + this.afterAdd() + } + this.mode = 'modify' + this.$emit('save', this.entityData.flowInstanceId) + }) + .finally(() => { + this.loading = false + }) + } else { + this.api + .modify(this.entityData) + .then((res) => { + this.entityData = res.data + if (this.afterModify) { + this.afterModify() + } + }) + .finally(() => { + this.loading = false + }) + } + } + }) + } + } +} diff --git a/src/mixin/listMixin.js b/src/mixin/listMixin.js new file mode 100644 index 0000000..2ec8659 --- /dev/null +++ b/src/mixin/listMixin.js @@ -0,0 +1,355 @@ +/** + * 列表页面混入 + */ +import CollapseTab from '@/components/abc/CollapseTab/index.vue' +import QueryText from '@/components/abc/QueryText/index.vue' +import QueryButton from '@/components/abc/QueryButton/index.vue' +import { ContentWrap } from '@/components/abc/ContentWrap' +import ListPager from '@/components/abc/ListPager/index.vue' +import ColumnsController from '@/components/abc/ColumnsController/index.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' + +export const listMixin = { + components: { + ContentWrap, + CollapseTab, + QueryText, + QueryButton, + ListPager, + ColumnsController, + DictionarySelect, + DictionaryRadioGroup, + OrganizationSingleSelect, + OrganizationMultipleSelect, + UserSingleSelect, + IconPicker + }, + + data() { + return { + // 表格数据 + tableData: [], + // 加载中 + loading: false, + // 当前行 + currentId: this.$constant.NO_ITEM_SELECTED, + // 复选框选中项 + multipleSelection: [], + // 分页信息 + pageInfo: { + // 页码 + pageNum: this.$constant.DEFAULT_PAGE_NUM, + // 页码大小 + pageSize: this.$constant.DEFAULT_PAGE_SIZE + }, + // 排序信息 + sortInfo: { + sort_field: 'orderNo', + sort_sortType: 'ascending' + }, + // 总页数 + pageTotal: 0, + queryCondition: { + // 默认值处理 + }, + // 名称键值 + nameKey: 'name' + } + }, + props: { + // 通用参数对象 + commonParam: { + type: Object, + required: false, + default: {} + } + }, + computed: { + showCols() { + return this.columnList.filter((item) => item.show) + }, + tableKey() { + const { path } = this.$route + return `${path}/table` + } + }, + watch: { + commonParam: { + immediate: true, + handler: 'handleParamChange', + deep: true + } + }, + mounted() { + this.init() + }, + methods: { + // 输入参数变化 + handleParamChange(value) { + if (this.commonParamChange) { + this.commonParamChange(value) + this.init() + } + }, + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + }) + }, + // 加载数据 + loadData() { + return new Promise((resolve) => { + this.loading = true + const params = Object.assign(this.queryCondition, this.pageInfo, this.sortInfo) + this.api + .page(params) + .then((res) => { + this.tableData = res.data.records + this.pageTotal = res.data.total + resolve() + }) + .finally(() => { + this.loading = false + this.currentId = this.$constant.NO_ITEM_SELECTED + }) + }) + }, + // 新增 + add() { + this.$refs.addPage.init() + }, + // 修改 + modify(row) { + this.$refs.modifyPage.init(row.id) + }, + // 查看 + view(id) { + if (this.$refs.viewPage) { + this.$refs.viewPage.init(id) + } + }, + // 单条移除 + remove(row) { + this.$confirm('此操作将删除数据, 是否继续?', '确认', { + type: 'warning' + }) + .then(() => { + this.api.remove(row.id).then(() => { + this.refresh() + }) + }) + .catch(() => { + this.$message.info('已取消') + }) + }, + // 批量移除 + batchRemove() { + if (!this.checkSelected()) { + return + } + this.$confirm('此操作将批量删除数据, 是否继续?', '确认', { + type: 'warning' + }) + .then(() => { + const ids = this.getCheckedId() + this.api.remove(ids).then(() => { + this.refresh() + }) + }) + .catch(() => { + this.$message.info('已取消') + }) + }, + // 复制新增 + addByCopy() { + if (!this.checkSelected()) { + return + } + this.$confirm('此操作将批量复制新增数据, 是否继续?', '确认', { + type: 'warning' + }) + .then(() => { + const ids = this.getCheckedId() + this.api.addByCopy(ids).then(() => { + this.refresh() + }) + }) + .catch(() => { + this.$message.info('已取消') + }) + }, + // 验证是否有勾选行 + checkSelected() { + if (this.multipleSelection.length === 0) { + this.$message.info('请至少选中一行') + return false + } + return true + }, + + // 获取表格勾选行id字符串 + getCheckedId() { + let ids = '' + this.multipleSelection.forEach(function (item) { + ids += item.id + ',' + }) + return ids + }, + // 获取表格勾选行id 数组 + getCheckedIdList() { + return this.multipleSelection.map((item) => item.id) + }, + // 刷新 + refresh() { + this.$emit('refresh') + this.loadData() + }, + // 处理排序 + // eslint-disable-next-line no-unused-vars + sortChange({ column, prop, order }) { + this.sortInfo.sort_field = prop + this.sortInfo.sort_sortType = order + this.refresh() + }, + // 当前行变化 + rowChange(currentRow) { + this.currentId = currentRow ? currentRow.id : this.$constant.NO_ITEM_SELECTED + }, + // 获取行记录 + getRow(id) { + if (id && id !== this.$constant.NO_ITEM_SELECTED) { + if (this.tableData && this.tableData.length > 0) { + for (let i = 0; i < this.tableData.length; i++) { + if (this.tableData[i].id === id) { + return this.tableData[i] + } + } + } + } + return undefined + }, + // 获取名称 + getName(id) { + const row = this.getRow(id) + if (row) { + return row[this.nameKey] + } + return undefined + }, + // 验证是否有选中行 + checkSelectedRowExist() { + if (this.currentId === this.$constant.NO_ITEM_SELECTED) { + this.$message.info('当前无选中行') + return false + } + return true + }, + + // 复选多行 + selectionChange(val) { + this.multipleSelection = val + }, + // 双击事件 + rowDoubleClick(row) { + this.view(row.id) + }, + // 处理查询 + query() { + // 查询之前,将当前页置为1 + this.pageInfo.pageNum = 1 + this.refresh() + }, + + // 处理分页变化 + pageChange(value) { + this.pageInfo.pageNum = value + this.refresh() + }, + // 处理分页大小变化 + pageSizeChange(value) { + this.pageInfo.pageSize = value + this.refresh() + }, + // 启用 + enable(row) { + this.$confirm('此操作将启用该数据, 是否继续?', '确认', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + .then(() => { + this.api.enable(row.id).then(() => { + this.refresh() + }) + }) + .catch(() => { + this.$message({ + type: 'info', + message: '已取消' + }) + }) + }, + // 停用 + disable(row) { + this.$confirm('此操作将停用该数据, 是否继续?', '确认', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + .then(() => { + this.api.disable(row.id).then(() => { + this.refresh() + }) + }) + .catch(() => { + this.$message({ + type: 'info', + message: '已取消' + }) + }) + }, + // 下载导入模板 + downloadImportTemplate() { + this.api.downloadImportTemplate() + }, + // 导入 + importData(file) { + const formData = new FormData() + formData.append('file', file.file) + this.api + .import(formData) + .then(() => { + this.refresh() + }) + .finally(() => { + this.clearFile() + }) + }, + // 导出 + exportData() { + const params = Object.assign({}, this.queryCondition) + this.api.export(params) + } + }, + provide() { + return { + query: this.query, + modify: this.modify, + view: this.view, + remove: this.remove, + pageChange: this.pageChange, + pageSizeChange: this.pageSizeChange + } + } +} diff --git a/src/mixin/modifyForAdvanceConfigMixin.js b/src/mixin/modifyForAdvanceConfigMixin.js new file mode 100644 index 0000000..d3ab13e --- /dev/null +++ b/src/mixin/modifyForAdvanceConfigMixin.js @@ -0,0 +1,91 @@ +/** + * 高级配置修改页面混入 + */ + +import { Dialog } from '@/components/abc/Dialog' + +export const modifyMixin = { + components: { + Dialog + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false, + fApi: {}, + entityId: '', + formValue: {} + } + }, + + methods: { + // 初始化 + init(id) { + if (this.beforeInit) { + this.beforeInit() + } + + this.api.get(id).then((res) => { + this.entityData = res.data + this.formValue = res.data + this.entityId = res.data.id + this.updateData() + if (this.afterInit) { + this.afterInit() + } + this.visible = true + }) + }, + //更新标签值 + updateData() { + const ruleString = JSON.stringify(this.rule) + .replaceAll('@EntityId@', this.entityId, 'g') + .replaceAll('@EntityType@', this.entityType, 'g') + .replaceAll('@ModuleCode@', this.moduleCode, 'g') + this.rule = JSON.parse(ruleString) + }, + // 保存 + save() { + if (this.beforeSave) { + this.beforeSave() + } + this.fApi.validate((valid, fail) => { + if (valid === true) { + if (this.validateData) { + // 数据验证通过后才执行保存操作 + if (this.validateData()) { + this.saveData() + } + } else { + // 无需数据验证,直接执行 + this.saveData() + } + } + }) + }, + // 关闭 + close() { + this.visible = false + }, + // 保存 + saveData() { + const data = Object.assign(this.entityData, this.formValue) + this.api + .modify(data) + .then((res) => { + this.entityData = res.data + this.formValue = res.data + if (this.afterSave) { + this.afterSave() + } + this.$emit('refresh', this.entityData) + this.close() + }) + .finally(() => { + this.loading = false + }) + } + } +} diff --git a/src/mixin/modifyMixin.js b/src/mixin/modifyMixin.js new file mode 100644 index 0000000..89d32aa --- /dev/null +++ b/src/mixin/modifyMixin.js @@ -0,0 +1,102 @@ +/** + * 修改页面混入 + */ + +import { Dialog } from '@/components/abc/Dialog' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DataDictionarySelect from '@/modules/system/view/dictionaryType/treeReferenceUseCode.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' +import { Editor } from '@/components/abc/Editor' +import CronExpression from '@/components/abc/CronExpression/index.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue' +import AttachmentManager from '@/modules/support/view/attachment/attachmentManager.vue' +import AttachmentUploader from '@/modules/support/view/attachment/attachmentUploader.vue' +import AttachmentViewer from '@/modules/support/view/attachment/attachmentViewer.vue' +import AttachmentManagerAndUploader from '@/modules/support/view/attachment/attachmentManagerAndUploader.vue' +export const modifyMixin = { + components: { + Dialog, + DictionaryRadioGroup, + DictionarySelect, + DataDictionarySelect, + Editor, + CronExpression, + OrganizationSingleSelect, + OrganizationMultipleSelect, + UserSingleSelect, + IconPicker, + AttachmentManager, + AttachmentUploader, + AttachmentViewer, + AttachmentManagerAndUploader + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false + } + }, + methods: { + // 初始化 + init(id) { + if (this.beforeInit) { + this.beforeInit() + } + this.api.get(id).then((res) => { + this.entityData = res.data + if (this.afterInit) { + this.afterInit() + } + this.visible = true + }) + }, + // 保存 + save() { + if (this.beforeSave) { + this.beforeSave() + } + this.$refs.form.validate((valid) => { + if (valid) { + if (this.validateData) { + // 数据验证通过后才执行保存操作 + if (this.validateData()) { + this.saveData() + } + } else { + // 无需数据验证,直接执行 + this.saveData() + } + } + }) + }, + // 关闭 + close() { + this.visible = false + }, + // 保存 + saveData() { + this.api + .modify(this.entityData) + .then((res) => { + this.entityData = res.data + if (this.afterSave) { + this.afterSave() + } + this.$emit('refresh', this.entityData) + this.close() + }) + .finally(() => { + this.loading = false + }) + }, + // 附件上传完成,刷新管理组件 + fileComplete() { + this.$refs.attachmentManager.list() + } + } +} diff --git a/src/mixin/referenceMixin.js b/src/mixin/referenceMixin.js new file mode 100644 index 0000000..197f569 --- /dev/null +++ b/src/mixin/referenceMixin.js @@ -0,0 +1,233 @@ +/** + * 参照页面混入 + */ +import { Dialog } from '@/components/abc/Dialog' +import CollapseTab from '@/components/abc/CollapseTab/index.vue' +import QueryText from '@/components/abc/QueryText/index.vue' +import QueryButton from '@/components/abc/QueryButton/index.vue' +import { ContentWrap } from '@/components/abc/ContentWrap' +import ListPager from '@/components/abc/ListPager/index.vue' +import ColumnsController from '@/components/abc/ColumnsController/index.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' + +export const referenceMixin = { + emits: ['update:modelValue', 'my-change'], + components: { + Dialog, + ContentWrap, + CollapseTab, + QueryText, + QueryButton, + ListPager, + ColumnsController, + DictionarySelect, + DictionaryRadioGroup, + OrganizationSingleSelect, + OrganizationMultipleSelect, + UserSingleSelect, + IconPicker + }, + props: { + modelValue: { + type: String, + default: '', + required: false + }, + disabled: { + type: Boolean, + required: false, + default: false + } + }, + data() { + return { + // 表格数据 + tableData: [], + // 加载中 + loading: false, + // 当前行 + currentId: this.$constant.NO_ITEM_SELECTED, + // 分页信息 + pageInfo: { + // 页码 + pageNum: this.$constant.DEFAULT_PAGE_NUM, + // 页码大小 + pageSize: this.$constant.DEFAULT_PAGE_SIZE + }, + // 排序信息 + sortInfo: { + sort_field: 'orderNo', + sort_sortType: 'ascending' + }, + // 总页数 + pageTotal: 0, + queryCondition: { + // 默认值处理 + }, + visible: false, + // 显示名称 + displayName: '' + } + }, + watch: { + modelValue: { + immediate: true, + handler: 'getSelectedName' + } + }, + computed: { + showCols() { + return this.columnList.filter((item) => item.show) + }, + tableKey() { + const { path } = this.$route + return `${path}/table` + } + }, + methods: { + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + // 确认选择 + confirm() { + if (!this.checkSelectedRowExist()) { + return + } + const selectedRow = this.getRow(this.currentId) + + this.displayName = selectedRow.name + // 更新父组件绑定值 + this.$emit('update:modelValue', selectedRow.id) + this.$emit('my-change', selectedRow.id, selectedRow) + this.visible = false + }, + // 关闭 + close() { + this.visible = false + }, + // 清空选择 + clear() { + this.displayName = '' + this.$emit('update:modelValue', '') + this.$emit('my-change', '') + }, + // 获取选中的名称 + getSelectedName() { + if (this.modelValue) { + this.api.get(this.modelValue).then((res) => { + this.displayName = res.data[this.nameKey] + }) + } + }, + // 加载数据 + loadData() { + return new Promise((resolve) => { + this.loading = true + const params = Object.assign(this.queryCondition, this.pageInfo, this.sortInfo) + this.api + .page(params) + .then((res) => { + this.tableData = res.data.records + this.pageTotal = res.data.total + resolve() + }) + .finally(() => { + this.loading = false + this.currentId = this.$constant.NO_ITEM_SELECTED + }) + }) + }, + // 查看 + view(id) { + if (this.$refs.viewPage) { + this.$refs.viewPage.init(id) + } + }, + // 刷新 + refresh() { + this.loadData() + }, + // 处理排序 + // eslint-disable-next-line no-unused-vars + sortChange({ column, prop, order }) { + this.sortInfo.sort_field = prop + this.sortInfo.sort_sortType = order + this.refresh() + }, + // 当前行变化 + rowChange(currentRow) { + this.currentId = currentRow ? currentRow.id : this.$constant.NO_ITEM_SELECTED + }, + // 获取行记录 + getRow(id) { + if (id && id !== this.$constant.NO_ITEM_SELECTED) { + if (this.tableData && this.tableData.length > 0) { + for (let i = 0; i < this.tableData.length; i++) { + if (this.tableData[i].id === id) { + return this.tableData[i] + } + } + } + } + return undefined + }, + // 获取名称 + getName(id) { + const row = this.getRow(id) + if (row) { + return row[this.nameKey] + } + return undefined + }, + // 验证是否有选中行 + checkSelectedRowExist() { + if (this.currentId === this.$constant.NO_ITEM_SELECTED) { + this.$message.info('当前无选中行') + return false + } + return true + }, + // 双击事件 + rowDoubleClick(row) { + this.view(row.id) + }, + // 处理查询 + query() { + // 查询之前,将当前页置为1 + this.pageInfo.pageNum = 1 + this.refresh() + }, + // 处理分页变化 + pageChange(value) { + this.pageInfo.pageNum = value + this.refresh() + }, + // 处理分页大小变化 + pageSizeChange(value) { + this.pageInfo.pageSize = value + this.refresh() + } + }, + provide() { + return { + query: this.query, + view: this.view, + pageChange: this.pageChange, + pageSizeChange: this.pageSizeChange + } + } +} diff --git a/src/mixin/treeListReferenceMixin.js b/src/mixin/treeListReferenceMixin.js new file mode 100644 index 0000000..9b4cc3a --- /dev/null +++ b/src/mixin/treeListReferenceMixin.js @@ -0,0 +1,229 @@ +/** + * 树表参照页面混入 + */ +import { Dialog } from '@/components/abc/Dialog' +import CollapseTab from '@/components/abc/CollapseTab/index.vue' +import QueryText from '@/components/abc/QueryText/index.vue' +import QueryButton from '@/components/abc/QueryButton/index.vue' +import { ContentWrap } from '@/components/abc/ContentWrap' +import ListPager from '@/components/abc/ListPager/index.vue' +import ColumnsController from '@/components/abc/ColumnsController/index.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' + +export const treeListReferenceMixin = { + emits: ['confirm'], + components: { + ContentWrap, + CollapseTab, + QueryText, + QueryButton, + ListPager, + ColumnsController, + DictionarySelect, + DictionaryRadioGroup, + Dialog, + OrganizationSingleSelect, + OrganizationMultipleSelect, + IconPicker + }, + props: { + modelValue: { + type: String, + default: '', + required: false + } + }, + data() { + return { + // 表格数据 + tableData: [], + // 加载中 + loading: false, + // 当前行 + currentId: this.$constant.NO_ITEM_SELECTED, + // 复选框选中项 + multipleSelection: [], + // 分页信息 + pageInfo: { + // 页码 + pageNum: this.$constant.DEFAULT_PAGE_NUM, + // 页码大小 + pageSize: this.$constant.DEFAULT_PAGE_SIZE + }, + // 排序信息 + sortInfo: { + sort_field: 'orderNo', + sort_sortType: 'ascending' + }, + // 总页数 + pageTotal: 0, + queryCondition: { + // 默认值处理 + }, + // 名称键值 + nameKey: 'name', + // 显示名称 + displayName: '', + // 可见性 + visible: false, + commonParam: {} + } + }, + computed: { + showCols() { + return this.columnList.filter((item) => item.show) + }, + tableKey() { + const { path } = this.$route + return `${path}/table` + } + }, + watch: { + modelValue: { + immediate: true, + handler: 'getSelectedName' + }, + commonParam: { + immediate: true, + handler: 'handleParamChange', + deep: true + } + }, + methods: { + // 输入参数变化 + handleParamChange(value) { + if (this.commonParamChange) { + this.commonParamChange(value) + this.loadData() + } + }, + changeNode(value, name) { + this.treeNodeId = value + this.treeNodeName = name + this.commonParam = Object.assign({}, this.commonParam, { id: value }) + }, + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + // 加载数据 + loadData() { + return new Promise((resolve) => { + this.loading = true + const params = Object.assign(this.queryCondition, this.pageInfo, this.sortInfo) + this.api + .page(params) + .then((res) => { + this.tableData = res.data.records + this.pageTotal = res.data.total + resolve() + }) + .finally(() => { + this.loading = false + this.currentId = this.$constant.NO_ITEM_SELECTED + }) + }) + }, + close() { + this.visible = false + }, + // 清空选择 + clear() { + this.displayName = '' + this.$emit('update:modelValue', '') + this.$emit('my-change', '') + }, + confirm() { + if (!this.checkSelectedRowExist()) { + return + } + const selectedRow = this.getRow(this.currentId) + + this.displayName = selectedRow.name + // 更新父组件绑定值 + this.$emit('update:modelValue', selectedRow.id) + this.$emit('my-change', selectedRow.id, selectedRow) + this.visible = false + }, + + // 获取选中的名称 + getSelectedName() { + if (this.modelValue) { + this.api.get(this.modelValue).then((res) => { + this.displayName = res.data[this.nameKey] + }) + } + }, + // 处理查询 + query() { + // 查询之前,将当前页置为1 + this.pageInfo.pageNum = 1 + this.refresh() + }, + // 处理分页变化 + pageChange(value) { + this.pageInfo.pageNum = value + this.refresh() + }, + // 处理分页大小变化 + pageSizeChange(value) { + this.pageInfo.pageSize = value + this.refresh() + }, + // 刷新 + refresh() { + this.loadData() + }, + // 处理排序 + // eslint-disable-next-line no-unused-vars + sortChange({ column, prop, order }) { + this.sortInfo.sort_field = prop + this.sortInfo.sort_sortType = order + this.refresh() + }, + // 当前行变化 + rowChange(currentRow) { + this.currentId = currentRow ? currentRow.id : this.$constant.NO_ITEM_SELECTED + }, + // 验证是否有选中行 + checkSelectedRowExist() { + if (this.currentId === this.$constant.NO_ITEM_SELECTED) { + this.$message.info('当前无选中行') + return false + } + return true + }, + // 获取行记录 + getRow(id) { + if (id && id !== this.$constant.NO_ITEM_SELECTED) { + if (this.tableData && this.tableData.length > 0) { + for (let i = 0; i < this.tableData.length; i++) { + if (this.tableData[i].id === id) { + return this.tableData[i] + } + } + } + } + return undefined + } + }, + provide() { + return { + query: this.query, + pageChange: this.pageChange, + pageSizeChange: this.pageSizeChange + } + } +} diff --git a/src/mixin/treeMixin.js b/src/mixin/treeMixin.js new file mode 100644 index 0000000..69fc2ac --- /dev/null +++ b/src/mixin/treeMixin.js @@ -0,0 +1,73 @@ +/** + * 树页面混入 + */ +import { ContentWrap } from '@/components/abc/ContentWrap' +export const treeMixin = { + components: { ContentWrap }, + data() { + return { + treeData: [], + cacheTreeExpandedKeys: [], + currentId: '', + currentName: '', + // 数据更新是否需要刷新树 + isRefreshTree: false + } + }, + mounted() { + this.init() + }, + methods: { + // 初始化树 + init(id) { + this.load(id) + }, + load(id) { + return new Promise((resolve) => { + this.api.tree().then((res) => { + this.treeData = res.data + if (id) { + this.$refs.tree.setCurrentKey(id) + this.parentId = id + this.cacheTreeExpandedKeys.push(id) + const node = this.$refs.tree.getCurrentNode + // 手工触发选择节点改变 + this.$emit('change-selected', id, node.label) + } else { + // 如没有默认选中节点 + if (!this.currentId || this.currentId === '') { + // 默认设置根节点 + this.currentId = this.treeData[0].id + this.currentName = this.treeData[0].label + + // 设置根节点默认展开 + this.cacheTreeExpandedKeys.push(this.treeData[0].id) + // 手工触发选择节点改变 + this.$emit('change-selected', this.treeData[0].id, this.treeData[0].label) + } + } + }) + }) + }, + // 树表相关操作 + handleTreeSelectChange(data) { + // 保存标识及名称用于新增操作 + this.currentId = data.id + this.currentName = data.label + this.$emit('change-selected', this.currentId, this.currentName) + }, + + // 展开树节点 + handleNodeExpand(data) { + this.cacheTreeExpandedKeys.push(data.id) + }, + // 折叠树节点 + handleNodeCollapse(data) { + const index = this.cacheTreeExpandedKeys.findIndex((item) => item === data.id) + this.cacheTreeExpandedKeys.splice(index, 1) + } + }, + provide() { + return {} + } +} diff --git a/src/mixin/treeMultipleDirectReferenceMixin.js b/src/mixin/treeMultipleDirectReferenceMixin.js new file mode 100644 index 0000000..5b3d90b --- /dev/null +++ b/src/mixin/treeMultipleDirectReferenceMixin.js @@ -0,0 +1,91 @@ +/** + * 树多选页面混入 + * 通过按钮触发对话框,确认后触发事件返回数据,而不是表单上某个属性触发和回写 + */ +import { Dialog } from '@/components/abc/Dialog' + +export const treeMultipleDirectReferenceMixin = { + emits: ['confirm'], + components: { + Dialog + }, + + data() { + return { + treeData: [], + cacheTreeExpandedKeys: [], + // 搜索值 + searchValue: '', + // 可见性 + visible: false, + checkedNodesId: [], + selectedValue: [] + } + }, + watch: { + searchValue(value) { + this.$refs.tree.filter(value) + } + }, + methods: { + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.searchValue = '' + this.cacheTreeExpandedKeys = [] + this.selectedValue = param.data + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + loadData() { + return new Promise((resolve) => { + this.api.tree().then((res) => { + this.treeData = res.data + // 默认展开根节点 + this.cacheTreeExpandedKeys.push(this.treeData[0].id) + this.checkedNodesId = [] + this.getLeafNodeChecked(this.treeData) + // this.$refs.tree.setCheckedKeys(this.checkedNodesId) + resolve() + }) + }) + }, + + // 根据名称查询树节点 + filterNode(value, data) { + if (!value) return true + return data.label.indexOf(value) !== -1 + }, + close() { + this.visible = false + }, + confirm() { + // 获取半选节点ID + const halfCheckedKeys = this.$refs.tree.getHalfCheckedKeys() + // 拼接全选节点ID + const idList = halfCheckedKeys.concat(this.$refs.tree.getCheckedKeys()) + this.$emit('confirm', idList) + this.visible = false + }, + getLeafNodeChecked(node) { + // 遍历树节点,设置 + for (const treeNode of node) { + // 如果节点有子节点,那他的选中状态不被考虑,继续往下找 + if (treeNode.children && treeNode.children.length > 0) { + this.getLeafNodeChecked(treeNode.children) + } else { + // 是叶子节点,如果是check状态就记录 + if (this.selectedValue.includes(treeNode.id)) { + this.checkedNodesId.push(treeNode.id) + } + } + } + } + } +} diff --git a/src/mixin/treeMultipleReferenceMixin.js b/src/mixin/treeMultipleReferenceMixin.js new file mode 100644 index 0000000..f153d6a --- /dev/null +++ b/src/mixin/treeMultipleReferenceMixin.js @@ -0,0 +1,117 @@ +/** + * 树多选页面混入 + * 表单上某个属性触发和回写,值为数组 + */ +import { Dialog } from '@/components/abc/Dialog' + +export const treeMultipleReferenceMixin = { + emits: ['confirm'], + components: { + Dialog + }, + props: { + modelValue: { + type: Array, + default: () => [], + required: false + } + }, + data() { + return { + treeData: [], + cacheTreeExpandedKeys: [], + // 显示名称 + displayName: '', + // 搜索值 + searchValue: '', + // 可见性 + visible: false, + checkedNodesId: [], + selectedValue: [] + } + }, + watch: { + modelValue: { + immediate: true, + handler: 'getSelectedName' + }, + searchValue(value) { + this.$refs.tree.filter(value) + } + }, + methods: { + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.searchValue = '' + this.cacheTreeExpandedKeys = [] + this.selectedValue = this.modelValue + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + loadData() { + return new Promise((resolve) => { + this.api.tree().then((res) => { + this.treeData = res.data + // 默认展开根节点 + this.cacheTreeExpandedKeys.push(this.treeData[0].id) + this.checkedNodesId = [] + this.getLeafNodeChecked(this.treeData) + resolve() + }) + }) + }, + + // 根据名称查询树节点 + filterNode(value, data) { + if (!value) return true + return data.label.indexOf(value) !== -1 + }, + close() { + this.visible = false + }, + // 清空选择 + clear() { + this.displayName = '' + this.$emit('update:modelValue', []) + }, + confirm() { + // 获取半选节点ID + const halfCheckedKeys = this.$refs.tree.getHalfCheckedKeys() + // 拼接全选节点ID + const idList = halfCheckedKeys.concat(this.$refs.tree.getCheckedKeys()) + this.$emit('update:modelValue', idList) + this.visible = false + }, + getLeafNodeChecked(node) { + if (this.selectedValue) { + // 遍历树节点,设置 + for (const treeNode of node) { + // 如果节点有子节点,那他的选中状态不被考虑,继续往下找 + if (treeNode.children && treeNode.children.length > 0) { + this.getLeafNodeChecked(treeNode.children) + } else { + // 是叶子节点,如果是check状态就记录 + if (this.selectedValue.includes(treeNode.id)) { + this.checkedNodesId.push(treeNode.id) + } + } + } + } + }, + // 获取选中的名称 + getSelectedName() { + let length = 0 + if (this.modelValue) { + length = this.modelValue.length + } + this.displayName = '已选择[ ' + length + ' ]条' + } + } +} diff --git a/src/mixin/treeMultipleSelectMixin.js b/src/mixin/treeMultipleSelectMixin.js new file mode 100644 index 0000000..6a89627 --- /dev/null +++ b/src/mixin/treeMultipleSelectMixin.js @@ -0,0 +1,120 @@ +/** + * 树多选页面混入 + * 表单上某个属性触发和回写,值为字符串(元素以逗号间隔) + */ +import { Dialog } from '@/components/abc/Dialog' + +export const treeMultipleSelectMixin = { + emits: ['confirm'], + components: { + Dialog + }, + props: { + modelValue: { + type: String, + default: '', + required: false + } + }, + data() { + return { + treeData: [], + cacheTreeExpandedKeys: [], + // 显示名称 + displayName: '', + // 搜索值 + searchValue: '', + // 可见性 + visible: false, + checkedNodesId: [], + selectedValue: [] + } + }, + watch: { + modelValue: { + immediate: true, + handler: 'getSelectedName' + }, + searchValue(value) { + this.$refs.tree.filter(value) + } + }, + methods: { + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.searchValue = '' + this.cacheTreeExpandedKeys = [] + if (this.modelValue) { + this.selectedValue = this.modelValue.split(',') + } + + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + loadData() { + return new Promise((resolve) => { + this.api.tree().then((res) => { + this.treeData = res.data + // 默认展开根节点 + this.cacheTreeExpandedKeys.push(this.treeData[0].id) + this.checkedNodesId = [] + this.getLeafNodeChecked(this.treeData) + resolve() + }) + }) + }, + + // 根据名称查询树节点 + filterNode(value, data) { + if (!value) return true + return data.label.indexOf(value) !== -1 + }, + close() { + this.visible = false + }, + // 清空选择 + clear() { + this.displayName = '' + this.$emit('update:modelValue', '') + }, + confirm() { + // 获取半选节点ID + const halfCheckedKeys = this.$refs.tree.getHalfCheckedKeys() + // 拼接全选节点ID + const idList = halfCheckedKeys.concat(this.$refs.tree.getCheckedKeys()) + this.$emit('update:modelValue', idList.join(',')) + this.visible = false + }, + getLeafNodeChecked(node) { + if (this.selectedValue) { + // 遍历树节点,设置 + for (const treeNode of node) { + // 如果节点有子节点,那他的选中状态不被考虑,继续往下找 + if (treeNode.children && treeNode.children.length > 0) { + this.getLeafNodeChecked(treeNode.children) + } else { + // 是叶子节点,如果是check状态就记录 + if (this.selectedValue.includes(treeNode.id)) { + this.checkedNodesId.push(treeNode.id) + } + } + } + } + }, + // 获取选中的名称 + getSelectedName() { + let length = 0 + if (this.modelValue) { + length = this.modelValue.split(',').length + } + this.displayName = '已选择[ ' + length + ' ]条' + } + } +} diff --git a/src/mixin/treeReferenceMixin.js b/src/mixin/treeReferenceMixin.js new file mode 100644 index 0000000..2d00f00 --- /dev/null +++ b/src/mixin/treeReferenceMixin.js @@ -0,0 +1,136 @@ +/** + * 树参照页面混入 + */ +import { Dialog } from '@/components/abc/Dialog' + +export const treeReferenceMixin = { + emits: ['update:modelValue', 'my-change', 'change-selected'], + components: { + Dialog + }, + + data() { + return { + treeData: [], + cacheTreeExpandedKeys: [], + currentId: '', + currentName: '', + // 显示名称 + displayName: '', + // 搜索值 + searchValue: '', + // 可见性 + visible: false + } + }, + props: { + modelValue: { + type: String, + default: '', + required: false + }, + disabled: { + type: Boolean, + required: false, + default: false + } + }, + watch: { + modelValue: { + immediate: true, + handler: 'getSelectedName' + }, + searchValue(value) { + this.$refs.tree.filter(value) + } + }, + methods: { + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.currentId = this.modelValue + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + loadData() { + return new Promise((resolve) => { + this.api.tree().then((res) => { + this.treeData = res.data + // 如没有默认选中节点 + if (!this.currentId || this.currentId === '') { + // 默认设置根节点 + this.currentId = this.treeData[0].id + this.currentName = this.treeData[0].label + + // 设置根节点默认展开 + this.cacheTreeExpandedKeys.push(this.treeData[0].id) + + // 手工触发选择节点改变 + this.$emit('change-selected', this.treeData[0].id, this.treeData[0].label) + } else { + // 默认展开当前节点 + this.cacheTreeExpandedKeys.push(this.currentId) + // 手工触发选择节点改变 + this.$emit('change-selected', this.currentId, this.currentName) + } + resolve() + }) + }) + }, + // 树表相关操作 + handleTreeSelectChange(data) { + // 保存标识及名称用于新增操作 + this.currentId = data.id + this.currentName = data.label + this.$emit('change-selected', this.currentId, this.currentName) + }, + + // 展开树节点 + handleNodeExpand(data) { + this.cacheTreeExpandedKeys.push(data.id) + }, + // 折叠树节点 + handleNodeCollapse(data) { + const index = this.cacheTreeExpandedKeys.findIndex((item) => item === data.id) + this.cacheTreeExpandedKeys.splice(index, 1) + }, + // 根据名称查询树节点 + filterNode(value, data) { + if (!value) return true + return data.label.indexOf(value) !== -1 + }, + close() { + this.visible = false + }, + confirm() { + // 更新父组件绑定值 + this.$emit('update:modelValue', this.currentId) + this.$emit('my-change', this.currentId, this.currentName) + this.visible = false + }, + // 清空选择 + clear() { + this.displayName = '' + this.$emit('update:modelValue', '') + this.$emit('my-change', '', '') + }, + // 获取选中的名称 + getSelectedName() { + if (this.modelValue) { + this.api.get(this.modelValue).then((res) => { + this.displayName = res.data[this.nameKey] + this.currentName = this.displayName + }) + } + } + }, + provide() { + return {} + } +} diff --git a/src/mixin/treeSingleSelectMixin.js b/src/mixin/treeSingleSelectMixin.js new file mode 100644 index 0000000..2fbb2fe --- /dev/null +++ b/src/mixin/treeSingleSelectMixin.js @@ -0,0 +1,77 @@ +/** + * 树单选页面混入 + * 通过按钮触发对话框,确认后触发事件返回数据,而不是表单上某个属性触发和回写 + */ +import { Dialog } from '@/components/abc/Dialog' + +export const treeSingleSelectMixin = { + emits: ['confirm'], + components: { + Dialog + }, + + data() { + return { + treeData: [], + cacheTreeExpandedKeys: [], + // 搜索值 + searchValue: '', + // 可见性 + visible: false, + + selectedValue: [] + } + }, + watch: { + searchValue(value) { + this.$refs.tree.filter(value) + } + }, + methods: { + // 初始化 + init(param) { + if (this.beforeInit != null) { + this.beforeInit(param) + } + this.searchValue = '' + this.cacheTreeExpandedKeys = [] + this.$refs.tree.setCurrentKey(param.data) + this.loadData().then((res) => { + if (this.afterInit) { + this.afterInit(param) + } + this.visible = true + }) + }, + loadData() { + return new Promise((resolve) => { + this.api.tree().then((res) => { + this.treeData = res.data + // 默认展开根节点 + this.cacheTreeExpandedKeys.push(this.treeData[0].id) + resolve() + }) + }) + }, + + // 根据名称查询树节点 + filterNode(value, data) { + if (!value) return true + return data.label.indexOf(value) !== -1 + }, + close() { + this.visible = false + }, + confirm() { + // 获取选中值 + const id = this.$refs.tree.getCurrentKey() + if (id) { + this.$emit('confirm', id) + this.visible = false + } else { + this.$message.warning('请选择树节点') + return + } + } + } +} diff --git a/src/mixin/treelistMixin.js b/src/mixin/treelistMixin.js new file mode 100644 index 0000000..37dea9c --- /dev/null +++ b/src/mixin/treelistMixin.js @@ -0,0 +1,23 @@ +/** + * 树表页面混入 + */ +export const treeListMixin = { + data() { + return { + commonParam: {}, + treeNodeId: '', + treeNodeName: '' + } + }, + + methods: { + changeNode(value, name) { + this.treeNodeId = value + this.treeNodeName = name + this.commonParam = Object.assign(this.commonParam, { id: value }) + }, + refresh() { + this.$refs.treeView.load() + } + } +} diff --git a/src/mixin/viewForAdvanceConfigMixin.js b/src/mixin/viewForAdvanceConfigMixin.js new file mode 100644 index 0000000..c6e255a --- /dev/null +++ b/src/mixin/viewForAdvanceConfigMixin.js @@ -0,0 +1,54 @@ +/** + * 查看页面混入 + */ + +import { Dialog } from '@/components/abc/Dialog' + +export const viewMixin = { + components: { + Dialog + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false, + fApi: {}, + entityId: '', + formValue: {} + } + }, + + methods: { + // 初始化 + init(id) { + if (this.beforeInit) { + this.beforeInit() + } + this.api.get(id).then((res) => { + this.entityData = res.data + this.formValue = res.data + this.entityId = res.data.id + this.updateData() + if (this.afterInit) { + this.afterInit() + } + this.visible = true + }) + }, + //更新标签值 + updateData() { + const ruleString = JSON.stringify(this.rule) + .replaceAll('@EntityId@', this.entityId, 'g') + .replaceAll('@EntityType@', this.entityType, 'g') + .replaceAll('@ModuleCode@', this.moduleCode, 'g') + this.rule = JSON.parse(ruleString) + }, + + // 关闭 + close() { + this.visible = false + } + } +} diff --git a/src/mixin/viewMixin.js b/src/mixin/viewMixin.js new file mode 100644 index 0000000..aa33483 --- /dev/null +++ b/src/mixin/viewMixin.js @@ -0,0 +1,58 @@ +/** + * 查看页面混入 + */ + +import { Dialog } from '@/components/abc/Dialog' +import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue' +import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue' +import DataDictionarySelect from '@/modules/system/view/dictionaryType/treeReferenceUseCode.vue' +import IconPicker from '@/components/abc/IconPicker/index.vue' +import { Editor } from '@/components/abc/Editor' +import CronExpression from '@/components/abc/CronExpression/index.vue' +import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue' +import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue' +import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue' +import AttachmentViewer from '@/modules/support/view/attachment/attachmentViewer.vue' +export const viewMixin = { + components: { + Dialog, + DictionaryRadioGroup, + DictionarySelect, + DataDictionarySelect, + IconPicker, + Editor, + CronExpression, + OrganizationSingleSelect, + OrganizationMultipleSelect, + UserSingleSelect, + AttachmentViewer + }, + data() { + return { + // 可见性 + visible: false, + // 加载中 + loading: false + } + }, + + methods: { + // 初始化 + init(id) { + if (this.beforeInit) { + this.beforeInit() + } + this.api.get(id).then((res) => { + this.entityData = res.data + if (this.afterInit) { + this.afterInit() + } + this.visible = true + }) + }, + // 关闭 + close() { + this.visible = false + } + } +} diff --git a/src/modules/businessflow/api/index.ts b/src/modules/businessflow/api/index.ts new file mode 100644 index 0000000..0368b1c --- /dev/null +++ b/src/modules/businessflow/api/index.ts @@ -0,0 +1,26 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'businessflow' + +// 请假申请 +export const leave = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'leave' + '/', + getByBillNo(billNo) { + return request.get({ url: this.serveUrl + 'getByBillNo/' + billNo }) + } +}) + +export const navigate = Object.assign( + {}, + { + serveUrl: '/' + moduleName + '/' + 'navigate' + '/', + get() { + return request.get({ url: this.serveUrl }) + }, + // 获取级联框数据 + cascader() { + return request.get({ url: this.serveUrl + 'cascader' }) + } + } +) diff --git a/src/modules/businessflow/constant/index.ts b/src/modules/businessflow/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/businessflow/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/businessflow/view/dictionary/list.js b/src/modules/businessflow/view/dictionary/list.js new file mode 100644 index 0000000..b216fbb --- /dev/null +++ b/src/modules/businessflow/view/dictionary/list.js @@ -0,0 +1,2 @@ +// 请假流程 +export { default as Leave } from '@/modules/businessflow/view/leave/detail.vue' diff --git a/src/modules/businessflow/view/leave/add.vue b/src/modules/businessflow/view/leave/add.vue new file mode 100644 index 0000000..87f29e6 --- /dev/null +++ b/src/modules/businessflow/view/leave/add.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/modules/businessflow/view/leave/detail.vue b/src/modules/businessflow/view/leave/detail.vue new file mode 100644 index 0000000..c5aaa18 --- /dev/null +++ b/src/modules/businessflow/view/leave/detail.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/src/modules/businessflow/view/leave/list.vue b/src/modules/businessflow/view/leave/list.vue new file mode 100644 index 0000000..bd9638c --- /dev/null +++ b/src/modules/businessflow/view/leave/list.vue @@ -0,0 +1,193 @@ + + + diff --git a/src/modules/businessflow/view/leave/modify.vue b/src/modules/businessflow/view/leave/modify.vue new file mode 100644 index 0000000..361b7fe --- /dev/null +++ b/src/modules/businessflow/view/leave/modify.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/modules/businessflow/view/leave/view.vue b/src/modules/businessflow/view/leave/view.vue new file mode 100644 index 0000000..3ba02d9 --- /dev/null +++ b/src/modules/businessflow/view/leave/view.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/modules/businessflow/view/navigate/index.vue b/src/modules/businessflow/view/navigate/index.vue new file mode 100644 index 0000000..2f269f8 --- /dev/null +++ b/src/modules/businessflow/view/navigate/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/modules/cip/api/index.ts b/src/modules/cip/api/index.ts new file mode 100644 index 0000000..bec21e7 --- /dev/null +++ b/src/modules/cip/api/index.ts @@ -0,0 +1,112 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'cip' + +// 应用 +export const app = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'app' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + // 重置密钥 + resetSecret(id) { + return request.put({ url: this.serveUrl + id + '/resetSecret' }) + }, + // 授予接口服务权限 + grantApiPermission(id, apiServiceIdList) { + return request.put({ url: this.serveUrl + id + '/grantApiPermission', data: apiServiceIdList }) + }, + // 收回接口服务权限 + withdrawApiPermission(id, apiServiceIdList) { + return request.put({ + url: this.serveUrl + id + '/withdrawApiPermission', + data: apiServiceIdList + }) + }, + // 授予消息主题权限 + grantMessagePermission(id, messageTopicIdList) { + return request.put({ + url: this.serveUrl + id + '/grantMessagePermission', + data: messageTopicIdList + }) + }, + // 收回消息主题权限 + withdrawMessagePermission(id, messageTopicIdList) { + return request.put({ + url: this.serveUrl + id + '/withdrawMessagePermission', + data: messageTopicIdList + }) + }, + // 订阅消息 + subscribeMessage(messageTopicIdList) { + return request.put({ + url: this.serveUrl + 'subscribeMessage', + data: messageTopicIdList + }) + }, + // 取消订阅消息 + unsubscribeMessage(messageTopicIdList) { + return request.put({ + url: this.serveUrl + 'unsubscribeMessage', + data: messageTopicIdList + }) + } +}) + +// 接口服务 +export const apiService = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'apiService' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + queryApiPermission(params) { + return request.get({ url: this.serveUrl + 'queryApiPermission', params }) + } +}) + +// 接口服务日志 +export const apiServiceLog = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'apiServiceLog' + '/' +}) + +// 消息主题 +export const messageTopic = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'messageTopic' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + queryMessagePermission(params) { + return request.get({ url: this.serveUrl + 'queryMessagePermission', params }) + }, + queryMessageSubscription(params) { + return request.get({ url: this.serveUrl + 'queryMessageSubscription', params }) + } +}) + +// 消息服务日志 +export const messageLog = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'messageLog' + '/' +}) + +// 数据权限 +export const appDataPermission = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'appDataPermission' + '/' +}) + +// 活跃消息 +export const activeMessage = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'activeMessage' + '/', + resend(id) { + return request.put({ url: this.serveUrl + 'resend' + '/' + id }) + } +}) diff --git a/src/modules/cip/constant/index.ts b/src/modules/cip/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/cip/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/cip/view/activeMessage/list.vue b/src/modules/cip/view/activeMessage/list.vue new file mode 100644 index 0000000..30aacee --- /dev/null +++ b/src/modules/cip/view/activeMessage/list.vue @@ -0,0 +1,295 @@ + + + diff --git a/src/modules/cip/view/activeMessage/view.vue b/src/modules/cip/view/activeMessage/view.vue new file mode 100644 index 0000000..ac8a2dc --- /dev/null +++ b/src/modules/cip/view/activeMessage/view.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/modules/cip/view/apiService/add.vue b/src/modules/cip/view/apiService/add.vue new file mode 100644 index 0000000..1dd1b2d --- /dev/null +++ b/src/modules/cip/view/apiService/add.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/src/modules/cip/view/apiService/list.vue b/src/modules/cip/view/apiService/list.vue new file mode 100644 index 0000000..3cce464 --- /dev/null +++ b/src/modules/cip/view/apiService/list.vue @@ -0,0 +1,204 @@ + + + diff --git a/src/modules/cip/view/apiService/modify.vue b/src/modules/cip/view/apiService/modify.vue new file mode 100644 index 0000000..d689c61 --- /dev/null +++ b/src/modules/cip/view/apiService/modify.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/src/modules/cip/view/apiService/reference.vue b/src/modules/cip/view/apiService/reference.vue new file mode 100644 index 0000000..a302aab --- /dev/null +++ b/src/modules/cip/view/apiService/reference.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/modules/cip/view/apiService/view.vue b/src/modules/cip/view/apiService/view.vue new file mode 100644 index 0000000..61811a3 --- /dev/null +++ b/src/modules/cip/view/apiService/view.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/modules/cip/view/apiServiceLog/list.vue b/src/modules/cip/view/apiServiceLog/list.vue new file mode 100644 index 0000000..930609c --- /dev/null +++ b/src/modules/cip/view/apiServiceLog/list.vue @@ -0,0 +1,210 @@ + + + diff --git a/src/modules/cip/view/apiServiceLog/view.vue b/src/modules/cip/view/apiServiceLog/view.vue new file mode 100644 index 0000000..7448ada --- /dev/null +++ b/src/modules/cip/view/apiServiceLog/view.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/modules/cip/view/app/add.vue b/src/modules/cip/view/app/add.vue new file mode 100644 index 0000000..88b4ec1 --- /dev/null +++ b/src/modules/cip/view/app/add.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/cip/view/app/apiServicePermission.vue b/src/modules/cip/view/app/apiServicePermission.vue new file mode 100644 index 0000000..daa29a1 --- /dev/null +++ b/src/modules/cip/view/app/apiServicePermission.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/src/modules/cip/view/app/list.vue b/src/modules/cip/view/app/list.vue new file mode 100644 index 0000000..c633887 --- /dev/null +++ b/src/modules/cip/view/app/list.vue @@ -0,0 +1,243 @@ + + + diff --git a/src/modules/cip/view/app/messagePermission.vue b/src/modules/cip/view/app/messagePermission.vue new file mode 100644 index 0000000..2870eb7 --- /dev/null +++ b/src/modules/cip/view/app/messagePermission.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/modules/cip/view/app/modify.vue b/src/modules/cip/view/app/modify.vue new file mode 100644 index 0000000..d283571 --- /dev/null +++ b/src/modules/cip/view/app/modify.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/cip/view/app/reference.vue b/src/modules/cip/view/app/reference.vue new file mode 100644 index 0000000..eb73de9 --- /dev/null +++ b/src/modules/cip/view/app/reference.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/modules/cip/view/app/view.vue b/src/modules/cip/view/app/view.vue new file mode 100644 index 0000000..bedad8e --- /dev/null +++ b/src/modules/cip/view/app/view.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/src/modules/cip/view/appDataPermission/add.vue b/src/modules/cip/view/appDataPermission/add.vue new file mode 100644 index 0000000..e1a6943 --- /dev/null +++ b/src/modules/cip/view/appDataPermission/add.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/cip/view/appDataPermission/list.vue b/src/modules/cip/view/appDataPermission/list.vue new file mode 100644 index 0000000..44619ae --- /dev/null +++ b/src/modules/cip/view/appDataPermission/list.vue @@ -0,0 +1,167 @@ + + + diff --git a/src/modules/cip/view/appDataPermission/modify.vue b/src/modules/cip/view/appDataPermission/modify.vue new file mode 100644 index 0000000..1cb3d7b --- /dev/null +++ b/src/modules/cip/view/appDataPermission/modify.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/cip/view/appDataPermission/view.vue b/src/modules/cip/view/appDataPermission/view.vue new file mode 100644 index 0000000..f4076e4 --- /dev/null +++ b/src/modules/cip/view/appDataPermission/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/cip/view/messageLog/list.vue b/src/modules/cip/view/messageLog/list.vue new file mode 100644 index 0000000..2962936 --- /dev/null +++ b/src/modules/cip/view/messageLog/list.vue @@ -0,0 +1,267 @@ + + + diff --git a/src/modules/cip/view/messageLog/view.vue b/src/modules/cip/view/messageLog/view.vue new file mode 100644 index 0000000..ab68812 --- /dev/null +++ b/src/modules/cip/view/messageLog/view.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/modules/cip/view/messageSubscription/list.vue b/src/modules/cip/view/messageSubscription/list.vue new file mode 100644 index 0000000..5164a28 --- /dev/null +++ b/src/modules/cip/view/messageSubscription/list.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/src/modules/cip/view/messageTopic/add.vue b/src/modules/cip/view/messageTopic/add.vue new file mode 100644 index 0000000..f921c40 --- /dev/null +++ b/src/modules/cip/view/messageTopic/add.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/cip/view/messageTopic/list.vue b/src/modules/cip/view/messageTopic/list.vue new file mode 100644 index 0000000..0ecfb3b --- /dev/null +++ b/src/modules/cip/view/messageTopic/list.vue @@ -0,0 +1,209 @@ + + + diff --git a/src/modules/cip/view/messageTopic/modify.vue b/src/modules/cip/view/messageTopic/modify.vue new file mode 100644 index 0000000..8ed7e76 --- /dev/null +++ b/src/modules/cip/view/messageTopic/modify.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/cip/view/messageTopic/reference.vue b/src/modules/cip/view/messageTopic/reference.vue new file mode 100644 index 0000000..9c30190 --- /dev/null +++ b/src/modules/cip/view/messageTopic/reference.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/modules/cip/view/messageTopic/view.vue b/src/modules/cip/view/messageTopic/view.vue new file mode 100644 index 0000000..2b49c28 --- /dev/null +++ b/src/modules/cip/view/messageTopic/view.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/modules/echart/monthlySales.vue b/src/modules/echart/monthlySales.vue new file mode 100644 index 0000000..239b498 --- /dev/null +++ b/src/modules/echart/monthlySales.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/modules/echart/stageSpeedGauge.vue b/src/modules/echart/stageSpeedGauge.vue new file mode 100644 index 0000000..af289c9 --- /dev/null +++ b/src/modules/echart/stageSpeedGauge.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/modules/echart/userSource.vue b/src/modules/echart/userSource.vue new file mode 100644 index 0000000..58806ef --- /dev/null +++ b/src/modules/echart/userSource.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/modules/echart/weeklyUserActivity.vue b/src/modules/echart/weeklyUserActivity.vue new file mode 100644 index 0000000..3dd15a1 --- /dev/null +++ b/src/modules/echart/weeklyUserActivity.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/modules/entityconfig/api/index.ts b/src/modules/entityconfig/api/index.ts new file mode 100644 index 0000000..a7f2381 --- /dev/null +++ b/src/modules/entityconfig/api/index.ts @@ -0,0 +1,177 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'entityconfig' + +// 实体 +export const entity = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'entity' + '/', + // 生成库表 + generateTable(idList) { + return request.post({ url: this.serveUrl + 'generateTable/' + idList }) + }, + // 生成代码 + generateCode(idList) { + return request.post({ url: this.serveUrl + 'generateCode/' + idList }) + } +}) + +// 实体模型 +export const entityModel = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'entityModel' + '/' +}) + +// 实体模型属性 +export const entityModelProperty = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'entityModelProperty' + '/', + // 获取实体模型完整属性列表 + getFullPropertyList(entityModelId) { + return request.get({ url: this.serveUrl + entityModelId + '/list' }) + } +}) + +// 实体视图 +export const entityView = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'entityView' + '/', + // 更新高级配置 + updateAdvanceConfig(params) { + return request.put({ url: this.serveUrl + 'updateAdvanceConfig', data: params }) + } +}) + +// 视图按钮模板 +export const viewButtonTemplate = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'viewButtonTemplate' + '/' +}) + +// 视图按钮 +export const viewButton = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'viewButton' + '/', + + // 获取视图对应的按钮列表,对按钮类型进行过滤 + listByViewAndType(viewId, buttonType) { + return request.get({ + url: this.serveUrl + viewId + '/listByViewAndType', + params: { + buttonType + } + }) + }, + // 更新按钮顺序 + updateButtonSort(viewId, params) { + return request.put({ url: this.serveUrl + viewId + '/updateButtonSort', data: params }) + }, + // 清空 + clear(viewId, buttonType) { + return request.delete({ url: this.serveUrl + viewId + '/' + buttonType + '/clear' }) + }, + // 通过模板添加 + addFromTemplate(viewId, buttonType, params) { + return request.post({ + url: this.serveUrl + viewId + '/' + buttonType + '/addFromTemplate/', + data: params + }) + } +}) + +// 列表视图配置 +export const listViewConfig = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'entityViewConfig' + '/' +}) + +// 修改视图配置 +export const modifyViewConfig = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'modifyViewConfig' + '/' +}) + +// 视图查询条件 +export const viewQueryCondition = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'viewQueryCondition' + '/', + // 获取视图对应的列表 + listByView(viewId) { + return request.get({ url: this.serveUrl + viewId + '/list' }) + }, + // 更新顺序 + updateSort(viewId, params) { + return request.put({ + url: this.serveUrl + viewId + '/updateSort', + data: params, + showInfo: false + }) + }, + // 通过模型属性添加单个 + addFromModelProperty(viewId, code) { + return request.post({ url: this.serveUrl + viewId + '/addFromModelProperty/' + code }) + }, + // 清空 + clear(viewId) { + return request.delete({ url: this.serveUrl + viewId + '/clear' }) + }, + // 通过模型属性批量添加 + addBatchFromModelProperty(viewId) { + return request.post({ url: this.serveUrl + viewId + '/addBatchFromModelProperty' }) + } +}) + +// 视图查询结果 +export const viewQueryResult = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'viewQueryResult' + '/', + // 获取视图对应的列表 + listByView(viewId) { + return request.get({ url: this.serveUrl + viewId + '/list' }) + }, + // 更新顺序 + updateSort(viewId, params) { + return request.put({ + url: this.serveUrl + viewId + '/updateSort', + data: params, + showInfo: false + }) + }, + // 通过模型属性添加单个 + addFromModelProperty(viewId, code) { + return request.post({ url: this.serveUrl + viewId + '/addFromModelProperty/' + code }) + }, + // 清空 + clear(viewId) { + return request.delete({ url: this.serveUrl + viewId + '/clear' }) + }, + // 通过模型属性批量添加 + addBatchFromModelProperty(viewId) { + return request.post({ url: this.serveUrl + viewId + '/addBatchFromModelProperty' }) + } +}) + +// 视图属性 +export const viewProperty = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'viewProperty' + '/', + // 获取视图对应的列表 + listByView(viewId) { + return request.get({ url: this.serveUrl + viewId + '/list' }) + }, + // 更新顺序 + updateSort(viewId, params) { + return request.put({ + url: this.serveUrl + viewId + '/updateSort', + data: params, + showInfo: false + }) + }, + // 通过模型属性添加单个 + addFromModelProperty(viewId, code) { + return request.post({ url: this.serveUrl + viewId + '/addFromModelProperty/' + code }) + }, + // 清空 + clear(viewId) { + return request.delete({ url: this.serveUrl + viewId + '/clear' }) + }, + // 通过模型属性批量添加 + addBatchFromModelProperty(viewId) { + return request.post({ url: this.serveUrl + viewId + '/addBatchFromModelProperty' }) + } +}) + +// 模板 +export const template = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'template' + '/' +}) diff --git a/src/modules/entityconfig/constant/index.ts b/src/modules/entityconfig/constant/index.ts new file mode 100644 index 0000000..4f704d3 --- /dev/null +++ b/src/modules/entityconfig/constant/index.ts @@ -0,0 +1,60 @@ +// 实体模型属性类型 +export const ENTITY_MODEL_PROPERTY_TYPE = 'EntityModelPropertyType' + +// 视图类型 +export const ENTITY_VIEW_TYPE = 'EntityViewType' + +// 基础模型 +export const BASE_MODEL = 'BaseModel' + +// 按钮模板默认视图标识,设置为-1,与正常的按钮区分 +export const BUTTON_TEMPLATE_VIEW_ID = '-1' + +// 文本匹配规则 +export const TEXT_PATTERN_RULE = 'TextPatternRule' + +// 数据字典 +// 数据类型 +export const DATA_TYPE = { + STRING: 'STRING', + INTEGER: 'INTEGER', + LONG: 'LONG', + DOUBLE: 'DOUBLE', + DECIMAL: 'DECIMAL', + DATETIME: 'DATETIME', + DATA_DICTIONARY: 'DATA_DICTIONARY', + ENTITY: 'ENTITY', + ICON_PICKER: 'ICON', + ORGANIZATION_SINGLE: 'ORGANIZATION_SINGLE', + ORGANIZATION_MULTIPLE: 'ORGANIZATION_MULTIPLE', + USER_SINGLE: 'USER_SINGLE', + ATTACHMENT: 'ATTACHMENT', + SERIAL_NO: 'SERIAL_NO' +} + +// 显示控件类型 +export const WIDGET_TYPE = { + TEXT: 'TextDisplayComponent', + NUMBER: 'NumberDisplayComponent', + DATETIME: 'DateTimeDisplayComponent', + DATA_DICTIONARY: 'DataDictionaryDisplayComponent', + ENTITY: 'EntityDisplayComponent', + ICON_PICKER: 'IconPickerDisplayComponent', + ATTACHMENT: 'AttachmentComponent', + ORGANIZATION_SINGLE: 'OrganizationSingleSelectDisplayComponent', + ORGANIZATION_MULTIPLE: 'OrganizationMultipleSelectDisplayComponent', + USER_SINGLE: 'UserSingleSelectDisplayComponent' +} + +// 视图按钮类型 +export const VIEW_BUTTON_TYPE = 'ViewButtonType' + +// 视图按钮类型项 +export const VIEW_BUTTON_TYPE_ITEM = { + LIST_PAGE: 'LIST_PAGE', + LIST_ROW: 'LIST_ROW', + MODIFY_PAGE: 'MODIFY_PAGE' +} + +// 应用 +export const APP_CODE = 'AppCode' diff --git a/src/modules/entityconfig/util/util.js b/src/modules/entityconfig/util/util.js new file mode 100644 index 0000000..cf33687 --- /dev/null +++ b/src/modules/entityconfig/util/util.js @@ -0,0 +1,59 @@ +import * as constant from '@/modules/entityconfig/constant/index.ts' +const util = { + // 根据数据类型获取控件类型数据字典编码 + getWidgetType: function (dataType) { + let code = '' + switch (dataType) { + case constant.DATA_TYPE.STRING: { + code = constant.WIDGET_TYPE.TEXT + break + } + case constant.DATA_TYPE.INTEGER: + case constant.DATA_TYPE.LONG: + case constant.DATA_TYPE.DOUBLE: + case constant.DATA_TYPE.DECIMAL: { + code = constant.WIDGET_TYPE.NUMBER + break + } + case constant.DATA_TYPE.DATETIME: { + code = constant.WIDGET_TYPE.DATETIME + break + } + case constant.DATA_TYPE.DATA_DICTIONARY: { + code = constant.WIDGET_TYPE.DATA_DICTIONARY + break + } + case constant.DATA_TYPE.ENTITY: { + code = constant.WIDGET_TYPE.ENTITY + break + } + case constant.DATA_TYPE.ICON_PICKER: { + code = constant.WIDGET_TYPE.ICON_PICKER + break + } + case constant.DATA_TYPE.ORGANIZATION_SINGLE: { + code = constant.WIDGET_TYPE.ORGANIZATION_SINGLE + break + } + case constant.DATA_TYPE.ORGANIZATION_MULTIPLE: { + code = constant.WIDGET_TYPE.ORGANIZATION_MULTIPLE + break + } + case constant.DATA_TYPE.ATTACHMENT: { + code = constant.WIDGET_TYPE.ATTACHMENT + break + } + case constant.DATA_TYPE.USER_SINGLE: { + code = constant.WIDGET_TYPE.USER_SINGLE + break + } + default: { + code = constant.WIDGET_TYPE.TEXT + break + } + } + return code + } +} + +export default util diff --git a/src/modules/entityconfig/view/entity/add.vue b/src/modules/entityconfig/view/entity/add.vue new file mode 100644 index 0000000..8a36e6d --- /dev/null +++ b/src/modules/entityconfig/view/entity/add.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/entityconfig/view/entity/config.vue b/src/modules/entityconfig/view/entity/config.vue new file mode 100644 index 0000000..0ce09e9 --- /dev/null +++ b/src/modules/entityconfig/view/entity/config.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/entityconfig/view/entity/list.vue b/src/modules/entityconfig/view/entity/list.vue new file mode 100644 index 0000000..4460aab --- /dev/null +++ b/src/modules/entityconfig/view/entity/list.vue @@ -0,0 +1,221 @@ + + + diff --git a/src/modules/entityconfig/view/entity/modify.vue b/src/modules/entityconfig/view/entity/modify.vue new file mode 100644 index 0000000..6790367 --- /dev/null +++ b/src/modules/entityconfig/view/entity/modify.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/entityconfig/view/entity/reference.vue b/src/modules/entityconfig/view/entity/reference.vue new file mode 100644 index 0000000..7185cd4 --- /dev/null +++ b/src/modules/entityconfig/view/entity/reference.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/modules/entityconfig/view/entity/view.vue b/src/modules/entityconfig/view/entity/view.vue new file mode 100644 index 0000000..0f87a6e --- /dev/null +++ b/src/modules/entityconfig/view/entity/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModel/add.vue b/src/modules/entityconfig/view/entityModel/add.vue new file mode 100644 index 0000000..82cb3c4 --- /dev/null +++ b/src/modules/entityconfig/view/entityModel/add.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModel/list.vue b/src/modules/entityconfig/view/entityModel/list.vue new file mode 100644 index 0000000..33dff3a --- /dev/null +++ b/src/modules/entityconfig/view/entityModel/list.vue @@ -0,0 +1,178 @@ + + + diff --git a/src/modules/entityconfig/view/entityModel/modify.vue b/src/modules/entityconfig/view/entityModel/modify.vue new file mode 100644 index 0000000..2467ef5 --- /dev/null +++ b/src/modules/entityconfig/view/entityModel/modify.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModel/reference.vue b/src/modules/entityconfig/view/entityModel/reference.vue new file mode 100644 index 0000000..5d02dde --- /dev/null +++ b/src/modules/entityconfig/view/entityModel/reference.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModel/singleSelect.vue b/src/modules/entityconfig/view/entityModel/singleSelect.vue new file mode 100644 index 0000000..ff30002 --- /dev/null +++ b/src/modules/entityconfig/view/entityModel/singleSelect.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModel/view.vue b/src/modules/entityconfig/view/entityModel/view.vue new file mode 100644 index 0000000..d95e285 --- /dev/null +++ b/src/modules/entityconfig/view/entityModel/view.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModelProperty/add.vue b/src/modules/entityconfig/view/entityModelProperty/add.vue new file mode 100644 index 0000000..cb2a389 --- /dev/null +++ b/src/modules/entityconfig/view/entityModelProperty/add.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModelProperty/list.vue b/src/modules/entityconfig/view/entityModelProperty/list.vue new file mode 100644 index 0000000..74f4ceb --- /dev/null +++ b/src/modules/entityconfig/view/entityModelProperty/list.vue @@ -0,0 +1,248 @@ + + + diff --git a/src/modules/entityconfig/view/entityModelProperty/modify.vue b/src/modules/entityconfig/view/entityModelProperty/modify.vue new file mode 100644 index 0000000..a465550 --- /dev/null +++ b/src/modules/entityconfig/view/entityModelProperty/modify.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModelProperty/reference.vue b/src/modules/entityconfig/view/entityModelProperty/reference.vue new file mode 100644 index 0000000..1563747 --- /dev/null +++ b/src/modules/entityconfig/view/entityModelProperty/reference.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityModelProperty/view.vue b/src/modules/entityconfig/view/entityModelProperty/view.vue new file mode 100644 index 0000000..50e8b80 --- /dev/null +++ b/src/modules/entityconfig/view/entityModelProperty/view.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityView/add.vue b/src/modules/entityconfig/view/entityView/add.vue new file mode 100644 index 0000000..3c99a52 --- /dev/null +++ b/src/modules/entityconfig/view/entityView/add.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityView/list.vue b/src/modules/entityconfig/view/entityView/list.vue new file mode 100644 index 0000000..9d31daf --- /dev/null +++ b/src/modules/entityconfig/view/entityView/list.vue @@ -0,0 +1,247 @@ + + + diff --git a/src/modules/entityconfig/view/entityView/modify.vue b/src/modules/entityconfig/view/entityView/modify.vue new file mode 100644 index 0000000..983065b --- /dev/null +++ b/src/modules/entityconfig/view/entityView/modify.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityView/view.vue b/src/modules/entityconfig/view/entityView/view.vue new file mode 100644 index 0000000..c9cd96f --- /dev/null +++ b/src/modules/entityconfig/view/entityView/view.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/advanceView/index.vue b/src/modules/entityconfig/view/entityViewConfig/advanceView/index.vue new file mode 100644 index 0000000..7fa0023 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/advanceView/index.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack.vue b/src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack.vue new file mode 100644 index 0000000..047394e --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack2.vue b/src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack2.vue new file mode 100644 index 0000000..8dbe080 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/advanceView/indexBack2.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/buttonArea/index.vue b/src/modules/entityconfig/view/entityViewConfig/buttonArea/index.vue new file mode 100644 index 0000000..c044ac7 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/buttonArea/index.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/detailView/index.vue b/src/modules/entityconfig/view/entityViewConfig/detailView/index.vue new file mode 100644 index 0000000..dd803f3 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/detailView/index.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/add.vue b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/add.vue new file mode 100644 index 0000000..bc05fbb --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/add.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/index.vue b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/index.vue new file mode 100644 index 0000000..cc98967 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/modify.vue b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/modify.vue new file mode 100644 index 0000000..b6f0333 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyArea/modify.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/detailView/propertyList/index.vue b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyList/index.vue new file mode 100644 index 0000000..1990c07 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/detailView/propertyList/index.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/index.vue b/src/modules/entityconfig/view/entityViewConfig/listView/index.vue new file mode 100644 index 0000000..02bbbdc --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/index.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/propertyList/index.vue b/src/modules/entityconfig/view/entityViewConfig/listView/propertyList/index.vue new file mode 100644 index 0000000..76ca88c --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/propertyList/index.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/add.vue b/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/add.vue new file mode 100644 index 0000000..26624e7 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/add.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/index.vue b/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/index.vue new file mode 100644 index 0000000..a06399d --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/index.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/modify.vue b/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/modify.vue new file mode 100644 index 0000000..94c0120 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/queryArea/modify.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/add.vue b/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/add.vue new file mode 100644 index 0000000..b226a2c --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/add.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/index.vue b/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/index.vue new file mode 100644 index 0000000..3078c4d --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/index.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/modify.vue b/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/modify.vue new file mode 100644 index 0000000..f25ae47 --- /dev/null +++ b/src/modules/entityconfig/view/entityViewConfig/listView/resultArea/modify.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/modules/entityconfig/view/template/add.vue b/src/modules/entityconfig/view/template/add.vue new file mode 100644 index 0000000..2eb7639 --- /dev/null +++ b/src/modules/entityconfig/view/template/add.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/src/modules/entityconfig/view/template/list.vue b/src/modules/entityconfig/view/template/list.vue new file mode 100644 index 0000000..85db3fa --- /dev/null +++ b/src/modules/entityconfig/view/template/list.vue @@ -0,0 +1,214 @@ + + + diff --git a/src/modules/entityconfig/view/template/modify.vue b/src/modules/entityconfig/view/template/modify.vue new file mode 100644 index 0000000..adf375d --- /dev/null +++ b/src/modules/entityconfig/view/template/modify.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/src/modules/entityconfig/view/template/reference.vue b/src/modules/entityconfig/view/template/reference.vue new file mode 100644 index 0000000..ba5a910 --- /dev/null +++ b/src/modules/entityconfig/view/template/reference.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/modules/entityconfig/view/template/view.vue b/src/modules/entityconfig/view/template/view.vue new file mode 100644 index 0000000..97e4d6b --- /dev/null +++ b/src/modules/entityconfig/view/template/view.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/src/modules/entityconfig/view/viewButton/add.vue b/src/modules/entityconfig/view/viewButton/add.vue new file mode 100644 index 0000000..e3ca9e8 --- /dev/null +++ b/src/modules/entityconfig/view/viewButton/add.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/modules/entityconfig/view/viewButton/modify.vue b/src/modules/entityconfig/view/viewButton/modify.vue new file mode 100644 index 0000000..9a487de --- /dev/null +++ b/src/modules/entityconfig/view/viewButton/modify.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/modules/entityconfig/view/viewButton/select.vue b/src/modules/entityconfig/view/viewButton/select.vue new file mode 100644 index 0000000..f0c78a6 --- /dev/null +++ b/src/modules/entityconfig/view/viewButton/select.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/modules/entityconfig/view/viewButtonTemplate/add.vue b/src/modules/entityconfig/view/viewButtonTemplate/add.vue new file mode 100644 index 0000000..14a47d6 --- /dev/null +++ b/src/modules/entityconfig/view/viewButtonTemplate/add.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/modules/entityconfig/view/viewButtonTemplate/list.vue b/src/modules/entityconfig/view/viewButtonTemplate/list.vue new file mode 100644 index 0000000..ec606ce --- /dev/null +++ b/src/modules/entityconfig/view/viewButtonTemplate/list.vue @@ -0,0 +1,175 @@ + + + diff --git a/src/modules/entityconfig/view/viewButtonTemplate/modify.vue b/src/modules/entityconfig/view/viewButtonTemplate/modify.vue new file mode 100644 index 0000000..6686b1a --- /dev/null +++ b/src/modules/entityconfig/view/viewButtonTemplate/modify.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/modules/entityconfig/view/viewButtonTemplate/view.vue b/src/modules/entityconfig/view/viewButtonTemplate/view.vue new file mode 100644 index 0000000..459e95d --- /dev/null +++ b/src/modules/entityconfig/view/viewButtonTemplate/view.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/modules/notification/api/index.ts b/src/modules/notification/api/index.ts new file mode 100644 index 0000000..b558508 --- /dev/null +++ b/src/modules/notification/api/index.ts @@ -0,0 +1,19 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'notification' + +// 系统消息 +export const systemMessage = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'systemMessage' + '/', + + getUnreadMessageCount() { + return request.get({ url: this.serveUrl + 'getUnreadMessageCount' }) + }, + read(id) { + return request.put({ url: this.serveUrl + id + '/read', params: { showInfo: false } }) + }, + setAllRead() { + return request.put({ url: this.serveUrl + 'setAllRead' }) + } +}) diff --git a/src/modules/notification/constant/index.ts b/src/modules/notification/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/notification/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/notification/view/systemMessage/list.vue b/src/modules/notification/view/systemMessage/list.vue new file mode 100644 index 0000000..3ea0983 --- /dev/null +++ b/src/modules/notification/view/systemMessage/list.vue @@ -0,0 +1,154 @@ + + + diff --git a/src/modules/notification/view/systemMessage/view.vue b/src/modules/notification/view/systemMessage/view.vue new file mode 100644 index 0000000..2b2824f --- /dev/null +++ b/src/modules/notification/view/systemMessage/view.vue @@ -0,0 +1,70 @@ + + + + diff --git a/src/modules/notification/view/systemMessage/webSocket.js b/src/modules/notification/view/systemMessage/webSocket.js new file mode 100644 index 0000000..cae55ef --- /dev/null +++ b/src/modules/notification/view/systemMessage/webSocket.js @@ -0,0 +1,183 @@ +import { getToken } from '@/utils/auth' + +import { ElNotification } from 'element-plus' +import { useNotificationStore } from '@/store/modules/notification' +const notificationStore = useNotificationStore() +// 重连标志位,防止重连过程中反复新建连接 +let lockReconnect = false +// 心跳请求发送间隔时间 +const heartbeatRequestInteval = 5 * 1000 +// 心跳请求计时器对象 +let heartbeatRequestTimer = null + +// 心跳响应超时时间 +const heartbeatResponseTimeout = 30 * 1000 + +// 心跳响应计时器对象 +let heartbeatResponseTimer = null + +// 重连计时器 +let reconnectTimer = null + +// 消息服务地址 +const NOTIFICATION_SERVER_URL = import.meta.env.VITE_NOTIFICATION_SERVER_URL + +let that +let webSocketInstance +class MyWebSocket { + constructor() { + // eslint-disable-next-line @typescript-eslint/no-this-alias + that = this + } + + // 开启心跳 + startHeartbeat() { + // 清理计时器 + that.clearTimer() + + // 设置定时发送心跳请求 + heartbeatRequestTimer = setInterval(() => { + that.sendMessage(JSON.stringify({ type: 'HEARTBEAT_REQUEST' })) + // 设置服务器端心跳响应计时器 + if (heartbeatResponseTimer) { + clearTimeout(heartbeatResponseTimer) + } + heartbeatResponseTimer = setTimeout(() => { + that.reconnect() + }, heartbeatResponseTimeout) + }, heartbeatRequestInteval) + } + + // 初始化weosocket + init() { + // 建立连接 + if (!webSocketInstance) { + webSocketInstance = new WebSocket(NOTIFICATION_SERVER_URL) + // 连接成功 + webSocketInstance.onopen = this.onopen + // 连接错误 + webSocketInstance.onerror = this.onerror + // 连接关闭 + webSocketInstance.onclose = this.onclose + // 接收信息 + webSocketInstance.onmessage = this.onmessage + } + } + + // 重新连接 + reconnect() { + // 获取token + const token = getToken() + // 如token为空,即未登录,则停止重新连接 + if (!token) { + return + } + + if (lockReconnect) { + return + } + lockReconnect = true + // 清理计时器 + this.clearTimer() + // 没连接上会一直重连,设置延迟避免请求过多 + if (reconnectTimer) { + clearTimeout(reconnectTimer) + } + reconnectTimer = setTimeout(function () { + // 新连接 + that.init() + lockReconnect = false + }, 5000) + } + + // 清理计时器 + clearTimer() { + // 清理心跳请求计时器 + if (heartbeatRequestTimer) { + clearInterval(heartbeatRequestTimer) + } + // 清理心跳响应计时器 + if (heartbeatResponseTimer) { + clearTimeout(heartbeatResponseTimer) + } + } + + // 向服务器发送信息 + sendMessage(msg) { + // 数据发送 + if (webSocketInstance.readyState === 1) { + webSocketInstance.send(msg) + } else { + this.reconnect() + } + } + + // 关闭websocket + close() { + this.clearTimer() + if (reconnectTimer) { + clearTimeout(reconnectTimer) + } + // 发送关闭消息 + this.sendMessage(JSON.stringify({ type: 'LOGOUT_REQUEST' })) + webSocketInstance = null + } + + // 连接成功事件 + onopen() { + // 获取token + const token = getToken() + // 发送token进行身份认证 + if (token) { + that.sendMessage(JSON.stringify({ type: 'LOGIN_REQUEST', content: token })) + } + } + // 接收服务器返回的数据 + onmessage(e) { + // 解析数据 + const message = JSON.parse(e.data) + if (message.type === 'LOGIN_RESPONSE') { + // 开启心跳 + that.startHeartbeat() + } else if (message.type === 'HEARTBEAT_RESPONSE') { + // 收到心跳响应,重置消息响应定时器 + clearTimeout(heartbeatResponseTimer) + } else if (message.type === 'BUSINESS_MESSAGE') { + // console.log('收到业务消息', message) + that.messageReceive(message) + } + } + + // 收到新消息 + messageReceive(message) { + // 未读消息加1 + notificationStore.increaseUnreadMessageCount() + // 弹出消息提示 + ElNotification({ + title: '收到新消息', + message: message.title, + position: 'bottom-right', + onClick: that.showMessage + }) + } + + // 发生错误 + onerror(e) { + // 重连 + lockReconnect = false + that.reconnect() + } + + // 连接关闭事件 + onclose() { + // 重连 + that.reconnect() + } + // 显示消息 + showMessage() { + // TODO:如何调用消息查看组件 + } +} + +const messageSocket = new MyWebSocket() +export default messageSocket diff --git a/src/modules/scheduler/api/index.ts b/src/modules/scheduler/api/index.ts new file mode 100644 index 0000000..f09cb6d --- /dev/null +++ b/src/modules/scheduler/api/index.ts @@ -0,0 +1,38 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'scheduler' +// 任务 +export const job = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'job' + '/' +}) + +// 任务参数 +export const jobParam = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'jobParam' + '/' +}) + +// 调度任务 +export const schedulerJob = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'schedulerJob' + '/', + pause(id) { + return request.put({ url: this.serveUrl + id + '/pause' }) + }, + resume(id) { + return request.put({ url: this.serveUrl + id + '/resume' }) + }, + pauseAll() { + return request.put({ url: this.serveUrl + 'pauseAll' }) + }, + resumeAll() { + return request.put({ url: this.serveUrl + 'resumeAll' }) + }, + execute(id) { + return request.put({ url: this.serveUrl + id + '/execute' }) + } +}) + +// 调度任务参数 +export const schedulerJobParam = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'schedulerJobParam' + '/' +}) diff --git a/src/modules/scheduler/constant/index.ts b/src/modules/scheduler/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/scheduler/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/scheduler/view/job/add.vue b/src/modules/scheduler/view/job/add.vue new file mode 100644 index 0000000..d52e81a --- /dev/null +++ b/src/modules/scheduler/view/job/add.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/scheduler/view/job/list.vue b/src/modules/scheduler/view/job/list.vue new file mode 100644 index 0000000..9802714 --- /dev/null +++ b/src/modules/scheduler/view/job/list.vue @@ -0,0 +1,192 @@ + + + diff --git a/src/modules/scheduler/view/job/modify.vue b/src/modules/scheduler/view/job/modify.vue new file mode 100644 index 0000000..33c0bb3 --- /dev/null +++ b/src/modules/scheduler/view/job/modify.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/scheduler/view/job/reference.vue b/src/modules/scheduler/view/job/reference.vue new file mode 100644 index 0000000..32c208f --- /dev/null +++ b/src/modules/scheduler/view/job/reference.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/modules/scheduler/view/job/view.vue b/src/modules/scheduler/view/job/view.vue new file mode 100644 index 0000000..42e06d6 --- /dev/null +++ b/src/modules/scheduler/view/job/view.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/modules/scheduler/view/jobParam/add.vue b/src/modules/scheduler/view/jobParam/add.vue new file mode 100644 index 0000000..96f2437 --- /dev/null +++ b/src/modules/scheduler/view/jobParam/add.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/scheduler/view/jobParam/list.vue b/src/modules/scheduler/view/jobParam/list.vue new file mode 100644 index 0000000..4b604c7 --- /dev/null +++ b/src/modules/scheduler/view/jobParam/list.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/modules/scheduler/view/jobParam/modify.vue b/src/modules/scheduler/view/jobParam/modify.vue new file mode 100644 index 0000000..3af73a7 --- /dev/null +++ b/src/modules/scheduler/view/jobParam/modify.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/scheduler/view/jobParam/view.vue b/src/modules/scheduler/view/jobParam/view.vue new file mode 100644 index 0000000..a1e41f9 --- /dev/null +++ b/src/modules/scheduler/view/jobParam/view.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJob/add.vue b/src/modules/scheduler/view/schedulerJob/add.vue new file mode 100644 index 0000000..ec89097 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJob/add.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJob/list.vue b/src/modules/scheduler/view/schedulerJob/list.vue new file mode 100644 index 0000000..0fcd233 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJob/list.vue @@ -0,0 +1,277 @@ + + + diff --git a/src/modules/scheduler/view/schedulerJob/modify.vue b/src/modules/scheduler/view/schedulerJob/modify.vue new file mode 100644 index 0000000..a711607 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJob/modify.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJob/reference.vue b/src/modules/scheduler/view/schedulerJob/reference.vue new file mode 100644 index 0000000..ed07a78 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJob/reference.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJob/view.vue b/src/modules/scheduler/view/schedulerJob/view.vue new file mode 100644 index 0000000..c7e9880 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJob/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJobParam/add.vue b/src/modules/scheduler/view/schedulerJobParam/add.vue new file mode 100644 index 0000000..8ce7c60 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJobParam/add.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJobParam/list.vue b/src/modules/scheduler/view/schedulerJobParam/list.vue new file mode 100644 index 0000000..368864b --- /dev/null +++ b/src/modules/scheduler/view/schedulerJobParam/list.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/modules/scheduler/view/schedulerJobParam/modify.vue b/src/modules/scheduler/view/schedulerJobParam/modify.vue new file mode 100644 index 0000000..228eb07 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJobParam/modify.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/scheduler/view/schedulerJobParam/view.vue b/src/modules/scheduler/view/schedulerJobParam/view.vue new file mode 100644 index 0000000..4df4262 --- /dev/null +++ b/src/modules/scheduler/view/schedulerJobParam/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/support/api/index.ts b/src/modules/support/api/index.ts new file mode 100644 index 0000000..3c5e05b --- /dev/null +++ b/src/modules/support/api/index.ts @@ -0,0 +1,98 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'support' + +// 通知公告 +export const notice = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'notice' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + setTop(id) { + return request.put({ url: this.serveUrl + id + '/setTop' }) + }, + cancelTop(id) { + return request.put({ url: this.serveUrl + id + '/cancelTop' }) + }, + + portlet(params) { + return request.get({ url: this.serveUrl + 'portlet', params }) + }, + // 阅读量加1 + updateReadCount(id) { + return request.put({ + url: this.serveUrl + id + '/updateReadCount', + params: { showInfo: false } + }) + }, + // 浏览单条 + view(id) { + return request.get({ url: this.serveUrl + id + '/view' }) + }, + // 浏览列表 + viewList(params) { + return request.get({ url: this.serveUrl + 'viewList', params }) + } +}) + +// 附件 +export const attachment = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'attachment' + '/', + // 上传操作内置于vue-simple-uploader中 + // 下载 + download(id) { + return request.download({ url: this.serveUrl + id + '/download' }) + }, + // 合并文件块 + mergeChunks(param) { + return request.post({ url: this.serveUrl + 'mergeChunks', data: param }) + }, + // 上传图片 + uploadImage(param) { + return request.upload({ url: this.serveUrl + 'uploadImage', data: param }) + } +}) + +// 流水号 +export const serialNo = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'serialNo' + '/' +}) + +// 内容模板 +export const contentTemplate = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'contentTemplate' + '/' +}) + +// 组件 +export const portlet = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'portlet' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + getPortletList() { + return request.get({ url: this.serveUrl + 'getPortletList' }) + } +}) + +// 组件参数 +export const portletParam = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'portletParam' + '/' +}) + +// 桌面模板 +export const desktopTemplate = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'desktopTemplate' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + } +}) diff --git a/src/modules/support/constant/index.ts b/src/modules/support/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/support/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/support/view/attachment/attachmentManager.vue b/src/modules/support/view/attachment/attachmentManager.vue new file mode 100644 index 0000000..f0b9aeb --- /dev/null +++ b/src/modules/support/view/attachment/attachmentManager.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/modules/support/view/attachment/attachmentManagerAndUploader.vue b/src/modules/support/view/attachment/attachmentManagerAndUploader.vue new file mode 100644 index 0000000..009a1a9 --- /dev/null +++ b/src/modules/support/view/attachment/attachmentManagerAndUploader.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/modules/support/view/attachment/attachmentUploader.vue b/src/modules/support/view/attachment/attachmentUploader.vue new file mode 100644 index 0000000..c854130 --- /dev/null +++ b/src/modules/support/view/attachment/attachmentUploader.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/src/modules/support/view/attachment/attachmentViewer.vue b/src/modules/support/view/attachment/attachmentViewer.vue new file mode 100644 index 0000000..6a67934 --- /dev/null +++ b/src/modules/support/view/attachment/attachmentViewer.vue @@ -0,0 +1,62 @@ + + diff --git a/src/modules/support/view/contentTemplate/add.vue b/src/modules/support/view/contentTemplate/add.vue new file mode 100644 index 0000000..a671a8c --- /dev/null +++ b/src/modules/support/view/contentTemplate/add.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/modules/support/view/contentTemplate/list.vue b/src/modules/support/view/contentTemplate/list.vue new file mode 100644 index 0000000..1bc35a2 --- /dev/null +++ b/src/modules/support/view/contentTemplate/list.vue @@ -0,0 +1,162 @@ + + + diff --git a/src/modules/support/view/contentTemplate/modify.vue b/src/modules/support/view/contentTemplate/modify.vue new file mode 100644 index 0000000..3931376 --- /dev/null +++ b/src/modules/support/view/contentTemplate/modify.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/modules/support/view/contentTemplate/view.vue b/src/modules/support/view/contentTemplate/view.vue new file mode 100644 index 0000000..1da45c8 --- /dev/null +++ b/src/modules/support/view/contentTemplate/view.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/support/view/desktopTemplate/add.vue b/src/modules/support/view/desktopTemplate/add.vue new file mode 100644 index 0000000..d94a205 --- /dev/null +++ b/src/modules/support/view/desktopTemplate/add.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/support/view/desktopTemplate/configDesktop.vue b/src/modules/support/view/desktopTemplate/configDesktop.vue new file mode 100644 index 0000000..88cbf60 --- /dev/null +++ b/src/modules/support/view/desktopTemplate/configDesktop.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/src/modules/support/view/desktopTemplate/desktop.vue b/src/modules/support/view/desktopTemplate/desktop.vue new file mode 100644 index 0000000..29ffddf --- /dev/null +++ b/src/modules/support/view/desktopTemplate/desktop.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/modules/support/view/desktopTemplate/list.vue b/src/modules/support/view/desktopTemplate/list.vue new file mode 100644 index 0000000..1ffdb44 --- /dev/null +++ b/src/modules/support/view/desktopTemplate/list.vue @@ -0,0 +1,172 @@ + + + diff --git a/src/modules/support/view/desktopTemplate/modify.vue b/src/modules/support/view/desktopTemplate/modify.vue new file mode 100644 index 0000000..f56e779 --- /dev/null +++ b/src/modules/support/view/desktopTemplate/modify.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/support/view/desktopTemplate/reference.vue b/src/modules/support/view/desktopTemplate/reference.vue new file mode 100644 index 0000000..27be473 --- /dev/null +++ b/src/modules/support/view/desktopTemplate/reference.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/modules/support/view/desktopTemplate/view.vue b/src/modules/support/view/desktopTemplate/view.vue new file mode 100644 index 0000000..1ba8141 --- /dev/null +++ b/src/modules/support/view/desktopTemplate/view.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/modules/support/view/notice/add.vue b/src/modules/support/view/notice/add.vue new file mode 100644 index 0000000..702236e --- /dev/null +++ b/src/modules/support/view/notice/add.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/modules/support/view/notice/list.vue b/src/modules/support/view/notice/list.vue new file mode 100644 index 0000000..e57c276 --- /dev/null +++ b/src/modules/support/view/notice/list.vue @@ -0,0 +1,259 @@ + + + diff --git a/src/modules/support/view/notice/modify.vue b/src/modules/support/view/notice/modify.vue new file mode 100644 index 0000000..63fdb08 --- /dev/null +++ b/src/modules/support/view/notice/modify.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/modules/support/view/notice/noticeView.vue b/src/modules/support/view/notice/noticeView.vue new file mode 100644 index 0000000..3077083 --- /dev/null +++ b/src/modules/support/view/notice/noticeView.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/support/view/notice/noticeViewList.vue b/src/modules/support/view/notice/noticeViewList.vue new file mode 100644 index 0000000..2fb977b --- /dev/null +++ b/src/modules/support/view/notice/noticeViewList.vue @@ -0,0 +1,182 @@ + + + diff --git a/src/modules/support/view/notice/view.vue b/src/modules/support/view/notice/view.vue new file mode 100644 index 0000000..75d356c --- /dev/null +++ b/src/modules/support/view/notice/view.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/modules/support/view/portlet/add.vue b/src/modules/support/view/portlet/add.vue new file mode 100644 index 0000000..69f58d6 --- /dev/null +++ b/src/modules/support/view/portlet/add.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/support/view/portlet/list.vue b/src/modules/support/view/portlet/list.vue new file mode 100644 index 0000000..e4bc5a5 --- /dev/null +++ b/src/modules/support/view/portlet/list.vue @@ -0,0 +1,209 @@ + + + diff --git a/src/modules/support/view/portlet/modify.vue b/src/modules/support/view/portlet/modify.vue new file mode 100644 index 0000000..e9139e3 --- /dev/null +++ b/src/modules/support/view/portlet/modify.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/support/view/portlet/reference.vue b/src/modules/support/view/portlet/reference.vue new file mode 100644 index 0000000..10bd88c --- /dev/null +++ b/src/modules/support/view/portlet/reference.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/modules/support/view/portlet/view.vue b/src/modules/support/view/portlet/view.vue new file mode 100644 index 0000000..1c3ac6b --- /dev/null +++ b/src/modules/support/view/portlet/view.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/modules/support/view/portletList/favorite.vue b/src/modules/support/view/portletList/favorite.vue new file mode 100644 index 0000000..fb09089 --- /dev/null +++ b/src/modules/support/view/portletList/favorite.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/modules/support/view/portletList/notice.vue b/src/modules/support/view/portletList/notice.vue new file mode 100644 index 0000000..e13af56 --- /dev/null +++ b/src/modules/support/view/portletList/notice.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/modules/support/view/portletParam/add.vue b/src/modules/support/view/portletParam/add.vue new file mode 100644 index 0000000..8b47cc3 --- /dev/null +++ b/src/modules/support/view/portletParam/add.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/support/view/portletParam/list.vue b/src/modules/support/view/portletParam/list.vue new file mode 100644 index 0000000..a0eaab4 --- /dev/null +++ b/src/modules/support/view/portletParam/list.vue @@ -0,0 +1,163 @@ + + + diff --git a/src/modules/support/view/portletParam/modify.vue b/src/modules/support/view/portletParam/modify.vue new file mode 100644 index 0000000..f08d3f9 --- /dev/null +++ b/src/modules/support/view/portletParam/modify.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/support/view/portletParam/view.vue b/src/modules/support/view/portletParam/view.vue new file mode 100644 index 0000000..acbcc58 --- /dev/null +++ b/src/modules/support/view/portletParam/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/support/view/serialNo/add.vue b/src/modules/support/view/serialNo/add.vue new file mode 100644 index 0000000..6f97411 --- /dev/null +++ b/src/modules/support/view/serialNo/add.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/modules/support/view/serialNo/list.vue b/src/modules/support/view/serialNo/list.vue new file mode 100644 index 0000000..1b63ac4 --- /dev/null +++ b/src/modules/support/view/serialNo/list.vue @@ -0,0 +1,191 @@ + + + diff --git a/src/modules/support/view/serialNo/modify.vue b/src/modules/support/view/serialNo/modify.vue new file mode 100644 index 0000000..fe910ed --- /dev/null +++ b/src/modules/support/view/serialNo/modify.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/modules/support/view/serialNo/reference.vue b/src/modules/support/view/serialNo/reference.vue new file mode 100644 index 0000000..1fdb764 --- /dev/null +++ b/src/modules/support/view/serialNo/reference.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/modules/support/view/serialNo/view.vue b/src/modules/support/view/serialNo/view.vue new file mode 100644 index 0000000..933c915 --- /dev/null +++ b/src/modules/support/view/serialNo/view.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/modules/system/api/index.ts b/src/modules/system/api/index.ts new file mode 100644 index 0000000..ef3c3e3 --- /dev/null +++ b/src/modules/system/api/index.ts @@ -0,0 +1,233 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'system' + +// 系统参数 +export const param = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'param' + '/' +}) + +// 系统日志 +export const log = { + serveUrl: '/' + moduleName + '/' + 'log' + '/', + + page(params) { + return request.get({ url: this.serveUrl + 'page', params }) + }, + get(id) { + return request.get({ url: this.serveUrl + id }) + } +} + +// 组织机构 +export const organization = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'organization' + '/', + tree() { + return request.get({ url: this.serveUrl + 'tree' }) + }, + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + // 下载导入模板 + downloadImportTemplate() { + return request.download({ url: this.serveUrl + 'downloadImportTemplate' }) + }, + // 导入 + import(formData) { + return request.upload({ url: this.serveUrl + 'importExcel', data: formData }) + }, + // 导出 + export(params) { + return request.download({ url: this.serveUrl + 'exportExcel', params }) + } + // // 根据id列表查询信息 + // getOrganization(idList) { + // return request.get({url:this.serveUrl + 'getOrganization?' + qs.stringify(idList, { arrayFormat: 'repeat' })) + // }, +}) + +// 用户 +export const user = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'user' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + resetPassword(id) { + return request.put({ url: this.serveUrl + id + '/resetPassword' }) + }, + unlock(id) { + return request.put({ url: this.serveUrl + id + '/unlock' }) + }, + // 用户修改密码 + changePassword(id, oldPassword, newPassword) { + return request.put({ + url: this.serveUrl + 'changePassword', + data: { userId: id, oldPassword, newPassword } + }) + }, + // 获取用户组对应的用户 + getUserByUserGroupId(userGroupId, params) { + return request.get({ url: this.serveUrl + 'userGroup/' + userGroupId, params }) + }, + // 获取用户对应的用户组 + getUserGroup(id) { + return request.get({ url: this.serveUrl + id + '/getUserGroup' }) + }, + // 保存用户对应的用户组列表 + saveUserGroup(id, userGroupIdList) { + return request.put({ + url: this.serveUrl + id + '/saveUserGroup', + data: userGroupIdList + }) + }, + + // 登录 + login(username, password) { + return request.post({ url: this.serveUrl + 'login', params: { username, password } }) + }, + // 注销 + logout() { + return request.post({ url: this.serveUrl + 'logout' }) + }, + // 根据id列表查询用户信息 + getUser(idList) { + return request.get({ url: this.serveUrl + 'getUser', params: idList }) + }, + // 下载导入模板 + downloadImportTemplate() { + return request.download({ url: this.serveUrl + 'downloadImportTemplate' }) + }, + // 导入 + import(formData) { + return request.upload({ url: this.serveUrl + 'importExcel', data: formData }) + }, + // 导出 + export(params) { + return request.download({ url: this.serveUrl + 'exportExcel', params }) + } + // // 高级查询初始化 + // initAdvanceQuery() { + // return request.get({ url: this.serveUrl + 'initAdvanceQuery' }) + // }, + // // 高级查询-分页 + // advanceQueryPage(params) { + // return request.get({ url: this.serveUrl + 'advanceQueryPage', params) + // } +}) + +// 权限项 +export const permissionItem = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'permissionItem' + '/', + tree() { + return request.get({ url: this.serveUrl + 'tree' }) + }, + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + } +}) + +// 用户组 +export const userGroup = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'userGroup' + '/', + tree() { + return request.get({ url: this.serveUrl + 'tree' }) + }, + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + + // 批量移除用户 + batchRemoveUser(id, idList) { + return request.put({ url: this.serveUrl + id + '/user', data: idList }) + }, + // 新增用户 + addUser(id, idList) { + return request.post({ url: this.serveUrl + id + '/user', data: idList }) + }, + // 获取权限 + getPermission(id) { + return request.get({ url: this.serveUrl + id + '/getPermission' }) + }, + // 保存权限 + savePermission(id, permissionIdList) { + return request.put({ url: this.serveUrl + id + '/savePermission', data: permissionIdList }) + }, + // 根据id列表查询 + getByIdList(idList) { + return request.get({ url: this.serveUrl + 'getByIdList', params: idList }) + } +}) + +// 字典类型 +export const dictionaryType = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'dictionaryType' + '/', + tree() { + return request.get({ url: this.serveUrl + 'tree' }) + }, + + // 通过编码获取数据 + getByCode(param) { + return request.get({ url: this.serveUrl + 'getByCode/' + param }) + }, + // 批量保存 + saveItem(id, itemList) { + return request.post({ url: this.serveUrl + id + '/item', data: itemList }) + }, + // 通过编码获取字典项,转换为列表项数据结构,用于公用选择控件 + getItem(code) { + return request.get({ url: this.serveUrl + 'getItem?code=' + code }) + } +}) + +// 字典项 +export const dictionaryItem = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'dictionaryItem' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + } +}) +// 用户设置 +export const userProfile = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'userProfile' + '/', + get() { + return request.get({ url: this.serveUrl }) + } +}) + +// 系统管理 +export const systemManage = Object.assign( + {}, + { + serveUrl: '/' + moduleName + '/' + 'systemManage' + '/', + // 重建系统缓存 + rebuildSystemCache() { + return request.put({ url: this.serveUrl + 'rebuildSystemCache' }) + }, + // 获取唯一性标识 + getUniqueId() { + // 获取唯一性标识 + return request.get({ url: this.serveUrl + 'getUniqueId' }) + } + } +) + +// 模块 +export const module = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'module' + '/' +}) diff --git a/src/modules/system/constant/index.ts b/src/modules/system/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/system/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/system/view/dictionaryItem/add.vue b/src/modules/system/view/dictionaryItem/add.vue new file mode 100644 index 0000000..1f21a0e --- /dev/null +++ b/src/modules/system/view/dictionaryItem/add.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/modules/system/view/dictionaryItem/list.vue b/src/modules/system/view/dictionaryItem/list.vue new file mode 100644 index 0000000..6febc27 --- /dev/null +++ b/src/modules/system/view/dictionaryItem/list.vue @@ -0,0 +1,185 @@ + + + diff --git a/src/modules/system/view/dictionaryItem/modify.vue b/src/modules/system/view/dictionaryItem/modify.vue new file mode 100644 index 0000000..ac5187e --- /dev/null +++ b/src/modules/system/view/dictionaryItem/modify.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/system/view/dictionaryItem/treeList.vue b/src/modules/system/view/dictionaryItem/treeList.vue new file mode 100644 index 0000000..3c3a357 --- /dev/null +++ b/src/modules/system/view/dictionaryItem/treeList.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/modules/system/view/dictionaryItem/view.vue b/src/modules/system/view/dictionaryItem/view.vue new file mode 100644 index 0000000..dc3890f --- /dev/null +++ b/src/modules/system/view/dictionaryItem/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/add.vue b/src/modules/system/view/dictionaryType/add.vue new file mode 100644 index 0000000..f5bd4a8 --- /dev/null +++ b/src/modules/system/view/dictionaryType/add.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/list.vue b/src/modules/system/view/dictionaryType/list.vue new file mode 100644 index 0000000..4d1d883 --- /dev/null +++ b/src/modules/system/view/dictionaryType/list.vue @@ -0,0 +1,173 @@ + + + diff --git a/src/modules/system/view/dictionaryType/modify.vue b/src/modules/system/view/dictionaryType/modify.vue new file mode 100644 index 0000000..78c5ed8 --- /dev/null +++ b/src/modules/system/view/dictionaryType/modify.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/tree.vue b/src/modules/system/view/dictionaryType/tree.vue new file mode 100644 index 0000000..81d6b23 --- /dev/null +++ b/src/modules/system/view/dictionaryType/tree.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/treeList.vue b/src/modules/system/view/dictionaryType/treeList.vue new file mode 100644 index 0000000..c2c4613 --- /dev/null +++ b/src/modules/system/view/dictionaryType/treeList.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/treeReference.vue b/src/modules/system/view/dictionaryType/treeReference.vue new file mode 100644 index 0000000..b533000 --- /dev/null +++ b/src/modules/system/view/dictionaryType/treeReference.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/treeReferenceUseCode.vue b/src/modules/system/view/dictionaryType/treeReferenceUseCode.vue new file mode 100644 index 0000000..2af1919 --- /dev/null +++ b/src/modules/system/view/dictionaryType/treeReferenceUseCode.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/modules/system/view/dictionaryType/view.vue b/src/modules/system/view/dictionaryType/view.vue new file mode 100644 index 0000000..9adc03d --- /dev/null +++ b/src/modules/system/view/dictionaryType/view.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/modules/system/view/log/list.vue b/src/modules/system/view/log/list.vue new file mode 100644 index 0000000..c3b7ef1 --- /dev/null +++ b/src/modules/system/view/log/list.vue @@ -0,0 +1,199 @@ + + + diff --git a/src/modules/system/view/log/view.vue b/src/modules/system/view/log/view.vue new file mode 100644 index 0000000..052c43a --- /dev/null +++ b/src/modules/system/view/log/view.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/modules/system/view/module/add.vue b/src/modules/system/view/module/add.vue new file mode 100644 index 0000000..410ebae --- /dev/null +++ b/src/modules/system/view/module/add.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/system/view/module/list.vue b/src/modules/system/view/module/list.vue new file mode 100644 index 0000000..291b9ba --- /dev/null +++ b/src/modules/system/view/module/list.vue @@ -0,0 +1,163 @@ + + + diff --git a/src/modules/system/view/module/modify.vue b/src/modules/system/view/module/modify.vue new file mode 100644 index 0000000..fac867b --- /dev/null +++ b/src/modules/system/view/module/modify.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/modules/system/view/module/reference.vue b/src/modules/system/view/module/reference.vue new file mode 100644 index 0000000..f526214 --- /dev/null +++ b/src/modules/system/view/module/reference.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/modules/system/view/module/view.vue b/src/modules/system/view/module/view.vue new file mode 100644 index 0000000..3f8a125 --- /dev/null +++ b/src/modules/system/view/module/view.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/modules/system/view/organization/add.vue b/src/modules/system/view/organization/add.vue new file mode 100644 index 0000000..bfd30eb --- /dev/null +++ b/src/modules/system/view/organization/add.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/modules/system/view/organization/list.vue b/src/modules/system/view/organization/list.vue new file mode 100644 index 0000000..da0b33a --- /dev/null +++ b/src/modules/system/view/organization/list.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/src/modules/system/view/organization/modify.vue b/src/modules/system/view/organization/modify.vue new file mode 100644 index 0000000..c34dbd4 --- /dev/null +++ b/src/modules/system/view/organization/modify.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/src/modules/system/view/organization/modifyBack.vue b/src/modules/system/view/organization/modifyBack.vue new file mode 100644 index 0000000..088a57f --- /dev/null +++ b/src/modules/system/view/organization/modifyBack.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/modules/system/view/organization/tree.vue b/src/modules/system/view/organization/tree.vue new file mode 100644 index 0000000..39104b2 --- /dev/null +++ b/src/modules/system/view/organization/tree.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/modules/system/view/organization/treeList.vue b/src/modules/system/view/organization/treeList.vue new file mode 100644 index 0000000..1decee1 --- /dev/null +++ b/src/modules/system/view/organization/treeList.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/modules/system/view/organization/treeMultipleReference.vue b/src/modules/system/view/organization/treeMultipleReference.vue new file mode 100644 index 0000000..3aa902d --- /dev/null +++ b/src/modules/system/view/organization/treeMultipleReference.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/modules/system/view/organization/treeMultipleSelect.vue b/src/modules/system/view/organization/treeMultipleSelect.vue new file mode 100644 index 0000000..a5321ab --- /dev/null +++ b/src/modules/system/view/organization/treeMultipleSelect.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/modules/system/view/organization/treeReference.vue b/src/modules/system/view/organization/treeReference.vue new file mode 100644 index 0000000..a43bc4e --- /dev/null +++ b/src/modules/system/view/organization/treeReference.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/modules/system/view/organization/view.vue b/src/modules/system/view/organization/view.vue new file mode 100644 index 0000000..fa16297 --- /dev/null +++ b/src/modules/system/view/organization/view.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/modules/system/view/param/add.vue b/src/modules/system/view/param/add.vue new file mode 100644 index 0000000..4a666ff --- /dev/null +++ b/src/modules/system/view/param/add.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/modules/system/view/param/list.vue b/src/modules/system/view/param/list.vue new file mode 100644 index 0000000..0d107ba --- /dev/null +++ b/src/modules/system/view/param/list.vue @@ -0,0 +1,151 @@ + + + diff --git a/src/modules/system/view/param/modify.vue b/src/modules/system/view/param/modify.vue new file mode 100644 index 0000000..ad157f9 --- /dev/null +++ b/src/modules/system/view/param/modify.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/modules/system/view/param/view.vue b/src/modules/system/view/param/view.vue new file mode 100644 index 0000000..d4242fe --- /dev/null +++ b/src/modules/system/view/param/view.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/add.vue b/src/modules/system/view/permissionItem/add.vue new file mode 100644 index 0000000..9fa55cc --- /dev/null +++ b/src/modules/system/view/permissionItem/add.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/list.vue b/src/modules/system/view/permissionItem/list.vue new file mode 100644 index 0000000..c607efc --- /dev/null +++ b/src/modules/system/view/permissionItem/list.vue @@ -0,0 +1,232 @@ + + + diff --git a/src/modules/system/view/permissionItem/modify.vue b/src/modules/system/view/permissionItem/modify.vue new file mode 100644 index 0000000..52b14f2 --- /dev/null +++ b/src/modules/system/view/permissionItem/modify.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/tree.vue b/src/modules/system/view/permissionItem/tree.vue new file mode 100644 index 0000000..52249dc --- /dev/null +++ b/src/modules/system/view/permissionItem/tree.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/treeList.vue b/src/modules/system/view/permissionItem/treeList.vue new file mode 100644 index 0000000..28b8993 --- /dev/null +++ b/src/modules/system/view/permissionItem/treeList.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/treeMultipleReference.vue b/src/modules/system/view/permissionItem/treeMultipleReference.vue new file mode 100644 index 0000000..ab1a2aa --- /dev/null +++ b/src/modules/system/view/permissionItem/treeMultipleReference.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/treeReference.vue b/src/modules/system/view/permissionItem/treeReference.vue new file mode 100644 index 0000000..0a7d78c --- /dev/null +++ b/src/modules/system/view/permissionItem/treeReference.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/modules/system/view/permissionItem/view.vue b/src/modules/system/view/permissionItem/view.vue new file mode 100644 index 0000000..5bf6e0a --- /dev/null +++ b/src/modules/system/view/permissionItem/view.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/modules/system/view/systemManage/list.vue b/src/modules/system/view/systemManage/list.vue new file mode 100644 index 0000000..55dde56 --- /dev/null +++ b/src/modules/system/view/systemManage/list.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/modules/system/view/user/add.vue b/src/modules/system/view/user/add.vue new file mode 100644 index 0000000..8287309 --- /dev/null +++ b/src/modules/system/view/user/add.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/modules/system/view/user/list.vue b/src/modules/system/view/user/list.vue new file mode 100644 index 0000000..3490a8c --- /dev/null +++ b/src/modules/system/view/user/list.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/src/modules/system/view/user/modify.vue b/src/modules/system/view/user/modify.vue new file mode 100644 index 0000000..4d6fbf8 --- /dev/null +++ b/src/modules/system/view/user/modify.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/modules/system/view/user/reference.vue b/src/modules/system/view/user/reference.vue new file mode 100644 index 0000000..4768b72 --- /dev/null +++ b/src/modules/system/view/user/reference.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/modules/system/view/user/treeList.vue b/src/modules/system/view/user/treeList.vue new file mode 100644 index 0000000..2660461 --- /dev/null +++ b/src/modules/system/view/user/treeList.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/modules/system/view/user/treeListReference.vue b/src/modules/system/view/user/treeListReference.vue new file mode 100644 index 0000000..04f2dea --- /dev/null +++ b/src/modules/system/view/user/treeListReference.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/src/modules/system/view/user/view.vue b/src/modules/system/view/user/view.vue new file mode 100644 index 0000000..3cfeb56 --- /dev/null +++ b/src/modules/system/view/user/view.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/modules/system/view/userGroup/add.vue b/src/modules/system/view/userGroup/add.vue new file mode 100644 index 0000000..00f4346 --- /dev/null +++ b/src/modules/system/view/userGroup/add.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/modules/system/view/userGroup/list.vue b/src/modules/system/view/userGroup/list.vue new file mode 100644 index 0000000..8683ae1 --- /dev/null +++ b/src/modules/system/view/userGroup/list.vue @@ -0,0 +1,229 @@ + + + diff --git a/src/modules/system/view/userGroup/modify.vue b/src/modules/system/view/userGroup/modify.vue new file mode 100644 index 0000000..e53dd05 --- /dev/null +++ b/src/modules/system/view/userGroup/modify.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/modules/system/view/userGroup/tree.vue b/src/modules/system/view/userGroup/tree.vue new file mode 100644 index 0000000..a889240 --- /dev/null +++ b/src/modules/system/view/userGroup/tree.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/modules/system/view/userGroup/treeList.vue b/src/modules/system/view/userGroup/treeList.vue new file mode 100644 index 0000000..eb80538 --- /dev/null +++ b/src/modules/system/view/userGroup/treeList.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/modules/system/view/userGroup/treeMultipleDirectReference.vue b/src/modules/system/view/userGroup/treeMultipleDirectReference.vue new file mode 100644 index 0000000..deab101 --- /dev/null +++ b/src/modules/system/view/userGroup/treeMultipleDirectReference.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/modules/system/view/userGroup/treeReference.vue b/src/modules/system/view/userGroup/treeReference.vue new file mode 100644 index 0000000..8e53cb0 --- /dev/null +++ b/src/modules/system/view/userGroup/treeReference.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/modules/system/view/userGroup/treeSingleSelect.vue b/src/modules/system/view/userGroup/treeSingleSelect.vue new file mode 100644 index 0000000..fba3987 --- /dev/null +++ b/src/modules/system/view/userGroup/treeSingleSelect.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/modules/system/view/userGroup/user/index.vue b/src/modules/system/view/userGroup/user/index.vue new file mode 100644 index 0000000..961a7b4 --- /dev/null +++ b/src/modules/system/view/userGroup/user/index.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/modules/system/view/userGroup/user/list.vue b/src/modules/system/view/userGroup/user/list.vue new file mode 100644 index 0000000..41768d8 --- /dev/null +++ b/src/modules/system/view/userGroup/user/list.vue @@ -0,0 +1,271 @@ + + + + + diff --git a/src/modules/system/view/userGroup/view.vue b/src/modules/system/view/userGroup/view.vue new file mode 100644 index 0000000..3ac0a27 --- /dev/null +++ b/src/modules/system/view/userGroup/view.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/modules/system/view/userProfile/modify.vue b/src/modules/system/view/userProfile/modify.vue new file mode 100644 index 0000000..e226e1c --- /dev/null +++ b/src/modules/system/view/userProfile/modify.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/template/api/index.ts b/src/modules/template/api/index.ts new file mode 100644 index 0000000..bbbbe83 --- /dev/null +++ b/src/modules/template/api/index.ts @@ -0,0 +1,12 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' + +const moduleName = 'template' + +// 模板 +export const notice = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'notice' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + } +}) diff --git a/src/modules/template/constant/index.ts b/src/modules/template/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/template/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/workflow/api/index.ts b/src/modules/workflow/api/index.ts new file mode 100644 index 0000000..489e244 --- /dev/null +++ b/src/modules/workflow/api/index.ts @@ -0,0 +1,185 @@ +import { COMMON_METHOD } from '@/constant/common' +import request from '@/config/axios' +import qs from 'qs' + +const moduleName = 'workflow' + +// 流程模板 +export const workflowTemplate = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'workflowTemplate' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + }, + publish(id) { + return request.put({ url: this.serveUrl + id + '/publish' }) + }, + upgrade(id) { + return request.put({ url: this.serveUrl + id + '/upgrade' }) + }, + valid(id) { + return request.put({ url: this.serveUrl + id + '/valid' }) + }, + // 生成临时版本 + generateTemporaryVersion(processDefinitionKey) { + return request.get({ + url: this.serveUrl + 'generateTemporaryVersion', + params: { processDefinitionKey } + }) + }, + // 获取模型 + getModelByProcessDefinitionId(processDefinitionId) { + return request.get({ + url: this.serveUrl + 'getModelByProcessDefinitionId', + params: { processDefinitionId } + }) + }, + // 获取所有用户类型节点 + getUserTaskNodeByProcessDefinitionId(processDefinitionId) { + return request.get({ + url: this.serveUrl + 'getUserTaskNodeByProcessDefinitionId', + params: { processDefinitionId } + }) + } +}) + +// 我的申请 +export const apply = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'apply' + '/', + portlet(params) { + return request.get({ url: this.serveUrl + 'portlet', params }) + } +}) + +// 我的待办 +export const todo = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'todo' + '/', + portlet(params) { + return request.get({ url: this.serveUrl + 'portlet', params }) + } +}) + +// 我的已办 +export const done = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'done' + '/' +}) + +// 工作流监听器 +export const workflowListener = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'workflowListener' + '/', + enable(id) { + return request.put({ url: this.serveUrl + id + '/enable' }) + }, + disable(id) { + return request.put({ url: this.serveUrl + id + '/disable' }) + } +}) + +// 流程 +export const processInstance = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'processInstance' + '/', + // 获取流程当前任务标识,通常用于流程启动首环节处理 + getTaskId(processInstanceId) { + return request.get({ url: this.serveUrl + processInstanceId + '/getTaskId' }) + }, + // 获取流程节点处理人 + getNodeHandler(processInstanceId, nodeId) { + return request.get({ + url: this.serveUrl + processInstanceId + '/' + nodeId + '/getNodeHandler' + }) + }, + // 获取某环节最后一次处理人 + getLastCommitter(processInstanceId, nodeId) { + return request.get({ + url: this.serveUrl + processInstanceId + '/' + nodeId + '/getLastCommitter' + }) + } +}) + +// 任务 +export const task = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'task' + '/', + // 提交 + commit(taskData) { + return request.put({ url: this.serveUrl + taskData.taskId + '/commit', data: taskData }) + }, + // 驳回 + reject(taskData) { + return request.put({ url: this.serveUrl + taskData.taskId + '/reject', data: taskData }) + }, + // 跳转 + jump(taskData) { + return request.put({ url: this.serveUrl + taskData.taskId + '/jump', data: taskData }) + }, + // 转办 + transfer(taskId, assignee, comment) { + return request.put({ url: this.serveUrl + taskId + '/transfer', params: { assignee, comment } }) + }, + // 委派 + delegate(taskId, assignee, comment) { + return request.put({ url: this.serveUrl + taskId + '/delegate', params: { assignee, comment } }) + }, + // 签收 + signIn(taskId) { + return request.put({ url: this.serveUrl + taskId + '/signIn' }) + }, + // 撤销签收 + cancelSignIn(taskId) { + return request.put({ url: this.serveUrl + taskId + '/cancelSignIn' }) + }, + // 获取后续环节列表 + getForwardNodeList(id) { + return request.get({ url: this.serveUrl + id + '/getForwardNodeList' }) + }, + // 获取回退环节列表 + getBackNodeList(id) { + return request.get({ url: this.serveUrl + id + '/getBackNodeList' }) + }, + // 获取跳转环节列表 + getJumpNodeList(id) { + return request.get({ url: this.serveUrl + id + '/getJumpNodeList' }) + } +}) + +// 处理意见 +export const workflowComment = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'workflowComment' + '/' +}) + +// 环节设置 +export const workflowNodeConfig = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'workflowNodeConfig' + '/', + // 获取环节设置 + getNodeConfig(processDefinitionId, definitionKey) { + return request.get({ + url: this.serveUrl + 'getNodeConfig?' + qs.stringify({ processDefinitionId, definitionKey }) + }) + } +}) + +// 环节权限设置 +export const workflowNodePermissionConfig = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'workflowNodePermissionConfig' + '/', + // 获取流程环节权限设置 + getNodePermissionConfig(processDefinitionId, definitionKey) { + return request.get({ + url: + this.serveUrl + + 'getNodePermissionConfig?' + + qs.stringify({ processDefinitionId, definitionKey }) + }) + }, + // 获取浏览模式环节权限设置(查询所有区域,将权限设置为只读) + getNodePermissionConfigForView(processDefinitionId) { + return request.get({ + url: this.serveUrl + 'getNodePermissionConfigForView?' + qs.stringify({ processDefinitionId }) + }) + } +}) + +// 监听器配置 +export const workflowListenerConfig = Object.assign({}, COMMON_METHOD, { + serveUrl: '/' + moduleName + '/' + 'workflowListenerConfig' + '/' +}) diff --git a/src/modules/workflow/constant/index.ts b/src/modules/workflow/constant/index.ts new file mode 100644 index 0000000..548f9af --- /dev/null +++ b/src/modules/workflow/constant/index.ts @@ -0,0 +1 @@ +export const A = 'a' diff --git a/src/modules/workflow/view/apply/list.vue b/src/modules/workflow/view/apply/list.vue new file mode 100644 index 0000000..56f0418 --- /dev/null +++ b/src/modules/workflow/view/apply/list.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/modules/workflow/view/done/list.vue b/src/modules/workflow/view/done/list.vue new file mode 100644 index 0000000..75d794e --- /dev/null +++ b/src/modules/workflow/view/done/list.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/src/modules/workflow/view/flow/create.vue b/src/modules/workflow/view/flow/create.vue new file mode 100644 index 0000000..ec05435 --- /dev/null +++ b/src/modules/workflow/view/flow/create.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/modules/workflow/view/flow/view.vue b/src/modules/workflow/view/flow/view.vue new file mode 100644 index 0000000..1075dbf --- /dev/null +++ b/src/modules/workflow/view/flow/view.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/modules/workflow/view/portletList/apply.vue b/src/modules/workflow/view/portletList/apply.vue new file mode 100644 index 0000000..e367215 --- /dev/null +++ b/src/modules/workflow/view/portletList/apply.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/modules/workflow/view/portletList/todo.vue b/src/modules/workflow/view/portletList/todo.vue new file mode 100644 index 0000000..e4fd57c --- /dev/null +++ b/src/modules/workflow/view/portletList/todo.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/src/modules/workflow/view/task/basicInfo.vue b/src/modules/workflow/view/task/basicInfo.vue new file mode 100644 index 0000000..c645d0e --- /dev/null +++ b/src/modules/workflow/view/task/basicInfo.vue @@ -0,0 +1,53 @@ + + + + diff --git a/src/modules/workflow/view/task/commit.vue b/src/modules/workflow/view/task/commit.vue new file mode 100644 index 0000000..db31576 --- /dev/null +++ b/src/modules/workflow/view/task/commit.vue @@ -0,0 +1,209 @@ + + + + diff --git a/src/modules/workflow/view/task/delegate.vue b/src/modules/workflow/view/task/delegate.vue new file mode 100644 index 0000000..a521f27 --- /dev/null +++ b/src/modules/workflow/view/task/delegate.vue @@ -0,0 +1,82 @@ + + + + diff --git a/src/modules/workflow/view/task/handle.vue b/src/modules/workflow/view/task/handle.vue new file mode 100644 index 0000000..6b5e3d8 --- /dev/null +++ b/src/modules/workflow/view/task/handle.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/modules/workflow/view/task/historyStep.vue b/src/modules/workflow/view/task/historyStep.vue new file mode 100644 index 0000000..d192fea --- /dev/null +++ b/src/modules/workflow/view/task/historyStep.vue @@ -0,0 +1,45 @@ + + + + diff --git a/src/modules/workflow/view/task/jump.vue b/src/modules/workflow/view/task/jump.vue new file mode 100644 index 0000000..014f662 --- /dev/null +++ b/src/modules/workflow/view/task/jump.vue @@ -0,0 +1,222 @@ + + + + diff --git a/src/modules/workflow/view/task/reject.vue b/src/modules/workflow/view/task/reject.vue new file mode 100644 index 0000000..4e24a7b --- /dev/null +++ b/src/modules/workflow/view/task/reject.vue @@ -0,0 +1,222 @@ + + + + diff --git a/src/modules/workflow/view/task/transfer.vue b/src/modules/workflow/view/task/transfer.vue new file mode 100644 index 0000000..d435768 --- /dev/null +++ b/src/modules/workflow/view/task/transfer.vue @@ -0,0 +1,82 @@ + + + + diff --git a/src/modules/workflow/view/todo/list.vue b/src/modules/workflow/view/todo/list.vue new file mode 100644 index 0000000..b99b437 --- /dev/null +++ b/src/modules/workflow/view/todo/list.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/modules/workflow/view/workflowListener/add.vue b/src/modules/workflow/view/workflowListener/add.vue new file mode 100644 index 0000000..26b8c5d --- /dev/null +++ b/src/modules/workflow/view/workflowListener/add.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/workflow/view/workflowListener/list.vue b/src/modules/workflow/view/workflowListener/list.vue new file mode 100644 index 0000000..5eec768 --- /dev/null +++ b/src/modules/workflow/view/workflowListener/list.vue @@ -0,0 +1,193 @@ + + + diff --git a/src/modules/workflow/view/workflowListener/modify.vue b/src/modules/workflow/view/workflowListener/modify.vue new file mode 100644 index 0000000..7879805 --- /dev/null +++ b/src/modules/workflow/view/workflowListener/modify.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/modules/workflow/view/workflowListener/reference.vue b/src/modules/workflow/view/workflowListener/reference.vue new file mode 100644 index 0000000..8695fed --- /dev/null +++ b/src/modules/workflow/view/workflowListener/reference.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/src/modules/workflow/view/workflowListener/view.vue b/src/modules/workflow/view/workflowListener/view.vue new file mode 100644 index 0000000..c79248e --- /dev/null +++ b/src/modules/workflow/view/workflowListener/view.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/add.vue b/src/modules/workflow/view/workflowTemplate/add.vue new file mode 100644 index 0000000..dc0d85f --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/add.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/list.vue b/src/modules/workflow/view/workflowTemplate/list.vue new file mode 100644 index 0000000..b2581d6 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/list.vue @@ -0,0 +1,287 @@ + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/ViewDiagramButton.vue b/src/modules/workflow/view/workflowTemplate/model/components/ViewDiagramButton.vue new file mode 100644 index 0000000..1b2b778 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/ViewDiagramButton.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/addNode.vue b/src/modules/workflow/view/workflowTemplate/model/components/addNode.vue new file mode 100644 index 0000000..39ca2d4 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/addNode.vue @@ -0,0 +1,206 @@ + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowListenerSelect.vue b/src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowListenerSelect.vue new file mode 100644 index 0000000..30d8069 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowListenerSelect.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowStepSelect.vue b/src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowStepSelect.vue new file mode 100644 index 0000000..1405c68 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/dialog/FlowStepSelect.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/dialog/ImportDialog.vue b/src/modules/workflow/view/workflowTemplate/model/components/dialog/ImportDialog.vue new file mode 100644 index 0000000..531153d --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/dialog/ImportDialog.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/dialog/ViewDialog.vue b/src/modules/workflow/view/workflowTemplate/model/components/dialog/ViewDialog.vue new file mode 100644 index 0000000..9dad287 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/dialog/ViewDialog.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/dialog/common.js b/src/modules/workflow/view/workflowTemplate/model/components/dialog/common.js new file mode 100644 index 0000000..b048a53 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/dialog/common.js @@ -0,0 +1,52 @@ +/* + * @Date: 2022-08-29 14:00:42 + * @LastEditors: StavinLi 495727881@qq.com + * @LastEditTime: 2023-03-29 15:53:05 + * @FilePath: /Workflow-Vue3/src/components/dialog/common.js + */ + +// import { getRoles, getDepartments, getEmployees } from '../../api/index.js' +import $func from '../../utils/index.js' +import { ref } from 'vue' +export let searchVal = ref('') +export let departments = ref({ + titleDepartments: [], + childDepartments: [], + employees: [] +}) +export let roles = ref({}) +export let getRoleList = async () => { + let { + data: { list } + } = await getRoles() + roles.value = list +} +export let getDepartmentList = async (parentId = 0) => { + let { data } = await getDepartments({ parentId }) + departments.value = data +} +export let getDebounceData = (event, type = 1) => { + $func.debounce(async () => { + if (event.target.value) { + let data = { + searchName: event.target.value, + pageNum: 1, + pageSize: 30 + } + if (type == 1) { + departments.value.childDepartments = [] + let res = await getEmployees(data) + departments.value.employees = res.data.list + } else { + let res = await getRoles(data) + roles.value = res.data.list + } + } else { + type == 1 ? await getDepartmentList() : await getRoleList() + } + })() +} + +const getRoles = () => {} +const getDepartments = () => {} +const getEmployees = () => {} diff --git a/src/modules/workflow/view/workflowTemplate/model/components/dialog/errorDialog.vue b/src/modules/workflow/view/workflowTemplate/model/components/dialog/errorDialog.vue new file mode 100644 index 0000000..24a3234 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/dialog/errorDialog.vue @@ -0,0 +1,77 @@ + + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/drawer/ConditionNodeConfig.vue b/src/modules/workflow/view/workflowTemplate/model/components/drawer/ConditionNodeConfig.vue new file mode 100644 index 0000000..fbe62ab --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/drawer/ConditionNodeConfig.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/drawer/HandleNodeConfig.vue b/src/modules/workflow/view/workflowTemplate/model/components/drawer/HandleNodeConfig.vue new file mode 100644 index 0000000..776e5b4 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/drawer/HandleNodeConfig.vue @@ -0,0 +1,307 @@ + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/drawer/RootNodeConfig.vue b/src/modules/workflow/view/workflowTemplate/model/components/drawer/RootNodeConfig.vue new file mode 100644 index 0000000..68f86bd --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/drawer/RootNodeConfig.vue @@ -0,0 +1,163 @@ + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/flowDesign.vue b/src/modules/workflow/view/workflowTemplate/model/components/flowDesign.vue new file mode 100644 index 0000000..db11b8b --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/flowDesign.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/flowPreview.vue b/src/modules/workflow/view/workflowTemplate/model/components/flowPreview.vue new file mode 100644 index 0000000..32312aa --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/flowPreview.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/components/nodeWrap.vue b/src/modules/workflow/view/workflowTemplate/model/components/nodeWrap.vue new file mode 100644 index 0000000..12161c8 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/components/nodeWrap.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/model/css/dialog.css b/src/modules/workflow/view/workflowTemplate/model/css/dialog.css new file mode 100644 index 0000000..847cfd2 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/css/dialog.css @@ -0,0 +1,34 @@ +.person_body { + border: 1px solid #f5f5f5; + height: 500px; +} +.tree_nav span { + display: inline-block; + padding-right: 10px; + margin-right: 5px; + max-width: 6em; + color: #38adff; + font-size: 12px; + cursor: pointer; + background: url(~@/assets/images/jiaojiao.png) no-repeat right center; +} +.tree_nav span:last-of-type { + background: none; +} +.person_tree { + padding: 10px 12px 0 8px; + width: 280px; + height: 100%; + border-right: 1px solid #f5f5f5; +} +.person_tree input { + padding-left: 22px; + width: 210px; + height: 30px; + font-size: 12px; + border-radius: 2px; + border: 1px solid #d5dadf; + background: url(~@/assets/images/list_search.png) no-repeat 10px center; + background-size: 14px 14px; + margin-bottom: 14px; +} \ No newline at end of file diff --git a/src/modules/workflow/view/workflowTemplate/model/css/workflow.css b/src/modules/workflow/view/workflowTemplate/model/css/workflow.css new file mode 100644 index 0000000..bc3ddcc --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/css/workflow.css @@ -0,0 +1,1722 @@ +body { + background: #eee +} + +@font-face { + font-family: Chinese Quote; + src: local("PingFang SC"), local("SimSun"); + unicode-range: u+2018, u+2019, u+201c, u+201d +} + +body, +html { + width: 100%; + /* height: 100% */ +} + +input::-ms-clear, +input::-ms-reveal { + display: none +} + +*, +:after, +:before { + box-sizing: border-box +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0) +} + +@-ms-viewport { + width: device-width +} + +article, +aside, +dialog, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { + display: block +} + +body { + margin: 0; + font-family: Chinese Quote, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif; + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + color: rgba(0, 0, 0, .65); + background-color: #fff +} + +[tabindex="-1"]:focus { + outline: none!important +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: .5em; + color: rgba(0, 0, 0, .85); + font-weight: 500 +} + +p { + margin-top: 0; + margin-bottom: 1em +} + +abbr[data-original-title], +abbr[title] { + text-decoration: underline; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0 +} + +address { + margin-bottom: 1em; + font-style: normal; + line-height: inherit +} + +input[type=number], +input[type=password], +input[type=text], +textarea { + -webkit-appearance: none +} + +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1em +} + +ol ol, +ol ul, +ul ol, +ul ul { + margin-bottom: 0 +} + +dt { + font-weight: 500 +} + +dd { + margin-bottom: .5em; + margin-left: 0 +} + +blockquote { + margin: 0 0 1em +} + +dfn { + font-style: italic +} + +b, +strong { + font-weight: bolder +} + +small { + font-size: 80% +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +a { + color: #1890ff; + background-color: transparent; + text-decoration: none; + outline: none; + cursor: pointer; + transition: color .3s; + -webkit-text-decoration-skip: objects +} + +a:focus { + text-decoration: underline; + text-decoration-skip: auto +} + +a:hover { + color: #40a9ff +} + +a:active { + color: #096dd9 +} + +a:active, +a:hover { + outline: 0; + text-decoration: none +} + +code, +kbd, +pre, +samp { + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace; + font-size: 1em +} + +pre { + margin-top: 0; + margin-bottom: 1em; + overflow: auto +} + +figure { + margin: 0 0 1em +} + +img { + vertical-align: middle; + border-style: none +} + +svg:not(:root) { + overflow: hidden +} + +[role=button], +a, +area, +button, +input:not([type=range]), +label, +select, +summary, +textarea { + touch-action: manipulation +} + +table { + border-collapse: collapse +} + +caption { + padding-top: .75em; + padding-bottom: .3em; + color: rgba(0, 0, 0, .45); + text-align: left; + caption-side: bottom +} + +th { + text-align: inherit +} + +button, +input, +optgroup, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: inherit +} + +button, +input { + overflow: visible +} + +button, +select { + text-transform: none +} + +[type=reset], +[type=submit], +button, +html [type=button] { + -webkit-appearance: button +} + +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner, +button::-moz-focus-inner { + padding: 0; + border-style: none +} + +input[type=checkbox], +input[type=radio] { + box-sizing: border-box; + padding: 0 +} + +input[type=date], +input[type=datetime-local], +input[type=month], +input[type=time] { + -webkit-appearance: listbox +} + +textarea { + overflow: auto; + resize: vertical +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0 +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5em; + font-size: 1.5em; + line-height: inherit; + color: inherit; + white-space: normal +} + +progress { + vertical-align: baseline +} + +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto +} + +[type=search] { + outline-offset: -2px; + -webkit-appearance: none +} + +[type=search]::-webkit-search-cancel-button, +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button +} + +output { + display: inline-block +} + +summary { + display: list-item +} + +template { + display: none +} + +[hidden] { + display: none!important +} + +mark { + padding: .2em; + background-color: #feffe6 +} + +::selection { + background: #1890ff; + color: #fff +} + +.clearfix { + zoom: 1 +} + +.clearfix:after, +.clearfix:before { + content: ""; + display: table +} + +.clearfix:after { + clear: both +} + +@font-face { + font-family: anticon; + font-display: fallback; + src: url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.eot"); + src: url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.woff") format("woff"), url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.ttf") format("truetype"), url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.svg#iconfont") format("svg") +} + +.anticon { + display: inline-block; + font-style: normal; + vertical-align: baseline; + text-align: center; + text-transform: none; + line-height: 1; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +.anticon:before { + display: block; + font-family: anticon!important +} +.anticon-close:before { + content: "\E633" +} +.anticon-right:before { + content: "\E61F" +} +.anticon-exclamation-circle{ + color: rgb(242, 86, 67) +} +.anticon-exclamation-circle:before { + content: "\E62C" +} + +.anticon-left:before { + content: "\E620" +} + +.anticon-close-circle:before { + content: "\E62E" +} + +.ant-btn { + line-height: 1.5; + display: inline-block; + font-weight: 400; + text-align: center; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 0 15px; + font-size: 14px; + border-radius: 4px; + height: 32px; + user-select: none; + transition: all .3s cubic-bezier(.645, .045, .355, 1); + position: relative; + color: rgba(0, 0, 0, .65); + background-color: #fff; + border-color: #d9d9d9 +} + +.ant-btn>.anticon { + line-height: 1 +} + +.ant-btn, +.ant-btn:active, +.ant-btn:focus { + outline: 0 +} + +.ant-btn>a:only-child { + color: currentColor +} + +.ant-btn>a:only-child:after { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: transparent +} + +.ant-btn:focus, +.ant-btn:hover { + color: #40a9ff; + background-color: #fff; + border-color: #40a9ff +} + +.ant-btn:focus>a:only-child, +.ant-btn:hover>a:only-child { + color: currentColor +} + +.ant-btn:focus>a:only-child:after, +.ant-btn:hover>a:only-child:after { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: transparent +} + +.ant-btn.active, +.ant-btn:active { + color: #096dd9; + background-color: #fff; + border-color: #096dd9 +} + +.ant-btn.active>a:only-child, +.ant-btn:active>a:only-child { + color: currentColor +} + +.ant-btn.active>a:only-child:after, +.ant-btn:active>a:only-child:after { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: transparent +} + +.ant-btn.active, +.ant-btn:active, +.ant-btn:focus, +.ant-btn:hover { + background: #fff; + text-decoration: none +} + +.ant-btn>i, +.ant-btn>span { + pointer-events: none +} + +.ant-btn:before { + position: absolute; + top: -1px; + left: -1px; + bottom: -1px; + right: -1px; + background: #fff; + opacity: .35; + content: ""; + border-radius: inherit; + z-index: 1; + transition: opacity .2s; + pointer-events: none; + display: none +} + +.ant-btn .anticon { + transition: margin-left .3s cubic-bezier(.645, .045, .355, 1) +} + +.ant-btn:active>span, +.ant-btn:focus>span { + position: relative +} + +.ant-btn>.anticon+span, +.ant-btn>span+.anticon { + margin-left: 8px +} + +.fd-nav-container { + display: inline-block; + position: relative +} + +.fd-nav-container .ghost-bar { + position: absolute; + width: 150px; + height: 100%; + left: 0; + background: #1583f2; + -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1); + transition: all .3s cubic-bezier(.645, .045, .355, 1) +} + +.fd-nav-container .ghost-bar:after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -5px; + width: 0; + height: 0; + border-style: solid; + border-width: 0 5px 6px; + border-color: transparent transparent #f6f6f6 +} + +.fd-nav-item { + position: relative; + cursor: pointer; + display: inline-block; + line-height: 60px; + width: 150px; + text-align: center; + white-space: nowrap +} + +.fd-nav-item .order-num { + display: inline-block; + width: 20px; + height: 20px; + line-height: 20px; + border: 1px solid #fff; + border-radius: 50%; + margin-right: 6px; + -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1); + transition: all .3s cubic-bezier(.645, .045, .355, 1) +} + +.fd-nav-item.active .order-num { + color: #1583f2; + background: #fff +} + +.ant-input { + font-family: Chinese Quote, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif; + font-variant: tabular-nums; + box-sizing: border-box; + margin: 0; + padding: 0; + list-style: none; + position: relative; + display: inline-block; + padding: 4px 11px; + width: 100%; + height: 32px; + font-size: 14px; + line-height: 1.5; + color: rgba(0, 0, 0, .65); + background-color: #fff; + background-image: none; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all .3s +} + +.ant-input::-moz-placeholder { + color: #bfbfbf; + opacity: 1 +} + +.ant-input:-ms-input-placeholder { + color: #bfbfbf +} + +.ant-input::-webkit-input-placeholder { + color: #bfbfbf +} + +.ant-input:focus, +.ant-input:hover { + border-color: #40a9ff; + border-right-width: 1px!important +} + +.ant-input:focus { + outline: 0; + box-shadow: 0 0 0 2px rgba(24, 144, 255, .2) +} + +textarea.ant-input { + max-width: 100%; + height: auto; + vertical-align: bottom; + transition: all .3s, height 0s; + min-height: 32px +} + +a, +abbr, +acronym, +address, +applet, +article, +aside, +audio, +b, +big, +blockquote, +body, +canvas, +caption, +center, +cite, +code, +dd, +del, +details, +dfn, +div, +dl, +dt, +em, +fieldset, +figcaption, +figure, +footer, +form, +h1, +h2, +h3, +h4, +h5, +h6, +header, +hgroup, +html, +i, +iframe, +img, +ins, +kbd, +label, +legend, +li, +mark, +menu, +nav, +object, +ol, +p, +pre, +q, +s, +samp, +section, +small, +span, +strike, +strong, +sub, +summary, +sup, +table, +tbody, +td, +tfoot, +th, +thead, +time, +tr, +tt, +u, +ul, +var, +video { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + font: inherit; + /* vertical-align: baseline */ +} + +*, +:after, +:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} + +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} + +body, +html { + font-size: 14px +} + +body { + font-family: Microsoft Yahei, Lucida Grande, Lucida Sans Unicode, Helvetica, Arial, Verdana, sans-serif; + line-height: 1.6; + background-color: #fff; + position: static!important; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0) +} + +ol, +ul { + list-style-type: none +} + +b, +strong { + font-weight: 700 +} + +img { + border: 0 +} + +button, +input, +select, +textarea { + font-family: inherit; + font-size: 100%; + margin: 0 +} + +textarea { + overflow: auto; + vertical-align: top; + -webkit-appearance: none +} + +button, +input { + line-height: normal +} + +button, +select { + text-transform: none +} + +button, +html input[type=button], +input[type=reset], +input[type=submit] { + -webkit-appearance: button; + cursor: pointer +} + +input[type=search] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box +} + +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0 +} + +table { + width: 100%; + border-spacing: 0; + border-collapse: collapse +} + +table, +td, +th { + border: 0 +} + +td, +th { + padding: 0; + vertical-align: top +} + +th { + font-weight: 700; + text-align: left +} + +thead th { + white-space: nowrap +} + +a { + text-decoration: none; + cursor: pointer; + color: #3296fa +} + +a:active, +a:hover { + outline: 0; + color: #3296fa +} + +small { + font-size: 80% +} + +body, +html { + font-size: 12px!important; + color: #191f25!important; + background: #f6f6f6!important +} + +.wrap { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + height: 100% +} + +@font-face { + font-family: IconFont; + src: url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot"); + src: url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.woff") format("woff"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.ttf") format("truetype"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.svg#IconFont2") format("svg") +} + +.iconfont { + font-family: IconFont!important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -webkit-text-stroke-width: .2px; + -moz-osx-font-smoothing: grayscale +} + +.fd-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 997; + width: 100%; + height: 60px; + font-size: 14px; + color: #fff; + background: #3296fa; + display: flex; + align-items: center +} + +.fd-nav>* { + flex: 1; + width: 100% +} + +.fd-nav .fd-nav-left { + display: -webkit-box; + display: flex; + align-items: center +} + +.fd-nav .fd-nav-center { + flex: none; + width: 600px; + text-align: center +} + +.fd-nav .fd-nav-right { + display: flex; + align-items: center; + justify-content: flex-end; + text-align: right +} + +.fd-nav .fd-nav-back { + display: inline-block; + width: 60px; + height: 60px; + font-size: 22px; + border-right: 1px solid #1583f2; + text-align: center; + cursor: pointer +} + +.fd-nav .fd-nav-back:hover { + background: #5af +} + +.fd-nav .fd-nav-back:active { + background: #1583f2 +} + +.fd-nav .fd-nav-back .anticon { + line-height: 60px +} + +.fd-nav .fd-nav-title { + width: 0; + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding: 0 15px +} + +.fd-nav a { + color: #fff; + margin-left: 12px +} + +.fd-nav .button-publish { + min-width: 80px; + margin-left: 4px; + margin-right: 15px; + color: #3296fa; + border-color: #fff +} + +.fd-nav .button-publish.ant-btn:focus, +.fd-nav .button-publish.ant-btn:hover { + color: #3296fa; + border-color: #fff; + box-shadow: 0 10px 20px 0 rgba(0, 0, 0, .3) +} + +.fd-nav .button-publish.ant-btn:active { + color: #3296fa; + background: #d6eaff; + box-shadow: none +} + +.fd-nav .button-preview { + min-width: 80px; + margin-left: 16px; + margin-right: 4px; + color: #fff; + border-color: #fff; + background: transparent +} + +.fd-nav .button-preview.ant-btn:focus, +.fd-nav .button-preview.ant-btn:hover { + color: #fff; + border-color: #fff; + background: #59acfc +} + +.fd-nav .button-preview.ant-btn:active { + color: #fff; + border-color: #fff; + background: #2186ef +} + +.fd-nav-content { + position: fixed; + top: 60px; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + overflow-x: hidden; + overflow-y: auto; + padding-bottom: 30px +} + +.error-modal-desc { + font-size: 13px; + color: rgba(25, 31, 37, .56); + line-height: 22px; + margin-bottom: 14px +} + +.error-modal-list { + height: 200px; + overflow-y: auto; + margin-right: -25px; + padding-right: 25px +} + +.error-modal-item { + padding: 10px 20px; + line-height: 21px; + background: #f6f6f6; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + border-radius: 4px +} + +.error-modal-item-label { + flex: none; + font-size: 15px; + color: rgba(25, 31, 37, .56); + padding-right: 10px +} + +.error-modal-item-content { + text-align: right; + flex: 1; + font-size: 13px; + color: #191f25 +} + +#body.blur { + -webkit-filter: blur(3px); + filter: blur(3px) +} + +.zoom { + display: flex; + position: fixed; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + height: 40px; + width: 125px; + right: 40px; + margin-top: 30px; + z-index: 10 +} + +.zoom .zoom-in, +.zoom .zoom-out { + width: 30px; + height: 30px; + background: #fff; + color: #c1c1cd; + cursor: pointer; + background-size: 100%; + background-repeat: no-repeat +} + +.zoom .zoom-out { + background-image: url(https://gw.alicdn.com/tfs/TB1s0qhBHGYBuNjy0FoXXciBFXa-90-90.png) +} + +.zoom .zoom-out.disabled { + opacity: .5 +} + +.zoom .zoom-in { + background-image: url(https://gw.alicdn.com/tfs/TB1UIgJBTtYBeNjy1XdXXXXyVXa-90-90.png) +} + +.zoom .zoom-in.disabled { + opacity: .5 +} + +.auto-judge:hover .editable-title, +.node-wrap-box:hover .editable-title { + border-bottom: 1px dashed #fff +} + +.auto-judge:hover .editable-title.editing, +.node-wrap-box:hover .editable-title.editing { + text-decoration: none; + border: 1px solid #d9d9d9 +} + +.auto-judge:hover .editable-title { + border-color: #15bc83 +} + +.editable-title { + line-height: 15px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + border-bottom: 1px dashed transparent +} + +.editable-title:before { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 40px +} + +.editable-title:hover { + border-bottom: 1px dashed #fff +} + +.editable-title-input { + flex: none; + height: 18px; + padding-left: 4px; + text-indent: 0; + font-size: 12px; + line-height: 18px; + z-index: 1 +} + +.editable-title-input:hover { + text-decoration: none +} + +.ant-btn { + position: relative +} + +.node-wrap-box { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + width: 220px; + min-height: 72px; + -ms-flex-negative: 0; + flex-shrink: 0; + background: #fff; + border-radius: 4px; + cursor: pointer +} + +.node-wrap-box:after { + pointer-events: none; + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + border-radius: 4px; + border: 1px solid transparent; + transition: all .1s cubic-bezier(.645, .045, .355, 1); + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) +} + +.node-wrap-box.active:after, +.node-wrap-box:active:after, +.node-wrap-box:hover:after { + border: 1px solid #3296fa; + box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3) +} + +.node-wrap-box.active .close, +.node-wrap-box:active .close, +.node-wrap-box:hover .close { + display: block +} + +.node-wrap-box.error:after { + border: 1px solid #f25643; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) +} + +.node-wrap-box .title { + position: relative; + display: flex; + align-items: center; + padding-left: 16px; + padding-right: 30px; + width: 100%; + height: 24px; + line-height: 24px; + font-size: 12px; + color: #fff; + text-align: left; + background: #576a95; + border-radius: 4px 4px 0 0 +} + +.node-wrap-box .title .iconfont { + font-size: 12px; + margin-right: 5px +} + +.node-wrap-box .placeholder { + color: #bfbfbf +} + +.node-wrap-box .close { + display: none; + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + width: 20px; + height: 20px; + font-size: 14px; + color: #fff; + border-radius: 50%; + text-align: center; + line-height: 20px +} + +.node-wrap-box .content { + position: relative; + font-size: 14px; + padding: 16px; + padding-right: 30px +} + +.node-wrap-box .content .text { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical +} + +.node-wrap-box .content .arrow { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + width: 20px; + height: 14px; + font-size: 14px; + color: #979797 +} + +.start-node.node-wrap-box .content .text { + display: block; + white-space: nowrap +} + +.node-wrap-box:before { + content: ""; + position: absolute; + top: -12px; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + width: 0; + height: 4px; + border-style: solid; + border-width: 8px 6px 4px; + border-color: #cacaca transparent transparent; + background: #f5f5f7 +} + +.node-wrap-box.start-node:before { + content: none +} + +.top-left-cover-line { + left: -1px +} + +.top-left-cover-line, +.top-right-cover-line { + position: absolute; + height: 8px; + width: 50%; + background-color: #f5f5f7; + top: -4px +} + +.top-right-cover-line { + right: -1px +} + +.bottom-left-cover-line { + left: -1px +} + +.bottom-left-cover-line, +.bottom-right-cover-line { + position: absolute; + height: 8px; + width: 50%; + background-color: #f5f5f7; + bottom: -4px +} + +.bottom-right-cover-line { + right: -1px +} + +.dingflow-design { + width: 100%; + background-color: #f5f5f7; + overflow: auto; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0 +} + +.dingflow-design .box-scale { + transform: scale(1); + display: inline-block; + position: relative; + width: 100%; + padding: 54.5px 0; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + min-width: -webkit-min-content; + min-width: -moz-min-content; + min-width: min-content; + background-color: #f5f5f7; + transform-origin: 50% 0px 0px; +} + +.dingflow-design .node-wrap { + flex-direction: column; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + padding: 0 50px; + position: relative +} + +.dingflow-design .branch-wrap, +.dingflow-design .node-wrap { + display: inline-flex; + width: 100% +} + +.dingflow-design .branch-box-wrap { + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-height: 270px; + width: 100%; + -ms-flex-negative: 0; + flex-shrink: 0 +} + +.dingflow-design .branch-box { + display: flex; + overflow: visible; + min-height: 180px; + height: auto; + border-bottom: 2px solid #ccc; + border-top: 2px solid #ccc; + position: relative; + margin-top: 15px +} + +.dingflow-design .branch-box .col-box { + background: #f5f5f7 +} + +.dingflow-design .branch-box .col-box:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 0; + margin: auto; + width: 2px; + height: 100%; + background-color: #cacaca +} + +.dingflow-design .add-branch { + border: none; + outline: none; + user-select: none; + justify-content: center; + font-size: 12px; + padding: 0 10px; + height: 30px; + line-height: 30px; + border-radius: 15px; + color: #3296fa; + background: #fff; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .1); + position: absolute; + top: -16px; + left: 50%; + transform: translateX(-50%); + transform-origin: center center; + cursor: pointer; + z-index: 1; + display: inline-flex; + align-items: center; + -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1); + transition: all .3s cubic-bezier(.645, .045, .355, 1) +} + +.dingflow-design .add-branch:hover { + transform: translateX(-50%) scale(1.1); + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, .1) +} + +.dingflow-design .add-branch:active { + transform: translateX(-50%); + box-shadow: none +} + +.dingflow-design .col-box { + display: inline-flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -webkit-box-align: center; + align-items: center; + position: relative +} + +.dingflow-design .condition-node { + min-height: 220px +} + +.dingflow-design .condition-node, +.dingflow-design .condition-node-box { + display: inline-flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -webkit-box-flex: 1 +} + +.dingflow-design .condition-node-box { + padding-top: 30px; + padding-right: 50px; + padding-left: 50px; + -webkit-box-pack: center; + justify-content: center; + -webkit-box-align: center; + align-items: center; + flex-grow: 1; + position: relative +} + +.dingflow-design .condition-node-box:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 2px; + height: 100%; + background-color: #cacaca +} + +.dingflow-design .auto-judge { + position: relative; + width: 220px; + min-height: 72px; + background: #fff; + border-radius: 4px; + padding: 14px 19px; + cursor: pointer +} + +.dingflow-design .auto-judge:after { + pointer-events: none; + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + border-radius: 4px; + border: 1px solid transparent; + transition: all .1s cubic-bezier(.645, .045, .355, 1); + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) +} + +.dingflow-design .auto-judge.active:after, +.dingflow-design .auto-judge:active:after, +.dingflow-design .auto-judge:hover:after { + border: 1px solid #3296fa; + box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3) +} + +.dingflow-design .auto-judge.active .close, +.dingflow-design .auto-judge:active .close, +.dingflow-design .auto-judge:hover .close { + display: block +} + +.dingflow-design .auto-judge.error:after { + border: 1px solid #f25643; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) +} + +.dingflow-design .auto-judge .title-wrapper { + position: relative; + font-size: 12px; + color: #15bc83; + text-align: left; + line-height: 16px +} + +.dingflow-design .auto-judge .title-wrapper .editable-title { + display: inline-block; + max-width: 120px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis +} + +.dingflow-design .auto-judge .title-wrapper .priority-title { + display: inline-block; + float: right; + margin-right: 10px; + color: rgba(25, 31, 37, .56) +} + +.dingflow-design .auto-judge .placeholder { + color: #bfbfbf +} + +.dingflow-design .auto-judge .close { + display: none; + position: absolute; + right: -10px; + top: -10px; + width: 20px; + height: 20px; + font-size: 14px; + color: rgba(0, 0, 0, .25); + border-radius: 50%; + text-align: center; + line-height: 20px; + z-index: 2 +} + +.dingflow-design .auto-judge .content { + font-size: 14px; + color: #191f25; + text-align: left; + margin-top: 6px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical +} + +.dingflow-design .auto-judge .sort-left, +.dingflow-design .auto-judge .sort-right { + position: absolute; + top: 0; + bottom: 0; + display: none; + z-index: 1 +} + +.dingflow-design .auto-judge .sort-left { + left: 0; + border-right: 1px solid #f6f6f6 +} + +.dingflow-design .auto-judge .sort-right { + right: 0; + border-left: 1px solid #f6f6f6 +} + +.dingflow-design .auto-judge:hover .sort-left, +.dingflow-design .auto-judge:hover .sort-right { + display: flex; + align-items: center +} + +.dingflow-design .auto-judge .sort-left:hover, +.dingflow-design .auto-judge .sort-right:hover { + background: #efefef +} + +.dingflow-design .end-node { + border-radius: 50%; + font-size: 14px; + color: rgba(25, 31, 37, .4); + text-align: left +} + +.dingflow-design .end-node .end-node-circle { + width: 10px; + height: 10px; + margin: auto; + border-radius: 50%; + background: #dbdcdc +} + +.dingflow-design .end-node .end-node-text { + margin-top: 5px; + text-align: center +} + +.approval-setting { + border-radius: 2px; + margin: 20px 0; + position: relative; + background: #fff +} + +.ant-btn { + position: relative +} + + diff --git a/src/modules/workflow/view/workflowTemplate/model/stores/index.js b/src/modules/workflow/view/workflowTemplate/model/stores/index.js new file mode 100644 index 0000000..31bdfff --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/stores/index.js @@ -0,0 +1,53 @@ +/* + * @Date: 2022-08-25 14:13:11 + * @LastEditors: StavinLi 495727881@qq.com + * @LastEditTime: 2023-05-24 15:00:32 + * @FilePath: /Workflow-Vue3/src/store/index.js + */ +import { defineStore } from 'pinia' + +export const useStore = defineStore('store', { + state: () => ({ + isTried: false, + // 发起节点配置可见性 + rootNodeConfigVisible: false, + // 发起节点配置 + rootNodeConfig: {}, + // 办理节点配置可见性 + handleNodeConfigVisible: false, + // 办理节点配置 + handleNodeConfig: {}, + // 条件节点配置可见性 + conditionNodeConfigVisible: false, + // 条件节点配置 + conditionNodeConfig: {}, + // 工作流程定义 + processDefinitionId: '' + }), + actions: { + setIsTried(payload) { + this.isTried = payload + }, + setRootNodeConfigVisible(showFlag) { + this.rootNodeConfigVisible = showFlag + }, + setRootNodeConfig(config) { + this.rootNodeConfig = config + }, + setHandleNodeConfigVisible(showFlag) { + this.handleNodeConfigVisible = showFlag + }, + setHandleNodeConfig(config) { + this.handleNodeConfig = config + }, + setConditionNodeConfigVisible(showFlag) { + this.conditionNodeConfigVisible = showFlag + }, + setConditionNodeConfig(config) { + this.conditionNodeConfig = config + }, + setProcessDefinitionId(processDefinitionId) { + this.processDefinitionId = processDefinitionId + } + } +}) diff --git a/src/modules/workflow/view/workflowTemplate/model/utils/index.js b/src/modules/workflow/view/workflowTemplate/model/utils/index.js new file mode 100644 index 0000000..73f4e11 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/model/utils/index.js @@ -0,0 +1,193 @@ +function All() {} +All.prototype = { + timer: '', + debounce(fn, delay = 500) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + var _this = this + return function (arg) { + //获取函数的作用域和变量 + // eslint-disable-next-line @typescript-eslint/no-this-alias + let that = this + let args = arg + clearTimeout(_this.timer) // 清除定时器 + _this.timer = setTimeout(function () { + fn.call(that, args) + }, delay) + } + }, + setCookie(val) { + //cookie设置[{key:value}]、获取key、清除['key1','key2'] + for (var i = 0, len = val.length; i < len; i++) { + for (var key in val[i]) { + document.cookie = key + '=' + encodeURIComponent(val[i][key]) + '; path=/' + } + } + }, + getCookie(name) { + var strCookie = document.cookie + var arrCookie = strCookie.split('; ') + for (var i = 0, len = arrCookie.length; i < len; i++) { + var arr = arrCookie[i].split('=') + if (name == arr[0]) { + return decodeURIComponent(arr[1]) + } + } + }, + clearCookie(name) { + var myDate = new Date() + myDate.setTime(-1000) //设置时间 + for (var i = 0, len = name.length; i < len; i++) { + document.cookie = '' + name[i] + "=''; path=/; expires=" + myDate.toGMTString() + } + }, + arrToStr(arr) { + if (arr) { + return arr + .map((item) => { + return item.name + }) + .toString() + } + }, + toggleClass(arr, elem, key = 'id') { + return arr.some((item) => { + return item[key] == elem[key] + }) + }, + toChecked(arr, elem, key = 'id') { + var isIncludes = this.toggleClass(arr, elem, key) + !isIncludes ? arr.push(elem) : this.removeEle(arr, elem, key) + }, + removeEle(arr, elem, key = 'id') { + var includesIndex + arr.map((item, index) => { + if (item[key] == elem[key]) { + includesIndex = index + } + }) + arr.splice(includesIndex, 1) + }, + setApproverStr(nodeConfig) { + if (nodeConfig.settype == 1) { + if (nodeConfig.nodeUserList.length == 1) { + return nodeConfig.nodeUserList[0].name + } else if (nodeConfig.nodeUserList.length > 1) { + if (nodeConfig.examineMode == 1) { + return this.arrToStr(nodeConfig.nodeUserList) + } else if (nodeConfig.examineMode == 2) { + return nodeConfig.nodeUserList.length + '人会签' + } + } + } else if (nodeConfig.settype == 2) { + let level = + nodeConfig.directorLevel == 1 ? '直接主管' : '第' + nodeConfig.directorLevel + '级主管' + if (nodeConfig.examineMode == 1) { + return level + } else if (nodeConfig.examineMode == 2) { + return level + '会签' + } + } else if (nodeConfig.settype == 4) { + if (nodeConfig.selectRange == 1) { + return '发起人自选' + } else { + if (nodeConfig.nodeUserList.length > 0) { + if (nodeConfig.selectRange == 2) { + return '发起人自选' + } else { + return '发起人从' + nodeConfig.nodeUserList[0].name + '中自选' + } + } else { + return '' + } + } + } else if (nodeConfig.settype == 5) { + return '发起人自己' + } else if (nodeConfig.settype == 7) { + return '从直接主管到通讯录中级别最高的第' + nodeConfig.examineEndDirectorLevel + '个层级主管' + } + }, + dealStr(str, obj) { + let arr = [] + let list = str.split(',') + for (var elem in obj) { + list.map((item) => { + if (item == elem) { + arr.push(obj[elem].value) + } + }) + } + return arr.join('或') + }, + // 动态计算分支节点显示的内容 + conditionStr(nodeConfig, index) { + const branchList = nodeConfig.branchList[index].branchList + if (branchList.length == 0) { + // 无子分支 + return index == nodeConfig.branchList.length - 1 && + nodeConfig.branchList[0].branchList.length != 0 + ? '其他条件进入此流程' + : '请设置条件' + } + //TODO 分支嵌套部分暂不处理 + // else { + // let str = '' + // for (var i = 0; i < branchList.length; i++) { + // var { + // columnId, + // columnType, + // showType, + // showName, + // optType, + // zdy1, + // opt1, + // zdy2, + // opt2, + // fixedDownBoxValue + // } = branchList[i] + // if (columnId == 0) { + // if (nodeUserList.length != 0) { + // str += '发起人属于:' + // str += + // nodeUserList + // .map((item) => { + // return item.name + // }) + // .join('或') + ' 并且 ' + // } + // } + // if (columnType == 'String' && showType == '3') { + // if (zdy1) { + // str += + // showName + '属于:' + this.dealStr(zdy1, JSON.parse(fixedDownBoxValue)) + ' 并且 ' + // } + // } + // if (columnType == 'Double') { + // if (optType != 6 && zdy1) { + // var optTypeStr = ['', '<', '>', '≤', '=', '≥'][optType] + // str += `${showName} ${optTypeStr} ${zdy1} 并且 ` + // } else if (optType == 6 && zdy1 && zdy2) { + // str += `${zdy1} ${opt1} ${showName} ${opt2} ${zdy2} 并且 ` + // } + // } + // } + // return str ? str.substring(0, str.length - 4) : '请设置条件' + // } + }, + copyerStr(nodeConfig) { + if (nodeConfig.nodeUserList.length != 0) { + return this.arrToStr(nodeConfig.nodeUserList) + } else { + if (nodeConfig.ccSelfSelectFlag == 1) { + return '发起人自选' + } + } + }, + toggleStrClass(item, key) { + let a = item.zdy1 ? item.zdy1.split(',') : [] + return a.some((item) => { + return item == key + }) + } +} + +export default new All() diff --git a/src/modules/workflow/view/workflowTemplate/modify.vue b/src/modules/workflow/view/workflowTemplate/modify.vue new file mode 100644 index 0000000..9706d13 --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/modify.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/modules/workflow/view/workflowTemplate/view.vue b/src/modules/workflow/view/workflowTemplate/view.vue new file mode 100644 index 0000000..79111ce --- /dev/null +++ b/src/modules/workflow/view/workflowTemplate/view.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/permission.ts b/src/permission.ts new file mode 100644 index 0000000..8f58cbd --- /dev/null +++ b/src/permission.ts @@ -0,0 +1,62 @@ +import router from './router' +import { useAppStoreWithOut } from '@/store/modules/app' +import { useCache } from '@/hooks/web/useCache' +import type { RouteRecordRaw } from 'vue-router' +import { useTitle } from '@/hooks/web/useTitle' +import { useNProgress } from '@/hooks/web/useNProgress' +import { usePermissionStoreWithOut } from '@/store/modules/permission' + +import { usePageLoading } from '@/hooks/web/usePageLoading' + +const permissionStore = usePermissionStoreWithOut() + +const appStore = useAppStoreWithOut() + +const { wsCache } = useCache() + +const { start, done } = useNProgress() + +const { loadStart, loadDone } = usePageLoading() + +const whiteList = ['/login'] // 不重定向白名单 + +router.beforeEach(async (to, from, next) => { + start() + loadStart() + const userInfo = wsCache.get(appStore.getUserInfo) + + if (userInfo) { + if (to.path === '/login') { + next({ path: '/' }) + } else { + if (permissionStore.getIsAddRouters) { + next() + return + } + + // 使用动态路由 + await permissionStore.generateRoutes(userInfo.menuPermission as AppCustomRouteRecordRaw[]) + + permissionStore.getAddRouters.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 + }) + const redirectPath = from.query.redirect || to.path + const redirect = decodeURIComponent(redirectPath as string) + const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } + permissionStore.setIsAddRouters(true) + next(nextData) + } + } else { + if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 + } + } +}) + +router.afterEach((to) => { + useTitle(to?.meta?.title as string) + done() // 结束Progress + loadDone() +}) diff --git a/src/plugins/animate.css/index.ts b/src/plugins/animate.css/index.ts new file mode 100644 index 0000000..3e93451 --- /dev/null +++ b/src/plugins/animate.css/index.ts @@ -0,0 +1 @@ +import 'animate.css' diff --git a/src/plugins/echarts/index.ts b/src/plugins/echarts/index.ts new file mode 100644 index 0000000..34f756f --- /dev/null +++ b/src/plugins/echarts/index.ts @@ -0,0 +1,41 @@ +import * as echarts from 'echarts/core' + +import { + BarChart, + LineChart, + PieChart, + MapChart, + PictorialBarChart, + RadarChart +} from 'echarts/charts' + +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent +} from 'echarts/components' + +import { CanvasRenderer } from 'echarts/renderers' + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + CanvasRenderer, + PictorialBarChart, + RadarChart +]) + +export default echarts diff --git a/src/plugins/elementPlus/index.ts b/src/plugins/elementPlus/index.ts new file mode 100644 index 0000000..a5362a1 --- /dev/null +++ b/src/plugins/elementPlus/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' + +// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题 +import { ElLoading, ElScrollbar } from 'element-plus' + +const plugins = [ElLoading] + +const components = [ElScrollbar] + +export const setupElementPlus = (app: App) => { + plugins.forEach((plugin) => { + app.use(plugin) + }) + + components.forEach((component) => { + app.component(component.name, component) + }) +} diff --git a/src/plugins/svgIcon/index.ts b/src/plugins/svgIcon/index.ts new file mode 100644 index 0000000..b5b7f70 --- /dev/null +++ b/src/plugins/svgIcon/index.ts @@ -0,0 +1,3 @@ +import 'virtual:svg-icons-register' + +import '@purge-icons/generated' diff --git a/src/plugins/vueI18n/helper.ts b/src/plugins/vueI18n/helper.ts new file mode 100644 index 0000000..da6bc8c --- /dev/null +++ b/src/plugins/vueI18n/helper.ts @@ -0,0 +1,3 @@ +export const setHtmlPageLang = (locale: LocaleType) => { + document.querySelector('html')?.setAttribute('lang', locale) +} diff --git a/src/plugins/vueI18n/index.ts b/src/plugins/vueI18n/index.ts new file mode 100644 index 0000000..f845b13 --- /dev/null +++ b/src/plugins/vueI18n/index.ts @@ -0,0 +1,42 @@ +import type { App } from 'vue' +import { createI18n } from 'vue-i18n' +import { useLocaleStoreWithOut } from '@/store/modules/locale' +import type { I18n, I18nOptions } from 'vue-i18n' +import { setHtmlPageLang } from './helper' + +export let i18n: ReturnType + +const createI18nOptions = async (): Promise => { + const localeStore = useLocaleStoreWithOut() + const locale = localeStore.getCurrentLocale + const localeMap = localeStore.getLocaleMap + const defaultLocal = await import(`../../locales/${locale.lang}.ts`) + const message = defaultLocal.default ?? {} + + setHtmlPageLang(locale.lang) + + localeStore.setCurrentLocale({ + lang: locale.lang + // elLocale: elLocal + }) + + return { + legacy: false, + locale: locale.lang, + fallbackLocale: locale.lang, + messages: { + [locale.lang]: message + }, + availableLocales: localeMap.map((v) => v.lang), + sync: true, + silentTranslationWarn: true, + missingWarn: false, + silentFallbackWarn: true + } +} + +export const setupI18n = async (app: App) => { + const options = await createI18nOptions() + i18n = createI18n(options) as I18n + app.use(i18n) +} diff --git a/src/plugins/windi.css/index.ts b/src/plugins/windi.css/index.ts new file mode 100644 index 0000000..dbdfbbf --- /dev/null +++ b/src/plugins/windi.css/index.ts @@ -0,0 +1,3 @@ +import 'virtual:windi.css' + +import 'virtual:windi-devtools' diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..33f0135 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,586 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import type { RouteRecordRaw } from 'vue-router' +import type { App } from 'vue' +import { Layout, getParentLayout } from '@/utils/routerHelper' +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +export const constantRouterMap: AppRouteRecordRaw[] = [ + { + path: '/', + component: Layout, + redirect: '/dashboard/analysis', + name: 'Root', + meta: { + hidden: true + } + }, + { + path: '/redirect', + component: Layout, + name: 'Redirect', + children: [ + { + path: '/redirect/:path(.*)', + name: 'Redirect', + component: () => import('@/views/Redirect/Redirect.vue'), + meta: {} + } + ], + meta: { + hidden: true, + noTagsView: true + } + }, + { + path: '/login', + component: () => import('@/views/Login/Login.vue'), + name: 'Login', + meta: { + hidden: true, + title: t('router.login'), + noTagsView: true + } + }, + { + path: '/404', + component: () => import('@/views/Error/404.vue'), + name: 'NoFind', + meta: { + hidden: true, + title: '404', + noTagsView: true + } + } +] + +export const asyncRouterMap: AppRouteRecordRaw[] = [ + { + path: '/dashboard', + component: Layout, + redirect: '/dashboard/analysis', + name: 'Dashboard', + meta: { + title: t('router.dashboard'), + icon: 'ant-design:dashboard-filled', + alwaysShow: true + }, + children: [ + { + path: 'analysis', + component: () => import('@/views/Dashboard/Analysis.vue'), + name: 'Analysis', + meta: { + title: t('router.analysis'), + noCache: true, + affix: true, + alwaysShow: false + } + }, + { + path: 'workplace', + component: () => import('@/views/Dashboard/Workplace.vue'), + name: 'Workplace', + meta: { + title: t('router.workplace'), + noCache: true + } + } + ] + }, + { + path: '/external-link', + component: Layout, + meta: {}, + name: 'ExternalLink', + children: [ + { + path: 'https://element-plus-admin-doc.cn/', + name: 'DocumentLink', + meta: { + title: t('router.document'), + icon: 'clarity:document-solid' + } + } + ] + }, + { + path: '/guide', + component: Layout, + name: 'Guide', + meta: {}, + children: [ + { + path: 'index', + component: () => import('@/views/Guide/Guide.vue'), + name: 'GuideDemo', + meta: { + title: t('router.guide'), + icon: 'cib:telegram-plane' + } + } + ] + }, + { + path: '/components', + component: Layout, + name: 'ComponentsDemo', + meta: { + title: t('router.component'), + icon: 'bx:bxs-component', + alwaysShow: true + }, + children: [ + { + path: 'form', + component: getParentLayout(), + redirect: '/components/form/default-form', + name: 'Form', + meta: { + title: t('router.form'), + alwaysShow: true + }, + children: [ + { + path: 'default-form', + component: () => import('@/views/Components/Form/DefaultForm.vue'), + name: 'DefaultForm', + meta: { + title: t('router.defaultForm') + } + }, + { + path: 'use-form', + component: () => import('@/views/Components/Form/UseFormDemo.vue'), + name: 'UseForm', + meta: { + title: 'UseForm' + } + }, + { + path: 'ref-form', + component: () => import('@/views/Components/Form/RefForm.vue'), + name: 'RefForm', + meta: { + title: 'RefForm' + } + } + ] + }, + { + path: 'table', + component: getParentLayout(), + redirect: '/components/table/default-table', + name: 'TableDemo', + meta: { + title: t('router.table'), + alwaysShow: true + }, + children: [ + { + path: 'default-table', + component: () => import('@/views/Components/Table/DefaultTable.vue'), + name: 'DefaultTable', + meta: { + title: t('router.defaultTable') + } + }, + { + path: 'use-table', + component: () => import('@/views/Components/Table/UseTableDemo.vue'), + name: 'UseTable', + meta: { + title: 'UseTable' + } + }, + { + path: 'ref-table', + component: () => import('@/views/Components/Table/RefTable.vue'), + name: 'RefTable', + meta: { + title: 'RefTable' + } + } + ] + }, + { + path: 'editor-demo', + component: getParentLayout(), + redirect: '/components/editor-demo/editor', + name: 'EditorDemo', + meta: { + title: t('router.editor'), + alwaysShow: true + }, + children: [ + { + path: 'editor', + component: () => import('@/views/Components/Editor/Editor.vue'), + name: 'Editor', + meta: { + title: t('router.richText') + } + } + ] + }, + { + path: 'search', + component: () => import('@/views/Components/Search.vue'), + name: 'Search', + meta: { + title: t('router.search') + } + }, + { + path: 'descriptions', + component: () => import('@/views/Components/Descriptions.vue'), + name: 'Descriptions', + meta: { + title: t('router.descriptions') + } + }, + { + path: 'image-viewer', + component: () => import('@/views/Components/ImageViewer.vue'), + name: 'ImageViewer', + meta: { + title: t('router.imageViewer') + } + }, + { + path: 'dialog', + component: () => import('@/views/Components/Dialog.vue'), + name: 'Dialog', + meta: { + title: t('router.dialog') + } + }, + { + path: 'icon', + component: () => import('@/views/Components/Icon.vue'), + name: 'Icon', + meta: { + title: t('router.icon') + } + }, + { + path: 'echart', + component: () => import('@/views/Components/Echart.vue'), + name: 'Echart', + meta: { + title: t('router.echart') + } + }, + { + path: 'count-to', + component: () => import('@/views/Components/CountTo.vue'), + name: 'CountTo', + meta: { + title: t('router.countTo') + } + }, + { + path: 'qrcode', + component: () => import('@/views/Components/Qrcode.vue'), + name: 'Qrcode', + meta: { + title: t('router.qrcode') + } + }, + { + path: 'highlight', + component: () => import('@/views/Components/Highlight.vue'), + name: 'Highlight', + meta: { + title: t('router.highlight') + } + }, + { + path: 'infotip', + component: () => import('@/views/Components/Infotip.vue'), + name: 'Infotip', + meta: { + title: t('router.infotip') + } + }, + { + path: 'input-password', + component: () => import('@/views/Components/InputPassword.vue'), + name: 'InputPassword', + meta: { + title: t('router.inputPassword') + } + }, + { + path: 'sticky', + component: () => import('@/views/Components/Sticky.vue'), + name: 'Sticky', + meta: { + title: t('router.sticky') + } + }, + { + path: 'CommonSelect', + component: () => import('@/views/Components/CommonSelect.vue'), + name: 'CommonSelect', + meta: { + title: '通用选择' + } + }, + { + path: 'MyEchart', + component: () => import('@/views/Components/MyEchart.vue'), + name: 'MyEchart', + meta: { + title: '图表测试' + } + } + ] + }, + { + path: '/hooks', + component: Layout, + redirect: '/hooks/useWatermark', + name: 'Hooks', + meta: { + title: 'hooks', + icon: 'ic:outline-webhook', + alwaysShow: true + }, + children: [ + { + path: 'useWatermark', + component: () => import('@/views/hooks/useWatermark.vue'), + name: 'UseWatermark', + meta: { + title: 'useWatermark' + } + }, + { + path: 'useCrudSchemas', + component: () => import('@/views/hooks/useCrudSchemas.vue'), + name: 'UseCrudSchemas', + meta: { + title: 'useCrudSchemas' + } + } + ] + }, + { + path: '/level', + component: Layout, + redirect: '/level/menu1/menu1-1/menu1-1-1', + name: 'Level', + meta: { + title: t('router.level'), + icon: 'carbon:skill-level-advanced' + }, + children: [ + { + path: 'menu1', + name: 'Menu1', + component: getParentLayout(), + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: t('router.menu1') + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11', + component: getParentLayout(), + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: t('router.menu11'), + alwaysShow: true + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111', + component: () => import('@/views/Level/Menu111.vue'), + meta: { + title: t('router.menu111') + } + } + ] + }, + { + path: 'menu1-2', + name: 'Menu12', + component: () => import('@/views/Level/Menu12.vue'), + meta: { + title: t('router.menu12') + } + } + ] + }, + { + path: 'menu2', + name: 'Menu2', + component: () => import('@/views/Level/Menu2.vue'), + meta: { + title: t('router.menu2') + } + } + ] + }, + { + path: '/example', + component: Layout, + redirect: '/example/example-dialog', + name: 'Example', + meta: { + title: t('router.example'), + icon: 'ep:management', + alwaysShow: true + }, + children: [ + { + path: 'example-dialog', + component: () => import('@/views/Example/Dialog/ExampleDialog.vue'), + name: 'ExampleDialog', + meta: { + title: t('router.exampleDialog') + } + }, + { + path: 'example-page', + component: () => import('@/views/Example/Page/ExamplePage.vue'), + name: 'ExamplePage', + meta: { + title: t('router.examplePage') + } + }, + { + path: 'example-add', + component: () => import('@/views/Example/Page/ExampleAdd.vue'), + name: 'ExampleAdd', + meta: { + title: t('router.exampleAdd'), + noTagsView: true, + noCache: true, + hidden: true, + canTo: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-edit', + component: () => import('@/views/Example/Page/ExampleEdit.vue'), + name: 'ExampleEdit', + meta: { + title: t('router.exampleEdit'), + noTagsView: true, + noCache: true, + hidden: true, + canTo: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-detail', + component: () => import('@/views/Example/Page/ExampleDetail.vue'), + name: 'ExampleDetail', + meta: { + title: t('router.exampleDetail'), + noTagsView: true, + noCache: true, + hidden: true, + canTo: true, + activeMenu: '/example/example-page' + } + } + ] + }, + { + path: '/error', + component: Layout, + redirect: '/error/404', + name: 'Error', + meta: { + title: t('router.errorPage'), + icon: 'ci:error', + alwaysShow: true + }, + children: [ + { + path: '404-demo', + component: () => import('@/views/Error/404.vue'), + name: '404Demo', + meta: { + title: '404' + } + }, + { + path: '403-demo', + component: () => import('@/views/Error/403.vue'), + name: '403Demo', + meta: { + title: '403' + } + }, + { + path: '500-demo', + component: () => import('@/views/Error/500.vue'), + name: '500Demo', + meta: { + title: '500' + } + } + ] + } + // { + // path: '/authorization', + // component: Layout, + // redirect: '/authorization/user', + // name: 'Authorization', + // meta: { + // title: t('router.authorization'), + // icon: 'eos-icons:role-binding', + // alwaysShow: true + // }, + // children: [ + // { + // path: 'user', + // component: () => import('@/views/Authorization/User.vue'), + // name: 'User', + // meta: { + // title: t('router.user') + // } + // }, + // { + // path: 'role', + // component: () => import('@/views/Authorization/Role.vue'), + // name: 'Role', + // meta: { + // title: t('router.role') + // } + // } + // ] + // } +] + +const router = createRouter({ + history: createWebHashHistory(), + strict: true, + routes: constantRouterMap as RouteRecordRaw[], + scrollBehavior: () => ({ left: 0, top: 0 }) +}) + +export const resetRouter = (): void => { + const resetWhiteNameList = ['Redirect', 'Login', 'NoFind', 'Root'] + router.getRoutes().forEach((route) => { + const { name } = route + if (name && !resetWhiteNameList.includes(name as string)) { + router.hasRoute(name) && router.removeRoute(name) + } + }) +} + +export const setupRouter = (app: App) => { + app.use(router) +} + +export default router diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..65964ea --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,10 @@ +import type { App } from 'vue' +import { createPinia } from 'pinia' + +const store = createPinia() + +export const setupStore = (app: App) => { + app.use(store) +} + +export { store } diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..8bb487f --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,267 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import { setCssVar, humpToUnderline } from '@/utils' +import { ElMessage } from 'element-plus' +import { ElementPlusSize } from '@/types/elementPlus' +import { useCache } from '@/hooks/web/useCache' +import { LayoutType } from '@/types/layout' +import { ThemeTypes } from '@/types/theme' +import { USER_KEY } from '@/constant/common' + +const { wsCache } = useCache() + +interface AppState { + breadcrumb: boolean + breadcrumbIcon: boolean + collapse: boolean + uniqueOpened: boolean + hamburger: boolean + screenfull: boolean + size: boolean + locale: boolean + tagsView: boolean + tagsViewIcon: boolean + logo: boolean + fixedHeader: boolean + greyMode: boolean + pageLoading: boolean + layout: LayoutType + title: string + userInfo: string + isDark: boolean + currentSize: ElementPlusSize + sizeMap: ElementPlusSize[] + mobile: boolean + footer: boolean + theme: ThemeTypes + fixedMenu: boolean +} + +export const useAppStore = defineStore('app', { + state: (): AppState => { + return { + userInfo: USER_KEY, // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突 + sizeMap: ['default', 'large', 'small'], + mobile: false, // 是否是移动端 + title: import.meta.env.VITE_APP_TITLE, // 标题 + pageLoading: false, // 路由跳转loading + + breadcrumb: true, // 面包屑 + breadcrumbIcon: true, // 面包屑图标 + collapse: false, // 折叠菜单 + uniqueOpened: false, // 是否只保持一个子菜单的展开 + hamburger: true, // 折叠图标 + screenfull: true, // 全屏图标 + size: true, // 尺寸图标 + locale: false, // 多语言图标 + tagsView: true, // 标签页 + tagsViewIcon: true, // 是否显示标签图标 + logo: false, // logo + fixedHeader: true, // 固定toolheader + footer: true, // 显示页脚 + greyMode: false, // 是否开始灰色模式,用于特殊悼念日 + fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单 + + layout: wsCache.get('layout') || 'classic', // layout布局 + isDark: wsCache.get('isDark') || false, // 是否是暗黑模式 + currentSize: wsCache.get('default') || 'default', // 组件尺寸 + theme: wsCache.get('theme') || { + // 主题色 + elColorPrimary: '#409eff', + // 左侧菜单边框颜色 + leftMenuBorderColor: 'inherit', + // 左侧菜单背景颜色 + leftMenuBgColor: '#001529', + // 左侧菜单浅色背景颜色 + leftMenuBgLightColor: '#0f2438', + // 左侧菜单选中背景颜色 + leftMenuBgActiveColor: 'var(--el-color-primary)', + // 左侧菜单收起选中背景颜色 + leftMenuCollapseBgActiveColor: 'var(--el-color-primary)', + // 左侧菜单字体颜色 + leftMenuTextColor: '#bfcbd9', + // 左侧菜单选中字体颜色 + leftMenuTextActiveColor: '#fff', + // logo字体颜色 + logoTitleTextColor: '#fff', + // logo边框颜色 + logoBorderColor: 'inherit', + // 头部背景颜色 + topHeaderBgColor: '#fff', + // 头部字体颜色 + topHeaderTextColor: 'inherit', + // 头部悬停颜色 + topHeaderHoverColor: '#f6f6f6', + // 头部边框颜色 + topToolBorderColor: '#eee' + } + } + }, + getters: { + getBreadcrumb(): boolean { + return this.breadcrumb + }, + getBreadcrumbIcon(): boolean { + return this.breadcrumbIcon + }, + getCollapse(): boolean { + return this.collapse + }, + getUniqueOpened(): boolean { + return this.uniqueOpened + }, + getHamburger(): boolean { + return this.hamburger + }, + getScreenfull(): boolean { + return this.screenfull + }, + getSize(): boolean { + return this.size + }, + getLocale(): boolean { + return this.locale + }, + getTagsView(): boolean { + return this.tagsView + }, + getTagsViewIcon(): boolean { + return this.tagsViewIcon + }, + getLogo(): boolean { + return this.logo + }, + getFixedHeader(): boolean { + return this.fixedHeader + }, + getGreyMode(): boolean { + return this.greyMode + }, + getFixedMenu(): boolean { + return this.fixedMenu + }, + getPageLoading(): boolean { + return this.pageLoading + }, + getLayout(): LayoutType { + return this.layout + }, + getTitle(): string { + return this.title + }, + getUserInfo(): string { + return this.userInfo + }, + getIsDark(): boolean { + return this.isDark + }, + getCurrentSize(): ElementPlusSize { + return this.currentSize + }, + getSizeMap(): ElementPlusSize[] { + return this.sizeMap + }, + getMobile(): boolean { + return this.mobile + }, + getTheme(): ThemeTypes { + return this.theme + }, + getFooter(): boolean { + return this.footer + } + }, + actions: { + setBreadcrumb(breadcrumb: boolean) { + this.breadcrumb = breadcrumb + }, + setBreadcrumbIcon(breadcrumbIcon: boolean) { + this.breadcrumbIcon = breadcrumbIcon + }, + setCollapse(collapse: boolean) { + this.collapse = collapse + }, + setUniqueOpened(uniqueOpened: boolean) { + this.uniqueOpened = uniqueOpened + }, + setHamburger(hamburger: boolean) { + this.hamburger = hamburger + }, + setScreenfull(screenfull: boolean) { + this.screenfull = screenfull + }, + setSize(size: boolean) { + this.size = size + }, + setLocale(locale: boolean) { + this.locale = locale + }, + setTagsView(tagsView: boolean) { + this.tagsView = tagsView + }, + setTagsViewIcon(tagsViewIcon: boolean) { + this.tagsViewIcon = tagsViewIcon + }, + setLogo(logo: boolean) { + this.logo = logo + }, + setFixedHeader(fixedHeader: boolean) { + this.fixedHeader = fixedHeader + }, + setGreyMode(greyMode: boolean) { + this.greyMode = greyMode + }, + setFixedMenu(fixedMenu: boolean) { + wsCache.set('fixedMenu', fixedMenu) + this.fixedMenu = fixedMenu + }, + setPageLoading(pageLoading: boolean) { + this.pageLoading = pageLoading + }, + setLayout(layout: LayoutType) { + if (this.mobile && layout !== 'classic') { + ElMessage.warning('移动端模式下不支持切换其他布局') + return + } + this.layout = layout + wsCache.set('layout', this.layout) + }, + setTitle(title: string) { + this.title = title + }, + setIsDark(isDark: boolean) { + this.isDark = isDark + if (this.isDark) { + document.documentElement.classList.add('dark') + document.documentElement.classList.remove('light') + } else { + document.documentElement.classList.add('light') + document.documentElement.classList.remove('dark') + } + wsCache.set('isDark', this.isDark) + }, + setCurrentSize(currentSize: ElementPlusSize) { + this.currentSize = currentSize + wsCache.set('currentSize', this.currentSize) + }, + setMobile(mobile: boolean) { + this.mobile = mobile + }, + setTheme(theme: ThemeTypes) { + this.theme = Object.assign(this.theme, theme) + wsCache.set('theme', this.theme) + }, + setCssVarTheme() { + for (const key in this.theme) { + setCssVar(`--${humpToUnderline(key)}`, this.theme[key]) + } + }, + setFooter(footer: boolean) { + this.footer = footer + } + } +}) + +export const useAppStoreWithOut = () => { + return useAppStore(store) +} diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts new file mode 100644 index 0000000..93dd46e --- /dev/null +++ b/src/store/modules/dict.ts @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia' +import { store } from '../index' + +export interface DictState { + isSetDict: boolean + dictObj: Recordable +} + +export const useDictStore = defineStore('dict', { + state: (): DictState => ({ + isSetDict: false, + dictObj: {} + }), + getters: { + getDictObj(): Recordable { + return this.dictObj + }, + getIsSetDict(): boolean { + return this.isSetDict + } + }, + actions: { + setDictObj(dictObj: Recordable) { + this.dictObj = dictObj + }, + setIsSetDict(isSetDict: boolean) { + this.isSetDict = isSetDict + } + } +}) + +export const useDictStoreWithOut = () => { + return useDictStore(store) +} diff --git a/src/store/modules/locale.ts b/src/store/modules/locale.ts new file mode 100644 index 0000000..1f7912f --- /dev/null +++ b/src/store/modules/locale.ts @@ -0,0 +1,59 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import en from 'element-plus/es/locale/lang/en' +import { useCache } from '@/hooks/web/useCache' +import { LocaleDropdownType } from '@/types/localeDropdown' + +const { wsCache } = useCache() + +const elLocaleMap = { + 'zh-CN': zhCn, + en: en +} +interface LocaleState { + currentLocale: LocaleDropdownType + localeMap: LocaleDropdownType[] +} + +export const useLocaleStore = defineStore('locales', { + state: (): LocaleState => { + return { + currentLocale: { + lang: wsCache.get('lang') || 'zh-CN', + elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN'] + }, + // 多语言 + localeMap: [ + { + lang: 'zh-CN', + name: '简体中文' + }, + { + lang: 'en', + name: 'English' + } + ] + } + }, + getters: { + getCurrentLocale(): LocaleDropdownType { + return this.currentLocale + }, + getLocaleMap(): LocaleDropdownType[] { + return this.localeMap + } + }, + actions: { + setCurrentLocale(localeMap: LocaleDropdownType) { + // this.locale = Object.assign(this.locale, localeMap) + this.currentLocale.lang = localeMap?.lang + this.currentLocale.elLocale = elLocaleMap[localeMap?.lang] + wsCache.set('lang', localeMap?.lang) + } + } +}) + +export const useLocaleStoreWithOut = () => { + return useLocaleStore(store) +} diff --git a/src/store/modules/notification.ts b/src/store/modules/notification.ts new file mode 100644 index 0000000..998d514 --- /dev/null +++ b/src/store/modules/notification.ts @@ -0,0 +1,35 @@ +import { defineStore } from 'pinia' +import { store } from '../index' + +interface NotificationState { + // 未读消息数量 + unreadMessageCount: number +} + +export const useNotificationStore = defineStore('notification', { + state: (): NotificationState => { + return { + unreadMessageCount: 0 + } + }, + getters: { + getUnreadMessageCount(): number { + return this.unreadMessageCount + } + }, + actions: { + setUnreadMessageCount(unreadMessageCount: number) { + this.unreadMessageCount = unreadMessageCount + }, + increaseUnreadMessageCount() { + this.unreadMessageCount = this.unreadMessageCount + 1 + }, + decreaseUnreadMessageCount() { + this.unreadMessageCount = this.unreadMessageCount - 1 + } + } +}) + +export const useNotificationStoreWithOut = () => { + return useNotificationStore(store) +} diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..c4f1ba1 --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,96 @@ +import { defineStore } from 'pinia' +import { asyncRouterMap } from '@/router' +import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper' +import { store } from '../index' +import { cloneDeep } from 'lodash-es' + +import { Layout, getParentLayout } from '@/utils/routerHelper' + +export interface PermissionState { + routers: AppRouteRecordRaw[] + addRouters: AppRouteRecordRaw[] + isAddRouters: boolean + menuTabRouters: AppRouteRecordRaw[] +} + +export const usePermissionStore = defineStore('permission', { + state: (): PermissionState => ({ + routers: [], + addRouters: [], + isAddRouters: false, + menuTabRouters: [] + }), + getters: { + getRouters(): AppRouteRecordRaw[] { + return this.routers + }, + getAddRouters(): AppRouteRecordRaw[] { + return flatMultiLevelRoutes(cloneDeep(this.addRouters)) + }, + getIsAddRouters(): boolean { + return this.isAddRouters + }, + getMenuTabRouters(): AppRouteRecordRaw[] { + return this.menuTabRouters + } + }, + actions: { + generateRoutes(routers?: AppCustomRouteRecordRaw[] | string[]): Promise { + return new Promise((resolve) => { + let routerMap: AppRouteRecordRaw[] = [] + const tempRouter: AppRouteRecordRaw[] = [ + { + path: '/desktop', + component: Layout, + name: 'desktop', + meta: {}, + children: [ + { + path: 'index', + component: () => import('@/modules/support/view/desktopTemplate/desktop.vue'), + name: 'config', + meta: { + title: '桌面', + icon: 'HomeFilled', + affix: true + } + } + ] + } + ] + // 后端过滤菜单 + routerMap = tempRouter + .concat(generateRoutesFn2(routers as AppCustomRouteRecordRaw[])) + .concat(routerMap) + // 输出路由,用于调试 + // console.log(routerMap) + + // 动态路由,404一定要放到最后面 + this.addRouters = routerMap.concat([ + { + path: '/:path(.*)*', + redirect: '/404', + name: '404Page', + meta: { + hidden: true, + breadcrumb: false + } + } + ]) + // 渲染菜单的所有路由 + this.routers = routerMap + resolve() + }) + }, + setIsAddRouters(state: boolean): void { + this.isAddRouters = state + }, + setMenuTabRouters(routers: AppRouteRecordRaw[]): void { + this.menuTabRouters = routers + } + } +}) + +export const usePermissionStoreWithOut = () => { + return usePermissionStore(store) +} diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..9369e33 --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,149 @@ +import router from '@/router' +import type { RouteLocationNormalizedLoaded } from 'vue-router' +import { getRawRoute } from '@/utils/routerHelper' +import { defineStore } from 'pinia' +import { store } from '../index' +import { findIndex } from '@/utils' + +export interface TagsViewState { + visitedViews: RouteLocationNormalizedLoaded[] + cachedViews: Set +} + +export const useTagsViewStore = defineStore('tagsView', { + state: (): TagsViewState => ({ + visitedViews: [], + cachedViews: new Set() + }), + getters: { + getVisitedViews(): RouteLocationNormalizedLoaded[] { + return this.visitedViews + }, + getCachedViews(): string[] { + return Array.from(this.cachedViews) + } + }, + actions: { + // 新增缓存和tag + addView(view: RouteLocationNormalizedLoaded): void { + this.addVisitedView(view) + this.addCachedView() + }, + // 新增tag + addVisitedView(view: RouteLocationNormalizedLoaded) { + if (this.visitedViews.some((v) => v.path === view.path)) return + if (view.meta?.noTagsView) return + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ) + }, + // 新增缓存 + addCachedView() { + const cacheMap: Set = new Set() + for (const v of this.visitedViews) { + const item = getRawRoute(v) + const needCache = !item.meta?.noCache + if (!needCache) { + continue + } + const name = item.name as string + cacheMap.add(name) + } + if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString()) + return + this.cachedViews = cacheMap + }, + // 删除某个 + delView(view: RouteLocationNormalizedLoaded) { + this.delVisitedView(view) + this.addCachedView() + }, + // 删除tag + delVisitedView(view: RouteLocationNormalizedLoaded) { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1) + break + } + } + }, + // 删除缓存 + delCachedView() { + const route = router.currentRoute.value + const index = findIndex(this.getCachedViews, (v) => v === route.name) + if (index > -1) { + this.cachedViews.delete(this.getCachedViews[index]) + } + }, + // 删除所有缓存和tag + delAllViews() { + this.delAllVisitedViews() + this.addCachedView() + }, + // 删除所有tag + delAllVisitedViews() { + this.visitedViews = [] + }, + // 删除所有缓存和tag,固定除外 + delAllViewsExcludeFixed() { + this.delAllVisitedViews() + this.addCachedView() + }, + // 删除所有tag,固定除外 + delAllVisitedViewsExcludeFixed() { + const affixTags = this.visitedViews.filter((tag) => tag.meta.affix) + this.visitedViews = affixTags + }, + // 删除其他 + delOthersViews(view: RouteLocationNormalizedLoaded) { + this.delOthersVisitedViews(view) + this.addCachedView() + }, + // 删除其他tag + delOthersVisitedViews(view: RouteLocationNormalizedLoaded) { + this.visitedViews = this.visitedViews.filter((v) => { + return v?.meta?.affix || v.path === view.path + }) + }, + // 删除左侧 + delLeftViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.path === view.path + ) + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.path === view.path || i > index + }) + this.addCachedView() + } + }, + // 删除右侧 + delRightViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.path === view.path + ) + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.path === view.path || i < index + }) + this.addCachedView() + } + }, + updateVisitedView(view: RouteLocationNormalizedLoaded) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + } + } +}) + +export const useTagsViewStoreWithOut = () => { + return useTagsViewStore(store) +} diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..1a46d13 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,65 @@ +import { store } from '../index' +import { defineStore } from 'pinia' +import { useCache } from '@/hooks/web/useCache' +import { USER_KEY } from '@/constant/common' +const { wsCache } = useCache() +import { setToken } from '@/utils/auth' + +interface UserState { + account: string + name: string + forceChangePasswordFlag: string + id: string + token: string + buttonPermission: string[] + menuPermission: string[] +} + +export const useUserStore = defineStore('user', { + state: (): UserState => ({ + account: '', + name: '', + forceChangePasswordFlag: '', + id: '', + token: '', + buttonPermission: [], + menuPermission: [] + }), + getters: { + getAccount(): string { + return this.account + } + }, + actions: { + async setUserAction(user) { + this.account = user.account + this.name = user.name + this.forceChangePasswordFlag = user.forceChangePasswordFlag + this.id = user.id + this.token = user.token + this.buttonPermission = user.buttonPermission + this.menuPermission = user.menuPermission + // 保存用户信息 + wsCache.set(USER_KEY, user) + // 保存令牌 + setToken(user.token) + }, + async clear() { + wsCache.clear() + this.resetState() + }, + resetState() { + this.account = '' + this.name = '' + this.forceChangePasswordFlag = '' + this.id = '' + this.token = '' + this.buttonPermission = [] + this.menuPermission = [] + } + } +}) + +export const useUserStoreWithOut = () => { + return useUserStore(store) +} diff --git a/src/styles/index.less b/src/styles/index.less new file mode 100644 index 0000000..cebb2c7 --- /dev/null +++ b/src/styles/index.less @@ -0,0 +1,7 @@ +@import './var.css'; +@import 'element-plus/theme-chalk/dark/css-vars.css'; + +// 解决抽屉弹出时,body宽度变化的问题 +.el-popup-parent--hidden { + width: 100% !important; +} \ No newline at end of file diff --git a/src/styles/theme.less b/src/styles/theme.less new file mode 100644 index 0000000..39b03b3 --- /dev/null +++ b/src/styles/theme.less @@ -0,0 +1,6 @@ +// .text-color { +// color: var(--el-text-color-regular); +// } +// .dark .dark\:text-color { +// color: rgba(255, 255, 255, var(--dark-text-color)); +// } diff --git a/src/styles/var.css b/src/styles/var.css new file mode 100644 index 0000000..0d7d0df --- /dev/null +++ b/src/styles/var.css @@ -0,0 +1,67 @@ +:root { + --dark-bg-color: #293146; + + /* left menu start */ + --left-menu-border-color: '#eee'; + + --left-menu-max-width: 200px; + + --left-menu-min-width: 64px; + + --left-menu-bg-color: #001529; + + --left-menu-bg-light-color: #0f2438; + + --left-menu-bg-active-color: var(--el-color-primary); + + --left-menu-text-color: #bfcbd9; + + --left-menu-text-active-color: #fff; + + --left-menu-collapse-bg-active-color: var(--el-color-primary); + /* left menu end */ + + /* logo start */ + --logo-height: 50px; + + --logo-title-text-color: #fff; + + --logo-border-color: 'inherit'; + /* logo end */ + + /* header start */ + --top-header-bg-color: '#fff'; + + --top-header-text-color: 'inherit'; + + --top-header-hover-color: #f6f6f6; + + --top-tool-height: var(--logo-height); + + --top-tool-p-x: 0; + + --top-tool-border-color: #eee; + + --tags-view-height: 35px; + + --tags-view-border-color: #eee; + /* header start */ + + /* tab menu start */ + --tab-menu-max-width: 80px; + + --tab-menu-min-width: 30px; + + --tab-menu-collapse-height: 36px; + + --tab-menu-border-color: #eee; + /* tab menu end */ + + --app-content-padding: 20px; + + --app-content-bg-color: #f5f7f9; + + --app-footer-height: 50px; + + --transition-time-02: 0.2s; +} diff --git a/src/styles/variables.module.less b/src/styles/variables.module.less new file mode 100644 index 0000000..e97028d --- /dev/null +++ b/src/styles/variables.module.less @@ -0,0 +1,10 @@ +// 命名空间 +@namespace: v; +// el命名空间 +@elNamespace: el; + +// 导出变量 +:export { + namespace: @namespace; + elNamespace: @elNamespace; +} diff --git a/src/types/components.d.ts b/src/types/components.d.ts new file mode 100644 index 0000000..45523f9 --- /dev/null +++ b/src/types/components.d.ts @@ -0,0 +1,52 @@ +export type ComponentName = + | 'Radio' + | 'RadioButton' + | 'Checkbox' + | 'CheckboxButton' + | 'Input' + | 'Autocomplete' + | 'InputNumber' + | 'Select' + | 'Cascader' + | 'Switch' + | 'Slider' + | 'TimePicker' + | 'DatePicker' + | 'Rate' + | 'ColorPicker' + | 'Transfer' + | 'Divider' + | 'TimeSelect' + | 'SelectV2' + | 'InputPassword' + | 'Editor' + +export type ColProps = { + span?: number + xs?: number + sm?: number + md?: number + lg?: number + xl?: number + tag?: string +} + +export type ComponentOptions = { + label?: string + value?: FormValueType + disabled?: boolean + key?: string | number + children?: ComponentOptions[] + options?: ComponentOptions[] +} & Recordable + +export type ComponentOptionsAlias = { + labelField?: string + valueField?: string +} + +export type ComponentProps = { + optionsAlias?: ComponentOptionsAlias + options?: ComponentOptions[] + optionsSlot?: boolean +} & Recordable diff --git a/src/types/configGlobal.d.ts b/src/types/configGlobal.d.ts new file mode 100644 index 0000000..f6d7b3c --- /dev/null +++ b/src/types/configGlobal.d.ts @@ -0,0 +1,4 @@ +import { ElementPlusSize } from './elementPlus' +export interface ConfigGlobalTypes { + size?: ElementPlusSize +} diff --git a/src/types/contextMenu.d.ts b/src/types/contextMenu.d.ts new file mode 100644 index 0000000..0738d0e --- /dev/null +++ b/src/types/contextMenu.d.ts @@ -0,0 +1,7 @@ +export type contextMenuSchema = { + disabled?: boolean + divided?: boolean + icon?: string + label: string + command?: (item: contextMenuSchema) => void +} diff --git a/src/types/descriptions.d.ts b/src/types/descriptions.d.ts new file mode 100644 index 0000000..6fb99b0 --- /dev/null +++ b/src/types/descriptions.d.ts @@ -0,0 +1,11 @@ +export interface DescriptionsSchema { + span?: number // 占多少分 + field: string // 字段名 + label?: string // label名 + width?: string | number + minWidth?: string | number + align?: 'left' | 'center' | 'right' + labelAlign?: 'left' | 'center' | 'right' + className?: string + labelClassName?: string +} diff --git a/src/types/elementPlus.d.ts b/src/types/elementPlus.d.ts new file mode 100644 index 0000000..2c6b76e --- /dev/null +++ b/src/types/elementPlus.d.ts @@ -0,0 +1,3 @@ +export type ElementPlusSize = 'default' | 'small' | 'large' + +export type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger' diff --git a/src/types/form.d.ts b/src/types/form.d.ts new file mode 100644 index 0000000..8673ec7 --- /dev/null +++ b/src/types/form.d.ts @@ -0,0 +1,45 @@ +import type { CSSProperties } from 'vue' +import { ColProps, ComponentProps, ComponentName } from '@/types/components' +import { FormValueType, FormValueType } from '@/types/form' +import type { AxiosPromise } from 'axios' + +export type FormSetPropsType = { + field: string + path: string + value: any +} + +export type FormValueType = string | number | string[] | number[] | boolean | undefined | null + +export type FormItemProps = { + labelWidth?: string | number + required?: boolean + rules?: Recordable + error?: string + showMessage?: boolean + inlineMessage?: boolean + style?: CSSProperties +} + +export type FormSchema = { + // 唯一值 + field: string + // 标题 + label?: string + // 提示 + labelMessage?: string + // col组件属性 + colProps?: ColProps + // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档 + componentProps?: { slots?: Recordable } & ComponentProps + // formItem组件属性 + formItemProps?: FormItemProps + // 渲染的组件 + component?: ComponentName + // 初始值 + value?: FormValueType + // 是否隐藏 + hidden?: boolean + // 远程加载下拉项 + api?: () => AxiosPromise +} diff --git a/src/types/icon.d.ts b/src/types/icon.d.ts new file mode 100644 index 0000000..d1ffcdb --- /dev/null +++ b/src/types/icon.d.ts @@ -0,0 +1,5 @@ +export interface IconTypes { + size?: number + color?: string + icon: string +} diff --git a/src/types/infoTip.d.ts b/src/types/infoTip.d.ts new file mode 100644 index 0000000..6eff083 --- /dev/null +++ b/src/types/infoTip.d.ts @@ -0,0 +1,4 @@ +export interface TipSchema { + label: string + keys?: string[] +} diff --git a/src/types/layout.d.ts b/src/types/layout.d.ts new file mode 100644 index 0000000..cad3e2a --- /dev/null +++ b/src/types/layout.d.ts @@ -0,0 +1 @@ +export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu' diff --git a/src/types/localeDropdown.d.ts b/src/types/localeDropdown.d.ts new file mode 100644 index 0000000..c749dce --- /dev/null +++ b/src/types/localeDropdown.d.ts @@ -0,0 +1,10 @@ +export interface Language { + el: Recordable + name: string +} + +export interface LocaleDropdownType { + lang: LocaleType + name?: string + elLocale?: Language +} diff --git a/src/types/qrcode.d.ts b/src/types/qrcode.d.ts new file mode 100644 index 0000000..86cdf0b --- /dev/null +++ b/src/types/qrcode.d.ts @@ -0,0 +1,9 @@ +export interface QrcodeLogo { + src?: string + logoSize?: number + bgColor?: string + borderSize?: number + crossOrigin?: string + borderRadius?: number + logoRadius?: number +} diff --git a/src/types/table.d.ts b/src/types/table.d.ts new file mode 100644 index 0000000..5adf91f --- /dev/null +++ b/src/types/table.d.ts @@ -0,0 +1,36 @@ +export type TableColumn = { + field: string + label?: string + children?: TableColumn[] +} & Recordable + +export type TableSlotDefault = { + row: Recordable + column: TableColumn + $index: number +} & Recordable + +export interface Pagination { + small?: boolean + background?: boolean + pageSize?: number + defaultPageSize?: number + total?: number + pageCount?: number + pagerCount?: number + currentPage?: number + defaultCurrentPage?: number + layout?: string + pageSizes?: number[] + popperClass?: string + prevText?: string + nextText?: string + disabled?: boolean + hideOnSinglePage?: boolean +} + +export interface TableSetPropsType { + field: string + path: string + value: any +} diff --git a/src/types/theme.d.ts b/src/types/theme.d.ts new file mode 100644 index 0000000..ad649b0 --- /dev/null +++ b/src/types/theme.d.ts @@ -0,0 +1,16 @@ +export type ThemeTypes = { + elColorPrimary?: string + leftMenuBorderColor?: string + leftMenuBgColor?: string + leftMenuBgLightColor?: string + leftMenuBgActiveColor?: string + leftMenuCollapseBgActiveColor?: string + leftMenuTextColor?: string + leftMenuTextActiveColor?: string + logoTitleTextColor?: string + logoBorderColor?: string + topHeaderBgColor?: string + topHeaderTextColor?: string + topHeaderHoverColor?: string + topToolBorderColor?: string +} diff --git a/src/utils/TableColumnFormatter.js b/src/utils/TableColumnFormatter.js new file mode 100644 index 0000000..a313305 --- /dev/null +++ b/src/utils/TableColumnFormatter.js @@ -0,0 +1,17 @@ +import dateFormatter from '@/utils/dateFormatter.js' + +export function formatDate(row, column, cellValue) { + return dateFormatter.formatUTCDate(cellValue) +} + +export function formatTime(row, column, cellValue) { + return dateFormatter.formatUTCTime(cellValue) +} + +export function getFormatMethod(name) { + if (name === 'FORMAT_DATE') { + return formatDate + } else if (name === 'FORMAT_TIME') { + return formatTime + } +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..3eb58f2 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,18 @@ +import { TOKEN_KEY } from '@/constant/common' +import { useCache } from '@/hooks/web/useCache' +const { wsCache } = useCache() + +// 获取token +export const getToken = () => { + return wsCache.get(TOKEN_KEY) ? wsCache.get(TOKEN_KEY) : '' +} + +// 设置token +export const setToken = (token) => { + wsCache.set(TOKEN_KEY, token) +} + +// 删除token +export const removeToken = () => { + wsCache.delete(TOKEN_KEY) +} diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..6888583 --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,153 @@ +/** + * 判断是否 十六进制颜色值. + * 输入形式可为 #fff000 #f00 + * + * @param String color 十六进制颜色值 + * @return Boolean + */ +export const isHexColor = (color: string) => { + const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/ + return reg.test(color) +} + +/** + * RGB 颜色值转换为 十六进制颜色值. + * r, g, 和 b 需要在 [0, 255] 范围内 + * + * @return String 类似#ff00ff + * @param r + * @param g + * @param b + */ +export const rgbToHex = (r: number, g: number, b: number) => { + // tslint:disable-next-line:no-bitwise + const hex = ((r << 16) | (g << 8) | b).toString(16) + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex +} + +/** + * Transform a HEX color to its RGB representation + * @param {string} hex The color to transform + * @returns The RGB representation of the passed color + */ +export const hexToRGB = (hex: string, opacity?: number) => { + let sHex = hex.toLowerCase() + if (isHexColor(hex)) { + if (sHex.length === 4) { + let sColorNew = '#' + for (let i = 1; i < 4; i += 1) { + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)) + } + sHex = sColorNew + } + const sColorChange: number[] = [] + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))) + } + return opacity + ? 'RGBA(' + sColorChange.join(',') + ',' + opacity + ')' + : 'RGB(' + sColorChange.join(',') + ')' + } + return sHex +} + +export const colorIsDark = (color: string) => { + if (!isHexColor(color)) return + const [r, g, b] = hexToRGB(color) + .replace(/(?:\(|\)|rgb|RGB)*/g, '') + .split(',') + .map((item) => Number(item)) + return r * 0.299 + g * 0.578 + b * 0.114 < 192 +} + +/** + * Darkens a HEX color given the passed percentage + * @param {string} color The color to process + * @param {number} amount The amount to change the color by + * @returns {string} The HEX representation of the processed color + */ +export const darken = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color + amount = Math.trunc((255 * amount) / 100) + return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( + color.substring(2, 4), + amount + )}${subtractLight(color.substring(4, 6), amount)}` +} + +/** + * Lightens a 6 char HEX color according to the passed percentage + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed color represented as HEX + */ +export const lighten = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color + amount = Math.trunc((255 * amount) / 100) + return `#${addLight(color.substring(0, 2), amount)}${addLight( + color.substring(2, 4), + amount + )}${addLight(color.substring(4, 6), amount)}` +} + +/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ +/** + * Sums the passed percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const addLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) + amount + const c = cc > 255 ? 255 : cc + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` +} + +/** + * Calculates luminance of an rgb color + * @param {number} r red + * @param {number} g green + * @param {number} b blue + */ +const luminanace = (r: number, g: number, b: number) => { + const a = [r, g, b].map((v) => { + v /= 255 + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) + }) + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722 +} + +/** + * Calculates contrast between two rgb colors + * @param {string} rgb1 rgb color 1 + * @param {string} rgb2 rgb color 2 + */ +const contrast = (rgb1: string[], rgb2: number[]) => { + return ( + (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / + (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05) + ) +} + +/** + * Determines what the best text color is (black or white) based con the contrast with the background + * @param hexColor - Last selected color by the user + */ +export const calculateBestTextColor = (hexColor: string) => { + const rgbColor = hexToRGB(hexColor.substring(1)) + const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]) + + return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF' +} + +/** + * Subtracts the indicated percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const subtractLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) - amount + const c = cc < 0 ? 0 : cc + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` +} diff --git a/src/utils/dateFormatter.js b/src/utils/dateFormatter.js new file mode 100644 index 0000000..cbf549e --- /dev/null +++ b/src/utils/dateFormatter.js @@ -0,0 +1,48 @@ +import moment from 'moment' +const dateFormatter = { + // 格式化UTC日期 + formatUTCDate: function (time, format = 'YYYY-MM-DD') { + // 获取系统时区,后期会改成用户选择时区 + const zoneOffset = moment().utcOffset() / 60 + return time ? moment(time).utcOffset(zoneOffset).format(format) : '' + }, + // 格式化UTC时间 + formatUTCTime: function (time, format = 'YYYY-MM-DD HH:mm:ss') { + // 获取系统时区,后期会改成用户选择时区 + const zoneOffset = moment().utcOffset() / 60 + return time ? moment(time).utcOffset(zoneOffset).format(format) : '' + }, + // 获取日期时间格式 + getDatetimeFormat: function (formatCode) { + let formatString = '' + switch (formatCode) { + case 'DAY': + formatString = 'YYYY-MM-DD 00:00:00' + break + case 'MINITE': + formatString = 'YYYY-MM-DD HH:mm:00' + break + case 'SECOND': + formatString = 'YYYY-MM-DD HH:mm:ss' + break + } + return formatString + }, + + // 获取日期时间控件类型 + getDatetimeType: function (formatCode) { + let type = '' + switch (formatCode) { + case 'DAY': + type = 'date' + break + case 'MINITE': + case 'SECOND': + type = 'datetime' + break + } + return type + } +} + +export default dateFormatter diff --git a/src/utils/domUtils.ts b/src/utils/domUtils.ts new file mode 100644 index 0000000..dbc1989 --- /dev/null +++ b/src/utils/domUtils.ts @@ -0,0 +1,289 @@ +import { isServer } from './is' +const ieVersion = isServer ? 0 : Number((document as any).documentMode) +const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g +const MOZ_HACK_REGEXP = /^moz([A-Z])/ + +export interface ViewportOffsetResult { + left: number + top: number + right: number + bottom: number + rightIncludeBody: number + bottomIncludeBody: number +} + +/* istanbul ignore next */ +const trim = function (string: string) { + return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '') +} + +/* istanbul ignore next */ +const camelCase = function (name: string) { + return name + .replace(SPECIAL_CHARS_REGEXP, function (_, __, letter, offset) { + return offset ? letter.toUpperCase() : letter + }) + .replace(MOZ_HACK_REGEXP, 'Moz$1') +} + +/* istanbul ignore next */ +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false + if (cls.indexOf(' ') !== -1) { + throw new Error('className should not contain space.') + } + if (el.classList) { + return el.classList.contains(cls) + } else { + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1 + } +} + +/* istanbul ignore next */ +export function addClass(el: Element, cls: string) { + if (!el) return + let curClass = el.className + const classes = (cls || '').split(' ') + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i] + if (!clsName) continue + + if (el.classList) { + el.classList.add(clsName) + } else if (!hasClass(el, clsName)) { + curClass += ' ' + clsName + } + } + if (!el.classList) { + el.className = curClass + } +} + +/* istanbul ignore next */ +export function removeClass(el: Element, cls: string) { + if (!el || !cls) return + const classes = cls.split(' ') + let curClass = ' ' + el.className + ' ' + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i] + if (!clsName) continue + + if (el.classList) { + el.classList.remove(clsName) + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(' ' + clsName + ' ', ' ') + } + } + if (!el.classList) { + el.className = trim(curClass) + } +} + +export function getBoundingClientRect(element: Element): DOMRect | number { + if (!element || !element.getBoundingClientRect) { + return 0 + } + return element.getBoundingClientRect() +} + +/** + * 获取当前元素的left、top偏移 + * left:元素最左侧距离文档左侧的距离 + * top:元素最顶端距离文档顶端的距离 + * right:元素最右侧距离文档右侧的距离 + * bottom:元素最底端距离文档底端的距离 + * rightIncludeBody:元素最左侧距离文档右侧的距离 + * bottomIncludeBody:元素最底端距离文档最底部的距离 + * + * @description: + */ +export function getViewportOffset(element: Element): ViewportOffsetResult { + const doc = document.documentElement + + const docScrollLeft = doc.scrollLeft + const docScrollTop = doc.scrollTop + const docClientLeft = doc.clientLeft + const docClientTop = doc.clientTop + + const pageXOffset = window.pageXOffset + const pageYOffset = window.pageYOffset + + const box = getBoundingClientRect(element) + + const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect + + const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0) + const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0) + const offsetLeft = retLeft + pageXOffset + const offsetTop = rectTop + pageYOffset + + const left = offsetLeft - scrollLeft + const top = offsetTop - scrollTop + + const clientWidth = window.document.documentElement.clientWidth + const clientHeight = window.document.documentElement.clientHeight + return { + left: left, + top: top, + right: clientWidth - rectWidth - left, + bottom: clientHeight - rectHeight - top, + rightIncludeBody: clientWidth - left, + bottomIncludeBody: clientHeight - top + } +} + +/* istanbul ignore next */ +export const on = function ( + element: HTMLElement | Document | Window, + event: string, + handler: EventListenerOrEventListenerObject +): void { + if (element && event && handler) { + element.addEventListener(event, handler, false) + } +} + +/* istanbul ignore next */ +export const off = function ( + element: HTMLElement | Document | Window, + event: string, + handler: any +): void { + if (element && event && handler) { + element.removeEventListener(event, handler, false) + } +} + +/* istanbul ignore next */ +export const once = function (el: HTMLElement, event: string, fn: EventListener): void { + const listener = function (this: any, ...args: unknown[]) { + if (fn) { + // @ts-ignore + fn.apply(this, args) + } + off(el, event, listener) + } + on(el, event, listener) +} + +/* istanbul ignore next */ +export const getStyle = + ieVersion < 9 + ? function (element: Element | any, styleName: string) { + if (isServer) return + if (!element || !styleName) return null + styleName = camelCase(styleName) + if (styleName === 'float') { + styleName = 'styleFloat' + } + try { + switch (styleName) { + case 'opacity': + try { + return element.filters.item('alpha').opacity / 100 + } catch (e) { + return 1.0 + } + default: + return element.style[styleName] || element.currentStyle + ? element.currentStyle[styleName] + : null + } + } catch (e) { + return element.style[styleName] + } + } + : function (element: Element | any, styleName: string) { + if (isServer) return + if (!element || !styleName) return null + styleName = camelCase(styleName) + if (styleName === 'float') { + styleName = 'cssFloat' + } + try { + const computed = (document as any).defaultView.getComputedStyle(element, '') + return element.style[styleName] || computed ? computed[styleName] : null + } catch (e) { + return element.style[styleName] + } + } + +/* istanbul ignore next */ +export function setStyle(element: Element | any, styleName: any, value: any) { + if (!element || !styleName) return + + if (typeof styleName === 'object') { + for (const prop in styleName) { + if (Object.prototype.hasOwnProperty.call(styleName, prop)) { + setStyle(element, prop, styleName[prop]) + } + } + } else { + styleName = camelCase(styleName) + if (styleName === 'opacity' && ieVersion < 9) { + element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')' + } else { + element.style[styleName] = value + } + } +} + +/* istanbul ignore next */ +export const isScroll = (el: Element, vertical: any) => { + if (isServer) return + + const determinedDirection = vertical !== null || vertical !== undefined + const overflow = determinedDirection + ? vertical + ? getStyle(el, 'overflow-y') + : getStyle(el, 'overflow-x') + : getStyle(el, 'overflow') + + return overflow.match(/(scroll|auto)/) +} + +/* istanbul ignore next */ +export const getScrollContainer = (el: Element, vertical?: any) => { + if (isServer) return + + let parent: any = el + while (parent) { + if ([window, document, document.documentElement].includes(parent)) { + return window + } + if (isScroll(parent, vertical)) { + return parent + } + parent = parent.parentNode + } + + return parent +} + +/* istanbul ignore next */ +export const isInContainer = (el: Element, container: any) => { + if (isServer || !el || !container) return false + + const elRect = el.getBoundingClientRect() + let containerRect + + if ([window, document, document.documentElement, null, undefined].includes(container)) { + containerRect = { + top: 0, + right: window.innerWidth, + bottom: window.innerHeight, + left: 0 + } + } else { + containerRect = container.getBoundingClientRect() + } + + return ( + elRect.top < containerRect.bottom && + elRect.bottom > containerRect.top && + elRect.right > containerRect.left && + elRect.left < containerRect.right + ) +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..6278af4 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,158 @@ +// import type { Plugin } from 'vue' + +/** + * + * @param component 需要注册的组件 + * @param alias 组件别名 + * @returns any + */ +export const withInstall = (component: T, alias?: string) => { + const comp = component as any + comp.install = (app: any) => { + app.component(comp.name || comp.displayName, component) + if (alias) { + app.config.globalProperties[alias] = component + } + } + return component as T & Plugin +} + +/** + * @param str 需要转下划线的驼峰字符串 + * @returns 字符串下划线 + */ +export const humpToUnderline = (str: string): string => { + return str.replace(/([A-Z])/g, '-$1').toLowerCase() +} + +/** + * @param str 需要转驼峰的下划线字符串 + * @returns 字符串驼峰 + */ +export const underlineToHump = (str: string): string => { + if (!str) return '' + return str.replace(/\-(\w)/g, (_, letter: string) => { + return letter.toUpperCase() + }) +} + +export const setCssVar = (prop: string, val: any, dom = document.documentElement) => { + dom.style.setProperty(prop, val) +} + +/** + * 查找数组对象的某个下标 + * @param {Array} ary 查找的数组 + * @param {Functon} fn 判断的方法 + */ +// eslint-disable-next-line +export const findIndex = (ary: Array, fn: Fn): number => { + if (ary.findIndex) { + return ary.findIndex(fn) + } + let index = -1 + ary.some((item: T, i: number, ary: Array) => { + const ret: T = fn(item, i, ary) + if (ret) { + index = i + return ret + } + }) + return index +} + +export const trim = (str: string) => { + return str.replace(/(^\s*)|(\s*$)/g, '') +} + +// 缩略 +export const abbreviate = (str: string, length: number) => { + // 长度未超出时,直接返回原字符串 + if (str) { + if (str.length <= length) { + return str + } + // 否则返回缩略 + return str.substring(0, length) + '……' + } else { + return '' + } +} + +/** + * @param {Date | number | string} time 需要转换的时间 + * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss + */ +export function formatTime(time: Date | number | string, fmt: string) { + if (!time) return '' + else { + const date = new Date(time) + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds() + } + if (/(y+)/.test(fmt)) { + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) + } + for (const k in o) { + if (new RegExp('(' + k + ')').test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) + ) + } + } + return fmt + } +} + +/** + * 生成随机字符串 + */ +export function toAnyString() { + const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => { + const r: number = (Math.random() * 16) | 0 + const v: number = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString() + }) + return str +} + +function _tid() { + const mydate = new Date() + const uuid = + mydate.getDay() + + mydate.getHours() + + mydate.getMinutes() + + mydate.getSeconds() + + mydate.getMilliseconds() + + Math.round(Math.random() * 10000) + return uuid +} +function _rid() { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) +} +/** + * 生成前端使用的16位uuid 2304_b820_05f7_6c9e + */ +export function uuid() { + return `${_tid()}_${_rid()}_${_rid()}_${_rid()}` +} + +import { useEmitt } from '@/hooks/web/useEmitt' +const { emitter } = useEmitt() +/** + * 关闭tab页,和头部的tab button关闭效果一致 + */ +export function closeCurrentTab() { + emitter.emit('closeCurrentTab') +} + +export function closeTab(view) { + emitter.emit('closeTab', view) +} diff --git a/src/utils/is.ts b/src/utils/is.ts new file mode 100644 index 0000000..3752985 --- /dev/null +++ b/src/utils/is.ts @@ -0,0 +1,105 @@ +// copy to vben-admin + +const toString = Object.prototype.toString + +export const is = (val: unknown, type: string) => { + return toString.call(val) === `[object ${type}]` +} + +export const isDef = (val?: T): val is T => { + return typeof val !== 'undefined' +} + +export const isUnDef = (val?: T): val is T => { + return !isDef(val) +} + +export const isObject = (val: any): val is Record => { + return val !== null && is(val, 'Object') +} + +export const isEmpty = (val: T): val is T => { + if (isArray(val) || isString(val)) { + return val.length === 0 + } + + if (val instanceof Map || val instanceof Set) { + return val.size === 0 + } + + if (isObject(val)) { + return Object.keys(val).length === 0 + } + + return false +} + +export const isDate = (val: unknown): val is Date => { + return is(val, 'Date') +} + +export const isNull = (val: unknown): val is null => { + return val === null +} + +export const isNullAndUnDef = (val: unknown): val is null | undefined => { + return isUnDef(val) && isNull(val) +} + +export const isNullOrUnDef = (val: unknown): val is null | undefined => { + return isUnDef(val) || isNull(val) +} + +export const isNumber = (val: unknown): val is number => { + return is(val, 'Number') +} + +export const isPromise = (val: unknown): val is Promise => { + return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) +} + +export const isString = (val: unknown): val is string => { + return is(val, 'String') +} + +export const isFunction = (val: unknown): val is Function => { + return typeof val === 'function' +} + +export const isBoolean = (val: unknown): val is boolean => { + return is(val, 'Boolean') +} + +export const isRegExp = (val: unknown): val is RegExp => { + return is(val, 'RegExp') +} + +export const isArray = (val: any): val is Array => { + return val && Array.isArray(val) +} + +export const isWindow = (val: any): val is Window => { + return typeof window !== 'undefined' && is(val, 'Window') +} + +export const isElement = (val: unknown): val is Element => { + return isObject(val) && !!val.tagName +} + +export const isMap = (val: unknown): val is Map => { + return is(val, 'Map') +} + +export const isServer = typeof window === 'undefined' + +export const isClient = !isServer + +export const isUrl = (path: string): boolean => { + const reg = + /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ + return reg.test(path) +} + +export const isDark = (): boolean => { + return window.matchMedia('(prefers-color-scheme: dark)').matches +} diff --git a/src/utils/propTypes.ts b/src/utils/propTypes.ts new file mode 100644 index 0000000..5d685f3 --- /dev/null +++ b/src/utils/propTypes.ts @@ -0,0 +1,29 @@ +import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types' +import { CSSProperties } from 'vue' + +// 自定义扩展vue-types +type PropTypes = VueTypesInterface & { + readonly style: VueTypeValidableDef +} + +const propTypes = createTypes({ + func: undefined, + bool: undefined, + string: undefined, + number: undefined, + object: undefined, + integer: undefined +}) as PropTypes + +// 需要自定义扩展的类型 +// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method +propTypes.extend([ + { + name: 'style', + getter: true, + type: [String, Object], + default: undefined + } +]) + +export { propTypes } diff --git a/src/utils/routerHelper.ts b/src/utils/routerHelper.ts new file mode 100644 index 0000000..eaa614a --- /dev/null +++ b/src/utils/routerHelper.ts @@ -0,0 +1,193 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import type { + Router, + RouteLocationNormalized, + RouteRecordNormalized, + RouteMeta, + RouteRecordRaw +} from 'vue-router' +import { isUrl } from '@/utils/is' +import { omit, cloneDeep } from 'lodash-es' + +const modules = import.meta.glob('../modules/**/*.{vue,tsx}') + +/* Layout */ +export const Layout = () => import('@/layout/Layout.vue') + +export const getParentLayout = () => { + return () => + new Promise((resolve) => { + resolve({ + name: 'ParentLayout' + }) + }) +} + +export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => { + if (!route) return route + const { matched, ...opt } = route + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ meta: item.meta, name: item.name, path: item.path })) + : undefined) as RouteRecordNormalized[] + } +} + +// 前端控制路由生成 +export const generateRoutesFn1 = ( + routes: AppRouteRecordRaw[], + keys: string[], + basePath = '/' +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const meta = route.meta as RouteMeta + // skip some route + if (meta.hidden && !meta.canTo) { + continue + } + + let data: Nullable = null + + let onlyOneChild: Nullable = null + if (route.children && route.children.length === 1 && !meta.alwaysShow) { + onlyOneChild = ( + isUrl(route.children[0].path) + ? route.children[0].path + : pathResolve(pathResolve(basePath, route.path), route.children[0].path) + ) as string + } + + // 开发者可以根据实际情况进行扩展 + for (const item of keys) { + // 通过路径去匹配 + if (isUrl(item) && (onlyOneChild === item || route.path === item)) { + data = Object.assign({}, route) + } else { + const routePath = pathResolve(basePath, onlyOneChild || route.path) + if (routePath === item || meta.followRoute === item) { + data = Object.assign({}, route) + } + } + } + + // recursive child routes + if (route.children && data) { + data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path)) + } + if (data) { + res.push(data as AppRouteRecordRaw) + } + } + return res +} + +// 后端控制路由生成 +export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const data: AppRouteRecordRaw = { + path: route.path, + name: route.name, + redirect: route.redirect, + meta: route.meta + } + if (route.component) { + const comModule = + modules[`../modules/${route.component}.vue`] || modules[`../modules/${route.component}.tsx`] + + const component = route.component as string + + if (!comModule && !component.includes('#')) { + console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`) + } else { + // 动态加载路由文件 + data.component = + component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule + } + } + // recursive child routes + if (route.children) { + data.children = generateRoutesFn2(route.children) + } + res.push(data as AppRouteRecordRaw) + } + return res +} + +export const pathResolve = (parentPath: string, path: string) => { + if (isUrl(path)) return path + const childPath = path.startsWith('/') || !path ? path : `/${path}` + return `${parentPath}${childPath}`.replace(/\/\//g, '/') +} + +// 路由降级 +export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => { + const modules: AppRouteRecordRaw[] = cloneDeep(routes) + for (let index = 0; index < modules.length; index++) { + const route = modules[index] + if (!isMultipleRoute(route)) { + continue + } + promoteRouteLevel(route) + } + return modules +} + +// 层级是否大于2 +const isMultipleRoute = (route: AppRouteRecordRaw) => { + if (!route || !Reflect.has(route, 'children') || !route.children?.length) { + return false + } + + const children = route.children + + let flag = false + for (let index = 0; index < children.length; index++) { + const child = children[index] + if (child.children?.length) { + flag = true + break + } + } + return flag +} + +// 生成二级路由 +const promoteRouteLevel = (route: AppRouteRecordRaw) => { + let router: Router | null = createRouter({ + routes: [route as RouteRecordRaw], + history: createWebHashHistory() + }) + + const routes = router.getRoutes() + addToChildren(routes, route.children || [], route) + router = null + + route.children = route.children?.map((item) => omit(item, 'children')) +} + +// 添加所有子菜单 +const addToChildren = ( + routes: RouteRecordNormalized[], + children: AppRouteRecordRaw[], + routeModule: AppRouteRecordRaw +) => { + for (let index = 0; index < children.length; index++) { + const child = children[index] + const route = routes.find((item) => item.name === child.name) + if (!route) { + continue + } + routeModule.children = routeModule.children || [] + if (!routeModule.children.find((item) => item.name === route.name)) { + routeModule.children?.push(route as unknown as AppRouteRecordRaw) + } + if (child.children?.length) { + addToChildren(routes, child.children, routeModule) + } + } +} diff --git a/src/utils/stringUtil.js b/src/utils/stringUtil.js new file mode 100644 index 0000000..682a273 --- /dev/null +++ b/src/utils/stringUtil.js @@ -0,0 +1,10 @@ +const StringUtil = { + // 将下划线或横杠连接的字符串转换为小驼峰命名 + toCamelCase: function (str) { + return str.toLowerCase().replace(/[-_]+([a-zA-Z])/g, function (match, letter) { + return letter.toUpperCase() + }) + } +} + +export default StringUtil diff --git a/src/utils/tree.ts b/src/utils/tree.ts new file mode 100644 index 0000000..fe0207d --- /dev/null +++ b/src/utils/tree.ts @@ -0,0 +1,207 @@ +interface TreeHelperConfig { + id: string + children: string + pid: string +} +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid' +} + +const getConfig = (config: Partial) => Object.assign({}, DEFAULT_CONFIG, config) + +// tree from list +export const listToTree = (list: any[], config: Partial = {}): T[] => { + const conf = getConfig(config) as TreeHelperConfig + const nodeMap = new Map() + const result: T[] = [] + const { id, children, pid } = conf + + for (const node of list) { + node[children] = node[children] || [] + nodeMap.set(node[id], node) + } + for (const node of list) { + const parent = nodeMap.get(node[pid]) + ;(parent ? parent.children : result).push(node) + } + return result +} + +export const treeToList = (tree: any, config: Partial = {}): T => { + config = getConfig(config) + const { children } = config + const result: any = [...tree] + for (let i = 0; i < result.length; i++) { + if (!result[i][children!]) continue + result.splice(i + 1, 0, ...result[i][children!]) + } + return result +} + +export const findNode = ( + tree: any, + func: Fn, + config: Partial = {} +): T | null => { + config = getConfig(config) + const { children } = config + const list = [...tree] + for (const node of list) { + if (func(node)) return node + node[children!] && list.push(...node[children!]) + } + return null +} + +export const findNodeAll = ( + tree: any, + func: Fn, + config: Partial = {} +): T[] => { + config = getConfig(config) + const { children } = config + const list = [...tree] + const result: T[] = [] + for (const node of list) { + func(node) && result.push(node) + node[children!] && list.push(...node[children!]) + } + return result +} + +export const findPath = ( + tree: any, + func: Fn, + config: Partial = {} +): T | T[] | null => { + config = getConfig(config) + const path: T[] = [] + const list = [...tree] + const visitedSet = new Set() + const { children } = config + while (list.length) { + const node = list[0] + if (visitedSet.has(node)) { + path.pop() + list.shift() + } else { + visitedSet.add(node) + node[children!] && list.unshift(...node[children!]) + path.push(node) + if (func(node)) { + return path + } + } + } + return null +} + +export const findPathAll = (tree: any, func: Fn, config: Partial = {}) => { + config = getConfig(config) + const path: any[] = [] + const list = [...tree] + const result: any[] = [] + const visitedSet = new Set(), + { children } = config + while (list.length) { + const node = list[0] + if (visitedSet.has(node)) { + path.pop() + list.shift() + } else { + visitedSet.add(node) + node[children!] && list.unshift(...node[children!]) + path.push(node) + func(node) && result.push([...path]) + } + } + return result +} + +export const filter = ( + tree: T[], + func: (n: T) => boolean, + config: Partial = {} +): T[] => { + config = getConfig(config) + const children = config.children as string + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + node[children] = node[children] && listFilter(node[children]) + return func(node) || (node[children] && node[children].length) + }) + } + return listFilter(tree) +} + +export const forEach = ( + tree: T[], + func: (n: T) => any, + config: Partial = {} +): void => { + config = getConfig(config) + const list: any[] = [...tree] + const { children } = config + for (let i = 0; i < list.length; i++) { + // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return + } + children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]) + } +} + +/** + * @description: Extract tree specified structure + */ +export const treeMap = ( + treeData: T[], + opt: { children?: string; conversion: Fn } +): T[] => { + return treeData.map((item) => treeMapEach(item, opt)) +} + +/** + * @description: Extract tree specified structure + */ +export const treeMapEach = ( + data: any, + { children = 'children', conversion }: { children?: string; conversion: Fn } +) => { + const haveChildren = Array.isArray(data[children]) && data[children].length > 0 + const conversionData = conversion(data) || {} + if (haveChildren) { + return { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion + }) + ) + } + } else { + return { + ...conversionData + } + } +} + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element + if (element.children) { + eachTree(element.children, callBack, newNode) + } + }) +} diff --git a/src/utils/tsxHelper.ts b/src/utils/tsxHelper.ts new file mode 100644 index 0000000..6087fa3 --- /dev/null +++ b/src/utils/tsxHelper.ts @@ -0,0 +1,16 @@ +import { Slots } from 'vue' +import { isFunction } from '@/utils/is' + +export const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => { + // Reflect.has 判断一个对象是否存在某个属性 + if (!slots || !Reflect.has(slots, slot)) { + return null + } + if (!isFunction(slots[slot])) { + console.error(`${slot} is not a function!`) + return null + } + const slotFn = slots[slot] + if (!slotFn) return null + return slotFn(data) +} diff --git a/src/views/Authorization/Role.vue b/src/views/Authorization/Role.vue new file mode 100644 index 0000000..5c30df2 --- /dev/null +++ b/src/views/Authorization/Role.vue @@ -0,0 +1,89 @@ + + + diff --git a/src/views/Authorization/User.vue b/src/views/Authorization/User.vue new file mode 100644 index 0000000..5c30df2 --- /dev/null +++ b/src/views/Authorization/User.vue @@ -0,0 +1,89 @@ + + + diff --git a/src/views/Components/CommonSelect.vue b/src/views/Components/CommonSelect.vue new file mode 100644 index 0000000..3f39ca1 --- /dev/null +++ b/src/views/Components/CommonSelect.vue @@ -0,0 +1,19 @@ + + + + diff --git a/src/views/Components/CountTo.vue b/src/views/Components/CountTo.vue new file mode 100644 index 0000000..4c906b9 --- /dev/null +++ b/src/views/Components/CountTo.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/views/Components/Descriptions.vue b/src/views/Components/Descriptions.vue new file mode 100644 index 0000000..5b66c68 --- /dev/null +++ b/src/views/Components/Descriptions.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/src/views/Components/Dialog.vue b/src/views/Components/Dialog.vue new file mode 100644 index 0000000..1aa0568 --- /dev/null +++ b/src/views/Components/Dialog.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/views/Components/Echart.vue b/src/views/Components/Echart.vue new file mode 100644 index 0000000..683d6b4 --- /dev/null +++ b/src/views/Components/Echart.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/views/Components/Editor/Editor.vue b/src/views/Components/Editor/Editor.vue new file mode 100644 index 0000000..9dde431 --- /dev/null +++ b/src/views/Components/Editor/Editor.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/views/Components/Form/DefaultForm.vue b/src/views/Components/Form/DefaultForm.vue new file mode 100644 index 0000000..29dc5fa --- /dev/null +++ b/src/views/Components/Form/DefaultForm.vue @@ -0,0 +1,1136 @@ + + + + + diff --git a/src/views/Components/Form/RefForm.vue b/src/views/Components/Form/RefForm.vue new file mode 100644 index 0000000..20825d2 --- /dev/null +++ b/src/views/Components/Form/RefForm.vue @@ -0,0 +1,270 @@ + + + diff --git a/src/views/Components/Form/UseFormDemo.vue b/src/views/Components/Form/UseFormDemo.vue new file mode 100644 index 0000000..84bc62e --- /dev/null +++ b/src/views/Components/Form/UseFormDemo.vue @@ -0,0 +1,280 @@ + + + diff --git a/src/views/Components/Highlight.vue b/src/views/Components/Highlight.vue new file mode 100644 index 0000000..96baedc --- /dev/null +++ b/src/views/Components/Highlight.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/Components/Icon.vue b/src/views/Components/Icon.vue new file mode 100644 index 0000000..7ec3273 --- /dev/null +++ b/src/views/Components/Icon.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/views/Components/ImageViewer.vue b/src/views/Components/ImageViewer.vue new file mode 100644 index 0000000..d732a4e --- /dev/null +++ b/src/views/Components/ImageViewer.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/views/Components/Infotip.vue b/src/views/Components/Infotip.vue new file mode 100644 index 0000000..49cff12 --- /dev/null +++ b/src/views/Components/Infotip.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/Components/InputPassword.vue b/src/views/Components/InputPassword.vue new file mode 100644 index 0000000..bfae7e5 --- /dev/null +++ b/src/views/Components/InputPassword.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/views/Components/MyEchart.vue b/src/views/Components/MyEchart.vue new file mode 100644 index 0000000..ebfbd2c --- /dev/null +++ b/src/views/Components/MyEchart.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/views/Components/Qrcode.vue b/src/views/Components/Qrcode.vue new file mode 100644 index 0000000..6b7d792 --- /dev/null +++ b/src/views/Components/Qrcode.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/views/Components/Search.vue b/src/views/Components/Search.vue new file mode 100644 index 0000000..4ecb091 --- /dev/null +++ b/src/views/Components/Search.vue @@ -0,0 +1,227 @@ + + + diff --git a/src/views/Components/Sticky.vue b/src/views/Components/Sticky.vue new file mode 100644 index 0000000..44fd2b9 --- /dev/null +++ b/src/views/Components/Sticky.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/views/Components/Table/DefaultTable.vue b/src/views/Components/Table/DefaultTable.vue new file mode 100644 index 0000000..dcbe5a5 --- /dev/null +++ b/src/views/Components/Table/DefaultTable.vue @@ -0,0 +1,107 @@ + + + diff --git a/src/views/Components/Table/RefTable.vue b/src/views/Components/Table/RefTable.vue new file mode 100644 index 0000000..95506bb --- /dev/null +++ b/src/views/Components/Table/RefTable.vue @@ -0,0 +1,182 @@ + + + diff --git a/src/views/Components/Table/UseTableDemo.vue b/src/views/Components/Table/UseTableDemo.vue new file mode 100644 index 0000000..d1ee46a --- /dev/null +++ b/src/views/Components/Table/UseTableDemo.vue @@ -0,0 +1,223 @@ + + + diff --git a/src/views/Dashboard/Analysis.vue b/src/views/Dashboard/Analysis.vue new file mode 100644 index 0000000..4ce6f27 --- /dev/null +++ b/src/views/Dashboard/Analysis.vue @@ -0,0 +1,127 @@ + + + diff --git a/src/views/Dashboard/Workplace.vue b/src/views/Dashboard/Workplace.vue new file mode 100644 index 0000000..16aa010 --- /dev/null +++ b/src/views/Dashboard/Workplace.vue @@ -0,0 +1,291 @@ + + + diff --git a/src/views/Dashboard/components/PanelGroup.vue b/src/views/Dashboard/components/PanelGroup.vue new file mode 100644 index 0000000..da9c278 --- /dev/null +++ b/src/views/Dashboard/components/PanelGroup.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/src/views/Dashboard/echarts-data.ts b/src/views/Dashboard/echarts-data.ts new file mode 100644 index 0000000..00a195e --- /dev/null +++ b/src/views/Dashboard/echarts-data.ts @@ -0,0 +1,309 @@ +import { EChartsOption } from 'echarts' +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +export const lineOptions: EChartsOption = { + title: { + text: t('analysis.monthlySales'), + left: 'center' + }, + xAxis: { + data: [ + t('analysis.january'), + t('analysis.february'), + t('analysis.march'), + t('analysis.april'), + t('analysis.may'), + t('analysis.june'), + t('analysis.july'), + t('analysis.august'), + t('analysis.september'), + t('analysis.october'), + t('analysis.november'), + t('analysis.december') + ], + boundaryGap: false, + axisTick: { + show: false + } + }, + grid: { + left: 20, + right: 20, + bottom: 20, + top: 80, + containLabel: true + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + padding: [5, 10] + }, + yAxis: { + axisTick: { + show: false + } + }, + legend: { + data: [t('analysis.estimate'), t('analysis.actual')], + top: 50 + }, + series: [ + { + name: t('analysis.estimate'), + smooth: true, + type: 'line', + data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123], + animationDuration: 2800, + animationEasing: 'cubicInOut' + }, + { + name: t('analysis.actual'), + smooth: true, + type: 'line', + itemStyle: {}, + data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123], + animationDuration: 2800, + animationEasing: 'quadraticOut' + } + ] +} + +export const pieOptions: EChartsOption = { + title: { + text: t('analysis.userAccessSource'), + left: 'center' + }, + tooltip: { + trigger: 'item', + formatter: '{a}
{b} : {c} ({d}%)' + }, + legend: { + orient: 'vertical', + left: 'left', + data: [ + t('analysis.directAccess'), + t('analysis.mailMarketing'), + t('analysis.allianceAdvertising'), + t('analysis.videoAdvertising'), + t('analysis.searchEngines') + ] + }, + series: [ + { + name: t('analysis.userAccessSource'), + type: 'pie', + radius: '55%', + center: ['50%', '60%'], + data: [ + { value: 335, name: t('analysis.directAccess') }, + { value: 310, name: t('analysis.mailMarketing') }, + { value: 234, name: t('analysis.allianceAdvertising') }, + { value: 135, name: t('analysis.videoAdvertising') }, + { value: 1548, name: t('analysis.searchEngines') } + ] + } + ] +} + +export const barOptions: EChartsOption = { + title: { + text: t('analysis.weeklyUserActivity'), + left: 'center' + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + grid: { + left: 50, + right: 20, + bottom: 20 + }, + xAxis: { + type: 'category', + data: [ + t('analysis.monday'), + t('analysis.tuesday'), + t('analysis.wednesday'), + t('analysis.thursday'), + t('analysis.friday'), + t('analysis.saturday'), + t('analysis.sunday') + ], + axisTick: { + alignWithLabel: true + } + }, + yAxis: { + type: 'value' + }, + series: [ + { + name: t('analysis.activeQuantity'), + data: [13253, 34235, 26321, 12340, 24643, 1322, 1324], + type: 'bar' + } + ] +} + +export const radarOption: EChartsOption = { + legend: { + data: [t('workplace.personal'), t('workplace.team')] + }, + radar: { + // shape: 'circle', + indicator: [ + { name: t('workplace.quote'), max: 65 }, + { name: t('workplace.contribution'), max: 160 }, + { name: t('workplace.hot'), max: 300 }, + { name: t('workplace.yield'), max: 130 }, + { name: t('workplace.follow'), max: 100 } + ] + }, + series: [ + { + name: `xxx${t('workplace.index')}`, + type: 'radar', + data: [ + { + value: [42, 30, 20, 35, 80], + name: t('workplace.personal') + }, + { + value: [50, 140, 290, 100, 90], + name: t('workplace.team') + } + ] + } + ] +} + +export const wordOptions = { + series: [ + { + type: 'wordCloud', + gridSize: 2, + sizeRange: [12, 50], + rotationRange: [-90, 90], + shape: 'pentagon', + width: 600, + height: 400, + drawOutOfBound: true, + textStyle: { + color: function () { + return ( + 'rgb(' + + [ + Math.round(Math.random() * 160), + Math.round(Math.random() * 160), + Math.round(Math.random() * 160) + ].join(',') + + ')' + ) + } + }, + emphasis: { + textStyle: { + shadowBlur: 10, + shadowColor: '#333' + } + }, + data: [ + { + name: 'Sam S Club', + value: 10000, + textStyle: { + color: 'black' + }, + emphasis: { + textStyle: { + color: 'red' + } + } + }, + { + name: 'Macys', + value: 6181 + }, + { + name: 'Amy Schumer', + value: 4386 + }, + { + name: 'Jurassic World', + value: 4055 + }, + { + name: 'Charter Communications', + value: 2467 + }, + { + name: 'Chick Fil A', + value: 2244 + }, + { + name: 'Planet Fitness', + value: 1898 + }, + { + name: 'Pitch Perfect', + value: 1484 + }, + { + name: 'Express', + value: 1112 + }, + { + name: 'Home', + value: 965 + }, + { + name: 'Johnny Depp', + value: 847 + }, + { + name: 'Lena Dunham', + value: 582 + }, + { + name: 'Lewis Hamilton', + value: 555 + }, + { + name: 'KXAN', + value: 550 + }, + { + name: 'Mary Ellen Mark', + value: 462 + }, + { + name: 'Farrah Abraham', + value: 366 + }, + { + name: 'Rita Ora', + value: 360 + }, + { + name: 'Serena Williams', + value: 282 + }, + { + name: 'NCAA baseball tournament', + value: 273 + }, + { + name: 'Point Break', + value: 265 + } + ] + } + ] +} diff --git a/src/views/Error/403.vue b/src/views/Error/403.vue new file mode 100644 index 0000000..615c940 --- /dev/null +++ b/src/views/Error/403.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/views/Error/404.vue b/src/views/Error/404.vue new file mode 100644 index 0000000..0a3fbbe --- /dev/null +++ b/src/views/Error/404.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/views/Error/500.vue b/src/views/Error/500.vue new file mode 100644 index 0000000..ddb5097 --- /dev/null +++ b/src/views/Error/500.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/views/Example/Dialog/ExampleDialog.vue b/src/views/Example/Dialog/ExampleDialog.vue new file mode 100644 index 0000000..2024a4e --- /dev/null +++ b/src/views/Example/Dialog/ExampleDialog.vue @@ -0,0 +1,282 @@ + + + diff --git a/src/views/Example/Dialog/components/Detail.vue b/src/views/Example/Dialog/components/Detail.vue new file mode 100644 index 0000000..42c5650 --- /dev/null +++ b/src/views/Example/Dialog/components/Detail.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/views/Example/Dialog/components/Write.vue b/src/views/Example/Dialog/components/Write.vue new file mode 100644 index 0000000..b78e19f --- /dev/null +++ b/src/views/Example/Dialog/components/Write.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/views/Example/Page/ExampleAdd.vue b/src/views/Example/Page/ExampleAdd.vue new file mode 100644 index 0000000..a29413d --- /dev/null +++ b/src/views/Example/Page/ExampleAdd.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/views/Example/Page/ExampleDetail.vue b/src/views/Example/Page/ExampleDetail.vue new file mode 100644 index 0000000..39f170f --- /dev/null +++ b/src/views/Example/Page/ExampleDetail.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/views/Example/Page/ExampleEdit.vue b/src/views/Example/Page/ExampleEdit.vue new file mode 100644 index 0000000..6818e9c --- /dev/null +++ b/src/views/Example/Page/ExampleEdit.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/views/Example/Page/ExamplePage.vue b/src/views/Example/Page/ExamplePage.vue new file mode 100644 index 0000000..899fe01 --- /dev/null +++ b/src/views/Example/Page/ExamplePage.vue @@ -0,0 +1,165 @@ + + + diff --git a/src/views/Example/Page/components/Detail.vue b/src/views/Example/Page/components/Detail.vue new file mode 100644 index 0000000..4f86a92 --- /dev/null +++ b/src/views/Example/Page/components/Detail.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/views/Example/Page/components/Write.vue b/src/views/Example/Page/components/Write.vue new file mode 100644 index 0000000..65c8d69 --- /dev/null +++ b/src/views/Example/Page/components/Write.vue @@ -0,0 +1,147 @@ + + + diff --git a/src/views/Guide/Guide.vue b/src/views/Guide/Guide.vue new file mode 100644 index 0000000..38a710a --- /dev/null +++ b/src/views/Guide/Guide.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/Level/Menu111.vue b/src/views/Level/Menu111.vue new file mode 100644 index 0000000..015d0f8 --- /dev/null +++ b/src/views/Level/Menu111.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/Level/Menu12.vue b/src/views/Level/Menu12.vue new file mode 100644 index 0000000..8e2725d --- /dev/null +++ b/src/views/Level/Menu12.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/Level/Menu2.vue b/src/views/Level/Menu2.vue new file mode 100644 index 0000000..72e2abb --- /dev/null +++ b/src/views/Level/Menu2.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue new file mode 100644 index 0000000..4b738cf --- /dev/null +++ b/src/views/Login/Login.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/views/Login/components/LoginForm.vue b/src/views/Login/components/LoginForm.vue new file mode 100644 index 0000000..8b75699 --- /dev/null +++ b/src/views/Login/components/LoginForm.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/src/views/Login/components/RegisterForm.vue b/src/views/Login/components/RegisterForm.vue new file mode 100644 index 0000000..fc0fb78 --- /dev/null +++ b/src/views/Login/components/RegisterForm.vue @@ -0,0 +1,145 @@ + + + diff --git a/src/views/Login/components/index.ts b/src/views/Login/components/index.ts new file mode 100644 index 0000000..aee8f27 --- /dev/null +++ b/src/views/Login/components/index.ts @@ -0,0 +1,3 @@ +import LoginForm from './LoginForm.vue' + +export { LoginForm } diff --git a/src/views/Redirect/Redirect.vue b/src/views/Redirect/Redirect.vue new file mode 100644 index 0000000..0b7af7b --- /dev/null +++ b/src/views/Redirect/Redirect.vue @@ -0,0 +1,30 @@ + + diff --git a/src/views/hooks/useCrudSchemas.vue b/src/views/hooks/useCrudSchemas.vue new file mode 100644 index 0000000..b8f6e7a --- /dev/null +++ b/src/views/hooks/useCrudSchemas.vue @@ -0,0 +1,223 @@ + + + diff --git a/src/views/hooks/useWatermark.vue b/src/views/hooks/useWatermark.vue new file mode 100644 index 0000000..88029c0 --- /dev/null +++ b/src/views/hooks/useWatermark.vue @@ -0,0 +1,32 @@ + + + diff --git a/stylelint.config.js b/stylelint.config.js new file mode 100644 index 0000000..d400572 --- /dev/null +++ b/stylelint.config.js @@ -0,0 +1,231 @@ +module.exports = { + root: true, + plugins: ['stylelint-order'], + customSyntax: 'postcss-html', + extends: ['stylelint-config-standard', 'stylelint-config-prettier'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'deep'] + } + ], + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin'] + } + ], + 'no-empty-source': null, + 'named-grid-areas-no-invalid': null, + 'unicode-bom': 'never', + 'no-descending-specificity': null, + 'font-family-no-missing-generic-family-keyword': null, + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + 'declaration-block-trailing-semicolon': null, + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'] + } + ], + 'unit-no-unknown': [ + true, + { + ignoreUnits: ['rpx'] + } + ], + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + type: 'at-rule', + name: 'supports' + }, + { + type: 'at-rule', + name: 'media' + }, + 'rules' + ], + { + severity: 'warning' + } + ], + // Specify the alphabetical order of the attributes in the declaration block + 'order/properties-order': [ + 'position', + 'top', + 'right', + 'bottom', + 'left', + 'z-index', + 'display', + 'float', + 'width', + 'height', + 'max-width', + 'max-height', + 'min-width', + 'min-height', + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + 'margin-collapse', + 'margin-top-collapse', + 'margin-right-collapse', + 'margin-bottom-collapse', + 'margin-left-collapse', + 'overflow', + 'overflow-x', + 'overflow-y', + 'clip', + 'clear', + 'font', + 'font-family', + 'font-size', + 'font-smoothing', + 'osx-font-smoothing', + 'font-style', + 'font-weight', + 'hyphens', + 'src', + 'line-height', + 'letter-spacing', + 'word-spacing', + 'color', + 'text-align', + 'text-decoration', + 'text-indent', + 'text-overflow', + 'text-rendering', + 'text-size-adjust', + 'text-shadow', + 'text-transform', + 'word-break', + 'word-wrap', + 'white-space', + 'vertical-align', + 'list-style', + 'list-style-type', + 'list-style-position', + 'list-style-image', + 'pointer-events', + 'cursor', + 'background', + 'background-attachment', + 'background-color', + 'background-image', + 'background-position', + 'background-repeat', + 'background-size', + 'border', + 'border-collapse', + 'border-top', + 'border-right', + 'border-bottom', + 'border-left', + 'border-color', + 'border-image', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color', + 'border-spacing', + 'border-style', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + 'border-width', + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + 'border-radius', + 'border-top-right-radius', + 'border-bottom-right-radius', + 'border-bottom-left-radius', + 'border-top-left-radius', + 'border-radius-topright', + 'border-radius-bottomright', + 'border-radius-bottomleft', + 'border-radius-topleft', + 'content', + 'quotes', + 'outline', + 'outline-offset', + 'opacity', + 'filter', + 'visibility', + 'size', + 'zoom', + 'transform', + 'box-align', + 'box-flex', + 'box-orient', + 'box-pack', + 'box-shadow', + 'box-sizing', + 'table-layout', + 'animation', + 'animation-delay', + 'animation-duration', + 'animation-iteration-count', + 'animation-name', + 'animation-play-state', + 'animation-timing-function', + 'animation-fill-mode', + 'transition', + 'transition-delay', + 'transition-duration', + 'transition-property', + 'transition-timing-function', + 'background-clip', + 'backface-visibility', + 'resize', + 'appearance', + 'user-select', + 'interpolation-mode', + 'direction', + 'marks', + 'page', + 'set-link-source', + 'unicode-bidi', + 'speak' + ] + }, + ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], + overrides: [ + { + files: ['*.vue', '**/*.vue', '*.html', '**/*.html'], + extends: ['stylelint-config-recommended', 'stylelint-config-html'], + rules: { + 'keyframes-name-pattern': null, + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['deep', 'global'] + } + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'] + } + ] + } + } + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d53cf62 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "baseUrl": ".", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "noImplicitAny": false, + "skipLibCheck": true, + "paths": { + "@/*": ["src/*"] + }, + "types": [ + "@intlify/unplugin-vue-i18n/types", + "vite/client", + "element-plus/global", + "@types/intro.js", + "@types/qrcode", + "vite-plugin-svg-icons/client", + "unplugin-vue-define-options/macros-global" + ], + "typeRoots": ["./node_modules/@types/", "./types"] + }, + "include": ["src/**/*", "types/**/*.d.ts", "mock/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/types/components.d.ts b/types/components.d.ts new file mode 100644 index 0000000..85db566 --- /dev/null +++ b/types/components.d.ts @@ -0,0 +1,7 @@ +declare module 'vue' { + export interface GlobalComponents { + Icon: typeof import('../components/Icon/src/Icon.vue')['default'] + } +} + +export {} diff --git a/types/custom-types.d.ts b/types/custom-types.d.ts new file mode 100644 index 0000000..3ef553c --- /dev/null +++ b/types/custom-types.d.ts @@ -0,0 +1,27 @@ +import { SlateDescendant } from '@wangeditor/editor' + +declare module 'slate' { + interface CustomTypes { + // 扩展 text + Text: { + text: string + bold?: boolean + italic?: boolean + code?: boolean + through?: boolean + underline?: boolean + sup?: boolean + sub?: boolean + color?: string + bgColor?: string + fontSize?: string + fontFamily?: string + } + + // 扩展 Element 的 type 属性 + Element: { + type: string + children: SlateDescendant[] + } + } +} diff --git a/types/env.d.ts b/types/env.d.ts new file mode 100644 index 0000000..417a62b --- /dev/null +++ b/types/env.d.ts @@ -0,0 +1,26 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} + +interface ImportMetaEnv { + readonly VITE_APP_TITLE: string + readonly VITE_API_BASEPATH: string + readonly VITE_BASE_PATH: string + readonly VITE_DROP_DEBUGGER: string + readonly VITE_DROP_CONSOLE: string + readonly VITE_SOURCEMAP: string + readonly VITE_OUT_DIR: string + readonly VITE_BASE_URL: string + readonly VITE_API_URL: string +} + +declare global { + interface ImportMeta { + readonly env: ImportMetaEnv + } +} diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 0000000..7314557 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,39 @@ +import type { CSSProperties } from 'vue' +declare global { + declare interface Fn { + (...arg: T[]): T + } + + declare type Nullable = T | null + + declare type ElRef = Nullable + + declare type Recordable = Record + + declare type ComponentRef = InstanceType + + declare type LocaleType = 'zh-CN' | 'en' + + declare type AxiosHeaders = + | 'application/json' + | 'application/x-www-form-urlencoded' + | 'multipart/form-data' + + declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put' + + declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' + + declare interface AxiosConfig { + params?: any + data?: any + url?: string + method?: AxiosMethod + headersType?: string + responseType?: AxiosResponseType + } + + declare interface IResponse { + code: string + data: T extends any ? T : T & any + } +} diff --git a/types/router.d.ts b/types/router.d.ts new file mode 100644 index 0000000..00c7443 --- /dev/null +++ b/types/router.d.ts @@ -0,0 +1,74 @@ +import type { RouteRecordRaw } from 'vue-router' +import { defineComponent } from 'vue' + +/** +* redirect: noredirect 当设置 noredirect 的时候该路由在面包屑导航中不可被点击 +* name:'router-name' 设定路由的名字,一定要填写不然使用时会出现各种问题 +* meta : { + hidden: true 当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false) + + alwaysShow: true 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式, + 只有一个时,会将那个子路由当做根路由显示在侧边栏, + 若你想不管路由下面的 children 声明的个数都显示你的根路由, + 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则, + 一直显示根路由(默认 false) + + title: 'title' 设置该路由在侧边栏和面包屑中展示的名字 + + icon: 'svg-name' 设置该路由的图标 + + noCache: true 如果设置为true,则不会被 缓存(默认 false) + + breadcrumb: false 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) + + affix: true 如果设置为true,则会一直固定在tag项中(默认 false) + + noTagsView: true 如果设置为true,则不会出现在tag中(默认 false) + + activeMenu: '/dashboard' 显示高亮的路由路径 + + followAuth: '/dashboard' 跟随哪个路由进行权限过滤 + + canTo: true 设置为true即使hidden为true,也依然可以进行路由跳转(默认 false) + } +**/ +declare module 'vue-router' { + interface RouteMeta extends Record { + hidden?: boolean + alwaysShow?: boolean + title?: string + icon?: string + noCache?: boolean + breadcrumb?: boolean + affix?: boolean + activeMenu?: string + noTagsView?: boolean + followAuth?: string + canTo?: boolean + } +} + +type Component = + | ReturnType + | (() => Promise) + | (() => Promise) + +declare global { + declare interface AppRouteRecordRaw extends Omit { + name: string + meta: RouteMeta + component?: Component | string + children?: AppRouteRecordRaw[] + props?: Recordable + fullPath?: string + } + + declare interface AppCustomRouteRecordRaw extends Omit { + name: string + meta: RouteMeta + component: string + path: string + redirect: string + children?: AppCustomRouteRecordRaw[] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..827e597 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,149 @@ +import { resolve } from 'path' +import { loadEnv } from 'vite' +import type { UserConfig, ConfigEnv } from 'vite' +import Vue from '@vitejs/plugin-vue' +import VueJsx from '@vitejs/plugin-vue-jsx' +import WindiCSS from 'vite-plugin-windicss' +import progress from 'vite-plugin-progress' +import EslintPlugin from 'vite-plugin-eslint' +import { ViteEjsPlugin } from "vite-plugin-ejs" +import { viteMockServe } from 'vite-plugin-mock' +import PurgeIcons from 'vite-plugin-purge-icons' +import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite" +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' +import DefineOptions from "unplugin-vue-define-options/vite" +import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' + +// https://vitejs.dev/config/ +const root = process.cwd() + +function pathResolve(dir: string) { + return resolve(root, '.', dir) +} + +export default ({ command, mode }: ConfigEnv): UserConfig => { + let env = {} as any + const isBuild = command === 'build' + if (!isBuild) { + env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root) + } else { + env = loadEnv(mode, root) + } + return { + base: env.VITE_BASE_PATH, + plugins: [ + Vue(), + VueJsx(), + WindiCSS(), + progress(), + createStyleImportPlugin({ + resolves: [ElementPlusResolve()], + libs: [{ + libraryName: 'element-plus', + esModule: true, + resolveStyle: (name) => { + return `element-plus/es/components/${name.substring(3)}/style/css` + } + }] + }), + EslintPlugin({ + cache: false, + include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件 + }), + VueI18nPlugin({ + runtimeOnly: true, + compositionOnly: true, + include: [resolve(__dirname, 'src/locales/**')] + }), + createSvgIconsPlugin({ + iconDirs: [pathResolve('src/assets/svgs')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: true + }), + PurgeIcons(), + viteMockServe({ + ignore: /^\_/, + mockPath: 'mock', + localEnabled: !isBuild, + prodEnabled: isBuild, + injectCode: ` + import { setupProdMockServer } from '../mock/_createProductionServer' + + setupProdMockServer() + ` + }), + DefineOptions(), + ViteEjsPlugin({ + title: env.VITE_APP_TITLE + }) + ], + + css: { + preprocessorOptions: { + less: { + additionalData: '@import "./src/styles/variables.module.less";', + javascriptEnabled: true + } + } + }, + resolve: { + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.less', '.css'], + alias: [ + { + find: 'vue-i18n', + replacement: 'vue-i18n/dist/vue-i18n.cjs.js' + }, + { + find: /\@\//, + replacement: `${pathResolve('src')}/` + } + ] + }, + build: { + minify: 'terser', + outDir: env.VITE_OUT_DIR || 'dist', + sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false, + // brotliSize: false, + terserOptions: { + compress: { + drop_debugger: env.VITE_DROP_DEBUGGER === 'true', + drop_console: env.VITE_DROP_CONSOLE === 'true' + } + } + }, + server: { + port: 4000, + proxy: { + // 选项写法 + '/base': { + target: env.VITE_BASE_URL, + changeOrigin: true, + rewrite: path => path.replace(/^\/base/, '') + } + }, + hmr: { + overlay: false + }, + host: '0.0.0.0' + }, + optimizeDeps: { + include: [ + 'vue', + 'vue-router', + 'vue-types', + 'element-plus/es/locale/lang/zh-cn', + 'element-plus/es/locale/lang/en', + '@iconify/iconify', + '@vueuse/core', + 'axios', + 'qs', + 'echarts', + 'echarts-wordcloud', + 'intro.js', + 'qrcode', + '@wangeditor/editor', + '@wangeditor/editor-for-vue' + ] + } + } +} diff --git a/windi.config.ts b/windi.config.ts new file mode 100644 index 0000000..4ef7b06 --- /dev/null +++ b/windi.config.ts @@ -0,0 +1,76 @@ +import { defineConfig } from 'windicss/helpers' +import plugin from 'windicss/plugin' + +function range(size, startAt = 1) { + return Array.from(Array(size).keys()).map((i) => i + startAt) +} + +export default defineConfig({ + extract: { + include: ['src/**/*.{vue,html,jsx,tsx}'], + exclude: ['node_modules', '.git'] + }, + darkMode: 'class', + attributify: false, + theme: { + extend: { + backgroundColor: { + // 暗黑背景色 + 'v-dark': 'var(--dark-bg-color)' + } + // screens: { + // sm: '768px', + // md: '992px', + // lg: '1200px', + // xl: '1920px' + // } + } + // height: { + // ...range(50).map((i) => `h-${i}px`) + // }, + // margin: { + // // ...range(50).map((i) => `mt-${i}px`), + // // ...range(50).map((i) => `mr-${i}px`), + // // ...range(50).map((i) => `mb-${i}px`), + // // ...range(50).map((i) => `ml-${i}px`) + // } + }, + plugins: [ + plugin(({ addComponents }) => { + const obj = {} + range(50).map((i) => { + obj[`.border-top-${i}`] = { + borderTopWidth: `${i}px` + } + obj[`.border-left-${i}`] = { + borderLeftWidth: `${i}px` + } + obj[`.border-right-${i}`] = { + borderRightWidth: `${i}px` + } + obj[`.border-bottom-${i}`] = { + borderBottomWidth: `${i}px` + } + }) + addComponents({ + '.hover-trigger': { + display: 'flex', + height: '100%', + padding: '1px 10px 0', + cursor: 'pointer', + alignItems: 'center', + transition: 'background var(--transition-time-02)', + '&:hover': { + backgroundColor: 'var(--top-header-hover-color)' + } + }, + '.dark .hover-trigger': { + '&:hover': { + backgroundColor: 'var(--el-bg-color-overlay)' + } + }, + ...obj + }) + }) + ] +})