commit 0f40b8ff4fa2323b5a9880abd4fca8af875829a6 Author: wuyy Date: Mon Jun 8 17:44:42 2026 +0800 输血管理 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..92b5028 --- /dev/null +++ b/.env.development @@ -0,0 +1,8 @@ +# 页面标题 +VITE_APP_TITLE = 输血系统 + +# 开发环境配置 +VITE_APP_ENV = 'development' + +# 输血系统/开发环境 +VITE_APP_BASE_API = '/dev-api' diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..1e3157a --- /dev/null +++ b/.env.production @@ -0,0 +1,11 @@ +# 页面标题 +VITE_APP_TITLE = 输血系统 + +# 生产环境配置 +VITE_APP_ENV = 'production' + +# 输血系统/生产环境 +VITE_APP_BASE_API = '/prod-api' + +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip \ No newline at end of file diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..99eb71b --- /dev/null +++ b/.env.staging @@ -0,0 +1,11 @@ +# 页面标题 +VITE_APP_TITLE = 输血系统 + +# 生产环境配置 +VITE_APP_ENV = 'staging' + +# 输血系统/生产环境 +VITE_APP_BASE_API = '/stage-api' + +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78a752d --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**/*.log + +tests/**/coverage/ +tests/e2e/reports +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8564f29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fc3cbb --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +

+ logo +

+

RuoYi v3.9.1

+

基于SpringBoot+Vue3前后端分离的Java快速开发框架

+

+ + + +

+ +## 平台简介 + +* 本仓库为前端技术栈 [TypeScript](https://www.typescriptlang.org) + [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 +* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast) 版本。 +* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   + +# 版本对比 + +RuoYi-Vue 前端项目的三个主要演进版本,方便你直观对比其技术栈差异(并行开发维护)。 + +| 项目名称 | **RuoYi-Vue** | **RuoYi-Vue3** | **RuoYi-Vue3-TypeScript** | +| :--- | :--- | :--- | :--- | +| **前端框架** | Vue 2 | Vue 3 | Vue 3 | +| **脚本语言** | JavaScript | JavaScript | TypeScript | +| **构建工具** | Vue CLI | Vite | Vite | +| **UI 组件库** | Element UI | Element Plus | Element Plus | +| **状态管理** | Vuex | Pinia | Pinia | +| **路由管理** | Vue Router 3 | Vue Router 4 | Vue Router 4 | +| **核心特点** | 1. 技术栈经典稳定
2. 社区资料丰富
3. 当前维护重心已转移 | 1. 现代前端技术栈
2. 开发体验与性能更优
3. 官方主推的活跃版本 | 1. 类型加持,减少沟通成本
2. 开发时有提示,效率更高
3. 多人协作企业级开发项目 | +| **仓库地址** | [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) | [RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3) | [RuoYi-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Vue3/tree/typescript) | + +## 前端运行 + +```bash +# 克隆项目 +git clone https://gitcode.com/yangzongzhuan/RuoYi-Vue3.git + +# 切换typescript分支 +git checkout typescript + +# 进入项目目录 +cd RuoYi-Vue3 + +# 安装依赖 +yarn --registry=https://registry.npmmirror.com + +# 启动服务 +yarn dev + +# 构建测试环境 yarn build:stage +# 构建生产环境 yarn build:prod +# 前端访问地址 http://localhost:80 +``` + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 缓存监控:对系统的缓存信息查询,命令统计等。 +17. 在线构建器:拖动表单元素生成相应的HTML代码。 +18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 + +## 在线体验 + +- admin/admin123 +- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 + +演示地址:http://vue.ruoyi.vip +文档地址:http://doc.ruoyi.vip + +## 演示图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +## 若依前后端分离交流群 + +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/已满-174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) [![加入QQ群](https://img.shields.io/badge/127358632-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=M9y5NjAl44lAL_Vh2crmEehZU_PMU6KS&authKey=ZSDz8hEREWSaPuxQV3gEwqGIaGjfRNnkB4rJjf0IvXhrSUGSGwQFmBA%2Boe8HFxyl&noverify=0&group_code=127358632) 点击按钮入群。 \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..ab12676 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,90 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineEmits: typeof import('vue')['defineEmits'] + const defineProps: typeof import('vue')['defineProps'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useLink: typeof import('vue-router')['useLink'] + const useModel: typeof import('vue')['useModel'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/bin/build.bat b/bin/build.bat new file mode 100644 index 0000000..ecbb454 --- /dev/null +++ b/bin/build.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [ÐÅϢ] ´ò°üWeb¹¤³̣¬Éú³ÉdistÎļþ¡£ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn build:prod + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..f5b24e0 --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [ÐÅϢ] °²װWeb¹¤³̣¬Éú³Énode_modulesÎļþ¡£ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn --registry=https://registry.npmmirror.com + +pause \ No newline at end of file diff --git a/bin/run-web.bat b/bin/run-web.bat new file mode 100644 index 0000000..d2fe397 --- /dev/null +++ b/bin/run-web.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [ÐÅϢ] ʹÓà Vite ÃüÁîÔËÐÐ Web ¹¤³̡£ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn dev + +pause \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..06c1998 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,82 @@ +import js from "@eslint/js"; +import globals from "globals"; +import pluginVue from "eslint-plugin-vue"; +import { defineConfig } from "eslint/config"; +// 引入 TypeScript ESLint 插件和解析器 +import tsParser from "@typescript-eslint/parser"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; + +export default defineConfig([ + // 1. 基础 JS 规则(适用于 JS/Vue 文件) + { + files: ["**/*.{js,mjs,cjs,vue}"], + plugins: { js }, + extends: ["js/recommended"] + }, + // 2. 浏览器环境全局变量(适用于 JS/Vue/TS 文件) + { + files: ["**/*.{js,mjs,cjs,vue,ts,tsx}"], + languageOptions: { + globals: globals.browser + } + }, + // 3. Vue 基础规则 + pluginVue.configs["flat/essential"], + // 4. Auto-import 插件配置(保持不变) + { + plugins: { + 'auto-import': { + extensions: ['.js', '.mjs', '.jsx', '.vue', '.ts', '.tsx'], // 增加 TS 扩展名 + }, + }, + }, + // 5. TypeScript 专属配置(核心) + { + // 仅对 TS/TSX 文件生效 + files: ["**/*.{ts,tsx}"], + // 指定 TS 解析器 + languageOptions: { + parser: tsParser, + // 解析器选项(需与 tsconfig.json 匹配) + parserOptions: { + ecmaVersion: "latest", // 支持最新 ES 语法 + sourceType: "module", // 模块类型(ESM) + project: "./tsconfig.json", // 指向项目的 tsconfig.json(启用类型检查) + tsconfigRootDir: process.cwd(), // 确保找到 tsconfig.json 的根目录 + }, + }, + // 引入 TS 插件 + plugins: { + "@typescript-eslint": tsPlugin, + }, + // 继承 TS 推荐规则(可选,按需调整) + extends: [ + "plugin:@typescript-eslint/recommended", // 基础推荐规则 + "plugin:@typescript-eslint/recommended-requiring-type-checking" // 依赖类型检查的规则(需 project 配置) + ], + // TS 专属规则(可自定义) + rules: { + // 示例:禁用某些严格规则(根据需求调整) + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "warn" + } + }, + // 6. 全局规则(覆盖所有文件) + { + rules: { + // 可在此处添加适用于所有文件的规则 + // 例如关闭不必要的规则 + "no-undef": "off" // TS 已有类型检查,可关闭 JS 的 undef 检查 + }, + }, + // 7. Auto-import 配置(增加 TS 支持) + { + settings: { + 'auto-import': { + imports: ['vue', 'vue-router'], // 自动导入的模块 + // 增加 TS 扩展名 + extensions: ['.js', '.mjs', '.jsx', '.vue', '.ts', '.tsx'], + }, + }, + }, +]); \ No newline at end of file diff --git a/html/ie.html b/html/ie.html new file mode 100644 index 0000000..052ffcd --- /dev/null +++ b/html/ie.html @@ -0,0 +1,46 @@ + + + + + + 请升级您的浏览器 + + + + + + +

请升级您的浏览器,以便我们更好的为您提供服务!

+

您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。

+
+

请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

+

自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明 。

+
+

您可以选择更先进的浏览器

+

推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。

+ +
+ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..d1d824e --- /dev/null +++ b/index.html @@ -0,0 +1,215 @@ + + + + + + + + + + %VITE_APP_TITLE% + + + + + +
+
+
+
+
+
正在加载系统资源,请耐心等待
+
+
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ae14030 --- /dev/null +++ b/package.json @@ -0,0 +1,66 @@ +{ + "name": "blood", + "version": "1.0", + "description": "输血管理系统", + "author": "若依", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "vite", + "build:prod": "vite build", + "build:stage": "vite build --mode staging", + "preview": "vite preview" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@element-plus/icons-vue": "2.3.2", + "@vueup/vue-quill": "1.2.0", + "@vueuse/core": "14.1.0", + "axios": "1.13.2", + "clipboard": "2.0.11", + "dayjs": "^1.11.19", + "echarts": "5.6.0", + "element-plus": "2.13.1", + "file-saver": "2.0.5", + "fuse.js": "7.1.0", + "js-beautify": "1.15.4", + "js-cookie": "3.0.5", + "jsencrypt": "3.3.2", + "mitt": "^3.0.1", + "nprogress": "0.2.0", + "pinia": "3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", + "pinyin-pro": "^3.28.0", + "sortablejs": "^1.15.7", + "splitpanes": "4.0.4", + "vue": "^3.5.26", + "vue-cropper": "1.1.1", + "vue-router": "4.6.4", + "vuedraggable": "4.1.0", + "vxe-pc-ui": "^4.3.2", + "vxe-table": "^4.9.4" + }, + "devDependencies": { + "@types/node": "^25.3.5", + "@types/sortablejs": "^1.15.9", + "@vitejs/plugin-vue": "5.2.4", + "@vue/tsconfig": "^0.5.1", + "sass-embedded": "1.97.2", + "typescript": "5.6.3", + "unplugin-auto-import": "0.18.6", + "unplugin-vue-setup-extend-plus": "1.0.1", + "vite": "^6.4.1", + "vite-plugin-compression": "0.5.1", + "vite-plugin-svg-icons": "2.0.1", + "vue-tsc": "2.1.10" + }, + "overrides": { + "quill": "2.0.2" + }, + "resolutions": { + "quill": "2.0.2" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..ebcd63d Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..7a309a9 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/api/liswork/dict/ComDict.ts b/src/api/liswork/dict/ComDict.ts new file mode 100644 index 0000000..ace0db9 --- /dev/null +++ b/src/api/liswork/dict/ComDict.ts @@ -0,0 +1,47 @@ +// @ts-ignore +import request from '@/utils/request' + +// 查询字典数据列表 +export function queryComDictListService(query?: Object) { + return request({ + url: '/comdict/query', + method: 'get', + params: query + }) +} +//增加字典数据 +export function addComDictService(data?: Object) { + return request({ + url: '/comdict/add', + method: 'post', + data + }) +} + +//修改字典数据 +export function updateComDictService(data?: Object) { + return request({ + url: '/comdict/update', + method: 'post', + data + }) + +} +//删除字典数据 +export function delComDictService(data?: Object) { + return request({ + url: '/comdict/del', + method: 'delete', + data + }) + +} + +// 根据字典类型查询字典数据信息 +export function getComDicts(dict_type?: Object) { + return request({ + url: '/transitdict/regdict/getInfoOne', + method: 'get', + params: dict_type + }) +} \ No newline at end of file diff --git a/src/api/liswork/dict/LabInstr.ts b/src/api/liswork/dict/LabInstr.ts new file mode 100644 index 0000000..a31d3b5 --- /dev/null +++ b/src/api/liswork/dict/LabInstr.ts @@ -0,0 +1,19 @@ +// @ts-ignore +import request from '@/utils/request' + +// 查询字典数据列表 +export function querylabInstrList(query?: Object) { + return request({ + url: '/instr/query', + method: 'get', + params: query + }) +} +// 查询字典数据列表 +export function querylabInstrListByLisgroup(query?: Object) { + return request({ + url: '/instr/querybylisgroup', + method: 'get', + params: query + }) +} \ No newline at end of file diff --git a/src/api/liswork/work/LisWork.ts b/src/api/liswork/work/LisWork.ts new file mode 100644 index 0000000..0ead2b9 --- /dev/null +++ b/src/api/liswork/work/LisWork.ts @@ -0,0 +1,12 @@ +import request from '@/utils/request' +import { ElLoading, ElMessage } from 'element-plus' + + +// 仪器组可操作仪器查询 +export function getGroupInstrdList(query?: Object) { + return request({ + url: '/liswork/getGroupInstrdList', + method: 'get', + params: query + }) +} diff --git a/src/api/liswork/xtwh/configdictApi.ts b/src/api/liswork/xtwh/configdictApi.ts new file mode 100644 index 0000000..1c87a87 --- /dev/null +++ b/src/api/liswork/xtwh/configdictApi.ts @@ -0,0 +1,46 @@ +//参数设置字典 +// @ts-ignore +import request from '@/utils/request' + +// 查询字典列表 +export function listconfigdict(params:any) { + return request({ + url: '/configdict/list', + method: 'get', + params + }) +} + +// 查询字典详细 +export function getconfigdict(id:number) { + return request({ + url: `/configdict/${id}`, + method: 'get' + }) +} + +// 新增字典 +export function addconfigdict(params:any) { + return request({ + url: '/configdict', + method: 'post', + data:params + }) +} + +// 修改字典 +export function updateconfigdict(params:any) { + return request({ + url: '/configdict', + method: 'put', + data:params + }) +} + +// 删除字典 +export function delconfigdict(id:any) { + return request({ + url: `/configdict/${id}`, + method: 'delete' + }) +} diff --git a/src/api/liswork/xtwh/localconfigApi.ts b/src/api/liswork/xtwh/localconfigApi.ts new file mode 100644 index 0000000..139878b --- /dev/null +++ b/src/api/liswork/xtwh/localconfigApi.ts @@ -0,0 +1,45 @@ +//本地参数设置 +// @ts-ignore +import request from '@/utils/request' + +// 查询字典列表 +export function listlocalconfig(params:any) { + return request({ + url: '/localconfig/list', + method: 'get', + params + }) +} +export function getLocalConfigDefault(params:any) { + return request({ + url: '/localconfig/resetdef', + method: 'get', + params + }) +} + +// 查询字典下拉框内容 +export function getoptionselect(params:any) { + return request({ + url: '/localconfig/optionselect', + method: 'get', + params + }) +} +// 修改字典 +export function updatelocalconfig(params:any) { + return request({ + url: '/localconfig', + method: 'put', + data:params + }) +} +// 批量保存所有结果 +export function savelocalconfig(params:any) { + return request({ + url: '/localconfig/editall', + method: 'put', + data:params + }) +} + diff --git a/src/api/login.ts b/src/api/login.ts new file mode 100644 index 0000000..517f72d --- /dev/null +++ b/src/api/login.ts @@ -0,0 +1,83 @@ +import request from '@/utils/request' +import type { SysRegister, LoginInfoResult, UserInfoResult, CaptchaInfoResult, AjaxResult } from '@/types' + +// 登录方法 +export function login(username: string, password: string, code: string, uuid: string, loginParam?: Object): Promise { + const data = { + username, + password, + code, + uuid, + loginParam + } + return request({ + url: '/login', + headers: { + isToken: false, + repeatSubmit: false + }, + method: 'post', + data: data + }) +} + +// 单点登录 +export function loginAnonymous(data: { username: string; yljg: string }): Promise { + return request({ + url: '/loginAnonymous', + method: 'post', + data: data + }) +} + +// 注册方法 +export function register(data: SysRegister): Promise { + return request({ + url: '/register', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +export function getLocalconfig(data: object) { + return request({ + url: '/getLocalconfig', + headers: { + isToken: false + }, + method: 'get', + params: data, + timeout: 20000 + }) +} + +// 获取用户详细信息 +export function getInfo(): Promise { + return request({ + url: '/getInfo', + method: 'get' + }) +} + +// 退出方法 +export function logout() { + return request({ + url: '/logout', + method: 'post' + }) +} + +// 获取验证码 +export function getCodeImg(): Promise { + return request({ + url: '/captchaImage', + headers: { + isToken: false + }, + method: 'get', + timeout: 20000 + }) +} diff --git a/src/api/menu.ts b/src/api/menu.ts new file mode 100644 index 0000000..092ceb9 --- /dev/null +++ b/src/api/menu.ts @@ -0,0 +1,10 @@ +import request from '@/utils/request' +import type { RouterVo, AjaxResult } from '@/types' + +// 获取路由 +export const getRouters = (): Promise> => { + return request({ + url: '/getRouters', + method: 'get' + }) +} diff --git a/src/api/monitor/cache.ts b/src/api/monitor/cache.ts new file mode 100644 index 0000000..93a4537 --- /dev/null +++ b/src/api/monitor/cache.ts @@ -0,0 +1,58 @@ +import request from '@/utils/request' +import type { SysCache, AjaxResult } from '@/types' + +// 查询缓存详细 +export function getCache(): Promise> { + return request({ + url: '/monitor/cache', + method: 'get' + }) +} + +// 查询缓存名称列表 +export function listCacheName(): Promise> { + return request({ + url: '/monitor/cache/getNames', + method: 'get' + }) +} + +// 查询缓存键名列表 +export function listCacheKey(cacheName: string): Promise> { + return request({ + url: '/monitor/cache/getKeys/' + cacheName, + method: 'get' + }) +} + +// 查询缓存内容 +export function getCacheValue(cacheName: string, cacheKey: string): Promise> { + return request({ + url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, + method: 'get' + }) +} + +// 清理指定名称缓存 +export function clearCacheName(cacheName: string): Promise { + return request({ + url: '/monitor/cache/clearCacheName/' + cacheName, + method: 'delete' + }) +} + +// 清理指定键名缓存 +export function clearCacheKey(cacheKey: string): Promise { + return request({ + url: '/monitor/cache/clearCacheKey/' + cacheKey, + method: 'delete' + }) +} + +// 清理全部缓存 +export function clearCacheAll(): Promise { + return request({ + url: '/monitor/cache/clearCacheAll', + method: 'delete' + }) +} diff --git a/src/api/monitor/job.ts b/src/api/monitor/job.ts new file mode 100644 index 0000000..28057b0 --- /dev/null +++ b/src/api/monitor/job.ts @@ -0,0 +1,71 @@ +import request from '@/utils/request' +import type { JobQueryParams, SysJob, AjaxResult, TableDataInfo } from '@/types' + +// 查询定时任务调度列表 +export function listJob(query: JobQueryParams): Promise> { + return request({ + url: '/monitor/job/list', + method: 'get', + params: query + }) +} + +// 查询定时任务调度详细 +export function getJob(jobId: number): Promise> { + return request({ + url: '/monitor/job/' + jobId, + method: 'get' + }) +} + +// 新增定时任务调度 +export function addJob(data: SysJob): Promise { + return request({ + url: '/monitor/job', + method: 'post', + data: data + }) +} + +// 修改定时任务调度 +export function updateJob(data: SysJob): Promise { + return request({ + url: '/monitor/job', + method: 'put', + data: data + }) +} + +// 删除定时任务调度 +export function delJob(jobId: number | number[]): Promise { + return request({ + url: '/monitor/job/' + jobId, + method: 'delete' + }) +} + +// 任务状态修改 +export function changeJobStatus(jobId: number, status: string): Promise { + const data = { + jobId, + status + } + return request({ + url: '/monitor/job/changeStatus', + method: 'put', + data: data + }) +} + +// 定时任务立即执行一次 +export function runJob(jobId: number, jobGroup: string): Promise { + const data = { + jobId, + jobGroup + } + return request({ + url: '/monitor/job/run', + method: 'put', + data: data + }) +} diff --git a/src/api/monitor/jobLog.ts b/src/api/monitor/jobLog.ts new file mode 100644 index 0000000..7ef73a5 --- /dev/null +++ b/src/api/monitor/jobLog.ts @@ -0,0 +1,28 @@ +import request from '@/utils/request' +import type { JobLogQueryParams, SysJobLog, AjaxResult, TableDataInfo } from '@/types' + +// 查询调度日志列表 +export function listJobLog(query: JobLogQueryParams): Promise> { + return request({ + url: '/monitor/jobLog/list', + method: 'get', + params: query + }) +} + +// 删除调度日志 +export function delJobLog(jobLogId: number | number[]): Promise { + return request({ + url: '/monitor/jobLog/' + jobLogId, + method: 'delete' + }) +} + +// 清空调度日志 +export function cleanJobLog(): Promise { + return request({ + url: '/monitor/jobLog/clean', + method: 'delete' + }) +} + diff --git a/src/api/monitor/logininfor.ts b/src/api/monitor/logininfor.ts new file mode 100644 index 0000000..438e8b3 --- /dev/null +++ b/src/api/monitor/logininfor.ts @@ -0,0 +1,35 @@ +import request from '@/utils/request' +import type { LogininforQueryParams, SysLogininfor, AjaxResult, TableDataInfo } from '@/types' + +// 查询登录日志列表 +export function list(query: LogininforQueryParams): Promise> { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params: query + }) +} + +// 删除登录日志 +export function delLogininfor(infoId: number | number[]): Promise { + return request({ + url: '/monitor/logininfor/' + infoId, + method: 'delete' + }) +} + +// 解锁用户登录状态 +export function unlockLogininfor(userName: string): Promise { + return request({ + url: '/monitor/logininfor/unlock/' + userName, + method: 'get' + }) +} + +// 清空登录日志 +export function cleanLogininfor(): Promise { + return request({ + url: '/monitor/logininfor/clean', + method: 'delete' + }) +} diff --git a/src/api/monitor/online.ts b/src/api/monitor/online.ts new file mode 100644 index 0000000..080d186 --- /dev/null +++ b/src/api/monitor/online.ts @@ -0,0 +1,19 @@ +import request from '@/utils/request' +import type { OnlineQueryParams, SysUserOnline, AjaxResult, TableDataInfo } from '@/types' + +// 查询在线用户列表 +export function list(query: OnlineQueryParams): Promise> { + return request({ + url: '/monitor/online/list', + method: 'get', + params: query + }) +} + +// 强退用户 +export function forceLogout(tokenId: string): Promise { + return request({ + url: '/monitor/online/' + tokenId, + method: 'delete' + }) +} diff --git a/src/api/monitor/operlog.ts b/src/api/monitor/operlog.ts new file mode 100644 index 0000000..c4a6b24 --- /dev/null +++ b/src/api/monitor/operlog.ts @@ -0,0 +1,27 @@ +import request from '@/utils/request' +import type { OperlogQueryParams, SysOperLog, AjaxResult, TableDataInfo } from '@/types' + +// 查询操作日志列表 +export function list(query: OperlogQueryParams): Promise> { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params: query + }) +} + +// 删除操作日志 +export function delOperlog(operId: number | number[]): Promise { + return request({ + url: '/monitor/operlog/' + operId, + method: 'delete' + }) +} + +// 清空操作日志 +export function cleanOperlog(): Promise { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }) +} diff --git a/src/api/monitor/server.ts b/src/api/monitor/server.ts new file mode 100644 index 0000000..9690d42 --- /dev/null +++ b/src/api/monitor/server.ts @@ -0,0 +1,10 @@ +import request from '@/utils/request' +import type { AjaxResult } from '@/types' + +// 获取服务信息 +export function getServer(): Promise> { + return request({ + url: '/monitor/server', + method: 'get' + }) +} diff --git a/src/api/regional/dict/diagnosis.ts b/src/api/regional/dict/diagnosis.ts new file mode 100644 index 0000000..34a886d --- /dev/null +++ b/src/api/regional/dict/diagnosis.ts @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询诊断列表 +export function listdiagnosis(query: Object) { + return request({ + url: '/transitdict/diagnosis/list', + method: 'get', + params: query + }) +} + +// 查询诊断详细 +export function getdiagnosis(diagnosisId: number) { + return request({ + url: '/transitdict/diagnosis/' + diagnosisId, + method: 'get' + }) +} + +// 新增诊断 +export function adddiagnosis(data: Object) { + return request({ + url: '/transitdict/diagnosis', + method: 'post', + data: data + }) +} + +// 修改诊断 +export function updatediagnosis(data: Object) { + return request({ + url: '/transitdict/diagnosis', + method: 'put', + data: data + }) +} + +// 删除诊断 +export function deldiagnosis(diagnosisId: number) { + return request({ + url: '/transitdict/diagnosis/' + diagnosisId, + method: 'delete' + }) +} diff --git a/src/api/regional/dict/hospital.ts b/src/api/regional/dict/hospital.ts new file mode 100644 index 0000000..68c5b4d --- /dev/null +++ b/src/api/regional/dict/hospital.ts @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询医疗机构列表 +export function listhospital(query?: Object) { + return request({ + url: '/transitdict/hospital/list', + method: 'get', + params: query + }) +} + +// 查询医疗机构详细 +export function gethospital(hospitalId: number) { + return request({ + url: '/transitdict/hospital/' + hospitalId, + method: 'get' + }) +} + +// 新增医疗机构 +export function addhospital(data: Object) { + return request({ + url: '/transitdict/hospital', + method: 'post', + data: data + }) +} + +// 修改医疗机构 +export function updatehospital(data: Object) { + return request({ + url: '/transitdict/hospital', + method: 'put', + data: data + }) +} + +// 删除医疗机构 +export function delhospital(hospitalId: number) { + return request({ + url: '/transitdict/hospital/' + hospitalId, + method: 'delete' + }) +} diff --git a/src/api/regional/dict/itemclass.ts b/src/api/regional/dict/itemclass.ts new file mode 100644 index 0000000..8bf6c9c --- /dev/null +++ b/src/api/regional/dict/itemclass.ts @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询诊断列表 +export function listitemclass(query: Object) { + return request({ + url: '/transitdict/itemclass/list', + method: 'get', + params: query + }) +} + +// 查询诊断详细 +export function getitemclass(itemclassId: number) { + return request({ + url: '/transitdict/itemclass/' + itemclassId, + method: 'get' + }) +} + +// 新增诊断 +export function additemclass(data: Object) { + return request({ + url: '/transitdict/itemclass', + method: 'post', + data: data + }) +} + +// 修改诊断 +export function updateitemclass(data: Object) { + return request({ + url: '/transitdict/itemclass', + method: 'put', + data: data + }) +} + +// 删除诊断 +export function delitemclass(itemclassId: number) { + return request({ + url: '/transitdict/itemclass/' + itemclassId, + method: 'delete' + }) +} diff --git a/src/api/regional/dict/regdict.ts b/src/api/regional/dict/regdict.ts new file mode 100644 index 0000000..33cbd26 --- /dev/null +++ b/src/api/regional/dict/regdict.ts @@ -0,0 +1,59 @@ +import request from '@/utils/request' + +// 查询字典列表 +export function listregdict(query: Object) { + return request({ + url: '/transitdict/regdict/list', + method: 'get', + params: query + }) +} +// 获取字典最大编码 +export function getMaxDictCode(query: Object) { + return request({ + url: '/transitdict/regdict/getMaxDictCode', + method: 'get', + params: query + }) +} + +// 查询字典详细 +export function getregdict(regdictId: number) { + return request({ + url: '/transitdict/regdict/' + regdictId, + method: 'get' + }) +} + +// 新增字典 +export function addregdict(data: Object) { + return request({ + url: '/transitdict/regdict', + method: 'post', + data: data + }) +} + +// 修改字典 +export function updateregdict(data: Object) { + return request({ + url: '/transitdict/regdict', + method: 'put', + data: data + }) +} + +// 删除字典 +export function delregdict(regdictId: number) { + return request({ + url: '/transitdict/regdict/' + regdictId, + method: 'delete' + }) +} +// 清空缓存 +export function clearCache() { + return request({ + url: '/transitdict/regdict/clearCache', + method: 'get' + }) +} diff --git a/src/api/regional/index.ts b/src/api/regional/index.ts new file mode 100644 index 0000000..6ef11ab --- /dev/null +++ b/src/api/regional/index.ts @@ -0,0 +1,108 @@ +// @ts-ignore +import request from '@/utils/request' + +// 获取流转数据 +export function querySample(query?: Object) { + return request({ + url: '/transit/querySample', + method: 'get', + params: query + }) +} +// 获取未签收的标本 +export function querySampleSign(query?: Object) { + return request({ + url: '/transit/querySampleSign', + method: 'get', + params: query + }) +} +// 签收标本 +export function sampleSign(query?: Object) { + return request({ + url: '/transit/sampleSign', + method: 'post', + data: query + }) +} +// 退回的标本 +export function querySampleSignBack(query?: Object) { + return request({ + url: '/transit/querySampleSignBack', + method: 'get', + params: query + }) +} +// 标本数量统计 +export function querySampleTypeCount(query?: Object) { + return request({ + url: '/transitReport/sampleTypeCount', + method: 'get', + params: query + }) +} +// 标本报表 +export function sampleTypeCount(query?: Object) { + return request({ + url: '/transitReport/sampleTypeCount', + method: 'get', + params: query + }) +} +// 标本报表费用 +export function sampleitemCount(query?: Object) { + return request({ + url: '/transitReport/itemCount', + method: 'get', + params: query + }) +} +// 标本报表费用 +export function getallcount(query?: Object) { + return request({ + url: '/transitstat/getallcount', + method: 'get', + params: query + }) +} +// 获取流转项目列表 +export function getXmList(query?: Object) { + return request({ + url: '/item/getList', + method: 'get', + params: query + }) +} +// 新增项目 +export function addXmInfo(query?: Object) { + return request({ + url: '/item/add', + method: 'post', + data: query + }) +} +// 修改项目 +export function updateXmInfo(query?: Object) { + return request({ + url: '/item/update', + method: 'post', + data: query + }) +} +// 删除项目 +export function deleteXmInfo(query?: Object) { + return request({ + url: '/item/delete', + method: 'post', + data: query + }) +} +// 获取已打印的条码数据 +export function sampleCollect(query?: Object) { + return request({ + url: '/transit/sampleCollect', + method: 'get', + params: query + }) +} + diff --git a/src/api/system/config.ts b/src/api/system/config.ts new file mode 100644 index 0000000..ddbd63e --- /dev/null +++ b/src/api/system/config.ts @@ -0,0 +1,61 @@ +import request from '@/utils/request' +import type { ConfigQueryParams, SysConfig, AjaxResult, TableDataInfo } from '@/types' + +// 查询参数列表 +export function listConfig(query: ConfigQueryParams): Promise> { + return request({ + url: '/system/config/list', + method: 'get', + params: query + }) +} + +// 查询参数详细 +export function getConfig(configId: number): Promise> { + return request({ + url: '/system/config/' + configId, + method: 'get' + }) +} + +// 根据参数键名查询参数值 +export function getConfigKey(configKey: string): Promise { + return request({ + url: '/system/config/configKey/' + configKey, + method: 'get' + }) +} + +// 新增参数配置 +export function addConfig(data: SysConfig): Promise { + return request({ + url: '/system/config', + method: 'post', + data: data + }) +} + +// 修改参数配置 +export function updateConfig(data: SysConfig): Promise { + return request({ + url: '/system/config', + method: 'put', + data: data + }) +} + +// 删除参数配置 +export function delConfig(configId: number | number[]): Promise { + return request({ + url: '/system/config/' + configId, + method: 'delete' + }) +} + +// 刷新参数缓存 +export function refreshCache(): Promise { + return request({ + url: '/system/config/refreshCache', + method: 'delete' + }) +} diff --git a/src/api/system/dept.ts b/src/api/system/dept.ts new file mode 100644 index 0000000..17d4807 --- /dev/null +++ b/src/api/system/dept.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request' +import type { DeptQueryParams, SysDept, AjaxResult } from '@/types' + +// 查询部门列表 +export function listDept(query?: DeptQueryParams): Promise> { + return request({ + url: '/system/dept/list', + method: 'get', + params: query + }) +} + +// 查询部门列表(排除节点) +export function listDeptExcludeChild(deptId: number): Promise> { + return request({ + url: '/system/dept/list/exclude/' + deptId, + method: 'get' + }) +} + +// 查询部门详细 +export function getDept(deptId: number): Promise> { + return request({ + url: '/system/dept/' + deptId, + method: 'get' + }) +} + +// 新增部门 +export function addDept(data: SysDept): Promise { + return request({ + url: '/system/dept', + method: 'post', + data: data + }) +} + +// 修改部门 +export function updateDept(data: SysDept): Promise { + return request({ + url: '/system/dept', + method: 'put', + data: data + }) +} + +// 删除部门 +export function delDept(deptId: number): Promise { + return request({ + url: '/system/dept/' + deptId, + method: 'delete' + }) +} diff --git a/src/api/system/dict/data.ts b/src/api/system/dict/data.ts new file mode 100644 index 0000000..9185ce6 --- /dev/null +++ b/src/api/system/dict/data.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request' +import type { DictDataQueryParams, SysDictData, AjaxResult, TableDataInfo } from '@/types' + +// 查询字典数据列表 +export function listData(query: DictDataQueryParams): Promise> { + return request({ + url: '/system/dict/data/list', + method: 'get', + params: query + }) +} + +// 查询字典数据详细 +export function getData(dictCode: number): Promise> { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'get' + }) +} + +// 根据字典类型查询字典数据信息 +export function getDicts(dictType: string): Promise> { + return request({ + url: '/system/dict/data/type/' + dictType, + method: 'get' + }) +} + +// 新增字典数据 +export function addData(data: SysDictData): Promise { + return request({ + url: '/system/dict/data', + method: 'post', + data: data + }) +} + +// 修改字典数据 +export function updateData(data: SysDictData): Promise { + return request({ + url: '/system/dict/data', + method: 'put', + data: data + }) +} + +// 删除字典数据 +export function delData(dictCode: number | number[]): Promise { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'delete' + }) +} diff --git a/src/api/system/dict/type.ts b/src/api/system/dict/type.ts new file mode 100644 index 0000000..ee7dcf9 --- /dev/null +++ b/src/api/system/dict/type.ts @@ -0,0 +1,61 @@ +import request from '@/utils/request' +import type { DictTypeQueryParams, SysDictType, AjaxResult, TableDataInfo } from '@/types' + +// 查询字典类型列表 +export function listType(query: DictTypeQueryParams): Promise> { + return request({ + url: '/system/dict/type/list', + method: 'get', + params: query + }) +} + +// 查询字典类型详细 +export function getType(dictId: number): Promise> { + return request({ + url: '/system/dict/type/' + dictId, + method: 'get' + }) +} + +// 新增字典类型 +export function addType(data: SysDictType): Promise { + return request({ + url: '/system/dict/type', + method: 'post', + data: data + }) +} + +// 修改字典类型 +export function updateType(data: SysDictType): Promise { + return request({ + url: '/system/dict/type', + method: 'put', + data: data + }) +} + +// 删除字典类型 +export function delType(dictId: number | number[]): Promise { + return request({ + url: '/system/dict/type/' + dictId, + method: 'delete' + }) +} + +// 刷新字典缓存 +export function refreshCache(): Promise { + return request({ + url: '/system/dict/type/refreshCache', + method: 'delete' + }) +} + +// 获取字典选择框列表 +export function optionselect(): Promise> { + return request({ + url: '/system/dict/type/optionselect', + method: 'get' + }) +} diff --git a/src/api/system/menu.ts b/src/api/system/menu.ts new file mode 100644 index 0000000..a6d0dc5 --- /dev/null +++ b/src/api/system/menu.ts @@ -0,0 +1,61 @@ +import request from '@/utils/request' +import type { MenuQueryParams, SysMenu, TreeSelect, AjaxResult, RoleMenuTreeselectResult } from '@/types' + +// 查询菜单列表 +export function listMenu(query?: MenuQueryParams): Promise> { + return request({ + url: '/system/menu/list', + method: 'get', + params: query + }) +} + +// 查询菜单详细 +export function getMenu(menuId: number): Promise> { + return request({ + url: '/system/menu/' + menuId, + method: 'get' + }) +} + +// 查询菜单下拉树结构 +export function treeselect(): Promise> { + return request({ + url: '/system/menu/treeselect', + method: 'get' + }) +} + +// 根据角色ID查询菜单下拉树结构 +export function roleMenuTreeselect(roleId: number): Promise { + return request({ + url: '/system/menu/roleMenuTreeselect/' + roleId, + method: 'get' + }) +} + +// 新增菜单 +export function addMenu(data: SysMenu): Promise { + return request({ + url: '/system/menu', + method: 'post', + data: data + }) +} + +// 修改菜单 +export function updateMenu(data: SysMenu): Promise { + return request({ + url: '/system/menu', + method: 'put', + data: data + }) +} + +// 删除菜单 +export function delMenu(menuId: number): Promise { + return request({ + url: '/system/menu/' + menuId, + method: 'delete' + }) +} diff --git a/src/api/system/notice.ts b/src/api/system/notice.ts new file mode 100644 index 0000000..e6ba17f --- /dev/null +++ b/src/api/system/notice.ts @@ -0,0 +1,45 @@ +import request from '@/utils/request' +import type { NoticeQueryParams, SysNotice, AjaxResult, TableDataInfo } from '@/types' + +// 查询公告列表 +export function listNotice(query: NoticeQueryParams): Promise> { + return request({ + url: '/system/notice/list', + method: 'get', + params: query + }) +} + +// 查询公告详细 +export function getNotice(noticeId: number): Promise> { + return request({ + url: '/system/notice/' + noticeId, + method: 'get' + }) +} + +// 新增公告 +export function addNotice(data: SysNotice): Promise { + return request({ + url: '/system/notice', + method: 'post', + data: data + }) +} + +// 修改公告 +export function updateNotice(data: SysNotice): Promise { + return request({ + url: '/system/notice', + method: 'put', + data: data + }) +} + +// 删除公告 +export function delNotice(noticeId: number | number[]): Promise { + return request({ + url: '/system/notice/' + noticeId, + method: 'delete' + }) +} diff --git a/src/api/system/post.ts b/src/api/system/post.ts new file mode 100644 index 0000000..c56c3eb --- /dev/null +++ b/src/api/system/post.ts @@ -0,0 +1,45 @@ +import request from '@/utils/request' +import type { PostQueryParams, SysPost, AjaxResult, TableDataInfo } from '@/types' + +// 查询岗位列表 +export function listPost(query: PostQueryParams): Promise> { + return request({ + url: '/system/post/list', + method: 'get', + params: query + }) +} + +// 查询岗位详细 +export function getPost(postId: number): Promise> { + return request({ + url: '/system/post/' + postId, + method: 'get' + }) +} + +// 新增岗位 +export function addPost(data: SysPost): Promise { + return request({ + url: '/system/post', + method: 'post', + data: data + }) +} + +// 修改岗位 +export function updatePost(data: SysPost): Promise { + return request({ + url: '/system/post', + method: 'put', + data: data + }) +} + +// 删除岗位 +export function delPost(postId: number | number[]): Promise { + return request({ + url: '/system/post/' + postId, + method: 'delete' + }) +} diff --git a/src/api/system/role.ts b/src/api/system/role.ts new file mode 100644 index 0000000..b1bd92d --- /dev/null +++ b/src/api/system/role.ts @@ -0,0 +1,120 @@ +import request from '@/utils/request' +import type { RoleQueryParams, AuthUserQueryParams, SysRole, SysUser, SysUserRole, AuthUserSelectParams, AjaxResult, RoleDeptTreeResult, TableDataInfo } from '@/types' + +// 查询角色列表 +export function listRole(query: RoleQueryParams): Promise> { + return request({ + url: '/system/role/list', + method: 'get', + params: query + }) +} + +// 查询角色详细 +export function getRole(roleId: number): Promise> { + return request({ + url: '/system/role/' + roleId, + method: 'get' + }) +} + +// 新增角色 +export function addRole(data: SysRole): Promise { + return request({ + url: '/system/role', + method: 'post', + data: data + }) +} + +// 修改角色 +export function updateRole(data: SysRole): Promise { + return request({ + url: '/system/role', + method: 'put', + data: data + }) +} + +// 角色数据权限 +export function dataScope(data: SysRole): Promise { + return request({ + url: '/system/role/dataScope', + method: 'put', + data: data + }) +} + +// 角色状态修改 +export function changeRoleStatus(roleId: number, status: string): Promise { + const data = { + roleId, + status + } + return request({ + url: '/system/role/changeStatus', + method: 'put', + data: data + }) +} + +// 删除角色 +export function delRole(roleId: number | number[]): Promise { + return request({ + url: '/system/role/' + roleId, + method: 'delete' + }) +} + +// 查询角色已授权用户列表 +export function allocatedUserList(query: AuthUserQueryParams): Promise> { + return request({ + url: '/system/role/authUser/allocatedList', + method: 'get', + params: query + }) +} + +// 查询角色未授权用户列表 +export function unallocatedUserList(query: AuthUserQueryParams): Promise> { + return request({ + url: '/system/role/authUser/unallocatedList', + method: 'get', + params: query + }) +} + +// 取消用户授权角色 +export function authUserCancel(data: SysUserRole): Promise { + return request({ + url: '/system/role/authUser/cancel', + method: 'put', + data: data + }) +} + +// 批量取消用户授权角色 +export function authUserCancelAll(data: AuthUserSelectParams): Promise { + return request({ + url: '/system/role/authUser/cancelAll', + method: 'put', + params: data + }) +} + +// 授权用户选择 +export function authUserSelectAll(data: AuthUserSelectParams): Promise { + return request({ + url: '/system/role/authUser/selectAll', + method: 'put', + params: data + }) +} + +// 根据角色ID查询部门树结构 +export function deptTreeSelect(roleId: number): Promise { + return request({ + url: '/system/role/deptTree/' + roleId, + method: 'get' + }) +} diff --git a/src/api/system/user.ts b/src/api/system/user.ts new file mode 100644 index 0000000..0ae8852 --- /dev/null +++ b/src/api/system/user.ts @@ -0,0 +1,137 @@ +import request from '@/utils/request' +import { parseStrEmpty } from "@/utils/ruoyi" +import type { UserQueryParams, UserFormDataResult, UserProfileResult, UserAuthRoleResult, UserProfileAvatarResult, SysUser, SysUserRole, SysUserRoles, AjaxResult, TableDataInfo, TreeSelect } from '@/types' + +// 查询用户列表 +export function listUser(query: UserQueryParams): Promise> { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }) +} + +// 查询用户详细 +export function getUser(userId?: number): Promise { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }) +} + +// 新增用户 +export function addUser(data: SysUser): Promise { + return request({ + url: '/system/user', + method: 'post', + data: data + }) +} + +// 修改用户 +export function updateUser(data: SysUser): Promise { + return request({ + url: '/system/user', + method: 'put', + data: data + }) +} + +// 删除用户 +export function delUser(userId: number | number[]): Promise { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }) +} + +// 用户密码重置 +export function resetUserPwd(userId: number, password: string): Promise { + const data = { + userId, + password + } + return request({ + url: '/system/user/resetPwd', + method: 'put', + data: data + }) +} + +// 用户状态修改 +export function changeUserStatus(userId: number, status: string): Promise { + const data = { + userId, + status + } + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }) +} + +// 查询用户个人信息 +export function getUserProfile(): Promise { + return request({ + url: '/system/user/profile', + method: 'get' + }) +} + +// 修改用户个人信息 +export function updateUserProfile(data: SysUser): Promise { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }) +} + +// 用户密码重置 +export function updateUserPwd(oldPassword: string, newPassword: string): Promise { + const data = { + oldPassword, + newPassword + } + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + data: data + }) +} + +// 用户头像上传 +export function uploadAvatar(file: FormData | File): Promise { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: file + }) +} + +// 查询授权角色 +export function getAuthRole(userId: number): Promise { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }) +} + +// 保存授权角色 +export function updateAuthRole(data: SysUserRoles): Promise { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }) +} + +// 查询部门下拉树结构 +export function deptTreeSelect(): Promise> { + return request({ + url: '/system/user/deptTree', + method: 'get' + }) +} diff --git a/src/api/tool/gen.ts b/src/api/tool/gen.ts new file mode 100644 index 0000000..44d74a9 --- /dev/null +++ b/src/api/tool/gen.ts @@ -0,0 +1,88 @@ +import request from '@/utils/request' +import type { GenQueryParams, GenTable, GenTableInfoResult, AjaxResult, TableDataInfo } from '@/types' + +// 查询生成表数据 +export function listTable(query: GenQueryParams): Promise> { + return request({ + url: '/tool/gen/list', + method: 'get', + params: query + }) +} + +// 查询db数据库列表 +export function listDbTable(query: GenQueryParams): Promise> { + return request({ + url: '/tool/gen/db/list', + method: 'get', + params: query + }) +} + +// 查询表详细信息 +export function getGenTable(tableId: number): Promise> { + return request({ + url: '/tool/gen/' + tableId, + method: 'get' + }) +} + +// 修改代码生成信息 +export function updateGenTable(data: GenTable): Promise { + return request({ + url: '/tool/gen', + method: 'put', + data: data + }) +} + +// 导入表 +export function importTable(data: any): Promise { + return request({ + url: '/tool/gen/importTable', + method: 'post', + params: data + }) +} + +// 创建表 +export function createTable(data: any): Promise { + return request({ + url: '/tool/gen/createTable', + method: 'post', + params: data + }) +} + +// 预览生成代码 +export function previewTable(tableId: number): Promise> { + return request({ + url: '/tool/gen/preview/' + tableId, + method: 'get' + }) +} + +// 删除表数据 +export function delTable(tableId: number | number[]): Promise { + return request({ + url: '/tool/gen/' + tableId, + method: 'delete' + }) +} + +// 生成代码(自定义路径) +export function genCode(tableName: string): Promise { + return request({ + url: '/tool/gen/genCode/' + tableName, + method: 'get' + }) +} + +// 同步数据库 +export function synchDb(tableName: string): Promise { + return request({ + url: '/tool/gen/synchDb/' + tableName, + method: 'get' + }) +} + diff --git a/src/assets/401_images/401.gif b/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/src/assets/401_images/401.gif differ diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/src/assets/404_images/404.png differ diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ diff --git a/src/assets/icons/svg/404.svg b/src/assets/icons/svg/404.svg new file mode 100644 index 0000000..6df5019 --- /dev/null +++ b/src/assets/icons/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/bug.svg b/src/assets/icons/svg/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/src/assets/icons/svg/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/build.svg b/src/assets/icons/svg/build.svg new file mode 100644 index 0000000..97c4688 --- /dev/null +++ b/src/assets/icons/svg/build.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/button.svg b/src/assets/icons/svg/button.svg new file mode 100644 index 0000000..904fddc --- /dev/null +++ b/src/assets/icons/svg/button.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/cascader.svg b/src/assets/icons/svg/cascader.svg new file mode 100644 index 0000000..e256024 --- /dev/null +++ b/src/assets/icons/svg/cascader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/chart.svg b/src/assets/icons/svg/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/src/assets/icons/svg/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/checkbox.svg b/src/assets/icons/svg/checkbox.svg new file mode 100644 index 0000000..013fd3a --- /dev/null +++ b/src/assets/icons/svg/checkbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/clipboard.svg b/src/assets/icons/svg/clipboard.svg new file mode 100644 index 0000000..90923ff --- /dev/null +++ b/src/assets/icons/svg/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/code.svg b/src/assets/icons/svg/code.svg new file mode 100644 index 0000000..5f9c5ab --- /dev/null +++ b/src/assets/icons/svg/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/color.svg b/src/assets/icons/svg/color.svg new file mode 100644 index 0000000..44a81aa --- /dev/null +++ b/src/assets/icons/svg/color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/component.svg b/src/assets/icons/svg/component.svg new file mode 100644 index 0000000..29c3458 --- /dev/null +++ b/src/assets/icons/svg/component.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/dashboard.svg b/src/assets/icons/svg/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/src/assets/icons/svg/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/date-range.svg b/src/assets/icons/svg/date-range.svg new file mode 100644 index 0000000..fda571e --- /dev/null +++ b/src/assets/icons/svg/date-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/date.svg b/src/assets/icons/svg/date.svg new file mode 100644 index 0000000..52dc73e --- /dev/null +++ b/src/assets/icons/svg/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/dict.svg b/src/assets/icons/svg/dict.svg new file mode 100644 index 0000000..4849377 --- /dev/null +++ b/src/assets/icons/svg/dict.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/documentation.svg b/src/assets/icons/svg/documentation.svg new file mode 100644 index 0000000..7043122 --- /dev/null +++ b/src/assets/icons/svg/documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/download.svg b/src/assets/icons/svg/download.svg new file mode 100644 index 0000000..c896951 --- /dev/null +++ b/src/assets/icons/svg/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/drag.svg b/src/assets/icons/svg/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/src/assets/icons/svg/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/druid.svg b/src/assets/icons/svg/druid.svg new file mode 100644 index 0000000..a2b4b4e --- /dev/null +++ b/src/assets/icons/svg/druid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/edit.svg b/src/assets/icons/svg/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/src/assets/icons/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/education.svg b/src/assets/icons/svg/education.svg new file mode 100644 index 0000000..7bfb01d --- /dev/null +++ b/src/assets/icons/svg/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/email.svg b/src/assets/icons/svg/email.svg new file mode 100644 index 0000000..74d25e2 --- /dev/null +++ b/src/assets/icons/svg/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/enter.svg b/src/assets/icons/svg/enter.svg new file mode 100644 index 0000000..f7cabf2 --- /dev/null +++ b/src/assets/icons/svg/enter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/example.svg b/src/assets/icons/svg/example.svg new file mode 100644 index 0000000..46f42b5 --- /dev/null +++ b/src/assets/icons/svg/example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/excel.svg b/src/assets/icons/svg/excel.svg new file mode 100644 index 0000000..74d97b8 --- /dev/null +++ b/src/assets/icons/svg/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/exit-fullscreen.svg b/src/assets/icons/svg/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/src/assets/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/eye-open.svg b/src/assets/icons/svg/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/src/assets/icons/svg/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/eye.svg b/src/assets/icons/svg/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/src/assets/icons/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/form.svg b/src/assets/icons/svg/form.svg new file mode 100644 index 0000000..dcbaa18 --- /dev/null +++ b/src/assets/icons/svg/form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/fullscreen.svg b/src/assets/icons/svg/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/src/assets/icons/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/github.svg b/src/assets/icons/svg/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/src/assets/icons/svg/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/guide.svg b/src/assets/icons/svg/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/src/assets/icons/svg/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/hongcha.svg b/src/assets/icons/svg/hongcha.svg new file mode 100644 index 0000000..e36eeeb --- /dev/null +++ b/src/assets/icons/svg/hongcha.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/icon.svg b/src/assets/icons/svg/icon.svg new file mode 100644 index 0000000..82be8ee --- /dev/null +++ b/src/assets/icons/svg/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/input.svg b/src/assets/icons/svg/input.svg new file mode 100644 index 0000000..ab91381 --- /dev/null +++ b/src/assets/icons/svg/input.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/international.svg b/src/assets/icons/svg/international.svg new file mode 100644 index 0000000..e9b56ee --- /dev/null +++ b/src/assets/icons/svg/international.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/job.svg b/src/assets/icons/svg/job.svg new file mode 100644 index 0000000..2a93a25 --- /dev/null +++ b/src/assets/icons/svg/job.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/language.svg b/src/assets/icons/svg/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/src/assets/icons/svg/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/link.svg b/src/assets/icons/svg/link.svg new file mode 100644 index 0000000..48197ba --- /dev/null +++ b/src/assets/icons/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/list.svg b/src/assets/icons/svg/list.svg new file mode 100644 index 0000000..20259ed --- /dev/null +++ b/src/assets/icons/svg/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/lock.svg b/src/assets/icons/svg/lock.svg new file mode 100644 index 0000000..74fee54 --- /dev/null +++ b/src/assets/icons/svg/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/log.svg b/src/assets/icons/svg/log.svg new file mode 100644 index 0000000..d879d33 --- /dev/null +++ b/src/assets/icons/svg/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/logininfor.svg b/src/assets/icons/svg/logininfor.svg new file mode 100644 index 0000000..267f844 --- /dev/null +++ b/src/assets/icons/svg/logininfor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/lvgou.svg b/src/assets/icons/svg/lvgou.svg new file mode 100644 index 0000000..e56f7ae --- /dev/null +++ b/src/assets/icons/svg/lvgou.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/message.svg b/src/assets/icons/svg/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/src/assets/icons/svg/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/money.svg b/src/assets/icons/svg/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/src/assets/icons/svg/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/monitor.svg b/src/assets/icons/svg/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/src/assets/icons/svg/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/moon.svg b/src/assets/icons/svg/moon.svg new file mode 100644 index 0000000..ec72d77 --- /dev/null +++ b/src/assets/icons/svg/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/more-up.svg b/src/assets/icons/svg/more-up.svg new file mode 100644 index 0000000..d30ac11 --- /dev/null +++ b/src/assets/icons/svg/more-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/nested.svg b/src/assets/icons/svg/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/src/assets/icons/svg/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/number.svg b/src/assets/icons/svg/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/src/assets/icons/svg/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/online.svg b/src/assets/icons/svg/online.svg new file mode 100644 index 0000000..330a202 --- /dev/null +++ b/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/password.svg b/src/assets/icons/svg/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/src/assets/icons/svg/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/pdf.svg b/src/assets/icons/svg/pdf.svg new file mode 100644 index 0000000..957aa0c --- /dev/null +++ b/src/assets/icons/svg/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/people.svg b/src/assets/icons/svg/people.svg new file mode 100644 index 0000000..2bd54ae --- /dev/null +++ b/src/assets/icons/svg/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/peoples.svg b/src/assets/icons/svg/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/src/assets/icons/svg/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/phone.svg b/src/assets/icons/svg/phone.svg new file mode 100644 index 0000000..ab8e8c4 --- /dev/null +++ b/src/assets/icons/svg/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/post.svg b/src/assets/icons/svg/post.svg new file mode 100644 index 0000000..2922c61 --- /dev/null +++ b/src/assets/icons/svg/post.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/qq.svg b/src/assets/icons/svg/qq.svg new file mode 100644 index 0000000..ee13d4e --- /dev/null +++ b/src/assets/icons/svg/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/question.svg b/src/assets/icons/svg/question.svg new file mode 100644 index 0000000..cf75bd4 --- /dev/null +++ b/src/assets/icons/svg/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/radio.svg b/src/assets/icons/svg/radio.svg new file mode 100644 index 0000000..0cde345 --- /dev/null +++ b/src/assets/icons/svg/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/rate.svg b/src/assets/icons/svg/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/src/assets/icons/svg/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/redis-list.svg b/src/assets/icons/svg/redis-list.svg new file mode 100644 index 0000000..98a15b2 --- /dev/null +++ b/src/assets/icons/svg/redis-list.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/redis.svg b/src/assets/icons/svg/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/src/assets/icons/svg/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/row.svg b/src/assets/icons/svg/row.svg new file mode 100644 index 0000000..0780992 --- /dev/null +++ b/src/assets/icons/svg/row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/search.svg b/src/assets/icons/svg/search.svg new file mode 100644 index 0000000..84233dd --- /dev/null +++ b/src/assets/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/select.svg b/src/assets/icons/svg/select.svg new file mode 100644 index 0000000..d628382 --- /dev/null +++ b/src/assets/icons/svg/select.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/server.svg b/src/assets/icons/svg/server.svg new file mode 100644 index 0000000..eb287e3 --- /dev/null +++ b/src/assets/icons/svg/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/shopping.svg b/src/assets/icons/svg/shopping.svg new file mode 100644 index 0000000..87513e7 --- /dev/null +++ b/src/assets/icons/svg/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/size.svg b/src/assets/icons/svg/size.svg new file mode 100644 index 0000000..1a409f5 --- /dev/null +++ b/src/assets/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/skill.svg b/src/assets/icons/svg/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/src/assets/icons/svg/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/slider.svg b/src/assets/icons/svg/slider.svg new file mode 100644 index 0000000..fbe4f39 --- /dev/null +++ b/src/assets/icons/svg/slider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/star.svg b/src/assets/icons/svg/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/src/assets/icons/svg/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/sunny.svg b/src/assets/icons/svg/sunny.svg new file mode 100644 index 0000000..cc628bf --- /dev/null +++ b/src/assets/icons/svg/sunny.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/swagger.svg b/src/assets/icons/svg/swagger.svg new file mode 100644 index 0000000..05d4e7b --- /dev/null +++ b/src/assets/icons/svg/swagger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/switch.svg b/src/assets/icons/svg/switch.svg new file mode 100644 index 0000000..0ba61e3 --- /dev/null +++ b/src/assets/icons/svg/switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/system.svg b/src/assets/icons/svg/system.svg new file mode 100644 index 0000000..5992593 --- /dev/null +++ b/src/assets/icons/svg/system.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tab.svg b/src/assets/icons/svg/tab.svg new file mode 100644 index 0000000..b4b48e4 --- /dev/null +++ b/src/assets/icons/svg/tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/table.svg b/src/assets/icons/svg/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/src/assets/icons/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/textarea.svg b/src/assets/icons/svg/textarea.svg new file mode 100644 index 0000000..2709f29 --- /dev/null +++ b/src/assets/icons/svg/textarea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/theme.svg b/src/assets/icons/svg/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/src/assets/icons/svg/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/time-range.svg b/src/assets/icons/svg/time-range.svg new file mode 100644 index 0000000..13c1202 --- /dev/null +++ b/src/assets/icons/svg/time-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/time.svg b/src/assets/icons/svg/time.svg new file mode 100644 index 0000000..b376e32 --- /dev/null +++ b/src/assets/icons/svg/time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tool.svg b/src/assets/icons/svg/tool.svg new file mode 100644 index 0000000..48e0e35 --- /dev/null +++ b/src/assets/icons/svg/tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tree-table.svg b/src/assets/icons/svg/tree-table.svg new file mode 100644 index 0000000..8aafdb8 --- /dev/null +++ b/src/assets/icons/svg/tree-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tree.svg b/src/assets/icons/svg/tree.svg new file mode 100644 index 0000000..dd4b7dd --- /dev/null +++ b/src/assets/icons/svg/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/upload.svg b/src/assets/icons/svg/upload.svg new file mode 100644 index 0000000..bae49c0 --- /dev/null +++ b/src/assets/icons/svg/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/user.svg b/src/assets/icons/svg/user.svg new file mode 100644 index 0000000..0ba0716 --- /dev/null +++ b/src/assets/icons/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/validCode.svg b/src/assets/icons/svg/validCode.svg new file mode 100644 index 0000000..cfb1021 --- /dev/null +++ b/src/assets/icons/svg/validCode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/wechat.svg b/src/assets/icons/svg/wechat.svg new file mode 100644 index 0000000..c586e55 --- /dev/null +++ b/src/assets/icons/svg/wechat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/zip.svg b/src/assets/icons/svg/zip.svg new file mode 100644 index 0000000..f806fc4 --- /dev/null +++ b/src/assets/icons/svg/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/blood_zero.png b/src/assets/images/blood_zero.png new file mode 100644 index 0000000..7475ca7 Binary files /dev/null and b/src/assets/images/blood_zero.png differ diff --git a/src/assets/images/dark.svg b/src/assets/images/dark.svg new file mode 100644 index 0000000..f646bd7 --- /dev/null +++ b/src/assets/images/dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/light.svg b/src/assets/images/light.svg new file mode 100644 index 0000000..ab7cc08 --- /dev/null +++ b/src/assets/images/light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/login-background.jpg b/src/assets/images/login-background.jpg new file mode 100644 index 0000000..b1e9e6f Binary files /dev/null and b/src/assets/images/login-background.jpg differ diff --git a/src/assets/images/login_left.png b/src/assets/images/login_left.png new file mode 100644 index 0000000..2344b3e Binary files /dev/null and b/src/assets/images/login_left.png differ diff --git a/src/assets/images/logo_new.png b/src/assets/images/logo_new.png new file mode 100644 index 0000000..74849a2 Binary files /dev/null and b/src/assets/images/logo_new.png differ diff --git a/src/assets/images/profile.jpg b/src/assets/images/profile.jpg new file mode 100644 index 0000000..bcc14d3 Binary files /dev/null and b/src/assets/images/profile.jpg differ diff --git a/src/assets/images/xjt.png b/src/assets/images/xjt.png new file mode 100644 index 0000000..c641ad5 Binary files /dev/null and b/src/assets/images/xjt.png differ diff --git a/src/assets/images/yjt.png b/src/assets/images/yjt.png new file mode 100644 index 0000000..0aec41f Binary files /dev/null and b/src/assets/images/yjt.png differ diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png new file mode 100644 index 0000000..ebcd63d Binary files /dev/null and b/src/assets/logo/logo.png differ diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss new file mode 100644 index 0000000..fee3ee1 --- /dev/null +++ b/src/assets/styles/btn.scss @@ -0,0 +1,99 @@ +@use './variables.module.scss' as *; + +@mixin colorBtn($color) { + background: $color; + + &:hover { + color: $color; + + &:before, + &:after { + background: $color; + } + } +} + +.blue-btn { + @include colorBtn($blue) +} + +.light-blue-btn { + @include colorBtn($light-blue) +} + +.red-btn { + @include colorBtn($red) +} + +.pink-btn { + @include colorBtn($pink) +} + +.green-btn { + @include colorBtn($green) +} + +.tiffany-btn { + @include colorBtn($tiffany) +} + +.yellow-btn { + @include colorBtn($yellow) +} + +.pan-btn { + font-size: 14px; + color: #fff; + padding: 14px 36px; + border-radius: 8px; + border: none; + outline: none; + transition: 600ms ease all; + position: relative; + display: inline-block; + + &:hover { + background: #fff; + + &:before, + &:after { + width: 100%; + transition: 600ms ease all; + } + } + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 2px; + width: 0; + transition: 400ms ease all; + } + + &::after { + right: inherit; + top: inherit; + left: 0; + bottom: 0; + } +} + +.custom-button { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #fff; + color: #fff; + -webkit-appearance: none; + text-align: center; + box-sizing: border-box; + outline: 0; + margin: 0; + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; +} diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss new file mode 100644 index 0000000..0f175f2 --- /dev/null +++ b/src/assets/styles/element-ui.scss @@ -0,0 +1,96 @@ +// cover some element-ui styles + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type="file"] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +.cell { + .el-tag { + margin-right: 0px; + } +} + +.small-padding { + .cell { + padding-left: 5px; + padding-right: 5px; + } +} + +.fixed-width { + .el-button--mini { + padding: 7px 10px; + width: 60px; + } +} + +.status-col { + .cell { + padding: 0 10px; + text-align: center; + + .el-tag { + margin-right: 0px; + } + } +} + +// to fixed https://github.com/ElemeFE/element/issues/2461 +.el-dialog { + transform: none; + left: 0; + position: relative; + margin: 0 auto; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block + } +} + +// fix date-picker ui bug in filter-item +.el-range-editor.el-input__inner { + display: inline-flex !important; +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +.el-menu--collapse + > div + > .el-submenu + > .el-submenu__title + .el-submenu__icon-arrow { + display: none; +} + +.el-dropdown .el-dropdown-link{ + color: var(--el-color-primary) !important; +} \ No newline at end of file diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100644 index 0000000..286fabb --- /dev/null +++ b/src/assets/styles/index.scss @@ -0,0 +1,716 @@ +@use './mixin.scss'; +@use './transition.scss'; +@use './element-ui.scss'; +@use './sidebar.scss'; +@use './btn.scss'; +@use './ruoyi.scss'; + +body { + height: 100%; + margin: 0; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 10px 10px 0 10px; + height: calc(100vh - 5.25rem); + background-color: #f9f9f9; + box-sizing: border-box; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 10px; +} + +.text-center { + text-align: center +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + + +.compact-form { + flex: 1; + position: relative; + height: 100%; + overflow-y: auto; + overflow-x: hidden; + + + .el-form-item { + margin-bottom: 0px; + } + + .el-form-item__label { + padding-bottom: 0px; + color: #08355E; + font-size: .75rem; + } + + // pointer-events: none; + + :deep(.el-radio__label) { + color: #08355E; + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} + + +.flex-between { + display: flex; + justify-content: space-between; +} + + +.time-line-box { + display: flex; + justify-content: space-around; + align-items: center; + margin-top: .35rem; + background-color: #f0f3f8; + padding: .3rem 2.5rem; + + + .time-line-item { + display: flex; + align-items: center; + justify-content: space-around; + width: 16.6%; + + .timeline-content { + display: flex; + flex-direction: column; + align-items: center; + + .index { + width: 1.875rem; + height: 1.875rem; + line-height: 1.875rem; + background: #E1E9F7; + border-radius: 50%; + text-align: center; + font-family: SourceHanSansCN, SourceHanSansCN; + font-weight: 500; + font-size: 1.125rem; + color: #4767A3; + } + + .active { + background-color: #4096ff; + box-shadow: 0 0 0 .25rem rgba(64, 150, 255, 0.2); + color: #FFF; + } + + + + .timeline-title { + font-family: SourceHanSansCN, SourceHanSansCN; + font-weight: 500; + font-size: 1rem; + color: #4767A3; + } + + .timeline-time { + font-family: SourceHanSansCN, SourceHanSansCN; + font-weight: 400; + font-size: .875rem; + color: #4767A3; + height: 1rem; + } + + } + + .no_active { + + .index, + .timeline-title { + color: #9b9999 !important; + } + } + + .timg { + width: 3.125rem; + } + } +} + +.next_box { + display: flex; + align-items: center; + justify-content: space-between; + + .jt_btn { + width: 1.5rem; + height: 2rem; + line-height: 2rem; + font-size: 1rem; + text-align: center; + background-color: #f6fafc; + color: #fff; + cursor: pointer; + } + + .next_btn { + margin-right: 0.2rem; + } +} + + +.search-form { + :deep(.el-form-item__label) { + padding: 0 !important; + } + + :deep(.el-form-item) { + margin-right: 0; + } + + .el-form-item--default { + margin-bottom: .3125rem !important; + } + + .el-form-item--small { + margin-bottom: .3125rem !important; + } +} + + +.page-box { + margin-top: 10px; + float: right; +} + + +.tabs_box { + .el-tabs__header { + height: 1.875rem !important; + margin-bottom: 0px; + font-size: .875rem; + + .el-tabs__item { + height: 1.875rem !important; + padding: 0 .625rem; + + &.is-active { + border-bottom: .125rem solid #00a5ff !important; + background-color: #00a5ff; + color: #fff; + } + } + } + + .el-tabs__nav-next, + .el-tabs__nav-prev { + line-height: 1.875rem; + color: #00a5ff; + } +} + +.dialog-footer { + margin-top: 20px; + text-align: right; +} + + +.el-tree-node__children { + padding-left: 15px !important; +} + + +/* 禁止文本选中 */ +.no-select { + user-select: none; + /* 兼容旧版浏览器 */ + -webkit-user-select: none; + /* Safari/Chrome */ + -moz-user-select: none; + /* Firefox */ + -ms-user-select: none; + /* IE/Edge */ + + cursor: pointer; +} + + +.el-form .el-form-item__label { + font-weight: 700; +} + +.el-form--inline .el-form-item { + margin-right: .9375rem; +} + +.el-form-item__label { + font-weight: 400; + font-size: .875rem; + color: #81878d; +} + +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; + padding: 0; + // border-radius: 20px; + + .el-dialog__body { + padding: 10px; + } +} + +.el-dialog.scrollbar .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 16px; +} + +.el-dialog__header { + background: linear-gradient(90deg, #0c1a97 0%, #1373d3 100%); + padding: 10px 20px; + position: relative; + font-family: SourceHanSansCN, SourceHanSansCN; + + &::before { + content: ''; + position: absolute; + left: 20px; + top: 50%; + transform: translateY(-50%); + width: 28px; + height: 28px; + background: url('@/assets/logo/logo.png') no-repeat center; + background-size: contain; + z-index: 1; + } + + // 标题文本样式 + .el-dialog__title { + color: #fff; + /* 标题文字颜色 */ + padding-left: 40px; + /* 给 Logo 留出空间 */ + font-size: 16px; + font-weight: 600; + } + + .el-dialog__headerbtn .el-dialog__close { + color: #fff; + /* 关闭按钮颜色 */ + font-size: 18px; + + &:hover { + color: #fff; + } + } +} + +.el-dialog__footer { + padding: 10px; +} + +.el-table { + padding: 0 !important; + + .el-table__header-wrapper, + .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f6fafc !important; + color: #3c526f; + height: 1.875rem !important; + font-size: 15px; + font-weight: 700; + } + } + + .el-table__body-wrapper { + .el-button [class*="el-icon-"]+span { + margin-left: 1px; + } + } + + .el-table__body td { + font-size: 15px; + + } + + + /* 表格最外层边框(可选) */ + .el-table--border { + border: 1px solid #cc3d61 !important; + } + + .el-scrollbar__thumb { + background-color: #d9ecf7 !important; + opacity: 1; + } + + .el-scrollbar__thumb:hover { + background-color: #d9ecf7 !important; + opacity: 1; + } + + .cell.el-tooltip { + min-width: 0; + } + + .el-table__body-wrapper .el-table-column--selection>.cell { + padding: 0 !important; + height: 1.8125rem; + line-height: 1.8125rem; + } + + // .el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell { + // background-color: #a4beef !important; + // color: #000 !important; + // } + + .cell.el-tooltip { + padding: 0 !important; + height: 1.8125rem; + line-height: 1.8125rem; + } + + .el-table-header-cell .cell { + padding: 0 !important; + } + + .el-table--default { + .cell { + padding: 0 !important; + } + } + + + .el-table__cell { + padding: 0 !important; + height: 1.8125rem; + line-height: 1.8125rem; + color: #3c526f; + font-size: 14px; + } + + .el-checkbox { + height: 100% !important; + } + + /* 修改选中行背景色 */ + .el-table__body tr.current-row>td { + background-color: #d9ecf7 !important; + } + + // /* 可选:修改悬停颜色 */ + // .el-table__body tr.hover-row>td { + // background-color: #a4beef !important; + // } + + .el-table__empty-block { + width: 100% !important; + } +} + + + +.el-input__wrapper, +.el-select__wrapper { + box-shadow: 0 0 0 1px #96B8D9 inset; +} + +.el-select__wrapper.is-disabled { + background-color: #dfdcdc !important; +} + +.el-select-dropdown__item.is-hovering { + background-color: #a4beef !important; +} + +.el-input.is-disabled .el-input__wrapper { + background-color: #dfdcdc !important; +} + +.el-input.is-disabled .el-input__inner { + -webkit-text-fill-color: #4e4c4c; +} + + +.el-date-editor.el-input__wrapper { + box-shadow: 0 0 0 1px #96B8D9 inset; + + .is-focus { + box-shadow: 0 0 0 1px #f6fafc inset !important; + } + + &:hover { + box-shadow: 0 0 0 1px #f6fafc inset !important; + } +} + + + + +.vxe-table { + border: 1px solid #96B8D9 !important; + border-top: none !important; + /* 蓝色外边框 */ + border-radius: .5rem; + /* 可选:添加边框圆角 */ + overflow: hidden; +} + +.mytable-style { + + .vxe-header--column { + word-break: break-word; + background-color: #f6fafc !important; + color: #3c526f; + height: 1.875rem !important; + line-height: 1.875rem !important; + padding: 0 !important; + font-size: 16px; + border-color: #f6fafc !important; + } + + .vxe-table--render-default.is--padding .vxe-body--column .vxe-cell { + padding: 0 !important; + } + + .vxe-table--render-default .vxe-header--column .vxe-cell { + padding: 0 !important; + } + + .vxe-table--render-default .vxe-body--column, + .vxe-footer--column, + .col--ellipsis { + height: 1.875rem !important; + line-height: 1.875rem !important; + } + + .el-checkbox { + height: 1.875rem !important; + } +} + + + + +// 高亮 +.vxe-table--render-default .vxe-body--row.row--current { + background-color: #a4beef !important; + color: #FFF !important; +} + +.vxe-table--render-default .vxe-body--row.row--current .vxe-cell { + background-color: #a4beef !important; + color: #FFF !important; +} + +// 滚动条 +.mytable-style ::-webkit-scrollbar { + width: .6rem; + height: .5rem; +} + +.mytable-style ::-webkit-scrollbar-thumb { + background-color: #817e7e; + cursor: pointer; +} + +.mytable-style ::-webkit-scrollbar-thumb:hover { + background-color: #817e7e; + cursor: pointer; +} + +.el-table--default .cell { + padding: 0 0 0 .3125rem !important; +} + +// 表格input宽度,表格select宽度 +.full-width-input .el-input__inner .el-select__inner { + width: 100%; + height: 100%; + + background: transparent !important; + border: none !important; +} + +.full-width-input { + .el-input__wrapper { + // box-shadow: 0 0 0 1px rgba(64, 158, 255, 0.2); + box-shadow: none; + background: transparent !important; + padding: 0 !important; + padding-left: .3125rem !important; + outline: none; + + &:hover { + box-shadow: none !important; + } + } + + .el-select__wrapper { + box-shadow: none; + background: transparent !important; + padding: 0 !important; + padding-left: .3125rem !important; + outline: none; + + &:hover { + box-shadow: none !important; + } + } +} \ No newline at end of file diff --git a/src/assets/styles/mixin.scss b/src/assets/styles/mixin.scss new file mode 100644 index 0000000..06fa061 --- /dev/null +++ b/src/assets/styles/mixin.scss @@ -0,0 +1,66 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } + + @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/src/assets/styles/ruoyi.scss b/src/assets/styles/ruoyi.scss new file mode 100644 index 0000000..5bcb881 --- /dev/null +++ b/src/assets/styles/ruoyi.scss @@ -0,0 +1,344 @@ +/** + * 通用css样式布局处理 + * Copyright (c) 2019 ruoyi + */ + +/** 基础通用 **/ +.pt5 { + padding-top: 5px; +} + +.pr5 { + padding-right: 5px; +} + +.pb5 { + padding-bottom: 5px; +} + +.mt5 { + margin-top: 5px; +} + +.mr5 { + margin-right: 5px; +} + +.mb5 { + margin-bottom: 5px; +} + +.mb8 { + margin-bottom: 8px; +} + +.ml5 { + margin-left: 5px; +} + +.mt10 { + margin-top: 10px; +} + +.mr10 { + margin-right: 10px; +} + +.mb10 { + margin-bottom: 10px; +} + +.ml10 { + margin-left: 10px; +} + +.mt20 { + margin-top: 20px; +} + +.mr20 { + margin-right: 20px; +} + +.mb20 { + margin-bottom: 20px; +} + +.ml20 { + margin-left: 20px; +} + +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-form--inline { + .el-form-item { + + .el-input, + .el-cascader, + .el-select, + .el-autocomplete { + width: 200px; + } + } +} + +.el-form .el-form-item__label { + font-weight: 700; +} + +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog.scrollbar .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + + .el-table__header-wrapper, + .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f8f8f9 !important; + color: #515a6e; + height: 40px !important; + font-size: 13px; + } + } + + .el-table__body-wrapper { + .el-button [class*="el-icon-"]+span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size: 15px; + color: #6379bb; + border-bottom: 1px solid #ddd; + margin: 8px 10px 25px 10px; + padding-bottom: 5px +} + +/** 表格布局 **/ +.pagination-container { + display: flex; + justify-content: flex-end; + margin-top: 20px; + background-color: transparent !important; +} + +/* 弹窗中的分页器 */ +.el-dialog .pagination-container { + position: static !important; + margin: 10px 0 0 0; + padding: 0 !important; + + .el-pagination { + position: static; + } +} + +/* 移动端适配 */ +@media (max-width: 768px) { + .pagination-container { + .el-pagination { + >.el-pagination__jump { + display: none !important; + } + + >.el-pagination__sizes { + display: none !important; + } + } + } +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid var(--el-border-color-light, #e5e6e7); + background: var(--el-bg-color, #FFFFFF) none; + border-radius: 4px; + width: 100%; +} + +.el-table .fixed-width .el-button--small { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/* horizontal el menu */ +.el-menu--horizontal .el-menu-item .svg-icon+span, +.el-menu--horizontal .el-sub-menu__title .svg-icon+span { + margin-left: 3px; +} + +.el-menu--horizontal .el-menu--popup { + min-width: 120px !important; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link { + cursor: pointer; + color: #00a5ff; + margin-left: 10px; +} + +.el-table .el-dropdown, +.el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content>.el-checkbox { + margin-right: 8px; +} + +.list-group-striped>.list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px !important; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px !important; +} + +.card-box { + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: absolute; + top: 50%; + transform: translate(50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost { + opacity: .8; + color: #fff !important; + background: #42b983 !important; +} + +/* 表格右侧工具栏样式 */ +.top-right-btn { + margin-left: auto; +} + +/* 分割面板样式 */ +.splitpanes.default-theme .splitpanes__pane { + background-color: var(--splitpanes-default-bg) !important; +} \ No newline at end of file diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss new file mode 100644 index 0000000..5ee4ac8 --- /dev/null +++ b/src/assets/styles/sidebar.scss @@ -0,0 +1,238 @@ +@use './variables.module.scss' as vars; + +#app { + + .main-container { + min-height: 100%; + transition: margin-left .28s; + margin-left: vars.$base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0!important; + } + + .sidebar-container { + transition: width 0.28s; + width: vars.$base-sidebar-width !important; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 10px !important; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, .menu-title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + .el-menu-item .el-menu-tooltip__trigger { + display: inline-block !important; + } + + // menu hover + .sub-menu-title-noDropdown, + .el-sub-menu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .is-active > .el-sub-menu__title { + color: vars.$base-menu-color-active !important; + } + + & .nest-menu .el-sub-menu>.el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: vars.$base-sidebar-width !important; + + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title, + & .theme-dark .el-sub-menu .el-menu-item { + background-color: vars.$base-sub-menu-background; + + &:hover { + background-color: vars.$base-sub-menu-hover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .sub-menu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + &>.el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + } + } + + .el-menu--collapse { + .el-sub-menu { + &>.el-sub-menu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + &>i { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: vars.$base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: vars.$base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-(vars.$base-sidebar-width), 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-sub-menu>.el-sub-menu__title, + .el-menu-item { + &:hover { + // you can use $sub-menuHover + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + // the scroll bar appears when the sub-menu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/src/assets/styles/transition.scss b/src/assets/styles/transition.scss new file mode 100644 index 0000000..1f74a7e --- /dev/null +++ b/src/assets/styles/transition.scss @@ -0,0 +1,80 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter-from, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter-from { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter-from, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} + +/* 黑暗模式下过渡效果 */ +::view-transition-new(root), ::view-transition-old(root) { + animation: none !important; + backface-visibility: hidden; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.dark::view-transition-old(root) { + z-index: 2147483646; + background: var(--bg-color-dark); +} + +.dark::view-transition-new(root) { + z-index: 1; + background: var(--bg-color); +} + +::view-transition-old(root) { + z-index: 1; + background: var(--bg-color); +} + +::view-transition-new(root) { + z-index: 2147483646; + background: var(--bg-color-dark); +} diff --git a/src/assets/styles/variables.module.scss b/src/assets/styles/variables.module.scss new file mode 100644 index 0000000..83706c6 --- /dev/null +++ b/src/assets/styles/variables.module.scss @@ -0,0 +1,280 @@ +// base color +$blue: #324157; +$light-blue: #333c46; +$red: #C03639; +$pink: #E65D6E; +$green: #30B08F; +$tiffany: #4AB7BD; +$yellow: #FEC171; +$panGreen: #30B08F; + +// 默认主题变量 +$menuText: #bfcbd9; +$menuActiveText: #00a5ff; +$menuBg: #304156; +$menuHover: #263445; + +// 浅色主题theme-light +$menuLightBg: #ffffff; +$menuLightHover: #f0f1f5; +$menuLightText: #303133; +$menuLightActiveText: #00a5ff; + +// 基础变量 +$base-sidebar-width: 200px; +$sideBarWidth: 200px; + +// 菜单暗色变量 +$base-menu-color: #bfcbd9; +$base-menu-color-active: #f4f4f5; +$base-menu-background: #304156; +$base-sub-menu-background: #1f2d3d; +$base-sub-menu-hover: #001528; + +// 组件变量 +$--color-primary: #00a5ff; +$--color-success: #67C23A; +$--color-warning: #E6A23C; +$--color-danger: #F56C6C; +$--color-info: #909399; + +:export { + menuText: $menuText; + menuActiveText: $menuActiveText; + menuBg: $menuBg; + menuHover: $menuHover; + menuLightBg: $menuLightBg; + menuLightHover: $menuLightHover; + menuLightText: $menuLightText; + menuLightActiveText: $menuLightActiveText; + sideBarWidth: $sideBarWidth; + // 导出基础颜色 + blue: $blue; + lightBlue: $light-blue; + red: $red; + pink: $pink; + green: $green; + tiffany: $tiffany; + yellow: $yellow; + panGreen: $panGreen; + // 导出组件颜色 + colorPrimary: $--color-primary; + colorSuccess: $--color-success; + colorWarning: $--color-warning; + colorDanger: $--color-danger; + colorInfo: $--color-info; +} + +// CSS变量定义 +:root { + /* 亮色模式变量 */ + --sidebar-bg: #{$menuBg}; + --sidebar-text: #{$menuText}; + --menu-hover: #{$menuHover}; + + --navbar-bg: #ffffff; + --navbar-text: #303133; + + /* splitpanes default-theme 变量 */ + --splitpanes-default-bg: #ffffff; + +} + +// 暗黑模式变量 +html.dark { + /* 默认通用 */ + --el-bg-color: #141414; + --el-bg-color-overlay: #1d1e1f; + --el-text-color-primary: #ffffff; + --el-text-color-regular: #d0d0d0; + --el-border-color: #434343; + --el-border-color-light: #434343; + + /* primary */ + --primary-bg: #18212b; + + /* 侧边栏 */ + --sidebar-bg: #141414; + --sidebar-text: #ffffff; + --menu-hover: #2d2d2d; + --menu-active-text: #{$menuActiveText}; + + /* 顶部导航栏 */ + --navbar-bg: #141414; + --navbar-text: #ffffff; + --navbar-hover: #141414; + + /* 标签栏 */ + --tags-bg: #141414; + --tags-item-bg: #1d1e1f; + --tags-item-border: #303030; + --tags-item-text: #d0d0d0; + --tags-item-hover: #2d2d2d; + --tags-close-hover: #64666a; + + /* splitpanes 组件暗黑模式变量 */ + --splitpanes-bg: #141414; + --splitpanes-border: #303030; + --splitpanes-splitter-bg: #1d1e1f; + --splitpanes-splitter-hover-bg: #2d2d2d; + + /* blockquote 暗黑模式变量 */ + --blockquote-bg: #1d1e1f; + --blockquote-border: #303030; + --blockquote-text: #d0d0d0; + + /* Cron 时间表达式 模式变量 */ + --cron-border: #303030; + + /* splitpanes default-theme 暗黑模式变量 */ + --splitpanes-default-bg: #141414; + + /* 侧边栏菜单覆盖 */ + .sidebar-container { + + .el-menu-item:not(.is-active), + .menu-title { + color: var(--el-text-color-regular); + } + + & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title, + & .theme-dark .el-sub-menu .el-menu-item { + background-color: var(--el-bg-color) !important; + } + } + + .topmenu-container { + + .el-menu-item, + .el-sub-menu .el-sub-menu__title { + color: var(--el-text-color-regular) !important; + } + } + + .topbar-menu.el-menu--horizontal>.el-sub-menu .el-sub-menu__title { + color: var(--el-text-color-regular) !important; + } + + /* 顶部栏栏菜单覆盖 */ + .el-menu--horizontal { + + .el-menu-item, + .el-sub-menu { + &:not(.is-disabled) { + + &:hover, + &:focus { + background-color: var(--navbar-hover) !important; + + .el-sub-menu__title { + background-color: var(--navbar-hover) !important; + } + } + } + } + } + + /* 分割窗格覆盖 */ + .splitpanes { + background-color: var(--splitpanes-bg); + + .splitpanes__pane { + background-color: var(--splitpanes-bg); + border-color: var(--splitpanes-border); + } + + .splitpanes__splitter { + background-color: var(--splitpanes-splitter-bg); + border-color: var(--splitpanes-border); + + &:hover { + background-color: var(--splitpanes-splitter-hover-bg); + } + + &:before, + &:after { + background-color: var(--splitpanes-border); + } + } + } + + /* 按钮样式覆盖 */ + .el-button--primary.is-plain { + background-color: var(--primary-bg); + border: 1px solid var(--el-color-primary-light-2); + color: var(--el-color-primary-light-2); + + &:hover { + background-color: var(--el-button-hover-bg-color); + border-color: var(--el-button-hover-border-color); + color: var(--el-button-hover-text-color); + } + + &.is-disabled { + background-color: var(--link-active-bg-color); + border-color: var(--el-color-primary-light-3); + color: var(--el-color-primary-light-3); + opacity: 0.5; + } + } + + /* primary tag 样式覆盖 */ + .el-tag--primary { + background-color: var(--primary-bg); + border: 1px solid var(--el-border-color-light); + color: var(--el-color-primary); + } + + /* 表格样式覆盖 */ + .el-table { + --el-table-header-bg-color: var(--el-bg-color-overlay) !important; + --el-table-header-text-color: var(--el-text-color-regular) !important; + --el-table-border-color: var(--el-border-color-light) !important; + --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important; + + .el-table__header-wrapper, + .el-table__fixed-header-wrapper { + th { + background-color: var(--el-bg-color-overlay, #f8f8f9) !important; + color: var(--el-text-color-regular, #515a6e); + } + } + } + + /* 树组件高亮样式覆盖 */ + .el-tree { + .el-tree-node.is-current>.el-tree-node__content { + background-color: var(--el-bg-color-overlay) !important; + color: var(--el-color-primary); + } + + .el-tree-node__content:hover { + background-color: var(--el-bg-color-overlay); + } + } + + /* 下拉菜单样式覆盖 */ + .el-dropdown-menu__item:not(.is-disabled):focus, + .el-dropdown-menu__item:not(.is-disabled):hover { + background-color: var(--navbar-hover) !important; + } + + /* blockquote样式覆盖 */ + blockquote { + background-color: var(--blockquote-bg) !important; + border-left-color: var(--blockquote-border) !important; + color: var(--blockquote-text) !important; + } + + /* 时间表达式标题样式覆盖 */ + .popup-result .title { + background: var(--cron-border); + } + + /* 底部版权样式覆盖 */ + .copyright { + background-color: var(--el-bg-color) !important; + color: var(--el-text-color-regular) !important; + border-top: 1px solid var(--el-bg-color) !important; + } +} \ No newline at end of file diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..19bf96c --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,97 @@ + + + + + \ No newline at end of file diff --git a/src/components/Crontab/day.vue b/src/components/Crontab/day.vue new file mode 100644 index 0000000..07ffe90 --- /dev/null +++ b/src/components/Crontab/day.vue @@ -0,0 +1,174 @@ + + + + \ No newline at end of file diff --git a/src/components/Crontab/hour.vue b/src/components/Crontab/hour.vue new file mode 100644 index 0000000..2eacf44 --- /dev/null +++ b/src/components/Crontab/hour.vue @@ -0,0 +1,133 @@ + + + + + \ No newline at end of file diff --git a/src/components/Crontab/index.vue b/src/components/Crontab/index.vue new file mode 100644 index 0000000..16a05f6 --- /dev/null +++ b/src/components/Crontab/index.vue @@ -0,0 +1,314 @@ + + + + + \ No newline at end of file diff --git a/src/components/Crontab/min.vue b/src/components/Crontab/min.vue new file mode 100644 index 0000000..dc61554 --- /dev/null +++ b/src/components/Crontab/min.vue @@ -0,0 +1,126 @@ + + + + \ No newline at end of file diff --git a/src/components/Crontab/month.vue b/src/components/Crontab/month.vue new file mode 100644 index 0000000..288226d --- /dev/null +++ b/src/components/Crontab/month.vue @@ -0,0 +1,141 @@ + + + + + \ No newline at end of file diff --git a/src/components/Crontab/result.vue b/src/components/Crontab/result.vue new file mode 100644 index 0000000..ee32e0d --- /dev/null +++ b/src/components/Crontab/result.vue @@ -0,0 +1,543 @@ + + + \ No newline at end of file diff --git a/src/components/Crontab/second.vue b/src/components/Crontab/second.vue new file mode 100644 index 0000000..69e3fc2 --- /dev/null +++ b/src/components/Crontab/second.vue @@ -0,0 +1,129 @@ + + + + + \ No newline at end of file diff --git a/src/components/Crontab/week.vue b/src/components/Crontab/week.vue new file mode 100644 index 0000000..6b6e3b4 --- /dev/null +++ b/src/components/Crontab/week.vue @@ -0,0 +1,197 @@ + + + + + \ No newline at end of file diff --git a/src/components/Crontab/year.vue b/src/components/Crontab/year.vue new file mode 100644 index 0000000..cbc5852 --- /dev/null +++ b/src/components/Crontab/year.vue @@ -0,0 +1,143 @@ + + + + + \ No newline at end of file diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue new file mode 100644 index 0000000..b71e68f --- /dev/null +++ b/src/components/DictTag/index.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue new file mode 100644 index 0000000..e154056 --- /dev/null +++ b/src/components/Editor/index.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue new file mode 100644 index 0000000..8ff9d10 --- /dev/null +++ b/src/components/FileUpload/index.vue @@ -0,0 +1,265 @@ + + + + diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue new file mode 100644 index 0000000..15a58a6 --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/components/HeaderSearch/index.vue b/src/components/HeaderSearch/index.vue new file mode 100644 index 0000000..f578be7 --- /dev/null +++ b/src/components/HeaderSearch/index.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue new file mode 100644 index 0000000..a9d230e --- /dev/null +++ b/src/components/IconSelect/index.vue @@ -0,0 +1,111 @@ + + + + + \ No newline at end of file diff --git a/src/components/IconSelect/requireIcons.ts b/src/components/IconSelect/requireIcons.ts new file mode 100644 index 0000000..de6b46a --- /dev/null +++ b/src/components/IconSelect/requireIcons.ts @@ -0,0 +1,8 @@ +const icons: string[] = [] +const modules = import.meta.glob('./../../assets/icons/svg/*.svg') +for (const path in modules) { + const p = path.split('assets/icons/svg/')[1].split('.svg')[0] + icons.push(p) +} + +export default icons diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue new file mode 100644 index 0000000..e1bdd85 --- /dev/null +++ b/src/components/ImagePreview/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue new file mode 100644 index 0000000..f0adf77 --- /dev/null +++ b/src/components/ImageUpload/index.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..64cbca7 --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,105 @@ + + + + + \ No newline at end of file diff --git a/src/components/ParentView/index.vue b/src/components/ParentView/index.vue new file mode 100644 index 0000000..7bf6148 --- /dev/null +++ b/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/RightToolbar/index.vue b/src/components/RightToolbar/index.vue new file mode 100644 index 0000000..11f0512 --- /dev/null +++ b/src/components/RightToolbar/index.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/components/RuoYi/Doc/index.vue b/src/components/RuoYi/Doc/index.vue new file mode 100644 index 0000000..c92195f --- /dev/null +++ b/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/src/components/RuoYi/Git/index.vue b/src/components/RuoYi/Git/index.vue new file mode 100644 index 0000000..c76acb7 --- /dev/null +++ b/src/components/RuoYi/Git/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue new file mode 100644 index 0000000..5508a1f --- /dev/null +++ b/src/components/Screenfull/index.vue @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/src/components/SelectTable/YQSelectTable/index.vue b/src/components/SelectTable/YQSelectTable/index.vue new file mode 100644 index 0000000..6ba03ed --- /dev/null +++ b/src/components/SelectTable/YQSelectTable/index.vue @@ -0,0 +1,77 @@ + + + + \ No newline at end of file diff --git a/src/components/SelectTable/index.vue b/src/components/SelectTable/index.vue new file mode 100644 index 0000000..d0c1cf7 --- /dev/null +++ b/src/components/SelectTable/index.vue @@ -0,0 +1,402 @@ + + + + + + \ No newline at end of file diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..707c1b3 --- /dev/null +++ b/src/components/SizeSelect/index.vue @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..e6ff2a3 --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/SvgIcon/svgicon.ts b/src/components/SvgIcon/svgicon.ts new file mode 100644 index 0000000..086c470 --- /dev/null +++ b/src/components/SvgIcon/svgicon.ts @@ -0,0 +1,11 @@ +import { App } from 'vue' +import * as components from '@element-plus/icons-vue' + +export default { + install: (app: App) => { + for (const key in components) { + const componentConfig = (components as any)[key] + app.component(componentConfig.name, componentConfig) + } + } +} diff --git a/src/components/TopNav/index.vue b/src/components/TopNav/index.vue new file mode 100644 index 0000000..be5363d --- /dev/null +++ b/src/components/TopNav/index.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/src/components/elTable/index.vue b/src/components/elTable/index.vue new file mode 100644 index 0000000..9e80611 --- /dev/null +++ b/src/components/elTable/index.vue @@ -0,0 +1,484 @@ + + + + \ No newline at end of file diff --git a/src/components/iFrame/index.vue b/src/components/iFrame/index.vue new file mode 100644 index 0000000..b0febe9 --- /dev/null +++ b/src/components/iFrame/index.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue new file mode 100644 index 0000000..53c1e74 --- /dev/null +++ b/src/layout/components/Navbar.vue @@ -0,0 +1,291 @@ + + + + + diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..5d8f1fe --- /dev/null +++ b/src/layout/components/Settings/index.vue @@ -0,0 +1,304 @@ + + + + + \ No newline at end of file diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..c659267 --- /dev/null +++ b/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..586bc8c --- /dev/null +++ b/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,102 @@ + + + + + \ No newline at end of file diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..fa1f69a --- /dev/null +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..0317753 --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..a26cdeb --- /dev/null +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..8825216 --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,408 @@ + + + + + + + \ No newline at end of file diff --git a/src/layout/components/TopBar/index.vue b/src/layout/components/TopBar/index.vue new file mode 100644 index 0000000..1ffb454 --- /dev/null +++ b/src/layout/components/TopBar/index.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/layout/components/index.ts b/src/layout/components/index.ts new file mode 100644 index 0000000..464f38e --- /dev/null +++ b/src/layout/components/index.ts @@ -0,0 +1,4 @@ +export { default as AppMain } from './AppMain.vue' +export { default as Navbar } from './Navbar.vue' +export { default as Settings } from './Settings/index.vue' +export { default as TagsView } from './TagsView/index.vue' diff --git a/src/layout/components/tabViews/index.vue b/src/layout/components/tabViews/index.vue new file mode 100644 index 0000000..0b8bd7d --- /dev/null +++ b/src/layout/components/tabViews/index.vue @@ -0,0 +1,189 @@ + + + + + \ No newline at end of file diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..fc98324 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,116 @@ + + + + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..ec787e4 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,105 @@ +import { createApp } from 'vue' + +import Cookies from 'js-cookie' + +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import locale from 'element-plus/es/locale/lang/zh-cn' +import VxeUI from 'vxe-pc-ui' +import 'vxe-pc-ui/lib/style.css' +import VXETable from 'vxe-table' +import 'vxe-table/lib/style.css' +import '@/assets/styles/index.scss' // global css +import '@/utils/polyfills'; +import { + setupRem +} from './utils/rem'; + + +setupRem(); // 初始化rem适配 + + +import App from './App.vue' +import store from './store' +import router from './router' +import directive from './directive' // directive + +// 注册指令 +import plugins from './plugins' // plugins +import { download } from '@/utils/request' + +// svg图标 +import 'virtual:svg-icons-register' +import SvgIcon from '@/components/SvgIcon/index.vue' +import elementIcons from '@/components/SvgIcon/svgicon' + +import './permission' // permission control + +import { useDict } from '@/utils/dict' +import { getConfigKey } from "@/api/system/config" +import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' + +// 分页组件 +import Pagination from '@/components/Pagination/index.vue' +// 自定义表格工具组件 +import RightToolbar from '@/components/RightToolbar/index.vue' +// 富文本组件 +import Editor from "@/components/Editor/index.vue" +// 文件上传组件 +import FileUpload from "@/components/FileUpload/index.vue" +// 图片上传组件 +import ImageUpload from "@/components/ImageUpload/index.vue" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview/index.vue" +// 字典标签组件 +import DictTag from '@/components/DictTag/index.vue' +// elTable表格组件 +import CustomTable from '@/components/elTable/index.vue' +// vxeTable 组件 +import CustomVxeTable from '@/components/vxeTable/index.vue' +// 下拉框表格组件 +import SelectTable from '@/components/SelectTable/index.vue' +const app = createApp(App) + +// 全局方法挂载 +app.config.globalProperties.useDict = useDict +app.config.globalProperties.download = download +app.config.globalProperties.parseTime = parseTime +app.config.globalProperties.resetForm = resetForm +app.config.globalProperties.handleTree = handleTree +app.config.globalProperties.addDateRange = addDateRange +app.config.globalProperties.getConfigKey = getConfigKey +app.config.globalProperties.selectDictLabel = selectDictLabel +app.config.globalProperties.selectDictLabels = selectDictLabels + +// 全局组件挂载 +app.component('DictTag', DictTag) +app.component('Pagination', Pagination) +app.component('FileUpload', FileUpload) +app.component('ImageUpload', ImageUpload) +app.component('ImagePreview', ImagePreview) +app.component('RightToolbar', RightToolbar) +app.component('Editor', Editor) +app.component('CustomTable', CustomTable) +app.component('CustomVxeTable', CustomVxeTable) +app.component('SelectTable', SelectTable) + +app.use(router) +app.use(store) +app.use(plugins) +app.use(elementIcons) +app.component('svg-icon', SvgIcon) +app.use(VxeUI) +app.use(VXETable) +directive(app) + +// 使用element-plus 并且设置全局的大小 +// @ts-ignore +app.use(ElementPlus, { + locale: locale, + // 支持 large、default、small + size: Cookies.get('size') || 'default' +}) + +app.mount('#app') diff --git a/src/permission.ts b/src/permission.ts new file mode 100644 index 0000000..0c19318 --- /dev/null +++ b/src/permission.ts @@ -0,0 +1,69 @@ +import router from './router' +import { ElMessage } from 'element-plus' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import { getToken } from '@/utils/auth' +import { isHttp, isPathMatch } from '@/utils/validate' +import { isRelogin } from '@/utils/request' +import useUserStore from '@/store/modules/user' +import useSettingsStore from '@/store/modules/settings' +import usePermissionStore from '@/store/modules/permission' + +NProgress.configure({ showSpinner: false }) + +const whiteList = ['/login', '/register'] + +const isWhiteList = (path: string): boolean => { + return whiteList.some((pattern: string) => isPathMatch(pattern, path)) +} + +router.beforeEach((to, from, next) => { + NProgress.start() + if (getToken()) { + to.meta.title && useSettingsStore().setTitle(to.meta.title as string) + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else if (isWhiteList(to.path)) { + next() + } else { + if (useUserStore().roles.length === 0) { + isRelogin.show = true + // 判断当前用户是否已拉取完user_info信息 + useUserStore().getInfo().then(() => { + isRelogin.show = false + usePermissionStore().generateRoutes().then((accessRoutes: any[]) => { + // 根据roles权限生成可访问的路由表 + accessRoutes.forEach((route: any) => { + if (!isHttp(route.path)) { + router.addRoute(route) // 动态添加可访问路由表 + } + }) + next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + }) + }).catch((err: any) => { + useUserStore().logOut().then(() => { + ElMessage.error(err as string) + next({ path: '/' }) + }) + }) + } else { + next() + } + } + } else { + // 没有token + if (isWhiteList(to.path)) { + // 在免登录白名单,直接进入 + next() + } else { + next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/src/plugins/auth.ts b/src/plugins/auth.ts new file mode 100644 index 0000000..ae5f2c7 --- /dev/null +++ b/src/plugins/auth.ts @@ -0,0 +1,60 @@ +import useUserStore from '@/store/modules/user' + +function authPermission(permission: string): boolean { + const all_permission = "*:*:*" + const permissions = useUserStore().permissions + if (permission && permission.length > 0) { + return permissions.some((v: string) => { + return all_permission === v || v === permission + }) + } else { + return false + } +} + +function authRole(role: string): boolean { + const super_admin = "admin" + const roles = useUserStore().roles + if (role && role.length > 0) { + return roles.some((v: string) => { + return super_admin === v || v === role + }) + } else { + return false + } +} + +export default { + // 验证用户是否具备某权限 + hasPermi(permission: string): boolean { + return authPermission(permission) + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions: string[]): boolean { + return permissions.some(item => { + return authPermission(item) + }) + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions: string[]): boolean { + return permissions.every(item => { + return authPermission(item) + }) + }, + // 验证用户是否具备某角色 + hasRole(role: string): boolean { + return authRole(role) + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles: string[]): boolean { + return roles.some(item => { + return authRole(item) + }) + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles: string[]): boolean { + return roles.every(item => { + return authRole(item) + }) + } +} diff --git a/src/plugins/cache.ts b/src/plugins/cache.ts new file mode 100644 index 0000000..686dab8 --- /dev/null +++ b/src/plugins/cache.ts @@ -0,0 +1,88 @@ +interface CacheInterface { + set(key: string, value: string): void + get(key: string): string | null + setJSON(key: string, jsonValue: any): void + getJSON(key: string): any + remove(key: string): void +} + +const sessionCache: CacheInterface = { + set (key: string, value: string) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + sessionStorage.setItem(key, value) + } + }, + get (key: string): string | null { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + return sessionStorage.getItem(key) + }, + setJSON (key: string, jsonValue: any) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key: string): any { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + return null + }, + remove (key: string) { + sessionStorage.removeItem(key) + } +} + +const localCache: CacheInterface = { + set (key: string, value: string) { + if (!localStorage) { + return + } + if (key != null && value != null) { + localStorage.setItem(key, value) + } + }, + get (key: string): string | null { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + return localStorage.getItem(key) + }, + setJSON (key: string, jsonValue: any) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key: string): any { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + return null + }, + remove (key: string) { + localStorage.removeItem(key) + } +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache +} diff --git a/src/plugins/download.ts b/src/plugins/download.ts new file mode 100644 index 0000000..37ccca8 --- /dev/null +++ b/src/plugins/download.ts @@ -0,0 +1,93 @@ +import axios from 'axios' +import { ElLoading, ElMessage } from 'element-plus' +import { saveAs } from 'file-saver' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from '@/utils/ruoyi' + +const baseURL = import.meta.env.VITE_APP_BASE_API +let downloadLoadingInstance: ReturnType + +export default { + name(name: string, isDelete = true) { + const url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() }, + onDownloadProgress: (progressEvent) => { + // console.log(progressEvent) + //progressEvent.loaded 下载文件的当前大小 + //progressEvent.total 下载文件的总大小 如果后端没有返回 请让他加上 + let progressPercent = Math.round((progressEvent.loaded / progressEvent.total) * 100); + let loading = ElLoading.service({ + lock: true, + text: `下载中,请稍候${progressPercent}%`, + background: 'rgba(0, 0, 0, 0.7)', + }) + loading.setText(`下载中,请稍候${progressPercent}%`) + if (progressPercent == 100) { + loading.close() + } + } + }).then((res: any) => { + const isBlob = blobValidate(res.data) + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data) + } + }) + }, + resource(resource: string) { + const url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource) + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res: any) => { + const isBlob = blobValidate(res.data) + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data) + } + }) + }, + zip(url: string, name: string) { + const downloadUrl = baseURL + url + downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) + axios({ + method: 'get', + url: downloadUrl, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res: any) => { + const isBlob = blobValidate(res.data) + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data) + } + downloadLoadingInstance.close() + }).catch((r: any) => { + console.error(r) + ElMessage.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close() + }) + }, + saveAs(text: Blob, name: string, opts?: any) { + saveAs(text, name, opts) + }, + async printErrMsg(data: any) { + const resText = await data.text() + const rspObj = JSON.parse(resText) + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + ElMessage.error(errMsg) + } +} diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..aace780 --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,19 @@ +import { App } from 'vue' +import tab from './tab' +import auth from './auth' +import cache from './cache' +import modal from './modal' +import download from './download' + +export default function installPlugins(app: App){ + // 页签操作 + app.config.globalProperties.$tab = tab + // 认证对象 + app.config.globalProperties.$auth = auth + // 缓存对象 + app.config.globalProperties.$cache = cache + // 模态框对象 + app.config.globalProperties.$modal = modal + // 下载文件 + app.config.globalProperties.$download = download +} diff --git a/src/plugins/modal.ts b/src/plugins/modal.ts new file mode 100644 index 0000000..12ffa37 --- /dev/null +++ b/src/plugins/modal.ts @@ -0,0 +1,82 @@ +import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus' + +let loadingInstance: ReturnType + +export default { + // 消息提示 + msg(content: string) { + ElMessage.info(content) + }, + // 错误消息 + msgError(content: string) { + ElMessage.error(content) + }, + // 成功消息 + msgSuccess(content: string) { + ElMessage.success(content) + }, + // 警告消息 + msgWarning(content: string) { + ElMessage.warning(content) + }, + // 弹出提示 + alert(content: string) { + ElMessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content: string) { + ElMessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content: string) { + ElMessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content: string) { + ElMessageBox.alert(content, "系统提示", { 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) { + return ElMessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content: string) { + return ElMessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content: string) { + loadingInstance = ElLoading.service({ + lock: true, + text: content, + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close() + } +} diff --git a/src/plugins/tab.ts b/src/plugins/tab.ts new file mode 100644 index 0000000..0475f98 --- /dev/null +++ b/src/plugins/tab.ts @@ -0,0 +1,71 @@ +import useTagsViewStore from '@/store/modules/tagsView' +import router from '@/router' + +export default { + // 刷新当前tab页签 + refreshPage(obj?: any) { + const { path, query, matched } = router.currentRoute.value + if (obj === undefined) { + matched.forEach((m: any) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query } + } + } + }) + } + return useTagsViewStore().delCachedView(obj).then(() => { + const { path, query } = obj + router.replace({ + path: '/redirect' + path, + query: query + }) + }) + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj?: any) { + useTagsViewStore().delView(router.currentRoute.value) + if (obj !== undefined) { + return router.push(obj) + } + }, + // 关闭指定tab页签 + closePage(obj?: any) { + if (obj === undefined) { + return useTagsViewStore().delView(router.currentRoute.value).then(({ visitedViews }: { visitedViews: any[] }) => { + const latestView = visitedViews.slice(-1)[0] + if (latestView) { + return router.push(latestView.fullPath) + } + return router.push('/') + }) + } + return useTagsViewStore().delView(obj) + }, + // 关闭所有tab页签 + closeAllPage() { + return useTagsViewStore().delAllViews() + }, + // 关闭左侧tab页签 + closeLeftPage(obj?: any) { + return useTagsViewStore().delLeftTags(obj || router.currentRoute.value) + }, + // 关闭右侧tab页签 + closeRightPage(obj?: any) { + return useTagsViewStore().delRightTags(obj || router.currentRoute.value) + }, + // 关闭其他tab页签 + closeOtherPage(obj?: any) { + return useTagsViewStore().delOthersViews(obj || router.currentRoute.value) + }, + // 打开tab页签 + openPage(title: string, url: string, params?: any) { + const obj = { path: url, meta: { title: title } } as any + useTagsViewStore().addView(obj) + return router.push({ path: url, query: params }) + }, + // 修改tab页签 + updatePage(obj: any) { + return useTagsViewStore().updateVisitedView(obj) + } +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..59f733d --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,175 @@ +import { createWebHistory, createRouter } from 'vue-router' +/* Layout */ +import Layout from '@/layout/index.vue' + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login.vue'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register.vue'), + hidden: true + }, + + { + path: "/:pathMatch(.*)*", + component: () => import('@/views/error/404.vue'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401.vue'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: '/index', + children: [ + { + path: '/index', + component: () => import('@/views/index.vue'), + name: 'Index', + meta: { title: '首页', icon: 'dashboard', affix: true } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile/:activeTab?', + component: () => import('@/views/system/user/profile/index.vue'), + name: 'Profile', + meta: { title: '个人中心', icon: 'user' } + } + ] + } +] + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole.vue'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser.vue'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data.vue'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log.vue'), + name: 'JobLog', + meta: { title: '调度日志', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable.vue'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + } + ] + } +] + +const router = createRouter({ + history: createWebHistory(), + routes: constantRoutes, + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition + } + return { top: 0 } + }, +}) + +export default router diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..55fdc65 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,57 @@ +export default { + /** + * 网页标题 + */ + title: import.meta.env.VITE_APP_TITLE, + + /** + * 侧边栏主题 深色主题theme-dark,浅色主题theme-light + */ + sideTheme: 'theme-dark', + + /** + * 是否系统布局配置 + */ + showSettings: true, + + /** + * 菜单导航模式 1、纯左侧 2、混合(左侧+顶部) 3、纯顶部 + */ + navType: 1, + + /** + * 是否显示 tagsView + */ + tagsView: true, + + /** + * 显示页签图标 + */ + tagsIcon: false, + + /** + * 是否固定头部 + */ + fixedHeader: true, + + /** + * 是否显示logo + */ + sidebarLogo: true, + + /** + * 是否显示动态标题 + */ + dynamicTitle: false, + + /** + * 是否显示底部版权 + */ + footerVisible: false, + + /** + * 底部版权文本内容 + */ + footerContent: 'Copyright © 2018-2026 RuoYi. All Rights Reserved.' +} + diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..db1107e --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,5 @@ +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' +const store = createPinia() +store.use(piniaPluginPersistedstate) // 注册持久化插件 + +export default store diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..1de6d2d --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,59 @@ +import Cookies from 'js-cookie' +import { defineStore } from 'pinia' + +interface SidebarState { + opened: boolean + withoutAnimation: boolean + hide: boolean +} + +interface AppState { + sidebar: SidebarState + device: string + size: string +} + +const useAppStore = defineStore( + 'app', + { + state: (): AppState => ({ + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus')! : true, + withoutAnimation: false, + hide: false + }, + device: 'desktop', + size: Cookies.get('size') || 'default' + }), + actions: { + toggleSideBar(withoutAnimation?: boolean) { + if (this.sidebar.hide) { + return false + } + this.sidebar.opened = !this.sidebar.opened + this.sidebar.withoutAnimation = withoutAnimation || false + if (this.sidebar.opened) { + Cookies.set('sidebarStatus', '1') + } else { + Cookies.set('sidebarStatus', '0') + } + }, + closeSideBar({ withoutAnimation }: { withoutAnimation?: boolean }) { + Cookies.set('sidebarStatus', '0') + this.sidebar.opened = false + this.sidebar.withoutAnimation = withoutAnimation || false + }, + toggleDevice(device: string) { + this.device = device + }, + setSize(size: string) { + this.size = size + Cookies.set('size', size) + }, + toggleSideBarHide(status: boolean) { + this.sidebar.hide = status + } + } + }) + +export default useAppStore diff --git a/src/store/modules/commonStore.ts b/src/store/modules/commonStore.ts new file mode 100644 index 0000000..d738c12 --- /dev/null +++ b/src/store/modules/commonStore.ts @@ -0,0 +1,28 @@ +//公共状态管理 +import { defineStore } from 'pinia' +// 定义配置对象接口 +interface DefaultConfig { + defaultinstr?: string //默认仪器 + lisgroupid?: string //默认分组id + [key: string]: any // 允许其他属性 +} +export const useCommonStore = defineStore('commonStore', { + state: () => ({ + + defaultConfig: {} as DefaultConfig, // 存储默认配置 + }), + actions: { + setDefaultConfig(config: DefaultConfig) { + this.defaultConfig = config + }, + }, + // 持久化配置 + persist: { + // 指定要持久化的状态字段 + pick: ['fingerprint', 'defaultConfig'], + // 可选配置:自定义存储键名(默认是store的id) + // key: 'common_store', + // 可选配置:指定存储位置(默认是localStorage 也可以用sessionStorage) + storage: localStorage, + } +}) \ No newline at end of file diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts new file mode 100644 index 0000000..46ef506 --- /dev/null +++ b/src/store/modules/dict.ts @@ -0,0 +1,67 @@ +interface DictItem { + key: string + value: any[] +} + +interface DictState { + dict: DictItem[] +} + +const useDictStore = defineStore( + 'dict', + { + state: (): DictState => ({ + dict: [] + }), + actions: { + // 获取字典 + getDict(_key: string): any[] | null { + if (_key == null && _key == "") { + return null + } + try { + for (let i = 0; i < this.dict.length; i++) { + if (this.dict[i].key == _key) { + return this.dict[i].value + } + } + } catch (e) { + return null + } + return null + }, + // 设置字典 + setDict(_key: string, value: any[]) { + if (_key !== null && _key !== "") { + this.dict.push({ + key: _key, + value: value + }) + } + }, + // 删除字典 + removeDict(_key: string): boolean { + let bln = false + try { + for (let i = 0; i < this.dict.length; i++) { + if (this.dict[i].key == _key) { + this.dict.splice(i, 1) + return true + } + } + } catch (e) { + bln = false + } + return bln + }, + // 清空字典 + cleanDict() { + this.dict = [] + }, + // 初始字典 + initDict() { + } + } + }) + +export default useDictStore diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..2c6d40e --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,127 @@ +import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' +import { getRouters } from '@/api/menu' +import Layout from '@/layout/index.vue' +import ParentView from '@/components/ParentView/index.vue' +import InnerLink from '@/layout/components/InnerLink/index.vue' + +// 匹配views里面所有的.vue文件 +const modules = import.meta.glob('./../../views/**/*.vue') + +const usePermissionStore = defineStore( + 'permission', + { + state: () => ({ + routes: [] as any[], + addRoutes: [] as any[], + defaultRoutes: [] as any[], + topbarRouters: [] as any[], + sidebarRouters: [] as any[] + }), + actions: { + setRoutes(routes: any[]) { + this.addRoutes = routes + this.routes = constantRoutes.concat(routes) + }, + setDefaultRoutes(routes: any[]) { + this.defaultRoutes = constantRoutes.concat(routes) + }, + setTopbarRoutes(routes: any[]) { + this.topbarRouters = routes + }, + setSidebarRouters(routes: any[]) { + this.sidebarRouters = routes + }, + generateRoutes(roles?: any[]): Promise { + return new Promise(resolve => { + // 向后端请求路由数据 + getRouters().then(res => { + const sdata = JSON.parse(JSON.stringify(res.data)) + const rdata = JSON.parse(JSON.stringify(res.data)) + const defaultData = JSON.parse(JSON.stringify(res.data)) + const sidebarRoutes = filterAsyncRouter(sdata) + const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const defaultRoutes = filterAsyncRouter(defaultData) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes) + asyncRoutes.forEach(route => { router.addRoute(route) }) + this.setRoutes(rewriteRoutes) + this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) + this.setDefaultRoutes(sidebarRoutes) + this.setTopbarRoutes(defaultRoutes) + resolve(rewriteRoutes) + }) + }) + } + } + }) + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap: any[], lastRouter = false, type = false) { + return asyncRouterMap.filter(route => { + if (type && route.children) { + route.children = filterChildren(route.children) + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout + } else if (route.component === 'ParentView') { + route.component = ParentView + } else if (route.component === 'InnerLink') { + route.component = InnerLink + } else { + route.component = loadView(route.component) + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type) + } else { + delete route['children'] + delete route['redirect'] + } + return true + }) +} + +function filterChildren(childrenMap: any[], lastRouter: any = false) { + var children: any[] = [] + childrenMap.forEach(el => { + el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path + if (el.children && el.children.length && el.component === 'ParentView') { + children = children.concat(filterChildren(el.children, el)) + } else { + children.push(el) + } + }) + return children +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes: any[]): any[] { + const res: any[] = [] + routes.forEach(route => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route) + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route) + } + } + }) + return res +} + +export const loadView = (view: string): any => { + let res + for (const path in modules) { + const dir = path.split('views/')[1].split('.vue')[0] + if (dir === view) { + res = () => modules[path]() + } + } + return res +} + +export default usePermissionStore diff --git a/src/store/modules/printStore.ts b/src/store/modules/printStore.ts new file mode 100644 index 0000000..f410c28 --- /dev/null +++ b/src/store/modules/printStore.ts @@ -0,0 +1,36 @@ +// 打印状态管理 +import { defineStore } from 'pinia' + +export const usePrintStore = defineStore('printStore', { + state: () => ({ + isConnected: false, // 存储连接状态 + clientInfo: null, // 存储客户端信息 + printerList: [] as any[], // 存储打印机列表 + defaultPrinter: '' as string, // 存储默认打印机名称 + }), + actions: { + setConnected(status: boolean) { + this.isConnected = status + }, + setClientInfo(info: any) { + this.clientInfo = info + }, + setPrinterList(list: any[]) { + this.printerList = list + }, + + setDefaultPrinter(printerName: string) { + this.defaultPrinter = printerName + } + }, + + // 持久化配置 + persist: { + // 指定要持久化的状态字段 + pick: ['defaultPrinter',], + // 可选配置:自定义存储键名(默认是store的id) + // key: 'common_store', + // 可选配置:指定存储位置(默认是localStorage 也可以用sessionStorage) + storage: localStorage, + } +}) \ No newline at end of file diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts new file mode 100644 index 0000000..f40b20c --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,67 @@ +import defaultSettings from '@/settings' +import { useDark, useToggle } from '@vueuse/core' +import { useDynamicTitle } from '@/utils/dynamicTitle' + +const isDark = useDark() +const toggleDark = useToggle(isDark) + +const { sideTheme, showSettings, navType, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings + +const storageSetting = JSON.parse(localStorage.getItem('layout-setting') || '{}') || {} + +interface SettingsState { + title: string + theme: string + sideTheme: string + showSettings: boolean + navType: number + tagsView: boolean + tagsIcon: boolean + fixedHeader: boolean + sidebarLogo: boolean + dynamicTitle: boolean + footerVisible: boolean + footerContent: string + isDark: boolean +} + +const useSettingsStore = defineStore( + 'settings', + { + state: (): SettingsState => ({ + title: '', + theme: storageSetting.theme || '#00a5ff', + sideTheme: storageSetting.sideTheme || sideTheme, + showSettings: showSettings, + navType: storageSetting.navType === undefined ? navType : storageSetting.navType, + tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, + tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon, + fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, + sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, + dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle, + footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible, + footerContent: footerContent, + isDark: isDark.value + }), + actions: { + // 修改布局设置 + changeSetting(data: { key: string; value: any }) { + const { key, value } = data + if (this.hasOwnProperty(key)) { + (this as any)[key] = value + } + }, + // 设置网页标题 + setTitle(title: string) { + this.title = title + useDynamicTitle() + }, + // 切换暗黑模式 + toggleTheme() { + this.isDark = !this.isDark + toggleDark() + } + } + }) + +export default useSettingsStore diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..1c87d30 --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,219 @@ +interface ViewMeta { + title?: string + noCache?: boolean + affix?: boolean + link?: string + [key: string]: any +} + +interface View { + path: string + name?: string + meta: ViewMeta + [key: string]: any +} + +const useTagsViewStore = defineStore( + 'tags-view', + { + state: () => ({ + visitedViews: [] as View[], + cachedViews: [] as string[], + iframeViews: [] as View[] + }), + + actions: { + addView(view: any) { + this.addVisitedView(view) + this.addCachedView(view) + }, + + addIframeView(view: any) { + if (this.iframeViews.some((v: any) => v.path === view.path)) return + this.iframeViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ) + }, + + addVisitedView(view: any) { + if (this.visitedViews.some((v: any) => v.path === view.path)) return + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ) + }, + + addCachedView(view: any) { + if (this.cachedViews.includes(view.name)) return + if (!view.meta?.noCache) { + this.cachedViews.push(view.name) + } + }, + + delView(view: any) { + return new Promise<{ visitedViews: View[], cachedViews: string[] }>(resolve => { + this.delVisitedView(view) + this.delCachedView(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews] + }) + }) + }, + + delVisitedView(view: any) { + return new Promise(resolve => { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1) + break + } + } + this.iframeViews = this.iframeViews.filter((item: any) => item.path !== view.path) + resolve([...this.visitedViews]) + }) + }, + + delIframeView(view: any) { + return new Promise(resolve => { + this.iframeViews = this.iframeViews.filter((item: any) => item.path !== view.path) + resolve([...this.iframeViews]) + }) + }, + + delCachedView(view: any) { + return new Promise(resolve => { + const index = this.cachedViews.indexOf(view.name) + index > -1 && this.cachedViews.splice(index, 1) + resolve([...this.cachedViews]) + }) + }, + + delOthersViews(view: any) { + return new Promise<{ visitedViews: View[], cachedViews: string[] }>(resolve => { + this.delOthersVisitedViews(view) + this.delOthersCachedViews(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews] + }) + }) + }, + + delOthersVisitedViews(view: any) { + return new Promise(resolve => { + this.visitedViews = this.visitedViews.filter((v: any) => { + return v.meta?.affix || v.path === view.path + }) + this.iframeViews = this.iframeViews.filter((item: any) => item.path === view.path) + resolve([...this.visitedViews]) + }) + }, + + delOthersCachedViews(view: any) { + return new Promise(resolve => { + const index = this.cachedViews.indexOf(view.name) + if (index > -1) { + this.cachedViews = this.cachedViews.slice(index, index + 1) + } else { + this.cachedViews = [] + } + resolve([...this.cachedViews]) + }) + }, + + delAllViews(view?: any) { + return new Promise<{ visitedViews: View[], cachedViews: string[] }>(resolve => { + this.delAllVisitedViews(view) + this.delAllCachedViews(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews] + }) + }) + }, + + delAllVisitedViews(view: any) { + return new Promise(resolve => { + const affixTags = this.visitedViews.filter((tag: any) => tag.meta?.affix) + this.visitedViews = affixTags + this.iframeViews = [] + resolve([...this.visitedViews]) + }) + }, + + delAllCachedViews(view: any) { + return new Promise(resolve => { + this.cachedViews = [] + resolve([...this.cachedViews]) + }) + }, + + updateVisitedView(view: any) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + }, + + delRightTags(view: any) { + return new Promise(resolve => { + const index = this.visitedViews.findIndex((v: any) => v.path === view.path) + if (index === -1) { + return + } + this.visitedViews = this.visitedViews.filter((item: any, idx: number) => { + if (idx <= index || (item.meta && item.meta.affix)) { + return true + } + if (item.name) { + const i = this.cachedViews.indexOf(item.name) + if (i > -1) { + this.cachedViews.splice(i, 1) + } + } + if (item.meta?.link) { + const fi = this.iframeViews.findIndex((v: any) => v.path === item.path) + this.iframeViews.splice(fi, 1) + } + return false + }) + resolve([...this.visitedViews]) + }) + }, + + delLeftTags(view: any) { + return new Promise(resolve => { + const index = this.visitedViews.findIndex((v: any) => v.path === view.path) + if (index === -1) { + return + } + this.visitedViews = this.visitedViews.filter((item: any, idx: number) => { + if (idx >= index || (item.meta && item.meta.affix)) { + return true + } + if (item.name) { + const i = this.cachedViews.indexOf(item.name) + if (i > -1) { + this.cachedViews.splice(i, 1) + } + } + if (item.meta?.link) { + const fi = this.iframeViews.findIndex((v: any) => v.path === item.path) + this.iframeViews.splice(fi, 1) + } + return false + }) + resolve([...this.visitedViews]) + }) + } + } + } +) + +export default useTagsViewStore diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..b23d037 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,135 @@ +import router from '@/router' +import { ElMessageBox } from 'element-plus' +import { login, logout, getInfo, loginAnonymous } from '@/api/login' +import { getToken, setToken, removeToken } from '@/utils/auth' +import { isHttp, isEmpty } from "@/utils/validate" +import defAva from '@/assets/images/profile.jpg' + +interface RoleInfo { + roleName: string + [key: string]: any +} + +interface UserState { + token: string | undefined + id: string | number + name: string + nickName: string + roleName: string + avatar: string + roles: string[] + permissions: string[] + sysLoginParam: Object + deptParam: Object +} + +const useUserStore = defineStore( + 'user', + { + state: (): UserState => ({ + token: getToken(), + id: '', + name: '', + nickName: '', + roleName: '', + avatar: '', + roles: [], + permissions: [], + sysLoginParam: {}, // 登录参数 + deptParam: {} // 部门参数 + }), + actions: { + // 单点登录 + loginAnonymous(data: { name: string; yljg: string }) { + return new Promise((resolve, reject) => { + loginAnonymous({ + username: data.name, + yljg: data.yljg + }).then(res => { + if (res.code == 200) { + setToken(res.token) + this.token = res.token + resolve(true) + } + + }).catch(error => { + reject(error) + }) + }) + }, + // 登录 + login(userInfo: { username: string; password: string; code: string; uuid: string; loginParam: Object }) { + const username = userInfo.username.trim() + const password = userInfo.password + const code = userInfo.code + const uuid = userInfo.uuid + const loginParam = userInfo.loginParam + return new Promise((resolve, reject) => { + login(username, password, code, uuid, loginParam).then(res => { + setToken(res.token) + this.token = res.token + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + // 获取用户信息 + getInfo() { + return new Promise((resolve, reject) => { + getInfo().then(res => { + const user = res.user + let avatar = user.avatar || '' + this.sysLoginParam = user.sysLoginParam + this.deptParam = user.dept + if (!isHttp(avatar)) { + avatar = (isEmpty(avatar)) ? defAva : import.meta.env.VITE_APP_BASE_API + avatar + } + const roles = res.user.roles as any[] + if (roles && roles.length > 0) { + this.roles = roles + this.permissions = res.permissions + this.roleName = (roles[0] as RoleInfo).roleName + } else { + this.roles = ['ROLE_DEFAULT'] + } + this.id = user.userId || '' + this.name = user.userName || '' + this.nickName = user.nickName || '' + this.avatar = avatar + /* 初始密码提示 */ + if (res.isDefaultModifyPwd) { + ElMessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { + router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } }) + }).catch(() => { }) + } + /* 过期密码提示 */ + if (!res.isDefaultModifyPwd && res.isPasswordExpired) { + ElMessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { + router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } }) + }).catch(() => { }) + } + resolve(res) + }).catch(error => { + reject(error) + }) + }) + }, + // 退出系统 + logOut() { + return new Promise((resolve, reject) => { + logout().then(() => { + this.token = '' + this.roles = [] + this.permissions = [] + removeToken() + resolve() + }).catch((error: any) => { + reject(error) + }) + }) + } + } + }) + +export default useUserStore diff --git a/src/types/Object.d.ts b/src/types/Object.d.ts new file mode 100644 index 0000000..0248fd1 --- /dev/null +++ b/src/types/Object.d.ts @@ -0,0 +1,212 @@ +export interface LabReportInstr { + index?: number; //序号 + yq: string; //仪器 + xh: number; //序号 + bgdh: string; //报告单号 + yljg: string; //医疗机构 + type: string; //使用类型 + condition: string; //使用条件 + status?: string; //状态 +} + +export interface DictData { + XMGL?: Array; + ITEMCLASS?: Array; + YQDL?: Array; + xmlbList?: Array; + YQ?: Array; + VA?: Array; + LF?: Array; + BT?: Array; + SLZQ?: Array; + UT?: Array; + MD?: Array; + ITEMCLASS?: Array; + GBDM?: Array; + [key: string]: any[] | undefined; // 添加索引签名以支持动态访问 +} + + +export class LabInstr { + yq: string; //仪器 + yqmc: string; //仪器名称 + yqdl: string; //仪器大类 +} + +export class XmInfoNew { + xmid?: string; //项目ID + xmdh?: string; //项目代号 + xmmc?: string; //项目名称 + cksx?: number;//参考值上限 + ckxx?: number;//参考值下限 + dw?: string; //单位 + dyckz?: string; //对应参考值 + dyxh?: number; //对应序号 + xmlb?: string; //项目类别 + jsxm?: string; //计算项目 + yqdl?: string; //仪器大类 + val1?: string; //备用1 + val2?: string; //备用2 + val3?: string; //备用3 + val4?: string; //备用4 + val5?: string; //备用5 + uflag?: boolean; //是否无效 + cp_jgz?: string; + jgbz?: string; + jgz?: string; + xmgl?: string; + mrz?: string; + qccv?: string; + xsbs?: string; + whocode?: string; + germflag?: string; + yymc?: string; + knowledge?: string; + xsws?: number; + dylx?: string; + zhbdh?: string; + bgdh?: string; + jg?: number; + zjf?: string; + csff?: string; + fzdyxh?: number; + itemclass?: string; + jsgs?: string; + ygxb?: string; + ygnl?: string; + ygbb?: string; + ygzq?: string; + ygzd?: string; + sjx?: string; + xjx?: string; + jgjg?: string; + hlimt?: string; + llimit?: string; + redoxx?: string; + redosx?: string; +} + +export class XmAlarmdetailNew { + xmid: number; + seq: number; + samplecondition: string; + resultcondition: string; + +} + +// 定义菜单项的类型 +export interface ContextMenuItem { + // 菜单项类型:普通项或分隔线 + type?: 'item' | 'divider'; + // 菜单项显示文本 + label?: string; + // 菜单项唯一标识,用于事件处理 + action?: string; + // 是否为危险操作(如删除) + danger?: boolean; + // 是否禁用 + disabled?: boolean; + // 快捷键提示文本 + shortcut?: string; + // 菜单项的子级菜单项 + props?: Record; +} + + +export class XmValNew { + xmid: number; + xmdh?: string; + qz: string; + srm?: string; + xh?: number; + jgbz?: string; + +} + +export class XmInfoVs { + vsid?: number; + yq: string; + xmid: number; + xs?: number; + zkxm?: string; + cksx?: number; + ckxx?: number; + dyckz?: string; + +} + +export class XmTextresultNew { + xmid?: string; + xmdh?: string; + xh?: number; + textresult?: string; +} + +export class LabResultItem { + xmdh: string; + xmmc: string; + csjg: string; + qz: string; + [key: string]: any; +} + +export class ymTableDataItem { + zhywdh: string; + ywdh: any; + ywmc: any; + mic: string; + rad: string; + csjg: string; + jgbz: string; + ckz: string; + bz: string; + txtresult: string; + cp_bz: string; +} + +// 定义原始数据项的接口 +interface OriginalItem { + zkph: string | null; + zkbz: string | null; + cs: number; + xmdh: string; + sd: string | null; + qccv: number | null; + xmmc: string; + cv: string | null; + zkjg: string | null; + yq: string | null; + ld: string | null; + zkrq: string | null; + daynum?: number | undefined; +} + +// 定义原始数据的接口 +interface OriginalData { + cs1: OriginalItem[]; + cs2: OriginalItem[]; + cs3: OriginalItem[]; + cs4: OriginalItem[]; + cs5: OriginalItem[]; + cs6: OriginalItem[]; + cs7: OriginalItem[]; +} + +// 定义最终结果的接口 +interface ResultItem { + zkph: number; + yq: string | undefined; + zkrq: string; + xmdh: string; + xmmc: string; + sd: string; + zkbz: string; + daynum?: number | undefined; + cs1: string; + cs2: string; + cs3: string; + cs4: string; + cs5: string; + cs6: string; + cs7: string; +} \ No newline at end of file diff --git a/src/types/SelectTable.d.ts b/src/types/SelectTable.d.ts new file mode 100644 index 0000000..04583e6 --- /dev/null +++ b/src/types/SelectTable.d.ts @@ -0,0 +1,32 @@ +export interface Emits { + (e: "update:data", val: any): void; + (e: "getDataValue", val: any): void; + (e: 'enter-press'): void; +} + +export interface Props { + data: any; + fields?: Field[]; + tableData?: object[]; + label?: string; + value?: string; + objKey?: string; + isHighlight?: boolean; + size?: string; + placeholder?: string; + border?: boolean; + width?: string | number; + disabled?: boolean; + dictType?: string; + optionselect?: boolean; + clearable?: boolean; + keyValue?: boolean; //是否使用键值对模式 +} + + +export interface Field { + prop: string; + label: string; + width?: number; + enablePinyinSearch?: boolean; //是否参与拼音搜索 +} \ No newline at end of file diff --git a/src/types/api/common.ts b/src/types/api/common.ts new file mode 100644 index 0000000..e6fc621 --- /dev/null +++ b/src/types/api/common.ts @@ -0,0 +1,85 @@ +/** API 通用响应类型 */ +export interface AjaxResult { + /** 状态码 */ + code: number + /** 返回内容 */ + msg: string + /** 数据对象 */ + data?: T +} + +/** API 表格响应类型 */ +export interface TableDataInfo { + /** 状态码 */ + code: number + /** 返回内容 */ + msg: string + /** 总记录数 */ + total: number + /** 列表数据 */ + rows: T[] +} + +/** 分页参数类型 */ +export interface PageDomain { + /** 当前记录起始索引 */ + pageNum?: number + /** 每页显示记录数 */ + pageSize?: number + /** 排序列 */ + orderByColumn?: string + /** 排序的方向desc或者asc */ + isAsc?: string + /** 分页参数合理化 */ + reasonable?: boolean +} + +/** Entity基类 */ +export interface BaseEntity { + /** 搜索值 */ + searchValue?: string + /** 创建者 */ + createBy?: string + /** 创建时间 */ + createTime?: string + /** 更新者 */ + updateBy?: string + /** 更新时间 */ + updateTime?: string + /** 备注 */ + remark?: string + /** 请求参数 */ + params?: Record +} + +/** Treeselect树结构类型 */ +export interface TreeSelect { + /** 节点ID */ + id?: number + /** 节点名称 */ + label?: string + /** 节点禁用 */ + disabled: boolean + /** 子节点 */ + children: TreeSelect[] +} + +/** 显隐列信息 */ +export interface TableShowColumns { + // 显示名称 + label: string + // 是否显示 + visible: boolean +} + +/** 文件上传响应数据 */ +export interface UploadFileResult extends AjaxResult { + /** 文件名称(包含路径) */ + fileName: string + /** 文件名称(不含路径) */ + newFileName: string + /** 完整URL地址 */ + url: string + /** 原始文件名称 */ + originalFilename: string +} diff --git a/src/types/api/index.ts b/src/types/api/index.ts new file mode 100644 index 0000000..2ffa461 --- /dev/null +++ b/src/types/api/index.ts @@ -0,0 +1,29 @@ +/** + * API 类型统一导出 + */ +export * from "./common"; + +// 登录模块 +export * from "./login"; +export * from "./menu"; + +// System 模块 +export * from "./system/user"; +export * from "./system/role"; +export * from "./system/menu"; +export * from "./system/dept"; +export * from "./system/post"; +export * from "./system/dict"; +export * from "./system/config"; +export * from "./system/notice"; + +// monitor 模块 +export * from "./monitor/cache"; +export * from "./monitor/job"; +export * from "./monitor/jobLog"; +export * from "./monitor/logininfor"; +export * from "./monitor/operlog"; +export * from "./monitor/online"; + +// 代码生成模块 +export * from "./tool/gen"; diff --git a/src/types/api/login.ts b/src/types/api/login.ts new file mode 100644 index 0000000..de8db26 --- /dev/null +++ b/src/types/api/login.ts @@ -0,0 +1,50 @@ +import type { AjaxResult } from './common' +import type { SysUser } from './system/user' + +// 登录响应 +export interface LoginInfoResult extends AjaxResult { + /** 令牌 */ + token: string +} + +/** 用户信息响应 */ +export interface UserInfoResult extends AjaxResult { + /** 用户信息 */ + user: SysUser + /** 角色数据 */ + roles: string[] + /** 权限数据 */ + permissions: string[] + /** 初始密码是否提醒修改 */ + isDefaultModifyPwd?: boolean + /** 密码是否过期 */ + isPasswordExpired?: boolean +} + +/** 验证码响应 */ +export interface CaptchaInfoResult extends AjaxResult { + /** 验证码缓存key */ + uuid: string; + /** 验证码图片Base64 */ + img: string; + /** 验证码开关 */ + captchaEnabled: boolean +} + +/** 注册提交信息 */ +export interface RegisterForm { + username: string + password: string + confirmPassword: string + code: string + uuid: string +} + +/** 登录提交信息 */ +export interface LoginForm { + username: string + password: string + rememberMe?: boolean | string + code: string + uuid: string +} diff --git a/src/types/api/menu.ts b/src/types/api/menu.ts new file mode 100644 index 0000000..c3d3db9 --- /dev/null +++ b/src/types/api/menu.ts @@ -0,0 +1,33 @@ +/** 路由信息 */ +export interface RouterVo { + /** 路由名字 */ + name?: string; + /** 路由地址 */ + path?: string; + /** 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 */ + hidden?: boolean; + /** 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 */ + redirect?: string; + /** 组件地址 */ + component?: string; + /** 路由参数:如 {"id": 1, "name": "ry"} */ + query?: string; + /** 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 */ + alwaysShow?: boolean; + /** 其他元素 */ + meta?: MetaVo; + /** 子路由 */ + children?: RouterVo[] +} + +/** 路由其他元素信息 */ +export interface MetaVo { + /** 设置该路由在侧边栏和面包屑中展示的名字 */ + title?: string + /** 设置该路由的图标,对应路径src/assets/icons/svg */ + icon?: string + /** 设置为true,则不会被 缓存 */ + noCache?: boolean + /** 内链地址(http(s)://开头) */ + link?: string +} diff --git a/src/types/api/monitor/cache.ts b/src/types/api/monitor/cache.ts new file mode 100644 index 0000000..7e076d9 --- /dev/null +++ b/src/types/api/monitor/cache.ts @@ -0,0 +1,11 @@ +/** 定时任务信息 */ +export interface SysCache { + /** 缓存名称 */ + cacheName?: string; + /** 缓存键名 */ + cacheKey?: string; + /** 缓存内容 */ + cacheValue?: string; + /** 备注 */ + remark?: string; +} diff --git a/src/types/api/monitor/job.ts b/src/types/api/monitor/job.ts new file mode 100644 index 0000000..04c713a --- /dev/null +++ b/src/types/api/monitor/job.ts @@ -0,0 +1,33 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 定时任务分页查询参数 */ +export interface JobQueryParams extends PageDomain { + /** 任务名称 */ + jobName?: string; + /** 任务组名 */ + jobGroup?: string; + /** 任务状态 */ + status?: string; +} + +/** 定时任务信息 */ +export interface SysJob extends BaseEntity { + /** 任务编号 */ + jobId?: number; + /** 任务名称 */ + jobName?: string; + /** 任务组名 */ + jobGroup?: string; + /** 调用目标字符串 */ + invokeTarget?: string; + /** 执行表达式 */ + cronExpression?: string; + /** 下次执行时间 */ + nextValidTime?: Date; + /** 计划策略 */ + misfirePolicy?: '1' | '2' | '3'; + /** 并发执行(0允许 1禁止) */ + concurrent?: '0' | '1'; + /** 状态(0正常 1暂停) */ + status?: '0' | '1'; +} diff --git a/src/types/api/monitor/jobLog.ts b/src/types/api/monitor/jobLog.ts new file mode 100644 index 0000000..7a61a46 --- /dev/null +++ b/src/types/api/monitor/jobLog.ts @@ -0,0 +1,34 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 定时任务日志分页查询参数 */ +export interface JobLogQueryParams extends PageDomain { + /** 任务名称 */ + jobName?: string; + /** 任务组名 */ + jobGroup?: string; + /** 执行状态 */ + status?: string; + /** 执行时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 定时任务日志信息 */ +export interface SysJobLog extends BaseEntity { + /** 任务日志编号 */ + jobLogId?: number; + /** 任务名称 */ + jobName?: string; + /** 任务组名 */ + jobGroup?: string; + /** 调用目标字符串 */ + invokeTarget?: string; + /** 日志信息 */ + jobMessage?: string; + /** 异常信息 */ + exceptionInfo?: string; + /** 状态(0正常 1失败) */ + status?: '0' | '1'; +} diff --git a/src/types/api/monitor/logininfor.ts b/src/types/api/monitor/logininfor.ts new file mode 100644 index 0000000..0b81e83 --- /dev/null +++ b/src/types/api/monitor/logininfor.ts @@ -0,0 +1,38 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 登录日志分页查询参数 */ +export interface LogininforQueryParams extends PageDomain { + /** 登录地址 */ + ipaddr?: string; + /** 用户名称 */ + userName?: string; + /** 状态 */ + status?: string; + /** 登录时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 登录日志信息 */ +export interface SysLogininfor extends BaseEntity { + /** 登录日志编号 */ + infoId?: number; + /** 用户账号 */ + userName?: string; + /** 登录地址 */ + ipaddr?: string; + /** 登录地点 */ + loginLocation?: string; + /** 浏览器 */ + browser?: string; + /** 操作系统 */ + os?: string; + /** 提示消息 */ + msg?: string; + /** 访问时间 */ + loginTime?: Date; + /** 状态(0正常 1失败) */ + status?: '0' | '1'; +} diff --git a/src/types/api/monitor/online.ts b/src/types/api/monitor/online.ts new file mode 100644 index 0000000..3ca96cd --- /dev/null +++ b/src/types/api/monitor/online.ts @@ -0,0 +1,27 @@ +/** 在线用户分页查询参数 */ +export interface OnlineQueryParams { + /** 登录地址 */ + ipaddr?: string; + /** 用户名称 */ + userName?: string; +} + +/** 在线用户信息 */ +export interface SysUserOnline { + /** 会话编号 */ + tokenId?: string; + /** 部门名称 */ + deptName?: string; + /** 用户名称 */ + userName?: string; + /** 登录IP */ + ipaddr?: string; + /** 登录地址 */ + loginLocation?: string; + /** 浏览器类型 */ + browser?: string; + /** 操作系统 */ + os?: string; + /** 登录时间 */ + loginTime?: number; +} diff --git a/src/types/api/monitor/operlog.ts b/src/types/api/monitor/operlog.ts new file mode 100644 index 0000000..a6530e3 --- /dev/null +++ b/src/types/api/monitor/operlog.ts @@ -0,0 +1,58 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 操作日志分页查询参数 */ +export interface OperlogQueryParams extends PageDomain { + /** 操作地址 */ + operIp?: string; + /** 系统模块 */ + title?: string; + /** 操作人员 */ + operName?: string; + /** 类型 */ + businessType?: number; + /** 状态 */ + status?: string; + /** 登录时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 操作日志信息 */ +export interface SysOperLog extends BaseEntity { + /** 操作日志编号 */ + operId?: number; + /** 操作模块 */ + title?: string; + /** 业务类型(0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据) */ + businessType?: number; + /** 请求方法 */ + method?: string; + /** 请求方式 */ + requestMethod?: string; + /** 操作类别 */ + operatorType?: string; + /** 操作人员 */ + operName?: string; + /** 部门名称 */ + deptName?: string; + /** 请求地址 */ + operUrl?: string; + /** 操作地址 */ + operIp?: string; + /** 操作地点 */ + operLocation?: string; + /** 请求参数 */ + operParam?: string; + /** 返回参数 */ + jsonResult?: string; + /** 错误消息 */ + errorMsg?: string; + /** 操作时间 */ + operTime?: Date; + /** 消耗时间 */ + costTime?: number; + /** 状态(0正常 1异常) */ + status?: '0' | '1'; +} diff --git a/src/types/api/system/config.ts b/src/types/api/system/config.ts new file mode 100644 index 0000000..3e02f6d --- /dev/null +++ b/src/types/api/system/config.ts @@ -0,0 +1,30 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 参数配置分页查询参数 */ +export interface ConfigQueryParams extends PageDomain { + /** 参数名称 */ + configName?: string; + /** 参数键名 */ + configKey?: string; + /** 系统内置 */ + configType?: string; + /** 创建时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 参数配置信息 */ +export interface SysConfig extends BaseEntity { + /** 参数编号 */ + configId?: number; + /** 参数名称 */ + configName?: string; + /** 参数键名 */ + configKey?: string; + /** 参数键值 */ + configValue?: string; + /** 系统内置(Y是 N否) */ + configType?: 'Y' | 'N'; +} diff --git a/src/types/api/system/dept.ts b/src/types/api/system/dept.ts new file mode 100644 index 0000000..ff29ba5 --- /dev/null +++ b/src/types/api/system/dept.ts @@ -0,0 +1,31 @@ +import type { BaseEntity } from "../common"; + +/** 部门查询参数 */ +export interface DeptQueryParams { + /** 部门名称 */ + deptName?: string; + /** 状态 */ + status?: string; +} + +/** 部门信息 */ +export interface SysDept extends BaseEntity { + /** 部门编号 */ + deptId?: number; + /** 父部门ID */ + parentId?: number; + /** 祖级列表 */ + ancestors?: string; + /** 部门名称 */ + deptName?: string; + /** 显示顺序 */ + orderNum?: number; + /** 负责人 */ + leader?: string; + /** 联系电话 */ + phone?: string; + /** 邮箱 */ + email?: string; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} diff --git a/src/types/api/system/dict.ts b/src/types/api/system/dict.ts new file mode 100644 index 0000000..fb1d7d3 --- /dev/null +++ b/src/types/api/system/dict.ts @@ -0,0 +1,60 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 字典分页查询参数 */ +export interface DictTypeQueryParams extends PageDomain { + /** 字典名称 */ + dictName?: string; + /** 字典类型 */ + dictType?: string; + /** 状态 */ + status?: string; + /** 创建时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 字典数据查询参数 */ +export interface DictDataQueryParams extends PageDomain { + /** 字典名称 */ + dictName?: string; + /** 字典标签 */ + dictLabel?: string; + /** 状态 */ + status?: string; +} + +/** 字典类型信息 */ +export interface SysDictType extends BaseEntity { + /** 字典编号 */ + dictId?: number; + /** 字典名称 */ + dictName?: string; + /** 字典类型 */ + dictType?: string; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} + +/** 字典数据信息 */ +export interface SysDictData extends BaseEntity { + /** 字典编码 */ + dictCode?: number; + /** 字典标签 */ + dictLabel?: string; + /** 字典键值 */ + dictValue?: string; + /** 字典类型 */ + dictType?: string; + /** 样式属性 */ + cssClass?: string; + /** 表格字典样式 */ + listClass?: string; + /** 字典排序 */ + dictSort?: number; + /** 是否默认(Y是 N否) */ + isDefault?: 'Y' | 'N'; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} diff --git a/src/types/api/system/menu.ts b/src/types/api/system/menu.ts new file mode 100644 index 0000000..6a4cbb6 --- /dev/null +++ b/src/types/api/system/menu.ts @@ -0,0 +1,50 @@ +import type { BaseEntity, AjaxResult, TreeSelect } from "../common"; + +/** 菜单查询参数 */ +export interface MenuQueryParams { + /** 菜单名称 */ + menuName?: string; + /** 状态 */ + status?: string; +} + +/** 菜单信息 */ +export interface SysMenu extends BaseEntity { + /** 菜单编号 */ + menuId?: number; + /** 父菜单ID */ + parentId?: number; + /** 菜单名称 */ + menuName?: string; + /** 显示顺序 */ + orderNum?: number; + /** 路由地址 */ + path?: string; + /** 组件路径 */ + component?: string; + /** 路由参数 */ + query?: string; + /** 路由名称 */ + routeName?: string; + /** 权限字符串 */ + perms?: string; + /** 菜单图标 */ + icon?: string; + /** 是否为外链(0是 1否) */ + isFrame?: '0' | '1'; + /** 是否缓存(0缓存 1不缓存) */ + isCache?: '0' | '1'; + /** 类型(M目录 C菜单 F按钮) */ + menuType?: 'M' | 'C' | 'F'; + /** 显示状态(0显示 1隐藏) */ + visible?: '0' | '1'; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} + +export interface RoleMenuTreeselectResult extends AjaxResult { + /** 已选中的菜单ID列表 */ + checkedKeys: number[] + /** 菜单树形结构 */ + menus: TreeSelect[] +} diff --git a/src/types/api/system/notice.ts b/src/types/api/system/notice.ts new file mode 100644 index 0000000..6506356 --- /dev/null +++ b/src/types/api/system/notice.ts @@ -0,0 +1,25 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 通知公告分页查询参数 */ +export interface NoticeQueryParams extends PageDomain { + /** 公告标题 */ + noticeTitle?: string; + /** 操作人员 */ + createBy?: string; + /** 公告类型 */ + noticeType?: string; +} + +/** 通知公告信息 */ +export interface SysNotice extends BaseEntity { + /** 公告编号 */ + noticeId?: number; + /** 公告标题 */ + noticeTitle?: string; + /** 公告类型(1通知 2公告) */ + noticeType?: '1' | '2'; + /** 公告内容 */ + noticeContent?: string; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} diff --git a/src/types/api/system/post.ts b/src/types/api/system/post.ts new file mode 100644 index 0000000..e49fbd7 --- /dev/null +++ b/src/types/api/system/post.ts @@ -0,0 +1,25 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 岗位分页查询参数 */ +export interface PostQueryParams extends PageDomain { + /** 岗位编码 */ + postCode?: string; + /** 岗位名称 */ + postName?: string; + /** 状态 */ + status?: string; +} + +/** 岗位信息 */ +export interface SysPost extends BaseEntity { + /** 岗位编号 */ + postId?: number; + /** 岗位编码 */ + postCode?: string; + /** 岗位名称 */ + postName?: string; + /** 岗位排序 */ + postSort?: number; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} diff --git a/src/types/api/system/role.ts b/src/types/api/system/role.ts new file mode 100644 index 0000000..55c5d9b --- /dev/null +++ b/src/types/api/system/role.ts @@ -0,0 +1,71 @@ +import type { PageDomain, BaseEntity, AjaxResult, TreeSelect } from "../common"; + +/** 角色分页查询参数 */ +export interface RoleQueryParams extends PageDomain { + /** 角色名称 */ + roleName?: string; + /** 角色权限 */ + roleKey?: string; + /** 状态 */ + status?: string; + /** 创建时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 批量授权用户参数 */ +export interface AuthUserSelectParams { + roleId: number; + userIds: number[]; +} + +/** 用户和角色关联信息 */ +export interface SysUserRole { + /** 用户编号 */ + userId?: number; + /** 角色编号 */ + roleId: number; +} + +/** 用户和多角色关联信息 */ +export interface SysUserRoles { + /** 用户编号 */ + userId?: number; + /** 角色编号组 */ + roleIds?: number[]; +} + + +/** 角色信息 */ +export interface SysRole extends BaseEntity { + /** 角色编号 */ + roleId?: number; + /** 角色名称 */ + roleName?: string; + /** 角色权限 */ + roleKey?: string; + /** 角色排序 */ + roleSort?: number; + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + dataScope?: '1' | '2' | '3' | '4' | '5'; + /** 菜单树选择项是否关联显示 */ + menuCheckStrictly?: boolean; + /** 部门树选择项是否关联显示 */ + deptCheckStrictly?: boolean; + /** 角色权限 */ + menuIds?: number[]; + /** 角色权限 */ + deptIds?: number[]; + /** 状态(0正常 1停用) */ + status?: '0' | '1'; +} + +/** 角色部门树响应 */ +export interface RoleDeptTreeResult extends AjaxResult { + /** 选中部门ID */ + checkedKeys: number[] + /** 部门树列表 */ + depts: TreeSelect[] +} diff --git a/src/types/api/system/user.ts b/src/types/api/system/user.ts new file mode 100644 index 0000000..02ea61a --- /dev/null +++ b/src/types/api/system/user.ts @@ -0,0 +1,109 @@ +import type { SysRole } from './role' +import type { SysDept } from './dept' +import type { SysPost } from './post' +import type { PageDomain, AjaxResult, BaseEntity } from "../common"; + +/** 用户分页查询参数 */ +export interface UserQueryParams extends PageDomain { + /** 用户名称 */ + userName?: string; + /** 手机号码 */ + phonenumber?: string; + /** 状态(0正常 1停用) */ + status?: string; + /** 部门编号 */ + deptId?: number; + /** 创建时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 角色授权用户分页查询参数 */ +export interface AuthUserQueryParams extends UserQueryParams { + /** 角色编号 */ + roleId?: number; +} + +/** 用户信息 */ +export interface SysUser extends BaseEntity { + /** 用户ID */ + userId?: number; + /** 部门ID */ + deptId?: number; + /** 用户账号 */ + userName?: string; + /** 用户昵称 */ + nickName?: string; + /** 用户邮箱 */ + email?: string; + /** 手机号码 */ + phonenumber?: string; + /** 用户性别(0男 1女 2未知) */ + sex?: '0' | '1' | '2'; + /** 用户头像 */ + avatar?: string; + /** 密码 */ + password?: string; + /** 账号状态(0正常 1停用) */ + status?: '0' | '1'; + /** 部门对象 */ + dept?: SysDept; + /** 角色对象 */ + roles?: SysRole[]; + /** 角色组 */ + roleIds?: number[]; + /** 岗位组 */ + postIds?: number[]; + // 登录参数 + sysLoginParam?: Object; +} + +/** 注册信息 */ +export interface SysRegister { + /** 用户账号 */ + userName?: string; + /** 密码 */ + password?: string; + /** 验证码 */ + code?: string; + /** 唯一标识 */ + uuid?: string; +} + +/** 用户详情查询响应 */ +export interface UserFormDataResult extends AjaxResult { + /** 用户信息 */ + data?: SysUser + /** 用户的岗位ID列表 */ + postIds?: number[] + /** 用户的角色ID列表 */ + roleIds?: number[] + /** 所有角色列表 */ + roles: SysRole[] + /** 所有岗位列表 */ + posts: SysPost[] +} + +/** 用户个人资料响应 */ +export interface UserProfileResult extends AjaxResult { + /** 角色分组 */ + roleGroup: string + /** 岗位分组 */ + postGroup: string +} + +/** 用户头像上传响应 */ +export interface UserProfileAvatarResult extends AjaxResult { + /** 头像地址 */ + imgUrl: string +} + +/** 用户授权角色响应 */ +export interface UserAuthRoleResult extends AjaxResult { + /** 用户信息 */ + user: SysUser + /** 角色列表 */ + roles: SysRole[] +} diff --git a/src/types/api/tool/gen.ts b/src/types/api/tool/gen.ts new file mode 100644 index 0000000..08135f3 --- /dev/null +++ b/src/types/api/tool/gen.ts @@ -0,0 +1,112 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** 数据表分页查询参数 */ +export interface GenQueryParams extends PageDomain { + /** 表名称 */ + tableName?: string; + /** 表描述 */ + tableComment?: string; + /** 创建时间 */ + params?: { + beginTime?: string; + endTime?: string; + }; +} + +/** 代码生成业务信息 */ +export interface GenTable extends BaseEntity { + /** 主键编号 */ + tableId?: number; + /** 表名称 */ + tableName?: string; + /** 表描述 */ + tableComment?: string; + /** 关联父表的表名 */ + subTableName?: string; + /** 本表关联父表的外键名 */ + subTableFkName?: string; + /** 实体类名称(首字母大写) */ + className?: string; + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + tplCategory?: 'crud' | 'tree' | 'sub' + /** 前端类型(element-ui模版 element-plus模版) */ + tplWebType?: 'element-ui' | 'element-plus' + /** 生成包路径 */ + packageName?: string; + /** 生成模块名 */ + moduleName?: string; + /** 生成业务名 */ + businessName?: string; + /** 生成功能名 */ + functionName?: string; + /** 生成作者 */ + functionAuthor?: string; + /** 生成代码方式(0zip压缩包 1自定义路径) */ + genType?: '0' | '1' + /** 生成路径(不填默认项目路径) */ + genPath?: string; + /** 其它生成选项 */ + options?: string; + /** 树编码字段 */ + treeCode?: string; + /** 树父编码字段 */ + treeParentCode?: string; + /** 树名称字段 */ + treeName?: string; + /** 上级菜单ID字段 */ + parentMenuId?: string; + /** 上级菜单名称字段 */ + parentMenuName?: string; + /** 表列信息 */ + columns?: GenTableColumn[] +} + +/** 生成表列字段信息 */ +export interface GenTableColumn extends BaseEntity { + /** 主键编号 */ + columnId?: number + /** 归属表编号 */ + tableId?: number + /** 列名称 */ + columnName?: string + /** 列描述 */ + columnComment?: string + /** 列类型 */ + columnType?: string + /** Java类型 */ + javaType?: string + /** Java字段名 */ + javaField?: string + /** 是否主键(1是) */ + isPk?: '1' | '0' + /** 是否自增(1是) */ + isIncrement?: '1' | '0' + /** 是否必填(1是) */ + isRequired?: '1' | '0' + /** 是否为插入字段(1是) */ + isInsert?: '1' | '0' + /** 是否编辑字段(1是) */ + isEdit?: '1' | '0' + /** 是否列表字段(1是) */ + isList?: '1' | '0' + /** 是否查询字段(1是) */ + isQuery?: '1' | '0' + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + queryType?: 'EQ' | 'NE' | 'GT' | 'LT' | 'LIKE' | 'BETWEEN' + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ + htmlType?: string + /** 字典类型 */ + dictType?: string + /** 排序 */ + sort?: number +} + +/** 代码生成信息响应 */ +export interface GenTableInfoResult { + /** 表信息 */ + info: GenTable + /** 列信息列表 */ + rows: GenTableColumn[] + /** 所有表信息 */ + tables: GenTable[] +} diff --git a/src/types/components.d.ts b/src/types/components.d.ts new file mode 100644 index 0000000..35d2077 --- /dev/null +++ b/src/types/components.d.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + } +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..4d55a97 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,134 @@ +/** 模块类型声明 */ +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +/** Vite 环境变量类型 */ +interface ImportMetaEnv { + readonly VITE_APP_TITLE: string + readonly VITE_APP_BASE_API: string + readonly VITE_APP_ENV: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + +// element-plus +declare module 'element-plus' + +// vue +declare module 'vue' { + interface ComponentInternalInstance { proxy: any } +} + +// nprogress +declare module 'nprogress' { + export interface NProgressOptions { + minimum?: number + template?: string + easing?: string + speed?: number + trickle?: boolean + trickleSpeed?: number + showSpinner?: boolean + parent?: string + barSelector?: string + } + + export interface NProgress { + start(): NProgress + set(n: number): NProgress + inc(amount?: number): NProgress + done(force?: boolean): NProgress + remove(): void + configure(options: NProgressOptions): NProgress + status: number | null + } + + const nprogress: NProgress + export default nprogress +} + +// js-cookie +declare module 'js-cookie' { + export default Cookies +} + +// file-saver +declare module 'file-saver' { + export function saveAs(data: Blob | string, filename?: string, options?: any): void + export function saveAs(data: Blob | string, filename?: string, disableAutoBOM?: boolean): void + export default saveAs +} + +// jsencrypt +declare module 'jsencrypt/bin/jsencrypt.min' { + import JSEncrypt from 'jsencrypt' + export default JSEncrypt +} + +// axios +declare module 'axios' + +// vue-quill +declare module '@vueup/vue-quill' + +// sortablejs +declare module 'sortablejs' { + export interface SortableEvent { + oldIndex: number; + newIndex: number; + } + + export interface SortableOptions { + ghostClass?: string; + onEnd?: (evt: SortableEvent) => void; + } + + export default class Sortable { + static create(el: HTMLElement, options: SortableOptions): Sortable; + } +} + +// fuse +declare module 'fuse.js' { + export interface FuseOptions { + keys: string[]; + threshold?: number; + includeScore?: boolean; + includeMatches?: boolean; + minMatchCharLength?: number; + shouldSort?: boolean; + // 添加其他你需要的选项 + } + + export default class Fuse { + constructor(list: T[], options?: FuseOptions) + search(pattern: string): T[] + } +} + +// vuedraggable +declare module 'vuedraggable/dist/vuedraggable.common' { + import { DefineComponent } from 'vue' + const draggable: DefineComponent + export default draggable +} + +// vue-cropper +declare module 'vue-cropper' { + import { DefineComponent } from 'vue' + const VueCropper: DefineComponent + export { VueCropper } +} + +// splitpanes +declare module 'splitpanes' { + import { DefineComponent } from 'vue' + + export const Splitpanes: DefineComponent + export const Pane: DefineComponent +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..e623738 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,7 @@ +/** + * 类型统一导出 + */ + +export * from "./api"; +export * from './SelectTable'; +export * from './Object'; diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..a4c77fc --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Admin_czlis-Token' + +export function getToken(): string | undefined { + return Cookies.get(TokenKey) +} + +export function setToken(token: string): string | undefined { + return Cookies.set(TokenKey, token) +} + +export function removeToken(): void { + Cookies.remove(TokenKey) +} diff --git a/src/utils/classCom.ts b/src/utils/classCom.ts new file mode 100644 index 0000000..55737d6 --- /dev/null +++ b/src/utils/classCom.ts @@ -0,0 +1,163 @@ + +/** + * 颜色处理工具类 + * 提供十进制颜色值转十六进制颜色码(支持BGR转RGB)及文字对比度计算功能 + */ + +function decimalToHexColor(decimal: number | string): string { + // 处理空值和非数字情况 + if (decimal === undefined || decimal === null || decimal === '') { + return '#FFFFFF'; + } + + // 统一转换为数字类型 + const num = typeof decimal === 'string' ? parseFloat(decimal) : decimal; + + if (isNaN(num)) { + return '#FFFFFF'; + } + + // 十进制转十六进制并转为大写 + let hex = Math.floor(num).toString(16).toUpperCase(); + let rgbHex = '' + // 确保十六进制字符串为6位,不足则补0 + if (hex.length < 6) { + hex = hex.padStart(6, '0'); + } + + // BGR转RGB(交换前后两位) + const r = hex.substring(4, 6); + const g = hex.substring(2, 4); + const b = hex.substring(0, 2); + rgbHex = r + g + b; + return `#${rgbHex}`; +} + + +// 处理颜色转换为十进制数字 +const convertHexToNumber = (hex: string) => { + if (!hex) return + hex = hex.replace('#', ''); + const bgr = hex.slice(4, 6) + hex.slice(2, 4) + hex.slice(0, 2); + return parseInt(bgr, 16); +}; +/** + * 根据背景色计算对比度文字颜色(黑/白) + * @param bgColor 十六进制背景色(如#FFFF80) + * @returns 对比度文字颜色(#333333或#FFFFFF) + */ +function getContrastTextColor(bgColor: string): string { + // 验证颜色格式 + if (!/^#([0-9A-F]{6})$/i.test(bgColor)) { + throw new Error('Invalid hex color format. Expected #RRGGBB'); + } + + // 提取RGB分量 + const r = parseInt(bgColor.slice(1, 3), 16); + const g = parseInt(bgColor.slice(3, 5), 16); + const b = parseInt(bgColor.slice(5, 7), 16); + + // 计算亮度(标准 luminance 公式) + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + + // 根据亮度返回对比色 + return luminance > 0.5 ? '#333333' : '#FFFFFF'; +} + +// 打印https pdf +function printPDF(pdfUrl: string) { + // 验证URL是否为HTTPS + if (pdfUrl.startsWith('http')) { + console.warn('PDF URL不是HTTPS协议,可能存在安全风险'); + } + + const iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = pdfUrl; + document.body.appendChild(iframe); + iframe.onload = () => { + iframe.contentWindow?.print(); + setTimeout(() => URL.revokeObjectURL(pdfUrl), 1000); // 释放内存 + }; +} + +// 打印base64 pdf +function printBase64PDF(base64: string) { + const blob = base64ToBlob(base64); + const pdfUrl = URL.createObjectURL(blob); + const iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = pdfUrl; + document.body.appendChild(iframe); + iframe.onload = () => { + iframe.contentWindow?.print(); + setTimeout(() => URL.revokeObjectURL(pdfUrl), 1000); // 释放内存 + }; +} + +function base64ToBlob(base64: string) { + const binaryString = atob(base64.replace(/[\n\r]/g, '')); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return new Blob([bytes], { type: 'application/pdf' }); +} + +// 计算两个日期的天数差(支持带时分秒) +const getDayDiff = (start: string, end: string): number => { + const startTime = new Date(start).getTime(); + const endTime = new Date(end).getTime(); + + if (isNaN(startTime) || isNaN(endTime)) { + return -2; // -2 表示日期无效 + } + + const dayDiff = Math.ceil((endTime - startTime) / (24 * 60 * 60 * 1000)); + return dayDiff < 0 ? -1 : dayDiff; // -1 表示结束时间早于开始时间,>=0 为正常天数 +}; + + +/** + * 自动根据屏幕分辨率切换按钮尺寸(small/default) + * @returns 按钮尺寸的响应式ref + */ +function useAutoSize() { + const autoSize = ref<'small' | 'default'>('default'); + // 防抖函数,避免频繁触发 + const debounce = (fn: Function, delay: number) => { + let timer: any; + return (...args: any[]) => { + clearTimeout(timer); + timer = setTimeout(() => fn.apply(null, args), delay); + }; + }; + + const calculateSize = () => { + const screenWidth = window.innerWidth || document.documentElement.clientWidth; + autoSize.value = screenWidth < 1600 ? 'small' : 'default'; + }; + + // 防抖版本的计算函数 + const debouncedCalculateSize = debounce(calculateSize, 100); + + onMounted(() => { + calculateSize(); + // 添加防抖的resize监听 + window.addEventListener('resize', debouncedCalculateSize); + + // 在组件实例上存储清理函数 + (autoSize as any)._cleanup = () => { + window.removeEventListener('resize', debouncedCalculateSize); + }; + }); + + onUnmounted(() => { + const cleanup = (autoSize as any)._cleanup; + if (cleanup) cleanup(); + }); + + return autoSize; +} + +export const classCom = { decimalToHexColor, convertHexToNumber, getContrastTextColor, printBase64PDF, printPDF, useAutoSize, getDayDiff } diff --git a/src/utils/dict.ts b/src/utils/dict.ts new file mode 100644 index 0000000..e5595b2 --- /dev/null +++ b/src/utils/dict.ts @@ -0,0 +1,101 @@ + +import useDictStore from '@/store/modules/dict'; +import { + getDicts +} from '@/api/system/dict/data'; +import { + getComDicts +} from '@/api/liswork/dict/ComDict'; + +// 定义字典项的类型 +interface DictItem { + label: string; + value: string; + elTagType: string; + elTagClass: string; +} + +// 定义返回值类型 +type DictResult = { + [key: string]: Ref; +}; + +/** + * 获取字典数据 + * @param args 字典类型列表 + * @returns 字典数据对象(包含响应式引用) + */ +export function useDict(...args: string[]): DictResult { + const res = ref<{ + [key: string]: DictItem[] + }>({}); + + args.forEach((dictType) => { + res.value[dictType] = []; + const dicts = useDictStore().getDict(dictType); + + if (dicts) { + res.value[dictType] = dicts as DictItem[]; + } else { + getDicts(dictType).then((resp: any) => { + const dictList = resp.data.map((p: any) => ({ + label: p.dictLabel, + value: p.dictValue, + elTagType: p.listClass || '', + elTagClass: p.cssClass || '' + })); + + res.value[dictType] = dictList; + useDictStore().setDict(dictType, dictList); + }); + } + }); + + return toRefs(res.value) as DictResult; +} + +/** + * 获取字典数据(改进版) + * 返回 Promise,确保所有字典数据加载完成后才 resolve + * @param args 字典类型列表 + * @returns 包含响应式字典数据的 Promise + */ +export function comDict(...args: string[]): Promise { + const res = ref<{ + [key: string]: DictItem[] + }>({}); + const promises: Promise[] = []; + + args.forEach((dictType) => { + res.value[dictType] = []; + const dicts = useDictStore().getDict(dictType); + + if (dicts) { + // 缓存存在,直接赋值 + res.value[dictType] = dicts as DictItem[]; + } else { + // 缓存不存在,发起请求并记录 Promise + const promise = getComDicts({ + dict_type: dictType + }).then((resp: any) => { + const dictList = resp.data.map((p: any) => ({ + label: p.dictName, + value: p.dictCode, + elTagType: "", + elTagClass: "" + })); + + res.value[dictType] = dictList; + useDictStore().setDict(dictType, dictList); + return dictList; + }); + + promises.push(promise); + } + }); + + // 等待所有 Promise 完成后才返回 + return Promise.all(promises).then(() => { + return toRefs(res.value) as DictResult; + }); +} \ No newline at end of file diff --git a/src/utils/dynamicTitle.ts b/src/utils/dynamicTitle.ts new file mode 100644 index 0000000..66a503a --- /dev/null +++ b/src/utils/dynamicTitle.ts @@ -0,0 +1,14 @@ +import defaultSettings from '@/settings' +import useSettingsStore from '@/store/modules/settings' + +/** + * 动态修改标题 + */ +export function useDynamicTitle(): void { + const settingsStore = useSettingsStore() + if (settingsStore.dynamicTitle) { + document.title = settingsStore.title + ' - ' + defaultSettings.title + } else { + document.title = defaultSettings.title + } +} diff --git a/src/utils/errorCode.ts b/src/utils/errorCode.ts new file mode 100644 index 0000000..ab2832d --- /dev/null +++ b/src/utils/errorCode.ts @@ -0,0 +1,8 @@ +const errorCode: Record = { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} + +export default errorCode diff --git a/src/utils/generator/config.js b/src/utils/generator/config.js new file mode 100644 index 0000000..449715f --- /dev/null +++ b/src/utils/generator/config.js @@ -0,0 +1,452 @@ +export const formConf = { + formRef: 'formRef', + formModel: 'formData', + size: 'default', + labelPosition: 'right', + labelWidth: 100, + formRules: 'rules', + gutter: 15, + disabled: false, + span: 24, + formBtns: true, +} + +export const inputComponents = [ + { + label: '单行文本', + tag: 'el-input', + tagIcon: 'input', + type: 'text', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/input', + }, + { + label: '多行文本', + tag: 'el-input', + tagIcon: 'textarea', + type: 'textarea', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + autosize: { + minRows: 4, + maxRows: 4, + }, + style: { width: '100%' }, + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/input', + }, + { + label: '密码', + tag: 'el-input', + tagIcon: 'password', + type: 'password', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + 'show-password': true, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/input', + }, + { + label: '计数器', + tag: 'el-input-number', + tagIcon: 'number', + placeholder: '', + defaultValue: undefined, + span: 24, + labelWidth: null, + min: undefined, + max: undefined, + step: undefined, + 'step-strictly': false, + precision: undefined, + 'controls-position': '', + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/input-number', + }, +] + +export const selectComponents = [ + { + label: '下拉选择', + tag: 'el-select', + tagIcon: 'select', + placeholder: '请选择', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + disabled: false, + required: true, + filterable: false, + multiple: false, + options: [ + { + label: '选项一', + value: 1, + }, + { + label: '选项二', + value: 2, + }, + ], + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/select', + }, + { + label: '级联选择', + tag: 'el-cascader', + tagIcon: 'cascader', + placeholder: '请选择', + defaultValue: [], + span: 24, + labelWidth: null, + style: { width: '100%' }, + props: { + props: { + multiple: false, + }, + }, + 'show-all-levels': true, + disabled: false, + clearable: true, + filterable: false, + required: true, + options: [ + { + id: 1, + value: 1, + label: '选项1', + children: [ + { + id: 2, + value: 2, + label: '选项1-1', + }, + ], + }, + ], + dataType: 'dynamic', + labelKey: 'label', + valueKey: 'value', + childrenKey: 'children', + separator: '/', + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/cascader', + }, + { + label: '单选框组', + tag: 'el-radio-group', + tagIcon: 'radio', + defaultValue: 0, + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'default', + disabled: false, + required: true, + options: [ + { + label: '选项一', + value: 1, + }, + { + label: '选项二', + value: 2, + }, + ], + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/radio', + }, + { + label: '多选框组', + tag: 'el-checkbox-group', + tagIcon: 'checkbox', + defaultValue: [], + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'default', + disabled: false, + required: true, + options: [ + { + label: '选项一', + value: 1, + }, + { + label: '选项二', + value: 2, + }, + ], + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/checkbox', + }, + { + label: '开关', + tag: 'el-switch', + tagIcon: 'switch', + defaultValue: false, + span: 24, + labelWidth: null, + style: {}, + disabled: false, + required: true, + 'active-text': '', + 'inactive-text': '', + 'active-color': null, + 'inactive-color': null, + 'active-value': true, + 'inactive-value': false, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/switch', + }, + { + label: '滑块', + tag: 'el-slider', + tagIcon: 'slider', + defaultValue: null, + span: 24, + labelWidth: null, + disabled: false, + required: true, + min: 0, + max: 100, + step: 1, + 'show-stops': false, + range: false, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/slider', + }, + { + label: '时间选择', + tag: 'el-time-picker', + tagIcon: 'time', + placeholder: '请选择', + defaultValue: '', + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/time-picker', + }, + { + label: '时间范围', + tag: 'el-time-picker', + tagIcon: 'time-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'is-range': true, + 'range-separator': '至', + 'start-placeholder': '开始时间', + 'end-placeholder': '结束时间', + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/time-picker', + }, + { + label: '日期选择', + tag: 'el-date-picker', + tagIcon: 'date', + placeholder: '请选择', + defaultValue: null, + type: 'date', + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + format: 'YYYY-MM-DD', + 'value-format': 'YYYY-MM-DD', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/date-picker', + }, + { + label: '日期范围', + tag: 'el-date-picker', + tagIcon: 'date-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + type: 'daterange', + 'range-separator': '至', + 'start-placeholder': '开始日期', + 'end-placeholder': '结束日期', + disabled: false, + clearable: true, + required: true, + format: 'YYYY-MM-DD', + 'value-format': 'YYYY-MM-DD', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/date-picker', + }, + { + label: '评分', + tag: 'el-rate', + tagIcon: 'rate', + defaultValue: 0, + span: 24, + labelWidth: null, + style: {}, + max: 5, + 'allow-half': false, + 'show-text': false, + 'show-score': false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/rate', + }, + { + label: '颜色选择', + tag: 'el-color-picker', + tagIcon: 'color', + defaultValue: null, + labelWidth: null, + 'show-alpha': false, + 'color-format': '', + disabled: false, + required: true, + size: 'default', + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/color-picker', + }, + { + label: '上传', + tag: 'el-upload', + tagIcon: 'upload', + action: 'https://jsonplaceholder.typicode.com/posts/', + defaultValue: null, + labelWidth: null, + disabled: false, + required: true, + accept: '', + name: 'file', + 'auto-upload': true, + showTip: false, + buttonText: '点击上传', + fileSize: 2, + sizeUnit: 'MB', + 'list-type': 'text', + multiple: false, + regList: [], + changeTag: true, + document: 'https://element-plus.org/zh-CN/component/upload', + tip: '只能上传不超过 2MB 的文件', + style: { width: '100%' }, + }, +] + +export const layoutComponents = [ + { + layout: 'rowFormItem', + tagIcon: 'row', + type: 'default', + justify: 'start', + align: 'top', + label: '行容器', + layoutTree: true, + children: [], + document: 'https://element-plus.org/zh-CN/component/layout', + }, + { + layout: 'colFormItem', + label: '按钮', + changeTag: true, + labelWidth: null, + tag: 'el-button', + tagIcon: 'button', + span: 24, + default: '主要按钮', + type: 'primary', + icon: 'Search', + size: 'default', + disabled: false, + document: 'https://element-plus.org/zh-CN/component/button', + }, +] + +// 组件rule的触发方式,无触发方式的组件不生成rule +export const trigger = { + 'el-input': 'blur', + 'el-input-number': 'blur', + 'el-select': 'change', + 'el-radio-group': 'change', + 'el-checkbox-group': 'change', + 'el-cascader': 'change', + 'el-time-picker': 'change', + 'el-date-picker': 'change', + 'el-rate': 'change', +} diff --git a/src/utils/generator/css.js b/src/utils/generator/css.js new file mode 100644 index 0000000..c1c62e6 --- /dev/null +++ b/src/utils/generator/css.js @@ -0,0 +1,18 @@ +const styles = { + 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', + 'el-upload': '.el-upload__tip{line-height: 1.2;}' +} + +function addCss(cssList, el) { + const css = styles[el.tag] + css && cssList.indexOf(css) === -1 && cssList.push(css) + if (el.children) { + el.children.forEach(el2 => addCss(cssList, el2)) + } +} + +export function makeUpCss(conf) { + const cssList = [] + conf.fields.forEach(el => addCss(cssList, el)) + return cssList.join('\n') +} diff --git a/src/utils/generator/drawingDefault.js b/src/utils/generator/drawingDefault.js new file mode 100644 index 0000000..3345c84 --- /dev/null +++ b/src/utils/generator/drawingDefault.js @@ -0,0 +1,37 @@ +export const drawingDefaultValue = [] + +export function initDrawingDefaultValue() { + if (drawingDefaultValue.length === 0) { + drawingDefaultValue.push({ + layout: 'colFormItem', + tagIcon: 'input', + label: '手机号', + vModel: 'mobile', + formId: 6, + tag: 'el-input', + placeholder: '请输入手机号', + defaultValue: '', + span: 24, + style: {width: '100%'}, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': 'Cellphone', + 'suffix-icon': '', + maxlength: 11, + 'show-word-limit': true, + readonly: false, + disabled: false, + required: true, + changeTag: true, + regList: [{ + pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', + message: '手机号格式错误' + }] + }) + } +} + +export function cleanDrawingDefaultValue() { + drawingDefaultValue.splice(0, drawingDefaultValue.length) +} diff --git a/src/utils/generator/html.js b/src/utils/generator/html.js new file mode 100644 index 0000000..4b29841 --- /dev/null +++ b/src/utils/generator/html.js @@ -0,0 +1,359 @@ +/* eslint-disable max-len */ +import { trigger } from './config' + +let confGlobal +let someSpanIsNot24 + +export function dialogWrapper(str) { + return ` + ${str} + + ` +} + +export function vueTemplate(str) { + return `` +} + +export function vueScript(str) { + return `` +} + +export function cssStyle(cssStr) { + return `` +} + +function buildFormTemplate(conf, child, type) { + let labelPosition = '' + if (conf.labelPosition !== 'right') { + labelPosition = `label-position="${conf.labelPosition}"` + } + const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '' + let str = ` + ${child} + ${buildFromBtns(conf, type)} + ` + if (someSpanIsNot24) { + str = ` + ${str} + ` + } + return str +} + +function buildFromBtns(conf, type) { + let str = '' + if (conf.formBtns && type === 'file') { + str = ` + 提交 + 重置 + ` + if (someSpanIsNot24) { + str = ` + ${str} + ` + } + } + return str +} + +// span不为24的用el-col包裹 +function colWrapper(element, str) { + if (someSpanIsNot24 || element.span !== 24) { + return ` + ${str} + ` + } + return str +} + +const layouts = { + colFormItem(element) { + let labelWidth = '' + if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) { + labelWidth = `label-width="${element.labelWidth}px"` + } + const required = !trigger[element.tag] && element.required ? 'required' : '' + const tagDom = tags[element.tag] ? tags[element.tag](element) : null + let str = ` + ${tagDom} + ` + str = colWrapper(element, str) + return str + }, + rowFormItem(element) { + const type = element.type === 'default' ? '' : `type="${element.type}"` + const justify = element.type === 'default' ? '' : `justify="${element.justify}"` + const align = element.type === 'default' ? '' : `align="${element.align}"` + const gutter = element.gutter ? `gutter="${element.gutter}"` : '' + const children = element.children.map(el => layouts[el.layout](el)) + let str = ` + ${children.join('\n')} + ` + str = colWrapper(element, str) + return str + } +} + +const tags = { + 'el-button': el => { + const { + tag, disabled + } = attrBuilder(el) + const type = el.type ? `type="${el.type}"` : '' + const icon = el.icon ? `icon="${el.icon}"` : '' + const size = el.size ? `size="${el.size}"` : '' + let child = buildElButtonChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}` + }, + 'el-input': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '' + const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '' + const readonly = el.readonly ? 'readonly' : '' + const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : '' + const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : '' + const showPassword = el['show-password'] ? 'show-password' : '' + const type = el.type ? `type="${el.type}"` : '' + const autosize = el.autosize && el.autosize.minRows + ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"` + : '' + let child = buildElInputChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}` + }, + 'el-input-number': el => { + const { disabled, vModel, placeholder } = attrBuilder(el) + const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : '' + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const stepStrictly = el['step-strictly'] ? 'step-strictly' : '' + const precision = el.precision ? `:precision='${el.precision}'` : '' + + return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}>` + }, + 'el-select': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const filterable = el.filterable ? 'filterable' : '' + const multiple = el.multiple ? 'multiple' : '' + let child = buildElSelectChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}` + }, + 'el-radio-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + let child = buildElRadioGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${size} ${disabled}>${child}` + }, + 'el-checkbox-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const min = el.min ? `:min="${el.min}"` : '' + const max = el.max ? `:max="${el.max}"` : '' + let child = buildElCheckboxGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}` + }, + 'el-switch': el => { + const { disabled, vModel } = attrBuilder(el) + const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : '' + const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : '' + const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : '' + const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : '' + const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : '' + const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : '' + + return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}>` + }, + 'el-cascader': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const options = el.options ? `:options="${el.vModel}Options"` : '' + const props = el.props ? `:props="${el.vModel}Props"` : '' + const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"' + const filterable = el.filterable ? 'filterable' : '' + const separator = el.separator === '/' ? '' : `separator="${el.separator}"` + + return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}>` + }, + 'el-slider': el => { + const { disabled, vModel } = attrBuilder(el) + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const range = el.range ? 'range' : '' + const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : '' + + return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}>` + }, + 'el-time-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const isRange = el['is-range'] ? 'is-range' : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : '' + + return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}>` + }, + 'el-date-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const type = el.type === 'date' ? '' : `type="${el.type}"` + const readonly = el.readonly ? 'readonly' : '' + + return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}>` + }, + 'el-rate': el => { + const { disabled, vModel } = attrBuilder(el) + const max = el.max ? `:max='${el.max}'` : '' + const allowHalf = el['allow-half'] ? 'allow-half' : '' + const showText = el['show-text'] ? 'show-text' : '' + const showScore = el['show-score'] ? 'show-score' : '' + + return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}>` + }, + 'el-color-picker': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const showAlpha = el['show-alpha'] ? 'show-alpha' : '' + const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : '' + + return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}>` + }, + 'el-upload': el => { + const disabled = el.disabled ? ':disabled=\'true\'' : '' + const action = el.action ? `:action="${el.vModel}Action"` : '' + const multiple = el.multiple ? 'multiple' : '' + const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '' + const accept = el.accept ? `accept="${el.accept}"` : '' + const name = el.name !== 'file' ? `name="${el.name}"` : '' + const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : '' + const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"` + const fileList = `:file-list="${el.vModel}fileList"` + const ref = `ref="${el.vModel}"` + let child = buildElUploadChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}` + } +} + +function attrBuilder(el) { + return { + vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`, + clearable: el.clearable ? 'clearable' : '', + placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '', + width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '', + disabled: el.disabled ? ':disabled=\'true\'' : '' + } +} + +// el-buttin 子级 +function buildElButtonChild(conf) { + const children = [] + if (conf.default) { + children.push(conf.default) + } + return children.join('\n') +} + +// el-input innerHTML +function buildElInputChild(conf) { + const children = [] + if (conf.prepend) { + children.push(``) + } + if (conf.append) { + children.push(``) + } + return children.join('\n') +} + +function buildElSelectChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + children.push(``) + } + return children.join('\n') +} + +function buildElRadioGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :value="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) + } + return children.join('\n') +} + +function buildElCheckboxGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :value="item.label" :disabled="item.disabled" ${border} />`) + } + return children.join('\n') +} + +function buildElUploadChild(conf) { + const list = [] + if (conf['list-type'] === 'picture-card') list.push('') + else list.push(`${conf.buttonText}`) + if (conf.showTip) list.push(`
只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件
`) + return list.join('\n') +} + +export function makeUpHtml(conf, type) { + const htmlList = [] + confGlobal = conf + someSpanIsNot24 = conf.fields.some(item => item.span !== 24) + conf.fields.forEach(el => { + htmlList.push(layouts[el.layout](el)) + }) + const htmlStr = htmlList.join('\n') + + let temp = buildFormTemplate(conf, htmlStr, type) + if (type === 'dialog') { + temp = dialogWrapper(temp) + } + confGlobal = null + return temp +} diff --git a/src/utils/generator/icon.json b/src/utils/generator/icon.json new file mode 100644 index 0000000..2d9999a --- /dev/null +++ b/src/utils/generator/icon.json @@ -0,0 +1 @@ +["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] \ No newline at end of file diff --git a/src/utils/generator/js.js b/src/utils/generator/js.js new file mode 100644 index 0000000..dc38bfe --- /dev/null +++ b/src/utils/generator/js.js @@ -0,0 +1,370 @@ +import { titleCase } from '@/utils/index' +import { trigger } from './config' +// 文件大小设置 +const units = { + KB: '1024', + MB: '1024 / 1024', + GB: '1024 / 1024 / 1024', +} +/** + * @name: 生成js需要的数据 + * @description: 生成js需要的数据 + * @param {*} conf + * @param {*} type 弹窗或表单 + * @return {*} + */ +export function makeUpJs(conf, type) { + conf = JSON.parse(JSON.stringify(conf)) + const dataList = [] + const ruleList = [] + const optionsList = [] + const propsList = [] + const methodList = [] + const uploadVarList = [] + + conf.fields.forEach((el) => { + buildAttributes( + el, + dataList, + ruleList, + optionsList, + methodList, + propsList, + uploadVarList + ) + }) + + const script = buildexport( + conf, + type, + dataList.join('\n'), + ruleList.join('\n'), + optionsList.join('\n'), + uploadVarList.join('\n'), + propsList.join('\n'), + methodList.join('\n') + ) + + return script +} +/** + * @name: 生成参数 + * @description: 生成参数,包括表单数据表单验证数据,多选选项数据,上传数据等 + * @return {*} + */ +function buildAttributes( + el, + dataList, + ruleList, + optionsList, + methodList, + propsList, + uploadVarList +){ + buildData(el, dataList) + buildRules(el, ruleList) + + if (el.options && el.options.length) { + buildOptions(el, optionsList) + if (el.dataType === 'dynamic') { + const model = `${el.vModel}Options` + const options = titleCase(model) + buildOptionMethod(`get${options}`, model, methodList) + } + } + + if (el.props && el.props.props) { + buildProps(el, propsList) + } + + if (el.action && el.tag === 'el-upload') { + uploadVarList.push( + ` + // 上传请求路径 + const ${el.vModel}Action = ref('${el.action}') + // 上传文件列表 + const ${el.vModel}fileList = ref([])` + ) + methodList.push(buildBeforeUpload(el)) + if (!el['auto-upload']) { + methodList.push(buildSubmitUpload(el)) + } + } + + if (el.children) { + el.children.forEach((el2) => { + buildAttributes( + el2, + dataList, + ruleList, + optionsList, + methodList, + propsList, + uploadVarList + ) + }) + } +} +/** + * @name: 生成表单数据formData + * @description: 生成表单数据formData + * @param {*} conf + * @param {*} dataList 数据列表 + * @return {*} + */ +function buildData(conf, dataList) { + if (conf.vModel === undefined) return + let defaultValue + if (typeof conf.defaultValue === 'string' && !conf.multiple) { + defaultValue = `'${conf.defaultValue}'` + } else { + defaultValue = `${JSON.stringify(conf.defaultValue)}` + } + dataList.push(`${conf.vModel}: ${defaultValue},`) +} +/** + * @name: 生成表单验证数据rule + * @description: 生成表单验证数据rule + * @param {*} conf + * @param {*} ruleList 验证数据列表 + * @return {*} + */ +function buildRules(conf, ruleList) { + if (conf.vModel === undefined) return + const rules = [] + if (trigger[conf.tag]) { + if (conf.required) { + const type = Array.isArray(conf.defaultValue) ? "type: 'array'," : '' + let message = Array.isArray(conf.defaultValue) + ? `请至少选择一个${conf.vModel}` + : conf.placeholder + if (message === undefined) message = `${conf.label}不能为空` + rules.push( + `{ required: true, ${type} message: '${message}', trigger: '${ + trigger[conf.tag] + }' }` + ) + } + if (conf.regList && Array.isArray(conf.regList)) { + conf.regList.forEach((item) => { + if (item.pattern) { + rules.push( + `{ pattern: new RegExp(${item.pattern}), message: '${ + item.message + }', trigger: '${trigger[conf.tag]}' }` + ) + } + }) + } + ruleList.push(`${conf.vModel}: [${rules.join(',')}],`) + } +} +/** + * @name: 生成选项数据 + * @description: 生成选项数据,单选多选下拉等 + * @param {*} conf + * @param {*} optionsList 选项数据列表 + * @return {*} + */ +function buildOptions(conf, optionsList) { + if (conf.vModel === undefined) return + if (conf.dataType === 'dynamic') { + conf.options = [] + } + const str = `const ${conf.vModel}Options = ref(${JSON.stringify(conf.options)})` + optionsList.push(str) +} +/** + * @name: 生成方法 + * @description: 生成方法 + * @param {*} methodName 方法名 + * @param {*} model + * @param {*} methodList 方法列表 + * @return {*} + */ +function buildOptionMethod(methodName, model, methodList) { + const str = `function ${methodName}() { + // TODO 发起请求获取数据 + ${model}.value + }` + methodList.push(str) +} +/** + * @name: 生成表单组件需要的props设置 + * @description: 生成表单组件需要的props设置,如;级联组件 + * @param {*} conf + * @param {*} propsList + * @return {*} + */ +function buildProps(conf, propsList) { + if (conf.dataType === 'dynamic') { + conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey) + conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey) + conf.childrenKey !== 'children' && + (conf.props.props.children = conf.childrenKey) + } + const str = ` + // props设置 + const ${conf.vModel}Props = ref(${JSON.stringify(conf.props.props)})` + propsList.push(str) +} +/** + * @name: 生成上传组件的相关内容 + * @description: 生成上传组件的相关内容 + * @param {*} conf + * @return {*} + */ +function buildBeforeUpload(conf) { + const unitNum = units[conf.sizeUnit] + let rightSizeCode = '' + let acceptCode = '' + const returnList = [] + if (conf.fileSize) { + rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize} + if(!isRightSize){ + proxy.$modal.msgError('文件大小超过 ${conf.fileSize}${conf.sizeUnit}') + }` + returnList.push('isRightSize') + } + if (conf.accept) { + acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type) + if(!isAccept){ + proxy.$modal.msgError('应该选择${conf.accept}类型的文件') + }` + returnList.push('isAccept') + } + const str = ` + /** + * @name: 上传之前的文件判断 + * @description: 上传之前的文件判断,判断文件大小文件类型等 + * @param {*} file + * @return {*} + */ + function ${conf.vModel}BeforeUpload(file) { + ${rightSizeCode} + ${acceptCode} + return ${returnList.join('&&')} + }` + return returnList.length ? str : '' +} +/** + * @name: 生成提交表单方法 + * @description: 生成提交表单方法 + * @param {Object} conf vModel 表单ref + * @return {*} + */ +function buildSubmitUpload(conf) { + const str = `function submitUpload() { + this.$refs['${conf.vModel}'].submit() + }` + return str +} +/** + * @name: 组装js代码 + * @description: 组装js代码方法 + * @return {*} + */ +function buildexport( + conf, + type, + data, + rules, + selectOptions, + uploadVar, + props, + methods +) { + let str = ` + const { proxy } = getCurrentInstance() + const ${conf.formRef} = ref() + const data = reactive({ + ${conf.formModel}: { + ${data} + }, + ${conf.formRules}: { + ${rules} + } + }) + + const {${conf.formModel}, ${conf.formRules}} = toRefs(data) + + ${selectOptions} + + ${uploadVar} + + ${props} + + ${methods} + ` + + if(type === 'dialog') { + str += ` + // 弹窗设置 + const dialogVisible = defineModel() + // 弹窗确认回调 + const emit = defineEmits(['confirm']) + /** + * @name: 弹窗打开后执行 + * @description: 弹窗打开后执行方法 + * @return {*} + */ + function onOpen(){ + + } + /** + * @name: 弹窗关闭时执行 + * @description: 弹窗关闭方法,重置表单 + * @return {*} + */ + function onClose(){ + ${conf.formRef}.value.resetFields() + } + /** + * @name: 弹窗取消 + * @description: 弹窗取消方法 + * @return {*} + */ + function close(){ + dialogVisible.value = false + } + /** + * @name: 弹窗表单提交 + * @description: 弹窗表单提交方法 + * @return {*} + */ + function handelConfirm(){ + ${conf.formRef}.value.validate((valid) => { + if (!valid) return + // TODO 提交表单 + + close() + // 回调父级组件 + emit('confirm') + }) + } + ` + } else { + str += ` + /** + * @name: 表单提交 + * @description: 表单提交方法 + * @return {*} + */ + function submitForm() { + ${conf.formRef}.value.validate((valid) => { + if (!valid) return + // TODO 提交表单 + }) + } + /** + * @name: 表单重置 + * @description: 表单重置方法 + * @return {*} + */ + function resetForm() { + ${conf.formRef}.value.resetFields() + } + ` + } + return str +} diff --git a/src/utils/generator/render.js b/src/utils/generator/render.js new file mode 100644 index 0000000..d6d4414 --- /dev/null +++ b/src/utils/generator/render.js @@ -0,0 +1,156 @@ +import { defineComponent, h } from 'vue' +import { makeMap } from '@/utils/index' + +const isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' + 'prefix-icon' +) +const isNotProps = makeMap( + 'layout,prepend,regList,tag,document,changeTag,defaultValue' +) + +function useVModel(props, emit) { + return { + modelValue: props.defaultValue, + 'onUpdate:modelValue': (val) => emit('update:modelValue', val), + } +} +const componentChild = { + 'el-button': { + default(h, conf, key) { + return conf[key] + }, + }, + 'el-select': { + options(h, conf, key) { + return conf.options.map(item => h(resolveComponent('el-option'), { + label: item.label, + value: item.value, + })) + } + }, + 'el-radio-group': { + options(h, conf, key) { + return conf.optionType === 'button' ? conf.options.map(item => h(resolveComponent('el-checkbox-button'), { + label: item.value, + }, () => item.label)) : conf.options.map(item => h(resolveComponent('el-radio'), { + label: item.value, + border: conf.border, + }, () => item.label)) + } + }, + 'el-checkbox-group': { + options(h, conf, key) { + return conf.optionType === 'button' ? conf.options.map(item => h(resolveComponent('el-checkbox-button'), { + label: item.value, + }, () => item.label)) : conf.options.map(item => h(resolveComponent('el-checkbox'), { + label: item.value, + border: conf.border, + }, () => item.label)) + } + }, + 'el-upload': { + 'list-type': (h, conf, key) => { + const option = {} + // if (conf.showTip) { + // tip = h('div', { + // class: "el-upload__tip" + // }, () => '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件') + // } + if (conf['list-type'] === 'picture-card') { + return h(resolveComponent('el-icon'), option, () => h(resolveComponent('Plus'))) + } else { + // option.size = "small" + option.type = "primary" + option.icon = "Upload" + return h(resolveComponent('el-button'), option, () => conf.buttonText) + } + }, + + } +} +const componentSlot = { + 'el-upload': { + 'tip': (h, conf, key) => { + if (conf.showTip) { + return () => h('div', { + class: "el-upload__tip" + }, '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件') + } + }, + } +} +export default defineComponent({ + + // 使用 render 函数 + render() { + const dataObject = { + attrs: {}, + props: {}, + on: {}, + style: {} + } + const confClone = JSON.parse(JSON.stringify(this.conf)) + const children = [] + const slot = {} + const childObjs = componentChild[confClone.tag] + if (childObjs) { + Object.keys(childObjs).forEach(key => { + const childFunc = childObjs[key] + if (confClone[key]) { + children.push(childFunc(h, confClone, key)) + } + }) + } + const slotObjs = componentSlot[confClone.tag] + if (slotObjs) { + Object.keys(slotObjs).forEach(key => { + const childFunc = slotObjs[key] + if (confClone[key]) { + slot[key] = childFunc(h, confClone, key) + } + }) + } + Object.keys(confClone).forEach(key => { + const val = confClone[key] + if (dataObject[key]) { + dataObject[key] = val + } else if (isAttr(key)) { + dataObject.attrs[key] = val + } else if (!isNotProps(key)) { + dataObject.props[key] = val + } + }) + if(children.length > 0){ + slot.default = () => children + } + + return h(resolveComponent(this.conf.tag), + { + modelValue: this.$attrs.modelValue, + ...dataObject.props, + ...dataObject.attrs, + style: { + ...dataObject.style + }, + } + , slot ?? null) + }, + props: { + conf: { + type: Object, + required: true, + }, + } +}) \ No newline at end of file diff --git a/src/utils/groupHelper.ts b/src/utils/groupHelper.ts new file mode 100644 index 0000000..607d73b --- /dev/null +++ b/src/utils/groupHelper.ts @@ -0,0 +1,145 @@ + +// 定义通用类型 +interface GroupOriginValues { + [key: string]: string | number | null | undefined +} + +interface GroupMetaItem { + groupSeq: number + key: string + originValues: GroupOriginValues + itemCount: number + firstRowIndex: number | null +} +// @ts-ignore +interface GroupedItem extends T { + rowSeq: number // 组内行序号 + groupSeq?: number // 组序号(可选) +} + +interface GroupData { + groupSeq: number + key: string + originValues: GroupOriginValues + items: GroupedItem[] +} + +interface UseGroupTableDataReturn { + tableData: ComputedRef<(GroupedItem & { [key: string]: string | number | '' })[]> + groupMeta: ComputedRef + groupedData: ComputedRef[]> +} + +/** + * 通用多字段分组函数 + * @param rawList - 原始数据列表 + * @param groupFields - 分组字段(单个字段传字符串,多个传数组) + * @param showFields - 每组仅第一条显示的字段 + * @param sortField - 排序字段(默认按orderDate降序) + * @param originalData - 原始数据赋值字段(可选) + * @returns 表格渲染数据 + 分组元信息 + */ +export function useGroupTableData>( + rawList: string[], + groupFields: string, + showFields: string | string[], + sortField: string = 'orderDate', + originalData: string[] +): UseGroupTableDataReturn { + // 处理参数格式:确保为数组 + const groupFieldArr = Array.isArray(groupFields) ? groupFields : [groupFields] + const showFieldArr = Array.isArray(showFields) ? showFields : [showFields] + + // 1. 核心分组逻辑 + const groupedData = computed[]>(() => { + const groups: Record[] + }> = {} + + rawList.forEach((item: any) => { + // 生成分组key(多字段拼接,避免重复) + const groupKey = groupFieldArr.map(field => item[field] ?? '').join('_') + + if (!groups[groupKey]) { + groups[groupKey] = { + key: groupKey, + originValues: groupFieldArr.reduce((obj, field) => { + obj[field] = item[field] ?? '' + return obj + }, {} as GroupOriginValues), + items: [] + } + } + // 处理仅第一条显示的字段 + showFieldArr.forEach((field, i) => { + originalData.forEach((v, _i) => { + if (i == _i) { + item[field] = item[v] + } + }) + }) + // 添加组内行序号 + groups[groupKey].items.push({ + ...item, + rowSeq: groups[groupKey].items.length + 1 + }) + }) + + // 2. 按指定字段降序排序分组 + const sortedGroupKeys = Object.keys(groups).sort((a, b) => { + const aSortValue = groups[a].originValues[sortField] ?? '' + const bSortValue = groups[b].originValues[sortField] ?? '' + // 兼容日期/字符串/数字排序 + if (aSortValue && bSortValue && !isNaN(new Date(aSortValue).getTime())) { + return new Date(bSortValue).getTime() - new Date(aSortValue).getTime() + } + return String(bSortValue).localeCompare(String(aSortValue)) + }) + + // 3. 生成带组序号的分组数据 + return sortedGroupKeys.map((key, groupIndex) => ({ + groupSeq: groupIndex + 1, + ...groups[key] + })) + }) + + // 4. 构造扁平化表格数据 + const tableData = computed(() => { + const result: (GroupedItem & { [key: string]: string | number | '' })[] = [] + groupedData.value.forEach(group => { + group.items.forEach((item, idx) => { + const processedItem = { ...item } + + // 处理仅第一条显示的字段 + showFieldArr.forEach(field => { + processedItem[field] = idx === 0 ? (item[field] ?? '') : '' + }) + + // 组序号仅每组第一条显示 + processedItem.groupSeq = idx === 0 ? group.groupSeq : '' + + result.push(processedItem) + }) + }) + return result + }) + + // 5. 生成分组元信息(用于合并单元格) + const groupMeta = computed(() => { + return groupedData.value.map(group => ({ + groupSeq: group.groupSeq, + key: group.key, + originValues: group.originValues, + itemCount: group.items.length, + firstRowIndex: null + })) + }) + + return { + tableData, + groupMeta, + groupedData + } +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..6cfe740 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,348 @@ +import { parseTime } from './ruoyi' + +/** + * 表格时间格式化 + */ +export function formatDate(cellValue: any): string { + if (cellValue == null || cellValue == "") return "" + const date = new Date(cellValue) + const year = date.getFullYear() + const month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + const hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + const minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + const seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds +} + +/** + * @param time + * @param option + * @returns {string} + */ +export function formatTime(time: number | string, option?: string): string { + if (('' + time).length === 10) { + time = parseInt(time as string) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d.getTime()) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) || '' + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param url + * @returns {Object} + */ +export function getQueryObject(url?: string | null): Record { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj: Record = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1: string, $2: string) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param input value + * @returns {number} output value + */ +export function byteLength(str: string): number { + // returns the byte length of an utf8 string + let s = str.length + for (let i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param actual + * @returns {Array} + */ +export function cleanArray(actual: any[]): any[] { + const newArray: any[] = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param json + * @returns {Array} + */ +export function param(json: Record): string { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param url + * @returns {Object} + */ +export function param2Obj(url: string): Record { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj: Record = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * @param val + * @returns {string} + */ +export function html2Text(val: string): string { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText || '' +} + +/** + * Merges two objects, giving the last one precedence + * @param target + * @param source + * @returns {Object} + */ +export function objectMerge(target: any, source: any): any { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param element + * @param className + */ +export function toggleClass(element: HTMLElement, className: string): void { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += ' ' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param type + * @returns {Date} + */ +export function getTime(type?: string): number { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()).getTime() + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param source + * @returns {Object} + */ +export function deepClone(source: T): T { + if (!source && typeof source !== 'object') { + throw new Error('error arguments') + } + const targetObj = (source as any).constructor === Array ? [] : {} + Object.keys(source as any).forEach(keys => { + if ((source as any)[keys] && typeof (source as any)[keys] === 'object') { + (targetObj as any)[keys] = deepClone((source as any)[keys]) + } else { + (targetObj as any)[keys] = (source as any)[keys] + } + }) + return targetObj as T +} + +/** + * @param arr + * @returns {Array} + */ +export function uniqueArr(arr: T[]): T[] { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString(): string { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536 + '') + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param elm + * @param cls + * @returns {boolean} + */ +export function hasClass(ele: HTMLElement, cls: string): boolean { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param ele + * @param cls + */ +export function addClass(ele: HTMLElement, cls: string): void { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param ele + * @param cls + */ +export function removeClass(ele: HTMLElement, cls: string): void { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function makeMap(str: string, expectsLowerCase?: boolean): (val: string) => boolean { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? (val: string) => map[val.toLowerCase()] + : (val: string) => map[val] +} + +export const exportDefault = 'export default ' + +export const beautifierConf = { + html: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'separate', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: false, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + }, + js: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'normal', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: true, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + } +} + +// 首字母大小 +export function titleCase(str: string): string { + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) +} + +// 下划转驼峰 +export function camelCase(str: string): string { + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) +} + +export function isNumberStr(str: string): boolean { + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) +} diff --git a/src/utils/jsencrypt.ts b/src/utils/jsencrypt.ts new file mode 100644 index 0000000..1f832cb --- /dev/null +++ b/src/utils/jsencrypt.ts @@ -0,0 +1,29 @@ +import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' + +// 密钥对生成 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 function encrypt(txt: string): string | false { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +export function decrypt(txt: string): string | false { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} diff --git a/src/utils/mitt.ts b/src/utils/mitt.ts new file mode 100644 index 0000000..68f108e --- /dev/null +++ b/src/utils/mitt.ts @@ -0,0 +1,30 @@ + +import mitt, { Emitter, EventType } from "mitt" + +// 扩展原始Emitter类型,添加自定义方法 +interface CustomEmitter extends Emitter> { + emitWithCache: (eventName: string, data: any) => void + getCache: (eventName: string) => any +} + +const emitter = mitt() as CustomEmitter + +// 全局缓存:key为事件名,value为最新的事件数据 +const eventCache = new Map() + +// 触发事件时自动缓存数据 +emitter.emitWithCache = (eventName: string, data: any) => { + // 缓存数据(覆盖旧数据,只保留最新一次) + eventCache.set(eventName, data) + // 正常触发事件(供已挂载的组件监听) + emitter.emit(eventName, data) +} + +// 获取并清除缓存(避免重复处理) +emitter.getCache = (eventName: string) => { + const data = eventCache.get(eventName) + eventCache.delete(eventName) // 取出后删除,确保只处理一次 + return data +} + +export default emitter \ No newline at end of file diff --git a/src/utils/permission.ts b/src/utils/permission.ts new file mode 100644 index 0000000..5327c59 --- /dev/null +++ b/src/utils/permission.ts @@ -0,0 +1,76 @@ +import useUserStore from '@/store/modules/user' + +/** + * 字符权限校验 + * @param value 校验值 + * @returns {Boolean} + */ +export function checkPermi(value: string[]): boolean { + if (value && value instanceof Array && value.length > 0) { + const permissions = useUserStore().permissions + const permissionDatas = value + const all_permission = "*:*:*" + + const hasPermission = permissions.some((permission: string) => { + return all_permission === permission || permissionDatas.includes(permission) + }) + + if (!hasPermission) { + return false + } + return true + } else { + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) + return false + } +} + +/** + * 角色权限校验 + * @param value 校验值 + * @returns {Boolean} + */ +export function checkRole(value: string[]): boolean { + if (!value || !(value instanceof Array) || value.length === 0) { + console.error(`need roles! Like checkRole="['admin','editor']"`) + return false + } + + const userStore = useUserStore() + const roles = userStore.roles + const permissionRoles = value + + // 取出 roleKey 进行判断 + const hasRole = roles.some((role: any) => { + const roleKey = role.roleKey || '' + // 是超级管理员 admin 或者 包含指定角色 + return roleKey === 'admin' || permissionRoles.includes(roleKey) + }) + + return hasRole +} + + +/** + * 管理员、检测机构权限校验 + * @param value 校验值 + * @returns {Boolean} + */ +export function checkDeptRole(value: string[]): boolean { + if (!value || !(value instanceof Array) || value.length === 0) { + console.error(`need roles! Like checkRole="['admin','editor']"`) + return false + } + + const userStore = useUserStore() + const roles = userStore.roles + const permissionRoles = value + const deptParam = userStore.deptParam + // 取出 roleKey 进行判断 + const hasRole = roles.some((role: any) => { + const roleKey = role.roleKey || '' + // 是超级管理员 admin 或者 包含指定角色 、 检测机构人员权限 203 + return roleKey === 'admin' || permissionRoles.includes(roleKey) || deptParam.parentId == 203 + }) + return hasRole +} diff --git a/src/utils/pinyin.ts b/src/utils/pinyin.ts new file mode 100644 index 0000000..c626b3d --- /dev/null +++ b/src/utils/pinyin.ts @@ -0,0 +1,60 @@ +// pinyin.ts - 拼音转换工具 +// src/utils/pinyin-utils.ts +import { + pinyin +} from 'pinyin-pro'; + +/** + * 获取文本的拼音首字母 + * @param {string} text - 要转换的文本 + * @returns {string} - 拼音首字母字符串 + */ +export function getFirstLetter(text: string): string { + if (!text) return ''; + + // 获取拼音(不带音调,数组形式) + const pinyinArray = pinyin(text, { + toneType: "none", + type: "array" + }) as string[]; + + // 提取每个拼音的首字母并连接 + return pinyinArray.map(word => word.charAt(0)).join(''); +} + +/** + * 获取文本的拼音全拼 + * @param {string} text - 要转换的文本 + * @returns {string} - 拼音全拼字符串 + */ +export function getFullPinyin(text: string): string { + if (!text) return ''; + + // 获取拼音(不带音调,字符串形式) + return pinyin(text, { + toneType: "none" + }).replace(/\s+/g, ''); +} + +interface Option { + id: string | number; + text: string; +} + +interface ProcessedOption extends Option { + pinyin: string; + firstLetters: string; +} + +/** + * 处理选项数据,添加拼音和首字母属性 + * @param {Option[]} options - 原始选项数组,每个元素包含id和text属性 + * @returns {ProcessedOption[]} - 处理后的选项数组,每个元素包含pinyin和firstLetters属性 + */ +export function processOptions(options: Option[]): ProcessedOption[] { + return options.map(option => ({ + ...option, + pinyin: getFullPinyin(option.text), + firstLetters: getFirstLetter(option.text) + })); +} \ No newline at end of file diff --git a/src/utils/polyfills.ts b/src/utils/polyfills.ts new file mode 100644 index 0000000..0e9a14a --- /dev/null +++ b/src/utils/polyfills.ts @@ -0,0 +1,21 @@ +// 检测浏览器是否支持,不支持则注入兼容版 +if (!Array.prototype.findLastIndex) { + Object.defineProperty(Array.prototype, 'findLastIndex', { + value: function ( + this: T[], + callback: (value: T, index: number, array: T[]) => boolean, + thisArg?: any + ): number { + const arr = this; + const len = arr.length >>> 0; + for (let i = len - 1; i >= 0; i--) { + if (i in arr && callback.call(thisArg, arr[i], i, arr)) { + return i; + } + } + return -1; + }, + writable: true, + configurable: true + }); +} \ No newline at end of file diff --git a/src/utils/printHelper.ts b/src/utils/printHelper.ts new file mode 100644 index 0000000..d604e3b --- /dev/null +++ b/src/utils/printHelper.ts @@ -0,0 +1,47 @@ +/** + * @file printHelper.ts 检查打印服务连接状态 + * @author: w + * @since: 2026-01-26 + */ + +import { ElMessageBox, ElMessage } from 'element-plus' +import Download from '@/plugins/download' +import { getWebSocketState } from '@/utils/webSocket'; + +export const checkPrintService = async (): Promise => { //连接成功返回true,失败返回false + try { + if (getWebSocketState() == 1) { // 1表示WebSocket已连接 + return true + } else { + await showPrintServiceError('打印服务未连接') + return false + } + } catch (error) { + console.error('打印服务检查失败:', error) + await showPrintServiceError('打印服务连接异常') + return false + } +} + + +export const showPrintServiceError = async (msg: string) => { + // 挂载downExe方法到window,供提示框内的onclick调用 + if (!(window as any).downExe) { + (window as any).downExe = () => { + Download.name('lisprintsetup.exe', false) + } + } + + await ElMessageBox.alert( + `${msg}!
请确保目标服务器已下载安装 并运行打印服务!`, + "客户端未连接", + { + dangerouslyUseHTMLString: true, + } + ) +} + +//触发打印服务安装包下载 +export const downloadPrintSetup = () => { + Download.name('lisprintsetup.exe', false) +} \ No newline at end of file diff --git a/src/utils/rem.ts b/src/utils/rem.ts new file mode 100644 index 0000000..c1fec08 --- /dev/null +++ b/src/utils/rem.ts @@ -0,0 +1,22 @@ +// src/utils/rem.ts +export const setupRem = () => { + const baseDesignWidth = 1920; // 设计稿宽度 + const baseFontSize = 16; // 设计稿对应根字体大小(1rem = 16px) + const minWidth = 1280; // 最小支持宽度(避免过小屏幕错乱) + + const updateRem = () => { + // 获取当前屏幕宽度(取可视区域宽度) + const clientWidth = document.documentElement.clientWidth || window.innerWidth; + // 限制最小宽度 + const actualWidth = Math.max(clientWidth, minWidth); + // 计算当前根字体大小(按比例缩放) + const currentFontSize = (actualWidth / baseDesignWidth) * baseFontSize; + // 设置到 html 元素 + document.documentElement.style.fontSize = `${currentFontSize.toFixed(2)}px`; + }; + + // 初始化计算 + updateRem(); + // 监听窗口尺寸变化,实时更新 + window.addEventListener('resize', updateRem); +}; \ No newline at end of file diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..8af479d --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,175 @@ +import axios from 'axios' +import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from '@/utils/ruoyi' +import cache from '@/plugins/cache' +import { saveAs } from 'file-saver' +import useUserStore from '@/store/modules/user' + +let downloadLoadingInstance: ReturnType +// 是否显示重新登录 +export const isRelogin = { show: false } + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: import.meta.env.VITE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use((config: any) => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + // 间隔时间(ms),小于此时间视为重复提交 + const interval = (config.headers || {}).interval || 1000 + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params) + url = url.slice(0, -1) + config.params = {} + config.url = url + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小 + const limitSize = 5 * 1024 * 1024 // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url // 请求地址 + const s_data = sessionObj.data // 请求数据 + const s_time = sessionObj.time // 请求时间 + 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.setJSON('sessionObj', requestObj) + } + } + } + return config +}, (error: any) => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use((res: any) => { + // 未设置状态码则默认成功状态 + // 未设置状态码则默认成功状态 + const code = (() => { + const rawCode = res.data.code; + + // 如果是 null/undefined,返回默认值 200 + if (rawCode === null || rawCode === undefined) { + return 200; + } + + // 如果是字符串,尝试转换为数字 + if (typeof rawCode === 'string') { + const numericCode = Number(rawCode); + return isNaN(numericCode) ? 200 : numericCode; + } + + // 如果已经是数字,直接返回 + if (typeof rawCode === 'number') { + return rawCode; + } + + // 其他情况,返回默认值 + return 200; + })(); + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code === 401) { + if (!isRelogin.show) { + isRelogin.show = true + ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false + useUserStore().logOut().then(() => { + location.href = '/index' + }) + }).catch(() => { + isRelogin.show = false + }) + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + } else if (code === 500) { + ElMessage({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + ElMessage({ message: msg, type: 'warning' }) + return Promise.reject(new Error(msg)) + } else if (code !== 200 && code !== 0) { + ElNotification.error({ title: msg }) + return Promise.reject('error') + } else { + return Promise.resolve(res.data) + } +}, + (error: any) => { + console.log('err' + error) + let { message } = error + if (message == "Network Error") { + message = "后端接口连接异常" + } else if (message.includes("timeout")) { + message = "系统接口请求超时" + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.slice(-3) + "异常" + } + ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url: string, params: any, filename: string, config?: any) { + downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params: any) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob', + ...config + }).then(async (data: any) => { + const isBlob = blobValidate(data) + if (isBlob) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text() + const rspObj = JSON.parse(resText) + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + ElMessage.error(errMsg) + } + downloadLoadingInstance.close() + }).catch((r: any) => { + console.error(r) + ElMessage.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close() + }) +} + +export default service diff --git a/src/utils/ruoyi.ts b/src/utils/ruoyi.ts new file mode 100644 index 0000000..9724779 --- /dev/null +++ b/src/utils/ruoyi.ts @@ -0,0 +1,230 @@ +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time: any, pattern?: string): string | null { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date: Date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '') + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj: Record = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 表单重置 +export function resetForm(refName: string): void { + // @ts-ignore + if ((this as any).$refs[refName]) { + // @ts-ignore + (this as any).$refs[refName].resetFields() + } +} + +// 添加日期范围 +export function addDateRange(params: any, dateRange: string[], propName?: string): any { + let search = params + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {} + dateRange = Array.isArray(dateRange) ? dateRange : [] + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0] + search.params['endTime'] = dateRange[1] + } else { + search.params['begin' + propName] = dateRange[0] + search.params['end' + propName] = dateRange[1] + } + return search +} + +// 回显数据字典 +export function selectDictLabel(datas: any, value: any): string { + if (value === undefined) { + return "" + } + const actions: string[] = [] + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label) + return true + } + }) + if (actions.length === 0) { + actions.push(value) + } + return actions.join('') +} + +// 回显数据字典(字符串、数组) +export function selectDictLabels(datas: any, value: any, separator?: string): string { + if (value === undefined || value.length === 0) { + return "" + } + if (Array.isArray(value)) { + value = value.join(",") + } + const actions: string[] = [] + const currentSeparator = undefined === separator ? "," : separator + const temp = value.split(currentSeparator) + Object.keys(value.split(currentSeparator)).some((val) => { + let match = false + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator) + match = true + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator) + } + }) + return actions.join('').substring(0, actions.join('').length - 1) +} + +// 字符串格式化(%s ) +export function sprintf(str: string, ...args: any[]): string { + let flag = true, i = 1 + str = str.replace(/%s/g, function () { + const arg = args[i++] + if (typeof arg === 'undefined') { + flag = false + return '' + } + return arg + }) + return flag ? str : '' +} + +// 转换字符串,undefined,null等转化为"" +export function parseStrEmpty(str: any): string { + if (!str || str == "undefined" || str == "null") { + return "" + } + return str +} + +// 数据合并 +export function mergeRecursive(source: any, target: any): any { + for (const p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]) + } else { + source[p] = target[p] + } + } catch (e) { + source[p] = target[p] + } + } + return source +} + +/** + * 构造树型结构数据 + * @param data 数据源 + * @param id id字段 默认 'id' + * @param parentId 父节点字段 默认 'parentId' + * @param children 孩子节点字段 默认 'children' + */ +export function handleTree(data: any[], id?: string, parentId?: string, children?: string): any[] { + const config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + } + + const childrenListMap: Record = {} + const tree: any[] = [] + for (const d of data) { + const id = d[config.id] + childrenListMap[id] = d + if (!d[config.childrenList]) { + d[config.childrenList] = [] + } + } + + for (const d of data) { + const parentId = d[config.parentId] + const parentObj = childrenListMap[parentId] + if (!parentObj) { + tree.push(d) + } else { + parentObj[config.childrenList].push(d) + } + } + return tree +} + +/** +* 参数处理 +* @param params 参数 +*/ +export function tansParams(params: Record): string { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName] + const part = encodeURIComponent(propName) + "=" + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + const params = propName + '[' + key + ']' + const subPart = encodeURIComponent(params) + "=" + result += subPart + encodeURIComponent(value[key]) + "&" + } + } + } else { + result += part + encodeURIComponent(value) + "&" + } + } + } + return result +} + +// 返回项目路径 +export function getNormalPath(p: string): string { + if (p.length === 0 || !p || p == 'undefined') { + return p + } + let res = p.replace('//', '/') + if (res[res.length - 1] === '/') { + return res.slice(0, res.length - 1) + } + return res +} + +// 验证是否为blob格式 +export function blobValidate(data: Blob): boolean { + return data.type !== 'application/json' +} diff --git a/src/utils/scroll-to.ts b/src/utils/scroll-to.ts new file mode 100644 index 0000000..9078347 --- /dev/null +++ b/src/utils/scroll-to.ts @@ -0,0 +1,64 @@ +declare global { + interface Math { + easeInOutQuad(t: number, b: number, c: number, d: number): number + } +} + +Math.easeInOutQuad = function(t: number, b: number, c: number, d: number): number { + t /= d / 2 + if (t < 1) { + return c / 2 * t * t + b + } + t-- + return -c / 2 * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +const requestAnimFrame = (function() { + return window.requestAnimationFrame || (window as any).webkitRequestAnimationFrame || (window as any).mozRequestAnimationFrame || function(callback: FrameRequestCallback) { window.setTimeout(callback, 1000 / 60) } +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param amount + */ +function move(amount: number): void { + document.documentElement.scrollTop = amount + ;(document.body.parentNode as HTMLElement).scrollTop = amount + document.body.scrollTop = amount +} + +function position(): number { + return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop +} + +/** + * @param to + * @param duration + * @param callback + */ +export function scrollTo(to: number, duration?: number, callback?: () => void): void { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = (typeof (duration) === 'undefined') ? 500 : duration + const animateScroll = function() { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + const val = Math.easeInOutQuad(currentTime, start, change, duration!) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof (callback) === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/src/utils/theme.ts b/src/utils/theme.ts new file mode 100644 index 0000000..3e2f518 --- /dev/null +++ b/src/utils/theme.ts @@ -0,0 +1,49 @@ +// 处理主题样式 +export function handleThemeStyle(theme: string): void { + document.documentElement.style.setProperty('--el-color-primary', theme) + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`) + } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`) + } +} + +// hex颜色转rgb颜色 +export function hexToRgb(str: string): number[] { + str = str.replace('#', '') + const hexs = str.match(/../g) || [] + for (let i = 0; i < 3; i++) { + hexs[i] = String(parseInt(hexs[i], 16)) + } + return hexs.map(h => parseInt(h)) +} + +// rgb颜色转Hex颜色 +export function rgbToHex(r: number, g: number, b: number): string { + const hexs = [r.toString(16), g.toString(16), b.toString(16)] + for (let i = 0; i < 3; i++) { + if (hexs[i].length == 1) { + hexs[i] = `0${hexs[i]}` + } + } + return `#${hexs.join('')}` +} + +// 变浅颜色值 +export function getLightColor(color: string, level: number): string { + const rgb = hexToRgb(color) + for (let i = 0; i < 3; i++) { + rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]) + } + return rgbToHex(rgb[0], rgb[1], rgb[2]) +} + +// 变深颜色值 +export function getDarkColor(color: string, level: number): string { + const rgb = hexToRgb(color) + for (let i = 0; i < 3; i++) { + rgb[i] = Math.floor(rgb[i] * (1 - level)) + } + return rgbToHex(rgb[0], rgb[1], rgb[2]) +} diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..ed99eee --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,114 @@ +/** + * 路径匹配器 + * @param pattern + * @param path + * @returns {Boolean} + */ +export function isPathMatch(pattern: string, path: string): boolean { + const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*') + const regex = new RegExp(`^${regexPattern}$`) + return regex.test(path) +} + +/** + * 判断value字符串是否为空 + * @param value + * @returns {Boolean} + */ +export function isEmpty(value: any): boolean { + if (value == null || value == "" || value == undefined || value == "undefined") { + return true + } + return false +} + +/** + * 判断url是否是http或https + * @param url + * @returns {Boolean} + */ +export function isHttp(url: string): boolean { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 +} + +/** + * 判断path是否为外链 + * @param path + * @returns {Boolean} + */ +export function isExternal(path: string): boolean { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @param str + * @returns {Boolean} + */ +export function validUsername(str: string): boolean { + const valid_map = ['admin', 'editor'] + return valid_map.indexOf(str.trim()) >= 0 +} + +/** + * @param url + * @returns {Boolean} + */ +export function validURL(url: string): boolean { + const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ + return reg.test(url) +} + +/** + * @param str + * @returns {Boolean} + */ +export function validLowerCase(str: string): boolean { + const reg = /^[a-z]+$/ + return reg.test(str) +} + +/** + * @param str + * @returns {Boolean} + */ +export function validUpperCase(str: string): boolean { + const reg = /^[A-Z]+$/ + return reg.test(str) +} + +/** + * @param str + * @returns {Boolean} + */ +export function validAlphabets(str: string): boolean { + const reg = /^[A-Za-z]+$/ + return reg.test(str) +} + +/** + * @param email + * @returns {Boolean} + */ +export function validEmail(email: string): boolean { + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return reg.test(email) +} + +/** + * @param str + * @returns {Boolean} + */ +export function isString(str: any): boolean { + return typeof str === 'string' || str instanceof String +} + +/** + * @param arg + * @returns {Boolean} + */ +export function isArray(arg: any): boolean { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]' + } + return Array.isArray(arg) +} diff --git a/src/utils/webSocket.ts b/src/utils/webSocket.ts new file mode 100644 index 0000000..f19309b --- /dev/null +++ b/src/utils/webSocket.ts @@ -0,0 +1,103 @@ +// WebSocket连接配置 +type WebSocketOptions = { + fullUrl: string; + onMessage?: (data: string) => void; + onOpen?: () => void; + onError?: (error: Event) => void; + onClose?: () => void; + reconnectInterval?: number; +}; + +let websocket: WebSocket | null = null; +let reconnectTimer: any = null; +let isManualClose = false; + +// 初始化WebSocket连接 +export function initWebSocket(options: WebSocketOptions) { + const { + fullUrl, + onMessage, + onOpen, + onError, + onClose, + reconnectInterval = 3000 + } = options; + + // 关闭旧连接 + if (websocket) { + websocket.close(); + } + + // 检查浏览器支持性 + if (!('WebSocket' in window)) { + console.error('当前浏览器不支持WebSocket'); + return; + } + + // 创建WebSocket连接 + try { + websocket = new WebSocket(fullUrl); + + // 连接成功回调 + websocket.onopen = () => { + clearTimeout(reconnectTimer!); + isManualClose = false; + onOpen?.(); + }; + + // 接收消息回调 + websocket.onmessage = (event) => { + onMessage?.(event.data); + }; + + // 错误回调 + websocket.onerror = (error) => { + onError?.(error); + startReconnect(() => initWebSocket(options), reconnectInterval); + }; + + // 关闭回调 + websocket.onclose = () => { + onClose?.(); + if (!isManualClose) { + startReconnect(() => initWebSocket(options), reconnectInterval); + } + }; + } catch (error) { + console.error('WebSocket连接创建失败:', error); + startReconnect(() => initWebSocket(options), reconnectInterval); + } +} + +// 发送消息 +export function sendWebSocketMessage(message: any): boolean { + if (websocket && websocket.readyState === WebSocket.OPEN) { + websocket.send(JSON.stringify(message)); + return true; + } + console.error('WebSocket未连接,无法发送消息'); + return false; +} + +// 手动关闭WebSocket +export function closeWebSocket(): void { + isManualClose = true; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + } + websocket?.close(); + websocket = null; +} + +// 启动重连 +function startReconnect(connectCallback: () => void, interval: number): void { + if (reconnectTimer) { + clearTimeout(reconnectTimer); + } + reconnectTimer = setTimeout(connectCallback, interval); +} + +// 获取当前连接状态 +export function getWebSocketState(): number | null { + return websocket?.readyState || null; +} \ No newline at end of file diff --git a/src/views/blood/deptPatient/components/bloodTransfusionApplication.vue b/src/views/blood/deptPatient/components/bloodTransfusionApplication.vue new file mode 100644 index 0000000..84bad7d --- /dev/null +++ b/src/views/blood/deptPatient/components/bloodTransfusionApplication.vue @@ -0,0 +1,645 @@ +/** +* @file bloodTransfusionApplication.vue +* @description: 输血申请 +* @author: w +* @since: 2026-06-04 +*/ + + + + + + \ No newline at end of file diff --git a/src/views/blood/deptPatient/components/bloodTransfusionAssessment.vue b/src/views/blood/deptPatient/components/bloodTransfusionAssessment.vue new file mode 100644 index 0000000..e93a98a --- /dev/null +++ b/src/views/blood/deptPatient/components/bloodTransfusionAssessment.vue @@ -0,0 +1,550 @@ +/** +* @file bloodTransfusionAssessment.vue +* @description: 输血评估 +* @author: w +* @since: 2026-06-04 +*/ + + + + + + \ No newline at end of file diff --git a/src/views/blood/deptPatient/index.vue b/src/views/blood/deptPatient/index.vue new file mode 100644 index 0000000..c052fa1 --- /dev/null +++ b/src/views/blood/deptPatient/index.vue @@ -0,0 +1,669 @@ +/** +* @file index.vue +* @description: 科室病人 +* @author: w +* @since: 2026-06-03 +*/ + + + + + + \ No newline at end of file diff --git a/src/views/blood/reactionRegist/index.vue b/src/views/blood/reactionRegist/index.vue new file mode 100644 index 0000000..3e96227 --- /dev/null +++ b/src/views/blood/reactionRegist/index.vue @@ -0,0 +1,308 @@ +/** +* @file index.vue +* @description: 反应登记 +* @author: w +* @since: 2026-06-03 +*/ + + + + + + + \ No newline at end of file diff --git a/src/views/blood/receiveBlood/index.vue b/src/views/blood/receiveBlood/index.vue new file mode 100644 index 0000000..cae5f6e --- /dev/null +++ b/src/views/blood/receiveBlood/index.vue @@ -0,0 +1,336 @@ +/** +* @file index.vue +* @description: 接收血液确认 +* @author: w +* @since: 2026-06-08 +*/ + + + + + + \ No newline at end of file diff --git a/src/views/blood/record/index.vue b/src/views/blood/record/index.vue new file mode 100644 index 0000000..24406a4 --- /dev/null +++ b/src/views/blood/record/index.vue @@ -0,0 +1,358 @@ +/** +* @file index.vue +* @description: 输血输注 +* @author: w +* @since: 2026-06-04 +*/ + + + \ No newline at end of file diff --git a/src/views/blood/review/index.vue b/src/views/blood/review/index.vue new file mode 100644 index 0000000..3b0b94d --- /dev/null +++ b/src/views/blood/review/index.vue @@ -0,0 +1,164 @@ +/** +* @file index.vue +* @description: 审核 +* @author: w +* @since: 2026-06-04 +*/ + + + + + + \ No newline at end of file diff --git a/src/views/error/401.vue b/src/views/error/401.vue new file mode 100644 index 0000000..ca11273 --- /dev/null +++ b/src/views/error/401.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/views/error/404.vue b/src/views/error/404.vue new file mode 100644 index 0000000..87b822b --- /dev/null +++ b/src/views/error/404.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/src/views/index.vue b/src/views/index.vue new file mode 100644 index 0000000..40035b7 --- /dev/null +++ b/src/views/index.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..4c4e34d --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,439 @@ + + + + + \ No newline at end of file diff --git a/src/views/monitor/cache/index.vue b/src/views/monitor/cache/index.vue new file mode 100644 index 0000000..1c33d20 --- /dev/null +++ b/src/views/monitor/cache/index.vue @@ -0,0 +1,132 @@ + + + diff --git a/src/views/monitor/cache/list.vue b/src/views/monitor/cache/list.vue new file mode 100644 index 0000000..7451fc1 --- /dev/null +++ b/src/views/monitor/cache/list.vue @@ -0,0 +1,259 @@ + + + diff --git a/src/views/monitor/druid/index.vue b/src/views/monitor/druid/index.vue new file mode 100644 index 0000000..d688394 --- /dev/null +++ b/src/views/monitor/druid/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/views/monitor/job/index.vue b/src/views/monitor/job/index.vue new file mode 100644 index 0000000..f8fef8d --- /dev/null +++ b/src/views/monitor/job/index.vue @@ -0,0 +1,450 @@ + + + diff --git a/src/views/monitor/job/log.vue b/src/views/monitor/job/log.vue new file mode 100644 index 0000000..4c03a08 --- /dev/null +++ b/src/views/monitor/job/log.vue @@ -0,0 +1,285 @@ + + + diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue new file mode 100644 index 0000000..72a0b2c --- /dev/null +++ b/src/views/monitor/logininfor/index.vue @@ -0,0 +1,234 @@ + + + diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue new file mode 100644 index 0000000..8bfc34c --- /dev/null +++ b/src/views/monitor/online/index.vue @@ -0,0 +1,98 @@ + + + diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue new file mode 100644 index 0000000..b15e3bb --- /dev/null +++ b/src/views/monitor/operlog/index.vue @@ -0,0 +1,311 @@ + + + diff --git a/src/views/monitor/server/index.vue b/src/views/monitor/server/index.vue new file mode 100644 index 0000000..95209aa --- /dev/null +++ b/src/views/monitor/server/index.vue @@ -0,0 +1,187 @@ + + + diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue new file mode 100644 index 0000000..542ae6a --- /dev/null +++ b/src/views/redirect/index.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/views/register.vue b/src/views/register.vue new file mode 100644 index 0000000..0a8b37b --- /dev/null +++ b/src/views/register.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue new file mode 100644 index 0000000..b093ed2 --- /dev/null +++ b/src/views/system/config/index.vue @@ -0,0 +1,262 @@ + + + diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue new file mode 100644 index 0000000..1695b68 --- /dev/null +++ b/src/views/system/dept/index.vue @@ -0,0 +1,254 @@ + + + diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue new file mode 100644 index 0000000..a7ffafc --- /dev/null +++ b/src/views/system/dict/data.vue @@ -0,0 +1,368 @@ + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue new file mode 100644 index 0000000..dbe36bf --- /dev/null +++ b/src/views/system/dict/index.vue @@ -0,0 +1,265 @@ + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..4f2e1bc --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,387 @@ + + + diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue new file mode 100644 index 0000000..fa07b52 --- /dev/null +++ b/src/views/system/notice/index.vue @@ -0,0 +1,246 @@ + + + diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue new file mode 100644 index 0000000..9165f07 --- /dev/null +++ b/src/views/system/post/index.vue @@ -0,0 +1,245 @@ + + + diff --git a/src/views/system/role/authUser.vue b/src/views/system/role/authUser.vue new file mode 100644 index 0000000..64d9cdb --- /dev/null +++ b/src/views/system/role/authUser.vue @@ -0,0 +1,180 @@ + + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..1efe12d --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,516 @@ + + + diff --git a/src/views/system/role/selectUser.vue b/src/views/system/role/selectUser.vue new file mode 100644 index 0000000..757b87e --- /dev/null +++ b/src/views/system/role/selectUser.vue @@ -0,0 +1,145 @@ + + + diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue new file mode 100644 index 0000000..96ffc34 --- /dev/null +++ b/src/views/system/user/authRole.vue @@ -0,0 +1,130 @@ + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..bb400a3 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,596 @@ + + + diff --git a/src/views/system/user/profile/index.vue b/src/views/system/user/profile/index.vue new file mode 100644 index 0000000..e4ab3da --- /dev/null +++ b/src/views/system/user/profile/index.vue @@ -0,0 +1,109 @@ + + + diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue new file mode 100644 index 0000000..93a81c7 --- /dev/null +++ b/src/views/system/user/profile/resetPwd.vue @@ -0,0 +1,68 @@ + + + diff --git a/src/views/system/user/profile/userAvatar.vue b/src/views/system/user/profile/userAvatar.vue new file mode 100644 index 0000000..01cae89 --- /dev/null +++ b/src/views/system/user/profile/userAvatar.vue @@ -0,0 +1,180 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/user/profile/userInfo.vue b/src/views/system/user/profile/userInfo.vue new file mode 100644 index 0000000..d60fc7e --- /dev/null +++ b/src/views/system/user/profile/userInfo.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/views/tool/build/CodeTypeDialog.vue b/src/views/tool/build/CodeTypeDialog.vue new file mode 100644 index 0000000..2b1fef2 --- /dev/null +++ b/src/views/tool/build/CodeTypeDialog.vue @@ -0,0 +1,71 @@ + + + \ No newline at end of file diff --git a/src/views/tool/build/DraggableItem.vue b/src/views/tool/build/DraggableItem.vue new file mode 100644 index 0000000..3b0fe2b --- /dev/null +++ b/src/views/tool/build/DraggableItem.vue @@ -0,0 +1,68 @@ + + diff --git a/src/views/tool/build/IconsDialog.vue b/src/views/tool/build/IconsDialog.vue new file mode 100644 index 0000000..d1b2dac --- /dev/null +++ b/src/views/tool/build/IconsDialog.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/views/tool/build/RightPanel.vue b/src/views/tool/build/RightPanel.vue new file mode 100644 index 0000000..672f63c --- /dev/null +++ b/src/views/tool/build/RightPanel.vue @@ -0,0 +1,923 @@ + + + + + \ No newline at end of file diff --git a/src/views/tool/build/TreeNodeDialog.vue b/src/views/tool/build/TreeNodeDialog.vue new file mode 100644 index 0000000..882c7ad --- /dev/null +++ b/src/views/tool/build/TreeNodeDialog.vue @@ -0,0 +1,95 @@ + + diff --git a/src/views/tool/build/index.vue b/src/views/tool/build/index.vue new file mode 100644 index 0000000..7a06694 --- /dev/null +++ b/src/views/tool/build/index.vue @@ -0,0 +1,663 @@ + + + + + diff --git a/src/views/tool/gen/basicInfoForm.vue b/src/views/tool/gen/basicInfoForm.vue new file mode 100644 index 0000000..4af82a7 --- /dev/null +++ b/src/views/tool/gen/basicInfoForm.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/views/tool/gen/createTable.vue b/src/views/tool/gen/createTable.vue new file mode 100644 index 0000000..5a2250e --- /dev/null +++ b/src/views/tool/gen/createTable.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/views/tool/gen/editTable.vue b/src/views/tool/gen/editTable.vue new file mode 100644 index 0000000..dfae521 --- /dev/null +++ b/src/views/tool/gen/editTable.vue @@ -0,0 +1,216 @@ + + + diff --git a/src/views/tool/gen/genInfoForm.vue b/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 0000000..db38f29 --- /dev/null +++ b/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,309 @@ + + + diff --git a/src/views/tool/gen/importTable.vue b/src/views/tool/gen/importTable.vue new file mode 100644 index 0000000..8eaedb5 --- /dev/null +++ b/src/views/tool/gen/importTable.vue @@ -0,0 +1,127 @@ + + + diff --git a/src/views/tool/gen/index.vue b/src/views/tool/gen/index.vue new file mode 100644 index 0000000..cf5f5df --- /dev/null +++ b/src/views/tool/gen/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/src/views/tool/swagger/index.vue b/src/views/tool/swagger/index.vue new file mode 100644 index 0000000..02dcbb4 --- /dev/null +++ b/src/views/tool/swagger/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e85d6f8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["esnext", "dom"], + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, + + // 严格性和类型检查相关配置 + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + // 模块和兼容性相关配置 + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true, + + // 调试和兼容性相关配置 + "sourceMap": true, + "useDefineForClassFields": true, + "allowJs": true, + "noEmit": true, + + // 类型声明相关配置 + "types": ["vue", "node", "vite/client", "element-plus/global"], + "skipDefaultLibCheck": true + }, + "include": [ + "src/**/*","src/**/*.ts", "src/**/*.vue","src/**/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "auto-imports.d.ts" + ], + "exclude": ["node_modules", "dist", "src/**/*.js"] +} + diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..94a86bb --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,80 @@ +import { defineConfig, loadEnv } from "vite"; +import path from "path"; +import createVitePlugins from "./vite/plugins"; + +const baseUrl = "http://localhost:8080"; // 后端接口 + +// https://vitejs.dev/config/ +export default defineConfig(({ mode, command }) => { + const env = loadEnv(mode, process.cwd()); + const { VITE_APP_ENV } = env; + return { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + base: VITE_APP_ENV === "production" ? "/" : "/", + plugins: createVitePlugins(env, command === "build"), + resolve: { + // https://cn.vitejs.dev/config/#resolve-alias + alias: { + // 设置路径 + "~": path.resolve(__dirname, "./"), + // 设置别名 + "@": path.resolve(__dirname, "./src"), + }, + // https://cn.vitejs.dev/config/#resolve-extensions + extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"], + }, + // 打包配置 + build: { + // https://vite.dev/config/build-options.html + sourcemap: command === "build" ? false : "inline", + outDir: "dist", + assetsDir: "assets", + chunkSizeWarningLimit: 2000, + rollupOptions: { + output: { + chunkFileNames: "static/js/[name]-[hash].js", + entryFileNames: "static/js/[name]-[hash].js", + assetFileNames: "static/[ext]/[name]-[hash].[ext]", + }, + }, + }, + // vite 相关配置 + server: { + port: 5174, + host: true, + open: false, + proxy: { + // https://cn.vitejs.dev/config/#server-proxy + "/dev-api": { + // target: "http://47.97.125.165:8905", + target: "http://47.97.125.165:8907", + changeOrigin: true, + rewrite: (p) => p.replace(/^\/dev-api/, ""), + }, + // springdoc proxy + "^/v3/api-docs/(.*)": { + target: baseUrl, + changeOrigin: true, + }, + }, + }, + css: { + postcss: { + plugins: [ + { + postcssPlugin: "internal:charset-removal", + AtRule: { + charset: (atRule: any) => { + if (atRule.name === "charset") { + atRule.remove(); + } + }, + }, + }, + ], + }, + }, + }; +}); diff --git a/vite/plugins/auto-import.ts b/vite/plugins/auto-import.ts new file mode 100644 index 0000000..c1d0fc4 --- /dev/null +++ b/vite/plugins/auto-import.ts @@ -0,0 +1,13 @@ +import autoImport from 'unplugin-auto-import/vite' + +export default function createAutoImport() { + return autoImport({ + imports: [ + 'vue', + 'vue-router', + 'pinia' + ], + dts: true + }) +} + diff --git a/vite/plugins/compression.ts b/vite/plugins/compression.ts new file mode 100644 index 0000000..3201198 --- /dev/null +++ b/vite/plugins/compression.ts @@ -0,0 +1,29 @@ +import { PluginOption } from 'vite' +import compression from 'vite-plugin-compression' + +export default function createCompression(env: Record) { + const { VITE_BUILD_COMPRESS } = env + const plugin: PluginOption[] = [] + if (VITE_BUILD_COMPRESS) { + const compressList = VITE_BUILD_COMPRESS.split(',') + if (compressList.includes('gzip')) { + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + plugin.push( + compression({ + ext: '.gz', + deleteOriginFile: false + }) + ) + } + if (compressList.includes('brotli')) { + plugin.push( + compression({ + ext: '.br', + algorithm: 'brotliCompress', + deleteOriginFile: false + }) + ) + } + } + return plugin +} diff --git a/vite/plugins/index.ts b/vite/plugins/index.ts new file mode 100644 index 0000000..28874fd --- /dev/null +++ b/vite/plugins/index.ts @@ -0,0 +1,16 @@ +import vue from '@vitejs/plugin-vue' + +import createAutoImport from './auto-import' +import createSvgIcon from './svg-icon' +import createCompression from './compression' +import createSetupExtend from './setup-extend' +import { PluginOption } from 'vite' + +export default function createVitePlugins(viteEnv: Record, isBuild = false) { + const vitePlugins: PluginOption[] = [vue()] + vitePlugins.push(createAutoImport()) + vitePlugins.push(createSetupExtend()) + vitePlugins.push(createSvgIcon(isBuild)) + isBuild && vitePlugins.push(...createCompression(viteEnv)) + return vitePlugins +} diff --git a/vite/plugins/setup-extend.ts b/vite/plugins/setup-extend.ts new file mode 100644 index 0000000..fb70609 --- /dev/null +++ b/vite/plugins/setup-extend.ts @@ -0,0 +1,5 @@ +import setupExtend from 'unplugin-vue-setup-extend-plus/vite' + +export default function createSetupExtend() { + return setupExtend({}) +} diff --git a/vite/plugins/svg-icon.ts b/vite/plugins/svg-icon.ts new file mode 100644 index 0000000..2862a9e --- /dev/null +++ b/vite/plugins/svg-icon.ts @@ -0,0 +1,10 @@ +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' +import path from 'path' + +export default function createSvgIcon(isBuild: boolean) { + return createSvgIconsPlugin({ + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: isBuild + }) +}