commit 8eb583397d28585ad44969c06cb7de74c9d8224d Author: xiangheng <11675084@qq.com> Date: Fri Nov 24 16:46:30 2023 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ae4f47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Build and Release Folders +.idea +bin-debug/ +bin-release/ +[Oo]bj/ +[Bb]in/ + +# Other files and folders +.settings/ + +# Executables +*.swf +*.air +*.ipa +*.apk + +/public/uploads/* +!/public/uploads/index.html +frontend/* +# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` +# should NOT be excluded as they contain compiler settings and other important +# information for Eclipse / Flash Builder. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4742ef0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 likeadmin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5f93f9 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +

likeadmin通用管理后台(Go)

+

🚀快速开发、✅后台多端自适应

+

+ + +
+

本仓库基于:https://gitee.com/likeadmin/likeadmin_go

+ +

+本仓库经过魔改精简。 +

+
+ +![](./docs/assets/work.png) + +
+
+ +## 👀 体验 + +### 管理后台 +本仓库演示地址:http://likeadmin.adtk.cn
+原仓库演示地址:https://go-admin.likeadmin.cn
+账号:admin 密码:123456 + +### 开发部署文档 +1. [go-打包.md](./docs/1.go-打包.md) +2. [go-运行.md](./docs/2.go-运行.md) +3. [go-nginx配置.md](./docs/3.go-nginx配置.md) + + +## 👨‍💻‍ 简介 + +我们希望能够为开源社区做出更多的贡献,推出永久免费开源的 likeadmin 通用前后端分离管理后台系统。遵循 MIT 开源许可协议,您可以免费使用,甚至允许把你基于 likeadmin 开发的软件应用开源、发布、销售。 +
+ +## 🧐 进一步了解 + +### 🧰 场景介绍 + +1.likeadmin 已经搭建好前后端分离的底层,包含程序安装、登录、登出、工作台、菜单权限控制、角色、管理员、部门管理、岗位管理、素材管理、网站设置、图库管理等基础功能,无需重复造轮子。 + +### 🐹 前端架构方面 + +#### 后台 + +1.使用最流性的前后端分离方案 typescript、vue3、vite 开发,保持了代码的简洁、一致和规范。
2.后台界面使用 element-plus UI 框架,简单精美的后台界面,丰富的组件库,方便快速开发,满足各种后台交互。 +
+ +#### 🛠️  代码生成器 + +一键生成前后端业务代码,大大提示开发效率。 +![](./docs/assets/genCode.png) diff --git a/admin/.editorconfig b/admin/.editorconfig new file mode 100644 index 0000000..f669cf1 --- /dev/null +++ b/admin/.editorconfig @@ -0,0 +1,14 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/admin/.env.development.example b/admin/.env.development.example new file mode 100644 index 0000000..8cd4d01 --- /dev/null +++ b/admin/.env.development.example @@ -0,0 +1,4 @@ +NODE_ENV = 'development' + +# 请求域名 +VITE_APP_BASE_URL='http://127.0.0.1:8001' diff --git a/admin/.env.production.example b/admin/.env.production.example new file mode 100644 index 0000000..e841ff9 --- /dev/null +++ b/admin/.env.production.example @@ -0,0 +1,4 @@ +NODE_ENV = 'production' + +# 请求域名 +VITE_APP_BASE_URL='' \ No newline at end of file diff --git a/admin/.eslintrc.cjs b/admin/.eslintrc.cjs new file mode 100644 index 0000000..fa81252 --- /dev/null +++ b/admin/.eslintrc.cjs @@ -0,0 +1,43 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + ignorePatterns: ['/auto-imports.d.ts', '/components.d.ts'], + extends: [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript/recommended', + '@vue/eslint-config-prettier', + './.eslintrc-auto-import.json' + ], + rules: { + 'prettier/prettier': [ + 'warn', + { + semi: false, + singleQuote: true, + printWidth: 100, + proseWrap: 'preserve', + bracketSameLine: false, + endOfLine: 'lf', + tabWidth: 4, + useTabs: false, + trailingComma: 'none' + } + ], + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + 'no-undef': 'off', + 'vue/prefer-import-from-vue': 'off', + 'no-prototype-builtins': 'off', + 'prefer-spread': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', + 'vue/no-mutating-props': 'off' + }, + globals: { + module: 'readonly' + } +} diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 0000000..d8c3f12 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,36 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +pnpm-lock.yaml +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# unplugin-auto-import +auto-imports.d.ts +components.d.ts +.eslintrc-auto-import.json + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# .env +.env.development +.env.production diff --git a/admin/.prettierrc b/admin/.prettierrc new file mode 100644 index 0000000..f37c5f4 --- /dev/null +++ b/admin/.prettierrc @@ -0,0 +1,11 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "proseWrap": "preserve", + "bracketSameLine": false, + "endOfLine": "lf", + "tabWidth": 4, + "useTabs": false, + "trailingComma": "none" +} diff --git a/admin/.vscode/extensions.json b/admin/.vscode/extensions.json new file mode 100644 index 0000000..91f12b2 --- /dev/null +++ b/admin/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/admin/.vscode/settings.json b/admin/.vscode/settings.json new file mode 100644 index 0000000..1b50e37 --- /dev/null +++ b/admin/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "editor.detectIndentation": false, + "editor.tabSize": 4, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "css.validate": false, + "less.validate": false, + "scss.validate": false +} diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 0000000..077a568 --- /dev/null +++ b/admin/README.md @@ -0,0 +1,46 @@ +# vue-project + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. + +If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: + +1. Disable the built-in TypeScript Extension + 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette + 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` +2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm run lint +``` diff --git a/admin/global.d.ts b/admin/global.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/admin/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..71d3dbb --- /dev/null +++ b/admin/index.html @@ -0,0 +1,66 @@ + + + + + + + 后台管理系统 + + + +
+
+ + + +
+
+ + + diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 0000000..5514b20 --- /dev/null +++ b/admin/package.json @@ -0,0 +1,63 @@ +{ + "name": "vue-project", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "dev": "vite", + "prod": "vite build", + "preview": "vite preview --port 4173", + "build": "node ./scripts/build.mjs", + "type-check": "vue-tsc --noEmit", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.1.0", + "@highlightjs/vue-plugin": "^2.1.0", + "@vue/shared": "^3.3.8", + "@vueuse/core": "^10.6.1", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", + "axios": "^1.6.2", + "consola": "^3.2.3", + "css-color-function": "^1.3.3", + "echarts": "^5.4.3", + "element-plus": "^2.4.2", + "highlight.js": "^11.9.0", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "pinia": "^2.1.7", + "vue": "^3.3.8", + "vue-clipboard3": "^2.0.0", + "vue-echarts": "^6.6.1", + "vue-router": "^4.2.5", + "vue3-video-play": "^1.3.2", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.5.1", + "@types/lodash-es": "^4.17.11", + "@types/node": "^20.9.2", + "@types/nprogress": "^0.2.3", + "@vitejs/plugin-vue": "^4.5.0", + "@vitejs/plugin-vue-jsx": "^3.1.0", + "@vue/eslint-config-prettier": "^8.0.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.4.0", + "autoprefixer": "^10.4.16", + "eslint": "^8.54.0", + "eslint-plugin-vue": "^9.18.1", + "execa": "^8.0.1", + "fs-extra": "^11.1.1", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "sass": "^1.69.5", + "tailwindcss": "^3.3.5", + "typescript": "~5.2.2", + "unplugin-auto-import": "^0.16.7", + "unplugin-vue-components": "^0.25.2", + "vite": "^4.5.0", + "vite-plugin-style-import": "^2.0.0", + "vite-plugin-svg-icons": "^2.0.1", + "vue-tsc": "^1.8.22" + } +} diff --git a/admin/postcss.config.js b/admin/postcss.config.js new file mode 100644 index 0000000..ff8ef3c --- /dev/null +++ b/admin/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} diff --git a/admin/scripts/build.mjs b/admin/scripts/build.mjs new file mode 100644 index 0000000..e015484 --- /dev/null +++ b/admin/scripts/build.mjs @@ -0,0 +1,37 @@ +import { execaCommand } from 'execa' +import path from 'path' +import fsExtra from 'fs-extra' +const { existsSync, remove, copy } = fsExtra +const cwd = process.cwd() +//打包发布路径,谨慎改动 +const releaseRelativePath = '../frontend' +const distPath = path.resolve(cwd, 'dist') +const releasePath = path.resolve(cwd, releaseRelativePath) + +async function build() { + await execaCommand('vite build', { stdio: 'inherit', encoding: 'utf-8', cwd }) + if (existsSync(releasePath)) { + await remove(releasePath) + } + console.log(`文件正在复制 ==> ${releaseRelativePath}`) + try { + await copyFile(distPath, releasePath) + } catch (error) { + console.log(`\n ${error}`) + } + console.log(`文件已复制 ==> ${releaseRelativePath}`) +} + +function copyFile(sourceDir, targetDir) { + return new Promise((resolve, reject) => { + copy(sourceDir, targetDir, (err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) +} + +build() diff --git a/admin/src/App.vue b/admin/src/App.vue new file mode 100644 index 0000000..08cba46 --- /dev/null +++ b/admin/src/App.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/admin/src/api/app.ts b/admin/src/api/app.ts new file mode 100644 index 0000000..3036211 --- /dev/null +++ b/admin/src/api/app.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// 配置 +export function getConfig() { + return request.get({ url: '/common/index/config' }) +} + +// 工作台主页 +export function getWorkbench() { + return request.get({ url: '/common/index/console' }) +} diff --git a/admin/src/api/article.ts b/admin/src/api/article.ts new file mode 100644 index 0000000..30a1a85 --- /dev/null +++ b/admin/src/api/article.ts @@ -0,0 +1,69 @@ +import request from '@/utils/request' + +// 文章分类列表 +export function articleCateLists(params?: any) { + return request.get({ url: '/article/cate/list', params }) +} +// 文章分类列表 +export function articleCateAll(params?: any) { + return request.get({ url: '/article/cate/all', params }) +} + +// 添加文章分类 +export function articleCateAdd(params: any) { + return request.post({ url: '/article/cate/add', params }) +} + +// 编辑文章分类 +export function articleCateEdit(params: any) { + return request.post({ url: '/article/cate/edit', params }) +} + +// 删除文章分类 +export function articleCateDelete(params: any) { + return request.post({ url: '/article/cate/del', params }) +} + +// 文章分类详情 +export function articleCateDetail(params: any) { + return request.get({ url: '/article/cate/detail', params }) +} + +// 文章分类状态 +export function articleCateStatus(params: any) { + return request.post({ url: '/article/cate/change', params }) +} + +// 文章列表 +export function articleLists(params?: any) { + return request.get({ url: '/article/list', params }) +} +// 文章列表 +export function articleAll(params?: any) { + return request.get({ url: '/article/all', params }) +} + +// 添加文章 +export function articleAdd(params: any) { + return request.post({ url: '/article/add', params }) +} + +// 编辑文章 +export function articleEdit(params: any) { + return request.post({ url: '/article/edit', params }) +} + +// 删除文章 +export function articleDelete(params: any) { + return request.post({ url: '/article/del', params }) +} + +// 文章详情 +export function articleDetail(params: any) { + return request.get({ url: '/article/detail', params }) +} + +// 文章分类状态 +export function articleStatus(params: any) { + return request.post({ url: '/article/change', params }) +} diff --git a/admin/src/api/article_collect.ts b/admin/src/api/article_collect.ts new file mode 100644 index 0000000..30ebab7 --- /dev/null +++ b/admin/src/api/article_collect.ts @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 文章收藏列表 +export function article_collect_lists(params?: Record) { + return request.get({ url: '/article_collect/list', params }) +} + +// 文章收藏详情 +export function article_collect_detail(params: Record) { + return request.get({ url: '/article_collect/detail', params }) +} + +// 文章收藏新增 +export function article_collect_add(params: Record) { + return request.post({ url: '/article_collect/add', params }) +} + +// 文章收藏编辑 +export function article_collect_edit(params: Record) { + return request.post({ url: '/article_collect/edit', params }) +} + +// 文章收藏删除 +export function article_collect_delete(params: Record) { + return request.post({ url: '/article_collect/del', params }) +} diff --git a/admin/src/api/channel/h5.ts b/admin/src/api/channel/h5.ts new file mode 100644 index 0000000..dfb5575 --- /dev/null +++ b/admin/src/api/channel/h5.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// H5渠道配置保存 +export function setH5Config(params: any) { + return request.post({ url: '/channel/h5/save', params }) +} + +// H5渠道配置详情 +export function getH5Config() { + return request.get({ url: '/channel/h5/detail' }) +} diff --git a/admin/src/api/channel/weapp.ts b/admin/src/api/channel/weapp.ts new file mode 100644 index 0000000..59ce0a7 --- /dev/null +++ b/admin/src/api/channel/weapp.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// 微信小程序配置保存 +export function setWeappConfig(params: any) { + return request.post({ url: '/channel/mp/save', params }) +} + +// 微信小程序配置详情 +export function getWeappConfig() { + return request.get({ url: '/channel/mp/detail' }) +} diff --git a/admin/src/api/channel/wx_dev.ts b/admin/src/api/channel/wx_dev.ts new file mode 100644 index 0000000..a39ca96 --- /dev/null +++ b/admin/src/api/channel/wx_dev.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +// 微信开发平台配置保存 +export function setWxDevConfig(params: any) { + return request.post({ url: '/channel/wx/save', params }) +} + +// 微信开发平台配置详情 +export function getWxDevConfig() { + return request.get({ url: '/channel/wx/detail' }) +} diff --git a/admin/src/api/channel/wx_oa.ts b/admin/src/api/channel/wx_oa.ts new file mode 100644 index 0000000..67c8431 --- /dev/null +++ b/admin/src/api/channel/wx_oa.ts @@ -0,0 +1,112 @@ +import request from '@/utils/request' +import { firstToUpperCase } from '@/utils/util' + +// 微信公众号配置保存 +export function setOaConfig(params: any) { + return request.post({ url: '/channel/oa/save', params }) +} + +// 微信公众号配置详情 +export function getOaConfig() { + return request.get({ url: '/channel/oa/detail' }) +} + +export interface Menu { + name: string + menuType?: number + visitType?: string + url?: string + appId?: string + pagePath?: string + subButtons: Menu[] | any +} + +/** + * @return { Promise } + * @description 获取菜单 + */ +export function getOaMenu() { + return request.get({ url: '/channel/oaMenu/detail' }) +} + +/** + * @return { Promise } + * @param { Menu } Menu + * @description 菜单保存 + */ +export function setOaMenuSave(params: Menu | any) { + return request.post({ url: '/channel/oaMenu/save', params }) +} + +/** + * @return { Promise } + * @param { Menu } Menu + * @description 菜单发布 + */ +export function setOaMenuPublish(params: Menu | any) { + return request.post({ url: '/channel/oaMenu/publish', params }) +} + +/** + * @description 默认回复列表 + */ +export function getOaReplyList(params: any) { + const type = firstToUpperCase(params.type) + return request.get({ url: `/channel/oaReply${type}/list`, params }) +} + +/** + * @return { Promise } + * @param { number } id + * @description 回复列表删除 + */ +export function oaReplyDel(params: any) { + const type = firstToUpperCase(params.type) + return request.post({ url: `/channel/oaReply${type}/del`, params }) +} + +/** + * @return { Promise } + * @param { number } id + * @description 回复状态修改 + */ +export function changeOaReplyStatus(params: any) { + const type = firstToUpperCase(params.type) + return request.post({ url: `/channel/oaReply${type}/status`, params }) +} + +export interface Reply { + content: string // 内容 + contentType: number // 内容类型: 1=文本 + keyword?: string // 关键词 + matchingType?: number // 匹配方式: [1=全匹配, 2=模糊匹配] + name: string // 规则名称 + status: number // 状态: 1=开启, 0=关闭 + type: string // 类型: follow=关注, keyword=关键词, default=默认 + sort: number // 排序 +} +/** + * @return { Promise } + * @description 默认回复编辑 + */ +export function oaReplyAdd(params: Reply) { + const type = firstToUpperCase(params.type) + return request.post({ url: `/channel/oaReply${type}/add`, params }) +} + +/** + * @return { Promise } + * @description 默认回复编辑 + */ +export function oaReplyEdit(params: Reply) { + const type = firstToUpperCase(params.type) + return request.post({ url: `/channel/oaReply${type}/edit`, params }) +} + +/** + * @description 默认回复详情 + */ +export function getOaReplyDetail(params: any) { + const type = firstToUpperCase(params.type) + return request.get({ url: `/channel/oaReply${type}/detail`, params }) +} diff --git a/admin/src/api/consumer.ts b/admin/src/api/consumer.ts new file mode 100644 index 0000000..1057729 --- /dev/null +++ b/admin/src/api/consumer.ts @@ -0,0 +1,16 @@ +import request from '@/utils/request' + +// 用户列表 +export function getUserList(params: any) { + return request.get({ url: '/user/list', params }) +} + +// 用户详情 +export function getUserDetail(params: any) { + return request.get({ url: '/user/detail', params }) +} + +// 用户编辑 +export function userEdit(params: any) { + return request.post({ url: '/user/edit', params }) +} diff --git a/admin/src/api/decoration.ts b/admin/src/api/decoration.ts new file mode 100644 index 0000000..28d61e6 --- /dev/null +++ b/admin/src/api/decoration.ts @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 页面装修详情 +export function getDecoratePages(params: any) { + return request.get({ url: '/decorate/pages/detail', params }, { ignoreCancelToken: true }) +} + +// 页面装修保存 +export function setDecoratePages(params: any) { + return request.post({ url: '/decorate/pages/save', params }) +} + +// 获取首页文章数据 +export function getDecorateArticle(params?: any) { + return request.get({ url: '/decorate/data/article', params }) +} + +// 底部导航详情 +export function getDecorateTabbar(params?: any) { + return request.get({ url: '/decorate/tabbar/detail', params }) +} + +// 底部导航保存 +export function setDecorateTabbar(params: any) { + return request.post({ url: '/decorate/tabbar/save', params }) +} diff --git a/admin/src/api/file.ts b/admin/src/api/file.ts new file mode 100644 index 0000000..4dccea5 --- /dev/null +++ b/admin/src/api/file.ts @@ -0,0 +1,39 @@ +import request from '@/utils/request' + +export function fileCateAdd(params: Record) { + return request.post({ url: '/common/album/cateAdd', params }) +} + +export function fileCateEdit(params: Record) { + return request.post({ url: '/common/album/cateRename', params }) +} + +// 文件分类删除 +export function fileCateDelete(params: Record) { + return request.post({ url: '/common/album/cateDel', params }) +} + +// 文件分类列表 +export function fileCateLists(params: Record) { + return request.get({ url: '/common/album/cateList', params }) +} + +// 文件列表 +export function fileList(params: Record) { + return request.get({ url: '/common/album/albumList', params }) +} + +// 文件删除 +export function fileDelete(params: Record) { + return request.post({ url: '/common/album/albumDel', params }) +} + +// 文件移动 +export function fileMove(params: Record) { + return request.post({ url: '/common/album/albumMove', params }) +} + +// 文件重命名 +export function fileRename(params: { id: number; name: string }) { + return request.post({ url: '/common/album/albumRename', params }) +} diff --git a/admin/src/api/message.ts b/admin/src/api/message.ts new file mode 100644 index 0000000..8d7ec49 --- /dev/null +++ b/admin/src/api/message.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request' + +// 通知设置列表 +export function noticeLists(params: any) { + return request.get({ url: '/setting/notice/list', params }) +} + +// 通知设置详情 +export function noticeDetail(params: any) { + return request.get({ url: '/setting/notice/detail', params }) +} + +// 通知设置保存 +export function setNoticeConfig(params: any) { + return request.post({ url: '/setting/notice/save', params }) +} + +// 短信设置列表 +export function smsLists() { + return request.get({ url: '/setting/sms/list' }) +} + +// 短信设置详情 +export function smsDetail(params: any) { + return request.get({ url: '/setting/sms/detail', params }) +} + +// 短信设置保存 +export function setSmsConfig(params: any) { + return request.post({ url: '/setting/sms/save', params }) +} diff --git a/admin/src/api/org/department.ts b/admin/src/api/org/department.ts new file mode 100644 index 0000000..b4899a0 --- /dev/null +++ b/admin/src/api/org/department.ts @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 部门列表 +export function deptLists(params?: any) { + return request.get({ url: '/system/dept/list', params }) +} + +// 添加部门 +export function deptAdd(params: any) { + return request.post({ url: '/system/dept/add', params }) +} + +// 编辑部门 +export function deptEdit(params: any) { + return request.post({ url: '/system/dept/edit', params }) +} + +// 删除部门 +export function deptDelete(params: any) { + return request.post({ url: '/system/dept/del', params }) +} + +// 部门详情 +export function deptDetail(params?: any) { + return request.get({ url: '/system/dept/detail', params }) +} diff --git a/admin/src/api/org/post.ts b/admin/src/api/org/post.ts new file mode 100644 index 0000000..63a15fc --- /dev/null +++ b/admin/src/api/org/post.ts @@ -0,0 +1,30 @@ +import request from '@/utils/request' + +// 岗位列表 +export function postLists(params?: any) { + return request.get({ url: '/system/post/list', params }) +} +// 岗位列表 +export function postAll(params?: any) { + return request.get({ url: '/system/post/all', params }) +} + +// 添加岗位 +export function postAdd(params: any) { + return request.post({ url: '/system/post/add', params }) +} + +// 编辑岗位 +export function postEdit(params: any) { + return request.post({ url: '/system/post/edit', params }) +} + +// 删除岗位 +export function postDelete(params: any) { + return request.post({ url: '/system/post/del', params }) +} + +// 岗位详情 +export function postDetail(params: any) { + return request.get({ url: '/system/post/detail', params }) +} diff --git a/admin/src/api/perms/admin.ts b/admin/src/api/perms/admin.ts new file mode 100644 index 0000000..a54b275 --- /dev/null +++ b/admin/src/api/perms/admin.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request' + +// 管理员列表 +export function adminLists(params: any) { + return request.get({ url: '/system/admin/list', params }) +} + +// 管理员添加 +export function adminAdd(params: any) { + return request.post({ url: '/system/admin/add', params }) +} + +// 管理员编辑 +export function adminDetail(params: any) { + return request.get({ url: '/system/admin/detail', params }) +} + +// 管理员编辑 +export function adminEdit(params: any) { + return request.post({ url: '/system/admin/edit', params }) +} + +// 管理员删除 +export function adminDelete(params: any) { + return request.post({ url: '/system/admin/del', params }) +} + +// 管理员删除 +export function adminStatus(params: any) { + return request.post({ url: '/system/admin/disable', params }) +} diff --git a/admin/src/api/perms/menu.ts b/admin/src/api/perms/menu.ts new file mode 100644 index 0000000..577e143 --- /dev/null +++ b/admin/src/api/perms/menu.ts @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 菜单列表 +export function menuLists(params?: Record) { + return request.get({ url: '/system/menu/list', params }) +} + +// 添加菜单 +export function menuAdd(params: Record) { + return request.post({ url: '/system/menu/add', params }) +} + +// 编辑菜单 +export function menuEdit(params: Record) { + return request.post({ url: '/system/menu/edit', params }) +} + +// 菜单删除 +export function menuDelete(params: Record) { + return request.post({ url: '/system/menu/del', params }) +} + +// 菜单删除 +export function menuDetail(params: Record) { + return request.get({ url: '/system/menu/detail', params }) +} diff --git a/admin/src/api/perms/role.ts b/admin/src/api/perms/role.ts new file mode 100644 index 0000000..3225443 --- /dev/null +++ b/admin/src/api/perms/role.ts @@ -0,0 +1,29 @@ +import request from '@/utils/request' + +// 角色列表 +export function roleLists(params: any) { + return request.get({ url: '/system/role/list', params }) +} + +// 角色列表 +export function roleAll(params?: any) { + return request.get({ url: '/system/role/all', params }) +} + +// 角色列表 +export function roleDetail(params: any) { + return request.get({ url: '/system/role/detail', params }) +} + +// 添加角色 +export function roleAdd(params: any) { + return request.post({ url: '/system/role/add', params }) +} +// 编辑角色 +export function roleEdit(params: any) { + return request.post({ url: '/system/role/edit', params }) +} +// 删除角色 +export function roleDelete(params: any) { + return request.post({ url: '/system/role/del', params }) +} diff --git a/admin/src/api/setting/dict.ts b/admin/src/api/setting/dict.ts new file mode 100644 index 0000000..1d5d2a8 --- /dev/null +++ b/admin/src/api/setting/dict.ts @@ -0,0 +1,61 @@ +import request from '@/utils/request' + +// 字典类型列表 +export function dictTypeLists(params?: any) { + return request.get({ url: '/setting/dict/type/list', params }) +} + +// 字典类型列表 +export function dictTypeAll(params?: any) { + return request.get({ url: '/setting/dict/type/all', params }) +} + +// 添加字典类型 +export function dictTypeAdd(params: any) { + return request.post({ url: '/setting/dict/type/add', params }) +} + +// 编辑字典类型 +export function dictTypeEdit(params: any) { + return request.post({ url: '/setting/dict/type/edit', params }) +} + +// 删除字典类型 +export function dictTypeDelete(params: any) { + return request.post({ url: '/setting/dict/type/del', params }) +} + +// 字典数据列表 +export function dictDataLists(params: any) { + return request.get( + { url: '/setting/dict/data/list', params }, + { + ignoreCancelToken: true + } + ) +} + +// 字典数据列表 +export function dictDataAll(params: any) { + return request.get( + { url: '/setting/dict/data/all', params }, + { + ignoreCancelToken: true + } + ) +} + +// 添加字典数据 +export function dictDataAdd(params: any) { + return request.post({ url: '/setting/dict/data/add', params }) +} + +// 编辑字典数据 +export function dictDataEdit(params: any) { + return request.post({ url: '/setting/dict/data/edit', params }) +} + +// 删除字典数据 +export function dictDataDelete(params: any) { + return request.post({ url: '/setting/dict/data/del', params }) +} diff --git a/admin/src/api/setting/search.ts b/admin/src/api/setting/search.ts new file mode 100644 index 0000000..715402d --- /dev/null +++ b/admin/src/api/setting/search.ts @@ -0,0 +1,27 @@ +import request from '@/utils/request' + +/** + * @return { Promise } + * @description 获取热门搜索数据 + */ +export function getSearch() { + return request.get({ url: '/setting/search/detail' }) +} + +export interface List { + name: string // 搜索关键字 + sort: number // 热门搜索排序 +} + +export interface Search { + isHotSearch: number // 是否开启搜索0/1 + list: List[] +} +/** + * @return { Promise } + * @param { Search } Search + * @description 设置热门搜索 + */ +export function setSearch(params: Search) { + return request.post({ url: '/setting/search/save', params }) +} diff --git a/admin/src/api/setting/storage.ts b/admin/src/api/setting/storage.ts new file mode 100644 index 0000000..268d254 --- /dev/null +++ b/admin/src/api/setting/storage.ts @@ -0,0 +1,21 @@ +import request from '@/utils/request' + +// 获取存储引擎列表 +export function storageLists() { + return request.get({ url: '/setting/storage/list' }) +} + +// 设置存储引擎信息 +export function storageChange(params: any) { + return request.post({ url: '/setting/storage/change', params }) +} + +// 设置存储引擎信息 +export function storageSetup(params: any) { + return request.post({ url: '/setting/storage/edit', params }) +} + +// 获取存储配置信息 +export function storageDetail(params: any) { + return request.get({ url: '/setting/storage/detail', params }) +} diff --git a/admin/src/api/setting/system.ts b/admin/src/api/setting/system.ts new file mode 100644 index 0000000..a9bcf6f --- /dev/null +++ b/admin/src/api/setting/system.ts @@ -0,0 +1,16 @@ +import request from '@/utils/request' + +// 获取系统环境 +export function systemInfo() { + return request.get({ url: '/monitor/server' }) +} + +// 获取系统日志列表 +export function systemLogLists(params: any) { + return request.get({ url: '/system/log/operate', params }) +} + +// 系统缓存监控 +export function systemCache() { + return request.get({ url: '/monitor/cache' }) +} diff --git a/admin/src/api/setting/user.ts b/admin/src/api/setting/user.ts new file mode 100644 index 0000000..946f4b0 --- /dev/null +++ b/admin/src/api/setting/user.ts @@ -0,0 +1,42 @@ +import request from '@/utils/request' + +/** + * @return { Promise } + * @description 获取用户设置 + */ +export function getUserSetup() { + return request.get({ url: '/setting/user/detail' }) +} + +/** + * @return { Promise } + * @param { string } defaultAvatar 默认用户头像 + * @description 设置用户设置 + */ +export function setUserSetup(params: { defaultAvatar: string }) { + return request.post({ url: '/setting/user/save', params }) +} + +/** + * @return { Promise } + * @description 设置登录注册规则 + */ +export function getLogin() { + return request.get({ url: '/setting/login/detail' }) +} + +export interface LoginSetup { + loginWay: number[] | any // 登录方式, 逗号隔开 + forceBindMobile: number // 强制绑定手机 0/1 + openAgreement: number // 是否开启协议 0/1 + openOtherAuth: number // 第三方登录 0/1 + autoLoginAuth: number[] | any // 第三方自动登录 逗号隔开 +} +/** + * @return { Promise } + * @param { LoginSetup } LoginSetup + * @description 设置登录注册规则 + */ +export function setLogin(params: LoginSetup) { + return request.post({ url: '/setting/login/save', params }) +} diff --git a/admin/src/api/setting/website.ts b/admin/src/api/setting/website.ts new file mode 100644 index 0000000..294575b --- /dev/null +++ b/admin/src/api/setting/website.ts @@ -0,0 +1,27 @@ +import request from '@/utils/request' + +// 获取备案信息 +export function getCopyright() { + return request.get({ url: '/setting/copyright/detail' }) +} +// 设置备案信息 +export function setCopyright(params: any) { + return request.post({ url: '/setting/copyright/save', params }) +} +// 获取网站信息 +export function getWebsite() { + return request.get({ url: '/setting/website/detail' }) +} +// 设置网站信息 +export function setWebsite(params: any) { + return request.post({ url: '/setting/website/save', params }) +} + +// 获取政策协议 +export function getProtocol() { + return request.get({ url: '/setting/protocol/detail' }) +} +// 设置政策协议 +export function setProtocol(params: any) { + return request.post({ url: '/setting/protocol/save', params }) +} diff --git a/admin/src/api/tools/code.ts b/admin/src/api/tools/code.ts new file mode 100644 index 0000000..980ead5 --- /dev/null +++ b/admin/src/api/tools/code.ts @@ -0,0 +1,66 @@ +import request from '@/utils/request' + +// 代码生成已选数据表列表接口 +export function generateTable(params: any) { + return request.get({ url: '/gen/list', params }) +} + +// 数据表列表接口 +export function dataTable(params: any) { + return request.get({ url: '/gen/db', params }) +} + +//选择要生成代码的数据表 +export function selectTable(params: any) { + return request.post( + { url: '/gen/importTable', params }, + { + isParamsToData: false + } + ) +} + +// 已选择的数据表详情 +export function tableDetail(params: any) { + return request.get({ url: '/gen/detail', params }) +} + +//同步字段 +export function syncColumn(params: any) { + return request.post( + { url: '/gen/syncTable', params }, + { + isParamsToData: false + } + ) +} + +//删除已选择的数据表 +export function generateDelete(params: any) { + return request.post({ url: '/gen/delTable', params }) +} + +//编辑已选表字段 +export function generateEdit(params: any) { + return request.post({ url: '/gen/editTable', params }) +} + +//预览代码 +export function generatePreview(params: any) { + return request.get({ url: '/gen/previewCode', params }) +} + +//生成代码 +export function generateCode(params: any) { + return request.get({ url: '/gen/genCode', params }) +} + +//下载代码 +export function downloadCode(params: any) { + return request.get( + { responseType: 'blob', url: '/gen/downloadCode', params }, + { + isTransformResponse: false + } + ) +} diff --git a/admin/src/api/user.ts b/admin/src/api/user.ts new file mode 100644 index 0000000..ce54b58 --- /dev/null +++ b/admin/src/api/user.ts @@ -0,0 +1,27 @@ +import config from '@/config' +import request from '@/utils/request' + +// 登录 +export function login(params: Record) { + return request.post({ url: '/system/login', params: { ...params, terminal: config.terminal } }) +} + +// 退出登录 +export function logout() { + return request.post({ url: '/system/logout' }) +} + +// 用户信息 +export function getUserInfo() { + return request.get({ url: '/system/admin/self' }) +} + +// 菜单路由 +export function getMenu() { + return request.get({ url: '/system/menu/route' }) +} + +// 编辑管理员信息 +export function setUserInfo(params: any) { + return request.post({ url: '/system/admin/upInfo', params }) +} diff --git a/admin/src/assets/icons/Androidfanhui.svg b/admin/src/assets/icons/Androidfanhui.svg new file mode 100644 index 0000000..e9ada24 --- /dev/null +++ b/admin/src/assets/icons/Androidfanhui.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/KMSguanli.svg b/admin/src/assets/icons/KMSguanli.svg new file mode 100644 index 0000000..7c65298 --- /dev/null +++ b/admin/src/assets/icons/KMSguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/KTVyuding.svg b/admin/src/assets/icons/KTVyuding.svg new file mode 100644 index 0000000..8187b5f --- /dev/null +++ b/admin/src/assets/icons/KTVyuding.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/a-tixingdengpao.svg b/admin/src/assets/icons/a-tixingdengpao.svg new file mode 100644 index 0000000..7074ae7 --- /dev/null +++ b/admin/src/assets/icons/a-tixingdengpao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/anquan.svg b/admin/src/assets/icons/anquan.svg new file mode 100644 index 0000000..bf90259 --- /dev/null +++ b/admin/src/assets/icons/anquan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/anquan_mian.svg b/admin/src/assets/icons/anquan_mian.svg new file mode 100644 index 0000000..f3486b5 --- /dev/null +++ b/admin/src/assets/icons/anquan_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/anquan_mian1.svg b/admin/src/assets/icons/anquan_mian1.svg new file mode 100644 index 0000000..f3486b5 --- /dev/null +++ b/admin/src/assets/icons/anquan_mian1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/banxing_mian.svg b/admin/src/assets/icons/banxing_mian.svg new file mode 100644 index 0000000..3cb468f --- /dev/null +++ b/admin/src/assets/icons/banxing_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/baoxian.svg b/admin/src/assets/icons/baoxian.svg new file mode 100644 index 0000000..9885e88 --- /dev/null +++ b/admin/src/assets/icons/baoxian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/bendishenghuodaxue.svg b/admin/src/assets/icons/bendishenghuodaxue.svg new file mode 100644 index 0000000..4754e16 --- /dev/null +++ b/admin/src/assets/icons/bendishenghuodaxue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/bianji.svg b/admin/src/assets/icons/bianji.svg new file mode 100644 index 0000000..c8674e7 --- /dev/null +++ b/admin/src/assets/icons/bianji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/biaoqing.svg b/admin/src/assets/icons/biaoqing.svg new file mode 100644 index 0000000..fcc84fb --- /dev/null +++ b/admin/src/assets/icons/biaoqing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/bukejian.svg b/admin/src/assets/icons/bukejian.svg new file mode 100644 index 0000000..c79cd78 --- /dev/null +++ b/admin/src/assets/icons/bukejian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/caipinguanli.svg b/admin/src/assets/icons/caipinguanli.svg new file mode 100644 index 0000000..3b1fc28 --- /dev/null +++ b/admin/src/assets/icons/caipinguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/caiwu.svg b/admin/src/assets/icons/caiwu.svg new file mode 100644 index 0000000..e3e92f4 --- /dev/null +++ b/admin/src/assets/icons/caiwu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/caiwu_jifen.svg b/admin/src/assets/icons/caiwu_jifen.svg new file mode 100644 index 0000000..07d01df --- /dev/null +++ b/admin/src/assets/icons/caiwu_jifen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/caiwu_tixian.svg b/admin/src/assets/icons/caiwu_tixian.svg new file mode 100644 index 0000000..982ac58 --- /dev/null +++ b/admin/src/assets/icons/caiwu_tixian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/canyinfuwu.svg b/admin/src/assets/icons/canyinfuwu.svg new file mode 100644 index 0000000..5d87395 --- /dev/null +++ b/admin/src/assets/icons/canyinfuwu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/carryout.svg b/admin/src/assets/icons/carryout.svg new file mode 100644 index 0000000..4a06640 --- /dev/null +++ b/admin/src/assets/icons/carryout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/chexiao.svg b/admin/src/assets/icons/chexiao.svg new file mode 100644 index 0000000..b9e6e5d --- /dev/null +++ b/admin/src/assets/icons/chexiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/chihuohongbao.svg b/admin/src/assets/icons/chihuohongbao.svg new file mode 100644 index 0000000..bb1fb66 --- /dev/null +++ b/admin/src/assets/icons/chihuohongbao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/chuangyiwuliao.svg b/admin/src/assets/icons/chuangyiwuliao.svg new file mode 100644 index 0000000..045057d --- /dev/null +++ b/admin/src/assets/icons/chuangyiwuliao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/close.svg b/admin/src/assets/icons/close.svg new file mode 100644 index 0000000..54f3e08 --- /dev/null +++ b/admin/src/assets/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/daiyunying.svg b/admin/src/assets/icons/daiyunying.svg new file mode 100644 index 0000000..dc1c7c5 --- /dev/null +++ b/admin/src/assets/icons/daiyunying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/danwei.svg b/admin/src/assets/icons/danwei.svg new file mode 100644 index 0000000..ba527e8 --- /dev/null +++ b/admin/src/assets/icons/danwei.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/danxuankuang.svg b/admin/src/assets/icons/danxuankuang.svg new file mode 100644 index 0000000..2ef171c --- /dev/null +++ b/admin/src/assets/icons/danxuankuang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/danxuanxuanzhong.svg b/admin/src/assets/icons/danxuanxuanzhong.svg new file mode 100644 index 0000000..c7f230c --- /dev/null +++ b/admin/src/assets/icons/danxuanxuanzhong.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dayin.svg b/admin/src/assets/icons/dayin.svg new file mode 100644 index 0000000..0d2291d --- /dev/null +++ b/admin/src/assets/icons/dayin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dayin_mian.svg b/admin/src/assets/icons/dayin_mian.svg new file mode 100644 index 0000000..7989ff6 --- /dev/null +++ b/admin/src/assets/icons/dayin_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/del.svg b/admin/src/assets/icons/del.svg new file mode 100644 index 0000000..f938140 --- /dev/null +++ b/admin/src/assets/icons/del.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/diancanshezhi.svg b/admin/src/assets/icons/diancanshezhi.svg new file mode 100644 index 0000000..8723cbf --- /dev/null +++ b/admin/src/assets/icons/diancanshezhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dianhua.svg b/admin/src/assets/icons/dianhua.svg new file mode 100644 index 0000000..be006a9 --- /dev/null +++ b/admin/src/assets/icons/dianhua.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dianhua_mian.svg b/admin/src/assets/icons/dianhua_mian.svg new file mode 100644 index 0000000..5554077 --- /dev/null +++ b/admin/src/assets/icons/dianhua_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dianpu_fengge.svg b/admin/src/assets/icons/dianpu_fengge.svg new file mode 100644 index 0000000..a7dc3e1 --- /dev/null +++ b/admin/src/assets/icons/dianpu_fengge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dianputuijian.svg b/admin/src/assets/icons/dianputuijian.svg new file mode 100644 index 0000000..0e8a4c8 --- /dev/null +++ b/admin/src/assets/icons/dianputuijian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dianzifapiao.svg b/admin/src/assets/icons/dianzifapiao.svg new file mode 100644 index 0000000..b2db463 --- /dev/null +++ b/admin/src/assets/icons/dianzifapiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dingcan.svg b/admin/src/assets/icons/dingcan.svg new file mode 100644 index 0000000..46d4e95 --- /dev/null +++ b/admin/src/assets/icons/dingcan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dingdan.svg b/admin/src/assets/icons/dingdan.svg new file mode 100644 index 0000000..e5b35a9 --- /dev/null +++ b/admin/src/assets/icons/dingdan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dingdan1.svg b/admin/src/assets/icons/dingdan1.svg new file mode 100644 index 0000000..5330a6a --- /dev/null +++ b/admin/src/assets/icons/dingdan1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dingdan_mian.svg b/admin/src/assets/icons/dingdan_mian.svg new file mode 100644 index 0000000..0929276 --- /dev/null +++ b/admin/src/assets/icons/dingdan_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dingwei.svg b/admin/src/assets/icons/dingwei.svg new file mode 100644 index 0000000..ef573a7 --- /dev/null +++ b/admin/src/assets/icons/dingwei.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/dingwei_mian.svg b/admin/src/assets/icons/dingwei_mian.svg new file mode 100644 index 0000000..8c86674 --- /dev/null +++ b/admin/src/assets/icons/dingwei_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/ditu.svg b/admin/src/assets/icons/ditu.svg new file mode 100644 index 0000000..6b5e5f2 --- /dev/null +++ b/admin/src/assets/icons/ditu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/ditu_mian.svg b/admin/src/assets/icons/ditu_mian.svg new file mode 100644 index 0000000..bb542bf --- /dev/null +++ b/admin/src/assets/icons/ditu_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/duizhang.svg b/admin/src/assets/icons/duizhang.svg new file mode 100644 index 0000000..f63d6df --- /dev/null +++ b/admin/src/assets/icons/duizhang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/elemo.svg b/admin/src/assets/icons/elemo.svg new file mode 100644 index 0000000..c1d5388 --- /dev/null +++ b/admin/src/assets/icons/elemo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/ezhanggui.svg b/admin/src/assets/icons/ezhanggui.svg new file mode 100644 index 0000000..cd50b0b --- /dev/null +++ b/admin/src/assets/icons/ezhanggui.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/falvfuwubaoxiaohei.svg b/admin/src/assets/icons/falvfuwubaoxiaohei.svg new file mode 100644 index 0000000..27ea856 --- /dev/null +++ b/admin/src/assets/icons/falvfuwubaoxiaohei.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fengniaopaotui.svg b/admin/src/assets/icons/fengniaopaotui.svg new file mode 100644 index 0000000..3f5f7b1 --- /dev/null +++ b/admin/src/assets/icons/fengniaopaotui.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fenxiang.svg b/admin/src/assets/icons/fenxiang.svg new file mode 100644 index 0000000..e4eb7cc --- /dev/null +++ b/admin/src/assets/icons/fenxiang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fukuan.svg b/admin/src/assets/icons/fukuan.svg new file mode 100644 index 0000000..939c745 --- /dev/null +++ b/admin/src/assets/icons/fukuan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fukuan_mian.svg b/admin/src/assets/icons/fukuan_mian.svg new file mode 100644 index 0000000..ba094cc --- /dev/null +++ b/admin/src/assets/icons/fukuan_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fullscreen-exit.svg b/admin/src/assets/icons/fullscreen-exit.svg new file mode 100644 index 0000000..e845a79 --- /dev/null +++ b/admin/src/assets/icons/fullscreen-exit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fullscreen.svg b/admin/src/assets/icons/fullscreen.svg new file mode 100644 index 0000000..516e890 --- /dev/null +++ b/admin/src/assets/icons/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fuwushichang.svg b/admin/src/assets/icons/fuwushichang.svg new file mode 100644 index 0000000..2564370 --- /dev/null +++ b/admin/src/assets/icons/fuwushichang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/fuzhi.svg b/admin/src/assets/icons/fuzhi.svg new file mode 100644 index 0000000..659c6e0 --- /dev/null +++ b/admin/src/assets/icons/fuzhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gaode.svg b/admin/src/assets/icons/gaode.svg new file mode 100644 index 0000000..8d26343 --- /dev/null +++ b/admin/src/assets/icons/gaode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gengduo.svg b/admin/src/assets/icons/gengduo.svg new file mode 100644 index 0000000..2956729 --- /dev/null +++ b/admin/src/assets/icons/gengduo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gengduoandroid.svg b/admin/src/assets/icons/gengduoandroid.svg new file mode 100644 index 0000000..ecde71b --- /dev/null +++ b/admin/src/assets/icons/gengduoandroid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gift.svg b/admin/src/assets/icons/gift.svg new file mode 100644 index 0000000..add9ce8 --- /dev/null +++ b/admin/src/assets/icons/gift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gongyingshang.svg b/admin/src/assets/icons/gongyingshang.svg new file mode 100644 index 0000000..9426995 --- /dev/null +++ b/admin/src/assets/icons/gongyingshang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/goods.svg b/admin/src/assets/icons/goods.svg new file mode 100644 index 0000000..b80fbed --- /dev/null +++ b/admin/src/assets/icons/goods.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gou.svg b/admin/src/assets/icons/gou.svg new file mode 100644 index 0000000..6354197 --- /dev/null +++ b/admin/src/assets/icons/gou.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gouwuche.svg b/admin/src/assets/icons/gouwuche.svg new file mode 100644 index 0000000..6e76f7d --- /dev/null +++ b/admin/src/assets/icons/gouwuche.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gouxuan.svg b/admin/src/assets/icons/gouxuan.svg new file mode 100644 index 0000000..8fe88a6 --- /dev/null +++ b/admin/src/assets/icons/gouxuan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gouxuan_mian.svg b/admin/src/assets/icons/gouxuan_mian.svg new file mode 100644 index 0000000..375972b --- /dev/null +++ b/admin/src/assets/icons/gouxuan_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/guanbi.svg b/admin/src/assets/icons/guanbi.svg new file mode 100644 index 0000000..831bd0e --- /dev/null +++ b/admin/src/assets/icons/guanbi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/guanli.svg b/admin/src/assets/icons/guanli.svg new file mode 100644 index 0000000..4848092 --- /dev/null +++ b/admin/src/assets/icons/guanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/guanli_mian.svg b/admin/src/assets/icons/guanli_mian.svg new file mode 100644 index 0000000..db46ff1 --- /dev/null +++ b/admin/src/assets/icons/guanli_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/gukefapiao.svg b/admin/src/assets/icons/gukefapiao.svg new file mode 100644 index 0000000..341b686 --- /dev/null +++ b/admin/src/assets/icons/gukefapiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/haibaosheji.svg b/admin/src/assets/icons/haibaosheji.svg new file mode 100644 index 0000000..f875157 --- /dev/null +++ b/admin/src/assets/icons/haibaosheji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/heshoujilu.svg b/admin/src/assets/icons/heshoujilu.svg new file mode 100644 index 0000000..cd25273 --- /dev/null +++ b/admin/src/assets/icons/heshoujilu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/heshoujilu1.svg b/admin/src/assets/icons/heshoujilu1.svg new file mode 100644 index 0000000..f8d1bd4 --- /dev/null +++ b/admin/src/assets/icons/heshoujilu1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/hexiao_order.svg b/admin/src/assets/icons/hexiao_order.svg new file mode 100644 index 0000000..f1c4034 --- /dev/null +++ b/admin/src/assets/icons/hexiao_order.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/hide-2.svg b/admin/src/assets/icons/hide-2.svg new file mode 100644 index 0000000..8c74146 --- /dev/null +++ b/admin/src/assets/icons/hide-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/hide.svg b/admin/src/assets/icons/hide.svg new file mode 100644 index 0000000..5cbbabd --- /dev/null +++ b/admin/src/assets/icons/hide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/hongbao.svg b/admin/src/assets/icons/hongbao.svg new file mode 100644 index 0000000..9d331b5 --- /dev/null +++ b/admin/src/assets/icons/hongbao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huiche.svg b/admin/src/assets/icons/huiche.svg new file mode 100644 index 0000000..5022718 --- /dev/null +++ b/admin/src/assets/icons/huiche.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huiyuanyingxiao.svg b/admin/src/assets/icons/huiyuanyingxiao.svg new file mode 100644 index 0000000..ae06769 --- /dev/null +++ b/admin/src/assets/icons/huiyuanyingxiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huodongbaoming.svg b/admin/src/assets/icons/huodongbaoming.svg new file mode 100644 index 0000000..0c60672 --- /dev/null +++ b/admin/src/assets/icons/huodongbaoming.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huodongguanli.svg b/admin/src/assets/icons/huodongguanli.svg new file mode 100644 index 0000000..def7109 --- /dev/null +++ b/admin/src/assets/icons/huodongguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huodongzhongxin.svg b/admin/src/assets/icons/huodongzhongxin.svg new file mode 100644 index 0000000..f22bb26 --- /dev/null +++ b/admin/src/assets/icons/huodongzhongxin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huojian.svg b/admin/src/assets/icons/huojian.svg new file mode 100644 index 0000000..f439dd8 --- /dev/null +++ b/admin/src/assets/icons/huojian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huojian_mian.svg b/admin/src/assets/icons/huojian_mian.svg new file mode 100644 index 0000000..812d3be --- /dev/null +++ b/admin/src/assets/icons/huojian_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/huolala.svg b/admin/src/assets/icons/huolala.svg new file mode 100644 index 0000000..a42ade7 --- /dev/null +++ b/admin/src/assets/icons/huolala.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/iOSfanhui.svg b/admin/src/assets/icons/iOSfanhui.svg new file mode 100644 index 0000000..8aeefbb --- /dev/null +++ b/admin/src/assets/icons/iOSfanhui.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jia.svg b/admin/src/assets/icons/jia.svg new file mode 100644 index 0000000..18fbd45 --- /dev/null +++ b/admin/src/assets/icons/jia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jia_mian.svg b/admin/src/assets/icons/jia_mian.svg new file mode 100644 index 0000000..5899c91 --- /dev/null +++ b/admin/src/assets/icons/jia_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jian.svg b/admin/src/assets/icons/jian.svg new file mode 100644 index 0000000..295037f --- /dev/null +++ b/admin/src/assets/icons/jian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jian_mian.svg b/admin/src/assets/icons/jian_mian.svg new file mode 100644 index 0000000..e9dfa91 --- /dev/null +++ b/admin/src/assets/icons/jian_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jianpan.svg b/admin/src/assets/icons/jianpan.svg new file mode 100644 index 0000000..5a7dcb9 --- /dev/null +++ b/admin/src/assets/icons/jianpan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jianpanshanchu.svg b/admin/src/assets/icons/jianpanshanchu.svg new file mode 100644 index 0000000..a5a5de1 --- /dev/null +++ b/admin/src/assets/icons/jianpanshanchu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jianshao.svg b/admin/src/assets/icons/jianshao.svg new file mode 100644 index 0000000..0a4d919 --- /dev/null +++ b/admin/src/assets/icons/jianshao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jiaopeiwangputong.svg b/admin/src/assets/icons/jiaopeiwangputong.svg new file mode 100644 index 0000000..ec73071 --- /dev/null +++ b/admin/src/assets/icons/jiaopeiwangputong.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jiaoyi.svg b/admin/src/assets/icons/jiaoyi.svg new file mode 100644 index 0000000..1396bac --- /dev/null +++ b/admin/src/assets/icons/jiaoyi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jiedan.svg b/admin/src/assets/icons/jiedan.svg new file mode 100644 index 0000000..fcbe7a1 --- /dev/null +++ b/admin/src/assets/icons/jiedan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jiekuan.svg b/admin/src/assets/icons/jiekuan.svg new file mode 100644 index 0000000..4b7377f --- /dev/null +++ b/admin/src/assets/icons/jiekuan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingshi.svg b/admin/src/assets/icons/jingshi.svg new file mode 100644 index 0000000..3cecfc7 --- /dev/null +++ b/admin/src/assets/icons/jingshi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingshi_mian.svg b/admin/src/assets/icons/jingshi_mian.svg new file mode 100644 index 0000000..fe636ee --- /dev/null +++ b/admin/src/assets/icons/jingshi_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingshi_mian1.svg b/admin/src/assets/icons/jingshi_mian1.svg new file mode 100644 index 0000000..fe636ee --- /dev/null +++ b/admin/src/assets/icons/jingshi_mian1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingyin.svg b/admin/src/assets/icons/jingyin.svg new file mode 100644 index 0000000..753f254 --- /dev/null +++ b/admin/src/assets/icons/jingyin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingyin_mian.svg b/admin/src/assets/icons/jingyin_mian.svg new file mode 100644 index 0000000..ce56e68 --- /dev/null +++ b/admin/src/assets/icons/jingyin_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingying.svg b/admin/src/assets/icons/jingying.svg new file mode 100644 index 0000000..5630813 --- /dev/null +++ b/admin/src/assets/icons/jingying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingying_mian.svg b/admin/src/assets/icons/jingying_mian.svg new file mode 100644 index 0000000..4e7ca35 --- /dev/null +++ b/admin/src/assets/icons/jingying_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingyinggonglve.svg b/admin/src/assets/icons/jingyinggonglve.svg new file mode 100644 index 0000000..0cd3755 --- /dev/null +++ b/admin/src/assets/icons/jingyinggonglve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jingzhunyingxiao.svg b/admin/src/assets/icons/jingzhunyingxiao.svg new file mode 100644 index 0000000..781b0bb --- /dev/null +++ b/admin/src/assets/icons/jingzhunyingxiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/jinhuo.svg b/admin/src/assets/icons/jinhuo.svg new file mode 100644 index 0000000..c5d9214 --- /dev/null +++ b/admin/src/assets/icons/jinhuo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kaitongwaimai.svg b/admin/src/assets/icons/kaitongwaimai.svg new file mode 100644 index 0000000..a6daad9 --- /dev/null +++ b/admin/src/assets/icons/kaitongwaimai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kanjia.svg b/admin/src/assets/icons/kanjia.svg new file mode 100644 index 0000000..9f6840b --- /dev/null +++ b/admin/src/assets/icons/kanjia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kefu.svg b/admin/src/assets/icons/kefu.svg new file mode 100644 index 0000000..f32cacd --- /dev/null +++ b/admin/src/assets/icons/kefu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kejian.svg b/admin/src/assets/icons/kejian.svg new file mode 100644 index 0000000..8b89874 --- /dev/null +++ b/admin/src/assets/icons/kejian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kejian_mian.svg b/admin/src/assets/icons/kejian_mian.svg new file mode 100644 index 0000000..62223b4 --- /dev/null +++ b/admin/src/assets/icons/kejian_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/keziyuyue.svg b/admin/src/assets/icons/keziyuyue.svg new file mode 100644 index 0000000..0d2f1f9 --- /dev/null +++ b/admin/src/assets/icons/keziyuyue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kezizhongxin.svg b/admin/src/assets/icons/kezizhongxin.svg new file mode 100644 index 0000000..7fbcc6c --- /dev/null +++ b/admin/src/assets/icons/kezizhongxin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/koubei.svg b/admin/src/assets/icons/koubei.svg new file mode 100644 index 0000000..a744077 --- /dev/null +++ b/admin/src/assets/icons/koubei.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/kuaijiehuifu.svg b/admin/src/assets/icons/kuaijiehuifu.svg new file mode 100644 index 0000000..72aa5c5 --- /dev/null +++ b/admin/src/assets/icons/kuaijiehuifu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/ladu_mian.svg b/admin/src/assets/icons/ladu_mian.svg new file mode 100644 index 0000000..a40816c --- /dev/null +++ b/admin/src/assets/icons/ladu_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/lanyadingwei.svg b/admin/src/assets/icons/lanyadingwei.svg new file mode 100644 index 0000000..205653f --- /dev/null +++ b/admin/src/assets/icons/lanyadingwei.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/list-2.svg b/admin/src/assets/icons/list-2.svg new file mode 100644 index 0000000..1f471f3 --- /dev/null +++ b/admin/src/assets/icons/list-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/mendiandongtai.svg b/admin/src/assets/icons/mendiandongtai.svg new file mode 100644 index 0000000..7a7b415 --- /dev/null +++ b/admin/src/assets/icons/mendiandongtai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/mishiyuding.svg b/admin/src/assets/icons/mishiyuding.svg new file mode 100644 index 0000000..b856afa --- /dev/null +++ b/admin/src/assets/icons/mishiyuding.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/mishiyuding1.svg b/admin/src/assets/icons/mishiyuding1.svg new file mode 100644 index 0000000..7ac9101 --- /dev/null +++ b/admin/src/assets/icons/mishiyuding1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/notice_buyer.svg b/admin/src/assets/icons/notice_buyer.svg new file mode 100644 index 0000000..bab1997 --- /dev/null +++ b/admin/src/assets/icons/notice_buyer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/open.svg b/admin/src/assets/icons/open.svg new file mode 100644 index 0000000..3cbd08c --- /dev/null +++ b/admin/src/assets/icons/open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/paiduiquhao.svg b/admin/src/assets/icons/paiduiquhao.svg new file mode 100644 index 0000000..fb7abc9 --- /dev/null +++ b/admin/src/assets/icons/paiduiquhao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/paimai.svg b/admin/src/assets/icons/paimai.svg new file mode 100644 index 0000000..0139a69 --- /dev/null +++ b/admin/src/assets/icons/paimai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/pingjia.svg b/admin/src/assets/icons/pingjia.svg new file mode 100644 index 0000000..9b39672 --- /dev/null +++ b/admin/src/assets/icons/pingjia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/pingtaifapiao.svg b/admin/src/assets/icons/pingtaifapiao.svg new file mode 100644 index 0000000..b6b3315 --- /dev/null +++ b/admin/src/assets/icons/pingtaifapiao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/pinpai.svg b/admin/src/assets/icons/pinpai.svg new file mode 100644 index 0000000..f4b129c --- /dev/null +++ b/admin/src/assets/icons/pinpai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qianbao.svg b/admin/src/assets/icons/qianbao.svg new file mode 100644 index 0000000..f384250 --- /dev/null +++ b/admin/src/assets/icons/qianbao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qianbao_mian.svg b/admin/src/assets/icons/qianbao_mian.svg new file mode 100644 index 0000000..897f029 --- /dev/null +++ b/admin/src/assets/icons/qianbao_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qiehuan.svg b/admin/src/assets/icons/qiehuan.svg new file mode 100644 index 0000000..37e0a94 --- /dev/null +++ b/admin/src/assets/icons/qiehuan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qingchu.svg b/admin/src/assets/icons/qingchu.svg new file mode 100644 index 0000000..dc898ac --- /dev/null +++ b/admin/src/assets/icons/qingchu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qingchu_mian.svg b/admin/src/assets/icons/qingchu_mian.svg new file mode 100644 index 0000000..94ecaa2 --- /dev/null +++ b/admin/src/assets/icons/qingchu_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qishoupeisong.svg b/admin/src/assets/icons/qishoupeisong.svg new file mode 100644 index 0000000..9adc068 --- /dev/null +++ b/admin/src/assets/icons/qishoupeisong.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qiyedingcan.svg b/admin/src/assets/icons/qiyedingcan.svg new file mode 100644 index 0000000..147c9e2 --- /dev/null +++ b/admin/src/assets/icons/qiyedingcan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qiyedingcan1.svg b/admin/src/assets/icons/qiyedingcan1.svg new file mode 100644 index 0000000..a3c3277 --- /dev/null +++ b/admin/src/assets/icons/qiyedingcan1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/quanbu.svg b/admin/src/assets/icons/quanbu.svg new file mode 100644 index 0000000..db088f5 --- /dev/null +++ b/admin/src/assets/icons/quanbu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/quanping.svg b/admin/src/assets/icons/quanping.svg new file mode 100644 index 0000000..0a5cfbe --- /dev/null +++ b/admin/src/assets/icons/quanping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qudao.svg b/admin/src/assets/icons/qudao.svg new file mode 100644 index 0000000..e0fbf39 --- /dev/null +++ b/admin/src/assets/icons/qudao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/qudao_xiaochengxu.svg b/admin/src/assets/icons/qudao_xiaochengxu.svg new file mode 100644 index 0000000..54af407 --- /dev/null +++ b/admin/src/assets/icons/qudao_xiaochengxu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/rencaizhaopin.svg b/admin/src/assets/icons/rencaizhaopin.svg new file mode 100644 index 0000000..6113e53 --- /dev/null +++ b/admin/src/assets/icons/rencaizhaopin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/rili.svg b/admin/src/assets/icons/rili.svg new file mode 100644 index 0000000..5d751bf --- /dev/null +++ b/admin/src/assets/icons/rili.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/rili2.svg b/admin/src/assets/icons/rili2.svg new file mode 100644 index 0000000..ba2d55d --- /dev/null +++ b/admin/src/assets/icons/rili2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/rizhi.svg b/admin/src/assets/icons/rizhi.svg new file mode 100644 index 0000000..bfdf2b0 --- /dev/null +++ b/admin/src/assets/icons/rizhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/saoma.svg b/admin/src/assets/icons/saoma.svg new file mode 100644 index 0000000..260981d --- /dev/null +++ b/admin/src/assets/icons/saoma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/set_pay.svg b/admin/src/assets/icons/set_pay.svg new file mode 100644 index 0000000..639bb8e --- /dev/null +++ b/admin/src/assets/icons/set_pay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/set_peisong.svg b/admin/src/assets/icons/set_peisong.svg new file mode 100644 index 0000000..a87ca69 --- /dev/null +++ b/admin/src/assets/icons/set_peisong.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/set_user.svg b/admin/src/assets/icons/set_user.svg new file mode 100644 index 0000000..800baf5 --- /dev/null +++ b/admin/src/assets/icons/set_user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/set_weihu.svg b/admin/src/assets/icons/set_weihu.svg new file mode 100644 index 0000000..e6765f1 --- /dev/null +++ b/admin/src/assets/icons/set_weihu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shanchu.svg b/admin/src/assets/icons/shanchu.svg new file mode 100644 index 0000000..9655138 --- /dev/null +++ b/admin/src/assets/icons/shanchu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shanchu_mian.svg b/admin/src/assets/icons/shanchu_mian.svg new file mode 100644 index 0000000..17ffa4e --- /dev/null +++ b/admin/src/assets/icons/shanchu_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shangchuan.svg b/admin/src/assets/icons/shangchuan.svg new file mode 100644 index 0000000..f2d1a01 --- /dev/null +++ b/admin/src/assets/icons/shangchuan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shangchuanzhaopian.svg b/admin/src/assets/icons/shangchuanzhaopian.svg new file mode 100644 index 0000000..5e90e91 --- /dev/null +++ b/admin/src/assets/icons/shangchuanzhaopian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shangpinguanli.svg b/admin/src/assets/icons/shangpinguanli.svg new file mode 100644 index 0000000..c15f1a1 --- /dev/null +++ b/admin/src/assets/icons/shangpinguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shangpinzhushou.svg b/admin/src/assets/icons/shangpinzhushou.svg new file mode 100644 index 0000000..9972561 --- /dev/null +++ b/admin/src/assets/icons/shangpinzhushou.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shangpuyuding.svg b/admin/src/assets/icons/shangpuyuding.svg new file mode 100644 index 0000000..4250f00 --- /dev/null +++ b/admin/src/assets/icons/shangpuyuding.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shebeiguanli.svg b/admin/src/assets/icons/shebeiguanli.svg new file mode 100644 index 0000000..1ca4ef0 --- /dev/null +++ b/admin/src/assets/icons/shebeiguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shengfuwangputong.svg b/admin/src/assets/icons/shengfuwangputong.svg new file mode 100644 index 0000000..467b687 --- /dev/null +++ b/admin/src/assets/icons/shengfuwangputong.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shengyin.svg b/admin/src/assets/icons/shengyin.svg new file mode 100644 index 0000000..9b1d63b --- /dev/null +++ b/admin/src/assets/icons/shengyin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shengyin_mian.svg b/admin/src/assets/icons/shengyin_mian.svg new file mode 100644 index 0000000..00c76a7 --- /dev/null +++ b/admin/src/assets/icons/shengyin_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shezhi.svg b/admin/src/assets/icons/shezhi.svg new file mode 100644 index 0000000..785b60f --- /dev/null +++ b/admin/src/assets/icons/shezhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shezhi_mian.svg b/admin/src/assets/icons/shezhi_mian.svg new file mode 100644 index 0000000..0bdc106 --- /dev/null +++ b/admin/src/assets/icons/shezhi_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shichang.svg b/admin/src/assets/icons/shichang.svg new file mode 100644 index 0000000..d5d5d88 --- /dev/null +++ b/admin/src/assets/icons/shichang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shichang_mian.svg b/admin/src/assets/icons/shichang_mian.svg new file mode 100644 index 0000000..083b301 --- /dev/null +++ b/admin/src/assets/icons/shichang_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shijian.svg b/admin/src/assets/icons/shijian.svg new file mode 100644 index 0000000..9ad8b2e --- /dev/null +++ b/admin/src/assets/icons/shijian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shijian_mian.svg b/admin/src/assets/icons/shijian_mian.svg new file mode 100644 index 0000000..6c00d41 --- /dev/null +++ b/admin/src/assets/icons/shijian_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shoudan.svg b/admin/src/assets/icons/shoudan.svg new file mode 100644 index 0000000..9967dd8 --- /dev/null +++ b/admin/src/assets/icons/shoudan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shouqi.svg b/admin/src/assets/icons/shouqi.svg new file mode 100644 index 0000000..e8386f1 --- /dev/null +++ b/admin/src/assets/icons/shouqi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shouqi_mian.svg b/admin/src/assets/icons/shouqi_mian.svg new file mode 100644 index 0000000..b022d4c --- /dev/null +++ b/admin/src/assets/icons/shouqi_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shouye.svg b/admin/src/assets/icons/shouye.svg new file mode 100644 index 0000000..288b24f --- /dev/null +++ b/admin/src/assets/icons/shouye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shouye_mian.svg b/admin/src/assets/icons/shouye_mian.svg new file mode 100644 index 0000000..d180e9b --- /dev/null +++ b/admin/src/assets/icons/shouye_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shouyiren.svg b/admin/src/assets/icons/shouyiren.svg new file mode 100644 index 0000000..3b409d2 --- /dev/null +++ b/admin/src/assets/icons/shouyiren.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/show.svg b/admin/src/assets/icons/show.svg new file mode 100644 index 0000000..2fdf9b9 --- /dev/null +++ b/admin/src/assets/icons/show.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuangjiantouxiangyou.svg b/admin/src/assets/icons/shuangjiantouxiangyou.svg new file mode 100644 index 0000000..56c0e61 --- /dev/null +++ b/admin/src/assets/icons/shuangjiantouxiangyou.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuangjiantouxiangzuo.svg b/admin/src/assets/icons/shuangjiantouxiangzuo.svg new file mode 100644 index 0000000..1a90a69 --- /dev/null +++ b/admin/src/assets/icons/shuangjiantouxiangzuo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuaxin.svg b/admin/src/assets/icons/shuaxin.svg new file mode 100644 index 0000000..a4686b3 --- /dev/null +++ b/admin/src/assets/icons/shuaxin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuju.svg b/admin/src/assets/icons/shuju.svg new file mode 100644 index 0000000..8418b76 --- /dev/null +++ b/admin/src/assets/icons/shuju.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuju2.svg b/admin/src/assets/icons/shuju2.svg new file mode 100644 index 0000000..bea4c25 --- /dev/null +++ b/admin/src/assets/icons/shuju2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuju_liuliang.svg b/admin/src/assets/icons/shuju_liuliang.svg new file mode 100644 index 0000000..21b048b --- /dev/null +++ b/admin/src/assets/icons/shuju_liuliang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/shuju_mian.svg b/admin/src/assets/icons/shuju_mian.svg new file mode 100644 index 0000000..5da2d78 --- /dev/null +++ b/admin/src/assets/icons/shuju_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/sort.svg b/admin/src/assets/icons/sort.svg new file mode 100644 index 0000000..1e760a8 --- /dev/null +++ b/admin/src/assets/icons/sort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/sousuo.svg b/admin/src/assets/icons/sousuo.svg new file mode 100644 index 0000000..2387e0a --- /dev/null +++ b/admin/src/assets/icons/sousuo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/sucai.svg b/admin/src/assets/icons/sucai.svg new file mode 100644 index 0000000..23499f8 --- /dev/null +++ b/admin/src/assets/icons/sucai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tianjia.svg b/admin/src/assets/icons/tianjia.svg new file mode 100644 index 0000000..b06fe16 --- /dev/null +++ b/admin/src/assets/icons/tianjia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tishi.svg b/admin/src/assets/icons/tishi.svg new file mode 100644 index 0000000..e14e118 --- /dev/null +++ b/admin/src/assets/icons/tishi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tishi_mian.svg b/admin/src/assets/icons/tishi_mian.svg new file mode 100644 index 0000000..04334ea --- /dev/null +++ b/admin/src/assets/icons/tishi_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tongxunlu_mian.svg b/admin/src/assets/icons/tongxunlu_mian.svg new file mode 100644 index 0000000..b7c06ab --- /dev/null +++ b/admin/src/assets/icons/tongxunlu_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tongzhi.svg b/admin/src/assets/icons/tongzhi.svg new file mode 100644 index 0000000..a227028 --- /dev/null +++ b/admin/src/assets/icons/tongzhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tongzhi_mian.svg b/admin/src/assets/icons/tongzhi_mian.svg new file mode 100644 index 0000000..876676f --- /dev/null +++ b/admin/src/assets/icons/tongzhi_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tuichuquanping.svg b/admin/src/assets/icons/tuichuquanping.svg new file mode 100644 index 0000000..3832030 --- /dev/null +++ b/admin/src/assets/icons/tuichuquanping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tuiguang.svg b/admin/src/assets/icons/tuiguang.svg new file mode 100644 index 0000000..7d5bb2d --- /dev/null +++ b/admin/src/assets/icons/tuiguang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tuiguang_mian.svg b/admin/src/assets/icons/tuiguang_mian.svg new file mode 100644 index 0000000..005323a --- /dev/null +++ b/admin/src/assets/icons/tuiguang_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tupian.svg b/admin/src/assets/icons/tupian.svg new file mode 100644 index 0000000..64b511d --- /dev/null +++ b/admin/src/assets/icons/tupian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/tupian_mian.svg b/admin/src/assets/icons/tupian_mian.svg new file mode 100644 index 0000000..0875efd --- /dev/null +++ b/admin/src/assets/icons/tupian_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/user_biaoqian.svg b/admin/src/assets/icons/user_biaoqian.svg new file mode 100644 index 0000000..206fff6 --- /dev/null +++ b/admin/src/assets/icons/user_biaoqian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/user_gaikuang.svg b/admin/src/assets/icons/user_gaikuang.svg new file mode 100644 index 0000000..b4ae10e --- /dev/null +++ b/admin/src/assets/icons/user_gaikuang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/user_guanli.svg b/admin/src/assets/icons/user_guanli.svg new file mode 100644 index 0000000..e00fd79 --- /dev/null +++ b/admin/src/assets/icons/user_guanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/wangpudiandan.svg b/admin/src/assets/icons/wangpudiandan.svg new file mode 100644 index 0000000..77cc413 --- /dev/null +++ b/admin/src/assets/icons/wangpudiandan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/weixin.svg b/admin/src/assets/icons/weixin.svg new file mode 100644 index 0000000..f043f12 --- /dev/null +++ b/admin/src/assets/icons/weixin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/weixin_mian.svg b/admin/src/assets/icons/weixin_mian.svg new file mode 100644 index 0000000..5c4e92e --- /dev/null +++ b/admin/src/assets/icons/weixin_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/wode.svg b/admin/src/assets/icons/wode.svg new file mode 100644 index 0000000..4cc5c10 --- /dev/null +++ b/admin/src/assets/icons/wode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/wode_mian.svg b/admin/src/assets/icons/wode_mian.svg new file mode 100644 index 0000000..ea9ebfb --- /dev/null +++ b/admin/src/assets/icons/wode_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/xiangji.svg b/admin/src/assets/icons/xiangji.svg new file mode 100644 index 0000000..a9f7b5f --- /dev/null +++ b/admin/src/assets/icons/xiangji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/xiaoxi.svg b/admin/src/assets/icons/xiaoxi.svg new file mode 100644 index 0000000..cf220c8 --- /dev/null +++ b/admin/src/assets/icons/xiaoxi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/xiazai.svg b/admin/src/assets/icons/xiazai.svg new file mode 100644 index 0000000..c741576 --- /dev/null +++ b/admin/src/assets/icons/xiazai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/xitongquanxian.svg b/admin/src/assets/icons/xitongquanxian.svg new file mode 100644 index 0000000..b34ac74 --- /dev/null +++ b/admin/src/assets/icons/xitongquanxian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yingxiao_qipao.svg b/admin/src/assets/icons/yingxiao_qipao.svg new file mode 100644 index 0000000..238ddf2 --- /dev/null +++ b/admin/src/assets/icons/yingxiao_qipao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yingyezizhi.svg b/admin/src/assets/icons/yingyezizhi.svg new file mode 100644 index 0000000..23ae5be --- /dev/null +++ b/admin/src/assets/icons/yingyezizhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yinhangka.svg b/admin/src/assets/icons/yinhangka.svg new file mode 100644 index 0000000..20c1fdc --- /dev/null +++ b/admin/src/assets/icons/yinhangka.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yiwen.svg b/admin/src/assets/icons/yiwen.svg new file mode 100644 index 0000000..ef07f2e --- /dev/null +++ b/admin/src/assets/icons/yiwen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/youhui.svg b/admin/src/assets/icons/youhui.svg new file mode 100644 index 0000000..4358e1c --- /dev/null +++ b/admin/src/assets/icons/youhui.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/youjian.svg b/admin/src/assets/icons/youjian.svg new file mode 100644 index 0000000..1304c01 --- /dev/null +++ b/admin/src/assets/icons/youjian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/youjiantou.svg b/admin/src/assets/icons/youjiantou.svg new file mode 100644 index 0000000..5c59926 --- /dev/null +++ b/admin/src/assets/icons/youjiantou.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yulibao.svg b/admin/src/assets/icons/yulibao.svg new file mode 100644 index 0000000..b785c04 --- /dev/null +++ b/admin/src/assets/icons/yulibao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yuyin.svg b/admin/src/assets/icons/yuyin.svg new file mode 100644 index 0000000..1ac06af --- /dev/null +++ b/admin/src/assets/icons/yuyin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yuyueguanli.svg b/admin/src/assets/icons/yuyueguanli.svg new file mode 100644 index 0000000..080255d --- /dev/null +++ b/admin/src/assets/icons/yuyueguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/yuyueguanlishezhi.svg b/admin/src/assets/icons/yuyueguanlishezhi.svg new file mode 100644 index 0000000..eac7549 --- /dev/null +++ b/admin/src/assets/icons/yuyueguanlishezhi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhankai.svg b/admin/src/assets/icons/zhankai.svg new file mode 100644 index 0000000..aef4e53 --- /dev/null +++ b/admin/src/assets/icons/zhankai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhankai_mian.svg b/admin/src/assets/icons/zhankai_mian.svg new file mode 100644 index 0000000..187e3e9 --- /dev/null +++ b/admin/src/assets/icons/zhankai_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhibo.svg b/admin/src/assets/icons/zhibo.svg new file mode 100644 index 0000000..d329b49 --- /dev/null +++ b/admin/src/assets/icons/zhibo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhibo_mian.svg b/admin/src/assets/icons/zhibo_mian.svg new file mode 100644 index 0000000..443e2cb --- /dev/null +++ b/admin/src/assets/icons/zhibo_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhuangxiu.svg b/admin/src/assets/icons/zhuangxiu.svg new file mode 100644 index 0000000..c692c45 --- /dev/null +++ b/admin/src/assets/icons/zhuangxiu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhuangxiu_mian.svg b/admin/src/assets/icons/zhuangxiu_mian.svg new file mode 100644 index 0000000..53e8deb --- /dev/null +++ b/admin/src/assets/icons/zhuangxiu_mian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zhuoweiguanli.svg b/admin/src/assets/icons/zhuoweiguanli.svg new file mode 100644 index 0000000..ee9d0cc --- /dev/null +++ b/admin/src/assets/icons/zhuoweiguanli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zichanzhuanrang.svg b/admin/src/assets/icons/zichanzhuanrang.svg new file mode 100644 index 0000000..8a8d546 --- /dev/null +++ b/admin/src/assets/icons/zichanzhuanrang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zuliao.svg b/admin/src/assets/icons/zuliao.svg new file mode 100644 index 0000000..e210df3 --- /dev/null +++ b/admin/src/assets/icons/zuliao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/icons/zuliaoyuding.svg b/admin/src/assets/icons/zuliaoyuding.svg new file mode 100644 index 0000000..69a9d99 --- /dev/null +++ b/admin/src/assets/icons/zuliaoyuding.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin/src/assets/images/icon_folder.png b/admin/src/assets/images/icon_folder.png new file mode 100644 index 0000000..99b800f Binary files /dev/null and b/admin/src/assets/images/icon_folder.png differ diff --git a/admin/src/assets/images/no_perms.png b/admin/src/assets/images/no_perms.png new file mode 100644 index 0000000..c37c89b Binary files /dev/null and b/admin/src/assets/images/no_perms.png differ diff --git a/admin/src/assets/images/theme_black.png b/admin/src/assets/images/theme_black.png new file mode 100644 index 0000000..417b4d4 Binary files /dev/null and b/admin/src/assets/images/theme_black.png differ diff --git a/admin/src/assets/images/theme_white.png b/admin/src/assets/images/theme_white.png new file mode 100644 index 0000000..52714ba Binary files /dev/null and b/admin/src/assets/images/theme_white.png differ diff --git a/admin/src/components/app-link/index.vue b/admin/src/components/app-link/index.vue new file mode 100644 index 0000000..e7fe926 --- /dev/null +++ b/admin/src/components/app-link/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/admin/src/components/color-picker/index.vue b/admin/src/components/color-picker/index.vue new file mode 100644 index 0000000..ed0bc3e --- /dev/null +++ b/admin/src/components/color-picker/index.vue @@ -0,0 +1,33 @@ + + diff --git a/admin/src/components/daterange-picker/index.vue b/admin/src/components/daterange-picker/index.vue new file mode 100644 index 0000000..9f97531 --- /dev/null +++ b/admin/src/components/daterange-picker/index.vue @@ -0,0 +1,44 @@ + + + diff --git a/admin/src/components/del-wrap/index.vue b/admin/src/components/del-wrap/index.vue new file mode 100644 index 0000000..787f3e2 --- /dev/null +++ b/admin/src/components/del-wrap/index.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/admin/src/components/dict-value/index.vue b/admin/src/components/dict-value/index.vue new file mode 100644 index 0000000..2935e5d --- /dev/null +++ b/admin/src/components/dict-value/index.vue @@ -0,0 +1,30 @@ + + diff --git a/admin/src/components/editor/index.vue b/admin/src/components/editor/index.vue new file mode 100644 index 0000000..ae39eee --- /dev/null +++ b/admin/src/components/editor/index.vue @@ -0,0 +1,143 @@ + + + + diff --git a/admin/src/components/footer-btns/index.vue b/admin/src/components/footer-btns/index.vue new file mode 100644 index 0000000..eb5aea2 --- /dev/null +++ b/admin/src/components/footer-btns/index.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/admin/src/components/icon/index.ts b/admin/src/components/icon/index.ts new file mode 100644 index 0000000..831fcad --- /dev/null +++ b/admin/src/components/icon/index.ts @@ -0,0 +1,19 @@ +import * as ElementPlusIcons from '@element-plus/icons-vue' +//@ts-ignore +import localIconsName from 'virtual:svg-icons-names' + +export const LOCAL_ICON_PREFIX = 'local-icon-' +export const EL_ICON_PREFIX = 'el-icon-' + +const elIconsName: string[] = [] + +for (const [, component] of Object.entries(ElementPlusIcons)) { + elIconsName.push(`${EL_ICON_PREFIX}${component.name}`) +} + +export function getElementPlusIconNames() { + return elIconsName +} +export function getLocalIconNames() { + return localIconsName +} diff --git a/admin/src/components/icon/index.vue b/admin/src/components/icon/index.vue new file mode 100644 index 0000000..738eef3 --- /dev/null +++ b/admin/src/components/icon/index.vue @@ -0,0 +1,49 @@ + diff --git a/admin/src/components/icon/picker.vue b/admin/src/components/icon/picker.vue new file mode 100644 index 0000000..27fc8cd --- /dev/null +++ b/admin/src/components/icon/picker.vue @@ -0,0 +1,185 @@ + + + diff --git a/admin/src/components/icon/svg-icon.vue b/admin/src/components/icon/svg-icon.vue new file mode 100644 index 0000000..10c6919 --- /dev/null +++ b/admin/src/components/icon/svg-icon.vue @@ -0,0 +1,38 @@ + + + diff --git a/admin/src/components/image-contain/index.vue b/admin/src/components/image-contain/index.vue new file mode 100644 index 0000000..1961025 --- /dev/null +++ b/admin/src/components/image-contain/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/admin/src/components/link/custom-link.vue b/admin/src/components/link/custom-link.vue new file mode 100644 index 0000000..d90264e --- /dev/null +++ b/admin/src/components/link/custom-link.vue @@ -0,0 +1,42 @@ + + + diff --git a/admin/src/components/link/index.ts b/admin/src/components/link/index.ts new file mode 100644 index 0000000..58f3673 --- /dev/null +++ b/admin/src/components/link/index.ts @@ -0,0 +1,11 @@ +export enum LinkTypeEnum { + 'SHOP_PAGES' = 'shop', + 'CUSTOM_LINK' = 'custom' +} + +export interface Link { + path: string + name?: string + type: string + query?: Record +} diff --git a/admin/src/components/link/index.vue b/admin/src/components/link/index.vue new file mode 100644 index 0000000..c3acb1b --- /dev/null +++ b/admin/src/components/link/index.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/admin/src/components/link/picker.vue b/admin/src/components/link/picker.vue new file mode 100644 index 0000000..cc89913 --- /dev/null +++ b/admin/src/components/link/picker.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/admin/src/components/link/shop-pages.vue b/admin/src/components/link/shop-pages.vue new file mode 100644 index 0000000..e5c747b --- /dev/null +++ b/admin/src/components/link/shop-pages.vue @@ -0,0 +1,101 @@ + + + diff --git a/admin/src/components/material/file.vue b/admin/src/components/material/file.vue new file mode 100644 index 0000000..a9f0500 --- /dev/null +++ b/admin/src/components/material/file.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/admin/src/components/material/hook.ts b/admin/src/components/material/hook.ts new file mode 100644 index 0000000..e490c9a --- /dev/null +++ b/admin/src/components/material/hook.ts @@ -0,0 +1,208 @@ +import { + fileCateAdd, + fileCateDelete, + fileCateEdit, + fileCateLists, + fileDelete, + fileList, + fileMove, + fileRename +} from '@/api/file' +import { usePaging } from '@/hooks/usePaging' +import feedback from '@/utils/feedback' +import { ElMessage, ElTree, type CheckboxValueType } from 'element-plus' +import { shallowRef, type Ref } from 'vue' + +// 左侧分组的钩子函数 +export function useCate(type: number) { + const treeRef = shallowRef>() + // 分组列表 + const cateLists = ref([]) + + // 选中的分组id + const cateId = ref('') + + // 获取分组列表 + const getCateLists = async () => { + const data = await fileCateLists({ + type + }) + const item: any[] = [ + // { + // name: '全部', + // id: '' + // }, + // { + // name: '未分组', + // id: 0 + // } + ] + cateLists.value = data + cateLists.value.unshift(...item) + setTimeout(() => { + treeRef.value?.setCurrentKey(cateId.value) + }, 0) + } + + // 添加分组 + const handleAddCate = async (value: string) => { + await fileCateAdd({ + type, + name: value, + pid: 0 + }) + getCateLists() + } + + // 编辑分组 + const handleEditCate = async (value: string, id: number) => { + await fileCateEdit({ + id, + name: value + }) + getCateLists() + } + + // 删除分组 + const handleDeleteCate = async (id: number) => { + await feedback.confirm('确定要删除?') + await fileCateDelete({ id }) + cateId.value = '' + getCateLists() + } + + //选中分类 + const handleCatSelect = (item: any) => { + cateId.value = item.id + } + + return { + treeRef, + cateId, + cateLists, + handleAddCate, + handleEditCate, + handleDeleteCate, + getCateLists, + handleCatSelect + } +} + +// 处理文件的钩子函数 +export function useFile( + cateId: Ref, + type: Ref, + limit: Ref, + size: number +) { + const tableRef = shallowRef() + const listShowType = ref('normal') + const moveId = ref(0) + const select = ref([]) + const isCheckAll = ref(false) + const isIndeterminate = ref(false) + const fileParams = reactive({ + name: '', + type: type, + cid: cateId + }) + const { pager, getLists, resetPage } = usePaging({ + fetchFun: fileList, + params: fileParams, + firstLoading: true, + size + }) + + const getFileList = () => { + getLists() + } + const refresh = () => { + resetPage() + } + + const isSelect = (id: number) => { + return !!select.value.find((item: any) => item.id == id) + } + + const batchFileDelete = async (id?: number[]) => { + await feedback.confirm( + '确认删除后,本地或云存储文件也将同步删除,如文件已被使用,请谨慎操作!' + ) + const ids = id ? id : select.value.map((item: any) => item.id) + await fileDelete({ ids }) + getFileList() + clearSelect() + } + + const batchFileMove = async () => { + const ids = select.value.map((item: any) => item.id) + await fileMove({ ids, cid: moveId.value }) + moveId.value = 0 + getFileList() + clearSelect() + } + + const selectFile = (item: any) => { + const index = select.value.findIndex((items: any) => items.id == item.id) + if (index != -1) { + select.value.splice(index, 1) + return + } + if (select.value.length == limit.value) { + if (limit.value == 1) { + select.value = [] + select.value.push(item) + return + } + ElMessage.warning('已达到选择上限') + return + } + select.value.push(item) + } + + const clearSelect = () => { + select.value = [] + } + + const cancelSelete = (id: number) => { + select.value = select.value.filter((item: any) => item.id != id) + } + + const selectAll = (value: CheckboxValueType) => { + isIndeterminate.value = false + tableRef.value?.toggleAllSelection() + if (value) { + select.value = [...pager.lists] + return + } + clearSelect() + } + + const handleFileRename = async (value: string, id: number) => { + await fileRename({ + id, + name: value + }) + getFileList() + } + return { + listShowType, + tableRef, + moveId, + pager, + fileParams, + select, + isCheckAll, + isIndeterminate, + getFileList, + refresh, + batchFileDelete, + batchFileMove, + selectFile, + isSelect, + clearSelect, + cancelSelete, + selectAll, + handleFileRename + } +} diff --git a/admin/src/components/material/index.vue b/admin/src/components/material/index.vue new file mode 100644 index 0000000..5c9eab8 --- /dev/null +++ b/admin/src/components/material/index.vue @@ -0,0 +1,584 @@ + + + + + diff --git a/admin/src/components/material/picker.vue b/admin/src/components/material/picker.vue new file mode 100644 index 0000000..88ef489 --- /dev/null +++ b/admin/src/components/material/picker.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/admin/src/components/material/preview.vue b/admin/src/components/material/preview.vue new file mode 100644 index 0000000..6accaa0 --- /dev/null +++ b/admin/src/components/material/preview.vue @@ -0,0 +1,72 @@ + + + diff --git a/admin/src/components/overflow-tooltip/index.vue b/admin/src/components/overflow-tooltip/index.vue new file mode 100644 index 0000000..57734db --- /dev/null +++ b/admin/src/components/overflow-tooltip/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/admin/src/components/pagination/index.vue b/admin/src/components/pagination/index.vue new file mode 100644 index 0000000..020d304 --- /dev/null +++ b/admin/src/components/pagination/index.vue @@ -0,0 +1,50 @@ + + + diff --git a/admin/src/components/popover-input/index.vue b/admin/src/components/popover-input/index.vue new file mode 100644 index 0000000..88ba84a --- /dev/null +++ b/admin/src/components/popover-input/index.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/admin/src/components/popup/index.vue b/admin/src/components/popup/index.vue new file mode 100644 index 0000000..f8683be --- /dev/null +++ b/admin/src/components/popup/index.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/admin/src/components/upload/index.vue b/admin/src/components/upload/index.vue new file mode 100644 index 0000000..8f4a5e9 --- /dev/null +++ b/admin/src/components/upload/index.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/admin/src/components/video-player/index.vue b/admin/src/components/video-player/index.vue new file mode 100644 index 0000000..fdf64e3 --- /dev/null +++ b/admin/src/components/video-player/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/admin/src/config/index.ts b/admin/src/config/index.ts new file mode 100644 index 0000000..926d4ff --- /dev/null +++ b/admin/src/config/index.ts @@ -0,0 +1,10 @@ +const config = { + terminal: 1, //终端 + title: '后台管理系统', //网站默认标题 + version: '1.3.3', //版本号 + baseUrl: `${import.meta.env.VITE_APP_BASE_URL || ''}/`, //请求接口域名 + urlPrefix: 'api', //请求默认前缀 + timeout: 60 * 1000 //请求超时时长 +} + +export default config diff --git a/admin/src/config/setting.ts b/admin/src/config/setting.ts new file mode 100644 index 0000000..3925433 --- /dev/null +++ b/admin/src/config/setting.ts @@ -0,0 +1,18 @@ +const defaultSetting = { + showCrumb: true, // 是否显示面包屑 + showLogo: true, // 是否显示logo + isUniqueOpened: false, //只展开一个一级菜单 + sideWidth: 200, //侧边栏宽度 + sideTheme: 'light', //侧边栏主题 + sideDarkColor: '#1d2124', //侧边栏深色主题颜色 + openMultipleTabs: true, // 是否开启多标签tab栏 + theme: '#4A5DFF', //主题色 + successTheme: '#67c23a', //成功主题色 + warningTheme: '#e6a23c', //警告主题色 + dangerTheme: '#f56c6c', //危险主题色 + errorTheme: '#f56c6c', //错误主题色 + infoTheme: '#909399' //信息主题色 +} +//以上各种主题色分别对应element-plus的几种行为主题 + +export default defaultSetting diff --git a/admin/src/enums/appEnums.ts b/admin/src/enums/appEnums.ts new file mode 100644 index 0000000..90ac145 --- /dev/null +++ b/admin/src/enums/appEnums.ts @@ -0,0 +1,40 @@ +//菜单主题类型 +export enum ThemeEnum { + LIGHT = 'light', + DARK = 'dark' +} + +// 菜单类型 +export enum MenuEnum { + CATALOGUE = 'M', + MENU = 'C', + BUTTON = 'A' +} + +// 屏幕 +export enum ScreenEnum { + SM = 640, + MD = 768, + LG = 1024, + XL = 1280, + '2XL' = 1536 +} + +// 客户端类型 +export enum ClientEnum { + MP_WEIXIN = 1, // 微信-小程序 + OA_WEIXIN = 2, // 微信-公众号 + H5 = 3, // H5 + PC = 4, // PC + IOS = 5, //苹果 + ANDROID = 6 //安卓 +} + +export const ClientMap = { + [ClientEnum.MP_WEIXIN]: '微信小程序', + [ClientEnum.OA_WEIXIN]: '微信公众号', + [ClientEnum.H5]: '手机H5', + [ClientEnum.PC]: '电脑PC', + [ClientEnum.IOS]: '苹果APP', + [ClientEnum.ANDROID]: '安卓APP' +} diff --git a/admin/src/enums/cacheEnums.ts b/admin/src/enums/cacheEnums.ts new file mode 100644 index 0000000..45624e4 --- /dev/null +++ b/admin/src/enums/cacheEnums.ts @@ -0,0 +1,8 @@ +// 本地缓冲key + +//token +export const TOKEN_KEY = 'token' +//账号 +export const ACCOUNT_KEY = 'account' +//设置 +export const SETTING_KEY = 'setting' diff --git a/admin/src/enums/pageEnum.ts b/admin/src/enums/pageEnum.ts new file mode 100644 index 0000000..89e6525 --- /dev/null +++ b/admin/src/enums/pageEnum.ts @@ -0,0 +1,9 @@ +export enum PageEnum { + //登录页面 + LOGIN = '/login', + //无权限页面 + ERROR_403 = '/403', + // 404 + ERROR_404 = '/:pathMatch(.*)*', + INDEX = '/' +} diff --git a/admin/src/enums/requestEnums.ts b/admin/src/enums/requestEnums.ts new file mode 100644 index 0000000..67d30b2 --- /dev/null +++ b/admin/src/enums/requestEnums.ts @@ -0,0 +1,28 @@ +export enum ContentTypeEnum { + // json + JSON = 'application/json;charset=UTF-8', + // form-data 上传资源(图片,视频) + FORM_DATA = 'multipart/form-data;charset=UTF-8' +} + +export enum RequestMethodsEnum { + GET = 'GET', + POST = 'POST' +} + +export enum RequestCodeEnum { + SUCCESS = 200, //成功 + FAILED = 300, // 失败 + PARAMS_VALID_ERROR = 310, //参数校验错误 + PARAMS_TYPE_ERROR = 311, //参数类型错误 + REQUEST_METHOD_ERROR = 312, //请求方法错误 + ASSERT_ARGUMENT_ERROR = 313, //断言参数错误 + ASSERT_MYBATIS_ERROR = 314, //断言mybatis错误 + LOGIN_ACCOUNT_ERROR = 330, //登陆账号或密码错误 + LOGIN_DISABLE_ERROR = 331, //登陆账号已被禁用 + TOKEN_EMPTY = 332, // TOKEN参数为空 + TOKEN_INVALID = 333, // TOKEN参数无效 + NO_PERMISSTION = 403, //无相关权限 + REQUEST_404_ERROR = 404, //请求接口不存在 + SYSTEM_ERROR = 500 //系统错误 +} diff --git a/admin/src/hooks/useDictOptions.ts b/admin/src/hooks/useDictOptions.ts new file mode 100644 index 0000000..30f5c11 --- /dev/null +++ b/admin/src/hooks/useDictOptions.ts @@ -0,0 +1,69 @@ +import { dictDataAll } from '@/api/setting/dict' +import { reactive, toRaw } from 'vue' + +interface Options { + [propName: string]: { + api: PromiseFun + params?: Record + transformData?(data: any): any + } +} + +// { +// dict: { +// api: dictData, +// params: { name: 'user' }, +// transformData(data: any) { +// return data.list +// } +// } +// } + +export function useDictOptions(options: Options) { + const optionsData: any = reactive({}) + const optionsKey = Object.keys(options) + const apiLists = optionsKey.map((key) => { + const value = options[key] + optionsData[key] = [] + return () => value.api(toRaw(value.params) || {}) + }) + + const refresh = async () => { + const res = await Promise.allSettled>(apiLists.map((api) => api())) + res.forEach((item, index) => { + const key = optionsKey[index] + if (item.status == 'fulfilled') { + const { transformData } = options[key] + const data = transformData ? transformData(item.value) : item.value + optionsData[key] = data + } + }) + } + refresh() + return { + optionsData: optionsData as T, + refresh + } +} + +// useDictOptions<{ +// dict: any[] +// }>({ +// dict: dictData +// }) + +export function useDictData(dict: string[]) { + const options: Options = {} + for (const type of dict) { + options[type] = { + api: dictDataAll, + params: { + dictType: type + } + } + } + const { optionsData } = useDictOptions(options) + return { + dictData: optionsData + } +} diff --git a/admin/src/hooks/useLockFn.ts b/admin/src/hooks/useLockFn.ts new file mode 100644 index 0000000..c4423d6 --- /dev/null +++ b/admin/src/hooks/useLockFn.ts @@ -0,0 +1,21 @@ +import { ref } from 'vue' + +export function useLockFn(fn: (...args: any[]) => Promise) { + const isLock = ref(false) + const lockFn = async (...args: any[]) => { + if (isLock.value) return + isLock.value = true + try { + const res = await fn(...args) + isLock.value = false + return res + } catch (e) { + isLock.value = false + throw e + } + } + return { + isLock, + lockFn + } +} diff --git a/admin/src/hooks/useMultipleTabs.ts b/admin/src/hooks/useMultipleTabs.ts new file mode 100644 index 0000000..02e1e43 --- /dev/null +++ b/admin/src/hooks/useMultipleTabs.ts @@ -0,0 +1,47 @@ +import useTabsStore from '@/stores/modules/multipleTabs' +import useSettingStore from '@/stores/modules/setting' + +export default function useMultipleTabs() { + const router = useRouter() + const route = useRoute() + const tabsStore = useTabsStore() + const settingStore = useSettingStore() + + const tabsLists = computed(() => { + return tabsStore.getTabList + }) + + const currentTab = computed(() => { + return route.fullPath + }) + + const addTab = () => { + if (!settingStore.openMultipleTabs) return + tabsStore.addTab(router) + } + + const removeTab = (fullPath?: any) => { + if (!settingStore.openMultipleTabs) return + fullPath = fullPath ?? route.fullPath + tabsStore.removeTab(fullPath, router) + } + + const removeOtherTab = () => { + if (!settingStore.openMultipleTabs) return + tabsStore.removeOtherTab(route) + } + + const removeAllTab = () => { + if (!settingStore.openMultipleTabs) return + tabsStore.removeAllTab(router) + } + + return { + tabsLists, + currentTab, + addTab, + removeTab, + removeOtherTab, + removeAllTab + } +} diff --git a/admin/src/hooks/usePaging.ts b/admin/src/hooks/usePaging.ts new file mode 100644 index 0000000..8a1aa59 --- /dev/null +++ b/admin/src/hooks/usePaging.ts @@ -0,0 +1,62 @@ +import { reactive, toRaw } from 'vue' + +// 分页钩子函数 +interface Options { + page?: number + size?: number + fetchFun: (_arg: any) => Promise + params?: Record + firstLoading?: boolean +} + +export function usePaging(options: Options) { + const { page = 1, size = 15, fetchFun, params = {}, firstLoading = false } = options + // 记录分页初始参数 + const paramsInit: Record = Object.assign({}, toRaw(params)) + // 分页数据 + const pager = reactive({ + page, + size, + loading: firstLoading, + count: 0, + lists: [] as any[] + }) + // 请求分页接口 + const getLists = () => { + pager.loading = true + return fetchFun({ + pageNo: pager.page, + pageSize: pager.size, + ...params + }) + .then((res: any) => { + pager.count = res?.count + pager.lists = res?.lists + return Promise.resolve(res) + }) + .catch((err: any) => { + return Promise.reject(err) + }) + .finally(() => { + pager.loading = false + }) + } + // 重置为第一页 + const resetPage = () => { + pager.page = 1 + getLists() + } + // 重置参数 + const resetParams = () => { + Object.keys(paramsInit).forEach((item) => { + params[item] = paramsInit[item] + }) + getLists() + } + return { + pager, + getLists, + resetParams, + resetPage + } +} diff --git a/admin/src/hooks/useWatchRoute.ts b/admin/src/hooks/useWatchRoute.ts new file mode 100644 index 0000000..381b2ec --- /dev/null +++ b/admin/src/hooks/useWatchRoute.ts @@ -0,0 +1,17 @@ +import type { RouteLocationNormalizedLoaded } from 'vue-router' + +export function useWatchRoute(callback: (route: RouteLocationNormalizedLoaded) => void) { + const route = useRoute() + watch( + route, + () => { + callback(route) + }, + { + immediate: true + } + ) + return { + route + } +} diff --git a/admin/src/install/directives/copy.ts b/admin/src/install/directives/copy.ts new file mode 100644 index 0000000..c0b1909 --- /dev/null +++ b/admin/src/install/directives/copy.ts @@ -0,0 +1,28 @@ +/** + * perm 操作权限处理 + * 指令用法: + * 编辑 + */ + +import feedback from '@/utils/feedback' +import useClipboard from 'vue-clipboard3' +const clipboard = 'data-clipboard-text' +export default { + mounted: (el: HTMLElement, binding: any) => { + el.setAttribute(clipboard, binding.value) + const { toClipboard } = useClipboard() + + el.onclick = () => { + toClipboard(el.getAttribute(clipboard)!) + .then(() => { + feedback.msgSuccess('复制成功') + }) + .catch(() => { + feedback.msgError('复制失败') + }) + } + }, + updated: (el: HTMLElement, binding: any) => { + el.setAttribute(clipboard, binding.value) + } +} diff --git a/admin/src/install/directives/perms.ts b/admin/src/install/directives/perms.ts new file mode 100644 index 0000000..e62765f --- /dev/null +++ b/admin/src/install/directives/perms.ts @@ -0,0 +1,28 @@ +/** + * perm 操作权限处理 + * 指令用法: + * 编辑 + */ + +import useUserStore from '@/stores/modules/user' +export default { + mounted: (el: HTMLElement, binding: any) => { + const { value } = binding + const userStore = useUserStore() + const permissions = userStore.perms + const all_permission = '*' + if (Array.isArray(value)) { + if (value.length > 0) { + const hasPermission = permissions.some((key: string) => { + return all_permission == key || value.includes(key) + }) + + if (!hasPermission) { + el.parentNode && el.parentNode.removeChild(el) + } + } + } else { + throw new Error('like v-perms="[\'auth.menu/edit\']"') + } + } +} diff --git a/admin/src/install/index.ts b/admin/src/install/index.ts new file mode 100644 index 0000000..eab8563 --- /dev/null +++ b/admin/src/install/index.ts @@ -0,0 +1,27 @@ +import type { App } from 'vue' +const modules = import.meta.glob('./**/*', { eager: true }) + +// 安装方法,执行某一类相同操作 +function install(app: App) { + Object.keys(modules).forEach((key) => { + const name = key.replace(/(.*\/)*([^.]+).*/gi, '$2') + const type = key.replace(/^\.\/([\w-]+).*/gi, '$1') + const module: any = modules[key] + if (module.default) { + switch (type) { + // 用于注册全局指令 + case 'directives': + app.directive(name, module.default) + break + // 使用插件 + case 'plugins': + typeof module.default === 'function' && module.default(app) + break + } + } + }) +} + +export default { + install +} diff --git a/admin/src/install/plugins/element.ts b/admin/src/install/plugins/element.ts new file mode 100644 index 0000000..ac6ae23 --- /dev/null +++ b/admin/src/install/plugins/element.ts @@ -0,0 +1,11 @@ +import * as ElementPlusIcons from '@element-plus/icons-vue' +import type { App } from 'vue' +//https://github.com/element-plus/element-plus/issues/7293 +import 'element-plus/es/components/dialog/style/css' + +export default (app: App) => { + // 全局注册ElementPlus图标 + for (const [key, component] of Object.entries(ElementPlusIcons)) { + app.component(key, component) + } +} diff --git a/admin/src/install/plugins/pinia.ts b/admin/src/install/plugins/pinia.ts new file mode 100644 index 0000000..6e72e75 --- /dev/null +++ b/admin/src/install/plugins/pinia.ts @@ -0,0 +1,6 @@ +import store from '@/stores' +import type { App } from 'vue' + +export default (app: App) => { + app.use(store) +} diff --git a/admin/src/install/plugins/router.ts b/admin/src/install/plugins/router.ts new file mode 100644 index 0000000..4e3647d --- /dev/null +++ b/admin/src/install/plugins/router.ts @@ -0,0 +1,6 @@ +import router from '@/router' +import type { App } from 'vue' + +export default (app: App) => { + app.use(router) +} diff --git a/admin/src/layout/Empty.vue b/admin/src/layout/Empty.vue new file mode 100644 index 0000000..069271f --- /dev/null +++ b/admin/src/layout/Empty.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/admin/src/layout/components/footer.vue b/admin/src/layout/components/footer.vue new file mode 100644 index 0000000..0dcf06b --- /dev/null +++ b/admin/src/layout/components/footer.vue @@ -0,0 +1,22 @@ + + + diff --git a/admin/src/layout/default/components/header/breadcrumb.vue b/admin/src/layout/default/components/header/breadcrumb.vue new file mode 100644 index 0000000..58c8ef2 --- /dev/null +++ b/admin/src/layout/default/components/header/breadcrumb.vue @@ -0,0 +1,20 @@ + + diff --git a/admin/src/layout/default/components/header/fold.vue b/admin/src/layout/default/components/header/fold.vue new file mode 100644 index 0000000..7ce9360 --- /dev/null +++ b/admin/src/layout/default/components/header/fold.vue @@ -0,0 +1,15 @@ + + + diff --git a/admin/src/layout/default/components/header/full-screen.vue b/admin/src/layout/default/components/header/full-screen.vue new file mode 100644 index 0000000..96cec1e --- /dev/null +++ b/admin/src/layout/default/components/header/full-screen.vue @@ -0,0 +1,10 @@ + + + diff --git a/admin/src/layout/default/components/header/index.vue b/admin/src/layout/default/components/header/index.vue new file mode 100644 index 0000000..3f6d839 --- /dev/null +++ b/admin/src/layout/default/components/header/index.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/admin/src/layout/default/components/header/multiple-tabs.vue b/admin/src/layout/default/components/header/multiple-tabs.vue new file mode 100644 index 0000000..16a5d48 --- /dev/null +++ b/admin/src/layout/default/components/header/multiple-tabs.vue @@ -0,0 +1,122 @@ + + + + diff --git a/admin/src/layout/default/components/header/refresh.vue b/admin/src/layout/default/components/header/refresh.vue new file mode 100644 index 0000000..a55ccba --- /dev/null +++ b/admin/src/layout/default/components/header/refresh.vue @@ -0,0 +1,14 @@ + + + diff --git a/admin/src/layout/default/components/header/user-drop-down.vue b/admin/src/layout/default/components/header/user-drop-down.vue new file mode 100644 index 0000000..3970709 --- /dev/null +++ b/admin/src/layout/default/components/header/user-drop-down.vue @@ -0,0 +1,34 @@ + + + diff --git a/admin/src/layout/default/components/main.vue b/admin/src/layout/default/components/main.vue new file mode 100644 index 0000000..b5386b3 --- /dev/null +++ b/admin/src/layout/default/components/main.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/admin/src/layout/default/components/setting/drawer.vue b/admin/src/layout/default/components/setting/drawer.vue new file mode 100644 index 0000000..efd08fd --- /dev/null +++ b/admin/src/layout/default/components/setting/drawer.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/admin/src/layout/default/components/setting/index.vue b/admin/src/layout/default/components/setting/index.vue new file mode 100644 index 0000000..d0f4957 --- /dev/null +++ b/admin/src/layout/default/components/setting/index.vue @@ -0,0 +1,19 @@ + + + diff --git a/admin/src/layout/default/components/sidebar/index.vue b/admin/src/layout/default/components/sidebar/index.vue new file mode 100644 index 0000000..28413a0 --- /dev/null +++ b/admin/src/layout/default/components/sidebar/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/admin/src/layout/default/components/sidebar/logo.vue b/admin/src/layout/default/components/sidebar/logo.vue new file mode 100644 index 0000000..f469f4e --- /dev/null +++ b/admin/src/layout/default/components/sidebar/logo.vue @@ -0,0 +1,61 @@ + + + + diff --git a/admin/src/layout/default/components/sidebar/menu-item.vue b/admin/src/layout/default/components/sidebar/menu-item.vue new file mode 100644 index 0000000..09b46aa --- /dev/null +++ b/admin/src/layout/default/components/sidebar/menu-item.vue @@ -0,0 +1,87 @@ + + + + diff --git a/admin/src/layout/default/components/sidebar/menu.vue b/admin/src/layout/default/components/sidebar/menu.vue new file mode 100644 index 0000000..7efba48 --- /dev/null +++ b/admin/src/layout/default/components/sidebar/menu.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/admin/src/layout/default/components/sidebar/side.vue b/admin/src/layout/default/components/sidebar/side.vue new file mode 100644 index 0000000..974dfef --- /dev/null +++ b/admin/src/layout/default/components/sidebar/side.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/admin/src/layout/default/index.vue b/admin/src/layout/default/index.vue new file mode 100644 index 0000000..ba6eb38 --- /dev/null +++ b/admin/src/layout/default/index.vue @@ -0,0 +1,22 @@ + + + diff --git a/admin/src/main.ts b/admin/src/main.ts new file mode 100644 index 0000000..79cae31 --- /dev/null +++ b/admin/src/main.ts @@ -0,0 +1,10 @@ +import { createApp } from 'vue' +import App from './App.vue' +import install from './install' +import './permission' +import './styles/index.scss' +import 'virtual:svg-icons-register' + +const app = createApp(App) +app.use(install) +app.mount('#app') diff --git a/admin/src/permission.ts b/admin/src/permission.ts new file mode 100644 index 0000000..ca7017a --- /dev/null +++ b/admin/src/permission.ts @@ -0,0 +1,84 @@ +/** + * 权限控制 + */ + +import NProgress from 'nprogress' +import router, { findFirstValidRoute } from './router' +import 'nprogress/nprogress.css' +import { isExternal } from './utils/validate' +import useUserStore from './stores/modules/user' +import { INDEX_ROUTE, INDEX_ROUTE_NAME } from './router/routes' +import { PageEnum } from './enums/pageEnum' +import useTabsStore from './stores/modules/multipleTabs' +import { clearAuthInfo } from './utils/auth' +import config from './config' + +// NProgress配置 +NProgress.configure({ showSpinner: false }) + +const loginPath = PageEnum.LOGIN +const defaultPath = PageEnum.INDEX +// 免登录白名单 +const whiteList: string[] = [PageEnum.LOGIN, PageEnum.ERROR_403] +router.beforeEach(async (to, from, next) => { + // 开始 Progress Bar + NProgress.start() + document.title = to.meta.title ?? config.title + const userStore = useUserStore() + const tabsStore = useTabsStore() + if (whiteList.includes(to.path)) { + // 在免登录白名单,直接进入 + next() + } else if (userStore.token) { + // 获取用户信息 + const hasGetUserInfo = Object.keys(userStore.userInfo).length !== 0 + if (hasGetUserInfo) { + if (to.path === loginPath) { + next({ path: defaultPath }) + } else { + next() + } + } else { + try { + await userStore.getUserInfo() + await userStore.getMenu() + const routes = userStore.routes + // 找到第一个有效路由 + const routeName = findFirstValidRoute(routes) + // 没有有效路由跳转到403页面 + if (!routeName) { + clearAuthInfo() + next(PageEnum.ERROR_403) + return + } + tabsStore.setRouteName(routeName!) + INDEX_ROUTE.redirect = { name: routeName } + + // 动态添加index路由 + router.addRoute(INDEX_ROUTE) + routes.forEach((route: any) => { + // https 则不插入 + if (isExternal(route.path)) { + return + } + if (!route.children) { + router.addRoute(INDEX_ROUTE_NAME, route) + return + } + // 动态添加可访问路由表 + router.addRoute(route) + }) + next({ ...to, replace: true }) + } catch (err) { + clearAuthInfo() + next({ path: loginPath, query: { redirect: to.fullPath } }) + } + } + } else { + next({ path: loginPath, query: { redirect: to.fullPath } }) + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/admin/src/router/index.ts b/admin/src/router/index.ts new file mode 100644 index 0000000..f4dd998 --- /dev/null +++ b/admin/src/router/index.ts @@ -0,0 +1,110 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { MenuEnum } from '@/enums/appEnums' +import { isExternal } from '@/utils/validate' +import { constantRoutes, INDEX_ROUTE_NAME, LAYOUT, Empty } from './routes' +import useUserStore from '@/stores/modules/user' + +// 匹配views里面所有的.vue文件,动态引入 +const modules = import.meta.glob('/src/views/**/*.vue') + +// +export function getModulesKey() { + return Object.keys(modules).map((item) => item.replace('/src/views/', '').replace('.vue', '')) +} + +// 过滤路由所需要的数据 +export function filterAsyncRoutes(routes: any[], firstRoute = true) { + return routes.map((route) => { + const routeRecord = createRouteRecord(route, firstRoute) + if (route.children != null && route.children && route.children.length) { + routeRecord.children = filterAsyncRoutes(route.children, false) + } + return routeRecord + }) +} + +// 创建一条路由记录 +export function createRouteRecord(route: any, firstRoute: boolean): RouteRecordRaw { + //@ts-ignore + const routeRecord: RouteRecordRaw = { + path: isExternal(route.paths) ? route.paths : firstRoute ? `/${route.paths}` : route.paths, + name: Symbol(route.paths), + meta: { + hidden: !route.isShow, + keepAlive: !!route.isCache, + title: route.menuName, + perms: route.perms, + query: route.params, + icon: route.menuIcon, + type: route.menuType, + activeMenu: route.selected + } + } + switch (route.menuType) { + case MenuEnum.CATALOGUE: + routeRecord.component = firstRoute ? LAYOUT : Empty + if (!route.children) { + routeRecord.component = Empty + } + break + case MenuEnum.MENU: + routeRecord.component = loadRouteView(route.component) + break + } + return routeRecord +} + +// 动态加载组件 +export function loadRouteView(component: string) { + try { + const key = Object.keys(modules).find((key) => { + return key.includes(`${component}.vue`) + }) + if (key) { + return modules[key] + } + throw Error(`找不到组件${component},请确保组件路径正确`) + } catch (error) { + console.error(error) + return Empty + } +} + +// 找到第一个有效的路由 +export function findFirstValidRoute(routes: RouteRecordRaw[]): string | undefined { + for (const route of routes) { + if (route.meta?.type == MenuEnum.MENU && !route.meta?.hidden && !isExternal(route.path)) { + return route.name as string + } + if (route.children) { + const name = findFirstValidRoute(route.children) + if (name) { + return name + } + } + } +} +//通过权限字符查询路由路径 +export function getRoutePath(perms: string) { + const routerObj = useRouter() || router + return routerObj.getRoutes().find((item) => item.meta?.perms == perms)?.path || '' +} + +// 重置路由 +export function resetRouter() { + router.removeRoute(INDEX_ROUTE_NAME) + const { routes } = useUserStore() + routes.forEach((route) => { + const name = route.name + if (name && router.hasRoute(name)) { + router.removeRoute(name) + } + }) +} + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: constantRoutes +}) + +export default router diff --git a/admin/src/router/routes.ts b/admin/src/router/routes.ts new file mode 100644 index 0000000..988f1aa --- /dev/null +++ b/admin/src/router/routes.ts @@ -0,0 +1,59 @@ +/** + * Note: 路由配置项 + * + * path: '/path' // 路由路径 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * meta : { + title: 'title' // 设置该路由在侧边栏的名字 + icon: 'icon-name' // 设置该路由的图标 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + query: '{"id": 1}' // 访问路由的默认传递参数 + hidden: true // 当设置 true 的时候该路由不会在侧边栏出现 + hideTab: true //当设置 true 的时候该路由不会在多标签tab栏出现 + } + */ + +import type { RouteRecordRaw } from 'vue-router' +import { PageEnum } from '@/enums/pageEnum' +// import Layout from '@/layout/default/index.vue' +// import Empty from '@/layout/Empty.vue' + +export const LAYOUT = () => import('@/layout/default/index.vue') // () => Promise.resolve(Layout) +export const Empty = () => import('@/layout/Empty.vue') + +export const INDEX_ROUTE_NAME = 'INDEX_ROUTE' + +export const constantRoutes: Array = [ + { + path: PageEnum.ERROR_404, + component: () => import('@/views/error/404.vue') + }, + { + path: PageEnum.ERROR_403, + component: () => import('@/views/error/403.vue') + }, + { + path: PageEnum.LOGIN, + component: () => import('@/views/account/login.vue') + }, + { + path: '/user', + component: LAYOUT, + children: [ + { + path: 'setting', + name: Symbol(), + component: () => import('@/views/user/setting.vue'), + meta: { + title: '个人设置' + } + } + ] + } +] + +export const INDEX_ROUTE: RouteRecordRaw = { + path: PageEnum.INDEX, + component: LAYOUT, + name: INDEX_ROUTE_NAME +} diff --git a/admin/src/stores/index.ts b/admin/src/stores/index.ts new file mode 100644 index 0000000..7c7ea69 --- /dev/null +++ b/admin/src/stores/index.ts @@ -0,0 +1,3 @@ +import { createPinia } from 'pinia' +const store = createPinia() +export default store diff --git a/admin/src/stores/modules/app.ts b/admin/src/stores/modules/app.ts new file mode 100644 index 0000000..f51e1f7 --- /dev/null +++ b/admin/src/stores/modules/app.ts @@ -0,0 +1,51 @@ +import { getConfig } from '@/api/app' +import { defineStore } from 'pinia' +interface AppSate { + config: Record + isMobile: boolean + isCollapsed: boolean + isRouteShow: boolean +} + +const useAppStore = defineStore({ + id: 'app', + state: (): AppSate => { + return { + config: {}, + isMobile: true, + isCollapsed: false, + isRouteShow: true + } + }, + actions: { + getImageUrl(url: string) { + return url ? `${this.config.ossDomain}${url}` : '' + }, + getConfig() { + return new Promise((resolve, reject) => { + getConfig() + .then((data) => { + this.config = data + resolve(data) + }) + .catch((err) => { + reject(err) + }) + }) + }, + setMobile(value: boolean) { + this.isMobile = value + }, + toggleCollapsed(toggle?: boolean) { + this.isCollapsed = toggle ?? !this.isCollapsed + }, + refreshView() { + this.isRouteShow = false + nextTick(() => { + this.isRouteShow = true + }) + } + } +}) + +export default useAppStore diff --git a/admin/src/stores/modules/multipleTabs.ts b/admin/src/stores/modules/multipleTabs.ts new file mode 100644 index 0000000..b14e366 --- /dev/null +++ b/admin/src/stores/modules/multipleTabs.ts @@ -0,0 +1,169 @@ +import { defineStore } from 'pinia' +import { isExternal } from '@/utils/validate' +import type { + LocationQuery, + RouteLocationNormalized, + RouteParamsRaw, + Router, + RouteRecordName +} from 'vue-router' +import { PageEnum } from '@/enums/pageEnum' + +interface TabItem { + name: RouteRecordName + fullPath: string + path: string + title?: string + query?: LocationQuery + params?: RouteParamsRaw +} + +interface TabsSate { + cacheTabList: Set + tabList: TabItem[] + tasMap: Record + indexRouteName: RouteRecordName +} + +const getHasTabIndex = (fullPath: string, tabList: TabItem[]) => { + return tabList.findIndex((item) => item.fullPath == fullPath) +} + +const isCannotAddRoute = (route: RouteLocationNormalized, router: Router) => { + const { path, meta, name } = route + if (!path || isExternal(path)) return true + if (meta?.hideTab) return true + if (!router.hasRoute(name!)) return true + if (([PageEnum.LOGIN, PageEnum.ERROR_403] as string[]).includes(path)) { + return true + } + return false +} + +const findTabsIndex = (fullPath: string, tabList: TabItem[]) => { + return tabList.findIndex((item) => item.fullPath === fullPath) +} + +const getComponentName = (route: RouteLocationNormalized) => { + return route.matched.at(-1)?.components?.default?.name +} + +export const getRouteParams = (tabItem: TabItem) => { + const { params, path, query } = tabItem + return { + params: params || {}, + path, + query: query || {} + } +} + +const useTabsStore = defineStore({ + id: 'tabs', + state: (): TabsSate => ({ + cacheTabList: new Set(), + tabList: [], + tasMap: {}, + indexRouteName: '' + }), + getters: { + getTabList(): TabItem[] { + return this.tabList + }, + getCacheTabList(): string[] { + return Array.from(this.cacheTabList) + } + }, + actions: { + setRouteName(name: RouteRecordName) { + this.indexRouteName = name + }, + addCache(componentName?: string) { + if (componentName) this.cacheTabList.add(componentName) + }, + removeCache(componentName?: string) { + if (componentName && this.cacheTabList.has(componentName)) { + this.cacheTabList.delete(componentName) + } + console.log(this.cacheTabList) + }, + clearCache() { + this.cacheTabList.clear() + }, + resetState() { + this.cacheTabList = new Set() + this.tabList = [] + this.tasMap = {} + this.indexRouteName = '' + }, + addTab(router: Router) { + const route = unref(router.currentRoute) + const { name, query, meta, params, fullPath, path } = route + if (isCannotAddRoute(route, router)) return + const hasTabIndex = getHasTabIndex(fullPath!, this.tabList) + const componentName = getComponentName(route) + const tabItem = { + name: name!, + path, + fullPath, + title: meta?.title, + query, + params + } + this.tasMap[fullPath] = tabItem + if (meta?.keepAlive) { + this.addCache(componentName) + } + if (hasTabIndex != -1) { + return + } + + this.tabList.push(tabItem) + }, + removeTab(fullPath: string, router: Router) { + const { currentRoute, push } = router + const index = findTabsIndex(fullPath, this.tabList) + // 移除tab + if (this.tabList.length > 1) { + index !== -1 && this.tabList.splice(index, 1) + } + const componentName = getComponentName(currentRoute.value) + this.removeCache(componentName) + if (fullPath !== currentRoute.value.fullPath) { + return + } + // 删除选中的tab + let toTab: TabItem | null = null + + if (index === 0) { + toTab = this.tabList[index] + } else { + toTab = this.tabList[index - 1] + } + + const toRoute = getRouteParams(toTab) + push(toRoute) + }, + removeOtherTab(route: RouteLocationNormalized) { + this.tabList = this.tabList.filter((item) => item.fullPath == route.fullPath) + const componentName = getComponentName(route) + this.cacheTabList.forEach((name) => { + if (componentName !== name) { + this.removeCache(name) + } + }) + }, + removeAllTab(router: Router) { + const { push, currentRoute } = router + const { name } = unref(currentRoute) + if (name == this.indexRouteName) { + this.removeOtherTab(currentRoute.value) + return + } + this.tabList = [] + this.clearCache() + push(PageEnum.INDEX) + } + } +}) + +export default useTabsStore diff --git a/admin/src/stores/modules/setting.ts b/admin/src/stores/modules/setting.ts new file mode 100644 index 0000000..219a58b --- /dev/null +++ b/admin/src/stores/modules/setting.ts @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia' +import defaultSetting from '@/config/setting' +import cache from '@/utils/cache' +import { isObject } from '@vue/shared' +import { setTheme } from '@/utils/theme' +import { SETTING_KEY } from '@/enums/cacheEnums' +const storageSetting = cache.get(SETTING_KEY) + +export const useSettingStore = defineStore({ + id: 'setting', + state: () => { + const state = { + showDrawer: false, + ...defaultSetting + } + isObject(storageSetting) && Object.assign(state, storageSetting) + return state + }, + actions: { + // 设置布局设置 + setSetting(data: Record) { + const { key, value } = data + if (this.hasOwnProperty(key)) { + //@ts-ignore + this[key] = value + } + const settings: any = Object.assign({}, this.$state) + delete settings.showDrawer + cache.set(SETTING_KEY, settings) + }, + // 设置主题色 + setTheme(isDark: boolean) { + setTheme( + { + primary: this.theme, + success: this.successTheme, + warning: this.warningTheme, + danger: this.dangerTheme, + error: this.errorTheme, + info: this.infoTheme + }, + isDark + ) + }, + resetTheme() { + for (const key in defaultSetting) { + //@ts-ignore + this[key] = defaultSetting[key] + } + cache.remove(SETTING_KEY) + } + } +}) + +export default useSettingStore diff --git a/admin/src/stores/modules/user.ts b/admin/src/stores/modules/user.ts new file mode 100644 index 0000000..d5248d6 --- /dev/null +++ b/admin/src/stores/modules/user.ts @@ -0,0 +1,96 @@ +import { defineStore } from 'pinia' +import cache from '@/utils/cache' +import type { RouteRecordRaw } from 'vue-router' +import { getUserInfo, login, logout, getMenu } from '@/api/user' +import router, { filterAsyncRoutes } from '@/router' +import { TOKEN_KEY } from '@/enums/cacheEnums' +import { PageEnum } from '@/enums/pageEnum' +import { clearAuthInfo, getToken } from '@/utils/auth' +export interface UserState { + token: string + userInfo: Record + routes: RouteRecordRaw[] + menu: any[] + perms: string[] +} + +const useUserStore = defineStore({ + id: 'user', + state: (): UserState => ({ + token: getToken() || '', + // 用户信息 + userInfo: {}, + // 路由 + routes: [], + menu: [], + // 权限 + perms: [] + }), + getters: {}, + actions: { + resetState() { + this.token = '' + this.userInfo = {} + this.perms = [] + }, + login(playload: any) { + const { account, password } = playload + return new Promise((resolve, reject) => { + login({ + username: account, + password: password + }) + .then((data) => { + this.token = data.token + cache.set(TOKEN_KEY, data.token) + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + }, + logout() { + return new Promise((resolve, reject) => { + logout() + .then(async (data) => { + this.token = '' + await router.push(PageEnum.LOGIN) + clearAuthInfo() + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + }, + getUserInfo() { + return new Promise((resolve, reject) => { + getUserInfo() + .then((data) => { + this.userInfo = data.user + this.perms = data.permissions + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + }, + getMenu() { + return new Promise((resolve, reject) => { + getMenu() + .then((data) => { + this.menu = data + this.routes = filterAsyncRoutes(data) + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) + } + } +}) + +export default useUserStore diff --git a/admin/src/styles/dark.css b/admin/src/styles/dark.css new file mode 100644 index 0000000..ae969fd --- /dev/null +++ b/admin/src/styles/dark.css @@ -0,0 +1,49 @@ +:root.dark { + color-scheme: dark; + --table-header-bg-color: var(--el-bg-color); + --el-bg-color-page: #0a0a0a; + --el-bg-color: #1d2124; + --el-bg-color-overlay: #1d1e1f; + --el-text-color-primary: #e5eaf3; + --el-text-color-regular: #cfd3dc; + --el-text-color-secondary: #a3a6ad; + --el-text-color-placeholder: #8d9095; + --el-text-color-disabled: #6c6e72; + --el-border-color-darker: #636466; + --el-border-color-dark: #58585b; + --el-border-color: #4c4d4f; + --el-border-color-light: #414243; + --el-border-color-lighter: #363637; + --el-border-color-extra-light: #2b2b2c; + --el-fill-color-darker: #424243; + --el-fill-color-dark: #39393a; + --el-fill-color: #303030; + --el-fill-color-light: #262727; + --el-fill-color-lighter: #1d1d1d; + --el-fill-color-extra-light: #191919; + --el-fill-color-blank: var(--el-bg-color); + --el-mask-color: rgba(0, 0, 0, 0.8); + --el-mask-color-extra-light: rgba(0, 0, 0, 0.3); + --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.36), 0px 8px 20px rgba(0, 0, 0, 0.72); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.72); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.72); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.72), 0px 12px 32px #000000, + 0px 8px 16px -8px #000000 !important; + /* wangeditor主题 */ + --w-e-textarea-bg-color: var(--el-bg-color); + --w-e-textarea-color: var(--el-text-color-primary); + --w-e-textarea-border-color: var(--el-border-color); + --w-e-textarea-slight-border-color: var(--el-border-color-light); + --w-e-textarea-slight-color: var(--el-border-color); + --w-e-textarea-slight-bg-color: var(--el-bg-color-page); + /* --w-e-textarea-selected-border-color: #b4d5ff; + --w-e-textarea-handler-bg-color: #4290f7; */ + --w-e-toolbar-color: var(--el-text-color-primary); + --w-e-toolbar-bg-color: var(--el-bg-color); + --w-e-toolbar-active-color: var(--el-text-color-primary); + --w-e-toolbar-active-bg-color: var(--el-bg-color); + --w-e-toolbar-disabled-color: var(--el-text-color-disabled); + --w-e-toolbar-border-color: var(--el-border-color); + --w-e-modal-button-bg-color: var(--el-bg-color); + --w-e-modal-button-border-color: var(--el-border-color); +} diff --git a/admin/src/styles/element.scss b/admin/src/styles/element.scss new file mode 100644 index 0000000..07d8e5d --- /dev/null +++ b/admin/src/styles/element.scss @@ -0,0 +1,145 @@ +:root { + // 弹窗居中 + .el-overlay-dialog { + display: flex; + justify-content: center; + align-items: center; + min-height: 100%; + position: static; + + .el-dialog { + --el-dialog-content-font-size: var(--el-font-size-base); + --el-dialog-margin-top: 50px; + max-width: calc(100vw - 30px); + flex: none; + display: flex; + flex-direction: column; + border-radius: 5px; + + &.body-padding .el-dialog__body { + padding: 0; + } + + .el-dialog__body { + flex: 1; + padding: 15px 20px; + } + .el-dialog__header { + font-size: var(--el-font-size-large); + } + } + } + + .el-drawer { + --el-drawer-padding-primary: 16px; + &__header { + margin-bottom: 0; + padding: 13px 16px; + border-bottom: 1px solid var(--el-border-color-lighter); + } + &__title { + @apply text-tx-primary; + } + } + + .el-table { + --el-table-header-text-color: var(--el-text-color-primary); + --el-table-header-bg-color: var(--table-header-bg-color); + font-size: var(--el-font-size-base); + + thead { + th { + font-weight: 400; + } + } + } + + .el-input-group__prepend { + background-color: var(--el-fill-color-blank); + } + + .el-checkbox { + --el-checkbox-font-size: var(--el-font-size-base); + } + + .el-menu--popup-container { + &.theme-light { + .el-menu { + .el-menu-item { + &.is-active { + @apply bg-primary-light-9 border-primary border-r-2; + } + } + .el-menu-item:hover, + .el-sub-menu__title:hover { + color: var(--el-color-primary); + } + } + } + &.theme-dark { + .el-menu { + .el-menu-item { + &.is-active { + @apply bg-primary; + } + } + } + } + } + + .el-message-box { + --el-messagebox-width: 350px; + } + .el-date-editor { + --el-date-editor-width: 280px; + .el-range-input { + font-size: var(--el-font-size-small); + } + } + + .el-button--primary { + --el-button-hover-link-text-color: var(--el-color-primary-light-3); + } + .el-button--success { + --el-button-hover-link-text-color: var(--el-color-success-light-3); + } + .el-button--info { + --el-button-hover-link-text-color: var(--el-color-info-light-3); + } + .el-button--warning { + --el-button-hover-link-text-color: var(--el-color-warning-light-3); + } + .el-button--danger { + --el-button-hover-link-text-color: var(--el-color-danger-light-3); + } + .el-image__error { + font-size: 12px; + } + .el-tabs__nav-wrap::after { + height: 1px; + } +} +@media (max-width: 768px) { + .el-pagination > .el-pagination__jump { + display: none !important; + } + .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-button { + // 防止被tailwindcss默认样式覆盖 + background-color: var(--el-button-bg-color, var(--el-color-white)); + + //覆盖el-button的点击样式 + &:focus { + border-color: var(--el-button-border-color); + background-color: var(--el-button-bg-color); + } + &:hover { + color: var(--el-button-hover-text-color); + border-color: var(--el-button-hover-border-color); + background-color: var(--el-button-hover-bg-color); + } +} diff --git a/admin/src/styles/index.scss b/admin/src/styles/index.scss new file mode 100644 index 0000000..7bd1726 --- /dev/null +++ b/admin/src/styles/index.scss @@ -0,0 +1,6 @@ + +@import 'element.scss'; +@import 'dark.css'; +@import 'var.css'; +@import 'tailwind.css'; +@import 'public.scss'; diff --git a/admin/src/styles/public.scss b/admin/src/styles/public.scss new file mode 100644 index 0000000..a11a8bd --- /dev/null +++ b/admin/src/styles/public.scss @@ -0,0 +1,18 @@ +body { + @apply text-base text-tx-primary overflow-hidden min-w-[375px]; +} +.form-tips { + @apply text-tx-secondary text-xs leading-6 mt-1; +} + +.clearfix:after { + content: ''; + display: block; + clear: both; + visibility: hidden; +} + +/* NProgress */ +#nprogress .bar { + @apply bg-primary #{!important}; +} diff --git a/admin/src/styles/tailwind.css b/admin/src/styles/tailwind.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/admin/src/styles/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/admin/src/styles/var.css b/admin/src/styles/var.css new file mode 100644 index 0000000..a10631a --- /dev/null +++ b/admin/src/styles/var.css @@ -0,0 +1,48 @@ +:root { + --el-font-family: theme(fontFamily.sans); + --el-font-weight-primary: 400; + --el-menu-item-height: 46px; + --el-menu-sub-item-height: var(--el-menu-item-height); + --el-menu-icon-width: 18px; + --aside-width: 200px; + --navbar-height: 50px; + --color-white: #ffffff; + --table-header-bg-color: #f8f8f8; + --el-font-size-extra-large: 18px; + --el-menu-base-level-padding: 16px; + --el-menu-level-padding: 26px; + --el-font-size-large: 16px; + --el-font-size-medium: 15px; + --el-font-size-base: 14px; + --el-font-size-small: 13px; + --el-font-size-extra-small: 12px; + + --el-bg-color: var(--color-white); + --el-bg-color-page: #f6f6f6; + --el-bg-color-overlay: #ffffff; + --el-text-color-primary: #333333; + --el-text-color-regular: #666666; + --el-text-color-secondary: #999999; + --el-text-color-placeholder: #a8abb2; + --el-text-color-disabled: #c0c4cc; + --el-border-color: #dcdfe6; + --el-border-color-light: #e4e7ed; + --el-border-color-lighter: #ebeef5; + --el-border-color-extra-light: #f2f2f2; + --el-border-color-dark: #d4d7de; + --el-border-color-darker: #cdd0d6; + --el-fill-color: #f0f2f5; + --el-fill-color-light: #f8f8f8; + --el-fill-color-lighter: #fafafa; + --el-fill-color-extra-light: #fafcff; + --el-fill-color-dark: #ebedf0; + --el-fill-color-darker: #e6e8eb; + --el-fill-color-blank: #ffffff; + --el-mask-color: rgba(255, 255, 255, 0.9); + --el-mask-color-extra-light: rgba(255, 255, 255, 0.3); + -el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.04), 0px 8px 20px rgba(0, 0, 0, 0.08); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.12); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.12); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12), + 0px 8px 16px -8px rgba(0, 0, 0, 0.16); +} diff --git a/admin/src/utils/auth.ts b/admin/src/utils/auth.ts new file mode 100644 index 0000000..e7a6877 --- /dev/null +++ b/admin/src/utils/auth.ts @@ -0,0 +1,18 @@ +import { TOKEN_KEY } from '@/enums/cacheEnums' +import { resetRouter } from '@/router' +import useTabsStore from '@/stores/modules/multipleTabs' +import useUserStore from '@/stores/modules/user' +import cache from './cache' + +export function getToken() { + return cache.get(TOKEN_KEY) +} + +export function clearAuthInfo() { + const userStore = useUserStore() + const tabsStore = useTabsStore() + userStore.resetState() + tabsStore.$reset() + cache.remove(TOKEN_KEY) + resetRouter() +} diff --git a/admin/src/utils/cache.ts b/admin/src/utils/cache.ts new file mode 100644 index 0000000..d98624f --- /dev/null +++ b/admin/src/utils/cache.ts @@ -0,0 +1,50 @@ +const cache = { + key: 'like_admin_', + //设置缓存(expire为缓存时效) + set(key: string, value: any, expire?: string) { + key = this.getKey(key) + let data: any = { + expire: expire ? this.time() + expire : '', + value + } + + if (typeof data === 'object') { + data = JSON.stringify(data) + } + try { + window.localStorage.setItem(key, data) + } catch (e) { + return null + } + }, + get(key: string) { + key = this.getKey(key) + try { + const data = window.localStorage.getItem(key) + if (!data) { + return null + } + const { value, expire } = JSON.parse(data) + if (expire && expire < this.time()) { + window.localStorage.removeItem(key) + return null + } + return value + } catch (e) { + return null + } + }, + //获取当前时间 + time() { + return Math.round(new Date().getTime() / 1000) + }, + remove(key: string) { + key = this.getKey(key) + window.localStorage.removeItem(key) + }, + getKey(key: string) { + return this.key + key + } +} + +export default cache diff --git a/admin/src/utils/echart.ts b/admin/src/utils/echart.ts new file mode 100644 index 0000000..3d3aee0 --- /dev/null +++ b/admin/src/utils/echart.ts @@ -0,0 +1,65 @@ +//引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。 + +import * as echarts from 'echarts/core' +//引入柱状图图表,图表后缀都为 Chart +import { + BarChart, + LineChart, + PieChart, + MapChart, + PictorialBarChart, + RadarChart, + ScatterChart, + GaugeChart +} from 'echarts/charts' +// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent +} from 'echarts/components' + +//引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步 +import { CanvasRenderer } from 'echarts/renderers' +//标签自动布局,全局过渡动画等特性 +import { LabelLayout, UniversalTransition } from 'echarts/features' + +// 注册必须的组件 +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + RadarChart, + PictorialBarChart, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, + ScatterChart, + CanvasRenderer, + LabelLayout, + UniversalTransition, + GaugeChart +]) diff --git a/admin/src/utils/env.ts b/admin/src/utils/env.ts new file mode 100644 index 0000000..d8951e9 --- /dev/null +++ b/admin/src/utils/env.ts @@ -0,0 +1,13 @@ +/** + * @description: 开发模式 + */ +export function isDevMode(): boolean { + return import.meta.env.DEV +} + +/** + * @description: 生成模式 + */ +export function isProdMode(): boolean { + return import.meta.env.PROD +} diff --git a/admin/src/utils/feedback.ts b/admin/src/utils/feedback.ts new file mode 100644 index 0000000..91672ab --- /dev/null +++ b/admin/src/utils/feedback.ts @@ -0,0 +1,95 @@ +import { + ElMessage, + ElMessageBox, + ElNotification, + ElLoading, + type ElMessageBoxOptions +} from 'element-plus' +import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading' + +export class Feedback { + private loadingInstance: LoadingInstance | null = null + static instance: Feedback | null = null + static getInstance() { + return this.instance ?? (this.instance = new Feedback()) + } + // 消息提示 + msg(msg: string) { + ElMessage.info(msg) + } + // 错误消息 + msgError(msg: string) { + ElMessage.error(msg) + } + // 成功消息 + msgSuccess(msg: string) { + ElMessage.success(msg) + } + // 警告消息 + msgWarning(msg: string) { + ElMessage.warning(msg) + } + // 弹出提示 + alert(msg: string) { + ElMessageBox.alert(msg, '系统提示') + } + // 错误提示 + alertError(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'error' }) + } + // 成功提示 + alertSuccess(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'success' }) + } + // 警告提示 + alertWarning(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'warning' }) + } + // 通知提示 + notify(msg: string) { + ElNotification.info(msg) + } + // 错误通知 + notifyError(msg: string) { + ElNotification.error(msg) + } + // 成功通知 + notifySuccess(msg: string) { + ElNotification.success(msg) + } + // 警告通知 + notifyWarning(msg: string) { + ElNotification.warning(msg) + } + // 确认窗体 + confirm(msg: string) { + return ElMessageBox.confirm(msg, '温馨提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + } + // 提交内容 + prompt(content: string, title: string, options?: ElMessageBoxOptions) { + return ElMessageBox.prompt(content, title, { + confirmButtonText: '确定', + cancelButtonText: '取消', + ...options + }) + } + // 打开全局loading + loading(msg: string) { + this.loadingInstance = ElLoading.service({ + lock: true, + text: msg + }) + } + // 关闭全局loading + closeLoading() { + this.loadingInstance?.close() + } +} + +const feedback = Feedback.getInstance() + +export default feedback diff --git a/admin/src/utils/file.ts b/admin/src/utils/file.ts new file mode 100644 index 0000000..a33a0f3 --- /dev/null +++ b/admin/src/utils/file.ts @@ -0,0 +1,16 @@ +/** + * @description + * @param file + */ +export function streamFileDownload(file: any, fileName = '文件名称.zip') { + const blob = new Blob([file], { type: 'application/octet-stream;charset=UTF-8' }) + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.style.display = 'none' + link.href = url + link.setAttribute('download', fileName) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) // 下载完成移除元素 + window.URL.revokeObjectURL(url) +} diff --git a/admin/src/utils/request/axios.ts b/admin/src/utils/request/axios.ts new file mode 100644 index 0000000..65703e0 --- /dev/null +++ b/admin/src/utils/request/axios.ts @@ -0,0 +1,165 @@ +import { RequestMethodsEnum } from '@/enums/requestEnums' +import axios, { + AxiosError, + type AxiosInstance, + type AxiosRequestConfig, + type AxiosResponse +} from 'axios' +import { isFunction, merge, cloneDeep } from 'lodash' +import axiosCancel from './cancel' +import type { RequestData, RequestOptions } from './type' + +export class Axios { + private axiosInstance: AxiosInstance + private readonly config: AxiosRequestConfig + private readonly options: RequestOptions + constructor(config: AxiosRequestConfig) { + this.config = config + this.options = config.requestOptions + this.axiosInstance = axios.create(config) + this.setupInterceptors() + } + + /** + * @description 获取axios实例 + */ + getAxiosInstance() { + return this.axiosInstance + } + + /** + * @description 设置拦截器 + */ + setupInterceptors() { + if (!this.config.axiosHooks) { + return + } + const { + requestInterceptorsHook, + requestInterceptorsCatchHook, + responseInterceptorsHook, + responseInterceptorsCatchHook + } = this.config.axiosHooks + this.axiosInstance.interceptors.request.use( + (config) => { + this.addCancelToken(config) + if (isFunction(requestInterceptorsHook)) { + config = requestInterceptorsHook(config) + } + return config + }, + (err: Error) => { + if (isFunction(requestInterceptorsCatchHook)) { + requestInterceptorsCatchHook(err) + } + return err + } + ) + this.axiosInstance.interceptors.response.use( + (response: AxiosResponse) => { + this.removeCancelToken(response.config.url!) + if (isFunction(responseInterceptorsHook)) { + response = responseInterceptorsHook(response) + } + return response + }, + (err: AxiosError) => { + if (isFunction(responseInterceptorsCatchHook)) { + responseInterceptorsCatchHook(err) + } + if (err.code != AxiosError.ERR_CANCELED) { + this.removeCancelToken(err.config?.url!) + } + + if (err.code == AxiosError.ECONNABORTED || err.code == AxiosError.ERR_NETWORK) { + return new Promise((resolve) => setTimeout(resolve, 500)).then(() => + this.retryRequest(err) + ) + } + return Promise.reject(err) + } + ) + } + + /** + * @description 添加CancelToken + */ + addCancelToken(config: AxiosRequestConfig) { + const { ignoreCancelToken } = config.requestOptions + !ignoreCancelToken && axiosCancel.add(config) + } + + /** + * @description 移除CancelToken + */ + removeCancelToken(url: string) { + axiosCancel.remove(url) + } + + /** + * @description 重新请求 + */ + retryRequest(error: AxiosError) { + const config = error.config + const { retryCount, isOpenRetry } = config.requestOptions + if (!isOpenRetry || config.method?.toUpperCase() == RequestMethodsEnum.POST) { + return Promise.reject(error) + } + config.retryCount = config.retryCount ?? 0 + + if (config.retryCount >= retryCount) { + return Promise.reject(error) + } + config.retryCount++ + + return this.axiosInstance.request(config) + } + /** + * @description get请求 + */ + get( + config: Partial, + options?: Partial + ): Promise { + return this.request({ ...config, method: RequestMethodsEnum.GET }, options) + } + + /** + * @description post请求 + */ + post( + config: Partial, + options?: Partial + ): Promise { + return this.request({ ...config, method: RequestMethodsEnum.POST }, options) + } + + /** + * @description 请求函数 + */ + request( + config: Partial, + options?: Partial + ): Promise { + const opt: RequestOptions = merge({}, this.options, options) + const axioxConfig: AxiosRequestConfig = { + ...cloneDeep(config), + requestOptions: opt + } + const { urlPrefix } = opt + // 拼接请求前缀如api + if (urlPrefix) { + axioxConfig.url = `${urlPrefix}${config.url}` + } + return new Promise((resolve, reject) => { + this.axiosInstance + .request>>(axioxConfig) + .then((res) => { + resolve(res) + }) + .catch((err) => { + reject(err) + }) + }) + } +} diff --git a/admin/src/utils/request/cancel.ts b/admin/src/utils/request/cancel.ts new file mode 100644 index 0000000..b092e1d --- /dev/null +++ b/admin/src/utils/request/cancel.ts @@ -0,0 +1,31 @@ +import axios, { type AxiosRequestConfig, type Canceler } from 'axios' + +const cancelerMap = new Map() + +export class AxiosCancel { + private static instance?: AxiosCancel + + static createInstance() { + return this.instance ?? (this.instance = new AxiosCancel()) + } + add(config: AxiosRequestConfig) { + const url = config.url! + this.remove(url) + config.cancelToken = new axios.CancelToken((cancel) => { + if (!cancelerMap.has(url)) { + cancelerMap.set(url, cancel) + } + }) + } + remove(url: string) { + if (cancelerMap.has(url)) { + const cancel = cancelerMap.get(url) + cancel && cancel(url) + cancelerMap.delete(url) + } + } +} + +const axiosCancel = AxiosCancel.createInstance() + +export default axiosCancel diff --git a/admin/src/utils/request/index.ts b/admin/src/utils/request/index.ts new file mode 100644 index 0000000..d278983 --- /dev/null +++ b/admin/src/utils/request/index.ts @@ -0,0 +1,130 @@ +import { merge } from 'lodash' +import configs from '@/config' +import { Axios } from './axios' +import { ContentTypeEnum, RequestCodeEnum, RequestMethodsEnum } from '@/enums/requestEnums' +import type { AxiosHooks } from './type' +import { clearAuthInfo, getToken } from '../auth' +import feedback from '../feedback' +import NProgress from 'nprogress' +import { AxiosError, type AxiosRequestConfig } from 'axios' +import router from '@/router' +import { PageEnum } from '@/enums/pageEnum' + +// 处理axios的钩子函数 +const axiosHooks: AxiosHooks = { + requestInterceptorsHook(config) { + NProgress.start() + const { withToken, isParamsToData } = config.requestOptions + const params = config.params || {} + const headers = config.headers || {} + + // 添加token + if (withToken) { + const token = getToken() + headers.token = token + } + // POST请求下如果无data,则将params视为data + if ( + isParamsToData && + !Reflect.has(config, 'data') && + config.method?.toUpperCase() === RequestMethodsEnum.POST + ) { + config.data = params + config.params = {} + } + config.headers = headers + return config + }, + requestInterceptorsCatchHook(err) { + NProgress.done() + return err + }, + async responseInterceptorsHook(response) { + NProgress.done() + const { isTransformResponse, isReturnDefaultResponse } = response.config.requestOptions + + //返回默认响应,当需要获取响应头及其他数据时可使用 + if (isReturnDefaultResponse) { + return response + } + // 是否需要对数据进行处理 + if (!isTransformResponse) { + return response.data + } + const { code, data, show, msg } = response.data + switch (code) { + case RequestCodeEnum.SUCCESS: + if (show) { + msg && feedback.msgSuccess(msg) + } + return data + + case RequestCodeEnum.PARAMS_TYPE_ERROR: + case RequestCodeEnum.PARAMS_VALID_ERROR: + case RequestCodeEnum.REQUEST_METHOD_ERROR: + case RequestCodeEnum.ASSERT_ARGUMENT_ERROR: + case RequestCodeEnum.ASSERT_MYBATIS_ERROR: + case RequestCodeEnum.LOGIN_ACCOUNT_ERROR: + case RequestCodeEnum.LOGIN_DISABLE_ERROR: + case RequestCodeEnum.NO_PERMISSTION: + case RequestCodeEnum.FAILED: + case RequestCodeEnum.SYSTEM_ERROR: + msg && feedback.msgError(msg) + return Promise.reject(data) + + case RequestCodeEnum.TOKEN_INVALID: + case RequestCodeEnum.TOKEN_EMPTY: + clearAuthInfo() + router.push(PageEnum.LOGIN) + return Promise.reject() + + default: + return data + } + }, + responseInterceptorsCatchHook(error) { + NProgress.done() + if (error.code !== AxiosError.ERR_CANCELED) { + error.message && feedback.msgError(error.message) + } + return Promise.reject(error) + } +} + +const defaultOptions: AxiosRequestConfig = { + timeout: configs.timeout, + // 基础接口地址 + baseURL: configs.baseUrl, + headers: { 'Content-Type': ContentTypeEnum.JSON, version: configs.version }, + + // 处理 axios的钩子函数 + axiosHooks: axiosHooks, + // 每个接口可以单独配置 + requestOptions: { + // 是否将params视为data参数,仅限post请求 + isParamsToData: true, + //是否返回默认的响应 + isReturnDefaultResponse: false, + // 需要对返回数据进行处理 + isTransformResponse: true, + // 接口拼接地址 + urlPrefix: configs.urlPrefix, + // 忽略重复请求 + ignoreCancelToken: false, + // 是否携带token + withToken: true, + // 开启请求超时重新发起请求请求机制 + isOpenRetry: true, + // 重新请求次数 + retryCount: 2 + } +} + +function createAxios(opt?: Partial) { + return new Axios( + // 深度合并 + merge(defaultOptions, opt || {}) + ) +} +const request = createAxios() +export default request diff --git a/admin/src/utils/request/type.d.ts b/admin/src/utils/request/type.d.ts new file mode 100644 index 0000000..a7f364d --- /dev/null +++ b/admin/src/utils/request/type.d.ts @@ -0,0 +1,38 @@ +import type { AxiosRequestConfig, AxiosResponse } from 'axios' + +import 'axios' +declare module 'axios' { + // 扩展 RouteMeta + interface AxiosRequestConfig { + retryCount?: number + axiosHooks?: AxiosHooks + requestOptions: RequestOptions + } +} + +export interface RequestOptions { + isParamsToData: boolean + isReturnDefaultResponse: boolean + isTransformResponse: boolean + urlPrefix: string + ignoreCancelToken: boolean + withToken: boolean + isOpenRetry: boolean + retryCount: number +} + +export interface AxiosHooks { + requestInterceptorsHook?: (config: AxiosRequestConfig) => AxiosRequestConfig + requestInterceptorsCatchHook?: (error: Error) => void + responseInterceptorsHook?: ( + response: AxiosResponse> + ) => AxiosResponse | RequestData | T + responseInterceptorsCatchHook?: (error: AxiosError) => void +} + +export interface RequestData { + code: number + data: T + msg: string + show: boolean +} diff --git a/admin/src/utils/theme.ts b/admin/src/utils/theme.ts new file mode 100644 index 0000000..d11fe18 --- /dev/null +++ b/admin/src/utils/theme.ts @@ -0,0 +1,74 @@ +import colors from 'css-color-function' +const lightConfig = { + 'dark-2': 'shade(20%)', + 'light-3': 'tint(30%)', + 'light-5': 'tint(50%)', + 'light-7': 'tint(70%)', + 'light-8': 'tint(80%)', + 'light-9': 'tint(90%)' +} + +const darkConfig = { + 'light-3': 'shade(20%)', + 'light-5': 'shade(30%)', + 'light-7': 'shade(50%)', + 'light-8': 'shade(60%)', + 'light-9': 'shade(70%)', + 'dark-2': 'tint(20%)' +} + +const themeId = 'theme-vars' + +/** + * @author Jason + * @description 用于生成elementui主题的行为变量 + * 可选值有primary、success、warning、danger、error、info + */ + +export const generateVars = (color: string, type = 'primary', isDark = false) => { + const colos = { + [`--el-color-${type}`]: color + } + const config: Record = isDark ? darkConfig : lightConfig + for (const key in config) { + colos[`--el-color-${type}-${key}`] = `color(${color} ${config[key]})` + } + return colos +} + +/** + * @author Jason + * @description 用于设置css变量 + * @param key css变量key 如 --color-primary + * @param value css变量值 如 #f40 + * @param dom dom元素 + */ +export const setCssVar = (key: string, value: string, dom = document.documentElement) => { + dom.style.setProperty(key, value) +} + +/** + * @author Jason + * @description 设置主题 + */ +export const setTheme = (options: Record, isDark = false) => { + const varsMap: Record = Object.keys(options).reduce((prev, key) => { + return Object.assign(prev, generateVars(options[key], key, isDark)) + }, {}) + + let theme = Object.keys(varsMap).reduce((prev, key) => { + const color = colors.convert(varsMap[key]) + return `${prev}${key}:${color};` + }, '') + theme = `:root{${theme}}` + let style = document.getElementById(themeId) + if (style) { + style.innerHTML = theme + return + } + style = document.createElement('style') + style.setAttribute('type', 'text/css') + style.setAttribute('id', themeId) + style.innerHTML = theme + document.head.append(style) +} diff --git a/admin/src/utils/util.ts b/admin/src/utils/util.ts new file mode 100644 index 0000000..1f54b8c --- /dev/null +++ b/admin/src/utils/util.ts @@ -0,0 +1,171 @@ +import { isObject } from '@vue/shared' +import { cloneDeep } from 'lodash' + +/** + * @description 添加单位 + * @param {String | Number} value 值 100 + * @param {String} unit 单位 px em rem + */ +export const addUnit = (value: string | number, unit = 'px') => { + return !Object.is(Number(value), NaN) ? `${value}${unit}` : value +} + +/** + * @description 添加单位 + * @param {unknown} value + * @return {Boolean} + */ +export const isEmpty = (value: unknown) => { + return value == null && typeof value == 'undefined' +} + +/** + * @description 树转数组,队列实现广度优先遍历 + * @param {Array} data 数据 + * @param {Object} props `{ children: 'children' }` + */ + +export const treeToArray = (data: any[], props = { children: 'children' }) => { + data = cloneDeep(data) + const { children } = props + const newData = [] + const queue: any[] = [] + data.forEach((child: any) => queue.push(child)) + while (queue.length) { + const item: any = queue.shift() + if (item[children]) { + item[children].forEach((child: any) => queue.push(child)) + delete item[children] + } + newData.push(item) + } + return newData +} + +/** + * @description 数组转 + * @param {Array} data 数据 + * @param {Object} props `{ parent: 'pid', children: 'children' }` + */ + +export const arrayToTree = ( + data: any[], + props = { id: 'id', parentId: 'pid', children: 'children' } +) => { + data = cloneDeep(data) + const { id, parentId, children } = props + const result: any[] = [] + const map = new Map() + data.forEach((item) => { + map.set(item[id], item) + const parent = map.get(item[parentId]) + if (parent) { + parent[children] = parent[children] ?? [] + parent[children].push(item) + } else { + result.push(item) + } + }) + return result +} + +/** + * @description 获取正确的路经 + * @param {String} path 数据 + */ +export function getNormalPath(path: string) { + if (path.length === 0 || !path || path == 'undefined') { + return path + } + const newPath = path.replace('//', '/') + const length = newPath.length + if (newPath[length - 1] === '/') { + return newPath.slice(0, length - 1) + } + return newPath +} + +/** + * @description对象格式化为Query语法 + * @param { Object } params + * @return {string} Query语法 + */ +export function objectToQuery(params: Record): string { + let query = '' + for (const props of Object.keys(params)) { + const value = params[props] + const part = encodeURIComponent(props) + '=' + if (!isEmpty(value)) { + if (isObject(value)) { + for (const key of Object.keys(value)) { + if (!isEmpty(value[key])) { + const params = props + '[' + key + ']' + const subPart = encodeURIComponent(params) + '=' + query += subPart + encodeURIComponent(value[key]) + '&' + } + } + } else { + query += part + encodeURIComponent(value) + '&' + } + } + } + return query.slice(0, -1) +} + +/** + * @description 时间格式化 + * @param dateTime { number } 时间戳 + * @param fmt { string } 时间格式 + * @return { string } + */ +// yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 +export const timeFormat = (dateTime: number, fmt = 'yyyy-mm-dd') => { + // 如果为null,则格式化当前时间 + if (!dateTime) { + dateTime = Number(new Date()) + } + // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式 + if (dateTime.toString().length == 10) { + dateTime *= 1000 + } + const date = new Date(dateTime) + let ret + const opt: any = { + 'y+': date.getFullYear().toString(), // 年 + 'm+': (date.getMonth() + 1).toString(), // 月 + 'd+': date.getDate().toString(), // 日 + 'h+': date.getHours().toString(), // 时 + 'M+': date.getMinutes().toString(), // 分 + 's+': date.getSeconds().toString() // 秒 + } + for (const k in opt) { + ret = new RegExp('(' + k + ')').exec(fmt) + if (ret) { + fmt = fmt.replace( + ret[1], + ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0') + ) + } + } + return fmt +} + +/** + * @description 获取不重复的id + * @param length { Number } id的长度 + * @return { String } id + */ +export const getNonDuplicateID = (length = 8) => { + let idStr = Date.now().toString(36) + idStr += Math.random().toString(36).substring(3, length) + return idStr +} + +/** + * @description 单词首字母大写 + * @param { String } str + * @return { String } id + */ +export const firstToUpperCase = (str = '') => { + return str.toLowerCase().replace(/( |^)[a-z]/g, ($1) => $1.toUpperCase()) +} diff --git a/admin/src/utils/validate.ts b/admin/src/utils/validate.ts new file mode 100644 index 0000000..0f8d8f1 --- /dev/null +++ b/admin/src/utils/validate.ts @@ -0,0 +1,7 @@ +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path: string) { + return /^(https?:|mailto:|tel:)/.test(path) +} diff --git a/admin/src/views/account/images/login_bg.png b/admin/src/views/account/images/login_bg.png new file mode 100644 index 0000000..787837a Binary files /dev/null and b/admin/src/views/account/images/login_bg.png differ diff --git a/admin/src/views/account/login.vue b/admin/src/views/account/login.vue new file mode 100644 index 0000000..8f813be --- /dev/null +++ b/admin/src/views/account/login.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/admin/src/views/article/column/edit.vue b/admin/src/views/article/column/edit.vue new file mode 100644 index 0000000..54ddf38 --- /dev/null +++ b/admin/src/views/article/column/edit.vue @@ -0,0 +1,95 @@ + + diff --git a/admin/src/views/article/column/index.vue b/admin/src/views/article/column/index.vue new file mode 100644 index 0000000..fd14f6b --- /dev/null +++ b/admin/src/views/article/column/index.vue @@ -0,0 +1,114 @@ + + diff --git a/admin/src/views/article/lists/edit.vue b/admin/src/views/article/lists/edit.vue new file mode 100644 index 0000000..fe338b9 --- /dev/null +++ b/admin/src/views/article/lists/edit.vue @@ -0,0 +1,175 @@ + + + diff --git a/admin/src/views/article/lists/index.vue b/admin/src/views/article/lists/index.vue new file mode 100644 index 0000000..5670f27 --- /dev/null +++ b/admin/src/views/article/lists/index.vue @@ -0,0 +1,168 @@ + + diff --git a/admin/src/views/article_collect/edit.vue b/admin/src/views/article_collect/edit.vue new file mode 100644 index 0000000..9a01115 --- /dev/null +++ b/admin/src/views/article_collect/edit.vue @@ -0,0 +1,116 @@ + + diff --git a/admin/src/views/article_collect/index.vue b/admin/src/views/article_collect/index.vue new file mode 100644 index 0000000..04a5b3e --- /dev/null +++ b/admin/src/views/article_collect/index.vue @@ -0,0 +1,108 @@ + + diff --git a/admin/src/views/channel/h5.vue b/admin/src/views/channel/h5.vue new file mode 100644 index 0000000..c884fea --- /dev/null +++ b/admin/src/views/channel/h5.vue @@ -0,0 +1,61 @@ + + diff --git a/admin/src/views/channel/weapp.vue b/admin/src/views/channel/weapp.vue new file mode 100644 index 0000000..cabfad8 --- /dev/null +++ b/admin/src/views/channel/weapp.vue @@ -0,0 +1,177 @@ + + diff --git a/admin/src/views/channel/wx_dev.vue b/admin/src/views/channel/wx_dev.vue new file mode 100644 index 0000000..d7c31e6 --- /dev/null +++ b/admin/src/views/channel/wx_dev.vue @@ -0,0 +1,62 @@ + + diff --git a/admin/src/views/channel/wx_oa/config.vue b/admin/src/views/channel/wx_oa/config.vue new file mode 100644 index 0000000..3547838 --- /dev/null +++ b/admin/src/views/channel/wx_oa/config.vue @@ -0,0 +1,195 @@ + + diff --git a/admin/src/views/channel/wx_oa/menu.vue b/admin/src/views/channel/wx_oa/menu.vue new file mode 100644 index 0000000..1a0e184 --- /dev/null +++ b/admin/src/views/channel/wx_oa/menu.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/admin/src/views/channel/wx_oa/menu_com/oa-attr.vue b/admin/src/views/channel/wx_oa/menu_com/oa-attr.vue new file mode 100644 index 0000000..663fd85 --- /dev/null +++ b/admin/src/views/channel/wx_oa/menu_com/oa-attr.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/admin/src/views/channel/wx_oa/menu_com/oa-menu-form-edit.vue b/admin/src/views/channel/wx_oa/menu_com/oa-menu-form-edit.vue new file mode 100644 index 0000000..f696f7c --- /dev/null +++ b/admin/src/views/channel/wx_oa/menu_com/oa-menu-form-edit.vue @@ -0,0 +1,73 @@ + + + diff --git a/admin/src/views/channel/wx_oa/menu_com/oa-menu-form.vue b/admin/src/views/channel/wx_oa/menu_com/oa-menu-form.vue new file mode 100644 index 0000000..a1729f5 --- /dev/null +++ b/admin/src/views/channel/wx_oa/menu_com/oa-menu-form.vue @@ -0,0 +1,107 @@ + + + diff --git a/admin/src/views/channel/wx_oa/menu_com/oa-phone.vue b/admin/src/views/channel/wx_oa/menu_com/oa-phone.vue new file mode 100644 index 0000000..da27572 --- /dev/null +++ b/admin/src/views/channel/wx_oa/menu_com/oa-phone.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/admin/src/views/channel/wx_oa/menu_com/useMenuOa.ts b/admin/src/views/channel/wx_oa/menu_com/useMenuOa.ts new file mode 100644 index 0000000..f86ba91 --- /dev/null +++ b/admin/src/views/channel/wx_oa/menu_com/useMenuOa.ts @@ -0,0 +1,164 @@ +import { ref } from 'vue' +import feedback from '@/utils/feedback' +import type { FormRules } from 'element-plus' +import { setOaMenuSave, getOaMenu, setOaMenuPublish } from '@/api/channel/wx_oa' +import type { Menu } from '@/api/channel/wx_oa' + +// 菜单实例 +export const menuRef = shallowRef() +// 菜单数据 +const menuList = ref([]) +const menuIndex = ref(0) + +// 校验 +export const rules = reactive({ + name: [ + { + required: true, + message: '必填项不能为空', + trigger: ['blur', 'change'] + }, + { + min: 1, + max: 12, + message: '长度限制12个字符', + trigger: ['blur', 'change'] + } + ], + menuType: [ + { + required: true, + message: '必填项不能为空', + trigger: ['blur', 'change'] + } + ], + visitType: [ + { + required: true, + message: '必填项不能为空', + trigger: ['blur', 'change'] + } + ], + url: [ + { + required: true, + message: '必填项不能为空', + trigger: ['blur', 'change'] + }, + { + pattern: + /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~\/])+$/, + message: '请输入合法的网址链接', + trigger: ['blur', 'change'] + } + ], + appId: [ + { + required: true, + message: '必填项不能为空', + trigger: ['blur', 'change'] + } + ], + pagePath: [ + { + required: true, + message: '必填项不能为空', + trigger: ['blur', 'change'] + } + ] +}) + +export const useMenuOa = (ref: any) => { + if (ref) menuRef.value = ref + + // 添加主菜单 + const handleAddMenu = () => { + menuList.value.push({ + name: '菜单名称', + menuType: 1, + visitType: 'view', + url: '', + appId: '', + pagePath: '', + subButtons: [] + }) + } + + // 添加子菜单 + const handleAddSubMenu = (event?: Menu) => { + const index = menuIndex.value + if (menuList.value[index].subButtons.length >= 5) { + feedback.msgError('已添加上限~') + return + } + menuList.value[index].subButtons.push(event) + } + + // 编辑子菜单 + const handleEditSubMenu = (event: Menu, subIndex: number) => { + const index = menuIndex.value + menuList.value[index].subButtons[subIndex] = event + } + + // 删除主菜单 + const handleDelMenu = (index: number) => { + menuList.value.splice(index, 1) + } + + // 删除子菜单 + const handleDelSubMenu = (index: number, subIndex: number) => { + menuList.value[index].subButtons.splice(subIndex, 1) + } + + // 获取菜单 + const getOaMenuFunc = async () => { + try { + menuList.value = await getOaMenu() + } catch (error) { + console.log('获取菜单=>', error) + } + } + + // 保存菜单 + const handleSave = async () => { + const refs = menuRef.value.value + for (let i = 0; i < refs.length; i++) { + try { + await refs[i].menuFormRef.validate() + } catch (error) { + menuIndex.value = i + return + } + } + await setOaMenuSave(menuList.value) + feedback.msgSuccess('保存成功') + } + + // 保存菜单 + const handlePublish = async () => { + const refs = menuRef.value.value + for (let i = 0; i < refs.length; i++) { + try { + await refs[i].menuFormRef.validate() + } catch (error) { + menuIndex.value = i + return + } + } + await setOaMenuPublish(menuList.value) + feedback.msgSuccess('发布成功') + } + + return { + menuList, + menuIndex, + handleAddMenu, + handleAddSubMenu, + handleEditSubMenu, + handleDelMenu, + handleDelSubMenu, + getOaMenuFunc, + handleSave, + handlePublish + } +} diff --git a/admin/src/views/channel/wx_oa/reply/default_reply.vue b/admin/src/views/channel/wx_oa/reply/default_reply.vue new file mode 100644 index 0000000..f8e765e --- /dev/null +++ b/admin/src/views/channel/wx_oa/reply/default_reply.vue @@ -0,0 +1,128 @@ + + diff --git a/admin/src/views/channel/wx_oa/reply/edit.vue b/admin/src/views/channel/wx_oa/reply/edit.vue new file mode 100644 index 0000000..6241b89 --- /dev/null +++ b/admin/src/views/channel/wx_oa/reply/edit.vue @@ -0,0 +1,189 @@ + + diff --git a/admin/src/views/channel/wx_oa/reply/follow_reply.vue b/admin/src/views/channel/wx_oa/reply/follow_reply.vue new file mode 100644 index 0000000..96949ee --- /dev/null +++ b/admin/src/views/channel/wx_oa/reply/follow_reply.vue @@ -0,0 +1,129 @@ + + diff --git a/admin/src/views/channel/wx_oa/reply/keyword_reply.vue b/admin/src/views/channel/wx_oa/reply/keyword_reply.vue new file mode 100644 index 0000000..b9f7c28 --- /dev/null +++ b/admin/src/views/channel/wx_oa/reply/keyword_reply.vue @@ -0,0 +1,145 @@ + + diff --git a/admin/src/views/consumer/lists/detail.vue b/admin/src/views/consumer/lists/detail.vue new file mode 100644 index 0000000..a92becd --- /dev/null +++ b/admin/src/views/consumer/lists/detail.vue @@ -0,0 +1,134 @@ + + + diff --git a/admin/src/views/consumer/lists/index.vue b/admin/src/views/consumer/lists/index.vue new file mode 100644 index 0000000..0d5b07f --- /dev/null +++ b/admin/src/views/consumer/lists/index.vue @@ -0,0 +1,94 @@ + + diff --git a/admin/src/views/decoration/component/add-nav.vue b/admin/src/views/decoration/component/add-nav.vue new file mode 100644 index 0000000..b01d037 --- /dev/null +++ b/admin/src/views/decoration/component/add-nav.vue @@ -0,0 +1,79 @@ + + + + diff --git a/admin/src/views/decoration/component/decoration-img.vue b/admin/src/views/decoration/component/decoration-img.vue new file mode 100644 index 0000000..03a0106 --- /dev/null +++ b/admin/src/views/decoration/component/decoration-img.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/admin/src/views/decoration/component/pages/attr-setting.vue b/admin/src/views/decoration/component/pages/attr-setting.vue new file mode 100644 index 0000000..d4c90f3 --- /dev/null +++ b/admin/src/views/decoration/component/pages/attr-setting.vue @@ -0,0 +1,28 @@ + + diff --git a/admin/src/views/decoration/component/pages/menu.vue b/admin/src/views/decoration/component/pages/menu.vue new file mode 100644 index 0000000..1027e41 --- /dev/null +++ b/admin/src/views/decoration/component/pages/menu.vue @@ -0,0 +1,44 @@ + + + + diff --git a/admin/src/views/decoration/component/pages/preview.vue b/admin/src/views/decoration/component/pages/preview.vue new file mode 100644 index 0000000..34a4f61 --- /dev/null +++ b/admin/src/views/decoration/component/pages/preview.vue @@ -0,0 +1,67 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/banner/attr.vue b/admin/src/views/decoration/component/widgets/banner/attr.vue new file mode 100644 index 0000000..3d5beb9 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/banner/attr.vue @@ -0,0 +1,79 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/banner/content.vue b/admin/src/views/decoration/component/widgets/banner/content.vue new file mode 100644 index 0000000..7633243 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/banner/content.vue @@ -0,0 +1,33 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/banner/index.ts b/admin/src/views/decoration/component/widgets/banner/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/banner/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/banner/options.ts b/admin/src/views/decoration/component/widgets/banner/options.ts new file mode 100644 index 0000000..e33d921 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/banner/options.ts @@ -0,0 +1,15 @@ +export default () => ({ + title: '首页轮播图', + name: 'banner', + content: { + enabled: 1, + data: [ + { + image: '', + name: '', + link: {} + } + ] + }, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/customer-service/attr.vue b/admin/src/views/decoration/component/widgets/customer-service/attr.vue new file mode 100644 index 0000000..7dd0f1a --- /dev/null +++ b/admin/src/views/decoration/component/widgets/customer-service/attr.vue @@ -0,0 +1,38 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/customer-service/content.vue b/admin/src/views/decoration/component/widgets/customer-service/content.vue new file mode 100644 index 0000000..99d4f7e --- /dev/null +++ b/admin/src/views/decoration/component/widgets/customer-service/content.vue @@ -0,0 +1,39 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/customer-service/index.ts b/admin/src/views/decoration/component/widgets/customer-service/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/customer-service/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/customer-service/options.ts b/admin/src/views/decoration/component/widgets/customer-service/options.ts new file mode 100644 index 0000000..3a5efda --- /dev/null +++ b/admin/src/views/decoration/component/widgets/customer-service/options.ts @@ -0,0 +1,11 @@ +export default () => ({ + title: '客服设置', + name: 'customer-service', + content: { + title: '添加客服二维码', + time: '', + mobile: '', + qrcode: '' + }, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/index.ts b/admin/src/views/decoration/component/widgets/index.ts new file mode 100644 index 0000000..20bf4bf --- /dev/null +++ b/admin/src/views/decoration/component/widgets/index.ts @@ -0,0 +1,14 @@ +const widgets: Record = import.meta.glob('./**/index.ts', { eager: true }) +interface Widget { + attr: any + content: any + options: any +} +console.log(widgets) +const exportWidgets: Record = {} +Object.keys(widgets).forEach((key) => { + const widgetName = key.replace(/^\.\/([\w-]+).*/gi, '$1') + exportWidgets[widgetName] = widgets[key]?.default +}) + +export default exportWidgets diff --git a/admin/src/views/decoration/component/widgets/my-service/attr.vue b/admin/src/views/decoration/component/widgets/my-service/attr.vue new file mode 100644 index 0000000..a29d894 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/my-service/attr.vue @@ -0,0 +1,38 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/my-service/content.vue b/admin/src/views/decoration/component/widgets/my-service/content.vue new file mode 100644 index 0000000..9034090 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/my-service/content.vue @@ -0,0 +1,59 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/my-service/index.ts b/admin/src/views/decoration/component/widgets/my-service/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/my-service/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/my-service/options.ts b/admin/src/views/decoration/component/widgets/my-service/options.ts new file mode 100644 index 0000000..f54952b --- /dev/null +++ b/admin/src/views/decoration/component/widgets/my-service/options.ts @@ -0,0 +1,16 @@ +export default () => ({ + title: '我的服务', + name: 'my-service', + content: { + style: 1, + title: '我的服务', + data: [ + { + image: '', + name: '导航名称', + link: {} + } + ] + }, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/nav/attr.vue b/admin/src/views/decoration/component/widgets/nav/attr.vue new file mode 100644 index 0000000..3128196 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/nav/attr.vue @@ -0,0 +1,36 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/nav/content.vue b/admin/src/views/decoration/component/widgets/nav/content.vue new file mode 100644 index 0000000..39ca477 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/nav/content.vue @@ -0,0 +1,32 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/nav/index.ts b/admin/src/views/decoration/component/widgets/nav/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/nav/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/nav/options.ts b/admin/src/views/decoration/component/widgets/nav/options.ts new file mode 100644 index 0000000..1eb3cfa --- /dev/null +++ b/admin/src/views/decoration/component/widgets/nav/options.ts @@ -0,0 +1,15 @@ +export default () => ({ + title: '导航菜单', + name: 'nav', + content: { + enabled: 1, + data: [ + { + image: '', + name: '导航名称', + link: {} + } + ] + }, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/news/attr.vue b/admin/src/views/decoration/component/widgets/news/attr.vue new file mode 100644 index 0000000..6645dc4 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/news/attr.vue @@ -0,0 +1,20 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/news/content.vue b/admin/src/views/decoration/component/widgets/news/content.vue new file mode 100644 index 0000000..197f917 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/news/content.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/admin/src/views/decoration/component/widgets/news/index.ts b/admin/src/views/decoration/component/widgets/news/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/news/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/news/options.ts b/admin/src/views/decoration/component/widgets/news/options.ts new file mode 100644 index 0000000..dde2c1f --- /dev/null +++ b/admin/src/views/decoration/component/widgets/news/options.ts @@ -0,0 +1,7 @@ +export default () => ({ + title: '资讯', + name: 'news', + disabled: 1, + content: {}, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/search/attr.vue b/admin/src/views/decoration/component/widgets/search/attr.vue new file mode 100644 index 0000000..93f9278 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/search/attr.vue @@ -0,0 +1,20 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/search/content.vue b/admin/src/views/decoration/component/widgets/search/content.vue new file mode 100644 index 0000000..db2a07a --- /dev/null +++ b/admin/src/views/decoration/component/widgets/search/content.vue @@ -0,0 +1,23 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/search/index.ts b/admin/src/views/decoration/component/widgets/search/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/search/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/search/options.ts b/admin/src/views/decoration/component/widgets/search/options.ts new file mode 100644 index 0000000..e02e298 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/search/options.ts @@ -0,0 +1,7 @@ +export default () => ({ + title: '搜索', + name: 'search', + disabled: 1, + content: {}, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/user-banner/attr.vue b/admin/src/views/decoration/component/widgets/user-banner/attr.vue new file mode 100644 index 0000000..73263d7 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-banner/attr.vue @@ -0,0 +1,79 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/user-banner/content.vue b/admin/src/views/decoration/component/widgets/user-banner/content.vue new file mode 100644 index 0000000..0a11a01 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-banner/content.vue @@ -0,0 +1,32 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/user-banner/index.ts b/admin/src/views/decoration/component/widgets/user-banner/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-banner/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/user-banner/options.ts b/admin/src/views/decoration/component/widgets/user-banner/options.ts new file mode 100644 index 0000000..4adc70e --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-banner/options.ts @@ -0,0 +1,15 @@ +export default () => ({ + title: '个人中心广告图', + name: 'user-banner', + content: { + enabled: 1, + data: [ + { + image: '', + name: '', + link: {} + } + ] + }, + styles: {} +}) diff --git a/admin/src/views/decoration/component/widgets/user-info/attr.vue b/admin/src/views/decoration/component/widgets/user-info/attr.vue new file mode 100644 index 0000000..93f9278 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-info/attr.vue @@ -0,0 +1,20 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/user-info/content.vue b/admin/src/views/decoration/component/widgets/user-info/content.vue new file mode 100644 index 0000000..b64e7e7 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-info/content.vue @@ -0,0 +1,16 @@ + + + + diff --git a/admin/src/views/decoration/component/widgets/user-info/images/default_avatar.png b/admin/src/views/decoration/component/widgets/user-info/images/default_avatar.png new file mode 100644 index 0000000..de31d02 Binary files /dev/null and b/admin/src/views/decoration/component/widgets/user-info/images/default_avatar.png differ diff --git a/admin/src/views/decoration/component/widgets/user-info/images/my_topbg.png b/admin/src/views/decoration/component/widgets/user-info/images/my_topbg.png new file mode 100644 index 0000000..8984438 Binary files /dev/null and b/admin/src/views/decoration/component/widgets/user-info/images/my_topbg.png differ diff --git a/admin/src/views/decoration/component/widgets/user-info/index.ts b/admin/src/views/decoration/component/widgets/user-info/index.ts new file mode 100644 index 0000000..c776bce --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-info/index.ts @@ -0,0 +1,8 @@ +import attr from './attr.vue' +import content from './content.vue' +import options from './options' +export default { + attr, + content, + options +} diff --git a/admin/src/views/decoration/component/widgets/user-info/options.ts b/admin/src/views/decoration/component/widgets/user-info/options.ts new file mode 100644 index 0000000..b346829 --- /dev/null +++ b/admin/src/views/decoration/component/widgets/user-info/options.ts @@ -0,0 +1,7 @@ +export default () => ({ + title: '用户信息', + name: 'user-info', + disabled: 1, + content: {}, + styles: {} +}) diff --git a/admin/src/views/decoration/pages/index.vue b/admin/src/views/decoration/pages/index.vue new file mode 100644 index 0000000..3ff7ee4 --- /dev/null +++ b/admin/src/views/decoration/pages/index.vue @@ -0,0 +1,105 @@ + + + diff --git a/admin/src/views/decoration/tabbar.vue b/admin/src/views/decoration/tabbar.vue new file mode 100644 index 0000000..c139695 --- /dev/null +++ b/admin/src/views/decoration/tabbar.vue @@ -0,0 +1,221 @@ + + + diff --git a/admin/src/views/dev_tools/code/edit.vue b/admin/src/views/dev_tools/code/edit.vue new file mode 100644 index 0000000..4d4c5a9 --- /dev/null +++ b/admin/src/views/dev_tools/code/edit.vue @@ -0,0 +1,388 @@ + + + diff --git a/admin/src/views/dev_tools/code/index.vue b/admin/src/views/dev_tools/code/index.vue new file mode 100644 index 0000000..871de38 --- /dev/null +++ b/admin/src/views/dev_tools/code/index.vue @@ -0,0 +1,246 @@ + + + diff --git a/admin/src/views/dev_tools/components/code-preview.vue b/admin/src/views/dev_tools/components/code-preview.vue new file mode 100644 index 0000000..9c1aea4 --- /dev/null +++ b/admin/src/views/dev_tools/components/code-preview.vue @@ -0,0 +1,73 @@ + + + diff --git a/admin/src/views/dev_tools/components/data-table.vue b/admin/src/views/dev_tools/components/data-table.vue new file mode 100644 index 0000000..e4d2d49 --- /dev/null +++ b/admin/src/views/dev_tools/components/data-table.vue @@ -0,0 +1,102 @@ + + + diff --git a/admin/src/views/error/403.vue b/admin/src/views/error/403.vue new file mode 100644 index 0000000..53905ec --- /dev/null +++ b/admin/src/views/error/403.vue @@ -0,0 +1,15 @@ + + + diff --git a/admin/src/views/error/404.vue b/admin/src/views/error/404.vue new file mode 100644 index 0000000..caa3d7f --- /dev/null +++ b/admin/src/views/error/404.vue @@ -0,0 +1,9 @@ + + + diff --git a/admin/src/views/error/components/error.vue b/admin/src/views/error/components/error.vue new file mode 100644 index 0000000..5f0b959 --- /dev/null +++ b/admin/src/views/error/components/error.vue @@ -0,0 +1,57 @@ + + + + diff --git a/admin/src/views/material/index.vue b/admin/src/views/material/index.vue new file mode 100644 index 0000000..6e6dafc --- /dev/null +++ b/admin/src/views/material/index.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/admin/src/views/message/notice/edit.vue b/admin/src/views/message/notice/edit.vue new file mode 100644 index 0000000..fee2422 --- /dev/null +++ b/admin/src/views/message/notice/edit.vue @@ -0,0 +1,122 @@ + + + diff --git a/admin/src/views/message/notice/index.vue b/admin/src/views/message/notice/index.vue new file mode 100644 index 0000000..672ba03 --- /dev/null +++ b/admin/src/views/message/notice/index.vue @@ -0,0 +1,95 @@ + + diff --git a/admin/src/views/message/short_letter/edit.vue b/admin/src/views/message/short_letter/edit.vue new file mode 100644 index 0000000..504de9b --- /dev/null +++ b/admin/src/views/message/short_letter/edit.vue @@ -0,0 +1,130 @@ + + diff --git a/admin/src/views/message/short_letter/index.vue b/admin/src/views/message/short_letter/index.vue new file mode 100644 index 0000000..0272670 --- /dev/null +++ b/admin/src/views/message/short_letter/index.vue @@ -0,0 +1,56 @@ + + diff --git a/admin/src/views/organization/department/edit.vue b/admin/src/views/organization/department/edit.vue new file mode 100644 index 0000000..07e3f5f --- /dev/null +++ b/admin/src/views/organization/department/edit.vue @@ -0,0 +1,182 @@ + + diff --git a/admin/src/views/organization/department/index.vue b/admin/src/views/organization/department/index.vue new file mode 100644 index 0000000..720491d --- /dev/null +++ b/admin/src/views/organization/department/index.vue @@ -0,0 +1,166 @@ + + diff --git a/admin/src/views/organization/post/edit.vue b/admin/src/views/organization/post/edit.vue new file mode 100644 index 0000000..1c582e0 --- /dev/null +++ b/admin/src/views/organization/post/edit.vue @@ -0,0 +1,127 @@ + + diff --git a/admin/src/views/organization/post/index.vue b/admin/src/views/organization/post/index.vue new file mode 100644 index 0000000..45f9058 --- /dev/null +++ b/admin/src/views/organization/post/index.vue @@ -0,0 +1,128 @@ + + diff --git a/admin/src/views/permission/admin/edit.vue b/admin/src/views/permission/admin/edit.vue new file mode 100644 index 0000000..41a60d2 --- /dev/null +++ b/admin/src/views/permission/admin/edit.vue @@ -0,0 +1,289 @@ + + diff --git a/admin/src/views/permission/admin/index.vue b/admin/src/views/permission/admin/index.vue new file mode 100644 index 0000000..0b88014 --- /dev/null +++ b/admin/src/views/permission/admin/index.vue @@ -0,0 +1,181 @@ + + + diff --git a/admin/src/views/permission/menu/edit.vue b/admin/src/views/permission/menu/edit.vue new file mode 100644 index 0000000..4686a10 --- /dev/null +++ b/admin/src/views/permission/menu/edit.vue @@ -0,0 +1,315 @@ + + diff --git a/admin/src/views/permission/menu/index.vue b/admin/src/views/permission/menu/index.vue new file mode 100644 index 0000000..d4ec4ff --- /dev/null +++ b/admin/src/views/permission/menu/index.vue @@ -0,0 +1,157 @@ + + diff --git a/admin/src/views/permission/role/auth.vue b/admin/src/views/permission/role/auth.vue new file mode 100644 index 0000000..3e16834 --- /dev/null +++ b/admin/src/views/permission/role/auth.vue @@ -0,0 +1,154 @@ + + diff --git a/admin/src/views/permission/role/edit.vue b/admin/src/views/permission/role/edit.vue new file mode 100644 index 0000000..ea95aab --- /dev/null +++ b/admin/src/views/permission/role/edit.vue @@ -0,0 +1,114 @@ + + diff --git a/admin/src/views/permission/role/index.vue b/admin/src/views/permission/role/index.vue new file mode 100644 index 0000000..e4b517b --- /dev/null +++ b/admin/src/views/permission/role/index.vue @@ -0,0 +1,108 @@ + + + diff --git a/admin/src/views/setting/dict/data/edit.vue b/admin/src/views/setting/dict/data/edit.vue new file mode 100644 index 0000000..8620e7e --- /dev/null +++ b/admin/src/views/setting/dict/data/edit.vue @@ -0,0 +1,129 @@ + + diff --git a/admin/src/views/setting/dict/data/index.vue b/admin/src/views/setting/dict/data/index.vue new file mode 100644 index 0000000..bf20d7c --- /dev/null +++ b/admin/src/views/setting/dict/data/index.vue @@ -0,0 +1,144 @@ + + + diff --git a/admin/src/views/setting/dict/type/edit.vue b/admin/src/views/setting/dict/type/edit.vue new file mode 100644 index 0000000..28c3408 --- /dev/null +++ b/admin/src/views/setting/dict/type/edit.vue @@ -0,0 +1,111 @@ + + diff --git a/admin/src/views/setting/dict/type/index.vue b/admin/src/views/setting/dict/type/index.vue new file mode 100644 index 0000000..1719726 --- /dev/null +++ b/admin/src/views/setting/dict/type/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/admin/src/views/setting/search/index.vue b/admin/src/views/setting/search/index.vue new file mode 100644 index 0000000..280d29d --- /dev/null +++ b/admin/src/views/setting/search/index.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/admin/src/views/setting/storage/edit.vue b/admin/src/views/setting/storage/edit.vue new file mode 100644 index 0000000..28dae8f --- /dev/null +++ b/admin/src/views/setting/storage/edit.vue @@ -0,0 +1,196 @@ + + diff --git a/admin/src/views/setting/storage/index.vue b/admin/src/views/setting/storage/index.vue new file mode 100644 index 0000000..029d7d4 --- /dev/null +++ b/admin/src/views/setting/storage/index.vue @@ -0,0 +1,53 @@ + + diff --git a/admin/src/views/setting/system/cache.vue b/admin/src/views/setting/system/cache.vue new file mode 100644 index 0000000..1861d21 --- /dev/null +++ b/admin/src/views/setting/system/cache.vue @@ -0,0 +1,261 @@ + + + + + + diff --git a/admin/src/views/setting/system/environment.vue b/admin/src/views/setting/system/environment.vue new file mode 100644 index 0000000..f6e580e --- /dev/null +++ b/admin/src/views/setting/system/environment.vue @@ -0,0 +1,148 @@ + + + + + + diff --git a/admin/src/views/setting/system/journal.vue b/admin/src/views/setting/system/journal.vue new file mode 100644 index 0000000..fadb294 --- /dev/null +++ b/admin/src/views/setting/system/journal.vue @@ -0,0 +1,128 @@ + + + + + + diff --git a/admin/src/views/setting/user/login_register.vue b/admin/src/views/setting/user/login_register.vue new file mode 100644 index 0000000..dd9af38 --- /dev/null +++ b/admin/src/views/setting/user/login_register.vue @@ -0,0 +1,180 @@ + + + + + + diff --git a/admin/src/views/setting/user/setup.vue b/admin/src/views/setting/user/setup.vue new file mode 100644 index 0000000..0c67855 --- /dev/null +++ b/admin/src/views/setting/user/setup.vue @@ -0,0 +1,65 @@ + + + + + + diff --git a/admin/src/views/setting/website/filing.vue b/admin/src/views/setting/website/filing.vue new file mode 100644 index 0000000..fd96332 --- /dev/null +++ b/admin/src/views/setting/website/filing.vue @@ -0,0 +1,93 @@ + + + + diff --git a/admin/src/views/setting/website/information.vue b/admin/src/views/setting/website/information.vue new file mode 100644 index 0000000..616e871 --- /dev/null +++ b/admin/src/views/setting/website/information.vue @@ -0,0 +1,150 @@ + + + + + + diff --git a/admin/src/views/setting/website/protocol.vue b/admin/src/views/setting/website/protocol.vue new file mode 100644 index 0000000..c219451 --- /dev/null +++ b/admin/src/views/setting/website/protocol.vue @@ -0,0 +1,58 @@ + + + diff --git a/admin/src/views/user/setting.vue b/admin/src/views/user/setting.vue new file mode 100644 index 0000000..46c55f4 --- /dev/null +++ b/admin/src/views/user/setting.vue @@ -0,0 +1,162 @@ + + + + + + diff --git a/admin/src/views/workbench/image/customer_service.png b/admin/src/views/workbench/image/customer_service.png new file mode 100644 index 0000000..20a0b89 Binary files /dev/null and b/admin/src/views/workbench/image/customer_service.png differ diff --git a/admin/src/views/workbench/image/menu_admin.png b/admin/src/views/workbench/image/menu_admin.png new file mode 100644 index 0000000..8cbb023 Binary files /dev/null and b/admin/src/views/workbench/image/menu_admin.png differ diff --git a/admin/src/views/workbench/image/menu_auth.png b/admin/src/views/workbench/image/menu_auth.png new file mode 100644 index 0000000..6dfe1f4 Binary files /dev/null and b/admin/src/views/workbench/image/menu_auth.png differ diff --git a/admin/src/views/workbench/image/menu_dept.png b/admin/src/views/workbench/image/menu_dept.png new file mode 100644 index 0000000..0d198e4 Binary files /dev/null and b/admin/src/views/workbench/image/menu_dept.png differ diff --git a/admin/src/views/workbench/image/menu_dict.png b/admin/src/views/workbench/image/menu_dict.png new file mode 100644 index 0000000..30ac7be Binary files /dev/null and b/admin/src/views/workbench/image/menu_dict.png differ diff --git a/admin/src/views/workbench/image/menu_file.png b/admin/src/views/workbench/image/menu_file.png new file mode 100644 index 0000000..8107b90 Binary files /dev/null and b/admin/src/views/workbench/image/menu_file.png differ diff --git a/admin/src/views/workbench/image/menu_generator.png b/admin/src/views/workbench/image/menu_generator.png new file mode 100644 index 0000000..eb023ad Binary files /dev/null and b/admin/src/views/workbench/image/menu_generator.png differ diff --git a/admin/src/views/workbench/image/menu_role.png b/admin/src/views/workbench/image/menu_role.png new file mode 100644 index 0000000..ba011d1 Binary files /dev/null and b/admin/src/views/workbench/image/menu_role.png differ diff --git a/admin/src/views/workbench/image/menu_web.png b/admin/src/views/workbench/image/menu_web.png new file mode 100644 index 0000000..2e3d5e8 Binary files /dev/null and b/admin/src/views/workbench/image/menu_web.png differ diff --git a/admin/src/views/workbench/image/qq_group.png b/admin/src/views/workbench/image/qq_group.png new file mode 100644 index 0000000..795142d Binary files /dev/null and b/admin/src/views/workbench/image/qq_group.png differ diff --git a/admin/src/views/workbench/index.vue b/admin/src/views/workbench/index.vue new file mode 100644 index 0000000..d9df3f6 --- /dev/null +++ b/admin/src/views/workbench/index.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/admin/tailwind.config.js b/admin/tailwind.config.js new file mode 100644 index 0000000..bab028a --- /dev/null +++ b/admin/tailwind.config.js @@ -0,0 +1,119 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], + theme: { + colors: { + white: 'var(--color-white)', + primary: { + DEFAULT: 'var(--el-color-primary)', + 'light-3': 'var(--el-color-primary-light-3)', + 'light-5': 'var(--el-color-primary-light-5)', + 'light-7': 'var(--el-color-primary-light-7)', + 'light-8': 'var(--el-color-primary-light-8)', + 'light-9': 'var(--el-color-primary-light-9)', + 'dark-2': 'var(--el-color-primary-dark-2)' + }, + success: 'var(--el-color-success)', + warning: 'var(--el-color-warning)', + danger: 'var(--el-color-danger)', + error: 'var(--el-color-error)', + info: 'var(--el-color-info)', + body: 'var(--el-bg-color)', + page: 'var(--el-bg-color-page)', + 'tx-primary': 'var(--el-text-color-primary)', + 'tx-regular': 'var(--el-text-color-regular)', + 'tx-secondary': 'var(--el-text-color-secondary)', + 'tx-placeholder': 'var(--el-text-color-placeholder)', + 'tx-disabled': 'var(--el-text-color-disabled)', + br: 'var(--el-border-color)', + 'br-light': 'var(--el-border-color-light)', + 'br-extra-light': 'var(--el-border-color-extra-light)', + 'br-dark': 'var( --el-border-color-dark)', + fill: 'var(--el-fill-color)', + 'fill-light': 'var(--el-fill-color-light)', + 'fill-lighter': 'var(--el-fill-color-lighter)', + mask: 'var(--el-mask-color)' + }, + fontFamily: { + sans: ['PingFang SC', 'Arial', 'Hiragino Sans GB', 'Microsoft YaHei', 'sans-serif'] + }, + boxShadow: { + DEFAULT: 'var(--el-box-shadow)', + light: 'var(--el-box-shadow-light)', + lighter: 'var(--el-box-shadow-lighter)', + dark: 'var(--el-box-shadow-dark)' + }, + fontSize: { + xs: 'var(--el-font-size-extra-small)', + sm: 'var( --el-font-size-small)', + base: 'var( --el-font-size-base)', + lg: 'var( --el-font-size-medium)', + xl: 'var( --el-font-size-large)', + '2xl': 'var( --el-font-size-extra-large)', + '3xl': '20px', + '4xl': '24px', + '5xl': '28px', + '6xl': '30px', + '7xl': '36px', + '8xl': '48px', + '9xl': '60px' + }, + spacing: { + px: '1px', + 0: '0px', + 0.5: '2px', + 1: '4px', + 1.5: '6px', + 2: '8px', + 2.5: '10px', + 3: '12px', + 3.5: '14px', + 4: '16px', + 5: '20px', + 6: '24px', + 7: '28px', + 8: '32px', + 9: '36px', + 10: '40px', + 11: '44px', + 12: '48px', + 14: '56px', + 16: '64px', + 20: '80px', + 24: '96px', + 28: '112px', + 32: '128px', + 36: '144px', + 40: '160px', + 44: '176px', + 48: '192px', + 52: '208px', + 56: '224px', + 60: '240px', + 64: '256px', + 72: '288px', + 80: '320px', + 96: '384px' + }, + lineHeight: { + none: '1', + tight: '1.25', + snug: '1.375', + normal: '1.5', + relaxed: '1.625', + loose: '2', + 3: '12px', + 4: '16px', + 5: '20px', + 6: '24px', + 7: '28px', + 8: '32px', + 9: '36px', + 10: '40px' + } + }, + + plugins: [ + // require('@tailwindcss/line-clamp') // 引入插件 + ] +} diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 0000000..5540a4f --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "include": [ + "global.d.ts", + "src/**/*", + "src/**/*.vue", + "components.d.ts", + "auto-imports.d.ts", + "typings/**/*.d.ts" + ], + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "isolatedModules": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/admin/typings/index.d.ts b/admin/typings/index.d.ts new file mode 100644 index 0000000..a95667e --- /dev/null +++ b/admin/typings/index.d.ts @@ -0,0 +1,3 @@ +declare module 'css-color-function' + +type PromiseFun = (...arg: any[]) => Promise diff --git a/admin/typings/router.d.ts b/admin/typings/router.d.ts new file mode 100644 index 0000000..9fae641 --- /dev/null +++ b/admin/typings/router.d.ts @@ -0,0 +1,14 @@ +import 'vue-router' +declare module 'vue-router' { + // 扩展 RouteMeta + interface RouteMeta { + type?: string + perms?: string + title?: string + icon?: string + hidden?: boolean + activeMenu?: string + hideTab?: boolean + keepAlive?: boolean + } +} diff --git a/admin/vite.config.ts b/admin/vite.config.ts new file mode 100644 index 0000000..7b4adbf --- /dev/null +++ b/admin/vite.config.ts @@ -0,0 +1,59 @@ +import { fileURLToPath, URL } from 'url' + +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' + +// https://vitejs.dev/config/ +export default ({ mode }) => { + const env = loadEnv(mode, process.cwd()) + console.log(env) + + return defineConfig({ + // base: '/admin/', + server: { + open: true, + host: '0.0.0.0', + proxy: { + '/api': { + target: env.VITE_APP_BASE_URL, + changeOrigin: true, + ws: true + } + } + }, + plugins: [ + vue(), + vueJsx(), + AutoImport({ + imports: ['vue', 'vue-router'], + resolvers: [ElementPlusResolver()], + eslintrc: { + enabled: true + } + }), + Components({ + directoryAsNamespace: true, + resolvers: [ElementPlusResolver()] + }), + createStyleImportPlugin({ + resolves: [ElementPlusResolve()] + }), + createSvgIconsPlugin({ + // 配置路劲在你的src里的svg存放文件 + iconDirs: [fileURLToPath(new URL('./src/assets/icons', import.meta.url))], + symbolId: 'local-icon-[dir]-[name]' + }) + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } + }) +} diff --git a/docs/1.go-打包.md b/docs/1.go-打包.md new file mode 100644 index 0000000..d65bb60 --- /dev/null +++ b/docs/1.go-打包.md @@ -0,0 +1,13 @@ +# 打包 +## 方式一:直接打包 +```bash +go build -o main.exe . +``` + +## 方式二:goreleaser 同时打包多平台 +```bash +# 安装工具 +go install github.com/goreleaser/goreleaser@latest +# 打包 +goreleaser release --snapshot --clean +``` diff --git a/docs/2.go-运行.md b/docs/2.go-运行.md new file mode 100644 index 0000000..a042544 --- /dev/null +++ b/docs/2.go-运行.md @@ -0,0 +1,34 @@ +# 部署 + +## 上传所有需要的文件 + +- 打包后的二进制文件.exe +- static/* +- .env + + +我是前端工程师,所以我推荐使用pm2管理进程 +https://pm2.io/ +## 安装 +```bash +# 需要node环境 +npm install pm2 -g +# 之前有一种不依赖node直接安装,好像不能用了 +``` +```bash +# 启动 +pm2 start 打包后的二进制文件名 --name like-server +# 开机启动 +pm2 startup +# 保存 +pm2 save + +# 停止 +```bash +pm2 stop like-server +# 重启 +```bash +pm2 restart like-server +# 查看日志 +pm2 log like-server +``` \ No newline at end of file diff --git a/docs/3.go-nginx配置.md b/docs/3.go-nginx配置.md new file mode 100644 index 0000000..0d31d0f --- /dev/null +++ b/docs/3.go-nginx配置.md @@ -0,0 +1,27 @@ + +# nginx配置 +## 反向代理接口 +```nginx +location ^~ /api +{ + proxy_pass http://127.0.0.1:8001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_http_version 1.1; + # proxy_hide_header Upgrade; + + add_header X-Cache $upstream_cache_status; + +} +``` +## 前端页面使用history模式需要重定向 +```nginx +location / { + index /index.html; + try_files $uri $uri/ /index.html; +} +``` \ No newline at end of file diff --git a/docs/assets/genCode.png b/docs/assets/genCode.png new file mode 100644 index 0000000..3e3bbdb Binary files /dev/null and b/docs/assets/genCode.png differ diff --git a/docs/assets/work.png b/docs/assets/work.png new file mode 100644 index 0000000..a8c7475 Binary files /dev/null and b/docs/assets/work.png differ diff --git a/server.code-workspace b/server.code-workspace new file mode 100644 index 0000000..8c6cb20 --- /dev/null +++ b/server.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "server" + }, + { + "path": "admin" + }, + { + "path": "docs" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 0000000..5f5b92d --- /dev/null +++ b/server/.env.example @@ -0,0 +1,15 @@ +# 可配置项参考:config\config.go + +#GIN_MODE='release' +# 项目端口 +SERVER_PORT=8001 +# 域名(不建议有) +# PUBLIC_URL='http://127.0.0.1:8000' +# 数据库 +DATABASE_URL='root:root@tcp(localhost:3306)/x_admin?charset=utf8mb4&parseTime=True&loc=Local' + +#Redis +REDIS_URL='redis://:passwd@localhost:6379' + +# 上传文件目录 +UPLOAD_DIRECTORY='/www/wwwroot/x_admin_go/public/uploads/' diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..51df7f7 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,60 @@ +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +*.prof +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# System file +.DS_Store +.DS_Store? +.AppleDouble +.LSOverride +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +.TemporaryItems +.fseventsd +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# IDE +.idea/ +/go_build_* +out/ +.vscode/* +!.vscode/settings.json +!.vscode/launch.json +*.sublime* +__debug_bin +.project + +# custom +# env +.env +# binary +main +# air +tmp +dist/ diff --git a/server/.goreleaser.yaml b/server/.goreleaser.yaml new file mode 100644 index 0000000..e8c4f23 --- /dev/null +++ b/server/.goreleaser.yaml @@ -0,0 +1,46 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 1 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + # - darwin + +# archives: +# - format: tar.gz +# # this name template makes the OS and Arch compatible with the results of `uname`. +# name_template: >- +# {{ .ProjectName }}_ +# {{- title .Os }}_ +# {{- if eq .Arch "amd64" }}x86_64 +# {{- else if eq .Arch "386" }}i386 +# {{- else }}{{ .Arch }}{{ end }} +# {{- if .Arm }}v{{ .Arm }}{{ end }} +# # use zip for windows archives +# format_overrides: +# - goos: windows +# format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/server/.vscode/launch.json b/server/.vscode/launch.json new file mode 100644 index 0000000..0261c36 --- /dev/null +++ b/server/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "." + } + ] +} \ No newline at end of file diff --git a/server/.vscode/settings.json b/server/.vscode/settings.json new file mode 100644 index 0000000..4a9da6f --- /dev/null +++ b/server/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "cSpell.words": [ + "gocode", + "gonic", + "gorm", + "x_admin", + "mapstructure", + "rmvb" + ] +} \ No newline at end of file diff --git a/server/admin/article_collect/article_collect_ctl.go b/server/admin/article_collect/article_collect_ctl.go new file mode 100644 index 0000000..a511f7c --- /dev/null +++ b/server/admin/article_collect/article_collect_ctl.go @@ -0,0 +1,64 @@ +package article_collect + +import ( + "x_admin/core/request" + "x_admin/core/response" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +type ArticleCollectHandler struct { + Service IArticleCollectService +} + +// list article_collect列表 +func (hd ArticleCollectHandler) List(c *gin.Context) { + var page request.PageReq + var listReq ArticleCollectListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := hd.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail article_collect详情 +func (hd ArticleCollectHandler) Detail(c *gin.Context) { + var detailReq ArticleCollectDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := hd.Service.Detail(detailReq.Id) + response.CheckAndRespWithData(c, res, err) +} + +// add article_collect新增 +func (hd ArticleCollectHandler) Add(c *gin.Context) { + var addReq ArticleCollectAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &addReq)) { + return + } + response.CheckAndResp(c, hd.Service.Add(addReq)) +} + +// edit article_collect编辑 +func (hd ArticleCollectHandler) Edit(c *gin.Context) { + var editReq ArticleCollectEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &editReq)) { + return + } + response.CheckAndResp(c, hd.Service.Edit(editReq)) +} + +// del article_collect删除 +func (hd ArticleCollectHandler) Del(c *gin.Context) { + var delReq ArticleCollectDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &delReq)) { + return + } + response.CheckAndResp(c, hd.Service.Del(delReq.Id)) +} diff --git a/server/admin/article_collect/article_collect_schema.go b/server/admin/article_collect/article_collect_schema.go new file mode 100644 index 0000000..707c40c --- /dev/null +++ b/server/admin/article_collect/article_collect_schema.go @@ -0,0 +1,41 @@ +package article_collect + +import "x_admin/core" + +//ArticleCollectListReq 文章收藏列表参数 +type ArticleCollectListReq struct { + UserId int `form:"userId"` // 用户ID + ArticleId int `form:"articleId"` // 文章ID +} + +//ArticleCollectDetailReq 文章收藏详情参数 +type ArticleCollectDetailReq struct { + Id int `form:"id"` // 主键 +} + +//ArticleCollectAddReq 文章收藏新增参数 +type ArticleCollectAddReq struct { + UserId int `form:"userId"` // 用户ID + ArticleId int `form:"articleId"` // 文章ID +} + +//ArticleCollectEditReq 文章收藏新增参数 +type ArticleCollectEditReq struct { + Id int `form:"id"` // 主键 + UserId int `form:"userId"` // 用户ID + ArticleId int `form:"articleId"` // 文章ID +} + +//ArticleCollectDelReq 文章收藏新增参数 +type ArticleCollectDelReq struct { + Id int `form:"id"` // 主键 +} + +//ArticleCollectResp 文章收藏返回信息 +type ArticleCollectResp struct { + Id int `json:"id" structs:"id"` // 主键 + UserId int `json:"userId" structs:"userId"` // 用户ID + ArticleId int `json:"articleId" structs:"articleId"` // 文章ID + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} diff --git a/server/admin/article_collect/article_collect_service.go b/server/admin/article_collect/article_collect_service.go new file mode 100644 index 0000000..8982db7 --- /dev/null +++ b/server/admin/article_collect/article_collect_service.go @@ -0,0 +1,122 @@ +package article_collect + +import ( + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model" + + "gorm.io/gorm" +) + +type IArticleCollectService interface { + List(page request.PageReq, listReq ArticleCollectListReq) (res response.PageResp, e error) + Detail(id int) (res ArticleCollectResp, e error) + Add(addReq ArticleCollectAddReq) (e error) + Edit(editReq ArticleCollectEditReq) (e error) + Del(id int) (e error) +} + +// NewArticleCollectService 初始化 +func NewArticleCollectService(db *gorm.DB) IArticleCollectService { + return &articleCollectService{db: db} +} + +// articleCollectService 文章收藏服务实现类 +type articleCollectService struct { + db *gorm.DB +} + +// List 文章收藏列表 +func (Service articleCollectService) List(page request.PageReq, listReq ArticleCollectListReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + dbModel := Service.db.Model(&model.ArticleCollect{}) + if listReq.UserId > 0 { + dbModel = dbModel.Where("user_id = ?", listReq.UserId) + } + if listReq.ArticleId > 0 { + dbModel = dbModel.Where("article_id = ?", listReq.ArticleId) + } + dbModel = dbModel.Where("is_delete = ?", 0) + // 总数 + var count int64 + err := dbModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + // 数据 + var objs []model.ArticleCollect + err = dbModel.Limit(limit).Offset(offset).Order("id desc").Find(&objs).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + resps := []ArticleCollectResp{} + response.Copy(&resps, objs) + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: resps, + }, nil +} + +// Detail 文章收藏详情 +func (Service articleCollectService) Detail(id int) (res ArticleCollectResp, e error) { + var obj model.ArticleCollect + err := Service.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&obj).Error + if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, obj) + return +} + +// Add 文章收藏新增 +func (Service articleCollectService) Add(addReq ArticleCollectAddReq) (e error) { + var obj model.ArticleCollect + response.Copy(&obj, addReq) + err := Service.db.Create(&obj).Error + e = response.CheckErr(err, "Add Create err") + return +} + +// Edit 文章收藏编辑 +func (Service articleCollectService) Edit(editReq ArticleCollectEditReq) (e error) { + var obj model.ArticleCollect + err := Service.db.Where("id = ? AND is_delete = ?", editReq.Id, 0).Limit(1).First(&obj).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + // 更新 + response.Copy(&obj, editReq) + err = Service.db.Model(&obj).Updates(obj).Error + e = response.CheckErr(err, "Edit Updates err") + return +} + +// Del 文章收藏删除 +func (Service articleCollectService) Del(id int) (e error) { + var obj model.ArticleCollect + err := Service.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&obj).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Del First err"); e != nil { + return + } + // 删除 + obj.IsDelete = 1 + err = Service.db.Save(&obj).Error + e = response.CheckErr(err, "Del Save err") + return +} diff --git a/server/admin/article_collect_route.go b/server/admin/article_collect_route.go new file mode 100644 index 0000000..38e17bc --- /dev/null +++ b/server/admin/article_collect_route.go @@ -0,0 +1,26 @@ +package admin + +import ( + "x_admin/admin/article_collect" + "x_admin/core" + "x_admin/middleware" + + "github.com/gin-gonic/gin" +) + +// 请在 admin/entry.go 目录引入这个函数 +// ArticleCollectRoute(rg) +func ArticleCollectRoute(rg *gin.RouterGroup) { + db := core.GetDB() + + server := article_collect.NewArticleCollectService(db) + + handle := article_collect.ArticleCollectHandler{Service: server} + + rg = rg.Group("/", middleware.TokenAuth()) + rg.GET("/article_collect/list", handle.List) + rg.GET("/article_collect/detail", handle.Detail) + rg.POST("/article_collect/add", handle.Add) + rg.POST("/article_collect/edit", handle.Edit) + rg.POST("/article_collect/del", handle.Del) +} diff --git a/server/admin/common/album/route.go b/server/admin/common/album/route.go new file mode 100644 index 0000000..1bbef1b --- /dev/null +++ b/server/admin/common/album/route.go @@ -0,0 +1,116 @@ +package album + +import ( + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func AlbumRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewAlbumService(db) + + handle := albumHandler{Service: server} + + rg = rg.Group("/common", middleware.TokenAuth()) + + rg.GET("/album/albumList", handle.albumList) + rg.POST("/album/albumRename", middleware.RecordLog("相册文件重命名"), handle.albumRename) + rg.POST("/album/albumMove", middleware.RecordLog("相册文件移动"), handle.albumMove) + rg.POST("/album/albumDel", middleware.RecordLog("相册文件删除"), handle.albumDel) + rg.GET("/album/cateList", handle.cateList) + rg.POST("/album/cateAdd", middleware.RecordLog("相册分类新增"), handle.cateAdd) + rg.POST("/album/cateRename", middleware.RecordLog("相册分类重命名"), handle.cateRename) + rg.POST("/album/cateDel", middleware.RecordLog("相册分类删除"), handle.cateDel) +} + +type albumHandler struct { + Service IAlbumService +} + +// albumList 相册文件列表 +func (ah albumHandler) albumList(c *gin.Context) { + var page request.PageReq + var listReq CommonAlbumListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := ah.Service.AlbumList(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// albumRename 相册文件重命名 +func (ah albumHandler) albumRename(c *gin.Context) { + var rnReq CommonAlbumRenameReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &rnReq)) { + return + } + response.CheckAndResp(c, ah.Service.AlbumRename(rnReq.ID, rnReq.Name)) +} + +// albumMove 相册文件移动 +func (ah albumHandler) albumMove(c *gin.Context) { + var mvReq CommonAlbumMoveReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &mvReq)) { + return + } + response.CheckAndResp(c, ah.Service.AlbumMove(mvReq.Ids, mvReq.Cid)) +} + +// albumDel 相册文件删除 +func (ah albumHandler) albumDel(c *gin.Context) { + var delReq CommonAlbumDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, ah.Service.AlbumDel(delReq.Ids)) +} + +// cateList 类目列表 +func (ah albumHandler) cateList(c *gin.Context) { + var listReq CommonCateListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := ah.Service.CateList(listReq) + response.CheckAndRespWithData(c, res, err) +} + +// cateAdd 类目新增 +func (ah albumHandler) cateAdd(c *gin.Context) { + var addReq CommonCateAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + return + } + response.CheckAndResp(c, ah.Service.CateAdd(addReq)) +} + +// cateRename 类目命名 +func (ah albumHandler) cateRename(c *gin.Context) { + var rnReq CommonCateRenameReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &rnReq)) { + return + } + response.CheckAndResp(c, ah.Service.CateRename(rnReq.ID, rnReq.Name)) +} + +// cateDel 类目删除 +func (ah albumHandler) cateDel(c *gin.Context) { + var delReq CommonCateDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, ah.Service.CateDel(delReq.ID)) +} diff --git a/server/admin/common/album/schema.go b/server/admin/common/album/schema.go new file mode 100644 index 0000000..f507f48 --- /dev/null +++ b/server/admin/common/album/schema.go @@ -0,0 +1,103 @@ +package album + +import "x_admin/core" + +type CommonUploadImageReq struct { + Cid uint `form:"cid" binding:"gte=0"` // 主键 +} + +//CommonAlbumListReq 相册文件列表参数 +type CommonAlbumListReq struct { + Cid int `form:"cid,default=-1"` // 类目ID + Type int `form:"type" binding:"omitempty,oneof=10 20"` // 文件类型: [10=图片, 20=视频] + Name string `form:"keyword"` // 文件名称 +} + +//CommonAlbumRenameReq 相册文件重命名参数 +type CommonAlbumRenameReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + Name string `form:"keyword" binding:"required,min=1,max=30"` // 文件名称 +} + +//CommonAlbumMoveReq 相册文件移动参数 +type CommonAlbumMoveReq struct { + Ids []uint `form:"ids" binding:"required"` // 主键 + Cid int `form:"cid,default=-1"` // 类目ID +} + +//CommonAlbumAddReq 相册文件新增参数 +type CommonAlbumAddReq struct { + Cid uint `form:"cid" binding:"gte=0"` // 类目ID + Aid uint `form:"aid" binding:"gte=0"` // 管理ID + Uid uint `form:"uid" binding:"gte=0"` // 用户ID + Type int `form:"type" binding:"oneof=10 20"` // 文件类型: [10=图片, 20=视频] + Name string `form:"name"` // 文件名称 + Uri string `form:"uri"` // 文件路径 + Ext string `form:"ext"` // 文件扩展 + Size int64 `form:"size"` // 文件大小 +} + +//CommonAlbumDelReq 相册文件删除参数 +type CommonAlbumDelReq struct { + Ids []uint `form:"ids" binding:"required"` // 主键 +} + +//CommonCateListReq 相册分类列表参数 +type CommonCateListReq struct { + Type int `form:"type" binding:"omitempty,oneof=10 20 30"` // 分类类型: [10=图片,20=视频] + Name string `form:"keyword"` // 分类名称 +} + +//CommonCateAddReq 相册分类新增参数 +type CommonCateAddReq struct { + Pid uint `form:"pid" binding:"gte=0"` // 父级ID + Type int `form:"type" binding:"required,oneof=10 20 30"` // 分类类型: [10=图片,20=视频] + Name string `form:"name" binding:"required,min=1,max=30"` // 分类名称 +} + +//CommonCateRenameReq 相册分类重命名参数 +type CommonCateRenameReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + Name string `form:"keyword" binding:"required,min=1,max=30"` // 分类名称 +} + +//CommonCateDelReq 相册分类删除参数 +type CommonCateDelReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//CommonUploadFileResp 上传图片返回信息 +type CommonUploadFileResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Cid uint `json:"cid" structs:"cid"` // 类目ID + Aid uint `json:"aid" structs:"aid"` // 管理ID + Uid uint `json:"uid" structs:"uid"` // 用户ID + Type int `json:"type" structs:"type"` // 文件类型: [10=图片, 20=视频] + Name string `json:"name" structs:"name"` // 文件名称 + Uri string `json:"url" structs:"url"` // 文件路径 + Path string `json:"path" structs:"path"` // 访问地址 + Ext string `json:"ext" structs:"ext"` // 文件扩展 + Size int64 `json:"size" structs:"size"` // 文件大小 +} + +//CommonAlbumListResp 相册文件列表返回信息 +type CommonAlbumListResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Cid uint `json:"cid" structs:"cid"` // 所属类目 + Name string `json:"name" structs:"name"` // 文件名称 + Path string `json:"path" structs:"path"` // 相对路径 + Uri string `json:"uri" structs:"uri"` // 文件路径 + Ext string `json:"ext" structs:"ext"` // 文件扩展 + Size string `json:"size" structs:"size"` // 文件大小 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +//CommonCateListResp 相册分类列表返回信息 +type CommonCateListResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Pid uint `json:"pid" structs:"pid"` // 父级ID + Name string `json:"name" structs:"name"` // 分类名称 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} diff --git a/server/admin/common/album/service.go b/server/admin/common/album/service.go new file mode 100644 index 0000000..1262400 --- /dev/null +++ b/server/admin/common/album/service.go @@ -0,0 +1,225 @@ +package album + +import ( + "path" + "time" + "x_admin/config" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/common" + "x_admin/util" + + "gorm.io/gorm" +) + +type IAlbumService interface { + AlbumList(page request.PageReq, listReq CommonAlbumListReq) (res response.PageResp, e error) + AlbumRename(id uint, name string) (e error) + AlbumMove(ids []uint, cid int) (e error) + AlbumAdd(addReq CommonAlbumAddReq) (res uint, e error) + AlbumDel(ids []uint) (e error) + CateList(listReq CommonCateListReq) (mapList []interface{}, e error) + CateAdd(addReq CommonCateAddReq) (e error) + CateRename(id uint, name string) (e error) + CateDel(id uint) (e error) +} + +// NewAlbumService 初始化 +func NewAlbumService(db *gorm.DB) IAlbumService { + return &albumService{db: db} +} + +// albumService 相册服务实现类 +type albumService struct { + db *gorm.DB +} + +// AlbumList 相册文件列表 +func (albSrv albumService) AlbumList(page request.PageReq, listReq CommonAlbumListReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + albumModel := albSrv.db.Model(&common.Album{}).Where("is_delete = ?", 0) + if listReq.Cid > 0 { + albumModel = albumModel.Where("cid = ?", listReq.Cid) + } + if listReq.Name != "" { + albumModel = albumModel.Where("name like ?", "%"+listReq.Name+"%") + } + if listReq.Type > 0 { + albumModel = albumModel.Where("type = ?", listReq.Type) + } + // 总数 + var count int64 + err := albumModel.Count(&count).Error + if e = response.CheckErr(err, "AlbumList Count err"); e != nil { + return + } + // 数据 + var albums []common.Album + err = albumModel.Limit(limit).Offset(offset).Order("id desc").Find(&albums).Error + if e = response.CheckErr(err, "AlbumList Find err"); e != nil { + return + } + albumResps := []CommonAlbumListResp{} + response.Copy(&albumResps, albums) + // TODO: engine默认local + engine := "local" + for i := 0; i < len(albumResps); i++ { + if engine == "local" { + albumResps[i].Path = path.Join(config.Config.PublicPrefix, albums[i].Uri) + } else { + // TODO: 其他engine + } + albumResps[i].Uri = util.UrlUtil.ToAbsoluteUrl(albums[i].Uri) + albumResps[i].Size = util.ServerUtil.GetFmtSize(uint64(albums[i].Size)) + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: albumResps, + }, nil +} + +// AlbumRename 相册文件重命名 +func (albSrv albumService) AlbumRename(id uint, name string) (e error) { + var album common.Album + err := albSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&album).Error + if e = response.CheckErrDBNotRecord(err, "文件丢失!"); e != nil { + return + } + if e = response.CheckErr(err, "AlbumRename First err"); e != nil { + return + } + album.Name = name + err = albSrv.db.Save(&album).Error + e = response.CheckErr(err, "AlbumRename Save err") + return +} + +// AlbumMove 相册文件移动 +func (albSrv albumService) AlbumMove(ids []uint, cid int) (e error) { + var albums []common.Album + err := albSrv.db.Where("id in ? AND is_delete = ?", ids, 0).Find(&albums).Error + if e = response.CheckErr(err, "AlbumMove Find err"); e != nil { + return + } + if len(albums) == 0 { + return response.AssertArgumentError.Make("文件丢失!") + } + if cid > 0 { + err = albSrv.db.Where("id = ? AND is_delete = ?", cid, 0).Limit(1).First(&common.AlbumCate{}).Error + if e = response.CheckErrDBNotRecord(err, "类目已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "AlbumMove First err"); e != nil { + return + } + } + err = albSrv.db.Model(&common.Album{}).Where("id in ?", ids).UpdateColumn("cid", cid).Error + e = response.CheckErr(err, "AlbumMove UpdateColumn err") + return +} + +// AlbumAdd 相册文件新增 +func (albSrv albumService) AlbumAdd(addReq CommonAlbumAddReq) (res uint, e error) { + var alb common.Album + //var params map[string]interface{} + //if err := mapstructure.Decode(params, &alb); err != nil { + // core.Logger.Errorf("AlbumAdd Decode err: err=[%+v]", err) + // return response.SystemError + //} + response.Copy(&alb, addReq) + err := albSrv.db.Create(&alb).Error + if e = response.CheckErr(err, "AlbumAdd Create err"); e != nil { + return + } + return alb.ID, nil +} + +// AlbumDel 相册文件删除 +func (albSrv albumService) AlbumDel(ids []uint) (e error) { + var albums []common.Album + err := albSrv.db.Where("id in ? AND is_delete = ?", ids, 0).Find(&albums).Error + if e = response.CheckErr(err, "AlbumDel Find err"); e != nil { + return + } + if len(albums) == 0 { + return response.AssertArgumentError.Make("文件丢失!") + } + err = albSrv.db.Model(&common.Album{}).Where("id in ?", ids).Updates( + common.Album{IsDelete: 1, DeleteTime: time.Now().Unix()}).Error + e = response.CheckErr(err, "AlbumDel UpdateColumn err") + return +} + +// CateList 相册分类列表 +func (albSrv albumService) CateList(listReq CommonCateListReq) (mapList []interface{}, e error) { + var cates []common.AlbumCate + cateModel := albSrv.db.Where("is_delete = ?", 0).Order("id desc") + if listReq.Type > 0 { + cateModel = cateModel.Where("type = ?", listReq.Type) + } + if listReq.Name != "" { + cateModel = cateModel.Where("name like ?", "%"+listReq.Name+"%") + } + err := cateModel.Find(&cates).Error + if e = response.CheckErr(err, "CateList Find err"); e != nil { + return + } + cateResps := []CommonCateListResp{} + response.Copy(&cateResps, cates) + return util.ArrayUtil.ListToTree( + util.ConvertUtil.StructsToMaps(cateResps), "id", "pid", "children"), nil +} + +// CateAdd 分类新增 +func (albSrv albumService) CateAdd(addReq CommonCateAddReq) (e error) { + var cate common.AlbumCate + response.Copy(&cate, addReq) + err := albSrv.db.Create(&cate).Error + e = response.CheckErr(err, "CateAdd Create err") + return +} + +// CateRename 分类重命名 +func (albSrv albumService) CateRename(id uint, name string) (e error) { + var cate common.AlbumCate + err := albSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&cate).Error + if e = response.CheckErrDBNotRecord(err, "分类已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "CateRename First err"); e != nil { + return + } + cate.Name = name + err = albSrv.db.Save(&cate).Error + e = response.CheckErr(err, "CateRename Save err") + return +} + +// CateDel 分类删除 +func (albSrv albumService) CateDel(id uint) (e error) { + var cate common.AlbumCate + err := albSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&cate).Error + if e = response.CheckErrDBNotRecord(err, "分类已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "CateDel First err"); e != nil { + return + } + r := albSrv.db.Where("cid = ? AND is_delete = ?", id, 0).Limit(1).Find(&common.Album{}) + if e = response.CheckErr(r.Error, "CateDel Find err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("当前分类正被使用中,不能删除!") + } + cate.IsDelete = 1 + cate.DeleteTime = time.Now().Unix() + err = albSrv.db.Save(&cate).Error + e = response.CheckErr(err, "CateDel Save err") + return +} diff --git a/server/admin/common/index/index.go b/server/admin/common/index/index.go new file mode 100644 index 0000000..ede4d09 --- /dev/null +++ b/server/admin/common/index/index.go @@ -0,0 +1,41 @@ +package index + +import ( + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + + "github.com/gin-gonic/gin" +) + +func IndexRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + // authSrv := system.NewSystemAuthMenuService(db, permSrv) + IndexService := NewIndexService(db) + handle := indexHandler{Service: IndexService} + + rg = rg.Group("/common", middleware.TokenAuth()) + rg.GET("/index/console", handle.console) + rg.GET("/index/config", handle.config) +} + +type indexHandler struct { + Service IIndexService +} + +// console 控制台 +func (ih indexHandler) console(c *gin.Context) { + res, err := ih.Service.Console() + response.CheckAndRespWithData(c, res, err) +} + +// config 公共配置 +func (ih indexHandler) config(c *gin.Context) { + res, err := ih.Service.Config() + response.CheckAndRespWithData(c, res, err) +} diff --git a/server/admin/common/index/service.go b/server/admin/common/index/service.go new file mode 100644 index 0000000..bf72b77 --- /dev/null +++ b/server/admin/common/index/service.go @@ -0,0 +1,101 @@ +package index + +import ( + "time" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/util" + + "gorm.io/gorm" +) + +type IIndexService interface { + Console() (res map[string]interface{}, e error) + Config() (res map[string]interface{}, e error) +} + +// NewIndexService 初始化 +func NewIndexService(db *gorm.DB) IIndexService { + return &indexService{db: db} +} + +// indexService 主页服务实现类 +type indexService struct { + db *gorm.DB +} + +// Console 控制台数据 +func (iSrv indexService) Console() (res map[string]interface{}, e error) { + // 版本信息 + name, err := util.ConfigUtil.GetVal(iSrv.db, "website", "name", "x_admin-Go") + if e = response.CheckErr(err, "Console Get err"); e != nil { + return + } + version := map[string]interface{}{ + "name": name, + "version": config.Config.Version, + "website": "www.x_admin.cn", + "based": "Vue3.x、ElementUI、MySQL", + "channel": map[string]string{ + "gitee": "https://gitee.com/x_admin/x_admin_python", + "website": "https://www.x_admin.cn", + }, + } + // 今日数据 + today := map[string]interface{}{ + "time": "2022-08-11 15:08:29", + "todayVisits": 10, // 访问量(人) + "totalVisits": 100, // 总访问量 + "todaySales": 30, // 销售额(元) + "totalSales": 65, // 总销售额 + "todayOrder": 12, // 订单量(笔) + "totalOrder": 255, // 总订单量 + "todayUsers": 120, // 新增用户 + "totalUsers": 360, // 总访用户 + } + // 访客图表 + now := time.Now() + var date []string + for i := 14; i >= 0; i-- { + date = append(date, now.AddDate(0, 0, -i).Format(core.DateFormat)) + } + visitor := map[string]interface{}{ + "date": date, + "list": []int{12, 13, 11, 5, 8, 22, 14, 9, 456, 62, 78, 12, 18, 22, 46}, + } + return map[string]interface{}{ + "version": version, + "today": today, + "visitor": visitor, + }, nil +} + +// Config 公共配置 +func (iSrv indexService) Config() (res map[string]interface{}, e error) { + website, err := util.ConfigUtil.Get(iSrv.db, "website") + if e = response.CheckErr(err, "Config Get err"); e != nil { + return + } + copyrightStr, err := util.ConfigUtil.GetVal(iSrv.db, "website", "copyright", "") + if e = response.CheckErr(err, "Config GetVal err"); e != nil { + return + } + var copyright []map[string]string + if copyrightStr != "" { + err = util.ToolsUtil.JsonToObj(copyrightStr, ©right) + if e = response.CheckErr(err, "Config JsonToObj err"); e != nil { + return + } + } else { + copyright = []map[string]string{} + } + return map[string]interface{}{ + "webName": website["name"], + "webLogo": util.UrlUtil.ToAbsoluteUrl(website["logo"]), + "webFavicon": util.UrlUtil.ToAbsoluteUrl(website["favicon"]), + "webBackdrop": util.UrlUtil.ToAbsoluteUrl(website["backdrop"]), + "ossDomain": config.Config.OssDomain, + "copyright": copyright, + }, nil +} diff --git a/server/admin/common/upload/service.go b/server/admin/common/upload/service.go new file mode 100644 index 0000000..73b0aeb --- /dev/null +++ b/server/admin/common/upload/service.go @@ -0,0 +1,53 @@ +package upload + +import ( + "mime/multipart" + "x_admin/admin/common/album" + "x_admin/core/response" + "x_admin/plugin" +) + +type IUploadService interface { + UploadImage(file *multipart.FileHeader, cid uint, aid uint) (res album.CommonUploadFileResp, e error) + UploadVideo(file *multipart.FileHeader, cid uint, aid uint) (res album.CommonUploadFileResp, e error) +} + +// NewUploadService 初始化 +func NewUploadService(albSrv album.IAlbumService) IUploadService { + return &uploadService{albSrv} +} + +// uploadService 上传服务实现类 +type uploadService struct { + albSrv album.IAlbumService +} + +// UploadImage 上传图片 +func (upSrv uploadService) UploadImage(file *multipart.FileHeader, cid uint, aid uint) (res album.CommonUploadFileResp, e error) { + return upSrv.uploadFile(file, "image", 10, cid, aid) +} + +// UploadVideo 上传视频 +func (upSrv uploadService) UploadVideo(file *multipart.FileHeader, cid uint, aid uint) (res album.CommonUploadFileResp, e error) { + return upSrv.uploadFile(file, "video", 20, cid, aid) +} + +// uploadFile 上传文件 +func (upSrv uploadService) uploadFile(file *multipart.FileHeader, folder string, fileType int, cid uint, aid uint) (res album.CommonUploadFileResp, e error) { + var upRes *plugin.UploadFile + if upRes, e = plugin.StorageDriver.Upload(file, folder, fileType); e != nil { + return + } + var addReq album.CommonAlbumAddReq + response.Copy(&addReq, upRes) + addReq.Aid = aid + addReq.Cid = cid + var albumId uint + if albumId, e = upSrv.albSrv.AlbumAdd(addReq); e != nil { + return + } + response.Copy(&res, addReq) + res.ID = albumId + res.Path = upRes.Path + return res, nil +} diff --git a/server/admin/common/upload/upload.go b/server/admin/common/upload/upload.go new file mode 100644 index 0000000..a0ccae8 --- /dev/null +++ b/server/admin/common/upload/upload.go @@ -0,0 +1,60 @@ +package upload + +import ( + "x_admin/admin/common/album" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func UploadRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + AlbumServer := album.NewAlbumService(db) + server := NewUploadService(AlbumServer) + + handle := uploadHandler{Service: server} + + rg = rg.Group("/common", middleware.TokenAuth()) + rg.POST("/upload/image", middleware.RecordLog("上传图片", middleware.RequestFile), handle.uploadImage) + rg.POST("/upload/video", middleware.RecordLog("上传视频", middleware.RequestFile), handle.uploadVideo) +} + +type uploadHandler struct { + Service IUploadService +} + +// uploadImage 上传图片 +func (uh uploadHandler) uploadImage(c *gin.Context) { + var uReq album.CommonUploadImageReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &uReq)) { + return + } + file, ve := util.VerifyUtil.VerifyFile(c, "file") + if response.IsFailWithResp(c, ve) { + return + } + res, err := uh.Service.UploadImage(file, uReq.Cid, config.AdminConfig.GetAdminId(c)) + response.CheckAndRespWithData(c, res, err) +} + +// uploadVideo 上传视频 +func (uh uploadHandler) uploadVideo(c *gin.Context) { + var uReq album.CommonUploadImageReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &uReq)) { + return + } + file, ve := util.VerifyUtil.VerifyFile(c, "file") + if response.IsFailWithResp(c, ve) { + return + } + res, err := uh.Service.UploadVideo(file, uReq.Cid, config.AdminConfig.GetAdminId(c)) + response.CheckAndRespWithData(c, res, err) +} diff --git a/server/admin/entry.go b/server/admin/entry.go new file mode 100644 index 0000000..a6688a5 --- /dev/null +++ b/server/admin/entry.go @@ -0,0 +1,47 @@ +package admin + +import ( + "x_admin/admin/common/album" + "x_admin/admin/common/index" + "x_admin/admin/common/upload" + "x_admin/admin/monitor" + "x_admin/admin/setting/copyright" + "x_admin/admin/setting/dict_data" + "x_admin/admin/setting/dict_type" + "x_admin/admin/setting/protocol" + "x_admin/admin/setting/storage" + "x_admin/admin/setting/website" + "x_admin/admin/system" + "x_admin/admin/system/dept" + "x_admin/admin/system/log" + "x_admin/admin/system/login" + "x_admin/admin/system/menu" + "x_admin/admin/system/post" + + "github.com/gin-gonic/gin" +) + +func RegisterGroup(rg *gin.RouterGroup) { + upload.UploadRoute(rg) + album.AlbumRoute(rg) + index.IndexRoute(rg) + + monitor.MonitorRoute(rg) + + copyright.CopyrightRoute(rg) + dict_data.DictDataRoute(rg) + dict_type.DictTypeRoute(rg) + protocol.ProtocolRoute(rg) + storage.StorageRoute(rg) + website.WebsiteRoute(rg) + + login.LoginRoute(rg) + system.AdminRoute(rg) + menu.MenuRoute(rg) + post.PostRoute(rg) + + dept.DeptRoute(rg) + system.RoleRoute(rg) + log.LogRoute(rg) + ArticleCollectRoute(rg) +} diff --git a/server/admin/monitor/monitor.go b/server/admin/monitor/monitor.go new file mode 100644 index 0000000..a0183eb --- /dev/null +++ b/server/admin/monitor/monitor.go @@ -0,0 +1,48 @@ +package monitor + +import ( + "strings" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func MonitorRoute(rg *gin.RouterGroup) { + handle := monitorHandler{} + + rg = rg.Group("/monitor", middleware.TokenAuth()) + rg.GET("/cache", middleware.RecordLog("缓存监控"), handle.cache) + rg.GET("/server", middleware.RecordLog("服务监控"), handle.server) +} + +type monitorHandler struct{} + +// cache 缓存监控 +func (mh monitorHandler) cache(c *gin.Context) { + cmdStatsMap := util.RedisUtil.Info("commandstats") + var stats []map[string]string + for k, v := range cmdStatsMap { + stats = append(stats, map[string]string{ + "name": strings.Split(k, "_")[1], + "value": v[strings.Index(v, "=")+1 : strings.Index(v, ",")], + }) + } + response.OkWithData(c, map[string]interface{}{ + "info": util.RedisUtil.Info(), + "commandStats": stats, + "dbSize": util.RedisUtil.DBSize(), + }) +} + +// server 服务监控 +func (mh monitorHandler) server(c *gin.Context) { + response.OkWithData(c, map[string]interface{}{ + "cpu": util.ServerUtil.GetCpuInfo(), + "mem": util.ServerUtil.GetMemInfo(), + "sys": util.ServerUtil.GetSysInfo(), + "disk": util.ServerUtil.GetDiskInfo(), + "go": util.ServerUtil.GetGoInfo(), + }) +} diff --git a/server/admin/setting/copyright/copyright.go b/server/admin/setting/copyright/copyright.go new file mode 100644 index 0000000..cf3255a --- /dev/null +++ b/server/admin/setting/copyright/copyright.go @@ -0,0 +1,38 @@ +package copyright + +import ( + "x_admin/core" + "x_admin/core/response" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func CopyrightRoute(rg *gin.RouterGroup) { + db := core.GetDB() + service := NewSettingCopyrightService(db) + handle := copyrightHandler{Service: service} + + rg = rg.Group("/setting") + rg.GET("/copyright/detail", handle.Detail) + rg.POST("/copyright/save", handle.save) +} + +type copyrightHandler struct { + Service ISettingCopyrightService +} + +// detail 获取备案信息 +func (ch copyrightHandler) Detail(c *gin.Context) { + res, err := ch.Service.Detail() + response.CheckAndRespWithData(c, res, err) +} + +// save 保存备案信息 +func (ch copyrightHandler) save(c *gin.Context) { + var cReqs []SettingCopyrightItemReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSONArray(c, &cReqs)) { + return + } + response.CheckAndResp(c, ch.Service.Save(cReqs)) +} diff --git a/server/admin/setting/copyright/schema.go b/server/admin/setting/copyright/schema.go new file mode 100644 index 0000000..e32eb43 --- /dev/null +++ b/server/admin/setting/copyright/schema.go @@ -0,0 +1,7 @@ +package copyright + +//SettingCopyrightItemReq 保存备案信息参数 +type SettingCopyrightItemReq struct { + Name string `form:"name" json:"name"` // 名称 + Link string `form:"link" json:"link"` // 链接 +} diff --git a/server/admin/setting/copyright/service.go b/server/admin/setting/copyright/service.go new file mode 100644 index 0000000..88a6332 --- /dev/null +++ b/server/admin/setting/copyright/service.go @@ -0,0 +1,44 @@ +package copyright + +import ( + "x_admin/core/response" + "x_admin/util" + + "gorm.io/gorm" +) + +type ISettingCopyrightService interface { + Detail() (res []map[string]interface{}, e error) + Save(cReqs []SettingCopyrightItemReq) (e error) +} + +// NewSettingCopyrightService 初始化 +func NewSettingCopyrightService(db *gorm.DB) ISettingCopyrightService { + return &settingCopyrightService{db: db} +} + +// settingCopyrightService 网站备案服务实现类 +type settingCopyrightService struct { + db *gorm.DB +} + +// Detail 获取网站备案信息 +func (cSrv settingCopyrightService) Detail() (res []map[string]interface{}, e error) { + data, err := util.ConfigUtil.GetVal(cSrv.db, "website", "copyright", "[]") + if e = response.CheckErr(err, "Detail GetVal err"); e != nil { + return + } + e = response.CheckErr(util.ToolsUtil.JsonToObj(data, &res), "Detail JsonToObj err") + return +} + +// Save 保存网站备案信息 +func (cSrv settingCopyrightService) Save(cReqs []SettingCopyrightItemReq) (e error) { + json, err := util.ToolsUtil.ObjToJson(cReqs) + if e = response.CheckErr(err, "Save ObjToJson err"); e != nil { + return + } + err = util.ConfigUtil.Set(cSrv.db, "website", "copyright", json) + e = response.CheckErr(err, "Save Set err") + return +} diff --git a/server/admin/setting/dict_data/dict_data.go b/server/admin/setting/dict_data/dict_data.go new file mode 100644 index 0000000..02b25d0 --- /dev/null +++ b/server/admin/setting/dict_data/dict_data.go @@ -0,0 +1,96 @@ +package dict_data + +import ( + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func DictDataRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + authSrv := NewSettingDictDataService(db) + + handle := dictDataHandler{Service: authSrv} + + rg = rg.Group("/setting", middleware.TokenAuth()) + rg.GET("/dict/data/all", handle.All) + rg.GET("/dict/data/list", handle.List) + rg.GET("/dict/data/detail", handle.Detail) + rg.POST("/dict/data/add", handle.Add) + rg.POST("/dict/data/edit", handle.Edit) + rg.POST("/dict/data/del", handle.Del) +} + +type dictDataHandler struct { + Service ISettingDictDataService +} + +// all 字典数据所有 +func (ddh dictDataHandler) All(c *gin.Context) { + var allReq SettingDictDataListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &allReq)) { + return + } + res, err := ddh.Service.All(allReq) + response.CheckAndRespWithData(c, res, err) +} + +// list 字典数据列表 +func (ddh dictDataHandler) List(c *gin.Context) { + var page request.PageReq + var listReq SettingDictDataListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := ddh.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail 字典数据详情 +func (ddh dictDataHandler) Detail(c *gin.Context) { + var detailReq SettingDictDataDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := ddh.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// detail 字典数据新增 +func (ddh dictDataHandler) Add(c *gin.Context) { + var addReq SettingDictDataAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + return + } + response.CheckAndResp(c, ddh.Service.Add(addReq)) +} + +// edit 字典数据编辑 +func (ddh dictDataHandler) Edit(c *gin.Context) { + var editReq SettingDictDataEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { + return + } + response.CheckAndResp(c, ddh.Service.Edit(editReq)) +} + +// del 字典数据删除 +func (ddh dictDataHandler) Del(c *gin.Context) { + var delReq SettingDictDataDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, ddh.Service.Del(delReq)) +} diff --git a/server/admin/setting/dict_data/schema.go b/server/admin/setting/dict_data/schema.go new file mode 100644 index 0000000..154000f --- /dev/null +++ b/server/admin/setting/dict_data/schema.go @@ -0,0 +1,55 @@ +package dict_data + +import "x_admin/core" + +//SettingDictDataResp 字典数据返回信息 +type SettingDictDataResp struct { + ID uint `json:"id" structs:"id"` // 主键 + TypeId uint `json:"typeId" structs:"typeId"` // 类型 + Name string `json:"name" structs:"name"` // 键 + Value string `json:"value" structs:"value"` // 值 + Remark string `json:"remark" structs:"remark"` // 备注 + Sort uint16 `json:"sort" structs:"sort"` // 排序 + Status uint8 `json:"status" structs:"status"` // 状态: [0=停用, 1=禁用] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +//SettingDictDataListReq 字典数据列表参数 +type SettingDictDataListReq struct { + DictType string `form:"dictType" binding:"max=200"` // 字典类型 + Name string `form:"name" binding:"max=100"` // 键 + Value string `form:"value" binding:"max=200"` // 值 + Status int8 `form:"status,default=-1" binding:"oneof=-1 0 1"` // 状态: 0=停用,1=启用 +} + +//SettingDictDataDetailReq 字典数据详情参数 +type SettingDictDataDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SettingDictDataAddReq 字典数据新增参数 +type SettingDictDataAddReq struct { + TypeId uint `form:"typeId" binding:"required,gt=0"` // 类型 + Name string `form:"name" binding:"required,max=100"` // 键 + Value string `form:"value" binding:"required,max=200"` // 值 + remark string `form:"remark" binding:"max=200"` // 备注 + Sort int `form:"sort" binding:"gte=0"` // 排序 + Status int8 `form:"status,default=-1" binding:"oneof=-1 0 1"` // 状态: 0=停用,1=启用 +} + +//SettingDictDataEditReq 字典数据编辑参数 +type SettingDictDataEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + TypeId uint `form:"typeId" binding:"required,gte=0"` // 类型 + Name string `form:"name" binding:"required,max=100"` // 键 + Value string `form:"value" binding:"required,max=200"` // 值 + remark string `form:"remark" binding:"max=200"` // 备注 + Sort int `form:"sort" binding:"gte=0"` // 排序 + Status int8 `form:"status,default=-1" binding:"oneof=-1 0 1"` // 状态: 0=停用,1=启用 +} + +//SettingDictDataDelReq 字典数据删除参数 +type SettingDictDataDelReq struct { + Ids []uint `form:"ids" binding:"required"` // 主键列表 +} diff --git a/server/admin/setting/dict_data/service.go b/server/admin/setting/dict_data/service.go new file mode 100644 index 0000000..0121cce --- /dev/null +++ b/server/admin/setting/dict_data/service.go @@ -0,0 +1,153 @@ +package dict_data + +import ( + "time" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/setting" + + "gorm.io/gorm" +) + +type ISettingDictDataService interface { + All(allReq SettingDictDataListReq) (res []SettingDictDataResp, e error) + List(page request.PageReq, listReq SettingDictDataListReq) (res response.PageResp, e error) + Detail(id uint) (res SettingDictDataResp, e error) + Add(addReq SettingDictDataAddReq) (e error) + Edit(editReq SettingDictDataEditReq) (e error) + Del(delReq SettingDictDataDelReq) (e error) +} + +// NewSettingDictDataService 初始化 +func NewSettingDictDataService(db *gorm.DB) ISettingDictDataService { + return &settingDictDataService{db: db} +} + +// settingDictDataService 字典数据服务实现类 +type settingDictDataService struct { + db *gorm.DB +} + +// All 字典数据所有 +func (ddSrv settingDictDataService) All(allReq SettingDictDataListReq) (res []SettingDictDataResp, e error) { + var dictType setting.DictType + err := ddSrv.db.Where("dict_type = ? AND is_delete = ?", allReq.DictType, 0).Limit(1).First(&dictType).Error + if e = response.CheckErrDBNotRecord(err, "该字典类型不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "All First err"); e != nil { + return + } + ddModel := ddSrv.db.Where("type_id = ? AND is_delete = ?", dictType.ID, 0) + if allReq.Name != "" { + ddModel = ddModel.Where("name like ?", "%"+allReq.Name+"%") + } + if allReq.Value != "" { + ddModel = ddModel.Where("value like ?", "%"+allReq.Value+"%") + } + if allReq.Status >= 0 { + ddModel = ddModel.Where("status = ?", allReq.Status) + } + var dictDatas []setting.DictData + err = ddModel.Order("id desc").Find(&dictDatas).Error + if e = response.CheckErr(err, "All Find err"); e != nil { + return + } + res = []SettingDictDataResp{} + response.Copy(&res, dictDatas) + return +} + +// List 字典数据列表 +func (ddSrv settingDictDataService) List(page request.PageReq, listReq SettingDictDataListReq) (res response.PageResp, e error) { + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + var dictType setting.DictType + err := ddSrv.db.Where("dict_type = ? AND is_delete = ?", listReq.DictType, 0).Limit(1).First(&dictType).Error + if e = response.CheckErrDBNotRecord(err, "该字典类型不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "List First err"); e != nil { + return + } + ddModel := ddSrv.db.Model(&setting.DictData{}).Where("type_id = ? AND is_delete = ?", dictType.ID, 0) + if listReq.Name != "" { + ddModel = ddModel.Where("name like ?", "%"+listReq.Name+"%") + } + if listReq.Value != "" { + ddModel = ddModel.Where("value like ?", "%"+listReq.Value+"%") + } + if listReq.Status >= 0 { + ddModel = ddModel.Where("status = ?", listReq.Status) + } + var count int64 + e = ddModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + var dds []setting.DictData + err = ddModel.Limit(limit).Offset(offset).Order("id desc").Find(&dds).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + dtResp := []SettingDictDataResp{} + response.Copy(&dtResp, dds) + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: dtResp, + }, nil +} + +// Detail 字典数据详情 +func (ddSrv settingDictDataService) Detail(id uint) (res SettingDictDataResp, e error) { + var dd setting.DictData + err := ddSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&dd).Error + if e = response.CheckErrDBNotRecord(err, "字典数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, dd) + return +} + +// Add 字典数据新增 +func (ddSrv settingDictDataService) Add(addReq SettingDictDataAddReq) (e error) { + if r := ddSrv.db.Where("name = ? AND is_delete = ?", addReq.Name, 0).Limit(1).First(&setting.DictData{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("字典数据已存在!") + } + var dd setting.DictData + response.Copy(&dd, addReq) + err := ddSrv.db.Create(&dd).Error + e = response.CheckErr(err, "Add Create err") + return +} + +// Edit 字典数据编辑 +func (ddSrv settingDictDataService) Edit(editReq SettingDictDataEditReq) (e error) { + err := ddSrv.db.Where("id = ? AND is_delete = ?", editReq.ID, 0).Limit(1).First(&setting.DictData{}).Error + if e = response.CheckErrDBNotRecord(err, "字典数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + if r := ddSrv.db.Where("id != ? AND name = ? AND is_delete = ?", editReq.ID, editReq.Name, 0).Limit(1).First(&setting.DictData{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("字典数据已存在!") + } + var dd setting.DictData + response.Copy(&dd, editReq) + err = ddSrv.db.Save(&dd).Error + e = response.CheckErr(err, "Edit Save err") + return +} + +// Del 字典数据删除 +func (ddSrv settingDictDataService) Del(delReq SettingDictDataDelReq) (e error) { + err := ddSrv.db.Model(&setting.DictData{}).Where("id IN ?", delReq.Ids).Updates( + setting.DictData{IsDelete: 1, DeleteTime: time.Now().Unix()}).Error + return response.CheckErr(err, "Del Update err") +} diff --git a/server/admin/setting/dict_type/dict_type.go b/server/admin/setting/dict_type/dict_type.go new file mode 100644 index 0000000..8a020d6 --- /dev/null +++ b/server/admin/setting/dict_type/dict_type.go @@ -0,0 +1,92 @@ +package dict_type + +import ( + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func DictTypeRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewSettingDictTypeService(db) + + handle := dictTypeHandler{Service: server} + + rg = rg.Group("/setting", middleware.TokenAuth()) + rg.GET("/dict/type/all", handle.All) + rg.GET("/dict/type/list", handle.List) + rg.GET("/dict/type/detail", handle.Detail) + rg.POST("/dict/type/add", handle.Add) + rg.POST("/dict/type/edit", handle.Edit) + rg.POST("/dict/type/del", handle.Del) +} + +type dictTypeHandler struct { + Service ISettingDictTypeService +} + +// all 字典类型所有 +func (dth dictTypeHandler) All(c *gin.Context) { + res, err := dth.Service.All() + response.CheckAndRespWithData(c, res, err) +} + +// list 字典类型列表 +func (dth dictTypeHandler) List(c *gin.Context) { + var page request.PageReq + var listReq SettingDictTypeListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := dth.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail 字典类型详情 +func (dth dictTypeHandler) Detail(c *gin.Context) { + var detailReq SettingDictTypeDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := dth.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// detail 字典类型新增 +func (dth dictTypeHandler) Add(c *gin.Context) { + var addReq SettingDictTypeAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + return + } + response.CheckAndResp(c, dth.Service.Add(addReq)) +} + +// edit 字典类型编辑 +func (dth dictTypeHandler) Edit(c *gin.Context) { + var editReq SettingDictTypeEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { + return + } + response.CheckAndResp(c, dth.Service.Edit(editReq)) +} + +// del 字典类型删除 +func (dth dictTypeHandler) Del(c *gin.Context) { + var delReq SettingDictTypeDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, dth.Service.Del(delReq)) +} diff --git a/server/admin/setting/dict_type/schema.go b/server/admin/setting/dict_type/schema.go new file mode 100644 index 0000000..4080802 --- /dev/null +++ b/server/admin/setting/dict_type/schema.go @@ -0,0 +1,48 @@ +package dict_type + +import "x_admin/core" + +//SettingDictTypeListReq 字典类型新增参数 +type SettingDictTypeListReq struct { + DictName string `form:"dictName" binding:"max=200"` // 字典名称 + DictType string `form:"dictType" binding:"max=200"` // 字典类型 + DictStatus int8 `form:"dictStatus,default=-1" binding:"oneof=-1 0 1"` // 字典状态: 0/1 +} + +//SettingDictTypeDetailReq 字典类型详情参数 +type SettingDictTypeDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SettingDictTypeAddReq 字典类型新增参数 +type SettingDictTypeAddReq struct { + DictName string `form:"dictName" binding:"required,max=200"` // 字典名称 + DictType string `form:"dictType" binding:"required,max=200"` // 字典类型 + DictRemark string `form:"dictRemark" binding:"max=200"` // 字典备注 + DictStatus int8 `form:"dictStatus" binding:"required,oneof=0 1"` // 字典状态: 0/1 +} + +//SettingDictTypeEditReq 字典类型编辑参数 +type SettingDictTypeEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + DictName string `form:"dictName" binding:"required,max=200"` // 字典名称 + DictType string `form:"dictType" binding:"required,max=200"` // 字典类型 + DictRemark string `form:"dictRemark" binding:"max=200"` // 字典备注 + DictStatus int8 `form:"dictStatus" binding:"required,oneof=0 1"` // 字典状态: 0/1 +} + +//SettingDictTypeDelReq 字典类型删除参数 +type SettingDictTypeDelReq struct { + Ids []uint `form:"ids" binding:"required"` // 主键列表 +} + +//SettingDictTypeResp 字典类型返回信息 +type SettingDictTypeResp struct { + ID uint `json:"id" structs:"id"` // 主键 + DictName string `json:"dictName" structs:"dictName"` // 字典名称 + DictType string `json:"dictType" structs:"dictType"` // 字典类型 + DictRemark string `json:"dictRemark" structs:"dictRemark"` // 字典备注 + DictStatus uint8 `json:"dictStatus" structs:"dictStatus"` // 字典状态 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} diff --git a/server/admin/setting/dict_type/service.go b/server/admin/setting/dict_type/service.go new file mode 100644 index 0000000..05b3b18 --- /dev/null +++ b/server/admin/setting/dict_type/service.go @@ -0,0 +1,133 @@ +package dict_type + +import ( + "time" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/setting" + + "gorm.io/gorm" +) + +type ISettingDictTypeService interface { + All() (res []SettingDictTypeResp, e error) + List(page request.PageReq, listReq SettingDictTypeListReq) (res response.PageResp, e error) + Detail(id uint) (res SettingDictTypeResp, e error) + Add(addReq SettingDictTypeAddReq) (e error) + Edit(editReq SettingDictTypeEditReq) (e error) + Del(delReq SettingDictTypeDelReq) (e error) +} + +// NewSettingDictTypeService 初始化 +func NewSettingDictTypeService(db *gorm.DB) ISettingDictTypeService { + return &settingDictTypeService{db: db} +} + +// settingDictTypeService 字典类型服务实现类 +type settingDictTypeService struct { + db *gorm.DB +} + +// All 字典类型所有 +func (dtSrv settingDictTypeService) All() (res []SettingDictTypeResp, e error) { + var dictTypes []setting.DictType + err := dtSrv.db.Where("is_delete = ?", 0).Order("id desc").Find(&dictTypes).Error + if e = response.CheckErr(err, "All Find err"); e != nil { + return + } + res = []SettingDictTypeResp{} + response.Copy(&res, dictTypes) + return +} + +// List 字典类型列表 +func (dtSrv settingDictTypeService) List(page request.PageReq, listReq SettingDictTypeListReq) (res response.PageResp, e error) { + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + dtModel := dtSrv.db.Model(&setting.DictType{}).Where("is_delete = ?", 0) + if listReq.DictName != "" { + dtModel = dtModel.Where("dict_name like ?", "%"+listReq.DictName+"%") + } + if listReq.DictType != "" { + dtModel = dtModel.Where("dict_type like ?", "%"+listReq.DictType+"%") + } + if listReq.DictStatus >= 0 { + dtModel = dtModel.Where("dict_status = ?", listReq.DictStatus) + } + var count int64 + err := dtModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + var dts []setting.DictType + err = dtModel.Limit(limit).Offset(offset).Order("id desc").Find(&dts).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + dtResp := []SettingDictTypeResp{} + response.Copy(&dtResp, dts) + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: dtResp, + }, nil +} + +// Detail 字典类型详情 +func (dtSrv settingDictTypeService) Detail(id uint) (res SettingDictTypeResp, e error) { + var dt setting.DictType + err := dtSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&dt).Error + if e = response.CheckErrDBNotRecord(err, "字典类型不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, dt) + return +} + +// Add 字典类型新增 +func (dtSrv settingDictTypeService) Add(addReq SettingDictTypeAddReq) (e error) { + if r := dtSrv.db.Where("dict_name = ? AND is_delete = ?", addReq.DictName, 0).Limit(1).First(&setting.DictType{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("字典名称已存在!") + } + if r := dtSrv.db.Where("dict_type = ? AND is_delete = ?", addReq.DictType, 0).Limit(1).First(&setting.DictType{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("字典类型已存在!") + } + var dt setting.DictType + response.Copy(&dt, addReq) + err := dtSrv.db.Create(&dt).Error + e = response.CheckErr(err, "Add Create err") + return +} + +// Edit 字典类型编辑 +func (dtSrv settingDictTypeService) Edit(editReq SettingDictTypeEditReq) (e error) { + err := dtSrv.db.Where("id = ? AND is_delete = ?", editReq.ID, 0).Limit(1).First(&setting.DictType{}).Error + if e = response.CheckErrDBNotRecord(err, "字典类型不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + if r := dtSrv.db.Where("id != ? AND dict_name = ? AND is_delete = ?", editReq.ID, editReq.DictName, 0).Limit(1).First(&setting.DictType{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("字典名称已存在!") + } + if r := dtSrv.db.Where("id != ? AND dict_type = ? AND is_delete = ?", editReq.ID, editReq.DictType, 0).Limit(1).First(&setting.DictType{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("字典类型已存在!") + } + var dt setting.DictType + response.Copy(&dt, editReq) + err = dtSrv.db.Save(&dt).Error + e = response.CheckErr(err, "Edit Save err") + return +} + +// Del 字典类型删除 +func (dtSrv settingDictTypeService) Del(delReq SettingDictTypeDelReq) (e error) { + err := dtSrv.db.Model(&setting.DictType{}).Where("id IN ?", delReq.Ids).Updates( + setting.DictType{IsDelete: 1, DeleteTime: time.Now().Unix()}).Error + return response.CheckErr(err, "Del Update err") +} diff --git a/server/admin/setting/protocol/protocol.go b/server/admin/setting/protocol/protocol.go new file mode 100644 index 0000000..95bc8d7 --- /dev/null +++ b/server/admin/setting/protocol/protocol.go @@ -0,0 +1,45 @@ +package protocol + +import ( + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func ProtocolRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewSettingProtocolService(db) + + handle := protocolHandler{Service: server} + + rg = rg.Group("/setting", middleware.TokenAuth()) + rg.GET("/protocol/detail", handle.Detail) + rg.POST("/protocol/save", handle.save) +} + +type protocolHandler struct { + Service ISettingProtocolService +} + +// detail 获取政策信息 +func (ph protocolHandler) Detail(c *gin.Context) { + res, err := ph.Service.Detail() + response.CheckAndRespWithData(c, res, err) +} + +// save 保存政策信息 +func (ph protocolHandler) save(c *gin.Context) { + var pReq SettingProtocolReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &pReq)) { + return + } + response.CheckAndResp(c, ph.Service.Save(pReq)) +} diff --git a/server/admin/setting/protocol/schema.go b/server/admin/setting/protocol/schema.go new file mode 100644 index 0000000..7692166 --- /dev/null +++ b/server/admin/setting/protocol/schema.go @@ -0,0 +1,13 @@ +package protocol + +//SettingProtocolItem 政策通用参数 +type SettingProtocolItem struct { + Name string `form:"name" json:"name"` // 名称 + Content string `form:"content" json:"content"` // 内容 +} + +//SettingProtocolReq 保存政策信息参数 +type SettingProtocolReq struct { + Service SettingProtocolItem `form:"service" json:"service"` // 服务协议 + Privacy SettingProtocolItem `form:"privacy" json:"privacy"` // 隐私协议 +} diff --git a/server/admin/setting/protocol/service.go b/server/admin/setting/protocol/service.go new file mode 100644 index 0000000..08eca2b --- /dev/null +++ b/server/admin/setting/protocol/service.go @@ -0,0 +1,67 @@ +package protocol + +import ( + "x_admin/core/response" + "x_admin/util" + + "gorm.io/gorm" +) + +type ISettingProtocolService interface { + Detail() (res map[string]interface{}, e error) + Save(pReq SettingProtocolReq) (e error) +} + +// NewSettingProtocolService 初始化 +func NewSettingProtocolService(db *gorm.DB) ISettingProtocolService { + return &settingProtocolService{db: db} +} + +// settingProtocolService 政策协议服务实现类 +type settingProtocolService struct { + db *gorm.DB +} + +// Detail 获取政策协议信息 +func (cSrv settingProtocolService) Detail() (res map[string]interface{}, e error) { + defaultVal := `{"name":"","content":""}` + json, err := util.ConfigUtil.GetVal(cSrv.db, "protocol", "service", defaultVal) + if e = response.CheckErr(err, "Detail GetVal service err"); e != nil { + return + } + var service map[string]interface{} + if e = response.CheckErr(util.ToolsUtil.JsonToObj(json, &service), "Detail JsonToObj service err"); e != nil { + return + } + json, err = util.ConfigUtil.GetVal(cSrv.db, "protocol", "privacy", defaultVal) + if e = response.CheckErr(err, "Detail GetVal privacy err"); e != nil { + return + } + var privacy map[string]interface{} + if e = response.CheckErr(util.ToolsUtil.JsonToObj(json, &privacy), "Detail JsonToObj privacy err"); e != nil { + return + } + return map[string]interface{}{ + "service": service, + "privacy": privacy, + }, nil +} + +// Save 保存政策协议信息 +func (cSrv settingProtocolService) Save(pReq SettingProtocolReq) (e error) { + serviceJson, err := util.ToolsUtil.ObjToJson(pReq.Service) + if e = response.CheckErr(err, "Save ObjToJson service err"); e != nil { + return + } + privacyJson, err := util.ToolsUtil.ObjToJson(pReq.Privacy) + if e = response.CheckErr(err, "Save ObjToJson privacy err"); e != nil { + return + } + err = util.ConfigUtil.Set(cSrv.db, "protocol", "service", serviceJson) + if e = response.CheckErr(err, "Save Set service err"); e != nil { + return + } + err = util.ConfigUtil.Set(cSrv.db, "protocol", "privacy", privacyJson) + e = response.CheckErr(err, "Save Set privacy err") + return +} diff --git a/server/admin/setting/storage/schema.go b/server/admin/setting/storage/schema.go new file mode 100644 index 0000000..6dbfac4 --- /dev/null +++ b/server/admin/setting/storage/schema.go @@ -0,0 +1,23 @@ +package storage + +//SettingStorageDetailReq 存储详情参数 +type SettingStorageDetailReq struct { + Alias string `form:"alias" binding:"required,oneof=local qiniu qcloud aliyun"` // 别名: [local,qiniu,qcloud,aliyun] +} + +//SettingStorageEditReq 存储编辑参数 +type SettingStorageEditReq struct { + Alias string `form:"alias" binding:"required,oneof=local qiniu qcloud aliyun"` // 别名: [local,qiniu,qcloud,aliyun] + Status int `form:"status" binding:"oneof=0 1"` // 状态: 0/1 + Bucket string `form:"bucket"` // 存储空间名 + SecretKey string `form:"secretKey"` // SK + AccessKey string `form:"accessKey"` // AK + Domain string `form:"domain"` // 访问域名 + Region string `form:"region"` // 地区,腾讯存储特有 +} + +//SettingStorageChangeReq 存储切换参数 +type SettingStorageChangeReq struct { + Alias string `form:"alias" binding:"required,oneof=local qiniu qcloud aliyun"` // 别名: [local,qiniu,qcloud,aliyun] + Status int `form:"status" binding:"oneof=0 1"` // 状态: 0/1 +} diff --git a/server/admin/setting/storage/service.go b/server/admin/setting/storage/service.go new file mode 100644 index 0000000..cb2cebe --- /dev/null +++ b/server/admin/setting/storage/service.go @@ -0,0 +1,103 @@ +package storage + +import ( + "fmt" + "x_admin/core/response" + "x_admin/util" + + "gorm.io/gorm" +) + +type ISettingStorageService interface { + List() ([]map[string]interface{}, error) + Detail(alias string) (res map[string]interface{}, e error) + Edit(editReq SettingStorageEditReq) (e error) + Change(alias string, status int) (e error) +} + +// NewSettingStorageService 初始化 +func NewSettingStorageService(db *gorm.DB) ISettingStorageService { + return &settingStorageService{db: db} +} + +// settingStorageService 存储配置服务实现类 +type settingStorageService struct { + db *gorm.DB +} + +var storageList = []map[string]interface{}{ + {"name": "本地存储", "alias": "local", "describe": "存储在本地服务器", "status": 0}, +} + +// List 存储列表 +func (sSrv settingStorageService) List() ([]map[string]interface{}, error) { + // TODO: engine默认local + engine := "local" + mapList := storageList + for i := 0; i < len(mapList); i++ { + if engine == mapList[i]["alias"] { + mapList[i]["status"] = 1 + } + } + return mapList, nil +} + +// Detail 存储详情 +func (sSrv settingStorageService) Detail(alias string) (res map[string]interface{}, e error) { + // TODO: engine默认local + engine := "local" + cnf, err := util.ConfigUtil.GetMap(sSrv.db, "storage", alias) + if e = response.CheckErr(err, "Detail GetMap err"); e != nil { + return + } + status := 0 + if engine == alias { + status = 1 + } + return map[string]interface{}{ + "name": cnf["name"], + "alias": alias, + "status": status, + }, nil +} + +// Edit 存储编辑 +func (sSrv settingStorageService) Edit(editReq SettingStorageEditReq) (e error) { + // TODO: engine默认local + engine := "local" + if engine != editReq.Alias { + return response.Failed.Make(fmt.Sprintf("engine:%s 暂时不支持", editReq.Alias)) + } + json, err := util.ToolsUtil.ObjToJson(map[string]interface{}{"name": "本地存储"}) + if e = response.CheckErr(err, "Edit ObjToJson err"); e != nil { + return + } + err = util.ConfigUtil.Set(sSrv.db, "storage", editReq.Alias, json) + if e = response.CheckErr(err, "Edit Set alias err"); e != nil { + return + } + if editReq.Status == 1 { + err = util.ConfigUtil.Set(sSrv.db, "storage", "default", editReq.Alias) + } else { + util.ConfigUtil.Set(sSrv.db, "storage", "default", "") + } + e = response.CheckErr(err, "Edit Set default err") + return +} + +// Change 存储切换 +func (sSrv settingStorageService) Change(alias string, status int) (e error) { + // TODO: engine默认local + engine := "local" + if engine != alias { + return response.Failed.Make(fmt.Sprintf("engine:%s 暂时不支持", alias)) + } + var err error + if engine == alias && status == 0 { + err = util.ConfigUtil.Set(sSrv.db, "storage", "default", "") + } else { + err = util.ConfigUtil.Set(sSrv.db, "storage", "default", alias) + } + e = response.CheckErr(err, "Change Set err") + return +} diff --git a/server/admin/setting/storage/storage.go b/server/admin/setting/storage/storage.go new file mode 100644 index 0000000..66d2741 --- /dev/null +++ b/server/admin/setting/storage/storage.go @@ -0,0 +1,66 @@ +package storage + +import ( + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func StorageRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewSettingStorageService(db) + + handle := storageHandler{Service: server} + + rg = rg.Group("/setting", middleware.TokenAuth()) + rg.GET("/storage/list", handle.List) + rg.GET("/storage/detail", handle.Detail) + rg.POST("/storage/edit", handle.Edit) + rg.POST("/storage/change", handle.change) +} + +type storageHandler struct { + Service ISettingStorageService +} + +// list 存储列表 +func (sh storageHandler) List(c *gin.Context) { + res, err := sh.Service.List() + response.CheckAndRespWithData(c, res, err) +} + +// detail 存储详情 +func (sh storageHandler) Detail(c *gin.Context) { + var detailReq SettingStorageDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := sh.Service.Detail(detailReq.Alias) + response.CheckAndRespWithData(c, res, err) +} + +// edit 存储编辑 +func (sh storageHandler) Edit(c *gin.Context) { + var editReq SettingStorageEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &editReq)) { + return + } + response.CheckAndResp(c, sh.Service.Edit(editReq)) +} + +// change 存储切换 +func (sh storageHandler) change(c *gin.Context) { + var changeReq SettingStorageChangeReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &changeReq)) { + return + } + response.CheckAndResp(c, sh.Service.Change(changeReq.Alias, changeReq.Status)) +} diff --git a/server/admin/setting/website/schema.go b/server/admin/setting/website/schema.go new file mode 100644 index 0000000..2ef8842 --- /dev/null +++ b/server/admin/setting/website/schema.go @@ -0,0 +1,11 @@ +package website + +//SettingWebsiteReq 保存网站信息参数 +type SettingWebsiteReq struct { + Name string `form:"name"` // 网站名称 + Logo string `form:"logo"` // 网站图标 + Favicon string `form:"favicon"` // 网站LOGO + Backdrop string `form:"backdrop"` // 登录页广告图 + ShopName string `form:"shopName"` // 商城名称 + ShopLogo string `form:"shopLogo"` // 商城Logo +} diff --git a/server/admin/setting/website/service.go b/server/admin/setting/website/service.go new file mode 100644 index 0000000..a78bff6 --- /dev/null +++ b/server/admin/setting/website/service.go @@ -0,0 +1,66 @@ +package website + +import ( + "x_admin/core/response" + "x_admin/util" + + "gorm.io/gorm" +) + +type ISettingWebsiteService interface { + Detail() (res map[string]string, e error) + Save(wsReq SettingWebsiteReq) (e error) +} + +// NewSettingWebsiteService 初始化 +func NewSettingWebsiteService(db *gorm.DB) ISettingWebsiteService { + return &settingWebsiteService{db: db} +} + +// settingWebsiteService 网站信息配置服务实现类 +type settingWebsiteService struct { + db *gorm.DB +} + +// Detail 获取网站信息 +func (wSrv settingWebsiteService) Detail() (res map[string]string, e error) { + data, err := util.ConfigUtil.Get(wSrv.db, "website") + if e = response.CheckErr(err, "Detail Get err"); e != nil { + return + } + return map[string]string{ + "name": data["name"], + "logo": util.UrlUtil.ToAbsoluteUrl(data["logo"]), + "favicon": util.UrlUtil.ToAbsoluteUrl(data["favicon"]), + "backdrop": util.UrlUtil.ToAbsoluteUrl(data["backdrop"]), + "shopName": data["shopName"], + "shopLogo": util.UrlUtil.ToAbsoluteUrl(data["shopLogo"]), + }, nil +} + +// Save 保存网站信息 +func (wSrv settingWebsiteService) Save(wsReq SettingWebsiteReq) (e error) { + err := util.ConfigUtil.Set(wSrv.db, "website", "name", wsReq.Name) + if e = response.CheckErr(err, "Save Set name err"); e != nil { + return + } + err = util.ConfigUtil.Set(wSrv.db, "website", "logo", util.UrlUtil.ToRelativeUrl(wsReq.Logo)) + if e = response.CheckErr(err, "Save Set logo err"); e != nil { + return + } + err = util.ConfigUtil.Set(wSrv.db, "website", "favicon", util.UrlUtil.ToRelativeUrl(wsReq.Favicon)) + if e = response.CheckErr(err, "Save Set favicon err"); e != nil { + return + } + err = util.ConfigUtil.Set(wSrv.db, "website", "backdrop", util.UrlUtil.ToRelativeUrl(wsReq.Backdrop)) + if e = response.CheckErr(err, "Save Set backdrop err"); e != nil { + return + } + err = util.ConfigUtil.Set(wSrv.db, "website", "shopName", wsReq.ShopName) + if e = response.CheckErr(err, "Save Set shopName err"); e != nil { + return + } + err = util.ConfigUtil.Set(wSrv.db, "website", "shopLogo", util.UrlUtil.ToRelativeUrl(wsReq.ShopLogo)) + e = response.CheckErr(err, "Save Set shopLogo err") + return +} diff --git a/server/admin/setting/website/website.go b/server/admin/setting/website/website.go new file mode 100644 index 0000000..14806e0 --- /dev/null +++ b/server/admin/setting/website/website.go @@ -0,0 +1,45 @@ +package website + +import ( + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func WebsiteRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewSettingWebsiteService(db) + + handle := websiteHandler{Service: server} + + rg = rg.Group("/setting", middleware.TokenAuth()) + rg.GET("/website/detail", handle.Detail) + rg.POST("/website/save", handle.save) +} + +type websiteHandler struct { + Service ISettingWebsiteService +} + +// detail 获取网站信息 +func (wh websiteHandler) Detail(c *gin.Context) { + res, err := wh.Service.Detail() + response.CheckAndRespWithData(c, res, err) +} + +// save 保存网站信息 +func (wh websiteHandler) save(c *gin.Context) { + var wsReq SettingWebsiteReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &wsReq)) { + return + } + response.CheckAndResp(c, wh.Service.Save(wsReq)) +} diff --git a/server/admin/system/admin/admin.go b/server/admin/system/admin/admin.go new file mode 100644 index 0000000..347a336 --- /dev/null +++ b/server/admin/system/admin/admin.go @@ -0,0 +1,114 @@ +package admin + +import ( + "x_admin/config" + "x_admin/core/request" + "x_admin/core/response" + + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +// func AdminRoute(rg *gin.RouterGroup) { +// db := core.GetDB() + +// permSrv := role.NewSystemAuthPermService(db) +// roleSrv := role.NewSystemAuthRoleService(db, permSrv) +// adminSrv := NewSystemAuthAdminService(db, permSrv, roleSrv) +// // service := NewSystemLoginService(db, adminSrv) + +// handle := AdminHandler{Service: adminSrv} + +// rg = rg.Group("/system", middleware.TokenAuth()) + +// rg.GET("/admin/self", handle.self) +// rg.GET("/admin/list", handle.List) +// rg.GET("/admin/detail", handle.Detail) +// rg.POST("/admin/add", middleware.RecordLog("管理员新增"), handle.Add) +// rg.POST("/admin/edit", middleware.RecordLog("管理员编辑"), handle.Edit) +// rg.POST("/admin/upInfo", middleware.RecordLog("管理员更新"), handle.upInfo) +// rg.POST("/admin/del", middleware.RecordLog("管理员删除"), handle.Del) +// rg.POST("/admin/disable", middleware.RecordLog("管理员状态切换"), handle.disable) +// } + +type AdminHandler struct { + Service ISystemAuthAdminService +} + +// self 管理员信息 +func (ah AdminHandler) Self(c *gin.Context) { + adminId := config.AdminConfig.GetAdminId(c) + res, err := ah.Service.Self(adminId) + response.CheckAndRespWithData(c, res, err) +} + +// list 管理员列表 +func (ah AdminHandler) List(c *gin.Context) { + var page request.PageReq + var listReq SystemAuthAdminListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := ah.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail 管理员详细 +func (ah AdminHandler) Detail(c *gin.Context) { + var detailReq SystemAuthAdminDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := ah.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// add 管理员新增 +func (ah AdminHandler) Add(c *gin.Context) { + var addReq SystemAuthAdminAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + return + } + response.CheckAndResp(c, ah.Service.Add(addReq)) +} + +// edit 管理员编辑 +func (ah AdminHandler) Edit(c *gin.Context) { + var editReq SystemAuthAdminEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { + return + } + response.CheckAndResp(c, ah.Service.Edit(c, editReq)) +} + +// upInfo 管理员更新 +func (ah AdminHandler) UpInfo(c *gin.Context) { + var updateReq SystemAuthAdminUpdateReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &updateReq)) { + return + } + response.CheckAndResp(c, ah.Service.Update( + c, updateReq, config.AdminConfig.GetAdminId(c))) +} + +// del 管理员删除 +func (ah AdminHandler) Del(c *gin.Context) { + var delReq SystemAuthAdminDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, ah.Service.Del(c, delReq.ID)) +} + +// disable 管理员状态切换 +func (ah AdminHandler) Disable(c *gin.Context) { + var disableReq SystemAuthAdminDisableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &disableReq)) { + return + } + response.CheckAndResp(c, ah.Service.Disable(c, disableReq.ID)) +} diff --git a/server/admin/system/admin/schema.go b/server/admin/system/admin/schema.go new file mode 100644 index 0000000..54f905a --- /dev/null +++ b/server/admin/system/admin/schema.go @@ -0,0 +1,102 @@ +package admin + +import "x_admin/core" + +//SystemAuthAdminListReq 管理员列表参数 +type SystemAuthAdminListReq struct { + Username string `form:"username"` // 账号 + Nickname string `form:"nickname"` // 昵称 + Role int `form:"role,default=-1"` // 角色ID +} + +//SystemAuthAdminDetailReq 管理员详情参数 +type SystemAuthAdminDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthAdminAddReq 管理员新增参数 +type SystemAuthAdminAddReq struct { + DeptId uint `form:"deptId" binding:"required,gt=0"` // 部门ID + PostId uint `form:"postId" binding:"required,gt=0"` // 岗位ID + Username string `form:"username" binding:"required,min=2,max=20"` // 账号 + Nickname string `form:"nickname" binding:"required,min=2,max=30"` // 昵称 + Password string `form:"password" binding:"required"` // 密码 + Avatar string `form:"avatar" binding:"required"` // 头像 + Role uint `form:"role" binding:"gte=0"` // 角色 + Sort int `form:"sort" binding:"gte=0"` // 排序 + IsDisable uint8 `form:"isDisable" binding:"oneof=0 1"` // 是否禁用: [0=否, 1=是] + IsMultipoint uint8 `form:"isMultipoint" binding:"oneof=0 1"` // 多端登录: [0=否, 1=是] +} + +//SystemAuthAdminEditReq 管理员编辑参数 +type SystemAuthAdminEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + DeptId uint `form:"deptId" binding:"required,gt=0"` // 部门ID + PostId uint `form:"postId" binding:"required,gt=0"` // 岗位ID + Username string `form:"username" binding:"required,min=2,max=20"` // 账号 + Nickname string `form:"nickname" binding:"required,min=2,max=30"` // 昵称 + Password string `form:"password"` // 密码 + Avatar string `form:"avatar"` // 头像 + Role uint `form:"role" binding:"gte=0"` // 角色 + Sort int `form:"sort" binding:"gte=0"` // 排序 + IsDisable uint8 `form:"isDisable" binding:"oneof=0 1"` // 是否禁用: [0=否, 1=是] + IsMultipoint uint8 `form:"isMultipoint" binding:"oneof=0 1"` // 多端登录: [0=否, 1=是] +} + +//SystemAuthAdminUpdateReq 管理员更新参数 +type SystemAuthAdminUpdateReq struct { + Nickname string `form:"nickname" binding:"required,min=2,max=30"` // 昵称 + Avatar string `form:"avatar"` // 头像 + Password string `form:"password" binding:"required"` // 密码 + CurrPassword string `form:"currPassword" binding:"required,min=6,max=32"` // 密码 +} + +//SystemAuthAdminDelReq 管理员删除参数 +type SystemAuthAdminDelReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthAdminDisableReq 管理员状态切换参数 +type SystemAuthAdminDisableReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthAdminResp 管理员返回信息 +type SystemAuthAdminResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Username string `json:"username" structs:"username"` // 账号 + Nickname string `json:"nickname" structs:"nickname"` // 昵称 + Avatar string `json:"avatar" structs:"avatar"` // 头像 + Role string `json:"role" structs:"role"` // 角色 + DeptId uint `json:"deptId" structs:"deptId"` // 部门ID + PostId uint `json:"postId" structs:"postId"` // 岗位ID + Dept string `json:"dept" structs:"dept"` // 部门 + IsMultipoint uint8 `json:"isMultipoint" structs:"isMultipoint"` // 多端登录: [0=否, 1=是] + IsDisable uint8 `json:"isDisable" structs:"isDisable"` // 是否禁用: [0=否, 1=是] + LastLoginIp string `json:"lastLoginIp" structs:"lastLoginIp"` // 最后登录IP + LastLoginTime core.TsTime `json:"lastLoginTime" structs:"lastLoginTime"` // 最后登录时间 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +//SystemAuthAdminSelfOneResp 当前管理员返回部分信息 +type SystemAuthAdminSelfOneResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Username string `json:"username" structs:"username"` // 账号 + Nickname string `json:"nickname" structs:"nickname"` // 昵称 + Avatar string `json:"avatar" structs:"avatar"` // 头像 + Role string `json:"role" structs:"role"` // 角色 + Dept string `json:"dept" structs:"dept"` // 部门 + IsMultipoint uint8 `json:"isMultipoint" structs:"isMultipoint"` // 多端登录: [0=否, 1=是] + IsDisable uint8 `json:"isDisable" structs:"isDisable"` // 是否禁用: [0=否, 1=是] + LastLoginIp string `json:"lastLoginIp" structs:"lastLoginIp"` // 最后登录IP + LastLoginTime core.TsTime `json:"lastLoginTime" structs:"lastLoginTime"` // 最后登录时间 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +//SystemAuthAdminSelfResp 当前系统管理员返回信息 +type SystemAuthAdminSelfResp struct { + User SystemAuthAdminSelfOneResp `json:"user" structs:"user"` // 用户信息 + Permissions []string `json:"permissions" structs:"permissions"` // 权限集合: [[*]=>所有权限, ['article:add']=>部分权限] +} diff --git a/server/admin/system/admin/service.go b/server/admin/system/admin/service.go new file mode 100644 index 0000000..87c1f68 --- /dev/null +++ b/server/admin/system/admin/service.go @@ -0,0 +1,407 @@ +package admin + +import ( + "fmt" + + "strconv" + "strings" + "time" + "x_admin/admin/system/role" + "x_admin/config" + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "github.com/fatih/structs" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type ISystemAuthAdminService interface { + FindByUsername(username string) (admin system.SystemAuthAdmin, err error) + Self(adminId uint) (res SystemAuthAdminSelfResp, e error) + List(page request.PageReq, listReq SystemAuthAdminListReq) (res response.PageResp, e error) + Detail(id uint) (res SystemAuthAdminResp, e error) + Add(addReq SystemAuthAdminAddReq) (e error) + Edit(c *gin.Context, editReq SystemAuthAdminEditReq) (e error) + Update(c *gin.Context, updateReq SystemAuthAdminUpdateReq, adminId uint) (e error) + Del(c *gin.Context, id uint) (e error) + Disable(c *gin.Context, id uint) (e error) + CacheAdminUserByUid(id uint) (err error) +} + +// NewSystemAuthAdminService 初始化 +func NewSystemAuthAdminService(db *gorm.DB, permSrv role.ISystemAuthPermService, roleSrv role.ISystemAuthRoleService) ISystemAuthAdminService { + return &systemAuthAdminService{db: db, permSrv: permSrv, roleSrv: roleSrv} +} + +// systemAuthAdminService 系统管理员服务实现类 +type systemAuthAdminService struct { + db *gorm.DB + permSrv role.ISystemAuthPermService + roleSrv role.ISystemAuthRoleService +} + +// FindByUsername 根据账号查找管理员 +func (adminSrv systemAuthAdminService) FindByUsername(username string) (admin system.SystemAuthAdmin, err error) { + err = adminSrv.db.Where("username = ?", username).Limit(1).First(&admin).Error + return +} + +// Self 当前管理员 +func (adminSrv systemAuthAdminService) Self(adminId uint) (res SystemAuthAdminSelfResp, e error) { + // 管理员信息 + var sysAdmin system.SystemAuthAdmin + err := adminSrv.db.Where("id = ? AND is_delete = ?", adminId, 0).Limit(1).First(&sysAdmin).Error + if e = response.CheckErr(err, "Self First err"); e != nil { + return + } + // 角色权限 + var auths []string + if adminId > 1 { + roleId, _ := strconv.ParseUint(sysAdmin.Role, 10, 32) + var menuIds []uint + if menuIds, e = adminSrv.permSrv.SelectMenuIdsByRoleId(uint(roleId)); e != nil { + return + } + if len(menuIds) > 0 { + var menus []system.SystemAuthMenu + err := adminSrv.db.Where( + "id in ? AND is_disable = ? AND menu_type in ?", menuIds, 0, []string{"C", "A"}).Order( + "menu_sort, id").Find(&menus).Error + if e = response.CheckErr(err, "Self SystemAuthMenu Find err"); e != nil { + return + } + if len(menus) > 0 { + for _, v := range menus { + auths = append(auths, strings.Trim(v.Perms, " ")) + } + } + } + if len(auths) > 0 { + auths = append(auths, "") + } + } else { + auths = append(auths, "*") + } + var admin SystemAuthAdminSelfOneResp + response.Copy(&admin, sysAdmin) + admin.Dept = strconv.FormatUint(uint64(sysAdmin.DeptId), 10) + admin.Avatar = util.UrlUtil.ToAbsoluteUrl(sysAdmin.Avatar) + return SystemAuthAdminSelfResp{User: admin, Permissions: auths}, nil +} + +// List 管理员列表 +func (adminSrv systemAuthAdminService) List(page request.PageReq, listReq SystemAuthAdminListReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + adminTbName := core.DBTableName(&system.SystemAuthAdmin{}) + roleTbName := core.DBTableName(&system.SystemAuthRole{}) + deptTbName := core.DBTableName(&system.SystemAuthDept{}) + adminModel := adminSrv.db.Table(adminTbName+" AS admin").Where("admin.is_delete = ?", 0).Joins( + fmt.Sprintf("LEFT JOIN %s ON admin.role = %s.id", roleTbName, roleTbName)).Joins( + fmt.Sprintf("LEFT JOIN %s ON admin.dept_id = %s.id", deptTbName, deptTbName)).Select( + fmt.Sprintf("admin.*, %s.name as dept, %s.name as role", deptTbName, roleTbName)) + // 条件 + if listReq.Username != "" { + adminModel = adminModel.Where("username like ?", "%"+listReq.Username+"%") + } + if listReq.Nickname != "" { + adminModel = adminModel.Where("nickname like ?", "%"+listReq.Nickname+"%") + } + if listReq.Role >= 0 { + adminModel = adminModel.Where("role = ?", listReq.Role) + } + // 总数 + var count int64 + err := adminModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + // 数据 + var adminResp []SystemAuthAdminResp + err = adminModel.Limit(limit).Offset(offset).Order("id desc, sort desc").Find(&adminResp).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + for i := 0; i < len(adminResp); i++ { + adminResp[i].Avatar = util.UrlUtil.ToAbsoluteUrl(adminResp[i].Avatar) + if adminResp[i].ID == 1 { + adminResp[i].Role = "系统管理员" + } + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: adminResp, + }, nil +} + +// Detail 管理员详细 +func (adminSrv systemAuthAdminService) Detail(id uint) (res SystemAuthAdminResp, e error) { + var sysAdmin system.SystemAuthAdmin + err := adminSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&sysAdmin).Error + if e = response.CheckErrDBNotRecord(err, "账号已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, sysAdmin) + res.Avatar = util.UrlUtil.ToAbsoluteUrl(res.Avatar) + if res.Dept == "" { + res.Dept = strconv.FormatUint(uint64(res.DeptId), 10) + } + return +} + +// Add 管理员新增 +func (adminSrv systemAuthAdminService) Add(addReq SystemAuthAdminAddReq) (e error) { + var sysAdmin system.SystemAuthAdmin + // 检查username + r := adminSrv.db.Where("username = ? AND is_delete = ?", addReq.Username, 0).Limit(1).Find(&sysAdmin) + err := r.Error + if e = response.CheckErr(err, "Add Find by username err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("账号已存在换一个吧!") + } + // 检查nickname + r = adminSrv.db.Where("nickname = ? AND is_delete = ?", addReq.Nickname, 0).Limit(1).Find(&sysAdmin) + err = r.Error + if e = response.CheckErr(err, "Add Find by nickname err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("昵称已存在换一个吧!") + } + var roleResp role.SystemAuthRoleResp + if roleResp, e = adminSrv.roleSrv.Detail(addReq.Role); e != nil { + return + } + if roleResp.IsDisable > 0 { + return response.AssertArgumentError.Make("当前角色已被禁用!") + } + passwdLen := len(addReq.Password) + if !(passwdLen >= 6 && passwdLen <= 20) { + return response.Failed.Make("密码必须在6~20位") + } + salt := util.ToolsUtil.RandomString(5) + response.Copy(&sysAdmin, addReq) + sysAdmin.Role = strconv.FormatUint(uint64(addReq.Role), 10) + sysAdmin.Salt = salt + sysAdmin.Password = util.ToolsUtil.MakeMd5(strings.Trim(addReq.Password, " ") + salt) + if addReq.Avatar == "" { + addReq.Avatar = "/api/static/backend_avatar.png" + } + sysAdmin.Avatar = util.UrlUtil.ToRelativeUrl(addReq.Avatar) + err = adminSrv.db.Create(&sysAdmin).Error + e = response.CheckErr(err, "Add Create err") + return +} + +// Edit 管理员编辑 +func (adminSrv systemAuthAdminService) Edit(c *gin.Context, editReq SystemAuthAdminEditReq) (e error) { + // 检查id + err := adminSrv.db.Where("id = ? AND is_delete = ?", editReq.ID, 0).Limit(1).First(&system.SystemAuthAdmin{}).Error + if e = response.CheckErrDBNotRecord(err, "账号不存在了!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + // 检查username + var admin system.SystemAuthAdmin + r := adminSrv.db.Where("username = ? AND is_delete = ? AND id != ?", editReq.Username, 0, editReq.ID).Find(&admin) + err = r.Error + if e = response.CheckErr(err, "Edit Find by username err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("账号已存在换一个吧!") + } + // 检查nickname + r = adminSrv.db.Where("nickname = ? AND is_delete = ? AND id != ?", editReq.Nickname, 0, editReq.ID).Find(&admin) + err = r.Error + if e = response.CheckErr(err, "Edit Find by nickname err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("昵称已存在换一个吧!") + } + // 检查role + if editReq.Role > 0 && editReq.ID != 1 { + if _, e = adminSrv.roleSrv.Detail(editReq.Role); e != nil { + return + } + } + // 更新管理员信息 + adminMap := structs.Map(editReq) + delete(adminMap, "ID") + adminMap["Avatar"] = util.UrlUtil.ToRelativeUrl(editReq.Avatar) + role := editReq.Role + if editReq.ID == 1 { + role = 0 + } + adminMap["Role"] = strconv.FormatUint(uint64(role), 10) + if editReq.ID == 1 { + delete(adminMap, "Username") + } + if editReq.Password != "" { + passwdLen := len(editReq.Password) + if !(passwdLen >= 6 && passwdLen <= 20) { + return response.Failed.Make("密码必须在6~20位") + } + salt := util.ToolsUtil.RandomString(5) + adminMap["Salt"] = salt + adminMap["Password"] = util.ToolsUtil.MakeMd5(strings.Trim(editReq.Password, "") + salt) + } else { + delete(adminMap, "Password") + } + err = adminSrv.db.Model(&admin).Where("id = ?", editReq.ID).Updates(adminMap).Error + if e = response.CheckErr(err, "Edit Updates err"); e != nil { + return + } + adminSrv.CacheAdminUserByUid(editReq.ID) + // 如果更改自己的密码,则删除旧缓存 + adminId := config.AdminConfig.GetAdminId(c) + if editReq.Password != "" && editReq.ID == adminId { + token := c.Request.Header.Get("token") + util.RedisUtil.Del(config.AdminConfig.BackstageTokenKey + token) + adminSetKey := config.AdminConfig.BackstageTokenSet + strconv.FormatUint(uint64(adminId), 10) + ts := util.RedisUtil.SGet(adminSetKey) + if len(ts) > 0 { + var tokenKeys []string + for _, t := range ts { + tokenKeys = append(tokenKeys, config.AdminConfig.BackstageTokenKey+t) + } + util.RedisUtil.Del(tokenKeys...) + } + util.RedisUtil.Del(adminSetKey) + util.RedisUtil.SSet(adminSetKey, token) + } + return +} + +// Update 管理员更新 +func (adminSrv systemAuthAdminService) Update(c *gin.Context, updateReq SystemAuthAdminUpdateReq, adminId uint) (e error) { + // 检查id + var admin system.SystemAuthAdmin + err := adminSrv.db.Where("id = ? AND is_delete = ?", adminId, 0).Limit(1).First(&admin).Error + if e = response.CheckErrDBNotRecord(err, "账号不存在了!"); e != nil { + return + } + if e = response.CheckErr(err, "Update First err"); e != nil { + return + } + // 更新管理员信息 + adminMap := structs.Map(updateReq) + delete(adminMap, "CurrPassword") + avatar := "/api/static/backend_avatar.png" + if updateReq.Avatar != "" { + avatar = updateReq.Avatar + } + adminMap["Avatar"] = util.UrlUtil.ToRelativeUrl(avatar) + delete(adminMap, "aaa") + if updateReq.Password != "" { + currPass := util.ToolsUtil.MakeMd5(updateReq.CurrPassword + admin.Salt) + if currPass != admin.Password { + return response.Failed.Make("当前密码不正确!") + } + passwdLen := len(updateReq.Password) + if !(passwdLen >= 6 && passwdLen <= 20) { + return response.Failed.Make("密码必须在6~20位") + } + salt := util.ToolsUtil.RandomString(5) + adminMap["Salt"] = salt + adminMap["Password"] = util.ToolsUtil.MakeMd5(strings.Trim(updateReq.Password, " ") + salt) + } else { + delete(adminMap, "Password") + } + err = adminSrv.db.Model(&admin).Updates(adminMap).Error + if e = response.CheckErr(err, "Update Updates err"); e != nil { + return + } + adminSrv.CacheAdminUserByUid(adminId) + // 如果更改自己的密码,则删除旧缓存 + if updateReq.Password != "" { + token := c.Request.Header.Get("token") + util.RedisUtil.Del(config.AdminConfig.BackstageTokenKey + token) + adminSetKey := config.AdminConfig.BackstageTokenSet + strconv.FormatUint(uint64(adminId), 10) + ts := util.RedisUtil.SGet(adminSetKey) + if len(ts) > 0 { + var tokenKeys []string + for _, t := range ts { + tokenKeys = append(tokenKeys, config.AdminConfig.BackstageTokenKey+t) + } + util.RedisUtil.Del(tokenKeys...) + } + util.RedisUtil.Del(adminSetKey) + util.RedisUtil.SSet(adminSetKey, token) + } + return +} + +// Del 管理员删除 +func (adminSrv systemAuthAdminService) Del(c *gin.Context, id uint) (e error) { + var admin system.SystemAuthAdmin + err := adminSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&admin).Error + if e = response.CheckErrDBNotRecord(err, "账号已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Del First err"); e != nil { + return + } + if id == 1 { + return response.AssertArgumentError.Make("系统管理员不允许删除!") + } + if id == config.AdminConfig.GetAdminId(c) { + return response.AssertArgumentError.Make("不能删除自己!") + } + err = adminSrv.db.Model(&admin).Updates(system.SystemAuthAdmin{IsDelete: 1, DeleteTime: time.Now().Unix()}).Error + e = response.CheckErr(err, "Del Updates err") + return +} + +// Disable 管理员状态切换 +func (adminSrv systemAuthAdminService) Disable(c *gin.Context, id uint) (e error) { + var admin system.SystemAuthAdmin + err := adminSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).Find(&admin).Error + if e = response.CheckErr(err, "Disable Find err"); e != nil { + return + } + if admin.ID == 0 { + return response.AssertArgumentError.Make("账号已不存在!") + } + if id == config.AdminConfig.GetAdminId(c) { + return response.AssertArgumentError.Make("不能禁用自己!") + } + var isDisable uint8 + if admin.IsDisable == 0 { + isDisable = 1 + } + err = adminSrv.db.Model(&admin).Updates(system.SystemAuthAdmin{IsDisable: isDisable, UpdateTime: time.Now().Unix()}).Error + e = response.CheckErr(err, "Disable Updates err") + return +} + +// CacheAdminUserByUid 缓存管理员 +func (adminSrv systemAuthAdminService) CacheAdminUserByUid(id uint) (err error) { + var admin system.SystemAuthAdmin + err = adminSrv.db.Where("id = ?", id).Limit(1).First(&admin).Error + if err != nil { + return + } + str, err := util.ToolsUtil.ObjToJson(&admin) + if err != nil { + return + } + util.RedisUtil.HSet(config.AdminConfig.BackstageManageKey, strconv.FormatUint(uint64(admin.ID), 10), str, 0) + return nil +} diff --git a/server/admin/system/dept/dept.go b/server/admin/system/dept/dept.go new file mode 100644 index 0000000..79411a6 --- /dev/null +++ b/server/admin/system/dept/dept.go @@ -0,0 +1,86 @@ +package dept + +import ( + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func DeptRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + // authSrv := system.NewSystemAuthMenuService(db, permSrv) + Dept := NewSystemAuthDeptService(db) + handle := deptHandler{Service: Dept} + + rg = rg.Group("/system", middleware.TokenAuth()) + rg.GET("/dept/all", handle.All) + rg.GET("/dept/list", handle.List) + rg.GET("/dept/detail", handle.Detail) + rg.POST("/dept/add", handle.Add) + rg.POST("/dept/edit", handle.Edit) + rg.POST("/dept/del", handle.Del) +} + +type deptHandler struct { + Service ISystemAuthDeptService +} + +// all 部门所有 +func (dh deptHandler) All(c *gin.Context) { + res, err := dh.Service.All() + response.CheckAndRespWithData(c, res, err) +} + +// list 部门列表 +func (dh deptHandler) List(c *gin.Context) { + var listReq SystemAuthDeptListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := dh.Service.List(listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail 部门详情 +func (dh deptHandler) Detail(c *gin.Context) { + var detailReq SystemAuthDeptDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := dh.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// add 部门新增 +func (dh deptHandler) Add(c *gin.Context) { + var addReq SystemAuthDeptAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &addReq)) { + return + } + response.CheckAndResp(c, dh.Service.Add(addReq)) +} + +// edit 部门编辑 +func (dh deptHandler) Edit(c *gin.Context) { + var editReq SystemAuthDeptEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &editReq)) { + return + } + response.CheckAndResp(c, dh.Service.Edit(editReq)) +} + +// del 部门删除 +func (dh deptHandler) Del(c *gin.Context) { + var delReq SystemAuthDeptDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &delReq)) { + return + } + response.CheckAndResp(c, dh.Service.Del(delReq.ID)) +} diff --git a/server/admin/system/dept/schema.go b/server/admin/system/dept/schema.go new file mode 100644 index 0000000..f312a8d --- /dev/null +++ b/server/admin/system/dept/schema.go @@ -0,0 +1,53 @@ +package dept + +import "x_admin/core" + +//SystemAuthDeptListReq 部门列表参数 +type SystemAuthDeptListReq struct { + Name string `form:"name"` // 部门名称 + IsStop int8 `form:"isStop,default=-1" binding:"oneof=-1 0 1"` // 是否停用: [0=否, 1=是] +} + +//SystemAuthDeptDetailReq 部门详情参数 +type SystemAuthDeptDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthDeptAddReq 部门新增参数 +type SystemAuthDeptAddReq struct { + Pid uint `form:"pid" binding:"gte=0"` // 部门父级 + Name string `form:"name" binding:"required,min=1,max=100"` // 部门名称 + Duty string `form:"duty" binding:"omitempty,min=1,max=30"` // 负责人 + Mobile string `form:"mobile" binding:"omitempty,len=11"` // 联系电话 + IsStop uint8 `form:"isStop" binding:"oneof=0 1"` // 是否停用: [0=否, 1=是] + Sort int `form:"sort" binding:"gte=0,lte=9999"` // 排序编号 +} + +//SystemAuthDeptEditReq 部门编辑参数 +type SystemAuthDeptEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + Pid uint `form:"pid" binding:"gte=0"` // 部门父级 + Name string `form:"name" binding:"required,min=1,max=100"` // 部门名称 + Duty string `form:"duty" binding:"omitempty,min=1,max=30"` // 负责人 + Mobile string `form:"mobile" binding:"omitempty,len=11"` // 联系电话 + IsStop uint8 `form:"isStop" binding:"oneof=0 1"` // 是否停用: [0=否, 1=是] + Sort int `form:"sort" binding:"gte=0,lte=9999"` // 排序编号 +} + +//SystemAuthDeptDelReq 部门删除参数 +type SystemAuthDeptDelReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthDeptResp 系统部门返回信息 +type SystemAuthDeptResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Pid uint `json:"pid" structs:"pid"` // 部门父级 + Name string `json:"name" structs:"name"` // 部门名称 + Duty string `json:"duty" structs:"duty"` // 负责人 + Mobile string `json:"mobile" structs:"mobile"` // 联系电话 + Sort uint16 `json:"sort" structs:"sort"` // 排序编号 + IsStop uint8 `json:"isStop" structs:"isStop"` // 是否停用: [0=否, 1=是] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} diff --git a/server/admin/system/dept/service.go b/server/admin/system/dept/service.go new file mode 100644 index 0000000..8cbccf8 --- /dev/null +++ b/server/admin/system/dept/service.go @@ -0,0 +1,151 @@ +package dept + +import ( + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "gorm.io/gorm" +) + +type ISystemAuthDeptService interface { + All() (res []SystemAuthDeptResp, e error) + List(listReq SystemAuthDeptListReq) (mapList []interface{}, e error) + Detail(id uint) (res SystemAuthDeptResp, e error) + Add(addReq SystemAuthDeptAddReq) (e error) + Edit(editReq SystemAuthDeptEditReq) (e error) + Del(id uint) (e error) +} + +// NewSystemAuthDeptService 初始化 +func NewSystemAuthDeptService(db *gorm.DB) ISystemAuthDeptService { + return &systemAuthDeptService{db: db} +} + +// systemAuthDeptService 系统部门服务实现类 +type systemAuthDeptService struct { + db *gorm.DB +} + +// All 部门所有 +func (deptSrv systemAuthDeptService) All() (res []SystemAuthDeptResp, e error) { + var depts []system.SystemAuthDept + err := deptSrv.db.Where("pid > ? AND is_delete = ?", 0, 0).Order("sort desc, id desc").Find(&depts).Error + if e = response.CheckErr(err, "All Find err"); e != nil { + return + } + res = []SystemAuthDeptResp{} + response.Copy(&res, depts) + return +} + +// List 部门列表 +func (deptSrv systemAuthDeptService) List(listReq SystemAuthDeptListReq) (mapList []interface{}, e error) { + deptModel := deptSrv.db.Where("is_delete = ?", 0) + if listReq.Name != "" { + deptModel = deptModel.Where("name like ?", "%"+listReq.Name+"%") + } + if listReq.IsStop >= 0 { + deptModel = deptModel.Where("is_stop = ?", listReq.IsStop) + } + var depts []system.SystemAuthDept + err := deptModel.Order("sort desc, id desc").Find(&depts).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + deptResps := []SystemAuthDeptResp{} + response.Copy(&deptResps, depts) + mapList = util.ArrayUtil.ListToTree( + util.ConvertUtil.StructsToMaps(deptResps), "id", "pid", "children") + return +} + +// Detail 部门详情 +func (deptSrv systemAuthDeptService) Detail(id uint) (res SystemAuthDeptResp, e error) { + var dept system.SystemAuthDept + err := deptSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&dept).Error + if e = response.CheckErrDBNotRecord(err, "部门已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, dept) + return +} + +// Add 部门新增 +func (deptSrv systemAuthDeptService) Add(addReq SystemAuthDeptAddReq) (e error) { + if addReq.Pid == 0 { + r := deptSrv.db.Where("pid = ? AND is_delete = ?", 0, 0).Limit(1).Find(&system.SystemAuthDept{}) + if e = response.CheckErr(r.Error, "Add Find err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("顶级部门只允许有一个!") + } + } + var dept system.SystemAuthDept + response.Copy(&dept, addReq) + err := deptSrv.db.Create(&dept).Error + e = response.CheckErr(err, "Add Create err") + return +} + +// Edit 部门编辑 +func (deptSrv systemAuthDeptService) Edit(editReq SystemAuthDeptEditReq) (e error) { + var dept system.SystemAuthDept + err := deptSrv.db.Where("id = ? AND is_delete = ?", editReq.ID, 0).Limit(1).First(&dept).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "部门不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + if dept.Pid == 0 && editReq.Pid > 0 { + return response.AssertArgumentError.Make("顶级部门不能修改上级!") + } + if editReq.ID == editReq.Pid { + return response.AssertArgumentError.Make("上级部门不能是自己!") + } + // 更新 + response.Copy(&dept, editReq) + err = deptSrv.db.Model(&dept).Updates(dept).Error + e = response.CheckErr(err, "Edit Updates err") + return +} + +// Del 部门删除 +func (deptSrv systemAuthDeptService) Del(id uint) (e error) { + var dept system.SystemAuthDept + err := deptSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&dept).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "部门不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Del First err"); e != nil { + return + } + if dept.Pid == 0 { + return response.AssertArgumentError.Make("顶级部门不能删除!") + } + r := deptSrv.db.Where("pid = ? AND is_delete = ?", id, 0).Limit(1).Find(&system.SystemAuthDept{}) + if e = response.CheckErr(r.Error, "Del Find dept err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("请先删除子级部门!") + } + r = deptSrv.db.Where("dept_id = ? AND is_delete = ?", id, 0).Limit(1).Find(&system.SystemAuthAdmin{}) + if e = response.CheckErr(r.Error, "Del Find admin err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("该部门已被管理员使用,请先移除!") + } + dept.IsDelete = 1 + err = deptSrv.db.Save(&dept).Error + e = response.CheckErr(err, "Del Save err") + return +} diff --git a/server/admin/system/enter.go b/server/admin/system/enter.go new file mode 100644 index 0000000..ae7ef21 --- /dev/null +++ b/server/admin/system/enter.go @@ -0,0 +1,51 @@ +package system + +import ( + "x_admin/admin/system/admin" + "x_admin/admin/system/role" + "x_admin/core" + "x_admin/middleware" + + "github.com/gin-gonic/gin" +) + +func AdminRoute(rg *gin.RouterGroup) { + db := core.GetDB() + + permSrv := role.NewSystemAuthPermService(db) + roleSrv := role.NewSystemAuthRoleService(db, permSrv) + adminSrv := admin.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := NewSystemLoginService(db, adminSrv) + + handle := admin.AdminHandler{Service: adminSrv} + + rg = rg.Group("/system", middleware.TokenAuth()) + + rg.GET("/admin/self", handle.Self) + rg.GET("/admin/list", handle.List) + rg.GET("/admin/detail", handle.Detail) + rg.POST("/admin/add", middleware.RecordLog("管理员新增"), handle.Add) + rg.POST("/admin/edit", middleware.RecordLog("管理员编辑"), handle.Edit) + rg.POST("/admin/upInfo", middleware.RecordLog("管理员更新"), handle.UpInfo) + rg.POST("/admin/del", middleware.RecordLog("管理员删除"), handle.Del) + rg.POST("/admin/disable", middleware.RecordLog("管理员状态切换"), handle.Disable) +} +func RoleRoute(rg *gin.RouterGroup) { + db := core.GetDB() + permSrv := role.NewSystemAuthPermService(db) + // roleSrv := NewSystemAuthRoleService(db, permSrv) + // adminSrv := NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := NewSystemLoginService(db, adminSrv) + + server := role.NewSystemAuthRoleService(db, permSrv) + + handle := role.RoleHandler{Service: server} + + rg = rg.Group("/system", middleware.TokenAuth()) + rg.GET("/role/all", handle.All) + rg.GET("/role/list", middleware.RecordLog("角色列表"), handle.List) + rg.GET("/role/detail", middleware.RecordLog("角色详情"), handle.Detail) + rg.POST("/role/add", middleware.RecordLog("角色新增"), handle.Add) + rg.POST("/role/edit", middleware.RecordLog("角色编辑"), handle.Edit) + rg.POST("/role/del", middleware.RecordLog("角色删除"), handle.Del) +} diff --git a/server/admin/system/log/log.go b/server/admin/system/log/log.go new file mode 100644 index 0000000..9fca752 --- /dev/null +++ b/server/admin/system/log/log.go @@ -0,0 +1,59 @@ +package log + +import ( + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func LogRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := NewSystemAuthPermService(db) + // roleSrv := NewSystemAuthRoleService(db, permSrv) + // adminSrv := NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := NewSystemLoginService(db, adminSrv) + + authSrv := NewSystemLogsServer(db) + + handle := logHandler{Service: authSrv} + + rg = rg.Group("/system", middleware.TokenAuth()) + rg.GET("/log/operate", handle.operate) + rg.GET("/log/login", handle.login) +} + +type logHandler struct { + Service ISystemLogsServer +} + +// operate 操作日志 +func (lh logHandler) operate(c *gin.Context) { + var page request.PageReq + var logReq SystemLogOperateReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &logReq)) { + return + } + res, err := lh.Service.Operate(page, logReq) + response.CheckAndRespWithData(c, res, err) +} + +// login 登录日志 +func (lh logHandler) login(c *gin.Context) { + var page request.PageReq + var logReq SystemLogLoginReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &logReq)) { + return + } + res, err := lh.Service.Login(page, logReq) + response.CheckAndRespWithData(c, res, err) +} diff --git a/server/admin/system/log/schema.go b/server/admin/system/log/schema.go new file mode 100644 index 0000000..9efe996 --- /dev/null +++ b/server/admin/system/log/schema.go @@ -0,0 +1,54 @@ +package log + +import ( + "time" + "x_admin/core" +) + +// //SystemLogOperateReq 操作日志列表参数 +type SystemLogOperateReq struct { + Title string `form:"title"` // 操作标题 + Username string `form:"username"` // 用户账号 + Ip string `form:"ip"` // 请求IP + Type string `form:"type" binding:"omitempty,oneof=GET POST PUT"` // 请求类型: GET/POST/PUT + Status int `form:"status" binding:"omitempty,oneof=1 2"` // 执行状态: [1=成功, 2=失败] + Url string `form:"url"` // 请求地址 + StartTime time.Time `form:"startTime" time_format:"2006-01-02"` // 开始时间 + EndTime time.Time `form:"endTime" time_format:"2006-01-02"` // 结束时间 +} +type SystemLogLoginReq struct { + Username string `form:"username"` // 登录账号 + Status int `form:"status" binding:"omitempty,oneof=1 2"` // 执行状态: [1=成功, 2=失败] + StartTime time.Time `form:"startTime" time_format:"2006-01-02"` // 开始时间 + EndTime time.Time `form:"endTime" time_format:"2006-01-02"` // 结束时间 +} + +// SystemLogOperateResp 操作日志返回信息 +type SystemLogOperateResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Username string `json:"username" structs:"username"` // 用户账号 + Nickname string `json:"nickname" structs:"nickname"` // 用户昵称 + Type string `json:"type" structs:"type"` // 请求类型: GET/POST/PUT + Title string `json:"title" structs:"title"` // 操作标题 + Method string `json:"method" structs:"method"` // 请求方式 + Ip string `json:"ip" structs:"ip"` // 请求IP + Url string `json:"url" structs:"url"` // 请求地址 + Args string `json:"args" structs:"args"` // 请求参数 + Error string `json:"error" structs:"error"` // 错误信息 + Status int `json:"status" structs:"status"` // 执行状态: [1=成功, 2=失败] + TaskTime string `json:"taskTime" structs:"taskTime"` // 执行耗时 + StartTime core.TsTime `json:"startTime" structs:"startTime"` // 开始时间 + EndTime core.TsTime `json:"endTime" structs:"endTime"` // 结束时间 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 +} + +// SystemLogLoginResp 登录日志返回信息 +type SystemLogLoginResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Username string `json:"username" structs:"username"` // 登录账号 + Ip string `json:"ip" structs:"ip"` // 来源IP + Os string `json:"os" structs:"os"` // 操作系统 + Browser string `json:"browser" structs:"browser"` // 浏览器 + Status int `json:"status" structs:"status"` // 操作状态: [1=成功, 2=失败] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 +} diff --git a/server/admin/system/log/service.go b/server/admin/system/log/service.go new file mode 100644 index 0000000..9d39624 --- /dev/null +++ b/server/admin/system/log/service.go @@ -0,0 +1,122 @@ +package log + +import ( + "fmt" + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/system" + + "gorm.io/gorm" +) + +type ISystemLogsServer interface { + Operate(page request.PageReq, logReq SystemLogOperateReq) (res response.PageResp, e error) + Login(page request.PageReq, logReq SystemLogLoginReq) (res response.PageResp, e error) +} + +// NewSystemLogsServer 初始化 +func NewSystemLogsServer(db *gorm.DB) ISystemLogsServer { + return &systemLogsServer{db: db} +} + +// systemLogsServer 系统日志服务实现类 +type systemLogsServer struct { + db *gorm.DB +} + +// Operate 系统操作日志 +func (logSrv systemLogsServer) Operate(page request.PageReq, logReq SystemLogOperateReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + logTbName := core.DBTableName(&system.SystemLogOperate{}) + adminTbName := core.DBTableName(&system.SystemAuthAdmin{}) + logModel := logSrv.db.Table(logTbName + " AS log").Joins( + fmt.Sprintf("LEFT JOIN %s AS admin ON log.admin_id = admin.id", adminTbName)).Select( + "log.*, admin.username, admin.nickname") + // 条件 + if logReq.Title != "" { + logModel = logModel.Where("title like ?", "%"+logReq.Title+"%") + } + if logReq.Username != "" { + logModel = logModel.Where("username like ?", "%"+logReq.Username+"%") + } + if logReq.Ip != "" { + logModel = logModel.Where("ip like ?", "%"+logReq.Ip+"%") + } + if logReq.Type != "" { + logModel = logModel.Where("type = ?", logReq.Type) + } + if logReq.Status > 0 { + logModel = logModel.Where("status = ?", logReq.Status) + } + if logReq.Url != "" { + logModel = logModel.Where("url = ?", logReq.Url) + } + if !logReq.StartTime.IsZero() { + logModel = logModel.Where("log.create_time >= ?", logReq.StartTime.Unix()) + } + if !logReq.EndTime.IsZero() { + logModel = logModel.Where("log.create_time <= ?", logReq.EndTime.Unix()) + } + // 总数 + var count int64 + err := logModel.Count(&count).Error + if e = response.CheckErr(err, "Operate Count err"); e != nil { + return + } + // 数据 + var logResp []SystemLogOperateResp + err = logModel.Limit(limit).Offset(offset).Order("id desc").Find(&logResp).Error + if e = response.CheckErr(err, "Operate Find err"); e != nil { + return + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: logResp, + }, nil +} + +// Login 系统登录日志 +func (logSrv systemLogsServer) Login(page request.PageReq, logReq SystemLogLoginReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + logModel := logSrv.db.Model(&system.SystemLogLogin{}) + // 条件 + if logReq.Username != "" { + logModel = logModel.Where("username like ?", "%"+logReq.Username+"%") + } + if logReq.Status > 0 { + logModel = logModel.Where("status = ?", logReq.Status) + } + if !logReq.StartTime.IsZero() { + logModel = logModel.Where("create_time >= ?", logReq.StartTime.Unix()) + } + if !logReq.EndTime.IsZero() { + logModel = logModel.Where("create_time <= ?", logReq.EndTime.Unix()) + } + // 总数 + var count int64 + err := logModel.Count(&count).Error + if e = response.CheckErr(err, "Login Count err"); e != nil { + return + } + // 数据 + var logResp []SystemLogLoginResp + err = logModel.Limit(limit).Offset(offset).Order("id desc").Find(&logResp).Error + if e = response.CheckErr(err, "Login Find err"); e != nil { + return + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: logResp, + }, nil +} diff --git a/server/admin/system/login/login.go b/server/admin/system/login/login.go new file mode 100644 index 0000000..c71eb77 --- /dev/null +++ b/server/admin/system/login/login.go @@ -0,0 +1,49 @@ +package login + +import ( + "x_admin/admin/system/admin" + "x_admin/admin/system/role" + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func LoginRoute(rg *gin.RouterGroup) { + db := core.GetDB() + permSrv := role.NewSystemAuthPermService(db) + roleSrv := role.NewSystemAuthRoleService(db, permSrv) + adminSrv := admin.NewSystemAuthAdminService(db, permSrv, roleSrv) + service := NewSystemLoginService(db, adminSrv) + + handle := loginHandler{Service: service} + + rg = rg.Group("/system", middleware.TokenAuth()) + rg.POST("/login", handle.login) + rg.POST("/logout", handle.logout) +} + +type loginHandler struct { + Service ISystemLoginService +} + +// login 登录系统 +func (lh loginHandler) login(c *gin.Context) { + var loginReq SystemLoginReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &loginReq)) { + return + } + res, err := lh.Service.Login(c, &loginReq) + response.CheckAndRespWithData(c, res, err) +} + +// logout 登录退出 +func (lh loginHandler) logout(c *gin.Context) { + var logoutReq SystemLogoutReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyHeader(c, &logoutReq)) { + return + } + response.CheckAndResp(c, lh.Service.Logout(&logoutReq)) +} diff --git a/server/admin/system/login/schema.go b/server/admin/system/login/schema.go new file mode 100644 index 0000000..9982eee --- /dev/null +++ b/server/admin/system/login/schema.go @@ -0,0 +1,39 @@ +package login + +import ( + "time" + "x_admin/core" +) + +type SystemLogLoginReq struct { + Username string `form:"username"` // 登录账号 + Status int `form:"status" binding:"omitempty,oneof=1 2"` // 执行状态: [1=成功, 2=失败] + StartTime time.Time `form:"startTime" time_format:"2006-01-02"` // 开始时间 + EndTime time.Time `form:"endTime" time_format:"2006-01-02"` // 结束时间 +} + +type SystemLoginResp struct { + Token string `json:"token"` +} + +// SystemLoginReq 系统登录参数 +type SystemLoginReq struct { + Username string `json:"username" binding:"required,min=2,max=20"` // 账号 + Password string `json:"password" binding:"required,min=6,max=32"` // 密码 +} + +// SystemLogoutReq 登录退出参数 +type SystemLogoutReq struct { + Token string `header:"token" binding:"required"` // 令牌 +} + +// SystemLogLoginResp 登录日志返回信息 +type SystemLogLoginResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Username string `json:"username" structs:"username"` // 登录账号 + Ip string `json:"ip" structs:"ip"` // 来源IP + Os string `json:"os" structs:"os"` // 操作系统 + Browser string `json:"browser" structs:"browser"` // 浏览器 + Status int `json:"status" structs:"status"` // 操作状态: [1=成功, 2=失败] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 +} diff --git a/server/admin/system/login/service.go b/server/admin/system/login/service.go new file mode 100644 index 0000000..1d9a6dd --- /dev/null +++ b/server/admin/system/login/service.go @@ -0,0 +1,148 @@ +package login + +import ( + "errors" + "runtime/debug" + "strconv" + "time" + "x_admin/admin/system/admin" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type ISystemLoginService interface { + Login(c *gin.Context, req *SystemLoginReq) (res SystemLoginResp, e error) + Logout(req *SystemLogoutReq) (e error) + RecordLoginLog(c *gin.Context, adminId uint, username string, errStr string) (e error) +} + +// NewSystemLoginService 初始化 +func NewSystemLoginService(db *gorm.DB, adminSrv admin.ISystemAuthAdminService) ISystemLoginService { + return &systemLoginService{db: db, adminSrv: adminSrv} +} + +// systemLoginService 系统登录服务实现类 +type systemLoginService struct { + db *gorm.DB + adminSrv admin.ISystemAuthAdminService +} + +// Login 登录 +func (loginSrv systemLoginService) Login(c *gin.Context, req *SystemLoginReq) (res SystemLoginResp, e error) { + sysAdmin, err := loginSrv.adminSrv.FindByUsername(req.Username) + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + if e = loginSrv.RecordLoginLog(c, 0, req.Username, response.LoginAccountError.Msg()); e != nil { + return + } + e = response.LoginAccountError + return + } else if err != nil { + core.Logger.Errorf("Login FindByUsername err: err=[%+v]", err) + if e = loginSrv.RecordLoginLog(c, 0, req.Username, response.Failed.Msg()); e != nil { + return + } + e = response.Failed + return + } + if sysAdmin.IsDelete == 1 { + if e = loginSrv.RecordLoginLog(c, 0, req.Username, response.LoginAccountError.Msg()); e != nil { + return + } + e = response.LoginAccountError + return + } + if sysAdmin.IsDisable == 1 { + if e = loginSrv.RecordLoginLog(c, sysAdmin.ID, req.Username, response.LoginDisableError.Msg()); e != nil { + return + } + e = response.LoginDisableError + return + } + md5Pwd := util.ToolsUtil.MakeMd5(req.Password + sysAdmin.Salt) + if sysAdmin.Password != md5Pwd { + if e = loginSrv.RecordLoginLog(c, sysAdmin.ID, req.Username, response.LoginAccountError.Msg()); e != nil { + return + } + e = response.LoginAccountError + return + } + defer func() { + if r := recover(); r != nil { + switch r.(type) { + // 自定义类型 + case response.RespType: + panic(r) + // 其他类型 + default: + core.Logger.Errorf("stacktrace from panic: %+v\n%s", r, string(debug.Stack())) + loginSrv.RecordLoginLog(c, sysAdmin.ID, req.Username, response.Failed.Msg()) + panic(response.Failed) + } + } + }() + token := util.ToolsUtil.MakeToken() + adminIdStr := strconv.FormatUint(uint64(sysAdmin.ID), 10) + + //非多处登录 + if sysAdmin.IsMultipoint == 0 { + sysAdminSetKey := config.AdminConfig.BackstageTokenSet + adminIdStr + ts := util.RedisUtil.SGet(sysAdminSetKey) + if len(ts) > 0 { + var keys []string + for _, t := range ts { + keys = append(keys, t) + } + util.RedisUtil.Del(keys...) + } + util.RedisUtil.Del(sysAdminSetKey) + util.RedisUtil.SSet(sysAdminSetKey, token) + } + + // 缓存登录信息 + util.RedisUtil.Set(config.AdminConfig.BackstageTokenKey+token, adminIdStr, 7200) + loginSrv.adminSrv.CacheAdminUserByUid(sysAdmin.ID) + + // 更新登录信息 + err = loginSrv.db.Model(&sysAdmin).Updates( + system.SystemAuthAdmin{LastLoginIp: c.ClientIP(), LastLoginTime: time.Now().Unix()}).Error + if err != nil { + if e = loginSrv.RecordLoginLog(c, sysAdmin.ID, req.Username, response.SystemError.Msg()); e != nil { + return + } + if e = response.CheckErr(err, "Login Updates err"); e != nil { + return + } + } + // 记录登录日志 + if e = loginSrv.RecordLoginLog(c, sysAdmin.ID, req.Username, ""); e != nil { + return + } + // 返回登录信息 + return SystemLoginResp{Token: token}, nil +} + +// Logout 退出 +func (loginSrv systemLoginService) Logout(req *SystemLogoutReq) (e error) { + util.RedisUtil.Del(config.AdminConfig.BackstageTokenKey + req.Token) + return +} + +// RecordLoginLog 记录登录日志 +func (loginSrv systemLoginService) RecordLoginLog(c *gin.Context, adminId uint, username string, errStr string) (e error) { + ua := core.UAParser.Parse(c.GetHeader("user-agent")) + var status uint8 + if errStr == "" { + status = 1 + } + err := loginSrv.db.Create(&system.SystemLogLogin{ + AdminId: adminId, Username: username, Ip: c.ClientIP(), Os: ua.Os.Family, + Browser: ua.UserAgent.Family, Status: status}).Error + e = response.CheckErr(err, "RecordLoginLog Create err") + return +} diff --git a/server/admin/system/menu/menu.go b/server/admin/system/menu/menu.go new file mode 100644 index 0000000..eddbd0e --- /dev/null +++ b/server/admin/system/menu/menu.go @@ -0,0 +1,88 @@ +package menu + +import ( + "strconv" + "x_admin/admin/system/role" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func MenuRoute(rg *gin.RouterGroup) { + db := core.GetDB() + permSrv := role.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + authSrv := NewSystemAuthMenuService(db, permSrv) + + handle := menuHandler{Service: authSrv} + + rg = rg.Group("/system", middleware.TokenAuth()) + rg.GET("/menu/route", handle.route) + rg.GET("/menu/list", handle.List) + rg.GET("/menu/detail", handle.Detail) + rg.POST("/menu/add", handle.Add) + rg.POST("/menu/edit", handle.Edit) + rg.POST("/menu/del", handle.Del) +} + +type menuHandler struct { + Service ISystemAuthMenuService +} + +// route 菜单路由 +func (mh menuHandler) route(c *gin.Context) { + roleId := config.AdminConfig.GetRoleId(c) + id, _ := strconv.ParseUint(roleId, 10, 64) + res, err := mh.Service.SelectMenuByRoleId(c, uint(id)) + response.CheckAndRespWithData(c, res, err) +} + +// list 菜单列表 +func (mh menuHandler) List(c *gin.Context) { + res, err := mh.Service.List() + response.CheckAndRespWithData(c, res, err) +} + +// detail 菜单详情 +func (mh menuHandler) Detail(c *gin.Context) { + var detailReq SystemAuthMenuDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := mh.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// add 新增菜单 +func (mh menuHandler) Add(c *gin.Context) { + var addReq SystemAuthMenuAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + return + } + response.CheckAndResp(c, mh.Service.Add(addReq)) +} + +// edit 编辑菜单 +func (mh menuHandler) Edit(c *gin.Context) { + var editReq SystemAuthMenuEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { + return + } + response.CheckAndResp(c, mh.Service.Edit(editReq)) +} + +// del 删除菜单 +func (mh menuHandler) Del(c *gin.Context) { + var delReq SystemAuthMenuDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, mh.Service.Del(delReq.ID)) +} diff --git a/server/admin/system/menu/schema.go b/server/admin/system/menu/schema.go new file mode 100644 index 0000000..bfc3786 --- /dev/null +++ b/server/admin/system/menu/schema.go @@ -0,0 +1,69 @@ +package menu + +import "x_admin/core" + +//SystemAuthMenuDetailReq 菜单详情参数 +type SystemAuthMenuDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthMenuAddReq 新增菜单参数 +type SystemAuthMenuAddReq struct { + Pid uint `form:"pid" binding:"gte=0"` // 上级菜单 + MenuType string `form:"menuType" binding:"oneof=M C A"` // 权限类型: [M=目录, C=菜单, A=按钮] + MenuName string `form:"menuName" binding:"required,min=1,max=30"` // 菜单名称 + MenuIcon string `form:"menuIcon" binding:"max=100"` // 菜单图标 + MenuSort int `form:"menuSort" binding:"gte=0"` // 菜单排序 + Perms string `form:"perms" binding:"max=100"` // 权限标识 + Paths string `form:"paths" binding:"max=200"` // 路由地址 + Component string `form:"component" binding:"max=200"` // 前端组件 + Selected string `form:"selected" binding:"max=200"` // 选中路径 + Params string `form:"params" binding:"max=200"` // 路由参数 + IsCache uint8 `form:"isCache" binding:"oneof=0 1"` // 是否缓存: [0=否, 1=是] + IsShow uint8 `form:"isShow" binding:"oneof=0 1"` // 是否显示: [0=否, 1=是] + IsDisable uint8 `form:"isDisable" binding:"oneof=0 1"` // 是否禁用: [0=否, 1=是] +} + +//SystemAuthMenuEditReq 编辑菜单参数 +type SystemAuthMenuEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + Pid uint `form:"pid" binding:"gte=0"` // 上级菜单 + MenuType string `form:"menuType" binding:"oneof=M C A"` // 权限类型: [M=目录, C=菜单, A=按钮] + MenuName string `form:"menuName" binding:"required,min=1,max=30"` // 菜单名称 + MenuIcon string `form:"menuIcon" binding:"max=100"` // 菜单图标 + MenuSort int `form:"menuSort" binding:"gte=0"` // 菜单排序 + Perms string `form:"perms" binding:"max=100"` // 权限标识 + Paths string `form:"paths" binding:"max=200"` // 路由地址 + Component string `form:"component" binding:"max=200"` // 前端组件 + Selected string `form:"selected" binding:"max=200"` // 选中路径 + Params string `form:"params" binding:"max=200"` // 路由参数 + IsCache uint8 `form:"isCache" binding:"oneof=0 1"` // 是否缓存: [0=否, 1=是] + IsShow uint8 `form:"isShow" binding:"oneof=0 1"` // 是否显示: [0=否, 1=是] + IsDisable uint8 `form:"isDisable" binding:"oneof=0 1"` // 是否禁用: [0=否, 1=是] +} + +//SystemAuthMenuDelReq 删除菜单参数 +type SystemAuthMenuDelReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthMenuResp 系统菜单返回信息 +type SystemAuthMenuResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Pid uint `json:"pid" structs:"pid"` // 上级菜单 + MenuType string `json:"menuType" structs:"menuType"` // 权限类型: [M=目录, C=菜单, A=按钮] + MenuName string `json:"menuName" structs:"menuName"` // 菜单名称 + MenuIcon string `json:"menuIcon" structs:"menuIcon"` // 菜单图标 + MenuSort uint16 `json:"menuSort" structs:"menuSort"` // 菜单排序 + Perms string `json:"perms" structs:"perms"` // 权限标识 + Paths string `json:"paths" structs:"paths"` // 路由地址 + Component string `json:"component" structs:"component"` // 前端组件 + Selected string `json:"selected" structs:"selected"` // 选中路径 + Params string `json:"params" structs:"params"` // 路由参数 + IsCache uint8 `json:"isCache" structs:"isCache"` // 是否缓存: [0=否, 1=是] + IsShow uint8 `json:"isShow" structs:"isShow"` // 是否显示: [0=否, 1=是] + IsDisable uint8 `json:"isDisable" structs:"isDisable"` // 是否禁用: [0=否, 1=是] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 + Children []SystemAuthMenuResp `json:"children,omitempty" structs:"children"` // 子集 +} diff --git a/server/admin/system/menu/service.go b/server/admin/system/menu/service.go new file mode 100644 index 0000000..11dd1e2 --- /dev/null +++ b/server/admin/system/menu/service.go @@ -0,0 +1,141 @@ +package menu + +import ( + "x_admin/admin/system/role" + "x_admin/config" + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "github.com/fatih/structs" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type ISystemAuthMenuService interface { + SelectMenuByRoleId(c *gin.Context, roleId uint) (mapList []interface{}, e error) + List() (res []interface{}, e error) + Detail(id uint) (res SystemAuthMenuResp, e error) + Add(addReq SystemAuthMenuAddReq) (e error) + Edit(editReq SystemAuthMenuEditReq) (e error) + Del(id uint) (e error) +} + +// NewSystemAuthMenuService 初始化 +func NewSystemAuthMenuService(db *gorm.DB, permSrv role.ISystemAuthPermService) ISystemAuthMenuService { + return &systemAuthMenuService{db: db, permSrv: permSrv} +} + +// systemAuthMenuService 系统菜单服务实现类 +type systemAuthMenuService struct { + db *gorm.DB + permSrv role.ISystemAuthPermService +} + +// SelectMenuByRoleId 根据角色ID获取菜单 +func (menuSrv systemAuthMenuService) SelectMenuByRoleId(c *gin.Context, roleId uint) (mapList []interface{}, e error) { + adminId := config.AdminConfig.GetAdminId(c) + var menuIds []uint + // 超管 + if adminId == config.AdminConfig.SuperAdminId { + menuIds = []uint{0} + } else if menuIds, e = menuSrv.permSrv.SelectMenuIdsByRoleId(roleId); e != nil { + return + } + if len(menuIds) == 0 { + menuIds = []uint{0} + } + chain := menuSrv.db.Where("menu_type in ? AND is_disable = ?", []string{"M", "C"}, 0) + if adminId != config.AdminConfig.SuperAdminId { + chain = chain.Where("id in ?", menuIds) + } + var menus []system.SystemAuthMenu + err := chain.Order("menu_sort desc, id").Find(&menus).Error + if e = response.CheckErr(err, "SelectMenuByRoleId Find err"); e != nil { + return + } + var menuResps []SystemAuthMenuResp + response.Copy(&menuResps, menus) + mapList = util.ArrayUtil.ListToTree( + util.ConvertUtil.StructsToMaps(menuResps), "id", "pid", "children") + return +} + +// List 菜单列表 +func (menuSrv systemAuthMenuService) List() (res []interface{}, e error) { + var menus []system.SystemAuthMenu + err := menuSrv.db.Order("menu_sort desc, id").Find(&menus).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + var menuResps []SystemAuthMenuResp + response.Copy(&menuResps, menus) + return util.ArrayUtil.ListToTree( + util.ConvertUtil.StructsToMaps(menuResps), "id", "pid", "children"), nil +} + +// Detail 菜单详情 +func (menuSrv systemAuthMenuService) Detail(id uint) (res SystemAuthMenuResp, e error) { + var menu system.SystemAuthMenu + err := menuSrv.db.Where("id = ?", id).Limit(1).First(&menu).Error + if e = response.CheckErrDBNotRecord(err, "菜单已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, menu) + return +} + +func (menuSrv systemAuthMenuService) Add(addReq SystemAuthMenuAddReq) (e error) { + var menu system.SystemAuthMenu + response.Copy(&menu, addReq) + err := menuSrv.db.Create(&menu).Error + if e = response.CheckErr(err, "Add Create err"); e != nil { + return + } + util.RedisUtil.Del(config.AdminConfig.BackstageRolesKey) + return +} + +func (menuSrv systemAuthMenuService) Edit(editReq SystemAuthMenuEditReq) (e error) { + var menu system.SystemAuthMenu + err := menuSrv.db.Where("id = ?", editReq.ID).Limit(1).Find(&menu).Error + if e = response.CheckErrDBNotRecord(err, "菜单已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit Find err"); e != nil { + return + } + response.Copy(&menu, editReq) + err = menuSrv.db.Model(&menu).Updates(structs.Map(menu)).Error + if e = response.CheckErr(err, "Edit Updates err"); e != nil { + return + } + util.RedisUtil.Del(config.AdminConfig.BackstageRolesKey) + return +} + +// Del 删除菜单 +func (menuSrv systemAuthMenuService) Del(id uint) (e error) { + var menu system.SystemAuthMenu + err := menuSrv.db.Where("id = ?", id).Limit(1).First(&menu).Error + if e = response.CheckErrDBNotRecord(err, "菜单已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Delete First err"); e != nil { + return + } + r := menuSrv.db.Where("pid = ?", id).Limit(1).Find(&system.SystemAuthMenu{}) + err = r.Error + if e = response.CheckErr(err, "Delete Find by pid err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("请先删除子菜单再操作!") + } + err = menuSrv.db.Delete(&menu).Error + e = response.CheckErr(err, "Delete Delete err") + return +} diff --git a/server/admin/system/post/post.go b/server/admin/system/post/post.go new file mode 100644 index 0000000..da27ea4 --- /dev/null +++ b/server/admin/system/post/post.go @@ -0,0 +1,92 @@ +package post + +import ( + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func PostRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewSystemAuthPostService(db) + + handle := postHandler{Service: server} + + rg = rg.Group("/system", middleware.TokenAuth()) + rg.GET("/post/all", handle.All) + rg.GET("/post/list", handle.List) + rg.GET("/post/detail", handle.Detail) + rg.POST("/post/add", handle.Add) + rg.POST("/post/edit", handle.Edit) + rg.POST("/post/del", handle.Del) +} + +type postHandler struct { + Service ISystemAuthPostService +} + +// all 岗位所有 +func (ph postHandler) All(c *gin.Context) { + res, err := ph.Service.All() + response.CheckAndRespWithData(c, res, err) +} + +// list 岗位列表 +func (ph postHandler) List(c *gin.Context) { + var page request.PageReq + var listReq SystemAuthPostListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := ph.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail 岗位详情 +func (ph postHandler) Detail(c *gin.Context) { + var detailReq SystemAuthPostDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := ph.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// add 岗位新增 +func (ph postHandler) Add(c *gin.Context) { + var addReq SystemAuthPostAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &addReq)) { + return + } + response.CheckAndResp(c, ph.Service.Add(addReq)) +} + +// edit 岗位编辑 +func (ph postHandler) Edit(c *gin.Context) { + var editReq SystemAuthPostEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &editReq)) { + return + } + response.CheckAndResp(c, ph.Service.Edit(editReq)) +} + +// del 岗位删除 +func (ph postHandler) Del(c *gin.Context) { + var delReq SystemAuthPostDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &delReq)) { + return + } + response.CheckAndResp(c, ph.Service.Del(delReq.ID)) +} diff --git a/server/admin/system/post/schema.go b/server/admin/system/post/schema.go new file mode 100644 index 0000000..5892a6e --- /dev/null +++ b/server/admin/system/post/schema.go @@ -0,0 +1,51 @@ +package post + +import "x_admin/core" + +//SystemAuthPostListReq 岗位列表参数 +type SystemAuthPostListReq struct { + Code string `form:"code"` // 岗位编码 + Name string `form:"name"` // 岗位名称 + IsStop int8 `form:"isStop,default=-1" binding:"oneof=-1 0 1"` // 是否停用: [0=否, 1=是] +} + +//SystemAuthPostDetailReq 岗位详情参数 +type SystemAuthPostDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthPostAddReq 岗位新增参数 +type SystemAuthPostAddReq struct { + Code string `form:"code" binding:"omitempty,min=1,max=30"` // 岗位编码 + Name string `form:"name" binding:"required,min=1,max=30"` // 岗位名称 + Remarks string `form:"remarks" binding:"max=250"` // 岗位备注 + IsStop uint8 `form:"isStop" binding:"oneof=0 1"` // 是否停用: [0=否, 1=是] + Sort int `form:"sort" binding:"gte=0"` // 排序编号 +} + +//SystemAuthPostEditReq 岗位编辑参数 +type SystemAuthPostEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + Code string `form:"code" binding:"omitempty,min=1,max=30"` // 岗位编码 + Name string `form:"name" binding:"required,min=1,max=30"` // 岗位名称 + Remarks string `form:"remarks" binding:"max=250"` // 岗位备注 + IsStop uint8 `form:"isStop" binding:"oneof=0 1"` // 是否停用: [0=否, 1=是] + Sort int `form:"sort" binding:"gte=0"` // 排序编号 +} + +//SystemAuthPostDelReq 岗位删除参数 +type SystemAuthPostDelReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthPostResp 系统岗位返回信息 +type SystemAuthPostResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Code string `json:"code" structs:"code"` // 岗位编号 + Name string `json:"name" structs:"name"` // 岗位名称 + Remarks string `json:"remarks" structs:"remarks"` // 岗位备注 + Sort uint16 `json:"sort" structs:"sort"` // 岗位排序 + IsStop uint8 `json:"isStop" structs:"isStop"` // 是否停用: [0=否, 1=是] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} diff --git a/server/admin/system/post/service.go b/server/admin/system/post/service.go new file mode 100644 index 0000000..79d9a85 --- /dev/null +++ b/server/admin/system/post/service.go @@ -0,0 +1,157 @@ +package post + +import ( + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/system" + + "gorm.io/gorm" +) + +type ISystemAuthPostService interface { + All() (res []SystemAuthPostResp, e error) + List(page request.PageReq, listReq SystemAuthPostListReq) (res response.PageResp, e error) + Detail(id uint) (res SystemAuthPostResp, e error) + Add(addReq SystemAuthPostAddReq) (e error) + Edit(editReq SystemAuthPostEditReq) (e error) + Del(id uint) (e error) +} + +// NewSystemAuthPostService 初始化 +func NewSystemAuthPostService(db *gorm.DB) ISystemAuthPostService { + return &systemAuthPostService{db: db} +} + +// systemAuthPostService 系统岗位服务实现类 +type systemAuthPostService struct { + db *gorm.DB +} + +// All 岗位所有 +func (postSrv systemAuthPostService) All() (res []SystemAuthPostResp, e error) { + var posts []system.SystemAuthPost + err := postSrv.db.Where("is_delete = ?", 0).Order("sort desc, id desc").Find(&posts).Error + if e = response.CheckErr(err, "All Find err"); e != nil { + return + } + res = []SystemAuthPostResp{} + response.Copy(&res, posts) + return +} + +// List 岗位列表 +func (postSrv systemAuthPostService) List(page request.PageReq, listReq SystemAuthPostListReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + postModel := postSrv.db.Model(&system.SystemAuthPost{}).Where("is_delete = ?", 0) + if listReq.Code != "" { + postModel = postModel.Where("code like ?", "%"+listReq.Code+"%") + } + if listReq.Name != "" { + postModel = postModel.Where("name like ?", "%"+listReq.Name+"%") + } + if listReq.IsStop >= 0 { + postModel = postModel.Where("is_stop = ?", listReq.IsStop) + } + // 总数 + var count int64 + err := postModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + // 数据 + var posts []system.SystemAuthPost + err = postModel.Limit(limit).Offset(offset).Order("id desc").Find(&posts).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + postResps := []SystemAuthPostResp{} + response.Copy(&postResps, posts) + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: postResps, + }, nil +} + +// Detail 部门详情 +func (postSrv systemAuthPostService) Detail(id uint) (res SystemAuthPostResp, e error) { + var post system.SystemAuthPost + err := postSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&post).Error + if e = response.CheckErrDBNotRecord(err, "岗位不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, post) + return +} + +// Add 部门新增 +func (postSrv systemAuthPostService) Add(addReq SystemAuthPostAddReq) (e error) { + r := postSrv.db.Where("(code = ? OR name = ?) AND is_delete = ?", addReq.Code, addReq.Name, 0).Limit(1).Find(&system.SystemAuthPost{}) + if e = response.CheckErr(r.Error, "Add Find err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("该岗位已存在!") + } + var post system.SystemAuthPost + response.Copy(&post, addReq) + err := postSrv.db.Create(&post).Error + e = response.CheckErr(err, "Add Create err") + return +} + +// Edit 部门编辑 +func (postSrv systemAuthPostService) Edit(editReq SystemAuthPostEditReq) (e error) { + var post system.SystemAuthPost + err := postSrv.db.Where("id = ? AND is_delete = ?", editReq.ID, 0).Limit(1).First(&post).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "部门不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + r := postSrv.db.Where("(code = ? OR name = ?) AND id != ? AND is_delete = ?", editReq.Code, editReq.Name, editReq.ID, 0).Limit(1).Find(&system.SystemAuthPost{}) + if e = response.CheckErr(r.Error, "Add Find err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("该岗位已存在!") + } + // 更新 + response.Copy(&post, editReq) + err = postSrv.db.Model(&post).Updates(post).Error + e = response.CheckErr(err, "Edit Updates err") + return +} + +// Del 部门删除 +func (postSrv systemAuthPostService) Del(id uint) (e error) { + var post system.SystemAuthPost + err := postSrv.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&post).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "部门不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Del First err"); e != nil { + return + } + r := postSrv.db.Where("post_id = ? AND is_delete = ?", id, 0).Limit(1).Find(&system.SystemAuthAdmin{}) + if e = response.CheckErr(r.Error, "Del Find err"); e != nil { + return + } + if r.RowsAffected > 0 { + return response.AssertArgumentError.Make("该岗位已被管理员使用,请先移除!") + } + post.IsDelete = 1 + err = postSrv.db.Save(&post).Error + e = response.CheckErr(err, "Del Save err") + return +} diff --git a/server/admin/system/role/authPermService.go b/server/admin/system/role/authPermService.go new file mode 100644 index 0000000..fa9cf3f --- /dev/null +++ b/server/admin/system/role/authPermService.go @@ -0,0 +1,116 @@ +package role + +import ( + "strconv" + "strings" + "x_admin/config" + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "gorm.io/gorm" +) + +type ISystemAuthPermService interface { + SelectMenuIdsByRoleId(roleId uint) (menuIds []uint, e error) + CacheRoleMenusByRoleId(roleId uint) (e error) + BatchSaveByMenuIds(roleId uint, menuIds string, db *gorm.DB) (e error) + BatchDeleteByRoleId(roleId uint, db *gorm.DB) (e error) + BatchDeleteByMenuId(menuId uint) (e error) +} + +// NewSystemAuthPermService 初始化 +func NewSystemAuthPermService(db *gorm.DB) ISystemAuthPermService { + return &systemAuthPermService{db: db} +} + +// systemAuthPermService 系统权限服务实现类 +type systemAuthPermService struct { + db *gorm.DB +} + +// SelectMenuIdsByRoleId 根据角色ID获取菜单ID +func (permSrv systemAuthPermService) SelectMenuIdsByRoleId(roleId uint) (menuIds []uint, e error) { + var role system.SystemAuthRole + err := permSrv.db.Where("id = ? AND is_disable = ?", roleId, 0).Limit(1).First(&role).Error + if e = response.CheckErr(err, "SelectMenuIdsByRoleId First err"); e != nil { + return []uint{}, e + } + var perms []system.SystemAuthPerm + err = permSrv.db.Where("role_id = ?", role.ID).Find(&perms).Error + if e = response.CheckErr(err, "SelectMenuIdsByRoleId Find err"); e != nil { + return []uint{}, e + } + for _, perm := range perms { + menuIds = append(menuIds, perm.MenuId) + } + return +} + +// CacheRoleMenusByRoleId 缓存角色菜单 +func (permSrv systemAuthPermService) CacheRoleMenusByRoleId(roleId uint) (e error) { + var perms []system.SystemAuthPerm + err := permSrv.db.Where("role_id = ?", roleId).Find(&perms).Error + if e = response.CheckErr(err, "CacheRoleMenusByRoleId Find perms err"); e != nil { + return + } + var menuIds []uint + for _, perm := range perms { + menuIds = append(menuIds, perm.MenuId) + } + var menus []system.SystemAuthMenu + err = permSrv.db.Where( + "is_disable = ? and id in ? and menu_type in ?", 0, menuIds, []string{"C", "A"}).Order( + "menu_sort, id").Find(&menus).Error + if e = response.CheckErr(err, "CacheRoleMenusByRoleId Find menus err"); e != nil { + return + } + var menuArray []string + for _, menu := range menus { + if menu.Perms != "" { + menuArray = append(menuArray, strings.Trim(menu.Perms, "")) + } + } + util.RedisUtil.HSet(config.AdminConfig.BackstageRolesKey, strconv.FormatUint(uint64(roleId), 10), strings.Join(menuArray, ","), 0) + return +} + +// BatchSaveByMenuIds 批量写入角色菜单 +func (permSrv systemAuthPermService) BatchSaveByMenuIds(roleId uint, menuIds string, db *gorm.DB) (e error) { + if menuIds == "" { + return + } + if db == nil { + db = permSrv.db + } + err := db.Transaction(func(tx *gorm.DB) error { + var perms []system.SystemAuthPerm + for _, menuIdStr := range strings.Split(menuIds, ",") { + menuId, _ := strconv.ParseUint(menuIdStr, 10, 32) + perms = append(perms, system.SystemAuthPerm{ID: util.ToolsUtil.MakeUuid(), RoleId: roleId, MenuId: uint(menuId)}) + } + txErr := tx.Create(&perms).Error + var te error + te = response.CheckErr(txErr, "BatchSaveByMenuIds Create in tx err") + return te + }) + e = response.CheckErr(err, "BatchSaveByMenuIds Transaction err") + return +} + +// BatchDeleteByRoleId 批量删除角色菜单(根据角色ID) +func (permSrv systemAuthPermService) BatchDeleteByRoleId(roleId uint, db *gorm.DB) (e error) { + if db == nil { + db = permSrv.db + } + err := db.Delete(&system.SystemAuthPerm{}, "role_id = ?", roleId).Error + e = response.CheckErr(err, "BatchDeleteByRoleId Delete err") + return +} + +// BatchDeleteByMenuId 批量删除角色菜单(根据菜单ID) +func (permSrv systemAuthPermService) BatchDeleteByMenuId(menuId uint) (e error) { + err := permSrv.db.Delete(&system.SystemAuthPerm{}, "menu_id = ?", menuId).Error + e = response.CheckErr(err, "BatchDeleteByMenuId Delete err") + return +} diff --git a/server/admin/system/role/role.go b/server/admin/system/role/role.go new file mode 100644 index 0000000..e224436 --- /dev/null +++ b/server/admin/system/role/role.go @@ -0,0 +1,86 @@ +package role + +import ( + "x_admin/core/request" + "x_admin/core/response" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +// func RoleRoute(rg *gin.RouterGroup) { +// db := core.GetDB() +// permSrv := NewSystemAuthPermService(db) +// // roleSrv := NewSystemAuthRoleService(db, permSrv) +// // adminSrv := NewSystemAuthAdminService(db, permSrv, roleSrv) +// // service := NewSystemLoginService(db, adminSrv) + +// server := NewSystemAuthRoleService(db, permSrv) + +// handle := RoleHandler{Service: server} + +// rg = rg.Group("/system", middleware.TokenAuth()) +// rg.GET("/role/all", handle.All) +// rg.GET("/role/list", middleware.RecordLog("角色列表"), handle.List) +// rg.GET("/role/detail", middleware.RecordLog("角色详情"), handle.Detail) +// rg.POST("/role/add", middleware.RecordLog("角色新增"), handle.Add) +// rg.POST("/role/edit", middleware.RecordLog("角色编辑"), handle.Edit) +// rg.POST("/role/del", middleware.RecordLog("角色删除"), handle.Del) +// } + +type RoleHandler struct { + Service ISystemAuthRoleService +} + +// all 角色所有 +func (rh RoleHandler) All(c *gin.Context) { + res, err := rh.Service.All() + response.CheckAndRespWithData(c, res, err) +} + +// list 角色列表 +func (rh RoleHandler) List(c *gin.Context) { + var page request.PageReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + res, err := rh.Service.List(page) + response.CheckAndRespWithData(c, res, err) +} + +// detail 角色详情 +func (rh RoleHandler) Detail(c *gin.Context) { + var detailReq SystemAuthRoleDetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := rh.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// add 新增角色 +func (rh RoleHandler) Add(c *gin.Context) { + var addReq SystemAuthRoleAddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { + return + } + response.CheckAndResp(c, rh.Service.Add(addReq)) +} + +// edit 编辑角色 +func (rh RoleHandler) Edit(c *gin.Context) { + var editReq SystemAuthRoleEditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { + return + } + response.CheckAndResp(c, rh.Service.Edit(editReq)) +} + +// del 删除角色 +func (rh RoleHandler) Del(c *gin.Context) { + var delReq SystemAuthRoleDelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + response.CheckAndResp(c, rh.Service.Del(delReq.ID)) +} diff --git a/server/admin/system/role/schema.go b/server/admin/system/role/schema.go new file mode 100644 index 0000000..89f70ce --- /dev/null +++ b/server/admin/system/role/schema.go @@ -0,0 +1,55 @@ +package role + +import "x_admin/core" + +//SystemAuthRoleSimpleResp 系统角色返回简单信息 +type SystemAuthRoleSimpleResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Name string `json:"name" structs:"name"` // 角色名称 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +//SystemAuthRoleResp 系统角色返回信息 +type SystemAuthRoleResp struct { + ID uint `json:"id" structs:"id"` // 主键 + Name string `json:"name" structs:"name"` // 角色名称 + Remark string `json:"remark" structs:"remark"` // 角色备注 + Menus []uint `json:"menus" structs:"menus"` // 关联菜单 + Member int64 `json:"member" structs:"member"` // 成员数量 + Sort uint16 `json:"sort" structs:"sort"` // 角色排序 + IsDisable uint8 `json:"isDisable" structs:"isDisable"` // 是否禁用: [0=否, 1=是] + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +// + +//SystemAuthRoleDetailReq 角色详情参数 +type SystemAuthRoleDetailReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +//SystemAuthRoleAddReq 新增角色参数 +type SystemAuthRoleAddReq struct { + Name string `form:"name" binding:"required,min=1,max=30"` // 角色名称 + Sort int `form:"sort" binding:"gte=0"` // 角色排序 + IsDisable uint8 `form:"isDisable" binding:"oneof=0 1"` // 是否禁用: [0=否, 1=是] + Remark string `form:"remark" binding:"max=200"` // 角色备注 + MenuIds string `form:"menuIds"` // 关联菜单 +} + +//SystemAuthRoleEditReq 编辑角色参数 +type SystemAuthRoleEditReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + Name string `form:"name" binding:"required,min=1,max=30"` // 角色名称 + Sort int `form:"sort" binding:"gte=0"` // 角色排序 + IsDisable uint8 `form:"isDisable" binding:"oneof=0 1"` // 是否禁用: [0=否, 1=是] + Remark string `form:"remark" binding:"max=200"` // 角色备注 + MenuIds string `form:"menuIds"` // 关联菜单 +} + +//SystemAuthRoleDelReq 删除角色参数 +type SystemAuthRoleDelReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} diff --git a/server/admin/system/role/service.go b/server/admin/system/role/service.go new file mode 100644 index 0000000..b8af967 --- /dev/null +++ b/server/admin/system/role/service.go @@ -0,0 +1,186 @@ +package role + +import ( + "strconv" + "strings" + "x_admin/config" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "github.com/fatih/structs" + "gorm.io/gorm" +) + +type ISystemAuthRoleService interface { + All() (res []SystemAuthRoleSimpleResp, e error) + List(page request.PageReq) (res response.PageResp, e error) + Detail(id uint) (res SystemAuthRoleResp, e error) + Add(addReq SystemAuthRoleAddReq) (e error) + Edit(editReq SystemAuthRoleEditReq) (e error) + Del(id uint) (e error) +} + +// NewSystemAuthRoleService 初始化 +func NewSystemAuthRoleService(db *gorm.DB, permSrv ISystemAuthPermService) ISystemAuthRoleService { + return &systemAuthRoleService{db: db, permSrv: permSrv} +} + +// systemAuthRoleService 系统角色服务实现类 +type systemAuthRoleService struct { + db *gorm.DB + permSrv ISystemAuthPermService +} + +// All 角色所有 +func (roleSrv systemAuthRoleService) All() (res []SystemAuthRoleSimpleResp, e error) { + var roles []system.SystemAuthRole + err := roleSrv.db.Order("sort desc, id desc").Find(&roles).Error + if e = response.CheckErr(err, "All Find err"); e != nil { + return + } + response.Copy(&res, roles) + return +} + +// List 根据角色ID获取菜单ID +func (roleSrv systemAuthRoleService) List(page request.PageReq) (res response.PageResp, e error) { + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + roleModel := roleSrv.db.Model(&system.SystemAuthRole{}) + var count int64 + err := roleModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + var roles []system.SystemAuthRole + err = roleModel.Limit(limit).Offset(offset).Order("sort desc, id desc").Find(&roles).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + var roleResp []SystemAuthRoleResp + response.Copy(&roleResp, roles) + for i := 0; i < len(roleResp); i++ { + roleResp[i].Menus = []uint{} + roleResp[i].Member = roleSrv.getMemberCnt(roleResp[i].ID) + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: roleResp, + }, nil +} + +// Detail 角色详情 +func (roleSrv systemAuthRoleService) Detail(id uint) (res SystemAuthRoleResp, e error) { + var role system.SystemAuthRole + err := roleSrv.db.Where("id = ?", id).Limit(1).First(&role).Error + if e = response.CheckErrDBNotRecord(err, "角色已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, role) + res.Member = roleSrv.getMemberCnt(role.ID) + res.Menus, e = roleSrv.permSrv.SelectMenuIdsByRoleId(role.ID) + return +} + +// getMemberCnt 根据角色ID获取成员数量 +func (roleSrv systemAuthRoleService) getMemberCnt(roleId uint) (count int64) { + roleSrv.db.Model(&system.SystemAuthAdmin{}).Where( + "role = ? AND is_delete = ?", roleId, 0).Count(&count) + return +} + +// Add 新增角色 +func (roleSrv systemAuthRoleService) Add(addReq SystemAuthRoleAddReq) (e error) { + var role system.SystemAuthRole + if r := roleSrv.db.Where("name = ?", strings.Trim(addReq.Name, " ")).Limit(1).First(&role); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("角色名称已存在!") + } + response.Copy(&role, addReq) + role.Name = strings.Trim(addReq.Name, " ") + // 事务 + err := roleSrv.db.Transaction(func(tx *gorm.DB) error { + txErr := tx.Create(&role).Error + var te error + if te = response.CheckErr(txErr, "Add Create in tx err"); te != nil { + return te + } + te = roleSrv.permSrv.BatchSaveByMenuIds(role.ID, addReq.MenuIds, tx) + return te + }) + e = response.CheckErr(err, "Add Transaction err") + return +} + +// Edit 编辑角色 +func (roleSrv systemAuthRoleService) Edit(editReq SystemAuthRoleEditReq) (e error) { + err := roleSrv.db.Where("id = ?", editReq.ID).Limit(1).First(&system.SystemAuthRole{}).Error + if e = response.CheckErrDBNotRecord(err, "角色已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + var role system.SystemAuthRole + if r := roleSrv.db.Where("id != ? AND name = ?", editReq.ID, strings.Trim(editReq.Name, " ")).Limit(1).First(&role); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("角色名称已存在!") + } + role.ID = editReq.ID + roleMap := structs.Map(editReq) + delete(roleMap, "ID") + delete(roleMap, "MenuIds") + roleMap["Name"] = strings.Trim(editReq.Name, " ") + // 事务 + err = roleSrv.db.Transaction(func(tx *gorm.DB) error { + txErr := tx.Model(&role).Updates(roleMap).Error + var te error + if te = response.CheckErr(txErr, "Edit Updates in tx err"); te != nil { + return te + } + if te = roleSrv.permSrv.BatchDeleteByRoleId(editReq.ID, tx); te != nil { + return te + } + if te = roleSrv.permSrv.BatchSaveByMenuIds(editReq.ID, editReq.MenuIds, tx); te != nil { + return te + } + te = roleSrv.permSrv.CacheRoleMenusByRoleId(editReq.ID) + return te + }) + e = response.CheckErr(err, "Edit Transaction err") + return +} + +// Del 删除角色 +func (roleSrv systemAuthRoleService) Del(id uint) (e error) { + err := roleSrv.db.Where("id = ?", id).Limit(1).First(&system.SystemAuthRole{}).Error + if e = response.CheckErrDBNotRecord(err, "角色已不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Del First err"); e != nil { + return + } + if r := roleSrv.db.Where("role = ? AND is_delete = ?", id, 0).Limit(1).Find(&system.SystemAuthAdmin{}); r.RowsAffected > 0 { + return response.AssertArgumentError.Make("角色已被管理员使用,请先移除!") + } + // 事务 + err = roleSrv.db.Transaction(func(tx *gorm.DB) error { + txErr := tx.Delete(&system.SystemAuthRole{}, "id = ?", id).Error + var te error + if te = response.CheckErr(txErr, "Del Delete in tx err"); te != nil { + return te + } + if te = roleSrv.permSrv.BatchDeleteByRoleId(id, tx); te != nil { + return te + } + util.RedisUtil.HDel(config.AdminConfig.BackstageRolesKey, strconv.FormatUint(uint64(id), 10)) + return nil + }) + e = response.CheckErr(err, "Del Transaction err") + return +} diff --git a/server/config/admin.go b/server/config/admin.go new file mode 100644 index 0000000..0b83c15 --- /dev/null +++ b/server/config/admin.go @@ -0,0 +1,96 @@ +package config + +import "github.com/gin-gonic/gin" + +//AdminConfig 后台公共配置 +var AdminConfig = adminConfig{ + // 管理缓存键 + BackstageManageKey: "backstage:manage", + // 角色缓存键 + BackstageRolesKey: "backstage:roles", + // 令牌缓存键 + BackstageTokenKey: "backstage:token:", + // 令牌的集合 + BackstageTokenSet: "backstage:token:set:", + + // 免登录验证 + NotLoginUri: []string{ + "system:login", // 登录接口 + "common:index:config", // 配置接口 + }, + + // 免权限验证 + NotAuthUri: []string{ + "system:logout", // 退出登录 + "system:menu:menus", // 系统菜单 + "system:menu:route", // 菜单路由 + "system:admin:upInfo", // 管理员更新 + "system:admin:self", // 管理员信息 + "system:role:all", // 所有角色 + "system:post:all", // 所有岗位 + "system:dept:list", // 所有部门 + "setting:dict:type:all", // 所有字典类型 + "setting:dict:data:all", // 所有字典数据 + "article:cate:all", // 所有文章分类 + }, + + // 演示模式白名单 + ShowWhitelistUri: []string{ + "system:login", // 登录接口 + "system:logout", // 退出登录 + }, + + // 管理员账号id + SuperAdminId: 1, + ReqAdminIdKey: "admin_id", + ReqRoleIdKey: "role", + ReqUsernameKey: "username", + ReqNicknameKey: "nickname", +} + +type adminConfig struct { + BackstageManageKey string + BackstageRolesKey string + BackstageTokenKey string + BackstageTokenSet string + NotLoginUri []string + NotAuthUri []string + ShowWhitelistUri []string + SuperAdminId uint + ReqAdminIdKey string + ReqRoleIdKey string + ReqUsernameKey string + ReqNicknameKey string +} + +func (cnf adminConfig) GetAdminId(c *gin.Context) uint { + adminId, ok := c.Get(cnf.ReqAdminIdKey) + if !ok { + return 0 + } + return adminId.(uint) +} + +func (cnf adminConfig) GetRoleId(c *gin.Context) string { + roleId, ok := c.Get(cnf.ReqRoleIdKey) + if !ok { + return "" + } + return roleId.(string) +} + +func (cnf adminConfig) GetUsername(c *gin.Context) string { + username, ok := c.Get(cnf.ReqUsernameKey) + if !ok { + return "" + } + return username.(string) +} + +func (cnf adminConfig) GetNickname(c *gin.Context) string { + nickname, ok := c.Get(cnf.ReqNicknameKey) + if !ok { + return "" + } + return nickname.(string) +} diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 0000000..fe99b6b --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,122 @@ +package config + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/spf13/viper" +) + +var Config = loadConfig(".") + +// envConfig 环境配置 +type envConfig struct { + RootPath string // 项目根目录 + GinMode string `mapstructure:"GIN_MODE"` // gin运行模式 + PublicUrl string `mapstructure:"PUBLIC_URL"` // 对外发布的Url + OssDomain string `mapstructure:"OSS_DOMAIN"` // OSS域名 + ServerPort int `mapstructure:"SERVER_PORT"` // 服务运行端口 + DisallowModify bool `mapstructure:"DISALLOW_MODIFY"` // 禁止修改操作 (演示功能,限制POST请求) + PublicPrefix string // 资源访问前缀 + UploadDirectory string `mapstructure:"UPLOAD_DIRECTORY"` // 上传文件路径 + RedisUrl string `mapstructure:"REDIS_URL"` // Redis源配置 + RedisPoolSize int // Redis连接池大小 + DatabaseUrl string `mapstructure:"DATABASE_URL"` // 数据源配置 + DbTablePrefix string // Mysql表前缀 + DbDefaultStringSize uint // 数据库string类型字段的默认长度 + DbMaxIdleConns int // 数据库空闲连接池最大值 + DbMaxOpenConns int // 数据库连接池最大值 + DbConnMaxLifetimeSeconds int16 // 连接可复用的最大时间(秒:默认28800秒),请根据这个sql查处的时间设置: show variables like 'wait_timeout' + Version string // 版本 + Secret string // 系统加密字符 + StaticPath string // 静态资源URL路径 + StaticDirectory string // 静态资源本地路径 + RedisPrefix string // Redis键前缀 + UploadImageSize int64 // 上传图片限制 + UploadVideoSize int64 // 上传视频限制 + UploadImageExt []string // 上传图片扩展 + UploadVideoExt []string // 上传视频扩展 +} + +// loadConfig 加载配置 +func loadConfig(envPath string) envConfig { + var cfgPath string + flag.StringVar(&cfgPath, "c", "", "config file envPath.") + flag.Parse() + if cfgPath == "" { + viper.AddConfigPath(envPath) + viper.SetConfigFile(".env") + } else { + viper.SetConfigFile(cfgPath) + } + viper.AutomaticEnv() + // var rootPath string + // if _, filename, _, ok := runtime.Caller(0); ok { + // rootPath = path.Dir(path.Dir(filename)) + // } + rootPath, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + fmt.Println("rootPath:", rootPath) + config := envConfig{ + RootPath: rootPath, + GinMode: "debug", + // 服务运行端口 + ServerPort: 8000, + + OssDomain: "", + + // 禁止修改操作 (演示功能,限制POST请求) + DisallowModify: false, + // 资源访问前缀 + PublicPrefix: "/api/uploads", + // 上传文件路径 + UploadDirectory: "/tmp/uploads/x_admin-go/", + // Redis源配置 + RedisUrl: "redis://localhost:6379", + RedisPoolSize: 100, + // 数据源配置 + DatabaseUrl: "x_admin:x_admin@tcp(localhost:3306)/x_admin?charset=utf8mb4&parseTime=True&loc=Local", + DbTablePrefix: "x_", + DbDefaultStringSize: 256, + DbMaxIdleConns: 10, + DbMaxOpenConns: 100, + // 连接可复用的最大时间(秒:默认28800秒) + DbConnMaxLifetimeSeconds: 28800, + // 全局配置 + // 版本 + Version: "v1.1.0", + // 系统加密字符 + Secret: "UVTIyzCy", + // 静态资源URL路径 + StaticPath: "/api/static", + // 静态资源本地路径 + StaticDirectory: "static", + // Redis键前缀 + RedisPrefix: "Like:", + // 上传图片限制 + UploadImageSize: 1024 * 1024 * 10, + // 上传视频限制 + UploadVideoSize: 1024 * 1024 * 30, + // 上传图片扩展 + UploadImageExt: []string{"png", "jpg", "jpeg", "gif", "ico", "bmp"}, + // 上传视频扩展 + UploadVideoExt: []string{"mp4", "mp3", "avi", "flv", "rmvb", "mov"}, + } + err = viper.ReadInConfig() + if err != nil { + log.Fatal("loadConfig ReadInConfig err:", err) + } + err = viper.Unmarshal(&config) + if err != nil { + log.Fatal("loadConfig Unmarshal err:", err) + } + // PublicUrl未设置设置默认值 + // if config.PublicUrl == "" { + // // config.PublicUrl = "http://127.0.0.1:" + strconv.Itoa(config.ServerPort) + // } + return config +} diff --git a/server/config/gen.go b/server/config/gen.go new file mode 100644 index 0000000..a414316 --- /dev/null +++ b/server/config/gen.go @@ -0,0 +1,17 @@ +package config + +//GenConfig 代码生成器公共配置 +var GenConfig = genConfig{ + // 基础包名 + PackageName: "gencode", + // 是否去除表前缀 + IsRemoveTablePrefix: true, + // 生成代码根路径 + GenRootPath: "/tmp/target", +} + +type genConfig struct { + PackageName string + IsRemoveTablePrefix bool + GenRootPath string +} diff --git a/server/core/db.go b/server/core/db.go new file mode 100644 index 0000000..9dfa6f1 --- /dev/null +++ b/server/core/db.go @@ -0,0 +1,76 @@ +package core + +import ( + "log" + "os" + "time" + "x_admin/config" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" +) + +var db = initMysql() + +func GetDB() *gorm.DB { + return db +} + +// initMysql 初始化mysql会话 +func initMysql() *gorm.DB { + // 日志配置 + slowThreshold := time.Second + ignoreRecordNotFoundError := true + logLevel := logger.Warn + if config.Config.GinMode == "debug" { + logLevel = logger.Info + slowThreshold = 200 * time.Millisecond + ignoreRecordNotFoundError = false + } + logger := logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) + logger.Config{ + SlowThreshold: slowThreshold, // 慢 SQL 阈值 + LogLevel: logLevel, // 日志级别 + IgnoreRecordNotFoundError: ignoreRecordNotFoundError, // 忽略ErrRecordNotFound(记录未找到)错误 + Colorful: true, // 彩色打印 + }, + ) + // 初始化会话 + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: config.Config.DatabaseUrl, // DSN data source name + DefaultStringSize: config.Config.DbDefaultStringSize, // string 类型字段的默认长度 + SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 + }), &gorm.Config{ + SkipDefaultTransaction: true, // 禁用默认事务 + NamingStrategy: schema.NamingStrategy{ + TablePrefix: config.Config.DbTablePrefix, // 表名前缀 + SingularTable: true, // 使用单一表名, eg. `User` => `user` + }, + DisableForeignKeyConstraintWhenMigrating: true, // 禁用自动创建外键约束 + Logger: logger, // 自定义Logger + }) + if err != nil { + log.Fatal("initMysql gorm.Open err:", err) + } + db.InstanceSet("gorm:table_options", "ENGINE=InnoDB") + sqlDB, err := db.DB() + if err != nil { + log.Fatal("initMysql db.DB err:", err) + } + // 数据库空闲连接池最大值 + sqlDB.SetMaxIdleConns(config.Config.DbMaxIdleConns) + // 数据库连接池最大值 + sqlDB.SetMaxOpenConns(config.Config.DbMaxOpenConns) + // 连接可复用的最大时间 + sqlDB.SetConnMaxLifetime(time.Duration(config.Config.DbConnMaxLifetimeSeconds) * time.Second) + return db +} + +func DBTableName(model interface{}) string { + stmt := &gorm.Statement{DB: db} + stmt.Parse(model) + return stmt.Schema.Table +} diff --git a/server/core/logger.go b/server/core/logger.go new file mode 100644 index 0000000..863c9e3 --- /dev/null +++ b/server/core/logger.go @@ -0,0 +1,21 @@ +package core + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" +) + +var Logger = initLogger() + +//initLogger 初始化zap日志 +func initLogger() *zap.SugaredLogger { + zap.NewDevelopmentConfig() + cfg := zap.NewProductionEncoderConfig() + cfg.EncodeTime = zapcore.ISO8601TimeEncoder + cfg.EncodeLevel = zapcore.CapitalLevelEncoder + cfg.FunctionKey = "F" + core := zapcore.NewCore(zapcore.NewConsoleEncoder(cfg), zapcore.AddSync(os.Stderr), zapcore.InfoLevel) + logger := zap.New(core, zap.AddCaller()) + return logger.Sugar() +} diff --git a/server/core/redis.go b/server/core/redis.go new file mode 100644 index 0000000..8beae23 --- /dev/null +++ b/server/core/redis.go @@ -0,0 +1,29 @@ +package core + +import ( + "context" + "log" + "time" + "x_admin/config" + + "github.com/go-redis/redis/v9" +) + +var Redis = initRedis() + +// initRedis 初始化redis客户端 +func initRedis() *redis.Client { + opt, err := redis.ParseURL(config.Config.RedisUrl) + if err != nil { + log.Fatal("initRedis redis.ParseURL err: ", err) + } + opt.PoolSize = config.Config.RedisPoolSize + client := redis.NewClient(opt) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + _, err = client.Ping(ctx).Result() + if err != nil { + log.Fatal("initRedis client.Ping err: ", err) + } + return client +} diff --git a/server/core/request/common.go b/server/core/request/common.go new file mode 100644 index 0000000..4091005 --- /dev/null +++ b/server/core/request/common.go @@ -0,0 +1,7 @@ +package request + +//PageReq 分页请求参数 +type PageReq struct { + PageNo int `form:"pageNo,default=1" validate:"omitempty,gte=1"` // 页码 + PageSize int `form:"pageSize,default=20" validate:"omitempty,gt=0,lte=60"` // 每页大小 +} diff --git a/server/core/response/common.go b/server/core/response/common.go new file mode 100644 index 0000000..5c963cb --- /dev/null +++ b/server/core/response/common.go @@ -0,0 +1,9 @@ +package response + +//PageResp 分页返回值 +type PageResp struct { + Count int64 `json:"count"` // 总数 + PageNo int `json:"pageNo"` // 页No + PageSize int `json:"pageSize"` // 每页Size + Lists interface{} `json:"lists"` // 数据 +} diff --git a/server/core/response/error.go b/server/core/response/error.go new file mode 100644 index 0000000..739e319 --- /dev/null +++ b/server/core/response/error.go @@ -0,0 +1,15 @@ +package response + +import ( + "github.com/gin-gonic/gin" +) + +//NoRoute 无路由的响应 +func NoRoute(c *gin.Context) { + Fail(c, Request404Error) +} + +//NoMethod 无方法的响应 +func NoMethod(c *gin.Context) { + Fail(c, Request405Error) +} diff --git a/server/core/response/response.go b/server/core/response/response.go new file mode 100644 index 0000000..5911382 --- /dev/null +++ b/server/core/response/response.go @@ -0,0 +1,208 @@ +package response + +import ( + "errors" + "net/http" + "strconv" + "x_admin/core" + + "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "go.uber.org/zap" + "gorm.io/gorm" +) + +// RespType 响应类型 +type RespType struct { + code int + msg string + data interface{} +} + +// Response 响应格式结构 +type Response struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` +} + +var ( + Success = RespType{code: 200, msg: "成功"} + Failed = RespType{code: 300, msg: "失败"} + + ParamsValidError = RespType{code: 310, msg: "参数校验错误"} + ParamsTypeError = RespType{code: 311, msg: "参数类型错误"} + RequestMethodError = RespType{code: 312, msg: "请求方法错误"} + AssertArgumentError = RespType{code: 313, msg: "断言参数错误"} + + LoginAccountError = RespType{code: 330, msg: "登录账号或密码错误"} + LoginDisableError = RespType{code: 331, msg: "登录账号已被禁用了"} + TokenEmpty = RespType{code: 332, msg: "token参数为空"} + TokenInvalid = RespType{code: 333, msg: "token参数无效"} + + NoPermission = RespType{code: 403, msg: "无相关权限"} + Request404Error = RespType{code: 404, msg: "请求接口不存在"} + Request405Error = RespType{code: 405, msg: "请求方法不允许"} + + SystemError = RespType{code: 500, msg: "系统错误"} +) + +// Error 实现error方法 +func (rt RespType) Error() string { + return strconv.Itoa(rt.code) + ":" + rt.msg +} + +// Make 以响应类型生成信息 +func (rt RespType) Make(msg string) RespType { + rt.msg = msg + return rt +} + +// MakeData 以响应类型生成数据 +func (rt RespType) MakeData(data interface{}) RespType { + rt.data = data + return rt +} + +// Code 获取code +func (rt RespType) Code() int { + return rt.code +} + +// Msg 获取msg +func (rt RespType) Msg() string { + return rt.msg +} + +// Data 获取data +func (rt RespType) Data() interface{} { + return rt.data +} + +// Result 统一响应 +func Result(c *gin.Context, resp RespType, data interface{}) { + if data == nil { + data = resp.data + } + if resp != Success { + c.Error(resp) + } + c.JSON(http.StatusOK, Response{ + Code: resp.code, + Msg: resp.msg, + Data: data, + }) +} + +// Copy 拷贝结构体 +func Copy(toValue interface{}, fromValue interface{}) interface{} { + if err := copier.Copy(toValue, fromValue); err != nil { + core.Logger.Errorf("Copy err: err=[%+v]", err) + panic(SystemError) + } + return toValue +} + +// Ok 正常响应 +func Ok(c *gin.Context) { + Result(c, Success, []string{}) +} + +// OkWithMsg 正常响应附带msg +func OkWithMsg(c *gin.Context, msg string) { + resp := Success + resp.msg = msg + Result(c, resp, []string{}) +} + +// OkWithData 正常响应附带data +func OkWithData(c *gin.Context, data interface{}) { + Result(c, Success, data) +} + +// respLogger 打印日志 +func respLogger(resp RespType, template string, args ...interface{}) { + loggerFunc := core.Logger.WithOptions(zap.AddCallerSkip(2)).Warnf + if resp.code >= 500 { + loggerFunc = core.Logger.WithOptions(zap.AddCallerSkip(1)).Errorf + } + loggerFunc(template, args...) +} + +// Fail 错误响应 +func Fail(c *gin.Context, resp RespType) { + respLogger(resp, "Request Fail: url=[%s], resp=[%+v]", c.Request.URL.Path, resp) + Result(c, resp, []string{}) +} + +// FailWithMsg 错误响应附带msg +func FailWithMsg(c *gin.Context, resp RespType, msg string) { + resp.msg = msg + respLogger(resp, "Request FailWithMsg: url=[%s], resp=[%+v]", c.Request.URL.Path, resp) + Result(c, resp, []string{}) +} + +// FailWithData 错误响应附带data +func FailWithData(c *gin.Context, resp RespType, data interface{}) { + respLogger(resp, "Request FailWithData: url=[%s], resp=[%+v], data=[%+v]", c.Request.URL.Path, resp, data) + Result(c, resp, data) +} + +// IsFailWithResp 判断是否出现错误,并追加错误返回信息 +func IsFailWithResp(c *gin.Context, err error) bool { + if err == nil { + return false + } + switch v := err.(type) { + // 自定义类型 + case RespType: + data := v.Data() + if data == nil { + data = []string{} + } + FailWithData(c, v, data) + // 其他类型 + default: + Fail(c, SystemError) + } + return true +} + +// CheckAndResp 判断是否出现错误,并返回对应响应 +func CheckAndResp(c *gin.Context, err error) { + if IsFailWithResp(c, err) { + return + } + Ok(c) +} + +// CheckAndRespWithData 判断是否出现错误,并返回对应响应(带data数据) +func CheckAndRespWithData(c *gin.Context, data interface{}, err error) { + if IsFailWithResp(c, err) { + return + } + OkWithData(c, data) +} + +// CheckErr 校验未知错误并抛出 +func CheckErr(err error, template string, args ...interface{}) (e error) { + prefix := ": " + if len(args) > 0 { + prefix = " ," + } + args = append(args, err) + if err != nil { + core.Logger.WithOptions(zap.AddCallerSkip(1)).Errorf(template+prefix+"err=[%+v]", args...) + return SystemError + } + return +} + +// CheckErrDBNotRecord 校验数据库记录不存在的错误 +func CheckErrDBNotRecord(err error, msg string) (e error) { + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + core.Logger.WithOptions(zap.AddCallerSkip(1)).Infof("CheckErrDBNotRecord err: err=[%+v]", err) + return AssertArgumentError.Make(msg) + } + return +} diff --git a/server/core/time.go b/server/core/time.go new file mode 100644 index 0000000..2f3af8d --- /dev/null +++ b/server/core/time.go @@ -0,0 +1,53 @@ +package core + +import ( + "encoding/json" + "time" +) + +const DateFormat = "2006-01-02" +const TimeFormat = "2006-01-02 15:04:05" + +//TsTime 自定义时间格式 +type TsTime int64 +type OnlyRespTsTime time.Time + +////TsDate 自定义日期格式 +//type TsDate int64 +// +//func (tsd *TsDate) UnmarshalJSON(bs []byte) error { +// var date string +// err := json.Unmarshal(bs, &date) +// if err != nil { +// return err +// } +// tt, _ := time.ParseInLocation(DateFormat, date, time.Local) +// *tsd = TsDate(tt.Unix()) +// return nil +//} +// +//func (tsd TsDate) MarshalJSON() ([]byte, error) { +// tt := time.Unix(int64(tsd), 0).Format(DateFormat) +// return json.Marshal(tt) +//} + +func (tst *TsTime) UnmarshalJSON(bs []byte) error { + var date string + err := json.Unmarshal(bs, &date) + if err != nil { + return err + } + tt, _ := time.ParseInLocation(TimeFormat, date, time.Local) + *tst = TsTime(tt.Unix()) + return nil +} + +func (tst TsTime) MarshalJSON() ([]byte, error) { + tt := time.Unix(int64(tst), 0).Format(TimeFormat) + return json.Marshal(tt) +} + +func (otst OnlyRespTsTime) MarshalJSON() ([]byte, error) { + tt := time.Time(otst).Format(TimeFormat) + return json.Marshal(tt) +} diff --git a/server/core/uaparser.go b/server/core/uaparser.go new file mode 100644 index 0000000..e80e5de --- /dev/null +++ b/server/core/uaparser.go @@ -0,0 +1,5 @@ +package core + +import "github.com/ua-parser/uap-go/uaparser" + +var UAParser = uaparser.NewFromSaved() diff --git a/server/generator/enter.go b/server/generator/enter.go new file mode 100644 index 0000000..f3cbd2f --- /dev/null +++ b/server/generator/enter.go @@ -0,0 +1,11 @@ +package generator + +import ( + "x_admin/generator/gen" + + "github.com/gin-gonic/gin" +) + +func RegisterGroup(rg *gin.RouterGroup) { + gen.GenRoute(rg) +} diff --git a/server/generator/gen/gen.go b/server/generator/gen/gen.go new file mode 100644 index 0000000..67a0d2a --- /dev/null +++ b/server/generator/gen/gen.go @@ -0,0 +1,161 @@ +package gen + +import ( + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + + "net/http" + "strings" + "x_admin/middleware" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +func GenRoute(rg *gin.RouterGroup) { + db := core.GetDB() + // permSrv := system.NewSystemAuthPermService(db) + // roleSrv := system.NewSystemAuthRoleService(db, permSrv) + // adminSrv := system.NewSystemAuthAdminService(db, permSrv, roleSrv) + // service := system.NewSystemLoginService(db, adminSrv) + + server := NewGenerateService(db) + + handle := genHandler{Service: server} + + rg = rg.Group("/gen", middleware.TokenAuth()) + rg.GET("/db", handle.dbTables) + rg.GET("/list", handle.List) + rg.GET("/detail", handle.Detail) + rg.POST("/importTable", handle.importTable) + rg.POST("/syncTable", handle.syncTable) + rg.POST("/editTable", handle.editTable) + rg.POST("/delTable", handle.delTable) + rg.GET("/previewCode", handle.previewCode) + rg.GET("/genCode", handle.genCode) + rg.GET("/downloadCode", handle.downloadCode) +} + +type genHandler struct { + Service IGenerateService +} + +// dbTables 数据表列表 +func (gh genHandler) dbTables(c *gin.Context) { + var page request.PageReq + var tbReq DbTablesReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &tbReq)) { + return + } + res, err := gh.Service.DbTables(page, tbReq) + response.CheckAndRespWithData(c, res, err) +} + +// list 生成列表 +func (gh genHandler) List(c *gin.Context) { + var page request.PageReq + var listReq ListTableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := gh.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +// detail 生成详情 +func (gh genHandler) Detail(c *gin.Context) { + var detailReq DetailTableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := gh.Service.Detail(detailReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// importTable 导入表结构 +func (gh genHandler) importTable(c *gin.Context) { + var importReq ImportTableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &importReq)) { + return + } + err := gh.Service.ImportTable(strings.Split(importReq.Tables, ",")) + response.CheckAndResp(c, err) +} + +// syncTable 同步表结构 +func (gh genHandler) syncTable(c *gin.Context) { + var syncReq SyncTableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &syncReq)) { + return + } + err := gh.Service.SyncTable(syncReq.ID) + response.CheckAndResp(c, err) +} + +// editTable 编辑表结构 +func (gh genHandler) editTable(c *gin.Context) { + var editReq EditTableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &editReq)) { + return + } + err := gh.Service.EditTable(editReq) + response.CheckAndResp(c, err) +} + +// delTable 删除表结构 +func (gh genHandler) delTable(c *gin.Context) { + var delReq DelTableReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &delReq)) { + return + } + err := gh.Service.DelTable(delReq.Ids) + response.CheckAndResp(c, err) +} + +// previewCode 预览代码 +func (gh genHandler) previewCode(c *gin.Context) { + var previewReq PreviewCodeReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &previewReq)) { + return + } + res, err := gh.Service.PreviewCode(previewReq.ID) + response.CheckAndRespWithData(c, res, err) +} + +// genCode 生成代码 +func (gh genHandler) genCode(c *gin.Context) { + var genReq GenCodeReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &genReq)) { + return + } + for _, table := range strings.Split(genReq.Tables, ",") { + err := gh.Service.GenCode(table) + if response.IsFailWithResp(c, err) { + return + } + } + response.Ok(c) +} + +// downloadCode 下载代码 +func (gh genHandler) downloadCode(c *gin.Context) { + var downloadReq DownloadReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &downloadReq)) { + return + } + zipBytes, err := gh.Service.DownloadCode(strings.Split(downloadReq.Tables, ",")) + if response.IsFailWithResp(c, err) { + return + } + contentType := "application/zip" + c.Header("Content-Type", contentType) + c.Header("Content-Disposition", "attachment; filename=gen-"+downloadReq.Tables+".zip") + c.Data(http.StatusOK, contentType, zipBytes) +} diff --git a/server/generator/gen/schema.go b/server/generator/gen/schema.go new file mode 100644 index 0000000..91f4d5c --- /dev/null +++ b/server/generator/gen/schema.go @@ -0,0 +1,179 @@ +package gen + +import ( + "time" + "x_admin/core" +) + +// DbTablesReq 库表列表参数 +type DbTablesReq struct { + TableName string `form:"tableName"` // 表名称 + TableComment string `form:"tableComment"` // 表描述 +} + +// ListTableReq 生成列表参数 +type ListTableReq struct { + TableName string `form:"tableName"` // 表名称 + TableComment string `form:"tableComment"` // 表描述 + StartTime time.Time `form:"startTime" time_format:"2006-01-02"` // 开始时间 + EndTime time.Time `form:"endTime" time_format:"2006-01-02"` // 结束时间 +} + +// DetailTableReq 生成详情参数 +type DetailTableReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +// ImportTableReq 导入表结构参数 +type ImportTableReq struct { + Tables string `form:"tables" binding:"required"` // 导入的表, 用","分隔 +} + +// SyncTableReq 同步表结构参数 +type SyncTableReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +// EditColumn 表编辑列 +type EditColumn struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + TableID uint `form:"tableId" binding:"required,gt=0"` // 表ID + + ColumnName string `form:"columnName" binding:"required,max=200"` // 列名称 + ColumnLength uint `form:"columnLength" binding:"required,max=5"` // 列长度 + ColumnType string `form:"columnType" binding:"required,max=100"` // 列类型 + + GoField string `form:"goField" binding:"required,max=100"` // 字段 + GoType string `form:"goType" binding:"required,max=100"` // 字段类型 + + ColumnComment string `form:"columnComment" binding:"required,max=200"` // 列描述 + + IsPk uint8 `form:"isPk" binding:"oneof=0 1"` // 是否主键: [0=否, 1=是] + IsIncrement uint8 `form:"isIncrement" binding:"oneof=0 1"` // 是否自增: [0=否, 1=是] + IsRequired uint8 `form:"isStop" binding:"oneof=0 1"` // 是否必填: [0=否, 1=是] + IsInsert uint8 `form:"isInsert" binding:"oneof=0 1"` // 是否新增字段: [0=否, 1=是] + IsEdit uint8 `form:"isEdit" binding:"oneof=0 1"` // 是否编辑字段: [0=否, 1=是] + IsList uint8 `form:"isList" binding:"oneof=0 1"` // 是否列表字段: [0=否, 1=是] + IsQuery uint8 `form:"isQuery" binding:"oneof=0 1"` // 是否查询字段: [0=否, 1=是] + QueryType string `form:"queryType" binding:"required,max=30"` // 查询方式 + HtmlType string `form:"htmlType" binding:"required,max=30"` // 表单类型 + DictType string `form:"dictType" binding:"required,max=200"` // 字典类型 +} + +// EditTableReq 编辑表结构参数 +type EditTableReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 + TableName string `form:"tableName" binding:"required,min=1,max=200"` // 表名称 + EntityName string `form:"entityName" binding:"required,min=1,max=200"` // 实体名称 + TableComment string `form:"tableComment" binding:"required,min=1,max=200"` // 表描述 + AuthorName string `form:"authorName" binding:"max=100"` // 作者名称 + Remarks string `form:"remarks" binding:"max=60"` // 备注信息 + GenTpl string `form:"genTpl" binding:"oneof=crud tree"` // 生成模板方式: [crud=单表, tree=树表] + ModuleName string `form:"moduleName" binding:"required,min=1,max=60"` // 生成模块名 + FunctionName string `form:"functionName" binding:"required,min=1,max=60"` // 生成功能名 + GenType int `form:"genType" binding:"oneof=0 1"` // 生成代码方式: [0=zip压缩包, 1=自定义路径] + GenPath string `form:"genPath,default=/" binding:"required,max=60"` // 生成路径 + TreePrimary string `form:"treePrimary"` // 树表主键 + TreeParent string `form:"treeParent"` // 树表父键 + TreeName string `form:"treeName"` // 树表名称 + SubTableName string `form:"subTableName"` // 子表名称 + SubTableFk string `form:"subTableFk"` // 子表外键 + Columns []EditColumn `form:"columns" binding:"required"` // 字段列表 +} + +// DelTableReq 删除表结构参数 +type DelTableReq struct { + Ids []uint `form:"ids" binding:"required"` // 主键 +} + +// PreviewCodeReq 预览代码参数 +type PreviewCodeReq struct { + ID uint `form:"id" binding:"required,gt=0"` // 主键 +} + +// GenCodeReq 生成代码参数 +type GenCodeReq struct { + Tables string `form:"tables" binding:"required"` // 生成的表, 用","分隔 +} + +// DownloadReq 下载代码参数 +type DownloadReq struct { + Tables string `form:"tables" binding:"required"` // 下载的表, 用","分隔 +} + +// DbTableResp 数据表返回信息 +type DbTableResp struct { + TableName string `json:"tableName" structs:"tableName"` // 表的名称 + TableComment string `json:"tableComment" structs:"tableComment"` // 表的描述 + CreateTime core.OnlyRespTsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.OnlyRespTsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +// GenTableResp 生成表返回信息 +type GenTableResp struct { + ID uint `json:"id" structs:"id"` // 主键 + GenType int `json:"genType" structs:"genType"` // 生成类型 + TableName string `json:"tableName" structs:"tableName"` // 表名称 + TableComment string `json:"tableComment" structs:"tableComment"` // 表描述 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +// GenTableBaseResp 生成表基本返回信息 +type GenTableBaseResp struct { + ID uint `json:"id" structs:"id"` // 主键 + TableName string `json:"tableName" structs:"tableName"` // 表的名称 + TableComment string `json:"tableComment" structs:"tableComment"` // 表的描述 + EntityName string `json:"entityName" structs:"entityName"` // 实体名称 + AuthorName string `json:"authorName" structs:"authorName"` // 作者名称 + Remarks string `json:"remarks" structs:"remarks"` // 备注信息 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +// GenTableGenResp 生成表生成返回信息 +type GenTableGenResp struct { + GenTpl string `json:"genTpl" structs:"genTpl"` // 生成模板方式: [crud=单表, tree=树表] + GenType int `json:"genType" structs:"genType"` // 生成代码方式: [0=zip压缩包, 1=自定义路径] + GenPath string `json:"genPath" structs:"genPath"` // 生成代码路径: [不填默认项目路径] + ModuleName string `json:"moduleName" structs:"moduleName"` // 生成模块名 + FunctionName string `json:"functionName" structs:"functionName"` // 生成功能名 + TreePrimary string `json:"treePrimary" structs:"treePrimary"` // 树主键字段 + TreeParent string `json:"treeParent" structs:"treeParent"` // 树父级字段 + TreeName string `json:"treeName" structs:"treeName"` // 树显示字段 + SubTableName string `json:"subTableName" structs:"subTableName"` // 关联表名称 + SubTableFk string `json:"subTableFk" structs:"subTableFk"` // 关联表外键 +} + +// GenColumnResp 生成列返回信息 +type GenColumnResp struct { + ID uint `json:"id" structs:"id"` // 字段主键 + TableID uint `json:"tableId" structs:"tableId"` // 归属表主键 + + ColumnName string `json:"columnName" structs:"columnName"` // 字段名称 + ColumnComment string `json:"columnComment" structs:"columnComment"` // 字段描述 + ColumnLength int `json:"columnLength" structs:"columnLength"` // 字段长度 + ColumnType string `json:"columnType" structs:"columnType"` // 字段类型 + GoType string `json:"goType" structs:"goType"` // Go类型 + GoField string `json:"goField" structs:"goField"` // Go字段 + + IsPk uint8 `json:"isPk" structs:"isPk"` + IsIncrement uint8 `json:"isIncrement" structs:"isIncrement"` + IsRequired uint8 `json:"isRequired" structs:"isRequired"` // 是否必填 + IsInsert uint8 `json:"isInsert" structs:"isInsert"` // 是否为插入字段 + IsEdit uint8 `json:"isEdit" structs:"isEdit"` // 是否编辑字段 + IsList uint8 `json:"isList" structs:"isList"` // 是否列表字段 + IsQuery uint8 `json:"isQuery" structs:"isQuery"` // 是否查询字段 + QueryType string `json:"queryType" structs:"queryType"` // 查询方式: [等于、不等于、大于、小于、范围] + HtmlType string `json:"htmlType" structs:"htmlType"` // 显示类型: [文本框、文本域、下拉框、复选框、单选框、日期控件] + DictType string `json:"dictType" structs:"dictType"` // 字典类型 + CreateTime core.TsTime `json:"createTime" structs:"createTime"` // 创建时间 + UpdateTime core.TsTime `json:"updateTime" structs:"updateTime"` // 更新时间 +} + +// GenTableDetailResp 生成表详情返回信息 +type GenTableDetailResp struct { + Base GenTableBaseResp `json:"base" structs:"base"` // 基本信息 + Gen GenTableGenResp `json:"gen" structs:"gen"` // 生成信息 + Column []GenColumnResp `json:"column" structs:"column"` // 字段列表 +} diff --git a/server/generator/gen/service.go b/server/generator/gen/service.go new file mode 100644 index 0000000..818b2f2 --- /dev/null +++ b/server/generator/gen/service.go @@ -0,0 +1,448 @@ +package gen + +import ( + "archive/zip" + "bytes" + "x_admin/config" + "x_admin/core/request" + "x_admin/core/response" + + "strings" + "x_admin/generator/tpl_utils" + "x_admin/model/gen" + "x_admin/util" + + "gorm.io/gorm" +) + +type IGenerateService interface { + DbTables(page request.PageReq, req DbTablesReq) (res response.PageResp, e error) + List(page request.PageReq, listReq ListTableReq) (res response.PageResp, e error) + Detail(id uint) (res GenTableDetailResp, e error) + ImportTable(tableNames []string) (e error) + SyncTable(id uint) (e error) + EditTable(editReq EditTableReq) (e error) + DelTable(ids []uint) (e error) + PreviewCode(id uint) (res map[string]string, e error) + GenCode(tableName string) (e error) + DownloadCode(tableNames []string) ([]byte, error) +} + +// NewGenerateService 初始化 +func NewGenerateService(db *gorm.DB) IGenerateService { + return &generateService{db: db} +} + +// GenerateService 代码生成服务实现类 +type generateService struct { + db *gorm.DB +} + +// DbTables 库表列表 +func (genSrv generateService) DbTables(page request.PageReq, dbReq DbTablesReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + tbModel := tpl_utils.GenUtil.GetDbTablesQuery(genSrv.db, dbReq.TableName, dbReq.TableComment) + // 总数 + var count int64 + err := tbModel.Count(&count).Error + if e = response.CheckErr(err, "DbTables Count err"); e != nil { + return + } + // 数据 + var tbResp []DbTableResp + err = tbModel.Limit(limit).Offset(offset).Find(&tbResp).Error + if e = response.CheckErr(err, "DbTables Find err"); e != nil { + return + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: tbResp, + }, nil +} + +// List 生成列表 +func (genSrv generateService) List(page request.PageReq, listReq ListTableReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + genModel := genSrv.db.Model(&gen.GenTable{}) + if listReq.TableName != "" { + genModel = genModel.Where("table_name like ?", "%"+listReq.TableName+"%") + } + if listReq.TableComment != "" { + genModel = genModel.Where("table_comment like ?", "%"+listReq.TableComment+"%") + } + if !listReq.StartTime.IsZero() { + genModel = genModel.Where("create_time >= ?", listReq.StartTime.Unix()) + } + if !listReq.EndTime.IsZero() { + genModel = genModel.Where("create_time <= ?", listReq.EndTime.Unix()) + } + // 总数 + var count int64 + err := genModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + // 数据 + var genResp []GenTableResp + err = genModel.Limit(limit).Offset(offset).Find(&genResp).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: genResp, + }, nil +} + +// Detail 生成详情 +func (genSrv generateService) Detail(id uint) (res GenTableDetailResp, e error) { + var genTb gen.GenTable + err := genSrv.db.Where("id = ?", id).Limit(1).First(&genTb).Error + if e = response.CheckErrDBNotRecord(err, "查询的数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail Find err"); e != nil { + return + } + var columns []gen.GenTableColumn + err = genSrv.db.Where("table_id = ?", id).Order("sort").Find(&columns).Error + if e = response.CheckErr(err, "Detail Find err"); e != nil { + return + } + var base GenTableBaseResp + response.Copy(&base, genTb) + var gen GenTableGenResp + response.Copy(&gen, genTb) + var colResp []GenColumnResp + response.Copy(&colResp, columns) + return GenTableDetailResp{ + Base: base, + Gen: gen, + Column: colResp, + }, e +} + +// ImportTable 导入表结构 +func (genSrv generateService) ImportTable(tableNames []string) (e error) { + var dbTbs []DbTableResp + err := tpl_utils.GenUtil.GetDbTablesQueryByNames(genSrv.db, tableNames).Find(&dbTbs).Error + if e = response.CheckErr(err, "ImportTable Find tables err"); e != nil { + return + } + var tables []gen.GenTable + response.Copy(&tables, dbTbs) + if len(tables) == 0 { + e = response.AssertArgumentError.Make("表不存在!") + return + } + err = genSrv.db.Transaction(func(tx *gorm.DB) error { + for i := 0; i < len(tables); i++ { + //生成表信息 + genTable := tpl_utils.GenUtil.InitTable(tables[i]) + txErr := tx.Create(&genTable).Error + if te := response.CheckErr(txErr, "ImportTable Create table err"); te != nil { + return te + } + // 生成列信息 + var columns []gen.GenTableColumn + txErr = tpl_utils.GenUtil.GetDbTableColumnsQueryByName(genSrv.db, tables[i].TableName).Find(&columns).Error + if te := response.CheckErr(txErr, "ImportTable Find columns err"); te != nil { + return te + } + for j := 0; j < len(columns); j++ { + column := tpl_utils.GenUtil.InitColumn(genTable.ID, columns[j]) + txErr = tx.Create(&column).Error + if te := response.CheckErr(txErr, "ImportTable Create column err"); te != nil { + return te + } + } + } + return nil + }) + e = response.CheckErr(err, "ImportTable Transaction err") + return nil +} + +// SyncTable 同步表结构 +func (genSrv generateService) SyncTable(id uint) (e error) { + //旧数据 + var genTable gen.GenTable + err := genSrv.db.Where("id = ?", id).Limit(1).First(&genTable).Error + if e = response.CheckErrDBNotRecord(err, "生成数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "SyncTable First err"); e != nil { + return + } + var genTableCols []gen.GenTableColumn + err = genSrv.db.Where("table_id = ?", id).Order("sort").Find(&genTableCols).Error + if e = response.CheckErr(err, "SyncTable Find err"); e != nil { + return + } + if len(genTableCols) <= 0 { + e = response.AssertArgumentError.Make("旧数据异常!") + return + } + prevColMap := make(map[string]gen.GenTableColumn) + for i := 0; i < len(genTableCols); i++ { + prevColMap[genTableCols[i].ColumnName] = genTableCols[i] + } + //新数据 + var columns []gen.GenTableColumn + err = tpl_utils.GenUtil.GetDbTableColumnsQueryByName(genSrv.db, genTable.TableName).Find(&columns).Error + if e = response.CheckErr(err, "SyncTable Find new err"); e != nil { + return + } + if len(columns) <= 0 { + e = response.AssertArgumentError.Make("同步结构失败,原表结构不存在!") + return + } + //事务处理 + err = genSrv.db.Transaction(func(tx *gorm.DB) error { + //处理新增和更新 + for i := 0; i < len(columns); i++ { + col := tpl_utils.GenUtil.InitColumn(id, columns[i]) + if prevCol, ok := prevColMap[columns[i].ColumnName]; ok { + //更新 + col.ID = prevCol.ID + if col.IsList == 0 { + col.DictType = prevCol.DictType + col.QueryType = prevCol.QueryType + } + if prevCol.IsRequired == 1 && prevCol.IsPk == 0 && prevCol.IsInsert == 1 || prevCol.IsEdit == 1 { + col.HtmlType = prevCol.HtmlType + col.IsRequired = prevCol.IsRequired + } + txErr := tx.Save(&col).Error + if te := response.CheckErr(txErr, "SyncTable Save err"); te != nil { + return te + } + } else { + //新增 + txErr := tx.Create(&col).Error + if te := response.CheckErr(txErr, "SyncTable Create err"); te != nil { + return te + } + } + } + //处理删除 + colNames := make([]string, len(columns)) + for i := 0; i < len(columns); i++ { + colNames[i] = columns[i].ColumnName + } + delColIds := make([]uint, 0) + for _, prevCol := range prevColMap { + if !util.ToolsUtil.Contains(colNames, prevCol.ColumnName) { + delColIds = append(delColIds, prevCol.ID) + } + } + txErr := tx.Delete(&gen.GenTableColumn{}, "id in ?", delColIds).Error + if te := response.CheckErr(txErr, "SyncTable Delete err"); te != nil { + return te + } + return nil + }) + e = response.CheckErr(err, "SyncTable Transaction err") + return nil +} + +// EditTable 编辑表结构 +func (genSrv generateService) EditTable(editReq EditTableReq) (e error) { + if editReq.GenTpl == tpl_utils.GenConstants.TplTree { + if editReq.TreePrimary == "" { + e = response.AssertArgumentError.Make("树主ID不能为空!") + return + } + if editReq.TreeParent == "" { + e = response.AssertArgumentError.Make("树父ID不能为空!") + return + } + } + var genTable gen.GenTable + err := genSrv.db.Where("id = ?", editReq.ID).Limit(1).First(&genTable).Error + if e = response.CheckErrDBNotRecord(err, "数据已丢失!"); e != nil { + return + } + if e = response.CheckErr(err, "EditTable First err"); e != nil { + return + } + response.Copy(&genTable, editReq) + err = genSrv.db.Transaction(func(tx *gorm.DB) error { + genTable.SubTableName = strings.Replace(editReq.SubTableName, config.Config.DbTablePrefix, "", 1) + txErr := tx.Save(&genTable).Error + if te := response.CheckErr(txErr, "EditTable Save GenTable err"); te != nil { + return te + } + for i := 0; i < len(editReq.Columns); i++ { + var col gen.GenTableColumn + response.Copy(&col, editReq.Columns[i]) + txErr = tx.Save(&col).Error + if te := response.CheckErr(txErr, "EditTable Save GenTableColumn err"); te != nil { + return te + } + } + return nil + }) + e = response.CheckErr(err, "EditTable Transaction err") + return +} + +// DelTable 删除表结构 +func (genSrv generateService) DelTable(ids []uint) (e error) { + err := genSrv.db.Transaction(func(tx *gorm.DB) error { + txErr := tx.Delete(&gen.GenTable{}, "id in ?", ids).Error + if te := response.CheckErr(txErr, "DelTable Delete GenTable err"); te != nil { + return te + } + txErr = tx.Delete(&gen.GenTableColumn{}, "table_id in ?", ids).Error + if te := response.CheckErr(txErr, "DelTable Delete GenTableColumn err"); te != nil { + return te + } + return nil + }) + e = response.CheckErr(err, "DelTable Transaction err") + return +} + +// getSubTableInfo 根据主表获取子表主键和列信息 +func (genSrv generateService) getSubTableInfo(genTable gen.GenTable) (pkCol gen.GenTableColumn, cols []gen.GenTableColumn, e error) { + if genTable.SubTableName == "" || genTable.SubTableFk == "" { + return + } + var table gen.GenTable + err := genSrv.db.Where("table_name = ?", genTable.SubTableName).Limit(1).First(&table).Error + if e = response.CheckErrDBNotRecord(err, "子表记录丢失!"); e != nil { + return + } + if e = response.CheckErr(err, "getSubTableInfo First err"); e != nil { + return + } + err = tpl_utils.GenUtil.GetDbTableColumnsQueryByName(genSrv.db, genTable.SubTableName).Find(&cols).Error + if e = response.CheckErr(err, "getSubTableInfo Find err"); e != nil { + return + } + pkCol = tpl_utils.GenUtil.InitColumn(table.ID, tpl_utils.GenUtil.GetTablePriCol(cols)) + return +} + +// renderCodeByTable 根据主表和模板文件渲染模板代码 +func (genSrv generateService) renderCodeByTable(genTable gen.GenTable) (res map[string]string, e error) { + var columns []gen.GenTableColumn + err := genSrv.db.Where("table_id = ?", genTable.ID).Order("sort").Find(&columns).Error + if e = response.CheckErr(err, "renderCodeByTable Find err"); e != nil { + return + } + //获取子表信息 + pkCol, cols, err := genSrv.getSubTableInfo(genTable) + if e = response.CheckErr(err, "renderCodeByTable getSubTableInfo err"); e != nil { + return + } + + //获取模板变量信息 + vars := tpl_utils.TemplateUtil.PrepareVars(genTable, columns, pkCol, cols) + //生成模板内容 + res = make(map[string]string) + tplPaths := tpl_utils.TemplateUtil.GetTemplatePaths(genTable.GenTpl) + for _, tplPath := range tplPaths { + res[tplPath], err = tpl_utils.TemplateUtil.Render(tplPath, vars) + if e = response.CheckErr(err, "renderCodeByTable Render err"); e != nil { + return + } + } + return +} + +// PreviewCode 预览代码 +func (genSrv generateService) PreviewCode(id uint) (res map[string]string, e error) { + var genTable gen.GenTable + err := genSrv.db.Where("id = ?", id).Limit(1).First(&genTable).Error + if e = response.CheckErrDBNotRecord(err, "记录丢失!"); e != nil { + return + } + if e = response.CheckErr(err, "PreviewCode First err"); e != nil { + return + } + //获取模板内容 + tplCodeMap, err := genSrv.renderCodeByTable(genTable) + if e = response.CheckErr(err, "PreviewCode renderCodeByTable err"); e != nil { + return + } + res = make(map[string]string) + for tplPath, tplCode := range tplCodeMap { + res[strings.ReplaceAll(tplPath, ".tpl", "")] = tplCode + } + return +} + +// GenCode 生成代码 (自定义路径) +func (genSrv generateService) GenCode(tableName string) (e error) { + var genTable gen.GenTable + err := genSrv.db.Where("table_name = ?", tableName).Order("id desc").Limit(1).First(&genTable).Error + if e = response.CheckErrDBNotRecord(err, "记录丢失!"); e != nil { + return + } + if e = response.CheckErr(err, "GenCode First err"); e != nil { + return + } + //获取模板内容 + tplCodeMap, err := genSrv.renderCodeByTable(genTable) + if e = response.CheckErr(err, "GenCode renderCodeByTable err"); e != nil { + return + } + //获取生成根路径 + basePath := tpl_utils.TemplateUtil.GetGenPath(genTable) + //生成代码文件 + err = tpl_utils.TemplateUtil.GenCodeFiles(tplCodeMap, genTable.TableName, basePath) + if e = response.CheckErr(err, "GenCode GenCodeFiles err"); e != nil { + return + } + return +} + +// genZipCode 生成代码 (压缩包下载) +func (genSrv generateService) genZipCode(zipWriter *zip.Writer, tableName string) (e error) { + var genTable gen.GenTable + err := genSrv.db.Where("table_name = ?", tableName).Order("id desc").Limit(1).First(&genTable).Error + if e = response.CheckErrDBNotRecord(err, "记录丢失!"); e != nil { + return + } + if e = response.CheckErr(err, "genZipCode First err"); e != nil { + return + } + //获取模板内容 + tplCodeMap, err := genSrv.renderCodeByTable(genTable) + if e = response.CheckErr(err, "genZipCode renderCodeByTable err"); e != nil { + return + } + //压缩文件 + err = tpl_utils.TemplateUtil.GenZip(zipWriter, tplCodeMap, genTable.ModuleName) + if e = response.CheckErr(err, "genZipCode GenZip err"); e != nil { + return + } + return +} + +// DownloadCode 下载代码 +func (genSrv generateService) DownloadCode(tableNames []string) ([]byte, error) { + buf := new(bytes.Buffer) + zipWriter := zip.NewWriter(buf) + for _, tableName := range tableNames { + err := genSrv.genZipCode(zipWriter, tableName) + if err != nil { + return nil, response.CheckErr(err, "DownloadCode genZipCode for %s err", tableName) + } + } + err := zipWriter.Close() + if err != nil { + return nil, response.CheckErr(err, "DownloadCode zipWriter.Close err") + } + return buf.Bytes(), nil +} diff --git a/server/generator/templates/gocode/controller.go copy.tpl b/server/generator/templates/gocode/controller.go copy.tpl new file mode 100644 index 0000000..45c660b --- /dev/null +++ b/server/generator/templates/gocode/controller.go copy.tpl @@ -0,0 +1,82 @@ +package {{{ .TableName }}} + +import ( + "github.com/gin-gonic/gin" + "x_admin/admin/schemas/req" + "x_admin/admin/service/system" + "x_admin/core" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/util" +) + + +func {{{ title (toCamelCase .ModuleName) }}}Route(rg *gin.RouterGroup) { + db := core.GetDB() + + server := New{{{ title (toCamelCase .EntityName) }}}Service(db) + + handle := productHandler{service: server} + + rg = rg.Group("/", middleware.TokenAuth()) + rg.GET("/{{{ .ModuleName }}}/list", handle.List) + rg.GET("/{{{ .ModuleName }}}/detail", handle.Detail) + rg.POST("/{{{ .ModuleName }}}/add", handle.Add) + rg.POST("/{{{ .ModuleName }}}/edit", handle.Edit) + rg.POST("/{{{ .ModuleName }}}/del", handle.Del) +} +type {{{ toCamelCase .ModuleName }}}Handler struct { + service I{{{ title (toCamelCase .EntityName) }}}Service +} + +//list {{{ .ModuleName }}}列表 +func (hd {{{ toCamelCase .ModuleName }}}Handler) List(c *gin.Context) { + var page request.PageReq + var listReq {{{ title (toCamelCase .EntityName) }}}ListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := hd.service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +//detail {{{ .ModuleName }}}详情 +func (hd {{{ toCamelCase .ModuleName }}}Handler) Detail(c *gin.Context) { + var detailReq {{{ title (toCamelCase .EntityName) }}}DetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := hd.service.Detail(detailReq.{{{ title (toCamelCase .PrimaryKey) }}}) + response.CheckAndRespWithData(c, res, err) +} + +//add {{{ .ModuleName }}}新增 +func (hd {{{ toCamelCase .ModuleName }}}Handler) Add(c *gin.Context) { + var addReq {{{ title (toCamelCase .EntityName) }}}AddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &addReq)) { + return + } + response.CheckAndResp(c, hd.service.Add(addReq)) +} + +//edit {{{ .ModuleName }}}编辑 +func (hd {{{ toCamelCase .ModuleName }}}Handler) Edit(c *gin.Context) { + var editReq {{{ title (toCamelCase .EntityName) }}}EditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &editReq)) { + return + } + response.CheckAndResp(c, hd.service.Edit(editReq)) +} + +//del {{{ .ModuleName }}}删除 +func (hd {{{ toCamelCase .ModuleName }}}Handler) Del(c *gin.Context) { + var delReq {{{ title (toCamelCase .EntityName) }}}DelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &delReq)) { + return + } + response.CheckAndResp(c, hd.service.Del(delReq.{{{ title (toCamelCase .PrimaryKey) }}})) +} diff --git a/server/generator/templates/gocode/controller.go.tpl b/server/generator/templates/gocode/controller.go.tpl new file mode 100644 index 0000000..706b3e3 --- /dev/null +++ b/server/generator/templates/gocode/controller.go.tpl @@ -0,0 +1,64 @@ +package {{{ .ModuleName }}} + +import ( + "github.com/gin-gonic/gin" + "x_admin/core/request" + "x_admin/core/response" + "x_admin/util" +) + + +type {{{ title (toCamelCase .ModuleName) }}}Handler struct { + Service I{{{ title (toCamelCase .EntityName) }}}Service +} + +//list {{{ .ModuleName }}}列表 +func (hd {{{ title (toCamelCase .ModuleName) }}}Handler) List(c *gin.Context) { + var page request.PageReq + var listReq {{{ title (toCamelCase .EntityName) }}}ListReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &page)) { + return + } + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { + return + } + res, err := hd.Service.List(page, listReq) + response.CheckAndRespWithData(c, res, err) +} + +//detail {{{ .ModuleName }}}详情 +func (hd {{{ title (toCamelCase .ModuleName) }}}Handler) Detail(c *gin.Context) { + var detailReq {{{ title (toCamelCase .EntityName) }}}DetailReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &detailReq)) { + return + } + res, err := hd.Service.Detail(detailReq.{{{ title (toCamelCase .PrimaryKey) }}}) + response.CheckAndRespWithData(c, res, err) +} + +//add {{{ .ModuleName }}}新增 +func (hd {{{ title (toCamelCase .ModuleName) }}}Handler) Add(c *gin.Context) { + var addReq {{{ title (toCamelCase .EntityName) }}}AddReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &addReq)) { + return + } + response.CheckAndResp(c, hd.Service.Add(addReq)) +} + +//edit {{{ .ModuleName }}}编辑 +func (hd {{{ title (toCamelCase .ModuleName) }}}Handler) Edit(c *gin.Context) { + var editReq {{{ title (toCamelCase .EntityName) }}}EditReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &editReq)) { + return + } + response.CheckAndResp(c, hd.Service.Edit(editReq)) +} + +//del {{{ .ModuleName }}}删除 +func (hd {{{ title (toCamelCase .ModuleName) }}}Handler) Del(c *gin.Context) { + var delReq {{{ title (toCamelCase .EntityName) }}}DelReq + if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &delReq)) { + return + } + response.CheckAndResp(c, hd.Service.Del(delReq.{{{ title (toCamelCase .PrimaryKey) }}})) +} diff --git a/server/generator/templates/gocode/model.go.tpl b/server/generator/templates/gocode/model.go.tpl new file mode 100644 index 0000000..f3b8434 --- /dev/null +++ b/server/generator/templates/gocode/model.go.tpl @@ -0,0 +1,10 @@ +package model + +//{{{ title (toCamelCase .EntityName) }}} {{{ .FunctionName }}}实体 +type {{{ title (toCamelCase .EntityName) }}} struct { + {{{- range .Columns }}} + {{{- if not (contains $.SubTableFields .ColumnName) }}} + {{{ title (toCamelCase .GoField) }}} {{{ if eq .GoType "core.TsTime" }}} int64 {{{ else }}} {{{ .GoType }}} {{{ end }}} `gorm:"{{{ if .IsPk }}}primarykey;{{{ end }}}comment:'{{{ .ColumnComment }}}'"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} diff --git a/server/generator/templates/gocode/route.go.tpl b/server/generator/templates/gocode/route.go.tpl new file mode 100644 index 0000000..0dceb0a --- /dev/null +++ b/server/generator/templates/gocode/route.go.tpl @@ -0,0 +1,25 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "x_admin/core" + "x_admin/middleware" + "x_admin/admin/{{{ .ModuleName }}}" +) + +// 请在 admin/entry.go 目录引入这个函数 +// {{{ title (toCamelCase .ModuleName) }}}Route(rg) +func {{{ title (toCamelCase .ModuleName) }}}Route(rg *gin.RouterGroup) { + db := core.GetDB() + + server := {{{ .ModuleName }}}.New{{{ title (toCamelCase .EntityName) }}}Service(db) + + handle := {{{ .ModuleName}}}.{{{ title (toCamelCase .EntityName) }}}Handler{Service: server} + + rg = rg.Group("/", middleware.TokenAuth()) + rg.GET("/{{{ .ModuleName }}}/list", handle.List) + rg.GET("/{{{ .ModuleName }}}/detail", handle.Detail) + rg.POST("/{{{ .ModuleName }}}/add", handle.Add) + rg.POST("/{{{ .ModuleName }}}/edit", handle.Edit) + rg.POST("/{{{ .ModuleName }}}/del", handle.Del) +} \ No newline at end of file diff --git a/server/generator/templates/gocode/schema.go.tpl b/server/generator/templates/gocode/schema.go.tpl new file mode 100644 index 0000000..6e22027 --- /dev/null +++ b/server/generator/templates/gocode/schema.go.tpl @@ -0,0 +1,57 @@ +package {{{ .ModuleName }}} + +import "x_admin/core" + +//{{{ title (toCamelCase .EntityName) }}}ListReq {{{ .FunctionName }}}列表参数 +type {{{ title (toCamelCase .EntityName) }}}ListReq struct { + {{{- range .Columns }}} + {{{- if .IsQuery }}} + {{{ title (toCamelCase .GoField) }}} {{{ .GoType }}} `form:"{{{ toCamelCase .GoField }}}"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} + +//{{{ title (toCamelCase .EntityName) }}}DetailReq {{{ .FunctionName }}}详情参数 +type {{{ title (toCamelCase .EntityName) }}}DetailReq struct { + {{{- range .Columns }}} + {{{- if .IsPk }}} + {{{ title (toCamelCase .GoField) }}} {{{ .GoType }}} `form:"{{{ toCamelCase .GoField }}}"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} + +//{{{ title (toCamelCase .EntityName) }}}AddReq {{{ .FunctionName }}}新增参数 +type {{{ title (toCamelCase .EntityName) }}}AddReq struct { + {{{- range .Columns }}} + {{{- if .IsInsert }}} + {{{ title (toCamelCase .GoField) }}} {{{ .GoType }}} `form:"{{{ toCamelCase .GoField }}}"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} + +//{{{ title (toCamelCase .EntityName) }}}EditReq {{{ .FunctionName }}}新增参数 +type {{{ title (toCamelCase .EntityName) }}}EditReq struct { + {{{- range .Columns }}} + {{{- if .IsEdit }}} + {{{ title (toCamelCase .GoField) }}} {{{ .GoType }}} `form:"{{{ toCamelCase .GoField }}}"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} + +//{{{ title (toCamelCase .EntityName) }}}DelReq {{{ .FunctionName }}}新增参数 +type {{{ title (toCamelCase .EntityName) }}}DelReq struct { + {{{- range .Columns }}} + {{{- if .IsPk }}} + {{{ title (toCamelCase .GoField) }}} {{{ .GoType }}} `form:"{{{ toCamelCase .GoField }}}"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} + +//{{{ title (toCamelCase .EntityName) }}}Resp {{{ .FunctionName }}}返回信息 +type {{{ title (toCamelCase .EntityName) }}}Resp struct { + {{{- range .Columns }}} + {{{- if or .IsList .IsPk }}} + {{{ title (toCamelCase .GoField) }}} {{{ .GoType }}} `json:"{{{ toCamelCase .GoField }}}" structs:"{{{ toCamelCase .GoField }}}"` // {{{ .ColumnComment }}} + {{{- end }}} + {{{- end }}} +} diff --git a/server/generator/templates/gocode/service.go.tpl b/server/generator/templates/gocode/service.go.tpl new file mode 100644 index 0000000..1141574 --- /dev/null +++ b/server/generator/templates/gocode/service.go.tpl @@ -0,0 +1,141 @@ +package {{{ .ModuleName }}} + +import ( + "x_admin/core/request" + "x_admin/core/response" + "x_admin/model" + "gorm.io/gorm" +) + +type I{{{ title (toCamelCase .EntityName) }}}Service interface { + List(page request.PageReq, listReq {{{ title (toCamelCase .EntityName) }}}ListReq) (res response.PageResp, e error) + Detail(id int) (res {{{ title (toCamelCase .EntityName) }}}Resp, e error) + Add(addReq {{{ title (toCamelCase .EntityName) }}}AddReq) (e error) + Edit(editReq {{{ title (toCamelCase .EntityName) }}}EditReq) (e error) + Del(id int) (e error) +} + +//New{{{ title (toCamelCase .EntityName) }}}Service 初始化 +func New{{{ title (toCamelCase .EntityName) }}}Service(db *gorm.DB) I{{{ title (toCamelCase .EntityName) }}}Service { + return &{{{ toCamelCase .EntityName }}}Service{db: db} +} + +//{{{ toCamelCase .EntityName }}}Service {{{ .FunctionName }}}服务实现类 +type {{{ toCamelCase .EntityName }}}Service struct { + db *gorm.DB +} + +//List {{{ .FunctionName }}}列表 +func (Service {{{ toCamelCase .EntityName }}}Service) List(page request.PageReq, listReq {{{ title (toCamelCase .EntityName) }}}ListReq) (res response.PageResp, e error) { + // 分页信息 + limit := page.PageSize + offset := page.PageSize * (page.PageNo - 1) + // 查询 + dbModel := Service.db.Model(&model.{{{ title (toCamelCase .EntityName) }}}{}) + {{{- range .Columns }}} + {{{- if .IsQuery }}} + {{{- $queryOpr := index $.ModelOprMap .QueryType }}} + {{{- if and (eq .GoType "string") (eq $queryOpr "like") }}} + if listReq.{{{ title (toCamelCase .ColumnName) }}} != "" { + dbModel = dbModel.Where("{{{ .ColumnName }}} like ?", "%"+listReq.{{{ title (toCamelCase .ColumnName) }}}+"%") + } + {{{- else }}} + if listReq.{{{ title (toCamelCase .ColumnName) }}} {{{ if eq .GoType "string" }}}!= ""{{{ else }}}> 0{{{ end }}} { + dbModel = dbModel.Where("{{{ .ColumnName }}} = ?", listReq.{{{ title (toCamelCase .ColumnName) }}}) + } + {{{- end }}} + {{{- end }}} + {{{- end }}} + {{{- if contains .AllFields "is_delete" }}} + dbModel = dbModel.Where("is_delete = ?", 0) + {{{- end }}} + // 总数 + var count int64 + err := dbModel.Count(&count).Error + if e = response.CheckErr(err, "List Count err"); e != nil { + return + } + // 数据 + var objs []model.{{{ title (toCamelCase .EntityName) }}} + err = dbModel.Limit(limit).Offset(offset).Order("id desc").Find(&objs).Error + if e = response.CheckErr(err, "List Find err"); e != nil { + return + } + resps := []{{{ title (toCamelCase .EntityName) }}}Resp{} + response.Copy(&resps, objs) + return response.PageResp{ + PageNo: page.PageNo, + PageSize: page.PageSize, + Count: count, + Lists: resps, + }, nil +} + +//Detail {{{ .FunctionName }}}详情 +func (Service {{{ toCamelCase .EntityName }}}Service) Detail(id int) (res {{{ title (toCamelCase .EntityName) }}}Resp, e error) { + var obj model.{{{ title (toCamelCase .EntityName) }}} + err := Service.db.Where("{{{ $.PrimaryKey }}} = ?{{{ if contains .AllFields "is_delete" }}} AND is_delete = ?{{{ end }}}", id{{{ if contains .AllFields "is_delete" }}}, 0{{{ end }}}).Limit(1).First(&obj).Error + if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Detail First err"); e != nil { + return + } + response.Copy(&res, obj) + {{{- range .Columns }}} + {{{- if and .IsEdit (contains (slice "image" "avatar" "logo" "img") .GoField) }}} + res.Avatar = util.UrlUtil.ToAbsoluteUrl(res.Avatar) + {{{- end }}} + {{{- end }}} + return +} + +//Add {{{ .FunctionName }}}新增 +func (Service {{{ toCamelCase .EntityName }}}Service) Add(addReq {{{ title (toCamelCase .EntityName) }}}AddReq) (e error) { + var obj model.{{{ title (toCamelCase .EntityName) }}} + response.Copy(&obj, addReq) + err := Service.db.Create(&obj).Error + e = response.CheckErr(err, "Add Create err") + return +} + +//Edit {{{ .FunctionName }}}编辑 +func (Service {{{ toCamelCase .EntityName }}}Service) Edit(editReq {{{ title (toCamelCase .EntityName) }}}EditReq) (e error) { + var obj model.{{{ title (toCamelCase .EntityName) }}} + err := Service.db.Where("{{{ $.PrimaryKey }}} = ?{{{ if contains .AllFields "is_delete" }}} AND is_delete = ?{{{ end }}}", editReq.Id{{{ if contains .AllFields "is_delete" }}}, 0{{{ end }}}).Limit(1).First(&obj).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Edit First err"); e != nil { + return + } + // 更新 + response.Copy(&obj, editReq) + err = Service.db.Model(&obj).Updates(obj).Error + e = response.CheckErr(err, "Edit Updates err") + return +} + +//Del {{{ .FunctionName }}}删除 +func (Service {{{ toCamelCase .EntityName }}}Service) Del(id int) (e error) { + var obj model.{{{ title (toCamelCase .EntityName) }}} + err := Service.db.Where("{{{ $.PrimaryKey }}} = ?{{{ if contains .AllFields "is_delete" }}} AND is_delete = ?{{{ end }}}", id{{{ if contains .AllFields "is_delete" }}}, 0{{{ end }}}).Limit(1).First(&obj).Error + // 校验 + if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil { + return + } + if e = response.CheckErr(err, "Del First err"); e != nil { + return + } + // 删除 + {{{- if contains .AllFields "is_delete" }}} + obj.IsDelete = 1 + err = Service.db.Save(&obj).Error + e = response.CheckErr(err, "Del Save err") + {{{- else }}} + err = Service.db.Delete(&obj).Error + e = response.CheckErr(err, "Del Delete err") + {{{- end }}} + return +} diff --git a/server/generator/templates/vue/api.ts.tpl b/server/generator/templates/vue/api.ts.tpl new file mode 100644 index 0000000..9617572 --- /dev/null +++ b/server/generator/templates/vue/api.ts.tpl @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// {{{.FunctionName}}}列表 +export function {{{.ModuleName}}}_lists(params?: Record) { + return request.get({ url: '/{{{.ModuleName}}}/list', params }) +} + +// {{{.FunctionName}}}详情 +export function {{{.ModuleName}}}_detail(params: Record) { + return request.get({ url: '/{{{.ModuleName}}}/detail', params }) +} + +// {{{.FunctionName}}}新增 +export function {{{.ModuleName}}}_add(params: Record) { + return request.post({ url: '/{{{.ModuleName}}}/add', params }) +} + +// {{{.FunctionName}}}编辑 +export function {{{.ModuleName}}}_edit(params: Record) { + return request.post({ url: '/{{{.ModuleName}}}/edit', params }) +} + +// {{{.FunctionName}}}删除 +export function {{{.ModuleName}}}_delete(params: Record) { + return request.post({ url: '/{{{.ModuleName}}}/del', params }) +} diff --git a/server/generator/templates/vue/edit.vue.tpl b/server/generator/templates/vue/edit.vue.tpl new file mode 100644 index 0000000..a79cad0 --- /dev/null +++ b/server/generator/templates/vue/edit.vue.tpl @@ -0,0 +1,252 @@ + + diff --git a/server/generator/templates/vue/index-tree.vue.tpl b/server/generator/templates/vue/index-tree.vue.tpl new file mode 100644 index 0000000..a401d5d --- /dev/null +++ b/server/generator/templates/vue/index-tree.vue.tpl @@ -0,0 +1,222 @@ + + diff --git a/server/generator/templates/vue/index.vue.tpl b/server/generator/templates/vue/index.vue.tpl new file mode 100644 index 0000000..65261fa --- /dev/null +++ b/server/generator/templates/vue/index.vue.tpl @@ -0,0 +1,183 @@ + + diff --git a/server/generator/tpl_utils/constants.go b/server/generator/tpl_utils/constants.go new file mode 100644 index 0000000..fe11c64 --- /dev/null +++ b/server/generator/tpl_utils/constants.go @@ -0,0 +1,93 @@ +package tpl_utils + +//GenConstants 代码生成常量 +var GenConstants = genConstants{ + UTF8: "utf-8", //编码 + TplCrud: "crud", //单表 (增删改查) + TplTree: "tree", //树表 (增删改查) + QueryLike: "LIKE", //模糊查询 + QueryEq: "=", //相等查询 + Require: 1, //需要的 +} + +//GoConstants Go相关常量 +var GoConstants = goConstants{ + TypeString: "string", //字符串类型 + TypeFloat: "float64", //浮点型 + TypeInt: "int", //整型 + TypeDate: "core.TsTime", //时间类型 +} + +//SqlConstants 数据库相关常量 +var SqlConstants = sqlConstants{ + //数据库字符串类型 + ColumnTypeStr: []string{"char", "varchar", "nvarchar", "varchar2"}, + //数据库文本类型 + ColumnTypeText: []string{"tinytext", "text", "mediumtext", "longtext"}, + //数据库时间类型 + ColumnTypeTime: []string{"datetime", "time", "date", "timestamp"}, + //数据库数字类型 + ColumnTypeNumber: []string{"tinyint", "smallint", "mediumint", "int", "integer", "bit", "bigint", + "float", "double", "decimal"}, + //时间日期字段名 + ColumnTimeName: []string{"create_time", "update_time", "delete_time", "start_time", "end_time"}, + //页面不需要插入字段 + ColumnNameNotAdd: []string{"id", "is_delete", "create_time", "update_time", "delete_time"}, + //页面不需要编辑字段 + ColumnNameNotEdit: []string{"is_delete", "create_time", "update_time", "delete_time"}, + //页面不需要列表字段 + ColumnNameNotList: []string{"id", "intro", "content", "is_delete", "delete_time"}, + //页面不需要查询字段 + ColumnNameNotQuery: []string{"is_delete", "create_time", "update_time", "delete_time"}, +} + +//HtmlConstants HTML相关常量 +var HtmlConstants = htmlConstants{ + HtmlInput: "input", //文本框 + HtmlTextarea: "textarea", //文本域 + HtmlSelect: "select", //下拉框 + HtmlRadio: "radio", //单选框 + HtmlDatetime: "datetime", //日期控件 + HtmlImageUpload: "imageUpload", //图片上传控件 + HtmlFileUpload: "fileUpload", //文件上传控件 + HtmlEditor: "editor", //富文本控件 +} + +type genConstants struct { + UTF8 string + TplCrud string + TplTree string + QueryLike string + QueryEq string + Require uint8 +} + +type goConstants struct { + TypeString string + TypeFloat string + TypeInt string + TypeDate string +} + +type sqlConstants struct { + ColumnTypeStr []string + ColumnTypeText []string + ColumnTypeTime []string + ColumnTypeNumber []string + ColumnTimeName []string + ColumnNameNotAdd []string + ColumnNameNotEdit []string + ColumnNameNotList []string + ColumnNameNotQuery []string +} + +type htmlConstants struct { + HtmlInput string + HtmlTextarea string + HtmlSelect string + HtmlRadio string + HtmlDatetime string + HtmlImageUpload string + HtmlFileUpload string + HtmlEditor string +} diff --git a/server/generator/tpl_utils/tpl.go b/server/generator/tpl_utils/tpl.go new file mode 100644 index 0000000..362ed5b --- /dev/null +++ b/server/generator/tpl_utils/tpl.go @@ -0,0 +1,279 @@ +package tpl_utils + +import ( + "archive/zip" + "bytes" + "io" + "os" + "path" + "strings" + "text/template" + "x_admin/config" + "x_admin/core/response" + "x_admin/model/gen" + "x_admin/util" +) + +var TemplateUtil = templateUtil{ + basePath: "generator/templates", + tpl: template.New("").Delims("{{{", "}}}").Funcs( + template.FuncMap{ + "sub": sub, + "slice": slice, + "title": strings.Title, + "toSnakeCase": util.StringUtil.ToSnakeCase, + "toCamelCase": util.StringUtil.ToCamelCase, + "contains": util.ToolsUtil.Contains, + }), +} + +// sub 模板-减函数 +func sub(a, b int) int { + return a - b +} + +// slice 模板-创建切片 +func slice(items ...interface{}) []interface{} { + return items +} + +// zFile 待加入zip的文件 +type zFile struct { + Name string + Body string +} + +// TplVars 模板变量 +type TplVars struct { + GenTpl string + TableName string + AuthorName string + PackageName string + EntityName string + EntitySnakeName string + ModuleName string + FunctionName string + JavaCamelField string + DateFields []string + PrimaryKey string + PrimaryField string + AllFields []string + SubPriCol gen.GenTableColumn + SubPriField string + SubTableFields []string + ListFields []string + DetailFields []string + DictFields []string + IsSearch bool + ModelOprMap map[string]string + Table gen.GenTable + Columns []gen.GenTableColumn + SubColumns []gen.GenTableColumn + //ModelTypeMap map[string]string +} + +// genUtil 模板工具 +type templateUtil struct { + basePath string + tpl *template.Template +} + +// PrepareVars 获取模板变量信息 +func (tu templateUtil) PrepareVars(table gen.GenTable, columns []gen.GenTableColumn, + oriSubPriCol gen.GenTableColumn, oriSubCols []gen.GenTableColumn) TplVars { + subPriField := "id" + isSearch := false + primaryKey := "id" + primaryField := "id" + functionName := "【请填写功能名称】" + var allFields []string + var subTableFields []string + var listFields []string + var detailFields []string + var dictFields []string + var subColumns []gen.GenTableColumn + var oriSubColNames []string + for _, column := range oriSubCols { + oriSubColNames = append(oriSubColNames, column.ColumnName) + } + if oriSubPriCol.ID > 0 { + subPriField = oriSubPriCol.ColumnName + subColumns = append(subColumns, oriSubPriCol) + } + for _, column := range columns { + allFields = append(allFields, column.ColumnName) + if util.ToolsUtil.Contains(oriSubColNames, column.ColumnName) { + subTableFields = append(subTableFields, column.ColumnName) + subColumns = append(subColumns, column) + } + if column.IsList == 1 { + listFields = append(listFields, column.ColumnName) + } + if column.IsEdit == 1 { + detailFields = append(detailFields, column.ColumnName) + } + if column.IsQuery == 1 { + isSearch = true + } + if column.IsPk == 1 { + primaryKey = column.GoField + primaryField = column.ColumnName + } + if column.DictType != "" && !util.ToolsUtil.Contains(dictFields, column.DictType) { + dictFields = append(dictFields, column.DictType) + } + } + //QueryType转换查询比较运算符 + modelOprMap := map[string]string{ + "=": "==", + "LIKE": "like", + } + if table.FunctionName != "" { + functionName = table.FunctionName + } + return TplVars{ + GenTpl: table.GenTpl, + TableName: table.TableName, + AuthorName: table.AuthorName, + PackageName: table.ModuleName, //config.GenConfig.PackageName, + EntityName: table.EntityName, + EntitySnakeName: util.StringUtil.ToSnakeCase(table.EntityName), + ModuleName: table.ModuleName, + FunctionName: functionName, + DateFields: SqlConstants.ColumnTimeName, + PrimaryKey: primaryKey, + PrimaryField: primaryField, + AllFields: allFields, + SubPriCol: oriSubPriCol, + SubPriField: subPriField, + SubTableFields: subTableFields, + ListFields: listFields, + DetailFields: detailFields, + DictFields: dictFields, + IsSearch: isSearch, + ModelOprMap: modelOprMap, + Columns: columns, + SubColumns: subColumns, + } +} + +// GetTemplatePaths 获取模板路径 +func (tu templateUtil) GetTemplatePaths(genTpl string) []string { + tplPaths := []string{ + "gocode/model.go.tpl", + "gocode/schema.go.tpl", + "gocode/service.go.tpl", + "gocode/route.go.tpl", + "gocode/controller.go.tpl", + "vue/api.ts.tpl", + "vue/edit.vue.tpl", + } + if genTpl == GenConstants.TplCrud { + tplPaths = append(tplPaths, "vue/index.vue.tpl") + } else if genTpl == GenConstants.TplTree { + tplPaths = append(tplPaths, "vue/index-tree.vue.tpl") + } + return tplPaths +} + +// Render 渲染模板 +func (tu templateUtil) Render(tplPath string, tplVars TplVars) (res string, e error) { + tpl, err := tu.tpl.ParseFiles(path.Join(config.Config.RootPath, tu.basePath, tplPath)) + if e = response.CheckErr(err, "TemplateUtil.Render ParseFiles err"); e != nil { + return "", e + } + buf := &bytes.Buffer{} + err = tpl.ExecuteTemplate(buf, path.Base(tplPath), tplVars) + if e = response.CheckErr(err, "TemplateUtil.Render Execute err"); e != nil { + return "", e + } + return buf.String(), nil +} + +// GetGenPath 获取生成路径 +func (tu templateUtil) GetGenPath(table gen.GenTable) string { + if table.GenPath == "/" { + //return path.Join(config.Config.RootPath, config.GenConfig.GenRootPath) + return config.GenConfig.GenRootPath + } + return table.GenPath +} + +// GetFilePaths 获取生成文件相对路径 +func (tu templateUtil) GetFilePaths(tplCodeMap map[string]string, TableName string) map[string]string { + //模板文件对应的输出文件 + fmtMap := map[string]string{ + "gocode/model.go.tpl": strings.Join([]string{"server/model/", TableName, ".go"}, ""), + "gocode/route.go.tpl": strings.Join([]string{"server/admin/", TableName, "_route.go"}, ""), + "gocode/schema.go.tpl": strings.Join([]string{"server/admin/", TableName, "/", TableName, "_schema.go"}, ""), //"server/admin/%s/%s_schema.go" + "gocode/service.go.tpl": strings.Join([]string{"server/admin/", TableName, "/", TableName, "_service.go"}, ""), //"server/admin/%s/%s_service.go", + // "server/admin/%s_route.go", + "gocode/controller.go.tpl": strings.Join([]string{"server/admin/", TableName, "/", TableName, "_ctl.go"}, ""), //"server/admin/%s/%s_ctl.go", + + "vue/api.ts.tpl": strings.Join([]string{"admin/src/api/", TableName, ".ts"}, ""), // "admin/src/api/%s.ts", + "vue/edit.vue.tpl": strings.Join([]string{"admin/src/views/", TableName, "/edit.vue"}, ""), // "admin/src/views/%s/edit.vue", + "vue/index.vue.tpl": strings.Join([]string{"admin/src/views/", TableName, "/index.vue"}, ""), // "admin/src/views/%s/index.vue", + "vue/index-tree.vue.tpl": strings.Join([]string{"admin/src/views/", TableName, "/index-tree.vue"}, ""), // "admin/src/views/%s/index-tree.vue", + } + filePath := make(map[string]string) + for tplPath, tplCode := range tplCodeMap { + file := fmtMap[tplPath] + filePath[file] = tplCode + } + return filePath +} + +// GenCodeFiles 生成代码文件 +func (tu templateUtil) GenCodeFiles(tplCodeMap map[string]string, TableName string, basePath string) error { + filePaths := tu.GetFilePaths(tplCodeMap, TableName) + for file, tplCode := range filePaths { + filePath := path.Join(basePath, file) + dir := path.Dir(filePath) + if !util.ToolsUtil.IsFileExist(dir) { + err := os.MkdirAll(dir, 0755) + if err != nil { + return response.CheckErr(err, "TemplateUtil.GenCodeFiles MkdirAll err") + } + } + err := os.WriteFile(filePath, []byte(tplCode), 0644) + if err != nil { + return response.CheckErr(err, "TemplateUtil.GenCodeFiles WriteFile err") + } + } + return nil +} + +func addFileToZip(zipWriter *zip.Writer, file zFile) error { + header := &zip.FileHeader{ + Name: file.Name, + Method: zip.Deflate, + } + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return response.CheckErr(err, "TemplateUtil.addFileToZip CreateHeader err") + } + _, err = io.WriteString(writer, file.Body) + if err != nil { + return response.CheckErr(err, "TemplateUtil.addFileToZip WriteString err") + } + return nil +} + +// GenZip 生成代码压缩包 +func (tu templateUtil) GenZip(zipWriter *zip.Writer, tplCodeMap map[string]string, TableName string) error { + filePaths := tu.GetFilePaths(tplCodeMap, TableName) + files := make([]zFile, 0) + for file, tplCode := range filePaths { + files = append(files, zFile{ + Name: file, + Body: tplCode, + }) + } + for _, file := range files { + err := addFileToZip(zipWriter, file) + if err != nil { + return response.CheckErr(err, "TemplateUtil.GenZip zipFiles err") + } + } + return nil +} diff --git a/server/generator/tpl_utils/utils.go b/server/generator/tpl_utils/utils.go new file mode 100644 index 0000000..db15596 --- /dev/null +++ b/server/generator/tpl_utils/utils.go @@ -0,0 +1,214 @@ +package tpl_utils + +import ( + "strconv" + "strings" + "time" + "x_admin/config" + "x_admin/model/gen" + "x_admin/util" + + "gorm.io/gorm" +) + +var GenUtil = genUtil{} + +// genUtil 代码生成工具 +type genUtil struct{} + +// GetDbTablesQuery 查询库中的数据表 +func (gu genUtil) GetDbTablesQuery(db *gorm.DB, tableName string, tableComment string) *gorm.DB { + whereStr := "" + if tableName != "" { + whereStr += `and lower(table_name) like lower("%` + tableName + `%")` + } + if tableComment != "" { + whereStr += `and lower(table_comment) like lower("%` + tableComment + `%")` + } + query := db.Table("information_schema.tables").Where( + `table_schema = (SELECT database()) + AND table_name NOT LIKE "qrtz_%" + AND table_name NOT LIKE "gen_%" + AND table_name NOT IN (select table_name from x_gen_table) ` + whereStr).Select( + "table_name, table_comment, create_time, update_time") + return query +} + +// GetDbTablesQueryByNames 根据表名集查询表 +func (gu genUtil) GetDbTablesQueryByNames(db *gorm.DB, tableNames []string) *gorm.DB { + query := db.Table("information_schema.tables").Where( + `table_schema = (SELECT database()) + AND table_name NOT LIKE "qrtz_%" + AND table_name NOT LIKE "gen_%" + AND table_name IN ?`, tableNames).Select( + "table_name, table_comment, create_time, update_time") + return query +} + +// GetDbTableColumnsQueryByName 根据表名查询列信息 +func (gu genUtil) GetDbTableColumnsQueryByName(db *gorm.DB, tableName string) *gorm.DB { + query := db.Table("information_schema.columns").Where( + `table_schema = (SELECT database()) + AND table_name = ?`, tableName).Order("ordinal_position").Select( + `column_name, + (CASE WHEN (is_nullable = "no" && column_key != "PRI") THEN "1" ELSE NULL END) AS is_required, + (CASE WHEN column_key = "PRI" THEN "1" ELSE "0" END) AS is_pk, + ordinal_position AS sort, column_comment, + (CASE WHEN extra = "auto_increment" THEN "1" ELSE "0" END) AS is_increment, column_type`) + return query +} + +// InitTable 初始化表 +func (gu genUtil) InitTable(table gen.GenTable) gen.GenTable { + return gen.GenTable{ + TableName: table.TableName, + TableComment: table.TableComment, + AuthorName: "", + EntityName: gu.ToClassName(table.TableName), + ModuleName: gu.ToModuleName(table.TableName), + FunctionName: strings.Replace(table.TableComment, "表", "", -1), + CreateTime: time.Now().Unix(), + UpdateTime: time.Now().Unix(), + } +} + +// InitColumn 初始化字段列 +func (gu genUtil) InitColumn(tableId uint, column gen.GenTableColumn) gen.GenTableColumn { + columnType := gu.GetDbType(column.ColumnType) + columnLen := gu.GetColumnLength(column.ColumnType) + col := gen.GenTableColumn{ + TableID: tableId, + ColumnName: column.ColumnName, + ColumnComment: column.ColumnComment, + ColumnType: columnType, + ColumnLength: columnLen, + GoField: column.ColumnName, + GoType: GoConstants.TypeString, + QueryType: GenConstants.QueryEq, + Sort: column.Sort, + IsPk: column.IsPk, + IsIncrement: column.IsIncrement, + IsRequired: column.IsRequired, + CreateTime: time.Now().Unix(), + UpdateTime: time.Now().Unix(), + } + if util.ToolsUtil.Contains(append(SqlConstants.ColumnTypeStr, SqlConstants.ColumnTypeText...), columnType) { + //文本域组 + if columnLen >= 500 || util.ToolsUtil.Contains(SqlConstants.ColumnTypeText, columnType) { + col.HtmlType = HtmlConstants.HtmlTextarea + } else { + col.HtmlType = HtmlConstants.HtmlInput + } + } else if util.ToolsUtil.Contains(SqlConstants.ColumnTypeTime, columnType) { + //日期字段 + col.GoType = GoConstants.TypeDate + col.HtmlType = HtmlConstants.HtmlDatetime + } else if util.ToolsUtil.Contains(SqlConstants.ColumnTimeName, col.ColumnName) { + //时间字段 + col.GoType = GoConstants.TypeDate + col.HtmlType = HtmlConstants.HtmlDatetime + } else if util.ToolsUtil.Contains(SqlConstants.ColumnTypeNumber, columnType) { + //数字字段 + col.HtmlType = HtmlConstants.HtmlInput + if strings.Contains(columnType, ",") { + col.GoType = GoConstants.TypeFloat + } else { + col.GoType = GoConstants.TypeInt + } + } + //非必填字段 + if util.ToolsUtil.Contains(SqlConstants.ColumnNameNotEdit, col.ColumnName) { + col.IsRequired = 0 + } + //需插入字段 + if !util.ToolsUtil.Contains(SqlConstants.ColumnNameNotAdd, col.ColumnName) { + col.IsInsert = GenConstants.Require + } + //需编辑字段 + if !util.ToolsUtil.Contains(SqlConstants.ColumnNameNotEdit, col.ColumnName) { + col.IsEdit = GenConstants.Require + col.IsRequired = GenConstants.Require + } + //需列表字段 + if !util.ToolsUtil.Contains(SqlConstants.ColumnNameNotList, col.ColumnName) && col.IsPk == 0 { + col.IsList = GenConstants.Require + } + //需查询字段 + if !util.ToolsUtil.Contains(SqlConstants.ColumnNameNotQuery, col.ColumnName) && col.IsPk == 0 { + col.IsQuery = GenConstants.Require + } + lowerColName := strings.ToLower(col.ColumnName) + //模糊查字段 + if strings.HasSuffix(lowerColName, "name") || util.ToolsUtil.Contains([]string{"title", "mobile"}, lowerColName) { + col.QueryType = GenConstants.QueryLike + } + //根据字段设置 + if strings.HasSuffix(lowerColName, "status") || util.ToolsUtil.Contains([]string{"is_show", "is_disable"}, lowerColName) { + //状态字段设置单选框 + col.HtmlType = HtmlConstants.HtmlRadio + } else if strings.HasSuffix(lowerColName, "type") || strings.HasSuffix(lowerColName, "sex") { + //类型&性别字段设置下拉框 + col.HtmlType = HtmlConstants.HtmlSelect + } else if strings.HasSuffix(lowerColName, "image") { + //图片字段设置图片上传 + col.HtmlType = HtmlConstants.HtmlImageUpload + } else if strings.HasSuffix(lowerColName, "file") { + //文件字段设置文件上传 + col.HtmlType = HtmlConstants.HtmlFileUpload + } else if strings.HasSuffix(lowerColName, "content") { + //富文本字段设置富文本编辑器 + col.HtmlType = HtmlConstants.HtmlEditor + } + return col +} + +// ToModuleName 表名转业务名 +func (gu genUtil) ToModuleName(name string) string { + names := strings.Split(name, config.Config.DbTablePrefix) + return names[len(names)-1] +} + +// ToClassName 表名转类名 +func (gu genUtil) ToClassName(name string) string { + tablePrefix := config.Config.DbTablePrefix + name = strings.TrimPrefix(name, tablePrefix) + // if config.GenConfig.IsRemoveTablePrefix && tablePrefix != "" { + // if strings.HasPrefix(name, tablePrefix) { + // name = name[len(tablePrefix):] + // } + // } + return util.StringUtil.ToCamelCase(name) +} + +// GetDbType 获取数据库类型字段 +func (gu genUtil) GetDbType(columnType string) string { + index := strings.IndexRune(columnType, '(') + if index < 0 { + return columnType + } + return columnType[:index] +} + +// GetColumnLength 获取字段长度 +func (gu genUtil) GetColumnLength(columnType string) int { + index := strings.IndexRune(columnType, '(') + if index < 0 { + return 0 + } + length, err := strconv.Atoi(columnType[index+1 : strings.IndexRune(columnType, ')')]) + if err != nil { + return 0 + } + return length +} + +// GetTablePriCol 获取主键列名称 +func (gu genUtil) GetTablePriCol(columns []gen.GenTableColumn) (res gen.GenTableColumn) { + for _, col := range columns { + if col.IsPk == 1 { + res = col + return + } + } + return +} diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..d3cd30f --- /dev/null +++ b/server/go.mod @@ -0,0 +1,74 @@ +module x_admin + +go 1.21 + +require ( + github.com/fatih/structs v1.1.0 + github.com/gin-contrib/cors v1.4.0 + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v9 v9.0.0-rc.2 + github.com/google/uuid v1.4.0 + github.com/jinzhu/copier v0.4.0 + github.com/shirou/gopsutil v3.21.11+incompatible + github.com/spf13/viper v1.17.0 + github.com/ua-parser/uap-go v0.0.0-20230823213814-f77b3e91e9dc + go.uber.org/zap v1.26.0 + gorm.io/driver/mysql v1.5.2 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/bytedance/sonic v1.10.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/frankban/quicktest v1.14.6 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.6.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..efdd1d4 --- /dev/null +++ b/server/go.sum @@ -0,0 +1,647 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= +github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= +github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-redis/redis/v9 v9.0.0-rc.2 h1:IN1eI8AvJJeWHjMW/hlFAv2sAfvTun2DVksDDJ3a6a0= +github.com/go-redis/redis/v9 v9.0.0-rc.2/go.mod h1:cgBknjwcBJa2prbnuHH/4k/Mlj4r0pWNV2HBanHujfY= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ua-parser/uap-go v0.0.0-20230823213814-f77b3e91e9dc h1:iT5lwxf894PiMq7cnMMQg/7VOD1pxmu//gQuHWAFy4s= +github.com/ua-parser/uap-go v0.0.0-20230823213814-f77b3e91e9dc/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= +golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..8046ebf --- /dev/null +++ b/server/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "log" + "net/http" + "strconv" + "time" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/middleware" + "x_admin/routers" + + "github.com/gin-gonic/gin" +) + +// initRouter 初始化router +func initRouter() *gin.Engine { + // 初始化gin + gin.SetMode(config.Config.GinMode) + router := gin.New() + // 设置静态路径 + router.Static(config.Config.PublicPrefix, config.Config.UploadDirectory) + router.Static(config.Config.StaticPath, config.Config.StaticDirectory) + // 设置中间件 + router.Use(gin.Logger(), middleware.Cors(), middleware.ErrorRecover()) + // 演示模式 + if config.Config.DisallowModify { + router.Use(middleware.ShowMode()) + } + // 特殊异常处理 + router.NoMethod(response.NoMethod) + router.NoRoute(response.NoRoute) + // 注册路由 + group := router.Group("/api") + + routers.RegisterGroup(group) + + return router +} + +// initServer 初始化server +func initServer(router *gin.Engine) *http.Server { + return &http.Server{ + Addr: ":" + strconv.Itoa(config.Config.ServerPort), + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } +} + +func main() { + // 刷新日志缓冲 + defer core.Logger.Sync() + // 程序结束前关闭数据库连接 + if core.GetDB() != nil { + db, _ := core.GetDB().DB() + defer db.Close() + } + // 初始化DI + // initDI() + // 初始化router + router := initRouter() + // 初始化server + s := initServer(router) + // 运行服务 + log.Fatalln(s.ListenAndServe().Error()) +} diff --git a/server/middleware/auth.go b/server/middleware/auth.go new file mode 100644 index 0000000..14ab426 --- /dev/null +++ b/server/middleware/auth.go @@ -0,0 +1,149 @@ +package middleware + +import ( + "strconv" + "strings" + "x_admin/admin/system/admin" + "x_admin/admin/system/role" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + sysModel "x_admin/model/system" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +var ( + permSrv = role.NewSystemAuthPermService(core.GetDB()) + roleSrv = role.NewSystemAuthRoleService(core.GetDB(), permSrv) + adminSrv = admin.NewSystemAuthAdminService(core.GetDB(), permSrv, roleSrv) +) + +// TokenAuth Token认证中间件 +func TokenAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 路由转权限 + auths := strings.ReplaceAll(strings.Replace(c.Request.URL.Path, "/api/", "", 1), "/", ":") + + // 免登录接口 + if util.ToolsUtil.Contains(config.AdminConfig.NotLoginUri, auths) { + c.Next() + return + } + + // Token是否为空 + token := c.Request.Header.Get("token") + if token == "" { + response.Fail(c, response.TokenEmpty) + c.Abort() + return + } + + // Token是否过期 + token = config.AdminConfig.BackstageTokenKey + token + existCnt := util.RedisUtil.Exists(token) + if existCnt < 0 { + response.Fail(c, response.SystemError) + c.Abort() + return + } else if existCnt == 0 { + response.Fail(c, response.TokenInvalid) + c.Abort() + return + } + + // 用户信息缓存 + uidStr := util.RedisUtil.Get(token) + var uid uint + if uidStr != "" { + i, err := strconv.ParseUint(uidStr, 10, 32) + if err != nil { + core.Logger.Errorf("TokenAuth Atoi uidStr err: err=[%+v]", err) + response.Fail(c, response.TokenInvalid) + c.Abort() + return + } + uid = uint(i) + } + + if !util.RedisUtil.HExists(config.AdminConfig.BackstageManageKey, uidStr) { + err := adminSrv.CacheAdminUserByUid(uid) + if err != nil { + core.Logger.Errorf("TokenAuth CacheAdminUserByUid err: err=[%+v]", err) + response.Fail(c, response.SystemError) + c.Abort() + return + } + } + + // 校验用户被删除 + var mapping sysModel.SystemAuthAdmin + err := util.ToolsUtil.JsonToObj(util.RedisUtil.HGet(config.AdminConfig.BackstageManageKey, uidStr), &mapping) + if err != nil { + core.Logger.Errorf("TokenAuth Unmarshal err: err=[%+v]", err) + response.Fail(c, response.SystemError) + c.Abort() + return + } + if mapping.IsDelete == 1 { + util.RedisUtil.Del(token) + util.RedisUtil.HDel(config.AdminConfig.BackstageManageKey + uidStr) + response.Fail(c, response.TokenInvalid) + c.Abort() + return + } + + // 校验用户被禁用 + if mapping.IsDisable == 1 { + response.Fail(c, response.LoginDisableError) + c.Abort() + return + } + + // 令牌剩余30分钟自动续签 + if util.RedisUtil.TTL(token) < 1800 { + util.RedisUtil.Expire(token, 7200) + } + + // 单次请求信息保存 + c.Set(config.AdminConfig.ReqAdminIdKey, uid) + c.Set(config.AdminConfig.ReqRoleIdKey, mapping.Role) + c.Set(config.AdminConfig.ReqUsernameKey, mapping.Username) + c.Set(config.AdminConfig.ReqNicknameKey, mapping.Nickname) + + // 免权限验证接口 + if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, auths) || uid == 1 { + c.Next() + return + } + + // 校验角色的权限,redis没有就重新查询 + roleId := mapping.Role + if !util.RedisUtil.HExists(config.AdminConfig.BackstageRolesKey, roleId) { + i, err := strconv.ParseUint(roleId, 10, 32) + if err != nil { + core.Logger.Errorf("TokenAuth Atoi roleId err: err=[%+v]", err) + response.Fail(c, response.SystemError) + c.Abort() + return + } + err = permSrv.CacheRoleMenusByRoleId(uint(i)) + if err != nil { + core.Logger.Errorf("TokenAuth CacheRoleMenusByRoleId err: err=[%+v]", err) + response.Fail(c, response.SystemError) + c.Abort() + return + } + } + + // 验证是否有权限操作 + menus := util.RedisUtil.HGet(config.AdminConfig.BackstageRolesKey, roleId) + if !(menus != "" && util.ToolsUtil.Contains(strings.Split(menus, ","), auths)) { + response.Fail(c, response.NoPermission) + c.Abort() + return + } + c.Next() + } +} diff --git a/server/middleware/cors.go b/server/middleware/cors.go new file mode 100644 index 0000000..aafbeee --- /dev/null +++ b/server/middleware/cors.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "time" +) + +//Cors CORS(跨域资源共享)中间件 +func Cors() gin.HandlerFunc { + return cors.New(cors.Config{ + AllowOrigins: []string{"*"}, + AllowHeaders: []string{"*"}, + AllowMethods: []string{"OPTIONS", "GET", "POST", "POST", "DELETE", "PUT"}, + MaxAge: 1 * time.Hour, + }) +} diff --git a/server/middleware/error.go b/server/middleware/error.go new file mode 100644 index 0000000..f797b34 --- /dev/null +++ b/server/middleware/error.go @@ -0,0 +1,37 @@ +package middleware + +import ( + "runtime/debug" + "x_admin/core" + "x_admin/core/response" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// ErrorRecover 异常恢复中间件 +func ErrorRecover() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + switch v := r.(type) { + // 自定义类型 + case response.RespType: + core.Logger.WithOptions(zap.AddCallerSkip(2)).Warnf( + "Request Fail by recover: url=[%s], resp=[%+v]", c.Request.URL.Path, v) + var data interface{} + if v.Data() == nil { + data = []string{} + } + response.Result(c, v, data) + // 其他类型 + default: + core.Logger.Errorf("stacktrace from panic: %+v\n%s", r, string(debug.Stack())) + response.Fail(c, response.SystemError) + } + c.Abort() + } + }() + c.Next() + } +} diff --git a/server/middleware/log.go b/server/middleware/log.go new file mode 100644 index 0000000..1e25754 --- /dev/null +++ b/server/middleware/log.go @@ -0,0 +1,129 @@ +package middleware + +import ( + "fmt" + "net/url" + "strings" + "time" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/model/system" + "x_admin/util" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "go.uber.org/zap" +) + +// requestType 请求参数类 +type requestType string + +const ( + RequestFile requestType = "file" // 文件类型 + RequestDefault requestType = "default" // 默认数据类型 +) + +// RecordLog 记录系统日志信息中间件 +func RecordLog(title string, reqTypes ...requestType) gin.HandlerFunc { + reqType := RequestDefault + if len(reqTypes) > 0 { + reqType = reqTypes[0] + } + return func(c *gin.Context) { + // 开始时间 + startTime := time.Now().UnixMilli() + // 异常信息 + errStr := "" + var status uint8 = 1 // 1=成功, 2=失败 + args := "" + // 请求方式 + reqMethod := c.Request.Method + // 获取请求参数 + if reqMethod == "POST" { + // POST请求 + if reqType == RequestFile { + // 文件类型 + var filenames []string + form, err := c.MultipartForm() + // 校验错误 + if response.IsFailWithResp(c, response.CheckErr(err, "RecordLog MultipartForm err")) { + c.Abort() + return + } + // 获取文件列表 + for _, files := range form.File { + for _, file := range files { + filenames = append(filenames, file.Filename) + } + } + args = strings.Join(filenames, ",") + } else { + //默认类型 + var formParams map[string]interface{} + err := c.ShouldBindBodyWith(&formParams, binding.JSON) + if err == nil { + val, err := util.ToolsUtil.ObjToJson(&formParams) + // 校验错误 + if response.IsFailWithResp(c, response.CheckErr(err, "RecordLog POST Marshal err")) { + c.Abort() + return + } + args = val + } + } + } else if reqMethod == "GET" { + // GET请求 + query := c.Request.URL.RawQuery + if query != "" { + args, _ = url.QueryUnescape(query) + } + } + // 处理异常 + defer func() { + if r := recover(); r != nil { + errStr = fmt.Sprintf("%+v", r) + status = 2 + // 结束时间 + endTime := time.Now().UnixMilli() + // 执行时间(毫秒) + taskTime := endTime - startTime + // 获取当前的用户 + adminId := config.AdminConfig.GetAdminId(c) + urlPath := c.Request.URL.Path + ip := c.ClientIP() + method := c.HandlerName() + err := core.GetDB().Create(&system.SystemLogOperate{ + AdminId: adminId, Type: reqMethod, Title: title, Ip: ip, + Url: urlPath, Method: method, Args: args, Error: errStr, Status: status, + StartTime: startTime / 1000, EndTime: endTime / 1000, TaskTime: taskTime, + }).Error + response.CheckErr(err, "RecordLog recover Create err") + core.Logger.WithOptions(zap.AddCallerSkip(2)).Infof( + "RecordLog recover: err=[%+v]", r) + panic(r) + } + }() + // 执行方法 + c.Next() + if len(c.Errors) > 0 { + errStr = c.Errors.String() + status = 2 + } + // 结束时间 + endTime := time.Now().UnixMilli() + // 执行时间(毫秒) + taskTime := endTime - startTime + // 获取当前的用户 + adminId := config.AdminConfig.GetAdminId(c) + urlPath := c.Request.URL.Path + ip := c.ClientIP() + method := c.HandlerName() + err := core.GetDB().Create(&system.SystemLogOperate{ + AdminId: adminId, Type: reqMethod, Title: title, Ip: ip, + Url: urlPath, Method: method, Args: args, Error: errStr, Status: status, + StartTime: startTime / 1000, EndTime: endTime / 1000, TaskTime: taskTime, + }).Error + response.CheckErr(err, "RecordLog Create err") + } +} diff --git a/server/middleware/show.go b/server/middleware/show.go new file mode 100644 index 0000000..4036243 --- /dev/null +++ b/server/middleware/show.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "strings" + "x_admin/config" + "x_admin/core/response" + "x_admin/util" + + "github.com/gin-gonic/gin" +) + +// ShowMode 演示模式中间件,演示模式禁止POST +func ShowMode() gin.HandlerFunc { + return func(c *gin.Context) { + // 路由转权限 + auths := strings.ReplaceAll(strings.Replace(c.Request.URL.Path, "/api/", "", 1), "/", ":") + // 禁止修改操作 (演示功能,限制POST请求) + if c.Request.Method == "POST" && !util.ToolsUtil.Contains(config.AdminConfig.ShowWhitelistUri, auths) { + response.FailWithMsg(c, response.NoPermission, "演示环境不支持修改数据,请下载源码本地部署体验!") + c.Abort() + return + } + } +} diff --git a/server/model/article_collect.go b/server/model/article_collect.go new file mode 100644 index 0000000..67043e5 --- /dev/null +++ b/server/model/article_collect.go @@ -0,0 +1,12 @@ +package model + +//ArticleCollect 文章收藏实体 +type ArticleCollect struct { + Id int `gorm:"primarykey;comment:'主键'"` // 主键 + UserId int `gorm:"comment:'用户ID'"` // 用户ID + ArticleId int `gorm:"comment:'文章ID'"` // 文章ID + IsDelete int `gorm:"comment:'是否删除'"` // 是否删除 + CreateTime int64 `gorm:"comment:'创建时间'"` // 创建时间 + UpdateTime int64 `gorm:"comment:'更新时间'"` // 更新时间 + DeleteTime int64 `gorm:"comment:'是否删除'"` // 是否删除 +} diff --git a/server/model/common/album.go b/server/model/common/album.go new file mode 100644 index 0000000..07ad2c8 --- /dev/null +++ b/server/model/common/album.go @@ -0,0 +1,30 @@ +package common + +//Album 相册实体 +type Album struct { + ID uint `gorm:"primarykey;comment:'主键ID'"` + Cid uint `gorm:"not null;default:0;comment:'类目ID'"` + Aid uint `gorm:"not null;default:0;comment:'管理ID'"` + Uid uint `gorm:"not null;default:0;comment:'用户ID'"` + Type int `gorm:"not null;default:10;comment:'文件类型: [10=图片, 20=视频]''"` + Name string `gorm:"not null;default:'';comment:'文件名称''"` + Uri string `gorm:"not null;comment:'文件路径'"` + Ext string `gorm:"not null;default:'';comment:'文件扩展'"` + Size int64 `gorm:"not null;default:0;comment:文件大小"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} + +//AlbumCate 相册分类实体 +type AlbumCate struct { + ID uint `gorm:"primarykey;comment:'主键ID'"` + Pid uint `gorm:"not null;default:0;comment:'父级ID'"` + Type int `gorm:"not null;default:10;comment:'文件类型: [10=图片, 20=视频]''"` + Name string `gorm:"not null;default:'';comment:'分类名称''"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} diff --git a/server/model/gen/gen.go b/server/model/gen/gen.go new file mode 100644 index 0000000..25df580 --- /dev/null +++ b/server/model/gen/gen.go @@ -0,0 +1,48 @@ +package gen + +//GenTable 代码生成业务实体 +type GenTable struct { + ID uint `gorm:"primarykey;comment:'主键'"` + TableName string `gorm:"not null;default:'';comment:'表名称''"` + TableComment string `gorm:"not null;default:'';comment:'表描述'"` + SubTableName string `gorm:"not null;default:'';comment:'关联表名称'"` + SubTableFk string `gorm:"not null;default:'';comment:'关联表外键'"` + AuthorName string `gorm:"not null;default:'';comment:'作者的名称'"` + EntityName string `gorm:"not null;default:'';comment:'实体的名称'"` + ModuleName string `gorm:"not null;default:'';comment:'生成模块名'"` + FunctionName string `gorm:"not null;default:'';comment:'生成功能名'"` + TreePrimary string `gorm:"not null;default:'';comment:'树主键字段'"` + TreeParent string `gorm:"not null;default:'';comment:'树父级字段'"` + TreeName string `gorm:"not null;default:'';comment:'树显示字段'"` + GenTpl string `gorm:"not null;default:'crud';comment:'生成模板方式: [crud=单表, tree=树表]'"` + GenType int `gorm:"not null;default:0;comment:'生成代码方式: [0=zip压缩包, 1=自定义路径]'"` + GenPath string `gorm:"not null;default:'/';comment:'生成代码路径: [不填默认项目路径]'"` + Remarks string `gorm:"not null;default:'';comment:'备注信息'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` +} + +//GenTableColumn 代码生成表列实体 +type GenTableColumn struct { + ID uint `gorm:"primarykey;comment:'列主键'"` + TableID uint `gorm:"not null;default:0;comment:'表外键'"` + ColumnName string `gorm:"not null;default:'';comment:'列名称'"` + ColumnComment string `gorm:"not null;default:'';comment:'列描述'"` + ColumnLength int `gorm:"not null;default:0;comment:'列长度'"` + ColumnType string `gorm:"not null;default:'';comment:'列类型'"` + GoType string `gorm:"not null;default:'';comment:'类型'"` + GoField string `gorm:"not null;default:'';comment:'字段名'"` + IsPk uint8 `gorm:"not null;default:0;comment:'是否主键: [1=是, 0=否]'"` + IsIncrement uint8 `gorm:"not null;default:0;comment:'是否自增: [1=是, 0=否]'"` + IsRequired uint8 `gorm:"not null;default:0;comment:'是否必填: [1=是, 0=否]'"` + IsInsert uint8 `gorm:"not null;default:0;comment:'是否为插入字段: [1=是, 0=否]'"` + IsEdit uint8 `gorm:"not null;default:0;comment:'是否编辑字段: [1=是, 0=否]'"` + IsList uint8 `gorm:"not null;default:0;comment:'是否列表字段: [1=是, 0=否]'"` + IsQuery uint8 `gorm:"not null;default:0;comment:'是否查询字段: [1=是, 0=否]'"` + QueryType string `gorm:"not null;default:'=';comment:'查询方式: [等于、不等于、大于、小于、范围]'"` + HtmlType string `gorm:"not null;default:'';comment:'显示类型: [文本框、文本域、下拉框、复选框、单选框、日期控件]'"` + DictType string `gorm:"not null;default:'';comment:'字典类型'"` + Sort int `gorm:"not null;default:0;comment:'排序编号'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` +} diff --git a/server/model/setting/dict_data.go b/server/model/setting/dict_data.go new file mode 100644 index 0000000..7cffe98 --- /dev/null +++ b/server/model/setting/dict_data.go @@ -0,0 +1,16 @@ +package setting + +//DictData 字典数据实体 +type DictData struct { + ID uint `gorm:"primarykey;comment:'主键'"` + TypeId uint `gorm:"not null;default:0;comment:'类型'"` + Name string `gorm:"not null;default:'';comment:'键名''"` + Value string `gorm:"not null;default:'';comment:'数值'"` + Remark string `gorm:"not null;default:'';comment:'备注'"` + Sort uint16 `gorm:"not null;default:0;comment:'排序'"` + Status uint8 `gorm:"not null;default:1;comment:'字典状态: 0=停用, 1=正常'"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} diff --git a/server/model/setting/dict_type.go b/server/model/setting/dict_type.go new file mode 100644 index 0000000..f066482 --- /dev/null +++ b/server/model/setting/dict_type.go @@ -0,0 +1,14 @@ +package setting + +//DictType 字典类型实体 +type DictType struct { + ID uint `gorm:"primarykey;comment:'主键'"` + DictName string `gorm:"not null;default:'';comment:'字典名称''"` + DictType string `gorm:"not null;default:'';comment:'字典类型'"` + DictRemark string `gorm:"not null;default:'';comment:'字典备注'"` + DictStatus uint8 `gorm:"not null;default:1;comment:'字典状态: 0=停用, 1=正常'"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} diff --git a/server/model/system/system.go b/server/model/system/system.go new file mode 100644 index 0000000..30c352f --- /dev/null +++ b/server/model/system/system.go @@ -0,0 +1,130 @@ +package system + +//SystemConfig 系统配置实体 +type SystemConfig struct { + ID uint `gorm:"primarykey;comment:'主键'"` + Type string `gorm:"default:'';comment:'类型''"` + Name string `gorm:"not null;default:'';comment:'键'"` + Value string `gorm:"type:text;not null;default:'';comment:'值'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` +} + +//SystemAuthAdmin 系统管理员实体 +type SystemAuthAdmin struct { + ID uint `gorm:"primarykey;comment:'主键'"` + DeptId uint `gorm:"not null;default:0;comment:'部门ID'"` + PostId uint `gorm:"not null;default:0;comment:'岗位ID'"` + Username string `gorm:"not null;default:'';comment:'用户账号''"` + Nickname string `gorm:"not null;default:'';comment:'用户昵称'"` + Password string `gorm:"not null;default:'';comment:'用户密码'"` + Avatar string `gorm:"not null;default:'';comment:'用户头像'"` + Role string `gorm:"not null;default:'';comment:'角色主键'"` + Salt string `gorm:"not null;default:'';comment:'加密盐巴'"` + Sort uint16 `gorm:"not null;default:0;comment:'排序编号'"` + IsMultipoint uint8 `gorm:"not null;default:0;comment:'多端登录: 0=否, 1=是''"` + IsDisable uint8 `gorm:"not null;default:0;comment:'是否禁用: 0=否, 1=是'"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + LastLoginIp string `gorm:"not null;default:'';comment:'最后登录IP'"` + LastLoginTime int64 `gorm:"not null;default:0;comment:'最后登录时间'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} + +//SystemAuthMenu 系统菜单实体 +type SystemAuthMenu struct { + ID uint `gorm:"primarykey;comment:'主键'"` + Pid uint `gorm:"not null;default:0;comment:'上级菜单'"` + MenuType string `gorm:"not null;default:'';comment:'权限类型: M=目录,C=菜单,A=按钮''"` + MenuName string `gorm:"not null;default:'';comment:'菜单名称'"` + MenuIcon string `gorm:"not null;default:'';comment:'菜单图标'"` + MenuSort uint16 `gorm:"not null;default:0;comment:'菜单排序'"` + Perms string `gorm:"not null;default:'';comment:'权限标识'"` + Paths string `gorm:"not null;default:'';comment:'路由地址'"` + Component string `gorm:"not null;default:'';comment:'前端组件'"` + Selected string `gorm:"not null;default:'';comment:'选中路径'"` + Params string `gorm:"not null;default:'';comment:'路由参数'"` + IsCache uint8 `gorm:"not null;default:0;comment:'是否缓存: 0=否, 1=是''"` + IsShow uint8 `gorm:"not null;default:1;comment:'是否显示: 0=否, 1=是'"` + IsDisable uint8 `gorm:"not null;default:0;comment:'是否禁用: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` +} + +//SystemAuthPerm 系统角色菜单实体 +type SystemAuthPerm struct { + ID string `gorm:"primarykey;comment:'主键'"` + RoleId uint `gorm:"not null;default:0;comment:'角色ID'"` + MenuId uint `gorm:"not null;default:0;comment:'菜单ID'"` +} + +//SystemAuthRole 系统角色实体 +type SystemAuthRole struct { + ID uint `gorm:"primarykey;comment:'主键'"` + Name string `gorm:"not null;default:'';comment:'角色名称''"` + Remark string `gorm:"not null;default:'';comment:'备注信息'"` + IsDisable uint8 `gorm:"not null;default:0;comment:'是否禁用: 0=否, 1=是'"` + Sort uint16 `gorm:"not null;default:0;comment:'角色排序'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` +} + +//SystemAuthDept 系统部门实体 +type SystemAuthDept struct { + ID uint `gorm:"primarykey;comment:'主键'"` + Pid uint `gorm:"not null;default:0;comment:'上级主键'"` + Name string `gorm:"not null;default:'';comment:'部门名称''"` + Duty string `gorm:"not null;default:'';comment:'负责人名'"` + Mobile string `gorm:"not null;default:'';comment:'联系电话'"` + Sort uint16 `gorm:"not null;default:0;comment:'排序编号'"` + IsStop uint8 `gorm:"not null;default:0;comment:'是否停用: 0=否, 1=是'"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} + +//SystemAuthPost 系统岗位管理 +type SystemAuthPost struct { + ID uint `gorm:"primarykey;comment:'主键'"` + Code string `gorm:"not null;default:'';comment:'岗位编码''"` + Name string `gorm:"not null;default:'';comment:'岗位名称''"` + Remarks string `gorm:"not null;default:'';comment:'岗位备注''"` + Sort uint16 `gorm:"not null;default:0;comment:'岗位排序'"` + IsStop uint8 `gorm:"not null;default:0;comment:'是否停用: 0=否, 1=是'"` + IsDelete uint8 `gorm:"not null;default:0;comment:'是否删除: 0=否, 1=是'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` + UpdateTime int64 `gorm:"autoUpdateTime;not null;comment:'更新时间'"` + DeleteTime int64 `gorm:"not null;default:0;comment:'删除时间'"` +} + +//SystemLogLogin 系统登录日志实体 +type SystemLogLogin struct { + ID uint `gorm:"primarykey;comment:'主键'"` + AdminId uint `gorm:"not null;default:0;comment:'管理员ID'"` + Username string `gorm:"not null;default:'';comment:'登录账号'"` + Ip string `gorm:"not null;default:'';comment:'登录地址'"` + Os string `gorm:"not null;default:'';comment:'操作系统'"` + Browser string `gorm:"not null;default:'';comment:'浏览器'"` + Status uint8 `gorm:"not null;default:0;comment:'操作状态: 1=成功, 0=失败'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` +} + +//SystemLogOperate 系统操作日志实体 +type SystemLogOperate struct { + ID uint `gorm:"primarykey;comment:'主键'"` + AdminId uint `gorm:"not null;default:0;comment:'操作人ID'"` + Type string `gorm:"not null;default:'';comment:'请求类型: GET/POST/PUT'"` + Title string `gorm:"default:'';comment:'操作标题'"` + Ip string `gorm:"not null;default:'';comment:'请求IP'"` + Url string `gorm:"not null;default:'';comment:'请求接口'"` + Method string `gorm:"not null;default:'';comment:'请求方法'"` + Args string `gorm:"comment:'请求参数'"` + Error string `gorm:"comment:'错误信息'"` + Status uint8 `gorm:"not null;default:0;comment:'执行状态: 1=成功, 2=失败'"` + StartTime int64 `gorm:"not null;default:0;comment:'开始时间'"` + EndTime int64 `gorm:"not null;default:0;comment:'结束时间'"` + TaskTime int64 `gorm:"not null;default:0;comment:'执行耗时'"` + CreateTime int64 `gorm:"autoCreateTime;not null;comment:'创建时间'"` +} diff --git a/server/plugin/storage.go b/server/plugin/storage.go new file mode 100644 index 0000000..ef6685b --- /dev/null +++ b/server/plugin/storage.go @@ -0,0 +1,134 @@ +package plugin + +import ( + "fmt" + "io" + "mime/multipart" + "os" + "path" + "strconv" + "strings" + "time" + "x_admin/config" + "x_admin/core" + "x_admin/core/response" + "x_admin/util" +) + +var StorageDriver = storageDriver{} + +// UploadFile 文件对象 +type UploadFile struct { + Name string // 文件名称 + Type int // 文件类型 + Size int64 // 文件大小 + Ext string // 文件扩展 + Uri string // 文件路径 + Path string // 访问地址 +} + +// storageDriver 存储引擎 +type storageDriver struct{} + +// Upload 根据引擎类型上传文件 +func (sd storageDriver) Upload(file *multipart.FileHeader, folder string, fileType int) (uf *UploadFile, e error) { + // TODO: engine默认local + if e = sd.checkFile(file, fileType); e != nil { + return + } + key := sd.buildSaveName(file) + engine := "local" + if engine == "local" { + if e = sd.localUpload(file, key, folder); e != nil { + return + } + } else { + core.Logger.Errorf("storageDriver.Upload engine err: err=[unsupported engine]") + return nil, response.Failed.Make(fmt.Sprintf("engine:%s 暂时不支持", engine)) + } + fileRelPath := path.Join(folder, key) + return &UploadFile{ + Name: file.Filename, + Type: fileType, + Size: file.Size, + Ext: strings.ToLower(strings.Replace(path.Ext(file.Filename), ".", "", 1)), + Uri: fileRelPath, + Path: util.UrlUtil.ToAbsoluteUrl(fileRelPath), + }, nil +} + +// localUpload 本地上传 (临时方法) +func (sd storageDriver) localUpload(file *multipart.FileHeader, key string, folder string) (e error) { + // TODO: 临时方法,后续调整 + // 映射目录 + directory := config.Config.UploadDirectory + // 打开源文件 + src, err := file.Open() + if err != nil { + core.Logger.Errorf("storageDriver.localUpload Open err: err=[%+v]", err) + return response.Failed.Make("打开文件失败!") + } + defer src.Close() + // 文件信息 + savePath := path.Join(directory, folder, path.Dir(key)) + saveFilePath := path.Join(directory, folder, key) + // 创建目录 + err = os.MkdirAll(savePath, 0755) + if err != nil && !os.IsExist(err) { + core.Logger.Errorf( + "storageDriver.localUpload MkdirAll err: path=[%s], err=[%+v]", savePath, err) + return response.Failed.Make("创建上传目录失败!") + } + // 创建目标文件 + out, err := os.Create(saveFilePath) + if err != nil { + core.Logger.Errorf( + "storageDriver.localUpload Create err: file=[%s], err=[%+v]", saveFilePath, err) + return response.Failed.Make("创建文件失败!") + } + defer out.Close() + // 写入目标文件 + _, err = io.Copy(out, src) + if err != nil { + core.Logger.Errorf( + "storageDriver.localUpload Copy err: file=[%s], err=[%+v]", saveFilePath, err) + return response.Failed.Make("上传文件失败: " + err.Error()) + } + return nil +} + +// checkFile 生成文件名称 +func (sd storageDriver) buildSaveName(file *multipart.FileHeader) string { + name := file.Filename + ext := strings.ToLower(path.Ext(name)) + date := time.Now().Format("20060201") + return path.Join(date, util.ToolsUtil.MakeUuid()+ext) +} + +// checkFile 文件验证 +func (sd storageDriver) checkFile(file *multipart.FileHeader, fileType int) (e error) { + fileName := file.Filename + fileExt := strings.ToLower(strings.Replace(path.Ext(fileName), ".", "", 1)) + fileSize := file.Size + if fileType == 10 { + // 图片文件 + if !util.ToolsUtil.Contains(config.Config.UploadImageExt, fileExt) { + return response.Failed.Make("不被支持的图片扩展: " + fileExt) + } + if fileSize > config.Config.UploadImageSize { + return response.Failed.Make("上传图片不能超出限制: " + strconv.FormatInt(config.Config.UploadImageSize/1024/1024, 10) + "M") + } + } else if fileType == 20 { + // 视频文件 + if !util.ToolsUtil.Contains(config.Config.UploadVideoExt, fileExt) { + return response.Failed.Make("不被支持的视频扩展: " + fileExt) + } + if fileSize > config.Config.UploadVideoSize { + return response.Failed.Make("上传视频不能超出限制: " + strconv.FormatInt(config.Config.UploadVideoSize/1024/1024, 10) + "M") + } + } else { + core.Logger.Errorf("storageDriver.checkFile fileType err: err=[unsupported fileType]") + return response.Failed.Make("上传文件类型错误") + } + return nil +} diff --git a/server/routers/enter.go b/server/routers/enter.go new file mode 100644 index 0000000..53798e8 --- /dev/null +++ b/server/routers/enter.go @@ -0,0 +1,13 @@ +package routers + +import ( + "x_admin/admin" + "x_admin/generator" + + "github.com/gin-gonic/gin" +) + +func RegisterGroup(rg *gin.RouterGroup) { + admin.RegisterGroup(rg) + generator.RegisterGroup(rg) +} diff --git a/server/static/backend_avatar.png b/server/static/backend_avatar.png new file mode 100644 index 0000000..2051719 Binary files /dev/null and b/server/static/backend_avatar.png differ diff --git a/server/static/backend_backdrop.png b/server/static/backend_backdrop.png new file mode 100644 index 0000000..a302011 Binary files /dev/null and b/server/static/backend_backdrop.png differ diff --git a/server/static/backend_favicon.ico b/server/static/backend_favicon.ico new file mode 100644 index 0000000..7c00219 Binary files /dev/null and b/server/static/backend_favicon.ico differ diff --git a/server/static/backend_logo.png b/server/static/backend_logo.png new file mode 100644 index 0000000..cb2f6d5 Binary files /dev/null and b/server/static/backend_logo.png differ diff --git a/server/util/array.go b/server/util/array.go new file mode 100644 index 0000000..28da4a5 --- /dev/null +++ b/server/util/array.go @@ -0,0 +1,38 @@ +package util + +var ArrayUtil = arrayUtil{} + +//arrayUtil 数组工具类 +type arrayUtil struct{} + +//ListToTree 字典列表转树形结构 +func (au arrayUtil) ListToTree(arr []map[string]interface{}, id string, pid string, child string) (mapList []interface{}) { + mapList = []interface{}{} + // 遍历以id_为key生成map + idValMap := make(map[uint]interface{}) + for _, m := range arr { + if idVal, ok := m[id]; ok { + idValMap[idVal.(uint)] = m + } + } + // 遍历 + for _, m := range arr { + // 获取父节点 + if pidVal, ok := m[pid]; ok { + if pNode, pok := idValMap[pidVal.(uint)]; pok { + // 有父节点则添加到父节点子集 + if cVal, cok := pNode.(map[string]interface{})[child]; cok { + if cVal == nil { + cVal = []interface{}{m} + } else { + cVal = append(cVal.([]interface{}), m) + } + pNode.(map[string]interface{})[child] = cVal + continue + } + } + } + mapList = append(mapList, m) + } + return +} diff --git a/server/util/config.go b/server/util/config.go new file mode 100644 index 0000000..05c1254 --- /dev/null +++ b/server/util/config.go @@ -0,0 +1,75 @@ +package util + +import ( + "errors" + "x_admin/model/system" + + "gorm.io/gorm" +) + +var ConfigUtil = configUtil{} + +// convertUtil 数据库配置操作工具 +type configUtil struct{} + +// Get 根据类型和名称获取配置字典 +func (cu configUtil) Get(db *gorm.DB, cnfType string, names ...string) (data map[string]string, err error) { + chain := db.Where("type = ?", cnfType) + if len(names) > 0 { + chain.Where("name = ?", names[0]) + } + var configs []system.SystemConfig + err = chain.Find(&configs).Error + if err != nil { + return nil, err + } + data = make(map[string]string) + for i := 0; i < len(configs); i++ { + data[configs[i].Name] = configs[i].Value + } + return data, nil +} + +// GetVal 根据类型和名称获取配置值 +func (cu configUtil) GetVal(db *gorm.DB, cnfType string, name string, defaultVal string) (data string, err error) { + config, err := cu.Get(db, cnfType, name) + if err != nil { + return data, err + } + data, ok := config[name] + if !ok { + data = defaultVal + } + return data, nil +} + +// GetMap 根据类型和名称获取配置值(Json字符串转dict) +func (cu configUtil) GetMap(db *gorm.DB, cnfType string, name string) (data map[string]string, err error) { + val, err := cu.GetVal(db, cnfType, name, "") + if err != nil { + return data, err + } + if val == "" { + return map[string]string{}, nil + } + err = ToolsUtil.JsonToObj(val, &data) + return data, err +} + +// Set 设置配置的值 +func (cu configUtil) Set(db *gorm.DB, cnfType string, name string, val string) (err error) { + var config system.SystemConfig + err = db.Where("type = ? AND name = ?", cnfType, name).First(&config).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + if err = db.Create(&config).Error; err != nil { + return err + } + return nil + } else if err != nil { + return err + } + if err = db.Model(&config).Update("value", val).Error; err != nil { + return err + } + return nil +} diff --git a/server/util/convert.go b/server/util/convert.go new file mode 100644 index 0000000..4256395 --- /dev/null +++ b/server/util/convert.go @@ -0,0 +1,27 @@ +package util + +import ( + "x_admin/core" + + "github.com/fatih/structs" + "github.com/jinzhu/copier" +) + +var ConvertUtil = convertUtil{} + +// convertUtil 转换工具 +type convertUtil struct{} + +// StructsToMaps 将结构体转换成Map列表 +func (cu convertUtil) StructsToMaps(objs interface{}) (data []map[string]interface{}) { + var objList []interface{} + err := copier.Copy(&objList, objs) + if err != nil { + core.Logger.Errorf("convertUtil.StructsToMaps err: err=[%+v]", err) + return nil + } + for _, v := range objList { + data = append(data, structs.Map(v)) + } + return data +} diff --git a/server/util/ip.go b/server/util/ip.go new file mode 100644 index 0000000..502ac15 --- /dev/null +++ b/server/util/ip.go @@ -0,0 +1,23 @@ +package util + +import ( + "net" + "x_admin/core" +) + +var IpUtil = ipUtil{} + +// serverUtil IP工具类 +type ipUtil struct{} + +// GetHostIp 获取本地主机名 +func (su ipUtil) GetHostIp() (ip string) { + conn, err := net.Dial("udp", "114.114.114.114:80") + if err != nil { + core.Logger.Errorf("GetHostIp Dial err: err=[%+v]", err) + return + } + defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) + return localAddr.IP.String() +} diff --git a/server/util/redis.go b/server/util/redis.go new file mode 100644 index 0000000..0708baf --- /dev/null +++ b/server/util/redis.go @@ -0,0 +1,214 @@ +package util + +import ( + "bufio" + "context" + "strings" + "time" + "x_admin/config" + "x_admin/core" + + "github.com/go-redis/redis/v9" +) + +var RedisUtil = redisUtil{redis: core.Redis} + +// redisUtil Redis操作工具类 +type redisUtil struct { + redis *redis.Client +} + +// stringToLines string拆分多行 +func stringToLines(s string) (lines []string, err error) { + scanner := bufio.NewScanner(strings.NewReader(s)) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + err = scanner.Err() + return +} + +// stringToKV string拆分key和val +func stringToKV(s string) (string, string) { + ss := strings.Split(s, ":") + if len(ss) < 2 { + return s, "" + } + return ss[0], ss[1] +} + +// Info Redis服务信息 +func (ru redisUtil) Info(sections ...string) (res map[string]string) { + infoStr, err := ru.redis.Info(context.Background(), sections...).Result() + res = map[string]string{} + if err != nil { + core.Logger.Errorf("redisUtil.Info err: err=[%+v]", err) + return res + } + // string拆分多行 + lines, err := stringToLines(infoStr) + if err != nil { + core.Logger.Errorf("stringToLines err: err=[%+v]", err) + return res + } + // 解析成Map + for i := 0; i < len(lines); i++ { + if lines[i] == "" || strings.HasPrefix(lines[i], "# ") { + continue + } + k, v := stringToKV(lines[i]) + res[k] = v + } + return res +} + +// DBSize 当前数据库key数量 +func (ru redisUtil) DBSize() int64 { + size, err := ru.redis.DBSize(context.Background()).Result() + if err != nil { + core.Logger.Errorf("redisUtil.DBSize err: err=[%+v]", err) + return 0 + } + return size +} + +// Set 设置键值对 +func (ru redisUtil) Set(key string, value interface{}, timeSec int) bool { + err := ru.redis.Set(context.Background(), + config.Config.RedisPrefix+key, value, time.Duration(timeSec)*time.Second).Err() + if err != nil { + core.Logger.Errorf("redisUtil.Set err: err=[%+v]", err) + return false + } + return true +} + +// Get 获取key的值 +func (ru redisUtil) Get(key string) string { + res, err := ru.redis.Get(context.Background(), config.Config.RedisPrefix+key).Result() + if err != nil { + core.Logger.Errorf("redisUtil.Get err: err=[%+v]", err) + return "" + } + return res +} + +// SSet 将数据放入set缓存 +func (ru redisUtil) SSet(key string, values ...interface{}) bool { + err := ru.redis.SAdd(context.Background(), config.Config.RedisPrefix+key, values...).Err() + if err != nil { + core.Logger.Errorf("redisUtil.SSet err: err=[%+v]", err) + return false + } + return true +} + +// SGet 根据key获取Set中的所有值 +func (ru redisUtil) SGet(key string) []string { + res, err := ru.redis.SMembers(context.Background(), config.Config.RedisPrefix+key).Result() + if err != nil { + core.Logger.Errorf("redisUtil.SGet err: err=[%+v]", err) + return []string{} + } + return res +} + +// HMSet 设置key, 通过字典的方式设置多个field, value对 +func (ru redisUtil) HMSet(key string, mapping map[string]string, timeSec int) bool { + err := ru.redis.HSet(context.Background(), config.Config.RedisPrefix+key, mapping).Err() + if err != nil { + core.Logger.Errorf("redisUtil.HMSet err: err=[%+v]", err) + return false + } + if timeSec > 0 { + if !ru.Expire(key, timeSec) { + return false + } + } + return true +} + +// HSet 向hash表中放入数据,如果不存在将创建 +func (ru redisUtil) HSet(key string, field string, value string, timeSec int) bool { + return ru.HMSet(key, map[string]string{field: value}, timeSec) +} + +// HGet 获取key中field域的值 +func (ru redisUtil) HGet(key string, field string) string { + res, err := ru.redis.HGet(context.Background(), config.Config.RedisPrefix+key, field).Result() + if err != nil { + core.Logger.Errorf("redisUtil.HGet err: err=[%+v]", err) + return "" + } + return res +} + +// HExists 判断key中有没有field域名 +func (ru redisUtil) HExists(key string, field string) bool { + res, err := ru.redis.HExists(context.Background(), config.Config.RedisPrefix+key, field).Result() + if err != nil { + core.Logger.Errorf("redisUtil.HExists err: err=[%+v]", err) + return false + } + return res +} + +// HDel 删除hash表中的值 +func (ru redisUtil) HDel(key string, fields ...string) bool { + err := ru.redis.HDel(context.Background(), config.Config.RedisPrefix+key, fields...).Err() + if err != nil { + core.Logger.Errorf("redisUtil.HDel err: err=[%+v]", err) + return false + } + return true +} + +// Exists 判断多项key是否存在 +func (ru redisUtil) Exists(keys ...string) int64 { + fullKeys := ru.toFullKeys(keys) + cnt, err := ru.redis.Exists(context.Background(), fullKeys...).Result() + if err != nil { + core.Logger.Errorf("redisUtil.Exists err: err=[%+v]", err) + return -1 + } + return cnt +} + +// Expire 指定缓存失效时间 +func (ru redisUtil) Expire(key string, timeSec int) bool { + err := ru.redis.Expire(context.Background(), config.Config.RedisPrefix+key, time.Duration(timeSec)*time.Second).Err() + if err != nil { + core.Logger.Errorf("redisUtil.Expire err: err=[%+v]", err) + return false + } + return true +} + +// TTL 根据key获取过期时间 +func (ru redisUtil) TTL(key string) int { + td, err := ru.redis.TTL(context.Background(), config.Config.RedisPrefix+key).Result() + if err != nil { + core.Logger.Errorf("redisUtil.TTL err: err=[%+v]", err) + return 0 + } + return int(td / time.Second) +} + +// Del 删除一个或多个键 +func (ru redisUtil) Del(keys ...string) bool { + fullKeys := ru.toFullKeys(keys) + err := ru.redis.Del(context.Background(), fullKeys...).Err() + if err != nil { + core.Logger.Errorf("redisUtil.Del err: err=[%+v]", err) + return false + } + return true +} + +// toFullKeys 为keys批量增加前缀 +func (ru redisUtil) toFullKeys(keys []string) (fullKeys []string) { + for _, k := range keys { + fullKeys = append(fullKeys, config.Config.RedisPrefix+k) + } + return +} diff --git a/server/util/server.go b/server/util/server.go new file mode 100644 index 0000000..a330646 --- /dev/null +++ b/server/util/server.go @@ -0,0 +1,171 @@ +package util + +import ( + "fmt" + "math" + "os" + "runtime" + "strconv" + "strings" + "time" + "x_admin/core" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/disk" + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/mem" + "github.com/shirou/gopsutil/process" +) + +var ServerUtil = serverUtil{} + +// serverUtil 服务器信息获取工具 +type serverUtil struct{} + +// GetFmtSize 按照正确的格式缩放字节 +func (su serverUtil) GetFmtSize(data uint64) string { + var factor float64 = 1024 + res := float64(data) + for _, unit := range []string{"", "K", "M", "G", "T", "P"} { + if res < factor { + return fmt.Sprintf("%.2f%sB", res, unit) + } + res /= factor + } + return fmt.Sprintf("%.2f%sB", res, "P") +} + +// GetFmtTime 格式化显示时间 (毫秒) +func (su serverUtil) GetFmtTime(ms int64) (res string) { + rem := ms / 1000 + days, rem := rem/86400, rem%86400 + hours, rem := rem/3600, rem%3600 + minutes := rem / 60 + res = strconv.FormatInt(minutes, 10) + "分钟" + if hours > 0 { + res = strconv.FormatInt(hours, 10) + "小时" + res + } + if days > 0 { + res = strconv.FormatInt(days, 10) + "天" + res + } + return res +} + +// GetCpuInfo 获取CPU信息 +func (su serverUtil) GetCpuInfo() (data map[string]interface{}) { + cnt, err := cpu.Counts(true) + if err != nil { + core.Logger.Errorf("GetCpuInfo Counts err: err=[%+v]", err) + return data + } + tss, err := cpu.Times(false) + if err != nil { + core.Logger.Errorf("GetCpuInfo Times err: err=[%+v]", err) + return data + } + ts := tss[0] + return map[string]interface{}{ + "cpu_num": cnt, + "total": ToolsUtil.Round(ts.Total(), 2), + "sys": ToolsUtil.Round(ts.System/ts.Total(), 2), + "used": ToolsUtil.Round(ts.User/ts.Total(), 2), + "wait": ToolsUtil.Round(ts.Iowait/ts.Total(), 2), + "free": ToolsUtil.Round(ts.Idle/ts.Total(), 2), + } +} + +// GetMemInfo 获取内存信息 +func (su serverUtil) GetMemInfo() (data map[string]interface{}) { + number := math.Pow(1024, 3) + vm, err := mem.VirtualMemory() + if err != nil { + core.Logger.Errorf("GetMemInfo VirtualMemory err: err=[%+v]", err) + return data + } + return map[string]interface{}{ + "total": ToolsUtil.Round(float64(vm.Total)/number, 2), + "used": ToolsUtil.Round(float64(vm.Used)/number, 2), + "free": ToolsUtil.Round(float64(vm.Available)/number, 2), + "usage": ToolsUtil.Round(vm.UsedPercent, 2), + } +} + +// GetSysInfo 获取服务器信息 +func (su serverUtil) GetSysInfo() (data map[string]interface{}) { + infoStat, err := host.Info() + if err != nil { + core.Logger.Errorf("GetSysInfo Info err: err=[%+v]", err) + return data + } + pwd, err := os.Getwd() + if err != nil { + core.Logger.Errorf("GetSysInfo Getwd err: err=[%+v]", err) + return data + } + return map[string]interface{}{ + "computerName": infoStat.Hostname, + "computerIp": IpUtil.GetHostIp(), + "userDir": pwd, + "osName": infoStat.OS, + "osArch": infoStat.KernelArch, + } +} + +// GetDiskInfo 获取磁盘信息 +func (su serverUtil) GetDiskInfo() (data []map[string]interface{}) { + partStats, err := disk.Partitions(false) + if err != nil { + core.Logger.Errorf("GetDiskInfo Partitions err: err=[%+v]", err) + return data + } + for i := 0; i < len(partStats); i++ { + part := partStats[i] + usage, uErr := disk.Usage(part.Mountpoint) + if uErr != nil { + core.Logger.Errorf("GetDiskInfo Usage err: err=[%+v]", err) + continue + } + data = append(data, map[string]interface{}{ + "dirName": part.Mountpoint, + "sysTypeName": part.Fstype, + "typeName": part.Device, + "total": su.GetFmtSize(usage.Total), + "free": su.GetFmtSize(usage.Free), + "used": su.GetFmtSize(usage.Used), + "usage": ToolsUtil.Round(usage.UsedPercent, 2), + }) + } + return data +} + +// GetGoInfo 获取Go环境及服务信息 +func (su serverUtil) GetGoInfo() (data map[string]interface{}) { + number := math.Pow(1024, 2) + curProc, err := process.NewProcess(int32(os.Getpid())) + if err != nil { + core.Logger.Errorf("GetGoInfo NewProcess err: err=[%+v]", err) + return data + } + memInfo, err := curProc.MemoryInfo() + if err != nil { + core.Logger.Errorf("GetGoInfo MemoryInfo err: err=[%+v]", err) + return data + } + startTime, err := curProc.CreateTime() + if err != nil { + core.Logger.Errorf("GetGoInfo CreateTime err: err=[%+v]", err) + return data + } + return map[string]interface{}{ + "name": "Go", + "version": runtime.Version(), + "home": os.Args[0], + "inputArgs": fmt.Sprintf("[%s]", strings.Join(os.Args[1:], ", ")), + "total": ToolsUtil.Round(float64(memInfo.VMS)/number, 2), + "max": ToolsUtil.Round(float64(memInfo.VMS)/number, 2), + "free": ToolsUtil.Round((float64(memInfo.VMS-memInfo.RSS))/number, 2), + "usage": ToolsUtil.Round(float64(memInfo.RSS)/number, 2), + "runTime": su.GetFmtTime(time.Now().UnixMilli() - startTime), + "startTime": time.UnixMilli(startTime).Format(core.TimeFormat), + } +} diff --git a/server/util/string.go b/server/util/string.go new file mode 100644 index 0000000..208acc8 --- /dev/null +++ b/server/util/string.go @@ -0,0 +1,34 @@ +package util + +import ( + "bytes" + "strings" + "unicode" +) + +var StringUtil = stringUtil{} + +//arrayUtil 数组工具类 +type stringUtil struct{} + +func (su stringUtil) ToSnakeCase(s string) string { + buf := bytes.Buffer{} + for i, r := range s { + if unicode.IsUpper(r) { + if i > 0 { + buf.WriteRune('_') + } + buf.WriteRune(unicode.ToLower(r)) + } else { + buf.WriteRune(r) + } + } + return buf.String() +} +func (su stringUtil) ToCamelCase(s string) string { + words := strings.Split(s, "_") + for i := 1; i < len(words); i++ { + words[i] = strings.Title(words[i]) + } + return strings.Join(words, "") +} diff --git a/server/util/tools.go b/server/util/tools.go new file mode 100644 index 0000000..b57611f --- /dev/null +++ b/server/util/tools.go @@ -0,0 +1,97 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "math" + "math/rand" + "os" + "reflect" + "strconv" + "strings" + "time" + "x_admin/config" + + "github.com/google/uuid" +) + +var ( + ToolsUtil = toolsUtil{} + allRandomStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +) + +// toolsUtil 常用工具集合类 +type toolsUtil struct{} + +// RandomString 返回随机字符串 +func (tu toolsUtil) RandomString(length int) string { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + byteList := make([]byte, length) + for i := 0; i < length; i++ { + byteList[i] = allRandomStr[r.Intn(62)] + } + return string(byteList) +} + +// MakeUuid 制作UUID +func (tu toolsUtil) MakeUuid() string { + return strings.ReplaceAll(uuid.New().String(), "-", "") +} + +// MakeMd5 制作MD5 +func (tu toolsUtil) MakeMd5(data string) string { + sum := md5.Sum([]byte(data)) + return hex.EncodeToString(sum[:]) +} + +// MakeToken 生成唯一Token +func (tu toolsUtil) MakeToken() string { + ms := time.Now().UnixMilli() + token := tu.MakeMd5(tu.MakeUuid() + strconv.FormatInt(ms, 10) + tu.RandomString(8)) + tokenSecret := token + config.Config.Secret + return tu.MakeMd5(tokenSecret) + tu.RandomString(6) +} + +// Contains 判断src是否包含elem元素 +func (tu toolsUtil) Contains(src interface{}, elem interface{}) bool { + srcArr := reflect.ValueOf(src) + if srcArr.Kind() == reflect.Ptr { + srcArr = srcArr.Elem() + } + if srcArr.Kind() == reflect.Slice { + for i := 0; i < srcArr.Len(); i++ { + if srcArr.Index(i).Interface() == elem { + return true + } + } + } + return false +} + +// Round float四舍五入 +func (tu toolsUtil) Round(val float64, n int) float64 { + base := math.Pow(10, float64(n)) + return math.Round(base*val) / base +} + +// JsonToObj JSON转Obj +func (tu toolsUtil) JsonToObj(jsonStr string, toVal interface{}) (err error) { + return json.Unmarshal([]byte(jsonStr), &toVal) +} + +// ObjToJson Obj转JSON +func (tu toolsUtil) ObjToJson(data interface{}) (res string, err error) { + b, err := json.Marshal(data) + if err != nil { + return res, err + } + res = string(b) + return res, nil +} + +// IsFileExist 判断文件或目录是否存在 +func (tu toolsUtil) IsFileExist(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} diff --git a/server/util/url.go b/server/util/url.go new file mode 100644 index 0000000..7727cde --- /dev/null +++ b/server/util/url.go @@ -0,0 +1,63 @@ +package util + +import ( + "net/url" + "path" + "strings" + "x_admin/config" + "x_admin/core" +) + +var ( + UrlUtil = urlUtil{} + publicUrl = config.Config.PublicUrl + publicPrefix = config.Config.PublicPrefix +) + +// urlUtil 文件路径处理工具 +type urlUtil struct{} + +// ToAbsoluteUrl 转绝对路径 +func (uu urlUtil) ToAbsoluteUrl(u string) string { + // TODO: engine默认local + if u == "" { + return "" + } + up, err := url.Parse(publicUrl) + if err != nil { + core.Logger.Errorf("ToAbsoluteUrl Parse err: err=[%+v]", err) + return u + } + if strings.HasPrefix(u, "/api/static/") { + up.Path = path.Join(up.Path, u) + return up.String() + } + engine := "local" + if engine == "local" { + up.Path = path.Join(up.Path, publicPrefix, u) + return up.String() + } + // TODO: 其他engine + return u +} + +func (uu urlUtil) ToRelativeUrl(u string) string { + // TODO: engine默认local + if u == "" { + return "" + } + up, err := url.Parse(u) + if err != nil { + core.Logger.Errorf("ToRelativeUrl Parse err: err=[%+v]", err) + return u + } + engine := "local" + if engine == "local" { + lu := up.String() + return strings.Replace( + strings.Replace(lu, publicUrl, "", 1), + publicPrefix, "", 1) + } + // TODO: 其他engine + return u +} diff --git a/server/util/verify.go b/server/util/verify.go new file mode 100644 index 0000000..9b98eb3 --- /dev/null +++ b/server/util/verify.go @@ -0,0 +1,71 @@ +package util + +import ( + "encoding/json" + "io" + "mime/multipart" + "x_admin/core/response" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +var VerifyUtil = verifyUtil{} + +// verifyUtil 参数验证工具类 +type verifyUtil struct{} + +func (vu verifyUtil) VerifyJSON(c *gin.Context, obj any) (e error) { + if err := c.ShouldBindBodyWith(obj, binding.JSON); err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + return +} + +func (vu verifyUtil) VerifyJSONArray(c *gin.Context, obj any) (e error) { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + err = json.Unmarshal(body, &obj) + if err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + return +} + +func (vu verifyUtil) VerifyBody(c *gin.Context, obj any) (e error) { + if err := c.ShouldBind(obj); err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + return +} + +func (vu verifyUtil) VerifyHeader(c *gin.Context, obj any) (e error) { + if err := c.ShouldBindHeader(obj); err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + return +} + +func (vu verifyUtil) VerifyQuery(c *gin.Context, obj any) (e error) { + if err := c.ShouldBindQuery(obj); err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + return +} + +func (vu verifyUtil) VerifyFile(c *gin.Context, name string) (file *multipart.FileHeader, e error) { + file, err := c.FormFile(name) + if err != nil { + e = response.ParamsValidError.MakeData(err.Error()) + return + } + return +} diff --git a/sql/x_admin.sql b/sql/x_admin.sql new file mode 100644 index 0000000..946aeab --- /dev/null +++ b/sql/x_admin.sql @@ -0,0 +1,826 @@ +/* + Navicat Premium Data Transfer + + Source Server : likeadmin + Source Server Type : MySQL + Source Server Version : 50726 (5.7.26-log) + Source Host : localhost:3306 + Source Schema : likeadmin + + Target Server Type : MySQL + Target Server Version : 50726 (5.7.26-log) + File Encoding : 65001 + + Date: 21/11/2023 10:12:07 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for x_album +-- ---------------------------- +DROP TABLE IF EXISTS `x_album`; +CREATE TABLE `x_album` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `cid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '类目ID', + `aid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '管理员ID', + `uid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID', + `type` tinyint(2) UNSIGNED NOT NULL DEFAULT 10 COMMENT '文件类型: [10=图片, 20=视频]', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名称', + `uri` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件路径', + `ext` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件扩展', + `size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小', + `is_delete` int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_cid`(`cid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '相册管理表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_album +-- ---------------------------- +INSERT INTO `x_album` VALUES (1, 0, 1, 0, 10, 'Alx_gp73pq.png', 'image/20230911/34a557325c004f498b1da01bb068f919.png', 'png', 7138309, 0, 1699499781, 1699499781, 0); +INSERT INTO `x_album` VALUES (2, 0, 1, 0, 20, '素材中心 和另外 1 个页面 - 个人 - Microsoft​ Edge 2023-11-20 15-37-39.mp4', 'video/20232011/e5f53b824e314ab7992ef4b6e7595b86.mp4', 'mp4', 4053956, 0, 1700465870, 1700465870, 0); + +-- ---------------------------- +-- Table structure for x_album_cate +-- ---------------------------- +DROP TABLE IF EXISTS `x_album_cate`; +CREATE TABLE `x_album_cate` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `pid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '父级ID', + `type` tinyint(2) UNSIGNED NOT NULL DEFAULT 10 COMMENT '类型: [10=图片, 20=视频]', + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '分类名称', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: [0=否, 1=是]', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '相册分类表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_album_cate +-- ---------------------------- +INSERT INTO `x_album_cate` VALUES (1, 0, 10, '1', 0, 1699519913, 1699519913, 0); + +-- ---------------------------- +-- Table structure for x_article +-- ---------------------------- +DROP TABLE IF EXISTS `x_article`; +CREATE TABLE `x_article` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `cid` int(10) UNSIGNED NOT NULL COMMENT '分类', + `title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '标题', + `intro` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '简介', + `summary` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '摘要', + `image` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '封面', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '内容', + `author` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '作者', + `visit` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '浏览', + `sort` int(10) UNSIGNED NOT NULL DEFAULT 50 COMMENT '排序', + `is_show` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否显示: 0=否, 1=是', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `cid_idx`(`cid`) USING BTREE COMMENT '分类索引' +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章资讯表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_article +-- ---------------------------- +INSERT INTO `x_article` VALUES (1, 1, '让生活更精致!五款居家好物推荐,实用性超高', '##好物推荐🔥', '随着当代生活节奏的忙碌,很多人在闲暇之余都想好好的享受生活。随着科技的发展,也出现了越来越多可以帮助我们提升幸福感,让生活变得更精致的产品,下面周周就给大家盘点五款居家必备的好物,都是实用性很高的产品,周周可以保证大家买了肯定会喜欢。', '/api/static/article01.png', '

\"\"

拥有一台投影仪,闲暇时可以在家里直接看影院级别的大片,光是想想都觉得超级爽。市面上很多投影仪大几千,其实周周觉得没必要,选泰捷这款一千多的足够了,性价比非常高。

泰捷的专业度很高,在电视TV领域研发已经十年,有诸多专利和技术创新,荣获国内外多项技术奖项,拿下了腾讯创新工场投资,打造的泰捷视频TV端和泰捷电视盒子都获得了极高评价。

这款投影仪的分辨率在3000元内无敌,做到了真1080P高分辨率,也就是跟市场售价三千DLP投影仪一样的分辨率,真正做到了分毫毕现,像桌布的花纹、天空的云彩等,这些细节都清晰可见。

亮度方面,泰捷达到了850ANSI流明,同价位一般是200ANSI。这是因为泰捷为了提升亮度和LCD技术透射率低的问题,首创高功率LED灯源,让其亮度做到同价位最好。专业媒体也进行了多次对比,效果与3000元价位投影仪相当。

操作系统周周也很喜欢,完全不卡。泰捷作为资深音视频品牌,在系统优化方面有十年的研发经验,打造出的“零极”系统是业内公认效率最高、速度最快的系统,用户也评价它流畅度能一台顶三台,而且为了解决行业广告多这一痛点,系统内不植入任何广告。

', '红花', 9, 0, 1, 0, 1663317759, 1663322726, 0); +INSERT INTO `x_article` VALUES (2, 1, '埋葬UI设计师的坟墓不是内卷,而是免费模式', '', '本文从另外一个角度,聊聊作者对UI设计师职业发展前景的担忧,欢迎从事UI设计的同学来参与讨论,会有赠书哦', '/api/static/article02.jpeg', '


一个职业,卷,根本就没什么大不了的,尤其是成熟且收入高的职业,不卷才不符合事物发展的规律。何况 UI 设计师的人力市场到今天也和 5 年前一样,还是停留在大型菜鸡互啄的场面。远不能和医疗、证券、教师或者演艺练习生相提并论。

真正会让我对 UI 设计师发展前景觉得悲观的事情就只有一件 —— 国内的互联网产品免费机制。这也是一个我一直以来想讨论的话题,就在这次写一写。

国内互联网市场的发展,是一部浩瀚的 “免费经济” 发展史。虽然今天免费已经是深入国内民众骨髓的认知,但最早的中文互联网也是需要付费的,网游也都是要花钱的。

只是自有国情在此,付费确实阻碍了互联网行业的扩张和普及,一批创业家就开始通过免费的模式为用户提供服务,从而扩大了自己的产品覆盖面和普及程度。

印象最深的就是免费急先锋周鸿祎,和现在鲜少出现在公众视野不同,一零年前他是当之无愧的互联网教主,因为他开发出了符合中国国情的互联网产品 “打法”,让 360 的发展如日中天。

就是他在自传中提到:

只要是在互联网上每个人都需要的服务,我们就认为它是基础服务,基础服务一定是免费的,这样的话不会形成价值歧视。就是说,只要这种服务是每个人都一定要用的,我一定免费提供,而且是无条件免费。增值服务不是所有人都需要的,这个比例可能会相当低,它只是百分之几甚至更少比例的人需要,所以这种服务一定要收费……

这就是互联网的游戏规则,它决定了要想建立一个有效的商业模式,就一定要有海量的用户基数……

', '一一', 23, 0, 1, 0, 1663320938, 1663322854, 0); +INSERT INTO `x_article` VALUES (3, 2, '金山电池公布“沪广深市民绿色生活方式”调查结果', '', '60%以上受访者认为高质量的10分钟足以完成“自我充电”', '/api/static/article03.png', '

深圳,2021年10月22日)生活在一线城市的沪广深市民一向以效率见称,工作繁忙和快节奏的生活容易缺乏充足的休息。近日,一项针对沪广深市民绿色生活方式而展开的网络问卷调查引起了大家的注意。问卷的问题设定集中于市民对休息时间的看法,以及从对循环充电电池的使用方面了解其对绿色生活方式的态度。该调查采用随机抽样的模式,并对最终收集的1,500份有效问卷进行专业分析后发现,超过60%的受访者表示,在每天的工作时段能拥有10分钟高质量的休息时间,就可以高效“自我充电”。该调查结果反映出,在快节奏时代下,人们需要高质量的休息时间,也要学会利用高效率的休息方式和工具来应对快节奏的生活,以时刻保持“满电”状态。

  60%以上受访者认为高质量的10分钟足以完成“自我充电”

  这次调查超过1,500人,主要聚焦18至85岁的沪广深市民,了解他们对于休息时间的观念及使用充电电池的习惯,结果发现:

  · 90%以上有工作受访者每天工作时间在7小时以上,平均工作时间为8小时,其中43%以上的受访者工作时间超过9小时

  · 70%受访者认为在工作期间拥有10分钟“自我充电”时间不是一件困难的事情

  · 60%受访者认为在工作期间有10分钟休息时间足以为自己快速充电

  临床心理学家黄咏诗女士在发布会上分享为自己快速充电的实用技巧,她表示:“事实上,只要选择正确的休息方法,10分钟也足以为自己充电。以喝咖啡为例,我们可以使用心灵休息法 ── 静观呼吸,慢慢感受咖啡的温度和气味,如果能配合着聆听流水或海洋的声音,能够有效放松大脑及心灵。”

  这次调查结果反映出沪广深市民的希望在繁忙的工作中适时停下来,抽出10分钟喝杯咖啡、聆听音乐或小睡片刻,为自己充电。金山电池全新推出的“绿再十分充”超快速充电器仅需10分钟就能充好电,喝一杯咖啡的时间既能完成“自我充电”,也满足设备使用的用电需求,为提升工作效率和放松身心注入新能量。

  金山电池推出10分钟超快电池充电器*绿再十分充,以创新科技为市场带来革新体验

  该问卷同时从沪广深市民对循环充电电池的使用方面进行了调查,以了解其对绿色生活方式的态度:

  · 87%受访者目前没有使用充电电池,其中61%表示会考虑使用充电电池

  · 58%受访者过往曾使用过充电电池,却只有20%左右市民仍在使用

  · 60%左右受访者认为充电电池尚未被广泛使用,主要障碍来自于充电时间过长、缺乏相关教育

  · 90%以上受访者认为充电电池充满电需要1小时或更长的时间

  金山电池一直致力于为大众提供安全可靠的充电电池,并与消费者的需求和生活方式一起演变及进步。今天,金山电池宣布推出10分钟超快电池充电器*绿再十分充,只需10分钟*即可将4粒绿再十分充充电电池充好电,充电速度比其他品牌提升3倍**。充电器的LED灯可以显示每粒电池的充电状态和模式,并提示用户是否错误插入已损坏电池或一次性电池。尽管其体型小巧,却具备多项创新科技 ,如拥有独特的充电算法以优化充电电流,并能根据各个电池类型、状况和温度用最短的时间为充电电池充好电;绿再十分充内置横流扇,有效防止电池温度过热和提供低噪音的充电环境等。

', '中网资讯科技', 3, 0, 1, 0, 1663322665, 1663322665, 0); + +-- ---------------------------- +-- Table structure for x_article_category +-- ---------------------------- +DROP TABLE IF EXISTS `x_article_category`; +CREATE TABLE `x_article_category` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '名称', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 50 COMMENT '排序', + `is_show` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否显示: 0=否, 1=是', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章分类表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_article_category +-- ---------------------------- +INSERT INTO `x_article_category` VALUES (1, '文章资讯', 0, 1, 0, 1663317280, 1663317282, 0); +INSERT INTO `x_article_category` VALUES (2, '社会热点', 0, 1, 0, 1663321464, 1663321494, 0); + +-- ---------------------------- +-- Table structure for x_article_collect +-- ---------------------------- +DROP TABLE IF EXISTS `x_article_collect`; +CREATE TABLE `x_article_collect` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID', + `article_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '文章ID', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章收藏表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_article_collect +-- ---------------------------- +INSERT INTO `x_article_collect` VALUES (1, 0, 0, 1, 0, 0, 0); +INSERT INTO `x_article_collect` VALUES (5, 1, 1, 1, 0, 0, 0); + +-- ---------------------------- +-- Table structure for x_decorate_page +-- ---------------------------- +DROP TABLE IF EXISTS `x_decorate_page`; +CREATE TABLE `x_decorate_page` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `page_type` tinyint(2) UNSIGNED NOT NULL DEFAULT 10 COMMENT '页面类型', + `page_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '页面名称', + `page_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '页面数据', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '页面装修表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_decorate_page +-- ---------------------------- +INSERT INTO `x_decorate_page` VALUES (1, 1, '商城首页', '[{\"title\":\"搜索\",\"name\":\"search\",\"disabled\":1,\"content\":{},\"styles\":{}},{\"title\":\"首页轮播图\",\"name\":\"banner\",\"content\":{\"enabled\":1,\"data\":[{\"image\":\"/api/static/banner01.png\",\"name\":\"\",\"link\":{\"path\":\"/pages/index/index\",\"name\":\"商城首页\",\"type\":\"shop\"}},{\"image\":\"/api/static/banner02.png\",\"name\":\"\",\"link\":{}}]},\"styles\":{}},{\"title\":\"导航菜单\",\"name\":\"nav\",\"content\":{\"enabled\":1,\"data\":[{\"image\":\"/api/static/nav01.png\",\"name\":\"资讯中心\",\"link\":{\"path\":\"/pages/news/news\",\"name\":\"文章资讯\",\"type\":\"shop\"}},{\"image\":\"/api/static/nav02.png\",\"name\":\"我的收藏\",\"link\":{\"path\":\"/pages/collection/collection\",\"name\":\"我的收藏\",\"type\":\"shop\"}},{\"image\":\"/api/static/nav03.png\",\"name\":\"个人设置\",\"link\":{\"path\":\"/pages/user_set/user_set\",\"name\":\"个人设置\",\"type\":\"shop\"}},{\"image\":\"/api/static/nav04.png\",\"name\":\"联系客服\",\"link\":{\"path\":\"/pages/customer_service/customer_service\",\"name\":\"联系客服\",\"type\":\"shop\"}},{\"image\":\"/api/static/nav05.png\",\"name\":\"关于我们\",\"link\":{\"path\":\"/pages/as_us/as_us\",\"name\":\"关于我们\",\"type\":\"shop\"}}]},\"styles\":{}},{\"id\":\"l84almsk2uhyf\",\"title\":\"资讯\",\"name\":\"news\",\"disabled\":1,\"content\":{},\"styles\":{}}]', 1661757188, 1663321380); +INSERT INTO `x_decorate_page` VALUES (2, 2, '个人中心', '[{\"title\":\"用户信息\",\"name\":\"user-info\",\"disabled\":1,\"content\":{},\"styles\":{}},{\"title\":\"我的服务\",\"name\":\"my-service\",\"content\":{\"style\":2,\"title\":\"服务中心\",\"data\":[{\"image\":\"/api/static/user_collect.png\",\"name\":\"我的收藏\",\"link\":{\"path\":\"/pages/collection/collection\",\"name\":\"我的收藏\",\"type\":\"shop\"}},{\"image\":\"/api/static/user_setting.png\",\"name\":\"个人设置\",\"link\":{\"path\":\"/pages/user_set/user_set\",\"name\":\"个人设置\",\"type\":\"shop\"}},{\"image\":\"/api/static/user_kefu.png\",\"name\":\"联系客服\",\"link\":{\"path\":\"/pages/customer_service/customer_service\",\"name\":\"联系客服\",\"type\":\"shop\"}}]},\"styles\":{}},{\"title\":\"个人中心广告图\",\"name\":\"user-banner\",\"content\":{\"enabled\":1,\"data\":[{\"image\":\"/api/static/ad01.jpg\",\"name\":\"\",\"link\":{}}]},\"styles\":{}}]', 1661757188, 1663320728); +INSERT INTO `x_decorate_page` VALUES (3, 3, '客服设置', '[{\"title\":\"客服设置\",\"name\":\"customer-service\",\"content\":{\"title\":\"添加客服二维码\",\"time\":\"早上 9:00 - 22:00\",\"mobile\":\"13800138000\",\"qrcode\":\"\"},\"styles\":{}}]', 1661757188, 1662689155); + +-- ---------------------------- +-- Table structure for x_decorate_tabbar +-- ---------------------------- +DROP TABLE IF EXISTS `x_decorate_tabbar`; +CREATE TABLE `x_decorate_tabbar` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '导航名称', + `selected` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '未选图标', + `unselected` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '已选图标', + `link` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '链接地址', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '底部装修表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_decorate_tabbar +-- ---------------------------- +INSERT INTO `x_decorate_tabbar` VALUES (13, '首页', '/api/static/tabbar_home_sel.png', '/api/static/tabbar_home.png', '{\"path\":\"/pages/index/index\",\"name\":\"商城首页\",\"type\":\"shop\"}', 1662688157, 1662688157); +INSERT INTO `x_decorate_tabbar` VALUES (14, '资讯', '/api/static/tabbar_text_sel.png', '/api/static/tabbar_text.png', '{\"path\":\"/pages/news/news\",\"name\":\"文章资讯\",\"type\":\"shop\"}', 1662688157, 1662688157); +INSERT INTO `x_decorate_tabbar` VALUES (15, '我的', '/api/static/tabbar_me_sel.png', '/api/static/tabbar_me.png', '{\"path\":\"/pages/user/user\",\"name\":\"个人中心\",\"type\":\"shop\"}', 1662688157, 1662688157); + +-- ---------------------------- +-- Table structure for x_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `x_dict_data`; +CREATE TABLE `x_dict_data` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `type_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '类型', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '键名', + `value` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数值', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '备注', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序', + `status` tinyint(1) NOT NULL COMMENT '状态: 0=停用, 1=正常', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_dict_data +-- ---------------------------- + +-- ---------------------------- +-- Table structure for x_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `x_dict_type`; +CREATE TABLE `x_dict_type` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '字典类型', + `dict_remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '字典备注', + `dict_status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '字典状态: 0=停用, 1=正常', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_dict_type +-- ---------------------------- +INSERT INTO `x_dict_type` VALUES (1, 'a', 'a', 'a', 1, 0, 1699343341, 1699343341, 0); + +-- ---------------------------- +-- Table structure for x_gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `x_gen_table`; +CREATE TABLE `x_gen_table` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '表名称', + `table_comment` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '表描述', + `sub_table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '关联表名称', + `sub_table_fk` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '关联表外键', + `author_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '作者的名称', + `entity_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '实体的名称', + `module_name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '生成模块名', + `function_name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '生成功能名', + `tree_primary` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '树主键字段', + `tree_parent` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '树父级字段', + `tree_name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '树显示字段', + `gen_tpl` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'crud' COMMENT '生成模板方式: [crud=单表, tree=树表]', + `gen_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '生成代码方式: [0=zip压缩包, 1=自定义路径]', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '/' COMMENT '生成代码路径: [不填默认项目路径]', + `remarks` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注信息', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_gen_table +-- ---------------------------- +INSERT INTO `x_gen_table` VALUES (10, 'x_article_collect', '文章收藏表', '', '', '', 'articleCollect', 'article_collect', '文章收藏', '', '', '', 'crud', 0, '/', '', 1700321456, 1700449469); + +-- ---------------------------- +-- Table structure for x_gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `x_gen_table_column`; +CREATE TABLE `x_gen_table_column` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '列主键', + `table_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '表外键', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '列名称', + `column_comment` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '列描述', + `column_length` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '列长度', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '列类型 ', + `go_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT 'JAVA类型', + `go_field` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'JAVA字段', + `is_pk` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否主键: [1=是, 0=否]', + `is_increment` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否自增: [1=是, 0=否]', + `is_required` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否必填: [1=是, 0=否]', + `is_insert` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否插入字段: [1=是, 0=否]', + `is_edit` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否编辑字段: [1=是, 0=否]', + `is_list` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否列表字段: [1=是, 0=否]', + `is_query` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否查询字段: [1=是, 0=否]', + `query_type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'EQ' COMMENT '查询方式: [等于、不等于、大于、小于、范围]', + `html_type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '显示类型: [文本框、文本域、下拉框、复选框、单选框、日期控件]', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '字典类型', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序编号', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 91 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成字段表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_gen_table_column +-- ---------------------------- +INSERT INTO `x_gen_table_column` VALUES (84, 10, 'id', '主键', '10', 'int', 'int', 'id', 1, 1, 0, 0, 0, 0, 0, '=', 'input', '', 0, 0, 1700449469); +INSERT INTO `x_gen_table_column` VALUES (85, 10, 'user_id', '用户ID', '10', 'int', 'int', 'user_id', 0, 0, 1, 1, 1, 1, 1, '=', 'select', 'a', 0, 0, 1700449469); +INSERT INTO `x_gen_table_column` VALUES (86, 10, 'article_id', '文章ID', '10', 'int', 'int', 'article_id', 0, 0, 1, 1, 1, 1, 1, '=', 'input', '', 0, 0, 1700449469); +INSERT INTO `x_gen_table_column` VALUES (87, 10, 'is_delete', '是否删除', '1', 'tinyint', 'int', 'is_delete', 0, 0, 0, 0, 0, 0, 0, '=', 'input', '', 0, 0, 1700449469); +INSERT INTO `x_gen_table_column` VALUES (88, 10, 'create_time', '创建时间', '10', 'int', 'core.TsTime', 'create_time', 0, 0, 0, 0, 0, 1, 0, '=', 'datetime', '', 0, 0, 1700449469); +INSERT INTO `x_gen_table_column` VALUES (89, 10, 'update_time', '更新时间', '10', 'int', 'core.TsTime', 'update_time', 0, 0, 0, 0, 0, 1, 0, '=', 'datetime', '', 0, 0, 1700449469); +INSERT INTO `x_gen_table_column` VALUES (90, 10, 'delete_time', '是否删除', '10', 'int', 'core.TsTime', 'delete_time', 0, 0, 0, 0, 0, 0, 0, '=', 'datetime', '', 0, 0, 1700449469); + +-- ---------------------------- +-- Table structure for x_hot_search +-- ---------------------------- +DROP TABLE IF EXISTS `x_hot_search`; +CREATE TABLE `x_hot_search` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '关键词', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '热门搜索配置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_hot_search +-- ---------------------------- + +-- ---------------------------- +-- Table structure for x_notice_setting +-- ---------------------------- +DROP TABLE IF EXISTS `x_notice_setting`; +CREATE TABLE `x_notice_setting` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `scene` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '场景编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '场景名称', + `remarks` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '场景描述', + `recipient` tinyint(1) NOT NULL DEFAULT 1 COMMENT '接收人员: [1=用户, 2=平台]', + `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '通知类型: [1=业务, 2=验证]', + `system_notice` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '系统的通知设置', + `sms_notice` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '短信的通知设置', + `oa_notice` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '公众号通知设置', + `mnp_notice` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '小程序通知设置', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '消息通知设置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_notice_setting +-- ---------------------------- +INSERT INTO `x_notice_setting` VALUES (1, 101, '登录验证码', '用户手机号码登录时发送', 1, 2, '{}', '{\"type\":\"sms\",\"templateId\":\"SMS_222458159\",\"content\":\"您正在登录,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"tips\":[\"可选变量 验证码:code\",\"示例:您正在登录,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"生效条件:1、管理后台完成短信设置。2、第三方短信平台申请模板。\"],\"status\":\"1\"}', '{}', '{}', 0, 1648696695, 1648696695, 0); +INSERT INTO `x_notice_setting` VALUES (2, 102, '绑定手机验证码', '用户绑定手机号码时发送', 1, 2, '{}', '{\"type\":\"sms\",\"templateId\":\"SMS_175615069\",\"content\":\"您正在绑定手机号,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"tips\":[\"可选变量 验证码:code\",\"示例:您正在绑定手机号,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"生效条件:1、管理后台完成短信设置。2、第三方短信平台申请模板。\"],\"status\":\"1\"}', '{}', '{}', 0, 1648696695, 1648696695, 0); +INSERT INTO `x_notice_setting` VALUES (3, 103, '变更手机验证码', '用户变更手机号码时发送', 1, 2, '{}', '{\"type\":\"sms\",\"templateId\":\"SMS_207952628\",\"content\":\"您正在变更手机号,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"tips\":[\"可选变量 验证码:code\",\"示例:您正在变更手机号,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"生效条件:1、管理后台完成短信设置。2、第三方短信平台申请模板。\"],\"status\":\"1\"}', '{}', '{}', 0, 1648696695, 1648696695, 0); +INSERT INTO `x_notice_setting` VALUES (4, 104, '找回登录密码验证码', '用户找回登录密码号码时发送', 1, 2, '{}', '{\"type\":\"sms\",\"templateId\":\"SMS_175615069\",\"content\":\"您正在找回登录密码,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"tips\":[\"可选变量 验证码:code\",\"示例:您正在找回登录密码,验证码${code},切勿将验证码泄露于他人,本条验证码有效期5分钟。\",\"条验证码有效期5分钟。\"],\"status\":\"1\"}', '{}', '{}', 0, 1648696695, 1648696695, 0); + +-- ---------------------------- +-- Table structure for x_official_reply +-- ---------------------------- +DROP TABLE IF EXISTS `x_official_reply`; +CREATE TABLE `x_official_reply` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '规则名', + `keyword` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '关键词', + `reply_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '回复类型: [1=关注回复 2=关键字回复, 3=默认回复]', + `matching_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '匹配方式: [1=全匹配, 2=模糊匹配]', + `content_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '内容类型: [1=文本]', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '启动状态: [1=启动, 0=关闭]', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '回复内容', + `sort` int(11) UNSIGNED NOT NULL DEFAULT 50 COMMENT '排序编号', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除', + `create_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公众号的回复表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_official_reply +-- ---------------------------- + +-- ---------------------------- +-- Table structure for x_product +-- ---------------------------- +DROP TABLE IF EXISTS `x_product`; +CREATE TABLE `x_product` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '产品名称', + `category` int(11) UNSIGNED NOT NULL COMMENT '产品分类', + `pics` mediumtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + PRIMARY KEY (`id`) USING BTREE, + FULLTEXT INDEX `name`(`name`) +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '产品表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of x_product +-- ---------------------------- +INSERT INTO `x_product` VALUES (1, 'aaa', 2, '11'); +INSERT INTO `x_product` VALUES (2, 'a', 0, '3'); + +-- ---------------------------- +-- Table structure for x_system_auth_admin +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_auth_admin`; +CREATE TABLE `x_system_auth_admin` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `dept_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '部门ID', + `post_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '岗位ID', + `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户账号', + `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称', + `password` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码', + `avatar` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户头像', + `role` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色主键', + `salt` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '加密盐巴', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序编号', + `is_multipoint` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '多端登录: 0=否, 1=是', + `is_disable` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否禁用: 0=否, 1=是', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `last_login_ip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '最后登录IP', + `last_login_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后登录', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理成员表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_auth_admin +-- ---------------------------- +INSERT INTO `x_system_auth_admin` VALUES (1, 1, 0, 'admin', 'admin', '7fac2474740becfaf1ecbdd6cc8fb076', '/api/static/backend_avatar.png', '0', '5Xar0', 0, 1, 0, 0, '127.0.0.1', 1700462131, 1642321599, 1700462131, 0); + +-- ---------------------------- +-- Table structure for x_system_auth_dept +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_auth_dept`; +CREATE TABLE `x_system_auth_dept` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `pid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '上级主键', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '部门名称', + `duty` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '负责人名', + `mobile` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '联系电话', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序编号', + `is_stop` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否禁用: 0=否, 1=是', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统部门管理表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_auth_dept +-- ---------------------------- +INSERT INTO `x_system_auth_dept` VALUES (1, 0, '默认部门', '康明', '18327647788', 10, 0, 0, 1649841995, 1660190949, 0); + +-- ---------------------------- +-- Table structure for x_system_auth_menu +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_auth_menu`; +CREATE TABLE `x_system_auth_menu` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `pid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '上级菜单', + `menu_type` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '权限类型: M=目录,C=菜单,A=按钮', + `menu_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '菜单名称', + `menu_icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '菜单图标', + `menu_sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单排序', + `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '权限标识', + `paths` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '前端组件', + `selected` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '选中路径', + `params` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '路由参数', + `is_cache` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否缓存: 0=否, 1=是', + `is_show` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否显示: 0=否, 1=是', + `is_disable` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否禁用: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 778 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统菜单管理表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_auth_menu +-- ---------------------------- +INSERT INTO `x_system_auth_menu` VALUES (1, 0, 'C', '工作台', 'el-icon-Monitor', 50, 'common:index:console', 'workbench', 'workbench/index', '', '', 1, 1, 0, 1650341765, 1668672757); +INSERT INTO `x_system_auth_menu` VALUES (100, 0, 'M', '权限管理', 'el-icon-Lock', 44, '', 'permission', '', '', '', 0, 1, 0, 1650341765, 1662626201); +INSERT INTO `x_system_auth_menu` VALUES (101, 100, 'C', '管理员', 'local-icon-wode', 0, 'system:admin:list', 'admin', 'permission/admin/index', '', '', 1, 1, 0, 1650341765, 1663301404); +INSERT INTO `x_system_auth_menu` VALUES (102, 101, 'A', '管理员详情', '', 0, 'system:admin:detail', '', '', '', '', 0, 1, 0, 1650341765, 1660201785); +INSERT INTO `x_system_auth_menu` VALUES (103, 101, 'A', '管理员新增', '', 0, 'system:admin:add', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (104, 101, 'A', '管理员编辑', '', 0, 'system:admin:edit', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (105, 101, 'A', '管理员删除', '', 0, 'system:admin:del', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (106, 101, 'A', '管理员状态', '', 0, 'system:admin:disable', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (110, 100, 'C', '角色管理', 'el-icon-Female', 0, 'system:role:list', 'role', 'permission/role/index', '', '', 1, 1, 0, 1650341765, 1663301451); +INSERT INTO `x_system_auth_menu` VALUES (111, 110, 'A', '角色详情', '', 0, 'system:role:detail', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (112, 110, 'A', '角色新增', '', 0, 'system:role:add', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (113, 110, 'A', '角色编辑', '', 0, 'system:role:edit', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (114, 110, 'A', '角色删除', '', 0, 'system:role:del', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (120, 100, 'C', '菜单管理', 'el-icon-Operation', 0, 'system:menu:list', 'menu', 'permission/menu/index', '', '', 1, 1, 0, 1650341765, 1663301388); +INSERT INTO `x_system_auth_menu` VALUES (121, 120, 'A', '菜单详情', '', 0, 'system:menu:detail', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (122, 120, 'A', '菜单新增', '', 0, 'system:menu:add', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (123, 120, 'A', '菜单编辑', '', 0, 'system:menu:edit', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (124, 120, 'A', '菜单删除', '', 0, 'system:menu:del', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (130, 0, 'M', '组织管理', 'el-icon-OfficeBuilding', 45, '', 'organization', '', '', '', 0, 1, 0, 1650341765, 1664416715); +INSERT INTO `x_system_auth_menu` VALUES (131, 130, 'C', '部门管理', 'el-icon-Coordinate', 0, 'system:dept:list', 'department', 'organization/department/index', '', '', 1, 1, 0, 1650341765, 1660201994); +INSERT INTO `x_system_auth_menu` VALUES (132, 131, 'A', '部门详情', '', 0, 'system:dept:detail', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (133, 131, 'A', '部门新增', '', 0, 'system:dept:add', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (134, 131, 'A', '部门编辑', '', 0, 'system:dept:edit', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (135, 131, 'A', '部门删除', '', 0, 'system:dept:del', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (140, 130, 'C', '岗位管理', 'el-icon-PriceTag', 0, 'system:post:list', 'post', 'organization/post/index', '', '', 1, 1, 0, 1650341765, 1660202057); +INSERT INTO `x_system_auth_menu` VALUES (141, 140, 'A', '岗位详情', '', 0, 'system:post:detail', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (142, 140, 'A', '岗位新增', '', 0, 'system:post:add', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (143, 140, 'A', '岗位编辑', '', 0, 'system:post:edit', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (144, 140, 'A', '岗位删除', '', 0, 'system:post:del', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (200, 0, 'M', '其它管理', '', 0, '', '', '', '', '', 0, 0, 0, 1650341765, 1660636870); +INSERT INTO `x_system_auth_menu` VALUES (201, 200, 'M', '图库管理', '', 0, '', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (202, 201, 'A', '文件列表', '', 0, 'albums:albumList', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (203, 201, 'A', '文件命名', '', 0, 'albums:albumRename', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (204, 201, 'A', '文件移动', '', 0, 'albums:albumMove', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (205, 201, 'A', '文件删除', '', 0, 'albums:albumDel', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (206, 201, 'A', '分类列表', '', 0, 'albums:cateList', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (207, 201, 'A', '分类新增', '', 0, 'albums:cateAdd', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (208, 201, 'A', '分类命名', '', 0, 'albums:cateRename', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (209, 201, 'A', '分类删除', '', 0, 'albums:cateDel', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (215, 200, 'M', '上传管理', '', 0, '', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (216, 215, 'A', '上传图片', '', 0, 'upload:image', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (217, 215, 'A', '上传视频', '', 0, 'upload:video', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (500, 0, 'M', '系统设置', 'el-icon-Setting', 0, '', 'setting', '', '', '', 0, 1, 0, 1650341765, 1662626322); +INSERT INTO `x_system_auth_menu` VALUES (501, 500, 'M', '网站设置', 'el-icon-Basketball', 10, '', 'website', '', '', '', 0, 1, 0, 1650341765, 1663233572); +INSERT INTO `x_system_auth_menu` VALUES (502, 501, 'C', '网站信息', '', 0, 'setting:website:detail', 'information', 'setting/website/information', '', '', 0, 1, 0, 1650341765, 1660202218); +INSERT INTO `x_system_auth_menu` VALUES (503, 502, 'A', '保存配置', '', 0, 'setting:website:save', '', '', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (505, 501, 'C', '网站备案', '', 0, 'setting:copyright:detail', 'filing', 'setting/website/filing', '', '', 0, 1, 0, 1650341765, 1660202294); +INSERT INTO `x_system_auth_menu` VALUES (506, 505, 'A', '备案保存', '', 0, 'setting:copyright:save', '', 'setting/website/protocol', '', '', 0, 0, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (510, 501, 'C', '政策协议', '', 0, 'setting:protocol:detail', 'protocol', 'setting/website/protocol', '', '', 0, 1, 0, 1660027606, 1660202312); +INSERT INTO `x_system_auth_menu` VALUES (511, 510, 'A', '协议保存', '', 0, 'setting:protocol:save', '', '', '', '', 0, 0, 0, 1660027606, 1663670865); +INSERT INTO `x_system_auth_menu` VALUES (515, 600, 'C', '字典管理', 'el-icon-Box', 0, 'setting:dict:type:list', 'dict', 'setting/dict/type/index', '', '', 0, 1, 0, 1660035436, 1663226087); +INSERT INTO `x_system_auth_menu` VALUES (516, 515, 'A', '字典类型新增', '', 0, 'setting:dict:type:add', '', '', '', '', 0, 1, 0, 1660202761, 1660202761); +INSERT INTO `x_system_auth_menu` VALUES (517, 515, 'A', '字典类型编辑', '', 0, 'setting:dict:type:edit', '', '', '', '', 0, 1, 0, 1660202842, 1660202842); +INSERT INTO `x_system_auth_menu` VALUES (518, 515, 'A', '字典类型删除', '', 0, 'setting:dict:type:del', '', '', '', '', 0, 1, 0, 1660202903, 1660202903); +INSERT INTO `x_system_auth_menu` VALUES (519, 600, 'C', '字典数据管理', '', 0, 'setting:dict:data:list', 'dict/data', 'setting/dict/data/index', '/dev_tools/dict', '', 0, 0, 0, 1660202948, 1663309252); +INSERT INTO `x_system_auth_menu` VALUES (520, 515, 'A', '字典数据新增', '', 0, 'setting:dict:data:add', '', '', '', '', 0, 1, 0, 1660203117, 1660203117); +INSERT INTO `x_system_auth_menu` VALUES (521, 515, 'A', '字典数据编辑', '', 0, 'setting:dict:data:edit', '', '', '', '', 0, 1, 0, 1660203142, 1660203142); +INSERT INTO `x_system_auth_menu` VALUES (522, 515, 'A', '字典数据删除', '', 0, 'setting:dict:data:del', '', '', '', '', 0, 1, 0, 1660203159, 1660203159); +INSERT INTO `x_system_auth_menu` VALUES (550, 500, 'M', '系统维护', 'el-icon-SetUp', 0, '', 'system', '', '', '', 0, 1, 0, 1650341765, 1660202466); +INSERT INTO `x_system_auth_menu` VALUES (551, 550, 'C', '系统环境', '', 0, 'monitor:server', 'environment', 'setting/system/environment', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (552, 550, 'C', '系统缓存', '', 0, 'monitor:cache', 'cache', 'setting/system/cache', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (553, 550, 'C', '系统日志', '', 0, 'system:log:operate', 'journal', 'setting/system/journal', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (555, 500, 'C', '存储设置', 'el-icon-FolderOpened', 6, 'setting:storage:list', 'storage', 'setting/storage/index', '', '', 0, 1, 0, 1650341765, 1663312996); +INSERT INTO `x_system_auth_menu` VALUES (556, 555, 'A', '保存配置', '', 0, 'setting:storage:edit', '', '', '', '', 0, 1, 0, 1650341765, 1650341765); +INSERT INTO `x_system_auth_menu` VALUES (600, 0, 'M', '开发工具', 'el-icon-EditPen', 0, '', 'dev_tools', '', '', '', 0, 1, 0, 1660027606, 1664335701); +INSERT INTO `x_system_auth_menu` VALUES (610, 600, 'C', '代码生成器', 'el-icon-DocumentAdd', 0, 'gen:list', 'code', 'dev_tools/code/index', '', '', 0, 1, 0, 1660028954, 1660532510); +INSERT INTO `x_system_auth_menu` VALUES (611, 610, 'A', '导入数据表', '', 0, 'gen:importTable', '', '', '', '', 0, 1, 0, 1660532389, 1660532389); +INSERT INTO `x_system_auth_menu` VALUES (612, 610, 'A', '生成代码', '', 0, 'gen:genCode', '', '', '', '', 0, 1, 0, 1660532421, 1660532421); +INSERT INTO `x_system_auth_menu` VALUES (613, 610, 'A', '下载代码', '', 0, 'gen:downloadCode', '', '', '', '', 0, 1, 0, 1660532437, 1660532437); +INSERT INTO `x_system_auth_menu` VALUES (614, 610, 'A', '预览代码', '', 0, 'gen:previewCode', '', '', '', '', 0, 1, 0, 1660532549, 1660532549); +INSERT INTO `x_system_auth_menu` VALUES (616, 610, 'A', '同步表结构', '', 0, 'gen:syncTable', '', '', '', '', 0, 1, 0, 1660532781, 1660532781); +INSERT INTO `x_system_auth_menu` VALUES (617, 610, 'A', '删除数据表', '', 0, 'gen:delTable', '', '', '', '', 0, 1, 0, 1660532800, 1660532800); +INSERT INTO `x_system_auth_menu` VALUES (618, 610, 'A', '数据表详情', '', 0, 'gen:detail', '', '', '', '', 0, 1, 0, 1660532964, 1660532977); +INSERT INTO `x_system_auth_menu` VALUES (700, 0, 'M', '素材管理', 'el-icon-Picture', 43, '', 'material', '', '', '', 0, 1, 0, 1660203293, 1663300847); +INSERT INTO `x_system_auth_menu` VALUES (701, 700, 'C', '素材中心', 'el-icon-PictureRounded', 0, '', 'index', 'material/index', '', '', 0, 1, 0, 1660203402, 1663301493); +INSERT INTO `x_system_auth_menu` VALUES (775, 600, 'C', '代码生成器编辑', 'el-icon-EditPen', 0, 'gen:editTable', 'dev_tools/code/edit', 'dev_tools/code/edit', '', '', 0, 0, 0, 1699344389, 1699344389); +INSERT INTO `x_system_auth_menu` VALUES (776, 0, 'C', '产品', 'el-icon-Aim', 0, '', 'product/index', 'product/index', '', '', 1, 1, 0, 1699719599, 1699719599); +INSERT INTO `x_system_auth_menu` VALUES (777, 0, 'C', 'article_collect', '', 0, 'article_collect:list', 'article_collect/index', 'article_collect/index', '', '', 1, 1, 0, 1700329092, 1700329092); + +-- ---------------------------- +-- Table structure for x_system_auth_perm +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_auth_perm`; +CREATE TABLE `x_system_auth_perm` ( + `id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '主键', + `role_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '角色ID', + `menu_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色菜单表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_auth_perm +-- ---------------------------- + +-- ---------------------------- +-- Table structure for x_system_auth_post +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_auth_post`; +CREATE TABLE `x_system_auth_post` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '岗位编码', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '岗位名称', + `remarks` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '岗位备注', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '岗位排序', + `is_stop` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否停用: 0=否, 1=是', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统岗位管理表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_auth_post +-- ---------------------------- +INSERT INTO `x_system_auth_post` VALUES (1, 'a', '啊', 'aa', 0, 0, 1, 1699718138, 1699718141, 0); + +-- ---------------------------- +-- Table structure for x_system_auth_role +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_auth_role`; +CREATE TABLE `x_system_auth_role` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名称', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注信息', + `sort` smallint(5) UNSIGNED NOT NULL DEFAULT 0 COMMENT '角色排序', + `is_disable` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否禁用: 0=否, 1=是', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色管理表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_auth_role +-- ---------------------------- +INSERT INTO `x_system_auth_role` VALUES (1, '审核员', '审核数据', 0, 0, 1668679451, 1699458583); + +-- ---------------------------- +-- Table structure for x_system_config +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_config`; +CREATE TABLE `x_system_config` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '类型', + `name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '键', + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '值', + `create_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 81 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统全局配置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_config +-- ---------------------------- +INSERT INTO `x_system_config` VALUES (1, 'storage', 'default', 'local', 1660620367, 1662620927); +INSERT INTO `x_system_config` VALUES (2, 'storage', 'local', '{\"name\":\"本地存储\"}', 1660620367, 1662620927); +INSERT INTO `x_system_config` VALUES (3, 'storage', 'qiniu', '{\"name\":\"七牛云存储\",\"bucket\":\"\",\"secretKey\":\"\",\"accessKey\":\"\",\"domain\":\"\"}', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (4, 'storage', 'aliyun', '{\"name\":\"阿里云存储\",\"bucket\":\"\",\"secretKey\":\"\",\"accessKey\":\"\",\"domain\":\"\"}', 1660620367, 1662620071); +INSERT INTO `x_system_config` VALUES (5, 'storage', 'qcloud', '{\"name\":\"腾讯云存储\",\"bucket\":\"\",\"secretKey\":\"\",\"accessKey\":\"\",\"domain\":\"\",\"region\":\"\"}', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (6, 'sms', 'default', 'aliyun', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (7, 'sms', 'aliyun', '{\"name\":\"阿里云短信\",\"alias\":\"aliyun\",\"sign\":\"\",\"appKey\":\"\",\"secretKey\":\"\"}', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (8, 'sms', 'tencent', '{\"name\":\"腾讯云短信\",\"alias\":\"tencent\",\"sign\":\"\",\"appId\":\"\",\"secretId\":\"\",\"secretKey\":\"\"}', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (9, 'sms', 'huawei', '{\"name\":\"华为云短信\",\"alias\":\"huawei\"}', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (10, 'website', 'name', 'Admin开源后台', 1660620367, 1699524151); +INSERT INTO `x_system_config` VALUES (11, 'website', 'logo', '/api/static/backend_logo.png', 1660620367, 1699524151); +INSERT INTO `x_system_config` VALUES (12, 'website', 'favicon', '/api/static/backend_favicon.ico', 1660620367, 1699524151); +INSERT INTO `x_system_config` VALUES (13, 'website', 'backdrop', '/api/static/backend_backdrop.png', 1660620367, 1699524151); +INSERT INTO `x_system_config` VALUES (14, 'website', 'copyright', '[{\"name\":\"LikeAdmin开源系统\",\"link\":\"http://www.beian.gov.cn\"}]', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (15, 'website', 'shopName', 'Admin开源系统', 1631255140, 1699524151); +INSERT INTO `x_system_config` VALUES (16, 'website', 'shopLogo', '/api/static/shop_logo.png', 1631255140, 1699524151); +INSERT INTO `x_system_config` VALUES (17, 'protocol', 'service', '{\"name\":\"服务协议\",\"content\":\"\\u003cp\\u003e服务协议\\u003c/p\\u003e\"}', 1660620367, 1699496132); +INSERT INTO `x_system_config` VALUES (18, 'protocol', 'privacy', '{\"name\":\"隐私协议\",\"content\":\"\\u003cp\\u003e隐私协议\\u003c/p\\u003e\"}', 1660620367, 1699496132); +INSERT INTO `x_system_config` VALUES (19, 'tabbar', 'style', '{\"defaultColor\":\"#4A5DFF\",\"selectedColor\":\"#EA5455\"}', 1660620367, 1662544900); +INSERT INTO `x_system_config` VALUES (20, 'search', 'isHotSearch', '0', 1660620367, 1662546997); +INSERT INTO `x_system_config` VALUES (30, 'h5_channel', 'status', '1', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (31, 'h5_channel', 'close', '0', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (32, 'h5_channel', 'url', '', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (40, 'mp_channel', 'name', '', 1660620367, 1662551403); +INSERT INTO `x_system_config` VALUES (41, 'mp_channel', 'primaryId', '', 1660620367, 1662551403); +INSERT INTO `x_system_config` VALUES (42, 'mp_channel', 'appId', '', 1660620367, 1662551403); +INSERT INTO `x_system_config` VALUES (43, 'mp_channel', 'appSecret', '', 1660620367, 1662551403); +INSERT INTO `x_system_config` VALUES (44, 'mp_channel', 'qrCode', '', 1660620367, 1662551403); +INSERT INTO `x_system_config` VALUES (50, 'wx_channel', 'appId', '', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (51, 'wx_channel', 'appSecret', '', 1660620367, 1660620367); +INSERT INTO `x_system_config` VALUES (55, 'oa_channel', 'name', '', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (56, 'oa_channel', 'primaryId', ' ', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (57, 'oa_channel', 'qrCode', '', 1662551337, 1662551337); +INSERT INTO `x_system_config` VALUES (58, 'oa_channel', 'appId', '', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (59, 'oa_channel', 'appSecret', '', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (60, 'oa_channel', 'url', '', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (61, 'oa_channel', 'token', '', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (62, 'oa_channel', 'encodingAesKey', '', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (63, 'oa_channel', 'encryptionType', '1', 1660620367, 1662551337); +INSERT INTO `x_system_config` VALUES (64, 'oa_channel', 'menus', '[]', 1631255140, 1663118712); +INSERT INTO `x_system_config` VALUES (70, 'login', 'loginWay', '1,2', 1660620367, 1662538771); +INSERT INTO `x_system_config` VALUES (71, 'login', 'forceBindMobile', '0', 1660620367, 1662538771); +INSERT INTO `x_system_config` VALUES (72, 'login', 'openAgreement', '1', 1660620367, 1662538771); +INSERT INTO `x_system_config` VALUES (73, 'login', 'openOtherAuth', '1', 1660620367, 1662538771); +INSERT INTO `x_system_config` VALUES (74, 'login', 'autoLoginAuth', '1,2', 1660620367, 1662538771); +INSERT INTO `x_system_config` VALUES (80, 'user', 'defaultAvatar', '/api/static/default_avatar.png', 1660620367, 1662535156); + +-- ---------------------------- +-- Table structure for x_system_log_login +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_log_login`; +CREATE TABLE `x_system_log_login` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '注解', + `admin_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '管理员ID', + `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '登录账号', + `ip` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录地址', + `os` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作系统', + `browser` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '操作状态: 1=成功, 2=失败', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统登录日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_log_login +-- ---------------------------- +INSERT INTO `x_system_log_login` VALUES (1, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699342613); +INSERT INTO `x_system_log_login` VALUES (2, 0, 'admin', '127.0.0.1', 'Windows', 'Edge', 0, 1699343960); +INSERT INTO `x_system_log_login` VALUES (3, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 0, 1699343975); +INSERT INTO `x_system_log_login` VALUES (4, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699343985); +INSERT INTO `x_system_log_login` VALUES (5, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699454100); +INSERT INTO `x_system_log_login` VALUES (6, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699454113); +INSERT INTO `x_system_log_login` VALUES (7, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699456315); +INSERT INTO `x_system_log_login` VALUES (8, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699493828); +INSERT INTO `x_system_log_login` VALUES (9, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699507466); +INSERT INTO `x_system_log_login` VALUES (10, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699547693); +INSERT INTO `x_system_log_login` VALUES (11, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699705884); +INSERT INTO `x_system_log_login` VALUES (12, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699706435); +INSERT INTO `x_system_log_login` VALUES (13, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699707397); +INSERT INTO `x_system_log_login` VALUES (14, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699715636); +INSERT INTO `x_system_log_login` VALUES (15, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1699803153); +INSERT INTO `x_system_log_login` VALUES (16, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700142837); +INSERT INTO `x_system_log_login` VALUES (17, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700150134); +INSERT INTO `x_system_log_login` VALUES (18, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700298162); +INSERT INTO `x_system_log_login` VALUES (19, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700381343); +INSERT INTO `x_system_log_login` VALUES (20, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700402870); +INSERT INTO `x_system_log_login` VALUES (21, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700449387); +INSERT INTO `x_system_log_login` VALUES (22, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700462131); +INSERT INTO `x_system_log_login` VALUES (23, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700493100); +INSERT INTO `x_system_log_login` VALUES (24, 1, 'admin', '127.0.0.1', 'Windows', 'Edge', 1, 1700493528); + +-- ---------------------------- +-- Table structure for x_system_log_operate +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_log_operate`; +CREATE TABLE `x_system_log_operate` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `admin_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作人ID', + `type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求类型: GET/POST/PUT', + `title` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作标题', + `ip` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求IP', + `url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求接口', + `method` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求方法', + `args` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数', + `error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '错误信息', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '执行状态: 1=成功, 2=失败', + `start_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '开始时间', + `end_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '结束时间', + `task_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '执行耗时', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 197 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统操作日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_log_operate +-- ---------------------------- +INSERT INTO `x_system_log_operate` VALUES (1, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/routers/system.roleHandler.list-fm', 'pageNo=1&pageSize=15', '', 1, 1699343137, 1699343138, 4, 1699343138); +INSERT INTO `x_system_log_operate` VALUES (187, 1, 'POST', '上传图片', '127.0.0.1', '/api/common/upload/image', 'likeadmin/admin/common/upload.uploadHandler.uploadImage-fm', '5c8950e543e8046320.mp3', 'Error #01: 300:不被支持的图片扩展: mp3\n', 2, 1700465126, 1700465126, 161, 1700465126); +INSERT INTO `x_system_log_operate` VALUES (188, 1, 'POST', '上传图片', '127.0.0.1', '/api/common/upload/image', 'likeadmin/admin/common/upload.uploadHandler.uploadImage-fm', 'hrxz.com-30nsh0dvpby60530.mp3', 'Error #01: 300:不被支持的图片扩展: mp3\n', 2, 1700465176, 1700465176, 1, 1700465176); +INSERT INTO `x_system_log_operate` VALUES (189, 1, 'POST', '上传图片', '127.0.0.1', '/api/common/upload/image', 'likeadmin/admin/common/upload.uploadHandler.uploadImage-fm', 'yiji.mp3', 'Error #01: 300:不被支持的图片扩展: mp3\n', 2, 1700465204, 1700465204, 10, 1700465204); +INSERT INTO `x_system_log_operate` VALUES (190, 1, 'POST', '上传视频', '127.0.0.1', '/api/common/upload/video', 'likeadmin/admin/common/upload.uploadHandler.uploadVideo-fm', '素材中心 和另外 1 个页面 - 个人 - Microsoft​ Edge 2023-11-20 15-37-39.mp4', '', 1, 1700465870, 1700465870, 85, 1700465870); +INSERT INTO `x_system_log_operate` VALUES (191, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/system/role.RoleHandler.List-fm', 'pageNo=1&pageSize=15', '', 1, 1700466101, 1700466101, 26, 1700466101); +INSERT INTO `x_system_log_operate` VALUES (192, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/system/role.RoleHandler.List-fm', 'pageNo=1&pageSize=15', '', 1, 1700466116, 1700466116, 3, 1700466116); +INSERT INTO `x_system_log_operate` VALUES (193, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/system/role.RoleHandler.List-fm', 'pageNo=1&pageSize=15', '', 1, 1700493553, 1700493553, 1, 1700493553); +INSERT INTO `x_system_log_operate` VALUES (194, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/system/role.RoleHandler.List-fm', 'pageNo=1&pageSize=15', '', 1, 1700494088, 1700494088, 2, 1700494088); +INSERT INTO `x_system_log_operate` VALUES (195, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/system/role.RoleHandler.List-fm', 'pageNo=1&pageSize=15', '', 1, 1700494182, 1700494182, 7, 1700494182); +INSERT INTO `x_system_log_operate` VALUES (196, 1, 'GET', '角色列表', '127.0.0.1', '/api/system/role/list', 'likeadmin/admin/system/role.RoleHandler.List-fm', 'pageNo=1&pageSize=15', '', 1, 1700497573, 1700497573, 2, 1700497573); + +-- ---------------------------- +-- Table structure for x_system_log_sms +-- ---------------------------- +DROP TABLE IF EXISTS `x_system_log_sms`; +CREATE TABLE `x_system_log_sms` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id', + `scene` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '场景编号', + `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号码', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '发送内容', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '发送状态:[0=发送中, 1=发送成功, 2=发送失败]', + `results` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '短信结果', + `send_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '发送时间', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统短信日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_system_log_sms +-- ---------------------------- + +-- ---------------------------- +-- Table structure for x_user +-- ---------------------------- +DROP TABLE IF EXISTS `x_user`; +CREATE TABLE `x_user` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `sn` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '编号', + `avatar` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像', + `real_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '真实姓名', + `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称', + `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户账号', + `password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码', + `mobile` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户电话', + `salt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '加密盐巴', + `sex` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户性别: [1=男, 2=女]', + `channel` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '注册渠道: [1=微信小程序, 2=微信公众号, 3=手机H5, 4=电脑PC, 5=苹果APP, 6=安卓APP]', + `is_disable` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否禁用: [0=否, 1=是]', + `is_delete` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除: [0=否, 1=是]', + `last_login_ip` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '最后登录IP', + `last_login_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后登录时间', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + `delete_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_user +-- ---------------------------- + +-- ---------------------------- +-- Table structure for x_user_auth +-- ---------------------------- +DROP TABLE IF EXISTS `x_user_auth`; +CREATE TABLE `x_user_auth` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID', + `openid` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Openid', + `unionid` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unionid', + `client` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '客户端类型: [1=微信小程序, 2=微信公众号, 3=手机H5, 4=电脑PC, 5=苹果APP, 6=安卓APP]', + `create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', + `update_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `openid`(`openid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户授权表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of x_user_auth +-- ---------------------------- + +SET FOREIGN_KEY_CHECKS = 1;