├── .cz-config.js ├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky ├── commit-msg ├── common.sh └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build ├── config │ └── themeConfig.ts ├── constant.ts ├── generate │ └── generateModifyVars.ts ├── getConfigFileName.ts ├── script │ ├── buildConf.ts │ └── postBuild.ts ├── utils.ts └── vite │ ├── plugin │ ├── components.ts │ ├── html.ts │ ├── index.ts │ ├── mock.ts │ ├── svgSprite.ts │ └── windicss.ts │ └── proxy.ts ├── commitlint.config.js ├── index.html ├── mock ├── _createProductionServer.ts ├── _util.ts ├── demo │ ├── account.ts │ └── table-demo.ts └── sys │ ├── menu.ts │ └── user.ts ├── package.json ├── plop-templates ├── api │ ├── index.hbs │ └── prompt.js ├── component │ ├── index.hbs │ └── prompt.js ├── store │ ├── index.hbs │ └── prompt.js └── views │ ├── index.hbs │ └── prompt.js ├── plopfile.js ├── pnpm-lock.yaml ├── public └── favicon.ico ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── src │ └── main.rs └── tauri.conf.json ├── src ├── App.vue ├── api │ ├── demo │ │ └── table.ts │ ├── sys │ │ └── account.ts │ └── test │ │ └── common.ts ├── assets │ ├── bg.svg │ ├── icons │ │ ├── logo.svg │ │ ├── moon.svg │ │ └── sun.svg │ ├── images │ │ ├── header.jpg │ │ └── login │ │ │ └── login_banner.png │ └── logo.png ├── components │ ├── Application │ │ ├── AppConfigure.vue │ │ ├── AppDarkModeToggle.vue │ │ ├── AppLocalePicker.vue │ │ ├── AppLogo.vue │ │ ├── index.ts │ │ ├── search │ │ │ ├── AppSearch.vue │ │ │ ├── AppSearchFooter.vue │ │ │ ├── AppSearchModal.vue │ │ │ └── useMenuSearch.ts │ │ └── useAppContext.ts │ ├── Basic │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicTips.vue │ │ │ └── BasicTitle.vue │ ├── Card │ │ ├── CardGrid.vue │ │ ├── CardGridItem.vue │ │ ├── CollapseCard.vue │ │ ├── CollapseCard_legacy.vue │ │ └── LoadingCard.vue │ ├── Container │ │ ├── index.ts │ │ └── src │ │ │ ├── LazyContainer.vue │ │ │ ├── ScrollContainer.vue │ │ │ └── typing.ts │ ├── CountDown │ │ ├── CountButton.vue │ │ ├── CountdownInput.vue │ │ └── useCountdown.ts │ ├── CountTo │ │ └── CountTo.vue │ ├── Form │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicForm.vue │ │ │ ├── componentMap.ts │ │ │ ├── helper.ts │ │ │ ├── hooks │ │ │ ├── useForm.ts │ │ │ ├── useFormContext.ts │ │ │ ├── useFormEvents.ts │ │ │ └── useFormValues.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── form.ts │ │ │ └── index.ts │ ├── Icon │ │ ├── data │ │ │ └── icons.data.ts │ │ ├── index.ts │ │ └── src │ │ │ ├── Icon.vue │ │ │ └── SvgIcon.vue │ ├── Loading │ │ ├── index.ts │ │ └── src │ │ │ ├── Loading.vue │ │ │ ├── createLoading.ts │ │ │ ├── type.ts │ │ │ └── useLoading.ts │ ├── Modal │ │ ├── README.MD │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicModal.vue │ │ │ ├── components │ │ │ ├── Modal.tsx │ │ │ ├── ModalClose.vue │ │ │ ├── ModalFooter.vue │ │ │ ├── ModalHeader.vue │ │ │ └── ModalWrapper.vue │ │ │ ├── hooks │ │ │ ├── useModal.ts │ │ │ ├── useModalContext.ts │ │ │ ├── useModalDrag.ts │ │ │ └── useModalFullScreen.ts │ │ │ ├── index.less │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── Page │ │ ├── PageFooter.vue │ │ └── PageWrapper.vue │ ├── Scrollbar │ │ ├── index.ts │ │ └── src │ │ │ ├── Scrollbar.vue │ │ │ ├── bar.ts │ │ │ ├── types.d.ts │ │ │ └── util.ts │ ├── Table │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicTable.vue │ │ │ ├── componentMap.ts │ │ │ ├── components │ │ │ ├── actions │ │ │ │ ├── TableAction.vue │ │ │ │ └── popConfirmWrapper.vue │ │ │ ├── editable │ │ │ │ ├── CellComponent.ts │ │ │ │ ├── EditableCell.vue │ │ │ │ ├── helper.ts │ │ │ │ └── index.ts │ │ │ └── settings │ │ │ │ └── ColumnSetting.vue │ │ │ ├── const.ts │ │ │ ├── hooks │ │ │ ├── useColumns.tsx │ │ │ ├── useDataSource.ts │ │ │ ├── useLoading.ts │ │ │ ├── usePagination.ts │ │ │ ├── useTableContext.ts │ │ │ └── useTableDropdown.tsx │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── componentType.ts │ │ │ ├── dropdown.ts │ │ │ ├── pagination.ts │ │ │ ├── table.ts │ │ │ └── tableAction.ts │ ├── Time │ │ ├── index.vue │ │ └── type.ts │ └── Transition │ │ ├── index.ts │ │ └── src │ │ ├── CollapseTransition.vue │ │ ├── CreateTransition.tsx │ │ └── ExpandTransition.ts ├── design │ ├── config.less │ ├── entry.css │ ├── index.less │ ├── transition │ │ ├── base.less │ │ ├── fade.less │ │ ├── index.less │ │ ├── scale.less │ │ ├── scroll.less │ │ ├── slide.less │ │ └── zoom.less │ └── var │ │ ├── easing.less │ │ └── index.less ├── directives │ ├── clickOutside.ts │ ├── index.ts │ ├── loading.ts │ └── permission.ts ├── enums │ ├── appEnum.ts │ ├── breakpointEnum.ts │ ├── cacheEnum.ts │ ├── exceptionEnum.ts │ ├── httpEnum.ts │ ├── menuEnum.ts │ ├── pageEnum.ts │ ├── roleEnum.ts │ └── sizeEnum.ts ├── hooks │ ├── component │ │ ├── useDrawer.ts │ │ └── useFormItem.ts │ ├── core │ │ ├── useAttrs.ts │ │ ├── useContext.ts │ │ ├── useNaiveInternal.ts │ │ ├── useRefs.ts │ │ └── useTimeout.ts │ ├── event │ │ ├── useBreakpoint.ts │ │ ├── useIntersectionObserver.ts │ │ ├── useScrollTo.ts │ │ └── useWindowSizeFn.ts │ ├── setting │ │ ├── index.ts │ │ ├── useHeaderSetting.ts │ │ ├── useMenuSetting.ts │ │ ├── useMultipleTabSetting.ts │ │ ├── useRootSetting.ts │ │ └── useTransitionSetting.ts │ ├── utilities │ │ └── toWritableRef.ts │ └── web │ │ ├── useAppInject.ts │ │ ├── useChaneTheme.ts │ │ ├── useDesign.ts │ │ ├── useECharts.ts │ │ ├── useFullContent.ts │ │ ├── useI18n.ts │ │ ├── useMessage.ts │ │ ├── usePage.ts │ │ ├── usePermission.ts │ │ ├── useTabs.ts │ │ └── useTitle.ts ├── layouts │ ├── default │ │ ├── content │ │ │ ├── index.vue │ │ │ └── useContentSizeContext.ts │ │ ├── footer │ │ │ └── index.vue │ │ ├── header │ │ │ ├── components │ │ │ │ ├── Breadcrumb.vue │ │ │ │ ├── FullScreen.vue │ │ │ │ ├── UserDropdown.vue │ │ │ │ ├── index.tsx │ │ │ │ └── notify │ │ │ │ │ ├── NoticeList.vue │ │ │ │ │ ├── data.ts │ │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── menu │ │ │ ├── index.vue │ │ │ └── useLayoutMenu.tsx │ │ ├── setting │ │ │ ├── SettingDrawer.vue │ │ │ ├── components │ │ │ │ ├── InputNumberItem.vue │ │ │ │ ├── LayoutModePicker.vue │ │ │ │ ├── Picker.vue │ │ │ │ ├── SelectItem.vue │ │ │ │ ├── SettingFooter.vue │ │ │ │ ├── SliderItem.vue │ │ │ │ ├── SwitchItem.vue │ │ │ │ ├── ThemeColorPicker.vue │ │ │ │ ├── ThemeModePicker.vue │ │ │ │ ├── Wrapper.vue │ │ │ │ └── index.ts │ │ │ ├── enum.ts │ │ │ ├── handler.ts │ │ │ └── index.vue │ │ ├── sider │ │ │ ├── LayoutSider.vue │ │ │ ├── MixSider.vue │ │ │ ├── SiderWrapper.vue │ │ │ └── index.vue │ │ ├── tabs │ │ │ ├── components │ │ │ │ ├── Button.vue │ │ │ │ ├── FoldButton.vue │ │ │ │ ├── Smart.vue │ │ │ │ ├── TabContent.vue │ │ │ │ ├── TabRedo.vue │ │ │ │ └── useExtra.ts │ │ │ ├── index.vue │ │ │ ├── types.ts │ │ │ ├── useMultipleTabs.ts │ │ │ └── useTabDropdown.tsx │ │ └── trigger │ │ │ ├── BarTrigger.vue │ │ │ ├── BottomTrigger.vue │ │ │ ├── ButtonTrigger.vue │ │ │ ├── HeaderTrigger.vue │ │ │ └── index.vue │ ├── iframe │ │ ├── index.vue │ │ └── useFrameKeepAlive.ts │ └── page │ │ ├── index.vue │ │ ├── useTransition.ts │ │ └── useTransitonContext.ts ├── locales │ ├── helper.ts │ ├── lang │ │ ├── en.ts │ │ ├── en │ │ │ ├── common.ts │ │ │ ├── component.ts │ │ │ ├── layout.ts │ │ │ ├── routes │ │ │ │ ├── basic.ts │ │ │ │ ├── dashboard.ts │ │ │ │ └── demo.ts │ │ │ └── sys.ts │ │ ├── zh_CN.ts │ │ └── zh_CN │ │ │ ├── common.ts │ │ │ ├── component.ts │ │ │ ├── layout.ts │ │ │ ├── routes │ │ │ ├── basic.ts │ │ │ ├── dashboard.ts │ │ │ └── demo.ts │ │ │ └── sys.ts │ ├── setupI18n.ts │ └── useLocale.ts ├── logics │ ├── error-handle │ │ └── index.ts │ ├── initAppConfig.ts │ ├── mitt │ │ ├── layoutContentResize.ts │ │ └── routeChange.ts │ └── theme │ │ ├── dark.ts │ │ └── util.ts ├── main.ts ├── router │ ├── constant.ts │ ├── guard │ │ ├── index.ts │ │ ├── permissionGuard.ts │ │ └── stateGuard.ts │ ├── helper │ │ ├── menuHelper.ts │ │ └── routeHelper.ts │ ├── index.ts │ ├── menus │ │ └── index.ts │ ├── routes │ │ ├── basic.ts │ │ ├── index.ts │ │ ├── mainOut.ts │ │ └── modules │ │ │ ├── about.ts │ │ │ ├── dashboard.ts │ │ │ └── demo │ │ │ ├── accout.ts │ │ │ ├── comp.ts │ │ │ ├── desc.ts │ │ │ ├── exception.ts │ │ │ ├── form.ts │ │ │ ├── iframe.ts │ │ │ ├── level.ts │ │ │ ├── list.ts │ │ │ └── result.ts │ └── types.ts ├── service │ ├── annotations.ts │ ├── common.ts │ └── index.ts ├── settings │ ├── componentSetting.ts │ ├── designSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ ├── projectSetting.ts │ └── siteSetting.ts ├── store │ ├── index.ts │ └── modules │ │ ├── app.ts │ │ ├── errorLog.ts │ │ ├── locale.ts │ │ ├── multipleTab.ts │ │ ├── permission.ts │ │ ├── service.ts │ │ └── user.ts ├── utils │ ├── auth │ │ └── index.ts │ ├── cache │ │ ├── index.ts │ │ ├── memory.ts │ │ ├── persistent.ts │ │ └── storageCache.ts │ ├── cipher.ts │ ├── dateUtil.ts │ ├── domUtils.ts │ ├── env.ts │ ├── event │ │ └── index.ts │ ├── factory │ │ └── createAsyncComponent.tsx │ ├── helper │ │ ├── tabsHelper.ts │ │ ├── treeHelper.ts │ │ └── tsxHelper.tsx │ ├── http │ │ └── axios │ │ │ ├── Axios.ts │ │ │ ├── axiosCancel.ts │ │ │ ├── axiosTransform.ts │ │ │ ├── checkStatus.ts │ │ │ ├── helper.ts │ │ │ └── index.ts │ ├── index.ts │ ├── is.ts │ ├── lib │ │ └── echarts.ts │ ├── log.ts │ └── propTypes.ts └── views │ ├── dashboard │ ├── analysis │ │ ├── components │ │ │ ├── BarLineMixChart.vue │ │ │ ├── Gauge.vue │ │ │ ├── GrowCard.vue │ │ │ ├── RingPie.vue │ │ │ ├── SiteAnalysis.vue │ │ │ ├── VisitAnalysis.vue │ │ │ ├── VisitAnalysisBar.vue │ │ │ ├── chartProps.ts │ │ │ ├── data.ts │ │ │ └── useLoading.ts │ │ └── index.vue │ └── workbench │ │ ├── components │ │ ├── Dynamicinfo.vue │ │ ├── ProjectCard.vue │ │ ├── QuickNav.vue │ │ ├── Team.vue │ │ ├── TeamCard.vue │ │ ├── WorkbenchHeader.vue │ │ ├── WorkbenchRadar.vue │ │ └── data.tsx │ │ └── index.vue │ ├── demo │ ├── comp │ │ ├── form │ │ │ ├── basic.vue │ │ │ ├── data.tsx │ │ │ └── useForm.vue │ │ ├── loading │ │ │ └── basic.vue │ │ ├── modal │ │ │ ├── Modal1.vue │ │ │ ├── Modal2.vue │ │ │ ├── Modal3.vue │ │ │ ├── Modal4.vue │ │ │ └── basic.vue │ │ └── table │ │ │ ├── basic.vue │ │ │ ├── columns.ts │ │ │ └── data.ts │ ├── main-out │ │ └── index.vue │ └── page │ │ ├── account │ │ ├── center │ │ │ ├── accountCenterPage.vue │ │ │ └── components │ │ │ │ ├── LeftCard.vue │ │ │ │ ├── RightCard.vue │ │ │ │ └── data.tsx │ │ └── setting │ │ │ ├── accountSettingPage.vue │ │ │ └── components │ │ │ ├── AccountBind.vue │ │ │ ├── BaseSetting.vue │ │ │ ├── MsgNotify.vue │ │ │ ├── SecureSetting.vue │ │ │ └── data.tsx │ │ ├── desc │ │ ├── basic │ │ │ ├── basicDescPage.vue │ │ │ └── data.tsx │ │ └── high │ │ │ ├── data.tsx │ │ │ └── highDescPage.vue │ │ ├── form │ │ ├── basicFormPage.vue │ │ ├── high │ │ │ ├── PersonTable.vue │ │ │ ├── data.ts │ │ │ └── highFormPage.vue │ │ └── step │ │ │ ├── Step1.vue │ │ │ ├── Step2.vue │ │ │ ├── Step3.vue │ │ │ ├── data.ts │ │ │ └── stepFormPage.vue │ │ ├── level │ │ ├── Menu111.vue │ │ ├── Menu12.vue │ │ ├── Menu2.vue │ │ └── Wrapper.vue │ │ ├── list │ │ ├── basicListPage.vue │ │ ├── cardListPage.vue │ │ └── components │ │ │ ├── DemoCard.vue │ │ │ └── DemoList.vue │ │ └── result │ │ ├── failResultPage.vue │ │ └── successResultPage.vue │ └── sys │ ├── about │ └── index.vue │ ├── exception │ ├── Exception.vue │ └── index.ts │ ├── iframe │ ├── FrameBlank.vue │ └── index.vue │ ├── login │ ├── Login.vue │ ├── LoginForm.vue │ ├── MobileForm.vue │ └── useLogin.ts │ └── redirect │ └── index.vue ├── tsconfig.json ├── types ├── axios.d.ts ├── config.d.ts ├── expose.d.ts ├── global.d.ts ├── index.d.ts ├── model.d.ts ├── model │ ├── account │ │ ├── accountModel.ts │ │ ├── menuModel.ts │ │ └── userModel.ts │ ├── baseModel.ts │ ├── demo │ │ └── tableModel.ts │ └── uploadModel.ts ├── store.d.ts └── vue-router.d.ts ├── uno.config.ts └── vite.config.mts /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: [ 3 | { value: 'feat', name: 'feat: 增加新功能' }, 4 | { value: 'fix', name: 'fix: 修复bug' }, 5 | { value: 'docs', name: 'docs: 文档/注释' }, 6 | { value: 'style', name: 'style: 代码风格相关不影响运行结果' }, 7 | { value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' }, 8 | { value: 'perf', name: 'perf: 优化/性能提升' }, 9 | { value: 'test', name: 'test: 添加、修改测试用例' }, 10 | { value: 'build', name: 'build: 构建/打包 流程' }, 11 | { value: 'workflow', name: 'workflow: 工作流改进' }, 12 | { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, 13 | { value: 'chore', name: 'chore: 依赖更新/脚手架配置修改等' }, 14 | { value: 'revert', name: 'revert: 撤销修改' }, 15 | { value: 'wip', name: 'wip: 开发中' } 16 | ], 17 | messages: { 18 | type: '遵循 Angular 提交规范!\n选择你要提交的类型:', 19 | customScope: '请输入 scope:', 20 | subject: '填写简短的变更描述:\n', 21 | body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n', 22 | breaking: '列举非兼容性重大的变更(可选):\n', 23 | footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n', 24 | confirmCommit: '确认提交?' 25 | }, 26 | allowBreakingChanges: ['feat', 'fix'], 27 | subjectLimit: 100, 28 | breaklineChar: '|' 29 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | 3 | # 表示是最顶层的 EditorConfig 配置文件 4 | root = true 5 | 6 | [*] # 表示所有文件适用 7 | charset = utf-8 # 设置文件字符集为 utf-8 8 | indent_style = tab # 缩进风格(tab | space) 9 | indent_size = 2 # 缩进大小 10 | end_of_line = lf # 控制换行类型(lf | cr | crlf) 11 | trim_trailing_whitespace = true # 去除行首的任意空白字符 12 | insert_final_newline = true # 始终在文件末尾插入一个新行 -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # port 2 | VITE_PORT = 3100 3 | 4 | # spa-title 5 | VITE_GLOB_APP_TITLE = Aso.design 6 | 7 | # spa shortname 8 | VITE_GLOB_APP_SHORT_NAME = aso_design 9 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Cross-domain proxy, you can configure multiple 8 | # Please note that no line breaks 9 | VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]] 10 | 11 | # Delete console 12 | VITE_DROP_CONSOLE = false 13 | 14 | # Basic interface address SPA 15 | VITE_GLOB_API_URL=/basic-api 16 | 17 | # File upload address, optional 18 | VITE_GLOB_UPLOAD_URL=/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Delete console 8 | VITE_DROP_CONSOLE = true 9 | 10 | # Basic interface address SPA 11 | VITE_GLOB_API_URL=/basic-api 12 | 13 | # File upload address, optional 14 | # It can be forwarded by nginx or write the actual address directly 15 | VITE_GLOB_UPLOAD_URL=/upload 16 | 17 | # Interface prefix 18 | VITE_GLOB_API_URL_PREFIX= 19 | 20 | # Whether to enable image compression 21 | VITE_USE_IMAGEMIN= true 22 | 23 | # use pwa 24 | VITE_USE_PWA = false 25 | 26 | # Is it compatible with older browsers 27 | VITE_LEGACY = false 28 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | lib 4 | *.md 5 | *.woff 6 | *.ttf 7 | .vscode 8 | .idea 9 | /dist/ 10 | /mock/ 11 | /public 12 | /docs 13 | .vscode 14 | .local 15 | index.html 16 | 17 | src/views/demo/main-out/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /types/components.d.ts 5 | *.sketch 6 | 7 | /src/views/**/Test* 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | pnpm-debug.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command_exists () { 3 | command -v "$1" >/dev/null 2>&1 4 | } 5 | 6 | # Workaround for Windows 10, Git Bash and Yarn 7 | if command_exists winpty && test -t 1; then 8 | exec < /dev/tty 9 | fi -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint:fix -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | /public/* -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, // 超过最大值换行 3 | tabWidth: 2, // 缩进字节数 4 | useTabs: false, // 缩进使用tab,不使用空格 5 | semi: true, // 句尾添加分号 6 | vueIndentScriptAndStyle: true, 7 | singleQuote: true, // 使用单引号代替双引号 8 | bracketSpacing: true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }" 9 | trailingComma: 'es5', // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号) 10 | jsxSingleQuote: false, // 在jsx中使用单引号代替双引号 11 | arrowParens: 'always', // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 12 | proseWrap: 'preserve', // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行 13 | htmlWhitespaceSensitivity: 'strict', // 指定HTML文件的全局空白区域敏感度 有效选项:"css"- 遵守CSS display属性的默认值。"strict" - 空格被认为是敏感的。"ignore" - 空格被认为是不敏感的。html 中空格也会占位,影响布局,prettier 格式化的时候可能会将文本换行,造成布局错乱 14 | endOfLine: 'auto', // 结尾是 \n \r \n\r auto 15 | }; 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 (2022-03-30) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * 修复点击空白区域右键菜单不隐藏bug ([e24e709](https://gitee.com/thunur/naive-admin/commits/e24e709083b7be1e649c1d403ce685e5f0bc502b)) 7 | * 修改Chrome下激活页签hover字体颜色 ([10c8546](https://gitee.com/thunur/naive-admin/commits/10c8546485c0c06fd905036382b0831d2b5a3862)) 8 | * 修改Chrome样式下黑暗模式下颜色不对的bug ([7902279](https://gitee.com/thunur/naive-admin/commits/7902279625a07932f9df3436f0a3b62e0046821a)) 9 | * 修改tabs动画重复的导致不生效 ([91a7481](https://gitee.com/thunur/naive-admin/commits/91a748159130632f3a2440755d4e0cf72134dff5)) 10 | * **移除原有tabs:** 移除原有tabs ([a1f6082](https://gitee.com/thunur/naive-admin/commits/a1f6082556a4ba5ee4f411302f038df581b6b084)) 11 | 12 | 13 | ### Features 14 | 15 | * 完成表格右键菜单 ([2ba0f6c](https://gitee.com/thunur/naive-admin/commits/2ba0f6c800280a1125abcc609399ade343ab6522)) 16 | * 修改tabs ([b4d2161](https://gitee.com/thunur/naive-admin/commits/b4d2161f773ebe402318eade370962c9be0ad346)) 17 | * 增加按钮样式调整 ([884bc68](https://gitee.com/thunur/naive-admin/commits/884bc683ba7d33d66b03c59215c83e67d80dff82)) 18 | 19 | 20 | ### Performance Improvements 21 | 22 | * 分栏模式下左侧菜单样式修改 ([db515e6](https://gitee.com/thunur/naive-admin/commits/db515e6a67db80db477c95c49f304ec0c6dc78ab)) 23 | * 优化modal开启拖动的鼠标样式 ([1424dd6](https://gitee.com/thunur/naive-admin/commits/1424dd67f0d8368facafbeb56e7601f1cfe012c1)) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 aso.design 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/config/themeConfig.ts: -------------------------------------------------------------------------------- 1 | export const primaryColor = '#0f62fe'; 2 | 3 | export const darkMode = 'light'; 4 | -------------------------------------------------------------------------------- /build/constant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The name of the configuration file entered in the production environment 3 | */ 4 | export const GLOB_CONFIG_FILE_NAME = '_app.config.js'; 5 | 6 | export const OUTPUT_DIR = 'dist'; 7 | -------------------------------------------------------------------------------- /build/generate/generateModifyVars.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | /** 4 | * less global variable 5 | */ 6 | export function generateModifyVars() { 7 | return { 8 | hack: `true;@import (reference) "${resolve('src/design/config.less')}";`, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /build/getConfigFileName.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the configuration file variable name 3 | * @param env 4 | */ 5 | export const getConfigFileName = (env: Record) => { 6 | return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` 7 | .toUpperCase() 8 | .replace(/\s/g, ''); 9 | }; 10 | -------------------------------------------------------------------------------- /build/script/postBuild.ts: -------------------------------------------------------------------------------- 1 | // #!/usr/bin/env node 2 | 3 | import { runBuildConfig } from './buildConf'; 4 | import chalk from 'chalk'; 5 | 6 | import pkg from '../../package.json'; 7 | 8 | export const runBuild = async () => { 9 | try { 10 | const argvList = process.argv.splice(2); 11 | 12 | // Generate configuration file 13 | if (!argvList.includes('disabled-config')) { 14 | runBuildConfig(); 15 | } 16 | 17 | console.log(`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); 18 | } catch (error) { 19 | console.log(chalk.red('vite build error:\n' + error)); 20 | process.exit(1); 21 | } 22 | }; 23 | runBuild(); 24 | -------------------------------------------------------------------------------- /build/vite/plugin/components.ts: -------------------------------------------------------------------------------- 1 | import ViteComponents from 'unplugin-vue-components/vite'; 2 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; 3 | 4 | export function configComponentsPlugin() { 5 | return ViteComponents({ 6 | dts: 'types/components.d.ts', 7 | // auto import dirs compontents 8 | dirs: ['src/components', 'src/layout'], 9 | 10 | include: [/\.vue$/, /\.vue\?vue/, /\.tsx$/], 11 | // auto import Naive compontents 12 | resolvers: [NaiveUiResolver()], 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /build/vite/plugin/html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Plugin to minimize and use ejs template syntax in index.html. 3 | * https://github.com/anncwb/vite-plugin-html 4 | */ 5 | import type { PluginOption } from 'vite'; 6 | import { createHtmlPlugin } from 'vite-plugin-html'; 7 | import pkg from '../../../package.json'; 8 | import { GLOB_CONFIG_FILE_NAME } from '../../constant'; 9 | 10 | export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { 11 | const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env; 12 | 13 | const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`; 14 | 15 | const getAppConfigSrc = () => { 16 | return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; 17 | }; 18 | 19 | const htmlPlugin: PluginOption[] = createHtmlPlugin({ 20 | minify: isBuild, 21 | inject: { 22 | // Inject data into ejs template 23 | data: { 24 | title: VITE_GLOB_APP_TITLE, 25 | }, 26 | // Embed the generated app.config.js file 27 | tags: isBuild 28 | ? [ 29 | { 30 | tag: 'script', 31 | attrs: { 32 | src: getAppConfigSrc(), 33 | }, 34 | }, 35 | ] 36 | : [], 37 | }, 38 | }); 39 | return htmlPlugin; 40 | } 41 | -------------------------------------------------------------------------------- /build/vite/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from 'vite'; 2 | 3 | import vue from '@vitejs/plugin-vue'; 4 | import vueJsx from '@vitejs/plugin-vue-jsx'; 5 | import { configHtmlPlugin } from './html'; 6 | import { configMockPlugin } from './mock'; 7 | import { configComponentsPlugin } from './components'; 8 | import { configSvgIconsPlugin } from './svgSprite'; 9 | import UnoCSS from 'unocss/vite'; 10 | 11 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { 12 | const { VITE_USE_MOCK } = viteEnv; 13 | 14 | const vitePlugins: (PluginOption | PluginOption[])[] = [ 15 | vue({ 16 | reactivityTransform: ['src/views/sys/**/*.vue'], 17 | }), 18 | vueJsx() as PluginOption, 19 | ]; 20 | vitePlugins.push(UnoCSS()); 21 | vitePlugins.push(configComponentsPlugin()); 22 | 23 | vitePlugins.push(configSvgIconsPlugin(isBuild)); 24 | 25 | // vite-plugin-html 26 | vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); 27 | 28 | // vite-plugin-mock 29 | VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild)); 30 | 31 | return vitePlugins; 32 | } 33 | -------------------------------------------------------------------------------- /build/vite/plugin/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock plugin for development and production. 3 | * https://github.com/anncwb/vite-plugin-mock 4 | */ 5 | import { viteMockServe } from 'vite-plugin-mock'; 6 | 7 | export function configMockPlugin(isBuild: boolean) { 8 | return viteMockServe({ 9 | ignore: /^\_/, 10 | mockPath: 'mock', 11 | localEnabled: !isBuild, 12 | prodEnabled: isBuild, 13 | injectCode: ` 14 | import { setupProdMockServer } from '../mock/_createProductionServer'; 15 | 16 | setupProdMockServer(); 17 | `, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /build/vite/plugin/svgSprite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vite Plugin for fast creating SVG sprites. 3 | * https://github.com/anncwb/vite-plugin-svg-icons 4 | */ 5 | 6 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; 7 | import path from 'path'; 8 | 9 | export function configSvgIconsPlugin(isBuild: boolean) { 10 | const svgIconsPlugin = createSvgIconsPlugin({ 11 | iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], 12 | svgoOptions: isBuild, 13 | // default 14 | symbolId: 'icon-[dir]-[name]', 15 | }); 16 | return svgIconsPlugin; 17 | } 18 | -------------------------------------------------------------------------------- /build/vite/plugin/windicss.ts: -------------------------------------------------------------------------------- 1 | import WindiCSS from 'vite-plugin-windicss'; 2 | 3 | export function configWindiCSSPlugin() { 4 | return WindiCSS(); 5 | } 6 | -------------------------------------------------------------------------------- /build/vite/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to parse the .env.development proxy configuration 3 | */ 4 | import type { ProxyOptions } from 'vite'; 5 | 6 | type ProxyItem = [string, string]; 7 | 8 | type ProxyList = ProxyItem[]; 9 | 10 | type ProxyTargetList = Record; 11 | 12 | const httpsRE = /^https:\/\//; 13 | 14 | /** 15 | * Generate proxy 16 | * @param list 17 | */ 18 | export function createProxy(list: ProxyList = []) { 19 | const ret: ProxyTargetList = {}; 20 | for (const [prefix, target] of list) { 21 | const isHttps = httpsRE.test(target); 22 | 23 | // https://github.com/http-party/node-http-proxy#options 24 | ret[prefix] = { 25 | target: target, 26 | changeOrigin: true, 27 | ws: true, 28 | rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), 29 | // https is require secure=false 30 | ...(isHttps ? { secure: false } : {}), 31 | }; 32 | } 33 | return ret; 34 | } 35 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | 3 | const modules = import.meta.glob('./**/*.ts', { eager: true }); 4 | 5 | const mockModules: any[] = []; 6 | Object.keys(modules).forEach((key) => { 7 | if (key.includes('/_')) { 8 | return; 9 | } 10 | mockModules.push(...modules[key].default); 11 | }); 12 | 13 | /** 14 | * Used in a production environment. Need to manually import all modules 15 | */ 16 | export function setupProdMockServer() { 17 | createProdMockServer(mockModules); 18 | } 19 | -------------------------------------------------------------------------------- /mock/demo/account.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | import { resultSuccess, resultError } from '../_util'; 3 | import { ResultEnum } from '../../src/enums/httpEnum'; 4 | 5 | const userInfo = { 6 | name: 'Thunur', 7 | nickName: 'Aso.design', 8 | userid: '00000001', 9 | email: 'aso.design@example.com', 10 | signature: '日月之行,若出其中', 11 | country: 'China', 12 | phone: '0592-268888888', 13 | Adress: { 14 | Provincey: '35', 15 | City: '0', 16 | }, 17 | }; 18 | 19 | export default [ 20 | { 21 | url: '/basic-api/sys/account/getAccountInfo', 22 | timeout: 1000, 23 | method: 'get', 24 | response: () => { 25 | return resultSuccess(userInfo); 26 | }, 27 | }, 28 | { 29 | url: '/basic-api/user/sessionTimeout', 30 | method: 'post', 31 | statusCode: 401, 32 | response: () => { 33 | return resultError(); 34 | }, 35 | }, 36 | { 37 | url: '/basic-api/user/tokenExpired', 38 | method: 'post', 39 | statusCode: 200, 40 | response: () => { 41 | return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number }); 42 | }, 43 | }, 44 | ] as MockMethod[]; 45 | -------------------------------------------------------------------------------- /plop-templates/api/index.hbs: -------------------------------------------------------------------------------- 1 | import { BaseService, Service } from '@/service/common'; 2 | 3 | @Service('{{ address }}') 4 | class {{ titleCase name }} extends BaseService { 5 | // 默认继承了page、add、list、info、delete、update请求,如其他请求可在此自行编写 6 | // 使用 7 | // import { useService } from '@/hooks/service'; 8 | // const service = useService(); 9 | // service.文件路径+文件名.请求的方法().then() 10 | // 例如/api/common文件夹只需要service.common.page().then() 11 | } 12 | 13 | export default {{ titleCase name }}; 14 | -------------------------------------------------------------------------------- /plop-templates/component/index.hbs: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /plop-templates/store/index.hbs: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '@/store'; 3 | 4 | export const use{{ properCase name }}Store = defineStore({ 5 | // 唯一ID 6 | id: '{{ camelCase name }}', 7 | state: () => ({}), 8 | getters: {}, 9 | actions: {}, 10 | }); 11 | 12 | // Need to be used outside the setup 13 | export function use{{ properCase name }}StoreWithOut() { 14 | return use{{ properCase name }}Store(store); 15 | } 16 | -------------------------------------------------------------------------------- /plop-templates/store/prompt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Thunur 3 | * @Date: 2022-01-28 10:49:17 4 | * @LastEditors: Thunur 5 | * @LastEditTime: 2022-01-28 10:55:54 6 | * @FilePath: /vue3-antd-admin/plop-tpls/store/prompt.js 7 | */ 8 | module.exports = { 9 | description: '创建全局状态管理', 10 | prompts: [ 11 | { 12 | type: 'input', 13 | name: 'name', 14 | message: '请输入模块名称', 15 | validate: (v) => { 16 | if (!v || v.trim === '') { 17 | return '模块名称不能为空'; 18 | } else { 19 | return true; 20 | } 21 | }, 22 | }, 23 | ], 24 | actions: () => { 25 | const actions = [ 26 | { 27 | type: 'add', 28 | path: 'src/store/modules/{{camelCase name}}.ts', 29 | templateFile: 'plop-templates/store/index.hbs', 30 | }, 31 | ]; 32 | return actions; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /plop-templates/views/index.hbs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /plopfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (plop) { 2 | plop.setWelcomeMessage('请选择要生成的类型'); 3 | plop.setGenerator('views', require('./plop-templates/views/prompt')); 4 | plop.setGenerator('component', require('./plop-templates/component/prompt')); 5 | plop.setGenerator('store', require('./plop-templates/store/prompt')); 6 | plop.setGenerator('api', require('./plop-templates/api/prompt')); 7 | }; 8 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/public/favicon.ico -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | default-run = "app" 9 | edition = "2021" 10 | rust-version = "1.57" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "1.1.1", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | tauri = { version = "1.1.1", features = ["api-all"] } 21 | 22 | [features] 23 | # by default Tauri runs in production mode 24 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 25 | default = [ "custom-protocol" ] 26 | # this feature is used for production builds where `devPath` points to the filesystem 27 | # DO NOT remove this 28 | custom-protocol = [ "tauri/custom-protocol" ] 29 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | fn main() { 7 | tauri::Builder::default() 8 | .run(tauri::generate_context!()) 9 | .expect("error while running tauri application"); 10 | } 11 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /src/api/demo/table.ts: -------------------------------------------------------------------------------- 1 | import { DemoParams } from '#/model/demo/tableModel'; 2 | import { BaseService, Service } from '@/service/common'; 3 | 4 | @Service('demo') 5 | class Demo extends BaseService { 6 | demoListApi(params: DemoParams) { 7 | console.log(params); 8 | return this.request({ 9 | url: '/table/getDemoList', 10 | method: 'GET', 11 | params, 12 | }); 13 | } 14 | } 15 | 16 | export default Demo; 17 | -------------------------------------------------------------------------------- /src/api/sys/account.ts: -------------------------------------------------------------------------------- 1 | import { BaseService, Service } from '@/service/common'; 2 | import { LoginParams } from '#/model/account/userModel'; 3 | import { ErrorMessageMode } from '#/axios'; 4 | 5 | @Service('sys/account') 6 | class account extends BaseService { 7 | // 默认继承了page、add、list、info、delete、update请求,如其他请求可在此自行编写 8 | // 使用 9 | // import { useService } from '@/hooks/service'; 10 | // const service = useService(); 11 | // service.文件路径+文件名.请求的方法().then() 12 | // 例如/api/common文件夹只需要service.common.page().then() 13 | login(params: LoginParams, mode: ErrorMessageMode = 'modal') { 14 | return this.request( 15 | { 16 | url: '/login', 17 | method: 'POST', 18 | params, 19 | }, 20 | { 21 | errorMessageMode: mode, 22 | } 23 | ); 24 | } 25 | accountInfoApi() { 26 | return this.request({ url: '/getAccountInfo' }); 27 | } 28 | getUserInfo() { 29 | return this.request({ 30 | url: '/getUserInfo/info', 31 | }); 32 | } 33 | doLogout() { 34 | return this.request({ 35 | url: '/logout', 36 | }); 37 | } 38 | getMenuList() { 39 | return this.request({ 40 | url: '/getMenuList', 41 | }); 42 | } 43 | getPermCode() { 44 | return this.request({ 45 | url: '/getPermCode', 46 | }); 47 | } 48 | } 49 | 50 | export default account; 51 | -------------------------------------------------------------------------------- /src/api/test/common.ts: -------------------------------------------------------------------------------- 1 | import { BaseService, Service } from '@/service/common'; 2 | 3 | @Service('getUserInfo') 4 | class Common extends BaseService {} 5 | 6 | export default Common; 7 | -------------------------------------------------------------------------------- /src/assets/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/icons/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src/assets/images/header.jpg -------------------------------------------------------------------------------- /src/assets/images/login/login_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src/assets/images/login/login_banner.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | // import { withInstall } from '@/utils'; 2 | 3 | // import appLogo from './src/AppLogo.vue'; 4 | // import appProvider from './src/AppProvider.vue'; 5 | // import appSearch from './src/search/AppSearch.vue'; 6 | // import appLocalePicker from './src/AppLocalePicker.vue'; 7 | // import appDarkModeToggle from './src/AppDarkModeToggle.vue'; 8 | 9 | // export { useAppProviderContext } from './src/useAppContext'; 10 | 11 | // export const AppLogo = withInstall(appLogo); 12 | // export const AppProvider = withInstall(appProvider); 13 | // export const AppSearch = withInstall(appSearch); 14 | // export const AppLocalePicker = withInstall(appLocalePicker); 15 | // export const AppDarkModeToggle = withInstall(appDarkModeToggle); 16 | -------------------------------------------------------------------------------- /src/components/Application/search/AppSearch.vue: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /src/components/Application/useAppContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | import { createContext, useContext } from '@/hooks/core/useContext'; 3 | 4 | export interface AppProviderContextProps { 5 | prefixCls: Ref; 6 | isMobile: Ref; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createAppProviderContext(context: AppProviderContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useAppProviderContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Basic/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTitle } from './src/BasicTitle.vue'; 2 | export { default as BasicTips } from './src/BasicTips.vue'; 3 | -------------------------------------------------------------------------------- /src/components/Card/CardGrid.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 29 | -------------------------------------------------------------------------------- /src/components/Card/CardGridItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 37 | 38 | 51 | -------------------------------------------------------------------------------- /src/components/Card/CollapseCard_legacy.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 39 | -------------------------------------------------------------------------------- /src/components/Card/LoadingCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /src/components/Container/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ScrollContainer } from './src/ScrollContainer.vue'; 2 | export { default as LazyContainer } from './src/LazyContainer.vue'; 3 | 4 | export * from './src/typing'; 5 | -------------------------------------------------------------------------------- /src/components/Container/src/typing.ts: -------------------------------------------------------------------------------- 1 | export type ScrollType = 'default' | 'main'; 2 | 3 | export interface ScrollContainerOptions { 4 | enableScroll?: boolean; 5 | type?: ScrollType; 6 | } 7 | 8 | export type ScrollActionType = RefType<{ 9 | scrollBottom: () => void; 10 | getScrollWrap: () => Nullable; 11 | scrollTo: (top: number) => void; 12 | }>; 13 | -------------------------------------------------------------------------------- /src/components/CountDown/CountdownInput.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | 23 | 43 | -------------------------------------------------------------------------------- /src/components/CountDown/useCountdown.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref } from 'vue'; 2 | import { tryOnScopeDispose } from '@vueuse/core'; 3 | 4 | export function useCountdown(count: number) { 5 | const currentCount = ref(count); 6 | 7 | const isStart = ref(false); 8 | 9 | let timerId: ReturnType | null; 10 | 11 | function clear() { 12 | timerId && window.clearInterval(timerId); 13 | } 14 | 15 | function stop() { 16 | isStart.value = false; 17 | clear(); 18 | timerId = null; 19 | } 20 | 21 | function start() { 22 | if (unref(isStart) || !!timerId) { 23 | return; 24 | } 25 | isStart.value = true; 26 | timerId = setInterval(() => { 27 | if (unref(currentCount) === 1) { 28 | stop(); 29 | currentCount.value = count; 30 | } else { 31 | currentCount.value -= 1; 32 | } 33 | }, 1000); 34 | } 35 | 36 | function reset() { 37 | currentCount.value = count; 38 | stop(); 39 | } 40 | 41 | function restart() { 42 | reset(); 43 | start(); 44 | } 45 | 46 | tryOnScopeDispose(() => { 47 | reset(); 48 | }); 49 | 50 | return { start, reset, restart, clear, stop, currentCount, isStart }; 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicForm } from './src/BasicForm.vue'; 2 | export { useForm } from './src/hooks/useForm'; 3 | export * from './src/types/form'; 4 | export * from './src/types/index'; 5 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject } from 'vue'; 2 | 3 | const key = Symbol('formElRef'); 4 | 5 | export function createFormContext(instance) { 6 | provide(key, instance); 7 | } 8 | 9 | export function useFormContext() { 10 | return inject(key); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Form/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputGroup' 4 | | 'NInputTextarea' 5 | | 'NInputPassword' 6 | | 'NInputGroupLabel' 7 | | 'NDynamicInput' 8 | | 'NInputNumber' 9 | | 'NSelect' 10 | | 'NTreeSelect' 11 | | 'NTransfer' 12 | | 'NRadioButtonGroup' 13 | | 'NRadioGroup' 14 | | 'NCheckbox' 15 | | 'NCheckboxGroup' 16 | | 'NAutoComplete' 17 | | 'NCascader' 18 | | 'NDatePicker' 19 | | 'NDateRangePicker' 20 | | 'NDatetimeRangePicker' 21 | | 'NColorPicker' 22 | | 'NTimePicker' 23 | | 'NSwitch' 24 | | 'NUpload' 25 | | 'NSlider' 26 | | 'NRate' 27 | | 'NDivider'; 28 | -------------------------------------------------------------------------------- /src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | import Icon from './src/Icon.vue'; 2 | import SvgIcon from './src/SvgIcon.vue'; 3 | // import IconPicker from './src/IconPicker.vue'; 4 | 5 | export { Icon, SvgIcon }; 6 | 7 | export default Icon; 8 | -------------------------------------------------------------------------------- /src/components/Icon/src/SvgIcon.vue: -------------------------------------------------------------------------------- 1 | 10 | 50 | -------------------------------------------------------------------------------- /src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | import Loading from './src/Loading.vue'; 2 | 3 | export { Loading }; 4 | export type { LoadingProps } from './src/type'; 5 | export { useLoading } from './src/useLoading'; 6 | export { createLoading } from './src/createLoading'; 7 | -------------------------------------------------------------------------------- /src/components/Loading/src/type.ts: -------------------------------------------------------------------------------- 1 | import { SpinProps } from 'naive-ui'; 2 | 3 | export interface LoadingProps extends SpinProps { 4 | absolute?: boolean; 5 | background?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Loading/src/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { unrefElement, MaybeElementRef } from '@vueuse/core'; 2 | import { createLoading } from './createLoading'; 3 | import type { LoadingProps } from './type'; 4 | 5 | export interface UseLoadingOptions { 6 | target?: any; 7 | props?: Partial; 8 | } 9 | 10 | interface Fn { 11 | (): void; 12 | } 13 | 14 | export function useLoading(props: Partial): [Fn, Fn, (string) => void]; 15 | export function useLoading(opt: Partial): [Fn, Fn, (string) => void]; 16 | 17 | export function useLoading( 18 | opt: Partial | Partial 19 | ): [Fn, Fn, (string) => void] { 20 | let props: Partial; 21 | let target: MaybeElementRef = document.body; 22 | 23 | if (Reflect.has(opt, 'target') || Reflect.has(opt, 'props')) { 24 | const options = opt as Partial; 25 | props = options.props || {}; 26 | target = options.target || document.body; 27 | } else { 28 | props = opt as Partial; 29 | } 30 | 31 | const instance = createLoading(props, undefined, true); 32 | 33 | const open = (): void => { 34 | const t = unrefElement(target as MaybeElementRef) as HTMLDivElement; 35 | if (!t) return; 36 | instance.open(t); 37 | }; 38 | 39 | const close = (): void => { 40 | instance.close(); 41 | }; 42 | 43 | const setProps = (props: Partial) => { 44 | instance.setProps(props); 45 | }; 46 | 47 | return [open, close, setProps]; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Modal/README.MD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunur/vue-naive-ui-admin/6b056b5c66641aafde948dcec2899e73540bf120/src/components/Modal/README.MD -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | import './src/index.less'; 2 | export { useModalContext } from './src/hooks/useModalContext'; 3 | export { useModal, useModalInner } from './src/hooks/useModal'; 4 | export * from './src/typing'; 5 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { NModal } from 'naive-ui'; 2 | import { defineComponent, toRefs, unref } from 'vue'; 3 | import { basicProps } from '../props'; 4 | import { useModalDragMove } from '../hooks/useModalDrag'; 5 | import { useAttrs } from '@/hooks/core/useAttrs'; 6 | import { extendSlots } from '@/utils/helper/tsxHelper'; 7 | 8 | export default defineComponent({ 9 | name: 'Modal', 10 | inheritAttrs: false, 11 | props: basicProps, 12 | emits: ['cancel'], 13 | setup(props, { slots }) { 14 | const { show, draggable } = toRefs(props); 15 | const attrs = useAttrs(); 16 | useModalDragMove({ 17 | show, 18 | draggable, 19 | }); 20 | 21 | return () => { 22 | const propsData = { ...unref(attrs), ...props } as Recordable; 23 | console.log(propsData); 24 | return {extendSlots(slots)}; 25 | }; 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalFooter.vue: -------------------------------------------------------------------------------- 1 | 20 | 41 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 34 | -------------------------------------------------------------------------------- /src/components/Modal/src/hooks/useModalContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from 'vue'; 2 | import { createContext, useContext } from '@/hooks/core/useContext'; 3 | 4 | export interface ModalContextProps { 5 | redoModalHeight: () => void; 6 | } 7 | 8 | const key: InjectionKey = Symbol(); 9 | 10 | export function createModalContext(context: ModalContextProps) { 11 | return createContext(context, key); 12 | } 13 | 14 | export function useModalContext() { 15 | return useContext(key); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Modal/src/index.less: -------------------------------------------------------------------------------- 1 | @prefix-cls: ~'@{namespace}-basic-modal'; 2 | .fullscreen-modal { 3 | overflow: hidden; 4 | top: 0 !important; 5 | right: 0 !important; 6 | bottom: 0 !important; 7 | left: 0 !important; 8 | width: 100% !important; 9 | height: 100vh; 10 | 11 | .n-card__content { 12 | height: 100%; 13 | } 14 | } 15 | .@{prefix-cls} { 16 | width: 520px; 17 | padding-bottom: 0; 18 | .n-card-header { 19 | border-bottom: var(--app-border-color) 1px solid; 20 | padding: 16px; 21 | position: relative; 22 | .n-card-header__extra { 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | bottom: 0; 27 | z-index: 10; 28 | padding: 0 16px 0 0; 29 | transition: color 0.3s; 30 | } 31 | } 32 | & .n-card__action, 33 | & .n-card__footer { 34 | border-top: var(--app-border-color) 1px solid; 35 | padding: 10px 16px !important; 36 | } 37 | & .aso-basic-title { 38 | cursor: move; 39 | } 40 | .n-card__content > .scrollbar { 41 | padding: 14px; 42 | } 43 | 44 | .n-card__content { 45 | padding: 0; 46 | 47 | > .scrollbar > .scrollbar__bar.is-horizontal { 48 | display: none; 49 | } 50 | } 51 | } 52 | 53 | @media screen and (max-height: 600px) { 54 | .n-modal { 55 | top: 60px; 56 | } 57 | } 58 | @media screen and (max-height: 540px) { 59 | .n-modal { 60 | top: 30px; 61 | } 62 | } 63 | @media screen and (max-height: 480px) { 64 | .n-modal { 65 | top: 10px; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/Scrollbar/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * copy from element-ui 3 | */ 4 | 5 | export { default as Scrollbar } from './src/Scrollbar.vue'; 6 | 7 | export type { ScrollbarType } from './src/types'; 8 | -------------------------------------------------------------------------------- /src/components/Scrollbar/src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface BarMapItem { 2 | offset: string; 3 | scroll: string; 4 | scrollSize: string; 5 | size: string; 6 | key: string; 7 | axis: string; 8 | client: string; 9 | direction: string; 10 | } 11 | export interface BarMap { 12 | vertical: BarMapItem; 13 | horizontal: BarMapItem; 14 | } 15 | 16 | export interface ScrollbarType { 17 | wrap: ElRef; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Scrollbar/src/util.ts: -------------------------------------------------------------------------------- 1 | import type { BarMap } from './types'; 2 | export const BAR_MAP: BarMap = { 3 | vertical: { 4 | offset: 'offsetHeight', 5 | scroll: 'scrollTop', 6 | scrollSize: 'scrollHeight', 7 | size: 'height', 8 | key: 'vertical', 9 | axis: 'Y', 10 | client: 'clientY', 11 | direction: 'top', 12 | }, 13 | horizontal: { 14 | offset: 'offsetWidth', 15 | scroll: 'scrollLeft', 16 | scrollSize: 'scrollWidth', 17 | size: 'width', 18 | key: 'horizontal', 19 | axis: 'X', 20 | client: 'clientX', 21 | direction: 'left', 22 | }, 23 | }; 24 | 25 | // @ts-ignore 26 | export function renderThumbStyle({ move, size, bar }) { 27 | const style = {} as any; 28 | const translate = `translate${bar.axis}(${move}%)`; 29 | 30 | style[bar.size] = size; 31 | style.transform = translate; 32 | style.msTransform = translate; 33 | style.webkitTransform = translate; 34 | 35 | return style; 36 | } 37 | 38 | function extend(to: T, _from: K): T & K { 39 | return Object.assign(to, _from); 40 | } 41 | 42 | export function toObject(arr: Array): Recordable { 43 | const res = {}; 44 | for (let i = 0; i < arr.length; i++) { 45 | if (arr[i]) { 46 | extend(res, arr[i]); 47 | } 48 | } 49 | return res; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/BasicTable.vue'; 2 | export { default as TableAction } from './src/components/actions/TableAction.vue'; 3 | export * from './src/types/table'; 4 | export * from './src/types/tableAction'; 5 | -------------------------------------------------------------------------------- /src/components/Table/src/componentMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue'; 2 | import { 3 | NInput, 4 | NSelect, 5 | NCheckbox, 6 | NInputNumber, 7 | NSwitch, 8 | NDatePicker, 9 | NTimePicker, 10 | } from 'naive-ui'; 11 | import type { ComponentType } from './types/componentType'; 12 | 13 | export enum EventEnum { 14 | NInput = 'on-input', 15 | NInputNumber = 'on-input', 16 | NSelect = 'on-update:value', 17 | NSwitch = 'on-update:value', 18 | NCheckbox = 'on-update:value', 19 | NDatePicker = 'on-update:value', 20 | NTimePicker = 'on-update:value', 21 | } 22 | 23 | const componentMap = new Map(); 24 | 25 | componentMap.set('NInput', NInput); 26 | componentMap.set('NInputNumber', NInputNumber); 27 | componentMap.set('NSelect', NSelect); 28 | componentMap.set('NSwitch', NSwitch); 29 | componentMap.set('NCheckbox', NCheckbox); 30 | componentMap.set('NDatePicker', NDatePicker); 31 | componentMap.set('NTimePicker', NTimePicker); 32 | 33 | export function add(compName: ComponentType, component: Component) { 34 | componentMap.set(compName, component); 35 | } 36 | 37 | export function del(compName: ComponentType) { 38 | componentMap.delete(compName); 39 | } 40 | 41 | export { componentMap }; 42 | -------------------------------------------------------------------------------- /src/components/Table/src/components/actions/popConfirmWrapper.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/CellComponent.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent, defineComponent } from 'vue'; 2 | import { componentMap } from '@/components/Table/src/componentMap'; 3 | 4 | import { h } from 'vue'; 5 | 6 | import { NPopover } from 'naive-ui'; 7 | import type { ComponentType } from '../../types/componentType'; 8 | 9 | export interface ComponentProps { 10 | component: ComponentType; 11 | rule: boolean; 12 | popoverVisible: boolean; 13 | ruleMessage: string; 14 | } 15 | 16 | export const CellComponent: FunctionalComponent = ( 17 | { component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps, 18 | { attrs } 19 | ) => { 20 | const Comp = componentMap.get(component) as typeof defineComponent; 21 | 22 | const DefaultComp = h(Comp, attrs); 23 | if (!rule) { 24 | return DefaultComp; 25 | } 26 | return h( 27 | NPopover, 28 | { 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' }, 29 | { 30 | trigger: () => DefaultComp, 31 | default: () => 32 | h( 33 | 'span', 34 | { 35 | style: { 36 | color: 'red', 37 | width: '90px', 38 | display: 'inline-block', 39 | }, 40 | }, 41 | { 42 | default: () => ruleMessage, 43 | } 44 | ), 45 | } 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from '../../types/componentType'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/index.ts: -------------------------------------------------------------------------------- 1 | import type { BasicColumn } from '@/components/Table/src/types/table'; 2 | import { h, Ref } from 'vue'; 3 | 4 | import EditableCell from './EditableCell.vue'; 5 | 6 | export function renderEditCell(column: BasicColumn) { 7 | return (record, index) => { 8 | const _key = column.key; 9 | const value = record[_key]; 10 | record.onEdit = async (edit: boolean, submit = false) => { 11 | if (!submit) { 12 | record.editable = edit; 13 | } 14 | 15 | if (!edit && submit) { 16 | const res = await record.onSubmitEdit?.(); 17 | if (res) { 18 | record.editable = false; 19 | return true; 20 | } 21 | return false; 22 | } 23 | // cancel 24 | if (!edit && !submit) { 25 | record.onCancelEdit?.(); 26 | } 27 | return true; 28 | }; 29 | return h(EditableCell, { 30 | value, 31 | record, 32 | column, 33 | index, 34 | }); 35 | }; 36 | } 37 | 38 | export type EditRecordRow = Partial< 39 | { 40 | onEdit: (editable: boolean, submit?: boolean) => Promise; 41 | editable: boolean; 42 | onCancel: Fn; 43 | onSubmit: Fn; 44 | submitCbs: Fn[]; 45 | cancelCbs: Fn[]; 46 | validCbs: Fn[]; 47 | editValueRefs: Recordable; 48 | } & T 49 | >; 50 | -------------------------------------------------------------------------------- /src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '@/settings/componentSetting'; 2 | 3 | const { table } = componentSetting; 4 | 5 | const { apiSetting, defaultPageSize, pageSizes } = table; 6 | 7 | export const DEFAULTPAGESIZE = defaultPageSize; 8 | 9 | export const APISETTING = apiSetting; 10 | 11 | export const PAGESIZES = pageSizes; 12 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { ref, ComputedRef, unref, computed, watch } from 'vue'; 2 | import type { BasicTableProps } from '../types/table'; 3 | 4 | export function useLoading(props: ComputedRef) { 5 | const loadingRef = ref(unref(props).loading); 6 | 7 | watch( 8 | () => unref(props).loading, 9 | (loading) => { 10 | loadingRef.value = loading; 11 | } 12 | ); 13 | 14 | const getLoading = computed(() => unref(loadingRef)); 15 | 16 | function setLoading(loading: boolean) { 17 | loadingRef.value = loading; 18 | } 19 | 20 | return { getLoading, setLoading }; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { provide, inject, ComputedRef } from 'vue'; 3 | import type { BasicTableProps, TableActionType } from '../types/table'; 4 | 5 | const key = Symbol('s-table'); 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref>; 9 | getBindValues: ComputedRef; 10 | }; 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef; 14 | }; 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance); 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableDropdown.tsx: -------------------------------------------------------------------------------- 1 | import { DropdownOption } from 'naive-ui'; 2 | import { DropEventEnum } from '../types/dropdown'; 3 | import Icon from '@/components/Icon'; 4 | import { useI18n } from '@/hooks/web/useI18n'; 5 | const { t } = useI18n(); 6 | 7 | /** 8 | * 表格右键菜单 9 | * @date 2022-03-16 10 | * @returns { DropdownOption[] } 11 | */ 12 | const dropMenuList: DropdownOption[] = [ 13 | { 14 | key: DropEventEnum.REFRESH, 15 | label: t('component.table.refresh'), 16 | icon: () => , 17 | }, 18 | { 19 | key: DropEventEnum.SELECT, 20 | label: t('component.table.select'), 21 | icon: () => , 22 | }, 23 | { 24 | key: DropEventEnum.EDITOR, 25 | label: t('component.table.editor'), 26 | icon: () => , 27 | }, 28 | { 29 | key: DropEventEnum.DELETE, 30 | label: t('component.table.delete'), 31 | icon: () => , 32 | }, 33 | ]; 34 | export { dropMenuList }; 35 | -------------------------------------------------------------------------------- /src/components/Table/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { propTypes } from '@/utils/propTypes'; 3 | import { dataTableProps } from 'naive-ui'; 4 | import { BasicColumn } from './types/table'; 5 | 6 | export const props = { 7 | ...dataTableProps, 8 | title: { 9 | type: String, 10 | default: null, 11 | }, 12 | titleTooltip: { 13 | type: String, 14 | default: null, 15 | }, 16 | size: { 17 | type: String, 18 | default: 'medium', 19 | }, 20 | tableData: { 21 | type: [Object], 22 | default: () => [], 23 | }, 24 | columns: { 25 | type: [Array] as PropType, 26 | default: () => [], 27 | required: true, 28 | }, 29 | request: { 30 | type: Function as PropType<(...arg: any[]) => Promise>, 31 | default: null, 32 | }, 33 | rowKey: { 34 | type: [String, Function] as PropType string)>, 35 | default: undefined, 36 | }, 37 | pagination: { 38 | type: [Object, Boolean], 39 | default: () => {}, 40 | }, 41 | actionColumn: { 42 | type: Object as PropType, 43 | default: null, 44 | }, 45 | showAdvanced: propTypes.bool.def(true), 46 | canResize: propTypes.bool.def(true), 47 | resizeHeightOffset: propTypes.number.def(0), 48 | }; 49 | -------------------------------------------------------------------------------- /src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputNumber' 4 | | 'NSelect' 5 | | 'NCheckbox' 6 | | 'NSwitch' 7 | | 'NDatePicker' 8 | | 'NTimePicker'; 9 | -------------------------------------------------------------------------------- /src/components/Table/src/types/dropdown.ts: -------------------------------------------------------------------------------- 1 | export enum DropEventEnum { 2 | REFRESH, 3 | SELECT, 4 | EDITOR, 5 | DELETE, 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Table/src/types/pagination.ts: -------------------------------------------------------------------------------- 1 | export interface PaginationProps { 2 | page?: number; 3 | pageCount?: number; 4 | pageSize?: number; 5 | pageSizes?: number[]; 6 | showSizePicker?: boolean; 7 | showQuickJumper?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Table/src/types/table.ts: -------------------------------------------------------------------------------- 1 | import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface'; 2 | import { ComponentType } from './componentType'; 3 | 4 | export interface BasicColumn extends TableBaseColumn { 5 | // 编辑表格 6 | edit?: boolean; 7 | editRow?: boolean; 8 | editable?: boolean; 9 | editComponent?: ComponentType; 10 | editComponentProps?: Recordable; 11 | editRule?: boolean | ((text: string, record: Recordable) => Promise); 12 | editValueMap?: (value: any) => string; 13 | onEditRow?: () => void; 14 | // 权限编码控制是否显示 15 | auth?: string[]; 16 | // 业务控制是否显示 17 | ifShow?: boolean | ((column: BasicColumn) => boolean); 18 | } 19 | 20 | export interface TableActionType { 21 | reload: (opt) => Promise; 22 | emit?: any; 23 | getColumns: (opt?) => BasicColumn[]; 24 | setColumns: (columns: BasicColumn[] | string[]) => void; 25 | } 26 | 27 | export interface BasicTableProps { 28 | title?: string; 29 | dataSource: Function; 30 | columns: any[]; 31 | pagination: object; 32 | showPagination: boolean; 33 | actionColumn: any[]; 34 | canResize: boolean; 35 | resizeHeightOffset: number; 36 | loading?: boolean; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Table/src/types/tableAction.ts: -------------------------------------------------------------------------------- 1 | import type { ButtonProps, DropdownOption } from 'naive-ui'; 2 | import { RoleEnum } from '@/enums/roleEnum'; 3 | 4 | export interface ActionItem extends ButtonProps { 5 | onClick?: Fn; 6 | label?: string; 7 | color?: 'success' | 'error' | 'warning'; 8 | icon?: string; 9 | popConfirm?: PopConfirm; 10 | disabled?: boolean; 11 | divider?: boolean; 12 | // 权限编码控制是否显示 13 | auth?: RoleEnum | RoleEnum[] | string | string[]; 14 | // 业务控制是否显示 15 | ifShow?: boolean | ((action: ActionItem) => boolean); 16 | } 17 | 18 | export type dropDownActionItem = DropdownOption & { 19 | popConfirm?: PopConfirm; 20 | // 权限编码控制是否显示 21 | auth?: RoleEnum | RoleEnum[] | string | string[]; 22 | // 业务控制是否显示 23 | ifShow?: boolean | ((action: dropDownActionItem) => boolean); 24 | }; 25 | 26 | export interface PopConfirm { 27 | title: string; 28 | positiveText?: string; 29 | negativeText?: string; 30 | onPositiveClick: (e: MouseEvent) => Promise | boolean | any; 31 | onNegativeClick?: (e: MouseEvent) => Promise | boolean | any; 32 | icon?: string; 33 | showIcon?: boolean; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Time/type.ts: -------------------------------------------------------------------------------- 1 | import { TimeProps as NTimeProps } from 'naive-ui'; 2 | 3 | export interface TimeProps extends NTimeProps { 4 | step?: 'requestAnimationFrame' | number; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Transition/index.ts: -------------------------------------------------------------------------------- 1 | import { createSimpleTransition, createJavascriptTransition } from './src/CreateTransition'; 2 | 3 | import ExpandTransitionGenerator from './src/ExpandTransition'; 4 | 5 | export { default as CollapseTransition } from './src/CollapseTransition.vue'; 6 | 7 | export const FadeTransition = createSimpleTransition('fade-transition'); 8 | export const ScaleTransition = createSimpleTransition('scale-transition'); 9 | export const SlideYTransition = createSimpleTransition('slide-y-transition'); 10 | export const ScrollYTransition = createSimpleTransition('scroll-y-transition'); 11 | export const SlideYReverseTransition = createSimpleTransition('slide-y-reverse-transition'); 12 | export const ScrollYReverseTransition = createSimpleTransition('scroll-y-reverse-transition'); 13 | export const SlideXTransition = createSimpleTransition('slide-x-transition'); 14 | export const ScrollXTransition = createSimpleTransition('scroll-x-transition'); 15 | export const SlideXReverseTransition = createSimpleTransition('slide-x-reverse-transition'); 16 | export const ScrollXReverseTransition = createSimpleTransition('scroll-x-reverse-transition'); 17 | export const ScaleRotateTransition = createSimpleTransition('scale-rotate-transition'); 18 | 19 | export const ExpandXTransition = createJavascriptTransition( 20 | 'expand-x-transition', 21 | ExpandTransitionGenerator('', true), 22 | ); 23 | 24 | export const ExpandTransition = createJavascriptTransition( 25 | 'expand-transition', 26 | ExpandTransitionGenerator(''), 27 | ); 28 | -------------------------------------------------------------------------------- /src/design/config.less: -------------------------------------------------------------------------------- 1 | @import (reference) 'var/index.less'; 2 | -------------------------------------------------------------------------------- /src/design/index.less: -------------------------------------------------------------------------------- 1 | @import 'transition/index.less'; 2 | @import 'var/index.less'; 3 | @import 'entry.css'; 4 | 5 | a:focus, a:active, button, div, svg, span { 6 | outline: none!important; 7 | } 8 | 9 | // 拖拽时的样式 10 | .drap-tabs { 11 | transition: background-color var(--app-transition-duration) var(--app-bezier), 12 | border-color var(--app-transition-duration) var(--app-bezier); 13 | background-color: #dbeafe !important; 14 | .n-tabs-tab { 15 | background-color: transparent !important; 16 | } 17 | } 18 | 19 | p { 20 | margin:0 21 | } 22 | 23 | *,:before,:after { 24 | -webkit-box-sizing: border-box; 25 | box-sizing: border-box; 26 | border-width: 0; 27 | border-style: solid; 28 | border-color: #e5e7eb 29 | } 30 | -------------------------------------------------------------------------------- /src/design/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active { 3 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 4 | } 5 | &-leave-active { 6 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 7 | } 8 | 9 | &-move { 10 | transition: transform 0.4s; 11 | } 12 | } 13 | 14 | .transition-primary(@property: all, @enterDuration: 0.38s, @leaveDuration:0.2s ) { 15 | &-enter-active { 16 | each(@property, { 17 | transition-property+:@value; 18 | transition-duration+:@enterDuration; 19 | transition-timing-function+: @sine-out; 20 | }); 21 | } 22 | &-leave-active { 23 | transition: opacity @leaveDuration @sine-in; 24 | } 25 | 26 | &-leave-to { 27 | opacity: 0; 28 | } 29 | } 30 | 31 | .expand-transition { 32 | .transition-default(); 33 | } 34 | 35 | .expand-x-transition { 36 | .transition-default(); 37 | } 38 | -------------------------------------------------------------------------------- /src/design/transition/fade.less: -------------------------------------------------------------------------------- 1 | /* fade */ 2 | .fade { 3 | .transition-primary(opacity,0.25s,0.25s); 4 | &-enter-from { 5 | opacity: 0; 6 | } 7 | } 8 | 9 | /* fade-slide */ 10 | .fade-slide-right { 11 | @property: opacity, transform; 12 | .transition-primary(@property); 13 | &-enter-from { 14 | opacity: 0; 15 | transform: translateX(-2.5%); 16 | } 17 | } 18 | 19 | .fade-slide-left { 20 | @property: opacity, transform; 21 | .transition-primary(@property); 22 | &-enter-from { 23 | opacity: 0; 24 | transform: translateX(2.5%); 25 | } 26 | } 27 | 28 | /* fade-top */ 29 | .fade-top { 30 | @property: opacity, transform; 31 | .transition-primary(@property); 32 | &-enter-from { 33 | opacity: 0; 34 | transform: translateY(1.5%); 35 | } 36 | } 37 | 38 | /* fade-bottom */ 39 | .fade-bottom { 40 | @property: opacity, transform; 41 | .transition-primary(@property); 42 | &-enter-from { 43 | opacity: 0; 44 | transform: translateY(-1.5%); 45 | } 46 | } 47 | 48 | /* fade-bottom */ 49 | .fade-scale { 50 | @property: opacity, transform; 51 | .transition-primary(@property); 52 | &-enter-from { 53 | opacity: 0; 54 | transform: scale(1.01); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/design/transition/index.less: -------------------------------------------------------------------------------- 1 | @import '../var/easing.less'; 2 | @import './base.less'; 3 | @import './fade.less'; 4 | @import './scale.less'; 5 | @import './slide.less'; 6 | @import './scroll.less'; 7 | @import './zoom.less'; 8 | 9 | .collapse-transition { 10 | transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; 11 | } 12 | -------------------------------------------------------------------------------- /src/design/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | opacity: 0; 8 | transform: scale(0); 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | opacity: 0; 19 | transform: scale(0) rotate(-45deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/design/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/design/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | transform: translateY(-15px); 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | opacity: 0; 17 | transform: translateY(15px); 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | opacity: 0; 27 | transform: translateX(-15px); 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | opacity: 0; 37 | transform: translateX(15px); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/design/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.2s ease-in-out, transform 0.3s ease-out; 5 | } 6 | 7 | .zoom-out-enter-from, 8 | .zoom-out-leave-to { 9 | opacity: 0; 10 | transform: scale(0); 11 | } 12 | 13 | // zoom-fade 14 | .zoom-fade-enter-active, 15 | .zoom-fade-leave-active { 16 | transition: transform 0.2s @ease-in-out, opacity 0.3s @ease-in-out; 17 | } 18 | 19 | .zoom-fade-enter-from { 20 | opacity: 0; 21 | transform: scale(0.97); 22 | } 23 | 24 | .zoom-fade-leave-to { 25 | opacity: 0; 26 | transform: scale(1.03); 27 | } 28 | -------------------------------------------------------------------------------- /src/design/var/easing.less: -------------------------------------------------------------------------------- 1 | // ================================= 2 | // ==============动画函数-=========== 3 | // ================================= 4 | 5 | @ease-base-out: cubic-bezier(0.7, 0.3, 0.1, 1); 6 | @ease-base-in: cubic-bezier(0.9, 0, 0.3, 0.7); 7 | 8 | @ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); 9 | @ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); 10 | @ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); 11 | 12 | @sine-in: cubic-bezier(0.47, 0, 0.75, 0.72); 13 | @sine-out: cubic-bezier(0.39, 0.58, 0.57, 1); 14 | @sine-in-out: cubic-bezier(0.45, 0.05, 0.55, 0.95); 15 | 16 | @quadratic-in: cubic-bezier(0.55, 0.09, 0.68, 0.53); 17 | @quadratic-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); 18 | @quadratic-in-out: cubic-bezier(0.46, 0.03, 0.52, 0.96); 19 | 20 | @cubic-in: cubic-bezier(0.55, 0.06, 0.68, 0.19); 21 | @cubic-out: cubic-bezier(0.22, 0.61, 0.36, 1); 22 | @cubic-in-out: cubic-bezier(0.65, 0.05, 0.36, 1); 23 | 24 | @ease-in-back: cubic-bezier(0.71, -0.46, 0.88, 0.6); 25 | @ease-out-back: cubic-bezier(0.12, 0.4, 0.29, 1.46); 26 | @ease-in-out-back: cubic-bezier(0.71, -0.46, 0.29, 1.46); 27 | 28 | @ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.34); 29 | @ease-out-circ: cubic-bezier(0.08, 0.82, 0.17, 1); 30 | @ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); 31 | 32 | @ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); 33 | @ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); 34 | @ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); 35 | -------------------------------------------------------------------------------- /src/design/var/index.less: -------------------------------------------------------------------------------- 1 | @import './easing.less'; 2 | 3 | @namespace: aso; 4 | 5 | // tabs 6 | @multiple-height: 32px; 7 | 8 | // headers 9 | @header-height: 50px; 10 | 11 | .bem(@n; @content) { 12 | @{namespace}-@{n} { 13 | @content(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure and register global directives 3 | */ 4 | import type { App } from 'vue'; 5 | 6 | import { setupPermissionDirective } from './permission'; 7 | import { setupLoadingDirective } from './loading'; 8 | 9 | export function setupGlobDirectives(app: App) { 10 | setupPermissionDirective(app); 11 | setupLoadingDirective(app); 12 | } 13 | -------------------------------------------------------------------------------- /src/directives/permission.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Global authority directive 3 | * Used for fine-grained control of component permissions 4 | * @Example v-auth="RoleEnum.TEST" 5 | */ 6 | import type { App, Directive, DirectiveBinding } from 'vue'; 7 | 8 | import { usePermission } from '../hooks/web/usePermission'; 9 | 10 | function isAuth(el: Element, binding: any) { 11 | const { hasPermission } = usePermission(); 12 | 13 | const { value } = binding; 14 | if (!value) return; 15 | if (!hasPermission(value)) { 16 | el.parentNode?.removeChild(el); 17 | } 18 | } 19 | 20 | const mounted = (el: Element, binding: DirectiveBinding) => { 21 | isAuth(el, binding); 22 | }; 23 | 24 | const authDirective: Directive = { 25 | mounted, 26 | }; 27 | 28 | export function setupPermissionDirective(app: App) { 29 | app.directive('auth', authDirective); 30 | } 31 | 32 | export default authDirective; 33 | -------------------------------------------------------------------------------- /src/enums/appEnum.ts: -------------------------------------------------------------------------------- 1 | export enum ContentEnum { 2 | // auto width 3 | FULL = 'full', 4 | // fixed width 5 | FIXED = 'fixed', 6 | } 7 | 8 | // menu theme enum 9 | export enum ThemeEnum { 10 | DARK = 'dark', 11 | LIGHT = 'light', 12 | } 13 | 14 | export enum ThemeStateEnum { 15 | DARK = 'dark', 16 | LIGHT = 'light', 17 | MENU_DARK = 'menuDark', 18 | } 19 | 20 | export enum SettingButtonPositionEnum { 21 | AUTO = 'auto', 22 | HEADER = 'header', 23 | FIXED = 'fixed', 24 | } 25 | 26 | export enum SessionTimeoutProcessingEnum { 27 | ROUTE_JUMP, 28 | PAGE_COVERAGE, 29 | } 30 | 31 | /** 32 | * 权限模式 33 | */ 34 | export enum PermissionModeEnum { 35 | // role 36 | ROLE = 'ROLE', 37 | // black 38 | BACK = 'BACK', 39 | // route mapping 40 | ROUTE_MAPPING = 'ROUTE_MAPPING', 41 | } 42 | 43 | // Route switching animation 44 | export enum RouterTransitionEnum { 45 | FADE = 'fade', 46 | FADE_SIDE_RIGHT = 'fade-slide-right', 47 | FADE_SIDE_LEFT = 'fade-slide-left', 48 | FADE_TOP = 'fade-top', 49 | FADE_BOTTOM = 'fade-bottom', 50 | FADE_SCALE = 'fade-scale', 51 | ZOOM_OUT = 'zoom-out', 52 | ZOOM_FADE = 'zoom-fade', 53 | } 54 | -------------------------------------------------------------------------------- /src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XXS = '2xs', 3 | XS = 'xs', 4 | SM = 'sm', 5 | MD = 'md', 6 | LG = 'lg', 7 | XL = 'xl', 8 | XXL = '2xl', 9 | } 10 | 11 | export enum screenEnum { 12 | XXS = 376, 13 | XS = 480, 14 | SM = 576, 15 | MD = 768, 16 | LG = 992, 17 | XL = 1200, 18 | XXL = 1600, 19 | } 20 | 21 | const screenMap = new Map(); 22 | 23 | screenMap.set(sizeEnum.XXS, screenEnum.XXS); 24 | screenMap.set(sizeEnum.XS, screenEnum.XS); 25 | screenMap.set(sizeEnum.SM, screenEnum.SM); 26 | screenMap.set(sizeEnum.MD, screenEnum.MD); 27 | screenMap.set(sizeEnum.LG, screenEnum.LG); 28 | screenMap.set(sizeEnum.XL, screenEnum.XL); 29 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 30 | 31 | function genBreakpoint(suffix?: string) { 32 | const breakpoints: { [k: string]: string | number } = {}; 33 | for (const [key, val] of screenMap) { 34 | breakpoints[key] = suffix ? `${val}${suffix}` : val; 35 | } 36 | return breakpoints; 37 | } 38 | 39 | const getBreakpoint = genBreakpoint(); 40 | const getWindiBreakpoint = genBreakpoint('px'); 41 | 42 | export { screenMap, getBreakpoint, getWindiBreakpoint }; 43 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN__'; 3 | 4 | export const LOCALE_KEY = 'LOCALE__'; 5 | 6 | // user info key 7 | export const USER_INFO_KEY = 'USER__INFO__'; 8 | 9 | // role info key 10 | export const ROLES_KEY = 'ROLES__KEY__'; 11 | 12 | // project config key 13 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 14 | 15 | // lock info 16 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 17 | 18 | export const MULTIPLE_TABS_KEY = 'MULTIPLE_TABS__KEY__'; 19 | 20 | export const APP_DARK_MODE_KEY_ = '__APP__DARK__MODE__'; 21 | 22 | export const APP_THEME_STATE_KEY_ = '__APP__THEME__STATE__'; 23 | 24 | // base global local key 25 | export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'; 26 | 27 | // base global session key 28 | export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'; 29 | 30 | export enum CacheTypeEnum { 31 | SESSION, 32 | LOCAL, 33 | } 34 | -------------------------------------------------------------------------------- /src/enums/exceptionEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Exception related enumeration 3 | */ 4 | export enum ExceptionEnum { 5 | // page not access 6 | PAGE_NOT_ACCESS = 403, 7 | 8 | // page not found 9 | PAGE_NOT_FOUND = 404, 10 | 11 | // error 12 | ERROR = 500, 13 | 14 | // net work error 15 | NET_WORK_ERROR = 10000, 16 | 17 | // No data on the page. In fact, it is not an exception page 18 | PAGE_NOT_DATA = 10100, 19 | } 20 | 21 | export enum ErrorTypeEnum { 22 | VUE = 'vue', 23 | SCRIPT = 'script', 24 | RESOURCE = 'resource', 25 | AJAX = 'ajax', 26 | PROMISE = 'promise', 27 | } 28 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 0, 6 | ERROR = 1, 7 | TIMEOUT = 401, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: request method 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PUT = 'PUT', 18 | DELETE = 'DELETE', 19 | } 20 | 21 | /** 22 | * @description: contentTyp 23 | */ 24 | export enum ContentTypeEnum { 25 | // json 26 | JSON = 'application/json;charset=UTF-8', 27 | // form-data qs 28 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 29 | // form-data upload 30 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 31 | } 32 | -------------------------------------------------------------------------------- /src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // basic login path 3 | BASE_LOGIN = '/login', 4 | // basic home path 5 | BASE_HOME = '/dashboard', 6 | // error page path 7 | ERROR_PAGE = '/exception', 8 | // error log page path 9 | ERROR_LOG_PAGE = '/error-log/list', 10 | } 11 | -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // super admin 3 | SUPER = 'super', 4 | 5 | // tester 6 | TEST = 'test', 7 | } 8 | -------------------------------------------------------------------------------- /src/enums/sizeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | DEFAULT = 'default', 3 | SMALL = 'small', 4 | LARGE = 'large', 5 | } 6 | 7 | export enum SizeNumberEnum { 8 | DEFAULT = 48, 9 | SMALL = 16, 10 | LARGE = 64, 11 | } 12 | 13 | export const sizeMap: Map = (() => { 14 | const map = new Map(); 15 | map.set(SizeEnum.DEFAULT, SizeNumberEnum.DEFAULT); 16 | map.set(SizeEnum.SMALL, SizeNumberEnum.SMALL); 17 | map.set(SizeEnum.LARGE, SizeNumberEnum.LARGE); 18 | return map; 19 | })(); 20 | -------------------------------------------------------------------------------- /src/hooks/component/useFormItem.ts: -------------------------------------------------------------------------------- 1 | import type { UnwrapRef, Ref } from 'vue'; 2 | import { 3 | reactive, 4 | readonly, 5 | computed, 6 | getCurrentInstance, 7 | watchEffect, 8 | unref, 9 | nextTick, 10 | toRaw, 11 | } from 'vue'; 12 | 13 | import { isEqual } from 'lodash-es'; 14 | 15 | export function useRuleFormItem( 16 | props: T, 17 | key: keyof T = 'value', 18 | changeEvent = 'change', 19 | emitData?: Ref 20 | ) { 21 | const instance = getCurrentInstance(); 22 | const emit = instance?.emit; 23 | 24 | const innerState = reactive({ 25 | value: props[key], 26 | }); 27 | 28 | const defaultState = readonly(innerState); 29 | 30 | const setState = (val: UnwrapRef): void => { 31 | innerState.value = val as T[keyof T]; 32 | }; 33 | 34 | watchEffect(() => { 35 | innerState.value = props[key]; 36 | }); 37 | 38 | const state: any = computed({ 39 | get() { 40 | return innerState.value; 41 | }, 42 | set(value) { 43 | if (isEqual(value, defaultState.value)) return; 44 | 45 | innerState.value = value as T[keyof T]; 46 | nextTick(() => { 47 | emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || [])); 48 | }); 49 | }, 50 | }); 51 | 52 | return [state, setState, defaultState]; 53 | } 54 | -------------------------------------------------------------------------------- /src/hooks/core/useAttrs.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; 2 | import type { Ref } from 'vue'; 3 | interface Params { 4 | excludeListeners?: boolean; 5 | excludeKeys?: string[]; 6 | excludeDefaultKeys?: boolean; 7 | } 8 | 9 | const DEFAULT_EXCLUDE_KEYS = ['class', 'style']; 10 | const LISTENER_PREFIX = /^on[A-Z]/; 11 | 12 | export function entries(obj: Recordable): [string, T][] { 13 | return Object.keys(obj).map((key: string) => [key, obj[key]]); 14 | } 15 | 16 | export function useAttrs(params: Params = {}): Ref | {} { 17 | const instance = getCurrentInstance(); 18 | if (!instance) return {}; 19 | 20 | const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params; 21 | const attrs = shallowRef({}); 22 | const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []); 23 | 24 | // Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance 25 | instance.attrs = reactive(instance.attrs); 26 | 27 | watchEffect(() => { 28 | const res = entries(instance.attrs).reduce((acm, [key, val]) => { 29 | if (!allExcludeKeys.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key))) { 30 | acm[key] = val; 31 | } 32 | 33 | return acm; 34 | }, {} as Recordable); 35 | 36 | attrs.value = res; 37 | }); 38 | 39 | return attrs; 40 | } 41 | -------------------------------------------------------------------------------- /src/hooks/core/useContext.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InjectionKey, 3 | provide, 4 | inject, 5 | reactive, 6 | readonly as defineReadonly, 7 | // defineComponent, 8 | UnwrapRef, 9 | } from 'vue'; 10 | 11 | export interface CreateContextOptions { 12 | readonly?: boolean; 13 | createProvider?: boolean; 14 | native?: boolean; 15 | } 16 | 17 | type ShallowUnwrap = { 18 | [P in keyof T]: UnwrapRef; 19 | }; 20 | 21 | export function createContext( 22 | context: any, 23 | key: InjectionKey = Symbol(), 24 | options: CreateContextOptions = {} 25 | ) { 26 | const { readonly = true, createProvider = false, native = false } = options; 27 | 28 | const state = reactive(context); 29 | const provideData = readonly ? defineReadonly(state) : state; 30 | !createProvider && provide(key, native ? context : provideData); 31 | 32 | return { 33 | state, 34 | }; 35 | } 36 | 37 | export function useContext(key: InjectionKey, native?: boolean): T; 38 | export function useContext(key: InjectionKey, defaultValue?: any, native?: boolean): T; 39 | 40 | export function useContext( 41 | key: InjectionKey = Symbol(), 42 | defaultValue?: any 43 | ): ShallowUnwrap { 44 | return inject(key, defaultValue || {}); 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/core/useNaiveInternal.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, computed } from 'vue'; 2 | import { darkTheme, lightTheme, GlobalTheme } from 'naive-ui'; 3 | import { useRootSetting } from '../setting/useRootSetting'; 4 | 5 | export function getNaiveCssVars(keyName: keyof GlobalTheme, isDark = false) { 6 | const _theme = isDark ? darkTheme : lightTheme; 7 | return _theme[keyName].self(_theme.common); 8 | } 9 | 10 | export function getAllNaiveCssVars(keyName: keyof GlobalTheme): Record { 11 | const lightList = getNaiveCssVars(keyName); 12 | const darkList = getNaiveCssVars(keyName, true); 13 | 14 | const all = new Proxy( 15 | {}, 16 | { 17 | get(_, propKey: string) { 18 | const match = propKey.match(/^(d_|dark_)(.+)$/); 19 | if (match) { 20 | return darkList[match[2]]; 21 | } 22 | return lightList[propKey]; 23 | }, 24 | } 25 | ); 26 | 27 | return Object.create(all); 28 | } 29 | 30 | export function getNaiveCssVarsRef(keyName: keyof GlobalTheme, propKey: string) { 31 | if (!getCurrentInstance()) return; 32 | const { getIsDarkMode } = useRootSetting(); 33 | const lightList = getNaiveCssVars(keyName); 34 | const darkList = getNaiveCssVars(keyName, true); 35 | const cssVarRef = computed((): string => { 36 | return getIsDarkMode.value ? darkList[propKey] : lightList[propKey]; 37 | }); 38 | 39 | return cssVarRef; 40 | } 41 | -------------------------------------------------------------------------------- /src/hooks/core/useRefs.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { ref, onBeforeUpdate } from 'vue'; 3 | 4 | export function useRefs(): [Ref, (index: number) => (el: HTMLElement) => void] { 5 | const refs = ref([]) as Ref; 6 | 7 | onBeforeUpdate(() => { 8 | refs.value = []; 9 | }); 10 | 11 | const setRefs = (index: number) => (el: HTMLElement) => { 12 | refs.value[index] = el; 13 | }; 14 | 15 | return [refs, setRefs]; 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/core/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | import { isFunction } from '@/utils/is'; 4 | 5 | export function useTimeoutFn(handle: Fn, wait: number, native = false) { 6 | if (!isFunction(handle)) { 7 | throw new Error('handle is not Function!'); 8 | } 9 | 10 | const { readyRef, stop, start } = useTimeoutRef(wait); 11 | if (native) { 12 | handle(); 13 | } else { 14 | watch( 15 | readyRef, 16 | (maturity) => { 17 | maturity && handle(); 18 | }, 19 | { immediate: false } 20 | ); 21 | } 22 | return { readyRef, stop, start }; 23 | } 24 | 25 | export function useTimeoutRef(wait: number) { 26 | const readyRef = ref(false); 27 | 28 | let timer: TimeoutHandle; 29 | function stop(): void { 30 | readyRef.value = false; 31 | timer && window.clearTimeout(timer); 32 | } 33 | function start(): void { 34 | stop(); 35 | timer = setTimeout(() => { 36 | readyRef.value = true; 37 | }, wait); 38 | } 39 | 40 | start(); 41 | 42 | tryOnUnmounted(stop); 43 | 44 | return { readyRef, stop, start }; 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/event/useBreakpoint.ts: -------------------------------------------------------------------------------- 1 | import { useBreakpoints } from '@vueuse/core'; 2 | import { getBreakpoint, sizeEnum } from '@/enums/breakpointEnum'; 3 | 4 | let breakpoints: ReturnType; 5 | export function useBreakpoint() { 6 | if (!breakpoints) { 7 | breakpoints = useBreakpoints(getBreakpoint); 8 | } 9 | return { ...breakpoints, sizeEnum }; 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/event/useIntersectionObserver.ts: -------------------------------------------------------------------------------- 1 | import { Ref, watchEffect, ref } from 'vue'; 2 | 3 | interface IntersectionObserverProps { 4 | target: Ref; 5 | root?: Ref; 6 | onIntersect: IntersectionObserverCallback; 7 | rootMargin?: string; 8 | threshold?: number; 9 | } 10 | 11 | export function useIntersectionObserver({ 12 | target, 13 | root, 14 | onIntersect, 15 | rootMargin = '0px', 16 | threshold = 0.1, 17 | }: IntersectionObserverProps) { 18 | let cleanup = () => {}; 19 | const observer: Ref> = ref(null); 20 | const stopEffect = watchEffect(() => { 21 | cleanup(); 22 | 23 | observer.value = new IntersectionObserver(onIntersect, { 24 | root: root ? root.value : null, 25 | rootMargin, 26 | threshold, 27 | }); 28 | 29 | const current = target.value; 30 | 31 | current && observer.value.observe(current); 32 | 33 | cleanup = () => { 34 | if (observer.value) { 35 | observer.value.disconnect(); 36 | target.value && observer.value.unobserve(target.value); 37 | } 38 | }; 39 | }); 40 | 41 | return { 42 | observer, 43 | stop: () => { 44 | cleanup(); 45 | stopEffect(); 46 | }, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/hooks/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; 2 | 3 | interface WindowSizeOptions { 4 | once?: boolean; 5 | immediate?: boolean; 6 | listenerOptions?: AddEventListenerOptions | boolean; 7 | } 8 | 9 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 10 | let handler = () => { 11 | fn(); 12 | }; 13 | const handleSize = useDebounceFn(handler, wait); 14 | handler = handleSize; 15 | 16 | const start = () => { 17 | if (options && options.immediate) { 18 | handler(); 19 | } 20 | window.addEventListener('resize', handler); 21 | }; 22 | 23 | const stop = () => { 24 | window.removeEventListener('resize', handler); 25 | }; 26 | 27 | tryOnMounted(() => { 28 | start(); 29 | }); 30 | 31 | tryOnUnmounted(() => { 32 | stop(); 33 | }); 34 | return [start, stop]; 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import type { GlobConfig } from '#/config'; 2 | 3 | import { warn } from '@/utils/log'; 4 | import { getAppEnvConfig } from '@/utils/env'; 5 | 6 | export const useGlobSetting = (): Readonly => { 7 | const { 8 | VITE_GLOB_APP_TITLE, 9 | VITE_GLOB_API_URL, 10 | VITE_GLOB_APP_SHORT_NAME, 11 | VITE_GLOB_API_URL_PREFIX, 12 | VITE_GLOB_UPLOAD_URL, 13 | } = getAppEnvConfig(); 14 | 15 | if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { 16 | warn( 17 | `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` 18 | ); 19 | } 20 | 21 | // Take global configuration 22 | const glob: Readonly = { 23 | title: VITE_GLOB_APP_TITLE, 24 | apiUrl: VITE_GLOB_API_URL, 25 | shortName: VITE_GLOB_APP_SHORT_NAME, 26 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 27 | uploadUrl: VITE_GLOB_UPLOAD_URL, 28 | }; 29 | return glob as Readonly; 30 | }; 31 | -------------------------------------------------------------------------------- /src/hooks/setting/useMultipleTabSetting.ts: -------------------------------------------------------------------------------- 1 | import type { MultiTabsSetting } from '#/config'; 2 | 3 | import { computed } from 'vue'; 4 | 5 | import { useAppStore } from '@/store/modules/app'; 6 | 7 | export function useMultipleTabSetting() { 8 | const appStore = useAppStore(); 9 | 10 | const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show); 11 | 12 | const getTabsStyle = computed(() => appStore.getMultiTabsSetting.tabs); 13 | 14 | function setMultipleTabSetting(multiTabsSetting: Partial) { 15 | appStore.setProjectConfig({ multiTabsSetting }); 16 | } 17 | return { 18 | setMultipleTabSetting, 19 | getShowMultipleTab, 20 | getTabsStyle, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/setting/useTransitionSetting.ts: -------------------------------------------------------------------------------- 1 | import type { TransitionSetting } from '#/config'; 2 | 3 | import { computed } from 'vue'; 4 | 5 | import { useAppStore } from '@/store/modules/app'; 6 | 7 | export function useTransitionSetting() { 8 | const appStore = useAppStore(); 9 | 10 | const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable); 11 | 12 | const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress); 13 | 14 | const getOpenPageLoading = computed((): boolean => { 15 | return !!appStore.getTransitionSetting?.openPageLoading; 16 | }); 17 | 18 | const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition); 19 | 20 | function setTransitionSetting(transitionSetting: Partial) { 21 | appStore.setProjectConfig({ transitionSetting }); 22 | } 23 | return { 24 | setTransitionSetting, 25 | 26 | getEnableTransition, 27 | getOpenNProgress, 28 | getOpenPageLoading, 29 | getBasicTransition, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/hooks/utilities/toWritableRef.ts: -------------------------------------------------------------------------------- 1 | import { watch, ref, Ref } from 'vue'; 2 | 3 | export function toWritableRef(object: T, key: K) { 4 | const val = ref(object[key]) as Ref; 5 | 6 | watch( 7 | () => object[key], 8 | (newValue) => { 9 | val.value = newValue; 10 | }, 11 | { flush: 'sync', deep: false, immediate: true } 12 | ); 13 | 14 | return val; 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/web/useAppInject.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '@/components/Application/useAppContext'; 2 | import { useContentSizeContext } from '@/layouts/default/content/useContentSizeContext'; 3 | import { usePageTransitionContext } from '@/layouts/page/useTransitonContext'; 4 | import { computed, unref } from 'vue'; 5 | 6 | export function useAppInject() { 7 | const values = useAppProviderContext(); 8 | 9 | return { 10 | getIsMobile: computed(() => unref(values.isMobile)), 11 | }; 12 | } 13 | 14 | export function useContentSizeInject() { 15 | const values = useContentSizeContext(); 16 | 17 | return { 18 | width: computed(() => unref(values.width)), 19 | height: computed(() => unref(values.height)), 20 | }; 21 | } 22 | 23 | export function usePageTransitionInject() { 24 | const values = usePageTransitionContext(); 25 | 26 | return { 27 | getPageTranstionState: computed(() => unref(values.pageTranstionState)), 28 | getPageTranstionName: computed(() => unref(values.pageTranstionName)), 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/hooks/web/useDesign.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '@/components/Application/useAppContext'; 2 | 3 | export function useDesign(scope: string) { 4 | const values = useAppProviderContext(); 5 | 6 | return { 7 | prefixCls: `${values.prefixCls}-${scope}`, 8 | prefixVar: values.prefixCls, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/web/useFullContent.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue'; 2 | 3 | import { useAppStore } from '@/store/modules/app'; 4 | 5 | import { useRouter } from 'vue-router'; 6 | 7 | /** 8 | * @description: Full screen display content 9 | */ 10 | export const useFullContent = () => { 11 | const appStore = useAppStore(); 12 | const router = useRouter(); 13 | const { currentRoute } = router; 14 | 15 | // Whether to display the content in full screen without displaying the menu 16 | const getFullContent = computed(() => { 17 | // Query parameters, the full screen is displayed when the address bar has a full parameter 18 | const route = unref(currentRoute); 19 | const { query } = route; 20 | if (query && Reflect.has(query, '__full__')) { 21 | return true; 22 | } 23 | // Return to the configuration in the configuration file 24 | return appStore.getProjectConfig.fullContent; 25 | }); 26 | 27 | return { getFullContent }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/hooks/web/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { watch, unref } from 'vue'; 2 | import { useI18n } from './useI18n'; 3 | import { useTitle as usePageTitle } from '@vueuse/core'; 4 | import { useGlobSetting } from '../setting'; 5 | import { useRouter } from 'vue-router'; 6 | import { useLocaleStore } from '@/store/modules/locale'; 7 | 8 | import { REDIRECT_NAME } from '@/router/constant'; 9 | 10 | /** 11 | * Listening to page changes and dynamically changing site titles 12 | */ 13 | export function useTitle() { 14 | const { title } = useGlobSetting(); 15 | const { t } = useI18n(); 16 | const { currentRoute } = useRouter(); 17 | const localeStore = useLocaleStore(); 18 | 19 | const pageTitle = usePageTitle(); 20 | 21 | watch( 22 | [() => currentRoute.value.path, () => localeStore.getLocale], 23 | () => { 24 | const route = unref(currentRoute); 25 | 26 | if (route.name === REDIRECT_NAME) { 27 | return; 28 | } 29 | 30 | const tTitle = t(route?.meta?.title as string); 31 | pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`; 32 | }, 33 | { immediate: true } 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/layouts/default/content/useContentSizeContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | import { createContext, useContext } from '@/hooks/core/useContext'; 3 | 4 | export interface ContentSizeContextProps { 5 | width: Ref; 6 | height: Ref; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createContentSizeContext(context: ContentSizeContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useContentSizeContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 19 | 44 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { NSkeleton } from 'naive-ui'; 2 | import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'; 3 | 4 | export { default as FullScreen } from './FullScreen.vue'; 5 | 6 | export const UserDropDown = createAsyncComponent(() => import('./UserDropdown.vue'), { 7 | loadingComponent: ( 8 |
9 | 10 | 11 |
12 | ), 13 | }); 14 | 15 | export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); 16 | 17 | export const Notify = createAsyncComponent(() => import('./notify/index.vue')); 18 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/SliderItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 42 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/SwitchItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 46 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/ThemeColorPicker.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 46 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/Wrapper.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/index.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'; 2 | 3 | export const LayoutModePicker = createAsyncComponent(() => import('./LayoutModePicker.vue')); 4 | export const ThemeModePicker = createAsyncComponent(() => import('./ThemeModePicker.vue')); 5 | export const ThemeColorPicker = createAsyncComponent(() => import('./ThemeColorPicker.vue')); 6 | export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue')); 7 | export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue')); 8 | export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue')); 9 | export const SliderItem = createAsyncComponent(() => import('./SliderItem.vue')); 10 | export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue')); 11 | -------------------------------------------------------------------------------- /src/layouts/default/setting/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 27 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/FoldButton.vue: -------------------------------------------------------------------------------- 1 | 7 | 31 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/TabRedo.vue: -------------------------------------------------------------------------------- 1 | 14 | 36 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/useExtra.ts: -------------------------------------------------------------------------------- 1 | export default function getCommonCls() { 2 | return { 3 | wrapperClass: 4 | 'flex-1 border-l border-$app-border-color transition-border duration-300 ease-in-out group flex justify-center items-center text-$app-text-color', 5 | iconClass: 'opacity-60 group-hover:opacity-100 text-18px', 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/types.ts: -------------------------------------------------------------------------------- 1 | // import type { DropMenu } from '@/components/Dropdown/index'; 2 | import type { RouteLocationNormalized } from 'vue-router'; 3 | 4 | export enum TabContentEnum { 5 | TAB_TYPE, 6 | EXTRA_TYPE, 7 | } 8 | 9 | // export type { DropMenu }; 10 | 11 | export interface TabContentProps { 12 | tabItem: RouteLocationNormalized; 13 | type?: TabContentEnum; 14 | trigger?: ('click' | 'hover' | 'contextmenu')[]; 15 | } 16 | 17 | export enum MenuEventEnum { 18 | REFRESH_PAGE, 19 | TOGGLE_FULL_SCREEN, 20 | CLOSE_CURRENT, 21 | CLOSE_LEFT, 22 | CLOSE_RIGHT, 23 | CLOSE_OTHER, 24 | CLOSE_ALL, 25 | SCALE, 26 | } 27 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/useMultipleTabs.ts: -------------------------------------------------------------------------------- 1 | import { toRaw, ref } from 'vue'; 2 | import type { RouteLocationNormalized } from 'vue-router'; 3 | import { useMultipleTabStore } from '@/store/modules/multipleTab'; 4 | import { useRouter } from 'vue-router'; 5 | 6 | export function initAffixTabs(): string[] { 7 | const affixList = ref([]); 8 | 9 | const tabStore = useMultipleTabStore(); 10 | const router = useRouter(); 11 | /** 12 | * @description: Filter all fixed routes 13 | */ 14 | function filterAffixTabs(routes: RouteLocationNormalized[]) { 15 | const tabs: RouteLocationNormalized[] = []; 16 | routes && 17 | routes.forEach((route) => { 18 | if (route.meta && route.meta.affix) { 19 | tabs.push(toRaw(route)); 20 | } 21 | }); 22 | return tabs; 23 | } 24 | 25 | /** 26 | * @description: Set fixed tabs 27 | */ 28 | function addAffixTabs(): void { 29 | const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[]); 30 | affixList.value = affixTabs; 31 | for (const tab of affixTabs) { 32 | tabStore.addTab({ 33 | meta: tab.meta, 34 | name: tab.name, 35 | path: tab.path, 36 | } as unknown as RouteLocationNormalized); 37 | } 38 | } 39 | 40 | let isAddAffix = false; 41 | 42 | if (!isAddAffix) { 43 | addAffixTabs(); 44 | isAddAffix = true; 45 | } 46 | return affixList.value.map((item) => item.meta?.title).filter(Boolean) as string[]; 47 | } 48 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/HeaderTrigger.vue: -------------------------------------------------------------------------------- 1 | 10 | 20 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 47 | -------------------------------------------------------------------------------- /src/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 30 | -------------------------------------------------------------------------------- /src/layouts/page/useTransitonContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | import { createContext, useContext } from '../../hooks/core/useContext'; 3 | 4 | export type PageTranstionState = 'readly' | 'running' | 'finshed'; 5 | 6 | export interface PageTransitionContextProps { 7 | pageTranstionName: Ref; 8 | pageTranstionState: Ref; 9 | } 10 | 11 | const key: InjectionKey = Symbol(); 12 | 13 | export function createPageTransitionContext(context: PageTransitionContextProps) { 14 | return createContext(context, key); 15 | } 16 | 17 | export function usePageTransitionContext() { 18 | return useContext(key); 19 | } 20 | -------------------------------------------------------------------------------- /src/locales/helper.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleType } from '#/config'; 2 | 3 | import { set } from 'lodash-es'; 4 | 5 | export const loadLocalePool: LocaleType[] = []; 6 | 7 | export function setHtmlPageLang(locale: LocaleType) { 8 | document.querySelector('html')?.setAttribute('lang', locale); 9 | } 10 | 11 | export function setLoadLocalePool(cb: (loadLocalePool: LocaleType[]) => void) { 12 | cb(loadLocalePool); 13 | } 14 | 15 | export function genMessage(langs: Record>, prefix = 'lang') { 16 | const obj: Recordable = {}; 17 | 18 | Object.keys(langs).forEach((key) => { 19 | const langFileModule = langs[key].default; 20 | let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, ''); 21 | const lastIndex = fileName.lastIndexOf('.'); 22 | fileName = fileName.substring(0, lastIndex); 23 | const keyList = fileName.split('/'); 24 | const moduleName = keyList.shift(); 25 | const objKey = keyList.join('.'); 26 | 27 | if (moduleName) { 28 | if (objKey) { 29 | set(obj, moduleName, obj[moduleName] || {}); 30 | set(obj[moduleName], objKey, langFileModule); 31 | } else { 32 | set(obj, moduleName, langFileModule || {}); 33 | } 34 | } 35 | }); 36 | return obj; 37 | } 38 | -------------------------------------------------------------------------------- /src/locales/lang/en.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | 3 | const modules = import.meta.glob('./en/**/*.ts', { eager: true }); 4 | export default { 5 | message: { 6 | ...genMessage(modules, 'en'), 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/locales/lang/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | okText: 'OK', 3 | closeText: 'Close', 4 | cancelText: 'Cancel', 5 | loadingText: 'Loading...', 6 | saveText: 'Save', 7 | delText: 'Delete', 8 | resetText: 'Reset', 9 | searchText: 'Search', 10 | queryText: 'Search', 11 | 12 | inputText: 'Please enter', 13 | chooseText: 'Please choose', 14 | 15 | redo: 'Refresh', 16 | back: 'Back', 17 | 18 | light: 'Light', 19 | dark: 'Dark', 20 | menuDark: 'MenuDark', 21 | }; 22 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: 'Login', 3 | errorLogList: 'Error Log', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: 'About', 4 | workbench: 'Workbench', 5 | analysis: 'Analysis', 6 | }; 7 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | 3 | const modules = import.meta.glob('./zh_CN/**/*.ts', { eager: true }); 4 | export default { 5 | message: { 6 | ...genMessage(modules, 'zh_CN'), 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | okText: '确认', 3 | closeText: '关闭', 4 | cancelText: '取消', 5 | loadingText: '加载中...', 6 | saveText: '保存', 7 | delText: '删除', 8 | resetText: '重置', 9 | searchText: '搜索', 10 | queryText: '查询', 11 | 12 | inputText: '请输入', 13 | chooseText: '请选择', 14 | 15 | redo: '刷新', 16 | back: '返回', 17 | 18 | light: '亮色主题', 19 | dark: '黑暗主题', 20 | menuDark: '菜单暗色主题', 21 | }; 22 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: '登录', 3 | errorLogList: '错误日志列表', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: '关于', 4 | workbench: '工作台', 5 | analysis: '分析页', 6 | }; 7 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/routes/demo.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | comp: '组件', 4 | basic: '基础组件', 5 | transition: '动画组件', 6 | countTo: '数字动画', 7 | 8 | tree: 'Tree', 9 | treeBasic: '基础树', 10 | editTree: '可搜索/工具栏', 11 | actionTree: '函数操作示例', 12 | 13 | modal: '弹窗扩展', 14 | drawer: '抽屉扩展', 15 | desc: '详情组件', 16 | 17 | loading: 'Loading', 18 | 19 | time: '相对时间', 20 | cropperImage: '图片裁剪', 21 | }, 22 | editor: { 23 | editor: '编辑器', 24 | jsonEditor: 'Json编辑器', 25 | markdown: 'markdown编辑器', 26 | 27 | tinymce: '富文本', 28 | }, 29 | form: { 30 | form: '表单', 31 | basic: '表单基础示例', 32 | useForm: 'useForm', 33 | }, 34 | iframe: { 35 | frame: '外部页面', 36 | naiveUI: 'naive-ui文档(内嵌)', 37 | doc: 'vue文档(内嵌)', 38 | }, 39 | level: { level: '多级菜单' }, 40 | page: { 41 | page: '页面', 42 | 43 | form: '表单页', 44 | formBasic: '基础表单', 45 | formStep: '分步表单', 46 | formHigh: '高级表单', 47 | 48 | desc: '详情页', 49 | descBasic: '基础详情页', 50 | descHigh: '高级详情页', 51 | 52 | result: '结果页', 53 | resultSuccess: '成功页', 54 | resultFail: '失败页', 55 | 56 | account: '个人页', 57 | accountCenter: '个人中心', 58 | accountSetting: '个人设置', 59 | 60 | exception: '异常页', 61 | netWorkError: '网络错误', 62 | notData: '无数据', 63 | 64 | list: '列表页', 65 | listCard: '卡片列表', 66 | listBasic: '标准列表', 67 | }, 68 | table: { 69 | table: '表格', 70 | basic: '基础表格', 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /src/locales/setupI18n.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import type { I18n, I18nOptions } from 'vue-i18n'; 3 | 4 | import { createI18n } from 'vue-i18n'; 5 | import { setHtmlPageLang, setLoadLocalePool } from './helper'; 6 | import { localeSetting } from '@/settings/localeSetting'; 7 | import { useLocaleStoreWithOut } from '@/store/modules/locale'; 8 | 9 | const { fallback, availableLocales } = localeSetting; 10 | 11 | export let i18n: ReturnType; 12 | 13 | async function createI18nOptions(): Promise { 14 | const localeStore = useLocaleStoreWithOut(); 15 | const locale = localeStore.getLocale; 16 | const defaultLocal = await import(`./lang/${locale}.ts`); 17 | const message = defaultLocal.default?.message ?? {}; 18 | 19 | setHtmlPageLang(locale); 20 | setLoadLocalePool((loadLocalePool) => { 21 | loadLocalePool.push(locale); 22 | }); 23 | 24 | return { 25 | legacy: false, 26 | locale, 27 | fallbackLocale: fallback, 28 | messages: { 29 | [locale]: message, 30 | }, 31 | availableLocales, 32 | sync: true, // If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false. 33 | silentTranslationWarn: true, // true - warning off 34 | missingWarn: false, 35 | silentFallbackWarn: true, 36 | }; 37 | } 38 | 39 | // setup i18n instance with glob 40 | export async function setupI18n(app: App) { 41 | const options = await createI18nOptions(); 42 | i18n = createI18n(options) as I18n; 43 | app.use(i18n); 44 | } 45 | -------------------------------------------------------------------------------- /src/logics/mitt/layoutContentResize.ts: -------------------------------------------------------------------------------- 1 | import { useEventBus, EventBusKey, useDebounceFn, tryOnScopeDispose } from '@vueuse/core'; 2 | 3 | type Fn = (...args: any[]) => any; 4 | type Opitons = { 5 | wait?: number; 6 | immediate?: boolean; 7 | isPassPars?: boolean; 8 | }; 9 | const key: EventBusKey = Symbol(); 10 | 11 | export const useLayoutContentResize = (_key?: string | number | symbol, autoOff = true) => { 12 | const { on: _on, off: _off, reset, emit } = useEventBus(_key ?? key); 13 | let debounceFn: Fn; 14 | let WrapDebounceFn: Fn; 15 | 16 | function on(callback: Fn, { wait = 150, immediate = false, isPassPars = true }: Opitons = {}) { 17 | debounceFn = useDebounceFn(callback, wait); 18 | WrapDebounceFn = () => { 19 | debounceFn(); 20 | }; 21 | if (isPassPars) { 22 | _on(debounceFn); 23 | } else { 24 | _on(() => { 25 | debounceFn(); 26 | }); 27 | } 28 | immediate && emit(); 29 | } 30 | 31 | function off(fun?: Fn) { 32 | const fn = fun || debounceFn || WrapDebounceFn; 33 | fn && _off(fn); 34 | } 35 | 36 | tryOnScopeDispose(() => { 37 | autoOff && off(); 38 | }); 39 | 40 | return { on, off, reset, emit }; 41 | }; 42 | -------------------------------------------------------------------------------- /src/logics/theme/dark.ts: -------------------------------------------------------------------------------- 1 | // import { darkCssIsReady, loadDarkThemeCss } from 'vite-plugin-theme/es/client'; 2 | import { addClass, hasClass, removeClass } from '@/utils/domUtils'; 3 | 4 | export async function updateDarkTheme(mode: string | null = 'light') { 5 | const htmlRoot = document.getElementById('htmlRoot'); 6 | if (!htmlRoot) { 7 | return; 8 | } 9 | const hasDarkClass = hasClass(htmlRoot, 'dark'); 10 | if (mode === 'dark') { 11 | // if (import.meta.env.PROD && !darkCssIsReady) { 12 | // await loadDarkThemeCss(); 13 | // } 14 | htmlRoot.setAttribute('data-theme', 'dark'); 15 | if (!hasDarkClass) { 16 | addClass(htmlRoot, 'dark'); 17 | } 18 | } else { 19 | htmlRoot.setAttribute('data-theme', 'light'); 20 | if (hasDarkClass) { 21 | removeClass(htmlRoot, 'dark'); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/logics/theme/util.ts: -------------------------------------------------------------------------------- 1 | const docEle = document.documentElement; 2 | export function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { 3 | const targetEl = target || document.body; 4 | let { className } = targetEl; 5 | className = className.replace(clsName, ''); 6 | targetEl.className = flag ? `${className} ${clsName} ` : className; 7 | } 8 | 9 | export function setCssVar(prop: string, val: any, dom = docEle) { 10 | dom.style.setProperty(prop, val); 11 | } 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'virtual:uno.css'; 2 | import '@/design/index.less'; 3 | // Register icon sprite 4 | import 'virtual:svg-icons-register'; 5 | import { createApp } from 'vue'; 6 | import App from './App.vue'; 7 | 8 | import { initAppConfigStore } from '@/logics/initAppConfig'; 9 | import router, { setupRouter } from '@/router'; 10 | import { setupRouterGuard } from '@/router/guard'; 11 | import { setupStore } from '@/store'; 12 | import { setupGlobDirectives } from '@/directives'; 13 | import { setupI18n } from '@/locales/setupI18n'; 14 | import { setupService } from './service'; 15 | 16 | async function bootstrap() { 17 | const app = createApp(App); 18 | 19 | // Configure store 20 | setupStore(app); 21 | // Register global request service 22 | setupService(); 23 | // Initialize internal system configuration 24 | initAppConfigStore(); 25 | 26 | // Configure routing 27 | setupRouter(app); 28 | 29 | // router-guard 30 | setupRouterGuard(router); 31 | 32 | // Multilingual configuration 33 | await setupI18n(app); 34 | 35 | // Register global directive 36 | setupGlobDirectives(app); 37 | 38 | await router.isReady(); 39 | 40 | app.mount('#app', true); 41 | } 42 | 43 | void bootstrap(); 44 | -------------------------------------------------------------------------------- /src/router/constant.ts: -------------------------------------------------------------------------------- 1 | export const REDIRECT_NAME = 'Redirect'; 2 | 3 | export const PARENT_LAYOUT_NAME = 'ParentLayout'; 4 | 5 | export const PAGE_NOT_FOUND_NAME = 'PageNotFound'; 6 | 7 | export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue'); 8 | 9 | /** 10 | * @description: default layout 11 | */ 12 | export const LAYOUT = () => import('@/layouts/default/index.vue'); 13 | 14 | /** 15 | * @description: parent-layout 16 | */ 17 | export const getParentLayout = (_name?: string) => { 18 | return () => 19 | new Promise((resolve) => { 20 | resolve({ 21 | name: PARENT_LAYOUT_NAME, 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/router/guard/stateGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | import { useAppStore } from '@/store/modules/app'; 3 | import { useMultipleTabStore } from '@/store/modules/multipleTab'; 4 | import { useUserStore } from '@/store/modules/user'; 5 | import { usePermissionStore } from '@/store/modules/permission'; 6 | import { PageEnum } from '@/enums/pageEnum'; 7 | import { removeTabChangeListener } from '@/logics/mitt/routeChange'; 8 | 9 | export function createStateGuard(router: Router) { 10 | router.afterEach((to) => { 11 | // Just enter the login page and clear the authentication information 12 | if (to.path === PageEnum.BASE_LOGIN) { 13 | const tabStore = useMultipleTabStore(); 14 | const userStore = useUserStore(); 15 | const appStore = useAppStore(); 16 | const permissionStore = usePermissionStore(); 17 | appStore.resetAllState(); 18 | permissionStore.resetState(); 19 | tabStore.resetState(); 20 | userStore.resetState(); 21 | removeTabChangeListener(); 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router'; 2 | import type { App } from 'vue'; 3 | 4 | import { createRouter, createWebHashHistory } from 'vue-router'; 5 | 6 | import { basicRoutes } from './routes'; 7 | 8 | // 白名单应该包含基本静态路由 9 | const WHITE_NAME_LIST: string[] = []; 10 | const getRouteNames = (array: any[]) => 11 | array.forEach((item) => { 12 | WHITE_NAME_LIST.push(item.name); 13 | getRouteNames(item.children || []); 14 | }); 15 | getRouteNames(basicRoutes); 16 | 17 | // app router 18 | export const router = createRouter({ 19 | history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH), 20 | routes: basicRoutes as unknown as RouteRecordRaw[], 21 | strict: true, 22 | scrollBehavior: () => ({ left: 0, top: 0 }), 23 | }); 24 | 25 | // reset router 26 | export function resetRouter() { 27 | router.getRoutes().forEach((route) => { 28 | const { name } = route; 29 | if (name && !WHITE_NAME_LIST.includes(name as string)) { 30 | router.hasRoute(name) && router.removeRoute(name); 31 | } 32 | }); 33 | } 34 | 35 | // config router 36 | export function setupRouter(app: App) { 37 | app.use(router); 38 | } 39 | 40 | export default router; 41 | -------------------------------------------------------------------------------- /src/router/routes/index.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteRecordRaw, AppRouteModule } from '@/router/types'; 2 | 3 | import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'; 4 | 5 | import { mainOutRoutes } from './mainOut'; 6 | import { PageEnum } from '@/enums/pageEnum'; 7 | import { t } from '@/hooks/web/useI18n'; 8 | 9 | const modules = import.meta.glob('./modules/**/*.ts', { eager: true }); 10 | 11 | const routeModuleList: AppRouteModule[] = []; 12 | 13 | // 加入到路由集合中 14 | Object.keys(modules).forEach((key) => { 15 | const mod = (modules as Recordable)[key].default || {}; 16 | const modList = Array.isArray(mod) ? [...mod] : [mod]; 17 | routeModuleList.push(...modList); 18 | }); 19 | 20 | export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; 21 | 22 | export const RootRoute: AppRouteRecordRaw = { 23 | path: '/', 24 | name: 'Root', 25 | redirect: PageEnum.BASE_HOME, 26 | meta: { 27 | title: 'Root', 28 | }, 29 | }; 30 | 31 | export const LoginRoute: AppRouteRecordRaw = { 32 | path: '/login', 33 | name: 'Login', 34 | component: () => import('@/views/sys/login/Login.vue'), 35 | meta: { 36 | title: t('routes.basic.login'), 37 | }, 38 | }; 39 | 40 | // Basic routing without permission 41 | export const basicRoutes = [ 42 | LoginRoute, 43 | RootRoute, 44 | ...mainOutRoutes, 45 | REDIRECT_ROUTE, 46 | PAGE_NOT_FOUND_ROUTE, 47 | ]; 48 | -------------------------------------------------------------------------------- /src/router/routes/mainOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | The routing of this file will not show the layout. 3 | It is an independent new page. 4 | the contents of the file still need to log in to access 5 | */ 6 | import type { AppRouteModule } from '@/router/types'; 7 | 8 | // test 9 | // http:ip:port/main-out 10 | export const mainOutRoutes: AppRouteModule[] = [ 11 | { 12 | path: '/main-out', 13 | name: 'MainOut', 14 | component: () => import('@/views/demo/main-out/index.vue'), 15 | meta: { 16 | title: 'MainOut', 17 | ignoreAuth: true, 18 | }, 19 | }, 20 | ]; 21 | 22 | export const mainOutRouteNames = mainOutRoutes.map((item) => item.name); 23 | -------------------------------------------------------------------------------- /src/router/routes/modules/about.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../hooks/web/useI18n'; 5 | 6 | const dashboard: AppRouteModule = { 7 | path: '/about', 8 | name: 'About', 9 | component: LAYOUT, 10 | redirect: '/about/index', 11 | meta: { 12 | hideChildrenInMenu: true, 13 | icon: 'bx:bx-message-dots', 14 | title: t('routes.dashboard.about'), 15 | orderNo: 100000, 16 | }, 17 | children: [ 18 | { 19 | path: 'index', 20 | name: 'AboutPage', 21 | component: () => import('@/views/sys/about/index.vue'), 22 | }, 23 | ], 24 | }; 25 | 26 | export default dashboard; 27 | -------------------------------------------------------------------------------- /src/router/routes/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../hooks/web/useI18n'; 5 | 6 | const dashboard: AppRouteModule = { 7 | path: '/dashboard', 8 | name: 'Dashboard', 9 | component: LAYOUT, 10 | redirect: '/dashboard/analysis', 11 | meta: { 12 | orderNo: 0, 13 | // icon: 'ion:grid-outline', 14 | icon: 'moon|svg', 15 | title: t('routes.dashboard.dashboard'), 16 | }, 17 | children: [ 18 | { 19 | path: 'analysis', 20 | name: 'Analysis', 21 | component: () => import('@/views/dashboard/analysis/index.vue'), 22 | meta: { 23 | title: t('routes.dashboard.analysis'), 24 | }, 25 | }, 26 | { 27 | path: 'workbench', 28 | name: 'Workbench', 29 | component: () => import('@/views/dashboard/workbench/index.vue'), 30 | meta: { 31 | // affix: true, 32 | title: t('routes.dashboard.workbench'), 33 | }, 34 | }, 35 | ], 36 | }; 37 | 38 | export default dashboard; 39 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/accout.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../../hooks/web/useI18n'; 5 | 6 | const routes: AppRouteModule = { 7 | path: '/account', 8 | name: 'AccountPage', 9 | component: LAYOUT, 10 | redirect: '/account/setting', 11 | meta: { 12 | orderNo: 600, 13 | icon: 'mdi:qrcode-plus', 14 | title: t('routes.demo.page.account'), 15 | }, 16 | children: [ 17 | { 18 | path: 'center', 19 | name: 'AccountCenterPage', 20 | component: () => import('@/views/demo/page/account/center/accountCenterPage.vue'), 21 | meta: { 22 | title: t('routes.demo.page.accountCenter'), 23 | }, 24 | }, 25 | { 26 | path: 'setting', 27 | name: 'AccountSettingPage', 28 | component: () => import('@/views/demo/page/account/setting/accountSettingPage.vue'), 29 | meta: { 30 | title: t('routes.demo.page.accountSetting'), 31 | }, 32 | }, 33 | ], 34 | }; 35 | 36 | export default routes; 37 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/desc.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../../hooks/web/useI18n'; 5 | 6 | const routes: AppRouteModule = { 7 | path: '/desc', 8 | name: 'DescPage', 9 | component: LAYOUT, 10 | redirect: '/desc/base-desc', 11 | meta: { 12 | orderNo: 300, 13 | icon: 'ic:outline-description', 14 | title: t('routes.demo.page.desc'), 15 | }, 16 | children: [ 17 | { 18 | path: 'base-desc', 19 | name: 'baseDescPage', 20 | component: () => import('@/views/demo/page/desc/basic/basicDescPage.vue'), 21 | meta: { 22 | title: t('routes.demo.page.descBasic'), 23 | }, 24 | }, 25 | { 26 | path: 'high-desc', 27 | name: 'highDescPage', 28 | component: () => import('@/views/demo/page/desc/high/highDescPage.vue'), 29 | meta: { 30 | title: t('routes.demo.page.descHigh'), 31 | }, 32 | }, 33 | ], 34 | }; 35 | 36 | export default routes; 37 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/form.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../../hooks/web/useI18n'; 5 | 6 | const routes: AppRouteModule = { 7 | path: '/form', 8 | name: 'FormPage', 9 | component: LAYOUT, 10 | redirect: '/form/base-form', 11 | meta: { 12 | orderNo: 100, 13 | icon: 'ant-design:form-outlined', 14 | title: t('routes.demo.page.form'), 15 | }, 16 | children: [ 17 | { 18 | path: 'base-form', 19 | name: 'BaseFromPage', 20 | component: () => import('@/views/demo/page/form/basicFormPage.vue'), 21 | meta: { 22 | title: t('routes.demo.page.formBasic'), 23 | }, 24 | }, 25 | { 26 | path: 'step-form', 27 | name: 'StepFormPage', 28 | component: () => import('@/views/demo/page/form/step/stepFormPage.vue'), 29 | meta: { 30 | title: t('routes.demo.page.formStep'), 31 | }, 32 | }, 33 | { 34 | path: 'high-form', 35 | name: 'HighFormPage', 36 | component: () => import('@/views/demo/page/form/high/highFormPage.vue'), 37 | meta: { 38 | title: t('routes.demo.page.formHigh'), 39 | }, 40 | }, 41 | ], 42 | }; 43 | 44 | export default routes; 45 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/iframe.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../../hooks/web/useI18n'; 5 | 6 | const IFrame = () => import('@/views/sys/iframe/FrameBlank.vue'); 7 | 8 | const routes: AppRouteModule = { 9 | path: '/frame', 10 | name: 'Frame', 11 | component: LAYOUT, 12 | redirect: '/frame/doc', 13 | meta: { 14 | orderNo: 800, 15 | icon: 'ion:tv-outline', 16 | title: t('routes.demo.iframe.frame'), 17 | }, 18 | 19 | children: [ 20 | { 21 | path: 'doc', 22 | name: 'Doc', 23 | component: IFrame, 24 | meta: { 25 | frameSrc: 'https://v3.cn.vuejs.org/', 26 | title: t('routes.demo.iframe.doc'), 27 | hideLoading: true, 28 | }, 29 | }, 30 | { 31 | path: 'naive-ui-doc', 32 | name: 'NaiveUiDoc', 33 | component: IFrame, 34 | meta: { 35 | frameSrc: 'https://www.naiveui.com/', 36 | title: t('routes.demo.iframe.naiveUI'), 37 | hideLoading: true, 38 | }, 39 | }, 40 | ], 41 | }; 42 | 43 | export default routes; 44 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/list.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../../hooks/web/useI18n'; 5 | 6 | const routes: AppRouteModule = { 7 | path: '/list', 8 | name: 'ListPage', 9 | component: LAYOUT, 10 | redirect: '/list/base-list', 11 | meta: { 12 | orderNo: 200, 13 | icon: 'ci:list-ul', 14 | title: t('routes.demo.page.list'), 15 | }, 16 | children: [ 17 | { 18 | path: 'base-list', 19 | name: 'BaseListPage', 20 | component: () => import('@/views/demo/page/list/basicListPage.vue'), 21 | meta: { 22 | title: t('routes.demo.page.listBasic'), 23 | }, 24 | }, 25 | { 26 | path: 'card-list', 27 | name: 'CardListPage', 28 | component: () => import('@/views/demo/page/list/cardListPage.vue'), 29 | meta: { 30 | title: t('routes.demo.page.listCard'), 31 | }, 32 | }, 33 | ], 34 | }; 35 | 36 | export default routes; 37 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/result.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '@/router/types'; 2 | 3 | import { LAYOUT } from '@/router/constant'; 4 | import { t } from '../../../../hooks/web/useI18n'; 5 | 6 | const routes: AppRouteModule = { 7 | path: '/result', 8 | name: 'ResultPage', 9 | component: LAYOUT, 10 | redirect: '/result/success-result', 11 | meta: { 12 | orderNo: 500, 13 | icon: 'akar-icons:circle-check', 14 | title: t('routes.demo.page.result'), 15 | }, 16 | children: [ 17 | { 18 | path: 'success-result', 19 | name: 'successResultPage', 20 | component: () => import('@/views/demo/page/result/successResultPage.vue'), 21 | meta: { 22 | title: t('routes.demo.page.resultSuccess'), 23 | }, 24 | }, 25 | { 26 | path: 'fail-result', 27 | name: 'failResultPage', 28 | component: () => import('@/views/demo/page/result/failResultPage.vue'), 29 | meta: { 30 | title: t('routes.demo.page.resultFail'), 31 | }, 32 | }, 33 | ], 34 | }; 35 | 36 | export default routes; 37 | -------------------------------------------------------------------------------- /src/router/types.ts: -------------------------------------------------------------------------------- 1 | import type { HTMLAttributes } from 'vue'; 2 | import type { RouteRecordRaw, RouteMeta } from 'vue-router'; 3 | import type { MenuOption } from 'naive-ui'; 4 | import { RoleEnum } from '@/enums/roleEnum'; 5 | import { defineComponent } from 'vue'; 6 | 7 | export type Component = 8 | | ReturnType 9 | | (() => Promise) 10 | | (() => Promise); 11 | 12 | // @ts-ignore 13 | export interface AppRouteRecordRaw extends Omit { 14 | name: string; 15 | meta?: RouteMeta; 16 | component?: Component | string; 17 | components?: Component; 18 | children?: AppRouteRecordRaw[]; 19 | props?: Recordable; 20 | fullPath?: string; 21 | } 22 | 23 | export interface MenuTag { 24 | type?: 'primary' | 'error' | 'warn' | 'success'; 25 | content?: string; 26 | dot?: boolean; 27 | } 28 | 29 | export type Menu = MenuOption & { 30 | label: string; 31 | name: string; 32 | icon?: string; 33 | props?: HTMLAttributes; 34 | path: string; 35 | paramPath?: string; 36 | disabled?: boolean; 37 | children?: Menu[]; 38 | orderNo?: number; 39 | roles?: RoleEnum[]; 40 | meta?: Partial; 41 | tag?: MenuTag; 42 | hideMenu?: boolean; 43 | redirect?: string; 44 | }; 45 | 46 | export interface MenuModule { 47 | orderNo?: number; 48 | menu: Menu; 49 | } 50 | 51 | // export type AppRouteModule = RouteModule | AppRouteRecordRaw; 52 | export type AppRouteModule = AppRouteRecordRaw; 53 | -------------------------------------------------------------------------------- /src/service/annotations.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '@/utils/is'; 2 | 3 | /** 4 | * 菜单请求的权限 5 | * @date 2022-03-08 6 | * @param {any} value:string 7 | * @returns {any} 8 | */ 9 | export function Permission(value: string) { 10 | return function (target: any, key: any, descriptor: any) { 11 | if (!target.permission) { 12 | target.permission = {}; 13 | } 14 | 15 | setTimeout(() => { 16 | target.permission[key] = ((target.namespace ? target.namespace + '/' : '') + value).replace( 17 | /\//g, 18 | ':' 19 | ); 20 | }, 0); 21 | return descriptor; 22 | }; 23 | } 24 | 25 | export function Service(value: any) { 26 | return function (target: any) { 27 | // 命名 28 | if (typeof value == 'string') { 29 | target.prototype.namespace = value; 30 | } 31 | 32 | // 复杂项 33 | if (isObject(value)) { 34 | const { proxy, namespace, url } = value; 35 | console.log(value); 36 | const item = __PROXY_LIST__[proxy]; 37 | 38 | if (proxy && !item) { 39 | console.error(`${proxy} 指向的地址不存在!`); 40 | } 41 | 42 | target.prototype.namespace = namespace; 43 | 44 | if (proxy) { 45 | target.prototype.proxy = proxy; 46 | target.prototype.url = url || item ? item.target : null; 47 | } 48 | } 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/service/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseService } from './common'; 2 | import { baseName } from '@/utils'; 3 | import { useServiceStoreWithOut } from '@/store/modules/service'; 4 | 5 | function deepFiles(list: any[]) { 6 | const modules: any = {}; 7 | 8 | list.forEach((e) => { 9 | const arr: any[] = e.path.split('/'); 10 | const parents: any[] = arr.slice(0, arr.length - 1); 11 | const name: string = baseName(e.path).replace('.ts', ''); 12 | 13 | let curr: any = modules; 14 | let prev: any = null; 15 | let key: any = null; 16 | 17 | parents.forEach((k) => { 18 | if (!curr[k]) { 19 | curr[k] = {}; 20 | } 21 | 22 | prev = curr; 23 | curr = curr[k]; 24 | key = k; 25 | }); 26 | 27 | if (name == 'index') { 28 | prev[key] = e.value; 29 | } else { 30 | curr[name] = e.value; 31 | } 32 | }); 33 | 34 | return modules; 35 | } 36 | 37 | function setupService() { 38 | // 扫描文件 39 | const files = import.meta.glob('/src/api/**/*.ts', { eager: true }); 40 | const array: any = []; 41 | for (const i in files) { 42 | const value = files[i].default; 43 | array.push({ 44 | path: i.replace('/src/api/', ''), 45 | value: new value(), 46 | }); 47 | } 48 | 49 | const s = deepFiles(array); 50 | s.request = new BaseService().request; 51 | // 将各个请求方法保存到vuex 52 | useServiceStoreWithOut().setService(s); 53 | return s; 54 | } 55 | 56 | export { BaseService, deepFiles, setupService }; 57 | -------------------------------------------------------------------------------- /src/settings/componentSetting.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // basic-table setting 3 | table: { 4 | apiSetting: { 5 | // 当前页的字段名 6 | pageField: 'page', 7 | // 每页数量字段名 8 | sizeField: 'pageSize', 9 | // 接口返回的数据字段名 10 | listField: 'items', 11 | // 接口返回总页数字段名 12 | totalField: 'total', 13 | }, 14 | // 默认分页数量 15 | defaultPageSize: 10, 16 | // 可切换每页数量集合 17 | pageSizes: [10, 20, 30, 40, 50], 18 | }, 19 | upload: { 20 | // 考虑接口规范不同 21 | apiSetting: { 22 | // 集合字段名 23 | infoField: 'data', 24 | // 图片地址字段名 25 | imgField: 'photo', 26 | }, 27 | // 最大上传图片大小 28 | maxSize: 2, 29 | // 图片上传类型 30 | fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'], 31 | }, 32 | // scrollbar setting 33 | scrollbar: { 34 | // Whether to use native scroll bar 35 | // After opening, the menu, modal, drawer will change the pop-up scroll bar to native 36 | native: false, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { ThemeEnum, ThemeStateEnum } from '../enums/appEnum'; 2 | 3 | export const prefixCls = 'aso'; 4 | 5 | export const darkMode = ThemeEnum.LIGHT; 6 | 7 | export const themeState = ThemeStateEnum.LIGHT; 8 | 9 | export const multipleTabsHeight = 30; 10 | export const layoutHeaderHeight = 48; 11 | export const layoutSiderCollapsedWidth = 48; 12 | 13 | // app theme preset color 14 | export const APP_PRESET_COLOR_LIST: string[] = [ 15 | '#0960bd', 16 | '#0084f4', 17 | '#009688', 18 | '#536dfe', 19 | '#ff5c93', 20 | '#ee4f12', 21 | '#0096c7', 22 | '#9c27b0', 23 | '#ff9800', 24 | ]; 25 | -------------------------------------------------------------------------------- /src/settings/encryptionSetting.ts: -------------------------------------------------------------------------------- 1 | import { isDevMode } from '@/utils/env'; 2 | 3 | // System default cache time, in seconds 4 | export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; 5 | 6 | // aes encryption key 7 | export const cacheCipher = { 8 | key: '_11111000001111@', 9 | iv: '@11111000001111_', 10 | }; 11 | 12 | // Whether the system cache is encrypted using aes 13 | export const enableStorageEncryption = !isDevMode(); 14 | -------------------------------------------------------------------------------- /src/settings/localeSetting.ts: -------------------------------------------------------------------------------- 1 | import { SelectOption } from 'naive-ui'; 2 | import type { LocaleSetting, LocaleType } from '#/config'; 3 | 4 | export const LOCALE: { [key: string]: LocaleType } = { 5 | ZH_CN: 'zh_CN', 6 | EN_US: 'en', 7 | }; 8 | 9 | export const localeSetting: LocaleSetting = { 10 | showPicker: true, 11 | // Locale 12 | locale: LOCALE.ZH_CN, 13 | // Default locale 14 | fallback: LOCALE.ZH_CN, 15 | // available Locales 16 | availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US], 17 | }; 18 | 19 | // locale list 20 | export const localeList: SelectOption[] = [ 21 | { 22 | label: '简体中文', 23 | value: LOCALE.ZH_CN, 24 | }, 25 | { 26 | label: 'English', 27 | value: LOCALE.EN_US, 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/settings/siteSetting.ts: -------------------------------------------------------------------------------- 1 | // github repo url 2 | export const GITHUB_URL = 'https://www.baidu.com'; 3 | 4 | // doc url 5 | export const DOC_URL = ''; 6 | 7 | // site url 8 | export const SITE_URL = ''; 9 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /src/store/modules/service.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '@/store'; 3 | interface ServiceState { 4 | list: any; 5 | } 6 | 7 | export const useServiceStore = defineStore({ 8 | id: 'app-service', 9 | state: (): ServiceState => ({ 10 | list: {}, 11 | }), 12 | getters: { 13 | // 返回service list 14 | service(): any { 15 | return this.list; 16 | }, 17 | }, 18 | actions: { 19 | setService(service: any) { 20 | this.list = service; 21 | }, 22 | }, 23 | }); 24 | 25 | // Need to be used outside the setup 26 | export function useServiceStoreWithOut() { 27 | return useServiceStore(store); 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { Persistent, BasicKeys } from '@/utils/cache/persistent'; 2 | import { CacheTypeEnum, TOKEN_KEY } from '@/enums/cacheEnum'; 3 | import projectSetting from '@/settings/projectSetting'; 4 | 5 | const { permissionCacheType } = projectSetting; 6 | const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; 7 | 8 | export function getToken() { 9 | return getAuthCache(TOKEN_KEY); 10 | } 11 | 12 | export function getAuthCache(key: BasicKeys) { 13 | const fn = isLocal ? Persistent.getLocal : Persistent.getSession; 14 | return fn(key) as T; 15 | } 16 | 17 | export function setAuthCache(key: BasicKeys, value) { 18 | const fn = isLocal ? Persistent.setLocal : Persistent.setSession; 19 | return fn(key, value, true); 20 | } 21 | 22 | export function clearAuthCache(immediate = true) { 23 | const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession; 24 | return fn(immediate); 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/cache/index.ts: -------------------------------------------------------------------------------- 1 | import { getStorageShortName } from '@/utils/env'; 2 | import { createStorage as create, CreateStorageParams } from './storageCache'; 3 | import { enableStorageEncryption, DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting'; 4 | 5 | export type Options = Partial; 6 | 7 | const createOptions = (storage: Storage, options: Options = {}): Options => { 8 | return { 9 | // No encryption in debug mode 10 | hasEncrypt: enableStorageEncryption, 11 | storage, 12 | prefixKey: getStorageShortName(), 13 | ...options, 14 | }; 15 | }; 16 | 17 | export const WebStorage = create(createOptions(sessionStorage)); 18 | 19 | export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => { 20 | return create(createOptions(storage, options)); 21 | }; 22 | 23 | export const createSessionStorage = (options: Options = {}) => { 24 | return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); 25 | }; 26 | 27 | export const createLocalStorage = (options: Options = {}) => { 28 | return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); 29 | }; 30 | 31 | export default WebStorage; 32 | -------------------------------------------------------------------------------- /src/utils/cipher.ts: -------------------------------------------------------------------------------- 1 | import { encrypt, decrypt } from 'crypto-js/aes'; 2 | import UTF8, { parse } from 'crypto-js/enc-utf8'; 3 | import pkcs7 from 'crypto-js/pad-pkcs7'; 4 | import ECB from 'crypto-js/mode-ecb'; 5 | import md5 from 'crypto-js/md5'; 6 | 7 | import Base64 from 'crypto-js/enc-base64'; 8 | 9 | export interface EncryptionParams { 10 | key: string; 11 | iv: string; 12 | } 13 | 14 | export class AesEncryption { 15 | private key; 16 | 17 | private iv; 18 | 19 | constructor(opt: Partial = {}) { 20 | const { key, iv } = opt; 21 | if (key) { 22 | this.key = parse(key); 23 | } 24 | if (iv) { 25 | this.iv = parse(iv); 26 | } 27 | } 28 | 29 | get getOptions() { 30 | return { 31 | mode: ECB, 32 | padding: pkcs7, 33 | iv: this.iv, 34 | }; 35 | } 36 | 37 | encryptByAES(cipherText: string) { 38 | return encrypt(cipherText, this.key, this.getOptions).toString(); 39 | } 40 | 41 | decryptByAES(cipherText: string) { 42 | return decrypt(cipherText, this.key, this.getOptions).toString(UTF8); 43 | } 44 | } 45 | 46 | export function encryptByBase64(cipherText: string) { 47 | return UTF8.parse(cipherText).toString(Base64); 48 | } 49 | 50 | export function decodeByBase64(cipherText: string) { 51 | return Base64.parse(cipherText).toString(UTF8); 52 | } 53 | 54 | export function encryptByMd5(password: string) { 55 | return md5(password).toString(); 56 | } 57 | -------------------------------------------------------------------------------- /src/utils/dateUtil.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Independent time operation tool to facilitate subsequent switch to dayjs 3 | */ 4 | import { format } from 'date-fns'; 5 | 6 | const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm'; 7 | const DATE_FORMAT = 'yyyy-MM-dd '; 8 | 9 | export function formatToDateTime( 10 | date: Date | number = Date.now(), 11 | formatType = DATE_TIME_FORMAT 12 | ): string { 13 | return format(date, formatType); 14 | } 15 | 16 | export function formatToDate(date: Date | number = Date.now(), formatType = DATE_FORMAT): string { 17 | return format(date, formatType); 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/event/index.ts: -------------------------------------------------------------------------------- 1 | import ResizeObserver from 'resize-observer-polyfill'; 2 | 3 | const isServer = typeof window === 'undefined'; 4 | 5 | /* istanbul ignore next */ 6 | function resizeHandler(entries: any[]) { 7 | for (const entry of entries) { 8 | const listeners = entry.target.__resizeListeners__ || []; 9 | if (listeners.length) { 10 | listeners.forEach((fn: () => any) => { 11 | fn(); 12 | }); 13 | } 14 | } 15 | } 16 | 17 | /* istanbul ignore next */ 18 | export function addResizeListener(element: any, fn: () => any) { 19 | if (isServer) return; 20 | if (!element.__resizeListeners__) { 21 | element.__resizeListeners__ = []; 22 | element.__ro__ = new ResizeObserver(resizeHandler); 23 | element.__ro__.observe(element); 24 | } 25 | element.__resizeListeners__.push(fn); 26 | } 27 | 28 | /* istanbul ignore next */ 29 | export function removeResizeListener(element: any, fn: () => any) { 30 | if (!element || !element.__resizeListeners__) return; 31 | element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); 32 | if (!element.__resizeListeners__.length) { 33 | element.__ro__.disconnect(); 34 | } 35 | } 36 | 37 | export function triggerWindowResize() { 38 | const event = document.createEvent('HTMLEvents'); 39 | event.initEvent('resize', true, true); 40 | (event as any).eventType = 'message'; 41 | window.dispatchEvent(event); 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/helper/tabsHelper.ts: -------------------------------------------------------------------------------- 1 | import { RouteLocationNormalized } from 'vue-router'; 2 | 3 | /** 4 | * 获取该页签在多页签数据中的索引 5 | * @param tabs - 多页签数据 6 | * @param path - 该页签的路径 7 | */ 8 | export function getIndexInTabRoutes(tabs: RouteLocationNormalized[], path: string) { 9 | return tabs.findIndex((tab) => tab.path === path); 10 | } 11 | 12 | /** 13 | * 判断该页签是否在多页签数据中 14 | * @param tabs - 多页签数据 15 | * @param path - 该页签的路径 16 | */ 17 | export function isInTabRoutes(tabs: RouteLocationNormalized[], path: string) { 18 | return getIndexInTabRoutes(tabs, path) > -1; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/helper/tsxHelper.tsx: -------------------------------------------------------------------------------- 1 | import { Slots } from 'vue'; 2 | import { isFunction } from '@/utils/is'; 3 | 4 | /** 5 | * @description: Get slot to prevent empty error 6 | */ 7 | export function getSlot(slots: Slots, slot = 'default', data?: any) { 8 | if (!slots || !Reflect.has(slots, slot)) { 9 | return null; 10 | } 11 | if (!isFunction(slots[slot])) { 12 | console.error(`${slot} is not a function!`); 13 | return null; 14 | } 15 | const slotFn = slots[slot]; 16 | if (!slotFn) return null; 17 | return slotFn(data); 18 | } 19 | 20 | /** 21 | * extends slots 22 | * @param slots 23 | * @param excludeKeys 24 | */ 25 | export function extendSlots(slots: Slots, excludeKeys: string[] = []) { 26 | const slotKeys = Object.keys(slots); 27 | const ret: any = {}; 28 | slotKeys.map((key) => { 29 | if (excludeKeys.includes(key)) { 30 | return null; 31 | } 32 | ret[key] = () => getSlot(slots, key); 33 | }); 34 | return ret; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/http/axios/helper.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from '@/utils/is'; 2 | 3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; 4 | 5 | export function joinTimestamp( 6 | join: boolean, 7 | restful: T 8 | ): T extends true ? string : object; 9 | 10 | export function joinTimestamp(join: boolean, restful = false): string | object { 11 | if (!join) { 12 | return restful ? '' : {}; 13 | } 14 | const now = new Date().getTime(); 15 | if (restful) { 16 | return `?_t=${now}`; 17 | } 18 | return { _t: now }; 19 | } 20 | 21 | /** 22 | * @description: Format request parameter time 23 | */ 24 | export function formatRequestDate(params: Recordable) { 25 | if (Object.prototype.toString.call(params) !== '[object Object]') { 26 | return; 27 | } 28 | 29 | for (const key in params) { 30 | if (params[key] && params[key]._isAMomentObject) { 31 | params[key] = params[key].format(DATE_TIME_FORMAT); 32 | } 33 | if (isString(key)) { 34 | const value = params[key]; 35 | if (value) { 36 | try { 37 | params[key] = isString(value) ? value.trim() : value; 38 | } catch (error: any) { 39 | throw new Error(error); 40 | } 41 | } 42 | } 43 | if (isObject(params[key])) { 44 | formatRequestDate(params[key]); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/lib/echarts.ts: -------------------------------------------------------------------------------- 1 | import * as echarts from 'echarts/core'; 2 | 3 | import { 4 | BarChart, 5 | LineChart, 6 | PieChart, 7 | MapChart, 8 | PictorialBarChart, 9 | RadarChart, 10 | GaugeChart, 11 | } from 'echarts/charts'; 12 | 13 | import { 14 | TitleComponent, 15 | TooltipComponent, 16 | GridComponent, 17 | PolarComponent, 18 | AriaComponent, 19 | ParallelComponent, 20 | LegendComponent, 21 | RadarComponent, 22 | ToolboxComponent, 23 | DataZoomComponent, 24 | VisualMapComponent, 25 | TimelineComponent, 26 | CalendarComponent, 27 | GraphicComponent, 28 | DatasetComponent, 29 | } from 'echarts/components'; 30 | 31 | import { SVGRenderer } from 'echarts/renderers'; 32 | 33 | echarts.use([ 34 | LegendComponent, 35 | TitleComponent, 36 | TooltipComponent, 37 | GridComponent, 38 | PolarComponent, 39 | AriaComponent, 40 | ParallelComponent, 41 | BarChart, 42 | LineChart, 43 | PieChart, 44 | MapChart, 45 | RadarChart, 46 | GaugeChart, 47 | SVGRenderer, 48 | PictorialBarChart, 49 | RadarComponent, 50 | ToolboxComponent, 51 | DataZoomComponent, 52 | VisualMapComponent, 53 | TimelineComponent, 54 | CalendarComponent, 55 | GraphicComponent, 56 | DatasetComponent, 57 | ]); 58 | 59 | export default echarts; 60 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, VNodeChild } from 'vue'; 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; 3 | 4 | export type VueNode = VNodeChild | JSX.Element; 5 | 6 | type PropTypes = VueTypesInterface & { 7 | readonly style: VueTypeValidableDef; 8 | readonly VNodeChild: VueTypeValidableDef; 9 | // readonly trueBool: VueTypeValidableDef; 10 | readonly stringNumber: VueTypeValidableDef; 11 | readonly Date: VueTypeValidableDef; 12 | }; 13 | 14 | const propTypes = createTypes({ 15 | func: undefined, 16 | bool: undefined, 17 | string: undefined, 18 | number: undefined, 19 | object: undefined, 20 | integer: undefined, 21 | }) as PropTypes; 22 | 23 | propTypes.extend([ 24 | { 25 | name: 'style', 26 | getter: true, 27 | type: [String, Object], 28 | default: undefined, 29 | }, 30 | { 31 | name: 'stringNumber', 32 | getter: true, 33 | type: [String, Number], 34 | default: undefined, 35 | }, 36 | { 37 | name: 'Date', 38 | getter: true, 39 | type: [Number, Date], 40 | default: undefined, 41 | }, 42 | { 43 | name: 'VNodeChild', 44 | getter: true, 45 | type: undefined, 46 | }, 47 | ]); 48 | export { propTypes }; 49 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/Gauge.vue: -------------------------------------------------------------------------------- 1 | 6 | 49 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/SiteAnalysis.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/chartProps.ts: -------------------------------------------------------------------------------- 1 | import { propTypes } from '@/utils/propTypes'; 2 | 3 | export const basicProps = (h: string, w = '100%') => ({ 4 | width: propTypes.string.def(w), 5 | height: propTypes.string.def(h), 6 | }); 7 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/data.ts: -------------------------------------------------------------------------------- 1 | export interface GrowCardItem { 2 | title: string; 3 | value: number; 4 | total: number; 5 | type: 'default' | 'error' | 'info' | 'success' | 'warning' | 'primary'; 6 | prefix: string; 7 | content: string; 8 | action: string; 9 | } 10 | 11 | export const growCardList: GrowCardItem[] = [ 12 | { 13 | title: '访问数', 14 | value: 2000, 15 | total: 34878, 16 | prefix: '', 17 | content: '日同比78% ', 18 | type: 'success', 19 | action: '日', 20 | }, 21 | { 22 | title: '成交额', 23 | value: 1987, 24 | total: 18986, 25 | prefix: '¥ ', 26 | content: '周同比11% ', 27 | type: 'warning', 28 | action: '周', 29 | }, 30 | { 31 | title: '下载数', 32 | value: 8000, 33 | total: 56489, 34 | prefix: '↓ ', 35 | content: '月同比98% ~', 36 | type: 'error', 37 | action: '月', 38 | }, 39 | { 40 | title: '成交数', 41 | value: 5000, 42 | total: 48987, 43 | prefix: '', 44 | content: '季同比123% ↑↑', 45 | type: 'info', 46 | action: '季', 47 | }, 48 | ]; 49 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/useLoading.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { createSharedComposable, autoResetRef } from '@vueuse/core'; 3 | 4 | export const useLoading = createSharedComposable( 5 | autoResetRef.bind(null, false, 1000) 6 | ) as () => Ref; 7 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/Dynamicinfo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 33 | 34 | 41 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/ProjectCard.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/QuickNav.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/Team.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/TeamCard.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /src/views/demo/comp/form/basic.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 41 | -------------------------------------------------------------------------------- /src/views/demo/comp/modal/Modal2.vue: -------------------------------------------------------------------------------- 1 | 12 | 28 | -------------------------------------------------------------------------------- /src/views/demo/comp/modal/Modal3.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /src/views/demo/comp/table/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '名称', 12 | key: 'name', 13 | width: 100, 14 | }, 15 | { 16 | title: '头像', 17 | key: 'avatar', 18 | width: 100, 19 | render(row) { 20 | return h(NAvatar, { 21 | size: 48, 22 | src: row.avatar, 23 | }); 24 | }, 25 | }, 26 | { 27 | title: '地址', 28 | key: 'address', 29 | auth: ['super'], // 同时根据权限控制是否显示 30 | ifShow: () => { 31 | return true; // 根据业务控制是否显示 32 | }, 33 | width: 150, 34 | }, 35 | { 36 | title: '开始日期', 37 | key: 'beginTime', 38 | width: 160, 39 | }, 40 | { 41 | title: '结束日期', 42 | key: 'endTime', 43 | width: 160, 44 | }, 45 | { 46 | title: '创建时间', 47 | key: 'date', 48 | width: 100, 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /src/views/demo/main-out/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/views/demo/page/account/center/accountCenterPage.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | -------------------------------------------------------------------------------- /src/views/demo/page/account/center/components/LeftCard.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 35 | -------------------------------------------------------------------------------- /src/views/demo/page/account/center/components/RightCard.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 36 | -------------------------------------------------------------------------------- /src/views/demo/page/account/center/components/data.tsx: -------------------------------------------------------------------------------- 1 | import Icon from '@/components/Icon'; 2 | 3 | export const userMessage = [ 4 | { 5 | icon: , 6 | text: '前端菜鸟', 7 | }, 8 | { 9 | icon: , 10 | text: 'XX公司-某某某事业群-某某平台部-某某技术部', 11 | }, 12 | { 13 | icon: , 14 | text: '福建省厦门市', 15 | }, 16 | ]; 17 | 18 | export const tags = [ 19 | '前端开发', 20 | 'vue3', 21 | '摸鱼大师', 22 | 'Bug之父', 23 | '不想加班', 24 | '配色鬼才', 25 | '扣脚黑丝大叔', 26 | ]; 27 | -------------------------------------------------------------------------------- /src/views/demo/page/account/setting/components/AccountBind.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/views/demo/page/account/setting/components/MsgNotify.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/views/demo/page/account/setting/components/SecureSetting.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/views/demo/page/desc/high/data.tsx: -------------------------------------------------------------------------------- 1 | export const pageHeaderDes: [string, string][] = [ 2 | ['创建人', '刘大包'], 3 | ['订购产品', 'OXO服务'], 4 | ['创建时间', '2020-12-24'], 5 | ['关联单据', '1234987'], 6 | ['生效日期', '2020-12-27 ~ 2022-01-05'], 7 | ['备注', '请于两个工作日内确认'], 8 | ]; 9 | 10 | export const pageContentDes: [string, string][] = [ 11 | ['用户姓名', '吕率缕'], 12 | ['会员卡号', '78914756417685489'], 13 | ['身份证', '4551450450540545050'], 14 | ['联系方式', '18841465471'], 15 | ['联系地址', '浙江省杭州市西湖区黄姑山路工专路交叉路口'], 16 | ]; 17 | 18 | export const pageContentDes2: [string, string][] = [ 19 | ['数据ID', '7987984'], 20 | ['更新时间', '2021-08-08'], 21 | ['数据ID', '2156465'], 22 | ['更新时间', '2021-11-04'], 23 | ]; 24 | 25 | export const pageContentDes3: [string, string][] = [ 26 | ['负责人', '江西溪'], 27 | ['角色码', '789465456'], 28 | ['所属部门', 'XX部-XX组'], 29 | ['过期时间', '2023-11-04'], 30 | ['描述', '这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长···'], 31 | ]; 32 | -------------------------------------------------------------------------------- /src/views/demo/page/form/step/Step1.vue: -------------------------------------------------------------------------------- 1 | 23 | 45 | -------------------------------------------------------------------------------- /src/views/demo/page/form/step/Step3.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /src/views/demo/page/level/Menu111.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/views/demo/page/level/Menu12.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/views/demo/page/level/Menu2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/views/demo/page/level/Wrapper.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/demo/page/list/basicListPage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/views/demo/page/list/cardListPage.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/views/demo/page/list/components/DemoCard.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 37 | -------------------------------------------------------------------------------- /src/views/demo/page/result/failResultPage.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/sys/about/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/sys/exception/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Exception } from './Exception.vue'; 2 | -------------------------------------------------------------------------------- /src/views/sys/iframe/FrameBlank.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | -------------------------------------------------------------------------------- /src/views/sys/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strictFunctionTypes": false, 10 | "jsx": "preserve", 11 | "baseUrl": ".", 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "esModuleInterop": true, 15 | "resolveJsonModule": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "experimentalDecorators": true, 19 | "lib": ["dom", "esnext"], 20 | "types": ["vite/client", "naive-ui/volar"], 21 | "typeRoots": ["./node_modules/@types/", "./types"], 22 | "noImplicitAny": false, 23 | "skipLibCheck": true, 24 | "paths": { 25 | "@/*": ["./src/*"], 26 | "#/*": ["./types/*"] 27 | } 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.d.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "types/**/*.d.ts", 36 | "types/**/*.ts", 37 | "build/**/*.ts", 38 | "build/**/*.d.ts", 39 | "mock/**/*.ts", 40 | "vite.config.mts" 41 | ], 42 | "exclude": ["node_modules", "dist", "**/*.js", "src/views/demo/main-out/*"] 43 | } 44 | -------------------------------------------------------------------------------- /types/axios.d.ts: -------------------------------------------------------------------------------- 1 | export type ErrorMessageMode = 'none' | 'modal' | 'message' | undefined; 2 | 3 | export interface RequestOptions { 4 | // Splicing request parameters to url 5 | joinParamsToUrl?: boolean; 6 | // Format request parameter time 7 | formatDate?: boolean; 8 | // Whether to process the request result 9 | isTransformResponse?: boolean; 10 | // Whether to return native response headers 11 | // For example: use this attribute when you need to get the response headers 12 | isReturnNativeResponse?: boolean; 13 | // Whether to join url 14 | joinPrefix?: boolean; 15 | // Interface address, use the default apiUrl if you leave it blank 16 | apiUrl?: string; 17 | // 请求拼接路径 18 | urlPrefix?: string; 19 | // Error message prompt type 20 | errorMessageMode?: ErrorMessageMode; 21 | // Whether to add a timestamp 22 | joinTime?: boolean; 23 | ignoreCancelToken?: boolean; 24 | // Whether to send token in header 25 | withToken?: boolean; 26 | } 27 | 28 | export interface Result { 29 | code: number; 30 | type: 'success' | 'error' | 'warning'; 31 | message: string; 32 | result: T; 33 | } 34 | 35 | // multipart/form-data: upload file 36 | export interface UploadFileParams { 37 | // Other parameters 38 | data?: Recordable; 39 | // File parameter interface field name 40 | name?: string; 41 | // file name 42 | file: File | Blob; 43 | // file name 44 | filename?: string; 45 | [key: string]: any; 46 | } 47 | -------------------------------------------------------------------------------- /types/expose.d.ts: -------------------------------------------------------------------------------- 1 | /** vue 的defineExpose导出的类型 */ 2 | declare namespace Expose { 3 | interface BetterScroll { 4 | instance: import('@better-scroll/core'); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type MaybeArray = T | T; 10 | 11 | declare type RefType = T | null; 12 | 13 | declare type LabelValueOptions = { 14 | label: string; 15 | value: any; 16 | [key: string]: string | number | boolean; 17 | }[]; 18 | 19 | declare type EmitType = (event: string, ...args: any[]) => void; 20 | 21 | declare type TargetContext = '_self' | '_blank'; 22 | 23 | declare interface ComponentElRef { 24 | $el: T; 25 | } 26 | 27 | declare type ComponentRef = ComponentElRef | null; 28 | 29 | declare type ElRef = Nullable; 30 | 31 | declare interface DomClassList extends DOMTokenList { 32 | replace(oldToken: string, newToken: string): boolean; 33 | } 34 | declare type DynamicProps = { 35 | [P in keyof T]: Ref | T[P] | ComputedRef; 36 | }; 37 | declare interface ResizeObserverEntryTarget extends Omit { 38 | target: HTMLElement; 39 | } 40 | 41 | declare const __PROXY_LIST__: any[]; 42 | -------------------------------------------------------------------------------- /types/model.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue'; 3 | const Component: ReturnType; 4 | export default Component; 5 | } 6 | 7 | declare module 'virtual:*' { 8 | const result: any; 9 | export default result; 10 | } 11 | -------------------------------------------------------------------------------- /types/model/account/accountModel.ts: -------------------------------------------------------------------------------- 1 | export interface GetAccountInfoModel { 2 | email: string; 3 | nickName: string; 4 | phone: string; 5 | Adress: { 6 | Provincey: string; 7 | City: string; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /types/model/account/menuModel.ts: -------------------------------------------------------------------------------- 1 | import type { RouteMeta } from 'vue-router'; 2 | 3 | export interface RouteItem { 4 | path: string; 5 | component: any; 6 | meta: RouteMeta; 7 | name?: string; 8 | alias?: string | string[]; 9 | redirect?: string; 10 | caseSensitive?: boolean; 11 | children?: RouteItem[]; 12 | } 13 | 14 | /** 15 | * @description: Get menu return value 16 | */ 17 | export type getMenuListResultModel = RouteItem[]; 18 | -------------------------------------------------------------------------------- /types/model/account/userModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Login interface parameters 3 | */ 4 | export interface LoginParams { 5 | username: string; 6 | password: string; 7 | } 8 | 9 | export interface RoleInfo { 10 | roleName: string; 11 | value: string; 12 | } 13 | 14 | /** 15 | * @description: Login interface return value 16 | */ 17 | export interface LoginResultModel { 18 | userId: string | number; 19 | token: string; 20 | role: RoleInfo; 21 | } 22 | 23 | /** 24 | * @description: Get user information return value 25 | */ 26 | export interface GetUserInfoModel { 27 | roles: RoleInfo[]; 28 | // 用户id 29 | userId: string | number; 30 | // 用户名 31 | username: string; 32 | // 真实名字 33 | realName: string; 34 | // 头像 35 | avatar: string; 36 | // 介绍 37 | desc?: string; 38 | } 39 | -------------------------------------------------------------------------------- /types/model/baseModel.ts: -------------------------------------------------------------------------------- 1 | export interface BasicPageParams { 2 | page: number; 3 | pageSize: number; 4 | } 5 | 6 | export interface BasicFetchResult { 7 | items: T[]; 8 | total: number; 9 | } 10 | -------------------------------------------------------------------------------- /types/model/demo/tableModel.ts: -------------------------------------------------------------------------------- 1 | import { BasicPageParams, BasicFetchResult } from '../baseModel'; 2 | /** 3 | * @description: Request list interface parameters 4 | */ 5 | export type DemoParams = BasicPageParams; 6 | 7 | export interface DemoListItem { 8 | id: string; 9 | beginTime: string; 10 | endTime: string; 11 | address: string; 12 | name: string; 13 | no: number; 14 | status: number; 15 | } 16 | 17 | /** 18 | * @description: Request list return value 19 | */ 20 | export type DemoListGetResultModel = BasicFetchResult; 21 | -------------------------------------------------------------------------------- /types/model/uploadModel.ts: -------------------------------------------------------------------------------- 1 | export interface UploadApiResult { 2 | message: string; 3 | code: number; 4 | url: string; 5 | } 6 | -------------------------------------------------------------------------------- /types/store.d.ts: -------------------------------------------------------------------------------- 1 | import { ErrorTypeEnum } from '@/enums/exceptionEnum'; 2 | import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum'; 3 | import { RoleInfo } from '@/api/sys/model/userModel'; 4 | 5 | // Lock screen information 6 | export interface LockInfo { 7 | // Password required 8 | pwd?: string | undefined; 9 | // Is it locked? 10 | isLock?: boolean; 11 | } 12 | 13 | // Error-log information 14 | export interface ErrorLogInfo { 15 | // Type of error 16 | type: ErrorTypeEnum; 17 | // Error file 18 | file: string; 19 | // Error name 20 | name?: string; 21 | // Error message 22 | message: string; 23 | // Error stack 24 | stack?: string; 25 | // Error detail 26 | detail: string; 27 | // Error url 28 | url: string; 29 | // Error time 30 | time?: string; 31 | } 32 | 33 | export interface UserInfo { 34 | userId: string | number; 35 | username: string; 36 | realName: string; 37 | avatar: string; 38 | desc?: string; 39 | homePath?: string; 40 | roles: RoleInfo[]; 41 | } 42 | 43 | export interface BeforeMiniState { 44 | menuCollapsed?: boolean; 45 | menuSplit?: boolean; 46 | menuMode?: MenuModeEnum; 47 | menuType?: MenuTypeEnum; 48 | } 49 | -------------------------------------------------------------------------------- /types/vue-router.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare module 'vue-router' { 4 | interface RouteMeta extends Record { 5 | orderNo?: number; 6 | // title 7 | title: string; 8 | // Whether to ignore permissions 9 | ignoreAuth?: boolean; 10 | // role info 11 | roles?: RoleEnum[]; 12 | // Whether not to cache 13 | ignoreKeepAlive?: boolean; 14 | // Is it fixed on tab 15 | affix?: boolean; 16 | // icon on tab 17 | icon?: string; 18 | frameSrc?: string; 19 | // current page no loading 20 | hideLoading?: boolean; 21 | // current page not used transition 22 | hidePageTransition?: boolean; 23 | // current page transition 24 | transitionName?: string; 25 | // Whether the route has been dynamically added 26 | hideBreadcrumb?: boolean; 27 | // Hide submenu 28 | hideChildrenInMenu?: boolean; 29 | // Carrying parameters 30 | carryParam?: boolean; 31 | // Used internally to mark single-level menus 32 | single?: boolean; 33 | // Currently active menu 34 | currentActiveMenu?: string; 35 | // Never show in tab 36 | hideTab?: boolean; 37 | // Never show in menu 38 | hideMenu?: boolean; 39 | isLink?: boolean; 40 | // only build for Menu 41 | ignoreRoute?: boolean; 42 | // Hide path for children 43 | hidePathForChildren?: boolean; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /uno.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presetTypography, presetUno } from 'unocss'; 2 | 3 | export default defineConfig({ 4 | presets: [presetUno(), presetTypography()], 5 | }); 6 | --------------------------------------------------------------------------------