diff --git a/.gitignore b/.gitignore index 4e7cc2f..87297f6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ dist-ssr *.sln *.sw? uploads -#docs +docs #.claude plugs sillytavern diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d7f4771 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,105 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +云酒馆 (SillyTavern Cloud) — a full-stack AI role-playing chat platform. Frontend is React + TypeScript + Tailwind + Vite (`web-app/`). Backend is Go + Gin + GORM (`server/`). The two are fully separated and communicate via REST API + SSE streaming. + +## Build & Run Commands + +### Frontend (`web-app/`) +```bash +npm install +npm run dev # Dev server at http://localhost:5174 +npm run build # tsc + vite build (type-check then bundle) +npm run preview # Preview production build +``` + +### Backend (`server/`) +```bash +go mod tidy +go build -o server . +./server -c config.yaml # Runs at http://localhost:8888 +``` + +### Type-check only (no emit) +```bash +# Frontend +cd web-app && npx tsc --noEmit + +# Backend +cd server && go build ./... +``` + +## Build Verification + +After any code edit, always run the build/compile step before reporting success. For Go files: `go build ./...`. For TypeScript files: `npx tsc --noEmit`. Never assume edits are correct without verification. + +## Architecture + +### Backend (`server/`) + +Layered architecture: **routes → API handlers → services → models/DB** + +``` +api/v1/app/ # App-facing HTTP handlers (auth, character, conversation, ai_config, etc.) +api/v1/system/ # Admin/system handlers +service/app/ # Business logic layer +model/app/ # GORM data models (AppUser, AiCharacter, Conversation, Preset, Worldbook, RegexScript) +router/app/ # Gin route registration +middleware/ # JWT auth, CORS, logging, recovery +initialize/ # DB, Redis, router, plugin setup (called from main.go) +core/ # Zap logging, Viper config loading, server startup +``` + +Key models: `AppUser`, `AiCharacter` (SillyTavern V2 card format), `Conversation`, `Preset` (sampling params), `Worldbook` (keyword-triggered context), `RegexScript` (message transforms). + +AI providers: OpenAI-compatible + Anthropic. Unified through the AI config service. Conversations use SSE streaming for real-time token delivery. + +### Frontend (`web-app/src/`) + +MVU (Model-View-Update) pattern via **Zustand** with `persist` middleware (localStorage). + +``` +store/index.ts # Single global Zustand store — source of truth for user, currentCharacter, + # currentConversation, messages, variables, UI state +api/ # One file per domain: auth, character, conversation, preset, worldbook, regex + # All share the axios instance in api/client.ts (injects JWT, handles 401) +pages/ # Route-level components (ChatPage, CharacterManagePage, AdminPage, etc.) +components/ # Reusable UI — ChatArea, CharacterPanel, SettingsPanel, Sidebar, Navbar, + # MessageContent (renders markdown + HTML safely via rehype-sanitize) +``` + +Routes are defined in `App.tsx`. Auth guard redirects to `/login` when `isAuthenticated` is false. + +The `variables: Record` field in the store implements the MVU variable system used for template substitution in AI prompts (e.g., `{{user}}`, `{{char}}`). + +### API Convention + +- Base URL: `http://localhost:8888` (configured via `VITE_API_BASE_URL` in `.env.development`) +- Auth: `Authorization: Bearer ` header, injected automatically by `api/client.ts` +- App routes: `/app/auth/...`, `/app/user/...`, `/app/character/...`, `/app/conversation/...` +- Admin routes: `/system/...` (Casbin RBAC enforced) + +## Go Development + +When editing Go files, always check all call sites of modified functions. If a function signature changes (parameters or return values), grep for all usages and update them in the same edit batch. + +Run `go build ./...` from `server/` to verify after every edit. + +## Frontend Development + +When editing Vue/TypeScript frontend files, verify all imports are present and no duplicate code remains after edits. Read the file back after editing to confirm structural integrity. + +Run `npx tsc --noEmit` from `web-app/` to verify after every edit. + +## Third-Party Integrations + +When integrating third-party SDKs or APIs (payment providers, AI model APIs), read the actual SDK source or docs first before writing code. Do not guess method names, parameter types, or key sizes. + +## Configuration + +Backend config file: `server/config.yaml` (not committed; copy from `config.example.yaml` if present). Supports MySQL, PostgreSQL, SQLite, SQL Server, Oracle via GORM. Redis for sessions/caching. + +Frontend env files: `web-app/.env.development` and `web-app/.env.production` — set `VITE_API_BASE_URL`. diff --git a/web-admin/.docker-compose/nginx/conf.d/my.conf b/web-admin/.docker-compose/nginx/conf.d/my.conf new file mode 100644 index 0000000..9a1685d --- /dev/null +++ b/web-admin/.docker-compose/nginx/conf.d/my.conf @@ -0,0 +1,26 @@ +server { + listen 8080; + server_name localhost; + + #charset koi8-r; + #access_log logs/host.access.log main; + + location / { + root /usr/share/nginx/html; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + rewrite ^/api/(.*)$ /$1 break; #重写 + proxy_pass http://177.7.0.12:8888; # 设置代理服务器的协议和地址 + } + + location /api/swagger/index.html { + proxy_pass http://127.0.0.1:8888/swagger/index.html; + } + } \ No newline at end of file diff --git a/web-admin/.docker-compose/nginx/conf.d/nginx.conf b/web-admin/.docker-compose/nginx/conf.d/nginx.conf new file mode 100644 index 0000000..29f68b8 --- /dev/null +++ b/web-admin/.docker-compose/nginx/conf.d/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name localhost; + + #charset koi8-r; + #access_log logs/host.access.log main; + + location / { + root /usr/share/nginx/html/dist; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + rewrite ^/api/(.*)$ /$1 break; #重写 + proxy_pass http://127.0.0.1:8888; # 设置代理服务器的协议和地址 + } + location /form-generator { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://127.0.0.1:8888; + } + location /api/swagger/index.html { + proxy_pass http://127.0.0.1:8888/swagger/index.html; + } + } \ No newline at end of file diff --git a/web-admin/.dockerignore b/web-admin/.dockerignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/web-admin/.dockerignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/web-admin/.env.development b/web-admin/.env.development new file mode 100644 index 0000000..25ffe1f --- /dev/null +++ b/web-admin/.env.development @@ -0,0 +1,11 @@ +ENV = 'development' +VITE_CLI_PORT = 8080 +VITE_SERVER_PORT = 8888 +VITE_BASE_API = /api +VITE_FILE_API = /api +VITE_BASE_PATH = http://127.0.0.1 +VITE_POSITION = open +VITE_EDITOR = code +// VITE_EDITOR = webstorm 如果使用webstorm开发且要使用dom定位到代码行功能 请先自定添加 webstorm到环境变量 再将VITE_EDITOR值修改为webstorm +// 如果使用docker-compose开发模式,设置为下面的地址或本机主机IP +//VITE_BASE_PATH = http://177.7.0.12 diff --git a/web-admin/.env.production b/web-admin/.env.production new file mode 100644 index 0000000..9345df2 --- /dev/null +++ b/web-admin/.env.production @@ -0,0 +1,7 @@ +ENV = 'production' + +#下方为上线需要用到的程序代理前缀,一般用于nginx代理转发 +VITE_BASE_API = /api +VITE_FILE_API = /api +#下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量) +VITE_BASE_PATH = https://demo.gin-vue-admin.com diff --git a/web-admin/.gitignore b/web-admin/.gitignore new file mode 100644 index 0000000..1a4abd9 --- /dev/null +++ b/web-admin/.gitignore @@ -0,0 +1,5 @@ +node_modules/* +package-lock.json +yarn.lock +bun.lockb +config.yaml \ No newline at end of file diff --git a/web-admin/.prettierrc b/web-admin/.prettierrc new file mode 100644 index 0000000..bc61a53 --- /dev/null +++ b/web-admin/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": "none", + "bracketSpacing": true, + "arrowParens": "always", + "vueIndentScriptAndStyle": true, + "endOfLine": "lf" +} diff --git a/web-admin/Dockerfile b/web-admin/Dockerfile new file mode 100644 index 0000000..af4464e --- /dev/null +++ b/web-admin/Dockerfile @@ -0,0 +1,25 @@ +# 如果需要用 cicd ,请设置环境变量: +# variables: +# DOCKER_BUILDKIT: 1 + +FROM node:20-slim AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +COPY . /app +WORKDIR /app + + +FROM base AS prod-deps +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod + +FROM base AS build +COPY --from=prod-deps /app/node_modules /app/node_modules +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install && pnpm run build + + +FROM nginx:alpine +LABEL MAINTAINER="bypanghu@163.com" +COPY --from=base /app/.docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf +COPY --from=build /app/dist /usr/share/nginx/html +RUN ls -al /usr/share/nginx/html diff --git a/web-admin/README.md b/web-admin/README.md new file mode 100644 index 0000000..06f1a8c --- /dev/null +++ b/web-admin/README.md @@ -0,0 +1,106 @@ +# gin-vue-admin web + +## Project setup + +``` +npm install +``` + +### Compiles and hot-reloads for development + +``` +npm run serve +``` + +### Compiles and minifies for production + +``` +npm run build +``` + +### Run your tests + +``` +npm run test +``` + +### Lints and fixes files + +``` +npm run lint +``` + +整理代码结构 + +```lua +web + ├── babel.config.js + ├── Dockerfile + ├── favicon.ico + ├── index.html -- 主页面 + ├── limit.js -- 助手代码 + ├── package.json -- 包管理器代码 + ├── src -- 源代码 + │ ├── api -- api 组 + │ ├── App.vue -- 主页面 + │ ├── assets -- 静态资源 + │ ├── components -- 全局组件 + │ ├── core -- gva 组件包 + │ │ ├── config.js -- gva网站配置文件 + │ │ ├── gin-vue-admin.js -- 注册欢迎文件 + │ │ └── global.js -- 统一导入文件 + │ ├── directive -- v-auth 注册文件 + │ ├── main.js -- 主文件 + │ ├── permission.js -- 路由中间件 + │ ├── pinia -- pinia 状态管理器,取代vuex + │ │ ├── index.js -- 入口文件 + │ │ └── modules -- modules + │ │ ├── dictionary.js + │ │ ├── router.js + │ │ └── user.js + │ ├── router -- 路由声明文件 + │ │ └── index.js + │ ├── style -- 全局样式 + │ │ ├── base.scss + │ │ ├── basics.scss + │ │ ├── element_visiable.scss -- 此处可以全局覆盖 element-plus 样式 + │ │ ├── iconfont.css -- 顶部几个icon的样式文件 + │ │ ├── main.scss + │ │ ├── mobile.scss + │ │ └── newLogin.scss + │ ├── utils -- 方法包库 + │ │ ├── asyncRouter.js -- 动态路由相关 + │ │ ├── bus.js -- 全局mitt声明文件 + │ │ ├── date.js -- 日期相关 + │ │ ├── dictionary.js -- 获取字典方法 + │ │ ├── downloadImg.js -- 下载图片方法 + │ │ ├── format.js -- 格式整理相关 + │ │ ├── image.js -- 图片相关方法 + │ │ ├── page.js -- 设置页面标题 + │ │ ├── request.js -- 请求 + │ │ └── stringFun.js -- 字符串文件 + | ├── view -- 主要view代码 + | | ├── about -- 关于我们 + | | ├── dashboard -- 面板 + | | ├── error -- 错误 + | | ├── example --上传案例 + | | ├── iconList -- icon列表 + | | ├── init -- 初始化数据 + | | | ├── index -- 新版本 + | | | ├── init -- 旧版本 + | | ├── layout -- layout约束页面 + | | | ├── aside + | | | ├── bottomInfo -- bottomInfo + | | | ├── screenfull -- 全屏设置 + | | | ├── setting -- 系统设置 + | | | └── index.vue -- base 约束 + | | ├── login --登录 + | | ├── person --个人中心 + | | ├── superAdmin -- 超级管理员操作 + | | ├── system -- 系统检测页面 + | | ├── systemTools -- 系统配置相关页面 + | | └── routerHolder.vue -- page 入口页面 + ├── vite.config.js -- vite 配置文件 + └── yarn.lock + +``` diff --git a/web-admin/babel.config.js b/web-admin/babel.config.js new file mode 100644 index 0000000..b1becff --- /dev/null +++ b/web-admin/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [], + plugins: [] +} diff --git a/web-admin/eslint.config.mjs b/web-admin/eslint.config.mjs new file mode 100644 index 0000000..3b443c5 --- /dev/null +++ b/web-admin/eslint.config.mjs @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import pluginVue from 'eslint-plugin-vue' +import globals from 'globals' + +export default [ + js.configs.recommended, + ...pluginVue.configs['flat/essential'], + { + name: 'app/files-to-lint', + files: ['**/*.{js,mjs,jsx,vue}'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: globals.node + }, + rules: { + 'vue/max-attributes-per-line': 0, + 'vue/no-v-model-argument': 0, + 'vue/multi-word-component-names': 'off', + 'no-lone-blocks': 'off', + 'no-extend-native': 'off', + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }] + } + }, + { + name: 'app/files-to-ignore', + ignores: ['**/dist/**', '**/build/*.js', '**/src/assets/**', '**/public/**'] + } +] diff --git a/web-admin/index.html b/web-admin/index.html new file mode 100644 index 0000000..eeb87a2 --- /dev/null +++ b/web-admin/index.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + +
+
+
+
+
+
系统正在加载中,请稍候...
+
+
+ + + diff --git a/web-admin/jsconfig.json b/web-admin/jsconfig.json new file mode 100644 index 0000000..ca45014 --- /dev/null +++ b/web-admin/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + } + }, + "exclude": ["node_modules", "dist"], + "include": ["src/**/*"] +} diff --git a/web-admin/limit.js b/web-admin/limit.js new file mode 100644 index 0000000..585c6ce --- /dev/null +++ b/web-admin/limit.js @@ -0,0 +1,38 @@ +// 运行项目前通过node执行此脚本 (此脚本与 node_modules 目录同级) +import fs from 'fs' +import path from 'path' + +const wfPath = path.resolve(__dirname, './node_modules/.bin') + +fs.readdir(wfPath, (err, files) => { + if (err) { + console.log(err) + } else { + if (files.length !== 0) { + files.forEach((item) => { + if (item.split('.')[1] === 'cmd') { + replaceStr(`${wfPath}/${item}`, /"%_prog%"/, '%_prog%') + } + }) + } + } +}) + +// 参数:[文件路径、 需要修改的字符串、修改后的字符串] (替换对应文件内字符串的公共函数) +function replaceStr(filePath, sourceRegx, targetSrt) { + fs.readFile(filePath, (err, data) => { + if (err) { + console.log(err) + } else { + let str = data.toString() + str = str.replace(sourceRegx, targetSrt) + fs.writeFile(filePath, str, (err) => { + if (err) { + console.log(err) + } else { + console.log('\x1B[42m%s\x1B[0m', '文件修改成功') + } + }) + } + }) +} diff --git a/web-admin/openDocument.js b/web-admin/openDocument.js new file mode 100644 index 0000000..03a886b --- /dev/null +++ b/web-admin/openDocument.js @@ -0,0 +1,20 @@ +/* +此文件受版权保护,未经授权禁止修改!如果您尚未获得授权,请通过微信(shouzi_1994)联系我们以购买授权。在未授权状态下,只需保留此代码,不会影响任何正常使用。 + 未经授权的商用使用可能会被我们的资产搜索引擎爬取,并可能导致后续索赔。索赔金额将不低于高级授权费的十倍。请您遵守版权法律法规,尊重知识产权。 +*/ + +import child_process from 'child_process' + +var url = 'https://www.gin-vue-admin.com' +var cmd = '' +switch (process.platform) { + case 'win32': + cmd = 'start' + child_process.exec(cmd + ' ' + url) + break + + case 'darwin': + cmd = 'open' + child_process.exec(cmd + ' ' + url) + break +} diff --git a/web-admin/package.json b/web-admin/package.json new file mode 100644 index 0000000..8677075 --- /dev/null +++ b/web-admin/package.json @@ -0,0 +1,87 @@ +{ + "name": "gin-vue-admin", + "version": "2.9.0", + "private": true, + "scripts": { + "dev": "node openDocument.js && vite --host --mode development", + "serve": "node openDocument.js && vite --host --mode development", + "build": "vite build --mode production", + "limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build", + "preview": "vite preview", + "fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit" + }, + "type": "module", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@form-create/designer": "^3.2.6", + "@form-create/element-ui": "^3.2.10", + "@iconify/vue": "^5.0.0", + "@unocss/transformer-directives": "^66.4.2", + "@vue-office/docx": "^1.6.2", + "@vue-office/excel": "^1.7.11", + "@vue-office/pdf": "^2.0.2", + "@vueuse/core": "^11.0.3", + "@vueuse/integrations": "^12.0.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", + "ace-builds": "^1.36.4", + "axios": "1.8.2", + "chokidar": "^4.0.0", + "core-js": "^3.38.1", + "echarts": "5.5.1", + "element-plus": "^2.10.2", + "highlight.js": "^11.10.0", + "install": "^0.13.0", + "marked": "14.1.1", + "marked-highlight": "^2.1.4", + "mitt": "^3.0.1", + "npm": "^11.3.0", + "nprogress": "^0.2.0", + "path": "^0.12.7", + "pinia": "^2.2.2", + "qs": "^6.13.0", + "screenfull": "^6.0.2", + "sortablejs": "^1.15.3", + "spark-md5": "^3.0.2", + "universal-cookie": "^7", + "vform3-builds": "^3.0.10", + "vite-auto-import-svg": "^2.1.0", + "vue": "^3.5.7", + "vue-cropper": "^1.1.4", + "vue-echarts": "^7.0.3", + "vue-qr": "^4.0.9", + "vue-router": "^4.4.3", + "vue3-ace-editor": "^2.2.4", + "vue3-sfc-loader": "^0.9.5", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.25.1", + "@eslint/js": "^8.56.0", + "@unocss/extractor-svelte": "^66.4.2", + "@unocss/preset-wind3": "^66.4.2", + "@unocss/vite": "^66.5.0", + "@vitejs/plugin-legacy": "^6.0.0", + "@vitejs/plugin-vue": "^5.0.3", + "@vue/cli-plugin-babel": "~5.0.8", + "@vue/cli-plugin-eslint": "~5.0.8", + "@vue/cli-plugin-router": "~5.0.8", + "@vue/cli-plugin-vuex": "~5.0.8", + "@vue/cli-service": "~5.0.8", + "@vue/compiler-sfc": "^3.5.1", + "autoprefixer": "^10.4.20", + "babel-plugin-import": "^1.13.8", + "chalk": "^5.3.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.19.2", + "globals": "^16.3.0", + "sass": "^1.78.0", + "terser": "^5.31.6", + "vite": "^6.2.3", + "vite-check-multiple-dom": "0.2.1", + "vite-plugin-banner": "^0.8.0", + "vite-plugin-importer": "^0.2.5", + "vite-plugin-vue-devtools": "^7.0.16" + } +} diff --git a/web-admin/public/favicon.ico b/web-admin/public/favicon.ico new file mode 100644 index 0000000..ee520ce Binary files /dev/null and b/web-admin/public/favicon.ico differ diff --git a/web-admin/public/logo.png b/web-admin/public/logo.png new file mode 100644 index 0000000..468cdab Binary files /dev/null and b/web-admin/public/logo.png differ diff --git a/web-admin/src/App.vue b/web-admin/src/App.vue new file mode 100644 index 0000000..2d882ae --- /dev/null +++ b/web-admin/src/App.vue @@ -0,0 +1,46 @@ + + + + diff --git a/web-admin/src/api/api.js b/web-admin/src/api/api.js new file mode 100644 index 0000000..8ccbbaf --- /dev/null +++ b/web-admin/src/api/api.js @@ -0,0 +1,206 @@ +import service from '@/utils/request' + +// @Tags api +// @Summary 分页获取角色列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body modelInterface.PageInfo true "分页获取用户列表" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /api/getApiList [post] +// { +// page int +// pageSize int +// } +export const getApiList = (data) => { + return service({ + url: '/api/getApiList', + method: 'post', + data + }) +} + +// @Tags Api +// @Summary 创建基础api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateApiParams true "创建api" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /api/createApi [post] +export const createApi = (data) => { + return service({ + url: '/api/createApi', + method: 'post', + data + }) +} + +// @Tags menu +// @Summary 根据id获取菜单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.GetById true "根据id获取菜单" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /menu/getApiById [post] +export const getApiById = (data) => { + return service({ + url: '/api/getApiById', + method: 'post', + data + }) +} + +// @Tags Api +// @Summary 更新api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateApiParams true "更新api" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /api/updateApi [post] +export const updateApi = (data) => { + return service({ + url: '/api/updateApi', + method: 'post', + data + }) +} + +// @Tags Api +// @Summary 更新api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateApiParams true "更新api" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /api/setAuthApi [post] +export const setAuthApi = (data) => { + return service({ + url: '/api/setAuthApi', + method: 'post', + data + }) +} + +// @Tags Api +// @Summary 获取所有的Api 不分页 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /api/getAllApis [post] +export const getAllApis = (data) => { + return service({ + url: '/api/getAllApis', + method: 'post', + data + }) +} + +// @Tags Api +// @Summary 删除指定api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body dbModel.Api true "删除api" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /api/deleteApi [post] +export const deleteApi = (data) => { + return service({ + url: '/api/deleteApi', + method: 'post', + data + }) +} + +// @Tags SysApi +// @Summary 删除选中Api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "ID" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /api/deleteApisByIds [delete] +export const deleteApisByIds = (data) => { + return service({ + url: '/api/deleteApisByIds', + method: 'delete', + data + }) +} + +// FreshCasbin +// @Tags SysApi +// @Summary 刷新casbin缓存 +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "刷新成功" +// @Router /api/freshCasbin [get] +export const freshCasbin = () => { + return service({ + url: '/api/freshCasbin', + method: 'get' + }) +} + +export const syncApi = () => { + return service({ + url: '/api/syncApi', + method: 'get' + }) +} + +export const getApiGroups = () => { + return service({ + url: '/api/getApiGroups', + method: 'get' + }) +} + +export const ignoreApi = (data) => { + return service({ + url: '/api/ignoreApi', + method: 'post', + data + }) +} + +export const enterSyncApi = (data) => { + return service({ + url: '/api/enterSyncApi', + method: 'post', + data + }) +} + +/** + * 获取拥有指定API权限的角色ID列表 + * @param {string} path API路径 + * @param {string} method 请求方法 + * @returns {Promise} 角色ID数组 + */ +export const getApiRoles = (path, method) => { + return service({ + url: '/api/getApiRoles', + method: 'get', + params: { path, method } + }) +} + +/** + * 全量覆盖某API关联的角色列表 + * @param {Object} data + * @param {string} data.path API路径 + * @param {string} data.method 请求方法 + * @param {number[]} data.authorityIds 角色ID列表 + * @returns {Promise} + */ +export const setApiRoles = (data) => { + return service({ + url: '/api/setApiRoles', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/attachmentCategory.js b/web-admin/src/api/attachmentCategory.js new file mode 100644 index 0000000..58980f6 --- /dev/null +++ b/web-admin/src/api/attachmentCategory.js @@ -0,0 +1,26 @@ +import service from '@/utils/request' +// 分类列表 +export const getCategoryList = () => { + return service({ + url: '/attachmentCategory/getCategoryList', + method: 'get', + }) +} + +// 添加/编辑分类 +export const addCategory = (data) => { + return service({ + url: '/attachmentCategory/addCategory', + method: 'post', + data + }) +} + +// 删除分类 +export const deleteCategory = (data) => { + return service({ + url: '/attachmentCategory/deleteCategory', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/authority.js b/web-admin/src/api/authority.js new file mode 100644 index 0000000..919a728 --- /dev/null +++ b/web-admin/src/api/authority.js @@ -0,0 +1,113 @@ +import service from '@/utils/request' +// @Router /authority/getAuthorityList [post] +export const getAuthorityList = (data) => { + return service({ + url: '/authority/getAuthorityList', + method: 'post', + data + }) +} + +// @Summary 删除角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body {authorityId uint} true "删除角色" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /authority/deleteAuthority [post] +export const deleteAuthority = (data) => { + return service({ + url: '/authority/deleteAuthority', + method: 'post', + data + }) +} + +// @Summary 创建角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateAuthorityPatams true "创建角色" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /authority/createAuthority [post] +export const createAuthority = (data) => { + return service({ + url: '/authority/createAuthority', + method: 'post', + data + }) +} + +// @Tags authority +// @Summary 拷贝角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateAuthorityPatams true "拷贝角色" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"拷贝成功"}" +// @Router /authority/copyAuthority [post] +export const copyAuthority = (data) => { + return service({ + url: '/authority/copyAuthority', + method: 'post', + data + }) +} + +// @Summary 设置角色资源权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body sysModel.SysAuthority true "设置角色资源权限" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}" +// @Router /authority/setDataAuthority [post] +export const setDataAuthority = (data) => { + return service({ + url: '/authority/setDataAuthority', + method: 'post', + data + }) +} + +// @Summary 修改角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysAuthority true "修改角色" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}" +// @Router /authority/setDataAuthority [post] +export const updateAuthority = (data) => { + return service({ + url: '/authority/updateAuthority', + method: 'put', + data + }) +} + +/** + * 获取拥有指定角色的用户ID列表 + * @param {number} authorityId 角色ID + * @returns {Promise} 用户ID数组 + */ +export const getUsersByAuthorityId = (authorityId) => { + return service({ + url: '/authority/getUsersByAuthority', + method: 'get', + params: { authorityId } + }) +} + +/** + * 全量覆盖某角色关联的用户列表 + * @param {Object} data + * @param {number} data.authorityId 角色ID + * @param {number[]} data.userIds 用户ID列表 + * @returns {Promise} + */ +export const setRoleUsers = (data) => { + return service({ + url: '/authority/setRoleUsers', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/authorityBtn.js b/web-admin/src/api/authorityBtn.js new file mode 100644 index 0000000..e12db47 --- /dev/null +++ b/web-admin/src/api/authorityBtn.js @@ -0,0 +1,25 @@ +import service from '@/utils/request' + +export const getAuthorityBtnApi = (data) => { + return service({ + url: '/authorityBtn/getAuthorityBtn', + method: 'post', + data + }) +} + +export const setAuthorityBtnApi = (data) => { + return service({ + url: '/authorityBtn/setAuthorityBtn', + method: 'post', + data + }) +} + +export const canRemoveAuthorityBtnApi = (params) => { + return service({ + url: '/authorityBtn/canRemoveAuthorityBtn', + method: 'post', + params + }) +} diff --git a/web-admin/src/api/autoCode.js b/web-admin/src/api/autoCode.js new file mode 100644 index 0000000..8d540d3 --- /dev/null +++ b/web-admin/src/api/autoCode.js @@ -0,0 +1,242 @@ +import service from '@/utils/request' + +export const preview = (data) => { + return service({ + url: '/autoCode/preview', + method: 'post', + data + }) +} + +export const createTemp = (data) => { + return service({ + url: '/autoCode/createTemp', + method: 'post', + data + }) +} + +// @Tags SysApi +// @Summary 获取当前所有数据库 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /autoCode/getDatabase [get] +export const getDB = (params) => { + return service({ + url: '/autoCode/getDB', + method: 'get', + params + }) +} + +// @Tags SysApi +// @Summary 获取当前数据库所有表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /autoCode/getTables [get] +export const getTable = (params) => { + return service({ + url: '/autoCode/getTables', + method: 'get', + params + }) +} + +// @Tags SysApi +// @Summary 获取当前数据库所有表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /autoCode/getColumn [get] +export const getColumn = (params) => { + return service({ + url: '/autoCode/getColumn', + method: 'get', + params + }) +} + +export const getSysHistory = (data) => { + return service({ + url: '/autoCode/getSysHistory', + method: 'post', + data + }) +} + +export const rollback = (data) => { + return service({ + url: '/autoCode/rollback', + method: 'post', + data + }) +} + +export const getMeta = (data) => { + return service({ + url: '/autoCode/getMeta', + method: 'post', + data + }) +} + +export const delSysHistory = (data) => { + return service({ + url: '/autoCode/delSysHistory', + method: 'post', + data + }) +} + +export const createPackageApi = (data) => { + return service({ + url: '/autoCode/createPackage', + method: 'post', + data + }) +} + +export const getPackageApi = () => { + return service({ + url: '/autoCode/getPackage', + method: 'post' + }) +} + +export const deletePackageApi = (data) => { + return service({ + url: '/autoCode/delPackage', + method: 'post', + data + }) +} + +export const getTemplatesApi = () => { + return service({ + url: '/autoCode/getTemplates', + method: 'get' + }) +} + +export const installPlug = (data) => { + return service({ + url: '/autoCode/installPlug', + method: 'post', + data + }) +} + +export const pubPlug = (params) => { + return service({ + url: '/autoCode/pubPlug', + method: 'post', + params + }) +} + +export const llmAuto = (data) => { + return service({ + url: '/autoCode/llmAuto', + method: 'post', + data: { ...data }, + timeout: 1000 * 60 * 10, + loadingOption: { + lock: true, + fullscreen: true, + text: `小淼正在思考,请稍候...` + } + }) +} + +export const addFunc = (data) => { + return service({ + url: '/autoCode/addFunc', + method: 'post', + data + }) +} + +export const initMenu = (data) => { + return service({ + url: '/autoCode/initMenu', + method: 'post', + data + }) +} + +export const initAPI = (data) => { + return service({ + url: '/autoCode/initAPI', + method: 'post', + data + }) +} + +export const initDictionary = (data) => { + return service({ + url: '/autoCode/initDictionary', + method: 'post', + data + }) +} + +export const mcp = (data) => { + return service({ + url: '/autoCode/mcp', + method: 'post', + data + }) +} + + +export const mcpList = (data) => { + return service({ + url: '/autoCode/mcpList', + method: 'post', + data + }) +} + + +export const mcpTest = (data) => { + return service({ + url: '/autoCode/mcpTest', + method: 'post', + data + }) +} + +// @Tags SysApi +// @Summary 获取插件列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /autoCode/getPluginList [get] +export const getPluginList = (params) => { + return service({ + url: '/autoCode/getPluginList', + method: 'get', + params + }) +} + +// @Tags SysApi +// @Summary 删除插件 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /autoCode/removePlugin [post] +export const removePlugin = (params) => { + return service({ + url: '/autoCode/removePlugin', + method: 'post', + params + }) +} diff --git a/web-admin/src/api/breakpoint.js b/web-admin/src/api/breakpoint.js new file mode 100644 index 0000000..1dbfba2 --- /dev/null +++ b/web-admin/src/api/breakpoint.js @@ -0,0 +1,43 @@ +import service from '@/utils/request' +// @Summary 设置角色资源权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body sysModel.SysAuthority true "设置角色资源权限" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}" +// @Router /authority/setDataAuthority [post] + +export const findFile = (params) => { + return service({ + url: '/fileUploadAndDownload/findFile', + method: 'get', + params + }) +} + +export const breakpointContinue = (data) => { + return service({ + url: '/fileUploadAndDownload/breakpointContinue', + method: 'post', + donNotShowLoading: true, + headers: { 'Content-Type': 'multipart/form-data' }, + data + }) +} + +export const breakpointContinueFinish = (params) => { + return service({ + url: '/fileUploadAndDownload/breakpointContinueFinish', + method: 'post', + params + }) +} + +export const removeChunk = (data, params) => { + return service({ + url: '/fileUploadAndDownload/removeChunk', + method: 'post', + data, + params + }) +} diff --git a/web-admin/src/api/casbin.js b/web-admin/src/api/casbin.js new file mode 100644 index 0000000..802e130 --- /dev/null +++ b/web-admin/src/api/casbin.js @@ -0,0 +1,32 @@ +import service from '@/utils/request' +// @Tags authority +// @Summary 更改角色api权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateAuthorityPatams true "更改角色api权限" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /casbin/UpdateCasbin [post] +export const UpdateCasbin = (data) => { + return service({ + url: '/casbin/updateCasbin', + method: 'post', + data + }) +} + +// @Tags casbin +// @Summary 获取权限列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.CreateAuthorityPatams true "获取权限列表" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /casbin/getPolicyPathByAuthorityId [post] +export const getPolicyPathByAuthorityId = (data) => { + return service({ + url: '/casbin/getPolicyPathByAuthorityId', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/customer.js b/web-admin/src/api/customer.js new file mode 100644 index 0000000..4776f1c --- /dev/null +++ b/web-admin/src/api/customer.js @@ -0,0 +1,80 @@ +import service from '@/utils/request' +// @Tags SysApi +// @Summary 删除客户 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body dbModel.ExaCustomer true "删除客户" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /customer/customer [post] +export const createExaCustomer = (data) => { + return service({ + url: '/customer/customer', + method: 'post', + data + }) +} + +// @Tags SysApi +// @Summary 更新客户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body dbModel.ExaCustomer true "更新客户信息" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /customer/customer [put] +export const updateExaCustomer = (data) => { + return service({ + url: '/customer/customer', + method: 'put', + data + }) +} + +// @Tags SysApi +// @Summary 创建客户 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body dbModel.ExaCustomer true "创建客户" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /customer/customer [delete] +export const deleteExaCustomer = (data) => { + return service({ + url: '/customer/customer', + method: 'delete', + data + }) +} + +// @Tags SysApi +// @Summary 获取单一客户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body dbModel.ExaCustomer true "获取单一客户信息" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /customer/customer [get] +export const getExaCustomer = (params) => { + return service({ + url: '/customer/customer', + method: 'get', + params + }) +} + +// @Tags SysApi +// @Summary 获取权限客户列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body modelInterface.PageInfo true "获取权限客户列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /customer/customerList [get] +export const getExaCustomerList = (params) => { + return service({ + url: '/customer/customerList', + method: 'get', + params + }) +} diff --git a/web-admin/src/api/email.js b/web-admin/src/api/email.js new file mode 100644 index 0000000..c2f16f4 --- /dev/null +++ b/web-admin/src/api/email.js @@ -0,0 +1,14 @@ +import service from '@/utils/request' +// @Tags email +// @Summary 发送测试邮件 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}" +// @Router /email/emailTest [post] +export const emailTest = (data) => { + return service({ + url: '/email/emailTest', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/exportTemplate.js b/web-admin/src/api/exportTemplate.js new file mode 100644 index 0000000..753547d --- /dev/null +++ b/web-admin/src/api/exportTemplate.js @@ -0,0 +1,145 @@ +import service from '@/utils/request' + +// @Tags SysExportTemplate +// @Summary 创建导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysExportTemplate true "创建导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /sysExportTemplate/createSysExportTemplate [post] +export const createSysExportTemplate = (data) => { + return service({ + url: '/sysExportTemplate/createSysExportTemplate', + method: 'post', + data + }) +} + +// @Tags SysExportTemplate +// @Summary 删除导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysExportTemplate true "删除导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysExportTemplate/deleteSysExportTemplate [delete] +export const deleteSysExportTemplate = (data) => { + return service({ + url: '/sysExportTemplate/deleteSysExportTemplate', + method: 'delete', + data + }) +} + +// @Tags SysExportTemplate +// @Summary 批量删除导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysExportTemplate/deleteSysExportTemplate [delete] +export const deleteSysExportTemplateByIds = (data) => { + return service({ + url: '/sysExportTemplate/deleteSysExportTemplateByIds', + method: 'delete', + data + }) +} + +// @Tags SysExportTemplate +// @Summary 更新导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysExportTemplate true "更新导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /sysExportTemplate/updateSysExportTemplate [put] +export const updateSysExportTemplate = (data) => { + return service({ + url: '/sysExportTemplate/updateSysExportTemplate', + method: 'put', + data + }) +} + +// @Tags SysExportTemplate +// @Summary 用id查询导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.SysExportTemplate true "用id查询导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysExportTemplate/findSysExportTemplate [get] +export const findSysExportTemplate = (params) => { + return service({ + url: '/sysExportTemplate/findSysExportTemplate', + method: 'get', + params + }) +} + +// @Tags SysExportTemplate +// @Summary 分页获取导出模板列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取导出模板列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysExportTemplate/getSysExportTemplateList [get] +export const getSysExportTemplateList = (params) => { + return service({ + url: '/sysExportTemplate/getSysExportTemplateList', + method: 'get', + params + }) +} + + +// ExportExcel 导出表格token +// @Tags SysExportTemplate +// @Summary 导出表格 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/exportExcel [get] +export const exportExcel = (params) => { + return service({ + url: '/sysExportTemplate/exportExcel', + method: 'get', + params + }) +} + +// ExportTemplate 导出表格模板 +// @Tags SysExportTemplate +// @Summary 导出表格模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/exportTemplate [get] +export const exportTemplate = (params) => { + return service({ + url: '/sysExportTemplate/exportTemplate', + method: 'get', + params + }) +} + +// PreviewSQL 预览最终生成的SQL +// @Tags SysExportTemplate +// @Summary 预览最终生成的SQL +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/previewSQL [get] +// @Param templateID query string true "导出模板ID" +// @Param params query string false "查询参数编码字符串,参考 ExportExcel 组件" +export const previewSQL = (params) => { + return service({ + url: '/sysExportTemplate/previewSQL', + method: 'get', + params + }) +} diff --git a/web-admin/src/api/fileUploadAndDownload.js b/web-admin/src/api/fileUploadAndDownload.js new file mode 100644 index 0000000..0f260b6 --- /dev/null +++ b/web-admin/src/api/fileUploadAndDownload.js @@ -0,0 +1,67 @@ +import service from '@/utils/request' +// @Tags FileUploadAndDownload +// @Summary 分页文件列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body modelInterface.PageInfo true "分页获取文件户列表" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /fileUploadAndDownload/getFileList [post] +export const getFileList = (data) => { + return service({ + url: '/fileUploadAndDownload/getFileList', + method: 'post', + data + }) +} + +// @Tags FileUploadAndDownload +// @Summary 删除文件 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body dbModel.FileUploadAndDownload true "传入文件里面id即可" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"返回成功"}" +// @Router /fileUploadAndDownload/deleteFile [post] +export const deleteFile = (data) => { + return service({ + url: '/fileUploadAndDownload/deleteFile', + method: 'post', + data + }) +} + +/** + * 编辑文件名或者备注 + * @param data + * @returns {*} + */ +export const editFileName = (data) => { + return service({ + url: '/fileUploadAndDownload/editFileName', + method: 'post', + data + }) +} + +/** + * 导入URL + * @param data + * @returns {*} + */ +export const importURL = (data) => { + return service({ + url: '/fileUploadAndDownload/importURL', + method: 'post', + data + }) +} + + +// 上传文件 暂时用于头像上传 +export const uploadFile = (data) => { + return service({ + url: "/fileUploadAndDownload/upload", + method: "post", + data, + }); +}; \ No newline at end of file diff --git a/web-admin/src/api/github.js b/web-admin/src/api/github.js new file mode 100644 index 0000000..38e1067 --- /dev/null +++ b/web-admin/src/api/github.js @@ -0,0 +1,19 @@ +import axios from 'axios' + +const service = axios.create() + +export function Commits(page) { + return service({ + url: + 'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' + + page, + method: 'get' + }) +} + +export function Members() { + return service({ + url: 'https://api.github.com/orgs/FLIPPED-AURORA/members', + method: 'get' + }) +} diff --git a/web-admin/src/api/initdb.js b/web-admin/src/api/initdb.js new file mode 100644 index 0000000..f1eb2f4 --- /dev/null +++ b/web-admin/src/api/initdb.js @@ -0,0 +1,27 @@ +import service from '@/utils/request' +// @Tags InitDB +// @Summary 初始化用户数据库 +// @Produce application/json +// @Param data body request.InitDB true "初始化数据库参数" +// @Success 200 {string} string "{"code":0,"data":{},"msg":"自动创建数据库成功"}" +// @Router /init/initdb [post] +export const initDB = (data) => { + return service({ + url: '/init/initdb', + method: 'post', + data, + donNotShowLoading: true + }) +} + +// @Tags CheckDB +// @Summary 初始化用户数据库 +// @Produce application/json +// @Success 200 {string} string "{"code":0,"data":{},"msg":"探测完成"}" +// @Router /init/checkdb [post] +export const checkDB = () => { + return service({ + url: '/init/checkdb', + method: 'post' + }) +} diff --git a/web-admin/src/api/jwt.js b/web-admin/src/api/jwt.js new file mode 100644 index 0000000..811ffc4 --- /dev/null +++ b/web-admin/src/api/jwt.js @@ -0,0 +1,14 @@ +import service from '@/utils/request' +// @Tags jwt +// @Summary jwt加入黑名单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"拉黑成功"}" +// @Router /jwt/jsonInBlacklist [post] +export const jsonInBlacklist = () => { + return service({ + url: '/jwt/jsonInBlacklist', + method: 'post' + }) +} diff --git a/web-admin/src/api/menu.js b/web-admin/src/api/menu.js new file mode 100644 index 0000000..9e7398d --- /dev/null +++ b/web-admin/src/api/menu.js @@ -0,0 +1,141 @@ +import service from '@/utils/request' +// @Summary 用户登录 获取动态路由 +// @Produce application/json +// @Param 可以什么都不填 调一下即可 +// @Router /menu/getMenu [post] +export const asyncMenu = () => { + return service({ + url: '/menu/getMenu', + method: 'post' + }) +} + +// @Summary 获取menu列表 +// @Produce application/json +// @Param { +// page int +// pageSize int +// } +// @Router /menu/getMenuList [post] +export const getMenuList = (data) => { + return service({ + url: '/menu/getMenuList', + method: 'post', + data + }) +} + +// @Summary 新增基础menu +// @Produce application/json +// @Param menu Object +// @Router /menu/getMenuList [post] +export const addBaseMenu = (data) => { + return service({ + url: '/menu/addBaseMenu', + method: 'post', + data + }) +} + +// @Summary 获取基础路由列表 +// @Produce application/json +// @Param 可以什么都不填 调一下即可 +// @Router /menu/getBaseMenuTree [post] +export const getBaseMenuTree = () => { + return service({ + url: '/menu/getBaseMenuTree', + method: 'post' + }) +} + +// @Summary 添加用户menu关联关系 +// @Produce application/json +// @Param menus Object authorityId string +// @Router /menu/getMenuList [post] +export const addMenuAuthority = (data) => { + return service({ + url: '/menu/addMenuAuthority', + method: 'post', + data + }) +} + +// @Summary 获取用户menu关联关系 +// @Produce application/json +// @Param authorityId string +// @Router /menu/getMenuAuthority [post] +export const getMenuAuthority = (data) => { + return service({ + url: '/menu/getMenuAuthority', + method: 'post', + data + }) +} + +// @Summary 删除menu +// @Produce application/json +// @Param ID float64 +// @Router /menu/deleteBaseMenu [post] +export const deleteBaseMenu = (data) => { + return service({ + url: '/menu/deleteBaseMenu', + method: 'post', + data + }) +} + +// @Summary 修改menu列表 +// @Produce application/json +// @Param menu Object +// @Router /menu/updateBaseMenu [post] +export const updateBaseMenu = (data) => { + return service({ + url: '/menu/updateBaseMenu', + method: 'post', + data + }) +} + +// @Tags menu +// @Summary 根据id获取菜单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.GetById true "根据id获取菜单" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /menu/getBaseMenuById [post] +export const getBaseMenuById = (data) => { + return service({ + url: '/menu/getBaseMenuById', + method: 'post', + data + }) +} + +/** + * 获取拥有指定菜单的角色ID列表 + * @param {number} menuId 菜单ID + * @returns {Promise} 角色ID数组 + */ +export const getMenuRoles = (menuId) => { + return service({ + url: '/menu/getMenuRoles', + method: 'get', + params: { menuId } + }) +} + +/** + * 全量覆盖某菜单关联的角色列表 + * @param {Object} data + * @param {number} data.menuId 菜单ID + * @param {number[]} data.authorityIds 角色ID列表 + * @returns {Promise} + */ +export const setMenuRoles = (data) => { + return service({ + url: '/menu/setMenuRoles', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/plugin/api.js b/web-admin/src/api/plugin/api.js new file mode 100644 index 0000000..5b37c2f --- /dev/null +++ b/web-admin/src/api/plugin/api.js @@ -0,0 +1,10 @@ +import service from '@/utils/request' + +export const getShopPluginList = (params) => { + return service({ + baseURL: "plugin", + url: '/shopPlugin/getShopPluginList', + method: 'get', + params + }) +} \ No newline at end of file diff --git a/web-admin/src/api/skills.js b/web-admin/src/api/skills.js new file mode 100644 index 0000000..801c52e --- /dev/null +++ b/web-admin/src/api/skills.js @@ -0,0 +1,169 @@ +import service from '@/utils/request' + +export const getSkillTools = () => { + return service({ + url: '/skills/getTools', + method: 'get' + }) +} + +export const getSkillList = (data) => { + return service({ + url: '/skills/getSkillList', + method: 'post', + data + }) +} + +export const getSkillDetail = (data) => { + return service({ + url: '/skills/getSkillDetail', + method: 'post', + data + }) +} + +export const saveSkill = (data) => { + return service({ + url: '/skills/saveSkill', + method: 'post', + data + }) +} + +export const deleteSkill = (data) => { + return service({ + url: '/skills/deleteSkill', + method: 'post', + data + }) +} + +export const createSkillScript = (data) => { + return service({ + url: '/skills/createScript', + method: 'post', + data + }) +} + +export const getSkillScript = (data) => { + return service({ + url: '/skills/getScript', + method: 'post', + data + }) +} + +export const saveSkillScript = (data) => { + return service({ + url: '/skills/saveScript', + method: 'post', + data + }) +} + +export const createSkillResource = (data) => { + return service({ + url: '/skills/createResource', + method: 'post', + data + }) +} + +export const getSkillResource = (data) => { + return service({ + url: '/skills/getResource', + method: 'post', + data + }) +} + +export const saveSkillResource = (data) => { + return service({ + url: '/skills/saveResource', + method: 'post', + data + }) +} + +export const createSkillReference = (data) => { + return service({ + url: '/skills/createReference', + method: 'post', + data + }) +} + +export const getSkillReference = (data) => { + return service({ + url: '/skills/getReference', + method: 'post', + data + }) +} + +export const saveSkillReference = (data) => { + return service({ + url: '/skills/saveReference', + method: 'post', + data + }) +} + +export const createSkillTemplate = (data) => { + return service({ + url: '/skills/createTemplate', + method: 'post', + data + }) +} + +export const getSkillTemplate = (data) => { + return service({ + url: '/skills/getTemplate', + method: 'post', + data + }) +} + +export const saveSkillTemplate = (data) => { + return service({ + url: '/skills/saveTemplate', + method: 'post', + data + }) +} + +export const getGlobalConstraint = (data) => { + return service({ + url: '/skills/getGlobalConstraint', + method: 'post', + data + }) +} + +export const saveGlobalConstraint = (data) => { + return service({ + url: '/skills/saveGlobalConstraint', + method: 'post', + data + }) +} + +export const packageSkill = (data) => { + return service({ + url: '/skills/packageSkill', + method: 'post', + data, + responseType: 'blob' + }) +} + +export const downloadOnlineSkill = (data) => { + return service({ + url: '/skills/downloadOnlineSkill', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/sysApiToken.js b/web-admin/src/api/sysApiToken.js new file mode 100644 index 0000000..f95c714 --- /dev/null +++ b/web-admin/src/api/sysApiToken.js @@ -0,0 +1,25 @@ +import service from '@/utils/request' + +export const createApiToken = (data) => { + return service({ + url: '/sysApiToken/createApiToken', + method: 'post', + data + }) +} + +export const getApiTokenList = (data) => { + return service({ + url: '/sysApiToken/getApiTokenList', + method: 'post', + data + }) +} + +export const deleteApiToken = (data) => { + return service({ + url: '/sysApiToken/deleteApiToken', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/sysDictionary.js b/web-admin/src/api/sysDictionary.js new file mode 100644 index 0000000..90a2583 --- /dev/null +++ b/web-admin/src/api/sysDictionary.js @@ -0,0 +1,112 @@ +import service from '@/utils/request' +// @Tags SysDictionary +// @Summary 创建SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionary true "创建SysDictionary" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionary/createSysDictionary [post] +export const createSysDictionary = (data) => { + return service({ + url: '/sysDictionary/createSysDictionary', + method: 'post', + data + }) +} + +// @Tags SysDictionary +// @Summary 删除SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionary true "删除SysDictionary" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysDictionary/deleteSysDictionary [delete] +export const deleteSysDictionary = (data) => { + return service({ + url: '/sysDictionary/deleteSysDictionary', + method: 'delete', + data + }) +} + +// @Tags SysDictionary +// @Summary 更新SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionary true "更新SysDictionary" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /sysDictionary/updateSysDictionary [put] +export const updateSysDictionary = (data) => { + return service({ + url: '/sysDictionary/updateSysDictionary', + method: 'put', + data + }) +} + +// @Tags SysDictionary +// @Summary 用id查询SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionary true "用id查询SysDictionary" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysDictionary/findSysDictionary [get] +export const findSysDictionary = (params) => { + return service({ + url: '/sysDictionary/findSysDictionary', + method: 'get', + params + }) +} + +// @Tags SysDictionary +// @Summary 分页获取SysDictionary列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.PageInfo true "分页获取SysDictionary列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionary/getSysDictionaryList [get] +export const getSysDictionaryList = (params) => { + return service({ + url: '/sysDictionary/getSysDictionaryList', + method: 'get', + params + }) +} + +// @Tags SysDictionary +// @Summary 导出字典JSON(包含字典详情) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.SysDictionary true "字典ID" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"导出成功"}" +// @Router /sysDictionary/exportSysDictionary [get] +export const exportSysDictionary = (params) => { + return service({ + url: '/sysDictionary/exportSysDictionary', + method: 'get', + params + }) +} + +// @Tags SysDictionary +// @Summary 导入字典JSON(包含字典详情) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body object true "字典JSON数据" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"导入成功"}" +// @Router /sysDictionary/importSysDictionary [post] +export const importSysDictionary = (data) => { + return service({ + url: '/sysDictionary/importSysDictionary', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/sysDictionaryDetail.js b/web-admin/src/api/sysDictionaryDetail.js new file mode 100644 index 0000000..1f4ab73 --- /dev/null +++ b/web-admin/src/api/sysDictionaryDetail.js @@ -0,0 +1,145 @@ +import service from '@/utils/request' +// @Tags SysDictionaryDetail +// @Summary 创建SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionaryDetail true "创建SysDictionaryDetail" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionaryDetail/createSysDictionaryDetail [post] +export const createSysDictionaryDetail = (data) => { + return service({ + url: '/sysDictionaryDetail/createSysDictionaryDetail', + method: 'post', + data + }) +} + +// @Tags SysDictionaryDetail +// @Summary 删除SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionaryDetail true "删除SysDictionaryDetail" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysDictionaryDetail/deleteSysDictionaryDetail [delete] +export const deleteSysDictionaryDetail = (data) => { + return service({ + url: '/sysDictionaryDetail/deleteSysDictionaryDetail', + method: 'delete', + data + }) +} + +// @Tags SysDictionaryDetail +// @Summary 更新SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionaryDetail true "更新SysDictionaryDetail" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /sysDictionaryDetail/updateSysDictionaryDetail [put] +export const updateSysDictionaryDetail = (data) => { + return service({ + url: '/sysDictionaryDetail/updateSysDictionaryDetail', + method: 'put', + data + }) +} + +// @Tags SysDictionaryDetail +// @Summary 用id查询SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysDictionaryDetail true "用id查询SysDictionaryDetail" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysDictionaryDetail/findSysDictionaryDetail [get] +export const findSysDictionaryDetail = (params) => { + return service({ + url: '/sysDictionaryDetail/findSysDictionaryDetail', + method: 'get', + params + }) +} + +// @Tags SysDictionaryDetail +// @Summary 分页获取SysDictionaryDetail列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.PageInfo true "分页获取SysDictionaryDetail列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionaryDetail/getSysDictionaryDetailList [get] +export const getSysDictionaryDetailList = (params) => { + return service({ + url: '/sysDictionaryDetail/getSysDictionaryDetailList', + method: 'get', + params + }) +} + +// @Tags SysDictionaryDetail +// @Summary 获取层级字典详情树形结构(根据字典ID) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param sysDictionaryID query string true "字典ID" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionaryDetail/getDictionaryTreeList [get] +export const getDictionaryTreeList = (params) => { + return service({ + url: '/sysDictionaryDetail/getDictionaryTreeList', + method: 'get', + params + }) +} + +// @Tags SysDictionaryDetail +// @Summary 获取层级字典详情树形结构(根据字典类型) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param dictType query string true "字典类型" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionaryDetail/getDictionaryTreeListByType [get] +export const getDictionaryTreeListByType = (params) => { + return service({ + url: '/sysDictionaryDetail/getDictionaryTreeListByType', + method: 'get', + params + }) +} + +// @Tags SysDictionaryDetail +// @Summary 根据父级ID获取字典详情 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param parentID query string true "父级ID" +// @Param includeChildren query boolean false "是否包含子级" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionaryDetail/getDictionaryDetailsByParent [get] +export const getDictionaryDetailsByParent = (params) => { + return service({ + url: '/sysDictionaryDetail/getDictionaryDetailsByParent', + method: 'get', + params + }) +} + +// @Tags SysDictionaryDetail +// @Summary 获取字典详情的完整路径 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param ID query string true "字典详情ID" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysDictionaryDetail/getDictionaryPath [get] +export const getDictionaryPath = (params) => { + return service({ + url: '/sysDictionaryDetail/getDictionaryPath', + method: 'get', + params + }) +} diff --git a/web-admin/src/api/sysLoginLog.js b/web-admin/src/api/sysLoginLog.js new file mode 100644 index 0000000..4c96de2 --- /dev/null +++ b/web-admin/src/api/sysLoginLog.js @@ -0,0 +1,33 @@ +import service from '@/utils/request' + +export const deleteLoginLog = (data) => { + return service({ + url: '/sysLoginLog/deleteLoginLog', + method: 'delete', + data + }) +} + +export const deleteLoginLogByIds = (data) => { + return service({ + url: '/sysLoginLog/deleteLoginLogByIds', + method: 'delete', + data + }) +} + +export const getLoginLogList = (params) => { + return service({ + url: '/sysLoginLog/getLoginLogList', + method: 'get', + params + }) +} + +export const findLoginLog = (params) => { + return service({ + url: '/sysLoginLog/findLoginLog', + method: 'get', + params + }) +} diff --git a/web-admin/src/api/sysOperationRecord.js b/web-admin/src/api/sysOperationRecord.js new file mode 100644 index 0000000..4428c03 --- /dev/null +++ b/web-admin/src/api/sysOperationRecord.js @@ -0,0 +1,48 @@ +import service from '@/utils/request' +// @Tags SysOperationRecord +// @Summary 删除SysOperationRecord +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysOperationRecord true "删除SysOperationRecord" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysOperationRecord/deleteSysOperationRecord [delete] +export const deleteSysOperationRecord = (data) => { + return service({ + url: '/sysOperationRecord/deleteSysOperationRecord', + method: 'delete', + data + }) +} + +// @Tags SysOperationRecord +// @Summary 删除SysOperationRecord +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "删除SysOperationRecord" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysOperationRecord/deleteSysOperationRecord [delete] +export const deleteSysOperationRecordByIds = (data) => { + return service({ + url: '/sysOperationRecord/deleteSysOperationRecordByIds', + method: 'delete', + data + }) +} + +// @Tags SysOperationRecord +// @Summary 分页获取SysOperationRecord列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.PageInfo true "分页获取SysOperationRecord列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysOperationRecord/getSysOperationRecordList [get] +export const getSysOperationRecordList = (params) => { + return service({ + url: '/sysOperationRecord/getSysOperationRecordList', + method: 'get', + params + }) +} diff --git a/web-admin/src/api/sysParams.js b/web-admin/src/api/sysParams.js new file mode 100644 index 0000000..348f1b5 --- /dev/null +++ b/web-admin/src/api/sysParams.js @@ -0,0 +1,111 @@ +import service from '@/utils/request' +// @Tags SysParams +// @Summary 创建参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysParams true "创建参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /sysParams/createSysParams [post] +export const createSysParams = (data) => { + return service({ + url: '/sysParams/createSysParams', + method: 'post', + data + }) +} + +// @Tags SysParams +// @Summary 删除参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysParams true "删除参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysParams/deleteSysParams [delete] +export const deleteSysParams = (params) => { + return service({ + url: '/sysParams/deleteSysParams', + method: 'delete', + params + }) +} + +// @Tags SysParams +// @Summary 批量删除参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysParams/deleteSysParams [delete] +export const deleteSysParamsByIds = (params) => { + return service({ + url: '/sysParams/deleteSysParamsByIds', + method: 'delete', + params + }) +} + +// @Tags SysParams +// @Summary 更新参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysParams true "更新参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /sysParams/updateSysParams [put] +export const updateSysParams = (data) => { + return service({ + url: '/sysParams/updateSysParams', + method: 'put', + data + }) +} + +// @Tags SysParams +// @Summary 用id查询参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.SysParams true "用id查询参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysParams/findSysParams [get] +export const findSysParams = (params) => { + return service({ + url: '/sysParams/findSysParams', + method: 'get', + params + }) +} + +// @Tags SysParams +// @Summary 分页获取参数列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取参数列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysParams/getSysParamsList [get] +export const getSysParamsList = (params) => { + return service({ + url: '/sysParams/getSysParamsList', + method: 'get', + params + }) +} + +// @Tags SysParams +// @Summary 不需要鉴权的参数接口 +// @accept application/json +// @Produce application/json +// @Param data query systemReq.SysParamsSearch true "分页获取参数列表" +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /sysParams/getSysParam [get] +export const getSysParam = (params) => { + return service({ + url: '/sysParams/getSysParam', + method: 'get', + params + }) +} diff --git a/web-admin/src/api/system.js b/web-admin/src/api/system.js new file mode 100644 index 0000000..ff41abf --- /dev/null +++ b/web-admin/src/api/system.js @@ -0,0 +1,55 @@ +import service from '@/utils/request' +// @Tags systrm +// @Summary 获取配置文件内容 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}" +// @Router /system/getSystemConfig [post] +export const getSystemConfig = () => { + return service({ + url: '/system/getSystemConfig', + method: 'post' + }) +} + +// @Tags system +// @Summary 设置配置文件内容 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body sysModel.System true +// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}" +// @Router /system/setSystemConfig [post] +export const setSystemConfig = (data) => { + return service({ + url: '/system/setSystemConfig', + method: 'post', + data + }) +} + +// @Tags system +// @Summary 获取服务器运行状态 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}" +// @Router /system/getServerInfo [post] +export const getSystemState = () => { + return service({ + url: '/system/getServerInfo', + method: 'post', + donNotShowLoading: true + }) +} + +/** + * 重载服务 + * @param data + * @returns {*} + */ +export const reloadSystem = (data) => { + return service({ + url: '/system/reloadSystem', + method: 'post', + data + }) +} diff --git a/web-admin/src/api/system/sysError.js b/web-admin/src/api/system/sysError.js new file mode 100644 index 0000000..4b3271b --- /dev/null +++ b/web-admin/src/api/system/sysError.js @@ -0,0 +1,126 @@ +import service from '@/utils/request' +// @Tags SysError +// @Summary 创建错误日志 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.SysError true "创建错误日志" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /sysError/createSysError [post] +export const createSysError = (data) => { + return service({ + url: '/sysError/createSysError', + method: 'post', + data + }) +} + +// @Tags SysError +// @Summary 删除错误日志 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.SysError true "删除错误日志" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysError/deleteSysError [delete] +export const deleteSysError = (params) => { + return service({ + url: '/sysError/deleteSysError', + method: 'delete', + params + }) +} + +// @Tags SysError +// @Summary 批量删除错误日志 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除错误日志" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysError/deleteSysError [delete] +export const deleteSysErrorByIds = (params) => { + return service({ + url: '/sysError/deleteSysErrorByIds', + method: 'delete', + params + }) +} + +// @Tags SysError +// @Summary 更新错误日志 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.SysError true "更新错误日志" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /sysError/updateSysError [put] +export const updateSysError = (data) => { + return service({ + url: '/sysError/updateSysError', + method: 'put', + data + }) +} + +// @Tags SysError +// @Summary 用id查询错误日志 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query model.SysError true "用id查询错误日志" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysError/findSysError [get] +export const findSysError = (params) => { + return service({ + url: '/sysError/findSysError', + method: 'get', + params + }) +} + +// @Tags SysError +// @Summary 分页获取错误日志列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取错误日志列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysError/getSysErrorList [get] +export const getSysErrorList = (params) => { + return service({ + url: '/sysError/getSysErrorList', + method: 'get', + params + }) +} + +// @Tags SysError +// @Summary 不需要鉴权的错误日志接口 +// @Accept application/json +// @Produce application/json +// @Param data query systemReq.SysErrorSearch true "分页获取错误日志列表" +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /sysError/getSysErrorPublic [get] +export const getSysErrorPublic = () => { + return service({ + url: '/sysError/getSysErrorPublic', + method: 'get', + }) +} + +// @Tags SysError +// @Summary 触发错误处理(异步) +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param id query string true "错误日志ID" +// @Success 200 {string} string "{\"success\":true,\"data\":{},\"msg\":\"处理已提交\"}" +// @Router /sysError/getSysErrorSolution [get] +export const getSysErrorSolution = (params) => { + return service({ + url: '/sysError/getSysErrorSolution', + method: 'get', + params + }) +} \ No newline at end of file diff --git a/web-admin/src/api/user.js b/web-admin/src/api/user.js new file mode 100644 index 0000000..2b357d0 --- /dev/null +++ b/web-admin/src/api/user.js @@ -0,0 +1,181 @@ +import service from '@/utils/request' +// @Summary 用户登录 +// @Produce application/json +// @Param data body {username:"string",password:"string"} +// @Router /base/login [post] +export const login = (data) => { + return service({ + url: '/base/login', + method: 'post', + data: data + }) +} + +// @Summary 获取验证码 +// @Produce application/json +// @Param data body {username:"string",password:"string"} +// @Router /base/captcha [post] +export const captcha = () => { + return service({ + url: '/base/captcha', + method: 'post' + }) +} + +// @Summary 用户注册 +// @Produce application/json +// @Param data body {username:"string",password:"string"} +// @Router /base/resige [post] +export const register = (data) => { + return service({ + url: '/user/admin_register', + method: 'post', + data: data + }) +} + +// @Summary 修改密码 +// @Produce application/json +// @Param data body {username:"string",password:"string",newPassword:"string"} +// @Router /user/changePassword [post] +export const changePassword = (data) => { + return service({ + url: '/user/changePassword', + method: 'post', + data: data + }) +} + +// @Tags User +// @Summary 分页获取用户列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body modelInterface.PageInfo true "分页获取用户列表" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /user/getUserList [post] +export const getUserList = (data) => { + return service({ + url: '/user/getUserList', + method: 'post', + data: data + }) +} + +// @Tags User +// @Summary 设置用户权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.SetUserAuth true "设置用户权限" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"修改成功"}" +// @Router /user/setUserAuthority [post] +export const setUserAuthority = (data) => { + return service({ + url: '/user/setUserAuthority', + method: 'post', + data: data + }) +} + +// @Tags SysUser +// @Summary 删除用户 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.SetUserAuth true "删除用户" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}" +// @Router /user/deleteUser [delete] +export const deleteUser = (data) => { + return service({ + url: '/user/deleteUser', + method: 'delete', + data: data + }) +} + +// @Tags SysUser +// @Summary 设置用户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysUser true "设置用户信息" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}" +// @Router /user/setUserInfo [put] +export const setUserInfo = (data) => { + return service({ + url: '/user/setUserInfo', + method: 'put', + data: data + }) +} + +// @Tags SysUser +// @Summary 设置用户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysUser true "设置用户信息" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}" +// @Router /user/setSelfInfo [put] +export const setSelfInfo = (data) => { + return service({ + url: '/user/setSelfInfo', + method: 'put', + data: data + }) +} + +// @Tags SysUser +// @Summary 设置自身界面配置 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.SysUser true "设置自身界面配置" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}" +// @Router /user/setSelfSetting [put] +export const setSelfSetting = (data) => { + return service({ + url: '/user/setSelfSetting', + method: 'put', + data: data + }) +} + +// @Tags User +// @Summary 设置用户权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body api.setUserAuthorities true "设置用户权限" +// @Success 200 {string} json "{"success":true,"data":{},"msg":"修改成功"}" +// @Router /user/setUserAuthorities [post] +export const setUserAuthorities = (data) => { + return service({ + url: '/user/setUserAuthorities', + method: 'post', + data: data + }) +} + +// @Tags User +// @Summary 获取用户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /user/getUserInfo [get] +export const getUserInfo = () => { + return service({ + url: '/user/getUserInfo', + method: 'get' + }) +} + +export const resetPassword = (data) => { + return service({ + url: '/user/resetPassword', + method: 'post', + data: data + }) +} diff --git a/web-admin/src/api/version.js b/web-admin/src/api/version.js new file mode 100644 index 0000000..b5b7dcc --- /dev/null +++ b/web-admin/src/api/version.js @@ -0,0 +1,114 @@ +import service from '@/utils/request' + +// @Tags SysVersion +// @Summary 删除版本管理 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.SysVersion true "删除版本管理" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysVersion/deleteSysVersion [delete] +export const deleteSysVersion = (params) => { + return service({ + url: '/sysVersion/deleteSysVersion', + method: 'delete', + params + }) +} + +// @Tags SysVersion +// @Summary 批量删除版本管理 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除版本管理" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysVersion/deleteSysVersion [delete] +export const deleteSysVersionByIds = (params) => { + return service({ + url: '/sysVersion/deleteSysVersionByIds', + method: 'delete', + params + }) +} + +// @Tags SysVersion +// @Summary 用id查询版本管理 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query model.SysVersion true "用id查询版本管理" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysVersion/findSysVersion [get] +export const findSysVersion = (params) => { + return service({ + url: '/sysVersion/findSysVersion', + method: 'get', + params + }) +} + +// @Tags SysVersion +// @Summary 分页获取版本管理列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取版本管理列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysVersion/getSysVersionList [get] +export const getSysVersionList = (params) => { + return service({ + url: '/sysVersion/getSysVersionList', + method: 'get', + params + }) +} + +// @Tags SysVersion +// @Summary 导出版本数据 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body object true "导出版本数据" +// @Success 200 {string} string "{\"success\":true,\"data\":{},\"msg\":\"导出成功\"}" +// @Router /sysVersion/exportVersion [post] +export const exportVersion = (data) => { + return service({ + url: '/sysVersion/exportVersion', + method: 'post', + data + }) +} + +// @Tags SysVersion +// @Summary 下载版本JSON数据 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param ID query string true "版本ID" +// @Success 200 {string} string "{\"success\":true,\"data\":{},\"msg\":\"下载成功\"}" +// @Router /sysVersion/downloadVersionJson [get] +export const downloadVersionJson = (params) => { + return service({ + url: '/sysVersion/downloadVersionJson', + method: 'get', + params, + responseType: 'blob' + }) +} + +// @Tags SysVersion +// @Summary 导入版本数据 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body object true "版本JSON数据" +// @Success 200 {string} string "{\"success\":true,\"data\":{},\"msg\":\"导入成功\"}" +// @Router /sysVersion/importVersion [post] +export const importVersion = (data) => { + return service({ + url: '/sysVersion/importVersion', + method: 'post', + data + }) +} diff --git a/web-admin/src/assets/404.png b/web-admin/src/assets/404.png new file mode 100644 index 0000000..f803724 Binary files /dev/null and b/web-admin/src/assets/404.png differ diff --git a/web-admin/src/assets/background.svg b/web-admin/src/assets/background.svg new file mode 100644 index 0000000..7375bb5 --- /dev/null +++ b/web-admin/src/assets/background.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-admin/src/assets/banner.jpg b/web-admin/src/assets/banner.jpg new file mode 100644 index 0000000..675411e Binary files /dev/null and b/web-admin/src/assets/banner.jpg differ diff --git a/web-admin/src/assets/banner2.jpg b/web-admin/src/assets/banner2.jpg new file mode 100644 index 0000000..62e08a4 Binary files /dev/null and b/web-admin/src/assets/banner2.jpg differ diff --git a/web-admin/src/assets/dashboard.png b/web-admin/src/assets/dashboard.png new file mode 100644 index 0000000..64981d0 Binary files /dev/null and b/web-admin/src/assets/dashboard.png differ diff --git a/web-admin/src/assets/docs.png b/web-admin/src/assets/docs.png new file mode 100644 index 0000000..bb98d6e Binary files /dev/null and b/web-admin/src/assets/docs.png differ diff --git a/web-admin/src/assets/flipped-aurora.png b/web-admin/src/assets/flipped-aurora.png new file mode 100644 index 0000000..c94033b Binary files /dev/null and b/web-admin/src/assets/flipped-aurora.png differ diff --git a/web-admin/src/assets/github.png b/web-admin/src/assets/github.png new file mode 100644 index 0000000..d1d200e Binary files /dev/null and b/web-admin/src/assets/github.png differ diff --git a/web-admin/src/assets/icons/ai-gva.svg b/web-admin/src/assets/icons/ai-gva.svg new file mode 100644 index 0000000..fcbea93 --- /dev/null +++ b/web-admin/src/assets/icons/ai-gva.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-admin/src/assets/icons/close.svg b/web-admin/src/assets/icons/close.svg new file mode 100644 index 0000000..1b1f631 --- /dev/null +++ b/web-admin/src/assets/icons/close.svg @@ -0,0 +1 @@ + diff --git a/web-admin/src/assets/icons/customer-gva.svg b/web-admin/src/assets/icons/customer-gva.svg new file mode 100644 index 0000000..1e72201 --- /dev/null +++ b/web-admin/src/assets/icons/customer-gva.svg @@ -0,0 +1 @@ + diff --git a/web-admin/src/assets/icons/idea.svg b/web-admin/src/assets/icons/idea.svg new file mode 100644 index 0000000..eac5a0d --- /dev/null +++ b/web-admin/src/assets/icons/idea.svg @@ -0,0 +1 @@ + diff --git a/web-admin/src/assets/icons/lock.svg b/web-admin/src/assets/icons/lock.svg new file mode 100644 index 0000000..4685191 --- /dev/null +++ b/web-admin/src/assets/icons/lock.svg @@ -0,0 +1 @@ + diff --git a/web-admin/src/assets/icons/server.svg b/web-admin/src/assets/icons/server.svg new file mode 100644 index 0000000..3b1375a --- /dev/null +++ b/web-admin/src/assets/icons/server.svg @@ -0,0 +1 @@ + diff --git a/web-admin/src/assets/icons/warn.svg b/web-admin/src/assets/icons/warn.svg new file mode 100644 index 0000000..73d7a15 --- /dev/null +++ b/web-admin/src/assets/icons/warn.svg @@ -0,0 +1 @@ + diff --git a/web-admin/src/assets/kefu.png b/web-admin/src/assets/kefu.png new file mode 100644 index 0000000..47cab15 Binary files /dev/null and b/web-admin/src/assets/kefu.png differ diff --git a/web-admin/src/assets/login_background.jpg b/web-admin/src/assets/login_background.jpg new file mode 100644 index 0000000..e601f24 Binary files /dev/null and b/web-admin/src/assets/login_background.jpg differ diff --git a/web-admin/src/assets/login_background.svg b/web-admin/src/assets/login_background.svg new file mode 100644 index 0000000..0a9514b --- /dev/null +++ b/web-admin/src/assets/login_background.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web-admin/src/assets/login_left.svg b/web-admin/src/assets/login_left.svg new file mode 100644 index 0000000..239cd1c --- /dev/null +++ b/web-admin/src/assets/login_left.svg @@ -0,0 +1,123 @@ + + + 搭建网站 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web-admin/src/assets/login_right_banner.jpg b/web-admin/src/assets/login_right_banner.jpg new file mode 100644 index 0000000..0a597c1 Binary files /dev/null and b/web-admin/src/assets/login_right_banner.jpg differ diff --git a/web-admin/src/assets/logo.jpg b/web-admin/src/assets/logo.jpg new file mode 100644 index 0000000..09502d5 Binary files /dev/null and b/web-admin/src/assets/logo.jpg differ diff --git a/web-admin/src/assets/logo.png b/web-admin/src/assets/logo.png new file mode 100644 index 0000000..b497ee0 Binary files /dev/null and b/web-admin/src/assets/logo.png differ diff --git a/web-admin/src/assets/logo_login.png b/web-admin/src/assets/logo_login.png new file mode 100644 index 0000000..e330458 Binary files /dev/null and b/web-admin/src/assets/logo_login.png differ diff --git a/web-admin/src/assets/nav_logo.png b/web-admin/src/assets/nav_logo.png new file mode 100644 index 0000000..468cdab Binary files /dev/null and b/web-admin/src/assets/nav_logo.png differ diff --git a/web-admin/src/assets/noBody.png b/web-admin/src/assets/noBody.png new file mode 100644 index 0000000..e16488e Binary files /dev/null and b/web-admin/src/assets/noBody.png differ diff --git a/web-admin/src/assets/notFound.png b/web-admin/src/assets/notFound.png new file mode 100644 index 0000000..59ca9f0 Binary files /dev/null and b/web-admin/src/assets/notFound.png differ diff --git a/web-admin/src/assets/qm.png b/web-admin/src/assets/qm.png new file mode 100644 index 0000000..9f598ca Binary files /dev/null and b/web-admin/src/assets/qm.png differ diff --git a/web-admin/src/assets/video.png b/web-admin/src/assets/video.png new file mode 100644 index 0000000..af4d35f Binary files /dev/null and b/web-admin/src/assets/video.png differ diff --git a/web-admin/src/components/application/index.vue b/web-admin/src/components/application/index.vue new file mode 100644 index 0000000..9a22c20 --- /dev/null +++ b/web-admin/src/components/application/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/web-admin/src/components/arrayCtrl/arrayCtrl.vue b/web-admin/src/components/arrayCtrl/arrayCtrl.vue new file mode 100644 index 0000000..54b1a01 --- /dev/null +++ b/web-admin/src/components/arrayCtrl/arrayCtrl.vue @@ -0,0 +1,67 @@ + + + diff --git a/web-admin/src/components/bottomInfo/bottomInfo.vue b/web-admin/src/components/bottomInfo/bottomInfo.vue new file mode 100644 index 0000000..376de05 --- /dev/null +++ b/web-admin/src/components/bottomInfo/bottomInfo.vue @@ -0,0 +1,44 @@ + + + + diff --git a/web-admin/src/components/charts/index.vue b/web-admin/src/components/charts/index.vue new file mode 100644 index 0000000..cbb5dd8 --- /dev/null +++ b/web-admin/src/components/charts/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/web-admin/src/components/commandMenu/index.vue b/web-admin/src/components/commandMenu/index.vue new file mode 100644 index 0000000..ed806a3 --- /dev/null +++ b/web-admin/src/components/commandMenu/index.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/web-admin/src/components/customPic/index.vue b/web-admin/src/components/customPic/index.vue new file mode 100644 index 0000000..84f7aea --- /dev/null +++ b/web-admin/src/components/customPic/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/web-admin/src/components/errorPreview/index.vue b/web-admin/src/components/errorPreview/index.vue new file mode 100644 index 0000000..470dcf4 --- /dev/null +++ b/web-admin/src/components/errorPreview/index.vue @@ -0,0 +1,126 @@ + + + diff --git a/web-admin/src/components/exportExcel/exportExcel.vue b/web-admin/src/components/exportExcel/exportExcel.vue new file mode 100644 index 0000000..645573a --- /dev/null +++ b/web-admin/src/components/exportExcel/exportExcel.vue @@ -0,0 +1,83 @@ + + + diff --git a/web-admin/src/components/exportExcel/exportTemplate.vue b/web-admin/src/components/exportExcel/exportTemplate.vue new file mode 100644 index 0000000..e88b1ec --- /dev/null +++ b/web-admin/src/components/exportExcel/exportTemplate.vue @@ -0,0 +1,40 @@ + + + diff --git a/web-admin/src/components/exportExcel/importExcel.vue b/web-admin/src/components/exportExcel/importExcel.vue new file mode 100644 index 0000000..364b790 --- /dev/null +++ b/web-admin/src/components/exportExcel/importExcel.vue @@ -0,0 +1,45 @@ + + + diff --git a/web-admin/src/components/logo/index.vue b/web-admin/src/components/logo/index.vue new file mode 100644 index 0000000..bf92357 --- /dev/null +++ b/web-admin/src/components/logo/index.vue @@ -0,0 +1,82 @@ + + + diff --git a/web-admin/src/components/office/docx.vue b/web-admin/src/components/office/docx.vue new file mode 100644 index 0000000..e607d0b --- /dev/null +++ b/web-admin/src/components/office/docx.vue @@ -0,0 +1,31 @@ + + + + diff --git a/web-admin/src/components/office/excel.vue b/web-admin/src/components/office/excel.vue new file mode 100644 index 0000000..5b22f91 --- /dev/null +++ b/web-admin/src/components/office/excel.vue @@ -0,0 +1,36 @@ + + + + diff --git a/web-admin/src/components/office/index.vue b/web-admin/src/components/office/index.vue new file mode 100644 index 0000000..d22d6da --- /dev/null +++ b/web-admin/src/components/office/index.vue @@ -0,0 +1,49 @@ + + + diff --git a/web-admin/src/components/office/pdf.vue b/web-admin/src/components/office/pdf.vue new file mode 100644 index 0000000..2ca4363 --- /dev/null +++ b/web-admin/src/components/office/pdf.vue @@ -0,0 +1,39 @@ + + + diff --git a/web-admin/src/components/richtext/rich-edit.vue b/web-admin/src/components/richtext/rich-edit.vue new file mode 100644 index 0000000..64703db --- /dev/null +++ b/web-admin/src/components/richtext/rich-edit.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/web-admin/src/components/richtext/rich-view.vue b/web-admin/src/components/richtext/rich-view.vue new file mode 100644 index 0000000..a116141 --- /dev/null +++ b/web-admin/src/components/richtext/rich-view.vue @@ -0,0 +1,131 @@ + + + + diff --git a/web-admin/src/components/selectFile/selectFile.vue b/web-admin/src/components/selectFile/selectFile.vue new file mode 100644 index 0000000..8bf0d6d --- /dev/null +++ b/web-admin/src/components/selectFile/selectFile.vue @@ -0,0 +1,87 @@ + + + diff --git a/web-admin/src/components/selectImage/selectComponent.vue b/web-admin/src/components/selectImage/selectComponent.vue new file mode 100644 index 0000000..18cc64a --- /dev/null +++ b/web-admin/src/components/selectImage/selectComponent.vue @@ -0,0 +1,86 @@ + + diff --git a/web-admin/src/components/selectImage/selectImage.vue b/web-admin/src/components/selectImage/selectImage.vue new file mode 100644 index 0000000..5958e0a --- /dev/null +++ b/web-admin/src/components/selectImage/selectImage.vue @@ -0,0 +1,503 @@ + + + + diff --git a/web-admin/src/components/svgIcon/svgIcon.vue b/web-admin/src/components/svgIcon/svgIcon.vue new file mode 100644 index 0000000..1783e44 --- /dev/null +++ b/web-admin/src/components/svgIcon/svgIcon.vue @@ -0,0 +1,44 @@ + + + diff --git a/web-admin/src/components/upload/QR-code.vue b/web-admin/src/components/upload/QR-code.vue new file mode 100644 index 0000000..d6e4ee1 --- /dev/null +++ b/web-admin/src/components/upload/QR-code.vue @@ -0,0 +1,65 @@ + + + diff --git a/web-admin/src/components/upload/common.vue b/web-admin/src/components/upload/common.vue new file mode 100644 index 0000000..1371c08 --- /dev/null +++ b/web-admin/src/components/upload/common.vue @@ -0,0 +1,90 @@ + + + diff --git a/web-admin/src/components/upload/cropper.vue b/web-admin/src/components/upload/cropper.vue new file mode 100644 index 0000000..afdaf12 --- /dev/null +++ b/web-admin/src/components/upload/cropper.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/web-admin/src/components/upload/image.vue b/web-admin/src/components/upload/image.vue new file mode 100644 index 0000000..57d231c --- /dev/null +++ b/web-admin/src/components/upload/image.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/web-admin/src/components/warningBar/warningBar.vue b/web-admin/src/components/warningBar/warningBar.vue new file mode 100644 index 0000000..9d01881 --- /dev/null +++ b/web-admin/src/components/warningBar/warningBar.vue @@ -0,0 +1,33 @@ + + diff --git a/web-admin/src/core/config.js b/web-admin/src/core/config.js new file mode 100644 index 0000000..02bb788 --- /dev/null +++ b/web-admin/src/core/config.js @@ -0,0 +1,55 @@ +/** + * 网站配置文件 + */ +import packageInfo from '../../package.json' + +const greenText = (text) => `\x1b[32m${text}\x1b[0m` + +export const config = { + appName: 'Gin-Vue-Admin', + showViteLogo: true, + keepAliveTabs: false, + logs: [] +} + +export const viteLogo = (env) => { + if (config.showViteLogo) { + console.log( + greenText( + `> 欢迎使用Gin-Vue-Admin,开源地址:https://github.com/flipped-aurora/gin-vue-admin` + ) + ) + console.log(greenText(`> 当前版本:v${packageInfo.version}`)) + console.log(greenText(`> 加群方式:微信:shouzi_1994 QQ群:470239250`)) + console.log( + greenText(`> 项目地址:https://github.com/flipped-aurora/gin-vue-admin`) + ) + console.log(greenText(`> 插件市场:https://plugin.gin-vue-admin.com`)) + console.log( + greenText(`> GVA讨论社区:https://support.qq.com/products/371961`) + ) + console.log( + greenText( + `> 默认自动化文档地址:http://127.0.0.1:${env.VITE_SERVER_PORT}/swagger/index.html` + ) + ) + console.log( + greenText(`> 默认前端文件运行地址:http://127.0.0.1:${env.VITE_CLI_PORT}`) + ) + console.log( + greenText( + `--------------------------------------版权声明--------------------------------------` + ) + ) + console.log(greenText(`** 版权所有方:flipped-aurora开源团队 **`)) + console.log(greenText(`** 版权持有公司:北京翻转极光科技有限责任公司 **`)) + console.log( + greenText( + `** 剔除授权标识需购买商用授权:https://plugin.gin-vue-admin.com/license **` + ) + ) + console.log('\n') + } +} + +export default config diff --git a/web-admin/src/core/error-handel.js b/web-admin/src/core/error-handel.js new file mode 100644 index 0000000..e897433 --- /dev/null +++ b/web-admin/src/core/error-handel.js @@ -0,0 +1,24 @@ +import {createSysError} from '@/api/system/sysError' + +function sendErrorTip(errorInfo) { + setTimeout(() => { + const errorData = { + form: errorInfo.type, + info: `${errorInfo.message}\nStack: ${errorInfo.stack}${errorInfo.component ? `\nComponent: ${errorInfo.component.name || 'Unknown'}` : ''}${errorInfo.vueInfo ? `\nVue Info: ${errorInfo.vueInfo}` : ''}${errorInfo.source ? `\nSource: ${errorInfo.source}:${errorInfo.lineno}:${errorInfo.colno}` : ''}`, + level: 'error', + solution: null + } + + createSysError(errorData).catch(apiErr => { + console.error('Failed to create error record:', apiErr) + }) + }, 0) +} + + window.addEventListener('unhandledrejection', (event) => { + sendErrorTip({ + type: '前端', + message: `错误信息: ${event.reason}`, + stack: `调用栈: ${event.reason?.stack || '没有调用栈信息'}`, + }); + }); diff --git a/web-admin/src/core/gin-vue-admin.js b/web-admin/src/core/gin-vue-admin.js new file mode 100644 index 0000000..dfd6308 --- /dev/null +++ b/web-admin/src/core/gin-vue-admin.js @@ -0,0 +1,29 @@ +/* + * gin-vue-admin web框架组 + * + * */ +// 加载网站配置文件夹 +import {register} from './global' +import packageInfo from '../../package.json' + +export default { + install: (app) => { + register(app) + console.log(` + 欢迎使用 Gin-Vue-Admin + 当前版本:v${packageInfo.version} + 加群方式:微信:shouzi_1994 QQ群:622360840 + 项目地址:https://github.com/flipped-aurora/gin-vue-admin + 插件市场:https://plugin.gin-vue-admin.com + GVA讨论社区:https://support.qq.com/products/371961 + 默认自动化文档地址:http://127.0.0.1:${import.meta.env.VITE_SERVER_PORT}/swagger/index.html + 默认前端文件运行地址:http://127.0.0.1:${import.meta.env.VITE_CLI_PORT} + 如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/coffee/index.html + --------------------------------------版权声明-------------------------------------- + ** 版权所有方:flipped-aurora开源团队 ** + ** 版权持有公司:北京翻转极光科技有限责任公司 ** + ** 剔除授权标识需购买商用授权:https://plugin.gin-vue-admin.com/license ** + ** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展** + `) + } +} diff --git a/web-admin/src/core/global.js b/web-admin/src/core/global.js new file mode 100644 index 0000000..a5f939c --- /dev/null +++ b/web-admin/src/core/global.js @@ -0,0 +1,63 @@ +import config from './config' +import {h} from 'vue' + +// 统一导入el-icon图标 +import * as ElIconModules from '@element-plus/icons-vue' +import svgIcon from '@/components/svgIcon/svgIcon.vue' +// 导入转换图标名称的函数 + +const createIconComponent = (name) => ({ + name: 'SvgIcon', + render() { + return h(svgIcon, { + localIcon: name + }) + } +}) + +const registerIcons = async (app) => { + const iconModules = import.meta.glob('@/assets/icons/**/*.svg') // 系统目录 svg 图标 + const pluginIconModules = import.meta.glob( + '@/plugin/**/assets/icons/**/*.svg' + ) // 插件目录 svg 图标 + const mergedIconModules = Object.assign({}, iconModules, pluginIconModules) // 合并所有 svg 图标 + let allKeys = [] + for (const path in mergedIconModules) { + let pluginName = '' + if (path.startsWith('/src/plugin/')) { + pluginName = `${path.split('/')[3]}-` + } + const iconName = path + .split('/') + .pop() + .replace(/\.svg$/, '') + // 如果iconName带空格则不加入到图标库中并且提示名称不合法 + if (iconName.indexOf(' ') !== -1) { + console.error(`icon ${iconName}.svg includes whitespace in ${path}`) + continue + } + const key = `${pluginName}${iconName}` + const iconComponent = createIconComponent(key) + config.logs.push({ + key: key, + label: key + }) + app.component(key, iconComponent) + + // 开发模式下列出所有 svg 图标,方便开发者直接查找复制使用 + allKeys.push(key) + } + + import.meta.env.MODE == 'development' && + console.log(`所有可用的本地图标: ${allKeys.join(', ')}`) +} + +export const register = (app) => { + // 统一注册el-icon图标 + for (const iconName in ElIconModules) { + app.component(iconName, ElIconModules[iconName]) + } + app.component('SvgIcon', svgIcon) + registerIcons(app) + app.config.globalProperties.$GIN_VUE_ADMIN = config +} diff --git a/web-admin/src/directive/auth.js b/web-admin/src/directive/auth.js new file mode 100644 index 0000000..29ea128 --- /dev/null +++ b/web-admin/src/directive/auth.js @@ -0,0 +1,26 @@ +// 权限按钮展示指令 +import {useUserStore} from '@/pinia/modules/user' + +export default { + install: (app) => { + const userStore = useUserStore() + app.directive('auth', { + // 当被绑定的元素插入到 DOM 中时…… + mounted: function (el, binding) { + const userInfo = userStore.userInfo + if (!binding.value){ + el.parentNode.removeChild(el) + return + } + const waitUse = binding.value.toString().split(',') + let flag = waitUse.some((item) => Number(item) === userInfo.authorityId) + if (binding.modifiers.not) { + flag = !flag + } + if (!flag) { + el.parentNode.removeChild(el) + } + } + }) + } +} diff --git a/web-admin/src/directive/clickOutSide.js b/web-admin/src/directive/clickOutSide.js new file mode 100644 index 0000000..d1bd502 --- /dev/null +++ b/web-admin/src/directive/clickOutSide.js @@ -0,0 +1,43 @@ +export default { + install: (app) => { + app.directive('click-outside', { + mounted(el, binding) { + const handler = (e) => { + // 如果绑定的元素包含事件目标,或元素已经被移除,则不触发 + if (!el || el.contains(e.target) || e.target === el) return + // 支持函数或对象 { handler: fn, exclude: [el1, el2], capture: true } + const value = binding.value + if (value && typeof value === 'object') { + if ( + value.exclude && + value.exclude.some( + (ex) => ex && ex.contains && ex.contains(e.target) + ) + ) + return + if (typeof value.handler === 'function') value.handler(e) + } else if (typeof value === 'function') { + value(e) + } + } + + // 存到 el 上,便于解绑 + el.__clickOutsideHandler__ = handler + + // 延迟注册,避免 mounted 时触发(比如当点击就是触发绑定动作时) + setTimeout(() => { + document.addEventListener('mousedown', handler) + document.addEventListener('touchstart', handler) + }, 0) + }, + unmounted(el) { + const h = el.__clickOutsideHandler__ + if (h) { + document.removeEventListener('mousedown', h) + document.removeEventListener('touchstart', h) + delete el.__clickOutsideHandler__ + } + } + }) + } +} diff --git a/web-admin/src/hooks/charts.js b/web-admin/src/hooks/charts.js new file mode 100644 index 0000000..aaf4f00 --- /dev/null +++ b/web-admin/src/hooks/charts.js @@ -0,0 +1,18 @@ +// 本组件参考 arco-pro 的实现 +// https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/chart-option.ts + +import {computed} from 'vue' +import {useAppStore} from '@/pinia' + +export default function useChartOption(sourceOption) { + const appStore = useAppStore() + const isDark = computed(() => { + return appStore.isDark + }) + const chartOption = computed(() => { + return sourceOption(isDark.value) + }) + return { + chartOption + } +} diff --git a/web-admin/src/hooks/responsive.js b/web-admin/src/hooks/responsive.js new file mode 100644 index 0000000..aaf6752 --- /dev/null +++ b/web-admin/src/hooks/responsive.js @@ -0,0 +1,35 @@ +// 本组件参考 arco-pro 的实现 +// https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/responsive.ts + +import {onBeforeMount, onBeforeUnmount, onMounted} from 'vue' +import {useDebounceFn} from '@vueuse/core' +import {useAppStore} from '@/pinia' +import {addEventListen, removeEventListen} from '@/utils/event' + +const WIDTH = 992 + +function queryDevice() { + const rect = document.body.getBoundingClientRect() + return rect.width - 1 < WIDTH +} + +export default function useResponsive(immediate) { + const appStore = useAppStore() + function resizeHandler() { + if (!document.hidden) { + const isMobile = queryDevice() + appStore.toggleDevice(isMobile ? 'mobile' : 'desktop') + // appStore.toggleDevice(isMobile); + } + } + const debounceFn = useDebounceFn(resizeHandler, 100) + onMounted(() => { + if (immediate) debounceFn() + }) + onBeforeMount(() => { + addEventListen(window, 'resize', debounceFn) + }) + onBeforeUnmount(() => { + removeEventListen(window, 'resize', debounceFn) + }) +} diff --git a/web-admin/src/hooks/use-windows-resize.js b/web-admin/src/hooks/use-windows-resize.js new file mode 100644 index 0000000..a3e1490 --- /dev/null +++ b/web-admin/src/hooks/use-windows-resize.js @@ -0,0 +1,23 @@ +// 监听 window 的 resize 事件,返回当前窗口的宽高 +import { shallowRef } from 'vue' +import { tryOnMounted, useEventListener } from '@vueuse/core' + +const width = shallowRef(0) +const height = shallowRef(0) + +export const useWindowResize = (cb) => { + const onResize = () => { + width.value = window.innerWidth + height.value = window.innerHeight + if (cb && typeof cb === 'function') { + cb(width.value, height.value) + } + } + + tryOnMounted(onResize) + useEventListener('resize', onResize, { passive: true }) + return { + width, + height + } +} diff --git a/web-admin/src/main.js b/web-admin/src/main.js new file mode 100644 index 0000000..76ca98f --- /dev/null +++ b/web-admin/src/main.js @@ -0,0 +1,37 @@ +import './style/element_visiable.scss' +import 'element-plus/theme-chalk/dark/css-vars.css' +import 'uno.css' +import {createApp} from 'vue' +import ElementPlus from 'element-plus' +import {setupVueRootValidator} from 'vite-check-multiple-dom/client'; + +import 'element-plus/dist/index.css' +// 引入gin-vue-admin前端初始化相关内容 +import './core/gin-vue-admin' +// 引入封装的router +import router from '@/router/index' +import '@/permission' +import run from '@/core/gin-vue-admin.js' +import auth from '@/directive/auth' +import clickOutSide from '@/directive/clickOutSide' +import {store} from '@/pinia' +import App from './App.vue' +import '@/core/error-handel' + +const app = createApp(App) + +app.config.productionTip = false + +setupVueRootValidator(app, { + lang: 'zh' + }) + +app + .use(run) + .use(ElementPlus) + .use(store) + .use(auth) + .use(clickOutSide) + .use(router) + .mount('#app') +export default app diff --git a/web-admin/src/pathInfo.json b/web-admin/src/pathInfo.json new file mode 100644 index 0000000..4042371 --- /dev/null +++ b/web-admin/src/pathInfo.json @@ -0,0 +1,85 @@ +{ + "/src/view/about/index.vue": "About", + "/src/view/dashboard/components/banner.vue": "Banner", + "/src/view/dashboard/components/card.vue": "Card", + "/src/view/dashboard/components/charts-content-numbers.vue": "ChartsContentNumbers", + "/src/view/dashboard/components/charts-people-numbers.vue": "ChartsPeopleNumbers", + "/src/view/dashboard/components/charts.vue": "Charts", + "/src/view/dashboard/components/notice.vue": "Notice", + "/src/view/dashboard/components/pluginTable.vue": "PluginTable", + "/src/view/dashboard/components/quickLinks.vue": "QuickLinks", + "/src/view/dashboard/components/table.vue": "Table", + "/src/view/dashboard/components/wiki.vue": "Wiki", + "/src/view/dashboard/index.vue": "Dashboard", + "/src/view/error/index.vue": "Error", + "/src/view/error/reload.vue": "Reload", + "/src/view/example/breakpoint/breakpoint.vue": "BreakPoint", + "/src/view/example/customer/customer.vue": "Customer", + "/src/view/example/index.vue": "Example", + "/src/view/example/upload/scanUpload.vue": "scanUpload", + "/src/view/example/upload/upload.vue": "Upload", + "/src/view/init/index.vue": "Init", + "/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu", + "/src/view/layout/aside/asideComponent/index.vue": "AsideComponent", + "/src/view/layout/aside/asideComponent/menuItem.vue": "MenuItem", + "/src/view/layout/aside/combinationMode.vue": "GvaAside", + "/src/view/layout/aside/headMode.vue": "GvaAside", + "/src/view/layout/aside/index.vue": "Index", + "/src/view/layout/aside/normalMode.vue": "GvaAside", + "/src/view/layout/aside/sidebarMode.vue": "SidebarMode", + "/src/view/layout/header/index.vue": "Index", + "/src/view/layout/header/tools.vue": "Tools", + "/src/view/layout/iframe.vue": "GvaLayoutIframe", + "/src/view/layout/index.vue": "GvaLayout", + "/src/view/layout/screenfull/index.vue": "Screenfull", + "/src/view/layout/search/search.vue": "BtnBox", + "/src/view/layout/setting/components/layoutModeCard.vue": "LayoutModeCard", + "/src/view/layout/setting/components/settingItem.vue": "SettingItem", + "/src/view/layout/setting/components/themeColorPicker.vue": "ThemeColorPicker", + "/src/view/layout/setting/components/themeModeSelector.vue": "ThemeModeSelector", + "/src/view/layout/setting/index.vue": "GvaSetting", + "/src/view/layout/setting/modules/appearance/index.vue": "AppearanceSettings", + "/src/view/layout/setting/modules/general/index.vue": "GeneralSettings", + "/src/view/layout/setting/modules/layout/index.vue": "LayoutSettings", + "/src/view/layout/tabs/index.vue": "HistoryComponent", + "/src/view/login/index.vue": "Login", + "/src/view/person/person.vue": "Person", + "/src/view/routerHolder.vue": "RouterHolder", + "/src/view/superAdmin/api/api.vue": "Api", + "/src/view/superAdmin/authority/authority.vue": "Authority", + "/src/view/superAdmin/authority/components/apis.vue": "Apis", + "/src/view/superAdmin/authority/components/datas.vue": "Datas", + "/src/view/superAdmin/authority/components/menus.vue": "Menus", + "/src/view/superAdmin/dictionary/sysDictionary.vue": "SysDictionary", + "/src/view/superAdmin/dictionary/sysDictionaryDetail.vue": "SysDictionaryDetail", + "/src/view/superAdmin/index.vue": "SuperAdmin", + "/src/view/superAdmin/menu/components/components-cascader.vue": "ComponentsCascader", + "/src/view/superAdmin/menu/icon.vue": "Icon", + "/src/view/superAdmin/menu/menu.vue": "Menus", + "/src/view/superAdmin/operation/sysOperationRecord.vue": "SysOperationRecord", + "/src/view/superAdmin/params/sysParams.vue": "SysParams", + "/src/view/superAdmin/user/user.vue": "User", + "/src/view/system/state.vue": "State", + "/src/view/systemTools/apiToken/index.vue": "Index", + "/src/view/systemTools/autoCode/component/fieldDialog.vue": "FieldDialog", + "/src/view/systemTools/autoCode/component/previewCodeDialog.vue": "PreviewCodeDialog", + "/src/view/systemTools/autoCode/index.vue": "AutoCode", + "/src/view/systemTools/autoCode/mcp.vue": "MCP", + "/src/view/systemTools/autoCode/mcpTest.vue": "MCPTest", + "/src/view/systemTools/autoCode/picture.vue": "Picture", + "/src/view/systemTools/autoCodeAdmin/index.vue": "AutoCodeAdmin", + "/src/view/systemTools/autoPkg/autoPkg.vue": "AutoPkg", + "/src/view/systemTools/exportTemplate/exportTemplate.vue": "ExportTemplate", + "/src/view/systemTools/formCreate/index.vue": "FormGenerator", + "/src/view/systemTools/index.vue": "System", + "/src/view/systemTools/installPlugin/index.vue": "Index", + "/src/view/systemTools/loginLog/index.vue": "Index", + "/src/view/systemTools/pubPlug/pubPlug.vue": "PubPlug", + "/src/view/systemTools/skills/index.vue": "Skills", + "/src/view/systemTools/sysError/sysError.vue": "SysError", + "/src/view/systemTools/system/system.vue": "Config", + "/src/view/systemTools/version/version.vue": "SysVersion", + "/src/plugin/announcement/form/info.vue": "InfoForm", + "/src/plugin/announcement/view/info.vue": "Info", + "/src/plugin/email/view/index.vue": "Email" +} \ No newline at end of file diff --git a/web-admin/src/permission.js b/web-admin/src/permission.js new file mode 100644 index 0000000..5127d1f --- /dev/null +++ b/web-admin/src/permission.js @@ -0,0 +1,224 @@ +import {useUserStore} from '@/pinia/modules/user' +import {useRouterStore} from '@/pinia/modules/router' +import getPageTitle from '@/utils/page' +import router from '@/router' +import Nprogress from 'nprogress' +import 'nprogress/nprogress.css' + +// 配置 NProgress +Nprogress.configure({ + showSpinner: false, + ease: 'ease', + speed: 500 +}) + +// 白名单路由 +const WHITE_LIST = ['Login', 'Init'] + +function isExternalUrl(val) { + return typeof val === 'string' && /^(https?:)?\/\//.test(val) +} + +// 工具函数:统一路径归一化 +function normalizeAbsolutePath(p) { + const s = '/' + String(p || '') + return s.replace(/\/+/g, '/') +} + +function normalizeRelativePath(p) { + return String(p || '').replace(/^\/+/, '') +} + +// 安全注册:仅在路由名未存在时注册顶级路由 +function addTopLevelIfAbsent(r) { + if (!router.hasRoute(r.name)) { + router.addRoute(r) + } +} + +// 将 n 级菜单扁平化为: +// - 常规:一级 layout + 二级页面组件 +// - 若某节点 meta.defaultMenu === true:该节点为顶级(不包裹在 layout 下),其子节点作为该顶级的二级页面组件 +function addRouteByChildren(route, segments = [], parentName = null) { + // 跳过外链根节点 + if (isExternalUrl(route?.path) || isExternalUrl(route?.name) || isExternalUrl(route?.component)) { + return + } + + // 顶层 layout 仅用于承载,不参与路径拼接 + if (route?.name === 'layout') { + route.children?.forEach((child) => addRouteByChildren(child, [], null)) + return + } + + // 如果标记为 defaultMenu,则该路由应作为顶级路由(不包裹在 layout 下) + if (route?.meta?.defaultMenu === true && parentName === null) { + const fullPath = [...segments, route.path].filter(Boolean).join('/') + const children = route.children ? [...route.children] : [] + const newRoute = { ...route, path: fullPath } + delete newRoute.children + delete newRoute.parent + // 顶级路由使用绝对路径 + newRoute.path = normalizeAbsolutePath(newRoute.path) + + // 若已存在同名路由则整体跳过(之前应已处理过其子节点) + if (router.hasRoute(newRoute.name)) return + addTopLevelIfAbsent(newRoute) + + // 若该 defaultMenu 节点仍有子节点,继续递归处理其子节点(挂载到该顶级路由下) + if (children.length) { + // 重置片段,使其成为顶级下的二级相对路径 + children.forEach((child) => addRouteByChildren(child, [], newRoute.name)) + } + return + } + + // 还有子节点,继续向下收集路径片段(忽略外链片段) + if (route?.children && route.children.length) { + if(!parentName){ + const firstChild = route.children[0] + if (firstChild) { + const fullParentPath = [...segments, route.path].filter(Boolean).join('/') + const redirectPath = normalizeRelativePath( + [fullParentPath, firstChild.path].filter(Boolean).join('/') + ) + const parentRoute = { + path: normalizeRelativePath(fullParentPath), + name: route.name, // 保留父级名称,以便 defaultRouter 可以指向它 + meta: route.meta, + redirect: "/layout/" + redirectPath, + } + router.addRoute('layout', parentRoute) + } + } + const nextSegments = isExternalUrl(route.path) ? segments : [...segments, route.path] + route.children.forEach((child) => addRouteByChildren(child, nextSegments, parentName)) + return + } + + // 叶子节点:注册为其父(defaultMenu 顶级或 layout)的二级子路由 + const fullPath = [...segments, route.path].filter(Boolean).join('/') + const newRoute = { ...route, path: fullPath } + delete newRoute.children + delete newRoute.parent + // 子路由使用相对路径,避免 /layout/layout/... 的问题 + newRoute.path = normalizeRelativePath(newRoute.path) + + if (parentName) { + // 挂载到 defaultMenu 顶级路由下 + router.addRoute(parentName, newRoute) + } else { + // 常规:挂载到 layout 下 + router.addRoute('layout', newRoute) + } +} + +// 处理路由加载 +const setupRouter = async (userStore) => { + try { + const routerStore = useRouterStore() + await Promise.all([routerStore.SetAsyncRouter(), userStore.GetUserInfo()]) + + // 确保先注册父级 layout + const baseRouters = routerStore.asyncRouters || [] + const layoutRoute = baseRouters[0] + if (layoutRoute?.name === 'layout' && !router.hasRoute('layout')) { + const bareLayout = { ...layoutRoute, children: [] } + router.addRoute(bareLayout) + } + + // 扁平化:将 layout.children 与其余顶层异步路由一并作为二级子路由注册到 layout 下 + const toRegister = [] + if (layoutRoute?.children?.length) { + toRegister.push(...layoutRoute.children) + } + if (baseRouters.length > 1) { + baseRouters.slice(1).forEach((r) => { + if (r?.name !== 'layout') toRegister.push(r) + }) + } + toRegister.forEach((r) => addRouteByChildren(r, [], null)) + return true + } catch (error) { + console.error('Setup router failed:', error) + return false + } +} + +// 移除加载动画 +const removeLoading = () => { + const element = document.getElementById('gva-loading-box') + element?.remove() +} + + +// 路由守卫 +router.beforeEach(async (to, from) => { + const userStore = useUserStore() + const routerStore = useRouterStore() + const token = userStore.token + + Nprogress.start() + + // 处理元数据和缓存 + to.meta.matched = [...to.matched] + await routerStore.handleKeepAlive(to) + // 设置页面标题 + document.title = getPageTitle(to.meta.title, to) + if (to.meta.client) { + return true + } + + // 白名单路由处理 + if (WHITE_LIST.includes(to.name)) { + if (token) { + if(!routerStore.asyncRouterFlag){ + await setupRouter(userStore) + } + if(userStore.userInfo.authority.defaultRouter){ + return { name: userStore.userInfo.authority.defaultRouter } + } + } + return true + } + + // 需要登录的路由处理 + if (token) { + // 处理需要跳转到首页的情况 + if (sessionStorage.getItem('needToHome') === 'true') { + sessionStorage.removeItem('needToHome') + return { path: '/' } + } + + // 处理异步路由 + if (!routerStore.asyncRouterFlag && !WHITE_LIST.includes(from.name)) { + await setupRouter(userStore) + return to + } + + return to.matched.length ? true : { path: '/layout/404' } + } + + // 未登录跳转登录页 + return { + name: 'Login', + query: { + redirect: to.fullPath + } + } +}) + +// 路由加载完成 +router.afterEach(() => { + document.querySelector('.main-cont.main-right')?.scrollTo(0, 0) + Nprogress.done() +}) + +// 路由错误处理 +router.onError((error) => { + console.error('Router error:', error) + Nprogress.remove() +}) + +// 移除初始加载动画 +removeLoading() diff --git a/web-admin/src/pinia/index.js b/web-admin/src/pinia/index.js new file mode 100644 index 0000000..5b936a5 --- /dev/null +++ b/web-admin/src/pinia/index.js @@ -0,0 +1,8 @@ +import {createPinia} from 'pinia' +import {useAppStore} from '@/pinia/modules/app' +import {useUserStore} from '@/pinia/modules/user' +import {useDictionaryStore} from '@/pinia/modules/dictionary' + +const store = createPinia() + +export { store, useAppStore, useUserStore, useDictionaryStore } diff --git a/web-admin/src/pinia/modules/app.js b/web-admin/src/pinia/modules/app.js new file mode 100644 index 0000000..cc3f416 --- /dev/null +++ b/web-admin/src/pinia/modules/app.js @@ -0,0 +1,162 @@ +import {defineStore} from 'pinia' +import {reactive, ref, watchEffect} from 'vue' +import {setBodyPrimaryColor} from '@/utils/format' +import {useDark, usePreferredDark} from '@vueuse/core' + +export const useAppStore = defineStore('app', () => { + const device = ref('') + const drawerSize = ref('') + const operateMinWith = ref('240') + const config = reactive({ + weakness: false, + grey: false, + primaryColor: '#3b82f6', + showTabs: true, + darkMode: 'auto', + layout_side_width: 256, + layout_side_collapsed_width: 80, + layout_side_item_height: 48, + show_watermark: true, + side_mode: 'normal', + // 页面过渡动画配置 + transition_type: 'slide', + global_size: 'default' + }) + + const isDark = useDark({ + selector: 'html', + attribute: 'class', + valueDark: 'dark', + valueLight: 'light' + }) + + const preferredDark = usePreferredDark() + + const toggleTheme = (darkMode) => { + isDark.value = darkMode + } + + const toggleWeakness = (e) => { + config.weakness = e + } + + const toggleGrey = (e) => { + config.grey = e + } + + const togglePrimaryColor = (e) => { + config.primaryColor = e + } + + const toggleTabs = (e) => { + config.showTabs = e + } + + const toggleDevice = (e) => { + if (e === 'mobile') { + drawerSize.value = '100%' + operateMinWith.value = '80' + } else { + drawerSize.value = '800' + operateMinWith.value = '240' + } + device.value = e + } + + const toggleDarkMode = (e) => { + config.darkMode = e + } + + // 监听系统主题变化 + watchEffect(() => { + if (config.darkMode === 'auto') { + isDark.value = preferredDark.value + return + } + isDark.value = config.darkMode === 'dark' + }) + + const toggleConfigSideWidth = (e) => { + config.layout_side_width = e + } + + const toggleConfigSideCollapsedWidth = (e) => { + config.layout_side_collapsed_width = e + } + + const toggleConfigSideItemHeight = (e) => { + config.layout_side_item_height = e + } + + const toggleConfigWatermark = (e) => { + config.show_watermark = e + } + + const toggleSideMode = (e) => { + config.side_mode = e + } + + const toggleTransition = (e) => { + config.transition_type = e + } + + const toggleGlobalSize = (e) => { + config.global_size = e + } + + const baseCoinfg = { + weakness: false, + grey: false, + primaryColor: '#3b82f6', + showTabs: true, + darkMode: 'auto', + layout_side_width: 256, + layout_side_collapsed_width: 80, + layout_side_item_height: 48, + show_watermark: true, + side_mode: 'normal', + // 页面过渡动画配置 + transition_type: 'slide', + global_size: 'default' + } + + const resetConfig = () => { + for (let baseCoinfgKey in baseCoinfg) { + config[baseCoinfgKey] = baseCoinfg[baseCoinfgKey] + } + } + + // 监听色弱模式和灰色模式 + watchEffect(() => { + document.documentElement.classList.toggle('html-weakenss', config.weakness) + document.documentElement.classList.toggle('html-grey', config.grey) + }) + + // 监听主题色 + watchEffect(() => { + setBodyPrimaryColor(config.primaryColor, isDark.value ? 'dark' : 'light') + }) + + return { + isDark, + device, + drawerSize, + operateMinWith, + config, + toggleTheme, + toggleDevice, + toggleWeakness, + toggleGrey, + togglePrimaryColor, + toggleTabs, + toggleDarkMode, + toggleConfigSideWidth, + toggleConfigSideCollapsedWidth, + toggleConfigSideItemHeight, + toggleConfigWatermark, + toggleSideMode, + toggleTransition, + resetConfig, + toggleGlobalSize + } +}) diff --git a/web-admin/src/pinia/modules/dictionary.js b/web-admin/src/pinia/modules/dictionary.js new file mode 100644 index 0000000..2fe1e4f --- /dev/null +++ b/web-admin/src/pinia/modules/dictionary.js @@ -0,0 +1,252 @@ +import {findSysDictionary} from '@/api/sysDictionary' +import {getDictionaryTreeListByType} from '@/api/sysDictionaryDetail' + +import {defineStore} from 'pinia' +import {ref} from 'vue' + +export const useDictionaryStore = defineStore('dictionary', () => { + const dictionaryMap = ref({}) + + const setDictionaryMap = (dictionaryRes) => { + dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes } + } + + // 过滤树形数据的深度 + const filterTreeByDepth = (items, currentDepth, targetDepth) => { + if (targetDepth === 0) { + // depth=0 返回全部数据 + return items + } + + if (currentDepth >= targetDepth) { + // 达到目标深度,移除children + return items.map((item) => ({ + label: item.label, + value: item.value, + extend: item.extend + })) + } + + // 递归处理子项 + return items.map((item) => ({ + label: item.label, + value: item.value, + extend: item.extend, + children: item.children + ? filterTreeByDepth(item.children, currentDepth + 1, targetDepth) + : undefined + })) + } + + // 将树形结构扁平化为数组(用于兼容原有的平铺格式) + const flattenTree = (items) => { + const result = [] + + const traverse = (nodes) => { + nodes.forEach((item) => { + result.push({ + label: item.label, + value: item.value, + extend: item.extend + }) + + if (item.children && item.children.length > 0) { + traverse(item.children) + } + }) + } + + traverse(items) + return result + } + + // 标准化树形数据,确保每个节点都包含标准的字段格式 + const normalizeTreeData = (items) => { + return items.map((item) => ({ + label: item.label, + value: item.value, + extend: item.extend, + children: + item.children && item.children.length > 0 + ? normalizeTreeData(item.children) + : undefined + })) + } + + // 根据value和depth查找指定节点并返回其children + const findNodeByValue = ( + items, + targetValue, + currentDepth = 1, + maxDepth = 0 + ) => { + for (const item of items) { + // 如果找到目标value的节点 + if (item.value === targetValue) { + // 如果maxDepth为0,返回所有children + if (maxDepth === 0) { + return item.children ? normalizeTreeData(item.children) : [] + } + // 否则根据depth限制返回children + if (item.children && item.children.length > 0) { + return filterTreeByDepth(item.children, 1, maxDepth) + } + return [] + } + + // 如果当前深度小于最大深度,继续在children中查找 + if ( + item.children && + item.children.length > 0 && + (maxDepth === 0 || currentDepth < maxDepth) + ) { + const result = findNodeByValue( + item.children, + targetValue, + currentDepth + 1, + maxDepth + ) + if (result !== null) { + return result + } + } + } + return null + } + + const getDictionary = async (type, depth = 0, value = null) => { + // 如果传入了value参数,则查找指定节点的children + if (value !== null) { + // 构建缓存key,包含value和depth信息 + const cacheKey = `${type}_value_${value}_depth_${depth}` + + if ( + dictionaryMap.value[cacheKey] && + dictionaryMap.value[cacheKey].length + ) { + return dictionaryMap.value[cacheKey] + } + + try { + // 获取完整的树形结构数据 + const treeRes = await getDictionaryTreeListByType({ type }) + if ( + treeRes.code === 0 && + treeRes.data && + treeRes.data.list && + treeRes.data.list.length > 0 + ) { + // 查找指定value的节点并返回其children + const targetNodeChildren = findNodeByValue( + treeRes.data.list, + value, + 1, + depth + ) + + if (targetNodeChildren !== null) { + let resultData + if (depth === 0) { + // depth=0 时返回完整的children树形结构 + resultData = targetNodeChildren + } else { + // 其他depth值:扁平化children数据 + resultData = flattenTree(targetNodeChildren) + } + + const dictionaryRes = {} + dictionaryRes[cacheKey] = resultData + setDictionaryMap(dictionaryRes) + return dictionaryMap.value[cacheKey] + } else { + // 如果没找到指定value的节点,返回空数组 + return [] + } + } + } catch (error) { + console.error('根据value获取字典数据失败:', error) + return [] + } + } + + // 原有的逻辑:不传value参数时的处理 + // 构建缓存key,包含depth信息 + const cacheKey = depth === 0 ? `${type}_tree` : `${type}_depth_${depth}` + + if (dictionaryMap.value[cacheKey] && dictionaryMap.value[cacheKey].length) { + return dictionaryMap.value[cacheKey] + } else { + try { + // 首先尝试获取树形结构数据 + const treeRes = await getDictionaryTreeListByType({ type }) + if ( + treeRes.code === 0 && + treeRes.data && + treeRes.data.list && + treeRes.data.list.length > 0 + ) { + // 使用树形结构数据 + const treeData = treeRes.data.list + + let resultData + if (depth === 0) { + // depth=0 时返回完整的树形结构,但要确保字段格式标准化 + resultData = normalizeTreeData(treeData) + } else { + // 其他depth值:根据depth参数过滤数据,然后扁平化 + const filteredData = filterTreeByDepth(treeData, 1, depth) + resultData = flattenTree(filteredData) + } + + const dictionaryRes = {} + dictionaryRes[cacheKey] = resultData + setDictionaryMap(dictionaryRes) + return dictionaryMap.value[cacheKey] + } else { + // 如果没有树形数据,回退到原有的平铺方式 + const res = await findSysDictionary({ type }) + if (res.code === 0) { + const dictionaryRes = {} + const dict = [] + res.data.resysDictionary.sysDictionaryDetails && + res.data.resysDictionary.sysDictionaryDetails.forEach((item) => { + dict.push({ + label: item.label, + value: item.value, + extend: item.extend + }) + }) + dictionaryRes[cacheKey] = dict + setDictionaryMap(dictionaryRes) + return dictionaryMap.value[cacheKey] + } + } + } catch (error) { + console.error('获取字典数据失败:', error) + // 发生错误时回退到原有方式 + const res = await findSysDictionary({ type }) + if (res.code === 0) { + const dictionaryRes = {} + const dict = [] + res.data.resysDictionary.sysDictionaryDetails && + res.data.resysDictionary.sysDictionaryDetails.forEach((item) => { + dict.push({ + label: item.label, + value: item.value, + extend: item.extend + }) + }) + dictionaryRes[cacheKey] = dict + setDictionaryMap(dictionaryRes) + return dictionaryMap.value[cacheKey] + } + } + } + } + + return { + dictionaryMap, + setDictionaryMap, + getDictionary + } +}) diff --git a/web-admin/src/pinia/modules/params.js b/web-admin/src/pinia/modules/params.js new file mode 100644 index 0000000..1f7786f --- /dev/null +++ b/web-admin/src/pinia/modules/params.js @@ -0,0 +1,31 @@ +import {getSysParam} from '@/api/sysParams' +import {defineStore} from 'pinia' +import {ref} from 'vue' + +export const useParamsStore = defineStore('params', () => { + const paramsMap = ref({}) + + const setParamsMap = (paramsRes) => { + paramsMap.value = { ...paramsMap.value, ...paramsRes } + } + + const getParams = async(key) => { + if (paramsMap.value[key] && paramsMap.value[key].length) { + return paramsMap.value[key] + } else { + const res = await getSysParam({ key }) + if (res.code === 0) { + const paramsRes = {} + paramsRes[key] = res.data.value + setParamsMap(paramsRes) + return paramsMap.value[key] + } + } + } + + return { + paramsMap, + setParamsMap, + getParams + } +}) diff --git a/web-admin/src/pinia/modules/router.js b/web-admin/src/pinia/modules/router.js new file mode 100644 index 0000000..639898f --- /dev/null +++ b/web-admin/src/pinia/modules/router.js @@ -0,0 +1,207 @@ +import { asyncRouterHandle } from '@/utils/asyncRouter' +import { emitter } from '@/utils/bus.js' +import { asyncMenu } from '@/api/menu' +import { defineStore } from 'pinia' +import { ref, watchEffect } from 'vue' +import pathInfo from '@/pathInfo.json' +import {useRoute} from "vue-router"; +import {config} from "@/core/config.js"; + +const notLayoutRouterArr = [] +const keepAliveRoutersArr = [] +const nameMap = {} + +const formatRouter = (routes, routeMap, parent) => { + routes && + routes.forEach((item) => { + item.parent = parent + item.meta.btns = item.btns + item.meta.hidden = item.hidden + if (item.meta.defaultMenu === true) { + if (!parent) { + item = { ...item, path: `/${item.path}` } + notLayoutRouterArr.push(item) + } + } + routeMap[item.name] = item + if (item.children && item.children.length > 0) { + formatRouter(item.children, routeMap, item) + } + }) +} + +const KeepAliveFilter = (routes) => { + routes && + routes.forEach((item) => { + // 子菜单中有 keep-alive 的,父菜单也必须 keep-alive,否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。 + if ( + (item.children && item.children.some((ch) => ch.meta.keepAlive)) || + item.meta.keepAlive + ) { + const path = item.meta.path + keepAliveRoutersArr.push(pathInfo[path]) + nameMap[item.name] = pathInfo[path] + } + if (item.children && item.children.length > 0) { + KeepAliveFilter(item.children) + } + }) +} + +export const useRouterStore = defineStore('router', () => { + const keepAliveRouters = ref([]) + const asyncRouterFlag = ref(0) + const setKeepAliveRouters = (history) => { + const keepArrTemp = [] + + // 1. 首先添加原有的keepAlive配置 + keepArrTemp.push(...keepAliveRoutersArr) + if (config.keepAliveTabs) { + history.forEach((item) => { + // 2. 为所有history中的路由强制启用keep-alive + // 通过routeMap获取路由信息,然后通过pathInfo获取组件名 + const routeInfo = routeMap[item.name] + if (routeInfo && routeInfo.meta && routeInfo.meta.path) { + const componentName = pathInfo[routeInfo.meta.path] + if (componentName) { + keepArrTemp.push(componentName) + } + } + + // 3. 如果子路由在tabs中打开,父路由也需要keepAlive + if (nameMap[item.name]) { + keepArrTemp.push(nameMap[item.name]) + } + }) + } + keepAliveRouters.value = Array.from(new Set(keepArrTemp)) + } + + // 处理组件缓存 + const handleKeepAlive = async (to) => { + if (!to.matched.some((item) => item.meta.keepAlive)) return + + if (to.matched?.length > 2) { + for (let i = 1; i < to.matched.length; i++) { + const element = to.matched[i - 1] + + if (element.name === 'layout') { + to.matched.splice(i, 1) + await handleKeepAlive(to) + continue + } + + if (typeof element.components.default === 'function') { + await element.components.default() + await handleKeepAlive(to) + } + } + } + } + + + const route = useRoute() + + emitter.on('setKeepAlive', setKeepAliveRouters) + + const asyncRouters = ref([]) + + const topMenu = ref([]) + + const leftMenu = ref([]) + + const menuMap = {} + + const topActive = ref('') + + const setLeftMenu = (name) => { + sessionStorage.setItem('topActive', name) + topActive.value = name + leftMenu.value = [] + if (menuMap[name]?.children) { + leftMenu.value = menuMap[name].children + } + return menuMap[name]?.children + } + + const findTopActive = (menuMap, routeName) => { + for (let topName in menuMap) { + const topItem = menuMap[topName]; + if (topItem.children?.some(item => item.name === routeName)) { + return topName; + } + const foundName = findTopActive(topItem.children || {}, routeName); + if (foundName) { + return topName; + } + } + return null; + }; + + watchEffect(() => { + let topActive = sessionStorage.getItem('topActive') + // 初始化菜单内容,防止重复添加 + topMenu.value = []; + asyncRouters.value[0]?.children.forEach((item) => { + if (item.hidden) return + menuMap[item.name] = item + topMenu.value.push({ ...item, children: [] }) + }) + if (!topActive || topActive === 'undefined' || topActive === 'null') { + topActive = findTopActive(menuMap, route.name); + } + setLeftMenu(topActive) + }) + + const routeMap = {} + // 从后台获取动态路由 + const SetAsyncRouter = async () => { + asyncRouterFlag.value++ + const baseRouter = [ + { + path: '/layout', + name: 'layout', + component: 'view/layout/index.vue', + meta: { + title: '底层layout' + }, + children: [] + } + ] + const asyncRouterRes = await asyncMenu() + const asyncRouter = asyncRouterRes.data.menus + asyncRouter && + asyncRouter.push({ + path: 'reload', + name: 'Reload', + hidden: true, + meta: { + title: '', + closeTab: true + }, + component: 'view/error/reload.vue' + }) + formatRouter(asyncRouter, routeMap) + baseRouter[0].children = asyncRouter + if (notLayoutRouterArr.length !== 0) { + baseRouter.push(...notLayoutRouterArr) + } + asyncRouterHandle(baseRouter) + KeepAliveFilter(asyncRouter) + asyncRouters.value = baseRouter + return true + } + + return { + topActive, + setLeftMenu, + topMenu, + leftMenu, + asyncRouters, + keepAliveRouters, + asyncRouterFlag, + SetAsyncRouter, + routeMap, + handleKeepAlive + } +}) diff --git a/web-admin/src/pinia/modules/user.js b/web-admin/src/pinia/modules/user.js new file mode 100644 index 0000000..ccbbfb7 --- /dev/null +++ b/web-admin/src/pinia/modules/user.js @@ -0,0 +1,150 @@ +import { login, getUserInfo } from '@/api/user' +import { jsonInBlacklist } from '@/api/jwt' +import router from '@/router/index' +import { ElLoading, ElMessage } from 'element-plus' +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { useRouterStore } from './router' +import { useCookies } from '@vueuse/integrations/useCookies' +import { useStorage } from '@vueuse/core' + +import { useAppStore } from '@/pinia' + +export const useUserStore = defineStore('user', () => { + const appStore = useAppStore() + const loadingInstance = ref(null) + + const userInfo = ref({ + uuid: '', + nickName: '', + headerImg: '', + authority: {} + }) + const token = useStorage('token', '') + const xToken = useCookies() + const currentToken = computed(() => token.value || xToken.get('x-token') || '') + + const setUserInfo = (val) => { + userInfo.value = val + if (val.originSetting) { + Object.keys(appStore.config).forEach((key) => { + if (val.originSetting[key] !== undefined) { + appStore.config[key] = val.originSetting[key] + } + }) + } + } + + const setToken = (val) => { + token.value = val + xToken.value = val + } + + const NeedInit = async () => { + await ClearStorage() + await router.push({ name: 'Init', replace: true }) + } + + const ResetUserInfo = (value = {}) => { + userInfo.value = { + ...userInfo.value, + ...value + } + } + /* 获取用户信息*/ + const GetUserInfo = async () => { + const res = await getUserInfo() + if (res.code === 0) { + setUserInfo(res.data.userInfo) + } + return res + } + /* 登录*/ + const LoginIn = async (loginInfo) => { + try { + loadingInstance.value = ElLoading.service({ + fullscreen: true, + text: '登录中,请稍候...' + }) + + const res = await login(loginInfo) + + if (res.code !== 0) { + return false + } + // 登陆成功,设置用户信息和权限相关信息 + setUserInfo(res.data.user) + setToken(res.data.token) + + // 初始化路由信息 + const routerStore = useRouterStore() + await routerStore.SetAsyncRouter() + const asyncRouters = routerStore.asyncRouters + + // 注册到路由表里 + asyncRouters.forEach((asyncRouter) => { + router.addRoute(asyncRouter) + }) + + if(router.currentRoute.value.query.redirect) { + await router.replace(router.currentRoute.value.query.redirect) + return true + } + + if (!router.hasRoute(userInfo.value.authority.defaultRouter)) { + ElMessage.error('不存在可以登陆的首页,请联系管理员进行配置') + } else { + await router.replace({ name: userInfo.value.authority.defaultRouter }) + } + + const isWindows = /windows/i.test(navigator.userAgent) + window.localStorage.setItem('osType', isWindows ? 'WIN' : 'MAC') + + // 全部操作均结束,关闭loading并返回 + return true + } catch (error) { + console.error('LoginIn error:', error) + return false + } finally { + loadingInstance.value?.close() + } + } + /* 登出*/ + const LoginOut = async () => { + const res = await jsonInBlacklist() + + // 登出失败 + if (res.code !== 0) { + return + } + + await ClearStorage() + + // 把路由定向到登录页,无需等待直接reload + router.push({ name: 'Login', replace: true }) + window.location.reload() + } + /* 清理数据 */ + const ClearStorage = async () => { + token.value = '' + // 使用remove方法正确删除cookie + xToken.remove() + sessionStorage.clear() + // 清理所有相关的localStorage项 + localStorage.removeItem('originSetting') + localStorage.removeItem('token') + } + + return { + userInfo, + token: currentToken, + NeedInit, + ResetUserInfo, + GetUserInfo, + LoginIn, + LoginOut, + setToken, + loadingInstance, + ClearStorage + } +}) diff --git a/web-admin/src/plugin/announcement/api/info.js b/web-admin/src/plugin/announcement/api/info.js new file mode 100644 index 0000000..e19770b --- /dev/null +++ b/web-admin/src/plugin/announcement/api/info.js @@ -0,0 +1,110 @@ +import service from '@/utils/request' + +// @Tags Info +// @Summary 创建公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "创建公告" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /info/createInfo [post] +export const createInfo = (data) => { + return service({ + url: '/info/createInfo', + method: 'post', + data + }) +} + +// @Tags Info +// @Summary 删除公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "删除公告" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /info/deleteInfo [delete] +export const deleteInfo = (params) => { + return service({ + url: '/info/deleteInfo', + method: 'delete', + params + }) +} + +// @Tags Info +// @Summary 批量删除公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除公告" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /info/deleteInfo [delete] +export const deleteInfoByIds = (params) => { + return service({ + url: '/info/deleteInfoByIds', + method: 'delete', + params + }) +} + +// @Tags Info +// @Summary 更新公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "更新公告" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /info/updateInfo [put] +export const updateInfo = (data) => { + return service({ + url: '/info/updateInfo', + method: 'put', + data + }) +} + +// @Tags Info +// @Summary 用id查询公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.Info true "用id查询公告" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /info/findInfo [get] +export const findInfo = (params) => { + return service({ + url: '/info/findInfo', + method: 'get', + params + }) +} + +// @Tags Info +// @Summary 分页获取公告列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取公告列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /info/getInfoList [get] +export const getInfoList = (params) => { + return service({ + url: '/info/getInfoList', + method: 'get', + params + }) +} +// @Tags Info +// @Summary 获取数据源 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /info/findInfoDataSource [get] +export const getInfoDataSource = () => { + return service({ + url: '/info/getInfoDataSource', + method: 'get' + }) +} diff --git a/web-admin/src/plugin/announcement/form/info.vue b/web-admin/src/plugin/announcement/form/info.vue new file mode 100644 index 0000000..48499b5 --- /dev/null +++ b/web-admin/src/plugin/announcement/form/info.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/web-admin/src/plugin/announcement/view/info.vue b/web-admin/src/plugin/announcement/view/info.vue new file mode 100644 index 0000000..cbb472e --- /dev/null +++ b/web-admin/src/plugin/announcement/view/info.vue @@ -0,0 +1,510 @@ + + + + + diff --git a/web-admin/src/plugin/email/api/email.js b/web-admin/src/plugin/email/api/email.js new file mode 100644 index 0000000..c3f6c7b --- /dev/null +++ b/web-admin/src/plugin/email/api/email.js @@ -0,0 +1,29 @@ +import service from '@/utils/request' +// @Tags System +// @Summary 发送测试邮件 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}" +// @Router /email/emailTest [post] +export const emailTest = (data) => { + return service({ + url: '/email/emailTest', + method: 'post', + data + }) +} + +// @Tags System +// @Summary 发送邮件 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body email_response.Email true "发送邮件必须的参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}" +// @Router /email/sendEmail [post] +export const sendEmail = (data) => { + return service({ + url: '/email/sendEmail', + method: 'post', + data + }) +} diff --git a/web-admin/src/plugin/email/view/index.vue b/web-admin/src/plugin/email/view/index.vue new file mode 100644 index 0000000..b7ad657 --- /dev/null +++ b/web-admin/src/plugin/email/view/index.vue @@ -0,0 +1,60 @@ + + + diff --git a/web-admin/src/router/index.js b/web-admin/src/router/index.js new file mode 100644 index 0000000..b045048 --- /dev/null +++ b/web-admin/src/router/index.js @@ -0,0 +1,41 @@ +import {createRouter, createWebHashHistory} from 'vue-router' + +const routes = [ + { + path: '/', + redirect: '/login' + }, + { + path: '/init', + name: 'Init', + component: () => import('@/view/init/index.vue') + }, + { + path: '/login', + name: 'Login', + component: () => import('@/view/login/index.vue') + }, + { + path: '/scanUpload', + name: 'ScanUpload', + meta: { + title: '扫码上传', + client: true + }, + component: () => import('@/view/example/upload/scanUpload.vue') + }, + { + path: '/:catchAll(.*)', + meta: { + closeTab: true + }, + component: () => import('@/view/error/index.vue') + }, +] + +const router = createRouter({ + history: createWebHashHistory(), + routes +}) + +export default router diff --git a/web-admin/src/style/element/index.scss b/web-admin/src/style/element/index.scss new file mode 100644 index 0000000..0c2de8d --- /dev/null +++ b/web-admin/src/style/element/index.scss @@ -0,0 +1,24 @@ +@forward 'element-plus/theme-chalk/src/common/var.scss' with ( + $colors: ( + 'white': #ffffff, + 'black': #000000, + 'primary': ( + 'base': #4d70ff + ), + 'success': ( + 'base': #67c23a + ), + 'warning': ( + 'base': #e6a23c + ), + 'danger': ( + 'base': #f56c6c + ), + 'error': ( + 'base': #f56c6c + ), + 'info': ( + 'base': #909399 + ) + ) +); diff --git a/web-admin/src/style/element_visiable.scss b/web-admin/src/style/element_visiable.scss new file mode 100644 index 0000000..39ef7cc --- /dev/null +++ b/web-admin/src/style/element_visiable.scss @@ -0,0 +1,138 @@ +@use '@/style/main.scss'; +@use '@/style/reset'; + + +.el-button { + font-weight: 400; + border-radius: 2px; +} + +.gva-pagination { + @apply flex justify-end; + .el-pagination__editor { + .el-input__inner { + @apply h-8; + } + } + + .is-active { + @apply rounded text-white; + background: var(--el-color-primary); + color: #ffffff !important; + } +} + +.el-drawer__header { + margin-bottom: 0 !important; + padding-top: 16px !important; + padding-bottom: 16px !important; + @apply border-0 border-b border-solid border-gray-200; +} + +.el-form--inline { + .el-form-item { + & > .el-input, + .el-cascader, + .el-select, + .el-date-editor, + .el-autocomplete { + @apply w-52; + } + } +} + +.el-dropdown { + @apply overflow-hidden; +} + +.el-table { + tr { + th { + @apply dark:bg-slate-900; + .cell { + @apply leading-[36px] text-gray-700 dark:text-gray-200; + } + } + } + .el-table__row { + td { + @apply dark:bg-slate-900; + .cell { + @apply leading-[32px] text-gray-600 dark:text-gray-300; + } + } + } + tr { + th { + &.is-leaf { + @apply dark:bg-slate-900; + } + } + } +} + +// layout + +// table +.el-pagination { + @apply mt-8; + .btn-prev, + .btn-next { + @apply border border-solid border-gray-300 dark:border-gray-700 rounded; + } + .el-pager { + li { + @apply border border-solid border-gray-300 dark:border-gray-600 rounded text-gray-600 text-sm mx-1; + } + } +} +.el-menu { + background-color: transparent !important; + li { + @apply my-1; + } +} +.el-menu--vertical { + .el-menu-item { + border-radius: 2px; + &.is-active { + background-color: var(--el-color-primary) !important; + color: #fff !important; + } + } +} + +.el-sub-menu.el-sub-menu__hide-arrow { + height: 44px; +} + +.el-tabs__header { + margin: 0 0 1px !important; +} + +.el-sub-menu.is-active { + > .el-sub-menu__title { + color: var(--el-color-primary) !important; + } +} + +.el-menu-item.is-active{ + color: var(--el-color-primary)!important; +} + +.el-sub-menu__title.el-tooltip__trigger, +.el-menu-item .el-menu-tooltip__trigger { + justify-content: center; +} + +.el-menu--horizontal .el-menu .el-sub-menu__title { + justify-content: flex-start; +} + +html.dark { + /* 自定义深色背景颜色 */ + --el-bg-color: rgb(30, 41, 59); + --el-bg-color-overlay: rgb(40, 51, 69); + --el-fill-color-light: rgb(15, 23, 42); + --el-fill-color: rgb(15, 23, 42); +} diff --git a/web-admin/src/style/iconfont.css b/web-admin/src/style/iconfont.css new file mode 100644 index 0000000..623bf13 --- /dev/null +++ b/web-admin/src/style/iconfont.css @@ -0,0 +1,47 @@ +@font-face { + font-family: 'gvaIcon'; + src: url('data:font/ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTZJUyU8AAA14AAAAHEdERUYAKQARAAANWAAAAB5PUy8yPJpJTAAAAVgAAABgY21hcM0T0L4AAAHYAAABWmdhc3D//wADAAANUAAAAAhnbHlmRk3UvwAAA0wAAAbYaGVhZB/a5jgAAADcAAAANmhoZWEHngOFAAABFAAAACRobXR4DaoBrAAAAbgAAAAebG9jYQbMCGgAAAM0AAAAGG1heHABGgB+AAABOAAAACBuYW1lXoIBAgAACiQAAAKCcG9zdN15OnUAAAyoAAAAqAABAAAAAQAA+a916l8PPPUACwQAAAAAAN5YUSMAAAAA3lhRIwBL/8ADwAM1AAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAPAAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAALAHIABQAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZADA5mXmfQOA/4AAAAPcAIAAAAABAAAAAAAAAAAAAAAgAAEEAAAAAAAAAAQAAAAEAACLAIoAYAB1AHYASwBLAGAAAAAAAAMAAAADAAAAHAABAAAAAABUAAMAAQAAABwABAA4AAAACgAIAAIAAuZm5mrmduZ9//8AAOZl5mrmdeZ7//8ZnhmbGZEZjQABAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYAigEcAbgCUAK6AxoDbAACAIsAIANsAswAEQAjAAAlIicBJjQ3ATYeAQYHCQEeAQYhIicBJjQ3ATYeAQYHCQEeAQYDSw0J/qsLCwFVChsSAgr+xAE8CgIV/qkNCP6qCgoBVgkbEgIK/sUBOwoCFCAJATULGQsBNQoCExwI/uL+4ggbFAkBNQsZCwE1CgITHAj+4v7iCRoUAAAAAAIAigAgA2sCzAARACIAAAE0JwEmDgEWFwkBDgEWMjcBNiUBJg4BFhcJAQ4BFjI3ATY0AiAL/qsJHBECCQE8/sQJAhQZCQFVCwFA/qsKGxICCgE8/sQKAhUZCQFVCwF1DQsBNQoCExwI/uL+4gkaFAkBNQskATUKAhMcCP7i/uIJGhQJATULGQADAGD/wAOgAzUATABcAGwAAAE1NCcmJyYiBwYHBh0BDgEdARQWOwEyNj0BNCYrATU0NzY3NjIXFhcWHQEjIgYdARQWOwEGBwYHLgEjIgYUFjMyNjc2NzY3PgE9ATQmBRUUBisBIiY9ATQ2OwEyFgUUBisBIiY9ATQ2OwEyFhUDYDAvT1O+U08vMBslLB9VHi0tHiAoJkFDnENBJiggHi0tHhUPJC5SChwRHCQkHBEeCHJAMxAfKiX9kAYFVQUGBgVVBQYCVQYFVQUGBgVVBQYByQxgUlAuMDAuUFJgDAQqG6seLCweqx4tCk5DQScnJydBQ04KLR6rHiwrGiAGDxElNiUSEAc1KkUBKx6rGyhFqwQGBgSrBQYGsAQGBgSrBQYGBQAABAB1//UDjQMLABsANwBSAHEAABMyNj0BFxYyNjQvATMyNjQmKwEiBwYHBh0BFBYFIgYdAScmIgYUHwEjIgYUFjsBMjc2NzY9ATYmJQc1NCYiBh0BFBcWFxY7ATI2NCYrATc2NCYGATQ1FSYnJisBIgYUFjsBBwYUFjI/ARUUFjI2PQEnJpUNE7wJHRMKvIcMFBQM1ggCDAgCFALiDRPJCRoTCcmJDBQUDNYIAg8CAwES/gbJExkUAggKBAbWDBQUDInJCRMXAgEHCwQG2AwUFAyJvAkSHgi8ExoTAgEB9RQMibwIEhkKvBMZFAIGDAQI1gwU6hQMickJExoJyRMZFAIICgQG2AwUIsmHDBQUDNYIAg8CAxQZE8kKGRMBAcABAQIOAwMUGRO8ChkTCbyHDBQUDNYFBAAABAB2//cDjgMMABoANQBRAG0AAAEjIgYUFjsBMjc2NzY9ATQmIgYdAScmIgYUFwEzMjY0JisBIgcGBwYdARQWMjY9ARcWMjY0JyUmJyYrASIGFBY7AQcGFBYyPwEVFBYyNj0BLgE3FhcWOwEyNjQmKwE3NjQmIg8BNTQmIgYdAR4BATqJDRMTDdUJAg8CAhMaE7cKGRQKAjeJDRMTDdUJAg8CAhMaE8gJHhIK/i8HCgQH1w0TEw2JyQoTHQnIFBkTAQKoBwoEBtYNExMNibwKFBkKvBMZFAICAhoUGRMCBwoEBtYNExMNib4KExoK/iAUGRMCBwoEB9UNExMNickIEhkK8w8CAhMZFMgKGRMJyYkNExMN1QIJzQ8CAhMZFLsKGhMKvIkNExMN1QMIAAAAAAUAS//LA7UDNQAUACkAKgA3AEQAAAEiBwYHBhQXFhcWMjc2NzY0JyYnJgMiJyYnJjQ3Njc2MhcWFxYUBwYHBgMjFB4BMj4BNC4BIg4BFyIGHQEUFjI2PQE0JgIAd2ZiOzs7O2Jm7mZiOzs7O2Jmd2VXVDIzMzJUV8pXVDIzMzJUV2UrDBQWFAwMFBYUDCsNExMaExMDNTs7YmbuZmI7Ozs7YmbuZmI7O/zWMzJUV8pXVDIzMzJUV8pXVDIzAjULFAwMFBYUDAwUgBQM6w0TEw3rDBQAAQBL/+ADwAMgAD0AAAEmBg8BLgEjIgcGBwYUFxYXFjMyPgE3Ni4BBgcOAiMiJyYnJjQ3Njc2MzIeARcnJg4BFh8BMj8BNj8BNCYDpgwXAxc5yXZyY184Ojo4X2NyWaB4HgULGhcFGWaJS2FUUTAwMTBRU2FIhGQbgA0WBw4NwgUIBAwDMQ0CsQMODFhmeDk3XmHiYV43OUV9UQ0XCQsMRWo6MC9PUr9TTy8wNmNBJQMOGhYDMwMBCAu6DRYAAAAAAgBg/8YDugMiAB4AMwAABSc+ATU0JyYnJiIHBgcGFBcWFxYzMjc2NxcWMjc2JiUiJyYnJjQ3Njc2MhcWFxYUBwYHBgOxviouNDFVV8lXVTIzMzJVV2RDPzwzvgkeCAcB/hxUSEYpKiopRkioSEYpKyspRkgCvjB9RGRYVDIzNDJVWMlXVTE0GBYqvgkJChuBKylGSKhIRikqKilGSKhIRikrAAAAABIA3gABAAAAAAAAABMAKAABAAAAAAABAAgATgABAAAAAAACAAcAZwABAAAAAAADAAgAgQABAAAAAAAEAAgAnAABAAAAAAAFAAsAvQABAAAAAAAGAAgA2wABAAAAAAAKACsBPAABAAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=') + format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} +.gvaIcon { + font-family: 'gvaIcon' !important; + font-size: 16px; + font-style: normal; + font-weight: 800; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.gvaIcon-arrow-double-left:before { + content: '\e665'; +} + +.gvaIcon-arrow-double-right:before { + content: '\e666'; +} + +.gvaIcon-fullscreen-shrink:before { + content: '\e676'; +} +.gvaIcon-customer-service:before { + content: '\e66a'; +} + +.gvaIcon-fullscreen-expand:before { + content: '\e675'; +} + +.gvaIcon-prompt:before { + content: '\e67b'; +} + +.gvaIcon-refresh:before { + content: '\e67c'; +} + +.gvaIcon-search:before { + content: '\e67d'; +} diff --git a/web-admin/src/style/main.scss b/web-admin/src/style/main.scss new file mode 100644 index 0000000..749d977 --- /dev/null +++ b/web-admin/src/style/main.scss @@ -0,0 +1,59 @@ +@use '@/style/iconfont.css'; +@use "./transition.scss"; + +.html-grey { + filter: grayscale(100%); +} + +.html-weakenss { + filter: invert(80%); +} + +.gva-table-box { + @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2; + .el-table { + @apply border-x border-t border-b-0 rounded border-table-border border-solid -mx-[1px]; + } +} + +.gva-btn-list { + @apply mb-3 flex items-center flex-wrap gap-2; + .el-button+.el-button{ + @apply ml-0 !important; + } + .el-upload{ + .el-button{ + @apply ml-0 !important; + } + } +} + +#nprogress .bar { + background: #29d !important; +} +.gva-customer-icon { + @apply w-4 h-4; +} + +::-webkit-scrollbar { + @apply hidden; +} + +.gva-search-box { + @apply p-4 pb-0 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2; +} + +.gva-form-box { + @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2; +} + +.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content { + background: var(--el-color-primary-bg) !important; +} + +.el-dropdown { + outline: none; + * { + outline: none; + } +} diff --git a/web-admin/src/style/reset.scss b/web-admin/src/style/reset.scss new file mode 100644 index 0000000..fe879bf --- /dev/null +++ b/web-admin/src/style/reset.scss @@ -0,0 +1,381 @@ +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color` +*/ + +*, +::before, +::after { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: var(--un-default-border-color, #e5e7eb); /* 2 */ +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -moz-tab-size: 4; /* 3 */ + tab-size: 4; /* 3 */ + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + 'Helvetica Neue', + Arial, + 'Noto Sans', + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + 'Noto Color Emoji'; /* 4 */ + + // TODO: 在下一个大版本更新的时候需要改回正确的16px + font-size: 14px; +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; /* 1 */ + line-height: inherit; /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; /* 1 */ + color: inherit; /* 2 */ + border-top-width: 1px; /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; /* 1 */ + border-color: inherit; /* 2 */ + border-collapse: collapse; /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-feature-settings: inherit; /* 1 */ + font-variation-settings: inherit; /* 1 */ + font-size: 100%; /* 1 */ + font-weight: inherit; /* 1 */ + line-height: inherit; /* 1 */ + color: inherit; /* 1 */ + margin: 0; /* 2 */ + padding: 0; /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; /* 1 */ + /* background-color: transparent; */ + background-image: none; /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::placeholder, +textarea::placeholder { + opacity: 1; /* 1 */ + color: #9ca3af; /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role='button'] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; /* 1 */ + vertical-align: middle; /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ +[hidden] { + display: none; +} diff --git a/web-admin/src/style/transition.scss b/web-admin/src/style/transition.scss new file mode 100644 index 0000000..09a2543 --- /dev/null +++ b/web-admin/src/style/transition.scss @@ -0,0 +1,68 @@ + +// 淡入淡出动画 +.fade-enter-active, +.fade-leave-active { + transition: all 0.3s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; + transform: translateY(10px); +} + +.header { + border-radius: 0 0 10px 10px; +} + +.body { + height: calc(100% - 6rem); +} + +@keyframes slideDown { + from { + transform: translateY(-20px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} +// 缩放动画 +.zoom-enter-active, +.zoom-leave-active { + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.zoom-enter-from, +.zoom-leave-to { + opacity: 0; + transform: scale(0.95); +} + + +/* fade-slide */ +.slide-leave-active, +.slide-enter-active { + transition: all 0.3s; +} +.slide-enter-from { + opacity: 0; + transform: translateX(-30px); +} +.slide-leave-to { + opacity: 0; + transform: translateX(30px); +} diff --git a/web-admin/src/utils/asyncRouter.js b/web-admin/src/utils/asyncRouter.js new file mode 100644 index 0000000..9e7a5ca --- /dev/null +++ b/web-admin/src/utils/asyncRouter.js @@ -0,0 +1,29 @@ +const viewModules = import.meta.glob('../view/**/*.vue') +const pluginModules = import.meta.glob('../plugin/**/*.vue') + +export const asyncRouterHandle = (asyncRouter) => { + asyncRouter.forEach((item) => { + if (item.component && typeof item.component === 'string') { + item.meta.path = '/src/' + item.component + if (item.component.split('/')[0] === 'view') { + item.component = dynamicImport(viewModules, item.component) + } else if (item.component.split('/')[0] === 'plugin') { + item.component = dynamicImport(pluginModules, item.component) + } + } + if (item.children) { + asyncRouterHandle(item.children) + } + }) +} + +function dynamicImport(dynamicViewsModules, component) { + const keys = Object.keys(dynamicViewsModules) + const matchKeys = keys.filter((key) => { + const k = key.replace('../', '') + return k === component + }) + const matchKey = matchKeys[0] + + return dynamicViewsModules[matchKey] +} diff --git a/web-admin/src/utils/btnAuth.js b/web-admin/src/utils/btnAuth.js new file mode 100644 index 0000000..c65d671 --- /dev/null +++ b/web-admin/src/utils/btnAuth.js @@ -0,0 +1,7 @@ +import {useRoute} from 'vue-router' +import {reactive} from 'vue' + +export const useBtnAuth = () => { + const route = useRoute() + return route.meta.btns || reactive({}) +} diff --git a/web-admin/src/utils/bus.js b/web-admin/src/utils/bus.js new file mode 100644 index 0000000..f2a3b92 --- /dev/null +++ b/web-admin/src/utils/bus.js @@ -0,0 +1,4 @@ +// using ES6 modules +import mitt from 'mitt' + +export const emitter = mitt() diff --git a/web-admin/src/utils/closeThisPage.js b/web-admin/src/utils/closeThisPage.js new file mode 100644 index 0000000..4ebd01f --- /dev/null +++ b/web-admin/src/utils/closeThisPage.js @@ -0,0 +1,5 @@ +import {emitter} from '@/utils/bus.js' + +export const closeThisPage = () => { + emitter.emit('closeThisPage') +} diff --git a/web-admin/src/utils/date.js b/web-admin/src/utils/date.js new file mode 100644 index 0000000..987a40d --- /dev/null +++ b/web-admin/src/utils/date.js @@ -0,0 +1,44 @@ +// 对Date的扩展,将 Date 转化为指定格式的String +// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, +// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) +// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 +// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18 +// eslint-disable-next-line no-extend-native +Date.prototype.Format = function(fmt) { + const o = { + 'M+': this.getMonth() + 1, // 月份 + 'd+': this.getDate(), // 日 + 'h+': this.getHours(), // 小时 + 'm+': this.getMinutes(), // 分 + 's+': this.getSeconds(), // 秒 + 'q+': Math.floor((this.getMonth() + 3) / 3), // 季度 + 'S': this.getMilliseconds() // 毫秒 + } + const reg = /(y+)/ + if (reg.test(fmt)) { + const t = reg.exec(fmt)[1] + fmt = fmt.replace( + t, + (this.getFullYear() + '').substring(4 - t.length) + ) + } + for (let k in o) { + const regx = new RegExp('(' + k + ')') + if (regx.test(fmt)) { + const t = regx.exec(fmt)[1] + fmt = fmt.replace( + t, + t.length === 1 ? o[k] : ('00' + o[k]).substring(('' + o[k]).length) + ) + } + } + return fmt +} + +export function formatTimeToStr(times, pattern) { + let d = new Date(times).Format('yyyy-MM-dd hh:mm:ss') + if (pattern) { + d = new Date(times).Format(pattern) + } + return d.toLocaleString() +} diff --git a/web-admin/src/utils/dictionary.js b/web-admin/src/utils/dictionary.js new file mode 100644 index 0000000..8044d0c --- /dev/null +++ b/web-admin/src/utils/dictionary.js @@ -0,0 +1,93 @@ +import {useDictionaryStore} from '@/pinia/modules/dictionary' + +/** + * 生成字典缓存key + * @param {string} type - 字典类型 + * @param {number} depth - 深度参数 + * @param {string|number|null} value - 指定节点的value + * @returns {string} 缓存key + */ +const generateCacheKey = (type, depth, value) => { + if (value !== null && value !== undefined) { + return `${type}_value_${value}_depth_${depth}` + } + return depth === 0 ? `${type}_tree` : `${type}_depth_${depth}` +} + +/** + * 获取字典数据 + * @param {string} type - 字典类型,必填 + * @param {Object} options - 可选参数 + * @param {number} options.depth - 指定获取字典的深度,默认为0(完整树形结构) + * @param {string|number|null} options.value - 指定节点的value,获取该节点的children,默认为null + * @returns {Promise} 字典数据数组 + * @example + * // 获取完整的字典树形结构 + * const dictTree = await getDict('user_status') + * + * // 获取指定深度的扁平化字典数据 + * const dictFlat = await getDict('user_status', { + * depth: 2 + * }) + * + * // 获取指定节点的children + * const children = await getDict('user_status', { + * value: 'active' + * }) + */ +export const getDict = async ( + type, + options = { + depth: 0, + value: null + } +) => { + // 参数验证 + if (!type || typeof type !== 'string') { + console.warn('getDict: type参数必须是非空字符串') + return [] + } + + if (typeof options.depth !== 'number' || options.depth < 0) { + console.warn('getDict: depth参数必须是非负数') + options.depth = 0 + } + + try { + const dictionaryStore = useDictionaryStore() + + // 调用store方法获取字典数据 + await dictionaryStore.getDictionary(type, options.depth, options.value) + + // 生成缓存key + const cacheKey = generateCacheKey(type, options.depth, options.value) + + // 从缓存中获取数据 + const result = dictionaryStore.dictionaryMap[cacheKey] + + // 返回数据,确保返回数组 + return Array.isArray(result) ? result : [] + } catch (error) { + console.error('getDict: 获取字典数据失败', { type, options, error }) + return [] + } +} + +// 字典文字展示方法 +export const showDictLabel = ( + dict, + code, + keyCode = 'value', + valueCode = 'label' +) => { + if (!dict) { + return '' + } + const dictMap = {} + dict.forEach((item) => { + if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) { + dictMap[item[keyCode]] = item[valueCode] + } + }) + return Reflect.has(dictMap, code) ? dictMap[code] : '' +} diff --git a/web-admin/src/utils/doc.js b/web-admin/src/utils/doc.js new file mode 100644 index 0000000..55a3949 --- /dev/null +++ b/web-admin/src/utils/doc.js @@ -0,0 +1,3 @@ +export const toDoc = (url) => { + window.open(url, '_blank') +} diff --git a/web-admin/src/utils/downloadImg.js b/web-admin/src/utils/downloadImg.js new file mode 100644 index 0000000..10506c7 --- /dev/null +++ b/web-admin/src/utils/downloadImg.js @@ -0,0 +1,20 @@ +export const downloadImage = (imgsrc, name) => { + // 下载图片地址和图片名 + var image = new Image() + image.setAttribute('crossOrigin', 'anonymous') + image.onload = function () { + var canvas = document.createElement('canvas') + canvas.width = image.width + canvas.height = image.height + var context = canvas.getContext('2d') + context.drawImage(image, 0, 0, image.width, image.height) + var url = canvas.toDataURL('image/png') // 得到图片的base64编码数据 + + var a = document.createElement('a') // 生成一个a元素 + var event = new MouseEvent('click') // 创建一个单击事件 + a.download = name || 'photo' // 设置图片名称 + a.href = url // 将生成的URL设置为a.href属性 + a.dispatchEvent(event) // 触发a的单击事件 + } + image.src = imgsrc +} diff --git a/web-admin/src/utils/env.js b/web-admin/src/utils/env.js new file mode 100644 index 0000000..7053375 --- /dev/null +++ b/web-admin/src/utils/env.js @@ -0,0 +1,3 @@ +export const isDev = import.meta.env.DEV; + +export const isProd = import.meta.env.PROD; diff --git a/web-admin/src/utils/event.js b/web-admin/src/utils/event.js new file mode 100644 index 0000000..4861bf7 --- /dev/null +++ b/web-admin/src/utils/event.js @@ -0,0 +1,17 @@ +export function addEventListen(target, event, handler, capture = false) { + if ( + target.addEventListener && + typeof target.addEventListener === 'function' + ) { + target.addEventListener(event, handler, capture) + } +} + +export function removeEventListen(target, event, handler, capture = false) { + if ( + target.removeEventListener && + typeof target.removeEventListener === 'function' + ) { + target.removeEventListener(event, handler, capture) + } +} diff --git a/web-admin/src/utils/fmtRouterTitle.js b/web-admin/src/utils/fmtRouterTitle.js new file mode 100644 index 0000000..bcaeb67 --- /dev/null +++ b/web-admin/src/utils/fmtRouterTitle.js @@ -0,0 +1,13 @@ +export const fmtTitle = (title, now) => { + const reg = /\$\{(.+?)\}/ + const reg_g = /\$\{(.+?)\}/g + const result = title.match(reg_g) + if (result) { + result.forEach((item) => { + const key = item.match(reg)[1] + const value = now.params[key] || now.query[key] + title = title.replace(item, value) + }) + } + return title +} diff --git a/web-admin/src/utils/format.js b/web-admin/src/utils/format.js new file mode 100644 index 0000000..c55d138 --- /dev/null +++ b/web-admin/src/utils/format.js @@ -0,0 +1,185 @@ +import {formatTimeToStr} from '@/utils/date' +import {getDict} from '@/utils/dictionary' +import {ref} from 'vue' + +export const formatBoolean = (bool) => { + if (bool !== null) { + return bool ? '是' : '否' + } else { + return '' + } +} +export const formatDate = (time) => { + if (time !== null && time !== '') { + var date = new Date(time) + return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss') + } else { + return '' + } +} + +export const filterDict = (value, options) => { + // 递归查找函数 + const findInOptions = (opts, targetValue) => { + if (!opts || !Array.isArray(opts)) return null + + for (const item of opts) { + if (item.value === targetValue) { + return item + } + + if (item.children && Array.isArray(item.children)) { + const found = findInOptions(item.children, targetValue) + if (found) return found + } + } + + return null + } + + const rowLabel = findInOptions(options, value) + return rowLabel && rowLabel.label +} + +export const filterDataSource = (dataSource, value) => { + // 递归查找函数 + const findInDataSource = (data, targetValue) => { + if (!data || !Array.isArray(data)) return null + + for (const item of data) { + // 检查当前项是否匹配 + if (item.value === targetValue) { + return item + } + + // 如果有children属性,递归查找 + if (item.children && Array.isArray(item.children)) { + const found = findInDataSource(item.children, targetValue) + if (found) return found + } + } + + return null + } + + if (Array.isArray(value)) { + return value.map((item) => { + const rowLabel = findInDataSource(dataSource, item) + return rowLabel?.label + }) + } + + const rowLabel = findInDataSource(dataSource, value) + return rowLabel?.label +} + +export const getDictFunc = async (type) => { + const dicts = await getDict(type) + return dicts +} + +const path = + import.meta.env.VITE_BASE_PATH + ':' + import.meta.env.VITE_SERVER_PORT + '/' +export const ReturnArrImg = (arr) => { + const imgArr = [] + if (arr instanceof Array) { + // 如果是数组类型 + for (const arrKey in arr) { + if (arr[arrKey].slice(0, 4) !== 'http') { + imgArr.push(path + arr[arrKey]) + } else { + imgArr.push(arr[arrKey]) + } + } + } else { + // 如果不是数组类型 + if (arr?.slice(0, 4) !== 'http') { + imgArr.push(path + arr) + } else { + imgArr.push(arr) + } + } + return imgArr +} + +export const returnArrImg = ReturnArrImg + +export const onDownloadFile = (url) => { + window.open(path + url) +} +const colorToHex = (u) => { + let e = u.replace('#', '').match(/../g) + for (let t = 0; t < 3; t++) e[t] = parseInt(e[t], 16) + return e +} + +const hexToColor = (u, e, t) => { + let a = [u.toString(16), e.toString(16), t.toString(16)] + for (let n = 0; n < 3; n++) a[n].length === 1 && (a[n] = `0${a[n]}`) + return `#${a.join('')}` +} +const generateAllColors = (u, e) => { + let t = colorToHex(u) + const target = [10, 10, 30] + for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e) + return hexToColor(t[0], t[1], t[2]) +} + +const generateAllLightColors = (u, e) => { + let t = colorToHex(u) + const target = [240, 248, 255] // RGB for blue white color + for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e) + return hexToColor(t[0], t[1], t[2]) +} + +function addOpacityToColor(u, opacity) { + let t = colorToHex(u) + return `rgba(${t[0]}, ${t[1]}, ${t[2]}, ${opacity})` +} + +export const setBodyPrimaryColor = (primaryColor, darkMode) => { + let fmtColorFunc = generateAllColors + if (darkMode === 'light') { + fmtColorFunc = generateAllLightColors + } + + document.documentElement.style.setProperty('--el-color-primary', primaryColor) + document.documentElement.style.setProperty( + '--el-color-primary-bg', + addOpacityToColor(primaryColor, 0.4) + ) + for (let times = 1; times <= 2; times++) { + document.documentElement.style.setProperty( + `--el-color-primary-dark-${times}`, + fmtColorFunc(primaryColor, times / 10) + ) + } + for (let times = 1; times <= 10; times++) { + document.documentElement.style.setProperty( + `--el-color-primary-light-${times}`, + fmtColorFunc(primaryColor, times / 10) + ) + } + document.documentElement.style.setProperty( + `--el-menu-hover-bg-color`, + addOpacityToColor(primaryColor, 0.2) + ) +} + +const baseUrl = ref(import.meta.env.VITE_BASE_API) + +export const getBaseUrl = () => { + return baseUrl.value === '/' ? '' : baseUrl.value +} + +export const CreateUUID = () => { + let d = new Date().getTime() + if (window.performance && typeof window.performance.now === 'function') { + d += performance.now() + } + return '00000000-0000-0000-0000-000000000000'.replace(/0/g, (c) => { + const r = (d + Math.random() * 16) % 16 | 0 // d是随机种子 + d = Math.floor(d / 16) + return (c === '0' ? r : (r & 0x3) | 0x8).toString(16) + }) +} diff --git a/web-admin/src/utils/image.js b/web-admin/src/utils/image.js new file mode 100644 index 0000000..8b65232 --- /dev/null +++ b/web-admin/src/utils/image.js @@ -0,0 +1,126 @@ +export default class ImageCompress { + constructor(file, fileSize, maxWH = 1920) { + this.file = file + this.fileSize = fileSize + this.maxWH = maxWH // 最大长宽 + } + + compress() { + // 压缩 + const fileType = this.file.type + const fileSize = this.file.size / 1024 + return new Promise((resolve) => { + const reader = new FileReader() + reader.readAsDataURL(this.file) + reader.onload = () => { + const canvas = document.createElement('canvas') + const img = document.createElement('img') + img.src = reader.result + img.onload = () => { + const ctx = canvas.getContext('2d') + const _dWH = this.dWH(img.width, img.height, this.maxWH) + canvas.width = _dWH.width + canvas.height = _dWH.height + + // 清空后, 重写画布 + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.drawImage(img, 0, 0, canvas.width, canvas.height) + + const newImgData = canvas.toDataURL(fileType, 0.9) + + // 压缩宽高后的图像大小 + const newImgSize = this.fileSizeKB(newImgData) + + if (newImgSize > this.fileSize) { + console.log('图片尺寸太大!' + fileSize + ' >> ' + newImgSize) + } + + const blob = this.dataURLtoBlob(newImgData, fileType) + const nfile = new File([blob], this.file.name) + resolve(nfile) + } + } + }) + } + + /** + * 长宽等比缩小 + * 图像的一边(长或宽)为最大目标值 + */ + dWH(srcW, srcH, dMax) { + const defaults = { + width: srcW, + height: srcH + } + if (Math.max(srcW, srcH) > dMax) { + if (srcW > srcH) { + defaults.width = dMax + defaults.height = Math.round(srcH * (dMax / srcW)) + return defaults + } else { + defaults.height = dMax + defaults.width = Math.round(srcW * (dMax / srcH)) + return defaults + } + } else { + return defaults + } + } + + fileSizeKB(dataURL) { + let sizeKB = 0 + sizeKB = Math.round((dataURL.split(',')[1].length * 3) / 4 / 1024) + return sizeKB + } + + /** + * 转为Blob + */ + dataURLtoBlob(dataURL, fileType) { + const byteString = atob(dataURL.split(',')[1]) + let mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0] + const ab = new ArrayBuffer(byteString.length) + const ia = new Uint8Array(ab) + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i) + } + if (fileType) { + mimeString = fileType + } + return new Blob([ab], { type: mimeString, lastModifiedDate: new Date() }) + } +} + +const path = import.meta.env.VITE_FILE_API +export const getUrl = (url) => { + if (url && url.slice(0, 4) !== 'http') { + if (path === '/') { + return url + } + if (url.slice(0, 1) === '/') { + return path + url + } + return path + '/' + url + } else { + return url + } +} + +const VIDEO_EXTENSIONS = ['.mp4', '.mov', '.webm', '.ogg'] +const VIDEO_MIME_TYPES = ['video/mp4', 'video/webm', 'video/ogg'] +const IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'] + +export const isVideoExt = (url) => { + const urlLower = url?.toLowerCase() || '' + return urlLower !== '' && VIDEO_EXTENSIONS.some(ext => urlLower.endsWith(ext)) +} + +export const isVideoMime = (type) => { + const typeLower = type?.toLowerCase() || '' + return typeLower !== '' && VIDEO_MIME_TYPES.includes(typeLower) +} + +export const isImageMime = (type) => { + const typeLower = type?.toLowerCase() || '' + return typeLower !== '' && IMAGE_MIME_TYPES.includes(typeLower) +} diff --git a/web-admin/src/utils/page.js b/web-admin/src/utils/page.js new file mode 100644 index 0000000..2c1f9df --- /dev/null +++ b/web-admin/src/utils/page.js @@ -0,0 +1,10 @@ +import {fmtTitle} from '@/utils/fmtRouterTitle' +import config from '@/core/config' + +export default function getPageTitle(pageTitle, route) { + if (pageTitle) { + const title = fmtTitle(pageTitle, route) + return `${title} - ${config.appName}` + } + return `${config.appName}` +} diff --git a/web-admin/src/utils/params.js b/web-admin/src/utils/params.js new file mode 100644 index 0000000..c51de82 --- /dev/null +++ b/web-admin/src/utils/params.js @@ -0,0 +1,14 @@ +import {useParamsStore} from '@/pinia/modules/params' +/* + * 获取参数方法 使用示例 getParams('key').then(res) 或者 async函数下 const res = await getParams('key') + * const res = ref('') + * const fun = async () => { + * res.value = await getParams('test') + * } + * fun() + */ +export const getParams = async(key) => { + const paramsStore = useParamsStore() + await paramsStore.getParams(key) + return paramsStore.paramsMap[key] +} diff --git a/web-admin/src/utils/request.js b/web-admin/src/utils/request.js new file mode 100644 index 0000000..706f6bb --- /dev/null +++ b/web-admin/src/utils/request.js @@ -0,0 +1,203 @@ +import axios from 'axios' // 引入axios +import {useUserStore} from '@/pinia/modules/user' +import {ElLoading, ElMessage} from 'element-plus' +import {emitter} from '@/utils/bus' +import router from '@/router/index' + +const service = axios.create({ + timeout: 99999 +}) +let activeAxios = 0 +let timer +let loadingInstance +let isLoadingVisible = false +let forceCloseTimer + +const showLoading = ( + option = { + target: null + } +) => { + const loadDom = document.getElementById('gva-base-load-dom') + activeAxios++ + + // 清除之前的定时器 + if (timer) { + clearTimeout(timer) + } + + // 清除强制关闭定时器 + if (forceCloseTimer) { + clearTimeout(forceCloseTimer) + } + + timer = setTimeout(() => { + // 再次检查activeAxios状态,防止竞态条件 + if (activeAxios > 0 && !isLoadingVisible) { + if (!option.target) option.target = loadDom + loadingInstance = ElLoading.service(option) + isLoadingVisible = true + + // 设置强制关闭定时器,防止loading永远不关闭(30秒超时) + forceCloseTimer = setTimeout(() => { + if (isLoadingVisible && loadingInstance) { + console.warn('Loading强制关闭:超时30秒') + loadingInstance.close() + isLoadingVisible = false + activeAxios = 0 // 重置计数器 + } + }, 30000) + } + }, 400) +} + +const closeLoading = () => { + activeAxios-- + if (activeAxios <= 0) { + activeAxios = 0 // 确保不会变成负数 + clearTimeout(timer) + + if (forceCloseTimer) { + clearTimeout(forceCloseTimer) + forceCloseTimer = null + } + + if (isLoadingVisible && loadingInstance) { + loadingInstance.close() + isLoadingVisible = false + } + loadingInstance = null + } +} + +// 全局重置loading状态的函数,用于异常情况 +const resetLoading = () => { + activeAxios = 0 + isLoadingVisible = false + + if (timer) { + clearTimeout(timer) + timer = null + } + + if (forceCloseTimer) { + clearTimeout(forceCloseTimer) + forceCloseTimer = null + } + + if (loadingInstance) { + try { + loadingInstance.close() + } catch (e) { + console.warn('关闭loading时出错:', e) + } + loadingInstance = null + } +} + +// http request 拦截器 +service.interceptors.request.use( + (config) => { + if (!config.donNotShowLoading) { + showLoading(config.loadingOption) + } + config.baseURL = config.baseURL || import.meta.env.VITE_BASE_API + const userStore = useUserStore() + config.headers = { + 'Content-Type': 'application/json', + 'x-token': userStore.token, + 'x-user-id': userStore.userInfo.ID, + ...config.headers + } + return config + }, + (error) => { + if (!error.config.donNotShowLoading) { + closeLoading() + } + emitter.emit('show-error', { + code: 'request', + message: error.message || '请求发送失败' + }) + return error + } +) + +function getErrorMessage(error) { + // 优先级: 响应体中的 msg > statusText > 默认消息 + return error.response?.data?.msg || error.response?.statusText || '请求失败' +} + +// http response 拦截器 +service.interceptors.response.use( + (response) => { + const userStore = useUserStore() + if (!response.config.donNotShowLoading) { + closeLoading() + } + if (response.headers['new-token']) { + userStore.setToken(response.headers['new-token']) + } + if (typeof response.data.code === 'undefined') { + return response + } + if (response.data.code === 0 || response.headers.success === 'true') { + if (response.headers.msg) { + response.data.msg = decodeURI(response.headers.msg) + } + return response.data + } else { + ElMessage({ + showClose: true, + message: response.data.msg || decodeURI(response.headers.msg), + type: 'error' + }) + return response.data.msg ? response.data : response + } + }, + (error) => { + if (!error.config.donNotShowLoading) { + closeLoading() + } + + if (!error.response) { + // 网络错误 + resetLoading() + emitter.emit('show-error', { + code: 'network', + message: getErrorMessage(error) + }) + return Promise.reject(error) + } + + // HTTP 状态码错误 + if (error.response.status === 401) { + emitter.emit('show-error', { + code: '401', + message: getErrorMessage(error), + fn: () => { + const userStore = useUserStore() + userStore.ClearStorage() + router.push({ name: 'Login', replace: true }) + } + }) + return Promise.reject(error) + } + + emitter.emit('show-error', { + code: error.response.status, + message: getErrorMessage(error) + }) + return Promise.reject(error) + } +) + +// 监听页面卸载事件,确保loading被正确清理 +if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', resetLoading) + window.addEventListener('unload', resetLoading) +} + +// 导出service和resetLoading函数 +export { resetLoading } +export default service diff --git a/web-admin/src/utils/stringFun.js b/web-admin/src/utils/stringFun.js new file mode 100644 index 0000000..baec83d --- /dev/null +++ b/web-admin/src/utils/stringFun.js @@ -0,0 +1,29 @@ +/* eslint-disable */ +export const toUpperCase = (str) => { + if (str[0]) { + return str.replace(str[0], str[0].toUpperCase()) + } else { + return '' + } +} + +export const toLowerCase = (str) => { + if (str[0]) { + return str.replace(str[0], str[0].toLowerCase()) + } else { + return '' + } +} + +// 驼峰转换下划线 +export const toSQLLine = (str) => { + if (str === 'ID') return 'ID' + return str.replace(/([A-Z])/g, '_$1').toLowerCase() +} + +// 下划线转换驼峰 +export const toHump = (name) => { + return name.replace(/\_(\w)/g, function (all, letter) { + return letter.toUpperCase() + }) +} diff --git a/web-admin/src/view/about/index.vue b/web-admin/src/view/about/index.vue new file mode 100644 index 0000000..a53612e --- /dev/null +++ b/web-admin/src/view/about/index.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/banner.vue b/web-admin/src/view/dashboard/components/banner.vue new file mode 100644 index 0000000..eb70041 --- /dev/null +++ b/web-admin/src/view/dashboard/components/banner.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/card.vue b/web-admin/src/view/dashboard/components/card.vue new file mode 100644 index 0000000..dbc4964 --- /dev/null +++ b/web-admin/src/view/dashboard/components/card.vue @@ -0,0 +1,45 @@ + + + + + + diff --git a/web-admin/src/view/dashboard/components/charts-content-numbers.vue b/web-admin/src/view/dashboard/components/charts-content-numbers.vue new file mode 100644 index 0000000..1476783 --- /dev/null +++ b/web-admin/src/view/dashboard/components/charts-content-numbers.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/charts-people-numbers.vue b/web-admin/src/view/dashboard/components/charts-people-numbers.vue new file mode 100644 index 0000000..0b8f794 --- /dev/null +++ b/web-admin/src/view/dashboard/components/charts-people-numbers.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/charts.vue b/web-admin/src/view/dashboard/components/charts.vue new file mode 100644 index 0000000..66525d7 --- /dev/null +++ b/web-admin/src/view/dashboard/components/charts.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/index.js b/web-admin/src/view/dashboard/components/index.js new file mode 100644 index 0000000..a59985b --- /dev/null +++ b/web-admin/src/view/dashboard/components/index.js @@ -0,0 +1,19 @@ +import GvaBanner from './banner.vue' +import GvaCard from './card.vue' +import GvaChart from './charts.vue' +import GvaTable from './table.vue' +import GvaNotice from './notice.vue' +import GvaQuickLink from './quickLinks.vue' +import GvaWiki from './wiki.vue' +import GvaPluginTable from './pluginTable.vue' + +export { + GvaBanner, + GvaCard, + GvaChart, + GvaTable, + GvaNotice, + GvaQuickLink, + GvaWiki, + GvaPluginTable +} diff --git a/web-admin/src/view/dashboard/components/notice.vue b/web-admin/src/view/dashboard/components/notice.vue new file mode 100644 index 0000000..c2e7365 --- /dev/null +++ b/web-admin/src/view/dashboard/components/notice.vue @@ -0,0 +1,67 @@ + + + + + + diff --git a/web-admin/src/view/dashboard/components/pluginTable.vue b/web-admin/src/view/dashboard/components/pluginTable.vue new file mode 100644 index 0000000..2718a46 --- /dev/null +++ b/web-admin/src/view/dashboard/components/pluginTable.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/quickLinks.vue b/web-admin/src/view/dashboard/components/quickLinks.vue new file mode 100644 index 0000000..3f96a84 --- /dev/null +++ b/web-admin/src/view/dashboard/components/quickLinks.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/table.vue b/web-admin/src/view/dashboard/components/table.vue new file mode 100644 index 0000000..05f2c37 --- /dev/null +++ b/web-admin/src/view/dashboard/components/table.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/web-admin/src/view/dashboard/components/wiki.vue b/web-admin/src/view/dashboard/components/wiki.vue new file mode 100644 index 0000000..0b1cf49 --- /dev/null +++ b/web-admin/src/view/dashboard/components/wiki.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/web-admin/src/view/dashboard/index.vue b/web-admin/src/view/dashboard/index.vue new file mode 100644 index 0000000..7f03c47 --- /dev/null +++ b/web-admin/src/view/dashboard/index.vue @@ -0,0 +1,120 @@ + + + + + + diff --git a/web-admin/src/view/error/index.vue b/web-admin/src/view/error/index.vue new file mode 100644 index 0000000..3d84872 --- /dev/null +++ b/web-admin/src/view/error/index.vue @@ -0,0 +1,49 @@ + + + diff --git a/web-admin/src/view/error/reload.vue b/web-admin/src/view/error/reload.vue new file mode 100644 index 0000000..5ec381b --- /dev/null +++ b/web-admin/src/view/error/reload.vue @@ -0,0 +1,14 @@ + + + diff --git a/web-admin/src/view/example/breakpoint/breakpoint.vue b/web-admin/src/view/example/breakpoint/breakpoint.vue new file mode 100644 index 0000000..b39cd4a --- /dev/null +++ b/web-admin/src/view/example/breakpoint/breakpoint.vue @@ -0,0 +1,335 @@ + + + + + \ No newline at end of file diff --git a/web-admin/src/view/example/customer/customer.vue b/web-admin/src/view/example/customer/customer.vue new file mode 100644 index 0000000..e858852 --- /dev/null +++ b/web-admin/src/view/example/customer/customer.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/web-admin/src/view/example/index.vue b/web-admin/src/view/example/index.vue new file mode 100644 index 0000000..d608bef --- /dev/null +++ b/web-admin/src/view/example/index.vue @@ -0,0 +1,20 @@ + + + diff --git a/web-admin/src/view/example/upload/scanUpload.vue b/web-admin/src/view/example/upload/scanUpload.vue new file mode 100644 index 0000000..59845d7 --- /dev/null +++ b/web-admin/src/view/example/upload/scanUpload.vue @@ -0,0 +1,245 @@ + + + + + + + diff --git a/web-admin/src/view/example/upload/upload.vue b/web-admin/src/view/example/upload/upload.vue new file mode 100644 index 0000000..fdc8682 --- /dev/null +++ b/web-admin/src/view/example/upload/upload.vue @@ -0,0 +1,502 @@ + + + diff --git a/web-admin/src/view/init/index.vue b/web-admin/src/view/init/index.vue new file mode 100644 index 0000000..1b26ad3 --- /dev/null +++ b/web-admin/src/view/init/index.vue @@ -0,0 +1,386 @@ + + + + + diff --git a/web-admin/src/view/layout/aside/asideComponent/asyncSubmenu.vue b/web-admin/src/view/layout/aside/asideComponent/asyncSubmenu.vue new file mode 100644 index 0000000..9b1f8c7 --- /dev/null +++ b/web-admin/src/view/layout/aside/asideComponent/asyncSubmenu.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/web-admin/src/view/layout/aside/asideComponent/index.vue b/web-admin/src/view/layout/aside/asideComponent/index.vue new file mode 100644 index 0000000..e56ed05 --- /dev/null +++ b/web-admin/src/view/layout/aside/asideComponent/index.vue @@ -0,0 +1,47 @@ + + + diff --git a/web-admin/src/view/layout/aside/asideComponent/menuItem.vue b/web-admin/src/view/layout/aside/asideComponent/menuItem.vue new file mode 100644 index 0000000..21e14c6 --- /dev/null +++ b/web-admin/src/view/layout/aside/asideComponent/menuItem.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/web-admin/src/view/layout/aside/combinationMode.vue b/web-admin/src/view/layout/aside/combinationMode.vue new file mode 100644 index 0000000..8117254 --- /dev/null +++ b/web-admin/src/view/layout/aside/combinationMode.vue @@ -0,0 +1,147 @@ + + diff --git a/web-admin/src/view/layout/aside/headMode.vue b/web-admin/src/view/layout/aside/headMode.vue new file mode 100644 index 0000000..3ce02ed --- /dev/null +++ b/web-admin/src/view/layout/aside/headMode.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/web-admin/src/view/layout/aside/index.vue b/web-admin/src/view/layout/aside/index.vue new file mode 100644 index 0000000..3beed87 --- /dev/null +++ b/web-admin/src/view/layout/aside/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/web-admin/src/view/layout/aside/normalMode.vue b/web-admin/src/view/layout/aside/normalMode.vue new file mode 100644 index 0000000..d543a1a --- /dev/null +++ b/web-admin/src/view/layout/aside/normalMode.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/web-admin/src/view/layout/aside/sidebarMode.vue b/web-admin/src/view/layout/aside/sidebarMode.vue new file mode 100644 index 0000000..853b0a5 --- /dev/null +++ b/web-admin/src/view/layout/aside/sidebarMode.vue @@ -0,0 +1,290 @@ + + + diff --git a/web-admin/src/view/layout/header/index.vue b/web-admin/src/view/layout/header/index.vue new file mode 100644 index 0000000..e90c96c --- /dev/null +++ b/web-admin/src/view/layout/header/index.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/web-admin/src/view/layout/header/tools.vue b/web-admin/src/view/layout/header/tools.vue new file mode 100644 index 0000000..3995338 --- /dev/null +++ b/web-admin/src/view/layout/header/tools.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/web-admin/src/view/layout/iframe.vue b/web-admin/src/view/layout/iframe.vue new file mode 100644 index 0000000..d4cdd0e --- /dev/null +++ b/web-admin/src/view/layout/iframe.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/web-admin/src/view/layout/index.vue b/web-admin/src/view/layout/index.vue new file mode 100644 index 0000000..7ff8739 --- /dev/null +++ b/web-admin/src/view/layout/index.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/web-admin/src/view/layout/screenfull/index.vue b/web-admin/src/view/layout/screenfull/index.vue new file mode 100644 index 0000000..e1438b6 --- /dev/null +++ b/web-admin/src/view/layout/screenfull/index.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/web-admin/src/view/layout/search/search.vue b/web-admin/src/view/layout/search/search.vue new file mode 100644 index 0000000..5375d70 --- /dev/null +++ b/web-admin/src/view/layout/search/search.vue @@ -0,0 +1,98 @@ + + + + diff --git a/web-admin/src/view/layout/setting/components/layoutModeCard.vue b/web-admin/src/view/layout/setting/components/layoutModeCard.vue new file mode 100644 index 0000000..e1d9bb1 --- /dev/null +++ b/web-admin/src/view/layout/setting/components/layoutModeCard.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/web-admin/src/view/layout/setting/components/settingItem.vue b/web-admin/src/view/layout/setting/components/settingItem.vue new file mode 100644 index 0000000..2d66c6a --- /dev/null +++ b/web-admin/src/view/layout/setting/components/settingItem.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/web-admin/src/view/layout/setting/components/themeColorPicker.vue b/web-admin/src/view/layout/setting/components/themeColorPicker.vue new file mode 100644 index 0000000..32234fc --- /dev/null +++ b/web-admin/src/view/layout/setting/components/themeColorPicker.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/web-admin/src/view/layout/setting/components/themeModeSelector.vue b/web-admin/src/view/layout/setting/components/themeModeSelector.vue new file mode 100644 index 0000000..bc34b2d --- /dev/null +++ b/web-admin/src/view/layout/setting/components/themeModeSelector.vue @@ -0,0 +1,70 @@ + + + diff --git a/web-admin/src/view/layout/setting/index.vue b/web-admin/src/view/layout/setting/index.vue new file mode 100644 index 0000000..c0a4e63 --- /dev/null +++ b/web-admin/src/view/layout/setting/index.vue @@ -0,0 +1,228 @@ + + + + + + + diff --git a/web-admin/src/view/layout/setting/modules/appearance/index.vue b/web-admin/src/view/layout/setting/modules/appearance/index.vue new file mode 100644 index 0000000..2ee327b --- /dev/null +++ b/web-admin/src/view/layout/setting/modules/appearance/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/web-admin/src/view/layout/setting/modules/general/index.vue b/web-admin/src/view/layout/setting/modules/general/index.vue new file mode 100644 index 0000000..e3bbbce --- /dev/null +++ b/web-admin/src/view/layout/setting/modules/general/index.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/web-admin/src/view/layout/setting/modules/layout/index.vue b/web-admin/src/view/layout/setting/modules/layout/index.vue new file mode 100644 index 0000000..d8113ff --- /dev/null +++ b/web-admin/src/view/layout/setting/modules/layout/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/web-admin/src/view/layout/tabs/index.vue b/web-admin/src/view/layout/tabs/index.vue new file mode 100644 index 0000000..530724e --- /dev/null +++ b/web-admin/src/view/layout/tabs/index.vue @@ -0,0 +1,421 @@ + + + + + diff --git a/web-admin/src/view/login/index.vue b/web-admin/src/view/login/index.vue new file mode 100644 index 0000000..5977fd4 --- /dev/null +++ b/web-admin/src/view/login/index.vue @@ -0,0 +1,251 @@ + + + diff --git a/web-admin/src/view/person/person.vue b/web-admin/src/view/person/person.vue new file mode 100644 index 0000000..539ed61 --- /dev/null +++ b/web-admin/src/view/person/person.vue @@ -0,0 +1,632 @@ + + + + + diff --git a/web-admin/src/view/routerHolder.vue b/web-admin/src/view/routerHolder.vue new file mode 100644 index 0000000..1b671ab --- /dev/null +++ b/web-admin/src/view/routerHolder.vue @@ -0,0 +1,22 @@ + + + + diff --git a/web-admin/src/view/superAdmin/api/api.vue b/web-admin/src/view/superAdmin/api/api.vue new file mode 100644 index 0000000..5de9bad --- /dev/null +++ b/web-admin/src/view/superAdmin/api/api.vue @@ -0,0 +1,918 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/authority/authority.vue b/web-admin/src/view/superAdmin/authority/authority.vue new file mode 100644 index 0000000..ae49ce2 --- /dev/null +++ b/web-admin/src/view/superAdmin/authority/authority.vue @@ -0,0 +1,595 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/authority/components/apis.vue b/web-admin/src/view/superAdmin/authority/components/apis.vue new file mode 100644 index 0000000..0b8fb4d --- /dev/null +++ b/web-admin/src/view/superAdmin/authority/components/apis.vue @@ -0,0 +1,174 @@ + + + diff --git a/web-admin/src/view/superAdmin/authority/components/datas.vue b/web-admin/src/view/superAdmin/authority/components/datas.vue new file mode 100644 index 0000000..2e02c8b --- /dev/null +++ b/web-admin/src/view/superAdmin/authority/components/datas.vue @@ -0,0 +1,145 @@ + + + diff --git a/web-admin/src/view/superAdmin/authority/components/menus.vue b/web-admin/src/view/superAdmin/authority/components/menus.vue new file mode 100644 index 0000000..f1a3c48 --- /dev/null +++ b/web-admin/src/view/superAdmin/authority/components/menus.vue @@ -0,0 +1,305 @@ + + + diff --git a/web-admin/src/view/superAdmin/dictionary/sysDictionary.vue b/web-admin/src/view/superAdmin/dictionary/sysDictionary.vue new file mode 100644 index 0000000..c676c84 --- /dev/null +++ b/web-admin/src/view/superAdmin/dictionary/sysDictionary.vue @@ -0,0 +1,924 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/dictionary/sysDictionaryDetail.vue b/web-admin/src/view/superAdmin/dictionary/sysDictionaryDetail.vue new file mode 100644 index 0000000..d4045dc --- /dev/null +++ b/web-admin/src/view/superAdmin/dictionary/sysDictionaryDetail.vue @@ -0,0 +1,430 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/index.vue b/web-admin/src/view/superAdmin/index.vue new file mode 100644 index 0000000..ee6ca39 --- /dev/null +++ b/web-admin/src/view/superAdmin/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/web-admin/src/view/superAdmin/menu/components/components-cascader.vue b/web-admin/src/view/superAdmin/menu/components/components-cascader.vue new file mode 100644 index 0000000..bf6f0e0 --- /dev/null +++ b/web-admin/src/view/superAdmin/menu/components/components-cascader.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/menu/icon.vue b/web-admin/src/view/superAdmin/menu/icon.vue new file mode 100644 index 0000000..d4548e4 --- /dev/null +++ b/web-admin/src/view/superAdmin/menu/icon.vue @@ -0,0 +1,1179 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/menu/menu.vue b/web-admin/src/view/superAdmin/menu/menu.vue new file mode 100644 index 0000000..ea52ee6 --- /dev/null +++ b/web-admin/src/view/superAdmin/menu/menu.vue @@ -0,0 +1,932 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/operation/sysOperationRecord.vue b/web-admin/src/view/superAdmin/operation/sysOperationRecord.vue new file mode 100644 index 0000000..27764c1 --- /dev/null +++ b/web-admin/src/view/superAdmin/operation/sysOperationRecord.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/params/sysParams.vue b/web-admin/src/view/superAdmin/params/sysParams.vue new file mode 100644 index 0000000..f18520c --- /dev/null +++ b/web-admin/src/view/superAdmin/params/sysParams.vue @@ -0,0 +1,604 @@ + + + + + diff --git a/web-admin/src/view/superAdmin/user/user.vue b/web-admin/src/view/superAdmin/user/user.vue new file mode 100644 index 0000000..3dfe17f --- /dev/null +++ b/web-admin/src/view/superAdmin/user/user.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/web-admin/src/view/system/state.vue b/web-admin/src/view/system/state.vue new file mode 100644 index 0000000..e54eeb9 --- /dev/null +++ b/web-admin/src/view/system/state.vue @@ -0,0 +1,192 @@ + + + + diff --git a/web-admin/src/view/systemTools/apiToken/index.vue b/web-admin/src/view/systemTools/apiToken/index.vue new file mode 100644 index 0000000..98bdc64 --- /dev/null +++ b/web-admin/src/view/systemTools/apiToken/index.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/web-admin/src/view/systemTools/autoCode/component/fieldDialog.vue b/web-admin/src/view/systemTools/autoCode/component/fieldDialog.vue new file mode 100644 index 0000000..b407a53 --- /dev/null +++ b/web-admin/src/view/systemTools/autoCode/component/fieldDialog.vue @@ -0,0 +1,502 @@ + + + diff --git a/web-admin/src/view/systemTools/autoCode/component/previewCodeDialog.vue b/web-admin/src/view/systemTools/autoCode/component/previewCodeDialog.vue new file mode 100644 index 0000000..74646e3 --- /dev/null +++ b/web-admin/src/view/systemTools/autoCode/component/previewCodeDialog.vue @@ -0,0 +1,119 @@ + + + diff --git a/web-admin/src/view/systemTools/autoCode/index.vue b/web-admin/src/view/systemTools/autoCode/index.vue new file mode 100644 index 0000000..fa169bd --- /dev/null +++ b/web-admin/src/view/systemTools/autoCode/index.vue @@ -0,0 +1,1667 @@ + + + + + diff --git a/web-admin/src/view/systemTools/autoCode/mcp.vue b/web-admin/src/view/systemTools/autoCode/mcp.vue new file mode 100644 index 0000000..8097b40 --- /dev/null +++ b/web-admin/src/view/systemTools/autoCode/mcp.vue @@ -0,0 +1,151 @@ + + + diff --git a/web-admin/src/view/systemTools/autoCode/mcpTest.vue b/web-admin/src/view/systemTools/autoCode/mcpTest.vue new file mode 100644 index 0000000..925ff8d --- /dev/null +++ b/web-admin/src/view/systemTools/autoCode/mcpTest.vue @@ -0,0 +1,261 @@ + + + \ No newline at end of file diff --git a/web-admin/src/view/systemTools/autoCode/picture.vue b/web-admin/src/view/systemTools/autoCode/picture.vue new file mode 100644 index 0000000..9eb5df5 --- /dev/null +++ b/web-admin/src/view/systemTools/autoCode/picture.vue @@ -0,0 +1,425 @@ + + + diff --git a/web-admin/src/view/systemTools/autoCodeAdmin/index.vue b/web-admin/src/view/systemTools/autoCodeAdmin/index.vue new file mode 100644 index 0000000..58e4e19 --- /dev/null +++ b/web-admin/src/view/systemTools/autoCodeAdmin/index.vue @@ -0,0 +1,614 @@ + + + diff --git a/web-admin/src/view/systemTools/autoPkg/autoPkg.vue b/web-admin/src/view/systemTools/autoPkg/autoPkg.vue new file mode 100644 index 0000000..bfbf0d8 --- /dev/null +++ b/web-admin/src/view/systemTools/autoPkg/autoPkg.vue @@ -0,0 +1,202 @@ + + + diff --git a/web-admin/src/view/systemTools/exportTemplate/code.js b/web-admin/src/view/systemTools/exportTemplate/code.js new file mode 100644 index 0000000..98823ee --- /dev/null +++ b/web-admin/src/view/systemTools/exportTemplate/code.js @@ -0,0 +1,32 @@ +export const getCode = (templateID) => { + return ` + +` +} diff --git a/web-admin/src/view/systemTools/exportTemplate/exportTemplate.vue b/web-admin/src/view/systemTools/exportTemplate/exportTemplate.vue new file mode 100644 index 0000000..3ef2b66 --- /dev/null +++ b/web-admin/src/view/systemTools/exportTemplate/exportTemplate.vue @@ -0,0 +1,1166 @@ + + + + + diff --git a/web-admin/src/view/systemTools/formCreate/index.vue b/web-admin/src/view/systemTools/formCreate/index.vue new file mode 100644 index 0000000..ea4c6d3 --- /dev/null +++ b/web-admin/src/view/systemTools/formCreate/index.vue @@ -0,0 +1,208 @@ + + + + + + diff --git a/web-admin/src/view/systemTools/index.vue b/web-admin/src/view/systemTools/index.vue new file mode 100644 index 0000000..387ed43 --- /dev/null +++ b/web-admin/src/view/systemTools/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/web-admin/src/view/systemTools/installPlugin/index.vue b/web-admin/src/view/systemTools/installPlugin/index.vue new file mode 100644 index 0000000..f204164 --- /dev/null +++ b/web-admin/src/view/systemTools/installPlugin/index.vue @@ -0,0 +1,125 @@ + + + diff --git a/web-admin/src/view/systemTools/loginLog/index.vue b/web-admin/src/view/systemTools/loginLog/index.vue new file mode 100644 index 0000000..82d44f9 --- /dev/null +++ b/web-admin/src/view/systemTools/loginLog/index.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/web-admin/src/view/systemTools/pubPlug/pubPlug.vue b/web-admin/src/view/systemTools/pubPlug/pubPlug.vue new file mode 100644 index 0000000..09a37ed --- /dev/null +++ b/web-admin/src/view/systemTools/pubPlug/pubPlug.vue @@ -0,0 +1,305 @@ + + + + + diff --git a/web-admin/src/view/systemTools/skills/index.vue b/web-admin/src/view/systemTools/skills/index.vue new file mode 100644 index 0000000..2524309 --- /dev/null +++ b/web-admin/src/view/systemTools/skills/index.vue @@ -0,0 +1,1460 @@ + + + + diff --git a/web-admin/src/view/systemTools/sysError/sysError.vue b/web-admin/src/view/systemTools/sysError/sysError.vue new file mode 100644 index 0000000..5203917 --- /dev/null +++ b/web-admin/src/view/systemTools/sysError/sysError.vue @@ -0,0 +1,457 @@ + + + diff --git a/web-admin/src/view/systemTools/system/system.vue b/web-admin/src/view/systemTools/system/system.vue new file mode 100644 index 0000000..4357e62 --- /dev/null +++ b/web-admin/src/view/systemTools/system/system.vue @@ -0,0 +1,1135 @@ + + + + + diff --git a/web-admin/src/view/systemTools/version/version.vue b/web-admin/src/view/systemTools/version/version.vue new file mode 100644 index 0000000..f61d1d2 --- /dev/null +++ b/web-admin/src/view/systemTools/version/version.vue @@ -0,0 +1,998 @@ + + + + + diff --git a/web-admin/uno.config.js b/web-admin/uno.config.js new file mode 100644 index 0000000..779a77e --- /dev/null +++ b/web-admin/uno.config.js @@ -0,0 +1,26 @@ +import { defineConfig } from '@unocss/vite'; +import presetWind3 from '@unocss/preset-wind3'; +import transformerDirectives from '@unocss/transformer-directives' + +export default defineConfig({ + theme: { + backgroundColor: { + main: '#F5F5F5' + }, + textColor: { + active: 'var(--el-color-primary)' + }, + boxShadowColor: { + active: 'var(--el-color-primary)' + }, + borderColor: { + 'table-border': 'var(--el-border-color-lighter)' + } + }, + presets: [ + presetWind3({ dark: 'class' }) + ], + transformers: [ + transformerDirectives(), + ], +}) diff --git a/web-admin/vite.config.js b/web-admin/vite.config.js new file mode 100644 index 0000000..ddefbb1 --- /dev/null +++ b/web-admin/vite.config.js @@ -0,0 +1,118 @@ +import legacyPlugin from '@vitejs/plugin-legacy' +import { viteLogo } from './src/core/config' +import Banner from 'vite-plugin-banner' +import * as path from 'path' +import { loadEnv } from 'vite' +import vuePlugin from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' +import VueFilePathPlugin from './vitePlugin/componentName/index.js' +import { svgBuilder } from 'vite-auto-import-svg' +import vueRootValidator from 'vite-check-multiple-dom' +import { AddSecret } from './vitePlugin/secret' +import UnoCSS from '@unocss/vite' + +// @see https://cn.vitejs.dev/config/ +export default ({ mode }) => { + AddSecret('') + const env = loadEnv(mode, process.cwd()) + viteLogo(env) + + const timestamp = Date.parse(new Date()) + + const optimizeDeps = {} + + const alias = { + '@': path.resolve(__dirname, './src'), + vue$: 'vue/dist/vue.runtime.esm-bundler.js' + } + + const esbuild = {} + + const rollupOptions = { + output: { + entryFileNames: 'assets/087AC4D233B64EB0[name].[hash].js', + chunkFileNames: 'assets/087AC4D233B64EB0[name].[hash].js', + assetFileNames: 'assets/087AC4D233B64EB0[name].[hash].[ext]' + } + } + + const base = '/' + const root = './' + const outDir = 'dist' + + const config = { + base: base, // 编译后js导入的资源路径 + root: root, // index.html文件所在位置 + publicDir: 'public', // 静态资源文件夹 + resolve: { + alias + }, + css: { + preprocessorOptions: { + scss: { + api: 'modern-compiler' // or "modern" + } + } + }, + server: { + // 如果使用docker-compose开发模式,设置为false + open: true, + port: Number(env.VITE_CLI_PORT), + proxy: { + // 把key的路径代理到target位置 + // detail: https://cli.vuejs.org/config/#devserver-proxy + [env.VITE_BASE_API]: { + // 需要代理的路径 例如 '/api' + target: `${env.VITE_BASE_PATH}:${env.VITE_SERVER_PORT}/`, // 代理到 目标路径 + changeOrigin: true, + rewrite: (path) => + path.replace(new RegExp('^' + env.VITE_BASE_API), '') + }, + '/plugin': { + // 需要代理的路径 例如 '/api' + target: `https://plugin.gin-vue-admin.com/api/`, // 代理到 目标路径 + changeOrigin: true, + rewrite: (path) => + path.replace(new RegExp('^/plugin'), '') + } + } + }, + build: { + minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用terser + manifest: false, // 是否产出manifest.json + sourcemap: false, // 是否产出sourcemap.json + outDir: outDir, // 产出目录 + terserOptions: { + compress: { + //生产环境时移除console + drop_console: true, + drop_debugger: true + } + }, + rollupOptions + }, + esbuild, + optimizeDeps, + plugins: [ + env.VITE_POSITION === 'open' && + vueDevTools({ launchEditor: env.VITE_EDITOR }), + legacyPlugin({ + targets: [ + 'Android > 39', + 'Chrome >= 60', + 'Safari >= 10.1', + 'iOS >= 10.3', + 'Firefox >= 54', + 'Edge >= 15' + ] + }), + vuePlugin(), + svgBuilder(['./src/plugin/', './src/assets/icons/'], base, outDir, 'assets', mode), + [Banner(`\n Build based on gin-vue-admin \n Time : ${timestamp}`)], + VueFilePathPlugin('./src/pathInfo.json'), + UnoCSS(), + vueRootValidator() + ] + } + return config +} diff --git a/web-admin/vitePlugin/componentName/index.js b/web-admin/vitePlugin/componentName/index.js new file mode 100644 index 0000000..5628992 --- /dev/null +++ b/web-admin/vitePlugin/componentName/index.js @@ -0,0 +1,88 @@ +import fs from 'fs' +import path from 'path' +import chokidar from 'chokidar' + +const toPascalCase = (str) => { + return str.replace(/(^\w|-\w)/g, clearAndUpper) +} + +const clearAndUpper = (text) => { + return text.replace(/-/, '').toUpperCase() +} + +// 递归获取目录下所有的 .vue 文件 +const getAllVueFiles = (dir, fileList = []) => { + const files = fs.readdirSync(dir) + files.forEach((file) => { + const filePath = path.join(dir, file) + if (fs.statSync(filePath).isDirectory()) { + getAllVueFiles(filePath, fileList) + } else if (filePath.endsWith('.vue')) { + fileList.push(filePath) + } + }) + return fileList +} + +// 从 .vue 文件内容中提取组件名称 +const extractComponentName = (fileContent) => { + const regex = /defineOptions\(\s*{\s*name:\s*["']([^"']+)["']/ + const match = fileContent.match(regex) + return match ? match[1] : null +} + +// Vite 插件定义 +const vueFilePathPlugin = (outputFilePath) => { + let root + let isDev = false + const generatePathNameMap = () => { + const vueFiles = [ + ...getAllVueFiles(path.join(root, 'src/view')), + ...getAllVueFiles(path.join(root, 'src/plugin')) + ] + const pathNameMap = vueFiles.reduce((acc, filePath) => { + const content = fs.readFileSync(filePath, 'utf-8') + const componentName = extractComponentName(content) + let relativePath = '/' + path.relative(root, filePath).replace(/\\/g, '/') + acc[relativePath] = + componentName || toPascalCase(path.basename(filePath, '.vue')) + return acc + }, {}) + const outputContent = JSON.stringify(pathNameMap, null, 2) + fs.writeFileSync(outputFilePath, outputContent) + } + + const watchDirectoryChanges = () => { + const watchDirectories = [ + path.join(root, 'src/view'), + path.join(root, 'src/plugin') + ] + const watcher = chokidar.watch(watchDirectories, { + persistent: true, + ignoreInitial: true + }) + watcher.on('all', () => { + generatePathNameMap() + }) + } + + return { + name: 'vue-file-path-plugin', + configResolved(resolvedConfig) { + root = resolvedConfig.root + if (resolvedConfig.mode === 'development') { + isDev = true + } + }, + buildStart() { + generatePathNameMap() + }, + buildEnd() { + if (isDev) { + watchDirectoryChanges() + } + } + } +} + +export default vueFilePathPlugin diff --git a/web-admin/vitePlugin/secret/index.js b/web-admin/vitePlugin/secret/index.js new file mode 100644 index 0000000..93a8464 --- /dev/null +++ b/web-admin/vitePlugin/secret/index.js @@ -0,0 +1,6 @@ +export function AddSecret(secret) { + if (!secret) { + secret = '' + } + global['gva-secret'] = secret +}