commit beb3c73b2da503c1331f2ad37292c5d2b864cc9d Author: qsh <> Date: Mon Jul 15 11:19:37 2024 +0800 初始化 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1b35697 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true +[*.{js,ts,vue}] +charset = utf-8 # 设置文件字符集为 utf-8 +end_of_line = lf # 控制换行类型(lf | cr | crlf) +insert_final_newline = true # 始终在文件末尾插入一个新行 +indent_style = space # 缩进风格(tab | space) +indent_size = 2 # 缩进大小 +max_line_length = 120 # 最大行长度 + +[*.md] # 仅 md 文件适用以下规则 +max_line_length = off # 关闭最大行长度限制 +trim_trailing_whitespace = false # 关闭末尾空格修剪 diff --git a/.env b/.env new file mode 100644 index 0000000..bf491a7 --- /dev/null +++ b/.env @@ -0,0 +1,20 @@ +# 标题 +VITE_APP_TITLE=OA系统 + +# 开发环境 +VITE_NODE_ENV=development + +# 项目本地运行端口号 +VITE_PORT=8000 + +# open 运行 npm run dev 时自动打开浏览器 +VITE_OPEN=true + +# 租户开关 +VITE_APP_TENANT_ENABLE=false + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=true + +# 百度统计 +VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc diff --git a/.env.base b/.env.base new file mode 100644 index 0000000..ca2e2f8 --- /dev/null +++ b/.env.base @@ -0,0 +1,21 @@ +# 本地开发环境 +VITE_NODE_ENV=development + +VITE_DEV=true + +# 请求路径 +VITE_BASE_URL='http://118.31.23.45:48080' +# VITE_BASE_URL='http://114.55.169.15:48080' + +# 上传路径 +VITE_UPLOAD_URL='http://118.31.23.45:48080/admin-api/system/file/upload' +# VITE_UPLOAD_URL='http://114.55.169.15:48080/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 打包路径 +VITE_BASE_PATH=/oa/ diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..81c0390 --- /dev/null +++ b/.env.dev @@ -0,0 +1,31 @@ +# 开发环境 +VITE_NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='http://localhost:48080' + +# 上传路径 +VITE_UPLOAD_URL='http://118.31.23.45:48080/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 打包路径 +VITE_BASE_PATH=/oa/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 输出路径 +VITE_OUT_DIR=dist-dev diff --git a/.env.front b/.env.front new file mode 100644 index 0000000..5340573 --- /dev/null +++ b/.env.front @@ -0,0 +1,34 @@ +# 本地开发环境 +VITE_NODE_ENV=development + +VITE_DEV=true + +# 请求路径 +VITE_BASE_URL='http://118.31.23.45:48080' + +# 上传路径 +VITE_UPLOAD_URL='http://118.31.23.45:48080/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 打包路径 +VITE_BASE_PATH=/oa/ + +# 项目本地运行端口号, 与.vscode/launch.json配合 +VITE_PORT=80 + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=false diff --git a/.env.pro b/.env.pro new file mode 100644 index 0000000..52746ef --- /dev/null +++ b/.env.pro @@ -0,0 +1,31 @@ +# 生产环境 +VITE_NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='/crm-api' + +# 上传路径 +VITE_UPLOAD_URL='/crm-api/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH=/oa/ + +# 输出路径 +VITE_OUT_DIR=dist-pro diff --git a/.env.stage b/.env.stage new file mode 100644 index 0000000..b849f05 --- /dev/null +++ b/.env.stage @@ -0,0 +1,31 @@ +# 生产环境 +VITE_NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn' + +# 上传路径 +VITE_UPLOAD_URL='http://118.31.23.45:48080/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH='/oa/' + +# 输出路径 +VITE_OUT_DIR=dist-stage diff --git a/.env.static b/.env.static new file mode 100644 index 0000000..a3c722b --- /dev/null +++ b/.env.static @@ -0,0 +1,31 @@ +# 开发环境 +VITE_NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='http://localhost:48080' + +# 上传路径 +VITE_UPLOAD_URL='http://118.31.23.45:48080/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH=/oa/ + +# 输出路径 +VITE_OUT_DIR=dist-dev diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..ee40e26 --- /dev/null +++ b/.env.test @@ -0,0 +1,31 @@ +# 测试环境 +VITE_NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='http://localhost:48080' + +# 上传路径 +VITE_UPLOAD_URL='http://118.31.23.45:48080/admin-api/system/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/crm-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 打包路径 +VITE_BASE_PATH=/oa/ + +# 输出路径 +VITE_OUT_DIR=dist-test diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..1e85c0f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +/build/ +/config/ +/dist/ +/*.js +/test/unit/coverage/ +/node_modules/* +/dist* +/src/main.ts diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json new file mode 100644 index 0000000..024c96a --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,259 @@ +{ + "globals": { + "EffectScope": true, + "ElMessage": true, + "ElMessageBox": true, + "ElTag": true, + "asyncComputed": true, + "autoResetRef": true, + "computed": true, + "computedAsync": true, + "computedEager": true, + "computedInject": true, + "computedWithControl": true, + "controlledComputed": true, + "controlledRef": true, + "createApp": true, + "createEventHook": true, + "createGlobalState": true, + "createInjectionState": true, + "createReactiveFn": true, + "createSharedComposable": true, + "createUnrefFn": true, + "customRef": true, + "debouncedRef": true, + "debouncedWatch": true, + "defineAsyncComponent": true, + "defineComponent": true, + "eagerComputed": true, + "effectScope": true, + "extendRef": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "ignorableWatch": true, + "inject": true, + "isDefined": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "makeDestructurable": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onClickOutside": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onKeyStroke": true, + "onLongPress": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onStartTyping": true, + "onUnmounted": true, + "onUpdated": true, + "pausableWatch": true, + "provide": true, + "reactify": true, + "reactifyObject": true, + "reactive": true, + "reactiveComputed": true, + "reactiveOmit": true, + "reactivePick": true, + "readonly": true, + "ref": true, + "refAutoReset": true, + "refDebounced": true, + "refDefault": true, + "refThrottled": true, + "refWithControl": true, + "resolveComponent": true, + "resolveRef": true, + "resolveUnref": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayJoin": true, + "useArrayMap": true, + "useArrayReduce": true, + "useArraySome": true, + "useAsyncQueue": true, + "useAsyncState": true, + "useAttrs": true, + "useBase64": true, + "useBattery": true, + "useBluetooth": true, + "useBreakpoints": true, + "useBroadcastChannel": true, + "useBrowserLocation": true, + "useCached": true, + "useClipboard": true, + "useColorMode": true, + "useConfirmDialog": true, + "useCounter": true, + "useCssModule": true, + "useCssVar": true, + "useCssVars": true, + "useCurrentElement": true, + "useCycleList": true, + "useDark": true, + "useDateFormat": true, + "useDebounce": true, + "useDebounceFn": true, + "useDebouncedRefHistory": true, + "useDeviceMotion": true, + "useDeviceOrientation": true, + "useDevicePixelRatio": true, + "useDevicesList": true, + "useDisplayMedia": true, + "useDocumentVisibility": true, + "useDraggable": true, + "useDropZone": true, + "useElementBounding": true, + "useElementByPoint": true, + "useElementHover": true, + "useElementSize": true, + "useElementVisibility": true, + "useEventBus": true, + "useEventListener": true, + "useEventSource": true, + "useEyeDropper": true, + "useFavicon": true, + "useFetch": true, + "useFileDialog": true, + "useFileSystemAccess": true, + "useFocus": true, + "useFocusWithin": true, + "useFps": true, + "useFullscreen": true, + "useGamepad": true, + "useGeolocation": true, + "useIdle": true, + "useImage": true, + "useInfiniteScroll": true, + "useIntersectionObserver": true, + "useInterval": true, + "useIntervalFn": true, + "useKeyModifier": true, + "useLastChanged": true, + "useLocalStorage": true, + "useMagicKeys": true, + "useManualRefHistory": true, + "useMediaControls": true, + "useMediaQuery": true, + "useMemoize": true, + "useMemory": true, + "useMounted": true, + "useMouse": true, + "useMouseInElement": true, + "useMousePressed": true, + "useMutationObserver": true, + "useNavigatorLanguage": true, + "useNetwork": true, + "useNow": true, + "useObjectUrl": true, + "useOffsetPagination": true, + "useOnline": true, + "usePageLeave": true, + "useParallax": true, + "usePermission": true, + "usePointer": true, + "usePointerSwipe": true, + "usePreferredColorScheme": true, + "usePreferredDark": true, + "usePreferredLanguages": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "useRoute": true, + "useRouter": true, + "useScreenOrientation": true, + "useScreenSafeArea": true, + "useScriptTag": true, + "useScroll": true, + "useScrollLock": true, + "useSessionStorage": true, + "useShare": true, + "useSlots": true, + "useSpeechRecognition": true, + "useSpeechSynthesis": true, + "useStepper": true, + "useStorage": true, + "useStorageAsync": true, + "useStyleTag": true, + "useSupported": true, + "useSwipe": true, + "useTemplateRefsList": true, + "useTextDirection": true, + "useTextSelection": true, + "useTextareaAutosize": true, + "useThrottle": true, + "useThrottleFn": true, + "useThrottledRefHistory": true, + "useTimeAgo": true, + "useTimeout": true, + "useTimeoutFn": true, + "useTimeoutPoll": true, + "useTimestamp": true, + "useTitle": true, + "useToggle": true, + "useTransition": true, + "useUrlSearchParams": true, + "useUserMedia": true, + "useVModel": true, + "useVModels": true, + "useVibrate": true, + "useVirtualList": true, + "useWakeLock": true, + "useWebNotification": true, + "useWebSocket": true, + "useWebWorker": true, + "useWebWorkerFn": true, + "useWindowFocus": true, + "useWindowScroll": true, + "useWindowSize": true, + "watch": true, + "watchArray": true, + "watchAtMost": true, + "watchDebounced": true, + "watchEffect": true, + "watchIgnorable": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true + } +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..244dbbb --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,69 @@ +// @ts-check +const { defineConfig } = require('eslint-define-config') +module.exports = defineConfig({ + root: true, + env: { + browser: true, + node: true, + es6: true + }, + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + jsxPragma: 'React', + ecmaFeatures: { + jsx: true + } + }, + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:prettier/recommended', + './.eslintrc-auto-import.json' + ], + rules: { + 'vue/script-setup-uses-vars': 'error', + 'vue/no-reserved-component-names': 'off', + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'vue/custom-event-name-casing': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'no-unused-vars': 'error', + 'space-before-function-paren': 'off', + + 'vue/attributes-order': 'off', + 'vue/one-component-per-file': 'off', + 'vue/html-closing-bracket-newline': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/attribute-hyphenation': 'off', + 'vue/require-default-prop': 'off', + 'vue/require-explicit-emits': 'off', + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'never', + component: 'always' + }, + svg: 'always', + math: 'always' + } + ], + 'vue/multi-word-component-names': 'off' + } +}) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f033cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +/dist* +*-lock.* +pnpm-debug +auto-*.d.ts +.idea +.history diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f68ea86 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +/node_modules/** +/dist/ +/dist* +/public/* +/docs/* +/vite.config.ts +/src/types/env.d.ts +/src/types/auto-components.d.ts +/src/types/auto-imports.d.ts +/docs/**/* +CHANGELOG diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..aa605b4 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,6 @@ +/dist/* +/public/* +public/* +/dist* +/src/types/env.d.ts +/docs/**/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9861118 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-present Archer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a0de6a --- /dev/null +++ b/README.md @@ -0,0 +1,235 @@ +**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!** + +**「我喜欢写代码,乐此不疲」** +**「我喜欢做开源,以此为乐」** + +我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。 + +如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 + +## 🐶 新手必读 + +* nodejs > 16.0.0 && pnpm > 7.30.0 +* 演示地址【Vue3 + element-plus】: +* 演示地址【Vue3 + vben(ant-design-vue)】: +* 演示地址【Vue2 + element-ui】: +* 启动文档: +* 视频教程: + +## 🐯 平台简介 + +**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。 + +* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 实现 +* 改换 saas,自动引入等功能 +* 使用 Element Plus 免费开源的中后台模版,具备如下特性: + +![首页](preview/home.png) + +* **最新技术栈**:使用 Vue3、Vite4 等前端前沿技术开发 +* **TypeScript**: 应用程序级 JavaScript 的语言 +* **主题**: 可配置的主题 +* **国际化**:内置完善的国际化方案 +* **权限**:内置完善的动态路由权限生成方案 +* **组件**:二次封装了多个常用的组件 +* **示例**:内置丰富的示例 + +## 技术栈 + +| 框架 | 说明 | 版本 | +|----------------------------------------------------------------------|------------------|--------| +| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.4 | +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.3.8 | +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.4 | +| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.0.4 | +| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.3 | +| [vueuse](https://vueuse.org/) | 常用工具集 | 10.1.2 | +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 | +| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.1 | +| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.0 | +| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 | + +## 开发工具 + +推荐 VS Code 开发,配合插件如下: + +| 插件名 | 功能 | +|-------------------------------|--------------------------| +| TypeScript Vue Plugin (Volar) | 用于 TypeScript 的 Vue 插件 | +| Vue Language Features (Volar) | Vue3.0 语法支持 | +| WindiCSS IntelliSense | 自动完成、语法突出显示、代码折叠和构建等高级功能 | +| Iconify IntelliSense | Iconify 预览和搜索 | +| i18n Ally | 国际化智能提示 | +| Stylelint | Css 格式化 | +| Prettier | 代码格式化 | +| ESLint | 脚本代码检查 | +| DotENV | env 文件高亮 | + +## 内置功能 + +系统内置多种多种业务功能,可以用于快速你的业务系统: + +* 系统功能 +* 基础设施 +* 工作流程 +* 支付系统 +* 会员中心 +* 数据报表 +* 商城系统 +* 微信公众号 + +### 系统功能 + +| | 功能 | 描述 | +|-----|-------|---------------------------------| +| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 | +| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 | +| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | +| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 | +| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 | +| | 岗位管理 | 配置系统用户所属担任职务 | +| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 | +| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 | +| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | +| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 | +| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 | +| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 | +| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 | +| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 | +| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 | +| | 通知公告 | 系统通知公告信息发布维护 | +| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 | +| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 | +| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 | + +### 工作流程 + +| | 功能 | 描述 | +|-----|-------|----------------------------------------| +| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 | +| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 | +| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 | +| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 | +| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 | +| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 | +| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 | + +### 支付系统 + +| | 功能 | 描述 | +|-----|------|---------------------------| +| 🚀 | 商户信息 | 管理商户信息,支持 Saas 场景下的多商户功能 | +| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 | +| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 | +| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 | + +ps:核心功能已经实现,正在对接微信小程序中... + +### 基础设施 + +| | 功能 | 描述 | +|-----|----------|----------------------------------------------| +| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 | +| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 | +| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 | +| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 | +| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 | +| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 | +| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 | +| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 | +| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 | +| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 | +| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 | +| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 | +| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 | +| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 | +| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 | +| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 | +| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 | +| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 | +| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 | + +### 数据报表 + +| | 功能 | 描述 | +|-----|-------|--------------------| +| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 | +| 🚀 | 大屏设计器 | 拖拽生成数据大屏,内置几十种图表组件 | + +### 微信公众号 + +| | 功能 | 描述 | +|-----|--------|-------------------------------| +| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | +| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | +| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | +| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | +| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | +| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | +| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | +| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | +| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | +| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | + +### 商城系统 + +建设中... + +![功能图](http://static.iocoder.cn/mall%20%E5%8A%9F%E8%83%BD%E5%9B%BE-min.png) + +![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-admin-min.gif) + +![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-h5-min.gif) + +## 🐷 演示图 + +### 系统功能 + +| 模块 | biu | biu | biu | +|------------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------| +| 登录 & 首页 | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg?imageView2/2/format/webp/w/1280) | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg?imageView2/2/format/webp/w/1280) | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg?imageView2/2/format/webp/w/1280) | +| 用户 & 应用 | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg?imageView2/2/format/webp/w/1280) | ![令牌管理](https://static.iocoder.cn/images/ruoyi-vue-pro/令牌管理.jpg?imageView2/2/format/webp/w/1280) | ![应用管理](https://static.iocoder.cn/images/ruoyi-vue-pro/应用管理.jpg?imageView2/2/format/webp/w/1280) | +| 租户 & 套餐 | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg?imageView2/2/format/webp/w/1280) | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | - | +| 部门 & 岗位 | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg?imageView2/2/format/webp/w/1280) | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg?imageView2/2/format/webp/w/1280) | - | +| 菜单 & 角色 | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg?imageView2/2/format/webp/w/1280) | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg?imageView2/2/format/webp/w/1280) | - | +| 审计日志 | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg?imageView2/2/format/webp/w/1280) | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg?imageView2/2/format/webp/w/1280) | - | +| 短信 | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg?imageView2/2/format/webp/w/1280) | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg?imageView2/2/format/webp/w/1280) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg?imageView2/2/format/webp/w/1280) | +| 字典 & 敏感词 | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg?imageView2/2/format/webp/w/1280) | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg?imageView2/2/format/webp/w/1280) | ![敏感词](https://static.iocoder.cn/images/ruoyi-vue-pro/敏感词.jpg?imageView2/2/format/webp/w/1280) | +| 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg?imageView2/2/format/webp/w/1280) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg?imageView2/2/format/webp/w/1280) | - | + +### 工作流程 + +| 模块 | biu | biu | biu | +|---------|------------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------| +| 流程模型 | ![流程模型-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-列表.jpg?imageView2/2/format/webp/w/1280) | ![流程模型-设计](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-设计.jpg?imageView2/2/format/webp/w/1280) | ![流程模型-定义](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-定义.jpg?imageView2/2/format/webp/w/1280) | +| 表单 & 分组 | ![流程表单](https://static.iocoder.cn/images/ruoyi-vue-pro/流程表单.jpg?imageView2/2/format/webp/w/1280) | ![用户分组](https://static.iocoder.cn/images/ruoyi-vue-pro/用户分组.jpg?imageView2/2/format/webp/w/1280) | - | +| 我的流程 | ![我的流程-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-列表.jpg?imageView2/2/format/webp/w/1280) | ![我的流程-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-发起.jpg?imageView2/2/format/webp/w/1280) | ![我的流程-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-详情.jpg?imageView2/2/format/webp/w/1280) | +| 待办 & 已办 | ![任务列表-审批](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-审批.jpg?imageView2/2/format/webp/w/1280) | ![任务列表-待办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-待办.jpg?imageView2/2/format/webp/w/1280) | ![任务列表-已办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-已办.jpg?imageView2/2/format/webp/w/1280) | +| OA 请假 | ![OA请假-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-列表.jpg?imageView2/2/format/webp/w/1280) | ![OA请假-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-发起.jpg?imageView2/2/format/webp/w/1280) | ![OA请假-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-详情.jpg?imageView2/2/format/webp/w/1280) | + +### 基础设施 + +| 模块 | biu | biu | biu | +|---------------|----------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------| +| 代码生成 | ![代码生成](https://static.iocoder.cn/images/ruoyi-vue-pro/代码生成.jpg?imageView2/2/format/webp/w/1280) | ![生成效果](https://static.iocoder.cn/images/ruoyi-vue-pro/生成效果.jpg?imageView2/2/format/webp/w/1280) | - | +| 文档 | ![系统接口](https://static.iocoder.cn/images/ruoyi-vue-pro/系统接口.jpg?imageView2/2/format/webp/w/1280) | ![数据库文档](https://static.iocoder.cn/images/ruoyi-vue-pro/数据库文档.jpg?imageView2/2/format/webp/w/1280) | - | +| 文件 & 配置 | ![文件配置](https://static.iocoder.cn/images/ruoyi-vue-pro/文件配置.jpg?imageView2/2/format/webp/w/1280) | ![文件管理](https://static.iocoder.cn/images/ruoyi-vue-pro/文件管理2.jpg?imageView2/2/format/webp/w/1280) | ![配置管理](https://static.iocoder.cn/images/ruoyi-vue-pro/配置管理.jpg?imageView2/2/format/webp/w/1280) | +| 定时任务 | ![定时任务](https://static.iocoder.cn/images/ruoyi-vue-pro/定时任务.jpg?imageView2/2/format/webp/w/1280) | ![任务日志](https://static.iocoder.cn/images/ruoyi-vue-pro/任务日志.jpg?imageView2/2/format/webp/w/1280) | - | +| API 日志 | ![访问日志](https://static.iocoder.cn/images/ruoyi-vue-pro/访问日志.jpg?imageView2/2/format/webp/w/1280) | ![错误日志](https://static.iocoder.cn/images/ruoyi-vue-pro/错误日志.jpg?imageView2/2/format/webp/w/1280) | - | +| MySQL & Redis | ![MySQL](https://static.iocoder.cn/images/ruoyi-vue-pro/MySQL.jpg?imageView2/2/format/webp/w/1280) | ![Redis](https://static.iocoder.cn/images/ruoyi-vue-pro/Redis.jpg?imageView2/2/format/webp/w/1280) | - | +| 监控平台 | ![Java监控](https://static.iocoder.cn/images/ruoyi-vue-pro/Java监控.jpg?imageView2/2/format/webp/w/1280) | ![链路追踪](https://static.iocoder.cn/images/ruoyi-vue-pro/链路追踪.jpg?imageView2/2/format/webp/w/1280) | ![日志中心](https://static.iocoder.cn/images/ruoyi-vue-pro/日志中心.jpg?imageView2/2/format/webp/w/1280) | + +### 支付系统 + +| 模块 | biu | biu | biu | +|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------| +| 商家 & 应用 | ![商户信息](https://static.iocoder.cn/images/ruoyi-vue-pro/商户信息.jpg?imageView2/2/format/webp/w/1280) | ![应用信息-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-列表.jpg?imageView2/2/format/webp/w/1280) | ![应用信息-编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-编辑.jpg?imageView2/2/format/webp/w/1280) | +| 支付 & 退款 | ![支付订单](https://static.iocoder.cn/images/ruoyi-vue-pro/支付订单.jpg?imageView2/2/format/webp/w/1280) | ![退款订单](https://static.iocoder.cn/images/ruoyi-vue-pro/退款订单.jpg?imageView2/2/format/webp/w/1280) | --- | + +### 数据报表 + +| 模块 | biu | biu | biu | +|-------|--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| +| 报表设计器 | ![数据报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-数据报表.jpg?imageView2/2/format/webp/w/1280) | ![图形报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-图形报表.jpg?imageView2/2/format/webp/w/1280) | ![报表设计器-打印设计](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-打印设计.jpg?imageView2/2/format/webp/w/1280) | +| 大屏设计器 | ![大屏列表](https://static.iocoder.cn/images/ruoyi-vue-pro/大屏设计器-列表.jpg?imageView2/2/format/webp/w/1280) | ![大屏预览](https://static.iocoder.cn/images/ruoyi-vue-pro/大屏设计器-预览.jpg?imageView2/2/format/webp/w/1280) | ![大屏编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/大屏设计器-编辑.jpg?imageView2/2/format/webp/w/1280) | diff --git a/build/vite/index.ts b/build/vite/index.ts new file mode 100644 index 0000000..574a0d6 --- /dev/null +++ b/build/vite/index.ts @@ -0,0 +1,109 @@ +import { resolve } from 'path' +import Vue from '@vitejs/plugin-vue' +import VueJsx from '@vitejs/plugin-vue-jsx' +import WindiCSS from 'vite-plugin-windicss' +import progress from 'vite-plugin-progress' +import EslintPlugin from 'vite-plugin-eslint' +import PurgeIcons from 'vite-plugin-purge-icons' +import { ViteEjsPlugin } from 'vite-plugin-ejs' +// @ts-ignore +import ElementPlus from 'unplugin-element-plus/vite' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import viteCompression from 'vite-plugin-compression' +import topLevelAwait from 'vite-plugin-top-level-await' +import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus' +import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' + +export function createVitePlugins() { + const root = process.cwd() + + // 路径查找 + function pathResolve(dir: string) { + return resolve(root, '.', dir) + } + + return [ + Vue(), + VueJsx(), + WindiCSS(), + progress(), + PurgeIcons(), + vueSetupExtend(), + ElementPlus({}), + AutoImport({ + include: [ + /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx + /\.vue$/, + /\.vue\?vue/, // .vue + /\.md$/ // .md + ], + imports: [ + 'vue', + 'vue-router', + // 可额外添加需要 autoImport 的组件 + { + '@/hooks/web/useI18n': ['useI18n'], + '@/hooks/web/useMessage': ['useMessage'], + '@/hooks/web/useTable': ['useTable'], + '@/hooks/web/useCrudSchemas': ['useCrudSchemas'], + '@/utils/formRules': ['required'], + '@/utils/dict': ['DICT_TYPE'] + } + ], + dts: 'src/types/auto-imports.d.ts', + resolvers: [ElementPlusResolver()], + eslintrc: { + enabled: false, // Default `false` + filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json` + globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable') + } + }), + Components({ + // 要搜索组件的目录的相对路径 + dirs: ['src/components'], + // 组件的有效文件扩展名 + extensions: ['vue', 'md'], + // 搜索子目录 + deep: true, + include: [/\.vue$/, /\.vue\?vue/], + // 生成自定义 `auto-components.d.ts` 全局声明 + dts: 'src/types/auto-components.d.ts', + // 自定义组件的解析器 + resolvers: [ElementPlusResolver()], + exclude: [/[\\/]node_modules[\\/]/] + }), + EslintPlugin({ + cache: false, + include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件 + }), + VueI18nPlugin({ + runtimeOnly: true, + compositionOnly: true, + include: [resolve(__dirname, 'src/locales/**')] + }), + createSvgIconsPlugin({ + iconDirs: [pathResolve('src/assets/svgs')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: true + }), + viteCompression({ + verbose: true, // 是否在控制台输出压缩结果 + disable: false, // 是否禁用 + threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b + algorithm: 'gzip', // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw'] + ext: '.gz', // 生成的压缩包后缀 + deleteOriginFile: false //压缩后是否删除源文件 + }), + ViteEjsPlugin(), + topLevelAwait({ + // https://juejin.cn/post/7152191742513512485 + // The export name of top-level await promise for each chunk module + promiseExportName: '__tla', + // The function to generate import names of top-level await promise in each chunk module + promiseImportName: (i) => `__tla_${i}` + }) + ] +} diff --git a/build/vite/optimize.ts b/build/vite/optimize.ts new file mode 100644 index 0000000..70fc3b8 --- /dev/null +++ b/build/vite/optimize.ts @@ -0,0 +1,111 @@ +const include = [ + 'qs', + 'url', + 'vue', + 'sass', + 'mitt', + 'axios', + 'pinia', + 'dayjs', + 'qrcode', + 'windicss', + 'vue-router', + 'vue-types', + 'vue-i18n', + 'xe-utils', + 'crypto-js', + 'cropperjs', + 'lodash-es', + 'nprogress', + 'web-storage-cache', + '@iconify/iconify', + '@vueuse/core', + '@zxcvbn-ts/core', + 'echarts/core', + 'echarts/charts', + 'echarts/components', + 'echarts/renderers', + 'echarts-wordcloud', + '@wangeditor/editor', + '@wangeditor/editor-for-vue', + 'element-plus', + 'element-plus/es', + 'element-plus/es/locale/lang/zh-cn', + 'element-plus/es/locale/lang/en', + 'element-plus/es/components/backtop/style/css', + 'element-plus/es/components/form/style/css', + 'element-plus/es/components/radio-group/style/css', + 'element-plus/es/components/radio/style/css', + 'element-plus/es/components/checkbox/style/css', + 'element-plus/es/components/checkbox-group/style/css', + 'element-plus/es/components/switch/style/css', + 'element-plus/es/components/time-picker/style/css', + 'element-plus/es/components/date-picker/style/css', + 'element-plus/es/components/descriptions/style/css', + 'element-plus/es/components/descriptions-item/style/css', + 'element-plus/es/components/link/style/css', + 'element-plus/es/components/tooltip/style/css', + 'element-plus/es/components/drawer/style/css', + 'element-plus/es/components/dialog/style/css', + 'element-plus/es/components/checkbox-button/style/css', + 'element-plus/es/components/option-group/style/css', + 'element-plus/es/components/radio-button/style/css', + 'element-plus/es/components/cascader/style/css', + 'element-plus/es/components/color-picker/style/css', + 'element-plus/es/components/input-number/style/css', + 'element-plus/es/components/rate/style/css', + 'element-plus/es/components/select-v2/style/css', + 'element-plus/es/components/tree-select/style/css', + 'element-plus/es/components/slider/style/css', + 'element-plus/es/components/time-select/style/css', + 'element-plus/es/components/autocomplete/style/css', + 'element-plus/es/components/image-viewer/style/css', + 'element-plus/es/components/upload/style/css', + 'element-plus/es/components/col/style/css', + 'element-plus/es/components/form-item/style/css', + 'element-plus/es/components/alert/style/css', + 'element-plus/es/components/breadcrumb/style/css', + 'element-plus/es/components/select/style/css', + 'element-plus/es/components/input/style/css', + 'element-plus/es/components/breadcrumb-item/style/css', + 'element-plus/es/components/tag/style/css', + 'element-plus/es/components/pagination/style/css', + 'element-plus/es/components/table/style/css', + 'element-plus/es/components/table-v2/style/css', + 'element-plus/es/components/table-column/style/css', + 'element-plus/es/components/card/style/css', + 'element-plus/es/components/row/style/css', + 'element-plus/es/components/button/style/css', + 'element-plus/es/components/menu/style/css', + 'element-plus/es/components/sub-menu/style/css', + 'element-plus/es/components/menu-item/style/css', + 'element-plus/es/components/option/style/css', + 'element-plus/es/components/dropdown/style/css', + 'element-plus/es/components/dropdown-menu/style/css', + 'element-plus/es/components/dropdown-item/style/css', + 'element-plus/es/components/skeleton/style/css', + 'element-plus/es/components/skeleton/style/css', + 'element-plus/es/components/backtop/style/css', + 'element-plus/es/components/menu/style/css', + 'element-plus/es/components/sub-menu/style/css', + 'element-plus/es/components/menu-item/style/css', + 'element-plus/es/components/dropdown/style/css', + 'element-plus/es/components/tree/style/css', + 'element-plus/es/components/dropdown-menu/style/css', + 'element-plus/es/components/dropdown-item/style/css', + 'element-plus/es/components/badge/style/css', + 'element-plus/es/components/breadcrumb/style/css', + 'element-plus/es/components/breadcrumb-item/style/css', + 'element-plus/es/components/image/style/css', + 'element-plus/es/components/collapse-transition/style/css', + 'element-plus/es/components/timeline/style/css', + 'element-plus/es/components/timeline-item/style/css', + 'element-plus/es/components/collapse/style/css', + 'element-plus/es/components/collapse-item/style/css', + 'element-plus/es/components/button-group/style/css', + 'element-plus/es/components/text/style/css' +] + +const exclude = ['@iconify/json'] + +export { include, exclude } diff --git a/index.html b/index.html new file mode 100644 index 0000000..49a25a4 --- /dev/null +++ b/index.html @@ -0,0 +1,143 @@ + + + + + + + + %VITE_APP_TITLE% + + +
+ +
+
+
+ +
%VITE_APP_TITLE%
+
+
+
+
+
+
+
+
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..fbda02b --- /dev/null +++ b/package.json @@ -0,0 +1,141 @@ +{ + "name": "ss-oa-manage", + "version": "1.0.0", + "description": "莳松oa管理系统", + "author": "ss", + "private": false, + "scripts": { + "i": "pnpm install", + "dev": "vite --mode base", + "dev-front": "vite --mode front", + "front": "vite --mode front", + "ts:check": "vue-tsc --noEmit", + "build:pro": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode pro", + "build:dev": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode dev", + "build:stage": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode stage", + "build:test": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode test", + "build:static": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode static", + "build:front": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode front", + "serve:pro": "vite preview --mode pro", + "serve:dev": "vite preview --mode dev", + "serve:test": "vite preview --mode test", + "preview": "pnpm build && vite preview", + "npm:check": "npx npm-check-updates", + "clean": "npx rimraf node_modules", + "clean:cache": "npx rimraf node_modules/.cache", + "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src", + "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"", + "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", + "lint:lint-staged": "lint-staged -c ", + "lint:pretty": "pretty-quick --staged" + }, + "dependencies": { + "@amap/amap-jsapi-loader": "^1.0.1", + "@element-plus/icons-vue": "^2.1.0", + "@form-create/designer": "^3.1.0", + "@form-create/element-ui": "^3.1.17", + "@iconify/iconify": "^3.1.0", + "@videojs-player/vue": "^1.0.0", + "@vueuse/core": "^10.1.2", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.10", + "@zxcvbn-ts/core": "^3.0.1", + "animate.css": "^4.1.1", + "axios": "^1.4.0", + "benz-amr-recorder": "^1.1.5", + "bpmn-js-token-simulation": "^0.10.0", + "camunda-bpmn-moddle": "^7.0.1", + "cropperjs": "^1.5.13", + "crypto-js": "^4.1.1", + "dayjs": "^1.11.7", + "diagram-js": "^11.6.0", + "echarts": "^5.4.2", + "echarts-wordcloud": "^2.1.0", + "element-plus": "2.3.4", + "fast-xml-parser": "^4.2.2", + "highlight.js": "^11.8.0", + "intro.js": "^7.0.1", + "jsencrypt": "^3.3.2", + "lodash-es": "^4.17.21", + "min-dash": "^4.1.1", + "mitt": "^3.0.0", + "nprogress": "^0.2.0", + "pinia": "^2.1.3", + "qrcode": "^1.5.3", + "qs": "^6.11.2", + "steady-xml": "^0.1.0", + "url": "^0.11.0", + "video.js": "^8.3.0", + "vue": "3.3.4", + "vue-amap": "^0.5.10", + "vue-dompurify-html": "^5.0.1", + "vue-i18n": "9.2.2", + "vue-router": "^4.2.1", + "vue-types": "^5.0.3", + "vuedraggable": "^4.1.0", + "web-storage-cache": "^1.1.1", + "xe-utils": "^3.5.7", + "xml-js": "^1.6.11" + }, + "devDependencies": { + "@commitlint/cli": "^17.6.3", + "@commitlint/config-conventional": "^17.6.3", + "@iconify/json": "^2.2.67", + "@intlify/unplugin-vue-i18n": "^0.10.0", + "@purge-icons/generated": "^0.9.0", + "@types/intro.js": "^5.1.1", + "@types/lodash-es": "^4.17.7", + "@types/node": "^18.16.0", + "@types/nprogress": "^0.2.0", + "@types/qrcode": "^1.5.0", + "@types/qs": "^6.9.7", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", + "@vitejs/plugin-legacy": "^4.0.3", + "@vitejs/plugin-vue": "^4.2.3", + "@vitejs/plugin-vue-jsx": "^3.0.1", + "autoprefixer": "^10.4.14", + "bpmn-js": "^8.9.0", + "bpmn-js-properties-panel": "^0.46.0", + "consola": "^3.1.0", + "eslint": "^8.40.0", + "eslint-config-prettier": "^8.8.0", + "eslint-define-config": "^1.20.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.13.0", + "lint-staged": "^13.2.2", + "postcss": "^8.4.23", + "postcss-html": "^1.5.0", + "postcss-scss": "^4.0.6", + "prettier": "^2.8.8", + "rimraf": "^5.0.1", + "rollup": "^3.22.0", + "sass": "^1.62.1", + "stylelint": "^15.6.2", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recommended": "^12.0.0", + "stylelint-config-standard": "^33.0.0", + "stylelint-order": "^6.0.3", + "terser": "^5.17.4", + "typescript": "5.0.4", + "unplugin-auto-import": "^0.16.0", + "unplugin-element-plus": "^0.7.1", + "unplugin-vue-components": "^0.24.1", + "vite": "4.3.8", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-ejs": "^1.6.4", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-progress": "^0.0.7", + "vite-plugin-purge-icons": "^0.9.2", + "vite-plugin-svg-icons": "^2.0.1", + "vite-plugin-top-level-await": "^1.3.0", + "vite-plugin-vue-setup-extend-plus": "^0.1.0", + "vite-plugin-windicss": "^1.9.0", + "vue-tsc": "^1.6.5", + "windicss": "^3.5.6" + }, + "engines": { + "node": ">=16.0.0" + }, + "license": "MIT" +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..961986e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {} + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..b014bbf --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,22 @@ +module.exports = { + printWidth: 100, // 每行代码长度(默认80) + tabWidth: 2, // 每个tab相当于多少个空格(默认2)ab进行缩进(默认false) + useTabs: false, // 是否使用tab + semi: false, // 声明结尾使用分号(默认true) + vueIndentScriptAndStyle: false, + singleQuote: true, // 使用单引号(默认false) + quoteProps: 'as-needed', + bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true) + trailingComma: 'none', // 多行使用拖尾逗号(默认none) + jsxSingleQuote: false, + // 箭头函数参数括号 默认avoid 可选 avoid| always + // avoid 能省略括号的时候就省略 例如x => x + // always 总是有括号 + arrowParens: 'always', + insertPragma: false, + requirePragma: false, + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + endOfLine: 'auto', + rangeStart: 0 +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b72b5ec Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/logo.gif b/public/logo.gif new file mode 100644 index 0000000..fdbd32c Binary files /dev/null and b/public/logo.gif differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..f75478c --- /dev/null +++ b/src/App.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/api/call/index.js b/src/api/call/index.js new file mode 100644 index 0000000..a987a27 --- /dev/null +++ b/src/api/call/index.js @@ -0,0 +1,13 @@ +import request from '@/config/axios' + +export default { + callLogin(data) { + return request.post({ url: '/call-api/openapi/V2.0.4/agentLogin', data }) + }, + callUserStatus(data) { + return request.post({ url: '/call-api/openapi/V2.0.4/getAgentStatus', data }) + }, + callNumber(data) { + return request.post({ url: '/call-api/openapi/V2.0.4/callNumber', data }) + } +} diff --git a/src/api/clue/afterSale.js b/src/api/clue/afterSale.js new file mode 100644 index 0000000..1321ec8 --- /dev/null +++ b/src/api/clue/afterSale.js @@ -0,0 +1,39 @@ +import request from '@/config/axios' + +// 查询(精简)列表 +export const getAfterSaleList = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-after-sale/list', params }) +} + +// 查询(精简)列表 +export const getAfterSalePage = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-after-sale/page', params }) +} + +// 新增 +export const createAfterSale = async (data) => { + return await request.post({ + url: '/admin-api/crm/sign-after-sale/create', + data, + isSubmitForm: true + }) +} + +// 审核 +export const auditAfterSale = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-after-sale/check', data }) +} + +export const batchAuditAfterSale = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-after-sale/batch/check', data }) +} + +// 撤销 +export const cancelApplyAfterSale = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-after-sale/revoke', params: data }) +} + +// 查询详情 +export const getAfterSaleDetail = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-after-sale/get', params }) +} diff --git a/src/api/clue/clueCache.js b/src/api/clue/clueCache.js new file mode 100644 index 0000000..9c9349e --- /dev/null +++ b/src/api/clue/clueCache.js @@ -0,0 +1,11 @@ +import request from '@/config/axios' + +// 查询用户配置 +export const getClueCache = async (params) => { + return await request.get({ url: '/admin-api/crm/param-user-setting/get-by-user', params }) +} + +// 保存用户配置 +export const setClueCache = async (data) => { + return await request.post({ url: '/admin-api/crm/param-user-setting/save', data }) +} diff --git a/src/api/clue/clueField.js b/src/api/clue/clueField.js new file mode 100644 index 0000000..0aaa2b9 --- /dev/null +++ b/src/api/clue/clueField.js @@ -0,0 +1,40 @@ +import request from '@/config/axios' + +// 创建 +export const createField = (data) => { + return request.post({ url: '/admin-api/crm/param/create', data: { ...data, module: 1 } }) +} + +// 更新 +export const updateField = (data) => { + return request.put({ url: '/admin-api/crm/param/update', data }) +} + +// 删除 +export const deleteField = (id) => { + return request.delete({ url: `/admin-api/crm/param/delete?id=${id}` }) +} + +// 获得 +export const getField = (id) => { + return request.get({ url: `/admin-api/crm/param/get?id=${id}` }) +} + +// 获得精简信息列表 +export const getSimpleFieldList = () => { + return request.get({ url: '/admin-api/crm/param/simple-list', params: { module: 1 } }) +} + +// 获取自定义字段 +export const getDiyFieldList = () => { + return request.get({ url: '/admin-api/crm/param/get-diy-param', params: { module: 1 } }) +} + +// 状态修改 +export const updateFieldStatus = (signParamId, status) => { + const data = { + signParamId, + status + } + return request.put({ url: '/admin-api/crm/param/status/update', data: data }) +} diff --git a/src/api/clue/clueGetSet.js b/src/api/clue/clueGetSet.js new file mode 100644 index 0000000..1877b27 --- /dev/null +++ b/src/api/clue/clueGetSet.js @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// 线索获取规则 +export const getClueGainRuleList = () => { + return request.get({ url: '/admin-api/crm/sch-clue-gain-rule/list' }) +} + +// 删除 +export const deleteClueGainRule = (id) => { + return request.delete({ url: `/admin-api/crm/sch-clue-gain-rule/delete?id=${id}` }) +} + +// 线索获取规则保存 +export const saveClueGainRule = (data) => { + return request.put({ url: '/admin-api/crm/sch-clue-gain-rule/batchUpdate', data }) +} + +// 线索规则 +export const getClueDistributeRuleByUser = (params) => { + return request.get({ url: '/admin-api/crm/sch-clue-distribution-rule/queryByUserId', params }) +} + +// 删除 +export const deleteClueDistributeRule = (id) => { + return request.delete({ url: `/admin-api/crm/sch-clue-gain-rule/delete?id=${id}` }) +} diff --git a/src/api/clue/delivery.js b/src/api/clue/delivery.js new file mode 100644 index 0000000..ca8d19b --- /dev/null +++ b/src/api/clue/delivery.js @@ -0,0 +1,21 @@ +import request from '@/config/axios' + +// 查询(精简)列表 +export const getDeliveryList = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-send/list', params }) +} + +// 查询分页列表 +export const getDeliveryPage = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-send/page', params }) +} + +// 新增 +export const createDelivery = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-send/create', data, isSubmitForm: true }) +} + +// 查询详情 +export const getDeliveryDetail = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-send/get', params }) +} diff --git a/src/api/clue/followRecord.js b/src/api/clue/followRecord.js new file mode 100644 index 0000000..6fa0c34 --- /dev/null +++ b/src/api/clue/followRecord.js @@ -0,0 +1,20 @@ +import request from '@/config/axios' + +// 查询(精简)列表 +export const getFollowList = async (params) => { + return await request.get({ url: '/admin-api/crm/clue-follow-record/list', params }) +} + +// 新增 +export const createFollow = async (data) => { + return await request.post({ + url: '/admin-api/crm/clue-follow-record/create', + data: data, + isSubmitForm: true + }) +} + +// 删除 +export const deleteFollow = async (id) => { + return await request.delete({ url: '/admin-api/crm/clue-follow-record/delete?id=' + id }) +} diff --git a/src/api/clue/index.js b/src/api/clue/index.js new file mode 100644 index 0000000..774909e --- /dev/null +++ b/src/api/clue/index.js @@ -0,0 +1,60 @@ +import request from '@/config/axios' + +// 查询(精简)列表 +export const getSimpleClueList = async () => { + return await request.get({ url: '/admin-api/crm/sch-clue/list-all-simple' }) +} + +// 查询列表 +export const getCluePage = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-clue/page', params }) +} + +// 查询详情 +export const getClue = async (id) => { + return await request.get({ url: '/admin-api/crm/sch-clue/get?id=' + id }) +} + +// 新增 +export const createClue = async (data) => { + return await request.post({ + url: '/admin-api/crm/sch-clue/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updateClue = async (params) => { + return await request.put({ url: '/admin-api/crm/sch-clue/update', data: params }) +} + +// 删除 +export const deleteClue = async (id) => { + return await request.delete({ url: '/admin-api/crm/sch-clue/delete?id=' + id }) +} + +// 释放 +export const releaseClue = async (data) => { + return await request.put({ url: '/admin-api/crm/sch-clue/public/save', data }) +} + +// 通用查询数量 +export const getClueCount = async () => { + return await request.get({ url: '/admin-api/crm/sch-clue/get-clue-num' }) +} + +// 获取操作记录 +export const getOpearateRecord = async (params) => { + return await request.get({ url: '/admin-api/crm/clue-operate-record/list', params }) +} + +// 获取公海线索 +export const getPublicClue = async (data) => { + return await request.put({ url: '/admin-api/crm/sch-clue/public/save', data }) +} + +// 获得线索跟进用户信息 +export const getFollowUserList = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-clue/get-follow-user', params }) +} diff --git a/src/api/clue/orderField.js b/src/api/clue/orderField.js new file mode 100644 index 0000000..9f63dd2 --- /dev/null +++ b/src/api/clue/orderField.js @@ -0,0 +1,40 @@ +import request from '@/config/axios' + +// 创建 +export const createField = (data) => { + return request.post({ url: '/admin-api/crm/param/create', data: { ...data, module: 2 } }) +} + +// 更新 +export const updateField = (data) => { + return request.put({ url: '/admin-api/crm/param/update', data }) +} + +// 删除 +export const deleteField = (id) => { + return request.delete({ url: `/admin-api/crm/param/delete?id=${id}` }) +} + +// 获得 +export const getField = (id) => { + return request.get({ url: `/admin-api/crm/param/get?id=${id}` }) +} + +// 获得精简信息列表 +export const getSimpleFieldList = () => { + return request.get({ url: '/admin-api/crm/param/simple-list', params: { module: 2 } }) +} + +// 获取自定义字段 +export const getDiyFieldList = () => { + return request.get({ url: '/admin-api/crm/param/get-diy-param', params: { module: 2 } }) +} + +// 状态修改 +export const updateFieldStatus = (signParamId, status) => { + const data = { + signParamId, + status + } + return request.put({ url: '/admin-api/crm/param/status/update', data: data }) +} diff --git a/src/api/clue/payment.js b/src/api/clue/payment.js new file mode 100644 index 0000000..f67ca24 --- /dev/null +++ b/src/api/clue/payment.js @@ -0,0 +1,39 @@ +import request from '@/config/axios' + +// 查询(精简)列表 +export const getPaymentList = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-pay-record/list', params }) +} + +// 查询(精简)列表 +export const getPaymentPage = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-pay-record/page', params }) +} + +// 新增 +export const createPayment = async (data) => { + return await request.post({ + url: '/admin-api/crm/sign-pay-record/create', + data, + isSubmitForm: true + }) +} + +// 审核 +export const auditPayment = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-pay-record/check', data }) +} + +export const batchAuditPayment = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-pay-record/batch/check', data }) +} + +// 撤销 +export const cancelApplyPayment = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-pay-record/revoke', params: data }) +} + +// 查询详情 +export const getPaymentDetail = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-pay-record/get', params }) +} diff --git a/src/api/clue/settle.js b/src/api/clue/settle.js new file mode 100644 index 0000000..c6614e5 --- /dev/null +++ b/src/api/clue/settle.js @@ -0,0 +1,16 @@ +import request from '@/config/axios' + +// 查询列表 +export const getSettlePage = async (params) => { + return await request.get({ url: '/admin-api/crm/sign-settlement/page', params }) +} + +// 查询详情 +export const getSettle = async (id) => { + return await request.get({ url: '/admin-api/crm/sign-settlement/get?id=' + id }) +} + +// 批量结算 +export const batchSaveSettle = async (data) => { + return await request.post({ url: '/admin-api/crm/sign-settlement/batch/save', data }) +} diff --git a/src/api/clue/sign.js b/src/api/clue/sign.js new file mode 100644 index 0000000..2dc80f2 --- /dev/null +++ b/src/api/clue/sign.js @@ -0,0 +1,31 @@ +import request from '@/config/axios' + +// 查询列表 +export const getSignPage = async (params) => { + return await request.get({ url: '/admin-api/crm/sign/page', params }) +} + +// 查询详情 +export const getSign = async (id) => { + return await request.get({ url: '/admin-api/crm/sign/get?id=' + id }) +} + +// 新增 +export const createSign = async (data) => { + return await request.post({ url: '/admin-api/crm/sign/create', data: data, isSubmitForm: true }) +} + +// 取消登记 +export const cancelDeal = async (id) => { + return await request.delete({ url: '/admin-api/crm/sign/delete?id=' + id }) +} + +// 新增支出 +export const signAddPay = async (data) => { + return await request.post({ url: '/admin-api/crm/sign/extra-pay/save', data: data }) +} + +// 查询支出 +export const getSignExtraPayList = async (params) => { + return await request.get({ url: '/admin-api/crm/sign/extra-pay/get', params }) +} diff --git a/src/api/clue/skill.js b/src/api/clue/skill.js new file mode 100644 index 0000000..3ee5c68 --- /dev/null +++ b/src/api/clue/skill.js @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// 获得列表 +export const getSkillPage = (params) => { + return request.get({ url: '/admin-api/crm/skill/page', params }) +} + +// 创建 +export const createSkill = (data) => { + return request.post({ url: '/admin-api/crm/skill/create', data, isSubmitForm: true }) +} + +// 更新 +export const updateSkill = (data) => { + return request.put({ url: '/admin-api/crm/skill/update', data }) +} + +// 删除 +export const deleteSkill = (id) => { + return request.delete({ url: `/admin-api/crm/skill/delete?id=${id}` }) +} + +// 获得 +export const getSkill = (id) => { + return request.get({ url: `/admin-api/crm/skill/get?id=${id}` }) +} diff --git a/src/api/clue/source.js b/src/api/clue/source.js new file mode 100644 index 0000000..afeb22a --- /dev/null +++ b/src/api/clue/source.js @@ -0,0 +1,31 @@ +import request from '@/config/axios' + +// 查询(精简)列表 +export const getSimpleSourceList = async () => { + return await request.get({ url: '/admin-api/crm/source/list' }) +} + +// 查询列表 +export const getSourcePage = async (params) => { + return await request.get({ url: '/admin-api/crm/source/list', params }) +} + +// 查询详情 +export const getSource = async (id) => { + return await request.get({ url: '/admin-api/crm/source/get?id=' + id }) +} + +// 新增 +export const createSource = async (data) => { + return await request.post({ url: '/admin-api/crm/source/create', data: data, isSubmitForm: true }) +} + +// 修改 +export const updateSource = async (params) => { + return await request.put({ url: '/admin-api/crm/source/update', data: params }) +} + +// 删除 +export const deleteSource = async (id) => { + return await request.delete({ url: '/admin-api/crm/source/delete?id=' + id }) +} diff --git a/src/api/home/index.js b/src/api/home/index.js new file mode 100644 index 0000000..ea137e1 --- /dev/null +++ b/src/api/home/index.js @@ -0,0 +1,20 @@ +import request from '@/config/axios' +// 线索情况 +export const getUserClueCount = async (params) => { + return await request.get({ url: '/admin-api/crm/home/get-user-count', params }) +} + +// 成交率 +export const getClueSignSignRate = async (params) => { + return await request.get({ url: '/admin-api/crm/home/get-sign-rate', params }) +} + +// 成交率 +export const getClueSignSignRank = async (params) => { + return await request.get({ url: '/admin-api/crm/home/get-sign-top10', params }) +} + +// 成交率 +export const getClueFollowRank = async (params) => { + return await request.get({ url: '/admin-api/crm/home/get-follow-top10', params }) +} diff --git a/src/api/infra/config/index.ts b/src/api/infra/config/index.ts new file mode 100644 index 0000000..bbee3b3 --- /dev/null +++ b/src/api/infra/config/index.ts @@ -0,0 +1,48 @@ +import request from '@/config/axios' + +export interface ConfigVO { + id: number | undefined + category: string + name: string + key: string + value: string + type: number + visible: boolean + remark: string + createTime: Date +} + +// 查询参数列表 +export const getConfigPage = (params: PageParam) => { + return request.get({ url: '/admin-api/infra/config/page', params }) +} + +// 查询参数详情 +export const getConfig = (id: number) => { + return request.get({ url: '/admin-api/infra/config/get?id=' + id }) +} + +// 根据参数键名查询参数值 +export const getConfigKey = (configKey: string) => { + return request.get({ url: '/admin-api/infra/config/get-value-by-key?key=' + configKey }) +} + +// 新增参数 +export const createConfig = (data: ConfigVO) => { + return request.post({ url: '/admin-api/infra/config/create', data }) +} + +// 修改参数 +export const updateConfig = (data: ConfigVO) => { + return request.put({ url: '/admin-api/infra/config/update', data }) +} + +// 删除参数 +export const deleteConfig = (id: number) => { + return request.delete({ url: '/admin-api/infra/config/delete?id=' + id }) +} + +// 导出参数 +export const exportConfig = (params) => { + return request.download({ url: '/admin-api/infra/config/export', params }) +} diff --git a/src/api/infra/file/index.ts b/src/api/infra/file/index.ts new file mode 100644 index 0000000..52a3723 --- /dev/null +++ b/src/api/infra/file/index.ts @@ -0,0 +1,45 @@ +import request from '@/config/axios' + +export interface FilePageReqVO extends PageParam { + path?: string + type?: string + createTime?: Date[] +} + +// 文件预签名地址 Response VO +export interface FilePresignedUrlRespVO { + // 文件配置编号 + configId: number + // 文件上传 URL + uploadUrl: string + // 文件 URL + url: string +} + +// 查询文件列表 +export const getFilePage = (params: FilePageReqVO) => { + return request.get({ url: '/infra/file/page', params }) +} + +// 删除文件 +export const deleteFile = (id: number) => { + return request.delete({ url: '/infra/file/delete?id=' + id }) +} + +// 获取文件预签名地址 +export const getFilePresignedUrl = (path: string) => { + return request.get({ + url: '/infra/file/presigned-url', + params: { path } + }) +} + +// 创建文件 +export const createFile = (data: any) => { + return request.post({ url: '/infra/file/create', data }) +} + +// 上传文件 +export const updateFile = (data: any) => { + return request.upload({ url: '/admin-api/system/file/upload', data }) +} diff --git a/src/api/login/index.ts b/src/api/login/index.ts new file mode 100644 index 0000000..a82c649 --- /dev/null +++ b/src/api/login/index.ts @@ -0,0 +1,73 @@ +import request from '@/config/axios' +import { getRefreshToken } from '@/utils/auth' +import type { UserLoginVO } from './types' + +export interface SmsCodeVO { + mobile: string + scene: number +} + +export interface SmsLoginVO { + mobile: string + code: string +} + +// 登录 +export const login = (data: UserLoginVO) => { + return request.post({ url: '/admin-api/system/auth/login', data }) +} + +// 刷新访问令牌 +export const refreshToken = () => { + return request.post({ + url: '/admin-api/system/auth/refresh-token?refreshToken=' + getRefreshToken() + }) +} + +// 使用租户名,获得租户编号 +export const getTenantIdByName = (name: string) => { + return request.get({ url: '/admin-api/system/tenant/get-id-by-name?name=' + name }) +} + +// 登出 +export const loginOut = () => { + return request.post({ url: '/admin-api/system/auth/logout' }) +} + +// 获取用户权限信息 +export const getInfo = (params) => { + return request.get({ url: '/admin-api/system/auth/get-permission-info', params }) +} + +//获取登录验证码 +export const sendSmsCode = (data: SmsCodeVO) => { + return request.post({ url: '/admin-api/system/auth/send-sms-code', data }) +} + +// 短信验证码登录 +export const smsLogin = (data: SmsLoginVO) => { + return request.post({ url: '/admin-api/system/auth/sms-login', data }) +} + +// 社交授权的跳转 +export const socialAuthRedirect = (type: number, redirectUri: string) => { + return request.get({ + url: '/admin-api/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri + }) +} +// 获取验证图片以及 token +export const getCode = (data) => { + return request.postOriginal({ url: '/admin-api/system/captcha/get', data }) +} + +// 滑动或者点选验证 +export const reqCheck = (data) => { + return request.postOriginal({ url: '/admin-api/system/captcha/check', data }) +} + +// 获取应用信息 +export const getAppInfo = (instanceId: number) => { + return request.get({ + url: '/admin-api/system/serviceInstance/getInstanceInfo?instanceId=' + instanceId + }) +} diff --git a/src/api/login/oauth2/index.ts b/src/api/login/oauth2/index.ts new file mode 100644 index 0000000..cc95188 --- /dev/null +++ b/src/api/login/oauth2/index.ts @@ -0,0 +1,41 @@ +import request from '@/config/axios' + +// 获得授权信息 +export const getAuthorize = (clientId: string) => { + return request.get({ url: '/admin-api/system/oauth2/authorize?clientId=' + clientId }) +} + +// 发起授权 +export const authorize = ( + responseType: string, + clientId: string, + redirectUri: string, + state: string, + autoApprove: boolean, + checkedScopes: string[], + uncheckedScopes: string[] +) => { + // 构建 scopes + const scopes = {} + for (const scope of checkedScopes) { + scopes[scope] = true + } + for (const scope of uncheckedScopes) { + scopes[scope] = false + } + // 发起请求 + return request.post({ + url: '/admin-api/system/oauth2/authorize', + headers: { + 'Content-type': 'application/x-www-form-urlencoded' + }, + params: { + response_type: responseType, + client_id: clientId, + redirect_uri: redirectUri, + state: state, + auto_approve: autoApprove, + scope: JSON.stringify(scopes) + } + }) +} diff --git a/src/api/login/types.ts b/src/api/login/types.ts new file mode 100644 index 0000000..b2173f7 --- /dev/null +++ b/src/api/login/types.ts @@ -0,0 +1,28 @@ +export type UserLoginVO = { + username: string + password: string + captchaVerification: string +} + +export type TokenType = { + id: number // 编号 + accessToken: string // 访问令牌 + refreshToken: string // 刷新令牌 + userId: number // 用户编号 + userType: number //用户类型 + clientId: string //客户端编号 + expiresTime: number //过期时间 +} + +export type UserVO = { + id: number + username: string + nickname: string + deptId: number + email: string + mobile: string + sex: number + avatar: string + loginIp: string + loginDate: string +} diff --git a/src/api/mall/product/brand.ts b/src/api/mall/product/brand.ts new file mode 100644 index 0000000..9191677 --- /dev/null +++ b/src/api/mall/product/brand.ts @@ -0,0 +1,61 @@ +import request from '@/config/axios' + +/** + * 商品品牌 + */ +export interface BrandVO { + /** + * 品牌编号 + */ + id?: number + /** + * 品牌名称 + */ + name: string + /** + * 品牌图片 + */ + picUrl: string + /** + * 品牌排序 + */ + sort?: number + /** + * 品牌描述 + */ + description?: string + /** + * 开启状态 + */ + status: number +} + +// 创建商品品牌 +export const createBrand = (data: BrandVO) => { + return request.post({ url: '/admin-api/crm/erp-product-brand/create', data, isSubmitForm: true }) +} + +// 更新商品品牌 +export const updateBrand = (data: BrandVO) => { + return request.put({ url: '/admin-api/crm/erp-product-brand/update', data }) +} + +// 删除商品品牌 +export const deleteBrand = (id: number) => { + return request.delete({ url: `/admin-api/crm/erp-product-brand/delete?id=${id}` }) +} + +// 获得商品品牌 +export const getBrand = (id: number) => { + return request.get({ url: `/admin-api/crm/erp-product-brand/get?id=${id}` }) +} + +// 获得商品品牌列表 +export const getBrandParam = (params: PageParam) => { + return request.get({ url: '/admin-api/crm/erp-product-brand/page', params }) +} + +// 获得商品品牌精简信息列表 +export const getSimpleBrandList = () => { + return request.get({ url: '/admin-api/crm/erp-product-brand/simple-list' }) +} diff --git a/src/api/mall/product/category.ts b/src/api/mall/product/category.ts new file mode 100644 index 0000000..efc85f5 --- /dev/null +++ b/src/api/mall/product/category.ts @@ -0,0 +1,69 @@ +import request from '@/config/axios' + +/** + * 产品分类 + */ +export interface CategoryVO { + /** + * 分类编号 + */ + id?: number + /** + * 父分类编号 + */ + parentId?: number + /** + * 分类名称 + */ + name: string + /** + * 移动端分类图 + */ + picUrl: string + /** + * PC 端分类图 + */ + bigPicUrl?: string + /** + * 分类排序 + */ + sort: number + /** + * 开启状态 + */ + status: number +} + +// 创建商品分类 +export const createCategory = (data: CategoryVO) => { + return request.post({ + url: '/admin-api/crm/erp-product-category/create', + data, + isSubmitForm: true + }) +} + +// 更新商品分类 +export const updateCategory = (data: CategoryVO) => { + return request.put({ url: '/admin-api/crm/erp-product-category/update', data }) +} + +// 删除商品分类 +export const deleteCategory = (id: number) => { + return request.delete({ url: `/admin-api/crm/erp-product-category/delete?id=${id}` }) +} + +// 获得商品分类 +export const getCategory = (id: number) => { + return request.get({ url: `/admin-api/crm/erp-product-category/get?id=${id}` }) +} + +// 获得商品分类列表 +export const getCategoryList = (params: any) => { + return request.get({ url: '/admin-api/crm/erp-product-category/list', params }) +} + +// 获得商品分类列表 +export const getCategorySimpleList = (params: any) => { + return request.get({ url: '/admin-api/crm/erp-product-category/simple-list', params }) +} diff --git a/src/api/mall/product/index.js b/src/api/mall/product/index.js new file mode 100644 index 0000000..80c9a98 --- /dev/null +++ b/src/api/mall/product/index.js @@ -0,0 +1,33 @@ +import request from '@/config/axios' +// 查询列表 +export const getProductPage = async (params) => { + return await request.get({ url: '/admin-api/crm/erp-product/page', params }) +} + +// 查询详情 +export const getProduct = async (id) => { + return await request.get({ url: '/admin-api/crm/erp-product/get?id=' + id }) +} + +// 新增 +export const createProduct = async (data) => { + return await request.post({ + url: '/admin-api/crm/erp-product/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updateProduct = async (params) => { + return await request.put({ url: '/admin-api/crm/erp-product/update', data: params }) +} + +// 删除 +export const deleteProduct = async (id) => { + return await request.delete({ url: '/admin-api/crm/erp-product/delete?id=' + id }) +} + +export const getSimpleProductList = async () => { + return await request.get({ url: '/admin-api/crm/erp-product/simple-list' }) +} diff --git a/src/api/mall/product/productField.js b/src/api/mall/product/productField.js new file mode 100644 index 0000000..f740e90 --- /dev/null +++ b/src/api/mall/product/productField.js @@ -0,0 +1,44 @@ +import request from '@/config/axios' + +// 创建 +export const createField = (data) => { + return request.post({ + url: '/admin-api/crm/param/create', + data: { ...data, module: 3 }, + isSubmitForm: true + }) +} + +// 更新 +export const updateField = (data) => { + return request.put({ url: '/admin-api/crm/param/update', data }) +} + +// 删除 +export const deleteField = (id) => { + return request.delete({ url: `/admin-api/crm/param/delete?id=${id}` }) +} + +// 获得 +export const getField = (id) => { + return request.get({ url: `/admin-api/crm/param/get?id=${id}` }) +} + +// 获得精简信息列表 +export const getSimpleFieldList = () => { + return request.get({ url: '/admin-api/crm/param/simple-list', params: { module: 3 } }) +} + +// 获取自定义字段 +export const getDiyFieldList = () => { + return request.get({ url: '/admin-api/crm/param/get-diy-param', params: { module: 3 } }) +} + +// 状态修改 +export const updateFieldStatus = (signParamId, status) => { + const data = { + signParamId, + status + } + return request.put({ url: '/admin-api/crm/param/status/update', data: data }) +} diff --git a/src/api/mall/product/property.ts b/src/api/mall/product/property.ts new file mode 100644 index 0000000..b2c2ce5 --- /dev/null +++ b/src/api/mall/product/property.ts @@ -0,0 +1,107 @@ +import request from '@/config/axios' + +/** + * 商品属性 + */ +export interface PropertyVO { + id?: number + /** 名称 */ + name: string + /** 备注 */ + remark?: string +} + +/** + * 属性值 + */ +export interface PropertyValueVO { + id?: number + /** 属性项的编号 */ + propertyId?: number + /** 名称 */ + name: string + /** 备注 */ + remark?: string +} + +/** + * 商品属性值的明细 + */ +export interface PropertyValueDetailVO { + /** 属性项的编号 */ + propertyId: number // 属性的编号 + /** 属性的名称 */ + propertyName: string + /** 属性值的编号 */ + valueId: number + /** 属性值的名称 */ + valueName: string +} + +// ------------------------ 属性项 ------------------- + +// 创建属性项 +export const createProperty = (data: PropertyVO) => { + return request.post({ + url: '/admin-api/crm/erp-product-property/create', + data, + isSubmitForm: true + }) +} + +// 更新属性项 +export const updateProperty = (data: PropertyVO) => { + return request.put({ url: '/product/property/update', data }) +} + +// 删除属性项 +export const deleteProperty = (id: number) => { + return request.delete({ url: `/product/property/delete?id=${id}` }) +} + +// 获得属性项 +export const getProperty = (id: number): Promise => { + return request.get({ url: `/product/property/get?id=${id}` }) +} + +// 获得属性项分页 +export const getPropertyPage = (params: PageParam) => { + return request.get({ url: '/product/property/page', params }) +} + +// 获得属性项列表 +export const getPropertyList = (params: any) => { + return request.get({ url: '/product/property/list', params }) +} + +// 获得属性项列表 +export const getPropertyListAndValue = (data: any) => { + return request.post({ url: '/product/property/get-value-list', data }) +} + +// ------------------------ 属性值 ------------------- + +// 获得属性值分页 +export const getPropertyValuePage = (params: PageParam & any) => { + return request.get({ url: '/product/property/value/page', params }) +} + +// 获得属性值 +export const getPropertyValue = (id: number): Promise => { + return request.get({ url: `/product/property/value/get?id=${id}` }) +} + +// 创建属性值 +export const createPropertyValue = (data: PropertyValueVO) => { + return request.post({ url: '/admin-api/crm/erp-product-property-value/create', data }) +} + +// 更新属性值 +export const updatePropertyValue = (data: PropertyValueVO) => { + return request.put({ url: '/product/property/value/update', data }) +} + +// 删除属性值 +export const deletePropertyValue = (id: number) => { + return request.delete({ url: `/product/property/value/delete?id=${id}` }) +} diff --git a/src/api/mall/purchase/index.js b/src/api/mall/purchase/index.js new file mode 100644 index 0000000..ea5e0a9 --- /dev/null +++ b/src/api/mall/purchase/index.js @@ -0,0 +1,29 @@ +import request from '@/config/axios' +// 查询列表 +export const getPurchasePage = async (params) => { + return await request.get({ url: '/admin-api/crm/erp-purchase/page', params }) +} + +// 新增 +export const createPurchase = async (data) => { + return await request.post({ + url: '/admin-api/crm/erp-purchase/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updatePurchase = async (params) => { + return await request.put({ url: '/admin-api/crm/erp-purchase/update', data: params }) +} + +// 删除 +export const deletePurchase = async (id) => { + return await request.delete({ url: '/admin-api/crm/erp-purchase/delete?id=' + id }) +} + +// 审核 +export const auditPurchase = async (data) => { + return await request.post({ url: '/admin-api/crm/erp-purchase/audit', data }) +} diff --git a/src/api/mall/trade/delivery/express/index.ts b/src/api/mall/trade/delivery/express/index.ts new file mode 100644 index 0000000..95429a4 --- /dev/null +++ b/src/api/mall/trade/delivery/express/index.ts @@ -0,0 +1,40 @@ +import request from '@/config/axios' + +export interface DeliveryExpressVO { + id: number + code: string + name: string + logo: string + sort: number + status: number +} + +// 查询快递公司列表 +export const getDeliveryExpressPage = async (params: PageParam) => { + return await request.get({ url: '/trade/delivery/express/page', params }) +} + +// 查询快递公司详情 +export const getDeliveryExpress = async (id: number) => { + return await request.get({ url: '/trade/delivery/express/get?id=' + id }) +} + +// 新增快递公司 +export const createDeliveryExpress = async (data: DeliveryExpressVO) => { + return await request.post({ url: '/trade/delivery/express/create', data }) +} + +// 修改快递公司 +export const updateDeliveryExpress = async (data: DeliveryExpressVO) => { + return await request.put({ url: '/trade/delivery/express/update', data }) +} + +// 删除快递公司 +export const deleteDeliveryExpress = async (id: number) => { + return await request.delete({ url: '/trade/delivery/express/delete?id=' + id }) +} + +// 导出快递公司 Excel +export const exportDeliveryExpressApi = async (params) => { + return await request.download({ url: '/trade/delivery/express/export-excel', params }) +} diff --git a/src/api/mall/trade/delivery/expressTemplate/index.ts b/src/api/mall/trade/delivery/expressTemplate/index.ts new file mode 100644 index 0000000..9414c84 --- /dev/null +++ b/src/api/mall/trade/delivery/expressTemplate/index.ts @@ -0,0 +1,54 @@ +import request from '@/config/axios' + +export interface DeliveryExpressTemplateVO { + id: number + name: string + chargeMode: number + sort: number + templateCharge: ExpressTemplateChargeVO[] + templateFree: ExpressTemplateFreeVO[] +} + +export declare type ExpressTemplateChargeVO = { + areaIds: number[] + startCount: number + startPrice: number + extraCount: number + extraPrice: number +} + +export declare type ExpressTemplateFreeVO = { + areaIds: number[] + freeCount: number + freePrice: number +} + +// 查询快递运费模板列表 +export const getDeliveryExpressTemplatePage = async (params: PageParam) => { + return await request.get({ url: '/trade/delivery/express-template/page', params }) +} + +// 查询快递运费模板详情 +export const getDeliveryExpressTemplate = async (id: number) => { + return await request.get({ url: '/trade/delivery/express-template/get?id=' + id }) +} + +// 新增快递运费模板 +export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => { + return await request.post({ url: '/trade/delivery/express-template/create', data }) +} + +// 修改快递运费模板 +export const updateDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => { + return await request.put({ url: '/trade/delivery/express-template/update', data }) +} + +// 删除快递运费模板 +export const deleteDeliveryExpressTemplate = async (id: number) => { + return await request.delete({ url: '/trade/delivery/express-template/delete?id=' + id }) +} + +// 导出快递运费模板 Excel +export const exportDeliveryExpressTemplateApi = async (params) => { + return await request.download({ url: '/trade/delivery/express-template/export-excel', params }) +} diff --git a/src/api/mall/warehouse/index.js b/src/api/mall/warehouse/index.js new file mode 100644 index 0000000..40a863a --- /dev/null +++ b/src/api/mall/warehouse/index.js @@ -0,0 +1,49 @@ +import request from '@/config/axios' +// 查询列表 +export const getWarehousePage = async (params) => { + return await request.get({ url: '/admin-api/crm/erp-warehouse/page', params }) +} + +// 新增 +export const createWarehouse = async (data) => { + return await request.post({ + url: '/admin-api/crm/erp-warehouse/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updateWarehouse = async (params) => { + return await request.put({ url: '/admin-api/crm/erp-warehouse/update', data: params }) +} + +// 删除 +export const deleteWarehouse = async (id) => { + return await request.delete({ url: '/admin-api/crm/erp-warehouse/delete?id=' + id }) +} + +// 获取仓库 +export const getWarehouse = async (id) => { + return await request.get({ url: '/admin-api/crm/erp-warehouse/get?id=' + id }) +} + +// 获取仓库列表 +export const getSimpleWarehouseList = async () => { + return await request.get({ url: '/admin-api/crm/erp-warehouse/simple-list' }) +} + +// 获取库存 +export const getInventoryList = async (params) => { + return await request.get({ url: '/admin-api/crm/erp-inventory/page', params }) +} + +// 获取库存变动记录 +export const getInventoryRecord = async (params) => { + return await request.get({ url: '/admin-api/crm/erp-inventory-record/page', params }) +} + +// 获取剩余库存 +export const getRemainInventoryList = async (params) => { + return await request.get({ url: '/admin-api/crm/erp-inventory-detail/list', params }) +} diff --git a/src/api/school/class/index.js b/src/api/school/class/index.js new file mode 100644 index 0000000..0331cf0 --- /dev/null +++ b/src/api/school/class/index.js @@ -0,0 +1,54 @@ +import request from '@/config/axios' + +// 查询列表-分页 +export const getClassTypePage = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-class-type/page', params }) +} + +export const getClassTypeList = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-class-type/list', params }) +} + +// 查询详情 +export const getClassType = async (id) => { + return await request.get({ url: '/admin-api/crm/sch-class-type/get?id=' + id }) +} + +// 新增 +export const createClassType = async (data) => { + return await request.post({ + url: '/admin-api/crm/sch-class-type/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updateClassType = async (params) => { + return await request.put({ url: '/admin-api/crm/sch-class-type/update', data: params }) +} + +// 删除 +export const deleteClassType = async (id) => { + return await request.delete({ url: '/admin-api/crm/sch-class-type/delete?id=' + id }) +} + +// 修改状态 +export const updateClassTypeStatus = async (data) => { + return request.put({ url: '/admin-api/crm/sch-class-type/status/update', data }) +} + +// 批量修改状态 +export const batchUpdateClassTypeStatus = async (data) => { + return request.put({ url: '/admin-api/crm/sch-class-type/status/batch-update', data }) +} + +// 批量删除 +export const batchDeleteClassType = async (ids) => { + return await request.delete({ url: '/admin-api/crm/sch-class-type/batch-delete?ids=' + ids }) +} + +// 获取提成结算参数 +export const getCommissionParams = async () => { + return await request.get({ url: '/admin-api/crm/sch-class-type/get-percentage-param' }) +} diff --git a/src/api/school/place/index.js b/src/api/school/place/index.js new file mode 100644 index 0000000..b469cdc --- /dev/null +++ b/src/api/school/place/index.js @@ -0,0 +1,43 @@ +import request from '@/config/axios' + +// 查询列表-分页 +export const getPlacePage = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-place/page', params }) +} + +// 查询列表-不分页 +export const getPlaceList = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-place/list', params }) +} + +// 查询详情 +export const getPlace = async (id) => { + return await request.get({ url: '/admin-api/crm/sch-place/get?id=' + id }) +} + +// 新增 +export const createPlace = async (data) => { + return await request.post({ + url: '/admin-api/crm/sch-place/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updatePlace = async (params) => { + return await request.put({ url: '/admin-api/crm/sch-place/update', data: params }) +} + +// 删除 +export const deletePlace = async (id) => { + return await request.delete({ url: '/admin-api/crm/sch-place/delete?id=' + id }) +} + +export const updatePlaceStatus = async (placeId, status) => { + const data = { + placeId, + status + } + return request.put({ url: '/admin-api/crm/sch-place/status/update', data: data }) +} diff --git a/src/api/school/sch/index.js b/src/api/school/sch/index.js new file mode 100644 index 0000000..22f4b5d --- /dev/null +++ b/src/api/school/sch/index.js @@ -0,0 +1,37 @@ +import request from '@/config/axios' +// 查询列表 +export const getSchoolPage = async (params) => { + return await request.get({ url: '/admin-api/crm/sch-school/page', params }) +} + +// 查询详情 +export const getSchool = async (id) => { + return await request.get({ url: '/admin-api/crm/sch-school/get?id=' + id }) +} + +// 新增 +export const createSchool = async (data) => { + return await request.post({ + url: '/admin-api/crm/sch-school/create', + data: data, + isSubmitForm: true + }) +} + +// 修改 +export const updateSchool = async (params) => { + return await request.put({ url: '/admin-api/crm/sch-school/update', data: params }) +} + +// 删除 +export const deleteSchool = async (id) => { + return await request.delete({ url: '/admin-api/crm/sch-school/delete?id=' + id }) +} + +export const updateSchoolStatus = async (schoolId, status) => { + const data = { + schoolId, + status + } + return request.put({ url: '/admin-api/crm/sch-school/status/update', data: data }) +} diff --git a/src/api/system/dept/index.ts b/src/api/system/dept/index.ts new file mode 100644 index 0000000..070d46b --- /dev/null +++ b/src/api/system/dept/index.ts @@ -0,0 +1,43 @@ +import request from '@/config/axios' + +export interface DeptVO { + id?: number + name: string + parentId: number + status: number + sort: number + leaderUserId: number + phone: string + email: string + createTime: Date +} + +// 查询部门(精简)列表 +export const getSimpleDeptList = async (): Promise => { + return await request.get({ url: '/admin-api/system/dept/list-all-simple' }) +} + +// 查询部门列表 +export const getDeptPage = async (params) => { + return await request.get({ url: '/admin-api/system/dept/list', params }) +} + +// 查询部门详情 +export const getDept = async (id: number) => { + return await request.get({ url: '/admin-api/system/dept/get?id=' + id }) +} + +// 新增部门 +export const createDept = async (data: DeptVO) => { + return await request.post({ url: '/admin-api/system/dept/create', data: data }) +} + +// 修改部门 +export const updateDept = async (params: DeptVO) => { + return await request.put({ url: '/admin-api/system/dept/update', data: params }) +} + +// 删除部门 +export const deleteDept = async (id: number) => { + return await request.delete({ url: '/admin-api/system/dept/delete?id=' + id }) +} diff --git a/src/api/system/dict/dict.data.ts b/src/api/system/dict/dict.data.ts new file mode 100644 index 0000000..06d86a7 --- /dev/null +++ b/src/api/system/dict/dict.data.ts @@ -0,0 +1,49 @@ +import request from '@/config/axios' + +export type DictDataVO = { + id: number | undefined + sort: number | undefined + label: string + value: string + dictType: string + status: number + colorType: string + cssClass: string + remark: string + createTime: Date +} + +// 查询字典数据(精简)列表 +export const listSimpleDictData = () => { + return request.get({ url: '/admin-api/oa/dict-data/simple-list' }) +} + +// 查询字典数据列表 +export const getDictDataPage = (params: PageParam) => { + return request.get({ url: '/admin-api/oa/dict-data/page', params }) +} + +// 查询字典数据详情 +export const getDictData = (id: number) => { + return request.get({ url: '/admin-api/oa/dict-data/get?id=' + id }) +} + +// 新增字典数据 +export const createDictData = (data: DictDataVO) => { + return request.post({ url: '/admin-api/oa/dict-data/create', data }) +} + +// 修改字典数据 +export const updateDictData = (data: DictDataVO) => { + return request.put({ url: '/admin-api/oa/dict-data/update', data }) +} + +// 删除字典数据 +export const deleteDictData = (id: number) => { + return request.delete({ url: '/admin-api/oa/dict-data/delete?id=' + id }) +} + +// 导出字典类型数据 +export const exportDictData = (params) => { + return request.get({ url: '/admin-api/oa/dict-data/export', params }) +} diff --git a/src/api/system/dict/dict.type.ts b/src/api/system/dict/dict.type.ts new file mode 100644 index 0000000..835b709 --- /dev/null +++ b/src/api/system/dict/dict.type.ts @@ -0,0 +1,44 @@ +import request from '@/config/axios' + +export type DictTypeVO = { + id: number | undefined + name: string + type: string + status: number + remark: string + createTime: Date +} + +// 查询字典(精简)列表 +export const getSimpleDictTypeList = () => { + return request.get({ url: '/admin-api/oa/dict-type/list-all-simple' }) +} + +// 查询字典列表 +export const getDictTypePage = (params: PageParam) => { + return request.get({ url: '/admin-api/oa/dict-type/page', params }) +} + +// 查询字典详情 +export const getDictType = (id: number) => { + return request.get({ url: '/admin-api/oa/dict-type/get?id=' + id }) +} + +// 新增字典 +export const createDictType = (data: DictTypeVO) => { + return request.post({ url: '/admin-api/oa/dict-type/create', data }) +} + +// 修改字典 +export const updateDictType = (data: DictTypeVO) => { + return request.put({ url: '/admin-api/oa/dict-type/update', data }) +} + +// 删除字典 +export const deleteDictType = (id: number) => { + return request.delete({ url: '/admin-api/oa/dict-type/delete?id=' + id }) +} +// 导出字典类型 +export const exportDictType = (params) => { + return request.get({ url: '/admin-api/oa/dict-type/export', params }) +} diff --git a/src/api/system/library/index.js b/src/api/system/library/index.js new file mode 100644 index 0000000..3dedfcf --- /dev/null +++ b/src/api/system/library/index.js @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// 查询知识库列表 +export const getLibraryPage = (params) => { + return request.get({ url: '/admin-api/crm/knowledge-lib/page', params }) +} + +// 查询知识库详情 +export const getLibrary = (id) => { + return request.get({ url: '/admin-api/crm/knowledge-lib/get?id=' + id }) +} + +// 新增知识库 +export const createLibrary = (data) => { + return request.post({ url: '/admin-api/crm/knowledge-lib/create', data }) +} + +// 修改知识库 +export const updateLibrary = (data) => { + return request.put({ url: '/admin-api/crm/knowledge-lib/update', data }) +} + +// 删除知识库 +export const deleteLibrary = (id) => { + return request.delete({ url: '/admin-api/crm/knowledge-lib/delete?id=' + id }) +} diff --git a/src/api/system/library/resource.js b/src/api/system/library/resource.js new file mode 100644 index 0000000..633244f --- /dev/null +++ b/src/api/system/library/resource.js @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// 查询资源列表 +export const getResourcePage = (params) => { + return request.get({ url: '/admin-api/crm/knowledge-lib-info/page', params }) +} + +// 查询资源详情 +export const getResource = (id) => { + return request.get({ url: '/admin-api/crm/knowledge-lib-info/get?id=' + id }) +} + +// 新增资源 +export const createResource = (data) => { + return request.post({ url: '/admin-api/crm/knowledge-lib-info/create', data }) +} + +// 修改资源 +export const updateResource = (data) => { + return request.put({ url: '/admin-api/crm/knowledge-lib-info/update', data }) +} + +// 删除资源 +export const deleteResource = (id) => { + return request.delete({ url: '/admin-api/crm/knowledge-lib-info/delete?id=' + id }) +} diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts new file mode 100644 index 0000000..39cdb91 --- /dev/null +++ b/src/api/system/menu/index.ts @@ -0,0 +1,54 @@ +import request from '@/config/axios' + +export interface MenuVO { + id: number + name: string + permission: string + type: number + sort: number + parentId: number + path: string + icon: string + component: string + componentName?: string + status: number + visible: boolean + keepAlive: boolean + alwaysShow?: boolean + createTime: Date +} + +// 获取服务列表 +export const getServiceAppList = () => { + return request.get({ url: '/admin-api/system/service/list' }) +} + +// 查询菜单(精简)列表 +export const getSimpleMenusList = () => { + return request.get({ url: '/admin-api/system/menu/list-all-simple' }) +} + +// 查询菜单列表 +export const getMenuList = (params) => { + return request.get({ url: '/admin-api/system/menu/list', params }) +} + +// 获取菜单详情 +export const getMenu = (id: number) => { + return request.get({ url: '/admin-api/system/menu/get?id=' + id }) +} + +// 新增菜单 +export const createMenu = (data: MenuVO) => { + return request.post({ url: '/admin-api/system/menu/create', data }) +} + +// 修改菜单 +export const updateMenu = (data: MenuVO) => { + return request.put({ url: '/admin-api/system/menu/update', data }) +} + +// 删除菜单 +export const deleteMenu = (id: number) => { + return request.delete({ url: '/admin-api/system/menu/delete?id=' + id }) +} diff --git a/src/api/system/notify/message/index.ts b/src/api/system/notify/message/index.ts new file mode 100644 index 0000000..07b6730 --- /dev/null +++ b/src/api/system/notify/message/index.ts @@ -0,0 +1,50 @@ +import request from '@/config/axios' +import qs from 'qs' + +export interface NotifyMessageVO { + id: number + userId: number + userType: number + templateId: number + templateCode: string + templateNickname: string + templateContent: string + templateType: number + templateParams: string + readStatus: boolean + readTime: Date +} + +// 查询站内信消息列表 +export const getNotifyMessagePage = async (params: PageParam) => { + return await request.get({ url: '/admin-api/system/notify-message/page', params }) +} + +// 获得我的站内信分页 +export const getMyNotifyMessagePage = async (params: PageParam) => { + return await request.get({ url: '/admin-api/system/notify-message/my-page', params }) +} + +// 批量标记已读 +export const updateNotifyMessageRead = async (ids) => { + return await request.put({ + url: + '/admin-api/system/notify-message/update-read?' + + qs.stringify({ ids: ids }, { indices: false }) + }) +} + +// 标记所有站内信为已读 +export const updateAllNotifyMessageRead = async () => { + return await request.put({ url: '/admin-api/system/notify-message/update-all-read' }) +} + +// 获取当前用户的最新站内信列表 +export const getUnreadNotifyMessageList = async () => { + return await request.get({ url: '/admin-api/system/notify-message/get-unread-list' }) +} + +// 获得当前用户的未读站内信数量 +export const getUnreadNotifyMessageCount = async () => { + return await request.get({ url: '/admin-api/system/notify-message/get-unread-count' }) +} diff --git a/src/api/system/notify/template/index.ts b/src/api/system/notify/template/index.ts new file mode 100644 index 0000000..cd0e122 --- /dev/null +++ b/src/api/system/notify/template/index.ts @@ -0,0 +1,49 @@ +import request from '@/config/axios' + +export interface NotifyTemplateVO { + id?: number + name: string + nickname: string + code: string + content: string + type: number + params: string + status: number + remark: string +} + +export interface NotifySendReqVO { + userId: number | null + templateCode: string + templateParams: Map +} + +// 查询站内信模板列表 +export const getNotifyTemplatePage = async (params: PageParam) => { + return await request.get({ url: '/system/notify-template/page', params }) +} + +// 查询站内信模板详情 +export const getNotifyTemplate = async (id: number) => { + return await request.get({ url: '/system/notify-template/get?id=' + id }) +} + +// 新增站内信模板 +export const createNotifyTemplate = async (data: NotifyTemplateVO) => { + return await request.post({ url: '/system/notify-template/create', data }) +} + +// 修改站内信模板 +export const updateNotifyTemplate = async (data: NotifyTemplateVO) => { + return await request.put({ url: '/system/notify-template/update', data }) +} + +// 删除站内信模板 +export const deleteNotifyTemplate = async (id: number) => { + return await request.delete({ url: '/system/notify-template/delete?id=' + id }) +} + +// 发送站内信 +export const sendNotify = (data: NotifySendReqVO) => { + return request.post({ url: '/system/notify-template/send-notify', data }) +} diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts new file mode 100644 index 0000000..2a73d55 --- /dev/null +++ b/src/api/system/permission/index.ts @@ -0,0 +1,42 @@ +import request from '@/config/axios' + +export interface PermissionAssignUserRoleReqVO { + userId: number + roleIds: number[] +} + +export interface PermissionAssignRoleMenuReqVO { + roleId: number + menuIds: number[] +} + +export interface PermissionAssignRoleDataScopeReqVO { + roleId: number + dataScope: number + dataScopeDeptIds: number[] +} + +// 查询角色拥有的菜单权限 +export const getRoleMenuList = async (roleId: number) => { + return await request.get({ url: '/admin-api/system/permission/list-role-menus?roleId=' + roleId }) +} + +// 赋予角色菜单权限 +export const assignRoleMenu = async (data: PermissionAssignRoleMenuReqVO) => { + return await request.post({ url: '/admin-api/system/permission/assign-role-menu', data }) +} + +// 赋予角色数据权限 +export const assignRoleDataScope = async (data: PermissionAssignRoleDataScopeReqVO) => { + return await request.post({ url: '/admin-api/system/permission/assign-role-data-scope', data }) +} + +// 查询用户拥有的角色数组 +export const getUserRoleList = async (userId: number) => { + return await request.get({ url: '/admin-api/system/permission/list-user-roles?userId=' + userId }) +} + +// 赋予用户角色 +export const assignUserRole = async (data: PermissionAssignUserRoleReqVO) => { + return await request.post({ url: '/admin-api/system/permission/assign-user-role', data }) +} diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts new file mode 100644 index 0000000..0787876 --- /dev/null +++ b/src/api/system/role/index.ts @@ -0,0 +1,53 @@ +import request from '@/config/axios' + +export interface RoleVO { + id: number + name: string + code: string + sort: number + status: number + type: number + dataScope: number + dataScopeDeptIds: number[] + createTime: Date +} + +export interface UpdateStatusReqVO { + id: number + status: number +} + +// 查询角色列表 +export const getRolePage = async (params: PageParam) => { + return await request.get({ url: '/admin-api/system/role/page', params }) +} + +// 查询角色(精简)列表 +export const getSimpleRoleList = async () => { + return await request.get({ url: '/admin-api/system/role/list-all-simple' }) +} + +// 查询角色详情 +export const getRole = async (id: number) => { + return await request.get({ url: '/admin-api/system/role/get?id=' + id }) +} + +// 新增角色 +export const createRole = async (data: RoleVO) => { + return await request.post({ url: '/admin-api/system/role/create', data }) +} + +// 修改角色 +export const updateRole = async (data: RoleVO) => { + return await request.put({ url: '/admin-api/system/role/update', data }) +} + +// 删除角色 +export const deleteRole = async (id: number) => { + return await request.delete({ url: '/admin-api/system/role/delete?id=' + id }) +} + +// 角色用户 +export const getRoleUsers = async (params) => { + return await request.get({ url: '/admin-api/system/role/getUserByRole', params }) +} diff --git a/src/api/system/set/index.js b/src/api/system/set/index.js new file mode 100644 index 0000000..2f83861 --- /dev/null +++ b/src/api/system/set/index.js @@ -0,0 +1,16 @@ +import request from '@/config/axios' + +// 通过key,查询内容 +export const getConfigByConfigKey = (params) => { + return request.get({ url: '/admin-api/crm/config/getConfigByConfigKey', params }) +} + +// 保存配置项 +export const updateConfig = (data) => { + return request.put({ url: '/admin-api/crm/config/batchUpdateConfigValue', data }) +} + +// 根据模块获取配置列表 +export const getConfigList = (params) => { + return request.get({ url: '/admin-api/crm/config/query', params }) +} diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts new file mode 100644 index 0000000..bbfa0e8 --- /dev/null +++ b/src/api/system/user/index.ts @@ -0,0 +1,76 @@ +import request from '@/config/axios' + +export interface UserVO { + id: number + username: string + nickname: string + deptId: number + postIds: string[] + email: string + mobile: string + sex: number + avatar: string + loginIp: string + status: number + remark: string + loginDate: Date + createTime: Date +} + +// 查询用户管理列表 +export const getUserPage = (params: PageParam) => { + return request.get({ url: '/admin-api/system/user/page', params }) +} + +// 查询用户详情 +export const getUser = (id: number) => { + return request.get({ url: '/admin-api/system/user/get?id=' + id }) +} + +// 新增用户 +export const createUser = (data: UserVO) => { + return request.post({ url: '/admin-api/system/user/create', data }) +} + +// 修改用户 +export const updateUser = (data: UserVO) => { + return request.put({ url: '/admin-api/system/user/update', data }) +} + +// 删除用户 +export const deleteUser = (id: number) => { + return request.delete({ url: '/admin-api/system/user/delete?id=' + id }) +} + +// 导出用户 +export const exportUser = (params) => { + return request.download({ url: '/admin-api/system/user/export', params }) +} + +// 下载用户导入模板 +export const importUserTemplate = () => { + return request.download({ url: '/admin-api/system/user/get-import-template' }) +} + +// 用户密码重置 +export const resetUserPwd = (id: number, password: string) => { + const data = { + id, + password + } + return request.put({ url: '/admin-api/system/user/update-password', data: data }) +} + +// 用户状态修改 +export const updateUserStatus = (id: number, status: number) => { + const data = { + id, + status + } + return request.put({ url: '/admin-api/system/user/update-status', data: data }) +} + +// 获取用户精简信息列表 +export const getSimpleUserList = (): Promise => { + return request.get({ url: '/admin-api/system/user/list-all-simple' }) +} diff --git a/src/api/system/user/profile.ts b/src/api/system/user/profile.ts new file mode 100644 index 0000000..e96d749 --- /dev/null +++ b/src/api/system/user/profile.ts @@ -0,0 +1,77 @@ +import request from '@/config/axios' + +export interface ProfileDept { + id: number + name: string +} +export interface ProfileRole { + id: number + name: string +} +export interface ProfilePost { + id: number + name: string +} +export interface SocialUser { + id: number + type: number + openid: string + token: string + rawTokenInfo: string + nickname: string + avatar: string + rawUserInfo: string + code: string + state: string +} +export interface ProfileVO { + id: number + username: string + nickname: string + dept: ProfileDept + roles: ProfileRole[] + posts: ProfilePost[] + socialUsers: SocialUser[] + email: string + mobile: string + sex: number + avatar: string + status: number + remark: string + loginIp: string + loginDate: Date + createTime: Date +} + +export interface UserProfileUpdateReqVO { + nickname: string + email: string + mobile: string + sex: number +} + +// 查询用户个人信息 +export const getUserProfile = () => { + return request.get({ url: '/admin-api/system/user/profile/get' }) +} + +// 修改用户个人信息 +export const updateUserProfile = (data: UserProfileUpdateReqVO) => { + return request.put({ url: '/admin-api/system/user/profile/update', data }) +} + +// 用户密码重置 +export const updateUserPassword = (oldPassword: string, newPassword: string) => { + return request.put({ + url: '/admin-api/system/user/profile/update-password', + data: { + oldPassword: oldPassword, + newPassword: newPassword + } + }) +} + +// 用户头像上传 +export const uploadAvatar = (data) => { + return request.upload({ url: '/admin-api/system/user/profile/update-avatar', data: data }) +} diff --git a/src/api/system/user/socialUser.ts b/src/api/system/user/socialUser.ts new file mode 100644 index 0000000..e3f24a6 --- /dev/null +++ b/src/api/system/user/socialUser.ts @@ -0,0 +1,31 @@ +import request from '@/config/axios' + +// 社交绑定,使用 code 授权码 +export const socialBind = (type, code, state) => { + return request.post({ + url: '/admin-api/system/social-user/bind', + data: { + type, + code, + state + } + }) +} + +// 取消社交绑定 +export const socialUnbind = (type, openid) => { + return request.delete({ + url: '/admin-api/system/social-user/unbind', + data: { + type, + openid + } + }) +} + +// 社交授权的跳转 +export const socialAuthRedirect = (type, redirectUri) => { + return request.get({ + url: '/admin-api/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri + }) +} diff --git a/src/assets/fonts/DISPLAY FREE TFB.ttf b/src/assets/fonts/DISPLAY FREE TFB.ttf new file mode 100644 index 0000000..d650705 Binary files /dev/null and b/src/assets/fonts/DISPLAY FREE TFB.ttf differ diff --git a/src/assets/fonts/字魂74号-飞墨手书_爱给网_aigei_com.ttf b/src/assets/fonts/字魂74号-飞墨手书_爱给网_aigei_com.ttf new file mode 100644 index 0000000..cbd867e Binary files /dev/null and b/src/assets/fonts/字魂74号-飞墨手书_爱给网_aigei_com.ttf differ diff --git a/src/assets/imgs/avatar.gif b/src/assets/imgs/avatar.gif new file mode 100644 index 0000000..fdbd32c Binary files /dev/null and b/src/assets/imgs/avatar.gif differ diff --git a/src/assets/imgs/avatar.jpg b/src/assets/imgs/avatar.jpg new file mode 100644 index 0000000..d46a70a Binary files /dev/null and b/src/assets/imgs/avatar.jpg differ diff --git a/src/assets/imgs/flag/flag_black.png b/src/assets/imgs/flag/flag_black.png new file mode 100644 index 0000000..0791062 Binary files /dev/null and b/src/assets/imgs/flag/flag_black.png differ diff --git a/src/assets/imgs/flag/flag_blue.png b/src/assets/imgs/flag/flag_blue.png new file mode 100644 index 0000000..78528e7 Binary files /dev/null and b/src/assets/imgs/flag/flag_blue.png differ diff --git a/src/assets/imgs/flag/flag_green.png b/src/assets/imgs/flag/flag_green.png new file mode 100644 index 0000000..e5b98ba Binary files /dev/null and b/src/assets/imgs/flag/flag_green.png differ diff --git a/src/assets/imgs/flag/flag_purple.png b/src/assets/imgs/flag/flag_purple.png new file mode 100644 index 0000000..8b80c04 Binary files /dev/null and b/src/assets/imgs/flag/flag_purple.png differ diff --git a/src/assets/imgs/flag/flag_red.png b/src/assets/imgs/flag/flag_red.png new file mode 100644 index 0000000..e7a1459 Binary files /dev/null and b/src/assets/imgs/flag/flag_red.png differ diff --git a/src/assets/imgs/flag/flag_yellow.png b/src/assets/imgs/flag/flag_yellow.png new file mode 100644 index 0000000..dd2887d Binary files /dev/null and b/src/assets/imgs/flag/flag_yellow.png differ diff --git a/src/assets/imgs/flag/position_black.png b/src/assets/imgs/flag/position_black.png new file mode 100644 index 0000000..8e83901 Binary files /dev/null and b/src/assets/imgs/flag/position_black.png differ diff --git a/src/assets/imgs/flag/position_blue.png b/src/assets/imgs/flag/position_blue.png new file mode 100644 index 0000000..12f5513 Binary files /dev/null and b/src/assets/imgs/flag/position_blue.png differ diff --git a/src/assets/imgs/flag/position_green.png b/src/assets/imgs/flag/position_green.png new file mode 100644 index 0000000..e8f80cf Binary files /dev/null and b/src/assets/imgs/flag/position_green.png differ diff --git a/src/assets/imgs/flag/position_purple.png b/src/assets/imgs/flag/position_purple.png new file mode 100644 index 0000000..ec2e317 Binary files /dev/null and b/src/assets/imgs/flag/position_purple.png differ diff --git a/src/assets/imgs/flag/position_red.png b/src/assets/imgs/flag/position_red.png new file mode 100644 index 0000000..884978c Binary files /dev/null and b/src/assets/imgs/flag/position_red.png differ diff --git a/src/assets/imgs/flag/position_yellow.png b/src/assets/imgs/flag/position_yellow.png new file mode 100644 index 0000000..bbab56f Binary files /dev/null and b/src/assets/imgs/flag/position_yellow.png differ diff --git a/src/assets/imgs/profile.jpg b/src/assets/imgs/profile.jpg new file mode 100644 index 0000000..e4bcf87 Binary files /dev/null and b/src/assets/imgs/profile.jpg differ diff --git a/src/assets/imgs/wechat.png b/src/assets/imgs/wechat.png new file mode 100644 index 0000000..6afc5e4 Binary files /dev/null and b/src/assets/imgs/wechat.png differ diff --git a/src/assets/svgs/403.svg b/src/assets/svgs/403.svg new file mode 100644 index 0000000..4500596 --- /dev/null +++ b/src/assets/svgs/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/404.svg b/src/assets/svgs/404.svg new file mode 100644 index 0000000..5244d8d --- /dev/null +++ b/src/assets/svgs/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/500.svg b/src/assets/svgs/500.svg new file mode 100644 index 0000000..9c02092 --- /dev/null +++ b/src/assets/svgs/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/icon.svg b/src/assets/svgs/icon.svg new file mode 100644 index 0000000..7024bec --- /dev/null +++ b/src/assets/svgs/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/login-bg.svg b/src/assets/svgs/login-bg.svg new file mode 100644 index 0000000..bbe06c1 --- /dev/null +++ b/src/assets/svgs/login-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/login-box-bg.svg b/src/assets/svgs/login-box-bg.svg new file mode 100644 index 0000000..ab10040 --- /dev/null +++ b/src/assets/svgs/login-box-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/message.svg b/src/assets/svgs/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/src/assets/svgs/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/money.svg b/src/assets/svgs/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/src/assets/svgs/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/peoples.svg b/src/assets/svgs/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/src/assets/svgs/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/shopping.svg b/src/assets/svgs/shopping.svg new file mode 100644 index 0000000..f395bc7 --- /dev/null +++ b/src/assets/svgs/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Backtop/index.ts b/src/components/Backtop/index.ts new file mode 100644 index 0000000..96de88d --- /dev/null +++ b/src/components/Backtop/index.ts @@ -0,0 +1,3 @@ +import Backtop from './src/Backtop.vue' + +export { Backtop } diff --git a/src/components/Backtop/src/Backtop.vue b/src/components/Backtop/src/Backtop.vue new file mode 100644 index 0000000..70cf78c --- /dev/null +++ b/src/components/Backtop/src/Backtop.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ConfigGlobal/index.ts b/src/components/ConfigGlobal/index.ts new file mode 100644 index 0000000..dda2462 --- /dev/null +++ b/src/components/ConfigGlobal/index.ts @@ -0,0 +1,3 @@ +import ConfigGlobal from './src/ConfigGlobal.vue' + +export { ConfigGlobal } diff --git a/src/components/ConfigGlobal/src/ConfigGlobal.vue b/src/components/ConfigGlobal/src/ConfigGlobal.vue new file mode 100644 index 0000000..20ff035 --- /dev/null +++ b/src/components/ConfigGlobal/src/ConfigGlobal.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/components/ContentDetailWrap/index.ts b/src/components/ContentDetailWrap/index.ts new file mode 100644 index 0000000..1871cac --- /dev/null +++ b/src/components/ContentDetailWrap/index.ts @@ -0,0 +1,3 @@ +import ContentDetailWrap from './src/ContentDetailWrap.vue' + +export { ContentDetailWrap } diff --git a/src/components/ContentDetailWrap/src/ContentDetailWrap.vue b/src/components/ContentDetailWrap/src/ContentDetailWrap.vue new file mode 100644 index 0000000..9469a55 --- /dev/null +++ b/src/components/ContentDetailWrap/src/ContentDetailWrap.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/components/ContentWrap/index.ts b/src/components/ContentWrap/index.ts new file mode 100644 index 0000000..8c22cc8 --- /dev/null +++ b/src/components/ContentWrap/index.ts @@ -0,0 +1,3 @@ +import ContentWrap from './src/ContentWrap.vue' + +export { ContentWrap } diff --git a/src/components/ContentWrap/src/ContentWrap.vue b/src/components/ContentWrap/src/ContentWrap.vue new file mode 100644 index 0000000..0fcfb16 --- /dev/null +++ b/src/components/ContentWrap/src/ContentWrap.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/CountTo/index.ts b/src/components/CountTo/index.ts new file mode 100644 index 0000000..2119f02 --- /dev/null +++ b/src/components/CountTo/index.ts @@ -0,0 +1,3 @@ +import CountTo from './src/CountTo.vue' + +export { CountTo } diff --git a/src/components/CountTo/src/CountTo.vue b/src/components/CountTo/src/CountTo.vue new file mode 100644 index 0000000..c4fa7b5 --- /dev/null +++ b/src/components/CountTo/src/CountTo.vue @@ -0,0 +1,180 @@ + + + diff --git a/src/components/Crontab/index.ts b/src/components/Crontab/index.ts new file mode 100644 index 0000000..6beeef8 --- /dev/null +++ b/src/components/Crontab/index.ts @@ -0,0 +1,2 @@ +import Crontab from './src/Crontab.vue' +export { Crontab } diff --git a/src/components/Crontab/src/Crontab.vue b/src/components/Crontab/src/Crontab.vue new file mode 100644 index 0000000..13096f7 --- /dev/null +++ b/src/components/Crontab/src/Crontab.vue @@ -0,0 +1,1009 @@ + + + + diff --git a/src/components/Cropper/index.ts b/src/components/Cropper/index.ts new file mode 100644 index 0000000..8fcc618 --- /dev/null +++ b/src/components/Cropper/index.ts @@ -0,0 +1,4 @@ +import CropperImage from './src/Cropper.vue' +import CropperAvatar from './src/CropperAvatar.vue' + +export { CropperImage, CropperAvatar } diff --git a/src/components/Cropper/src/CopperModal.vue b/src/components/Cropper/src/CopperModal.vue new file mode 100644 index 0000000..c303b58 --- /dev/null +++ b/src/components/Cropper/src/CopperModal.vue @@ -0,0 +1,257 @@ + + + diff --git a/src/components/Cropper/src/Cropper.vue b/src/components/Cropper/src/Cropper.vue new file mode 100644 index 0000000..ced43f9 --- /dev/null +++ b/src/components/Cropper/src/Cropper.vue @@ -0,0 +1,181 @@ + + + diff --git a/src/components/Cropper/src/CropperAvatar.vue b/src/components/Cropper/src/CropperAvatar.vue new file mode 100644 index 0000000..179e0ec --- /dev/null +++ b/src/components/Cropper/src/CropperAvatar.vue @@ -0,0 +1,141 @@ + + + diff --git a/src/components/Cropper/src/types.ts b/src/components/Cropper/src/types.ts new file mode 100644 index 0000000..bcad3b4 --- /dev/null +++ b/src/components/Cropper/src/types.ts @@ -0,0 +1,8 @@ +import type Cropper from 'cropperjs' + +export interface CropendResult { + imgBase64: string + imgInfo: Cropper.Data +} + +export type { Cropper } diff --git a/src/components/Descriptions/index.ts b/src/components/Descriptions/index.ts new file mode 100644 index 0000000..91b0eb4 --- /dev/null +++ b/src/components/Descriptions/index.ts @@ -0,0 +1,3 @@ +import Descriptions from './src/Descriptions.vue' + +export { Descriptions } diff --git a/src/components/Descriptions/src/Descriptions.vue b/src/components/Descriptions/src/Descriptions.vue new file mode 100644 index 0000000..d11d4ee --- /dev/null +++ b/src/components/Descriptions/src/Descriptions.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/src/components/Dialog/index.ts b/src/components/Dialog/index.ts new file mode 100644 index 0000000..1655dad --- /dev/null +++ b/src/components/Dialog/index.ts @@ -0,0 +1,3 @@ +import Dialog from './src/Dialog.vue' + +export { Dialog } diff --git a/src/components/Dialog/src/Dialog.vue b/src/components/Dialog/src/Dialog.vue new file mode 100644 index 0000000..34b7a32 --- /dev/null +++ b/src/components/Dialog/src/Dialog.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/components/DictTag/index.ts b/src/components/DictTag/index.ts new file mode 100644 index 0000000..4db2742 --- /dev/null +++ b/src/components/DictTag/index.ts @@ -0,0 +1,3 @@ +import DictTag from './src/DictTag.vue' + +export { DictTag } diff --git a/src/components/DictTag/src/DictTag.vue b/src/components/DictTag/src/DictTag.vue new file mode 100644 index 0000000..db37f71 --- /dev/null +++ b/src/components/DictTag/src/DictTag.vue @@ -0,0 +1,60 @@ + diff --git a/src/components/DocAlert/index.vue b/src/components/DocAlert/index.vue new file mode 100644 index 0000000..7ca29f7 --- /dev/null +++ b/src/components/DocAlert/index.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/Echart/index.ts b/src/components/Echart/index.ts new file mode 100644 index 0000000..4822092 --- /dev/null +++ b/src/components/Echart/index.ts @@ -0,0 +1,3 @@ +import Echart from './src/Echart.vue' + +export { Echart } diff --git a/src/components/Echart/src/Echart.vue b/src/components/Echart/src/Echart.vue new file mode 100644 index 0000000..7604765 --- /dev/null +++ b/src/components/Echart/src/Echart.vue @@ -0,0 +1,114 @@ + + + diff --git a/src/components/Editor/index.ts b/src/components/Editor/index.ts new file mode 100644 index 0000000..3fbf0a9 --- /dev/null +++ b/src/components/Editor/index.ts @@ -0,0 +1,8 @@ +import Editor from './src/Editor.vue' +import { IDomEditor } from '@wangeditor/editor' + +export interface EditorExpose { + getEditorRef: () => Promise +} + +export { Editor } diff --git a/src/components/Editor/src/Editor.vue b/src/components/Editor/src/Editor.vue new file mode 100644 index 0000000..a75ef4a --- /dev/null +++ b/src/components/Editor/src/Editor.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/src/components/Editor/src/README.md b/src/components/Editor/src/README.md new file mode 100644 index 0000000..3fdcc2c --- /dev/null +++ b/src/components/Editor/src/README.md @@ -0,0 +1,68 @@ + +**「工具栏key」** +[ + "headerSelect",// 标题 + "blockquote", // 引用 + "bold", // 加粗 + "underline", // 下划线 + "italic", // 斜体 + // 删除线、清除格式等 + "group-more-style", + { + key: "group-more-style", + title: "更多", + iconSvg: + '', + menuKeys: Array(5) + }, + "color", // 文字颜色 + "bgColor", // 背景色 + "fontSize", // 字号 + "fontFamily", // 字体 + "lineHeight", // 行高 + "bulletedList", // 无序列表 + "numberedList", // 有序列表 + "todo", // 代办 + // 对齐 + "group-justify", + { + key: "group-justify", + title: "对齐", + iconSvg: + '', + menuKeys: Array(4) + }, + // 缩进 + "group-indent", + { + key: "group-indent", + title: "缩进", + iconSvg: + '', + menuKeys: Array(2) + }, + "emotion",// 表情 + "insertLink",// 插入链接 + "group-image",// 上传图片 + { + key: "group-image", + title: "图片", + iconSvg: + '', + menuKeys: Array(2) + }, + "group-video",// 上传视频 + { + key: "group-video", + title: "视频", + iconSvg: + '', + menuKeys: Array(2) + }, + "insertTable",// 插入表格 + "codeBlock", // 代码块 + "divider", // 分割线 + "undo", // 撤销 + "redo", // 重做 + "fullScreen" // 全屏 +] diff --git a/src/components/Error/index.ts b/src/components/Error/index.ts new file mode 100644 index 0000000..a52c6f9 --- /dev/null +++ b/src/components/Error/index.ts @@ -0,0 +1,3 @@ +import Error from './src/Error.vue' + +export { Error } diff --git a/src/components/Error/src/Error.vue b/src/components/Error/src/Error.vue new file mode 100644 index 0000000..f8652a1 --- /dev/null +++ b/src/components/Error/src/Error.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/components/Form/index.ts b/src/components/Form/index.ts new file mode 100644 index 0000000..484c7a2 --- /dev/null +++ b/src/components/Form/index.ts @@ -0,0 +1,15 @@ +import Form from './src/Form.vue' +import { ElForm } from 'element-plus' +import { FormSchema, FormSetPropsType } from '@/types/form' + +export interface FormExpose { + setValues: (data: Recordable) => void + setProps: (props: Recordable) => void + delSchema: (field: string) => void + addSchema: (formSchema: FormSchema, index?: number) => void + setSchema: (schemaProps: FormSetPropsType[]) => void + formModel: Recordable + getElFormRef: () => ComponentRef +} + +export { Form } diff --git a/src/components/Form/src/Form.vue b/src/components/Form/src/Form.vue new file mode 100644 index 0000000..d3a00bf --- /dev/null +++ b/src/components/Form/src/Form.vue @@ -0,0 +1,314 @@ + + + diff --git a/src/components/Form/src/componentMap.ts b/src/components/Form/src/componentMap.ts new file mode 100644 index 0000000..5af9b40 --- /dev/null +++ b/src/components/Form/src/componentMap.ts @@ -0,0 +1,55 @@ +import type { Component } from 'vue' +import { + ElCascader, + ElCheckboxGroup, + ElColorPicker, + ElDatePicker, + ElInput, + ElInputNumber, + ElRadioGroup, + ElRate, + ElSelect, + ElSelectV2, + ElTreeSelect, + ElSlider, + ElSwitch, + ElTimePicker, + ElTimeSelect, + ElTransfer, + ElAutocomplete, + ElDivider +} from 'element-plus' +import { InputPassword } from '@/components/InputPassword' +import { Editor } from '@/components/Editor' +import { UploadImg, UploadImgs, UploadFile } from '@/components/UploadFile' +import { ComponentName } from '@/types/components' + +const componentMap: Recordable = { + Radio: ElRadioGroup, + Checkbox: ElCheckboxGroup, + CheckboxButton: ElCheckboxGroup, + Input: ElInput, + Autocomplete: ElAutocomplete, + InputNumber: ElInputNumber, + Select: ElSelect, + Cascader: ElCascader, + Switch: ElSwitch, + Slider: ElSlider, + TimePicker: ElTimePicker, + DatePicker: ElDatePicker, + Rate: ElRate, + ColorPicker: ElColorPicker, + Transfer: ElTransfer, + Divider: ElDivider, + TimeSelect: ElTimeSelect, + SelectV2: ElSelectV2, + TreeSelect: ElTreeSelect, + RadioButton: ElRadioGroup, + InputPassword: InputPassword, + Editor: Editor, + UploadImg: UploadImg, + UploadImgs: UploadImgs, + UploadFile: UploadFile +} + +export { componentMap } diff --git a/src/components/Form/src/components/useRenderCheckbox.tsx b/src/components/Form/src/components/useRenderCheckbox.tsx new file mode 100644 index 0000000..c3e71fb --- /dev/null +++ b/src/components/Form/src/components/useRenderCheckbox.tsx @@ -0,0 +1,26 @@ +import { FormSchema } from '@/types/form' +import { ElCheckbox, ElCheckboxButton } from 'element-plus' +import { defineComponent } from 'vue' + +export const useRenderCheckbox = () => { + const renderCheckboxOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField || 'id' + const valueAlias = item?.componentProps?.optionsAlias?.valueField || 'name' + const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType< + typeof defineComponent + > + return item?.options?.map((option) => { + const { ...other } = option + return ( + + {option[labelAlias || 'label']} + + ) + }) + } + + return { + renderCheckboxOptions + } +} diff --git a/src/components/Form/src/components/useRenderRadio.tsx b/src/components/Form/src/components/useRenderRadio.tsx new file mode 100644 index 0000000..c3aae80 --- /dev/null +++ b/src/components/Form/src/components/useRenderRadio.tsx @@ -0,0 +1,26 @@ +import { FormSchema } from '@/types/form' +import { ElRadio, ElRadioButton } from 'element-plus' +import { defineComponent } from 'vue' + +export const useRenderRadio = () => { + const renderRadioOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField || 'id' + const valueAlias = item?.componentProps?.optionsAlias?.valueField || 'name' + const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType< + typeof defineComponent + > + return item?.options?.map((option) => { + const { ...other } = option + return ( + + {option[labelAlias || 'label']} + + ) + }) + } + + return { + renderRadioOptions + } +} diff --git a/src/components/Form/src/components/useRenderSelect.tsx b/src/components/Form/src/components/useRenderSelect.tsx new file mode 100644 index 0000000..f222785 --- /dev/null +++ b/src/components/Form/src/components/useRenderSelect.tsx @@ -0,0 +1,57 @@ +import { FormSchema } from '@/types/form' +import { ComponentOptions } from '@/types/components' +import { ElOption, ElOptionGroup } from 'element-plus' +import { getSlot } from '@/utils/tsxHelper' +import { Slots } from 'vue' + +export const useRenderSelect = (slots: Slots) => { + // 渲染 select options + const renderSelectOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + return item?.options?.map((option) => { + if (option?.length) { + return ( + + {() => { + return option?.map((v) => { + return renderSelectOptionItem(item, v) + }) + }} + + ) + } else { + return renderSelectOptionItem(item, option) + } + }) + } + + // 渲染 select option item + const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField || 'id' + const valueAlias = item?.componentProps?.optionsAlias?.valueField || 'name' + + const { label, value, ...other } = option + + return ( + + {{ + default: () => + // option 插槽名规则,{field}-option + item?.componentProps?.optionsSlot + ? getSlot(slots, `${item.field}-option`, { item: option }) + : undefined + }} + + ) + } + + return { + renderSelectOptions + } +} diff --git a/src/components/Form/src/helper.ts b/src/components/Form/src/helper.ts new file mode 100644 index 0000000..cdfc8ca --- /dev/null +++ b/src/components/Form/src/helper.ts @@ -0,0 +1,148 @@ +import type { Slots } from 'vue' +import { getSlot } from '@/utils/tsxHelper' +import { PlaceholderModel } from './types' +import { FormSchema } from '@/types/form' +import { ColProps } from '@/types/components' + +/** + * + * @param schema 对应组件数据 + * @returns 返回提示信息对象 + * @description 用于自动设置placeholder + */ +export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => { + const { t } = useI18n() + const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword'] + const selectMap = ['Select', 'SelectV2', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect'] + if (textMap.includes(schema?.component as string)) { + return { + placeholder: t('common.inputText') + schema.label + } + } + if (selectMap.includes(schema?.component as string)) { + // 一些范围选择器 + const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange'] + if ( + twoTextMap.includes( + (schema?.componentProps?.type || schema?.componentProps?.isRange) as string + ) + ) { + return { + startPlaceholder: t('common.startTimeText'), + endPlaceholder: t('common.endTimeText'), + rangeSeparator: '-' + } + } else { + return { + placeholder: t('common.selectText') + schema.label + } + } + } + return {} +} + +/** + * + * @param col 内置栅格 + * @returns 返回栅格属性 + * @description 合并传入进来的栅格属性 + */ +export const setGridProp = (col: ColProps = {}): ColProps => { + const colProps: ColProps = { + // 如果有span,代表用户优先级更高,所以不需要默认栅格 + ...(col.span + ? {} + : { + xs: 24, + sm: 12, + md: 12, + lg: 12, + xl: 12 + }), + ...col + } + return colProps +} + +/** + * + * @param item 传入的组件属性 + * @returns 默认添加 clearable 属性 + */ +export const setComponentProps = (item: FormSchema): Recordable => { + const notNeedClearable = ['ColorPicker'] + const componentProps: Recordable = notNeedClearable.includes(item.component as string) + ? { ...item.componentProps } + : { + clearable: true, + ...item.componentProps + } + // 需要删除额外的属性 + delete componentProps?.slots + return componentProps +} + +/** + * + * @param slots 插槽 + * @param slotsProps 插槽属性 + * @param field 字段名 + */ +export const setItemComponentSlots = ( + slots: Slots, + slotsProps: Recordable = {}, + field: string +): Recordable => { + const slotObj: Recordable = {} + for (const key in slotsProps) { + if (slotsProps[key]) { + // 由于组件有可能重复,需要有一个唯一的前缀 + slotObj[key] = (data: Recordable) => { + return getSlot(slots, `${field}-${key}`, data) + } + } + } + return slotObj +} + +/** + * + * @param schema Form表单结构化数组 + * @param formModel FormModel + * @returns FormModel + * @description 生成对应的formModel + */ +export const initModel = (schema: FormSchema[], formModel: Recordable) => { + const model: Recordable = { ...formModel } + schema.map((v) => { + // 如果是hidden,就删除对应的值 + if (v.hidden) { + delete model[v.field] + } else if (v.component && v.component !== 'Divider') { + const hasField = Reflect.has(model, v.field) + // 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值 + model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : '' + } + }) + return model +} + +/** + * @param slots 插槽 + * @param field 字段名 + * @returns 返回FormIiem插槽 + */ +export const setFormItemSlots = (slots: Slots, field: string): Recordable => { + const slotObj: Recordable = {} + if (slots[`${field}-error`]) { + slotObj['error'] = (data: Recordable) => { + return getSlot(slots, `${field}-error`, data) + } + } + if (slots[`${field}-label`]) { + slotObj['label'] = (data: Recordable) => { + return getSlot(slots, `${field}-label`, data) + } + } + return slotObj +} diff --git a/src/components/Form/src/types.ts b/src/components/Form/src/types.ts new file mode 100644 index 0000000..dcd01e7 --- /dev/null +++ b/src/components/Form/src/types.ts @@ -0,0 +1,17 @@ +import { FormSchema } from '@/types/form' + +export interface PlaceholderModel { + placeholder?: string + startPlaceholder?: string + endPlaceholder?: string + rangeSeparator?: string +} + +export type FormProps = { + schema?: FormSchema[] + isCol?: boolean + model?: Recordable + autoSetPlaceholder?: boolean + isCustom?: boolean + labelWidth?: string | number +} & Recordable diff --git a/src/components/Highlight/index.ts b/src/components/Highlight/index.ts new file mode 100644 index 0000000..3e2d9ed --- /dev/null +++ b/src/components/Highlight/index.ts @@ -0,0 +1,3 @@ +import Highlight from './src/Highlight.vue' + +export { Highlight } diff --git a/src/components/Highlight/src/Highlight.vue b/src/components/Highlight/src/Highlight.vue new file mode 100644 index 0000000..ef923a9 --- /dev/null +++ b/src/components/Highlight/src/Highlight.vue @@ -0,0 +1,65 @@ + diff --git a/src/components/IFrame/index.ts b/src/components/IFrame/index.ts new file mode 100644 index 0000000..9f8cf24 --- /dev/null +++ b/src/components/IFrame/index.ts @@ -0,0 +1,3 @@ +import IFrame from './src/IFrame.vue' + +export { IFrame } diff --git a/src/components/IFrame/src/IFrame.vue b/src/components/IFrame/src/IFrame.vue new file mode 100644 index 0000000..d578e35 --- /dev/null +++ b/src/components/IFrame/src/IFrame.vue @@ -0,0 +1,30 @@ + + diff --git a/src/components/Icon/index.ts b/src/components/Icon/index.ts new file mode 100644 index 0000000..33d1de3 --- /dev/null +++ b/src/components/Icon/index.ts @@ -0,0 +1,4 @@ +import Icon from './src/Icon.vue' +import IconSelect from './src/IconSelect.vue' + +export { Icon, IconSelect } diff --git a/src/components/Icon/src/Icon.vue b/src/components/Icon/src/Icon.vue new file mode 100644 index 0000000..92d791c --- /dev/null +++ b/src/components/Icon/src/Icon.vue @@ -0,0 +1,83 @@ + + + diff --git a/src/components/Icon/src/IconSelect.vue b/src/components/Icon/src/IconSelect.vue new file mode 100644 index 0000000..04f4a56 --- /dev/null +++ b/src/components/Icon/src/IconSelect.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/src/components/Icon/src/data.ts b/src/components/Icon/src/data.ts new file mode 100644 index 0000000..2a4ed5a --- /dev/null +++ b/src/components/Icon/src/data.ts @@ -0,0 +1,1961 @@ +export const IconJson = { + 'ep:': [ + 'add-location', + 'aim', + 'alarm-clock', + 'apple', + 'arrow-down', + 'arrow-down-bold', + 'arrow-left', + 'arrow-left-bold', + 'arrow-right', + 'arrow-right-bold', + 'arrow-up', + 'arrow-up-bold', + 'avatar', + 'back', + 'baseball', + 'basketball', + 'bell', + 'bell-filled', + 'bicycle', + 'bottom', + 'bottom-left', + 'bottom-right', + 'bowl', + 'box', + 'briefcase', + 'brush', + 'brush-filled', + 'burger', + 'calendar', + 'camera', + 'camera-filled', + 'caret-bottom', + 'caret-left', + 'caret-right', + 'caret-top', + 'cellphone', + 'chat-dot-round', + 'chat-dot-square', + 'chat-line-round', + 'chat-line-square', + 'chat-round', + 'chat-square', + 'check', + 'checked', + 'cherry', + 'chicken', + 'circle-check', + 'circle-check-filled', + 'circle-close', + 'circle-close-filled', + 'circle-plus', + 'circle-plus-filled', + 'clock', + 'close', + 'close-bold', + 'cloudy', + 'coffee', + 'coffee-cup', + 'coin', + 'cold-drink', + 'collection', + 'collection-tag', + 'comment', + 'compass', + 'connection', + 'coordinate', + 'copy-document', + 'cpu', + 'credit-card', + 'crop', + 'd-arrow-left', + 'd-arrow-right', + 'd-caret', + 'data-analysis', + 'data-board', + 'data-line', + 'delete', + 'delete-filled', + 'delete-location', + 'dessert', + 'discount', + 'dish', + 'dish-dot', + 'document', + 'document-add', + 'document-checked', + 'document-copy', + 'document-delete', + 'document-remove', + 'download', + 'drizzling', + 'edit', + 'edit-pen', + 'eleme', + 'eleme-filled', + 'expand', + 'failed', + 'female', + 'files', + 'film', + 'filter', + 'finished', + 'first-aid-kit', + 'flag', + 'fold', + 'folder', + 'folder-add', + 'folder-checked', + 'folder-delete', + 'folder-opened', + 'folder-remove', + 'food', + 'football', + 'fork-spoon', + 'fries', + 'full-screen', + 'goblet', + 'goblet-full', + 'goblet-square', + 'goblet-square-full', + 'goods', + 'goods-filled', + 'grape', + 'grid', + 'guide', + 'headset', + 'help', + 'help-filled', + 'histogram', + 'home-filled', + 'hot-water', + 'house', + 'ice-cream', + 'ice-cream-round', + 'ice-cream-square', + 'ice-drink', + 'ice-tea', + 'info-filled', + 'iphone', + 'key', + 'knife-fork', + 'lightning', + 'link', + 'list', + 'loading', + 'location', + 'location-filled', + 'location-information', + 'lock', + 'lollipop', + 'magic-stick', + 'magnet', + 'male', + 'management', + 'map-location', + 'medal', + 'menu', + 'message', + 'message-box', + 'mic', + 'microphone', + 'milk-tea', + 'minus', + 'money', + 'monitor', + 'moon', + 'moon-night', + 'more', + 'more-filled', + 'mostly-cloudy', + 'mouse', + 'mug', + 'mute', + 'mute-notification', + 'no-smoking', + 'notebook', + 'notification', + 'odometer', + 'office-building', + 'open', + 'operation', + 'opportunity', + 'orange', + 'paperclip', + 'partly-cloudy', + 'pear', + 'phone', + 'phone-filled', + 'picture', + 'picture-filled', + 'picture-rounded', + 'pie-chart', + 'place', + 'platform', + 'plus', + 'pointer', + 'position', + 'postcard', + 'pouring', + 'present', + 'price-tag', + 'printer', + 'promotion', + 'question-filled', + 'rank', + 'reading', + 'reading-lamp', + 'refresh', + 'refresh-left', + 'refresh-right', + 'refrigerator', + 'remove', + 'remove-filled', + 'right', + 'scale-to-original', + 'school', + 'scissor', + 'search', + 'select', + 'sell', + 'semi-select', + 'service', + 'set-up', + 'setting', + 'share', + 'ship', + 'shop', + 'shopping-bag', + 'shopping-cart', + 'shopping-cart-full', + 'smoking', + 'soccer', + 'sold-out', + 'sort', + 'sort-down', + 'sort-up', + 'stamp', + 'star', + 'star-filled', + 'stopwatch', + 'success-filled', + 'sugar', + 'suitcase', + 'sunny', + 'sunrise', + 'sunset', + 'switch', + 'switch-button', + 'takeaway-box', + 'ticket', + 'tickets', + 'timer', + 'toilet-paper', + 'tools', + 'top', + 'top-left', + 'top-right', + 'trend-charts', + 'trophy', + 'turn-off', + 'umbrella', + 'unlock', + 'upload', + 'upload-filled', + 'user', + 'user-filled', + 'van', + 'video-camera', + 'video-camera-filled', + 'video-pause', + 'video-play', + 'view', + 'wallet', + 'wallet-filled', + 'warning', + 'warning-filled', + 'watch', + 'watermelon', + 'wind-power', + 'zoom-in', + 'zoom-out' + ], + 'fa:': [ + '500px', + 'address-book', + 'address-book-o', + 'address-card', + 'address-card-o', + 'adjust', + 'adn', + 'align-center', + 'align-justify', + 'align-left', + 'amazon', + 'ambulance', + 'american-sign-language-interpreting', + 'anchor', + 'android', + 'angellist', + 'angle-double-left', + 'angle-double-up', + 'angle-down', + 'angle-left', + 'angle-up', + 'apple', + 'archive', + 'area-chart', + 'arrow-circle-left', + 'arrow-circle-o-left', + 'arrow-circle-o-up', + 'arrow-circle-up', + 'arrow-left', + 'arrow-up', + 'arrows', + 'arrows-alt', + 'arrows-h', + 'arrows-v', + 'assistive-listening-systems', + 'asterisk', + 'at', + 'audio-description', + 'automobile', + 'backward', + 'balance-scale', + 'ban', + 'bandcamp', + 'bank', + 'bar-chart', + 'barcode', + 'bars', + 'bath', + 'battery', + 'battery-0', + 'battery-1', + 'battery-2', + 'battery-3', + 'bed', + 'beer', + 'behance', + 'behance-square', + 'bell', + 'bell-o', + 'bell-slash', + 'bell-slash-o', + 'bicycle', + 'binoculars', + 'birthday-cake', + 'bitbucket', + 'bitbucket-square', + 'bitcoin', + 'black-tie', + 'blind', + 'bluetooth', + 'bluetooth-b', + 'bold', + 'bolt', + 'bomb', + 'book', + 'bookmark', + 'bookmark-o', + 'braille', + 'briefcase', + 'bug', + 'building', + 'building-o', + 'bullhorn', + 'bullseye', + 'bus', + 'buysellads', + 'cab', + 'calculator', + 'calendar', + 'calendar-check-o', + 'calendar-minus-o', + 'calendar-o', + 'calendar-plus-o', + 'calendar-times-o', + 'camera', + 'camera-retro', + 'caret-down', + 'caret-left', + 'caret-square-o-left', + 'caret-square-o-up', + 'caret-up', + 'cart-arrow-down', + 'cart-plus', + 'cc', + 'cc-amex', + 'cc-diners-club', + 'cc-discover', + 'cc-jcb', + 'cc-mastercard', + 'cc-paypal', + 'cc-stripe', + 'cc-visa', + 'certificate', + 'chain', + 'chain-broken', + 'check', + 'check-circle', + 'check-circle-o', + 'check-square', + 'check-square-o', + 'chevron-circle-left', + 'chevron-circle-up', + 'chevron-down', + 'chevron-left', + 'chevron-up', + 'child', + 'chrome', + 'circle', + 'circle-o', + 'circle-o-notch', + 'circle-thin', + 'clipboard', + 'clock-o', + 'clone', + 'close', + 'cloud', + 'cloud-download', + 'cloud-upload', + 'cny', + 'code', + 'code-fork', + 'codepen', + 'codiepie', + 'coffee', + 'cog', + 'cogs', + 'columns', + 'comment', + 'comment-o', + 'commenting', + 'commenting-o', + 'comments', + 'comments-o', + 'compass', + 'compress', + 'connectdevelop', + 'contao', + 'copy', + 'copyright', + 'creative-commons', + 'credit-card', + 'credit-card-alt', + 'crop', + 'crosshairs', + 'css3', + 'cube', + 'cubes', + 'cut', + 'cutlery', + 'dashboard', + 'dashcube', + 'database', + 'deaf', + 'dedent', + 'delicious', + 'desktop', + 'deviantart', + 'diamond', + 'digg', + 'dollar', + 'dot-circle-o', + 'download', + 'dribbble', + 'drivers-license', + 'drivers-license-o', + 'dropbox', + 'drupal', + 'edge', + 'edit', + 'eercast', + 'eject', + 'ellipsis-h', + 'ellipsis-v', + 'empire', + 'envelope', + 'envelope-o', + 'envelope-open', + 'envelope-open-o', + 'envelope-square', + 'envira', + 'eraser', + 'etsy', + 'eur', + 'exchange', + 'exclamation', + 'exclamation-circle', + 'exclamation-triangle', + 'expand', + 'expeditedssl', + 'external-link', + 'external-link-square', + 'eye', + 'eye-slash', + 'eyedropper', + 'fa', + 'facebook', + 'facebook-official', + 'facebook-square', + 'fast-backward', + 'fax', + 'feed', + 'female', + 'fighter-jet', + 'file', + 'file-archive-o', + 'file-audio-o', + 'file-code-o', + 'file-excel-o', + 'file-image-o', + 'file-movie-o', + 'file-o', + 'file-pdf-o', + 'file-powerpoint-o', + 'file-text', + 'file-text-o', + 'file-word-o', + 'film', + 'filter', + 'fire', + 'fire-extinguisher', + 'firefox', + 'first-order', + 'flag', + 'flag-checkered', + 'flag-o', + 'flask', + 'flickr', + 'floppy-o', + 'folder', + 'folder-o', + 'folder-open', + 'folder-open-o', + 'font', + 'fonticons', + 'fort-awesome', + 'forumbee', + 'foursquare', + 'free-code-camp', + 'frown-o', + 'futbol-o', + 'gamepad', + 'gavel', + 'gbp', + 'genderless', + 'get-pocket', + 'gg', + 'gg-circle', + 'gift', + 'git', + 'git-square', + 'github', + 'github-alt', + 'github-square', + 'gitlab', + 'gittip', + 'glass', + 'glide', + 'glide-g', + 'globe', + 'google', + 'google-plus', + 'google-plus-circle', + 'google-plus-square', + 'google-wallet', + 'graduation-cap', + 'grav', + 'group', + 'h-square', + 'hacker-news', + 'hand-grab-o', + 'hand-lizard-o', + 'hand-o-left', + 'hand-o-up', + 'hand-paper-o', + 'hand-peace-o', + 'hand-pointer-o', + 'hand-scissors-o', + 'hand-spock-o', + 'handshake-o', + 'hashtag', + 'hdd-o', + 'header', + 'headphones', + 'heart', + 'heart-o', + 'heartbeat', + 'history', + 'home', + 'hospital-o', + 'hourglass', + 'hourglass-1', + 'hourglass-2', + 'hourglass-3', + 'hourglass-o', + 'houzz', + 'html5', + 'i-cursor', + 'id-badge', + 'ils', + 'image', + 'imdb', + 'inbox', + 'indent', + 'industry', + 'info', + 'info-circle', + 'inr', + 'instagram', + 'internet-explorer', + 'intersex', + 'ioxhost', + 'italic', + 'joomla', + 'jsfiddle', + 'key', + 'keyboard-o', + 'krw', + 'language', + 'laptop', + 'lastfm', + 'lastfm-square', + 'leaf', + 'leanpub', + 'lemon-o', + 'level-up', + 'life-bouy', + 'lightbulb-o', + 'line-chart', + 'linkedin', + 'linkedin-square', + 'linode', + 'linux', + 'list', + 'list-alt', + 'list-ol', + 'list-ul', + 'location-arrow', + 'lock', + 'long-arrow-left', + 'long-arrow-up', + 'low-vision', + 'magic', + 'magnet', + 'mail-forward', + 'mail-reply', + 'mail-reply-all', + 'male', + 'map', + 'map-marker', + 'map-o', + 'map-pin', + 'map-signs', + 'mars', + 'mars-double', + 'mars-stroke', + 'mars-stroke-h', + 'mars-stroke-v', + 'maxcdn', + 'meanpath', + 'medium', + 'medkit', + 'meetup', + 'meh-o', + 'mercury', + 'microchip', + 'microphone', + 'microphone-slash', + 'minus', + 'minus-circle', + 'minus-square', + 'minus-square-o', + 'mixcloud', + 'mobile', + 'modx', + 'money', + 'moon-o', + 'motorcycle', + 'mouse-pointer', + 'music', + 'neuter', + 'newspaper-o', + 'object-group', + 'object-ungroup', + 'odnoklassniki', + 'odnoklassniki-square', + 'opencart', + 'openid', + 'opera', + 'optin-monster', + 'pagelines', + 'paint-brush', + 'paper-plane', + 'paper-plane-o', + 'paperclip', + 'paragraph', + 'pause', + 'pause-circle', + 'pause-circle-o', + 'paw', + 'paypal', + 'pencil', + 'pencil-square', + 'percent', + 'phone', + 'phone-square', + 'pie-chart', + 'pied-piper', + 'pied-piper-alt', + 'pied-piper-pp', + 'pinterest', + 'pinterest-p', + 'pinterest-square', + 'plane', + 'play', + 'play-circle', + 'play-circle-o', + 'plug', + 'plus', + 'plus-circle', + 'plus-square', + 'plus-square-o', + 'podcast', + 'power-off', + 'print', + 'product-hunt', + 'puzzle-piece', + 'qq', + 'qrcode', + 'question', + 'question-circle', + 'question-circle-o', + 'quora', + 'quote-left', + 'quote-right', + 'ra', + 'random', + 'ravelry', + 'recycle', + 'reddit', + 'reddit-alien', + 'reddit-square', + 'refresh', + 'registered', + 'renren', + 'repeat', + 'retweet', + 'road', + 'rocket', + 'rotate-left', + 'rouble', + 'rss-square', + 'safari', + 'scribd', + 'search', + 'search-minus', + 'search-plus', + 'sellsy', + 'server', + 'share-alt', + 'share-alt-square', + 'share-square', + 'share-square-o', + 'shield', + 'ship', + 'shirtsinbulk', + 'shopping-bag', + 'shopping-basket', + 'shopping-cart', + 'shower', + 'sign-in', + 'sign-language', + 'sign-out', + 'signal', + 'simplybuilt', + 'sitemap', + 'skyatlas', + 'skype', + 'slack', + 'sliders', + 'slideshare', + 'smile-o', + 'snapchat', + 'snapchat-ghost', + 'snapchat-square', + 'snowflake-o', + 'sort', + 'sort-alpha-asc', + 'sort-alpha-desc', + 'sort-amount-asc', + 'sort-amount-desc', + 'sort-asc', + 'sort-numeric-asc', + 'sort-numeric-desc', + 'soundcloud', + 'space-shuttle', + 'spinner', + 'spoon', + 'spotify', + 'square', + 'square-o', + 'stack-exchange', + 'stack-overflow', + 'star', + 'star-half', + 'star-half-empty', + 'star-o', + 'steam', + 'steam-square', + 'step-backward', + 'stethoscope', + 'sticky-note', + 'sticky-note-o', + 'stop', + 'stop-circle', + 'stop-circle-o', + 'street-view', + 'strikethrough', + 'stumbleupon', + 'stumbleupon-circle', + 'subscript', + 'subway', + 'suitcase', + 'sun-o', + 'superpowers', + 'superscript', + 'table', + 'tablet', + 'tag', + 'tags', + 'tasks', + 'telegram', + 'television', + 'tencent-weibo', + 'terminal', + 'text-height', + 'text-width', + 'th', + 'th-large', + 'th-list', + 'themeisle', + 'thermometer', + 'thermometer-0', + 'thermometer-1', + 'thermometer-2', + 'thermometer-3', + 'thumb-tack', + 'thumbs-down', + 'thumbs-o-up', + 'thumbs-up', + 'ticket', + 'times-circle', + 'times-circle-o', + 'times-rectangle', + 'times-rectangle-o', + 'tint', + 'toggle-off', + 'toggle-on', + 'trademark', + 'train', + 'transgender-alt', + 'trash', + 'trash-o', + 'tree', + 'trello', + 'tripadvisor', + 'trophy', + 'truck', + 'try', + 'tty', + 'tumblr', + 'tumblr-square', + 'twitch', + 'twitter', + 'twitter-square', + 'umbrella', + 'underline', + 'universal-access', + 'unlock', + 'unlock-alt', + 'upload', + 'usb', + 'user', + 'user-circle', + 'user-circle-o', + 'user-md', + 'user-o', + 'user-plus', + 'user-secret', + 'user-times', + 'venus', + 'venus-double', + 'venus-mars', + 'viacoin', + 'viadeo', + 'viadeo-square', + 'video-camera', + 'vimeo', + 'vimeo-square', + 'vine', + 'vk', + 'volume-control-phone', + 'volume-down', + 'volume-off', + 'volume-up', + 'wechat', + 'weibo', + 'whatsapp', + 'wheelchair', + 'wheelchair-alt', + 'wifi', + 'wikipedia-w', + 'window-maximize', + 'window-minimize', + 'window-restore', + 'windows', + 'wordpress', + 'wpbeginner', + 'wpexplorer', + 'wpforms', + 'wrench', + 'xing', + 'xing-square', + 'y-combinator', + 'yahoo', + 'yelp', + 'yoast', + 'youtube', + 'youtube-play', + 'youtube-square' + ], + 'fa-solid:': [ + 'abacus', + 'ad', + 'address-book', + 'address-card', + 'adjust', + 'air-freshener', + 'align-center', + 'align-justify', + 'align-left', + 'align-right', + 'allergies', + 'ambulance', + 'american-sign-language-interpreting', + 'anchor', + 'angle-double-down', + 'angle-double-left', + 'angle-double-right', + 'angle-double-up', + 'angle-down', + 'angle-left', + 'angle-right', + 'angle-up', + 'angry', + 'ankh', + 'apple-alt', + 'archive', + 'archway', + 'arrow-alt-circle-down', + 'arrow-alt-circle-left', + 'arrow-alt-circle-right', + 'arrow-alt-circle-up', + 'arrow-circle-down', + 'arrow-circle-left', + 'arrow-circle-right', + 'arrow-circle-up', + 'arrow-down', + 'arrow-left', + 'arrow-right', + 'arrow-up', + 'arrows-alt', + 'arrows-alt-h', + 'arrows-alt-v', + 'assistive-listening-systems', + 'asterisk', + 'at', + 'atlas', + 'atom', + 'audio-description', + 'award', + 'baby', + 'baby-carriage', + 'backspace', + 'backward', + 'bacon', + 'bacteria', + 'bacterium', + 'bahai', + 'balance-scale', + 'balance-scale-left', + 'balance-scale-right', + 'ban', + 'band-aid', + 'barcode', + 'bars', + 'baseball-ball', + 'basketball-ball', + 'bath', + 'battery-empty', + 'battery-full', + 'battery-half', + 'battery-quarter', + 'battery-three-quarters', + 'bed', + 'beer', + 'bell', + 'bell-slash', + 'bezier-curve', + 'bible', + 'bicycle', + 'biking', + 'binoculars', + 'biohazard', + 'birthday-cake', + 'blender', + 'blender-phone', + 'blind', + 'blog', + 'bold', + 'bolt', + 'bomb', + 'bone', + 'bong', + 'book', + 'book-dead', + 'book-medical', + 'book-open', + 'book-reader', + 'bookmark', + 'border-all', + 'border-none', + 'border-style', + 'bowling-ball', + 'box', + 'box-open', + 'box-tissue', + 'boxes', + 'braille', + 'brain', + 'bread-slice', + 'briefcase', + 'briefcase-medical', + 'broadcast-tower', + 'broom', + 'brush', + 'bug', + 'building', + 'bullhorn', + 'bullseye', + 'burn', + 'bus', + 'bus-alt', + 'business-time', + 'calculator', + 'calculator-alt', + 'calendar', + 'calendar-alt', + 'calendar-check', + 'calendar-day', + 'calendar-minus', + 'calendar-plus', + 'calendar-times', + 'calendar-week', + 'camera', + 'camera-retro', + 'campground', + 'candy-cane', + 'cannabis', + 'capsules', + 'car', + 'car-alt', + 'car-battery', + 'car-crash', + 'car-side', + 'caravan', + 'caret-down', + 'caret-left', + 'caret-right', + 'caret-square-down', + 'caret-square-left', + 'caret-square-right', + 'caret-square-up', + 'caret-up', + 'carrot', + 'cart-arrow-down', + 'cart-plus', + 'cash-register', + 'cat', + 'certificate', + 'chair', + 'chalkboard', + 'chalkboard-teacher', + 'charging-station', + 'chart-area', + 'chart-bar', + 'chart-line', + 'chart-pie', + 'check', + 'check-circle', + 'check-double', + 'check-square', + 'cheese', + 'chess', + 'chess-bishop', + 'chess-board', + 'chess-king', + 'chess-knight', + 'chess-pawn', + 'chess-queen', + 'chess-rook', + 'chevron-circle-down', + 'chevron-circle-left', + 'chevron-circle-right', + 'chevron-circle-up', + 'chevron-down', + 'chevron-left', + 'chevron-right', + 'chevron-up', + 'child', + 'church', + 'circle', + 'circle-notch', + 'city', + 'clinic-medical', + 'clipboard', + 'clipboard-check', + 'clipboard-list', + 'clock', + 'clone', + 'closed-captioning', + 'cloud', + 'cloud-download-alt', + 'cloud-meatball', + 'cloud-moon', + 'cloud-moon-rain', + 'cloud-rain', + 'cloud-showers-heavy', + 'cloud-sun', + 'cloud-sun-rain', + 'cloud-upload-alt', + 'cocktail', + 'code', + 'code-branch', + 'coffee', + 'cog', + 'cogs', + 'coins', + 'columns', + 'comment', + 'comment-alt', + 'comment-dollar', + 'comment-dots', + 'comment-medical', + 'comment-slash', + 'comments', + 'comments-dollar', + 'compact-disc', + 'compass', + 'compress', + 'compress-alt', + 'compress-arrows-alt', + 'concierge-bell', + 'cookie', + 'cookie-bite', + 'copy', + 'copyright', + 'couch', + 'credit-card', + 'crop', + 'crop-alt', + 'cross', + 'crosshairs', + 'crow', + 'crown', + 'crutch', + 'cube', + 'cubes', + 'cut', + 'database', + 'deaf', + 'democrat', + 'desktop', + 'dharmachakra', + 'diagnoses', + 'dice', + 'dice-d20', + 'dice-d6', + 'dice-five', + 'dice-four', + 'dice-one', + 'dice-six', + 'dice-three', + 'dice-two', + 'digital-tachograph', + 'directions', + 'disease', + 'divide', + 'dizzy', + 'dna', + 'dog', + 'dollar-sign', + 'dolly', + 'dolly-flatbed', + 'donate', + 'door-closed', + 'door-open', + 'dot-circle', + 'dove', + 'download', + 'drafting-compass', + 'dragon', + 'draw-polygon', + 'drum', + 'drum-steelpan', + 'drumstick-bite', + 'dumbbell', + 'dumpster', + 'dumpster-fire', + 'dungeon', + 'edit', + 'egg', + 'eject', + 'ellipsis-h', + 'ellipsis-v', + 'empty-set', + 'envelope', + 'envelope-open', + 'envelope-open-text', + 'envelope-square', + 'equals', + 'eraser', + 'ethernet', + 'euro-sign', + 'exchange-alt', + 'exclamation', + 'exclamation-circle', + 'exclamation-triangle', + 'expand', + 'expand-alt', + 'expand-arrows-alt', + 'external-link-alt', + 'external-link-square-alt', + 'eye', + 'eye-dropper', + 'eye-slash', + 'fan', + 'fast-backward', + 'fast-forward', + 'faucet', + 'fax', + 'feather', + 'feather-alt', + 'female', + 'fighter-jet', + 'file', + 'file-alt', + 'file-archive', + 'file-audio', + 'file-code', + 'file-contract', + 'file-csv', + 'file-download', + 'file-excel', + 'file-export', + 'file-image', + 'file-import', + 'file-invoice', + 'file-invoice-dollar', + 'file-medical', + 'file-medical-alt', + 'file-pdf', + 'file-powerpoint', + 'file-prescription', + 'file-signature', + 'file-upload', + 'file-video', + 'file-word', + 'fill', + 'fill-drip', + 'film', + 'filter', + 'fingerprint', + 'fire', + 'fire-alt', + 'fire-extinguisher', + 'first-aid', + 'fish', + 'fist-raised', + 'flag', + 'flag-checkered', + 'flag-usa', + 'flask', + 'flushed', + 'folder', + 'folder-minus', + 'folder-open', + 'folder-plus', + 'font', + 'football-ball', + 'forward', + 'frog', + 'frown', + 'frown-open', + 'function', + 'funnel-dollar', + 'futbol', + 'gamepad', + 'gas-pump', + 'gavel', + 'gem', + 'genderless', + 'ghost', + 'gift', + 'gifts', + 'glass-cheers', + 'glass-martini', + 'glass-martini-alt', + 'glass-whiskey', + 'glasses', + 'globe', + 'globe-africa', + 'globe-americas', + 'globe-asia', + 'globe-europe', + 'golf-ball', + 'gopuram', + 'graduation-cap', + 'greater-than', + 'greater-than-equal', + 'grimace', + 'grin', + 'grin-alt', + 'grin-beam', + 'grin-beam-sweat', + 'grin-hearts', + 'grin-squint', + 'grin-squint-tears', + 'grin-stars', + 'grin-tears', + 'grin-tongue', + 'grin-tongue-squint', + 'grin-tongue-wink', + 'grin-wink', + 'grip-horizontal', + 'grip-lines', + 'grip-lines-vertical', + 'grip-vertical', + 'guitar', + 'h-square', + 'hamburger', + 'hammer', + 'hamsa', + 'hand-holding', + 'hand-holding-heart', + 'hand-holding-medical', + 'hand-holding-usd', + 'hand-holding-water', + 'hand-lizard', + 'hand-middle-finger', + 'hand-paper', + 'hand-peace', + 'hand-point-down', + 'hand-point-left', + 'hand-point-right', + 'hand-point-up', + 'hand-pointer', + 'hand-rock', + 'hand-scissors', + 'hand-sparkles', + 'hand-spock', + 'hands', + 'hands-helping', + 'hands-wash', + 'handshake', + 'handshake-alt-slash', + 'handshake-slash', + 'hanukiah', + 'hard-hat', + 'hashtag', + 'hat-cowboy', + 'hat-cowboy-side', + 'hat-wizard', + 'hdd', + 'head-side-cough', + 'head-side-cough-slash', + 'head-side-mask', + 'head-side-virus', + 'heading', + 'headphones', + 'headphones-alt', + 'headset', + 'heart', + 'heart-broken', + 'heartbeat', + 'helicopter', + 'highlighter', + 'hiking', + 'hippo', + 'history', + 'hockey-puck', + 'holly-berry', + 'home', + 'horse', + 'horse-head', + 'hospital', + 'hospital-alt', + 'hospital-symbol', + 'hospital-user', + 'hot-tub', + 'hotdog', + 'hotel', + 'hourglass', + 'hourglass-end', + 'hourglass-half', + 'hourglass-start', + 'house-damage', + 'house-user', + 'hryvnia', + 'i-cursor', + 'ice-cream', + 'icicles', + 'icons', + 'id-badge', + 'id-card', + 'id-card-alt', + 'igloo', + 'image', + 'images', + 'inbox', + 'indent', + 'industry', + 'infinity', + 'info', + 'info-circle', + 'integral', + 'intersection', + 'italic', + 'jedi', + 'joint', + 'journal-whills', + 'kaaba', + 'key', + 'keyboard', + 'khanda', + 'kiss', + 'kiss-beam', + 'kiss-wink-heart', + 'kiwi-bird', + 'lambda', + 'landmark', + 'language', + 'laptop', + 'laptop-code', + 'laptop-house', + 'laptop-medical', + 'laugh', + 'laugh-beam', + 'laugh-squint', + 'laugh-wink', + 'layer-group', + 'leaf', + 'lemon', + 'less-than', + 'less-than-equal', + 'level-down-alt', + 'level-up-alt', + 'life-ring', + 'lightbulb', + 'link', + 'lira-sign', + 'list', + 'list-alt', + 'list-ol', + 'list-ul', + 'location-arrow', + 'lock', + 'lock-open', + 'long-arrow-alt-down', + 'long-arrow-alt-left', + 'long-arrow-alt-right', + 'long-arrow-alt-up', + 'low-vision', + 'luggage-cart', + 'lungs', + 'lungs-virus', + 'magic', + 'magnet', + 'mail-bulk', + 'male', + 'map', + 'map-marked', + 'map-marked-alt', + 'map-marker', + 'map-marker-alt', + 'map-pin', + 'map-signs', + 'marker', + 'mars', + 'mars-double', + 'mars-stroke', + 'mars-stroke-h', + 'mars-stroke-v', + 'mask', + 'medal', + 'medkit', + 'meh', + 'meh-blank', + 'meh-rolling-eyes', + 'memory', + 'menorah', + 'mercury', + 'meteor', + 'microchip', + 'microphone', + 'microphone-alt', + 'microphone-alt-slash', + 'microphone-slash', + 'microscope', + 'minus', + 'minus-circle', + 'minus-square', + 'mitten', + 'mobile', + 'mobile-alt', + 'money-bill', + 'money-bill-alt', + 'money-bill-wave', + 'money-bill-wave-alt', + 'money-check', + 'money-check-alt', + 'monument', + 'moon', + 'mortar-pestle', + 'mosque', + 'motorcycle', + 'mountain', + 'mouse', + 'mouse-pointer', + 'mug-hot', + 'music', + 'network-wired', + 'neuter', + 'newspaper', + 'not-equal', + 'notes-medical', + 'object-group', + 'object-ungroup', + 'oil-can', + 'om', + 'omega', + 'otter', + 'outdent', + 'pager', + 'paint-brush', + 'paint-roller', + 'palette', + 'pallet', + 'paper-plane', + 'paperclip', + 'parachute-box', + 'paragraph', + 'parking', + 'passport', + 'pastafarianism', + 'paste', + 'pause', + 'pause-circle', + 'paw', + 'peace', + 'pen', + 'pen-alt', + 'pen-fancy', + 'pen-nib', + 'pen-square', + 'pencil-alt', + 'pencil-ruler', + 'people-arrows', + 'people-carry', + 'pepper-hot', + 'percent', + 'percentage', + 'person-booth', + 'phone', + 'phone-alt', + 'phone-slash', + 'phone-square', + 'phone-square-alt', + 'phone-volume', + 'photo-video', + 'pi', + 'piggy-bank', + 'pills', + 'pizza-slice', + 'place-of-worship', + 'plane', + 'plane-arrival', + 'plane-departure', + 'plane-slash', + 'play', + 'play-circle', + 'plug', + 'plus', + 'plus-circle', + 'plus-square', + 'podcast', + 'poll', + 'poll-h', + 'poo', + 'poo-storm', + 'poop', + 'portrait', + 'pound-sign', + 'power-off', + 'pray', + 'praying-hands', + 'prescription', + 'prescription-bottle', + 'prescription-bottle-alt', + 'print', + 'procedures', + 'project-diagram', + 'pump-medical', + 'pump-soap', + 'puzzle-piece', + 'qrcode', + 'question', + 'question-circle', + 'quidditch', + 'quote-left', + 'quote-right', + 'quran', + 'radiation', + 'radiation-alt', + 'rainbow', + 'random', + 'receipt', + 'record-vinyl', + 'recycle', + 'redo', + 'redo-alt', + 'registered', + 'remove-format', + 'reply', + 'reply-all', + 'republican', + 'restroom', + 'retweet', + 'ribbon', + 'ring', + 'road', + 'robot', + 'rocket', + 'route', + 'rss', + 'rss-square', + 'ruble-sign', + 'ruler', + 'ruler-combined', + 'ruler-horizontal', + 'ruler-vertical', + 'running', + 'rupee-sign', + 'sad-cry', + 'sad-tear', + 'satellite', + 'satellite-dish', + 'save', + 'school', + 'screwdriver', + 'scroll', + 'sd-card', + 'search', + 'search-dollar', + 'search-location', + 'search-minus', + 'search-plus', + 'seedling', + 'server', + 'shapes', + 'share', + 'share-alt', + 'share-alt-square', + 'share-square', + 'shekel-sign', + 'shield-alt', + 'shield-virus', + 'ship', + 'shipping-fast', + 'shoe-prints', + 'shopping-bag', + 'shopping-basket', + 'shopping-cart', + 'shower', + 'shuttle-van', + 'sigma', + 'sign', + 'sign-in-alt', + 'sign-language', + 'sign-out-alt', + 'signal', + 'signal-alt', + 'signal-alt-slash', + 'signal-slash', + 'signature', + 'sim-card', + 'sink', + 'sitemap', + 'skating', + 'skiing', + 'skiing-nordic', + 'skull', + 'skull-crossbones', + 'slash', + 'sleigh', + 'sliders-h', + 'smile', + 'smile-beam', + 'smile-wink', + 'smog', + 'smoking', + 'smoking-ban', + 'sms', + 'snowboarding', + 'snowflake', + 'snowman', + 'snowplow', + 'soap', + 'socks', + 'solar-panel', + 'sort', + 'sort-alpha-down', + 'sort-alpha-down-alt', + 'sort-alpha-up', + 'sort-alpha-up-alt', + 'sort-amount-down', + 'sort-amount-down-alt', + 'sort-amount-up', + 'sort-amount-up-alt', + 'sort-down', + 'sort-numeric-down', + 'sort-numeric-down-alt', + 'sort-numeric-up', + 'sort-numeric-up-alt', + 'sort-up', + 'spa', + 'space-shuttle', + 'spell-check', + 'spider', + 'spinner', + 'splotch', + 'spray-can', + 'square', + 'square-full', + 'square-root', + 'square-root-alt', + 'stamp', + 'star', + 'star-and-crescent', + 'star-half', + 'star-half-alt', + 'star-of-david', + 'star-of-life', + 'step-backward', + 'step-forward', + 'stethoscope', + 'sticky-note', + 'stop', + 'stop-circle', + 'stopwatch', + 'stopwatch-20', + 'store', + 'store-alt', + 'store-alt-slash', + 'store-slash', + 'stream', + 'street-view', + 'strikethrough', + 'stroopwafel', + 'subscript', + 'subway', + 'suitcase', + 'suitcase-rolling', + 'sun', + 'superscript', + 'surprise', + 'swatchbook', + 'swimmer', + 'swimming-pool', + 'synagogue', + 'sync', + 'sync-alt', + 'syringe', + 'table', + 'table-tennis', + 'tablet', + 'tablet-alt', + 'tablets', + 'tachometer-alt', + 'tag', + 'tags', + 'tally', + 'tape', + 'tasks', + 'taxi', + 'teeth', + 'teeth-open', + 'temperature-high', + 'temperature-low', + 'tenge', + 'terminal', + 'text-height', + 'text-width', + 'th', + 'th-large', + 'th-list', + 'theater-masks', + 'thermometer', + 'thermometer-empty', + 'thermometer-full', + 'thermometer-half', + 'thermometer-quarter', + 'thermometer-three-quarters', + 'theta', + 'thumbs-down', + 'thumbs-up', + 'thumbtack', + 'ticket-alt', + 'tilde', + 'times', + 'times-circle', + 'tint', + 'tint-slash', + 'tired', + 'toggle-off', + 'toggle-on', + 'toilet', + 'toilet-paper', + 'toilet-paper-slash', + 'toolbox', + 'tools', + 'tooth', + 'torah', + 'torii-gate', + 'tractor', + 'trademark', + 'traffic-light', + 'trailer', + 'train', + 'tram', + 'transgender', + 'transgender-alt', + 'trash', + 'trash-alt', + 'trash-restore', + 'trash-restore-alt', + 'tree', + 'trophy', + 'truck', + 'truck-loading', + 'truck-monster', + 'truck-moving', + 'truck-pickup', + 'tshirt', + 'tty', + 'tv', + 'umbrella', + 'umbrella-beach', + 'underline', + 'undo', + 'undo-alt', + 'union', + 'universal-access', + 'university', + 'unlink', + 'unlock', + 'unlock-alt', + 'upload', + 'user', + 'user-alt', + 'user-alt-slash', + 'user-astronaut', + 'user-check', + 'user-circle', + 'user-clock', + 'user-cog', + 'user-edit', + 'user-friends', + 'user-graduate', + 'user-injured', + 'user-lock', + 'user-md', + 'user-minus', + 'user-ninja', + 'user-nurse', + 'user-plus', + 'user-secret', + 'user-shield', + 'user-slash', + 'user-tag', + 'user-tie', + 'user-times', + 'users', + 'users-cog', + 'users-slash', + 'utensil-spoon', + 'utensils', + 'value-absolute', + 'vector-square', + 'venus', + 'venus-double', + 'venus-mars', + 'vest', + 'vest-patches', + 'vial', + 'vials', + 'video', + 'video-slash', + 'vihara', + 'virus', + 'virus-slash', + 'viruses', + 'voicemail', + 'volleyball-ball', + 'volume', + 'volume-down', + 'volume-mute', + 'volume-off', + 'volume-slash', + 'volume-up', + 'vote-yea', + 'vr-cardboard', + 'walking', + 'wallet', + 'warehouse', + 'water', + 'wave-square', + 'weight', + 'weight-hanging', + 'wheelchair', + 'wifi', + 'wifi-slash', + 'wind', + 'window-close', + 'window-maximize', + 'window-minimize', + 'window-restore', + 'wine-bottle', + 'wine-glass', + 'wine-glass-alt', + 'won-sign', + 'wrench', + 'x-ray', + 'yen-sign', + 'yin-yang' + ] +} diff --git a/src/components/ImageViewer/index.ts b/src/components/ImageViewer/index.ts new file mode 100644 index 0000000..3868135 --- /dev/null +++ b/src/components/ImageViewer/index.ts @@ -0,0 +1,33 @@ +import ImageViewer from './src/ImageViewer.vue' +import { isClient } from '@/utils/is' +import { createVNode, render, VNode } from 'vue' +import { ImageViewerProps } from './src/types' + +let instance: Nullable = null + +export function createImageViewer(options: ImageViewerProps) { + if (!isClient) return + const { + urlList, + initialIndex = 0, + infinite = true, + hideOnClickModal = false, + appendToBody = false, + zIndex = 2000, + show = true + } = options + + const propsData: Partial = {} + const container = document.createElement('div') + propsData.urlList = urlList + propsData.initialIndex = initialIndex + propsData.infinite = infinite + propsData.hideOnClickModal = hideOnClickModal + propsData.appendToBody = appendToBody + propsData.zIndex = zIndex + propsData.show = show + + document.body.appendChild(container) + instance = createVNode(ImageViewer, propsData) + render(instance, container) +} diff --git a/src/components/ImageViewer/src/ImageViewer.vue b/src/components/ImageViewer/src/ImageViewer.vue new file mode 100644 index 0000000..c416f56 --- /dev/null +++ b/src/components/ImageViewer/src/ImageViewer.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/ImageViewer/src/types.ts b/src/components/ImageViewer/src/types.ts new file mode 100644 index 0000000..1932d74 --- /dev/null +++ b/src/components/ImageViewer/src/types.ts @@ -0,0 +1,9 @@ +export interface ImageViewerProps { + urlList?: string[] + zIndex?: number + initialIndex?: number + infinite?: boolean + hideOnClickModal?: boolean + appendToBody?: boolean + show?: boolean +} diff --git a/src/components/Infotip/index.ts b/src/components/Infotip/index.ts new file mode 100644 index 0000000..413fa5f --- /dev/null +++ b/src/components/Infotip/index.ts @@ -0,0 +1,3 @@ +import Infotip from './src/Infotip.vue' + +export { Infotip } diff --git a/src/components/Infotip/src/Infotip.vue b/src/components/Infotip/src/Infotip.vue new file mode 100644 index 0000000..dde5f4b --- /dev/null +++ b/src/components/Infotip/src/Infotip.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/components/InputPassword/index.ts b/src/components/InputPassword/index.ts new file mode 100644 index 0000000..1dcc38e --- /dev/null +++ b/src/components/InputPassword/index.ts @@ -0,0 +1,3 @@ +import InputPassword from './src/InputPassword.vue' + +export { InputPassword } diff --git a/src/components/InputPassword/src/InputPassword.vue b/src/components/InputPassword/src/InputPassword.vue new file mode 100644 index 0000000..19ce032 --- /dev/null +++ b/src/components/InputPassword/src/InputPassword.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..cb9cb9e --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/components/Qrcode/index.ts b/src/components/Qrcode/index.ts new file mode 100644 index 0000000..ce46161 --- /dev/null +++ b/src/components/Qrcode/index.ts @@ -0,0 +1,3 @@ +import Qrcode from './src/Qrcode.vue' + +export { Qrcode } diff --git a/src/components/Qrcode/src/Qrcode.vue b/src/components/Qrcode/src/Qrcode.vue new file mode 100644 index 0000000..2b72ef8 --- /dev/null +++ b/src/components/Qrcode/src/Qrcode.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/src/components/RouterSearch/index.vue b/src/components/RouterSearch/index.vue new file mode 100644 index 0000000..d44ce26 --- /dev/null +++ b/src/components/RouterSearch/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/SSTable/index.vue b/src/components/SSTable/index.vue new file mode 100644 index 0000000..5440bf7 --- /dev/null +++ b/src/components/SSTable/index.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/components/Search/index.ts b/src/components/Search/index.ts new file mode 100644 index 0000000..fcc6f16 --- /dev/null +++ b/src/components/Search/index.ts @@ -0,0 +1,3 @@ +import Search from './src/Search.vue' + +export { Search } diff --git a/src/components/Search/src/Search.vue b/src/components/Search/src/Search.vue new file mode 100644 index 0000000..084982e --- /dev/null +++ b/src/components/Search/src/Search.vue @@ -0,0 +1,233 @@ + + + diff --git a/src/components/Sticky/index.ts b/src/components/Sticky/index.ts new file mode 100644 index 0000000..5e1de45 --- /dev/null +++ b/src/components/Sticky/index.ts @@ -0,0 +1,3 @@ +import Sticky from './src/Sticky.vue' + +export { Sticky } diff --git a/src/components/Sticky/src/Sticky.vue b/src/components/Sticky/src/Sticky.vue new file mode 100644 index 0000000..bf9fc56 --- /dev/null +++ b/src/components/Sticky/src/Sticky.vue @@ -0,0 +1,141 @@ + + diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts new file mode 100644 index 0000000..689f64a --- /dev/null +++ b/src/components/Table/index.ts @@ -0,0 +1,12 @@ +import Table from './src/Table.vue' +import { ElTable } from 'element-plus' +import { TableSetPropsType } from '@/types/table' + +export interface TableExpose { + setProps: (props: Recordable) => void + setColumn: (columnProps: TableSetPropsType[]) => void + selections: Recordable[] + elTableRef: ComponentRef +} + +export { Table } diff --git a/src/components/Table/src/Table.vue b/src/components/Table/src/Table.vue new file mode 100644 index 0000000..8ca5968 --- /dev/null +++ b/src/components/Table/src/Table.vue @@ -0,0 +1,309 @@ + + diff --git a/src/components/Table/src/helper.ts b/src/components/Table/src/helper.ts new file mode 100644 index 0000000..d8b34a8 --- /dev/null +++ b/src/components/Table/src/helper.ts @@ -0,0 +1,8 @@ +export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => { + const newIndex = index + 1 + if (reserveIndex) { + return size * (current - 1) + newIndex + } else { + return newIndex + } +} diff --git a/src/components/Table/src/types.ts b/src/components/Table/src/types.ts new file mode 100644 index 0000000..1c7ff76 --- /dev/null +++ b/src/components/Table/src/types.ts @@ -0,0 +1,26 @@ +import { Pagination, TableColumn } from '@/types/table' + +export type TableProps = { + pageSize?: number + currentPage?: number + // 是否多选 + selection?: boolean + // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip, + showOverflowTooltip?: boolean + // 表头 + columns?: TableColumn[] + // 是否展示分页 + pagination?: Pagination | undefined + // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key) + reserveSelection?: boolean + // 加载状态 + loading?: boolean + // 是否叠加索引 + reserveIndex?: boolean + // 对齐方式 + align?: 'left' | 'center' | 'right' + // 表头对齐方式 + headerAlign?: 'left' | 'center' | 'right' + data?: Recordable + expand?: boolean +} & Recordable diff --git a/src/components/Tooltip/index.ts b/src/components/Tooltip/index.ts new file mode 100644 index 0000000..ab66ddf --- /dev/null +++ b/src/components/Tooltip/index.ts @@ -0,0 +1,3 @@ +import Tooltip from './src/Tooltip.vue' + +export { Tooltip } diff --git a/src/components/Tooltip/src/Tooltip.vue b/src/components/Tooltip/src/Tooltip.vue new file mode 100644 index 0000000..eac37ad --- /dev/null +++ b/src/components/Tooltip/src/Tooltip.vue @@ -0,0 +1,15 @@ + + diff --git a/src/components/UploadFile/index.ts b/src/components/UploadFile/index.ts new file mode 100644 index 0000000..97c1d66 --- /dev/null +++ b/src/components/UploadFile/index.ts @@ -0,0 +1,5 @@ +import UploadImg from './src/UploadImg.vue' +import UploadImgs from './src/UploadImgs.vue' +import UploadFile from './src/UploadFile.vue' + +export { UploadImg, UploadImgs, UploadFile } diff --git a/src/components/UploadFile/src/UploadFile.vue b/src/components/UploadFile/src/UploadFile.vue new file mode 100644 index 0000000..a8442d5 --- /dev/null +++ b/src/components/UploadFile/src/UploadFile.vue @@ -0,0 +1,170 @@ + + + diff --git a/src/components/UploadFile/src/UploadImg.vue b/src/components/UploadFile/src/UploadImg.vue new file mode 100644 index 0000000..9765f08 --- /dev/null +++ b/src/components/UploadFile/src/UploadImg.vue @@ -0,0 +1,252 @@ + + + + diff --git a/src/components/UploadFile/src/UploadImgs.vue b/src/components/UploadFile/src/UploadImgs.vue new file mode 100644 index 0000000..a011eeb --- /dev/null +++ b/src/components/UploadFile/src/UploadImgs.vue @@ -0,0 +1,323 @@ + + + + diff --git a/src/components/UploadFile/src/useUpload.ts b/src/components/UploadFile/src/useUpload.ts new file mode 100644 index 0000000..cadad9e --- /dev/null +++ b/src/components/UploadFile/src/useUpload.ts @@ -0,0 +1,93 @@ +import * as FileApi from '@/api/infra/file' +import CryptoJS from 'crypto-js' +import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload' +import axios from 'axios' + +export const useUpload = () => { + // 后端上传地址 + const uploadUrl = import.meta.env.VITE_UPLOAD_URL + // 是否使用前端直连上传 + const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE + // 重写ElUpload上传方法 + const httpRequest = async (options: UploadRequestOptions) => { + // 模式一:前端上传 + if (isClientUpload) { + // 1.1 生成文件名称 + const fileName = await generateFileName(options.file) + // 1.2 获取文件预签名地址 + const presignedInfo = await FileApi.getFilePresignedUrl(fileName) + // 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持) + return axios.put(presignedInfo.uploadUrl, options.file).then(() => { + // 1.4. 记录文件信息到后端(异步) + createFile(presignedInfo, fileName, options.file) + // 通知成功,数据格式保持与后端上传的返回结果一致 + return { data: presignedInfo.url } + }) + } else { + // 模式二:后端上传 + // 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子 + return new Promise((resolve, reject) => { + FileApi.updateFile({ file: options.file }) + .then((res) => { + if (res.code === 0) { + resolve(res) + } else { + reject(res) + } + }) + .catch((res) => { + reject(res) + }) + }) + } + } + + return { + uploadUrl, + httpRequest + } +} + +/** + * 创建文件信息 + * @param vo 文件预签名信息 + * @param name 文件名称 + * @param file 文件 + */ +function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) { + const fileVo = { + configId: vo.configId, + url: vo.url, + path: name, + name: file.name, + type: file.type, + size: file.size + } + FileApi.createFile(fileVo) + return fileVo +} + +/** + * 生成文件名称(使用算法SHA256) + * @param file 要上传的文件 + */ +async function generateFileName(file: UploadRawFile) { + // 读取文件内容 + const data = await file.arrayBuffer() + const wordArray = CryptoJS.lib.WordArray.create(data) + // 计算SHA256 + const sha256 = CryptoJS.SHA256(wordArray).toString() + // 拼接后缀 + const ext = file.name.substring(file.name.lastIndexOf('.')) + return `${sha256}${ext}` +} + +/** + * 上传类型 + */ +enum UPLOAD_TYPE { + // 客户端直接上传(只支持S3服务) + CLIENT = 'client', + // 客户端发送到后端上传 + SERVER = 'server' +} diff --git a/src/components/Verifition/index.ts b/src/components/Verifition/index.ts new file mode 100644 index 0000000..bcfe6d9 --- /dev/null +++ b/src/components/Verifition/index.ts @@ -0,0 +1,3 @@ +import Verify from './src/Verify.vue' + +export { Verify } diff --git a/src/components/Verifition/src/Verify.vue b/src/components/Verifition/src/Verify.vue new file mode 100644 index 0000000..861ed16 --- /dev/null +++ b/src/components/Verifition/src/Verify.vue @@ -0,0 +1,453 @@ + + + diff --git a/src/components/Verifition/src/Verify/VerifyPoints.vue b/src/components/Verifition/src/Verify/VerifyPoints.vue new file mode 100644 index 0000000..5537c6e --- /dev/null +++ b/src/components/Verifition/src/Verify/VerifyPoints.vue @@ -0,0 +1,250 @@ + + diff --git a/src/components/Verifition/src/Verify/VerifySlide.vue b/src/components/Verifition/src/Verify/VerifySlide.vue new file mode 100644 index 0000000..68d8685 --- /dev/null +++ b/src/components/Verifition/src/Verify/VerifySlide.vue @@ -0,0 +1,376 @@ + + diff --git a/src/components/Verifition/src/Verify/index.ts b/src/components/Verifition/src/Verify/index.ts new file mode 100644 index 0000000..0daa63a --- /dev/null +++ b/src/components/Verifition/src/Verify/index.ts @@ -0,0 +1,4 @@ +import VerifySlide from './VerifySlide.vue' +import VerifyPoints from './VerifyPoints.vue' + +export { VerifySlide, VerifyPoints } diff --git a/src/components/Verifition/src/utils/ase.ts b/src/components/Verifition/src/utils/ase.ts new file mode 100644 index 0000000..d2e6b98 --- /dev/null +++ b/src/components/Verifition/src/utils/ase.ts @@ -0,0 +1,14 @@ +import CryptoJS from 'crypto-js' +/** + * @word 要加密的内容 + * @keyWord String 服务器随机返回的关键字 + * */ +export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') { + const key = CryptoJS.enc.Utf8.parse(keyWord) + const srcs = CryptoJS.enc.Utf8.parse(word) + const encrypted = CryptoJS.AES.encrypt(srcs, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }) + return encrypted.toString() +} diff --git a/src/components/Verifition/src/utils/util.ts b/src/components/Verifition/src/utils/util.ts new file mode 100644 index 0000000..15c1627 --- /dev/null +++ b/src/components/Verifition/src/utils/util.ts @@ -0,0 +1,97 @@ +export function resetSize(vm) { + let img_width, img_height, bar_width, bar_height //图片的宽度、高度,移动条的宽度、高度 + const EmployeeWindow = window as any + const parentWidth = vm.$el.parentNode.offsetWidth || EmployeeWindow.offsetWidth + const parentHeight = vm.$el.parentNode.offsetHeight || EmployeeWindow.offsetHeight + if (vm.imgSize.width.indexOf('%') != -1) { + img_width = (parseInt(vm.imgSize.width) / 100) * parentWidth + 'px' + } else { + img_width = vm.imgSize.width + } + + if (vm.imgSize.height.indexOf('%') != -1) { + img_height = (parseInt(vm.imgSize.height) / 100) * parentHeight + 'px' + } else { + img_height = vm.imgSize.height + } + + if (vm.barSize.width.indexOf('%') != -1) { + bar_width = (parseInt(vm.barSize.width) / 100) * parentWidth + 'px' + } else { + bar_width = vm.barSize.width + } + + if (vm.barSize.height.indexOf('%') != -1) { + bar_height = (parseInt(vm.barSize.height) / 100) * parentHeight + 'px' + } else { + bar_height = vm.barSize.height + } + + return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height } +} + +export const _code_chars = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' +] +export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0'] +export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC'] diff --git a/src/components/XButton/index.ts b/src/components/XButton/index.ts new file mode 100644 index 0000000..be0f0d4 --- /dev/null +++ b/src/components/XButton/index.ts @@ -0,0 +1,4 @@ +import XButton from './src/XButton.vue' +import XTextButton from './src/XTextButton.vue' + +export { XButton, XTextButton } diff --git a/src/components/XButton/src/XButton.vue b/src/components/XButton/src/XButton.vue new file mode 100644 index 0000000..e68dff2 --- /dev/null +++ b/src/components/XButton/src/XButton.vue @@ -0,0 +1,48 @@ + + + + diff --git a/src/components/XButton/src/XTextButton.vue b/src/components/XButton/src/XTextButton.vue new file mode 100644 index 0000000..c2ff979 --- /dev/null +++ b/src/components/XButton/src/XTextButton.vue @@ -0,0 +1,47 @@ + + + + diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue new file mode 100644 index 0000000..fb1af30 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue @@ -0,0 +1,701 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue new file mode 100644 index 0000000..a90d468 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue @@ -0,0 +1,551 @@ + + + + + diff --git a/src/components/bpmnProcessDesigner/package/designer/index.ts b/src/components/bpmnProcessDesigner/package/designer/index.ts new file mode 100644 index 0000000..8522846 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/index.ts @@ -0,0 +1,8 @@ +import MyProcessDesigner from './ProcessDesigner.vue' + +MyProcessDesigner.install = function (Vue) { + Vue.component(MyProcessDesigner.name, MyProcessDesigner) +} + +// 流程图的设计器,可编辑 +export default MyProcessDesigner diff --git a/src/components/bpmnProcessDesigner/package/designer/index2.ts b/src/components/bpmnProcessDesigner/package/designer/index2.ts new file mode 100644 index 0000000..ebe8ca7 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/index2.ts @@ -0,0 +1,8 @@ +import MyProcessViewer from './ProcessViewer.vue' + +MyProcessViewer.install = function (Vue) { + Vue.component(MyProcessViewer.name, MyProcessViewer) +} + +// 流程图的查看器,不可编辑 +export default MyProcessViewer diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/content-pad/contentPadProvider.js b/src/components/bpmnProcessDesigner/package/designer/plugins/content-pad/contentPadProvider.js new file mode 100644 index 0000000..8783493 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/content-pad/contentPadProvider.js @@ -0,0 +1,423 @@ +import { assign, forEach, isArray } from 'min-dash' + +import { is } from 'bpmn-js/lib/util/ModelUtil' + +import { isExpanded, isEventSubProcess } from 'bpmn-js/lib/util/DiUtil' + +import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil' + +import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil' + +import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse' + +/** + * A provider for BPMN 2.0 elements context pad + */ +export default function ContextPadProvider( + config, + injector, + eventBus, + contextPad, + modeling, + elementFactory, + connect, + create, + popupMenu, + canvas, + rules, + translate +) { + config = config || {} + + contextPad.registerProvider(this) + + this._contextPad = contextPad + + this._modeling = modeling + + this._elementFactory = elementFactory + this._connect = connect + this._create = create + this._popupMenu = popupMenu + this._canvas = canvas + this._rules = rules + this._translate = translate + + if (config.autoPlace !== false) { + this._autoPlace = injector.get('autoPlace', false) + } + + eventBus.on('create.end', 250, function (event) { + const context = event.context, + shape = context.shape + + if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) { + return + } + + const entries = contextPad.getEntries(shape) + + if (entries.replace) { + entries.replace.action.click(event, shape) + } + }) +} + +ContextPadProvider.$inject = [ + 'config.contextPad', + 'injector', + 'eventBus', + 'contextPad', + 'modeling', + 'elementFactory', + 'connect', + 'create', + 'popupMenu', + 'canvas', + 'rules', + 'translate', + 'elementRegistry' +] + +ContextPadProvider.prototype.getContextPadEntries = function (element) { + const contextPad = this._contextPad, + modeling = this._modeling, + elementFactory = this._elementFactory, + connect = this._connect, + create = this._create, + popupMenu = this._popupMenu, + canvas = this._canvas, + rules = this._rules, + autoPlace = this._autoPlace, + translate = this._translate + + const actions = {} + + if (element.type === 'label') { + return actions + } + + const businessObject = element.businessObject + + function startConnect(event, element) { + connect.start(event, element) + } + + function removeElement() { + modeling.removeElements([element]) + } + + function getReplaceMenuPosition(element) { + const Y_OFFSET = 5 + + const diagramContainer = canvas.getContainer(), + pad = contextPad.getPad(element).html + + const diagramRect = diagramContainer.getBoundingClientRect(), + padRect = pad.getBoundingClientRect() + + const top = padRect.top - diagramRect.top + const left = padRect.left - diagramRect.left + + const pos = { + x: left, + y: top + padRect.height + Y_OFFSET + } + + return pos + } + + /** + * Create an append action + * + * @param {string} type + * @param {string} className + * @param {string} [title] + * @param {Object} [options] + * + * @return {Object} descriptor + */ + function appendAction(type, className, title, options) { + if (typeof title !== 'string') { + options = title + title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') }) + } + + function appendStart(event, element) { + const shape = elementFactory.createShape(assign({ type: type }, options)) + create.start(event, shape, { + source: element + }) + } + + const append = autoPlace + ? function (event, element) { + const shape = elementFactory.createShape(assign({ type: type }, options)) + + autoPlace.append(element, shape) + } + : appendStart + + return { + group: 'model', + className: className, + title: title, + action: { + dragstart: appendStart, + click: append + } + } + } + + function splitLaneHandler(count) { + return function (event, element) { + // actual split + modeling.splitLane(element, count) + + // refresh context pad after split to + // get rid of split icons + contextPad.open(element, true) + } + } + + if (isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) && isExpanded(businessObject)) { + const childLanes = getChildLanes(element) + + assign(actions, { + 'lane-insert-above': { + group: 'lane-insert-above', + className: 'bpmn-icon-lane-insert-above', + title: translate('Add Lane above'), + action: { + click: function (event, element) { + modeling.addLane(element, 'top') + } + } + } + }) + + if (childLanes.length < 2) { + if (element.height >= 120) { + assign(actions, { + 'lane-divide-two': { + group: 'lane-divide', + className: 'bpmn-icon-lane-divide-two', + title: translate('Divide into two Lanes'), + action: { + click: splitLaneHandler(2) + } + } + }) + } + + if (element.height >= 180) { + assign(actions, { + 'lane-divide-three': { + group: 'lane-divide', + className: 'bpmn-icon-lane-divide-three', + title: translate('Divide into three Lanes'), + action: { + click: splitLaneHandler(3) + } + } + }) + } + } + + assign(actions, { + 'lane-insert-below': { + group: 'lane-insert-below', + className: 'bpmn-icon-lane-insert-below', + title: translate('Add Lane below'), + action: { + click: function (event, element) { + modeling.addLane(element, 'bottom') + } + } + } + }) + } + + if (is(businessObject, 'bpmn:FlowNode')) { + if (is(businessObject, 'bpmn:EventBasedGateway')) { + assign(actions, { + 'append.receive-task': appendAction( + 'bpmn:ReceiveTask', + 'bpmn-icon-receive-task', + translate('Append ReceiveTask') + ), + 'append.message-intermediate-event': appendAction( + 'bpmn:IntermediateCatchEvent', + 'bpmn-icon-intermediate-event-catch-message', + translate('Append MessageIntermediateCatchEvent'), + { eventDefinitionType: 'bpmn:MessageEventDefinition' } + ), + 'append.timer-intermediate-event': appendAction( + 'bpmn:IntermediateCatchEvent', + 'bpmn-icon-intermediate-event-catch-timer', + translate('Append TimerIntermediateCatchEvent'), + { eventDefinitionType: 'bpmn:TimerEventDefinition' } + ), + 'append.condition-intermediate-event': appendAction( + 'bpmn:IntermediateCatchEvent', + 'bpmn-icon-intermediate-event-catch-condition', + translate('Append ConditionIntermediateCatchEvent'), + { eventDefinitionType: 'bpmn:ConditionalEventDefinition' } + ), + 'append.signal-intermediate-event': appendAction( + 'bpmn:IntermediateCatchEvent', + 'bpmn-icon-intermediate-event-catch-signal', + translate('Append SignalIntermediateCatchEvent'), + { eventDefinitionType: 'bpmn:SignalEventDefinition' } + ) + }) + } else if ( + isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition') + ) { + assign(actions, { + 'append.compensation-activity': appendAction( + 'bpmn:Task', + 'bpmn-icon-task', + translate('Append compensation activity'), + { + isForCompensation: true + } + ) + }) + } else if ( + !is(businessObject, 'bpmn:EndEvent') && + !businessObject.isForCompensation && + !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') && + !isEventSubProcess(businessObject) + ) { + assign(actions, { + 'append.end-event': appendAction( + 'bpmn:EndEvent', + 'bpmn-icon-end-event-none', + translate('Append EndEvent') + ), + 'append.gateway': appendAction( + 'bpmn:ExclusiveGateway', + 'bpmn-icon-gateway-none', + translate('Append Gateway') + ), + 'append.append-task': appendAction( + 'bpmn:UserTask', + 'bpmn-icon-user-task', + translate('Append Task') + ), + 'append.intermediate-event': appendAction( + 'bpmn:IntermediateThrowEvent', + 'bpmn-icon-intermediate-event-none', + translate('Append Intermediate/Boundary Event') + ) + }) + } + } + + if (!popupMenu.isEmpty(element, 'bpmn-replace')) { + // Replace menu entry + assign(actions, { + replace: { + group: 'edit', + className: 'bpmn-icon-screw-wrench', + title: '修改类型', + action: { + click: function (event, element) { + const position = assign(getReplaceMenuPosition(element), { + cursor: { x: event.x, y: event.y } + }) + + popupMenu.open(element, 'bpmn-replace', position) + } + } + } + }) + } + + if ( + isAny(businessObject, [ + 'bpmn:FlowNode', + 'bpmn:InteractionNode', + 'bpmn:DataObjectReference', + 'bpmn:DataStoreReference' + ]) + ) { + assign(actions, { + 'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation'), + + connect: { + group: 'connect', + className: 'bpmn-icon-connection-multi', + title: translate( + 'Connect using ' + + (businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or ') + + 'Association' + ), + action: { + click: startConnect, + dragstart: startConnect + } + } + }) + } + + if (isAny(businessObject, ['bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) { + assign(actions, { + connect: { + group: 'connect', + className: 'bpmn-icon-connection-multi', + title: translate('Connect using DataInputAssociation'), + action: { + click: startConnect, + dragstart: startConnect + } + } + }) + } + + if (is(businessObject, 'bpmn:Group')) { + assign(actions, { + 'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation') + }) + } + + // delete element entry, only show if allowed by rules + let deleteAllowed = rules.allowed('elements.delete', { elements: [element] }) + + if (isArray(deleteAllowed)) { + // was the element returned as a deletion candidate? + deleteAllowed = deleteAllowed[0] === element + } + + if (deleteAllowed) { + assign(actions, { + delete: { + group: 'edit', + className: 'bpmn-icon-trash', + title: translate('Remove'), + action: { + click: removeElement + } + } + }) + } + + return actions +} + +// helpers ///////// + +function isEventType(eventBo, type, definition) { + const isType = eventBo.$instanceOf(type) + let isDefinition = false + + const definitions = eventBo.eventDefinitions || [] + forEach(definitions, function (def) { + if (def.$type === definition) { + isDefinition = true + } + }) + + return isType && isDefinition +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/content-pad/index.js b/src/components/bpmnProcessDesigner/package/designer/plugins/content-pad/index.js new file mode 100644 index 0000000..80009ef --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/content-pad/index.js @@ -0,0 +1,6 @@ +import CustomContextPadProvider from './contentPadProvider' + +export default { + __init__: ['contextPadProvider'], + contextPadProvider: ['type', CustomContextPadProvider] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/defaultEmpty.js b/src/components/bpmnProcessDesigner/package/designer/plugins/defaultEmpty.js new file mode 100644 index 0000000..f3bc894 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/defaultEmpty.js @@ -0,0 +1,24 @@ +export default (key, name, type) => { + if (!type) type = 'camunda' + const TYPE_TARGET = { + activiti: 'http://activiti.org/bpmn', + camunda: 'http://bpmn.io/schema/bpmn', + flowable: 'http://flowable.org/bpmn' + } + return ` + + + + + + + +` +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/activitiDescriptor.json b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/activitiDescriptor.json new file mode 100644 index 0000000..db5e490 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/activitiDescriptor.json @@ -0,0 +1,994 @@ +{ + "name": "Activiti", + "uri": "http://activiti.org/bpmn", + "prefix": "activiti", + "xml": { + "tagAlias": "lowerCase" + }, + "associations": [], + "types": [ + { + "name": "Definitions", + "isAbstract": true, + "extends": ["bpmn:Definitions"], + "properties": [ + { + "name": "diagramRelationId", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "InOutBinding", + "superClass": ["Element"], + "isAbstract": true, + "properties": [ + { + "name": "source", + "isAttr": true, + "type": "String" + }, + { + "name": "sourceExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "target", + "isAttr": true, + "type": "String" + }, + { + "name": "businessKey", + "isAttr": true, + "type": "String" + }, + { + "name": "local", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "variables", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "In", + "superClass": ["InOutBinding"], + "meta": { + "allowedIn": ["bpmn:CallActivity"] + } + }, + { + "name": "Out", + "superClass": ["InOutBinding"], + "meta": { + "allowedIn": ["bpmn:CallActivity"] + } + }, + { + "name": "AsyncCapable", + "isAbstract": true, + "extends": ["bpmn:Activity", "bpmn:Gateway", "bpmn:Event"], + "properties": [ + { + "name": "async", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "asyncBefore", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "asyncAfter", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "exclusive", + "isAttr": true, + "type": "Boolean", + "default": true + } + ] + }, + { + "name": "JobPriorized", + "isAbstract": true, + "extends": ["bpmn:Process", "activiti:AsyncCapable"], + "properties": [ + { + "name": "jobPriority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "SignalEventDefinition", + "isAbstract": true, + "extends": ["bpmn:SignalEventDefinition"], + "properties": [ + { + "name": "async", + "isAttr": true, + "type": "Boolean", + "default": false + } + ] + }, + { + "name": "ErrorEventDefinition", + "isAbstract": true, + "extends": ["bpmn:ErrorEventDefinition"], + "properties": [ + { + "name": "errorCodeVariable", + "isAttr": true, + "type": "String" + }, + { + "name": "errorMessageVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Error", + "isAbstract": true, + "extends": ["bpmn:Error"], + "properties": [ + { + "name": "activiti:errorMessage", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "PotentialStarter", + "superClass": ["Element"], + "properties": [ + { + "name": "resourceAssignmentExpression", + "type": "bpmn:ResourceAssignmentExpression" + } + ] + }, + { + "name": "FormSupported", + "isAbstract": true, + "extends": ["bpmn:StartEvent", "bpmn:UserTask"], + "properties": [ + { + "name": "formHandlerClass", + "isAttr": true, + "type": "String" + }, + { + "name": "formKey", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "TemplateSupported", + "isAbstract": true, + "extends": ["bpmn:Process", "bpmn:FlowElement"], + "properties": [ + { + "name": "modelerTemplate", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Initiator", + "isAbstract": true, + "extends": ["bpmn:StartEvent"], + "properties": [ + { + "name": "initiator", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ScriptTask", + "isAbstract": true, + "extends": ["bpmn:ScriptTask"], + "properties": [ + { + "name": "resultVariable", + "isAttr": true, + "type": "String" + }, + { + "name": "resource", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Process", + "isAbstract": true, + "extends": ["bpmn:Process"], + "properties": [ + { + "name": "candidateStarterGroups", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateStarterUsers", + "isAttr": true, + "type": "String" + }, + { + "name": "versionTag", + "isAttr": true, + "type": "String" + }, + { + "name": "historyTimeToLive", + "isAttr": true, + "type": "String" + }, + { + "name": "isStartableInTasklist", + "isAttr": true, + "type": "Boolean", + "default": true + }, + { + "name": "executionListener", + "isAbstract": true, + "type": "Expression" + } + ] + }, + { + "name": "EscalationEventDefinition", + "isAbstract": true, + "extends": ["bpmn:EscalationEventDefinition"], + "properties": [ + { + "name": "escalationCodeVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "FormalExpression", + "isAbstract": true, + "extends": ["bpmn:FormalExpression"], + "properties": [ + { + "name": "resource", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "multiinstance_type", + "superClass": ["Element"] + }, + { + "name": "multiinstance_condition", + "superClass": ["Element"] + }, + { + "name": "Assignable", + "extends": ["bpmn:UserTask"], + "properties": [ + { + "name": "assignee", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateUsers", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateGroups", + "isAttr": true, + "type": "String" + }, + { + "name": "dueDate", + "isAttr": true, + "type": "String" + }, + { + "name": "followUpDate", + "isAttr": true, + "type": "String" + }, + { + "name": "priority", + "isAttr": true, + "type": "String" + }, + { + "name": "multiinstance_condition", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "CallActivity", + "extends": ["bpmn:CallActivity"], + "properties": [ + { + "name": "calledElementBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "calledElementVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "calledElementVersionTag", + "isAttr": true, + "type": "String" + }, + { + "name": "calledElementTenantId", + "isAttr": true, + "type": "String" + }, + { + "name": "caseRef", + "isAttr": true, + "type": "String" + }, + { + "name": "caseBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "caseVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "caseTenantId", + "isAttr": true, + "type": "String" + }, + { + "name": "variableMappingClass", + "isAttr": true, + "type": "String" + }, + { + "name": "variableMappingDelegateExpression", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ServiceTaskLike", + "extends": [ + "bpmn:ServiceTask", + "bpmn:BusinessRuleTask", + "bpmn:SendTask", + "bpmn:MessageEventDefinition" + ], + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "resultVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "DmnCapable", + "extends": ["bpmn:BusinessRuleTask"], + "properties": [ + { + "name": "decisionRef", + "isAttr": true, + "type": "String" + }, + { + "name": "decisionRefBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "decisionRefVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "mapDecisionResult", + "isAttr": true, + "type": "String", + "default": "resultList" + }, + { + "name": "decisionRefTenantId", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ExternalCapable", + "extends": ["activiti:ServiceTaskLike"], + "properties": [ + { + "name": "type", + "isAttr": true, + "type": "String" + }, + { + "name": "topic", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "TaskPriorized", + "extends": ["bpmn:Process", "activiti:ExternalCapable"], + "properties": [ + { + "name": "taskPriority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Properties", + "superClass": ["Element"], + "meta": { + "allowedIn": ["*"] + }, + "properties": [ + { + "name": "values", + "type": "Property", + "isMany": true + } + ] + }, + { + "name": "Property", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "value", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "Connector", + "superClass": ["Element"], + "meta": { + "allowedIn": ["activiti:ServiceTaskLike"] + }, + "properties": [ + { + "name": "inputOutput", + "type": "InputOutput" + }, + { + "name": "connectorId", + "type": "String" + } + ] + }, + { + "name": "InputOutput", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:FlowNode", "activiti:Connector"] + }, + "properties": [ + { + "name": "inputOutput", + "type": "InputOutput" + }, + { + "name": "connectorId", + "type": "String" + }, + { + "name": "inputParameters", + "isMany": true, + "type": "InputParameter" + }, + { + "name": "outputParameters", + "isMany": true, + "type": "OutputParameter" + } + ] + }, + { + "name": "InputOutputParameter", + "properties": [ + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + }, + { + "name": "definition", + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "InputOutputParameterDefinition", + "isAbstract": true + }, + { + "name": "List", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "items", + "isMany": true, + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "Map", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "entries", + "isMany": true, + "type": "Entry" + } + ] + }, + { + "name": "Entry", + "properties": [ + { + "name": "key", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + }, + { + "name": "definition", + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "Value", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "id", + "isAttr": true, + "type": "String" + }, + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "Script", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "scriptFormat", + "isAttr": true, + "type": "String" + }, + { + "name": "resource", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "Field", + "superClass": ["Element"], + "meta": { + "allowedIn": [ + "activiti:ServiceTaskLike", + "activiti:ExecutionListener", + "activiti:TaskListener" + ] + }, + "properties": [ + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "expression", + "type": "String" + }, + { + "name": "stringValue", + "isAttr": true, + "type": "String" + }, + { + "name": "string", + "type": "String" + } + ] + }, + { + "name": "InputParameter", + "superClass": ["InputOutputParameter"] + }, + { + "name": "OutputParameter", + "superClass": ["InputOutputParameter"] + }, + { + "name": "Collectable", + "isAbstract": true, + "extends": ["bpmn:MultiInstanceLoopCharacteristics"], + "superClass": ["activiti:AsyncCapable"], + "properties": [ + { + "name": "collection", + "isAttr": true, + "type": "String" + }, + { + "name": "elementVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "FailedJobRetryTimeCycle", + "superClass": ["Element"], + "meta": { + "allowedIn": ["activiti:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"] + }, + "properties": [ + { + "name": "body", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "ExecutionListener", + "superClass": ["Element"], + "meta": { + "allowedIn": [ + "bpmn:Task", + "bpmn:ServiceTask", + "bpmn:UserTask", + "bpmn:BusinessRuleTask", + "bpmn:ScriptTask", + "bpmn:ReceiveTask", + "bpmn:ManualTask", + "bpmn:ExclusiveGateway", + "bpmn:SequenceFlow", + "bpmn:ParallelGateway", + "bpmn:InclusiveGateway", + "bpmn:EventBasedGateway", + "bpmn:StartEvent", + "bpmn:IntermediateCatchEvent", + "bpmn:IntermediateThrowEvent", + "bpmn:EndEvent", + "bpmn:BoundaryEvent", + "bpmn:CallActivity", + "bpmn:SubProcess", + "bpmn:Process" + ] + }, + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "event", + "isAttr": true, + "type": "String" + }, + { + "name": "script", + "type": "Script" + }, + { + "name": "fields", + "type": "Field", + "isMany": true + } + ] + }, + { + "name": "TaskListener", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:UserTask"] + }, + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "event", + "isAttr": true, + "type": "String" + }, + { + "name": "script", + "type": "Script" + }, + { + "name": "fields", + "type": "Field", + "isMany": true + } + ] + }, + { + "name": "FormProperty", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"] + }, + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "required", + "type": "String", + "isAttr": true + }, + { + "name": "readable", + "type": "String", + "isAttr": true + }, + { + "name": "writable", + "type": "String", + "isAttr": true + }, + { + "name": "variable", + "type": "String", + "isAttr": true + }, + { + "name": "expression", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "default", + "type": "String", + "isAttr": true + }, + { + "name": "values", + "type": "Value", + "isMany": true + } + ] + }, + { + "name": "FormProperty", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "label", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "defaultValue", + "type": "String", + "isAttr": true + }, + { + "name": "properties", + "type": "Properties" + }, + { + "name": "validation", + "type": "Validation" + }, + { + "name": "values", + "type": "Value", + "isMany": true + } + ] + }, + { + "name": "Validation", + "superClass": ["Element"], + "properties": [ + { + "name": "constraints", + "type": "Constraint", + "isMany": true + } + ] + }, + { + "name": "Constraint", + "superClass": ["Element"], + "properties": [ + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "config", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "ConditionalEventDefinition", + "isAbstract": true, + "extends": ["bpmn:ConditionalEventDefinition"], + "properties": [ + { + "name": "variableName", + "isAttr": true, + "type": "String" + }, + { + "name": "variableEvent", + "isAttr": true, + "type": "String" + } + ] + } + ], + "emumerations": [] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/camundaDescriptor.json b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/camundaDescriptor.json new file mode 100644 index 0000000..79b86bc --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/camundaDescriptor.json @@ -0,0 +1,1010 @@ +{ + "name": "Camunda", + "uri": "http://camunda.org/schema/1.0/bpmn", + "prefix": "camunda", + "xml": { + "tagAlias": "lowerCase" + }, + "associations": [], + "types": [ + { + "name": "Definitions", + "isAbstract": true, + "extends": ["bpmn:Definitions"], + "properties": [ + { + "name": "diagramRelationId", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "InOutBinding", + "superClass": ["Element"], + "isAbstract": true, + "properties": [ + { + "name": "source", + "isAttr": true, + "type": "String" + }, + { + "name": "sourceExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "target", + "isAttr": true, + "type": "String" + }, + { + "name": "businessKey", + "isAttr": true, + "type": "String" + }, + { + "name": "local", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "variables", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "In", + "superClass": ["InOutBinding"], + "meta": { + "allowedIn": ["bpmn:CallActivity", "bpmn:SignalEventDefinition"] + } + }, + { + "name": "Out", + "superClass": ["InOutBinding"], + "meta": { + "allowedIn": ["bpmn:CallActivity"] + } + }, + { + "name": "AsyncCapable", + "isAbstract": true, + "extends": ["bpmn:Activity", "bpmn:Gateway", "bpmn:Event"], + "properties": [ + { + "name": "async", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "asyncBefore", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "asyncAfter", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "exclusive", + "isAttr": true, + "type": "Boolean", + "default": true + } + ] + }, + { + "name": "JobPriorized", + "isAbstract": true, + "extends": ["bpmn:Process", "camunda:AsyncCapable"], + "properties": [ + { + "name": "jobPriority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "SignalEventDefinition", + "isAbstract": true, + "extends": ["bpmn:SignalEventDefinition"], + "properties": [ + { + "name": "async", + "isAttr": true, + "type": "Boolean", + "default": false + } + ] + }, + { + "name": "ErrorEventDefinition", + "isAbstract": true, + "extends": ["bpmn:ErrorEventDefinition"], + "properties": [ + { + "name": "errorCodeVariable", + "isAttr": true, + "type": "String" + }, + { + "name": "errorMessageVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Error", + "isAbstract": true, + "extends": ["bpmn:Error"], + "properties": [ + { + "name": "camunda:errorMessage", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "PotentialStarter", + "superClass": ["Element"], + "properties": [ + { + "name": "resourceAssignmentExpression", + "type": "bpmn:ResourceAssignmentExpression" + } + ] + }, + { + "name": "FormSupported", + "isAbstract": true, + "extends": ["bpmn:StartEvent", "bpmn:UserTask"], + "properties": [ + { + "name": "formHandlerClass", + "isAttr": true, + "type": "String" + }, + { + "name": "formKey", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "TemplateSupported", + "isAbstract": true, + "extends": ["bpmn:Process", "bpmn:FlowElement"], + "properties": [ + { + "name": "modelerTemplate", + "isAttr": true, + "type": "String" + }, + { + "name": "modelerTemplateVersion", + "isAttr": true, + "type": "Integer" + } + ] + }, + { + "name": "Initiator", + "isAbstract": true, + "extends": ["bpmn:StartEvent"], + "properties": [ + { + "name": "initiator", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ScriptTask", + "isAbstract": true, + "extends": ["bpmn:ScriptTask"], + "properties": [ + { + "name": "resultVariable", + "isAttr": true, + "type": "String" + }, + { + "name": "resource", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Process", + "isAbstract": true, + "extends": ["bpmn:Process"], + "properties": [ + { + "name": "candidateStarterGroups", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateStarterUsers", + "isAttr": true, + "type": "String" + }, + { + "name": "versionTag", + "isAttr": true, + "type": "String" + }, + { + "name": "historyTimeToLive", + "isAttr": true, + "type": "String" + }, + { + "name": "isStartableInTasklist", + "isAttr": true, + "type": "Boolean", + "default": true + } + ] + }, + { + "name": "EscalationEventDefinition", + "isAbstract": true, + "extends": ["bpmn:EscalationEventDefinition"], + "properties": [ + { + "name": "escalationCodeVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "FormalExpression", + "isAbstract": true, + "extends": ["bpmn:FormalExpression"], + "properties": [ + { + "name": "resource", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Assignable", + "extends": ["bpmn:UserTask"], + "properties": [ + { + "name": "assignee", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateUsers", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateGroups", + "isAttr": true, + "type": "String" + }, + { + "name": "dueDate", + "isAttr": true, + "type": "String" + }, + { + "name": "followUpDate", + "isAttr": true, + "type": "String" + }, + { + "name": "priority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "CallActivity", + "extends": ["bpmn:CallActivity"], + "properties": [ + { + "name": "calledElementBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "calledElementVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "calledElementVersionTag", + "isAttr": true, + "type": "String" + }, + { + "name": "calledElementTenantId", + "isAttr": true, + "type": "String" + }, + { + "name": "caseRef", + "isAttr": true, + "type": "String" + }, + { + "name": "caseBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "caseVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "caseTenantId", + "isAttr": true, + "type": "String" + }, + { + "name": "variableMappingClass", + "isAttr": true, + "type": "String" + }, + { + "name": "variableMappingDelegateExpression", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ServiceTaskLike", + "extends": [ + "bpmn:ServiceTask", + "bpmn:BusinessRuleTask", + "bpmn:SendTask", + "bpmn:MessageEventDefinition" + ], + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "resultVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "DmnCapable", + "extends": ["bpmn:BusinessRuleTask"], + "properties": [ + { + "name": "decisionRef", + "isAttr": true, + "type": "String" + }, + { + "name": "decisionRefBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "decisionRefVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "mapDecisionResult", + "isAttr": true, + "type": "String", + "default": "resultList" + }, + { + "name": "decisionRefTenantId", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ExternalCapable", + "extends": ["camunda:ServiceTaskLike"], + "properties": [ + { + "name": "type", + "isAttr": true, + "type": "String" + }, + { + "name": "topic", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "TaskPriorized", + "extends": ["bpmn:Process", "camunda:ExternalCapable"], + "properties": [ + { + "name": "taskPriority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Properties", + "superClass": ["Element"], + "meta": { + "allowedIn": ["*"] + }, + "properties": [ + { + "name": "values", + "type": "Property", + "isMany": true + } + ] + }, + { + "name": "Property", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "value", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "Connector", + "superClass": ["Element"], + "meta": { + "allowedIn": ["camunda:ServiceTaskLike"] + }, + "properties": [ + { + "name": "inputOutput", + "type": "InputOutput" + }, + { + "name": "connectorId", + "type": "String" + } + ] + }, + { + "name": "InputOutput", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:FlowNode", "camunda:Connector"] + }, + "properties": [ + { + "name": "inputOutput", + "type": "InputOutput" + }, + { + "name": "connectorId", + "type": "String" + }, + { + "name": "inputParameters", + "isMany": true, + "type": "InputParameter" + }, + { + "name": "outputParameters", + "isMany": true, + "type": "OutputParameter" + } + ] + }, + { + "name": "InputOutputParameter", + "properties": [ + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + }, + { + "name": "definition", + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "InputOutputParameterDefinition", + "isAbstract": true + }, + { + "name": "List", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "items", + "isMany": true, + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "Map", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "entries", + "isMany": true, + "type": "Entry" + } + ] + }, + { + "name": "Entry", + "properties": [ + { + "name": "key", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + }, + { + "name": "definition", + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "Value", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "id", + "isAttr": true, + "type": "String" + }, + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "Script", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "scriptFormat", + "isAttr": true, + "type": "String" + }, + { + "name": "resource", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "Field", + "superClass": ["Element"], + "meta": { + "allowedIn": [ + "camunda:ServiceTaskLike", + "camunda:ExecutionListener", + "camunda:TaskListener" + ] + }, + "properties": [ + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "expression", + "type": "String" + }, + { + "name": "stringValue", + "isAttr": true, + "type": "String" + }, + { + "name": "string", + "type": "String" + } + ] + }, + { + "name": "InputParameter", + "superClass": ["InputOutputParameter"] + }, + { + "name": "OutputParameter", + "superClass": ["InputOutputParameter"] + }, + { + "name": "Collectable", + "isAbstract": true, + "extends": ["bpmn:MultiInstanceLoopCharacteristics"], + "superClass": ["camunda:AsyncCapable"], + "properties": [ + { + "name": "collection", + "isAttr": true, + "type": "String" + }, + { + "name": "elementVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "FailedJobRetryTimeCycle", + "superClass": ["Element"], + "meta": { + "allowedIn": ["camunda:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"] + }, + "properties": [ + { + "name": "body", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "ExecutionListener", + "superClass": ["Element"], + "meta": { + "allowedIn": [ + "bpmn:Task", + "bpmn:ServiceTask", + "bpmn:UserTask", + "bpmn:BusinessRuleTask", + "bpmn:ScriptTask", + "bpmn:ReceiveTask", + "bpmn:ManualTask", + "bpmn:ExclusiveGateway", + "bpmn:SequenceFlow", + "bpmn:ParallelGateway", + "bpmn:InclusiveGateway", + "bpmn:EventBasedGateway", + "bpmn:StartEvent", + "bpmn:IntermediateCatchEvent", + "bpmn:IntermediateThrowEvent", + "bpmn:EndEvent", + "bpmn:BoundaryEvent", + "bpmn:CallActivity", + "bpmn:SubProcess", + "bpmn:Process" + ] + }, + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "event", + "isAttr": true, + "type": "String" + }, + { + "name": "script", + "type": "Script" + }, + { + "name": "fields", + "type": "Field", + "isMany": true + } + ] + }, + { + "name": "TaskListener", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:UserTask"] + }, + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "event", + "isAttr": true, + "type": "String" + }, + { + "name": "script", + "type": "Script" + }, + { + "name": "fields", + "type": "Field", + "isMany": true + }, + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "eventDefinitions", + "type": "bpmn:TimerEventDefinition", + "isMany": true + } + ] + }, + { + "name": "FormProperty", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"] + }, + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "required", + "type": "String", + "isAttr": true + }, + { + "name": "readable", + "type": "String", + "isAttr": true + }, + { + "name": "writable", + "type": "String", + "isAttr": true + }, + { + "name": "variable", + "type": "String", + "isAttr": true + }, + { + "name": "expression", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "default", + "type": "String", + "isAttr": true + }, + { + "name": "values", + "type": "Value", + "isMany": true + } + ] + }, + { + "name": "FormData", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"] + }, + "properties": [ + { + "name": "fields", + "type": "FormField", + "isMany": true + }, + { + "name": "businessKey", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "FormField", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "label", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "defaultValue", + "type": "String", + "isAttr": true + }, + { + "name": "properties", + "type": "Properties" + }, + { + "name": "validation", + "type": "Validation" + }, + { + "name": "values", + "type": "Value", + "isMany": true + } + ] + }, + { + "name": "Validation", + "superClass": ["Element"], + "properties": [ + { + "name": "constraints", + "type": "Constraint", + "isMany": true + } + ] + }, + { + "name": "Constraint", + "superClass": ["Element"], + "properties": [ + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "config", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "ConditionalEventDefinition", + "isAbstract": true, + "extends": ["bpmn:ConditionalEventDefinition"], + "properties": [ + { + "name": "variableName", + "isAttr": true, + "type": "String" + }, + { + "name": "variableEvents", + "isAttr": true, + "type": "String" + } + ] + } + ], + "emumerations": [] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json new file mode 100644 index 0000000..7fe7ad1 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json @@ -0,0 +1,1207 @@ +{ + "name": "Flowable", + "uri": "http://flowable.org/bpmn", + "prefix": "flowable", + "xml": { + "tagAlias": "lowerCase" + }, + "associations": [], + "types": [ + { + "name": "InOutBinding", + "superClass": ["Element"], + "isAbstract": true, + "properties": [ + { + "name": "source", + "isAttr": true, + "type": "String" + }, + { + "name": "sourceExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "target", + "isAttr": true, + "type": "String" + }, + { + "name": "businessKey", + "isAttr": true, + "type": "String" + }, + { + "name": "local", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "variables", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "In", + "superClass": ["InOutBinding"], + "meta": { + "allowedIn": ["bpmn:CallActivity"] + } + }, + { + "name": "Out", + "superClass": ["InOutBinding"], + "meta": { + "allowedIn": ["bpmn:CallActivity"] + } + }, + { + "name": "AsyncCapable", + "isAbstract": true, + "extends": ["bpmn:Activity", "bpmn:Gateway", "bpmn:Event"], + "properties": [ + { + "name": "async", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "asyncBefore", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "asyncAfter", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "exclusive", + "isAttr": true, + "type": "Boolean", + "default": true + } + ] + }, + { + "name": "JobPriorized", + "isAbstract": true, + "extends": ["bpmn:Process", "flowable:AsyncCapable"], + "properties": [ + { + "name": "jobPriority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "SignalEventDefinition", + "isAbstract": true, + "extends": ["bpmn:SignalEventDefinition"], + "properties": [ + { + "name": "async", + "isAttr": true, + "type": "Boolean", + "default": false + } + ] + }, + { + "name": "ErrorEventDefinition", + "isAbstract": true, + "extends": ["bpmn:ErrorEventDefinition"], + "properties": [ + { + "name": "errorCodeVariable", + "isAttr": true, + "type": "String" + }, + { + "name": "errorMessageVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Error", + "isAbstract": true, + "extends": ["bpmn:Error"], + "properties": [ + { + "name": "flowable:errorMessage", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "PotentialStarter", + "superClass": ["Element"], + "properties": [ + { + "name": "resourceAssignmentExpression", + "type": "bpmn:ResourceAssignmentExpression" + } + ] + }, + { + "name": "FormSupported", + "isAbstract": true, + "extends": ["bpmn:StartEvent", "bpmn:UserTask"], + "properties": [ + { + "name": "formHandlerClass", + "isAttr": true, + "type": "String" + }, + { + "name": "formKey", + "isAttr": true, + "type": "String" + }, + { + "name": "formType", + "isAttr": true, + "type": "String" + }, + { + "name": "formReadOnly", + "isAttr": true, + "type": "Boolean", + "default": false + }, + { + "name": "formInit", + "isAttr": true, + "type": "Boolean", + "default": true + } + ] + }, + { + "name": "TemplateSupported", + "isAbstract": true, + "extends": ["bpmn:Process", "bpmn:FlowElement"], + "properties": [ + { + "name": "modelerTemplate", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Initiator", + "isAbstract": true, + "extends": ["bpmn:StartEvent"], + "properties": [ + { + "name": "initiator", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ScriptTask", + "isAbstract": true, + "extends": ["bpmn:ScriptTask"], + "properties": [ + { + "name": "resultVariable", + "isAttr": true, + "type": "String" + }, + { + "name": "resource", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Process", + "isAbstract": true, + "extends": ["bpmn:Process"], + "properties": [ + { + "name": "candidateStarterGroups", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateStarterUsers", + "isAttr": true, + "type": "String" + }, + { + "name": "versionTag", + "isAttr": true, + "type": "String" + }, + { + "name": "historyTimeToLive", + "isAttr": true, + "type": "String" + }, + { + "name": "isStartableInTasklist", + "isAttr": true, + "type": "Boolean", + "default": true + } + ] + }, + { + "name": "EscalationEventDefinition", + "isAbstract": true, + "extends": ["bpmn:EscalationEventDefinition"], + "properties": [ + { + "name": "escalationCodeVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "FormalExpression", + "isAbstract": true, + "extends": ["bpmn:FormalExpression"], + "properties": [ + { + "name": "resource", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Assignable", + "extends": ["bpmn:UserTask"], + "properties": [ + { + "name": "assignee", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateUsers", + "isAttr": true, + "type": "String" + }, + { + "name": "candidateGroups", + "isAttr": true, + "type": "String" + }, + { + "name": "dueDate", + "isAttr": true, + "type": "String" + }, + { + "name": "followUpDate", + "isAttr": true, + "type": "String" + }, + { + "name": "priority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Assignee", + "supperClass": "Element", + "meta": { + "allowedIn": ["*"] + }, + "properties": [ + { + "name": "label", + "type": "String", + "isAttr": true + }, + { + "name": "viewId", + "type": "Number", + "isAttr": true + } + ] + }, + { + "name": "CallActivity", + "extends": ["bpmn:CallActivity"], + "properties": [ + { + "name": "calledElementBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "calledElementVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "calledElementVersionTag", + "isAttr": true, + "type": "String" + }, + { + "name": "calledElementTenantId", + "isAttr": true, + "type": "String" + }, + { + "name": "caseRef", + "isAttr": true, + "type": "String" + }, + { + "name": "caseBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "caseVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "caseTenantId", + "isAttr": true, + "type": "String" + }, + { + "name": "variableMappingClass", + "isAttr": true, + "type": "String" + }, + { + "name": "variableMappingDelegateExpression", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ServiceTaskLike", + "extends": [ + "bpmn:ServiceTask", + "bpmn:BusinessRuleTask", + "bpmn:SendTask", + "bpmn:MessageEventDefinition" + ], + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "resultVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "DmnCapable", + "extends": ["bpmn:BusinessRuleTask"], + "properties": [ + { + "name": "decisionRef", + "isAttr": true, + "type": "String" + }, + { + "name": "decisionRefBinding", + "isAttr": true, + "type": "String", + "default": "latest" + }, + { + "name": "decisionRefVersion", + "isAttr": true, + "type": "String" + }, + { + "name": "mapDecisionResult", + "isAttr": true, + "type": "String", + "default": "resultList" + }, + { + "name": "decisionRefTenantId", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "ExternalCapable", + "extends": ["flowable:ServiceTaskLike"], + "properties": [ + { + "name": "type", + "isAttr": true, + "type": "String" + }, + { + "name": "topic", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "TaskPriorized", + "extends": ["bpmn:Process", "flowable:ExternalCapable"], + "properties": [ + { + "name": "taskPriority", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Properties", + "superClass": ["Element"], + "meta": { + "allowedIn": ["*"] + }, + "properties": [ + { + "name": "values", + "type": "Property", + "isMany": true + } + ] + }, + { + "name": "Property", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "value", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "Button", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:UserTask"] + }, + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "code", + "type": "String", + "isAttr": true + }, + { + "name": "isHide", + "type": "String", + "isAttr": true + }, + { + "name": "next", + "type": "String", + "isAttr": true + }, + { + "name": "sort", + "type": "Integer", + "isAttr": true + } + ] + }, + { + "name": "Assignee", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:UserTask"] + }, + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "value", + "type": "String", + "isAttr": true + }, + { + "name": "condition", + "type": "String", + "isAttr": true + }, + { + "name": "operationType", + "type": "String", + "isAttr": true + }, + { + "name": "sort", + "type": "Integer", + "isAttr": true + } + ] + }, + { + "name": "Connector", + "superClass": ["Element"], + "meta": { + "allowedIn": ["flowable:ServiceTaskLike"] + }, + "properties": [ + { + "name": "inputOutput", + "type": "InputOutput" + }, + { + "name": "connectorId", + "type": "String" + } + ] + }, + { + "name": "InputOutput", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:FlowNode", "flowable:Connector"] + }, + "properties": [ + { + "name": "inputOutput", + "type": "InputOutput" + }, + { + "name": "connectorId", + "type": "String" + }, + { + "name": "inputParameters", + "isMany": true, + "type": "InputParameter" + }, + { + "name": "outputParameters", + "isMany": true, + "type": "OutputParameter" + } + ] + }, + { + "name": "InputOutputParameter", + "properties": [ + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + }, + { + "name": "definition", + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "InputOutputParameterDefinition", + "isAbstract": true + }, + { + "name": "List", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "items", + "isMany": true, + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "Map", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "entries", + "isMany": true, + "type": "Entry" + } + ] + }, + { + "name": "Entry", + "properties": [ + { + "name": "key", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + }, + { + "name": "definition", + "type": "InputOutputParameterDefinition" + } + ] + }, + { + "name": "Value", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "id", + "isAttr": true, + "type": "String" + }, + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "Script", + "superClass": ["InputOutputParameterDefinition"], + "properties": [ + { + "name": "scriptFormat", + "isAttr": true, + "type": "String" + }, + { + "name": "resource", + "isAttr": true, + "type": "String" + }, + { + "name": "value", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "Field", + "superClass": ["Element"], + "meta": { + "allowedIn": [ + "flowable:ServiceTaskLike", + "flowable:ExecutionListener", + "flowable:TaskListener" + ] + }, + "properties": [ + { + "name": "name", + "isAttr": true, + "type": "String" + }, + { + "name": "expression", + "type": "String" + }, + { + "name": "stringValue", + "isAttr": true, + "type": "String" + }, + { + "name": "string", + "type": "String" + } + ] + }, + { + "name": "ChildField", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "required", + "type": "String", + "isAttr": true + }, + { + "name": "readable", + "type": "String", + "isAttr": true + }, + { + "name": "writable", + "type": "String", + "isAttr": true + }, + { + "name": "variable", + "type": "String", + "isAttr": true + }, + { + "name": "expression", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "default", + "type": "String", + "isAttr": true + }, + { + "name": "values", + "type": "Value", + "isMany": true + } + ] + }, + { + "name": "InputParameter", + "superClass": ["InputOutputParameter"] + }, + { + "name": "OutputParameter", + "superClass": ["InputOutputParameter"] + }, + { + "name": "Collectable", + "isAbstract": true, + "extends": ["bpmn:MultiInstanceLoopCharacteristics"], + "superClass": ["flowable:AsyncCapable"], + "properties": [ + { + "name": "collection", + "isAttr": true, + "type": "String" + }, + { + "name": "elementVariable", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "FailedJobRetryTimeCycle", + "superClass": ["Element"], + "meta": { + "allowedIn": ["flowable:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"] + }, + "properties": [ + { + "name": "body", + "isBody": true, + "type": "String" + } + ] + }, + { + "name": "ExecutionListener", + "superClass": ["Element"], + "meta": { + "allowedIn": [ + "bpmn:Task", + "bpmn:ServiceTask", + "bpmn:UserTask", + "bpmn:BusinessRuleTask", + "bpmn:ScriptTask", + "bpmn:ReceiveTask", + "bpmn:ManualTask", + "bpmn:ExclusiveGateway", + "bpmn:SequenceFlow", + "bpmn:ParallelGateway", + "bpmn:InclusiveGateway", + "bpmn:EventBasedGateway", + "bpmn:StartEvent", + "bpmn:IntermediateCatchEvent", + "bpmn:IntermediateThrowEvent", + "bpmn:EndEvent", + "bpmn:BoundaryEvent", + "bpmn:CallActivity", + "bpmn:SubProcess", + "bpmn:Process" + ] + }, + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "event", + "isAttr": true, + "type": "String" + }, + { + "name": "script", + "type": "Script" + }, + { + "name": "fields", + "type": "Field", + "isMany": true + } + ] + }, + { + "name": "TaskListener", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:UserTask"] + }, + "properties": [ + { + "name": "expression", + "isAttr": true, + "type": "String" + }, + { + "name": "class", + "isAttr": true, + "type": "String" + }, + { + "name": "delegateExpression", + "isAttr": true, + "type": "String" + }, + { + "name": "event", + "isAttr": true, + "type": "String" + }, + { + "name": "script", + "type": "Script" + }, + { + "name": "fields", + "type": "Field", + "isMany": true + } + ] + }, + { + "name": "FormProperty", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"] + }, + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "required", + "type": "String", + "isAttr": true + }, + { + "name": "readable", + "type": "String", + "isAttr": true + }, + { + "name": "writable", + "type": "String", + "isAttr": true + }, + { + "name": "variable", + "type": "String", + "isAttr": true + }, + { + "name": "expression", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "default", + "type": "String", + "isAttr": true + }, + { + "name": "values", + "type": "Value", + "isMany": true + }, + { + "name": "children", + "type": "ChildField", + "isMany": true + }, + { + "name": "extensionElements", + "type": "bpmn:ExtensionElements", + "isMany": true + } + ] + }, + { + "name": "FormData", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"] + }, + "properties": [ + { + "name": "fields", + "type": "FormField", + "isMany": true + }, + { + "name": "businessKey", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "FormField", + "superClass": ["Element"], + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "label", + "type": "String", + "isAttr": true + }, + { + "name": "type", + "type": "String", + "isAttr": true + }, + { + "name": "datePattern", + "type": "String", + "isAttr": true + }, + { + "name": "defaultValue", + "type": "String", + "isAttr": true + }, + { + "name": "properties", + "type": "Properties" + }, + { + "name": "validation", + "type": "Validation" + }, + { + "name": "values", + "type": "Value", + "isMany": true + } + ] + }, + { + "name": "Validation", + "superClass": ["Element"], + "properties": [ + { + "name": "constraints", + "type": "Constraint", + "isMany": true + } + ] + }, + { + "name": "Constraint", + "superClass": ["Element"], + "properties": [ + { + "name": "name", + "type": "String", + "isAttr": true + }, + { + "name": "config", + "type": "String", + "isAttr": true + } + ] + }, + { + "name": "ConditionalEventDefinition", + "isAbstract": true, + "extends": ["bpmn:ConditionalEventDefinition"], + "properties": [ + { + "name": "variableName", + "isAttr": true, + "type": "String" + }, + { + "name": "variableEvent", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Condition", + "superClass": ["Element"], + "meta": { + "allowedIn": ["bpmn:SequenceFlow"] + }, + "properties": [ + { + "name": "id", + "type": "String", + "isAttr": true + }, + { + "name": "field", + "type": "String", + "isAttr": true + }, + { + "name": "compare", + "type": "String", + "isAttr": true + }, + { + "name": "value", + "type": "String", + "isAttr": true + }, + { + "name": "logic", + "type": "String", + "isAttr": true + }, + { + "name": "sort", + "type": "Integer", + "isAttr": true + } + ] + } + ], + "emumerations": [] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/activiti/activitiExtension.js b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/activiti/activitiExtension.js new file mode 100644 index 0000000..56ef38a --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/activiti/activitiExtension.js @@ -0,0 +1,83 @@ +'use strict' + +import { some } from 'min-dash' + +// const some = require('min-dash').some +// const some = some + +const ALLOWED_TYPES = { + FailedJobRetryTimeCycle: [ + 'bpmn:StartEvent', + 'bpmn:BoundaryEvent', + 'bpmn:IntermediateCatchEvent', + 'bpmn:Activity' + ], + Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'], + Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'] +} + +function is(element, type) { + return element && typeof element.$instanceOf === 'function' && element.$instanceOf(type) +} + +function exists(element) { + return element && element.length +} + +function includesType(collection, type) { + return ( + exists(collection) && + some(collection, function (element) { + return is(element, type) + }) + ) +} + +function anyType(element, types) { + return some(types, function (type) { + return is(element, type) + }) +} + +function isAllowed(propName, propDescriptor, newElement) { + const name = propDescriptor.name, + types = ALLOWED_TYPES[name.replace(/activiti:/, '')] + + return name === propName && anyType(newElement, types) +} + +function ActivitiModdleExtension(eventBus) { + eventBus.on( + 'property.clone', + function (context) { + const newElement = context.newElement, + propDescriptor = context.propertyDescriptor + + this.canCloneProperty(newElement, propDescriptor) + }, + this + ) +} + +ActivitiModdleExtension.$inject = ['eventBus'] + +ActivitiModdleExtension.prototype.canCloneProperty = function (newElement, propDescriptor) { + if (isAllowed('activiti:FailedJobRetryTimeCycle', propDescriptor, newElement)) { + return ( + includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') || + includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') || + is(newElement.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics') + ) + } + + if (isAllowed('activiti:Connector', propDescriptor, newElement)) { + return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition') + } + + if (isAllowed('activiti:Field', propDescriptor, newElement)) { + return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition') + } +} + +// module.exports = ActivitiModdleExtension; +export default ActivitiModdleExtension diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/activiti/index.js b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/activiti/index.js new file mode 100644 index 0000000..c22ca34 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/activiti/index.js @@ -0,0 +1,11 @@ +/* + * @author igdianov + * address https://github.com/igdianov/activiti-bpmn-moddle + * */ + +import activitiExtension from './activitiExtension' + +export default { + __init__: ['ActivitiModdleExtension'], + ActivitiModdleExtension: ['type', activitiExtension] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/camunda/extension.js b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/camunda/extension.js new file mode 100644 index 0000000..b8c37a5 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/camunda/extension.js @@ -0,0 +1,151 @@ +'use strict' + +import { isFunction, isObject, some } from 'min-dash' + +// const isFunction = isFunction, +// isObject = isObject, +// some = some +// const isFunction = require('min-dash').isFunction, +// isObject = require('min-dash').isObject, +// some = require('min-dash').some + +const WILDCARD = '*' + +function CamundaModdleExtension(eventBus) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this + + eventBus.on('moddleCopy.canCopyProperty', function (context) { + const property = context.property, + parent = context.parent + + return self.canCopyProperty(property, parent) + }) +} + +CamundaModdleExtension.$inject = ['eventBus'] + +/** + * Check wether to disallow copying property. + */ +CamundaModdleExtension.prototype.canCopyProperty = function (property, parent) { + // (1) check wether property is allowed in parent + if (isObject(property) && !isAllowedInParent(property, parent)) { + return false + } + + // (2) check more complex scenarios + + if (is(property, 'camunda:InputOutput') && !this.canHostInputOutput(parent)) { + return false + } + + if (isAny(property, ['camunda:Connector', 'camunda:Field']) && !this.canHostConnector(parent)) { + return false + } + + if (is(property, 'camunda:In') && !this.canHostIn(parent)) { + return false + } +} + +CamundaModdleExtension.prototype.canHostInputOutput = function (parent) { + // allowed in camunda:Connector + const connector = getParent(parent, 'camunda:Connector') + + if (connector) { + return true + } + + // special rules inside bpmn:FlowNode + const flowNode = getParent(parent, 'bpmn:FlowNode') + + if (!flowNode) { + return false + } + + if (isAny(flowNode, ['bpmn:StartEvent', 'bpmn:Gateway', 'bpmn:BoundaryEvent'])) { + return false + } + + return !(is(flowNode, 'bpmn:SubProcess') && flowNode.get('triggeredByEvent')) +} + +CamundaModdleExtension.prototype.canHostConnector = function (parent) { + const serviceTaskLike = getParent(parent, 'camunda:ServiceTaskLike') + + if (is(serviceTaskLike, 'bpmn:MessageEventDefinition')) { + // only allow on throw and end events + return getParent(parent, 'bpmn:IntermediateThrowEvent') || getParent(parent, 'bpmn:EndEvent') + } + + return true +} + +CamundaModdleExtension.prototype.canHostIn = function (parent) { + const callActivity = getParent(parent, 'bpmn:CallActivity') + + if (callActivity) { + return true + } + + const signalEventDefinition = getParent(parent, 'bpmn:SignalEventDefinition') + + if (signalEventDefinition) { + // only allow on throw and end events + return getParent(parent, 'bpmn:IntermediateThrowEvent') || getParent(parent, 'bpmn:EndEvent') + } + + return true +} + +// module.exports = CamundaModdleExtension; +export default CamundaModdleExtension + +// helpers ////////// + +function is(element, type) { + return element && isFunction(element.$instanceOf) && element.$instanceOf(type) +} + +function isAny(element, types) { + return some(types, function (t) { + return is(element, t) + }) +} + +function getParent(element, type) { + if (!type) { + return element.$parent + } + + if (is(element, type)) { + return element + } + + if (!element.$parent) { + return + } + + return getParent(element.$parent, type) +} + +function isAllowedInParent(property, parent) { + // (1) find property descriptor + const descriptor = property.$type && property.$model.getTypeDescriptor(property.$type) + + const allowedIn = descriptor && descriptor.meta && descriptor.meta.allowedIn + + if (!allowedIn || isWildcard(allowedIn)) { + return true + } + + // (2) check wether property has parent of allowed type + return some(allowedIn, function (type) { + return getParent(parent, type) + }) +} + +function isWildcard(allowedIn) { + return allowedIn.indexOf(WILDCARD) !== -1 +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/camunda/index.js b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/camunda/index.js new file mode 100644 index 0000000..1da1bc7 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/camunda/index.js @@ -0,0 +1,8 @@ +'use strict' + +import extension from './extension' + +export default { + __init__: ['camundaModdleExtension'], + camundaModdleExtension: ['type', extension] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/flowable/flowableExtension.js b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/flowable/flowableExtension.js new file mode 100644 index 0000000..3dcea67 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/flowable/flowableExtension.js @@ -0,0 +1,83 @@ +'use strict' + +import { some } from 'min-dash' + +// const some = some +// const some = require('min-dash').some + +const ALLOWED_TYPES = { + FailedJobRetryTimeCycle: [ + 'bpmn:StartEvent', + 'bpmn:BoundaryEvent', + 'bpmn:IntermediateCatchEvent', + 'bpmn:Activity' + ], + Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'], + Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'] +} + +function is(element, type) { + return element && typeof element.$instanceOf === 'function' && element.$instanceOf(type) +} + +function exists(element) { + return element && element.length +} + +function includesType(collection, type) { + return ( + exists(collection) && + some(collection, function (element) { + return is(element, type) + }) + ) +} + +function anyType(element, types) { + return some(types, function (type) { + return is(element, type) + }) +} + +function isAllowed(propName, propDescriptor, newElement) { + const name = propDescriptor.name, + types = ALLOWED_TYPES[name.replace(/flowable:/, '')] + + return name === propName && anyType(newElement, types) +} + +function FlowableModdleExtension(eventBus) { + eventBus.on( + 'property.clone', + function (context) { + const newElement = context.newElement, + propDescriptor = context.propertyDescriptor + + this.canCloneProperty(newElement, propDescriptor) + }, + this + ) +} + +FlowableModdleExtension.$inject = ['eventBus'] + +FlowableModdleExtension.prototype.canCloneProperty = function (newElement, propDescriptor) { + if (isAllowed('flowable:FailedJobRetryTimeCycle', propDescriptor, newElement)) { + return ( + includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') || + includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') || + is(newElement.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics') + ) + } + + if (isAllowed('flowable:Connector', propDescriptor, newElement)) { + return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition') + } + + if (isAllowed('flowable:Field', propDescriptor, newElement)) { + return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition') + } +} + +// module.exports = FlowableModdleExtension; +export default FlowableModdleExtension diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/flowable/index.js b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/flowable/index.js new file mode 100644 index 0000000..6d59b67 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/extension-moddle/flowable/index.js @@ -0,0 +1,10 @@ +/* + * @author igdianov + * address https://github.com/igdianov/activiti-bpmn-moddle + * */ +import flowableExtension from './flowableExtension' + +export default { + __init__: ['FlowableModdleExtension'], + FlowableModdleExtension: ['type', flowableExtension] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js new file mode 100644 index 0000000..5e2803b --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js @@ -0,0 +1,221 @@ +import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider' +import { assign } from 'min-dash' + +export default function CustomPalette( + palette, + create, + elementFactory, + spaceTool, + lassoTool, + handTool, + globalConnect, + translate +) { + PaletteProvider.call( + this, + palette, + create, + elementFactory, + spaceTool, + lassoTool, + handTool, + globalConnect, + translate, + 2000 + ) +} + +const F = function () {} // 核心,利用空对象作为中介; +F.prototype = PaletteProvider.prototype // 核心,将父类的原型赋值给空对象F; + +// 利用中介函数重写原型链方法 +F.prototype.getPaletteEntries = function () { + const actions = {}, + create = this._create, + elementFactory = this._elementFactory, + spaceTool = this._spaceTool, + lassoTool = this._lassoTool, + handTool = this._handTool, + globalConnect = this._globalConnect, + translate = this._translate + + function createAction(type, group, className, title, options) { + function createListener(event) { + const shape = elementFactory.createShape(assign({ type: type }, options)) + + if (options) { + shape.businessObject.di.isExpanded = options.isExpanded + } + + create.start(event, shape) + } + + const shortType = type.replace(/^bpmn:/, '') + + return { + group: group, + className: className, + title: title || translate('Create {type}', { type: shortType }), + action: { + dragstart: createListener, + click: createListener + } + } + } + + function createSubprocess(event) { + const subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + x: 0, + y: 0, + isExpanded: true + }) + + const startEvent = elementFactory.createShape({ + type: 'bpmn:StartEvent', + x: 40, + y: 82, + parent: subProcess + }) + + create.start(event, [subProcess, startEvent], { + hints: { + autoSelect: [startEvent] + } + }) + } + + function createParticipant(event) { + create.start(event, elementFactory.createParticipantShape()) + } + + assign(actions, { + 'hand-tool': { + group: 'tools', + className: 'bpmn-icon-hand-tool', + title: '激活抓手工具', + // title: translate("Activate the hand tool"), + action: { + click: function (event) { + handTool.activateHand(event) + } + } + }, + 'lasso-tool': { + group: 'tools', + className: 'bpmn-icon-lasso-tool', + title: translate('Activate the lasso tool'), + action: { + click: function (event) { + lassoTool.activateSelection(event) + } + } + }, + 'space-tool': { + group: 'tools', + className: 'bpmn-icon-space-tool', + title: translate('Activate the create/remove space tool'), + action: { + click: function (event) { + spaceTool.activateSelection(event) + } + } + }, + 'global-connect-tool': { + group: 'tools', + className: 'bpmn-icon-connection-multi', + title: translate('Activate the global connect tool'), + action: { + click: function (event) { + globalConnect.toggle(event) + } + } + }, + 'tool-separator': { + group: 'tools', + separator: true + }, + 'create.start-event': createAction( + 'bpmn:StartEvent', + 'event', + 'bpmn-icon-start-event-none', + translate('Create StartEvent') + ), + 'create.intermediate-event': createAction( + 'bpmn:IntermediateThrowEvent', + 'event', + 'bpmn-icon-intermediate-event-none', + translate('Create Intermediate/Boundary Event') + ), + 'create.end-event': createAction( + 'bpmn:EndEvent', + 'event', + 'bpmn-icon-end-event-none', + translate('Create EndEvent') + ), + 'create.exclusive-gateway': createAction( + 'bpmn:ExclusiveGateway', + 'gateway', + 'bpmn-icon-gateway-none', + translate('Create Gateway') + ), + 'create.user-task': createAction( + 'bpmn:UserTask', + 'activity', + 'bpmn-icon-user-task', + translate('Create User Task') + ), + 'create.data-object': createAction( + 'bpmn:DataObjectReference', + 'data-object', + 'bpmn-icon-data-object', + translate('Create DataObjectReference') + ), + 'create.data-store': createAction( + 'bpmn:DataStoreReference', + 'data-store', + 'bpmn-icon-data-store', + translate('Create DataStoreReference') + ), + 'create.subprocess-expanded': { + group: 'activity', + className: 'bpmn-icon-subprocess-expanded', + title: translate('Create expanded SubProcess'), + action: { + dragstart: createSubprocess, + click: createSubprocess + } + }, + 'create.participant-expanded': { + group: 'collaboration', + className: 'bpmn-icon-participant', + title: translate('Create Pool/Participant'), + action: { + dragstart: createParticipant, + click: createParticipant + } + }, + 'create.group': createAction( + 'bpmn:Group', + 'artifact', + 'bpmn-icon-group', + translate('Create Group') + ) + }) + + return actions +} + +CustomPalette.$inject = [ + 'palette', + 'create', + 'elementFactory', + 'spaceTool', + 'lassoTool', + 'handTool', + 'globalConnect', + 'translate' +] + +CustomPalette.prototype = new F() // 核心,将 F的实例赋值给子类; +CustomPalette.prototype.constructor = CustomPalette // 修复子类CustomPalette的构造器指向,防止原型链的混乱; diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/index.js b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/index.js new file mode 100644 index 0000000..8e4f3ac --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/index.js @@ -0,0 +1,22 @@ +// import PaletteModule from "diagram-js/lib/features/palette"; +// import CreateModule from "diagram-js/lib/features/create"; +// import SpaceToolModule from "diagram-js/lib/features/space-tool"; +// import LassoToolModule from "diagram-js/lib/features/lasso-tool"; +// import HandToolModule from "diagram-js/lib/features/hand-tool"; +// import GlobalConnectModule from "diagram-js/lib/features/global-connect"; +// import translate from "diagram-js/lib/i18n/translate"; +// +// import PaletteProvider from "./paletteProvider"; +// +// export default { +// __depends__: [PaletteModule, CreateModule, SpaceToolModule, LassoToolModule, HandToolModule, GlobalConnectModule, translate], +// __init__: ["paletteProvider"], +// paletteProvider: ["type", PaletteProvider] +// }; + +import CustomPalette from './CustomPalette' + +export default { + __init__: ['paletteProvider'], + paletteProvider: ['type', CustomPalette] +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js new file mode 100644 index 0000000..7098981 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js @@ -0,0 +1,213 @@ +import { assign } from 'min-dash' + +/** + * A palette provider for BPMN 2.0 elements. + */ +export default function PaletteProvider( + palette, + create, + elementFactory, + spaceTool, + lassoTool, + handTool, + globalConnect, + translate +) { + this._palette = palette + this._create = create + this._elementFactory = elementFactory + this._spaceTool = spaceTool + this._lassoTool = lassoTool + this._handTool = handTool + this._globalConnect = globalConnect + this._translate = translate + + palette.registerProvider(this) +} + +PaletteProvider.$inject = [ + 'palette', + 'create', + 'elementFactory', + 'spaceTool', + 'lassoTool', + 'handTool', + 'globalConnect', + 'translate' +] + +PaletteProvider.prototype.getPaletteEntries = function () { + const actions = {}, + create = this._create, + elementFactory = this._elementFactory, + spaceTool = this._spaceTool, + lassoTool = this._lassoTool, + handTool = this._handTool, + globalConnect = this._globalConnect, + translate = this._translate + + function createAction(type, group, className, title, options) { + function createListener(event) { + const shape = elementFactory.createShape(assign({ type: type }, options)) + + if (options) { + shape.businessObject.di.isExpanded = options.isExpanded + } + + create.start(event, shape) + } + + const shortType = type.replace(/^bpmn:/, '') + + return { + group: group, + className: className, + title: title || translate('Create {type}', { type: shortType }), + action: { + dragstart: createListener, + click: createListener + } + } + } + + function createSubprocess(event) { + const subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + x: 0, + y: 0, + isExpanded: true + }) + + const startEvent = elementFactory.createShape({ + type: 'bpmn:StartEvent', + x: 40, + y: 82, + parent: subProcess + }) + + create.start(event, [subProcess, startEvent], { + hints: { + autoSelect: [startEvent] + } + }) + } + + function createParticipant(event) { + create.start(event, elementFactory.createParticipantShape()) + } + + assign(actions, { + 'hand-tool': { + group: 'tools', + className: 'bpmn-icon-hand-tool', + title: translate('Activate the hand tool'), + action: { + click: function (event) { + handTool.activateHand(event) + } + } + }, + 'lasso-tool': { + group: 'tools', + className: 'bpmn-icon-lasso-tool', + title: translate('Activate the lasso tool'), + action: { + click: function (event) { + lassoTool.activateSelection(event) + } + } + }, + 'space-tool': { + group: 'tools', + className: 'bpmn-icon-space-tool', + title: translate('Activate the create/remove space tool'), + action: { + click: function (event) { + spaceTool.activateSelection(event) + } + } + }, + 'global-connect-tool': { + group: 'tools', + className: 'bpmn-icon-connection-multi', + title: translate('Activate the global connect tool'), + action: { + click: function (event) { + globalConnect.toggle(event) + } + } + }, + 'tool-separator': { + group: 'tools', + separator: true + }, + 'create.start-event': createAction( + 'bpmn:StartEvent', + 'event', + 'bpmn-icon-start-event-none', + translate('Create StartEvent') + ), + 'create.intermediate-event': createAction( + 'bpmn:IntermediateThrowEvent', + 'event', + 'bpmn-icon-intermediate-event-none', + translate('Create Intermediate/Boundary Event') + ), + 'create.end-event': createAction( + 'bpmn:EndEvent', + 'event', + 'bpmn-icon-end-event-none', + translate('Create EndEvent') + ), + 'create.exclusive-gateway': createAction( + 'bpmn:ExclusiveGateway', + 'gateway', + 'bpmn-icon-gateway-none', + translate('Create Gateway') + ), + 'create.user-task': createAction( + 'bpmn:UserTask', + 'activity', + 'bpmn-icon-user-task', + translate('Create User Task') + ), + 'create.data-object': createAction( + 'bpmn:DataObjectReference', + 'data-object', + 'bpmn-icon-data-object', + translate('Create DataObjectReference') + ), + 'create.data-store': createAction( + 'bpmn:DataStoreReference', + 'data-store', + 'bpmn-icon-data-store', + translate('Create DataStoreReference') + ), + 'create.subprocess-expanded': { + group: 'activity', + className: 'bpmn-icon-subprocess-expanded', + title: translate('Create expanded SubProcess'), + action: { + dragstart: createSubprocess, + click: createSubprocess + } + }, + 'create.participant-expanded': { + group: 'collaboration', + className: 'bpmn-icon-participant', + title: translate('Create Pool/Participant'), + action: { + dragstart: createParticipant, + click: createParticipant + } + }, + 'create.group': createAction( + 'bpmn:Group', + 'artifact', + 'bpmn-icon-group', + translate('Create Group') + ) + }) + + return actions +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/translate/customTranslate.js b/src/components/bpmnProcessDesigner/package/designer/plugins/translate/customTranslate.js new file mode 100644 index 0000000..c1b99e1 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/translate/customTranslate.js @@ -0,0 +1,44 @@ +// import translations from "./zh"; +// +// export default function customTranslate(template, replacements) { +// replacements = replacements || {}; +// +// // Translate +// template = translations[template] || template; +// +// // Replace +// return template.replace(/{([^}]+)}/g, function(_, key) { +// let str = replacements[key]; +// if ( +// translations[replacements[key]] !== null && +// translations[replacements[key]] !== "undefined" +// ) { +// // eslint-disable-next-line no-mixed-spaces-and-tabs +// str = translations[replacements[key]]; +// // eslint-disable-next-line no-mixed-spaces-and-tabs +// } +// return str || "{" + key + "}"; +// }); +// } + +export default function customTranslate(translations) { + return function (template, replacements) { + replacements = replacements || {} + // Translate + template = translations[template] || template + + // Replace + return template.replace(/{([^}]+)}/g, function (_, key) { + let str = replacements[key] + if ( + translations[replacements[key]] !== null && + translations[replacements[key]] !== undefined + ) { + // eslint-disable-next-line no-mixed-spaces-and-tabs + str = translations[replacements[key]] + // eslint-disable-next-line no-mixed-spaces-and-tabs + } + return str || '{' + key + '}' + }) + } +} diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js b/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js new file mode 100644 index 0000000..777db3e --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js @@ -0,0 +1,240 @@ +/** + * This is a sample file that should be replaced with the actual translation. + * + * Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available + * translations and labels to translate. + */ +export default { + // 添加部分 + 'Append EndEvent': '追加结束事件', + 'Append Gateway': '追加网关', + 'Append Task': '追加任务', + 'Append Intermediate/Boundary Event': '追加中间抛出事件/边界事件', + + 'Activate the global connect tool': '激活全局连接工具', + 'Append {type}': '添加 {type}', + 'Add Lane above': '在上面添加道', + 'Divide into two Lanes': '分割成两个道', + 'Divide into three Lanes': '分割成三个道', + 'Add Lane below': '在下面添加道', + 'Append compensation activity': '追加补偿活动', + 'Change type': '修改类型', + 'Connect using Association': '使用关联连接', + 'Connect using Sequence/MessageFlow or Association': '使用顺序/消息流或者关联连接', + 'Connect using DataInputAssociation': '使用数据输入关联连接', + Remove: '移除', + 'Activate the hand tool': '激活抓手工具', + 'Activate the lasso tool': '激活套索工具', + 'Activate the create/remove space tool': '激活创建/删除空间工具', + 'Create expanded SubProcess': '创建扩展子过程', + 'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件', + 'Create Pool/Participant': '创建池/参与者', + 'Parallel Multi Instance': '并行多重事件', + 'Sequential Multi Instance': '时序多重事件', + DataObjectReference: '数据对象参考', + DataStoreReference: '数据存储参考', + Loop: '循环', + 'Ad-hoc': '即席', + 'Create {type}': '创建 {type}', + Task: '任务', + 'Send Task': '发送任务', + 'Receive Task': '接收任务', + 'User Task': '用户任务', + 'Manual Task': '手工任务', + 'Business Rule Task': '业务规则任务', + 'Service Task': '服务任务', + 'Script Task': '脚本任务', + 'Call Activity': '调用活动', + 'Sub Process (collapsed)': '子流程(折叠的)', + 'Sub Process (expanded)': '子流程(展开的)', + 'Start Event': '开始事件', + StartEvent: '开始事件', + 'Intermediate Throw Event': '中间事件', + 'End Event': '结束事件', + EndEvent: '结束事件', + 'Create StartEvent': '创建开始事件', + 'Create EndEvent': '创建结束事件', + 'Create Task': '创建任务', + 'Create User Task': '创建用户任务', + 'Create Gateway': '创建网关', + 'Create DataObjectReference': '创建数据对象', + 'Create DataStoreReference': '创建数据存储', + 'Create Group': '创建分组', + 'Create Intermediate/Boundary Event': '创建中间/边界事件', + 'Message Start Event': '消息开始事件', + 'Timer Start Event': '定时开始事件', + 'Conditional Start Event': '条件开始事件', + 'Signal Start Event': '信号开始事件', + 'Error Start Event': '错误开始事件', + 'Escalation Start Event': '升级开始事件', + 'Compensation Start Event': '补偿开始事件', + 'Message Start Event (non-interrupting)': '消息开始事件(非中断)', + 'Timer Start Event (non-interrupting)': '定时开始事件(非中断)', + 'Conditional Start Event (non-interrupting)': '条件开始事件(非中断)', + 'Signal Start Event (non-interrupting)': '信号开始事件(非中断)', + 'Escalation Start Event (non-interrupting)': '升级开始事件(非中断)', + 'Message Intermediate Catch Event': '消息中间捕获事件', + 'Message Intermediate Throw Event': '消息中间抛出事件', + 'Timer Intermediate Catch Event': '定时中间捕获事件', + 'Escalation Intermediate Throw Event': '升级中间抛出事件', + 'Conditional Intermediate Catch Event': '条件中间捕获事件', + 'Link Intermediate Catch Event': '链接中间捕获事件', + 'Link Intermediate Throw Event': '链接中间抛出事件', + 'Compensation Intermediate Throw Event': '补偿中间抛出事件', + 'Signal Intermediate Catch Event': '信号中间捕获事件', + 'Signal Intermediate Throw Event': '信号中间抛出事件', + 'Message End Event': '消息结束事件', + 'Escalation End Event': '定时结束事件', + 'Error End Event': '错误结束事件', + 'Cancel End Event': '取消结束事件', + 'Compensation End Event': '补偿结束事件', + 'Signal End Event': '信号结束事件', + 'Terminate End Event': '终止结束事件', + 'Message Boundary Event': '消息边界事件', + 'Message Boundary Event (non-interrupting)': '消息边界事件(非中断)', + 'Timer Boundary Event': '定时边界事件', + 'Timer Boundary Event (non-interrupting)': '定时边界事件(非中断)', + 'Escalation Boundary Event': '升级边界事件', + 'Escalation Boundary Event (non-interrupting)': '升级边界事件(非中断)', + 'Conditional Boundary Event': '条件边界事件', + 'Conditional Boundary Event (non-interrupting)': '条件边界事件(非中断)', + 'Error Boundary Event': '错误边界事件', + 'Cancel Boundary Event': '取消边界事件', + 'Signal Boundary Event': '信号边界事件', + 'Signal Boundary Event (non-interrupting)': '信号边界事件(非中断)', + 'Compensation Boundary Event': '补偿边界事件', + 'Exclusive Gateway': '互斥网关', + 'Parallel Gateway': '并行网关', + 'Inclusive Gateway': '相容网关', + 'Complex Gateway': '复杂网关', + 'Event based Gateway': '事件网关', + Transaction: '转运', + 'Sub Process': '子流程', + 'Event Sub Process': '事件子流程', + 'Collapsed Pool': '折叠池', + 'Expanded Pool': '展开池', + + // Errors + 'no parent for {element} in {parent}': '在{parent}里,{element}没有父类', + 'no shape type specified': '没有指定的形状类型', + 'flow elements must be children of pools/participants': '流元素必须是池/参与者的子类', + 'out of bounds release': 'out of bounds release', + 'more than {count} child lanes': '子道大于{count} ', + 'element required': '元素不能为空', + 'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范', + 'no diagram to display': '没有可展示的流程图', + 'no process or collaboration to display': '没有可展示的流程/协作', + 'element {element} referenced by {referenced}#{property} not yet drawn': + '由{referenced}#{property}引用的{element}元素仍未绘制', + 'already rendered {element}': '{element} 已被渲染', + 'failed to import {element}': '导入{element}失败', + //属性面板的参数 + Id: '编号', + Name: '名称', + General: '常规', + Details: '详情', + 'Message Name': '消息名称', + Message: '消息', + Initiator: '创建者', + 'Asynchronous Continuations': '持续异步', + 'Asynchronous Before': '异步前', + 'Asynchronous After': '异步后', + 'Job Configuration': '工作配置', + Exclusive: '排除', + 'Job Priority': '工作优先级', + 'Retry Time Cycle': '重试时间周期', + Documentation: '文档', + 'Element Documentation': '元素文档', + 'History Configuration': '历史配置', + 'History Time To Live': '历史的生存时间', + Forms: '表单', + 'Form Key': '表单key', + 'Form Fields': '表单字段', + 'Business Key': '业务key', + 'Form Field': '表单字段', + ID: '编号', + Type: '类型', + Label: '名称', + 'Default Value': '默认值', + 'Default Flow': '默认流转路径', + 'Conditional Flow': '条件流转路径', + 'Sequence Flow': '普通流转路径', + Validation: '校验', + 'Add Constraint': '添加约束', + Config: '配置', + Properties: '属性', + 'Add Property': '添加属性', + Value: '值', + Listeners: '监听器', + 'Execution Listener': '执行监听', + 'Event Type': '事件类型', + 'Listener Type': '监听器类型', + 'Java Class': 'Java类', + Expression: '表达式', + 'Must provide a value': '必须提供一个值', + 'Delegate Expression': '代理表达式', + Script: '脚本', + 'Script Format': '脚本格式', + 'Script Type': '脚本类型', + 'Inline Script': '内联脚本', + 'External Script': '外部脚本', + Resource: '资源', + 'Field Injection': '字段注入', + Extensions: '扩展', + 'Input/Output': '输入/输出', + 'Input Parameters': '输入参数', + 'Output Parameters': '输出参数', + Parameters: '参数', + 'Output Parameter': '输出参数', + 'Timer Definition Type': '定时器定义类型', + 'Timer Definition': '定时器定义', + Date: '日期', + Duration: '持续', + Cycle: '循环', + Signal: '信号', + 'Signal Name': '信号名称', + Escalation: '升级', + Error: '错误', + 'Link Name': '链接名称', + Condition: '条件名称', + 'Variable Name': '变量名称', + 'Variable Event': '变量事件', + 'Specify more than one variable change event as a comma separated list.': + '多个变量事件以逗号隔开', + 'Wait for Completion': '等待完成', + 'Activity Ref': '活动参考', + 'Version Tag': '版本标签', + Executable: '可执行文件', + 'External Task Configuration': '扩展任务配置', + 'Task Priority': '任务优先级', + External: '外部', + Connector: '连接器', + 'Must configure Connector': '必须配置连接器', + 'Connector Id': '连接器编号', + Implementation: '实现方式', + 'Field Injections': '字段注入', + Fields: '字段', + 'Result Variable': '结果变量', + Topic: '主题', + 'Configure Connector': '配置连接器', + 'Input Parameter': '输入参数', + Assignee: '代理人', + 'Candidate Users': '候选用户', + 'Candidate Groups': '候选组', + 'Due Date': '到期时间', + 'Follow Up Date': '跟踪日期', + Priority: '优先级', + 'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)': + '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00', + 'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)': + '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00', + Variables: '变量', + 'Candidate Starter Configuration': '候选人起动器配置', + 'Candidate Starter Groups': '候选人起动器组', + 'This maps to the process definition key.': '这映射到流程定义键。', + 'Candidate Starter Users': '候选人起动器的用户', + 'Specify more than one user as a comma separated list.': '指定多个用户作为逗号分隔的列表。', + 'Tasklist Configuration': 'Tasklist配置', + Startable: '启动', + 'Specify more than one group as a comma separated list.': '指定多个组作为逗号分隔的列表。' +} diff --git a/src/components/bpmnProcessDesigner/package/index.ts b/src/components/bpmnProcessDesigner/package/index.ts new file mode 100644 index 0000000..ce44a3c --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/index.ts @@ -0,0 +1,11 @@ +import MyProcessDesigner from './designer' +import MyProcessPenal from './penal' +import MyProcessViewer from './designer/index2' + +import './theme/index.scss' +import 'bpmn-js/dist/assets/diagram-js.css' +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css' +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css' +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css' + +export { MyProcessDesigner, MyProcessPenal, MyProcessViewer } diff --git a/src/components/bpmnProcessDesigner/package/palette/ProcessPalette.vue b/src/components/bpmnProcessDesigner/package/palette/ProcessPalette.vue new file mode 100644 index 0000000..38e4ba1 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/palette/ProcessPalette.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue new file mode 100644 index 0000000..74d8b00 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue @@ -0,0 +1,208 @@ + + diff --git a/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue b/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue new file mode 100644 index 0000000..e2bfecb --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue @@ -0,0 +1,181 @@ + + diff --git a/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue b/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue new file mode 100644 index 0000000..2429bc9 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue @@ -0,0 +1,189 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue new file mode 100644 index 0000000..198f0f3 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue @@ -0,0 +1,463 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/index.js b/src/components/bpmnProcessDesigner/package/penal/index.js new file mode 100644 index 0000000..7fa5617 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/index.js @@ -0,0 +1,7 @@ +import MyPropertiesPanel from './PropertiesPanel.vue' + +MyPropertiesPanel.install = function (Vue) { + Vue.component(MyPropertiesPanel.name, MyPropertiesPanel) +} + +export default MyPropertiesPanel diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue new file mode 100644 index 0000000..5c2db5a --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue @@ -0,0 +1,400 @@ + + diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue new file mode 100644 index 0000000..e4201b5 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue @@ -0,0 +1,448 @@ + + diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/template.js b/src/components/bpmnProcessDesigner/package/penal/listeners/template.js new file mode 100644 index 0000000..430dc64 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/template.js @@ -0,0 +1,178 @@ +export const template = (isTaskListener) => { + return ` +
+ + + + + + + + +
+ 添加监听器 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + ${ + isTaskListener + ? "" + + "" + + "" + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + '' + : '' + } + + +

+ 注入字段: + 添加字段 +

+ + + + + + + + + + +
+ 取 消 + 保 存 +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ ` +} diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts b/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts new file mode 100644 index 0000000..5f46abd --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts @@ -0,0 +1,62 @@ +// 初始化表单数据 +export function initListenerForm(listener) { + let self = { + ...listener + } + if (listener.script) { + self = { + ...listener, + ...listener.script, + scriptType: listener.script.resource ? 'externalScript' : 'inlineScript' + } + } + if (listener.event === 'timeout' && listener.eventDefinitions) { + if (listener.eventDefinitions.length) { + let k = '' + for (const key in listener.eventDefinitions[0]) { + console.log(listener.eventDefinitions, key) + if (key.indexOf('time') !== -1) { + k = key + self.eventDefinitionType = key.replace('time', '').toLowerCase() + } + } + console.log(k) + self.eventTimeDefinitions = listener.eventDefinitions[0][k].body + } + } + return self +} + +export function initListenerType(listener) { + let listenerType + if (listener.class) listenerType = 'classListener' + if (listener.expression) listenerType = 'expressionListener' + if (listener.delegateExpression) listenerType = 'delegateExpressionListener' + if (listener.script) listenerType = 'scriptListener' + return { + ...JSON.parse(JSON.stringify(listener)), + ...(listener.script ?? {}), + listenerType: listenerType + } +} + +export const listenerType = { + classListener: 'Java 类', + expressionListener: '表达式', + delegateExpressionListener: '代理表达式', + scriptListener: '脚本' +} + +export const eventType = { + create: '创建', + assignment: '指派', + complete: '完成', + delete: '删除', + update: '更新', + timeout: '超时' +} + +export const fieldType = { + string: '字符串', + expression: '表达式' +} diff --git a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue new file mode 100644 index 0000000..2e5bc8a --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue @@ -0,0 +1,252 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/other/ElementOtherConfig.vue b/src/components/bpmnProcessDesigner/package/penal/other/ElementOtherConfig.vue new file mode 100644 index 0000000..2c55481 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/other/ElementOtherConfig.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue new file mode 100644 index 0000000..f1670ff --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue @@ -0,0 +1,167 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue b/src/components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue new file mode 100644 index 0000000..2d47b8f --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue @@ -0,0 +1,111 @@ + + diff --git a/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue new file mode 100644 index 0000000..23e750a --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/ReceiveTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ReceiveTask.vue new file mode 100644 index 0000000..ef2ba0b --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ReceiveTask.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue new file mode 100644 index 0000000..bacdec0 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue @@ -0,0 +1,98 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue new file mode 100644 index 0000000..725335e --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue @@ -0,0 +1,97 @@ + + + diff --git a/src/components/bpmnProcessDesigner/package/theme/element-variables.scss b/src/components/bpmnProcessDesigner/package/theme/element-variables.scss new file mode 100644 index 0000000..49bd326 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/theme/element-variables.scss @@ -0,0 +1,70 @@ +/* 改变主题色变量 */ +$--color-primary: #1890ff; +$--color-danger: #ff4d4f; + +/* 改变 icon 字体路径变量,必需 */ +$--font-path: '~element-ui/lib/theme-chalk/fonts'; + +@import '~element-ui/packages/theme-chalk/src/index'; + +.el-table td, +.el-table th { + color: #333; +} +.el-drawer__header { + padding: 16px 16px 8px 16px; + margin: 0; + line-height: 24px; + font-size: 18px; + color: #303133; + box-sizing: border-box; + border-bottom: 1px solid #e8e8e8; +} +div[class^='el-drawer']:focus, +span:focus { + outline: none; +} +.el-drawer__body { + box-sizing: border-box; + padding: 16px; + width: 100%; + overflow-y: auto; +} + +.el-dialog { + margin-top: 50vh !important; + transform: translateY(-50%); + overflow: hidden; +} +.el-dialog__wrapper { + overflow: hidden; + max-height: 100vh; +} +.el-dialog__header { + padding: 16px 16px 8px 16px; + box-sizing: border-box; + border-bottom: 1px solid #e8e8e8; +} +.el-dialog__body { + padding: 16px; + max-height: 80vh; + box-sizing: border-box; + overflow-y: auto; +} +.el-dialog__footer { + padding: 16px; + box-sizing: border-box; + border-top: 1px solid #e8e8e8; +} +.el-dialog__close { + font-weight: 600; +} +.el-select { + width: 100%; +} +.el-divider:not(.el-divider--horizontal) { + margin: 0 8px; +} +.el-divider.el-divider--horizontal { + margin: 16px 0; +} diff --git a/src/components/bpmnProcessDesigner/package/theme/index.scss b/src/components/bpmnProcessDesigner/package/theme/index.scss new file mode 100644 index 0000000..2e60fad --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/theme/index.scss @@ -0,0 +1,2 @@ +@import './process-designer.scss'; +@import './process-panel.scss'; diff --git a/src/components/bpmnProcessDesigner/package/theme/process-designer.scss b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss new file mode 100644 index 0000000..6af945d --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss @@ -0,0 +1,161 @@ +@import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css'; +@import 'bpmn-js-token-simulation/assets/css/font-awesome.min.css'; +@import 'bpmn-js-token-simulation/assets/css/normalize.css'; + +// 边框被 token-simulation 样式覆盖了 +.djs-palette { + background: var(--palette-background-color); + border: solid 1px var(--palette-border-color) !important; + border-radius: 2px; +} + +.my-process-designer { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + box-sizing: border-box; + .my-process-designer__header { + width: 100%; + min-height: 36px; + .el-button { + text-align: center; + } + .el-button-group { + margin: 4px; + } + .el-tooltip__popper { + .el-button { + width: 100%; + text-align: left; + padding-left: 8px; + padding-right: 8px; + } + .el-button:hover { + background: rgba(64, 158, 255, 0.8); + color: #ffffff; + } + } + .align { + position: relative; + i { + &:after { + content: '|'; + position: absolute; + // transform: rotate(90deg) translate(200%, 60%); + transform: rotate(180deg) translate(271%, -10%); + } + } + } + .align.align-left i { + transform: rotate(90deg); + } + .align.align-right i { + transform: rotate(-90deg); + } + .align.align-top i { + transform: rotate(180deg); + } + .align.align-bottom i { + transform: rotate(0deg); + } + .align.align-center i { + transform: rotate(0deg); + &:after { + // transform: rotate(90deg) translate(0, 60%); + transform: rotate(0deg) translate(-0%, -5%); + } + } + .align.align-middle i { + transform: rotate(-90deg); + &:after { + // transform: rotate(90deg) translate(0, 60%); + transform: rotate(0deg) translate(0, -10%); + } + } + } + .my-process-designer__container { + display: inline-flex; + width: 100%; + flex: 1; + .my-process-designer__canvas { + flex: 1; + height: 100%; + position: relative; + background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') + repeat !important; + div.toggle-mode { + display: none; + } + } + .my-process-designer__property-panel { + height: 100%; + overflow: scroll; + overflow-y: auto; + z-index: 10; + * { + box-sizing: border-box; + } + } + svg { + width: 100%; + height: 100%; + min-height: 100%; + overflow: hidden; + } + } +} + +//侧边栏配置 +// .djs-palette .two-column .open { +.open { + // .djs-palette.open { + .djs-palette-entries { + div[class^='bpmn-icon-']:before, + div[class*='bpmn-icon-']:before { + line-height: unset; + } + div.entry { + position: relative; + } + div.entry:hover { + &::after { + width: max-content; + content: attr(title); + vertical-align: text-bottom; + position: absolute; + right: -10px; + top: 0; + bottom: 0; + overflow: hidden; + transform: translateX(100%); + font-size: 0.5em; + display: inline-block; + text-decoration: inherit; + font-variant: normal; + text-transform: none; + background: #fafafa; + box-shadow: 0 0 6px #eeeeee; + border: 1px solid #cccccc; + box-sizing: border-box; + padding: 0 16px; + border-radius: 4px; + z-index: 100; + } + } + } +} +pre { + margin: 0; + height: 100%; + overflow: hidden; + max-height: calc(80vh - 32px); + overflow-y: auto; +} +.hljs { + word-break: break-word; + white-space: pre-wrap; +} +.hljs * { + font-family: Consolas, Monaco, monospace; +} diff --git a/src/components/bpmnProcessDesigner/package/theme/process-panel.scss b/src/components/bpmnProcessDesigner/package/theme/process-panel.scss new file mode 100644 index 0000000..f840cdd --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/theme/process-panel.scss @@ -0,0 +1,107 @@ +.process-panel__container { + box-sizing: border-box; + padding: 0 8px; + border-left: 1px solid #eeeeee; + box-shadow: 0 0 8px #cccccc; + max-height: 100%; + overflow-y: scroll; +} +.panel-tab__title { + font-weight: 600; + padding: 0 8px; + font-size: 1.1em; + line-height: 1.2em; + i { + margin-right: 8px; + font-size: 1.2em; + } +} +.panel-tab__content { + width: 100%; + box-sizing: border-box; + border-top: 1px solid #eeeeee; + padding: 8px 16px; + .panel-tab__content--title { + display: flex; + justify-content: space-between; + padding-bottom: 8px; + span { + flex: 1; + text-align: left; + } + } +} +.element-property { + width: 100%; + display: flex; + align-items: flex-start; + margin: 8px 0; + .element-property__label { + display: block; + width: 90px; + text-align: right; + overflow: hidden; + padding-right: 12px; + line-height: 32px; + font-size: 14px; + box-sizing: border-box; + } + .element-property__value { + flex: 1; + line-height: 32px; + } + .el-form-item { + width: 100%; + margin-bottom: 0; + padding-bottom: 18px; + } +} +.list-property { + flex-direction: column; + .element-listener-item { + width: 100%; + display: inline-grid; + grid-template-columns: 16px auto 32px 32px; + grid-column-gap: 8px; + } + .element-listener-item + .element-listener-item { + margin-top: 8px; + } +} +.listener-filed__title { + display: inline-flex; + width: 100%; + justify-content: space-between; + align-items: center; + margin-top: 0; + span { + width: 200px; + text-align: left; + font-size: 14px; + } + i { + margin-right: 8px; + } +} +.element-drawer__button { + margin-top: 8px; + width: 100%; + display: inline-flex; + justify-content: space-around; +} +.element-drawer__button > .el-button { + width: 100%; +} + +.el-collapse-item__content { + padding-bottom: 0; +} +.el-input.is-disabled .el-input__inner { + color: #999999; +} +.el-form-item.el-form-item--mini { + margin-bottom: 0; + & + .el-form-item { + margin-top: 16px; + } +} diff --git a/src/components/bpmnProcessDesigner/package/utils.ts b/src/components/bpmnProcessDesigner/package/utils.ts new file mode 100644 index 0000000..bb6c5d5 --- /dev/null +++ b/src/components/bpmnProcessDesigner/package/utils.ts @@ -0,0 +1,77 @@ +import { toRaw } from 'vue' +const bpmnInstances = () => (window as any)?.bpmnInstances +// 创建监听器实例 +export function createListenerObject(options, isTask, prefix) { + const listenerObj = Object.create(null) + listenerObj.event = options.event + isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段 + switch (options.listenerType) { + case 'scriptListener': + listenerObj.script = createScriptObject(options, prefix) + break + case 'expressionListener': + listenerObj.expression = options.expression + break + case 'delegateExpressionListener': + listenerObj.delegateExpression = options.delegateExpression + break + default: + listenerObj.class = options.class + } + // 注入字段 + if (options.fields) { + listenerObj.fields = options.fields.map((field) => { + return createFieldObject(field, prefix) + }) + } + // 任务监听器的 定时器 设置 + if (isTask && options.event === 'timeout' && !!options.eventDefinitionType) { + const timeDefinition = bpmnInstances().moddle.create('bpmn:FormalExpression', { + body: options.eventTimeDefinitions + }) + const TimerEventDefinition = bpmnInstances().moddle.create('bpmn:TimerEventDefinition', { + id: `TimerEventDefinition_${uuid(8)}`, + [`time${options.eventDefinitionType.replace(/^\S/, (s) => s.toUpperCase())}`]: timeDefinition + }) + listenerObj.eventDefinitions = [TimerEventDefinition] + } + return bpmnInstances().moddle.create( + `${prefix}:${isTask ? 'TaskListener' : 'ExecutionListener'}`, + listenerObj + ) +} + +// 创建 监听器的注入字段 实例 +export function createFieldObject(option, prefix) { + const { name, fieldType, string, expression } = option + const fieldConfig = fieldType === 'string' ? { name, string } : { name, expression } + return bpmnInstances().moddle.create(`${prefix}:Field`, fieldConfig) +} + +// 创建脚本实例 +export function createScriptObject(options, prefix) { + const { scriptType, scriptFormat, value, resource } = options + const scriptConfig = + scriptType === 'inlineScript' ? { scriptFormat, value } : { scriptFormat, resource } + return bpmnInstances().moddle.create(`${prefix}:Script`, scriptConfig) +} + +// 更新元素扩展属性 +export function updateElementExtensions(element, extensionList) { + const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', { + values: extensionList + }) + bpmnInstances().modeling.updateProperties(toRaw(element), { + extensionElements: extensions + }) +} + +// 创建一个id +export function uuid(length = 8, chars?) { + let result = '' + const charsString = chars || '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + for (let i = length; i > 0; --i) { + result += charsString[Math.floor(Math.random() * charsString.length)] + } + return result +} diff --git a/src/components/bpmnProcessDesigner/src/highlight/index.js b/src/components/bpmnProcessDesigner/src/highlight/index.js new file mode 100644 index 0000000..5df38c9 --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/highlight/index.js @@ -0,0 +1,5 @@ +const hljs = require('highlight.js/lib/core') +hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml')) +hljs.registerLanguage('json', require('highlight.js/lib/languages/json')) + +module.exports = hljs diff --git a/src/components/bpmnProcessDesigner/src/modules/custom-renderer/CustomRenderer.js b/src/components/bpmnProcessDesigner/src/modules/custom-renderer/CustomRenderer.js new file mode 100644 index 0000000..e876031 --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/modules/custom-renderer/CustomRenderer.js @@ -0,0 +1,14 @@ +import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer' + +export default function CustomRenderer(config, eventBus, styles, pathMap, canvas, textRenderer) { + BpmnRenderer.call(this, config, eventBus, styles, pathMap, canvas, textRenderer, 2000) + + this.handlers['label'] = function () { + return null + } +} + +const F = function () {} // 核心,利用空对象作为中介; +F.prototype = BpmnRenderer.prototype // 核心,将父类的原型赋值给空对象F; +CustomRenderer.prototype = new F() // 核心,将 F的实例赋值给子类; +CustomRenderer.prototype.constructor = CustomRenderer // 修复子类CustomRenderer的构造器指向,防止原型链的混乱; diff --git a/src/components/bpmnProcessDesigner/src/modules/custom-renderer/index.js b/src/components/bpmnProcessDesigner/src/modules/custom-renderer/index.js new file mode 100644 index 0000000..79d8bd0 --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/modules/custom-renderer/index.js @@ -0,0 +1,6 @@ +import CustomRenderer from './CustomRenderer' + +export default { + __init__: ['customRenderer'], + customRenderer: ['type', CustomRenderer] +} diff --git a/src/components/bpmnProcessDesigner/src/modules/rules/CustomRules.js b/src/components/bpmnProcessDesigner/src/modules/rules/CustomRules.js new file mode 100644 index 0000000..9fa1d14 --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/modules/rules/CustomRules.js @@ -0,0 +1,16 @@ +import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules' +import inherits from 'inherits' + +export default function CustomRules(eventBus) { + BpmnRules.call(this, eventBus) +} + +inherits(CustomRules, BpmnRules) + +CustomRules.prototype.canDrop = function () { + return false +} + +CustomRules.prototype.canMove = function () { + return false +} diff --git a/src/components/bpmnProcessDesigner/src/modules/rules/index.js b/src/components/bpmnProcessDesigner/src/modules/rules/index.js new file mode 100644 index 0000000..12cf05a --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/modules/rules/index.js @@ -0,0 +1,6 @@ +import CustomRules from './CustomRules' + +export default { + __init__: ['customRules'], + customRules: ['type', CustomRules] +} diff --git a/src/components/bpmnProcessDesigner/src/translations.ts b/src/components/bpmnProcessDesigner/src/translations.ts new file mode 100644 index 0000000..5f9b9a5 --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/translations.ts @@ -0,0 +1,25 @@ +/** + * This is a sample file that should be replaced with the actual translation. + * + * Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available + * translations and labels to translate. + */ +export default { + 'Exclusive Gateway': 'Exklusives Gateway', + 'Parallel Gateway': 'Paralleles Gateway', + 'Inclusive Gateway': 'Inklusives Gateway', + 'Complex Gateway': 'Komplexes Gateway', + 'Event based Gateway': 'Ereignis-basiertes Gateway', + 'Message Start Event': '消息启动事件', + 'Timer Start Event': '定时启动事件', + 'Conditional Start Event': '条件启动事件', + 'Signal Start Event': '信号启动事件', + 'Error Start Event': '错误启动事件', + 'Escalation Start Event': '升级启动事件', + 'Compensation Start Event': '补偿启动事件', + 'Message Start Event (non-interrupting)': '消息启动事件 (非中断)', + 'Timer Start Event (non-interrupting)': '定时启动事件 (非中断)', + 'Conditional Start Event (non-interrupting)': '条件启动事件 (非中断)', + 'Signal Start Event (non-interrupting)': '信号启动事件 (非中断)', + 'Escalation Start Event (non-interrupting)': '升级启动事件 (非中断)' +} diff --git a/src/components/bpmnProcessDesigner/src/utils/directive/clickOutSide.js b/src/components/bpmnProcessDesigner/src/utils/directive/clickOutSide.js new file mode 100644 index 0000000..bb71d44 --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/utils/directive/clickOutSide.js @@ -0,0 +1,39 @@ +//outside.js + +const ctx = '@@clickoutsideContext' + +export default { + bind(el, binding, vnode) { + const ele = el + const documentHandler = (e) => { + if (!vnode.context || ele.contains(e.target)) { + return false + } + // 调用指令回调 + if (binding.expression) { + vnode.context[el[ctx].methodName](e) + } else { + el[ctx].bindingFn(e) + } + } + // 将方法添加到ele + ele[ctx] = { + documentHandler, + methodName: binding.expression, + bindingFn: binding.value + } + + setTimeout(() => { + document.addEventListener('touchstart', documentHandler) // 为document绑定事件 + }) + }, + update(el, binding) { + const ele = el + ele[ctx].methodName = binding.expression + ele[ctx].bindingFn = binding.value + }, + unbind(el) { + document.removeEventListener('touchstart', el[ctx].documentHandler) // 解绑 + delete el[ctx] + } +} diff --git a/src/components/bpmnProcessDesigner/src/utils/index.js b/src/components/bpmnProcessDesigner/src/utils/index.js new file mode 100644 index 0000000..7d970ec --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/utils/index.js @@ -0,0 +1,10 @@ +export function debounce(fn, delay = 500) { + let timer + return function (...args) { + if (timer) { + clearTimeout(timer) + timer = null + } + timer = setTimeout(fn.bind(this, ...args), delay) + } +} diff --git a/src/components/bpmnProcessDesigner/src/utils/xml2json.js b/src/components/bpmnProcessDesigner/src/utils/xml2json.js new file mode 100644 index 0000000..fe1a52f --- /dev/null +++ b/src/components/bpmnProcessDesigner/src/utils/xml2json.js @@ -0,0 +1,50 @@ +function xmlStr2XmlObj(xmlStr) { + let xmlObj = {} + if (document.all) { + const xmlDom = new window.ActiveXObject('Microsoft.XMLDOM') + xmlDom.loadXML(xmlStr) + xmlObj = xmlDom + } else { + xmlObj = new DOMParser().parseFromString(xmlStr, 'text/xml') + } + return xmlObj +} + +function xml2json(xml) { + try { + let obj = {} + if (xml.children.length > 0) { + for (let i = 0; i < xml.children.length; i++) { + const item = xml.children.item(i) + const nodeName = item.nodeName + if (typeof obj[nodeName] == 'undefined') { + obj[nodeName] = xml2json(item) + } else { + if (typeof obj[nodeName].push == 'undefined') { + const old = obj[nodeName] + obj[nodeName] = [] + obj[nodeName].push(old) + } + obj[nodeName].push(xml2json(item)) + } + } + } else { + obj = xml.textContent + } + return obj + } catch (e) { + console.log(e.message) + } +} + +function xmlObj2json(xml) { + const xmlObj = xmlStr2XmlObj(xml) + console.log(xmlObj) + let jsonObj = {} + if (xmlObj.childNodes.length > 0) { + jsonObj = xml2json(xmlObj) + } + return jsonObj +} + +export default xmlObj2json diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..4d030c3 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,6 @@ +import type { App } from 'vue' +import { Icon } from './Icon' + +export const setupGlobCom = (app: App): void => { + app.component('Icon', Icon) +} diff --git a/src/config/axios/config.ts b/src/config/axios/config.ts new file mode 100644 index 0000000..079df83 --- /dev/null +++ b/src/config/axios/config.ts @@ -0,0 +1,28 @@ +const config: { + base_url: string + result_code: number | string + default_headers: AxiosHeaders + request_timeout: number +} = { + /** + * api请求基础路径 + */ + base_url: import.meta.env.VITE_API_BASEPATH, + /** + * 接口成功返回状态码 + */ + result_code: 200, + + /** + * 接口请求超时时间 + */ + request_timeout: 30000, + + /** + * 默认接口请求类型 + * 可选值:application/x-www-form-urlencoded multipart/form-data + */ + default_headers: 'application/json' +} + +export { config } diff --git a/src/config/axios/errorCode.ts b/src/config/axios/errorCode.ts new file mode 100644 index 0000000..94d719f --- /dev/null +++ b/src/config/axios/errorCode.ts @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + default: '系统未知错误,请反馈给管理员' +} diff --git a/src/config/axios/index.ts b/src/config/axios/index.ts new file mode 100644 index 0000000..f857d5a --- /dev/null +++ b/src/config/axios/index.ts @@ -0,0 +1,51 @@ +import { service } from './service' + +import { config } from './config' + +const { default_headers } = config + +const request = (option: any) => { + const { url, method, params, data, headersType, responseType, isSubmitForm } = option + return service({ + url: url, + method, + isSubmitForm, + params, + data, + responseType: responseType, + headers: { + 'Content-Type': headersType || default_headers + } + }) +} +export default { + get: async (option: any) => { + const res = await request({ method: 'GET', ...option }) + return res.data as unknown as T + }, + post: async (option: any) => { + const res = await request({ method: 'POST', ...option }) + return res.data as unknown as T + }, + postOriginal: async (option: any) => { + const res = await request({ method: 'POST', ...option }) + return res + }, + delete: async (option: any) => { + const res = await request({ method: 'DELETE', ...option }) + return res.data as unknown as T + }, + put: async (option: any) => { + const res = await request({ method: 'PUT', ...option }) + return res.data as unknown as T + }, + download: async (option: any) => { + const res = await request({ method: 'GET', responseType: 'blob', ...option }) + return res as unknown as Promise + }, + upload: async (option: any) => { + option.headersType = 'multipart/form-data' + const res = await request({ method: 'POST', ...option }) + return res as unknown as Promise + } +} diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts new file mode 100644 index 0000000..f3d7a71 --- /dev/null +++ b/src/config/axios/service.ts @@ -0,0 +1,276 @@ +import axios, { + AxiosError, + AxiosInstance, + AxiosRequestHeaders, + AxiosResponse, + InternalAxiosRequestConfig +} from 'axios' + +import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' +import qs from 'qs' +import { config } from '@/config/axios/config' +import { + getAccessToken, + getRefreshToken, + getTenantId, + getAppId, + removeToken, + setToken +} from '@/utils/auth' +import errorCode from './errorCode' + +import { resetRouter } from '@/router' +import { useCache } from '@/hooks/web/useCache' +import cache from '@/plugins/cache' + +const { result_code, base_url, request_timeout } = config + +// 需要忽略的提示。忽略后,自动 Promise.reject('error') +const ignoreMsgs = [ + '无效的刷新令牌', // 刷新令牌被删除时,不用提示 + '刷新令牌已过期' // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面 +] +// 是否显示重新登录 +export const isRelogin = { show: false } +// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 +// 请求队列 +let requestList: any[] = [] +// 是否正在刷新中 +let isRefreshToken = false +// 请求白名单,无须token的接口 +const whiteList: string[] = ['/login', '/refresh-token'] + +// 创建axios实例 +const service: AxiosInstance = axios.create({ + baseURL: base_url, // api 的 base_url + timeout: request_timeout, // 请求超时时间 + withCredentials: false // 禁用 Cookie 等信息 +}) + +// request拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // 是否需要设置 token + let isToken = (config!.headers || {}).isToken === false + whiteList.some((v) => { + if (config.url) { + config.url.indexOf(v) > -1 + return (isToken = false) + } + }) + if (getAccessToken() && !isToken) { + ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token + } + // 设置租户 + const tenantId = getTenantId() + if (tenantId) (config as Recordable).headers['tenant-id'] = tenantId + + // 设置实例 + const appId = getAppId() + if (appId) (config as Recordable).headers['instance-id'] = appId + + const params = config.params || {} + const data = config.data || false + if ( + config.method?.toUpperCase() === 'POST' && + (config.headers as AxiosRequestHeaders)['Content-Type'] === + 'application/x-www-form-urlencoded' + ) { + config.data = qs.stringify(data) + } + // get参数编码 + if (config.method?.toUpperCase() === 'GET' && params) { + config.params = {} + const paramsStr = qs.stringify(params, { allowDots: true }) + if (paramsStr) { + config.url = config.url + '?' + paramsStr + } + } + // 是否提交表单型的接口,如果是,不要频繁调接口 + if (config?.isSubmitForm) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const sessionObj = cache.session.get('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.set('sessionObj', requestObj) + } else { + const s_url = sessionObj.url // 请求地址 + const s_data = sessionObj.data // 请求数据 + const s_time = sessionObj.time // 请求时间 + const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交 + if ( + s_data === requestObj.data && + requestObj.time - s_time < interval && + s_url === requestObj.url + ) { + const message = '数据正在处理,请勿重复提交' + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.set('sessionObj', requestObj) + } + } + } + return config + }, + (error: AxiosError) => { + // Do something with request error + console.log(error) // for debug + Promise.reject(error) + } +) + +// response 拦截器 +service.interceptors.response.use( + async (response: AxiosResponse) => { + const { data } = response + const config = response.config + if (!data) { + // 返回“[HTTP]请求没有返回值”; + throw new Error() + } + const { t } = useI18n() + // 未设置状态码则默认成功状态 + const code = data.code || result_code + // 二进制数据则直接返回 + if ( + response.request.responseType === 'blob' || + response.request.responseType === 'arraybuffer' + ) { + return response.data + } + // 获取错误信息 + const msg = data.msg || errorCode[code] || errorCode['default'] + if (ignoreMsgs.indexOf(msg) !== -1) { + // 如果是忽略的错误码,直接返回 msg 异常 + return Promise.reject(msg) + } else if (code === 401) { + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 + if (!isRefreshToken) { + isRefreshToken = true + // 1. 如果获取不到刷新令牌,则只能执行登出操作 + if (!getRefreshToken()) { + return handleAuthorized() + } + // 2. 进行刷新访问令牌 + try { + const refreshTokenRes = await refreshToken() + // 2.1 刷新成功,则回放队列的请求 + 当前请求 + setToken((await refreshTokenRes).data.data) + config.headers!.Authorization = 'Bearer ' + getAccessToken() + requestList.forEach((cb: any) => { + cb() + }) + requestList = [] + return service(config) + } catch (e) { + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 + // 2.2 刷新失败,只回放队列的请求 + requestList.forEach((cb: any) => { + cb() + }) + // 提示是否要登出。即不回放当前请求!不然会形成递归 + return handleAuthorized() + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 添加到队列,等待刷新获取到新的令牌 + return new Promise((resolve) => { + requestList.push(() => { + config.headers!.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(service(config)) + }) + }) + } + } else if (code === 500) { + ElMessage.error(t(msg || 'sys.api.errMsg500')) + return Promise.reject(new Error(msg)) + } else if (code === 901) { + ElMessage.error({ + offset: 300, + dangerouslyUseHTMLString: true, + message: + '
' + + t('sys.api.errMsg901') + + '
' + + '
 
' + + '
5 分钟搭建本地环境
' + }) + return Promise.reject(new Error(msg)) + } else if (code !== 200) { + if (msg === '无效的刷新令牌') { + // hard coding:忽略这个提示,直接登出 + console.log(msg) + } else { + ElNotification.error({ title: msg }) + if (code == 403) { + ElMessageBox.confirm(`${msg},是否重新登陆`, '提示', { + type: 'warning' + }).then(() => { + // 无访问权限,退出登录 + const { wsCache } = useCache() + const tenantId = wsCache.get('TENANT_ID') + const appId = wsCache.get('App_ID') + resetRouter() // 重置静态路由表 + wsCache.clear() + removeToken() + window.location.href = `/oa/login?tenantId=${tenantId}&appId=${appId}` + }) + } + } + return Promise.reject('error') + } else { + return data + } + }, + (error: AxiosError) => { + console.log('err' + error) // for debug + let { message } = error + const { t } = useI18n() + if (message === 'Network Error') { + message = t('sys.api.errorMessage') + } else if (message.includes('timeout')) { + message = t('sys.api.apiTimeoutMessage') + } else if (message.includes('Request failed with status code')) { + message = t('sys.api.apiRequestFailed') + message.substr(message.length - 3) + } + ElMessage.error(message) + return Promise.reject(error) + } +) + +const refreshToken = async () => { + axios.defaults.headers.common['tenant-id'] = getTenantId() + return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken()) +} +const handleAuthorized = () => { + const { t } = useI18n() + if (!isRelogin.show) { + isRelogin.show = true + ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), { + showCancelButton: false, + closeOnClickModal: false, + showClose: false, + confirmButtonText: t('login.relogin'), + type: 'warning' + }).then(() => { + const { wsCache } = useCache() + const tenantId = wsCache.get('TENANT_ID') + const appId = wsCache.get('App_ID') + resetRouter() // 重置静态路由表 + wsCache.clear() + removeToken() + isRelogin.show = false + // 干掉token后再走一次路由让它过router.beforeEach的校验 + // window.location.href = window.location.href + window.location.href = `/oa/login?tenantId=${tenantId}&appId=${appId}` + }) + } + return Promise.reject(t('sys.api.timeoutMessage')) +} +export { service } diff --git a/src/directives/index.ts b/src/directives/index.ts new file mode 100644 index 0000000..89cc8ba --- /dev/null +++ b/src/directives/index.ts @@ -0,0 +1,13 @@ +import type { App } from 'vue' +import { hasRole } from './permission/hasRole' +import { hasPermi } from './permission/hasPermi' + +/** + * 导出指令:v-xxx + * @methods hasRole 用户权限,用法: v-hasRole + * @methods hasPermi 按钮权限,用法: v-hasPermi + */ +export const setupAuth = (app: App) => { + hasRole(app) + hasPermi(app) +} diff --git a/src/directives/permission/hasPermi.ts b/src/directives/permission/hasPermi.ts new file mode 100644 index 0000000..d86d2f5 --- /dev/null +++ b/src/directives/permission/hasPermi.ts @@ -0,0 +1,27 @@ +import type { App } from 'vue' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' + +const { t } = useI18n() // 国际化 + +export function hasPermi(app: App) { + app.directive('hasPermi', (el, binding) => { + const { wsCache } = useCache() + const { value } = binding + const all_permission = '*:*:*' + const permissions = wsCache.get(CACHE_KEY.USER).permissions + + if (value && value instanceof Array && value.length > 0) { + const permissionFlag = value + + const hasPermissions = permissions.some((permission: string) => { + return all_permission === permission || permissionFlag.includes(permission) + }) + + if (!hasPermissions) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error(t('permission.hasPermission')) + } + }) +} diff --git a/src/directives/permission/hasRole.ts b/src/directives/permission/hasRole.ts new file mode 100644 index 0000000..31a352a --- /dev/null +++ b/src/directives/permission/hasRole.ts @@ -0,0 +1,27 @@ +import type { App } from 'vue' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' + +const { t } = useI18n() // 国际化 + +export function hasRole(app: App) { + app.directive('hasRole', (el, binding) => { + const { wsCache } = useCache() + const { value } = binding + const super_admin = 'admin' + const roles = wsCache.get(CACHE_KEY.USER).roles + + if (value && value instanceof Array && value.length > 0) { + const roleFlag = value + + const hasRole = roles.some((role: string) => { + return super_admin === role || roleFlag.includes(role) + }) + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error(t('permission.hasRole')) + } + }) +} diff --git a/src/hooks/event/useScrollTo.ts b/src/hooks/event/useScrollTo.ts new file mode 100644 index 0000000..92aec87 --- /dev/null +++ b/src/hooks/event/useScrollTo.ts @@ -0,0 +1,60 @@ +export interface ScrollToParams { + el: HTMLElement + to: number + position: string + duration?: number + callback?: () => void +} + +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2 + if (t < 1) { + return (c / 2) * t * t + b + } + t-- + return (-c / 2) * (t * (t - 2) - 1) + b +} +const move = (el: HTMLElement, position: string, amount: number) => { + el[position] = amount +} + +export function useScrollTo({ + el, + position = 'scrollLeft', + to, + duration = 500, + callback +}: ScrollToParams) { + const isActiveRef = ref(false) + const start = el[position] + const change = to - start + const increment = 20 + let currentTime = 0 + + function animateScroll() { + if (!unref(isActiveRef)) { + return + } + currentTime += increment + const val = easeInOutQuad(currentTime, start, change, duration) + move(el, position, val) + if (currentTime < duration && unref(isActiveRef)) { + requestAnimationFrame(animateScroll) + } else { + if (callback) { + callback() + } + } + } + + function run() { + isActiveRef.value = true + animateScroll() + } + + function stop() { + isActiveRef.value = false + } + + return { start: run, stop } +} diff --git a/src/hooks/web/useCache.ts b/src/hooks/web/useCache.ts new file mode 100644 index 0000000..6d2a931 --- /dev/null +++ b/src/hooks/web/useCache.ts @@ -0,0 +1,27 @@ +/** + * 配置浏览器本地存储的方式,可直接存储对象数组。 + */ + +import WebStorageCache from 'web-storage-cache' + +type CacheType = 'localStorage' | 'sessionStorage' + +export const CACHE_KEY = { + IS_DARK: 'isDark', + USER: 'user', + LANG: 'lang', + THEME: 'theme', + LAYOUT: 'layout', + ROLE_ROUTERS: 'roleRouters', + DICT_CACHE: 'dictCache' +} + +export const useCache = (type: CacheType = 'localStorage') => { + const wsCache: WebStorageCache = new WebStorageCache({ + storage: type + }) + + return { + wsCache + } +} diff --git a/src/hooks/web/useConfigGlobal.ts b/src/hooks/web/useConfigGlobal.ts new file mode 100644 index 0000000..afb3db3 --- /dev/null +++ b/src/hooks/web/useConfigGlobal.ts @@ -0,0 +1,9 @@ +import { ConfigGlobalTypes } from '@/types/configGlobal' + +export const useConfigGlobal = () => { + const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes + + return { + configGlobal + } +} diff --git a/src/hooks/web/useCrudSchemas.ts b/src/hooks/web/useCrudSchemas.ts new file mode 100644 index 0000000..39e42d7 --- /dev/null +++ b/src/hooks/web/useCrudSchemas.ts @@ -0,0 +1,307 @@ +import { reactive } from 'vue' +import { AxiosPromise } from 'axios' +import { findIndex } from '@/utils' +import { eachTree, treeMap, filter } from '@/utils/tree' +import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict' + +import { FormSchema } from '@/types/form' +import { TableColumn } from '@/types/table' +import { DescriptionsSchema } from '@/types/descriptions' +import { ComponentOptions, ComponentProps } from '@/types/components' +import { DictTag } from '@/components/DictTag' + +export type CrudSchema = Omit & { + isSearch?: boolean // 是否在查询显示 + search?: CrudSearchParams // 查询的详细配置 + isTable?: boolean // 是否在列表显示 + table?: CrudTableParams // 列表的详细配置 + isForm?: boolean // 是否在表单显示 + form?: CrudFormParams // 表单的详细配置 + isDetail?: boolean // 是否在详情显示 + detail?: CrudDescriptionsParams // 详情的详细配置 + children?: CrudSchema[] + dictType?: string // 字典类型 + dictClass?: 'string' | 'number' | 'boolean' // 字典数据类型 string | number | boolean +} + +type CrudSearchParams = { + // 是否显示在查询项 + show?: boolean + // 接口 + api?: () => Promise + // 搜索字段 + field?: string +} & Omit + +type CrudTableParams = { + // 是否显示表头 + show?: boolean +} & Omit + +type CrudFormParams = { + // 是否显示表单项 + show?: boolean + // 接口 + api?: () => Promise +} & Omit + +type CrudDescriptionsParams = { + // 是否显示表单项 + show?: boolean +} & Omit + +interface AllSchemas { + searchSchema: FormSchema[] + tableColumns: TableColumn[] + formSchema: FormSchema[] + detailSchema: DescriptionsSchema[] +} + +const { t } = useI18n() + +// 过滤所有结构 +export const useCrudSchemas = ( + crudSchema: CrudSchema[] +): { + allSchemas: AllSchemas +} => { + // 所有结构数据 + const allSchemas = reactive({ + searchSchema: [], + tableColumns: [], + formSchema: [], + detailSchema: [] + }) + + const searchSchema = filterSearchSchema(crudSchema, allSchemas) + allSchemas.searchSchema = searchSchema || [] + + const tableColumns = filterTableSchema(crudSchema) + allSchemas.tableColumns = tableColumns || [] + + const formSchema = filterFormSchema(crudSchema, allSchemas) + allSchemas.formSchema = formSchema + + const detailSchema = filterDescriptionsSchema(crudSchema) + allSchemas.detailSchema = detailSchema + + return { + allSchemas + } +} + +// 过滤 Search 结构 +const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const searchSchema: FormSchema[] = [] + + // 获取字典列表队列 + const searchRequestTask: Array<() => Promise> = [] + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.isSearch || schemaItem.search?.show) { + let component = schemaItem?.search?.component || 'Input' + const options: ComponentOptions[] = [] + let comonentProps: ComponentProps = {} + if (schemaItem.dictType) { + const allOptions: ComponentOptions = { label: '全部', value: '' } + options.push(allOptions) + getDictOptions(schemaItem.dictType).forEach((dict) => { + options.push(dict) + }) + comonentProps = { + options: options + } + if (!schemaItem.search?.component) component = 'Select' + } + const searchSchemaItem = { + // 默认为 input + component: component, + componentProps: comonentProps, + ...schemaItem.search, + field: schemaItem.field, + label: schemaItem.search?.label || schemaItem.label, + id: schemaItem.clueParamId + } + if (searchSchemaItem.api) { + searchRequestTask.push(async () => { + const res = await (searchSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => { + return v.field === searchSchemaItem.field + }) + if (index !== -1) { + allSchemas.searchSchema[index]!.componentProps!.options = filterOptions( + res, + searchSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + // 删除不必要的字段 + delete searchSchemaItem.show + + searchSchema.push(searchSchemaItem) + } + }) + for (const task of searchRequestTask) { + task() + } + return searchSchema +} + +// 过滤 table 结构 +const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => { + const tableColumns = treeMap(crudSchema, { + conversion: (schema: CrudSchema) => { + if (schema?.isTable !== false && schema?.table?.show !== false) { + // add by 芋艿:增加对 dict 字典数据的支持 + if (!schema.formatter && schema.dictType) { + schema.formatter = (_: Recordable, __: TableColumn, cellValue: any) => { + return h(DictTag, { + type: schema.dictType!, // ! 表示一定不为空 + value: cellValue + }) + } + } + return { + ...schema.table, + ...schema, + id: schema.clueParamId + } + } + } + }) + + // 第一次过滤会有 undefined 所以需要二次过滤 + return filter(tableColumns as TableColumn[], (data) => { + if (data.children === void 0) { + delete data.children + } + return !!data.field + }) +} + +// 过滤 form 结构 +const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const formSchema: FormSchema[] = [] + + // 获取字典列表队列 + const formRequestTask: Array<() => Promise> = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) { + let component = schemaItem?.form?.component || 'Input' + let defaultValue: any = '' + if (schemaItem.form?.value) { + defaultValue = schemaItem.form?.value + } else { + if (component === 'InputNumber') { + defaultValue = 0 + } + } + let comonentProps: ComponentProps = {} + if (schemaItem.dictType) { + const options: ComponentOptions[] = [] + if (schemaItem.dictClass && schemaItem.dictClass === 'number') { + getIntDictOptions(schemaItem.dictType).forEach((dict) => { + options.push(dict) + }) + } else if (schemaItem.dictClass && schemaItem.dictClass === 'boolean') { + getBoolDictOptions(schemaItem.dictType).forEach((dict) => { + options.push(dict) + }) + } else { + getDictOptions(schemaItem.dictType).forEach((dict) => { + options.push(dict) + }) + } + comonentProps = { + options: options + } + if (!(schemaItem.form && schemaItem.form.component)) component = 'Select' + } + const formSchemaItem = { + // 默认为 input + component: component, + componentProps: comonentProps, + value: defaultValue, + ...schemaItem.form, + field: schemaItem.field, + label: schemaItem.form?.label || schemaItem.label + } + + if (formSchemaItem.api) { + formRequestTask.push(async () => { + const res = await (formSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.formSchema, (v: FormSchema) => { + return v.field === formSchemaItem.field + }) + if (index !== -1) { + allSchemas.formSchema[index]!.componentProps!.options = filterOptions( + res, + formSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + + // 删除不必要的字段 + delete formSchemaItem.show + + formSchema.push(formSchemaItem) + } + }) + + for (const task of formRequestTask) { + task() + } + return formSchema +} + +// 过滤 descriptions 结构 +const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => { + const descriptionsSchema: FormSchema[] = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.isDetail !== false && schemaItem.detail?.show !== false) { + const descriptionsSchemaItem = { + ...schemaItem.detail, + field: schemaItem.field, + label: schemaItem.detail?.label || schemaItem.label + } + if (schemaItem.dictType) { + descriptionsSchemaItem.dictType = schemaItem.dictType + } + if (schemaItem.detail?.dateFormat || schemaItem.formatter == 'formatDate') { + // 优先使用 detail 下的配置,如果没有默认为 YYYY-MM-DD HH:mm:ss + descriptionsSchemaItem.dateFormat = schemaItem?.detail?.dateFormat + ? schemaItem?.detail?.dateFormat + : 'YYYY-MM-DD HH:mm:ss' + } + + // 删除不必要的字段 + delete descriptionsSchemaItem.show + + descriptionsSchema.push(descriptionsSchemaItem) + } + }) + + return descriptionsSchema +} + +// 给options添加国际化 +const filterOptions = (options: Recordable, labelField?: string) => { + return options?.map((v: Recordable) => { + if (labelField) { + v['labelField'] = t(v.labelField) + } else { + v['label'] = t(v.label) + } + return v + }) +} diff --git a/src/hooks/web/useDesign.ts b/src/hooks/web/useDesign.ts new file mode 100644 index 0000000..8ee3b38 --- /dev/null +++ b/src/hooks/web/useDesign.ts @@ -0,0 +1,18 @@ +import variables from '@/styles/global.module.scss' + +export const useDesign = () => { + const scssVariables = variables + + /** + * @param scope 类名 + * @returns 返回空间名-类名 + */ + const getPrefixCls = (scope: string) => { + return `${scssVariables.namespace}-${scope}` + } + + return { + variables: scssVariables, + getPrefixCls + } +} diff --git a/src/hooks/web/useEmitt.ts b/src/hooks/web/useEmitt.ts new file mode 100644 index 0000000..d4efea7 --- /dev/null +++ b/src/hooks/web/useEmitt.ts @@ -0,0 +1,22 @@ +import mitt from 'mitt' + +interface Option { + name: string // 事件名称 + callback: Fn // 回调 +} + +const emitter = mitt() + +export const useEmitt = (option?: Option) => { + if (option) { + emitter.on(option.name, option.callback) + + onBeforeUnmount(() => { + emitter.off(option.name) + }) + } + + return { + emitter + } +} diff --git a/src/hooks/web/useForm.ts b/src/hooks/web/useForm.ts new file mode 100644 index 0000000..53a8a94 --- /dev/null +++ b/src/hooks/web/useForm.ts @@ -0,0 +1,94 @@ +import type { Form, FormExpose } from '@/components/Form' +import type { ElForm } from 'element-plus' +import type { FormProps } from '@/components/Form/src/types' +import { FormSchema, FormSetPropsType } from '@/types/form' + +export const useForm = (props?: FormProps) => { + // From实例 + const formRef = ref() + + // ElForm实例 + const elFormRef = ref>() + + /** + * @param ref Form实例 + * @param elRef ElForm实例 + */ + const register = (ref: typeof Form & FormExpose, elRef: ComponentRef) => { + formRef.value = ref + elFormRef.value = elRef + } + + const getForm = async () => { + await nextTick() + const form = unref(formRef) + if (!form) { + console.error('The form is not registered. Please use the register method to register') + } + return form + } + + // 一些内置的方法 + const methods: { + setProps: (props: Recordable) => void + setValues: (data: Recordable) => void + getFormData: () => Promise + setSchema: (schemaProps: FormSetPropsType[]) => void + addSchema: (formSchema: FormSchema, index?: number) => void + delSchema: (field: string) => void + } = { + setProps: async (props: FormProps = {}) => { + const form = await getForm() + form?.setProps(props) + if (props.model) { + form?.setValues(props.model) + } + }, + + setValues: async (data: Recordable) => { + const form = await getForm() + form?.setValues(data) + }, + + /** + * @param schemaProps 需要设置的schemaProps + */ + setSchema: async (schemaProps: FormSetPropsType[]) => { + const form = await getForm() + form?.setSchema(schemaProps) + }, + + /** + * @param formSchema 需要新增数据 + * @param index 在哪里新增 + */ + addSchema: async (formSchema: FormSchema, index?: number) => { + const form = await getForm() + form?.addSchema(formSchema, index) + }, + + /** + * @param field 删除哪个数据 + */ + delSchema: async (field: string) => { + const form = await getForm() + form?.delSchema(field) + }, + + /** + * @returns form data + */ + getFormData: async (): Promise => { + const form = await getForm() + return form?.formModel as T + } + } + + props && methods.setProps(props) + + return { + register, + elFormRef, + methods + } +} diff --git a/src/hooks/web/useI18n.ts b/src/hooks/web/useI18n.ts new file mode 100644 index 0000000..d1ab70f --- /dev/null +++ b/src/hooks/web/useI18n.ts @@ -0,0 +1,53 @@ +import { i18n } from '@/plugins/vueI18n' + +type I18nGlobalTranslation = { + (key: string): string + (key: string, locale: string): string + (key: string, locale: string, list: unknown[]): string + (key: string, locale: string, named: Record): string + (key: string, list: unknown[]): string + (key: string, named: Record): string +} + +type I18nTranslationRestParameters = [string, any] + +const getKey = (namespace: string | undefined, key: string) => { + if (!namespace) { + return key + } + if (key.startsWith(namespace)) { + return key + } + return `${namespace}.${key}` +} + +export const useI18n = ( + namespace?: string +): { + t: I18nGlobalTranslation +} => { + const normalFn = { + t: (key: string) => { + return getKey(namespace, key) + } + } + + if (!i18n) { + return normalFn + } + + const { t, ...methods } = i18n.global + + const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => { + if (!key) return '' + if (!key.includes('.') && !namespace) return key + //@ts-ignore + return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters)) + } + return { + ...methods, + t: tFn + } +} + +export const t = (key: string) => key diff --git a/src/hooks/web/useIcon.ts b/src/hooks/web/useIcon.ts new file mode 100644 index 0000000..3500204 --- /dev/null +++ b/src/hooks/web/useIcon.ts @@ -0,0 +1,8 @@ +import { h } from 'vue' +import type { VNode } from 'vue' +import { Icon } from '@/components/Icon' +import { IconTypes } from '@/types/icon' + +export const useIcon = (props: IconTypes): VNode => { + return h(Icon, props) +} diff --git a/src/hooks/web/useIntro.ts b/src/hooks/web/useIntro.ts new file mode 100644 index 0000000..7fe0084 --- /dev/null +++ b/src/hooks/web/useIntro.ts @@ -0,0 +1,47 @@ +import introJs from 'intro.js' +import { IntroJs, Step, Options } from 'intro.js' +import 'intro.js/introjs.css' + +import { useDesign } from '@/hooks/web/useDesign' + +export const useIntro = (setps?: Step[], options?: Options) => { + const { t } = useI18n() + + const { variables } = useDesign() + + const defaultSetps: Step[] = setps || [ + { + element: `#${variables.namespace}-menu`, + title: t('common.menu'), + intro: t('common.menuDes'), + position: 'right' + }, + { + element: `#${variables.namespace}-tool-header`, + title: t('common.tool'), + intro: t('common.toolDes'), + position: 'left' + }, + { + element: `#${variables.namespace}-tags-view`, + title: t('common.tagsView'), + intro: t('common.tagsViewDes'), + position: 'bottom' + } + ] + + const defaultOptions: Options = options || { + prevLabel: t('common.prevLabel'), + nextLabel: t('common.nextLabel'), + skipLabel: t('common.skipLabel'), + doneLabel: t('common.doneLabel') + } + + const introRef: IntroJs = introJs() + + introRef.addSteps(defaultSetps).setOptions(defaultOptions) + + return { + introRef + } +} diff --git a/src/hooks/web/useLocale.ts b/src/hooks/web/useLocale.ts new file mode 100644 index 0000000..c65070e --- /dev/null +++ b/src/hooks/web/useLocale.ts @@ -0,0 +1,35 @@ +import { i18n } from '@/plugins/vueI18n' +import { useLocaleStoreWithOut } from '@/store/modules/locale' +import { setHtmlPageLang } from '@/plugins/vueI18n/helper' + +const setI18nLanguage = (locale: LocaleType) => { + const localeStore = useLocaleStoreWithOut() + + if (i18n.mode === 'legacy') { + i18n.global.locale = locale + } else { + ;(i18n.global.locale as any).value = locale + } + localeStore.setCurrentLocale({ + lang: locale + }) + setHtmlPageLang(locale) +} + +export const useLocale = () => { + // Switching the language will change the locale of useI18n + // And submit to configuration modification + const changeLocale = async (locale: LocaleType) => { + const globalI18n = i18n.global + + const langModule = await import(`../../locales/${locale}.ts`) + + globalI18n.setLocaleMessage(locale, langModule.default) + + setI18nLanguage(locale) + } + + return { + changeLocale + } +} diff --git a/src/hooks/web/useMessage.ts b/src/hooks/web/useMessage.ts new file mode 100644 index 0000000..ac2b552 --- /dev/null +++ b/src/hooks/web/useMessage.ts @@ -0,0 +1,95 @@ +import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' +import { useI18n } from './useI18n' +export const useMessage = () => { + const { t } = useI18n() + return { + // 消息提示 + info(content: string) { + ElMessage.info(content) + }, + // 错误消息 + error(content: string) { + ElMessage.error(content) + }, + // 成功消息 + success(content: string) { + ElMessage.success(content) + }, + // 警告消息 + warning(content: string) { + ElMessage.warning(content) + }, + // 弹出提示 + alert(content: string) { + ElMessageBox.alert(content, t('common.confirmTitle')) + }, + // 错误提示 + alertError(content: string) { + ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'error' }) + }, + // 成功提示 + alertSuccess(content: string) { + ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'success' }) + }, + // 警告提示 + alertWarning(content: string) { + ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'warning' }) + }, + // 通知提示 + notify(content: string) { + ElNotification.info(content) + }, + // 错误通知 + notifyError(content: string) { + ElNotification.error(content) + }, + // 成功通知 + notifySuccess(content: string) { + ElNotification.success(content) + }, + // 警告通知 + notifyWarning(content: string) { + ElNotification.warning(content) + }, + // 确认窗体 + confirm(content: string, tip?: string) { + return ElMessageBox.confirm(content, tip ? tip : t('common.confirmTitle'), { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + }) + }, + // 删除窗体 + delConfirm(content?: string, tip?: string) { + return ElMessageBox.confirm( + content ? content : t('common.delMessage'), + tip ? tip : t('common.confirmTitle'), + { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + } + ) + }, + // 导出窗体 + exportConfirm(content?: string, tip?: string) { + return ElMessageBox.confirm( + content ? content : t('common.exportMessage'), + tip ? tip : t('common.confirmTitle'), + { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + } + ) + }, + // 提交内容 + prompt(content: string, tip: string) { + return ElMessageBox.prompt(content, tip, { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + }) + } + } +} diff --git a/src/hooks/web/useNProgress.ts b/src/hooks/web/useNProgress.ts new file mode 100644 index 0000000..6d8c0b9 --- /dev/null +++ b/src/hooks/web/useNProgress.ts @@ -0,0 +1,33 @@ +import { useCssVar } from '@vueuse/core' +import type { NProgressOptions } from 'nprogress' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' + +const primaryColor = useCssVar('--el-color-primary', document.documentElement) + +export const useNProgress = () => { + NProgress.configure({ showSpinner: false } as NProgressOptions) + + const initColor = async () => { + await nextTick() + const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef + if (bar) { + bar.style.background = unref(primaryColor.value) + } + } + + initColor() + + const start = () => { + NProgress.start() + } + + const done = () => { + NProgress.done() + } + + return { + start, + done + } +} diff --git a/src/hooks/web/usePageLoading.ts b/src/hooks/web/usePageLoading.ts new file mode 100644 index 0000000..bb89457 --- /dev/null +++ b/src/hooks/web/usePageLoading.ts @@ -0,0 +1,18 @@ +import { useAppStoreWithOut } from '@/store/modules/app' + +const appStore = useAppStoreWithOut() + +export const usePageLoading = () => { + const loadStart = () => { + appStore.setPageLoading(true) + } + + const loadDone = () => { + appStore.setPageLoading(false) + } + + return { + loadStart, + loadDone + } +} diff --git a/src/hooks/web/useTable.ts b/src/hooks/web/useTable.ts new file mode 100644 index 0000000..3f0d505 --- /dev/null +++ b/src/hooks/web/useTable.ts @@ -0,0 +1,225 @@ +import download from '@/utils/download' +import { Table, TableExpose } from '@/components/Table' +import { ElMessage, ElMessageBox, ElTable } from 'element-plus' +import { computed, nextTick, reactive, ref, unref, watch } from 'vue' +import type { TableProps } from '@/components/Table/src/types' + +import { TableSetPropsType } from '@/types/table' + +const { t } = useI18n() +interface ResponseType { + list: T[] + total?: number +} + +interface UseTableConfig { + getListApi: (option: any) => Promise + delListApi?: (option: any) => Promise + exportListApi?: (option: any) => Promise + // 返回数据格式配置 + response?: ResponseType + // 默认传递的参数 + defaultParams?: Recordable + props?: TableProps +} + +interface TableObject { + pageSize: number + currentPage: number + total: number + tableList: T[] + params: any + loading: boolean + exportLoading: boolean + currentRow: Nullable +} + +export const useTable = (config?: UseTableConfig) => { + const tableObject = reactive>({ + // 页数 + pageSize: 10, + // 当前页 + currentPage: 1, + // 总条数 + total: 10, + // 表格数据 + tableList: [], + // AxiosConfig 配置 + params: { + ...(config?.defaultParams || {}) + }, + // 加载中 + loading: true, + // 导出加载中 + exportLoading: false, + // 当前行的数据 + currentRow: null + }) + + const paramsObj = computed(() => { + return { + ...tableObject.params, + pageSize: tableObject.pageSize, + pageNo: tableObject.currentPage + } + }) + + watch( + () => tableObject.currentPage, + () => { + methods.getList() + } + ) + + watch( + () => tableObject.pageSize, + () => { + // 当前页不为1时,修改页数后会导致多次调用getList方法 + if (tableObject.currentPage === 1) { + methods.getList() + } else { + tableObject.currentPage = 1 + methods.getList() + } + } + ) + + // Table实例 + const tableRef = ref() + + // ElTable实例 + const elTableRef = ref>() + + const register = (ref: typeof Table & TableExpose, elRef: ComponentRef) => { + tableRef.value = ref + elTableRef.value = elRef + } + + const getTable = async () => { + await nextTick() + const table = unref(tableRef) + if (!table) { + console.error('The table is not registered. Please use the register method to register') + } + return table + } + + const delData = async (ids: string | number | string[] | number[]) => { + let idsLength = 1 + if (ids instanceof Array) { + idsLength = ids.length + await Promise.all( + ids.map(async (id: string | number) => { + await (config?.delListApi && config?.delListApi(id)) + }) + ) + } else { + await (config?.delListApi && config?.delListApi(ids)) + } + ElMessage.success(t('common.delSuccess')) + + // 计算出临界点 + tableObject.currentPage = + tableObject.total % tableObject.pageSize === idsLength || tableObject.pageSize === 1 + ? tableObject.currentPage > 1 + ? tableObject.currentPage - 1 + : tableObject.currentPage + : tableObject.currentPage + await methods.getList() + } + + const methods = { + getList: async () => { + tableObject.loading = true + const res = await config?.getListApi(unref(paramsObj)).finally(() => { + tableObject.loading = false + }) + if (res) { + tableObject.tableList = (res as unknown as ResponseType).list + if ((res as unknown as ResponseType).total) { + tableObject.total = (res as unknown as ResponseType).total as unknown as number + } + } + }, + setProps: async (props: TableProps = {}) => { + const table = await getTable() + table?.setProps(props) + }, + setColumn: async (columnProps: TableSetPropsType[]) => { + const table = await getTable() + table?.setColumn(columnProps) + }, + getSelections: async () => { + const table = await getTable() + return (table?.selections || []) as T[] + }, + // 与Search组件结合 + setSearchParams: (data: Recordable) => { + tableObject.params = Object.assign(tableObject.params, { + pageSize: tableObject.pageSize, + pageNo: 1, + ...data + }) + // 页码不等于1时更新页码重新获取数据,页码等于1时重新获取数据 + if (tableObject.currentPage !== 1) { + tableObject.currentPage = 1 + } else { + methods.getList() + } + }, + // 删除数据 + delList: async ( + ids: string | number | string[] | number[], + multiple: boolean, + message = true + ) => { + const tableRef = await getTable() + if (multiple) { + if (!tableRef?.selections.length) { + ElMessage.warning(t('common.delNoData')) + return + } + } + if (message) { + ElMessageBox.confirm(t('common.delMessage'), t('common.confirmTitle'), { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + }).then(async () => { + await delData(ids) + }) + } else { + await delData(ids) + } + }, + // 导出列表 + exportList: async (fileName: string) => { + tableObject.exportLoading = true + ElMessageBox.confirm(t('common.exportMessage'), t('common.confirmTitle'), { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + }) + .then(async () => { + const res = await config?.exportListApi?.(unref(paramsObj) as unknown as T) + if (res) { + download.excel(res as unknown as Blob, fileName) + } + }) + .finally(() => { + tableObject.exportLoading = false + }) + } + } + + config?.props && methods.setProps(config.props) + + return { + register, + elTableRef, + tableObject, + methods, + // add by 芋艿:返回 tableMethods 属性,和 tableObject 更统一 + tableMethods: methods + } +} diff --git a/src/hooks/web/useTimeAgo.ts b/src/hooks/web/useTimeAgo.ts new file mode 100644 index 0000000..a6da281 --- /dev/null +++ b/src/hooks/web/useTimeAgo.ts @@ -0,0 +1,49 @@ +import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core' +import { useLocaleStoreWithOut } from '@/store/modules/locale' + +const TIME_AGO_MESSAGE_MAP: { + 'zh-CN': UseTimeAgoMessages + en: UseTimeAgoMessages +} = { + // @ts-ignore + 'zh-CN': { + justNow: '刚刚', + past: (n) => (n.match(/\d/) ? `${n}前` : n), + future: (n) => (n.match(/\d/) ? `${n}后` : n), + month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`), + year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`), + day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`), + week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`), + hour: (n) => `${n} 小时`, + minute: (n) => `${n} 分钟`, + second: (n) => `${n} 秒` + }, + // @ts-ignore + en: { + justNow: 'just now', + past: (n) => (n.match(/\d/) ? `${n} ago` : n), + future: (n) => (n.match(/\d/) ? `in ${n}` : n), + month: (n, past) => + n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`, + year: (n, past) => + n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`, + day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`), + week: (n, past) => + n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`, + hour: (n) => `${n} hour${n > 1 ? 's' : ''}`, + minute: (n) => `${n} minute${n > 1 ? 's' : ''}`, + second: (n) => `${n} second${n > 1 ? 's' : ''}` + } +} + +export const useTimeAgo = (time: Date | number | string) => { + const localeStore = useLocaleStoreWithOut() + + const currentLocale = computed(() => localeStore.getCurrentLocale) + + const timeAgo = useTimeAgoCore(time, { + messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang] + }) + + return timeAgo +} diff --git a/src/hooks/web/useTitle.ts b/src/hooks/web/useTitle.ts new file mode 100644 index 0000000..020a9b7 --- /dev/null +++ b/src/hooks/web/useTitle.ts @@ -0,0 +1,24 @@ +import { watch, ref } from 'vue' +import { isString } from '@/utils/is' +import { useAppStoreWithOut } from '@/store/modules/app' + +const appStore = useAppStoreWithOut() + +export const useTitle = (newTitle?: string) => { + const { t } = useI18n() + const title = ref( + newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle + ) + + watch( + title, + (n, o) => { + if (isString(n) && n !== o && document) { + document.title = n + } + }, + { immediate: true } + ) + + return title +} diff --git a/src/hooks/web/useValidator.ts b/src/hooks/web/useValidator.ts new file mode 100644 index 0000000..0c16fa3 --- /dev/null +++ b/src/hooks/web/useValidator.ts @@ -0,0 +1,62 @@ +const { t } = useI18n() + +type Callback = (error?: string | Error | undefined) => void + +interface LengthRange { + min: number + max: number + message: string +} + +export const useValidator = () => { + const required = (message?: string) => { + return { + required: true, + message: message || t('common.required') + } + } + + const lengthRange = (val: any, callback: Callback, options: LengthRange) => { + const { min, max, message } = options + if (val.length < min || val.length > max) { + callback(new Error(message)) + } else { + callback() + } + } + + const notSpace = (val: any, callback: Callback, message: string) => { + // 用户名不能有空格 + if (val.indexOf(' ') !== -1) { + callback(new Error(message)) + } else { + callback() + } + } + + const notSpecialCharacters = (val: any, callback: Callback, message: string) => { + // 密码不能是特殊字符 + if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) { + callback(new Error(message)) + } else { + callback() + } + } + + // 两个字符串是否想等 + const isEqual = (val1: string, val2: string, callback: Callback, message: string) => { + if (val1 === val2) { + callback() + } else { + callback(new Error(message)) + } + } + + return { + required, + lengthRange, + notSpace, + notSpecialCharacters, + isEqual + } +} diff --git a/src/hooks/web/useWatermark.ts b/src/hooks/web/useWatermark.ts new file mode 100644 index 0000000..07cd09c --- /dev/null +++ b/src/hooks/web/useWatermark.ts @@ -0,0 +1,55 @@ +const domSymbol = Symbol('watermark-dom') + +export function useWatermark(appendEl: HTMLElement | null = document.body) { + let func: Fn = () => {} + const id = domSymbol.toString() + const clear = () => { + const domId = document.getElementById(id) + if (domId) { + const el = appendEl + el && el.removeChild(domId) + } + window.removeEventListener('resize', func) + } + const createWatermark = (str: string) => { + clear() + + const can = document.createElement('canvas') + can.width = 500 + can.height = 240 + + const cans = can.getContext('2d') + if (cans) { + cans.rotate((-20 * Math.PI) / 120) + cans.font = '15px Vedana' + cans.fillStyle = 'rgba(0, 0, 0, 0.05)' + cans.textAlign = 'left' + cans.textBaseline = 'middle' + cans.fillText(str, can.width / 20, can.height) + } + + const div = document.createElement('div') + div.id = id + div.style.pointerEvents = 'none' + div.style.top = '0px' + div.style.left = '0px' + div.style.position = 'absolute' + div.style.zIndex = '100000000' + div.style.width = document.documentElement.clientWidth + 'px' + div.style.height = document.documentElement.clientHeight + 'px' + div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat' + const el = appendEl + el && el.appendChild(div) + return id + } + + function setWatermark(str: string) { + createWatermark(str) + func = () => { + createWatermark(str) + } + window.addEventListener('resize', func) + } + + return { setWatermark, clear } +} diff --git a/src/layout/Layout.vue b/src/layout/Layout.vue new file mode 100644 index 0000000..5c43e52 --- /dev/null +++ b/src/layout/Layout.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/layout/components/AppView.vue b/src/layout/components/AppView.vue new file mode 100644 index 0000000..24d2635 --- /dev/null +++ b/src/layout/components/AppView.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/layout/components/Breadcrumb/index.ts b/src/layout/components/Breadcrumb/index.ts new file mode 100644 index 0000000..93ffe70 --- /dev/null +++ b/src/layout/components/Breadcrumb/index.ts @@ -0,0 +1,3 @@ +import Breadcrumb from './src/Breadcrumb.vue' + +export { Breadcrumb } diff --git a/src/layout/components/Breadcrumb/src/Breadcrumb.vue b/src/layout/components/Breadcrumb/src/Breadcrumb.vue new file mode 100644 index 0000000..de03665 --- /dev/null +++ b/src/layout/components/Breadcrumb/src/Breadcrumb.vue @@ -0,0 +1,128 @@ + + + diff --git a/src/layout/components/Breadcrumb/src/helper.ts b/src/layout/components/Breadcrumb/src/helper.ts new file mode 100644 index 0000000..fb3ec19 --- /dev/null +++ b/src/layout/components/Breadcrumb/src/helper.ts @@ -0,0 +1,31 @@ +import { pathResolve } from '@/utils/routerHelper' +import type { RouteMeta } from 'vue-router' + +export const filterBreadcrumb = ( + routes: AppRouteRecordRaw[], + parentPath = '' +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const meta = route?.meta as RouteMeta + if (meta.hidden && !meta.canTo) { + continue + } + + const data: AppRouteRecordRaw = + !meta.alwaysShow && route.children?.length === 1 + ? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) } + : { ...route } + + data.path = pathResolve(parentPath, data.path) + + if (data.children) { + data.children = filterBreadcrumb(data.children, data.path) + } + if (data) { + res.push(data) + } + } + return res +} diff --git a/src/layout/components/Collapse/index.ts b/src/layout/components/Collapse/index.ts new file mode 100644 index 0000000..73f65a3 --- /dev/null +++ b/src/layout/components/Collapse/index.ts @@ -0,0 +1,3 @@ +import Collapse from './src/Collapse.vue' + +export { Collapse } diff --git a/src/layout/components/Collapse/src/Collapse.vue b/src/layout/components/Collapse/src/Collapse.vue new file mode 100644 index 0000000..523c34d --- /dev/null +++ b/src/layout/components/Collapse/src/Collapse.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/layout/components/ContextMenu/index.ts b/src/layout/components/ContextMenu/index.ts new file mode 100644 index 0000000..2a7c1f0 --- /dev/null +++ b/src/layout/components/ContextMenu/index.ts @@ -0,0 +1,10 @@ +import ContextMenu from './src/ContextMenu.vue' +import { ElDropdown } from 'element-plus' +import type { RouteLocationNormalizedLoaded } from 'vue-router' + +export interface ContextMenuExpose { + elDropdownMenuRef: ComponentRef + tagItem: RouteLocationNormalizedLoaded +} + +export { ContextMenu } diff --git a/src/layout/components/ContextMenu/src/ContextMenu.vue b/src/layout/components/ContextMenu/src/ContextMenu.vue new file mode 100644 index 0000000..de81969 --- /dev/null +++ b/src/layout/components/ContextMenu/src/ContextMenu.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/layout/components/Footer/index.ts b/src/layout/components/Footer/index.ts new file mode 100644 index 0000000..bd052e0 --- /dev/null +++ b/src/layout/components/Footer/index.ts @@ -0,0 +1,3 @@ +import Footer from './src/Footer.vue' + +export { Footer } diff --git a/src/layout/components/Footer/src/Footer.vue b/src/layout/components/Footer/src/Footer.vue new file mode 100644 index 0000000..c9fcd89 --- /dev/null +++ b/src/layout/components/Footer/src/Footer.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/layout/components/Logo/index.ts b/src/layout/components/Logo/index.ts new file mode 100644 index 0000000..1c4224c --- /dev/null +++ b/src/layout/components/Logo/index.ts @@ -0,0 +1,3 @@ +import Logo from './src/Logo.vue' + +export { Logo } diff --git a/src/layout/components/Logo/src/Logo.vue b/src/layout/components/Logo/src/Logo.vue new file mode 100644 index 0000000..ffb2d27 --- /dev/null +++ b/src/layout/components/Logo/src/Logo.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/layout/components/Menu/index.ts b/src/layout/components/Menu/index.ts new file mode 100644 index 0000000..a6ec696 --- /dev/null +++ b/src/layout/components/Menu/index.ts @@ -0,0 +1,3 @@ +import Menu from './src/Menu.vue' + +export { Menu } diff --git a/src/layout/components/Menu/src/Menu.vue b/src/layout/components/Menu/src/Menu.vue new file mode 100644 index 0000000..4d22f7d --- /dev/null +++ b/src/layout/components/Menu/src/Menu.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/src/layout/components/Menu/src/components/useRenderMenuItem.tsx b/src/layout/components/Menu/src/components/useRenderMenuItem.tsx new file mode 100644 index 0000000..17a520a --- /dev/null +++ b/src/layout/components/Menu/src/components/useRenderMenuItem.tsx @@ -0,0 +1,59 @@ +import { ElSubMenu, ElMenuItem } from 'element-plus' +import type { RouteMeta } from 'vue-router' +import { hasOneShowingChild } from '../helper' +import { isUrl } from '@/utils/is' +import { useRenderMenuTitle } from './useRenderMenuTitle' +import { useDesign } from '@/hooks/web/useDesign' +import { pathResolve } from '@/utils/routerHelper' + +export const useRenderMenuItem = ( + // allRouters: AppRouteRecordRaw[] = [], + menuMode: 'vertical' | 'horizontal' +) => { + const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => { + return routers.map((v) => { + const meta = (v.meta ?? {}) as RouteMeta + if (!meta.hidden) { + const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v) + const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath(allRouters, v.path).join('/') + + const { renderMenuTitle } = useRenderMenuTitle() + + if ( + oneShowingChild && + (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) && + !meta?.alwaysShow + ) { + return ( + + {{ + default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta) + }} + + ) + } else { + const { getPrefixCls } = useDesign() + + const preFixCls = getPrefixCls('menu-popper') + return ( + + {{ + title: () => renderMenuTitle(meta), + default: () => renderMenuItem(v.children!, fullPath) + }} + + ) + } + } + }) + } + + return { + renderMenuItem + } +} diff --git a/src/layout/components/Menu/src/components/useRenderMenuTitle.tsx b/src/layout/components/Menu/src/components/useRenderMenuTitle.tsx new file mode 100644 index 0000000..fc30b90 --- /dev/null +++ b/src/layout/components/Menu/src/components/useRenderMenuTitle.tsx @@ -0,0 +1,22 @@ +import type { RouteMeta } from 'vue-router' +import { Icon } from '@/components/Icon' + +export const useRenderMenuTitle = () => { + const renderMenuTitle = (meta: RouteMeta) => { + const { t } = useI18n() + const { title = 'Please set title', icon } = meta + + return icon ? ( + <> + + {t(title as string)} + + ) : ( + {t(title as string)} + ) + } + + return { + renderMenuTitle + } +} diff --git a/src/layout/components/Menu/src/helper.ts b/src/layout/components/Menu/src/helper.ts new file mode 100644 index 0000000..c26f5f4 --- /dev/null +++ b/src/layout/components/Menu/src/helper.ts @@ -0,0 +1,54 @@ +import type { RouteMeta } from 'vue-router' +import { findPath } from '@/utils/tree' + +type OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean } + +interface HasOneShowingChild { + oneShowingChild?: boolean + onlyOneChild?: OnlyOneChildType +} + +export const getAllParentPath = (treeData: T[], path: string) => { + const menuList = findPath(treeData, (n) => n.path === path) as AppRouteRecordRaw[] + return (menuList || []).map((item) => item.path) +} + +export const hasOneShowingChild = ( + children: AppRouteRecordRaw[] = [], + parent: AppRouteRecordRaw +): HasOneShowingChild => { + const onlyOneChild = ref() + + const showingChildren = children.filter((v) => { + const meta = (v.meta ?? {}) as RouteMeta + if (meta.hidden) { + return false + } else { + // Temp set(will be used if only has one showing child) + onlyOneChild.value = v + return true + } + }) + + // When there is only one child router, the child router is displayed by default + if (showingChildren.length === 1) { + return { + oneShowingChild: true, + onlyOneChild: unref(onlyOneChild) + } + } + + // Show parent if there are no child router to display + if (!showingChildren.length) { + onlyOneChild.value = { ...parent, path: '', noShowingChildren: true } + return { + oneShowingChild: true, + onlyOneChild: unref(onlyOneChild) + } + } + + return { + oneShowingChild: false, + onlyOneChild: unref(onlyOneChild) + } +} diff --git a/src/layout/components/Message/index.ts b/src/layout/components/Message/index.ts new file mode 100644 index 0000000..dfe0207 --- /dev/null +++ b/src/layout/components/Message/index.ts @@ -0,0 +1,3 @@ +import Message from './src/Message.vue' + +export { Message } diff --git a/src/layout/components/Message/src/Message.vue b/src/layout/components/Message/src/Message.vue new file mode 100644 index 0000000..f51751f --- /dev/null +++ b/src/layout/components/Message/src/Message.vue @@ -0,0 +1,125 @@ + + + diff --git a/src/layout/components/RoleSelect/index.vue b/src/layout/components/RoleSelect/index.vue new file mode 100644 index 0000000..2c4f767 --- /dev/null +++ b/src/layout/components/RoleSelect/index.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/layout/components/Screenfull/index.ts b/src/layout/components/Screenfull/index.ts new file mode 100644 index 0000000..faec2d8 --- /dev/null +++ b/src/layout/components/Screenfull/index.ts @@ -0,0 +1,3 @@ +import Screenfull from './src/Screenfull.vue' + +export { Screenfull } diff --git a/src/layout/components/Screenfull/src/Screenfull.vue b/src/layout/components/Screenfull/src/Screenfull.vue new file mode 100644 index 0000000..d0c4c44 --- /dev/null +++ b/src/layout/components/Screenfull/src/Screenfull.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/layout/components/Setting/index.ts b/src/layout/components/Setting/index.ts new file mode 100644 index 0000000..b64c9ad --- /dev/null +++ b/src/layout/components/Setting/index.ts @@ -0,0 +1,3 @@ +import Setting from './src/Setting.vue' + +export { Setting } diff --git a/src/layout/components/Setting/src/Setting.vue b/src/layout/components/Setting/src/Setting.vue new file mode 100644 index 0000000..908ed99 --- /dev/null +++ b/src/layout/components/Setting/src/Setting.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/src/layout/components/Setting/src/components/ColorRadioPicker.vue b/src/layout/components/Setting/src/components/ColorRadioPicker.vue new file mode 100644 index 0000000..8ef0f8c --- /dev/null +++ b/src/layout/components/Setting/src/components/ColorRadioPicker.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/layout/components/Setting/src/components/InterfaceDisplay.vue b/src/layout/components/Setting/src/components/InterfaceDisplay.vue new file mode 100644 index 0000000..b791a85 --- /dev/null +++ b/src/layout/components/Setting/src/components/InterfaceDisplay.vue @@ -0,0 +1,222 @@ + + + diff --git a/src/layout/components/Setting/src/components/LayoutRadioPicker.vue b/src/layout/components/Setting/src/components/LayoutRadioPicker.vue new file mode 100644 index 0000000..b500683 --- /dev/null +++ b/src/layout/components/Setting/src/components/LayoutRadioPicker.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/layout/components/TabMenu/index.ts b/src/layout/components/TabMenu/index.ts new file mode 100644 index 0000000..b5fd71c --- /dev/null +++ b/src/layout/components/TabMenu/index.ts @@ -0,0 +1,3 @@ +import TabMenu from './src/TabMenu.vue' + +export { TabMenu } diff --git a/src/layout/components/TabMenu/src/TabMenu.vue b/src/layout/components/TabMenu/src/TabMenu.vue new file mode 100644 index 0000000..015d6c7 --- /dev/null +++ b/src/layout/components/TabMenu/src/TabMenu.vue @@ -0,0 +1,251 @@ + + + diff --git a/src/layout/components/TabMenu/src/helper.ts b/src/layout/components/TabMenu/src/helper.ts new file mode 100644 index 0000000..cce3932 --- /dev/null +++ b/src/layout/components/TabMenu/src/helper.ts @@ -0,0 +1,51 @@ +import { getAllParentPath } from '@/layout/components/Menu/src/helper' +import type { RouteMeta } from 'vue-router' +import { isUrl } from '@/utils/is' +import { cloneDeep } from 'lodash-es' + +export type TabMapTypes = { + [key: string]: string[] +} + +export const tabPathMap = reactive({}) + +export const initTabMap = (routes: AppRouteRecordRaw[]) => { + for (const v of routes) { + const meta = (v.meta ?? {}) as RouteMeta + if (!meta?.hidden) { + tabPathMap[v.path] = [] + } + } +} + +export const filterMenusPath = ( + routes: AppRouteRecordRaw[], + allRoutes: AppRouteRecordRaw[] +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + for (const v of routes) { + let data: Nullable = null + const meta = (v.meta ?? {}) as RouteMeta + if (!meta.hidden || meta.canTo) { + const allParentPath = getAllParentPath(allRoutes, v.path) + + const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/') + + data = cloneDeep(v) + data.path = fullPath + if (v.children && data) { + data.children = filterMenusPath(v.children, allRoutes) + } + + if (data) { + res.push(data) + } + + if (allParentPath.length && Reflect.has(tabPathMap, allParentPath[0])) { + tabPathMap[allParentPath[0]].push(fullPath) + } + } + } + + return res +} diff --git a/src/layout/components/TagsView/index.ts b/src/layout/components/TagsView/index.ts new file mode 100644 index 0000000..30e604a --- /dev/null +++ b/src/layout/components/TagsView/index.ts @@ -0,0 +1,3 @@ +import TagsView from './src/TagsView.vue' + +export { TagsView } diff --git a/src/layout/components/TagsView/src/TagsView.vue b/src/layout/components/TagsView/src/TagsView.vue new file mode 100644 index 0000000..6b4f7be --- /dev/null +++ b/src/layout/components/TagsView/src/TagsView.vue @@ -0,0 +1,587 @@ + + + + + diff --git a/src/layout/components/TagsView/src/helper.ts b/src/layout/components/TagsView/src/helper.ts new file mode 100644 index 0000000..22f6a50 --- /dev/null +++ b/src/layout/components/TagsView/src/helper.ts @@ -0,0 +1,21 @@ +import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router' +import { pathResolve } from '@/utils/routerHelper' + +export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => { + let tags: RouteLocationNormalizedLoaded[] = [] + routes.forEach((route) => { + const meta = route.meta as RouteMeta + const tagPath = pathResolve(parentPath, route.path) + if (meta?.affix) { + tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded) + } + if (route.children) { + const tempTags: RouteLocationNormalizedLoaded[] = filterAffixTags(route.children, tagPath) + if (tempTags.length >= 1) { + tags = [...tags, ...tempTags] + } + } + }) + + return tags +} diff --git a/src/layout/components/ThemeSwitch/index.ts b/src/layout/components/ThemeSwitch/index.ts new file mode 100644 index 0000000..823a276 --- /dev/null +++ b/src/layout/components/ThemeSwitch/index.ts @@ -0,0 +1,3 @@ +import ThemeSwitch from './src/ThemeSwitch.vue' + +export { ThemeSwitch } diff --git a/src/layout/components/ThemeSwitch/src/ThemeSwitch.vue b/src/layout/components/ThemeSwitch/src/ThemeSwitch.vue new file mode 100644 index 0000000..5a92650 --- /dev/null +++ b/src/layout/components/ThemeSwitch/src/ThemeSwitch.vue @@ -0,0 +1,44 @@ + + + + diff --git a/src/layout/components/ToolHeader.vue b/src/layout/components/ToolHeader.vue new file mode 100644 index 0000000..0670e47 --- /dev/null +++ b/src/layout/components/ToolHeader.vue @@ -0,0 +1,75 @@ + + + diff --git a/src/layout/components/UserInfo/index.ts b/src/layout/components/UserInfo/index.ts new file mode 100644 index 0000000..c3a34ab --- /dev/null +++ b/src/layout/components/UserInfo/index.ts @@ -0,0 +1,3 @@ +import UserInfo from './src/UserInfo.vue' + +export { UserInfo } diff --git a/src/layout/components/UserInfo/src/UserInfo.vue b/src/layout/components/UserInfo/src/UserInfo.vue new file mode 100644 index 0000000..d305009 --- /dev/null +++ b/src/layout/components/UserInfo/src/UserInfo.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/layout/components/useRenderLayout.tsx b/src/layout/components/useRenderLayout.tsx new file mode 100644 index 0000000..ab42e43 --- /dev/null +++ b/src/layout/components/useRenderLayout.tsx @@ -0,0 +1,278 @@ +import { computed } from 'vue' +import { useAppStore } from '@/store/modules/app' +import { Menu } from '@/layout/components/Menu' +import { TabMenu } from '@/layout/components/TabMenu' +import { TagsView } from '@/layout/components/TagsView' +import { Logo } from '@/layout/components/Logo' +import AppView from './AppView.vue' +import ToolHeader from './ToolHeader.vue' +import { ElScrollbar } from 'element-plus' +import { useDesign } from '@/hooks/web/useDesign' + +const { getPrefixCls } = useDesign() + +const prefixCls = getPrefixCls('layout') + +const appStore = useAppStore() + +const pageLoading = computed(() => appStore.getPageLoading) + +// 标签页 +const tagsView = computed(() => appStore.getTagsView) + +// 菜单折叠 +const collapse = computed(() => appStore.getCollapse) + +// logo +const logo = computed(() => appStore.logo) + +// 固定头部 +const fixedHeader = computed(() => appStore.getFixedHeader) + +// 是否是移动端 +const mobile = computed(() => appStore.getMobile) + +// 固定菜单 +const fixedMenu = computed(() => appStore.getFixedMenu) + +export const useRenderLayout = () => { + const renderClassic = () => { + return ( + <> +
+ {logo.value ? ( + + ) : undefined} + +
+
+ +
+ + + {tagsView.value ? ( + + ) : undefined} +
+ + +
+
+ + ) + } + + const renderTopLeft = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ +
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+
+ + ) + } + + const renderTop = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+ + ) + } + + const renderCutMenu = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ +
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+
+ + ) + } + + return { + renderClassic, + renderTopLeft, + renderTop, + renderCutMenu + } +} diff --git a/src/locales/en.ts b/src/locales/en.ts new file mode 100644 index 0000000..4f4d489 --- /dev/null +++ b/src/locales/en.ts @@ -0,0 +1,447 @@ +export default { + common: { + inputText: 'Please input', + selectText: 'Please select', + startTimeText: 'Start time', + endTimeText: 'End time', + login: 'Login', + required: 'This is required', + loginOut: 'Login out', + document: 'Document', + profile: 'User Center', + reminder: 'Reminder', + loginOutMessage: 'Exit the system?', + back: 'Back', + ok: 'OK', + save: 'Save', + cancel: 'Cancel', + close: 'Close', + reload: 'Reload current', + success: 'Success', + closeTab: 'Close current', + closeTheLeftTab: 'Close left', + closeTheRightTab: 'Close right', + closeOther: 'Close other', + closeAll: 'Close all', + prevLabel: 'Prev', + nextLabel: 'Next', + skipLabel: 'Jump', + doneLabel: 'End', + menu: 'Menu', + menuDes: 'Menu bar rendered in routed structure', + collapse: 'Collapse', + collapseDes: 'Expand and zoom the menu bar', + tagsView: 'Tags view', + tagsViewDes: 'Used to record routing history', + tool: 'Tool', + toolDes: 'Used to set up custom systems', + query: 'Query', + reset: 'Reset', + shrink: 'Put away', + expand: 'Expand', + confirmTitle: 'System Hint', + exportMessage: 'Whether to confirm export data item?', + importMessage: 'Whether to confirm import data item?', + createSuccess: 'Create Success', + updateSuccess: 'Update Success', + delMessage: 'Delete the selected data?', + delDataMessage: 'Delete the data?', + delNoData: 'Please select the data to delete', + delSuccess: 'Deleted successfully', + index: 'Index', + status: 'Status', + createTime: 'Create Time', + updateTime: 'Update Time', + copy: 'Copy', + copySuccess: 'Copy Success', + copyError: 'Copy Error' + }, + error: { + noPermission: `Sorry, you don't have permission to access this page.`, + pageError: 'Sorry, the page you visited does not exist.', + networkError: 'Sorry, the server reported an error.', + returnToHome: 'Return to home' + }, + permission: { + hasPermission: `Please set the operation permission label value`, + hasRole: `Please set the role permission tag value` + }, + setting: { + projectSetting: 'Project setting', + theme: 'Theme', + layout: 'Layout', + systemTheme: 'System theme', + menuTheme: 'Menu theme', + interfaceDisplay: 'Interface display', + breadcrumb: 'Breadcrumb', + breadcrumbIcon: 'Breadcrumb icon', + collapseMenu: 'Collapse menu', + hamburgerIcon: 'Hamburger icon', + screenfullIcon: 'Screenfull icon', + sizeIcon: 'Size icon', + localeIcon: 'Locale icon', + messageIcon: 'Message icon', + tagsView: 'Tags view', + logo: 'Logo', + greyMode: 'Grey mode', + fixedHeader: 'Fixed header', + headerTheme: 'Header theme', + cutMenu: 'Cut Menu', + copy: 'Copy', + clearAndReset: 'Clear cache and reset', + copySuccess: 'Copy success', + copyFailed: 'Copy failed', + footer: 'Footer', + uniqueOpened: 'Unique opened', + tagsViewIcon: 'Tags view icon', + reExperienced: 'Please exit the login experience again', + fixedMenu: 'Fixed menu' + }, + size: { + default: 'Default', + large: 'Large', + small: 'Small' + }, + login: { + welcome: 'Welcome to the system', + message: 'Backstage management system', + tenantname: 'TenantName', + username: 'Username', + password: 'Password', + code: 'verification code', + login: 'Sign in', + relogin: 'Sign in again', + otherLogin: 'Sign in with', + register: 'Register', + checkPassword: 'Confirm password', + remember: 'Remember me', + hasUser: 'Existing account? Go to login', + forgetPassword: 'Forget password?', + tenantNamePlaceholder: 'Please Enter Tenant Name', + usernamePlaceholder: 'Please Enter Username', + passwordPlaceholder: 'Please Enter Password', + codePlaceholder: 'Please Enter Verification Code', + mobileTitle: 'Mobile sign in', + mobileNumber: 'Mobile Number', + mobileNumberPlaceholder: 'Plaease Enter Mobile Number', + backLogin: 'back', + getSmsCode: 'Get SMS Code', + btnMobile: 'Mobile sign in', + btnQRCode: 'QR code sign in', + qrcode: 'Scan the QR code to log in', + btnRegister: 'Sign up', + SmsSendMsg: 'code has been sent' + }, + captcha: { + verification: 'Please complete security verification', + slide: 'Swipe right to complete verification', + point: 'Please click', + success: 'Verification succeeded', + fail: 'verification failed' + }, + router: { + login: 'Login', + home: 'Home', + analysis: 'Analysis', + workplace: 'Workplace' + }, + analysis: { + newUser: 'New user', + unreadInformation: 'Unread information', + transactionAmount: 'Transaction amount', + totalShopping: 'Total Shopping', + monthlySales: 'Monthly sales', + userAccessSource: 'User access source', + january: 'January', + february: 'February', + march: 'March', + april: 'April', + may: 'May', + june: 'June', + july: 'July', + august: 'August', + september: 'September', + october: 'October', + november: 'November', + december: 'December', + estimate: 'Estimate', + actual: 'Actual', + directAccess: 'Airect access', + mailMarketing: 'Mail marketing', + allianceAdvertising: 'Alliance advertising', + videoAdvertising: 'Video advertising', + searchEngines: 'Search engines', + weeklyUserActivity: 'Weekly user activity', + activeQuantity: 'Active quantity', + monday: 'Monday', + tuesday: 'Tuesday', + wednesday: 'Wednesday', + thursday: 'Thursday', + friday: 'Friday', + saturday: 'Saturday', + sunday: 'Sunday' + }, + workplace: { + welcome: 'Hello', + happyDay: 'Wish you happy every day!', + toady: `It's sunny today`, + notice: 'Announcement', + project: 'Project', + access: 'Project access', + toDo: 'To do', + introduction: 'A serious introduction', + shortcutOperation: 'Quick entry', + operation: 'Operation', + index: 'Index', + personal: 'Personal', + team: 'Team', + quote: 'Quote', + contribution: 'Contribution', + hot: 'Hot', + yield: 'Yield', + dynamic: 'Dynamic', + push: 'push', + follow: 'Follow' + }, + form: { + input: 'Input', + inputNumber: 'InputNumber', + default: 'Default', + icon: 'Icon', + mixed: 'Mixed', + textarea: 'Textarea', + slot: 'Slot', + position: 'Position', + autocomplete: 'Autocomplete', + select: 'Select', + selectGroup: 'Select Group', + selectV2: 'SelectV2', + cascader: 'Cascader', + switch: 'Switch', + rate: 'Rate', + colorPicker: 'Color Picker', + transfer: 'Transfer', + render: 'Render', + radio: 'Radio', + button: 'Button', + checkbox: 'Checkbox', + slider: 'Slider', + datePicker: 'Date Picker', + shortcuts: 'Shortcuts', + today: 'Today', + yesterday: 'Yesterday', + aWeekAgo: 'A week ago', + week: 'Week', + year: 'Year', + month: 'Month', + dates: 'Dates', + daterange: 'Date Range', + monthrange: 'Month Range', + dateTimePicker: 'DateTimePicker', + dateTimerange: 'Datetime Range', + timePicker: 'Time Picker', + timeSelect: 'Time Select', + inputPassword: 'input Password', + passwordStrength: 'Password Strength', + operate: 'operate', + change: 'Change', + restore: 'Restore', + disabled: 'Disabled', + disablement: 'Disablement', + delete: 'Delete', + add: 'Add', + setValue: 'Set value', + resetValue: 'Reset value', + set: 'Set', + subitem: 'Subitem', + formValidation: 'Form validation', + verifyReset: 'Verify reset', + remark: 'Remark' + }, + watermark: { + watermark: 'Watermark' + }, + table: { + table: 'Table', + index: 'Index', + title: 'Title', + author: 'Author', + createTime: 'Create time', + action: 'Action', + pagination: 'pagination', + reserveIndex: 'Reserve index', + restoreIndex: 'Restore index', + showSelections: 'Show selections', + hiddenSelections: 'Restore selections', + showExpandedRows: 'Show expanded rows', + hiddenExpandedRows: 'Hidden expanded rows', + header: 'Header' + }, + action: { + create: 'Create', + add: 'Add', + del: 'Delete', + delete: 'Delete', + edit: 'Edit', + update: 'Update', + preview: 'Preview', + more: 'More', + sync: 'Sync', + save: 'Save', + detail: 'Detail', + export: 'Export', + import: 'Import', + generate: 'Generate', + logout: 'Login Out', + test: 'Test', + typeCreate: 'Dict Type Create', + typeUpdate: 'Dict Type Eidt', + dataCreate: 'Dict Data Create', + dataUpdate: 'Dict Data Eidt', + fileUpload: 'File Upload' + }, + dialog: { + dialog: 'Dialog', + open: 'Open', + close: 'Close' + }, + sys: { + api: { + operationFailed: 'Operation failed', + errorTip: 'Error Tip', + errorMessage: 'The operation failed, the system is abnormal!', + timeoutMessage: 'Login timed out, please log in again!', + apiTimeoutMessage: 'The interface request timed out, please refresh the page and try again!', + apiRequestFailed: 'The interface request failed, please try again later!', + networkException: 'network anomaly', + networkExceptionMsg: + 'Please check if your network connection is normal! The network is abnormal', + + errMsg401: 'The user does not have permission (token, user name, password error)!', + errMsg403: 'The user is authorized, but access is forbidden!', + errMsg404: 'Network request error, the resource was not found!', + errMsg405: 'Network request error, request method not allowed!', + errMsg408: 'Network request timed out!', + errMsg500: 'Server error, please contact the administrator!', + errMsg501: 'The network is not implemented!', + errMsg502: 'Network Error!', + errMsg503: 'The service is unavailable, the server is temporarily overloaded or maintained!', + errMsg504: 'Network timeout!', + errMsg505: 'The http version does not support the request!', + errMsg901: 'Demo mode, no write operations are possible!' + }, + app: { + logoutTip: 'Reminder', + logoutMessage: 'Confirm to exit the system?', + menuLoading: 'Menu loading...' + }, + exception: { + backLogin: 'Back Login', + backHome: 'Back Home', + subTitle403: "Sorry, you don't have access to this page.", + subTitle404: 'Sorry, the page you visited does not exist.', + subTitle500: 'Sorry, the server is reporting an error.', + noDataTitle: 'No data on the current page.', + networkErrorTitle: 'Network Error', + networkErrorSubTitle: + 'Sorry, Your network connection has been disconnected, please check your network!' + }, + lock: { + unlock: 'Click to unlock', + alert: 'Lock screen password error', + backToLogin: 'Back to login', + entry: 'Enter the system', + placeholder: 'Please enter the lock screen password or user password' + }, + login: { + backSignIn: 'Back sign in', + mobileSignInFormTitle: 'Mobile sign in', + qrSignInFormTitle: 'Qr code sign in', + signInFormTitle: 'Sign in', + signUpFormTitle: 'Sign up', + forgetFormTitle: 'Reset password', + + signInTitle: 'Backstage management system', + signInDesc: 'Enter your personal details and get started!', + policy: 'I agree to the xxx Privacy Policy', + scanSign: `scanning the code to complete the login`, + + loginButton: 'Sign in', + registerButton: 'Sign up', + rememberMe: 'Remember me', + forgetPassword: 'Forget Password?', + otherSignIn: 'Sign in with', + + // notify + loginSuccessTitle: 'Login successful', + loginSuccessDesc: 'Welcome back', + + // placeholder + accountPlaceholder: 'Please input username', + passwordPlaceholder: 'Please input password', + smsPlaceholder: 'Please input sms code', + mobilePlaceholder: 'Please input mobile', + policyPlaceholder: 'Register after checking', + diffPwd: 'The two passwords are inconsistent', + + userName: 'Username', + password: 'Password', + confirmPassword: 'Confirm Password', + email: 'Email', + smsCode: 'SMS code', + mobile: 'Mobile' + } + }, + profile: { + user: { + title: 'Personal Information', + username: 'User Name', + nickname: 'Nick Name', + mobile: 'Phone Number', + email: 'User Mail', + dept: 'Department', + posts: 'Position', + roles: 'Own Role', + sex: 'Sex', + man: 'Man', + woman: 'Woman', + createTime: 'Created Date' + }, + info: { + title: 'Basic Information', + basicInfo: 'Basic Information', + resetPwd: 'Reset Password', + userSocial: 'Social Information' + }, + rules: { + nickname: 'Please Enter User Nickname', + mail: 'Please Input The Email Address', + truemail: 'Please Input The Correct Email Address', + phone: 'Please Enter The Phone Number', + truephone: 'Please Enter The Correct Phone Number' + }, + password: { + oldPassword: 'Old PassWord', + newPassword: 'New Password', + confirmPassword: 'Confirm Password', + oldPwdMsg: 'Please Enter Old Password', + newPwdMsg: 'Please Enter New Password', + cfPwdMsg: 'Please Enter Confirm Password', + diffPwd: 'The Passwords Entered Twice No Match' + } + }, + cropper: { + selectImage: 'Select Image', + uploadSuccess: 'Uploaded success!', + modalTitle: 'Avatar upload', + okText: 'Confirm and upload', + btn_reset: 'Reset', + btn_rotate_left: 'Counterclockwise rotation', + btn_rotate_right: 'Clockwise rotation', + btn_scale_x: 'Flip horizontal', + btn_scale_y: 'Flip vertical', + btn_zoom_in: 'Zoom in', + btn_zoom_out: 'Zoom out', + preview: 'Preivew' + } +} diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts new file mode 100644 index 0000000..100d449 --- /dev/null +++ b/src/locales/zh-CN.ts @@ -0,0 +1,441 @@ +export default { + common: { + inputText: '请输入', + selectText: '请选择', + startTimeText: '开始时间', + endTimeText: '结束时间', + login: '登录', + required: '该项为必填项', + loginOut: '退出系统', + document: '项目文档', + profile: '个人中心', + reminder: '温馨提示', + loginOutMessage: '是否退出本系统?', + back: '返回', + ok: '确定', + save: '保存', + cancel: '取消', + close: '关闭', + reload: '重新加载', + success: '成功', + closeTab: '关闭标签页', + closeTheLeftTab: '关闭左侧标签页', + closeTheRightTab: '关闭右侧标签页', + closeOther: '关闭其他标签页', + closeAll: '关闭全部标签页', + prevLabel: '上一步', + nextLabel: '下一步', + skipLabel: '跳过', + doneLabel: '结束', + menu: '菜单', + menuDes: '以路由的结构渲染的菜单栏', + collapse: '展开缩收', + collapseDes: '展开和缩放菜单栏', + tagsView: '标签页', + tagsViewDes: '用于记录路由历史记录', + tool: '工具', + toolDes: '用于设置定制系统', + query: '查询', + reset: '重置', + shrink: '收起', + expand: '展开', + confirmTitle: '系统提示', + exportMessage: '是否确认导出数据项?', + importMessage: '是否确认导入数据项?', + createSuccess: '新增成功', + updateSuccess: '修改成功', + delMessage: '是否删除所选中数据?', + delDataMessage: '是否删除数据?', + delNoData: '请选择需要删除的数据', + delSuccess: '删除成功', + index: '序号', + status: '状态', + createTime: '创建时间', + updateTime: '更新时间', + copy: '复制', + copySuccess: '复制成功', + copyError: '复制失败' + }, + error: { + noPermission: `抱歉,您无权访问此页面。`, + pageError: '抱歉,您访问的页面不存在。', + networkError: '抱歉,服务器报告错误。', + returnToHome: '返回首页' + }, + permission: { + hasPermission: `请设置操作权限标签值`, + hasRole: `请设置角色权限标签值` + }, + setting: { + projectSetting: '主题配置', + theme: '主题', + layout: '布局', + systemTheme: '系统主题', + menuTheme: '菜单主题', + interfaceDisplay: '界面显示', + breadcrumb: '面包屑', + breadcrumbIcon: '面包屑图标', + collapseMenu: '折叠菜单', + hamburgerIcon: '折叠图标', + screenfullIcon: '全屏图标', + sizeIcon: '尺寸图标', + localeIcon: '多语言图标', + messageIcon: '消息图标', + tagsView: '标签页', + logo: '标志', + greyMode: '灰色模式', + fixedHeader: '固定头部', + headerTheme: '头部主题', + cutMenu: '切割菜单', + copy: '拷贝', + clearAndReset: '清除缓存并且重置', + copySuccess: '拷贝成功', + copyFailed: '拷贝失败', + footer: '页脚', + uniqueOpened: '菜单手风琴', + tagsViewIcon: '标签页图标', + reExperienced: '请重新退出登录体验', + fixedMenu: '固定菜单' + }, + size: { + default: '默认', + large: '大', + small: '小' + }, + login: { + welcome: '欢迎使用本系统', + message: '欢迎使用本系统', + tenantname: '租户名称', + username: '用户名', + password: '密码', + code: '验证码', + login: '登录', + relogin: '重新登录', + otherLogin: '其他登录方式', + register: '注册', + checkPassword: '确认密码', + remember: '记住我', + hasUser: '已有账号?去登录', + forgetPassword: '忘记密码?', + tenantNamePlaceholder: '请输入租户名称', + usernamePlaceholder: '请输入用户名', + passwordPlaceholder: '请输入密码', + codePlaceholder: '请输入验证码', + mobileTitle: '手机登录', + mobileNumber: '手机号码', + mobileNumberPlaceholder: '请输入手机号码', + backLogin: '返回', + getSmsCode: '获取验证码', + btnMobile: '手机登录', + btnQRCode: '二维码登录', + qrcode: '扫描二维码登录', + btnRegister: '注册', + SmsSendMsg: '验证码已发送' + }, + captcha: { + verification: '请完成安全验证', + slide: '向右滑动完成验证', + point: '请依次点击', + success: '验证成功', + fail: '验证失败' + }, + router: { + login: '登录', + home: '首页', + analysis: '分析页', + workplace: '工作台' + }, + analysis: { + newUser: '新增用户', + unreadInformation: '未读消息', + transactionAmount: '成交金额', + totalShopping: '购物总量', + monthlySales: '每月销售额', + userAccessSource: '用户访问来源', + january: '一月', + february: '二月', + march: '三月', + april: '四月', + may: '五月', + june: '六月', + july: '七月', + august: '八月', + september: '九月', + october: '十月', + november: '十一月', + december: '十二月', + estimate: '预计', + actual: '实际', + directAccess: '直接访问', + mailMarketing: '邮件营销', + allianceAdvertising: '联盟广告', + videoAdvertising: '视频广告', + searchEngines: '搜索引擎', + weeklyUserActivity: '每周用户活跃量', + activeQuantity: '活跃量', + monday: '周一', + tuesday: '周二', + wednesday: '周三', + thursday: '周四', + friday: '周五', + saturday: '周六', + sunday: '周日' + }, + workplace: { + welcome: '你好', + happyDay: '祝你开心每一天!', + toady: '今日晴', + notice: '通知公告', + project: '项目数', + access: '项目访问', + toDo: '待办', + introduction: '一个正经的简介', + shortcutOperation: '快捷入口', + operation: '操作', + index: '指数', + personal: '个人', + team: '团队', + quote: '引用', + contribution: '贡献', + hot: '热度', + yield: '产量', + dynamic: '动态', + push: '推送', + follow: '关注' + }, + form: { + input: '输入框', + inputNumber: '数字输入框', + default: '默认', + icon: '图标', + mixed: '复合型', + textarea: '多行文本', + slot: '插槽', + position: '位置', + autocomplete: '自动补全', + select: '选择器', + selectGroup: '选项分组', + selectV2: '虚拟列表选择器', + cascader: '级联选择器', + switch: '开关', + rate: '评分', + colorPicker: '颜色选择器', + transfer: '穿梭框', + render: '渲染器', + radio: '单选框', + button: '按钮', + checkbox: '多选框', + slider: '滑块', + datePicker: '日期选择器', + shortcuts: '快捷选项', + today: '今天', + yesterday: '昨天', + aWeekAgo: '一周前', + week: '周', + year: '年', + month: '月', + dates: '日期', + daterange: '日期范围', + monthrange: '月份范围', + dateTimePicker: '日期时间选择器', + dateTimerange: '日期时间范围', + timePicker: '时间选择器', + timeSelect: '时间选择', + inputPassword: '密码输入框', + passwordStrength: '密码强度', + operate: '操作', + change: '更改', + restore: '还原', + disabled: '禁用', + disablement: '解除禁用', + delete: '删除', + add: '添加', + setValue: '设置值', + resetValue: '重置值', + set: '设置', + subitem: '子项', + formValidation: '表单验证', + verifyReset: '验证重置', + remark: '备注' + }, + watermark: { + watermark: '水印' + }, + table: { + table: '表格', + index: '序号', + title: '标题', + author: '作者', + createTime: '创建时间', + action: '操作', + pagination: '分页', + reserveIndex: '叠加序号', + restoreIndex: '还原序号', + showSelections: '显示多选', + hiddenSelections: '隐藏多选', + showExpandedRows: '显示展开行', + hiddenExpandedRows: '隐藏展开行', + header: '头部' + }, + action: { + create: '新增', + add: '新增', + del: '删除', + delete: '删除', + edit: '编辑', + update: '编辑', + preview: '预览', + more: '更多', + sync: '同步', + save: '保存', + detail: '详情', + export: '导出', + import: '导入', + generate: '生成', + logout: '强制退出', + test: '测试', + typeCreate: '字典类型新增', + typeUpdate: '字典类型编辑', + dataCreate: '字典数据新增', + dataUpdate: '字典数据编辑' + }, + dialog: { + dialog: '弹窗', + open: '打开', + close: '关闭' + }, + sys: { + api: { + operationFailed: '操作失败', + errorTip: '错误提示', + errorMessage: '操作失败,系统异常!', + timeoutMessage: '登录超时,请重新登录!', + apiTimeoutMessage: '接口请求超时,请刷新页面重试!', + apiRequestFailed: '请求出错,请稍候重试', + networkException: '网络异常', + networkExceptionMsg: '网络异常,请检查您的网络连接是否正常!', + errMsg401: '用户没有权限(令牌、用户名、密码错误)!', + errMsg403: '用户得到授权,但是访问是被禁止的。!', + errMsg404: '网络请求错误,未找到该资源!', + errMsg405: '网络请求错误,请求方法未允许!', + errMsg408: '网络请求超时!', + errMsg500: '服务器错误,请联系管理员!', + errMsg501: '网络未实现!', + errMsg502: '网络错误!', + errMsg503: '服务不可用,服务器暂时过载或维护!', + errMsg504: '网络超时!', + errMsg505: 'http版本不支持该请求!', + errMsg901: '演示模式,无法进行写操作!' + }, + app: { + logoutTip: '温馨提醒', + logoutMessage: '是否确认退出系统?', + menuLoading: '菜单加载中...' + }, + exception: { + backLogin: '返回登录', + backHome: '返回首页', + subTitle403: '抱歉,您无权访问此页面。', + subTitle404: '抱歉,您访问的页面不存在。', + subTitle500: '抱歉,服务器报告错误。', + noDataTitle: '当前页无数据', + networkErrorTitle: '网络错误', + networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!' + }, + lock: { + unlock: '点击解锁', + alert: '锁屏密码错误', + backToLogin: '返回登录', + entry: '进入系统', + placeholder: '请输入锁屏密码或者用户密码' + }, + login: { + backSignIn: '返回', + signInFormTitle: '登录', + ssoFormTitle: '三方授权', + mobileSignInFormTitle: '手机登录', + qrSignInFormTitle: '二维码登录', + signUpFormTitle: '注册', + forgetFormTitle: '重置密码', + signInTitle: '开箱即用的中后台管理系统', + signInDesc: '输入您的个人详细信息开始使用!', + policy: '我同意xxx隐私政策', + scanSign: `扫码后点击"确认",即可完成登录`, + loginButton: '登录', + registerButton: '注册', + rememberMe: '记住我', + forgetPassword: '忘记密码?', + otherSignIn: '其他登录方式', + // notify + loginSuccessTitle: '登录成功', + loginSuccessDesc: '欢迎回来', + // placeholder + accountPlaceholder: '请输入账号', + passwordPlaceholder: '请输入密码', + smsPlaceholder: '请输入验证码', + mobilePlaceholder: '请输入手机号码', + policyPlaceholder: '勾选后才能注册', + diffPwd: '两次输入密码不一致', + userName: '账号', + password: '密码', + confirmPassword: '确认密码', + email: '邮箱', + smsCode: '短信验证码', + mobile: '手机号码' + } + }, + profile: { + user: { + title: '个人信息', + username: '用户名称', + nickname: '用户昵称', + mobile: '手机号码', + email: '用户邮箱', + dept: '所属部门', + posts: '所属岗位', + roles: '所属角色', + sex: '性别', + man: '男', + woman: '女', + createTime: '创建日期' + }, + info: { + title: '基本信息', + basicInfo: '基本资料', + resetPwd: '修改密码', + userSocial: '社交信息' + }, + rules: { + nickname: '请输入用户昵称', + mail: '请输入邮箱地址', + truemail: '请输入正确的邮箱地址', + phone: '请输入正确的手机号码', + truephone: '请输入正确的手机号码' + }, + password: { + oldPassword: '旧密码', + newPassword: '新密码', + confirmPassword: '确认密码', + oldPwdMsg: '请输入旧密码', + newPwdMsg: '请输入新密码', + cfPwdMsg: '请输入确认密码', + pwdRules: '长度在 6 到 20 个字符', + diffPwd: '两次输入密码不一致' + } + }, + cropper: { + selectImage: '选择图片', + uploadSuccess: '上传成功', + modalTitle: '头像上传', + okText: '确认并上传', + btn_reset: '重置', + btn_rotate_left: '逆时针旋转', + btn_rotate_right: '顺时针旋转', + btn_scale_x: '水平翻转', + btn_scale_y: '垂直翻转', + btn_zoom_in: '放大', + btn_zoom_out: '缩小', + preview: '预览' + }, + 'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错 +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..bb6458c --- /dev/null +++ b/src/main.js @@ -0,0 +1,72 @@ +// 引入windi css +import '@/plugins/windi.css' + +// 导入全局的svg图标 +import '@/plugins/svgIcon' + +// 初始化多语言 +import { setupI18n } from '@/plugins/vueI18n' + +// 引入状态管理 +import { setupStore } from '@/store' + +// 全局组件 +import { setupGlobCom } from '@/components' + +// 引入 element-plus +import { setupElementPlus } from '@/plugins/elementPlus' + +// 引入 form-create +import { setupFormCreate } from '@/plugins/formCreate' + +// 引入全局样式 +import '@/styles/index.scss' + +// 引入动画 +import '@/plugins/animate.css' + +// 路由 +import router, { setupRouter } from '@/router' + +// 权限 +import { setupAuth } from '@/directives' + +import { createApp } from 'vue' + +import App from './App.vue' + +import './permission' + +import '@/plugins/tongji' // 百度统计 + +import Logger from '@/utils/Logger' +import VueDOMPurifyHTML from 'vue-dompurify-html' + +// 创建实例 +const setupAll = async () => { + const app = createApp(App) + + await setupI18n(app) + + setupStore(app) + + setupGlobCom(app) + + setupElementPlus(app) + + setupFormCreate(app) + + setupRouter(app) + + setupAuth(app) + + await router.isReady() + + app.use(VueDOMPurifyHTML) + + app.mount('#app') +} + +setupAll() + +Logger.prettyPrimary(`欢迎使用`, import.meta.env.VITE_APP_TITLE) diff --git a/src/permission.js b/src/permission.js new file mode 100644 index 0000000..60f59f9 --- /dev/null +++ b/src/permission.js @@ -0,0 +1,69 @@ +import router from './router' +import { isRelogin } from '@/config/axios/service' +import { getAccessToken } from '@/utils/auth' +import { useTitle } from '@/hooks/web/useTitle' +import { useNProgress } from '@/hooks/web/useNProgress' +import { usePageLoading } from '@/hooks/web/usePageLoading' +import { useDictStoreWithOut } from '@/store/modules/dict' +import { useUserStoreWithOut } from '@/store/modules/user' +import { usePermissionStoreWithOut } from '@/store/modules/permission' +import { getTenantId, getAppId } from '@/utils/auth' + +const { start, done } = useNProgress() + +const { loadStart, loadDone } = usePageLoading() +// 路由不重定向白名单 +const whiteList = ['/login', '/social-login', '/auth-redirect', '/bind', '/register', '/swagger'] + +// 路由加载前 +router.beforeEach(async (to, from, next) => { + start() + loadStart() + if (getAccessToken()) { + if (to.path === '/login') { + next({ path: '/' }) + } else { + // 获取所有字典 + const dictStore = useDictStoreWithOut() + const userStore = useUserStoreWithOut() + const permissionStore = usePermissionStoreWithOut() + if (!dictStore.getIsSetDict) { + await dictStore.setDictMap() + } + if (!userStore.getIsSetUser) { + isRelogin.show = true + await userStore.setUserInfoAction() + isRelogin.show = false + // 后端过滤菜单 + await permissionStore.generateRoutes() + permissionStore.getAddRouters.forEach((route) => { + router.addRoute(route) // 动态添加可访问路由表 + }) + const redirectPath = from.query.redirect || to.path + const redirect = decodeURIComponent(redirectPath) + const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } + next(nextData) + } else { + next() + } + } + } else { + if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + const tenantId = getTenantId() + const appId = getAppId() + if (tenantId && appId) { + next(`/crm/login?tenantId=${tenantId}&appId=${appId}&redirect=${to.fullPath}`) // 否则全部重定向到登录页 + } else { + next(`/crm/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 + } + } + } +}) + +router.afterEach((to) => { + useTitle(to?.meta?.title) + done() // 结束Progress + loadDone() +}) diff --git a/src/plugins/animate.css/index.ts b/src/plugins/animate.css/index.ts new file mode 100644 index 0000000..3e93451 --- /dev/null +++ b/src/plugins/animate.css/index.ts @@ -0,0 +1 @@ +import 'animate.css' diff --git a/src/plugins/cache/index.js b/src/plugins/cache/index.js new file mode 100644 index 0000000..fae09f8 --- /dev/null +++ b/src/plugins/cache/index.js @@ -0,0 +1,98 @@ +import router from '@/router' +import { name as appName } from '../../../package.json' + +let name = `${appName}-${import.meta.env.VITE_NODE_ENV}` + +const sessionCache = { + set(key, value) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + let storageStr = sessionStorage.getItem(name) + let storage = storageStr ? JSON.parse(storageStr) : {} + Reflect.set(storage, key, value) + sessionStorage.setItem(name, JSON.stringify(storage)) + } + }, + get(key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + let storageStr = sessionStorage.getItem(name) + let storage = storageStr ? JSON.parse(storageStr) : {} + return storage[key] + }, + remove(key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + let storageStr = sessionStorage.getItem(name) + let storage = storageStr ? JSON.parse(storageStr) : {} + delete storage[key] + sessionStorage.setItem(name, JSON.stringify(storage)) + } +} +const localCache = { + set(key, value) { + if (!localStorage) { + return + } + if (key != null && value != null) { + let storageStr = localStorage.getItem(name) + let storage = storageStr ? JSON.parse(storageStr) : {} + Reflect.set(storage, key, value) + localStorage.setItem(name, JSON.stringify(storage)) + } + }, + get(key) { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + let storageStr = localStorage.getItem(name) + let storage = storageStr ? JSON.parse(storageStr) : {} + return storage[key] + }, + remove(key) { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + let storageStr = localStorage.getItem(name) + let storage = storageStr ? JSON.parse(storageStr) : {} + delete storage[key] + localStorage.setItem(name, JSON.stringify(storage)) + } +} + +function setParams(params) { + sessionCache.set(`${router.currentRoute.value.name}-search`, params) +} + +function getParams() { + return sessionCache.get(`${router.currentRoute.value.name}-search`) +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache, + setParams, + getParams +} diff --git a/src/plugins/echarts/index.ts b/src/plugins/echarts/index.ts new file mode 100644 index 0000000..bfccbb4 --- /dev/null +++ b/src/plugins/echarts/index.ts @@ -0,0 +1,45 @@ +import * as echarts from 'echarts/core' + +import { + BarChart, + LineChart, + PieChart, + MapChart, + PictorialBarChart, + RadarChart, + GaugeChart +} from 'echarts/charts' + +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent, + ToolboxComponent +} from 'echarts/components' + +import { CanvasRenderer } from 'echarts/renderers' + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + ToolboxComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + CanvasRenderer, + PictorialBarChart, + RadarChart, + GaugeChart +]) + +export default echarts diff --git a/src/plugins/elementPlus/index.ts b/src/plugins/elementPlus/index.ts new file mode 100644 index 0000000..0ae2a8b --- /dev/null +++ b/src/plugins/elementPlus/index.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue' +// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题 +import { ElLoading, ElScrollbar, ElButton } from 'element-plus' + +const plugins = [ElLoading] + +const components = [ElScrollbar, ElButton] + +export const setupElementPlus = (app: App) => { + plugins.forEach((plugin) => { + app.use(plugin) + }) + + components.forEach((component) => { + app.component(component.name, component) + }) +} diff --git a/src/plugins/formCreate/index.ts b/src/plugins/formCreate/index.ts new file mode 100644 index 0000000..a6cb821 --- /dev/null +++ b/src/plugins/formCreate/index.ts @@ -0,0 +1,43 @@ +import type { App } from 'vue' +// 👇使用 form-create 需额外全局引入 element plus 组件 +import { + ElAside, + ElPopconfirm, + ElHeader, + ElMain, + ElContainer, + ElDivider, + ElTransfer, + ElAlert, + ElTabs, + ElTable, + ElTableColumn, + ElTabPane +} from 'element-plus' + +import formCreate from '@form-create/element-ui' +import install from '@form-create/element-ui/auto-import' + +const components = [ + ElAside, + ElPopconfirm, + ElHeader, + ElMain, + ElContainer, + ElDivider, + ElTransfer, + ElAlert, + ElTabs, + ElTable, + ElTableColumn, + ElTabPane +] + +// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档 +export const setupFormCreate = (app: App) => { + components.forEach((component) => { + app.component(component.name, component) + }) + formCreate.use(install) + app.use(formCreate) +} diff --git a/src/plugins/svgIcon/index.ts b/src/plugins/svgIcon/index.ts new file mode 100644 index 0000000..b5b7f70 --- /dev/null +++ b/src/plugins/svgIcon/index.ts @@ -0,0 +1,3 @@ +import 'virtual:svg-icons-register' + +import '@purge-icons/generated' diff --git a/src/plugins/tongji/index.ts b/src/plugins/tongji/index.ts new file mode 100644 index 0000000..ec261a1 --- /dev/null +++ b/src/plugins/tongji/index.ts @@ -0,0 +1,23 @@ +import router from '@/router' + +// 用于 router push +window._hmt = window._hmt || [] +// HM_ID +const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE +;(function () { + // 有值的时候,才开启 + if (!HM_ID) { + return + } + const hm = document.createElement('script') + hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID + const s = document.getElementsByTagName('script')[0] + s.parentNode.insertBefore(hm, s) +})() + +router.afterEach(function (to) { + if (!HM_ID) { + return + } + _hmt.push(['_trackPageview', to.fullPath]) +}) diff --git a/src/plugins/vueI18n/helper.ts b/src/plugins/vueI18n/helper.ts new file mode 100644 index 0000000..da6bc8c --- /dev/null +++ b/src/plugins/vueI18n/helper.ts @@ -0,0 +1,3 @@ +export const setHtmlPageLang = (locale: LocaleType) => { + document.querySelector('html')?.setAttribute('lang', locale) +} diff --git a/src/plugins/vueI18n/index.ts b/src/plugins/vueI18n/index.ts new file mode 100644 index 0000000..f845b13 --- /dev/null +++ b/src/plugins/vueI18n/index.ts @@ -0,0 +1,42 @@ +import type { App } from 'vue' +import { createI18n } from 'vue-i18n' +import { useLocaleStoreWithOut } from '@/store/modules/locale' +import type { I18n, I18nOptions } from 'vue-i18n' +import { setHtmlPageLang } from './helper' + +export let i18n: ReturnType + +const createI18nOptions = async (): Promise => { + const localeStore = useLocaleStoreWithOut() + const locale = localeStore.getCurrentLocale + const localeMap = localeStore.getLocaleMap + const defaultLocal = await import(`../../locales/${locale.lang}.ts`) + const message = defaultLocal.default ?? {} + + setHtmlPageLang(locale.lang) + + localeStore.setCurrentLocale({ + lang: locale.lang + // elLocale: elLocal + }) + + return { + legacy: false, + locale: locale.lang, + fallbackLocale: locale.lang, + messages: { + [locale.lang]: message + }, + availableLocales: localeMap.map((v) => v.lang), + sync: true, + silentTranslationWarn: true, + missingWarn: false, + silentFallbackWarn: true + } +} + +export const setupI18n = async (app: App) => { + const options = await createI18nOptions() + i18n = createI18n(options) as I18n + app.use(i18n) +} diff --git a/src/plugins/windi.css/index.ts b/src/plugins/windi.css/index.ts new file mode 100644 index 0000000..dbdfbbf --- /dev/null +++ b/src/plugins/windi.css/index.ts @@ -0,0 +1,3 @@ +import 'virtual:windi.css' + +import 'virtual:windi-devtools' diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..b818421 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,28 @@ +import type { App } from 'vue' +import type { RouteRecordRaw } from 'vue-router' +import { createRouter, createWebHistory } from 'vue-router' +import remainingRouter from './modules/remaining' + +// 创建路由实例 +const router = createRouter({ + history: createWebHistory(import.meta.env.VITE_BASE_PATH), // createWebHashHistory URL带#,createWebHistory URL不带# + strict: true, + routes: remainingRouter as RouteRecordRaw[], + scrollBehavior: () => ({ left: 0, top: 0 }) +}) + +export const resetRouter = (): void => { + const resetWhiteNameList = ['Redirect', 'Login', 'NoFind', 'Root'] + router.getRoutes().forEach((route) => { + const { name } = route + if (name && !resetWhiteNameList.includes(name as string)) { + router.hasRoute(name) && router.removeRoute(name) + } + }) +} + +export const setupRouter = (app: App) => { + app.use(router) +} + +export default router diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts new file mode 100644 index 0000000..4458222 --- /dev/null +++ b/src/router/modules/remaining.ts @@ -0,0 +1,161 @@ +import { Layout } from '@/utils/routerHelper' + +const { t } = useI18n() +/** + * redirect: noredirect 当设置 noredirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' 设定路由的名字,一定要填写不然使用时会出现各种问题 + * meta : { + hidden: true 当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false) + + alwaysShow: true 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式, + 只有一个时,会将那个子路由当做根路由显示在侧边栏, + 若你想不管路由下面的 children 声明的个数都显示你的根路由, + 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则, + 一直显示根路由(默认 false) + + title: 'title' 设置该路由在侧边栏和面包屑中展示的名字 + + icon: 'svg-name' 设置该路由的图标 + + noCache: true 如果设置为true,则不会被 缓存(默认 false) + + breadcrumb: false 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) + + affix: true 如果设置为true,则会一直固定在tag项中(默认 false) + + noTagsView: true 如果设置为true,则不会出现在tag中(默认 false) + + activeMenu: '/dashboard' 显示高亮的路由路径 + + followAuth: '/dashboard' 跟随哪个路由进行权限过滤 + + canTo: true 设置为true即使hidden为true,也依然可以进行路由跳转(默认 false) + } + **/ +const remainingRouter: AppRouteRecordRaw[] = [ + { + path: '/redirect', + component: Layout, + name: 'Redirect', + children: [ + { + path: '/redirect/:path(.*)', + name: 'Redirect', + component: () => import('@/views/Redirect/Redirect.vue'), + meta: {} + } + ], + meta: { + hidden: true, + noTagsView: true + } + }, + { + path: '', + component: Layout, + redirect: '/Home/index', + name: '', + meta: { + title: '首页', + hidden: true + } + }, + { + path: '/swagger', + component: () => import('@/views/Basic/Swagger/index.vue'), + name: 'swagger', + meta: { + title: '接口文档', + noCache: true, + hidden: true, + canTo: true, + icon: '' + } + }, + { + path: '/user', + component: Layout, + name: 'UserInfo', + meta: { + hidden: true + }, + children: [ + { + path: 'profile', + component: () => import('@/views/Profile/Index.vue'), + name: 'Profile', + meta: { + canTo: true, + hidden: true, + noTagsView: false, + icon: 'ep:user', + title: t('common.profile') + } + }, + { + path: 'notify-message', + component: () => import('@/views/Profile/NotifyMessage.vue'), + name: 'NotifyMessage', + meta: { + canTo: true, + hidden: true, + noTagsView: false, + icon: 'ep:message', + title: '我的站内信' + } + } + ] + }, + { + path: '/login', + component: () => import('@/views/Login/Login.vue'), + name: 'Login', + meta: { + hidden: true, + title: t('router.login'), + noTagsView: true + } + }, + { + path: '/sso', + component: () => import('@/views/Login/Login.vue'), + name: 'SSOLogin', + meta: { + hidden: true, + title: t('router.login'), + noTagsView: true + } + }, + { + path: '/403', + component: () => import('@/views/Error/403.vue'), + name: 'NoAccess', + meta: { + hidden: true, + title: '403', + noTagsView: true + } + }, + { + path: '/404', + component: () => import('@/views/Error/404.vue'), + name: 'NoFound', + meta: { + hidden: true, + title: '404', + noTagsView: true + } + }, + { + path: '/500', + component: () => import('@/views/Error/500.vue'), + name: 'Error', + meta: { + hidden: true, + title: '500', + noTagsView: true + } + } +] + +export default remainingRouter diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..65964ea --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,10 @@ +import type { App } from 'vue' +import { createPinia } from 'pinia' + +const store = createPinia() + +export const setupStore = (app: App) => { + app.use(store) +} + +export { store } diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..5810b43 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,286 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import { setCssVar, humpToUnderline } from '@/utils' +import { ElMessage } from 'element-plus' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +import { ElementPlusSize } from '@/types/elementPlus' +import { LayoutType } from '@/types/layout' +import { ThemeTypes } from '@/types/theme' +import { getAppInfo } from '@/api/login' + +const { wsCache } = useCache() + +interface AppState { + breadcrumb: boolean + breadcrumbIcon: boolean + collapse: boolean + uniqueOpened: boolean + hamburger: boolean + screenfull: boolean + size: boolean + locale: boolean + message: boolean + tagsView: boolean + tagsViewIcon: boolean + logo: boolean + fixedHeader: boolean + greyMode: boolean + pageLoading: boolean + layout: LayoutType + title: string + userInfo: string + isDark: boolean + currentSize: ElementPlusSize + sizeMap: ElementPlusSize[] + mobile: boolean + footer: boolean + theme: ThemeTypes + fixedMenu: boolean + appInfo: any +} + +export const useAppStore = defineStore('app', { + state: (): AppState => { + return { + userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突 + sizeMap: ['default', 'large', 'small'], + mobile: false, // 是否是移动端 + title: import.meta.env.VITE_APP_TITLE, // 标题 + appInfo: wsCache.get('appInfo'), + pageLoading: false, // 路由跳转loading + + breadcrumb: true, // 面包屑 + breadcrumbIcon: false, // 面包屑图标 + collapse: false, // 折叠菜单 + uniqueOpened: true, // 是否只保持一个子菜单的展开 + hamburger: true, // 折叠图标 + screenfull: true, // 全屏图标 + size: false, // 尺寸图标 + locale: false, // 多语言图标 + message: true, // 消息图标 + tagsView: true, // 标签页 + tagsViewIcon: false, // 是否显示标签图标 + logo: true, // logo + fixedHeader: true, // 固定toolheader + footer: false, // 显示页脚 + greyMode: false, // 是否开始灰色模式,用于特殊悼念日 + fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单 + + layout: wsCache.get(CACHE_KEY.LAYOUT) || 'classic', // layout布局 + isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式 + currentSize: wsCache.get('default') || 'default', // 组件尺寸 + theme: wsCache.get(CACHE_KEY.THEME) || { + // 主题色 + elColorPrimary: '#409eff', + // 左侧菜单边框颜色 + leftMenuBorderColor: 'inherit', + // 左侧菜单背景颜色 + leftMenuBgColor: '#001529', + // 左侧菜单浅色背景颜色 + leftMenuBgLightColor: '#0f2438', + // 左侧菜单选中背景颜色 + leftMenuBgActiveColor: 'var(--el-color-primary)', + // 左侧菜单收起选中背景颜色 + leftMenuCollapseBgActiveColor: 'var(--el-color-primary)', + // 左侧菜单字体颜色 + leftMenuTextColor: '#bfcbd9', + // 左侧菜单选中字体颜色 + leftMenuTextActiveColor: '#fff', + // logo字体颜色 + logoTitleTextColor: '#fff', + // logo边框颜色 + logoBorderColor: 'inherit', + // 头部背景颜色 + topHeaderBgColor: '#fff', + // 头部字体颜色 + topHeaderTextColor: 'inherit', + // 头部悬停颜色 + topHeaderHoverColor: '#f6f6f6', + // 头部边框颜色 + topToolBorderColor: '#eee' + } + } + }, + getters: { + getBreadcrumb(): boolean { + return this.breadcrumb + }, + getBreadcrumbIcon(): boolean { + return this.breadcrumbIcon + }, + getCollapse(): boolean { + return this.collapse + }, + getUniqueOpened(): boolean { + return this.uniqueOpened + }, + getHamburger(): boolean { + return this.hamburger + }, + getScreenfull(): boolean { + return this.screenfull + }, + getSize(): boolean { + return this.size + }, + getLocale(): boolean { + return this.locale + }, + getMessage(): boolean { + return this.message + }, + getTagsView(): boolean { + return this.tagsView + }, + getTagsViewIcon(): boolean { + return this.tagsViewIcon + }, + getLogo(): boolean { + return this.logo + }, + getFixedHeader(): boolean { + return this.fixedHeader + }, + getGreyMode(): boolean { + return this.greyMode + }, + getFixedMenu(): boolean { + return this.fixedMenu + }, + getPageLoading(): boolean { + return this.pageLoading + }, + getLayout(): LayoutType { + return this.layout + }, + getTitle(): string { + return this.title + }, + getUserInfo(): string { + return this.userInfo + }, + getIsDark(): boolean { + return this.isDark + }, + getCurrentSize(): ElementPlusSize { + return this.currentSize + }, + getSizeMap(): ElementPlusSize[] { + return this.sizeMap + }, + getMobile(): boolean { + return this.mobile + }, + getTheme(): ThemeTypes { + return this.theme + }, + getFooter(): boolean { + return this.footer + }, + getAppInfo(): any { + return this.appInfo + } + }, + actions: { + setBreadcrumb(breadcrumb: boolean) { + this.breadcrumb = breadcrumb + }, + setBreadcrumbIcon(breadcrumbIcon: boolean) { + this.breadcrumbIcon = breadcrumbIcon + }, + setCollapse(collapse: boolean) { + this.collapse = collapse + }, + setUniqueOpened(uniqueOpened: boolean) { + this.uniqueOpened = uniqueOpened + }, + setHamburger(hamburger: boolean) { + this.hamburger = hamburger + }, + setScreenfull(screenfull: boolean) { + this.screenfull = screenfull + }, + setSize(size: boolean) { + this.size = size + }, + setLocale(locale: boolean) { + this.locale = locale + }, + setMessage(message: boolean) { + this.message = message + }, + setTagsView(tagsView: boolean) { + this.tagsView = tagsView + }, + setTagsViewIcon(tagsViewIcon: boolean) { + this.tagsViewIcon = tagsViewIcon + }, + setLogo(logo: boolean) { + this.logo = logo + }, + setFixedHeader(fixedHeader: boolean) { + this.fixedHeader = fixedHeader + }, + setGreyMode(greyMode: boolean) { + this.greyMode = greyMode + }, + setFixedMenu(fixedMenu: boolean) { + wsCache.set('fixedMenu', fixedMenu) + this.fixedMenu = fixedMenu + }, + setPageLoading(pageLoading: boolean) { + this.pageLoading = pageLoading + }, + setLayout(layout: LayoutType) { + if (this.mobile && layout !== 'classic') { + ElMessage.warning('移动端模式下不支持切换其他布局') + return + } + this.layout = layout + wsCache.set(CACHE_KEY.LAYOUT, this.layout) + }, + setTitle(title: string) { + this.title = title + }, + setIsDark(isDark: boolean) { + this.isDark = isDark + if (this.isDark) { + document.documentElement.classList.add('dark') + document.documentElement.classList.remove('light') + } else { + document.documentElement.classList.add('light') + document.documentElement.classList.remove('dark') + } + wsCache.set(CACHE_KEY.IS_DARK, this.isDark) + }, + setCurrentSize(currentSize: ElementPlusSize) { + this.currentSize = currentSize + wsCache.set('currentSize', this.currentSize) + }, + setMobile(mobile: boolean) { + this.mobile = mobile + }, + setTheme(theme: ThemeTypes) { + this.theme = Object.assign(this.theme, theme) + wsCache.set(CACHE_KEY.THEME, this.theme) + }, + setCssVarTheme() { + for (const key in this.theme) { + setCssVar(`--${humpToUnderline(key)}`, this.theme[key]) + } + }, + setFooter(footer: boolean) { + this.footer = footer + }, + async setAppInfo(appId: number) { + const appInfo = await getAppInfo(appId) + wsCache.set('appInfo', appInfo) + this.appInfo = appInfo + return appInfo + } + } +}) + +export const useAppStoreWithOut = () => { + return useAppStore(store) +} diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts new file mode 100644 index 0000000..822547b --- /dev/null +++ b/src/store/modules/dict.ts @@ -0,0 +1,104 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +// @ts-ignore +import { DictDataVO } from '@/api/system/dict/types' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +const { wsCache } = useCache('sessionStorage') +import { listSimpleDictData } from '@/api/system/dict/dict.data' + +export interface DictValueType { + value: any + label: string + clorType?: string + cssClass?: string +} +export interface DictTypeType { + dictType: string + dictValue: DictValueType[] +} +export interface DictState { + dictMap: Map + isSetDict: boolean +} + +export const useDictStore = defineStore('dict', { + state: (): DictState => ({ + dictMap: new Map(), + isSetDict: false + }), + getters: { + getDictMap(): Recordable { + const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE) + if (dictMap) { + this.dictMap = dictMap + } + return this.dictMap + }, + getIsSetDict(): boolean { + return this.isSetDict + } + }, + actions: { + async setDictMap() { + const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE) + if (dictMap) { + this.dictMap = dictMap + this.isSetDict = true + } else { + const res = await listSimpleDictData() + // 设置数据 + const dictDataMap = new Map() + res.forEach((dictData: DictDataVO) => { + // 获得 dictType 层级 + const enumValueObj = dictDataMap[dictData.dictType] + if (!enumValueObj) { + dictDataMap[dictData.dictType] = [] + } + // 处理 dictValue 层级 + dictDataMap[dictData.dictType].push({ + value: dictData.value, + label: dictData.label, + colorType: dictData.colorType, + cssClass: dictData.cssClass + }) + }) + this.dictMap = dictDataMap + this.isSetDict = true + wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期 + } + }, + getDictByType(type: string) { + if (!this.isSetDict) { + this.setDictMap() + } + return this.dictMap[type] + }, + async resetDict() { + wsCache.delete(CACHE_KEY.DICT_CACHE) + const res = await listSimpleDictData() + // 设置数据 + const dictDataMap = new Map() + res.forEach((dictData: DictDataVO) => { + // 获得 dictType 层级 + const enumValueObj = dictDataMap[dictData.dictType] + if (!enumValueObj) { + dictDataMap[dictData.dictType] = [] + } + // 处理 dictValue 层级 + dictDataMap[dictData.dictType].push({ + value: dictData.value, + label: dictData.label, + colorType: dictData.colorType, + cssClass: dictData.cssClass + }) + }) + this.dictMap = dictDataMap + this.isSetDict = true + wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期 + } + } +}) + +export const useDictStoreWithOut = () => { + return useDictStore(store) +} diff --git a/src/store/modules/locale.ts b/src/store/modules/locale.ts new file mode 100644 index 0000000..1fc772a --- /dev/null +++ b/src/store/modules/locale.ts @@ -0,0 +1,59 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import en from 'element-plus/es/locale/lang/en' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +import { LocaleDropdownType } from '@/types/localeDropdown' + +const { wsCache } = useCache() + +const elLocaleMap = { + 'zh-CN': zhCn, + en: en +} +interface LocaleState { + currentLocale: LocaleDropdownType + localeMap: LocaleDropdownType[] +} + +export const useLocaleStore = defineStore('locales', { + state: (): LocaleState => { + return { + currentLocale: { + lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN', + elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN'] + }, + // 多语言 + localeMap: [ + { + lang: 'zh-CN', + name: '简体中文' + }, + { + lang: 'en', + name: 'English' + } + ] + } + }, + getters: { + getCurrentLocale(): LocaleDropdownType { + return this.currentLocale + }, + getLocaleMap(): LocaleDropdownType[] { + return this.localeMap + } + }, + actions: { + setCurrentLocale(localeMap: LocaleDropdownType) { + // this.locale = Object.assign(this.locale, localeMap) + this.currentLocale.lang = localeMap?.lang + this.currentLocale.elLocale = elLocaleMap[localeMap?.lang] + wsCache.set(CACHE_KEY.LANG, localeMap?.lang) + } + } +}) + +export const useLocaleStoreWithOut = () => { + return useLocaleStore(store) +} diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..27f8ce8 --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,66 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import { cloneDeep } from 'lodash-es' +import remainingRouter from '@/router/modules/remaining' +import { generateRoute, flatMultiLevelRoutes } from '@/utils/routerHelper' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' + +const { wsCache } = useCache() + +export interface PermissionState { + routers: AppRouteRecordRaw[] + addRouters: AppRouteRecordRaw[] + menuTabRouters: AppRouteRecordRaw[] +} + +export const usePermissionStore = defineStore('permission', { + state: (): PermissionState => ({ + routers: [], + addRouters: [], + menuTabRouters: [] + }), + getters: { + getRouters(): AppRouteRecordRaw[] { + return this.routers + }, + getAddRouters(): AppRouteRecordRaw[] { + return flatMultiLevelRoutes(cloneDeep(this.addRouters)) + }, + getMenuTabRouters(): AppRouteRecordRaw[] { + return this.menuTabRouters + } + }, + actions: { + async generateRoutes(): Promise { + return new Promise(async (resolve) => { + let res: AppCustomRouteRecordRaw[] = [] + if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) { + res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[] + } + const routerMap: AppRouteRecordRaw[] = generateRoute(res) + // 动态路由,404一定要放到最后面 + this.addRouters = routerMap.concat([ + { + path: '/:path(.*)*', + redirect: '/404', + name: '404Page', + meta: { + hidden: true, + breadcrumb: false + } + } + ]) + // 渲染菜单的所有路由 + this.routers = cloneDeep(remainingRouter).concat(routerMap) + resolve() + }) + }, + setMenuTabRouters(routers: AppRouteRecordRaw[]): void { + this.menuTabRouters = routers + } + } +}) + +export const usePermissionStoreWithOut = () => { + return usePermissionStore(store) +} diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..a60d0e4 --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,140 @@ +import router from '@/router' +import type { RouteLocationNormalizedLoaded } from 'vue-router' +import { getRawRoute } from '@/utils/routerHelper' +import { defineStore } from 'pinia' +import { store } from '../index' +import { findIndex } from '@/utils' + +export interface TagsViewState { + visitedViews: RouteLocationNormalizedLoaded[] + cachedViews: Set +} + +export const useTagsViewStore = defineStore('tagsView', { + state: (): TagsViewState => ({ + visitedViews: [], + cachedViews: new Set() + }), + getters: { + getVisitedViews(): RouteLocationNormalizedLoaded[] { + return this.visitedViews + }, + getCachedViews(): string[] { + return Array.from(this.cachedViews) + } + }, + actions: { + // 新增缓存和tag + addView(view: RouteLocationNormalizedLoaded): void { + this.addVisitedView(view) + this.addCachedView() + }, + // 新增tag + addVisitedView(view: RouteLocationNormalizedLoaded) { + if (this.visitedViews.some((v) => v.path === view.path)) return + if (view.meta?.noTagsView) return + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ) + }, + // 新增缓存 + addCachedView() { + const cacheMap: Set = new Set() + for (const v of this.visitedViews) { + const item = getRawRoute(v) + const needCache = !item.meta?.noCache + if (!needCache) { + continue + } + const name = item.name as string + cacheMap.add(name) + } + if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString()) + return + this.cachedViews = cacheMap + }, + // 删除某个 + delView(view: RouteLocationNormalizedLoaded) { + this.delVisitedView(view) + this.delCachedView() + }, + // 删除tag + delVisitedView(view: RouteLocationNormalizedLoaded) { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1) + break + } + } + }, + // 删除缓存 + delCachedView() { + const route = router.currentRoute.value + const index = findIndex(this.getCachedViews, (v) => v === route.name) + if (index > -1) { + this.cachedViews.delete(this.getCachedViews[index]) + } + }, + // 删除所有缓存和tag + delAllViews() { + this.delAllVisitedViews() + this.delCachedView() + }, + // 删除所有tag + delAllVisitedViews() { + // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix) + this.visitedViews = [] + }, + // 删除其他 + delOthersViews(view: RouteLocationNormalizedLoaded) { + this.delOthersVisitedViews(view) + this.addCachedView() + }, + // 删除其他tag + delOthersVisitedViews(view: RouteLocationNormalizedLoaded) { + this.visitedViews = this.visitedViews.filter((v) => { + return v?.meta?.affix || v.path === view.path + }) + }, + // 删除左侧 + delLeftViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.path === view.path + ) + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.path === view.path || i > index + }) + this.addCachedView() + } + }, + // 删除右侧 + delRightViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.path === view.path + ) + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.path === view.path || i < index + }) + this.addCachedView() + } + }, + updateVisitedView(view: RouteLocationNormalizedLoaded) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + } + } +}) + +export const useTagsViewStoreWithOut = () => { + return useTagsViewStore(store) +} diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..a612d56 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,89 @@ +import { store } from '../index' +import { defineStore } from 'pinia' +import { getAccessToken, removeToken } from '@/utils/auth' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +import { getInfo, loginOut } from '@/api/login' + +const { wsCache } = useCache() + +interface UserVO { + id: number + avatar: string + nickname: string +} +interface UserInfoVO { + permissions: string[] + roles: string[] + isSetUser: boolean + user: UserVO +} + +export const useUserStore = defineStore('admin-user', { + state: (): UserInfoVO => ({ + permissions: [], + roles: [], + isSetUser: false, + user: { + id: 0, + avatar: '', + nickname: '' + } + }), + getters: { + getPermissions(): string[] { + return this.permissions + }, + getRoles(): string[] { + return this.roles + }, + getIsSetUser(): boolean { + return this.isSetUser + }, + getUser(): UserVO { + return this.user + } + }, + actions: { + async setUserInfoAction() { + if (!getAccessToken()) { + this.resetState() + return null + } + let userInfo = wsCache.get(CACHE_KEY.USER) + if (!userInfo) { + userInfo = await getInfo({}) + } + this.permissions = userInfo.permissions + this.roles = userInfo.roles + this.user = userInfo.user + this.isSetUser = true + wsCache.set(CACHE_KEY.USER, userInfo) + wsCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus) + }, + async loginOut() { + await loginOut() + removeToken() + wsCache.clear() + this.resetState() + }, + resetState() { + this.permissions = [] + this.roles = [] + this.isSetUser = false + this.user = { + id: 0, + avatar: '', + nickname: '' + } + }, + refresh() { + wsCache.delete(CACHE_KEY.USER) + this.resetState() + window.location.href = '' + } + } +}) + +export const useUserStoreWithOut = () => { + return useUserStore(store) +} diff --git a/src/styles/global.module.scss b/src/styles/global.module.scss new file mode 100644 index 0000000..8448a92 --- /dev/null +++ b/src/styles/global.module.scss @@ -0,0 +1,6 @@ +@import './variables.scss'; +// 导出变量 +:export { + namespace: $namespace; + elNamespace: $elNamespace; +} diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..34381d3 --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,71 @@ +@import './var.css'; +@import 'element-plus/theme-chalk/dark/css-vars.css'; + +.reset-margin [class*='el-icon'] + span { + margin-left: 2px !important; +} + +// 解决抽屉弹出时,body宽度变化的问题 +.el-popup-parent--hidden { + width: 100% !important; +} + +// 解决表格内容超过表格总宽度后,横向滚动条前端顶不到表格边缘的问题 +.el-scrollbar__bar { + display: flex; + justify-content: flex-start; +} + +.el-dialog__body { + overflow-y: auto; + padding-top: 18px; + padding-bottom: 0; + // max-height: calc(100% - 118px); +} + +/* nprogress 适配 element-plus 的主题色 */ +#nprogress { + & .bar { + background-color: var(--el-color-primary) !important; + } + + & .peg { + box-shadow: 0 0 10px var(--el-color-primary), 0 0 5px var(--el-color-primary) !important; + } + + & .spinner-icon { + border-top-color: var(--el-color-primary); + border-left-color: var(--el-color-primary); + } +} + +.search-form-item { + margin-right: 10px !important; + margin-bottom: 10px !important; + & .el-form-item__label { + padding-right: 0; + overflow: hidden; + } +} + +.crud-form-item .el-input__wrapper { + width: 100%; +} + +.el-dialog { + margin-top: 5vh; + // max-height: 90vh; +} + +.el-form--inline .el-form-item { + margin-right: 15px; +} + +.el-row .el-form-item { + .el-input, + .el-select, + .el-input-number, + .el-input__wrapper { + width: 100%; + } +} diff --git a/src/styles/theme.scss b/src/styles/theme.scss new file mode 100644 index 0000000..39b03b3 --- /dev/null +++ b/src/styles/theme.scss @@ -0,0 +1,6 @@ +// .text-color { +// color: var(--el-text-color-regular); +// } +// .dark .dark\:text-color { +// color: rgba(255, 255, 255, var(--dark-text-color)); +// } diff --git a/src/styles/var.css b/src/styles/var.css new file mode 100644 index 0000000..2333ca0 --- /dev/null +++ b/src/styles/var.css @@ -0,0 +1,73 @@ +:root { + --dark-bg-color: #293146; + + /* left menu start */ + --left-menu-border-color: '#eee'; + + --left-menu-max-width: 200px; + + --left-menu-min-width: 64px; + + --left-menu-bg-color: #001529; + + --left-menu-bg-light-color: #0f2438; + + --left-menu-bg-active-color: var(--el-color-primary); + + --left-menu-text-color: #bfcbd9; + + --left-menu-text-active-color: #fff; + + --left-menu-collapse-bg-active-color: var(--el-color-primary); + /* left menu end */ + + /* logo start */ + --logo-height: 40px; + + --logo-title-text-color: #fff; + + --logo-border-color: 'inherit'; + /* logo end */ + + /* header start */ + --top-header-bg-color: '#fff'; + + --top-header-text-color: 'inherit'; + + --top-header-hover-color: #f6f6f6; + + --top-tool-height: var(--logo-height); + + --top-tool-p-x: 0; + + --top-tool-border-color: #eee; + + --tags-view-height: 35px; + + --tags-view-border-color: #eee; + /* header start */ + + /* tab menu start */ + --tab-menu-max-width: 80px; + + --tab-menu-min-width: 30px; + + --tab-menu-collapse-height: 36px; + + --tab-menu-border-color: #eee; + /* tab menu end */ + + --app-content-padding: 20px; + + --app-contnet-bg-color: #f5f7f9; + + --app-footer-height: 50px; + + --transition-time-02: 0.2s; +} + +html, +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..00b66f1 --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,4 @@ +// 命名空间 +$namespace: v; +// el命名空间 +$elNamespace: el; diff --git a/src/types/components.d.ts b/src/types/components.d.ts new file mode 100644 index 0000000..8de1f33 --- /dev/null +++ b/src/types/components.d.ts @@ -0,0 +1,56 @@ +export type ComponentName = + | 'Radio' + | 'RadioButton' + | 'Checkbox' + | 'CheckboxButton' + | 'Input' + | 'Autocomplete' + | 'InputNumber' + | 'Select' + | 'Cascader' + | 'Switch' + | 'Slider' + | 'TimePicker' + | 'DatePicker' + | 'Rate' + | 'ColorPicker' + | 'Transfer' + | 'Divider' + | 'TimeSelect' + | 'SelectV2' + | 'TreeSelect' + | 'InputPassword' + | 'Editor' + | 'UploadImg' + | 'UploadImgs' + | 'UploadFile' + +export type ColProps = { + span?: number + xs?: number + sm?: number + md?: number + lg?: number + xl?: number + tag?: string +} + +export type ComponentOptions = { + label?: string + value?: FormValueType + disabled?: boolean + key?: string | number + children?: ComponentOptions[] + options?: ComponentOptions[] +} & Recordable + +export type ComponentOptionsAlias = { + labelField?: string + valueField?: string +} + +export type ComponentProps = { + optionsAlias?: ComponentOptionsAlias + options?: ComponentOptions[] + optionsSlot?: boolean +} & Recordable diff --git a/src/types/configGlobal.d.ts b/src/types/configGlobal.d.ts new file mode 100644 index 0000000..f6d7b3c --- /dev/null +++ b/src/types/configGlobal.d.ts @@ -0,0 +1,4 @@ +import { ElementPlusSize } from './elementPlus' +export interface ConfigGlobalTypes { + size?: ElementPlusSize +} diff --git a/src/types/contextMenu.d.ts b/src/types/contextMenu.d.ts new file mode 100644 index 0000000..0738d0e --- /dev/null +++ b/src/types/contextMenu.d.ts @@ -0,0 +1,7 @@ +export type contextMenuSchema = { + disabled?: boolean + divided?: boolean + icon?: string + label: string + command?: (item: contextMenuSchema) => void +} diff --git a/src/types/descriptions.d.ts b/src/types/descriptions.d.ts new file mode 100644 index 0000000..35c0b81 --- /dev/null +++ b/src/types/descriptions.d.ts @@ -0,0 +1,13 @@ +export interface DescriptionsSchema { + span?: number // 占多少分 + field: string // 字段名 + label?: string // label名 + width?: string | number + minWidth?: string | number + align?: 'left' | 'center' | 'right' + labelAlign?: 'left' | 'center' | 'right' + className?: string + labelClassName?: string + dateFormat?: string // add by 星语:支持时间的格式化 + dictType?: string // add by 星语:支持 dict 字典数据 +} diff --git a/src/types/elementPlus.d.ts b/src/types/elementPlus.d.ts new file mode 100644 index 0000000..2c6b76e --- /dev/null +++ b/src/types/elementPlus.d.ts @@ -0,0 +1,3 @@ +export type ElementPlusSize = 'default' | 'small' | 'large' + +export type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger' diff --git a/src/types/form.d.ts b/src/types/form.d.ts new file mode 100644 index 0000000..980c8cc --- /dev/null +++ b/src/types/form.d.ts @@ -0,0 +1,44 @@ +import type { CSSProperties } from 'vue' +import { ColProps, ComponentProps, ComponentName } from '@/types/components' +import type { AxiosPromise } from 'axios' + +export type FormSetPropsType = { + field: string + path: string + value: any +} + +export type FormValueType = string | number | string[] | number[] | boolean | undefined | null + +export type FormItemProps = { + labelWidth?: string | number + required?: boolean + rules?: Recordable + error?: string + showMessage?: boolean + inlineMessage?: boolean + style?: CSSProperties +} + +export type FormSchema = { + // 唯一值 + field: string + // 标题 + label?: string + // 提示 + labelMessage?: string + // col组件属性 + colProps?: ColProps + // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档 + componentProps?: { slots?: Recordable } & ComponentProps + // formItem组件属性 + formItemProps?: FormItemProps + // 渲染的组件 + component?: ComponentName + // 初始值 + value?: FormValueType + // 是否隐藏 + hidden?: boolean + // 远程加载下拉项 + api?: () => AxiosPromise +} diff --git a/src/types/icon.d.ts b/src/types/icon.d.ts new file mode 100644 index 0000000..d1ffcdb --- /dev/null +++ b/src/types/icon.d.ts @@ -0,0 +1,5 @@ +export interface IconTypes { + size?: number + color?: string + icon: string +} diff --git a/src/types/infoTip.d.ts b/src/types/infoTip.d.ts new file mode 100644 index 0000000..6eff083 --- /dev/null +++ b/src/types/infoTip.d.ts @@ -0,0 +1,4 @@ +export interface TipSchema { + label: string + keys?: string[] +} diff --git a/src/types/layout.d.ts b/src/types/layout.d.ts new file mode 100644 index 0000000..cad3e2a --- /dev/null +++ b/src/types/layout.d.ts @@ -0,0 +1 @@ +export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu' diff --git a/src/types/localeDropdown.d.ts b/src/types/localeDropdown.d.ts new file mode 100644 index 0000000..c749dce --- /dev/null +++ b/src/types/localeDropdown.d.ts @@ -0,0 +1,10 @@ +export interface Language { + el: Recordable + name: string +} + +export interface LocaleDropdownType { + lang: LocaleType + name?: string + elLocale?: Language +} diff --git a/src/types/qrcode.d.ts b/src/types/qrcode.d.ts new file mode 100644 index 0000000..86cdf0b --- /dev/null +++ b/src/types/qrcode.d.ts @@ -0,0 +1,9 @@ +export interface QrcodeLogo { + src?: string + logoSize?: number + bgColor?: string + borderSize?: number + crossOrigin?: string + borderRadius?: number + logoRadius?: number +} diff --git a/src/types/table.d.ts b/src/types/table.d.ts new file mode 100644 index 0000000..3294234 --- /dev/null +++ b/src/types/table.d.ts @@ -0,0 +1,42 @@ +export type TableColumn = { + field: string + label?: string + children?: TableColumn[] +} & Recordable + +export type VxeTableColumn = { + field: string + title?: string + children?: TableColumn[] +} & Recordable + +export type TableSlotDefault = { + row: Recordable + column: TableColumn + $index: number +} & Recordable + +export interface Pagination { + small?: boolean + background?: boolean + pageSize?: number + defaultPageSize?: number + total?: number + pageCount?: number + pagerCount?: number + currentPage?: number + defaultCurrentPage?: number + layout?: string + pageSizes?: number[] + popperClass?: string + prevText?: string + nextText?: string + disabled?: boolean + hideOnSinglePage?: boolean +} + +export interface TableSetPropsType { + field: string + path: string + value: any +} diff --git a/src/types/theme.d.ts b/src/types/theme.d.ts new file mode 100644 index 0000000..ad649b0 --- /dev/null +++ b/src/types/theme.d.ts @@ -0,0 +1,16 @@ +export type ThemeTypes = { + elColorPrimary?: string + leftMenuBorderColor?: string + leftMenuBgColor?: string + leftMenuBgLightColor?: string + leftMenuBgActiveColor?: string + leftMenuCollapseBgActiveColor?: string + leftMenuTextColor?: string + leftMenuTextActiveColor?: string + logoTitleTextColor?: string + logoBorderColor?: string + topHeaderBgColor?: string + topHeaderTextColor?: string + topHeaderHoverColor?: string + topToolBorderColor?: string +} diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts new file mode 100644 index 0000000..ca58df2 --- /dev/null +++ b/src/utils/Logger.ts @@ -0,0 +1,100 @@ +const isArray = function (obj: any): boolean { + return Object.prototype.toString.call(obj) === '[object Array]' +} + +const Logger = () => {} + +Logger.typeColor = function (type: string) { + let color = '' + switch (type) { + case 'primary': + color = '#2d8cf0' + break + case 'success': + color = '#19be6b' + break + case 'info': + color = '#909399' + break + case 'warn': + color = '#ff9900' + break + case 'error': + color = '#f03f14' + break + default: + color = '#35495E' + break + } + return color +} + +Logger.print = function (type = 'default', text: any, back = false) { + if (typeof text === 'object') { + // 如果是對象則調用打印對象方式 + isArray(text) ? console.table(text) : console.dir(text) + return + } + if (back) { + // 如果是打印帶背景圖的 + console.log( + `%c ${text} `, + `background:${Logger.typeColor(type)}; padding: 2px; border-radius: 4px; color: #fff;` + ) + } else { + console.log( + `%c ${text} `, + `border: 1px solid ${Logger.typeColor(type)}; + padding: 2px; border-radius: 4px; + color: ${Logger.typeColor(type)};` + ) + } +} + +Logger.printBack = function (type = 'primary', text) { + this.print(type, text, true) +} + +Logger.pretty = function (type = 'primary', title, text) { + if (typeof text === 'object') { + console.group('Console Group', title) + console.log( + `%c ${title}`, + `background:${Logger.typeColor(type)};border:1px solid ${Logger.typeColor(type)}; + padding: 1px; border-radius: 4px; color: #fff;` + ) + isArray(text) ? console.table(text) : console.dir(text) + console.groupEnd() + return + } + console.log( + `%c ${title} %c ${text} %c`, + `background:${Logger.typeColor(type)};border:1px solid ${Logger.typeColor(type)}; + padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`, + `border:1px solid ${Logger.typeColor(type)}; + padding: 1px; border-radius: 0 4px 4px 0; color: ${Logger.typeColor(type)};`, + 'background:transparent' + ) +} + +Logger.prettyPrimary = function (title, ...text) { + text.forEach((t) => this.pretty('primary', title, t)) +} + +Logger.prettySuccess = function (title, ...text) { + text.forEach((t) => this.pretty('success', title, t)) +} + +Logger.prettyWarn = function (title, ...text) { + text.forEach((t) => this.pretty('warn', title, t)) +} + +Logger.prettyError = function (title, ...text) { + text.forEach((t) => this.pretty('error', title, t)) +} + +Logger.prettyInfo = function (title, ...text) { + text.forEach((t) => this.pretty('info', title, t)) +} + +export default Logger diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..ae3d138 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,120 @@ +import { useCache } from '@/hooks/web/useCache' +import { TokenType } from '@/api/login/types' +import { decrypt, encrypt } from '@/utils/jsencrypt' + +const { wsCache } = useCache() + +const AccessTokenKey = 'ACCESS_TOKEN' +const RefreshTokenKey = 'REFRESH_TOKEN' + +// 获取token +export const getAccessToken = () => { + // 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错 + return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN') +} + +// 刷新token +export const getRefreshToken = () => { + return wsCache.get(RefreshTokenKey) +} + +// 设置token +export const setToken = (token: TokenType) => { + wsCache.set(RefreshTokenKey, token.refreshToken, { exp: token.expiresTime }) + wsCache.set(AccessTokenKey, token.accessToken) +} + +// 删除token +export const removeToken = () => { + wsCache.delete(AccessTokenKey) + wsCache.delete(RefreshTokenKey) +} + +/** 格式化token(jwt格式) */ +export const formatToken = (token: string): string => { + return 'Bearer ' + token +} +// ========== 账号相关 ========== + +const LoginFormKey = 'LOGINFORM' + +export type LoginFormType = { + tenantId: number + appId: number + username: string + password: string + rememberMe: boolean +} + +export const getLoginForm = () => { + const loginForm: LoginFormType = wsCache.get(LoginFormKey) + if (loginForm) { + loginForm.password = decrypt(loginForm.password) as string + } + return loginForm +} + +export const setLoginForm = (loginForm: LoginFormType) => { + loginForm.password = encrypt(loginForm.password) as string + wsCache.set(LoginFormKey, loginForm, { exp: 30 * 24 * 60 * 60 }) +} + +export const removeLoginForm = () => { + wsCache.delete(LoginFormKey) +} + +// ========== 租户相关 ========== + +const TenantIdKey = 'TENANT_ID' +const TenantNameKey = 'TENANT_NAME' + +export const getTenantName = () => { + return wsCache.get(TenantNameKey) +} + +export const setTenantName = (username: string) => { + wsCache.set(TenantNameKey, username, { exp: 30 * 24 * 60 * 60 }) +} + +export const removeTenantName = () => { + wsCache.delete(TenantNameKey) +} + +export const getTenantId = () => { + return wsCache.get(TenantIdKey) +} + +export const setTenantId = (username: string) => { + wsCache.set(TenantIdKey, username) +} + +export const removeTenantId = () => { + wsCache.delete(TenantIdKey) +} + +const AppIdKey = 'App_ID' +const AppNameKey = 'App_NAME' + +export const getAPPName = () => { + return wsCache.get(AppNameKey) +} + +export const setAppName = (name: string) => { + wsCache.set(AppNameKey, name, { exp: 30 * 24 * 60 * 60 }) +} + +export const removeAppName = () => { + wsCache.delete(AppNameKey) +} + +export const getAppId = () => { + return wsCache.get(AppIdKey) +} + +export const setAppId = (id: number) => { + wsCache.set(AppIdKey, id) +} + +export const removeAppId = () => { + wsCache.delete(AppIdKey) +} diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..6888583 --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,153 @@ +/** + * 判断是否 十六进制颜色值. + * 输入形式可为 #fff000 #f00 + * + * @param String color 十六进制颜色值 + * @return Boolean + */ +export const isHexColor = (color: string) => { + const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/ + return reg.test(color) +} + +/** + * RGB 颜色值转换为 十六进制颜色值. + * r, g, 和 b 需要在 [0, 255] 范围内 + * + * @return String 类似#ff00ff + * @param r + * @param g + * @param b + */ +export const rgbToHex = (r: number, g: number, b: number) => { + // tslint:disable-next-line:no-bitwise + const hex = ((r << 16) | (g << 8) | b).toString(16) + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex +} + +/** + * Transform a HEX color to its RGB representation + * @param {string} hex The color to transform + * @returns The RGB representation of the passed color + */ +export const hexToRGB = (hex: string, opacity?: number) => { + let sHex = hex.toLowerCase() + if (isHexColor(hex)) { + if (sHex.length === 4) { + let sColorNew = '#' + for (let i = 1; i < 4; i += 1) { + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)) + } + sHex = sColorNew + } + const sColorChange: number[] = [] + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))) + } + return opacity + ? 'RGBA(' + sColorChange.join(',') + ',' + opacity + ')' + : 'RGB(' + sColorChange.join(',') + ')' + } + return sHex +} + +export const colorIsDark = (color: string) => { + if (!isHexColor(color)) return + const [r, g, b] = hexToRGB(color) + .replace(/(?:\(|\)|rgb|RGB)*/g, '') + .split(',') + .map((item) => Number(item)) + return r * 0.299 + g * 0.578 + b * 0.114 < 192 +} + +/** + * Darkens a HEX color given the passed percentage + * @param {string} color The color to process + * @param {number} amount The amount to change the color by + * @returns {string} The HEX representation of the processed color + */ +export const darken = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color + amount = Math.trunc((255 * amount) / 100) + return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( + color.substring(2, 4), + amount + )}${subtractLight(color.substring(4, 6), amount)}` +} + +/** + * Lightens a 6 char HEX color according to the passed percentage + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed color represented as HEX + */ +export const lighten = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color + amount = Math.trunc((255 * amount) / 100) + return `#${addLight(color.substring(0, 2), amount)}${addLight( + color.substring(2, 4), + amount + )}${addLight(color.substring(4, 6), amount)}` +} + +/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ +/** + * Sums the passed percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const addLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) + amount + const c = cc > 255 ? 255 : cc + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` +} + +/** + * Calculates luminance of an rgb color + * @param {number} r red + * @param {number} g green + * @param {number} b blue + */ +const luminanace = (r: number, g: number, b: number) => { + const a = [r, g, b].map((v) => { + v /= 255 + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) + }) + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722 +} + +/** + * Calculates contrast between two rgb colors + * @param {string} rgb1 rgb color 1 + * @param {string} rgb2 rgb color 2 + */ +const contrast = (rgb1: string[], rgb2: number[]) => { + return ( + (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / + (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05) + ) +} + +/** + * Determines what the best text color is (black or white) based con the contrast with the background + * @param hexColor - Last selected color by the user + */ +export const calculateBestTextColor = (hexColor: string) => { + const rgbColor = hexToRGB(hexColor.substring(1)) + const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]) + + return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF' +} + +/** + * Subtracts the indicated percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const subtractLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) - amount + const c = cc < 0 ? 0 : cc + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..25f7e92 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,240 @@ +/** + * Created by 莳松 + * + * 枚举类 + */ + +// 全局通用状态枚举 +export const CommonStatusEnum = { + ENABLE: 0, // 开启 + DISABLE: 1 // 禁用 +} + +/** + * 菜单的类型枚举 + */ +export const SystemMenuTypeEnum = { + DIR: 1, // 目录 + MENU: 2, // 菜单 + BUTTON: 3 // 按钮 +} + +/** + * 角色的类型枚举 + */ +export const SystemRoleTypeEnum = { + SYSTEM: 1, // 内置角色 + CUSTOM: 2 // 自定义角色 +} + +/** + * 数据权限的范围枚举 + */ +export const SystemDataScopeEnum = { + ALL: 1, // 全部数据权限 + DEPT_CUSTOM: 2, // 指定部门数据权限 + DEPT_ONLY: 3, // 部门数据权限 + DEPT_AND_CHILD: 4, // 部门及以下数据权限 + DEPT_SELF: 5 // 仅本人数据权限 +} + +/** + * 代码生成模板类型 + */ +export const InfraCodegenTemplateTypeEnum = { + CRUD: 1, // 基础 CRUD + TREE: 2, // 树形 CRUD + SUB: 3 // 主子表 CRUD +} + +/** + * 任务状态的枚举 + */ +export const InfraJobStatusEnum = { + INIT: 0, // 初始化中 + NORMAL: 1, // 运行中 + STOP: 2 // 暂停运行 +} + +/** + * API 异常数据的处理状态 + */ +export const InfraApiErrorLogProcessStatusEnum = { + INIT: 0, // 未处理 + DONE: 1, // 已处理 + IGNORE: 2 // 已忽略 +} + +/** + * 用户的社交平台的类型枚举 + */ +export const SystemUserSocialTypeEnum = { + DINGTALK: { + title: '钉钉', + type: 20, + source: 'dingtalk', + img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png' + }, + WECHAT_ENTERPRISE: { + title: '企业微信', + type: 30, + source: 'wechat_enterprise', + img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png' + } +} + +/** + * 支付渠道枚举 + */ +export const PayChannelEnum = { + WX_PUB: { + code: 'wx_pub', + name: '微信 JSAPI 支付' + }, + WX_LITE: { + code: 'wx_lite', + name: '微信小程序支付' + }, + WX_APP: { + code: 'wx_app', + name: '微信 APP 支付' + }, + ALIPAY_PC: { + code: 'alipay_pc', + name: '支付宝 PC 网站支付' + }, + ALIPAY_WAP: { + code: 'alipay_wap', + name: '支付宝 WAP 网站支付' + }, + ALIPAY_APP: { + code: 'alipay_app', + name: '支付宝 APP 支付' + }, + ALIPAY_QR: { + code: 'alipay_qr', + name: '支付宝扫码支付' + }, + ALIPAY_BAR: { + code: 'alipay_bar', + name: '支付宝条码支付' + } +} + +/** + * 支付类型枚举 + */ +export const PayType = { + WECHAT: 'WECHAT', + ALIPAY: 'ALIPAY' +} + +/** + * 支付订单状态枚举 + */ +export const PayOrderStatusEnum = { + WAITING: { + status: 0, + name: '未支付' + }, + SUCCESS: { + status: 10, + name: '已支付' + }, + CLOSED: { + status: 20, + name: '未支付' + } +} + +/** + * 支付订单回调状态枚举 + */ +export const PayOrderNotifyStatusEnum = { + NO: { + status: 0, + name: '未通知' + }, + SUCCESS: { + status: 10, + name: '通知成功' + }, + FAILURE: { + status: 20, + name: '通知失败' + } +} + +/** + * 支付订单退款状态枚举 + */ +export const PayOrderRefundStatusEnum = { + NO: { + status: 0, + name: '未退款' + }, + SOME: { + status: 10, + name: '部分退款' + }, + ALL: { + status: 20, + name: '全部退款' + } +} + +/** + * 支付退款订单状态枚举 + */ +export const PayRefundStatusEnum = { + CREATE: { + status: 0, + name: '退款订单生成' + }, + SUCCESS: { + status: 1, + name: '退款成功' + }, + FAILURE: { + status: 2, + name: '退款失败' + }, + PROCESSING_NOTIFY: { + status: 3, + name: '退款中,渠道通知结果' + }, + PROCESSING_QUERY: { + status: 4, + name: '退款中,系统查询结果' + }, + UNKNOWN_RETRY: { + status: 5, + name: '状态未知,请重试' + }, + UNKNOWN_QUERY: { + status: 6, + name: '状态未知,系统查询结果' + }, + CLOSE: { + status: 99, + name: '退款关闭' + } +} + +/** + * 商品SPU枚举类 + */ +export const ProductSpuStatusEnum = { + RECYCLE: { + status: -1, + name: '回收站' + }, + DISABLE: { + status: 0, + name: '下架' + }, + ENABLE: { + status: 1, + name: '上架' + } +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts new file mode 100644 index 0000000..594c5b2 --- /dev/null +++ b/src/utils/dict.ts @@ -0,0 +1,118 @@ +/** + * 数据字典工具类 + */ +import { useDictStoreWithOut } from '@/store/modules/dict' +import { ElementPlusInfoType } from '@/types/elementPlus' + +const dictStore = useDictStoreWithOut() + +/** + * 获取 dictType 对应的数据字典数组 + * + * @param dictType 数据类型 + * @returns {*|Array} 数据字典数组 + */ +export interface DictDataType { + dictType: string + label: string + value: string | number | boolean + colorType: ElementPlusInfoType | '' + cssClass: string +} + +export const getDictOptions = (dictType: string) => { + return dictStore.getDictByType(dictType) || [] +} + +export const getIntDictOptions = (dictType: string) => { + const dictOption: DictDataType[] = [] + const dictOptions: DictDataType[] = getDictOptions(dictType) + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: parseInt(dict.value + '') + }) + }) + + return dictOption +} + +export const getStrDictOptions = (dictType: string) => { + const dictOption: DictDataType[] = [] + const dictOptions: DictDataType[] = getDictOptions(dictType) + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: dict.value + '' + }) + }) + return dictOption +} + +export const getBoolDictOptions = (dictType: string) => { + const dictOption: DictDataType[] = [] + const dictOptions: DictDataType[] = getDictOptions(dictType) + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: dict.value + '' === 'true' + }) + }) + return dictOption +} + +export const getDictObj = (dictType: string, value: any) => { + const dictOptions: DictDataType[] = getDictOptions(dictType) + dictOptions.forEach((dict: DictDataType) => { + if (dict.value === value.toString()) { + return dict + } + }) +} + +/** + * 获得字典数据的文本展示 + * + * @param dictType 字典类型 + * @param value 字典数据的值 + */ +export const getDictLabel = (dictType: string, value: any) => { + const dictOptions: DictDataType[] = getDictOptions(dictType) + const dictLabel = ref('') + dictOptions.forEach((dict: DictDataType) => { + if (dict.value === value) { + dictLabel.value = dict.label + } + }) + return dictLabel.value +} + +export enum DICT_TYPE { + USER_TYPE = 'user_type', + COMMON_STATUS = 'common_status', + SYSTEM_TENANT_PACKAGE_ID = 'system_tenant_package_id', + + // ========== SYSTEM 模块 ========== + SYSTEM_USER_SEX = 'system_user_sex', + SYSTEM_MENU_TYPE = 'system_menu_type', + SYSTEM_ROLE_TYPE = 'system_role_type', + SYSTEM_DATA_SCOPE = 'system_data_scope', + SYSTEM_NOTICE_TYPE = 'system_notice_type', + SYSTEM_OPERATE_TYPE = 'system_operate_type', + SYSTEM_LOGIN_TYPE = 'system_login_type', + SYSTEM_LOGIN_RESULT = 'system_login_result', + SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code', + SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type', + SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status', + SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status', + SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type', + SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type', + SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status', + SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type', + + // ========== MALL 模块 ========== + PRODUCT_UNIT = 'product_unit', // 商品单位 + PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态 + // ========== 驾校管理 ========== + LINCENSE_TYPE = 'license_type' //驾照类型 +} diff --git a/src/utils/domUtils.ts b/src/utils/domUtils.ts new file mode 100644 index 0000000..dbc1989 --- /dev/null +++ b/src/utils/domUtils.ts @@ -0,0 +1,289 @@ +import { isServer } from './is' +const ieVersion = isServer ? 0 : Number((document as any).documentMode) +const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g +const MOZ_HACK_REGEXP = /^moz([A-Z])/ + +export interface ViewportOffsetResult { + left: number + top: number + right: number + bottom: number + rightIncludeBody: number + bottomIncludeBody: number +} + +/* istanbul ignore next */ +const trim = function (string: string) { + return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '') +} + +/* istanbul ignore next */ +const camelCase = function (name: string) { + return name + .replace(SPECIAL_CHARS_REGEXP, function (_, __, letter, offset) { + return offset ? letter.toUpperCase() : letter + }) + .replace(MOZ_HACK_REGEXP, 'Moz$1') +} + +/* istanbul ignore next */ +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false + if (cls.indexOf(' ') !== -1) { + throw new Error('className should not contain space.') + } + if (el.classList) { + return el.classList.contains(cls) + } else { + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1 + } +} + +/* istanbul ignore next */ +export function addClass(el: Element, cls: string) { + if (!el) return + let curClass = el.className + const classes = (cls || '').split(' ') + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i] + if (!clsName) continue + + if (el.classList) { + el.classList.add(clsName) + } else if (!hasClass(el, clsName)) { + curClass += ' ' + clsName + } + } + if (!el.classList) { + el.className = curClass + } +} + +/* istanbul ignore next */ +export function removeClass(el: Element, cls: string) { + if (!el || !cls) return + const classes = cls.split(' ') + let curClass = ' ' + el.className + ' ' + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i] + if (!clsName) continue + + if (el.classList) { + el.classList.remove(clsName) + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(' ' + clsName + ' ', ' ') + } + } + if (!el.classList) { + el.className = trim(curClass) + } +} + +export function getBoundingClientRect(element: Element): DOMRect | number { + if (!element || !element.getBoundingClientRect) { + return 0 + } + return element.getBoundingClientRect() +} + +/** + * 获取当前元素的left、top偏移 + * left:元素最左侧距离文档左侧的距离 + * top:元素最顶端距离文档顶端的距离 + * right:元素最右侧距离文档右侧的距离 + * bottom:元素最底端距离文档底端的距离 + * rightIncludeBody:元素最左侧距离文档右侧的距离 + * bottomIncludeBody:元素最底端距离文档最底部的距离 + * + * @description: + */ +export function getViewportOffset(element: Element): ViewportOffsetResult { + const doc = document.documentElement + + const docScrollLeft = doc.scrollLeft + const docScrollTop = doc.scrollTop + const docClientLeft = doc.clientLeft + const docClientTop = doc.clientTop + + const pageXOffset = window.pageXOffset + const pageYOffset = window.pageYOffset + + const box = getBoundingClientRect(element) + + const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect + + const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0) + const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0) + const offsetLeft = retLeft + pageXOffset + const offsetTop = rectTop + pageYOffset + + const left = offsetLeft - scrollLeft + const top = offsetTop - scrollTop + + const clientWidth = window.document.documentElement.clientWidth + const clientHeight = window.document.documentElement.clientHeight + return { + left: left, + top: top, + right: clientWidth - rectWidth - left, + bottom: clientHeight - rectHeight - top, + rightIncludeBody: clientWidth - left, + bottomIncludeBody: clientHeight - top + } +} + +/* istanbul ignore next */ +export const on = function ( + element: HTMLElement | Document | Window, + event: string, + handler: EventListenerOrEventListenerObject +): void { + if (element && event && handler) { + element.addEventListener(event, handler, false) + } +} + +/* istanbul ignore next */ +export const off = function ( + element: HTMLElement | Document | Window, + event: string, + handler: any +): void { + if (element && event && handler) { + element.removeEventListener(event, handler, false) + } +} + +/* istanbul ignore next */ +export const once = function (el: HTMLElement, event: string, fn: EventListener): void { + const listener = function (this: any, ...args: unknown[]) { + if (fn) { + // @ts-ignore + fn.apply(this, args) + } + off(el, event, listener) + } + on(el, event, listener) +} + +/* istanbul ignore next */ +export const getStyle = + ieVersion < 9 + ? function (element: Element | any, styleName: string) { + if (isServer) return + if (!element || !styleName) return null + styleName = camelCase(styleName) + if (styleName === 'float') { + styleName = 'styleFloat' + } + try { + switch (styleName) { + case 'opacity': + try { + return element.filters.item('alpha').opacity / 100 + } catch (e) { + return 1.0 + } + default: + return element.style[styleName] || element.currentStyle + ? element.currentStyle[styleName] + : null + } + } catch (e) { + return element.style[styleName] + } + } + : function (element: Element | any, styleName: string) { + if (isServer) return + if (!element || !styleName) return null + styleName = camelCase(styleName) + if (styleName === 'float') { + styleName = 'cssFloat' + } + try { + const computed = (document as any).defaultView.getComputedStyle(element, '') + return element.style[styleName] || computed ? computed[styleName] : null + } catch (e) { + return element.style[styleName] + } + } + +/* istanbul ignore next */ +export function setStyle(element: Element | any, styleName: any, value: any) { + if (!element || !styleName) return + + if (typeof styleName === 'object') { + for (const prop in styleName) { + if (Object.prototype.hasOwnProperty.call(styleName, prop)) { + setStyle(element, prop, styleName[prop]) + } + } + } else { + styleName = camelCase(styleName) + if (styleName === 'opacity' && ieVersion < 9) { + element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')' + } else { + element.style[styleName] = value + } + } +} + +/* istanbul ignore next */ +export const isScroll = (el: Element, vertical: any) => { + if (isServer) return + + const determinedDirection = vertical !== null || vertical !== undefined + const overflow = determinedDirection + ? vertical + ? getStyle(el, 'overflow-y') + : getStyle(el, 'overflow-x') + : getStyle(el, 'overflow') + + return overflow.match(/(scroll|auto)/) +} + +/* istanbul ignore next */ +export const getScrollContainer = (el: Element, vertical?: any) => { + if (isServer) return + + let parent: any = el + while (parent) { + if ([window, document, document.documentElement].includes(parent)) { + return window + } + if (isScroll(parent, vertical)) { + return parent + } + parent = parent.parentNode + } + + return parent +} + +/* istanbul ignore next */ +export const isInContainer = (el: Element, container: any) => { + if (isServer || !el || !container) return false + + const elRect = el.getBoundingClientRect() + let containerRect + + if ([window, document, document.documentElement, null, undefined].includes(container)) { + containerRect = { + top: 0, + right: window.innerWidth, + bottom: window.innerHeight, + left: 0 + } + } else { + containerRect = container.getBoundingClientRect() + } + + return ( + elRect.top < containerRect.bottom && + elRect.bottom > containerRect.top && + elRect.right > containerRect.left && + elRect.left < containerRect.right + ) +} diff --git a/src/utils/download.ts b/src/utils/download.ts new file mode 100644 index 0000000..ab20014 --- /dev/null +++ b/src/utils/download.ts @@ -0,0 +1,38 @@ +const download0 = (data: Blob, fileName: string, mineType: string) => { + // 创建 blob + const blob = new Blob([data], { type: mineType }) + // 创建 href 超链接,点击进行下载 + window.URL = window.URL || window.webkitURL + const href = URL.createObjectURL(blob) + const downA = document.createElement('a') + downA.href = href + downA.download = fileName + downA.click() + // 销毁超连接 + window.URL.revokeObjectURL(href) +} + +const download = { + // 下载 Excel 方法 + excel: (data: Blob, fileName: string) => { + download0(data, fileName, 'application/vnd.ms-excel') + }, + // 下载 Word 方法 + word: (data: Blob, fileName: string) => { + download0(data, fileName, 'application/msword') + }, + // 下载 Zip 方法 + zip: (data: Blob, fileName: string) => { + download0(data, fileName, 'application/zip') + }, + // 下载 Html 方法 + html: (data: Blob, fileName: string) => { + download0(data, fileName, 'text/html') + }, + // 下载 Markdown 方法 + markdown: (data: Blob, fileName: string) => { + download0(data, fileName, 'text/markdown') + } +} + +export default download diff --git a/src/utils/filt.ts b/src/utils/filt.ts new file mode 100644 index 0000000..b1a7b2c --- /dev/null +++ b/src/utils/filt.ts @@ -0,0 +1,157 @@ +export const openWindow = ( + url: string, + opt?: { + target?: '_self' | '_blank' | string + noopener?: boolean + noreferrer?: boolean + } +) => { + const { target = '__blank', noopener = true, noreferrer = true } = opt || {} + const feature: string[] = [] + + noopener && feature.push('noopener=yes') + noreferrer && feature.push('noreferrer=yes') + + window.open(url, target, feature.join(',')) +} + +/** + * @description: base64 to blob + */ +export const dataURLtoBlob = (base64Buf: string): Blob => { + const arr = base64Buf.split(',') + const typeItem = arr[0] + const mime = typeItem.match(/:(.*?);/)![1] + const bstr = window.atob(arr[1]) + let n = bstr.length + const u8arr = new Uint8Array(n) + while (n--) { + u8arr[n] = bstr.charCodeAt(n) + } + return new Blob([u8arr], { type: mime }) +} + +/** + * img url to base64 + * @param url + */ +export const urlToBase64 = (url: string, mineType?: string): Promise => { + return new Promise((resolve, reject) => { + let canvas = document.createElement('CANVAS') as Nullable + const ctx = canvas!.getContext('2d') + + const img = new Image() + img.crossOrigin = '' + img.onload = function () { + if (!canvas || !ctx) { + return reject() + } + canvas.height = img.height + canvas.width = img.width + ctx.drawImage(img, 0, 0) + const dataURL = canvas.toDataURL(mineType || 'image/png') + canvas = null + resolve(dataURL) + } + img.src = url + }) +} + +/** + * Download online pictures + * @param url + * @param filename + * @param mime + * @param bom + */ +export const downloadByOnlineUrl = ( + url: string, + filename: string, + mime?: string, + bom?: BlobPart +) => { + urlToBase64(url).then((base64) => { + downloadByBase64(base64, filename, mime, bom) + }) +} + +/** + * Download pictures based on base64 + * @param buf + * @param filename + * @param mime + * @param bom + */ +export const downloadByBase64 = (buf: string, filename: string, mime?: string, bom?: BlobPart) => { + const base64Buf = dataURLtoBlob(buf) + downloadByData(base64Buf, filename, mime, bom) +} + +/** + * Download according to the background interface file stream + * @param {*} data + * @param {*} filename + * @param {*} mime + * @param {*} bom + */ +export const downloadByData = (data: BlobPart, filename: string, mime?: string, bom?: BlobPart) => { + const blobData = typeof bom !== 'undefined' ? [bom, data] : [data] + const blob = new Blob(blobData, { type: mime || 'application/octet-stream' }) + + const blobURL = window.URL.createObjectURL(blob) + const tempLink = document.createElement('a') + tempLink.style.display = 'none' + tempLink.href = blobURL + tempLink.setAttribute('download', filename) + if (typeof tempLink.download === 'undefined') { + tempLink.setAttribute('target', '_blank') + } + document.body.appendChild(tempLink) + tempLink.click() + document.body.removeChild(tempLink) + window.URL.revokeObjectURL(blobURL) +} + +/** + * Download file according to file address + * @param {*} sUrl + */ +export const downloadByUrl = ({ + url, + target = '_blank', + fileName +}: { + url: string + target?: '_self' | '_blank' + fileName?: string +}): boolean => { + const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1 + const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1 + + if (/(iP)/g.test(window.navigator.userAgent)) { + console.error('Your browser does not support download!') + return false + } + if (isChrome || isSafari) { + const link = document.createElement('a') + link.href = url + link.target = target + + if (link.download !== undefined) { + link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length) + } + + if (document.createEvent) { + const e = document.createEvent('MouseEvents') + e.initEvent('click', true, true) + link.dispatchEvent(e) + return true + } + } + if (url.indexOf('?') === -1) { + url += '?download' + } + + openWindow(url, { target }) + return true +} diff --git a/src/utils/formCreate.ts b/src/utils/formCreate.ts new file mode 100644 index 0000000..6d7dbc7 --- /dev/null +++ b/src/utils/formCreate.ts @@ -0,0 +1,54 @@ +/** + * 针对 https://github.com/xaboy/form-create-designer 封装的工具类 + */ + +// 编码表单 Conf +export const encodeConf = (designerRef: object) => { + // @ts-ignore + return JSON.stringify(designerRef.value.getOption()) +} + +// 编码表单 Fields +export const encodeFields = (designerRef: object) => { + // @ts-ignore + const rule = designerRef.value.getRule() + const fields: string[] = [] + rule.forEach((item) => { + fields.push(JSON.stringify(item)) + }) + return fields +} + +// 解码表单 Fields +export const decodeFields = (fields: string[]) => { + const rule: object[] = [] + fields.forEach((item) => { + rule.push(JSON.parse(item)) + }) + return rule +} + +// 设置表单的 Conf 和 Fields +export const setConfAndFields = (designerRef: object, conf: string, fields: string) => { + // @ts-ignore + designerRef.value.setOption(JSON.parse(conf)) + // @ts-ignore + designerRef.value.setRule(decodeFields(fields)) +} + +// 设置表单的 Conf 和 Fields +export const setConfAndFields2 = ( + detailPreview: object, + conf: string, + fields: string, + value?: object +) => { + // @ts-ignore + detailPreview.value.option = JSON.parse(conf) + // @ts-ignore + detailPreview.value.rule = decodeFields(fields) + if (value) { + // @ts-ignore + detailPreview.value.value = value + } +} diff --git a/src/utils/formRules.ts b/src/utils/formRules.ts new file mode 100644 index 0000000..2989867 --- /dev/null +++ b/src/utils/formRules.ts @@ -0,0 +1,7 @@ +const { t } = useI18n() + +// 必填项 +export const required = { + required: true, + message: t('common.required') +} diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts new file mode 100644 index 0000000..156e176 --- /dev/null +++ b/src/utils/formatTime.ts @@ -0,0 +1,201 @@ +import dayjs from 'dayjs' + +/** + * 时间日期转换 + * @param date 当前时间,new Date() 格式 + * @param format 需要转换的时间格式字符串 + * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd` + * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ" + * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW" + * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ" + * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" + * @returns 返回拼接后的时间字符串 + */ +export function formatDate(date: Date, format?: string): string { + // 日期不存在,则返回空 + if (!date) { + return '' + } + // 日期存在,则进行格式化 + if (format === undefined) { + format = 'YYYY-MM-DD' + } + return dayjs(date).format(format) +} + +/** + * 获取当前日期是第几周 + * @param dateTime 当前传入的日期值 + * @returns 返回第几周数字值 + */ +export function getWeek(dateTime: Date): number { + const temptTime = new Date(dateTime.getTime()) + // 周几 + const weekday = temptTime.getDay() || 7 + // 周1+5天=周六 + temptTime.setDate(temptTime.getDate() - weekday + 1 + 5) + let firstDay = new Date(temptTime.getFullYear(), 0, 1) + const dayOfWeek = firstDay.getDay() + let spendDay = 1 + if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1 + firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay) + const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000) + return Math.ceil(d / 7) +} + +/** + * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前` + * @param param 当前时间,new Date() 格式或者字符串时间格式 + * @param format 需要转换的时间格式字符串 + * @description param 10秒: 10 * 1000 + * @description param 1分: 60 * 1000 + * @description param 1小时: 60 * 60 * 1000 + * @description param 24小时:60 * 60 * 24 * 1000 + * @description param 3天: 60 * 60* 24 * 1000 * 3 + * @returns 返回拼接后的时间字符串 + */ +export function formatPast(param: string | Date, format = 'YYYY-mm-dd HH:MM:SS'): string { + // 传入格式处理、存储转换值 + let t: any, s: number + // 获取js 时间戳 + let time: number = new Date().getTime() + // 是否是对象 + typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param) + // 当前时间戳 - 传入时间戳 + time = Number.parseInt(`${time - t}`) + if (time < 10000) { + // 10秒内 + return '刚刚' + } else if (time < 60000 && time >= 10000) { + // 超过10秒少于1分钟内 + s = Math.floor(time / 1000) + return `${s}秒前` + } else if (time < 3600000 && time >= 60000) { + // 超过1分钟少于1小时 + s = Math.floor(time / 60000) + return `${s}分钟前` + } else if (time < 86400000 && time >= 3600000) { + // 超过1小时少于24小时 + s = Math.floor(time / 3600000) + return `${s}小时前` + } else if (time < 259200000 && time >= 86400000) { + // 超过1天少于3天内 + s = Math.floor(time / 86400000) + return `${s}天前` + } else { + // 超过3天 + const date = typeof param === 'string' || 'object' ? new Date(param) : param + return formatDate(date, format) + } +} + +/** + * 时间问候语 + * @param param 当前时间,new Date() 格式 + * @description param 调用 `formatAxis(new Date())` 输出 `上午好` + * @returns 返回拼接后的时间字符串 + */ +export function formatAxis(param: Date): string { + const hour: number = new Date(param).getHours() + if (hour < 6) return '凌晨好' + else if (hour < 9) return '早上好' + else if (hour < 12) return '上午好' + else if (hour < 14) return '中午好' + else if (hour < 17) return '下午好' + else if (hour < 19) return '傍晚好' + else if (hour < 22) return '晚上好' + else return '夜里好' +} + +/** + * 将毫秒,转换成时间字符串。例如说,xx 分钟 + * + * @param ms 毫秒 + * @returns {string} 字符串 + */ +export function formatPast2(ms) { + const day = Math.floor(ms / (24 * 60 * 60 * 1000)) + const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24) + const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60) + const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60) + if (day > 0) { + return day + '天' + hour + '小时' + minute + '分钟' + } + if (hour > 0) { + return hour + '小时' + minute + '分钟' + } + if (minute > 0) { + return minute + '分钟' + } + if (second > 0) { + return second + '秒' + } else { + return 0 + '秒' + } +} + +/** + * element plus 的时间 Formatter 实现,使用 YYYY-MM-DD HH:mm:ss 格式 + * + * @param row 行数据 + * @param column 字段 + * @param cellValue 字段值 + */ +// @ts-ignore +export const dateFormatter = (row: any, column: any, cellValue: any) => { + if (!cellValue) { + return + } + return formatDate(cellValue) +} + +/** + * 设置起始日期,时间为00:00:00 + * @param param 传入日期 + * @returns 带时间00:00:00的日期 + */ +export function beginOfDay(param: Date) { + return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0, 0) +} + +/** + * 设置结束日期,时间为23:59:59 + * @param param 传入日期 + * @returns 带时间23:59:59的日期 + */ +export function endOfDay(param: Date) { + return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59, 999) +} + +/** + * 计算两个日期间隔天数 + * @param param1 日期1 + * @param param2 日期2 + */ +export function betweenDay(param1: Date, param2: Date) { + param1 = convertDate(param1) + param2 = convertDate(param2) + // 计算差值 + return Math.floor((param2.getTime() - param1.getTime()) / (24 * 3600 * 1000)) +} + +/** + * 日期计算 + * @param param1 日期 + * @param param2 添加的时间 + */ +export function addTime(param1: Date, param2: number) { + param1 = convertDate(param1) + return new Date(param1.getTime() + param2) +} + +/** + * 日期转换 + * @param param 日期 + */ +export function convertDate(param: Date | string) { + if (typeof param === 'string') { + return new Date(param) + } + return param +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..4877020 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,222 @@ +/** + * + * @param component 需要注册的组件 + * @param alias 组件别名 + * @returns any + */ +export const withInstall = (component: T, alias?: string) => { + const comp = component as any + comp.install = (app: any) => { + app.component(comp.name || comp.displayName, component) + if (alias) { + app.config.globalProperties[alias] = component + } + } + return component as T & Plugin +} + +/** + * @param str 需要转下划线的驼峰字符串 + * @returns 字符串下划线 + */ +export const humpToUnderline = (str: string): string => { + return str.replace(/([A-Z])/g, '-$1').toLowerCase() +} + +/** + * @param str 需要转驼峰的下划线字符串 + * @returns 字符串驼峰 + */ +export const underlineToHump = (str: string): string => { + if (!str) return '' + return str.replace(/\-(\w)/g, (_, letter: string) => { + return letter.toUpperCase() + }) +} + +export const setCssVar = (prop: string, val: any, dom = document.documentElement) => { + dom.style.setProperty(prop, val) +} + +/** + * 查找数组对象的某个下标 + * @param {Array} ary 查找的数组 + * @param {Functon} fn 判断的方法 + */ +// eslint-disable-next-line +export const findIndex = (ary: Array, fn: Fn): number => { + if (ary.findIndex) { + return ary.findIndex(fn) + } + let index = -1 + ary.some((item: T, i: number, ary: Array) => { + const ret: T = fn(item, i, ary) + if (ret) { + index = i + return ret + } + }) + return index +} + +export const trim = (str: string) => { + return str.replace(/(^\s*)|(\s*$)/g, '') +} + +/** + * @param {Date | number | string} time 需要转换的时间 + * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss + */ +export const formatTime = (time: Date | number | string, fmt: string) => { + if (!time) return '' + else { + const date = new Date(time) + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds() + } + if (/(y+)/.test(fmt)) { + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) + } + for (const k in o) { + if (new RegExp('(' + k + ')').test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) + ) + } + } + return fmt + } +} + +/** + * 生成随机字符串 + */ +export const toAnyString = () => { + const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => { + const r: number = (Math.random() * 16) | 0 + const v: number = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString() + }) + return str +} + +export const generateUUID = () => { + if (typeof crypto === 'object') { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID() + } + if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') { + const callback = (c: any) => { + const num = Number(c) + return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString( + 16 + ) + } + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback) + } + } + let timestamp = new Date().getTime() + let performanceNow = + (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0 + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + let random = Math.random() * 16 + if (timestamp > 0) { + random = (timestamp + random) % 16 | 0 + timestamp = Math.floor(timestamp / 16) + } else { + random = (performanceNow + random) % 16 | 0 + performanceNow = Math.floor(performanceNow / 16) + } + return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16) + }) +} + +/** + * element plus 的文件大小 Formatter 实现 + * + * @param row 行数据 + * @param column 字段 + * @param cellValue 字段值 + */ +// @ts-ignore +export const fileSizeFormatter = (row, column, cellValue) => { + const fileSize = cellValue + const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + const srcSize = parseFloat(fileSize) + const index = Math.floor(Math.log(srcSize) / Math.log(1024)) + const size = srcSize / Math.pow(1024, index) + const sizeStr = size.toFixed(2) //保留的小数位数 + return sizeStr + ' ' + unitArr[index] +} + +/** + * 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2} + * @param target 目标对象 + * @param source 源对象 + */ +export const copyValueToTarget = (target, source) => { + const newObj = Object.assign({}, target, source) + // 删除多余属性 + Object.keys(newObj).forEach((key) => { + // 如果不是target中的属性则删除 + if (Object.keys(target).indexOf(key) === -1) { + delete newObj[key] + } + }) + // 更新目标对象值 + Object.assign(target, newObj) +} + +// TODO @puhui999:返回要带上 .00 哈.例如说 1.00 +/** + * 将一个整数转换为分数保留两位小数 + * @param num + */ +export const formatToFraction = (num: number | string | undefined): number => { + if (typeof num === 'undefined') return 0 + const parsedNumber = typeof num === 'string' ? parseFloat(num) : num + return parseFloat((parsedNumber / 100).toFixed(2)) +} + +/** + * 将一个分数转换为整数 + * @param num + */ +export const convertToInteger = (num: number | string | undefined): number => { + if (typeof num === 'undefined') return 0 + const parsedNumber = typeof num === 'string' ? parseFloat(num) : num + // TODO 分转元后还有小数则四舍五入 + return Math.round(parsedNumber * 100) +} + +/** + * 元转分 + */ +export const yuanToFen = (amount: string | number): number => { + return Math.round(Number(amount) * 100) +} + +/** + * 分转元 + */ +export const fenToYuan = (amount: string | number): number => { + return Number((Number(amount) / 100).toFixed(2)) +} + +export const removeNullField = (obj: Object) => { + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + if (obj[key] == '') { + delete obj[key] + } + } + } + return obj +} diff --git a/src/utils/is.ts b/src/utils/is.ts new file mode 100644 index 0000000..3752985 --- /dev/null +++ b/src/utils/is.ts @@ -0,0 +1,105 @@ +// copy to vben-admin + +const toString = Object.prototype.toString + +export const is = (val: unknown, type: string) => { + return toString.call(val) === `[object ${type}]` +} + +export const isDef = (val?: T): val is T => { + return typeof val !== 'undefined' +} + +export const isUnDef = (val?: T): val is T => { + return !isDef(val) +} + +export const isObject = (val: any): val is Record => { + return val !== null && is(val, 'Object') +} + +export const isEmpty = (val: T): val is T => { + if (isArray(val) || isString(val)) { + return val.length === 0 + } + + if (val instanceof Map || val instanceof Set) { + return val.size === 0 + } + + if (isObject(val)) { + return Object.keys(val).length === 0 + } + + return false +} + +export const isDate = (val: unknown): val is Date => { + return is(val, 'Date') +} + +export const isNull = (val: unknown): val is null => { + return val === null +} + +export const isNullAndUnDef = (val: unknown): val is null | undefined => { + return isUnDef(val) && isNull(val) +} + +export const isNullOrUnDef = (val: unknown): val is null | undefined => { + return isUnDef(val) || isNull(val) +} + +export const isNumber = (val: unknown): val is number => { + return is(val, 'Number') +} + +export const isPromise = (val: unknown): val is Promise => { + return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) +} + +export const isString = (val: unknown): val is string => { + return is(val, 'String') +} + +export const isFunction = (val: unknown): val is Function => { + return typeof val === 'function' +} + +export const isBoolean = (val: unknown): val is boolean => { + return is(val, 'Boolean') +} + +export const isRegExp = (val: unknown): val is RegExp => { + return is(val, 'RegExp') +} + +export const isArray = (val: any): val is Array => { + return val && Array.isArray(val) +} + +export const isWindow = (val: any): val is Window => { + return typeof window !== 'undefined' && is(val, 'Window') +} + +export const isElement = (val: unknown): val is Element => { + return isObject(val) && !!val.tagName +} + +export const isMap = (val: unknown): val is Map => { + return is(val, 'Map') +} + +export const isServer = typeof window === 'undefined' + +export const isClient = !isServer + +export const isUrl = (path: string): boolean => { + const reg = + /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ + return reg.test(path) +} + +export const isDark = (): boolean => { + return window.matchMedia('(prefers-color-scheme: dark)').matches +} diff --git a/src/utils/jsencrypt.ts b/src/utils/jsencrypt.ts new file mode 100644 index 0000000..374d5f6 --- /dev/null +++ b/src/utils/jsencrypt.ts @@ -0,0 +1,31 @@ +import { JSEncrypt } from 'jsencrypt' + +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = + 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' + +const privateKey = + 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + + 'UP8iWi1Qw0Y=' + +// 加密 +export const encrypt = (txt: string) => { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +export const decrypt = (txt: string) => { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} diff --git a/src/utils/permission.ts b/src/utils/permission.ts new file mode 100644 index 0000000..a63ee62 --- /dev/null +++ b/src/utils/permission.ts @@ -0,0 +1,45 @@ +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' + +const { t } = useI18n() // 国际化 + +/** + * 字符权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkPermi(value: string[]) { + if (value && value instanceof Array && value.length > 0) { + const { wsCache } = useCache() + const permissionDatas = value + const all_permission = '*:*:*' + const permissions = wsCache.get(CACHE_KEY.USER).permissions + const hasPermission = permissions.some((permission) => { + return all_permission === permission || permissionDatas.includes(permission) + }) + return !!hasPermission + } else { + console.error(t('permission.hasPermission')) + return false + } +} + +/** + * 角色权限校验 + * @param {string[]} value 校验值 + * @returns {Boolean} + */ +export function checkRole(value: string[]) { + if (value && value instanceof Array && value.length > 0) { + const { wsCache } = useCache() + const permissionRoles = value + const super_admin = 'admin' + const roles = wsCache.get(CACHE_KEY.USER).roles + const hasRole = roles.some((role) => { + return super_admin === role || permissionRoles.includes(role) + }) + return !!hasRole + } else { + console.error(t('permission.hasRole')) + return false + } +} diff --git a/src/utils/propTypes.ts b/src/utils/propTypes.ts new file mode 100644 index 0000000..fb8f84e --- /dev/null +++ b/src/utils/propTypes.ts @@ -0,0 +1,28 @@ +import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types' +import { CSSProperties } from 'vue' + +// 自定义扩展vue-types +type PropTypes = VueTypesInterface & { + readonly style: VueTypeValidableDef +} + +const propTypes = createTypes({ + func: undefined, + bool: undefined, + string: undefined, + number: undefined, + object: undefined, + integer: undefined +}) as PropTypes + +// 需要自定义扩展的类型 +// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method +// propTypes.extend([ +// { +// name: 'style', +// getter: true, +// type: [String, Object], +// default: undefined +// } +// ]) +export { propTypes } diff --git a/src/utils/routerHelper.ts b/src/utils/routerHelper.ts new file mode 100644 index 0000000..a682565 --- /dev/null +++ b/src/utils/routerHelper.ts @@ -0,0 +1,238 @@ +import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router' +import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' +import { isUrl } from '@/utils/is' +import { cloneDeep, omit } from 'lodash-es' + +const modules = import.meta.glob('../views/**/*.{vue,tsx}') +/** + * 注册一个异步组件 + * @param componentPath 例:/bpm/oa/leave/detail + */ +export const registerComponent = (componentPath: string) => { + for (const item in modules) { + if (item.includes(componentPath)) { + // 使用异步组件的方式来动态加载组件 + // @ts-ignore + return defineAsyncComponent(modules[item]) + } + } +} +/* Layout */ +export const Layout = () => import('@/layout/Layout.vue') + +export const getParentLayout = () => { + return () => + new Promise((resolve) => { + resolve({ + name: 'ParentLayout' + }) + }) +} + +// 按照路由中meta下的rank等级升序来排序路由 +export const ascending = (arr: any[]) => { + arr.forEach((v) => { + if (v?.meta?.rank === null) v.meta.rank = undefined + if (v?.meta?.rank === 0) { + if (v.name !== 'home' && v.path !== '/') { + console.warn('rank only the home page can be 0') + } + } + }) + return arr.sort((a: { meta: { rank: number } }, b: { meta: { rank: number } }) => { + return a?.meta?.rank - b?.meta?.rank + }) +} + +export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => { + if (!route) return route + const { matched, ...opt } = route + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ + meta: item.meta, + name: item.name, + path: item.path + })) + : undefined) as RouteRecordNormalized[] + } +} + +// 后端控制路由生成 +export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + const modulesRoutesKeys = Object.keys(modules) + for (const route of routes) { + const meta = { + title: route.name, + icon: route.icon, + hidden: !route.visible, + noCache: !route.keepAlive, + alwaysShow: + route.children && + route.children.length === 1 && + (route.alwaysShow !== undefined ? route.alwaysShow : true) + } + // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive + let data: AppRouteRecordRaw = { + path: route.path, + name: + route.componentName && route.componentName.length > 0 + ? route.componentName + : toCamelCase(route.path, true), + redirect: route.redirect, + meta: meta + } + //处理顶级非目录路由 + if (!route.children && route.parentId == 0 && route.component) { + data.component = Layout + data.meta = {} + data.name = toCamelCase(route.path, true) + 'Parent' + data.redirect = '' + meta.alwaysShow = true + const childrenData: AppRouteRecordRaw = { + path: '', + name: toCamelCase(route.path, true), + redirect: route.redirect, + meta: meta + } + const index = route?.component + ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component)) + : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path)) + childrenData.component = modules[modulesRoutesKeys[index]] + data.children = [childrenData] + } else { + // 目录 + if (route.children) { + data.component = Layout + data.redirect = getRedirect(route.path, route.children) + // 外链 + } else if (isUrl(route.path)) { + data = { + path: '/external-link', + component: Layout, + meta: { + name: route.name + }, + children: [data] + } as AppRouteRecordRaw + // 菜单 + } else { + // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会根path保持一致) + const index = route?.component + ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component)) + : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path)) + data.component = modules[modulesRoutesKeys[index]] + } + if (route.children) { + data.children = generateRoute(route.children) + } + } + res.push(data as AppRouteRecordRaw) + } + return res +} +export const getRedirect = (parentPath: string, children: AppCustomRouteRecordRaw[]) => { + if (!children || children.length == 0) { + return parentPath + } + const path = generateRoutePath(parentPath, children[0].path) + // 递归子节点 + if (children[0].children) return getRedirect(path, children[0].children) +} +const generateRoutePath = (parentPath: string, path: string) => { + if (parentPath.endsWith('/')) { + parentPath = parentPath.slice(0, -1) // 移除默认的 / + } + if (!path.startsWith('/')) { + path = '/' + path + } + return parentPath + path +} +export const pathResolve = (parentPath: string, path: string) => { + if (isUrl(path)) return path + const childPath = path.startsWith('/') || !path ? path : `/${path}` + return `${parentPath}${childPath}`.replace(/\/\//g, '/') +} + +// 路由降级 +export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => { + const modules: AppRouteRecordRaw[] = cloneDeep(routes) + for (let index = 0; index < modules.length; index++) { + const route = modules[index] + if (!isMultipleRoute(route)) { + continue + } + promoteRouteLevel(route) + } + return modules +} + +// 层级是否大于2 +const isMultipleRoute = (route: AppRouteRecordRaw) => { + if (!route || !Reflect.has(route, 'children') || !route.children?.length) { + return false + } + + const children = route.children + + let flag = false + for (let index = 0; index < children.length; index++) { + const child = children[index] + if (child.children?.length) { + flag = true + break + } + } + return flag +} + +// 生成二级路由 +const promoteRouteLevel = (route: AppRouteRecordRaw) => { + let router: Router | null = createRouter({ + routes: [route as RouteRecordRaw], + history: createWebHashHistory() + }) + + const routes = router.getRoutes() + addToChildren(routes, route.children || [], route) + router = null + + route.children = route.children?.map((item) => omit(item, 'children')) +} + +// 添加所有子菜单 +const addToChildren = ( + routes: RouteRecordNormalized[], + children: AppRouteRecordRaw[], + routeModule: AppRouteRecordRaw +) => { + for (let index = 0; index < children.length; index++) { + const child = children[index] + const route = routes.find((item) => item.name === child.name) + if (!route) { + continue + } + routeModule.children = routeModule.children || [] + if (!routeModule.children.find((item) => item.name === route.name)) { + routeModule.children?.push(route as unknown as AppRouteRecordRaw) + } + if (child.children?.length) { + addToChildren(routes, child.children, routeModule) + } + } +} +const toCamelCase = (str: string, upperCaseFirst: boolean) => { + str = (str || '') + .replace(/-(.)/g, function (group1: string) { + return group1.toUpperCase() + }) + .replaceAll('-', '') + + if (upperCaseFirst && str) { + str = str.charAt(0).toUpperCase() + str.slice(1) + } + + return str +} diff --git a/src/utils/tree.ts b/src/utils/tree.ts new file mode 100644 index 0000000..445adf1 --- /dev/null +++ b/src/utils/tree.ts @@ -0,0 +1,304 @@ +interface TreeHelperConfig { + id: string + children: string + pid: string +} +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid' +} +export const defaultProps = { + children: 'children', + label: 'name', + value: 'id', + isLeaf: 'leaf' +} + +const getConfig = (config: Partial) => Object.assign({}, DEFAULT_CONFIG, config) + +// tree from list +export const listToTree = (list: any[], config: Partial = {}): T[] => { + const conf = getConfig(config) as TreeHelperConfig + const nodeMap = new Map() + const result: T[] = [] + const { id, children, pid } = conf + + for (const node of list) { + node[children] = node[children] || [] + nodeMap.set(node[id], node) + } + for (const node of list) { + const parent = nodeMap.get(node[pid]) + ;(parent ? parent.children : result).push(node) + } + return result +} + +export const treeToList = (tree: any, config: Partial = {}): T => { + config = getConfig(config) + const { children } = config + const result: any = [...tree] + for (let i = 0; i < result.length; i++) { + if (!result[i][children!]) continue + result.splice(i + 1, 0, ...result[i][children!]) + } + return result +} + +export const findNode = ( + tree: any, + func: Fn, + config: Partial = {} +): T | null => { + config = getConfig(config) + const { children } = config + const list = [...tree] + for (const node of list) { + if (func(node)) return node + node[children!] && list.push(...node[children!]) + } + return null +} + +export const findNodeAll = ( + tree: any, + func: Fn, + config: Partial = {} +): T[] => { + config = getConfig(config) + const { children } = config + const list = [...tree] + const result: T[] = [] + for (const node of list) { + func(node) && result.push(node) + node[children!] && list.push(...node[children!]) + } + return result +} + +export const findPath = ( + tree: any, + func: Fn, + config: Partial = {} +): T | T[] | null => { + config = getConfig(config) + const path: T[] = [] + const list = [...tree] + const visitedSet = new Set() + const { children } = config + while (list.length) { + const node = list[0] + if (visitedSet.has(node)) { + path.pop() + list.shift() + } else { + visitedSet.add(node) + node[children!] && list.unshift(...node[children!]) + path.push(node) + if (func(node)) { + return path + } + } + } + return null +} + +export const findPathAll = (tree: any, func: Fn, config: Partial = {}) => { + config = getConfig(config) + const path: any[] = [] + const list = [...tree] + const result: any[] = [] + const visitedSet = new Set(), + { children } = config + while (list.length) { + const node = list[0] + if (visitedSet.has(node)) { + path.pop() + list.shift() + } else { + visitedSet.add(node) + node[children!] && list.unshift(...node[children!]) + path.push(node) + func(node) && result.push([...path]) + } + } + return result +} + +export const filter = ( + tree: T[], + func: (n: T) => boolean, + config: Partial = {} +): T[] => { + config = getConfig(config) + const children = config.children as string + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + node[children] = node[children] && listFilter(node[children]) + return func(node) || (node[children] && node[children].length) + }) + } + return listFilter(tree) +} + +export const forEach = ( + tree: T[], + func: (n: T) => any, + config: Partial = {} +): void => { + config = getConfig(config) + const list: any[] = [...tree] + const { children } = config + for (let i = 0; i < list.length; i++) { + // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return + } + children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]) + } +} + +/** + * @description: Extract tree specified structure + */ +export const treeMap = ( + treeData: T[], + opt: { children?: string; conversion: Fn } +): T[] => { + return treeData.map((item) => treeMapEach(item, opt)) +} + +/** + * @description: Extract tree specified structure + */ +export const treeMapEach = ( + data: any, + { children = 'children', conversion }: { children?: string; conversion: Fn } +) => { + const haveChildren = Array.isArray(data[children]) && data[children].length > 0 + const conversionData = conversion(data) || {} + if (haveChildren) { + return { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion + }) + ) + } + } else { + return { + ...conversionData + } + } +} + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element + if (element.children) { + eachTree(element.children, callBack, newNode) + } + }) +} + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => { + if (!Array.isArray(data)) { + console.warn('data must be an array') + return [] + } + const config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + } + + const childrenListMap = {} + const nodeIds = {} + const tree: any[] = [] + + for (const d of data) { + const parentId = d[config.parentId] + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = [] + } + nodeIds[d[config.id]] = d + childrenListMap[parentId].push(d) + } + + for (const d of data) { + const parentId = d[config.parentId] + if (nodeIds[parentId] == null) { + tree.push(d) + } + } + + for (const t of tree) { + adaptToChildrenList(t) + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]] + } + if (o[config.childrenList]) { + for (const c of o[config.childrenList]) { + adaptToChildrenList(c) + } + } + } + return tree +} + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + * @param {*} rootId 根Id 默认 0 + */ +// @ts-ignore +export const handleTree2 = (data, id, parentId, children, rootId) => { + id = id || 'id' + parentId = parentId || 'parentId' + // children = children || 'children' + rootId = + rootId || + Math.min( + ...data.map((item) => { + return item[parentId] + }) + ) || + 0 + // 对源数据深度克隆 + const cloneData = JSON.parse(JSON.stringify(data)) + // 循环所有项 + const treeData = cloneData.filter((father) => { + const branchArr = cloneData.filter((child) => { + // 返回每一项的子级数组 + return father[id] === child[parentId] + }) + branchArr.length > 0 ? (father.children = branchArr) : '' + // 返回第一层 + return father[parentId] === rootId + }) + return treeData !== '' ? treeData : data +} diff --git a/src/utils/tsxHelper.ts b/src/utils/tsxHelper.ts new file mode 100644 index 0000000..6087fa3 --- /dev/null +++ b/src/utils/tsxHelper.ts @@ -0,0 +1,16 @@ +import { Slots } from 'vue' +import { isFunction } from '@/utils/is' + +export const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => { + // Reflect.has 判断一个对象是否存在某个属性 + if (!slots || !Reflect.has(slots, slot)) { + return null + } + if (!isFunction(slots[slot])) { + console.error(`${slot} is not a function!`) + return null + } + const slotFn = slots[slot] + if (!slotFn) return null + return slotFn(data) +} diff --git a/src/views/Basic/Dept/DeptForm.vue b/src/views/Basic/Dept/DeptForm.vue new file mode 100644 index 0000000..9f61981 --- /dev/null +++ b/src/views/Basic/Dept/DeptForm.vue @@ -0,0 +1,179 @@ + + diff --git a/src/views/Basic/Dept/index.vue b/src/views/Basic/Dept/index.vue new file mode 100644 index 0000000..d337d7c --- /dev/null +++ b/src/views/Basic/Dept/index.vue @@ -0,0 +1,113 @@ + + diff --git a/src/views/Basic/Dict/DictTypeForm.vue b/src/views/Basic/Dict/DictTypeForm.vue new file mode 100644 index 0000000..d789785 --- /dev/null +++ b/src/views/Basic/Dict/DictTypeForm.vue @@ -0,0 +1,122 @@ + + diff --git a/src/views/Basic/Dict/data/DictDataForm.vue b/src/views/Basic/Dict/data/DictDataForm.vue new file mode 100644 index 0000000..5f093d1 --- /dev/null +++ b/src/views/Basic/Dict/data/DictDataForm.vue @@ -0,0 +1,181 @@ + + diff --git a/src/views/Basic/Dict/data/index.vue b/src/views/Basic/Dict/data/index.vue new file mode 100644 index 0000000..cb4c076 --- /dev/null +++ b/src/views/Basic/Dict/data/index.vue @@ -0,0 +1,207 @@ + + diff --git a/src/views/Basic/Dict/index.vue b/src/views/Basic/Dict/index.vue new file mode 100644 index 0000000..755b941 --- /dev/null +++ b/src/views/Basic/Dict/index.vue @@ -0,0 +1,229 @@ + + + diff --git a/src/views/Basic/Menu/MenuForm.vue b/src/views/Basic/Menu/MenuForm.vue new file mode 100644 index 0000000..5bdbd6a --- /dev/null +++ b/src/views/Basic/Menu/MenuForm.vue @@ -0,0 +1,261 @@ + + diff --git a/src/views/Basic/Menu/index.vue b/src/views/Basic/Menu/index.vue new file mode 100644 index 0000000..7720244 --- /dev/null +++ b/src/views/Basic/Menu/index.vue @@ -0,0 +1,184 @@ + + diff --git a/src/views/Basic/Role/Comp/RoleEmployee.vue b/src/views/Basic/Role/Comp/RoleEmployee.vue new file mode 100644 index 0000000..5360843 --- /dev/null +++ b/src/views/Basic/Role/Comp/RoleEmployee.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/views/Basic/Role/RoleAssignMenuForm.vue b/src/views/Basic/Role/RoleAssignMenuForm.vue new file mode 100644 index 0000000..1ac26ed --- /dev/null +++ b/src/views/Basic/Role/RoleAssignMenuForm.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/views/Basic/Role/RoleDataPermissionForm.vue b/src/views/Basic/Role/RoleDataPermissionForm.vue new file mode 100644 index 0000000..9d68235 --- /dev/null +++ b/src/views/Basic/Role/RoleDataPermissionForm.vue @@ -0,0 +1,166 @@ + + diff --git a/src/views/Basic/Role/RoleForm.vue b/src/views/Basic/Role/RoleForm.vue new file mode 100644 index 0000000..9ed4e84 --- /dev/null +++ b/src/views/Basic/Role/RoleForm.vue @@ -0,0 +1,96 @@ + + diff --git a/src/views/Basic/Role/index.vue b/src/views/Basic/Role/index.vue new file mode 100644 index 0000000..6ba9419 --- /dev/null +++ b/src/views/Basic/Role/index.vue @@ -0,0 +1,147 @@ + + + + diff --git a/src/views/Basic/Salary/Comp/CommissionPlan.vue b/src/views/Basic/Salary/Comp/CommissionPlan.vue new file mode 100644 index 0000000..a3bd493 --- /dev/null +++ b/src/views/Basic/Salary/Comp/CommissionPlan.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/Basic/Salary/Comp/DialogSalary.vue b/src/views/Basic/Salary/Comp/DialogSalary.vue new file mode 100644 index 0000000..ec64a33 --- /dev/null +++ b/src/views/Basic/Salary/Comp/DialogSalary.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/views/Basic/Salary/Comp/EmployeeList.vue b/src/views/Basic/Salary/Comp/EmployeeList.vue new file mode 100644 index 0000000..263fad1 --- /dev/null +++ b/src/views/Basic/Salary/Comp/EmployeeList.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/views/Basic/Salary/index.vue b/src/views/Basic/Salary/index.vue new file mode 100644 index 0000000..383ab55 --- /dev/null +++ b/src/views/Basic/Salary/index.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/src/views/Basic/Swagger/index.vue b/src/views/Basic/Swagger/index.vue new file mode 100644 index 0000000..8404015 --- /dev/null +++ b/src/views/Basic/Swagger/index.vue @@ -0,0 +1,24 @@ +