diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3c3960b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org + +root = true + +[*] # 表示所有文件适用 +charset = utf-8 # 设置文件字符集为 utf-8 +indent_style = space # 缩进风格(tab | space) +indent_size = 2 # 缩进大小 +end_of_line = lf # 控制换行类型(lf | cr | crlf) +trim_trailing_whitespace = true # 去除行首的任意空白字符 +insert_final_newline = true # 始终在文件末尾插入一个新行 + +[*.md] # 表示仅 md 文件适用以下规则 +max_line_length = off +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..fe7dce2 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +# 应用标题 +VITE_APP_TITLE=爱萝湾皮肤检测仪管理平台 + +# 生产环境主题颜色 - 蓝色 +VITE_APP_THEME = '#409eff' diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..7e6e7af --- /dev/null +++ b/.env.development @@ -0,0 +1,13 @@ +## 开发环境 + +# 变量必须以 VITE_ 为前缀才能暴露给外部读取 +NODE_ENV=development + +VITE_APP_PORT=3078 +VITE_APP_TITLE=爱萝湾皮肤检测仪管理平台 + +VITE_APP_BASE_API=https://skin-test-api.ailuowan.com +PROJECTID=1 + +# 测试环境主题颜色 - 橙色(用于区分测试环境) +VITE_APP_THEME = '#f5a623' diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..40f450b --- /dev/null +++ b/.env.production @@ -0,0 +1,9 @@ +## 生产环境 +NODE_ENV='production' + +VITE_APP_TITLE = '管理平台' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = '/prod-api' +VITE_APP_BASE_API = 'https://api.anjiesoft.com/base/' +# VITE_APP_BASE_API = 'http://role.api.local' +PROJECTID = 1 diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..9af65f3 --- /dev/null +++ b/.env.staging @@ -0,0 +1,6 @@ +## 模拟环境 +NODE_ENV='staging' + +VITE_APP_TITLE = '管理平台' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = '/prod--api' diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..998a95a --- /dev/null +++ b/.env.test @@ -0,0 +1,8 @@ +## 开发环境 + +# 变量必须以 VITE_ 为前缀才能暴露给外部读取 +NODE_ENV='test' + +VITE_APP_TITLE = '管理平台' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = 'http://api.school.local/' diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..fcf78b7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,17 @@ +*.sh +node_modules +*.md +*.woff +*.ttf +.vscode +.idea +dist +/public +/docs +.husky +.local +/bin +.eslintrc.js +prettier.config.js +src/assets +src/ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..92e186c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,30 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true + }, + globals: { + defineProps: 'readonly', + defineEmits: 'readonly', + defineExpose: 'readonly' + }, + parser: 'vue-eslint-parser', + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-essential', + 'plugin:@typescript-eslint/recommended' + ], + parserOptions: { + ecmaVersion: 'latest', + parser: '@typescript-eslint/parser', + sourceType: 'module' + }, + plugins: ['vue', '@typescript-eslint'], + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-empty-function': 'off', // 关闭空方法检查 + '@typescript-eslint/no-explicit-any': 'off', // 关闭any类型的警告 + 'vue/no-v-model-argument': 'off' + } +}; diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..abd739c --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build-deploy: + runs-on: runner + + defaults: + run: + shell: sh + + steps: + - name: Checkout + uses: https://git.ailuowan.com/deploy/checkout@v4 + + - name: Install + run: npm install --ignore-scripts + + - name: Build + run: npm run build:development + + - name: Deploy + run: | + mkdir -p /data/www/beautifier-web + rm -rf /data/www/beautifier-web/* + cp -r dist/. /data/www/beautifier-web/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cadf3a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +node_modules +dist +.DS_Store +*.local + +# Editor directories and files +.idea +.vscode +.husky +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d251d2e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +/dist/* +.local +.output.js +/node_modules/** + +**/*.svg +**/*.sh + +/public/* \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..7a42426 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,36 @@ +/** + * 代码格式化配置 + */ +module.exports = { + // 指定每个缩进级别的空格数 + tabWidth: 2, + // 使用制表符而不是空格缩进行 + useTabs: false, + // 在语句末尾打印分号 + semi: true, + // 使用单引号而不是双引号 + singleQuote: true, + // 更改引用对象属性的时间 可选值"" + quoteProps: 'as-needed', + // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"",默认none + trailingComma: 'none', + // 在对象文字中的括号之间打印空格 + bracketSpacing: true, + // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x + arrowParens: 'avoid', + // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 + rangeStart: 0, + rangeEnd: Infinity, + // 指定要使用的解析器,不需要写文件开头的 @prettier + requirePragma: false, + // 不需要自动在文件开头插入 @prettier + insertPragma: false, + // 换行设置 always\never\preserve + proseWrap: 'never', + // 指定HTML文件的全局空格敏感度 css\strict\ignore + htmlWhitespaceSensitivity: 'css', + // Vue文件脚本和样式标签缩进 + vueIndentScriptAndStyle: false, + // 换行符使用 lf 结尾是 可选值"" + endOfLine: 'lf' +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..6511dc1 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# authority-vip + +#### 官方地址 + +[安捷(Anjie)](https://anjiesoft.com) + +#### 演示地址 + +[安捷演示(Anjie)](https://demo.anjiesoft.com) +默认账号:admin +默认密码:123456 + +#### 介绍 +**Anjie 权限系统** 是一款基于项目管理制度设计的多项目权限管控平台,核心目标是通过一套统一的权限体系,实现对多个独立项目的访问控制与资源管理。系统采用前后端分离架构,具备高扩展性、安全性及易用性,可灵活适配不同规模团队的项目权限管理需求。 + + +#### 软件架构 +软件架构说明 + +| 技术 / 框架 | 版本要求 | 用途说明 | +| :----------------- | :---- | :--------------------------- | +| Vue | 3.2+ | 前端开发主框架 | +| Vue3-Element-Admin | 1.0+ | 开源中后台前端解决方案,提供基础布局、权限控制、组件库等 | +| Element Plus | 2.2+ | UI 组件库,构建美观、一致的前端界面 | +| Axios | 1.3+ | HTTP 客户端,实现前后端数据交互 | +| Vue Router | 4.1+ | 前端路由管理,控制页面跳转与权限拦截 | +| Pinia | 2.1+ | 状态管理库,管理全局与组件状态 + + +#### 前端地址 +Gitee 官方地址 [地址连接](https://gitee.com/ymxnetg/anjie-authority-base-web) +Github 官方地址 [地址连接](https://github.com/anjiesoft/anjie-authority-base-web) + +#### 后端地址 +Gitee 官方地址 [地址连接](https://gitee.com/ymxnetg/anjie-authority-base) +Github 官方地址 [地址连接](https://github.com/anjiesoft/anjie-authority-base) + + + +#### 联系方式 + +1. [联系我们1](mailto:bigapegao@163.com) +2. [联系我们2](mailto:bigape.gao@gmail.com) diff --git a/bolt-web.iml b/bolt-web.iml new file mode 100644 index 0000000..80cc739 --- /dev/null +++ b/bolt-web.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..efff054 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,26 @@ +module.exports = { + // 继承的规则 + extends: ['@commitlint/config-conventional'], + // 定义规则类型 + rules: { + // type 类型定义,表示 git 提交的 type 必须在以下类型范围内 + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新功能 feature + 'fix', // 修复 bug + 'docs', // 文档注释 + 'style', // 代码格式(不影响代码运行的变动) + 'refactor', // 重构(既不增加新功能,也不是修复bug) + 'perf', // 性能优化 + 'test', // 增加测试 + 'chore', // 构建过程或辅助工具的变动 + 'revert', // 回退 + 'build' // 打包 + ] + ], + // subject 大小写不做校验 + 'subject-case': [0] + } +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..10d92dd --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + 爱萝湾皮肤检测仪管理平台 + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..2852837 --- /dev/null +++ b/package.json @@ -0,0 +1,74 @@ +{ + "name": "anjie-base", + "version": "1.0.0", + "private": true, + "devDependencies": { + "@commitlint/cli": "^16.2.3", + "@commitlint/config-conventional": "^16.2.1", + "@types/node": "^16.11.7", + "@types/nprogress": "^0.2.0", + "@types/path-browserify": "^1.0.0", + "@types/sortablejs": "^1.15.8", + "@typescript-eslint/eslint-plugin": "^5.19.0", + "@typescript-eslint/parser": "^5.19.0", + "@vitejs/plugin-vue": "^1.9.3", + "@vue/cli-plugin-babel": "~5.0.0", + "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-service": "~5.0.0", + "eslint": "^8.14.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-vue": "^8.6.0", + "fast-glob": "^3.2.11", + "husky": "^7.0.4", + "prettier": "^2.6.2", + "sass": "^1.43.4", + "typescript": "^4.5.4", + "vite": "^2.9.7", + "vite-plugin-svg-icons": "^2.0.1", + "vue-tsc": "^0.34.7" + }, + "scripts": { + "dev": "vite serve --mode development", + "test": "vite serve --mode test", + "prod": "vite build --mode production", + "serve": "vite preview", + "build": "vite build --mode production", + "build:development": "vite build --mode development", + "build:develop": "vite build --mode develop", + "lint": "eslint src/**/*.{ts,js,vue} --fix", + "prepare": "husky install", + "prettier": "prettier --write ." + }, + "dependencies": { + "@element-plus/icons-vue": "^1.0.0", + "@vueuse/core": "^14.3.0", + "@wangeditor/editor": "^5.0.0", + "@wangeditor/editor-for-vue": "^5.1.10", + "axios": "^0.24.0", + "better-scroll": "^2.4.2", + "crypto-js": "^4.2.0", + "echarts": "^5.2.2", + "element-plus": "^2.2.5", + "nprogress": "^0.2.0", + "path-browserify": "^1.0.1", + "path-to-regexp": "^6.2.0", + "pinia": "^2.0.12", + "qiniu-js": "^3.4.1", + "screenfull": "^6.0.0", + "sortablejs": "^1.15.6", + "vue": "^3.2.25", + "vue-i18n": "^9.1.9", + "vue-router": "^4.0.10" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/ymxnetg/authority-base-web" + }, + "author": { + "name": "有来开源组织" + }, + "license": "MIT", + "readme": "ERROR: No README data found!", + "_id": "vue3-element-admin@1.0.0" +} diff --git a/public/favicon-192.png b/public/favicon-192.png new file mode 100644 index 0000000..f9feaeb Binary files /dev/null and b/public/favicon-192.png differ diff --git a/public/favicon-32.png b/public/favicon-32.png new file mode 100644 index 0000000..443043b Binary files /dev/null and b/public/favicon-32.png differ diff --git a/public/favicon-48.png b/public/favicon-48.png new file mode 100644 index 0000000..6a6a5ba Binary files /dev/null and b/public/favicon-48.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..56d1fa8 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,20 @@ +User-agent: * +Disallow: / + +User-agent: Baiduspider +Disallow: / + +User-agent: Googlebot +Disallow: / + +User-agent: Bingbot +Disallow: / + +User-agent: Sogou web spider +Disallow: / + +User-agent: 360Spider +Disallow: / + +User-agent: YisouSpider +Disallow: / diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..f2d6697 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/api/GraphDescription/index.ts b/src/api/GraphDescription/index.ts new file mode 100644 index 0000000..4add9b1 --- /dev/null +++ b/src/api/GraphDescription/index.ts @@ -0,0 +1,44 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { CONFIG_DATA } from '@/utils/code'; + +//获取图谱说明列表 +export function getGraphDescriptionList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/graph/list', + method: 'post', + data: { ...data, auth: CONFIG_DATA.graphDescriptionListId } + }); +} +//图谱说明分类列表 +export function getGraphDescriptionCategoryList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/graph/types', + method: 'post', + data: { ...data, auth: CONFIG_DATA.graphDescriptionCategoryListId } + }); +} +//添加图谱说明 +export function addGraphDescription(data: any): AxiosPromise { + return request({ + url: '/admin/v1/graph/create', + method: 'post', + data: { ...data, auth: CONFIG_DATA.graphDescriptionAddId } + }); +} +//编辑图谱说明 +export function editGraphDescription(data: any): AxiosPromise { + return request({ + url: '/admin/v1/graph/update', + method: 'post', + data: { ...data, auth: CONFIG_DATA.graphDescriptionEditId } + }); +} +//删除图谱说明 +export function deleteGraphDescription(data: any): AxiosPromise { + return request({ + url: '/admin/v1/graph/delete', + method: 'post', + data: { ...data, auth: CONFIG_DATA.graphDescriptionDeleteId } + }); +} diff --git a/src/api/area/index.ts b/src/api/area/index.ts new file mode 100644 index 0000000..98b1ec3 --- /dev/null +++ b/src/api/area/index.ts @@ -0,0 +1,9 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +export function getAreaList(data: any): AxiosPromise { + return request({ + url: '/api/area', + method: 'post', + data + }); +} \ No newline at end of file diff --git a/src/api/authority/admin.ts b/src/api/authority/admin.ts new file mode 100644 index 0000000..a00b6cf --- /dev/null +++ b/src/api/authority/admin.ts @@ -0,0 +1,92 @@ +import { + DataParam, + FormParam, + QueryParam, +} from '@/types/api/authority/admin'; + +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import {RepData} from "@/types/api/base"; +import { getAuthByKey } from '@/utils/filter'; +import { CONFIG_DATA } from '@/utils/code'; + +/** + * 获取分页数据 + * + */ +export function list(data : QueryParam): AxiosPromise { + data.auth = getAuthByKey(CONFIG_DATA.adminListId) + return request({ + url: '/admin/admin/items', + method: 'post', + data, + }); +} + +/** + * 编辑 + * + * @param data + */ +export function edit(data : FormParam) : AxiosPromise> { + data.auth = getAuthByKey(CONFIG_DATA.adminEditId) + return request({ + url: '/admin/admin/edit', + method: 'post', + data, + }); +} + +/** + * 详情 + * @param id + */ +export function detail(id : number) : AxiosPromise { + const data = {id:id, auth: getAuthByKey(CONFIG_DATA.adminDetailId)} + return request({ + url: '/admin/admin/info', + method: 'post', + data, + }); +} + +/** + * 状态 + * @param id + * @param status + */ +export function status(id : number, status : number, reason:string) : AxiosPromise { + const data = {id : id, status : status, reason:reason, auth: getAuthByKey(CONFIG_DATA.adminStatusId)} + return request({ + url: '/admin/admin/status', + method: 'post', + data, + }); +} + +export function password(id : number, password : string) : AxiosPromise { + const data = {id : id, password : password, auth: getAuthByKey(CONFIG_DATA.adminPasswordId)} + return request({ + url: '/admin/admin/password', + method: 'post', + data, + }); +} + +export function roles() : AxiosPromise { + const data = {auth: getAuthByKey(CONFIG_DATA.roleAuthsTitleId)} + return request({ + url: '/admin/role/name_items', + method: 'post', + data, + }); +} + +export function ownPassword(pwd:string): AxiosPromise { + const data = {"password":pwd} + return request({ + url: '/admin/admin/ownpwd', + method: 'post', + data, + }); +} \ No newline at end of file diff --git a/src/api/authority/index.ts b/src/api/authority/index.ts new file mode 100644 index 0000000..1770b6e --- /dev/null +++ b/src/api/authority/index.ts @@ -0,0 +1,109 @@ +import { + DataParam, + FormParam, Option, + QueryParam +} from '@/types/api/authority/index'; + +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import {RepData} from "@/types/api/base"; +import { CONFIG_DATA } from '@/utils/code'; +import { getAuthByKey } from '@/utils/filter'; + +/** + * 列表 + * + */ +export function list(data : QueryParam): AxiosPromise { + data.auth = getAuthByKey(CONFIG_DATA.authorityListId) + return request({ + url: '/admin/authority/items', + method: 'post', + data, + }); +} + +/** + * 下拉列表 + */ +export function sortList(data : QueryParam): AxiosPromise> { + data.auth = getAuthByKey(CONFIG_DATA.authorityTitlesId) + data.type = 1 + data.project = CONFIG_DATA.projectId + return request({ + url: '/admin/authority/name_items', + method: 'post', + data, + }); +} + +/** + * 编辑 + * + * @param data + */ +export function edit(id:number, data : FormParam) : AxiosPromise> { + data.id = id + data.auth = getAuthByKey(CONFIG_DATA.authorityEditId) + return request({ + url: '/admin/authority/edit', + method: 'post', + data, + }); +} + + +/** + * 详情 + * @param id + */ +export function detail(id : number) : AxiosPromise { + const data = {id : id, auth:getAuthByKey(CONFIG_DATA.authorityDetailId), project:CONFIG_DATA.projectId} + return request({ + url: '/admin/authority/info', + method: 'post', + data, + }); +} + +/** + * 状态 + * @param id + * @param status + */ +export function status(id : number, status : number, reason:string) : AxiosPromise { + const data = {id : id, status : status, reason:reason, auth:getAuthByKey(CONFIG_DATA.authorityStatusId), project:CONFIG_DATA.projectId} + return request({ + url: '/admin/authority/status', + method: 'post', + data, + }); +} + +/** + * 状态 + * @param id + * @param status + */ +export function del(id : number, reason:string) : AxiosPromise { + const data = {auth:getAuthByKey(CONFIG_DATA.roleDelId), id : id, reason:reason} + return request({ + url: '/admin/authority/del', + method: 'post', + data, + }); +} + +/** + * 状态 + * @param id + * @param status + */ +export function projectSortList() : AxiosPromise { + const data = {auth:getAuthByKey(CONFIG_DATA.publicProjectTitlesId)} + return request({ + url: '/admin/project/name_items', + method: 'post', + data, + }); +} \ No newline at end of file diff --git a/src/api/authority/role.ts b/src/api/authority/role.ts new file mode 100644 index 0000000..94c5269 --- /dev/null +++ b/src/api/authority/role.ts @@ -0,0 +1,79 @@ +import { + DataParam, + FormParam, +} from '@/types/api/authority/role'; + +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import {RepData} from "@/types/api/base"; +import { getAuthByKey } from '@/utils/filter'; +import { CONFIG_DATA } from '@/utils/code'; + +/** + * 获取分页数据 + * + */ +export function list(projectId:number|string): AxiosPromise { + const data = {auth:getAuthByKey(CONFIG_DATA.roleListId), project_id:projectId} + return request({ + url: '/admin/role/items', + method: 'post', + data, + }); +} + +/** + * 编辑 + * + * @param data + */ +export function edit(data : FormParam, projectId:number) : AxiosPromise> { + data.auth = getAuthByKey(CONFIG_DATA.roleEdidId) + data.project_id = projectId + return request({ + url: '/admin/role/edit', + method: 'post', + data, + }); +} + +/** + * 详情 + * @param id + */ +export function auths(projectId : number) : AxiosPromise { + const data = {project_id:projectId, auth:getAuthByKey(CONFIG_DATA.authorityTitlesId), type : 5} + return request({ + url: '/admin/authority/name_items', + method: 'post', + data, + }); +} + +/** + * 详情 + * @param id + */ +export function detail(id : number) : AxiosPromise { + const data = {id:id, auth:getAuthByKey(CONFIG_DATA.roleDetailId)} + return request({ + url: '/admin/role/info', + method: 'post', + data, + }); +} + +/** + * 状态 + * @param id + * @param status + */ +export function status(id : number, status : number, reason:string) : AxiosPromise { + const data = {id : id, status : status,reason:reason, auth:getAuthByKey(CONFIG_DATA.roleStatusId)} + return request({ + url: '/admin/role/status', + method: 'post', + data, + }); +} + diff --git a/src/api/company/project.ts b/src/api/company/project.ts new file mode 100644 index 0000000..5c17fe0 --- /dev/null +++ b/src/api/company/project.ts @@ -0,0 +1,64 @@ +import { + FormParam, +} from '@/types/api/company/project'; + +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import {RepData} from "@/types/api/base"; +import { getAuthByKey } from '@/utils/filter'; +import { CONFIG_DATA } from '@/utils/code'; + +/** + * 列表 + * + */ +export function list(): AxiosPromise> { + const data = {auth : getAuthByKey(CONFIG_DATA.projectListId)} + return request({ + url: '/admin/project/items', + method: 'post', + data, + }); +} + +/** + * 编辑 + * + * @param data + */ +export function edit(data : FormParam) : AxiosPromise> { + data.auth = getAuthByKey(CONFIG_DATA.projectEditId) + return request({ + url: '/admin/project/edit', + method: 'post', + data, + }); +} + +/** + * 详情 + * @param auth + * @param id + */ +export function detail(id : number) : AxiosPromise { + const data = {id:id, auth : getAuthByKey(CONFIG_DATA.projectDetailId)} + return request({ + url: '/admin/project/info', + method: 'post', + data, + }); +} + +/** + * 状态 + * @param id + * @param status + */ +export function status(id : number, status : number, reason:string) : AxiosPromise { + const data = {auth : getAuthByKey(CONFIG_DATA.projectStatusId), id : id, status : status, reason:reason} + return request({ + url: '/admin/project/status', + method: 'post', + data, + }); +} \ No newline at end of file diff --git a/src/api/log/index.ts b/src/api/log/index.ts new file mode 100644 index 0000000..0eda431 --- /dev/null +++ b/src/api/log/index.ts @@ -0,0 +1,44 @@ +import { + ActionQueryParam, + DataParam, + QueryParam, +} from '@/types/api/log/login'; + +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { getAuthByKey } from '@/utils/filter'; +import { CONFIG_DATA } from '@/utils/code'; + +/** + * 获取分页数据 + * + */ +export function loginList(data : QueryParam): AxiosPromise { + data.auth = getAuthByKey(CONFIG_DATA.logLoginId) + //@ts-ignore + if (data.status == "") { + delete data.status + } + return request({ + url: '/admin/log/login', + method: 'post', + data, + }); +} + +export function actionList(data : ActionQueryParam): AxiosPromise { + data.auth = getAuthByKey(CONFIG_DATA.logActionId) + //@ts-ignore + if (data.type == "") { + delete data.type + } else { + //@ts-ignore + data.type = parseInt(data.type) + } + + return request({ + url: '/admin/log/action', + method: 'post', + data, + }); +} \ No newline at end of file diff --git a/src/api/login/index.ts b/src/api/login/index.ts new file mode 100644 index 0000000..dc56b90 --- /dev/null +++ b/src/api/login/index.ts @@ -0,0 +1,29 @@ +import { + LoginFormData, + Login2FormData, + LoginResponseData, +} from '@/types/api/system/login'; +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; + +/** + * 登录 + * @param data + */ +export function login(data: LoginFormData): AxiosPromise { + return request({ + url: 'admin/login', + method: 'post', + data + }) +} + +/** + * 注销 + */ +export function logout(): AxiosPromise { + return request({ + url: 'admin/logout', + method: 'post' + }) +} diff --git a/src/api/privacy/index.ts b/src/api/privacy/index.ts new file mode 100644 index 0000000..fdf319a --- /dev/null +++ b/src/api/privacy/index.ts @@ -0,0 +1,45 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { CONFIG_DATA } from '@/utils/code'; + +//隐私协议列表 +export function privacyList(data): AxiosPromise { + return request({ + url: '/admin/v1/agreement/list', + method: 'post', + data: { ...data, auth: CONFIG_DATA.privacyListId } + }); +} +//协议类型列表 +export function agreementTypeList(data): AxiosPromise { + return request({ + url: '/admin/v1/agreement/types', + method: 'post', + data: { ...data, auth: CONFIG_DATA.privacyTypesId } + }); +} +//隐私协议添加 +export function addPrivacy(data): AxiosPromise { + return request({ + url: '/admin/v1/agreement/create', + method: 'post', + data: { ...data, auth: CONFIG_DATA.privacyAddId } + }) +} +//隐私协议修改 +export function updatePrivacy(data): AxiosPromise { + return request({ + url: '/admin/v1/agreement/update', + method: 'post', + data: { ...data, auth: CONFIG_DATA.privacyEditId } + }) +} + +//隐私协议删除 +export function deletePrivacy(data): AxiosPromise { + return request({ + url: '/admin/v1/agreement/delete', + method: 'post', + data: { ...data, auth: CONFIG_DATA.privacyDeleteId } + }) +} \ No newline at end of file diff --git a/src/api/public/index.ts b/src/api/public/index.ts new file mode 100644 index 0000000..f28120a --- /dev/null +++ b/src/api/public/index.ts @@ -0,0 +1,21 @@ +import { + DataParam, + FormParam, Option, + QueryParam, +} from '@/types/api/authority/index'; + +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import {RepData} from "@/types/api/base"; + +/** + * 列表 + * + */ +export function imageToken(params ?: QueryParam): AxiosPromise { + return request({ + url: '/public/image/token', + method: 'post', + params, + }); +} diff --git a/src/api/store/index.ts b/src/api/store/index.ts new file mode 100644 index 0000000..5065e91 --- /dev/null +++ b/src/api/store/index.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { CONFIG_DATA } from '@/utils/code'; + +//获取门店授权-用户列表 +export function getStoreUserList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/bind/user', + method: 'post', + data: { ...data, auth: CONFIG_DATA.sotreAuthListId } + }); +} +//获取门店账号列表 +export function getStoreAccountList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/store/list', + method: 'post', + data: { ...data, auth: CONFIG_DATA.storeListId } + }); +} + +//添加门店账号 +export function addStoreAccount(data: any): AxiosPromise { + return request({ + url: '/admin/v1/store/create', + method: 'post', + data: { ...data, auth: CONFIG_DATA.storeAddId } + }); +} +//编辑门店账号 +export function editStoreAccount(data: any): AxiosPromise { + return request({ + url: '/admin/v1/store/update', + method: 'post', + data: { ...data, auth: CONFIG_DATA.storeEditId } + }); +} +//删除门店账号 +export function deleteStoreAccount(data: any): AxiosPromise { + return request({ + url: '/admin/v1/store/delete', + method: 'post', + data: { ...data, auth: CONFIG_DATA.storeDeleteId } + }); +} +//添加账号时选择的门店 +export function getADdStoreList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/store/index', + method: 'post', + data: { ...data, auth: CONFIG_DATA.storeAddStoreListId } + }); +} diff --git a/src/api/system/dict.ts b/src/api/system/dict.ts new file mode 100644 index 0000000..2afa739 --- /dev/null +++ b/src/api/system/dict.ts @@ -0,0 +1,27 @@ +import { Option } from '@/types/common'; +import { + DictFormTypeData, + DictItemFormData, + DictItemPageResult, + DictItemQueryParam, + DictPageResult, + DictQueryParam, +} from '@/types/api/system/dict'; +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +/** + * 根据字典类型编码获取字典数据项 + * + * @param typeCode 字典类型编码 + */ +export function getDictItemsByTypeCode( + typeCode: string +): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/dict-items/select_list', + method: 'get', + params: { typeCode: typeCode }, + }); +} + + diff --git a/src/api/system/menu.ts b/src/api/system/menu.ts new file mode 100644 index 0000000..0c2674b --- /dev/null +++ b/src/api/system/menu.ts @@ -0,0 +1,108 @@ +import { + MenuFormData, + MenuItem, + MenuGetParam, + MenuQueryParam, +} from '@/types/api/system/menu'; +import { Option } from '@/types/common'; +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; + + +/** + * 获取路由列表 + */ +export function listRoutes(data: MenuGetParam) { + return request({ + url: 'admin/routes', + method: 'post', + data, + }); +} + +/** + * 获取菜单表格列表 + * + * @param queryParams + */ +export function listMenus( + queryParams: MenuQueryParam +): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/menus', + method: 'get', + params: queryParams, + }); +} + + +/** + * 获取菜单下拉树形列表 + */ +export function listMenuOptions(): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/menus/options', + method: 'get', + }); +} + +/** + * 批量删除菜单 + * + * @param ids 菜单ID,多个以英文逗号(,)分割 + */ +export function deleteMenus(ids: string) { + return request({ + url: '/youlai-admin/api/v1/menus/' + ids, + method: 'delete', + }); +} + + +/** + * 获取菜单权限树形列表 + */ +export function getResource(): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/menus/resources', + method: 'get', + }); +} + +/** + * 获取菜单详情 + * @param id + */ +export function getMenuDetail(id: number): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/menus/' + id, + method: 'get', + }); +} + +/** + * 添加菜单 + * + * @param data + */ +export function addMenu(data: MenuFormData) { + return request({ + url: '/youlai-admin/api/v1/menus', + method: 'post', + data: data, + }); +} + +/** + * 修改菜单 + * + * @param id + * @param data + */ +export function updateMenu(id: string, data: MenuFormData) { + return request({ + url: '/youlai-admin/api/v1/menus/' + id, + method: 'put', + data: data, + }); +} \ No newline at end of file diff --git a/src/api/system/role.ts b/src/api/system/role.ts new file mode 100644 index 0000000..bedfa7e --- /dev/null +++ b/src/api/system/role.ts @@ -0,0 +1,119 @@ +import { + RoleFormData, + RolePageResult, + RoleQueryParam, + RoleResourceData, +} from '@/types/api/system/role'; + +import { Option } from '@/types/common'; +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; + +/** + * 获取角色分页数据 + * + * @param queryParams + */ +export function listRolePages( + queryParams?: RoleQueryParam +): AxiosPromise { + return request({ + url: '/authority/list', + method: 'post', + params: queryParams, + }); +} + +/** + * 获取角色下拉数据 + * + * @param queryParams + */ +export function listRoleOptions( + queryParams?: RoleQueryParam +): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/roles/options', + method: 'get', + params: queryParams, + }); +} + +/** + * 获取角色拥有的资源ID集合 + * + * @param queryParams + */ +export function getRoleResourceIds(roleId: string): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/roles/' + roleId + '/resource_ids', + method: 'get', + }); +} + +/** + * 修改角色资源权限 + * + * @param queryParams + */ +export function updateRoleResource( + roleId: string, + data: RoleResourceData +): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/roles/' + roleId + '/resources', + method: 'put', + data: data, + }); +} + +/** + * 获取角色详情 + * + * @param id + */ +export function getRoleFormDetail(id: number): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/roles/' + id, + method: 'get', + }); +} + +/** + * 添加角色 + * + * @param data + */ +export function addRole(data: RoleFormData) { + return request({ + url: '/youlai-admin/api/v1/roles', + method: 'post', + data: data, + }); +} + +/** + * 更新角色 + * + * @param id + * @param data + */ +export function updateRole(id: number, data: RoleFormData) { + return request({ + url: '/youlai-admin/api/v1/roles/' + id, + method: 'put', + data: data, + }); +} + +/** + * 批量删除角色,多个以英文逗号(,)分割 + * + * @param ids + */ +export function deleteRoles(ids: string) { + return request({ + url: '/youlai-admin/api/v1/roles/' + ids, + method: 'delete', + }); +} diff --git a/src/api/system/user.ts b/src/api/system/user.ts new file mode 100644 index 0000000..0ae67fe --- /dev/null +++ b/src/api/system/user.ts @@ -0,0 +1,146 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { + UserFormData, + UserInfo, + UserPageResult, + UserQueryParam, +} from '@/types/api/system/user'; + +/** + * 登录成功后获取用户信息(昵称、头像、权限集合和角色集合) + */ +export function getUserInfo(): AxiosPromise { + return request({ + url: 'admin/info', + method: 'post', + }); +} + +/** + * 获取用户分页列表 + * + * @param queryParams + */ +export function listUserPages( + queryParams: UserQueryParam +): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/users/pages', + method: 'get', + params: queryParams, + }); +} + +/** + * 获取用户表单详情 + * + * @param userId + */ +export function getUserFormData(userId: number): AxiosPromise { + return request({ + url: '/youlai-admin/api/v1/users/' + userId + '/form_data', + method: 'get', + }); +} + +/** + * 添加用户 + * + * @param data + */ +export function addUser(data: any) { + return request({ + url: '/youlai-admin/api/v1/users', + method: 'post', + data: data, + }); +} + +/** + * 修改用户 + * + * @param id + * @param data + */ +export function updateUser(id: number, data: UserFormData) { + return request({ + url: '/youlai-admin/api/v1/users/' + id, + method: 'put', + data: data, + }); +} + +/** + * 选择性修改用户 + * + * @param id + * @param data + */ +export function updateUserPart(id: number, data: any) { + return request({ + url: '/youlai-admin/api/v1/users/' + id, + method: 'patch', + data: data, + }); +} + +/** + * 删除用户 + * + * @param ids + */ +export function deleteUsers(ids: string) { + return request({ + url: '/youlai-admin/api/v1/users/' + ids, + method: 'delete', + }); +} + +/** + * 下载用户导入模板 + * + * @returns + */ +export function downloadTemplate() { + return request({ + url: '/youlai-admin/api/v1/users/template', + method: 'get', + responseType: 'arraybuffer', + }); +} + +/** + * 导出用户 + * + * @param queryParams + * @returns + */ +export function exportUser(queryParams: UserQueryParam) { + return request({ + url: '/youlai-admin/api/v1/users/_export', + method: 'get', + params: queryParams, + responseType: 'arraybuffer', + }); +} + +/** + * 导入用户 + * + * @param file + */ +export function importUser(deptId: number, roleIds: string, file: File) { + const formData = new FormData(); + formData.append('file', file); + formData.append('deptId', deptId.toString()); + formData.append('roleIds', roleIds); + return request({ + url: '/youlai-admin/api/v1/users/_import', + method: 'post', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +} diff --git a/src/api/upload/index.ts b/src/api/upload/index.ts new file mode 100644 index 0000000..57799ee --- /dev/null +++ b/src/api/upload/index.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; + +//上传 +export function uploadImage(data: any): AxiosPromise { + return request({ + url: '/api/upload', + method: 'post', + data + }); +} \ No newline at end of file diff --git a/src/api/user/index.ts b/src/api/user/index.ts new file mode 100644 index 0000000..da02676 --- /dev/null +++ b/src/api/user/index.ts @@ -0,0 +1,62 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { CONFIG_DATA } from '@/utils/code'; + +//获取用户列表 +export function getUserList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/user/list', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userListId } + }); +} +//获取用户档案接口 +export function getUserarchive(data: any): AxiosPromise { + return request({ + url: '/admin/v1/user/archive', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userCustomerId } + }); +} +//获取用户档案详情接口 +export function getUserarchiveDetail(data: any): AxiosPromise { + return request({ + url: '/admin/v1/user/detail', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userAuthDetailId } + }); +} + +//获取用户授权门店列表 +export function getUserAuthStoreList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/bind/store', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userAuthListId } + }); +} + +//获取用户检测列表 +export function getUserDetectionList(data: any): AxiosPromise { + return request({ + url: '/admin/v1/scan/list', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userDetectionId } + }); +} +//用户检测-操作 +export function userDetectionOperation(data: any): AxiosPromise { + return request({ + url: '/admin/v1/scan/state', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userQuestionNoId } + }); +} +//用户检测-分析列表详情接口 +export function getUserDetectionDetail(data: any): AxiosPromise { + return request({ + url: '/admin/v1/scan/detail', + method: 'post', + data: { ...data, auth: CONFIG_DATA.userAnalyDetailId } + }); +} \ No newline at end of file diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/src/assets/404_images/404.png differ diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ diff --git a/src/assets/icons/Customer management.svg b/src/assets/icons/Customer management.svg new file mode 100644 index 0000000..bc011a4 --- /dev/null +++ b/src/assets/icons/Customer management.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/ad.svg b/src/assets/icons/ad.svg new file mode 100644 index 0000000..4c22c64 --- /dev/null +++ b/src/assets/icons/ad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/add-account.svg b/src/assets/icons/add-account.svg new file mode 100644 index 0000000..0ab025e --- /dev/null +++ b/src/assets/icons/add-account.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/advert.svg b/src/assets/icons/advert.svg new file mode 100644 index 0000000..5adcf43 --- /dev/null +++ b/src/assets/icons/advert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/bookmark-one.svg b/src/assets/icons/bookmark-one.svg new file mode 100644 index 0000000..74f124e --- /dev/null +++ b/src/assets/icons/bookmark-one.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/brand.svg b/src/assets/icons/brand.svg new file mode 100644 index 0000000..e4b7cee --- /dev/null +++ b/src/assets/icons/brand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/bug.svg b/src/assets/icons/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/src/assets/icons/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/cascader.svg b/src/assets/icons/cascader.svg new file mode 100644 index 0000000..e256024 --- /dev/null +++ b/src/assets/icons/cascader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/category.svg b/src/assets/icons/category.svg new file mode 100644 index 0000000..c4bbbe9 --- /dev/null +++ b/src/assets/icons/category.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/chart.svg b/src/assets/icons/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/src/assets/icons/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/client.svg b/src/assets/icons/client.svg new file mode 100644 index 0000000..ad4bc15 --- /dev/null +++ b/src/assets/icons/client.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg new file mode 100644 index 0000000..5b5057f --- /dev/null +++ b/src/assets/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_all.svg b/src/assets/icons/close_all.svg new file mode 100644 index 0000000..aa13cd7 --- /dev/null +++ b/src/assets/icons/close_all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_left.svg b/src/assets/icons/close_left.svg new file mode 100644 index 0000000..e5708ea --- /dev/null +++ b/src/assets/icons/close_left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_other.svg b/src/assets/icons/close_other.svg new file mode 100644 index 0000000..212e6c2 --- /dev/null +++ b/src/assets/icons/close_other.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/close_right.svg b/src/assets/icons/close_right.svg new file mode 100644 index 0000000..14d3cf3 --- /dev/null +++ b/src/assets/icons/close_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/config.svg b/src/assets/icons/config.svg new file mode 100644 index 0000000..57694d6 --- /dev/null +++ b/src/assets/icons/config.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/copy.svg b/src/assets/icons/copy.svg new file mode 100644 index 0000000..cd228d4 --- /dev/null +++ b/src/assets/icons/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/coupon.svg b/src/assets/icons/coupon.svg new file mode 100644 index 0000000..2f952b2 --- /dev/null +++ b/src/assets/icons/coupon.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/dashboard.svg b/src/assets/icons/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/src/assets/icons/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/data.svg b/src/assets/icons/data.svg new file mode 100644 index 0000000..b0e641b --- /dev/null +++ b/src/assets/icons/data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/dict.svg b/src/assets/icons/dict.svg new file mode 100644 index 0000000..22a8278 --- /dev/null +++ b/src/assets/icons/dict.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/assets/icons/dict_item.svg b/src/assets/icons/dict_item.svg new file mode 100644 index 0000000..903109a --- /dev/null +++ b/src/assets/icons/dict_item.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/assets/icons/dollar.svg b/src/assets/icons/dollar.svg new file mode 100644 index 0000000..0bba2cf --- /dev/null +++ b/src/assets/icons/dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/download.svg b/src/assets/icons/download.svg new file mode 100644 index 0000000..c896951 --- /dev/null +++ b/src/assets/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/drag.svg b/src/assets/icons/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/src/assets/icons/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/src/assets/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/exit-fullscreen.svg b/src/assets/icons/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/src/assets/icons/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/eye-open.svg b/src/assets/icons/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/src/assets/icons/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/eye.svg b/src/assets/icons/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/src/assets/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/fullscreen.svg b/src/assets/icons/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/src/assets/icons/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/github.svg b/src/assets/icons/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/src/assets/icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/goods-list.svg b/src/assets/icons/goods-list.svg new file mode 100644 index 0000000..fcb971e --- /dev/null +++ b/src/assets/icons/goods-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/goods.svg b/src/assets/icons/goods.svg new file mode 100644 index 0000000..60c1c73 --- /dev/null +++ b/src/assets/icons/goods.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/guide.svg b/src/assets/icons/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/src/assets/icons/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/homepage.svg b/src/assets/icons/homepage.svg new file mode 100644 index 0000000..48f4e24 --- /dev/null +++ b/src/assets/icons/homepage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/lab.svg b/src/assets/icons/lab.svg new file mode 100644 index 0000000..d4d60aa --- /dev/null +++ b/src/assets/icons/lab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/language.svg b/src/assets/icons/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/src/assets/icons/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/link.svg b/src/assets/icons/link.svg new file mode 100644 index 0000000..d3f9e5a --- /dev/null +++ b/src/assets/icons/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/menu.svg b/src/assets/icons/menu.svg new file mode 100644 index 0000000..92c364c --- /dev/null +++ b/src/assets/icons/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/message center.svg b/src/assets/icons/message center.svg new file mode 100644 index 0000000..203aab9 --- /dev/null +++ b/src/assets/icons/message center.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/message.svg b/src/assets/icons/message.svg new file mode 100644 index 0000000..ea1ddef --- /dev/null +++ b/src/assets/icons/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/money.svg b/src/assets/icons/money.svg new file mode 100644 index 0000000..60f7acf --- /dev/null +++ b/src/assets/icons/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/monitor.svg b/src/assets/icons/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/src/assets/icons/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/nested.svg b/src/assets/icons/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/src/assets/icons/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/number.svg b/src/assets/icons/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/src/assets/icons/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/order.svg b/src/assets/icons/order.svg new file mode 100644 index 0000000..8f2107e --- /dev/null +++ b/src/assets/icons/order.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/paper-money.svg b/src/assets/icons/paper-money.svg new file mode 100644 index 0000000..bffdbb7 --- /dev/null +++ b/src/assets/icons/paper-money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/password.svg b/src/assets/icons/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/src/assets/icons/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/peoples.svg b/src/assets/icons/peoples.svg new file mode 100644 index 0000000..383b82d --- /dev/null +++ b/src/assets/icons/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/perm.svg b/src/assets/icons/perm.svg new file mode 100644 index 0000000..b38d065 --- /dev/null +++ b/src/assets/icons/perm.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/publish.svg b/src/assets/icons/publish.svg new file mode 100644 index 0000000..e9b489c --- /dev/null +++ b/src/assets/icons/publish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/rabbitmq.svg b/src/assets/icons/rabbitmq.svg new file mode 100644 index 0000000..65aa198 --- /dev/null +++ b/src/assets/icons/rabbitmq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/rate.svg b/src/assets/icons/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/src/assets/icons/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/redis.svg b/src/assets/icons/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/src/assets/icons/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/refresh.svg b/src/assets/icons/refresh.svg new file mode 100644 index 0000000..1f549f1 --- /dev/null +++ b/src/assets/icons/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/role.svg b/src/assets/icons/role.svg new file mode 100644 index 0000000..c484b13 --- /dev/null +++ b/src/assets/icons/role.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/security.svg b/src/assets/icons/security.svg new file mode 100644 index 0000000..bcd9d2e --- /dev/null +++ b/src/assets/icons/security.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/service.svg b/src/assets/icons/service.svg new file mode 100644 index 0000000..2c576a2 --- /dev/null +++ b/src/assets/icons/service.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/set.svg b/src/assets/icons/set.svg new file mode 100644 index 0000000..0bed8a5 --- /dev/null +++ b/src/assets/icons/set.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/shopping.svg b/src/assets/icons/shopping.svg new file mode 100644 index 0000000..8d2b4bf --- /dev/null +++ b/src/assets/icons/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/size.svg b/src/assets/icons/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/src/assets/icons/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/skill.svg b/src/assets/icons/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/src/assets/icons/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/system.svg b/src/assets/icons/system.svg new file mode 100644 index 0000000..63feb20 --- /dev/null +++ b/src/assets/icons/system.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/tag-one.svg b/src/assets/icons/tag-one.svg new file mode 100644 index 0000000..f049d37 --- /dev/null +++ b/src/assets/icons/tag-one.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/theme.svg b/src/assets/icons/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/src/assets/icons/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/tree.svg b/src/assets/icons/tree.svg new file mode 100644 index 0000000..d40a414 --- /dev/null +++ b/src/assets/icons/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/user.svg b/src/assets/icons/user.svg new file mode 100644 index 0000000..e4c7b38 --- /dev/null +++ b/src/assets/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/uv.svg b/src/assets/icons/uv.svg new file mode 100644 index 0000000..ca4c301 --- /dev/null +++ b/src/assets/icons/uv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/valid_code.svg b/src/assets/icons/valid_code.svg new file mode 100644 index 0000000..39bf478 --- /dev/null +++ b/src/assets/icons/valid_code.svg @@ -0,0 +1,9 @@ + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..443043b Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components.d.ts b/src/components.d.ts new file mode 100644 index 0000000..94e8b82 --- /dev/null +++ b/src/components.d.ts @@ -0,0 +1,9 @@ +// 全局组件类型声明 +import Pagination from '@/components/Pagination/index.vue'; + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Pagination: typeof Pagination; + } +} +export {}; diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..0b0b05f --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/src/components/Common/TableDialog.vue b/src/components/Common/TableDialog.vue new file mode 100644 index 0000000..4e3a72c --- /dev/null +++ b/src/components/Common/TableDialog.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/components/GithubCorner/index.vue b/src/components/GithubCorner/index.vue new file mode 100644 index 0000000..c9a4b32 --- /dev/null +++ b/src/components/GithubCorner/index.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue new file mode 100644 index 0000000..6704cae --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue new file mode 100644 index 0000000..9670dcf --- /dev/null +++ b/src/components/IconSelect/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue new file mode 100644 index 0000000..34b2374 --- /dev/null +++ b/src/components/LangSelect/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/components/ObSelectTree/index.vue b/src/components/ObSelectTree/index.vue new file mode 100644 index 0000000..504647e --- /dev/null +++ b/src/components/ObSelectTree/index.vue @@ -0,0 +1,32 @@ + diff --git a/src/components/ObStatus/index.vue b/src/components/ObStatus/index.vue new file mode 100644 index 0000000..09e8761 --- /dev/null +++ b/src/components/ObStatus/index.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..e85345c --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/src/components/RightPanel/index.vue b/src/components/RightPanel/index.vue new file mode 100644 index 0000000..5dacdbe --- /dev/null +++ b/src/components/RightPanel/index.vue @@ -0,0 +1,163 @@ + + + + + + + diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue new file mode 100644 index 0000000..f0ed7d6 --- /dev/null +++ b/src/components/Screenfull/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..503b2c4 --- /dev/null +++ b/src/components/SizeSelect/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/components/Store/StoreAccountDialog.vue b/src/components/Store/StoreAccountDialog.vue new file mode 100644 index 0000000..8847377 --- /dev/null +++ b/src/components/Store/StoreAccountDialog.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..1f23cd6 --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/src/components/ThemePicker/index.vue b/src/components/ThemePicker/index.vue new file mode 100644 index 0000000..d77e8eb --- /dev/null +++ b/src/components/ThemePicker/index.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/components/Upload/SingleUpload.vue b/src/components/Upload/SingleUpload.vue new file mode 100644 index 0000000..0b3c8d9 --- /dev/null +++ b/src/components/Upload/SingleUpload.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/src/components/User/UserArchiveDetailDialog.vue b/src/components/User/UserArchiveDetailDialog.vue new file mode 100644 index 0000000..fdd588f --- /dev/null +++ b/src/components/User/UserArchiveDetailDialog.vue @@ -0,0 +1,647 @@ + + + + + + + diff --git a/src/components/User/UserArchiveDialog.vue b/src/components/User/UserArchiveDialog.vue new file mode 100644 index 0000000..8c2648c --- /dev/null +++ b/src/components/User/UserArchiveDialog.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/components/User/UserAuthStoreDialog.vue b/src/components/User/UserAuthStoreDialog.vue new file mode 100644 index 0000000..c31ffce --- /dev/null +++ b/src/components/User/UserAuthStoreDialog.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/WangEditor/index.vue b/src/components/WangEditor/index.vue new file mode 100644 index 0000000..6f8d683 --- /dev/null +++ b/src/components/WangEditor/index.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/directive/index.ts b/src/directive/index.ts new file mode 100644 index 0000000..39edd27 --- /dev/null +++ b/src/directive/index.ts @@ -0,0 +1 @@ +export { hasPerm, hasRole } from './permission'; diff --git a/src/directive/permission/index.ts b/src/directive/permission/index.ts new file mode 100644 index 0000000..40fd337 --- /dev/null +++ b/src/directive/permission/index.ts @@ -0,0 +1,56 @@ +import useStore from '@/store'; +import { Directive, DirectiveBinding } from 'vue'; + +/** + * 按钮权限校验 + */ +export const hasPerm: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + // 「超级管理员」拥有所有的按钮权限 + const { user } = useStore(); + const roles = user.roles; + if (roles.includes('ROOT')) { + return true; + } + // 「其他角色」按钮权限校验 + const { value } = binding; + if (value) { + const requiredPerms = value; // DOM绑定需要的按钮权限标识 + + const hasPerm = user.perms?.some(perm => { + return requiredPerms.includes(perm); + }); + + if (!hasPerm) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error( + "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\"" + ); + } + } +}; + +/** + * 角色权限校验 + */ +export const hasRole: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding; + + if (value) { + const requiredRoles = value; // DOM绑定需要的角色编码 + const { user } = useStore(); + const hasRole = user.roles.some(perm => { + return requiredRoles.includes(perm); + }); + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error("need roles! Like v-has-role=\"['admin','test']\""); + } + } +}; diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..bcddf3e --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,19 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} + +// 环境变量 TypeScript的智能提示 +interface ImportMetaEnv { + VITE_APP_TITLE: string; + VITE_APP_PORT: string; + VITE_APP_BASE_API: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/src/lang/en.ts b/src/lang/en.ts new file mode 100644 index 0000000..6b29624 --- /dev/null +++ b/src/lang/en.ts @@ -0,0 +1,26 @@ +export default { + // 路由国际化 + route: { + dashboard: 'Dashboard', + document: 'Document' + }, + // 登录页面国际化 + login: { + title: 'vue3-element-admin', + username: 'Username', + password: 'Password', + login: 'Login', + code: 'Verification Code', + copyright: '', + icp: '' + }, + // 导航栏国际化 + navbar: { + dashboard: 'Dashboard', + logout: 'Logout', + password: 'Password', + add:"Add", + search:"Search", + refresh:"ReFresh", + } +}; diff --git a/src/lang/index.ts b/src/lang/index.ts new file mode 100644 index 0000000..c527075 --- /dev/null +++ b/src/lang/index.ts @@ -0,0 +1,45 @@ +// 自定义国际化配置 +import { createI18n } from 'vue-i18n'; +import { localStorage } from '@/utils/storage'; + +// 本地语言包 +import enLocale from './en'; +import zhCnLocale from './zh-cn'; + +const messages = { + 'zh-cn': { + ...zhCnLocale + }, + en: { + ...enLocale + } +}; + +/** + * 获取当前系统使用语言字符串 + * + * @returns zh-cn|en ... + */ +export const getLanguage = () => { + // 本地缓存获取 + let language = localStorage.get('language'); + if (language) { + return language; + } + // 浏览器使用语言 + language = navigator.language.toLowerCase(); + const locales = Object.keys(messages); + for (const locale of locales) { + if (language.indexOf(locale) > -1) { + return locale; + } + } + return 'zh-cn'; +}; + +const i18n = createI18n({ + locale: getLanguage(), + messages: messages +}); + +export default i18n; diff --git a/src/lang/zh-cn.ts b/src/lang/zh-cn.ts new file mode 100644 index 0000000..3722da1 --- /dev/null +++ b/src/lang/zh-cn.ts @@ -0,0 +1,25 @@ +export default { + // 路由国际化 + route: { + dashboard: '首页', + document: '项目文档' + }, + // 登录页面国际化 + login: { + title: '管理平台', + username: '用户名', + password: '密码', + login: '登 录', + code: '请输入验证码', + copyright: '', + icp: '' + }, + navbar: { + dashboard: '首页', + logout: '注销', + password: '修改密码', + add:"新建", + search:"搜索", + refresh:"重置", + } +}; diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue new file mode 100644 index 0000000..7084cd1 --- /dev/null +++ b/src/layout/components/AppMain.vue @@ -0,0 +1,54 @@ + + + + + + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue new file mode 100644 index 0000000..cf8736b --- /dev/null +++ b/src/layout/components/Navbar.vue @@ -0,0 +1,216 @@ + + + + diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..f2c24af --- /dev/null +++ b/src/layout/components/Settings/index.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..babd008 --- /dev/null +++ b/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..9e4f3da --- /dev/null +++ b/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..0cfbed3 --- /dev/null +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..d6affaf --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..1de36c0 --- /dev/null +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..39053cb --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/src/layout/components/index.ts b/src/layout/components/index.ts new file mode 100644 index 0000000..4dca96e --- /dev/null +++ b/src/layout/components/index.ts @@ -0,0 +1,4 @@ +export { default as Navbar } from './Navbar.vue'; +export { default as AppMain } from './AppMain.vue'; +export { default as Settings } from './Settings/index.vue'; +export { default as TagsView } from './TagsView/index.vue'; diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..4b761ce --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..b1eb0c8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,78 @@ +import { createApp, Directive } from 'vue'; +import App from './App.vue'; +import router from '@/router'; + +import { createPinia } from 'pinia'; + +import ElementPlus from 'element-plus'; +import 'element-plus/theme-chalk/index.css'; +import Pagination from '@/components/Pagination/index.vue'; +import '@/permission'; + +// 引入svg注册脚本 +import 'virtual:svg-icons-register'; + +// 国际化 +import i18n from '@/lang/index'; + +// 自定义样式 +import '@/styles/index.scss'; + +// 根据字典编码获取字典列表全局方法 +import { getDictItemsByTypeCode } from '@/api/system/dict'; + +// 主题颜色混合工具 +import { mix } from '@/utils'; + +// 根据环境变量初始化主题颜色 +const initTheme = () => { + const node = document.documentElement; + const envTheme = import.meta.env.VITE_APP_THEME; + const localStorageTheme = localStorage.getItem('theme'); + + // 优先使用环境变量,其次使用 localStorage + const themeColor = envTheme || localStorageTheme; + + if (themeColor) { + // 设置主主题色 + node.style.setProperty('--el-color-primary', themeColor); + + // 设置渐变色 + const mixWhite = '#ffffff'; + const mixBlack = '#000000'; + + for (let i = 1; i < 10; i += 1) { + node.style.setProperty( + `--el-color-primary-light-${i}`, + mix(themeColor, mixWhite, i * 0.1) + ); + } + node.style.setProperty('--el-color-primary-dark', mix(themeColor, mixBlack, 0.1)); + + localStorage.setItem('theme', themeColor); + } +}; + +// 初始化主题颜色 +initTheme(); + +const app = createApp(App); + +// 自定义指令 +import * as directive from '@/directive'; + +Object.keys(directive).forEach((key) => { + app.directive(key, (directive as { [key: string]: Directive })[key]); +}); + +// 全局方法 +app.config.globalProperties.$getDictItemsByTypeCode = getDictItemsByTypeCode; + +// 注册全局组件 +app + .component('Pagination', Pagination) + .use(createPinia()) + .use(router) + .use(ElementPlus) + .use(i18n) + .mount('#app'); diff --git a/src/permission.ts b/src/permission.ts new file mode 100644 index 0000000..47e8ebc --- /dev/null +++ b/src/permission.ts @@ -0,0 +1,64 @@ +import router from '@/router'; +import { ElMessage } from 'element-plus'; +import useStore from '@/store'; +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +NProgress.configure({ showSpinner: false }); // 进度环显示/隐藏 + +// 白名单路由 +const whiteList = ['/login']; + +router.beforeEach(async (to, from, next) => { + NProgress.start(); + const { user, permission } = useStore(); + const hasToken = user.token; + if (hasToken) { + console.log('hasToken',hasToken,to.path) + // 登录成功,跳转到首页 + if (to.path === '/login') { + console.log('跳转到首页') + next({ path: '/' }); + NProgress.done(); + } else { + const hasGetUserInfo = user.roles.length > 0; + if (hasGetUserInfo) { + if (to.matched.length === 0) { + from.name ? next({ name: from.name as any }) : next('/401'); + } else { + next(); + } + } else { + try { + await user.getUserInfo(); + const roles = user.roles; + console.log(roles,'roles') + const accessRoutes: any = await permission.generateRoutes(roles); + console.log(accessRoutes,'accessRoutes') + accessRoutes.forEach((route: any) => { + router.addRoute(route); + }); + next({ ...to, replace: true }); + } catch (error) { + console.log(error) + // 移除 token 并跳转登录页 + await user.resetToken(); + ElMessage.error((error as any) || 'Has Error'); + next(`/login?redirect=${to.path}`); + NProgress.done(); + } + } + } + } else { + // 未登录可以访问白名单页面(登录页面) + if (whiteList.indexOf(to.path) !== -1) { + next(); + } else { + next(`/login?redirect=${to.path}`); + NProgress.done(); + } + } +}); + +router.afterEach(() => { + NProgress.done(); +}); diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..eb16804 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,123 @@ +import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; +import useStore from '@/store'; + +export const Layout = () => import('@/layout/index.vue'); + +// 参数说明: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html +// 静态路由 +export const constantRoutes: Array = [ + { + path: '/redirect', + component: Layout, + meta: { hidden: true }, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login/index.vue'), + meta: { hidden: true } + }, + { + path: '/404', + component: () => import('@/views/error-page/404.vue'), + meta: { hidden: true } + }, + + { + path: '/', + component: Layout, + redirect: '/dashboard', + children: [ + { + path: 'dashboard', + component: () => import('@/views/dashboard/index.vue'), + name: 'Dashboard', + meta: { title: 'dashboard', icon: 'homepage', affix: true } + }, + { + path: '401', + component: () => import('@/views/error-page/401.vue'), + meta: { hidden: true } + }, + ] + }, + + // 外部链接 + // { + // path: '/external-link', + // component: Layout, + // children: [ + // { + // path: 'https://www.cnblogs.com/haoxianrui/', + // meta: { title: '外部链接', icon: 'link' } + // } + // ] + // }, + // 多级嵌套路由 + /* { + path: '/nested', + component: Layout, + redirect: '/nested/level1/level2', + name: 'Nested', + meta: {title: '多级菜单', icon: 'nested'}, + children: [ + { + path: 'level1', + component: () => import('@/views/nested/level1/index.vue'), + name: 'Level1', + meta: {title: '菜单一级'}, + redirect: '/nested/level1/level2', + children: [ + { + path: 'level2', + component: () => import('@/views/nested/level1/level2/index.vue'), + name: 'Level2', + meta: {title: '菜单二级'}, + redirect: '/nested/level1/level2/level3', + children: [ + { + path: 'level3-1', + component: () => import('@/views/nested/level1/level2/level3/index1.vue'), + name: 'Level3-1', + meta: {title: '菜单三级-1'} + }, + { + path: 'level3-2', + component: () => import('@/views/nested/level1/level2/level3/index2.vue'), + name: 'Level3-2', + meta: {title: '菜单三级-2'} + } + ] + } + ] + }, + ] + }*/ +]; + +// 创建路由 +const router = createRouter({ + history: createWebHashHistory(), + routes: constantRoutes as RouteRecordRaw[], + // 刷新时,滚动条位置还原 + scrollBehavior: () => ({ left: 0, top: 0 }) +}); + +// 重置路由 +export function resetRouter() { + const { permission } = useStore(); + //@ts-ignore + permission.routes.forEach(route => { + const name = route.name; + if (name && router.hasRoute(name)) { + router.removeRoute(name); + } + }); +} + +export default router; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..cef7a0f --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,20 @@ +interface DefaultSettings { + title: string; + showSettings: boolean; + tagsView: boolean; + fixedHeader: boolean; + sidebarLogo: boolean; + errorLog: string; +} + +const defaultSettings: DefaultSettings = { + title: '皮肤检测仪管理平台', + showSettings: true, + tagsView: true, + fixedHeader: false, + // 是否显示Logo + sidebarLogo: true, + errorLog: 'production' +}; + +export default defaultSettings; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..0b3e6dc --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,17 @@ +import useUserStore from './modules/user'; +import useAppStore from './modules/app'; +import usePermissionStore from './modules/permission'; +import useSettingStore from './modules/settings'; +import useTagsViewStore from './modules/tagsView'; +import myTools from "@/store/modules/my"; + +const useStore = () => ({ + user: useUserStore(), + app: useAppStore(), + permission: usePermissionStore(), + setting: useSettingStore(), + tagsView: useTagsViewStore(), + my: myTools(), +}); + +export default useStore; diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..064d386 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,48 @@ +import { AppState } from '@/types/store/app'; +import { localStorage } from '@/utils/storage'; +import { defineStore } from 'pinia'; +import { getLanguage } from '@/lang/index'; + +const useAppStore = defineStore({ + id: 'app', + state: (): AppState => ({ + device: 'desktop', + sidebar: { + opened: localStorage.get('sidebarStatus') + ? !!+localStorage.get('sidebarStatus') + : true, + withoutAnimation: false, + }, + language: getLanguage(), + size: localStorage.get('size') || 'default', + }), + actions: { + toggleSidebar() { + this.sidebar.opened = !this.sidebar.opened; + this.sidebar.withoutAnimation = false; + if (this.sidebar.opened) { + localStorage.set('sidebarStatus', 1); + } else { + localStorage.set('sidebarStatus', 0); + } + }, + closeSideBar(withoutAnimation: any) { + localStorage.set('sidebarStatus', 0); + this.sidebar.opened = false; + this.sidebar.withoutAnimation = withoutAnimation; + }, + toggleDevice(device: string) { + this.device = device; + }, + setSize(size: string) { + this.size = size; + localStorage.set('size', size); + }, + setLanguage(language: string) { + this.language = language; + localStorage.set('language', language); + }, + }, +}); + +export default useAppStore; diff --git a/src/store/modules/my.ts b/src/store/modules/my.ts new file mode 100644 index 0000000..f0794c4 --- /dev/null +++ b/src/store/modules/my.ts @@ -0,0 +1,90 @@ +import { defineStore } from 'pinia'; +import { ElMessage } from 'element-plus'; +import * as qiniu from 'qiniu-js' +import {CODES} from "@/utils/code"; +import { imageToken } from '@/api/public/index'; +import { localStorage } from '@/utils/storage'; + +const myTools = defineStore({ + id: 'my', + state :() => ( + { + } + ), + actions: { + reminder(message ?: string) { + // @ts-ignore + ElMessage.error(message || '系统出错'); + }, + ok(message ?: string) { + // @ts-ignore + ElMessage.success(message); + }, + upImage(file:any, type : number, callback : any) { + let fileInfo = file.name.split('.') + let fileType = fileInfo[fileInfo.length-1] + imageToken().then(res => { + // @ts-ignore + if (res.code === CODES.ok.code) { + this._upImage(file, res.data, + this._getFileName(type, fileType), callback) + } else { + // @ts-ignore + this.reminder(res.message); + } + }); + }, + _upImage(file : any, upInfo : object, fineName : string, callback:any) { + let obj = this + const observer = { + state: undefined, + next(res: any){ + }, + error(err: any){ + obj.reminder('上传失败'); + }, + complete(res: any){ + // @ts-ignore + callback(upInfo.url + res.key, res.key) + } + } + let config = { + useCdnDomain: true,//是否使用CDN加速域名 + disableStatisticsReport: true,//是否禁用日志报告 + retryCount: 6//上传自动重试次数 + }; + let putExtra = { + customVars: {} + }; + + const options = { + quality: 0.92, + noCompressIfLarger: true + } + // qiniu.compressImage(file, options).then(data => { + // @ts-ignore + const observable = qiniu.upload(file, fineName, upInfo.token, putExtra, config) + observable.subscribe(observer) // 上传开始 + // }) + }, + _getFileName(type : any, fileType:string) { + let date=new Date(); + let year=date.getFullYear(); //获取当前年份 + let mon=date.getMonth()+1; //获取当前月份 + let da=date.getDate(); //获取当前日 + let h=date.getHours(); //获取小时 + let m=date.getMinutes(); //获取分钟 + let s=date.getSeconds(); //获取秒 + let hs=date.getMilliseconds(); //获取毫秒 + let rand = Math.random() + // @ts-ignore + rand = parseInt(rand * 1000) + + // @ts-ignore + return (CODES.imageType[type] ?? '') + '/' + year + '/' + mon + '/' + + da + '/' + h + m + s + hs + rand + '.' + fileType + } + }, +}); + +export default myTools; diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..a5acc6a --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,172 @@ +import { PermissionState } from '@/types/store/permission'; +import { RouteRecordRaw } from 'vue-router'; +import { defineStore } from 'pinia'; +import { constantRoutes } from '@/router'; +import { listRoutes } from '@/api/system/menu'; +import {CODES, CONFIG_DATA, Numbers} from "@/utils/code"; +import { localStorage } from '@/utils/storage'; +import { fa } from 'element-plus/es/locale'; + +const modules = import.meta.glob('../../views/**/**.vue'); +export const Layout = () => import('@/layout/index.vue'); + +const hasPermission = (roles: string[], route: RouteRecordRaw) => { + if (route.meta && route.meta.roles) { + if (roles.includes('ROOT')) { + + return true; + } + return roles.some((role) => { + if (route.meta?.roles !== undefined) { + return (route.meta.roles as string[]).includes(role); + } + }); + } + return false; +}; + +export const filterAsyncRoutes = ( + routes: RouteRecordRaw[], + roles: string[] +) => { + const res: RouteRecordRaw[] = []; + routes.forEach((route) => { + const tmp = { ...route } as any; + if (hasPermission(roles, tmp)) { + if (tmp.component == 'Layout') { + tmp.component = Layout; + } else { + const component = modules[`../../views/${tmp.component}.vue`] as any; + if (component) { + tmp.component = modules[`../../views/${tmp.component}.vue`]; + } else { + tmp.component = modules[`../../views/error-page/404.vue`]; + } + } + res.push(tmp); + if (tmp.children) { + tmp.children = filterAsyncRoutes(tmp.children, roles); + } + } + }); + return res; +}; + +const usePermissionStore = defineStore({ + id: 'permission', + state: (): PermissionState => ({ + routes: [], + addRoutes: [], + }), + actions: { + setRoutes(routes: RouteRecordRaw[]) { + this.addRoutes = routes; + this.routes = constantRoutes.concat(routes); + }, + generateRoutes(roles: string[]) { + return new Promise((resolve, reject) => { + listRoutes({ + project_id:CONFIG_DATA.projectId + }) + .then((response) => { + // @ts-ignore + if (response.code !== CODES.ok.code) { + // @ts-ignore + return reject(response.message || 'Error'); + } + // @ts-ignore + var routes = [] + localStorage.remove(CONFIG_DATA.authKey) + routes = getRoutes(response.data['menu']) + setAuths(response.data['auth']) + localStorage.set(CONFIG_DATA.authKey, storageKeys) + // @ts-ignore + const accessedRoutes = filterAsyncRoutes(routes, roles); + this.setRoutes(accessedRoutes); + resolve(accessedRoutes); + }) + }); + }, + }, +}); + +const setAuths = (data: string[]) => { + if (!data) { + return + } + + for (var i=0;i< data.length ;i++) { + // @ts-ignore + storageKeys[data[i]['identification']] = data[i]['identification'] + } + +} + +var storageKeys = {} +const getRoutes = (data : string[]) => { + + if (!data) { + return [] + } + var ret = []; + for (var i=0;i< data.length ;i++) { + // 权限写入缓存中 + // @ts-ignore + if (data[i]['type'] !== Numbers.authTypeButton){ + var tmp = {}; + // @ts-ignore + tmp["path"] = data[i]['path'] + // @ts-ignore + if (data[i]['level'] == 1 ){ + // @ts-ignore + tmp["component"] = "Layout" + // @ts-ignore + tmp["redirect"] = "/" + } else { + // @ts-ignore + // tmp["component"] = "111" + tmp["component"] = data[i]['view_path'] + // @ts-ignore + tmp["name"] = data[i]['path'] + } + // @ts-ignore + tmp['meta'] = {} + // @ts-ignore + tmp['meta']["title"] = data[i]['name'] + // @ts-ignore + tmp['type'] = data[i]['type'] + // @ts-ignore + tmp['meta']["icon"] = data[i]['icon'] + // @ts-ignore + if (data[i]['children'] == null || data[i]['children'].length < 1 || data[i]['type'] == Numbers.authTypeWeb){ + // @ts-ignore + tmp['meta']["alwaysShow"] = false + } else { + // @ts-ignore + tmp['meta']["alwaysShow"] = true + } + // @ts-ignore + tmp['meta']["hidden"] = data[i]['is_show'] == 1 ? false : true + // @ts-ignore + tmp['meta']["roles"] = ["ADMIN"] + // @ts-ignore + tmp['meta']["keepAlive"] = true + // @ts-ignore + if (data[i]['children'] == null || data[i]['children'].length < 1){ + // @ts-ignore + tmp["children"] = [] + } else { + // @ts-ignore + tmp["children"] = getRoutes(data[i]['children']) + } + //@ts-ignore + ret.push(tmp) + + } + } + + + return ret +} + +export default usePermissionStore; diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts new file mode 100644 index 0000000..e6ef023 --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia'; +import { SettingState } from '@/types/store/setting'; +import defaultSettings from '../../settings'; +import { localStorage } from '@/utils/storage'; + +const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings; +const el = document.documentElement; + +// 从环境变量获取主题颜色 +const envTheme = import.meta.env.VITE_APP_THEME; + +export const useSettingStore = defineStore({ + id: 'setting', + state: (): SettingState => ({ + // 优先使用环境变量中的主题颜色,其次使用 localStorage,最后使用默认值 + theme: + envTheme || + localStorage.get('theme') || + getComputedStyle(el).getPropertyValue(`--el-color-primary`), + showSettings: showSettings, + tagsView: + localStorage.get('tagsView') != null + ? localStorage.get('tagsView') + : tagsView, + fixedHeader: fixedHeader, + sidebarLogo: sidebarLogo, + }), + actions: { + async changeSetting(payload: { key: string; value: any }) { + const { key, value } = payload; + switch (key) { + case 'theme': + this.theme = value; + break; + case 'showSettings': + this.showSettings = value; + break; + case 'fixedHeader': + this.fixedHeader = value; + break; + case 'tagsView': + this.tagsView = value; + localStorage.set('tagsView', value); + break; + case 'sidebarLogo': + this.sidebarLogo = value; + break; + default: + break; + } + }, + }, +}); + +export default useSettingStore; diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..77c553e --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,181 @@ +import { defineStore } from 'pinia'; +import { TagsViewState } from '@/types/store/tagsview'; + +const useTagsViewStore = defineStore({ + id: 'tagsView', + state: (): TagsViewState => ({ + visitedViews: [], + cachedViews: [], // keepAlive 缓存页面 + }), + actions: { + addVisitedView(view: any) { + if (this.visitedViews.some((v) => v.path === view.path)) return; + if (view.meta && view.meta.affix) { + this.visitedViews.unshift( + Object.assign({}, view, { + title: view.meta?.title || 'no-name', + }) + ); + } else { + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name', + }) + ); + } + }, + addCachedView(view: any) { + if (this.cachedViews.includes(view.name)) return; + if (view.meta.keepAlive) { + this.cachedViews.push(view.name); + } + }, + delVisitedView(view: any) { + return new Promise((resolve) => { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1); + break; + } + } + resolve([...this.visitedViews]); + }); + }, + delCachedView(view: any) { + return new Promise((resolve) => { + const index = this.cachedViews.indexOf(view.name); + index > -1 && this.cachedViews.splice(index, 1); + resolve([...this.cachedViews]); + }); + }, + delOtherVisitedViews(view: any) { + return new Promise((resolve) => { + this.visitedViews = this.visitedViews.filter((v) => { + return v.meta?.affix || v.path === view.path; + }); + resolve([...this.visitedViews]); + }); + }, + delOtherCachedViews(view: any) { + return new Promise((resolve) => { + const index = this.cachedViews.indexOf(view.name); + if (index > -1) { + this.cachedViews = this.cachedViews.slice(index, index + 1); + } else { + // if index = -1, there is no cached tags + this.cachedViews = []; + } + resolve([...this.cachedViews]); + }); + }, + + updateVisitedView(view: any) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view); + break; + } + } + }, + addView(view: any) { + this.addVisitedView(view); + this.addCachedView(view); + }, + delView(view: any) { + return new Promise((resolve) => { + this.delVisitedView(view); + this.delCachedView(view); + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews], + }); + }); + }, + delOtherViews(view: any) { + return new Promise((resolve) => { + this.delOtherVisitedViews(view); + this.delOtherCachedViews(view); + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews], + }); + }); + }, + delLeftViews(view: any) { + return new Promise((resolve) => { + const currIndex = this.visitedViews.findIndex( + (v) => v.path === view.path + ); + if (currIndex === -1) { + return; + } + this.visitedViews = this.visitedViews.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index >= currIndex || (item.meta && item.meta.affix)) { + return true; + } + + const cacheIndex = this.cachedViews.indexOf(item.name as string); + if (cacheIndex > -1) { + this.cachedViews.splice(cacheIndex, 1); + } + return false; + }); + resolve({ + visitedViews: [...this.visitedViews], + }); + }); + }, + delRightViews(view: any) { + return new Promise((resolve) => { + const currIndex = this.visitedViews.findIndex( + (v) => v.path === view.path + ); + if (currIndex === -1) { + return; + } + this.visitedViews = this.visitedViews.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index <= currIndex || (item.meta && item.meta.affix)) { + return true; + } + + const cacheIndex = this.cachedViews.indexOf(item.name as string); + if (cacheIndex > -1) { + this.cachedViews.splice(cacheIndex, 1); + } + return false; + }); + resolve({ + visitedViews: [...this.visitedViews], + }); + }); + }, + delAllViews() { + return new Promise((resolve) => { + const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix); + this.visitedViews = affixTags; + this.cachedViews = []; + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews], + }); + }); + }, + delAllVisitedViews() { + return new Promise((resolve) => { + const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix); + this.visitedViews = affixTags; + resolve([...this.visitedViews]); + }); + }, + delAllCachedViews() { + return new Promise((resolve) => { + this.cachedViews = []; + resolve([...this.cachedViews]); + }); + }, + }, +}); + +export default useTagsViewStore; diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..2310e32 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,127 @@ +import { defineStore } from 'pinia'; +import { LoginFormData } from '@/types/api/system/login'; +import { UserState } from '@/types/store/user'; +import { ElMessage } from 'element-plus'; + +import { localStorage } from '@/utils/storage'; +import { login, logout } from '@/api/login'; +import { getUserInfo } from '@/api/system/user'; +import { resetRouter } from '@/router'; +import {CODES, CONFIG_DATA} from "@/utils/code"; + + +const useUserStore = defineStore({ + id: 'user', + state: (): UserState => ({ + token: localStorage.get('access_token') || '', + nickname: '', + avatar: '', + roles: [], + perms: [], + }), + actions: { + async RESET_STATE() { + this.$reset(); + }, + /** + * 登录 + */ + login(loginData: LoginFormData) { + const { username, password } = loginData; + return new Promise((resolve, reject) => { + login({ + username: username.trim(), + password: password, + }) + .then((response) => { + console.log('登录',response) + // @ts-ignore + const { code, data, message} = response; + if (code === CODES.ok.code) { + console.log(data.token) + localStorage.set(CONFIG_DATA.autoKey, data.token); + localStorage.set(CONFIG_DATA.autoRefreshKey, data.token); + // @ts-ignore + this.token = data.token; + // this.nickname = data.info.username; + // this.avatar = data.info.avatar; + // this.roles = data.info.roles; + // this.perms = perms; + + resolve(data); + } else { + ElMessage.error(message) + // @ts-ignore + return reject(message); + } + }) + .catch((error) => { + reject(error); + }); + }); + }, + /** + * 获取用户信息(昵称、头像、角色集合、权限集合) + */ + getUserInfo() { + return new Promise((resolve, reject) => { + getUserInfo() + .then(({ data }) => { + if (!data) { + return reject('Verification failed, please Login again.'); + } + const { name, avatar, perms } = data; + this.nickname = name; + this.avatar = avatar; + this.roles = ['ADMIN']; + this.perms = perms; + + resolve(data); + }) + .catch((error) => { + reject(error); + }); + }); + }, + + /** + * 注销 + */ + logout() { + return new Promise((resolve, reject) => { + logout() + .then(() => { + localStorage.remove(CONFIG_DATA.autoKey); + localStorage.remove(CONFIG_DATA.autoRefreshKey); + localStorage.remove(CONFIG_DATA.authKey); + this.RESET_STATE(); + resetRouter(); + resolve(null); + }) + .catch((error) => { + localStorage.remove(CONFIG_DATA.autoKey); + localStorage.remove(CONFIG_DATA.autoRefreshKey); + localStorage.remove(CONFIG_DATA.authKey); + this.RESET_STATE(); + resetRouter(); + resolve(null); + }); + }); + }, + + /** + * 清除 Token + */ + resetToken() { + return new Promise((resolve) => { + localStorage.remove(CONFIG_DATA.autoKey); + localStorage.remove(CONFIG_DATA.autoRefreshKey); + localStorage.remove(CONFIG_DATA.authKey); + this.RESET_STATE(); + resolve(null); + }); + }, + }, +}); + +export default useUserStore; diff --git a/src/styles/element-plus.scss b/src/styles/element-plus.scss new file mode 100644 index 0000000..dbe38db --- /dev/null +++ b/src/styles/element-plus.scss @@ -0,0 +1,79 @@ +:root { + // 这里可以设置你自定义的颜色变量 + // 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改 + --el-color-primary-dark: #0d84ff; + // element plus 2.1.0 禁用文本色值和正常文本色值无法区分问题 + --el-text-color-disabled: #ccc; +} + +// 覆盖 element-plus 的样式 +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type='file'] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +// dropdown +.el-dropdown-menu { + a { + display: block; + } +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +// 选中行背景色值 +.el-table__body tr.current-row td { + background-color: #e1f3d8b5 !important; +} + +// card 的header统一高度 +.el-card__header { + height: 60px !important; +} + +// 表格表头和表体未对齐 +.el-table__header col[name='gutter'] { + display: table-cell !important; +} + + +// 仅对「实心按钮」补边框/背景;text、link 需同时排除(逗号分隔的 :not 会误伤 link) +.el-button:not(.is-text):not(.is-link):not(.el-button--text) { + background-color: var(--el-button-bg-color); + border: var(--el-border); + border-color: var(--el-button-border-color) +} + +.el-button:not(.is-text):not(.is-link):not(.el-button--text):focus, +.el-button:not(.is-text):not(.is-link):not(.el-button--text):hover { + color: var(--el-button-hover-text-color); + border-color: var(--el-button-hover-border-color); + background-color: var(--el-button-hover-bg-color); + outline: 0 +} + +.el-button:not(.is-text):not(.is-link):not(.el-button--text):active { + color: var(--el-button-active-text-color); + border-color: var(--el-button-active-border-color); + background-color: var(--el-button-active-bg-color); + outline: 0 +} + +.el-button:not(.is-text):not(.is-link):not(.el-button--text):focus-visible { + border-color: transparent; + outline: 2px solid var(--el-button-border-color); + outline-offset: 1px +} diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..90f2d40 --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,68 @@ +@import 'src/styles/variables.module'; +@import './mixin.scss'; +@import './transition.scss'; +@import 'src/styles/element-plus'; +@import './sidebar.scss'; + +body { + margin: 0; + padding: 0; + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, + Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: ' '; + clear: both; + height: 0; + } +} + +// main-container global css +.app-container { + padding: 20px; +} diff --git a/src/styles/mixin.scss b/src/styles/mixin.scss new file mode 100644 index 0000000..3ca7168 --- /dev/null +++ b/src/styles/mixin.scss @@ -0,0 +1,28 @@ +@mixin clearfix { + &:after { + content: ''; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss new file mode 100644 index 0000000..651e763 --- /dev/null +++ b/src/styles/sidebar.scss @@ -0,0 +1,228 @@ +#app { + .main-container { + min-height: 100%; + transition: margin-left 0.28s; + margin-left: $sideBarWidth; + position: relative; + } + + .sidebar-container { + transition: width 0.28s; + width: $sideBarWidth !important; + background-color: $menuBg; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, + 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-sub-menu__title { + &:hover { + background-color: $menuHover !important; + } + } + + .is-active > .el-sub-menu__title { + color: $subMenuActiveText !important; + } + + & .nest-menu .el-sub-menu > .el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: $sideBarWidth !important; + background-color: $subMenuBg !important; + + &:hover { + background-color: $subMenuHover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + .svg-icon { + margin-right: 0px; + } + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + & > .el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + + .el-sub-menu__icon-arrow { + display: none; + } + } + } + + .el-menu--collapse { + .el-sub-menu { + & > .el-sub-menu__title { + & > span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: $sideBarWidth !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform 0.28s; + width: $sideBarWidth !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$sideBarWidth, 0, 0); + } + } + } + + .withoutAnimation { + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + & > .el-menu { + .svg-icon { + margin-right: 16px; + } + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + } + + .nest-menu .el-sub-menu > .el-sub-menu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: $menuHover !important; + } + } + + // the scroll bar appears when the subMenu is too long + > .el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/src/styles/table.scss b/src/styles/table.scss new file mode 100644 index 0000000..7aaaa05 --- /dev/null +++ b/src/styles/table.scss @@ -0,0 +1,42 @@ +.resource-tree-node { + width: 100%; + flex-wrap: wrap; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 14px; + padding-right: 8px; + margin-left: -28px !important; + + &__content { + display: flex; + flex-wrap: wrap; + } + .el-checkbox--default { + background-color: transparent !important; + } +} +.el-tree-node__content { + height: auto !important; +} + +.el-checkbox-group { + display: flex; + flex-wrap: wrap; + &:hover { + background-color: var(--el-tree-node-hover-bg-color); + } + &:active { + background-color: var(--el-tree-node-hover-bg-color); + } +} + +.el-checkbox.el-checkbox--small { + margin: 5px; + z-index: 999; + background: #fff; +} + +.el-tag { + margin-right: 10px; +} diff --git a/src/styles/transition.scss b/src/styles/transition.scss new file mode 100644 index 0000000..b02f60b --- /dev/null +++ b/src/styles/transition.scss @@ -0,0 +1,48 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all 0.5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all 0.5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all 0.5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/src/styles/variables.module.scss b/src/styles/variables.module.scss new file mode 100644 index 0000000..8e20bc3 --- /dev/null +++ b/src/styles/variables.module.scss @@ -0,0 +1,25 @@ +// sidebar +$menuText: #bfcbd9; +$menuActiveText: #409eff; +$subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951 + +$menuBg: #304156; +$menuHover: #263445; + +$subMenuBg: #1f2d3d; +$subMenuHover: #001528; + +$sideBarWidth: 210px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuText: $menuText; + menuActiveText: $menuActiveText; + subMenuActiveText: $subMenuActiveText; + menuBg: $menuBg; + menuHover: $menuHover; + subMenuBg: $subMenuBg; + subMenuHover: $subMenuHover; + sideBarWidth: $sideBarWidth; +} diff --git a/src/types/api/authority/admin.d.ts b/src/types/api/authority/admin.d.ts new file mode 100644 index 0000000..75572b5 --- /dev/null +++ b/src/types/api/authority/admin.d.ts @@ -0,0 +1,56 @@ +import {PageQueryParam} from "@/types/api/base"; + +/** + * 查询参数类型 + */ +export interface QueryParam extends PageQueryParam { + status?: number; + company_id?: number; + size?: number; + name?: string; + auth:string; +} + +export interface Option { + value: string; + label: string; +} + +export interface DataParam { + id: number; + username: string; + avatar: string; + name: string; + phone: string; + email: string; + roles: Array; + last_time: string; + status: number; +} + +/** + * 添加与编辑 + */ +export interface FormParam { + id?: number; + username: string; + name: string; + phone: string; + email:string + password?: string; + roles?: array; + avatar ?: string + company_id ?: number + auth : string +} + +export interface RoleOption { + id: number; + name: string; + items?: RoleOption[]; +} + +export interface PwdOption { + id: number; + password: string; +} \ No newline at end of file diff --git a/src/types/api/authority/index.d.ts b/src/types/api/authority/index.d.ts new file mode 100644 index 0000000..c7e5374 --- /dev/null +++ b/src/types/api/authority/index.d.ts @@ -0,0 +1,85 @@ +import {PageQueryParam} from "@/types/api/base"; + +/** + * 查询参数类型 + */ +export interface QueryParam extends PageQueryParam { + status?: number; + project_id?:number|string; + project:number, + auth:string, + type ?:int +} + +export interface QueParam { + data : any; + code : number + message : string +} + +export interface Option { + value: string; + label: string; + checked?: boolean; + children?: Option[]; +} + +export interface SortName { + id: number; + name:string; + logo:string; +} + +/** + * 详情 + */ +export interface DetailParam { + auth:string; + type:number; + auth_id:number; +} + +/** + * 添加与编辑 + */ +export interface FormParam { + id?: number; + api?: string; + view_path?: string; + path?: string; + remarks?: string; + icon?: string; + name?: string; + sort?: number; + parent_id?: number; + is_show?: number; + identification?:string + project_id:number + remarks?:string + auth:number|string, + type:number, + parent_type:number, +} + +export interface DataParam { + id?: number; + api?: string; + view_path?: string; + path?: string; + icon?: string; + name?: string; + sort?: number; + parent_id?: number; + type?: number; + status?: number; + is_show?: number; + level?: number; + parent_ids?: string; + remarks?: string; + jurisdiction?: string; + create_time?: string; + update_time?: string; + items ?: any; + identification ?: string; + reason ?: string; +} \ No newline at end of file diff --git a/src/types/api/authority/role.d.ts b/src/types/api/authority/role.d.ts new file mode 100644 index 0000000..4459456 --- /dev/null +++ b/src/types/api/authority/role.d.ts @@ -0,0 +1,52 @@ +import {PageQueryParam} from "@/types/api/base"; + +/** + * 查询参数类型 + */ +export interface QueryParam extends PageQueryParam { + status?: number; +} + +export interface QueParam { + data : any; + code : number + message : string +} + +export interface Option { + value: string; + label: string; + checked?: boolean; + children?: Option[]; +} + + +/** + * 添加与编辑 + */ +export interface FormParam { + id?: number; + name?: string; + status?: number; + rules?: array; + auth:string; + project_id:number; +} + +export interface DataParam { + id?: number; + name?: string; + status?: number; + auths?: array; +} + + +export interface AuthDataParam { + id?: number; + value?: number; + label?:string; + id?: number; + name?: string; + status?: number; + auths?: array; +} \ No newline at end of file diff --git a/src/types/api/base.d.ts b/src/types/api/base.d.ts new file mode 100644 index 0000000..38f73e4 --- /dev/null +++ b/src/types/api/base.d.ts @@ -0,0 +1,15 @@ +export interface PageQueryParam { + page ?: number; + size ?: number; +} + +export interface PageResult { + list: T; + total: number; +} + +export interface RepData { + code: number; + data: T; + message : string +} diff --git a/src/types/api/company/project.d.ts b/src/types/api/company/project.d.ts new file mode 100644 index 0000000..417fb3a --- /dev/null +++ b/src/types/api/company/project.d.ts @@ -0,0 +1,31 @@ +import {PageQueryParam} from "@/types/api/base"; + +/** + * 查询参数类型 + */ +export interface QueryParam extends PageQueryParam { + status?: number; + name ?: string; + auth :string; +} + +/** + * 添加与编辑 + */ +export interface FormParam { + id?: number; + principal_id: number; + name: string; + remarks?: string; + auth :string; +} + +export interface DataParam { + id?: number; + name?: number; + status?: number; + remarks?:number; + logo?:number; + reason?:number; + create_time?:string; +} diff --git a/src/types/api/log/login.d.ts b/src/types/api/log/login.d.ts new file mode 100644 index 0000000..d4dd256 --- /dev/null +++ b/src/types/api/log/login.d.ts @@ -0,0 +1,60 @@ +import {PageQueryParam} from "@/types/api/base"; + +/** + * 查询参数类型 + */ +export interface QueryParam extends PageQueryParam { + status?: number; + size?: number; + name?: string; + time?:Array; + auth:string; + username?:string; +} + +export interface DataParam { + id: number; + admin_id: number; + username: string; + name: string; + browser_info: string; + browser_name: string; + browser_version: Array; + ip: string; + status: number; + create_time:string; + reason:string; +} + +/** + * 查询参数类型 + */ +export interface ActionQueryParam extends PageQueryParam { + size?: number; + admin_name?: string; + type?:number; + module_name?:string; + time?:Array; + auth:string; + project_id:number; +} + +export interface ActionDataParam { + id: number; + admin_id: number; + admin_name: string; + type: string; + browser_info: string; + browser_name: string; + browser_version: Array; + ip: string; + content: number; + create_time:string; + module_name:string; + reason:string; +} + +export interface ActionTypes { + id:number; + name:string; +} \ No newline at end of file diff --git a/src/types/api/system/dict.d.ts b/src/types/api/system/dict.d.ts new file mode 100644 index 0000000..f268a39 --- /dev/null +++ b/src/types/api/system/dict.d.ts @@ -0,0 +1,86 @@ +import { PageQueryParam, PageResult } from '../base'; + +/** + * 字典查询参数类型声明 + */ +export interface DictQueryParam extends PageQueryParam { + /** + * 字典名称 + */ + name: string | undefined; +} + +/** + * 字典分页列表项声明 + */ +export interface Dict { + id: number; + code: string; + name: string; + status: number; + remark: string; +} + +/** + * 字典分页项类型声明 + */ +export type DictPageResult = PageResult; + +/** + * 字典表单类型声明 + */ +export interface DictFormTypeData { + id: number | undefined; + name: string; + code: string; + status: number; + remark: string; +} + +/** + * 字典项查询参数类型声明 + */ +export interface DictItemQueryParam extends PageQueryParam { + /** + * 字典项名称 + */ + name?: string; + /** + * 字典类型编码 + */ + typeCode?: string; +} + +/** + * 字典分页列表项声明 + */ +export interface DictItem { + id: number; + name: string; + value: string; + dictCode: string; + sort: number; + status: number; + defaulted: number; + remark?: string; +} + +/** + * 字典分页项类型声明 + */ +export type DictItemPageResult = PageResult; + +/** + * 字典表单类型声明 + */ +export interface DictItemFormData { + id?: number; + typeCode?: string; + typeName?: string; + name: string; + code: string; + value: string; + status: number; + sort: number; + remark: string; +} diff --git a/src/types/api/system/login.d.ts b/src/types/api/system/login.d.ts new file mode 100644 index 0000000..c4f231f --- /dev/null +++ b/src/types/api/system/login.d.ts @@ -0,0 +1,34 @@ +/** + * 登录表单类型声明 + */ +export interface LoginFormData { + username: string; + password: string; +} + +/** + * 登录表单类型声明 + */ +export interface Login2FormData { + name: string; + pwd: string; +} + +/** + * 登录响应类型声明 + */ +export interface LoginResponseData { + refresh_token: any; + access_token:any; + code: number; + data: string, + message: string; +} + +/** + * 验证码类型声明 + */ +export interface Captcha { + img: string; + uuid: string; +} diff --git a/src/types/api/system/menu.d.ts b/src/types/api/system/menu.d.ts new file mode 100644 index 0000000..b2fe6ee --- /dev/null +++ b/src/types/api/system/menu.d.ts @@ -0,0 +1,71 @@ +/** + * 菜单查询参数类型声明 + */ +export interface MenuQueryParam { + name: string; +} + +export interface MenuGetParam { + project_id: number; +} + +/** + * 菜单分页列表项声明 + */ + +export interface MenuItem { + id: number; + parentId: number; + createTime: string; + updateTime: string; + name: string; + icon: string; + component: string; + sort: number; + visible: number; + children: MenuItem[]; +} + +/** + * 菜单表单类型声明 + */ +export interface MenuFormData { + /** + * 菜单ID + */ + id?: string; + /** + * 父菜单ID + */ + parentId: string; + /** + * 菜单名称 + */ + name: string; + /** + * 菜单是否可见(1:是;0:否;) + */ + visible: number; + icon?: string; + /** + * 排序 + */ + sort: number; + /** + * 组件路径 + */ + component?: string; + /** + * 路由路径 + */ + path: string; + /** + * 跳转路由路径 + */ + redirect?: string; + + /** + * 菜单类型(1:菜单;2:目录;3:外链) + */ + type: string; +} diff --git a/src/types/api/system/role.d.ts b/src/types/api/system/role.d.ts new file mode 100644 index 0000000..3ce9a22 --- /dev/null +++ b/src/types/api/system/role.d.ts @@ -0,0 +1,46 @@ +import { PageQueryParam, PageResult } from '../base'; + +/** + * 角色查询参数类型 + */ +export interface RoleQueryParam extends PageQueryParam { + name?: string; +} + +/** + * 角色分页列表项 + */ +export interface RoleItem { + id: string; + name: string; + code: string; + sort: number; + status: number; + deleted: number; + menuIds?: any; + permissionIds?: any; +} + +/** + * 角色分页项类型 + */ +export type RolePageResult = PageResult; + +/** + * 角色表单类型 + */ +export interface RoleFormData { + id: string | undefined; + name: string; + code: string; + sort: number; + status: number; +} + +/** + * + */ +export interface RoleResourceData { + menuIds: string[]; + permIds: string[]; +} diff --git a/src/types/api/system/user.d.ts b/src/types/api/system/user.d.ts new file mode 100644 index 0000000..04019e2 --- /dev/null +++ b/src/types/api/system/user.d.ts @@ -0,0 +1,74 @@ +import internal from 'stream'; +import { PageQueryParam, PageResult } from '../base'; + +/** + * 登录用户类型声明 + */ +export interface UserInfo { + name: string; + avatar: string; + last_time:string; + post_id:internal; + department_id:internal; + email:string; + phone:string; + + roles: string[]; + perms: string[]; +} + +/** + * 用户查询参数类型声明 + */ +export interface UserQueryParam extends PageQueryParam { + keywords: string; + status: number; + deptId: number; +} + +/** + * 用户分页列表项声明 + */ +export interface UserItem { + id: string; + username: string; + nickname: string; + mobile: string; + gender: number; + avatar: string; + email: string; + status: number; + deptName: string; + roleNames: string; + createTime: string; +} + +/** + * 用户分页项类型声明 + */ +export type UserPageResult = PageResult; + +/** + * 用户表单类型声明 + */ +export interface UserFormData { + id: number | undefined; + deptId: number; + username: string; + nickname: string; + password: string; + mobile: string; + email: string; + gender: number; + status: number; + remark: string; + roleIds: number[]; +} + +/** + * 用户导入表单类型声明 + */ +export interface UserImportFormData { + deptId: number; + roleIds: number[]; +} diff --git a/src/types/common.d.ts b/src/types/common.d.ts new file mode 100644 index 0000000..9fb673f --- /dev/null +++ b/src/types/common.d.ts @@ -0,0 +1,17 @@ +/** + * 弹窗类型 + */ +export interface Dialog { + title: string; + visible: boolean; +} + +/** + * 通用组件选择项类型 + */ +export interface Option { + value: number; + label: string; + checked?: boolean; + children?: Option[]; +} diff --git a/src/types/store/!user.d.ts b/src/types/store/!user.d.ts new file mode 100644 index 0000000..3ab1cde --- /dev/null +++ b/src/types/store/!user.d.ts @@ -0,0 +1,12 @@ +export interface UserState { + token: string; + nickname: string; + avatar: string; + id: number | null, //登录用户ID + phone: string, //手机号 + email: string, //邮箱 + create_time: string, //创建时间 + userState: number, //状态 1正常 2禁用 + roles: string[]; + perms: string[]; +} diff --git a/src/types/store/app.d.ts b/src/types/store/app.d.ts new file mode 100644 index 0000000..569fcf9 --- /dev/null +++ b/src/types/store/app.d.ts @@ -0,0 +1,12 @@ +/** + * 系统类型声明 + */ + export interface AppState { + device: string; + sidebar: { + opened: boolean; + withoutAnimation: boolean; + }; + language: string; + size: string; +} diff --git a/src/types/store/permission.d.ts b/src/types/store/permission.d.ts new file mode 100644 index 0000000..c0ec4dc --- /dev/null +++ b/src/types/store/permission.d.ts @@ -0,0 +1,7 @@ +/** + * 权限类型声明 + */ + export interface PermissionState { + routes: RouteRecordRaw[]; + addRoutes: RouteRecordRaw[]; +} diff --git a/src/types/store/setting.d.ts b/src/types/store/setting.d.ts new file mode 100644 index 0000000..e83734d --- /dev/null +++ b/src/types/store/setting.d.ts @@ -0,0 +1,10 @@ +/** + * 设置状态类型声明 + */ + export interface SettingState { + theme: string; + tagsView: boolean; + fixedHeader: boolean; + showSettings: boolean; + sidebarLogo: boolean; +} diff --git a/src/types/store/tagsview.d.ts b/src/types/store/tagsview.d.ts new file mode 100644 index 0000000..ce38807 --- /dev/null +++ b/src/types/store/tagsview.d.ts @@ -0,0 +1,13 @@ +import { RouteLocationNormalized } from 'vue-router'; + +/** + * 标签状态类型声明 + */ +export interface TagView extends Partial { + title?: string; +} + +export interface TagsViewState { + visitedViews: TagView[]; + cachedViews: string[]; +} diff --git a/src/types/store/user.d.ts b/src/types/store/user.d.ts new file mode 100644 index 0000000..698c744 --- /dev/null +++ b/src/types/store/user.d.ts @@ -0,0 +1,7 @@ +export interface UserState { + token: string; + nickname: string; + avatar: string; + roles: string[]; + perms: string[]; +} diff --git a/src/utils/code.ts b/src/utils/code.ts new file mode 100644 index 0000000..90275f6 --- /dev/null +++ b/src/utils/code.ts @@ -0,0 +1,131 @@ +export const CODES = { + ok: { code: 10000, message: '成功' }, + error: { code: 10001, message: '失败' }, + logout: { code: 20004, message: '无法获取登录信息' }, + imageType: { 1: 'logo', 2: 'cover', 3: 'content' }, + stauts: [ + { id: 1, name: '正常' }, + { id: 2, name: '禁用' } + ] +}; + +export const Numbers = { + statusOk: 1, + statusForbidden: 2, + + imageTypeLogo: 1, + imageTypeCover: 2, + imageTypeContent: 3, + + zero: 0, + yes: 1, + no: 2, + + one: 1, + two: 2, + three: 3, + + all: 100000, + + authTypeDir: 1, + authTypeWeb: 2, + authTypeButton: 3, + authTypeUrl: 4, + + errorNoLogin: 10003, + errorJwtError: 10004, + errorJwtExpired: 10005 +}; + +export const CONFIG_DATA = { + projectId: 1, + + authKey: 'auths', + autoKey: 'access_token', + autoRefreshKey: 'refresh_token', + authTypeMenu: 1, + authTypeData: 2, + + //用户 + userListId: 'user:items:data', //用户列表 + userDetailId: 'user:items:detail', //用户详情 + userAuthListId: 'user:items:userauth', //用户授权列表 + userAuthDetailId:'user:items:detail',//用户授权列表详情 + userCustomerId: 'user:items:userCustomer', //用户档案列表 + userDetectionId: 'user:items:detectionList', //用户检测列表 + userAnalyListId: 'user:items:userAnalysisList', //用户分析列表 + userAnalyDetailId: 'user:items:userAnalysisdetail', //用户分析详情 + userAnalyOKlId: 'user:items:userAnalystateok', //用户分析OK + userAnalyNolId: 'user:items:userAnalystateno', //用户分析问题 + userQuestionListId: 'userQuestion:list', //用户问题列表 + userQuestionNoId: 'userQuestion:No', //用户问题退款 + userQuestionOkId: 'userQuestion:OK', //用户问题OK + + //门店 + sotreAuthListId: 'store:authList', //门店授权列表-查看 + storeEditId: 'store:edit', //门店编辑 + storeDeleteId: 'store:delete', //门店删除 + storeListId: 'store:List', //门店列表 + storeAddId: 'store:addstore', //门店添加 + storeAddStoreListId: 'store:addAccountStore', //添加门店时选择的门店列表 + //隐私协议 + privacyListId: 'privacy:List', //隐私协议列表 + privacyEditId: 'privacy:edit', //隐私协议列表修改 + privacyDeleteId: 'privacy:delete', //隐私协议列表删除 + privacyAddId: 'privacy:add', //隐私协议列表添加 + privacyTypesId: 'privacy:type', //隐私协议类型 + //图谱说明 + graphDescriptionListId: 'graph:List', //图谱说明列表 + graphDescriptionEditId: 'graph:edit', //图谱说明编辑 + graphDescriptionDeleteId: 'graph:delete', //图谱说明删除 + graphDescriptionAddId: 'graph:add', //图谱说明添加 + graphDescriptionCategoryListId: 'graph:types' //图谱说明分类列表 + + // //权限组 + // authorityListId: 'auth:list', //权限列表 + // authorityEditId: 'auth:edit', //权限编辑 + // authorityDetailId: 'auth:detail', //权限详情 + // authorityTitlesId: 'auth:titles', //权限下拉 + // authorityStatusId: 'auth:status', //权限状态 + + // //公共 + // publicProjectTitlesId: 'project:sortList', //项目下拉 + + // //角色组 + // roleEdidId: 'role:edit', //角色编辑 + // roleStatusId: 'role:status', //角色状态 + // roleListId: 'role:list', //角色列表 + // roleDetailId: 'role:detail', //角色详情 + // roleAuthsTitleId: 'role:auths', //下拉所有角色 + // roleDelId: 'role:del', //角色删除 + + // //部门组 + // departmentListId: 'department:list', //公司列表 + // departmentEditId: 'department:edit', //公司编辑 + // departmentStatusId: 'department:status', //公司状态 + // departmentDetailId: 'department:detail', //公司详情 + + // //项目组 + // projectListId: 'project:list', //项目列表 + // projectDelId: 'project:del', //项目删除 + // projectEditId: 'project:edit', //项目编辑 + // projectStatusId: 'project:status', //项目状态 + // projectDetailId: 'project:detail', //项目详情 + // projectMemberId: 'project:member', //项目成员详情 + // projectMemberEditId: 'project:memberedit', //项目成员编辑 + + // //管理员组 + // adminSortListId: 'admin:sortList', //管理员下拉 + // adminListId: 'admin:list', //管理员列表 + // adminEditId: 'admin:edit', //管理员编辑 + // adminStatusId: 'admin:status', //管理员状态 + // adminDelId: 'admin:del', //管理员删除, 普通管理员有权限 + // adminComapnyId: 'admin:company', //管理员公司下拉,超级管理员有权限 + // adminRoleId: 'admin:role', //管理员角色 + // adminDetailId: 'admin:detail', //管理员详情 + // adminPasswordId: 'admin:password', //权限添加 + + // //日志 + // logLoginId: 'log:login', //登录日志 + // logActionId: 'log:action' //操作日志 +}; diff --git a/src/utils/filter.ts b/src/utils/filter.ts new file mode 100644 index 0000000..31a84df --- /dev/null +++ b/src/utils/filter.ts @@ -0,0 +1,104 @@ +import { t } from "@wangeditor/editor"; +import { CONFIG_DATA } from "./code"; +import { localStorage } from "./storage"; + +/** + * Show plural label if time is plural number + * @param {number} time + * @param {string} label + * @return {string} + */ +function pluralize(time: number, label: string) { + if (time === 1) { + return time + label; + } + return time + label + 's'; +} + +/** + * @param {number} time + */ +export function timeAgo(time: number) { + const between = Date.now() / 1000 - Number(time); + if (between < 3600) { + return pluralize(~~(between / 60), ' minute'); + } else if (between < 86400) { + return pluralize(~~(between / 3600), ' hour'); + } else { + return pluralize(~~(between / 86400), ' day'); + } +} + +/** + * Number formatting + * like 10000 => 10k + * @param {number} num + * @param {number} digits + */ +export function numberFormatter(num: number, digits: number) { + const si = [ + { value: 1e18, symbol: 'E' }, + { value: 1e15, symbol: 'P' }, + { value: 1e12, symbol: 'T' }, + { value: 1e9, symbol: 'G' }, + { value: 1e6, symbol: 'M' }, + { value: 1e3, symbol: 'k' } + ]; + for (let i = 0; i < si.length; i++) { + if (num >= si[i].value) { + return ( + (num / si[i].value) + .toFixed(digits) + .replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol + ); + } + } + return num.toString(); +} + +/** + * 10000 => "10,000" + * @param {number} num + */ +export function toThousandFilter(num: number) { + return (+num || 0) + .toString() + .replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')); +} + +/** + * Upper case first char + * @param {String} string + */ +export function uppercaseFirst(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +/** + * 金额转换(分->元) + * 100 => 1 + * @param {number} num + */ +export function moneyFormatter(num: number) { + return '¥' + (isNaN(num) ? 0.0 : parseFloat((num / 100).toFixed(2))); +} + + +export function getAuthByKey(key: number|string) { + var auths = localStorage.get(CONFIG_DATA.authKey) + return auths[key] ? auths[key] : '' +} + +/** + * 判断是否有某个权限 + * @param key 权限标识 + * @returns boolean 是否有权限 + */ +export function hasAuth(key: string | number): boolean { + const auths = localStorage.get(CONFIG_DATA.authKey) || {}; + return auths[key] !== undefined && auths[key] !== ''; +} + +export function getParams(data: {}, key: number|string, type:number) { + +} \ No newline at end of file diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 0000000..b95552a --- /dev/null +++ b/src/utils/i18n.ts @@ -0,0 +1,12 @@ +// translate router.meta.title, be used in breadcrumb sidebar tagsview +import i18n from '@/lang/index'; + +export function generateTitle(title: any) { + // 判断是否存在国际化配置,如果没有原生返回 + const hasKey = i18n.global.te('route.' + title); + if (hasKey) { + const translatedTitle = i18n.global.t('route.' + title); + return translatedTitle; + } + return title; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..fee5a09 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,47 @@ +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele: HTMLElement, cls: string) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele: HTMLElement, cls: string) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls; +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele: HTMLElement, cls: string) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); + ele.className = ele.className.replace(reg, ' '); + } +} + +export function mix(color1: string, color2: string, weight: number) { + weight = Math.max(Math.min(Number(weight), 1), 0); + const r1 = parseInt(color1.substring(1, 3), 16); + const g1 = parseInt(color1.substring(3, 5), 16); + const b1 = parseInt(color1.substring(5, 7), 16); + const r2 = parseInt(color2.substring(1, 3), 16); + const g2 = parseInt(color2.substring(3, 5), 16); + const b2 = parseInt(color2.substring(5, 7), 16); + const r = Math.round(r1 * (1 - weight) + r2 * weight); + const g = Math.round(g1 * (1 - weight) + g2 * weight); + const b = Math.round(b1 * (1 - weight) + b2 * weight); + const rStr = ('0' + (r || 0).toString(16)).slice(-2); + const gStr = ('0' + (g || 0).toString(16)).slice(-2); + const bStr = ('0' + (b || 0).toString(16)).slice(-2); + return '#' + rStr + gStr + bStr; +} diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..f21eb8f --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,163 @@ +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; +import { ElMessage, ElMessageBox, parseDate } from 'element-plus'; +import { localStorage } from '@/utils/storage'; +import useStore from '@/store'; +import { CONFIG_DATA, Numbers } from './code'; +import CryptoJS from 'crypto-js'; + +//解密 +const originalKey = 't9T3lustIMW38BZtoaTiA8kFN6u2OEKL'; +function GetEncrypt(key: string, ...nums: number[]) { + let str = key; + for (let num of nums) { + num = num % str.length; + if (num > 0) { + str = str.slice(num) + str.slice(0, num); + } else { + num = Math.abs(num); + str = str.slice(-num) + str.slice(0, -num); + } + } + return str; +} +const aesKey = GetEncrypt(originalKey, 15, 28, 18); + +function Decrypt(data: string, iv: string) { + try { + const decrypted = CryptoJS.AES.decrypt( + data, + CryptoJS.enc.Utf8.parse(aesKey), + { + iv: CryptoJS.enc.Utf8.parse(iv), + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + } + ); + const decryptedText = decrypted.toString(CryptoJS.enc.Utf8); + return decryptedText; + } catch (e) { + console.error('解密错误:', e); + return data; + } +} + +//清理特殊字符 +function cleanDecryptedText(str: string) { + return str.replace(/[\x00-\x1F\x7F-\x9F]/g, '').trim(); +} +//解密结束 + +// 创建 axios 实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 50000, + headers: { 'Content-Type': 'application/json;charset=utf-8' } +}); + +// 请求拦截器 +service.interceptors.request.use( + (config: AxiosRequestConfig) => { + if (!config.headers) { + throw new Error( + `Expected 'config' and 'config.headers' not to be undefined` + ); + } + const { user } = useStore(); + if (user.token) { + config.headers.Authorization = `${localStorage.get( + 'access_token' + )}`; + config.headers.Project = CONFIG_DATA.projectId + ''; + } + + if ( + typeof config.data != 'undefined' && + typeof config.data.auth != 'undefined' + ) { + config.headers.Auth = config.data.auth; + delete config.data.auth + } + + return config; + }, + error => { + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + // 检查业务层面的错误 code(不是 HTTP 错误) + const resCode = response.data?.code; + if (resCode !== undefined && resCode !== 10000) { + const errMsg = response.data?.msg || response.data?.message || '请求失败'; + // 未登录相关的 code,提示后跳转登录页 + if ( + resCode == Numbers.errorNoLogin || + resCode == Numbers.errorJwtExpired || + resCode == Numbers.errorJwtError + ) { + localStorage.clear(); + ElMessageBox.alert('当前页面已失效,请重新登录', '提示', { + confirmButtonText: '确定', + callback: () => { + window.location.href = '/'; + } + }).catch(() => window.location.href = '/'); + } else { + ElMessage.error(errMsg); + } + return Promise.reject(new Error(errMsg)); + } + + if (response.data.data && response.data.data.iv) { + const encryptedData = response.data.data.data; + const iv = response.data.data.iv; + const decrypted = Decrypt(encryptedData, iv); + const cleanedResult = cleanDecryptedText(decrypted); + try { + const parsedData = JSON.parse(cleanedResult); + const result = { + code: response.data.code, + message: response.data.message, + data: parsedData, + meta:response.data.meta + }; + console.log('解析数据', result); + return result; + } catch (e) { + console.log(e,'e') + throw new Error('登录数据解析失败,请重试'); + } + } + return response.data; + }, + error => { + const { code, message, msg, data } = error.response || {}; + console.log('请求错误响应:', error.response, 'code:', code, 'message:', message, 'msg:', msg); + + const msgText = msg || message || '系统出错'; + //没有登录或失效 + if ( + code == Numbers.errorNoLogin || + code == Numbers.errorJwtExpired || + code == Numbers.errorJwtError + ) { + // token 过期 + localStorage.clear(); // 清除浏览器全部缓存 + window.location.href = '/'; // 跳转登录页 + ElMessageBox.alert('当前页面已失效,请重新登录', '提示', {}); + } else { + ElMessage({ + message: msgText, + type: 'error', + duration: 0 + }); + } + return Promise.reject(new Error(msgText)); + } +); + +// 导出 axios 实例 +export default service; diff --git a/src/utils/resize.ts b/src/utils/resize.ts new file mode 100644 index 0000000..343bb0f --- /dev/null +++ b/src/utils/resize.ts @@ -0,0 +1,73 @@ +import { ref } from 'vue'; +export default function () { + const chart = ref(); + const sidebarElm = ref(); + + const chartResizeHandler = () => { + if (chart.value) { + chart.value.resize(); + } + }; + + const sidebarResizeHandler = (e: TransitionEvent) => { + if (e.propertyName === 'width') { + chartResizeHandler(); + } + }; + + const initResizeEvent = () => { + window.addEventListener('resize', chartResizeHandler, {passive:true}); + }; + + const destroyResizeEvent = () => { + window.removeEventListener('resize', chartResizeHandler); + }; + + const initSidebarResizeEvent = () => { + sidebarElm.value = document.getElementsByClassName('sidebar-container')[0]; + if (sidebarElm.value) { + sidebarElm.value.addEventListener( + 'transitionend', + sidebarResizeHandler as EventListener, + {passive:true} + ); + } + }; + + const destroySidebarResizeEvent = () => { + if (sidebarElm.value) { + sidebarElm.value.removeEventListener( + 'transitionend', + sidebarResizeHandler as EventListener + ); + } + }; + + const mounted = () => { + initResizeEvent(); + initSidebarResizeEvent(); + }; + + const beforeDestroy = () => { + destroyResizeEvent(); + destroySidebarResizeEvent(); + }; + + const activated = () => { + initResizeEvent(); + initSidebarResizeEvent(); + }; + + const deactivated = () => { + destroyResizeEvent(); + destroySidebarResizeEvent(); + }; + + return { + chart, + mounted, + beforeDestroy, + activated, + deactivated + }; +} diff --git a/src/utils/scroll-to.ts b/src/utils/scroll-to.ts new file mode 100644 index 0000000..591e3ec --- /dev/null +++ b/src/utils/scroll-to.ts @@ -0,0 +1,69 @@ +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2; + if (t < 1) { + return (c / 2) * t * t + b; + } + t--; + return (-c / 2) * (t * (t - 2) - 1) + b; +}; + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +const requestAnimFrame = (function () { + return ( + window.requestAnimationFrame || + (window as any).webkitRequestAnimationFrame || + (window as any).mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + } + ); +})(); + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +const move = (amount: number) => { + document.documentElement.scrollTop = amount; + (document.body.parentNode as HTMLElement).scrollTop = amount; + document.body.scrollTop = amount; +}; + +const position = () => { + return ( + document.documentElement.scrollTop || + (document.body.parentNode as HTMLElement).scrollTop || + document.body.scrollTop + ); +}; + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export const scrollTo = (to: number, duration: number, callback?: any) => { + const start = position(); + const change = to - start; + const increment = 20; + let currentTime = 0; + duration = typeof duration === 'undefined' ? 500 : duration; + const animateScroll = function () { + // increment the time + currentTime += increment; + // find the value with the quadratic in-out easing function + const val = easeInOutQuad(currentTime, start, change, duration); + // move the document.body + move(val); + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll); + } else { + if (callback && typeof callback === 'function') { + // the animation is done so lets callback + callback(); + } + } + }; + animateScroll(); +}; diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..131a181 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,55 @@ +/** + * window.localStorage 浏览器永久缓存 + */ +export const localStorage = { + // 设置永久缓存 + set(key: string, val: any) { + window.localStorage.setItem(key, JSON.stringify(val)); + }, + // 获取永久缓存 + get(key: string) { + const json: any = window.localStorage.getItem(key); + if (json === null || json === undefined) return null; + try { + return JSON.parse(json); + } catch { + return json; // 如果不是 JSON 格式,直接返回原始字符串 + } + }, + // 移除永久缓存 + remove(key: string) { + window.localStorage.removeItem(key); + }, + // 移除全部永久缓存 + clear() { + window.localStorage.clear(); + } +}; + +/** + * window.sessionStorage 浏览器临时缓存 + */ +export const sessionStorage = { + // 设置临时缓存 + set(key: string, val: any) { + window.sessionStorage.setItem(key, JSON.stringify(val)); + }, + // 获取临时缓存 + get(key: string) { + const json: any = window.sessionStorage.getItem(key); + if (json === null || json === undefined) return null; + try { + return JSON.parse(json); + } catch { + return json; // 如果不是 JSON 格式,直接返回原始字符串 + } + }, + // 移除临时缓存 + remove(key: string) { + window.sessionStorage.removeItem(key); + }, + // 移除全部临时缓存 + clear() { + window.sessionStorage.clear(); + } +}; diff --git a/src/utils/userArchiveImages.ts b/src/utils/userArchiveImages.ts new file mode 100644 index 0000000..9bc1e38 --- /dev/null +++ b/src/utils/userArchiveImages.ts @@ -0,0 +1,253 @@ +/** + * 用户档案 / 检测详情里的 images 结构解析(与弹窗详情共用) + */ + +export type StateAngles = { left: string; front: string; right: string }; + +export interface ImageEntry { + key: string; + label: string; + angles: StateAngles; + thumbUrl: string; +} + +const IMAGE_LABELS: Record = { + standard: '标准', + brown_area: '棕区', + intersection: '交叉偏振', + parallel: '平行偏振', + red_zone: '红区图', + red_zone_heat: '红区热力', + redness: '红血丝', + sunlight: '日光图', + ultraviolet: '紫外线', + uv_spot: 'UV斑', +}; + +const THUMB_ORDER = [ + 'standard', + 'brown_area', + 'intersection', + 'parallel', + 'red_zone', + 'red_zone_heat', + 'redness', + 'sunlight', + 'ultraviolet', + 'uv_spot', +]; + +function normUrl(u: unknown): string { + return u && String(u).trim() ? String(u).trim() : ''; +} + +function isFlatStateUrlMap(obj: unknown): obj is Record { + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false; + const vals = Object.values(obj); + if (!vals.length) return false; + return vals.every((v) => typeof v === 'string' || v == null || v === ''); +} + +function mergeAngles(a: StateAngles, b: StateAngles): StateAngles { + return { + left: a.left || b.left, + front: a.front || b.front, + right: a.right || b.right, + }; +} + +function mergeAngleMaps(a: Map, b: Map): Map { + const keys = new Set([...a.keys(), ...b.keys()]); + const out = new Map(); + for (const k of keys) { + const merged = mergeAngles( + a.get(k) ?? { left: '', front: '', right: '' }, + b.get(k) ?? { left: '', front: '', right: '' } + ); + if (merged.left || merged.front || merged.right) out.set(k, merged); + } + return out; +} + +function sortImageEntries(entries: ImageEntry[]): ImageEntry[] { + const byKey = new Map(entries.map((e) => [e.key, e])); + const known: ImageEntry[] = []; + const used = new Set(); + for (const key of THUMB_ORDER) { + const e = byKey.get(key); + if (e) { + used.add(key); + known.push(e); + } + } + const rest = entries.filter((e) => !used.has(e.key)).sort((a, b) => a.key.localeCompare(b.key)); + return [...known, ...rest]; +} + +function buildFromImagesFrontAndImagesNested(d: any): ImageEntry[] { + const imgs = d?.images; + const imx = d?.imagex; + const thumbA = imgs?.front; + const thumbB = imx?.front; + const tripleA = imgs?.images; + const tripleB = imx?.images; + + const thumbFlatA = thumbA && typeof thumbA === 'object' && !Array.isArray(thumbA) && isFlatStateUrlMap(thumbA); + const thumbFlatB = thumbB && typeof thumbB === 'object' && !Array.isArray(thumbB) && isFlatStateUrlMap(thumbB); + const hasTripleA = tripleA && typeof tripleA === 'object' && !Array.isArray(tripleA); + const hasTripleB = tripleB && typeof tripleB === 'object' && !Array.isArray(tripleB); + + if (!thumbFlatA && !thumbFlatB && !hasTripleA && !hasTripleB) return []; + + const keys = new Set(); + if (thumbFlatA) Object.keys(thumbA as object).forEach((k) => keys.add(k)); + if (thumbFlatB) Object.keys(thumbB as object).forEach((k) => keys.add(k)); + if (hasTripleA) Object.keys(tripleA as object).forEach((k) => keys.add(k)); + if (hasTripleB) Object.keys(tripleB as object).forEach((k) => keys.add(k)); + + const thumbFor = (key: string) => + normUrl(thumbFlatA ? (thumbA as Record)[key] : '') || + normUrl(thumbFlatB ? (thumbB as Record)[key] : ''); + + const tripleFor = (key: string): StateAngles => { + const pick = (root: unknown) => { + if (!root || typeof root !== 'object' || Array.isArray(root)) return { left: '', front: '', right: '' }; + const tri = (root as Record)[key]; + if (!tri || typeof tri !== 'object' || Array.isArray(tri)) return { left: '', front: '', right: '' }; + const o = tri as Record; + return { left: normUrl(o.left), front: normUrl(o.front), right: normUrl(o.right) }; + }; + return mergeAngles(pick(tripleA), pick(tripleB)); + }; + + const entries: ImageEntry[] = []; + for (const key of keys) { + const thumbUrl = thumbFor(key); + const tri = tripleFor(key); + const angles: StateAngles = { left: tri.left, front: tri.front || thumbUrl, right: tri.right }; + if (!thumbUrl && !angles.left && !angles.front && !angles.right) continue; + entries.push({ + key, + label: IMAGE_LABELS[key] || key, + angles, + thumbUrl: thumbUrl || tri.front || tri.left || tri.right, + }); + } + return entries.length ? sortImageEntries(entries) : []; +} + +function buildFromAngleRootMapsMap(d: any): Map { + const pick = (angle: 'left' | 'front' | 'right', stateKey: string) => + normUrl(d?.images?.[angle]?.[stateKey]) || normUrl(d?.imagex?.[angle]?.[stateKey]); + const frontObj = d?.images?.front ?? d?.imagex?.front; + const leftObj = d?.images?.left ?? d?.imagex?.left; + const rightObj = d?.images?.right ?? d?.imagex?.right; + + const keys = new Set(); + [leftObj, rightObj].forEach((m) => { + if (m && typeof m === 'object' && !Array.isArray(m)) Object.keys(m as object).forEach((k) => keys.add(k)); + }); + if (frontObj && typeof frontObj === 'object' && !Array.isArray(frontObj) && !isFlatStateUrlMap(frontObj)) { + Object.keys(frontObj as object).forEach((k) => keys.add(k)); + } + + const map = new Map(); + for (const key of keys) { + const angles: StateAngles = { left: pick('left', key), front: pick('front', key), right: pick('right', key) }; + if (angles.left || angles.front || angles.right) map.set(key, angles); + } + return map; +} + +function buildFromStateNestedMapsMap(d: any): Map { + const roots = [d?.images, d?.imagex].filter((x) => x && typeof x === 'object' && !Array.isArray(x)) as Record< + string, + unknown + >[]; + const byKey = new Map(); + + function mergeTriplet(stateKey: string, left: string, front: string, right: string) { + if (!left && !front && !right) return; + const prev = byKey.get(stateKey) ?? { left: '', front: '', right: '' }; + byKey.set(stateKey, { + left: prev.left || left, + front: prev.front || front, + right: prev.right || right, + }); + } + + for (const imgRoot of roots) { + for (const [key, val] of Object.entries(imgRoot)) { + if (['left', 'front', 'right'].includes(key)) continue; + if (key === 'images' && val && typeof val === 'object' && !Array.isArray(val)) { + for (const [stateKey, tri] of Object.entries(val as Record)) { + if (!tri || typeof tri !== 'object' || Array.isArray(tri)) continue; + const o = tri as Record; + mergeTriplet(stateKey, normUrl(o.left), normUrl(o.front), normUrl(o.right)); + } + continue; + } + if (!val || typeof val !== 'object' || Array.isArray(val)) continue; + const o = val as Record; + mergeTriplet(key, normUrl(o.left), normUrl(o.front), normUrl(o.right)); + } + } + return byKey; +} + +function mapToImageEntries(map: Map): ImageEntry[] { + const entries: ImageEntry[] = []; + for (const [key, angles] of map) { + if (!angles.left && !angles.front && !angles.right) continue; + entries.push({ + key, + label: IMAGE_LABELS[key] || key, + angles, + thumbUrl: angles.front || angles.left || angles.right, + }); + } + return sortImageEntries(entries); +} + +/** 从单条档案/检测记录解析可切换的分析模式列表 */ +export function buildImageEntries(d: any): ImageEntry[] { + if (!d || typeof d !== 'object') return []; + const primary = buildFromImagesFrontAndImagesNested(d); + if (primary.length) return primary; + const fromAngles = buildFromAngleRootMapsMap(d); + const fromNested = buildFromStateNestedMapsMap(d); + const merged = mergeAngleMaps(fromAngles, fromNested); + if (merged.size) return mapToImageEntries(merged); + const flat = (d?.images?.front ?? d?.imagex?.front) as Record | undefined; + if (!flat || typeof flat !== 'object' || !isFlatStateUrlMap(flat)) return []; + return Object.entries(flat) + .map(([key, url]) => { + const u = normUrl(url); + if (!u) return null; + return { + key, + label: IMAGE_LABELS[key] || key, + angles: { left: '', front: u, right: '' } as StateAngles, + thumbUrl: u, + } as ImageEntry; + }) + .filter((x): x is ImageEntry => x != null); +} + +/** 合并多条解析结果里的模式 key,顺序与缩略条一致 */ +export function orderedImageKeys(...entryLists: ImageEntry[][]): string[] { + const set = new Set(); + for (const list of entryLists) { + for (const e of list) set.add(e.key); + } + const known: string[] = []; + const used = new Set(); + for (const k of THUMB_ORDER) { + if (set.has(k)) { + known.push(k); + used.add(k); + } + } + const rest = [...set].filter((k) => !used.has(k)).sort((a, b) => a.localeCompare(b)); + return [...known, ...rest]; +} diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..bc8ccee --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,12 @@ +/** + * Created by PanJiaChen on 16/11/18. + */ + +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path: string) { + const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path); + return isExternal; +} diff --git a/src/views/GraphDescription/GraphDescriptionList.vue b/src/views/GraphDescription/GraphDescriptionList.vue new file mode 100644 index 0000000..39f52c3 --- /dev/null +++ b/src/views/GraphDescription/GraphDescriptionList.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/src/views/admin/components/editDialog.vue b/src/views/admin/components/editDialog.vue new file mode 100644 index 0000000..89b601e --- /dev/null +++ b/src/views/admin/components/editDialog.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/views/admin/components/editPassword.vue b/src/views/admin/components/editPassword.vue new file mode 100644 index 0000000..9373bce --- /dev/null +++ b/src/views/admin/components/editPassword.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/views/admin/index.vue b/src/views/admin/index.vue new file mode 100644 index 0000000..2246ff3 --- /dev/null +++ b/src/views/admin/index.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/src/views/authority/admin/components/editDialog.vue b/src/views/authority/admin/components/editDialog.vue new file mode 100644 index 0000000..e7517e8 --- /dev/null +++ b/src/views/authority/admin/components/editDialog.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/views/authority/admin/index.vue b/src/views/authority/admin/index.vue new file mode 100644 index 0000000..f41b0a5 --- /dev/null +++ b/src/views/authority/admin/index.vue @@ -0,0 +1,282 @@ + + + + + + \ No newline at end of file diff --git a/src/views/authority/authority/components/editDialog.vue b/src/views/authority/authority/components/editDialog.vue new file mode 100644 index 0000000..6ef0396 --- /dev/null +++ b/src/views/authority/authority/components/editDialog.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/views/authority/authority/index.vue b/src/views/authority/authority/index.vue new file mode 100644 index 0000000..eca0f28 --- /dev/null +++ b/src/views/authority/authority/index.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/src/views/authority/role/components/editDialog.vue b/src/views/authority/role/components/editDialog.vue new file mode 100644 index 0000000..fef5868 --- /dev/null +++ b/src/views/authority/role/components/editDialog.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/views/authority/role/index.vue b/src/views/authority/role/index.vue new file mode 100644 index 0000000..208922c --- /dev/null +++ b/src/views/authority/role/index.vue @@ -0,0 +1,229 @@ + + + + + + \ No newline at end of file diff --git a/src/views/company/project/components/editDialog.vue b/src/views/company/project/components/editDialog.vue new file mode 100644 index 0000000..b18177f --- /dev/null +++ b/src/views/company/project/components/editDialog.vue @@ -0,0 +1,139 @@ + + + + + \ No newline at end of file diff --git a/src/views/company/project/index.vue b/src/views/company/project/index.vue new file mode 100644 index 0000000..09df058 --- /dev/null +++ b/src/views/company/project/index.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/src/views/dashboard/components/Project/index.vue b/src/views/dashboard/components/Project/index.vue new file mode 100644 index 0000000..59a837a --- /dev/null +++ b/src/views/dashboard/components/Project/index.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..584cecb --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,167 @@ + + + + + + + diff --git a/src/views/error-page/401.vue b/src/views/error-page/401.vue new file mode 100644 index 0000000..7210aaa --- /dev/null +++ b/src/views/error-page/401.vue @@ -0,0 +1,107 @@ + + + + + + + + diff --git a/src/views/error-page/404.vue b/src/views/error-page/404.vue new file mode 100644 index 0000000..d4d424e --- /dev/null +++ b/src/views/error-page/404.vue @@ -0,0 +1,269 @@ + + + + + + + + diff --git a/src/views/log/action.vue b/src/views/log/action.vue new file mode 100644 index 0000000..ac851ea --- /dev/null +++ b/src/views/log/action.vue @@ -0,0 +1,254 @@ + + + + + + + \ No newline at end of file diff --git a/src/views/log/login.vue b/src/views/log/login.vue new file mode 100644 index 0000000..22ca6fc --- /dev/null +++ b/src/views/log/login.vue @@ -0,0 +1,193 @@ + + + + + + + \ No newline at end of file diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..1e4562a --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,359 @@ + + + + + + + diff --git a/src/views/privacy/components/PrivacyDialog.vue b/src/views/privacy/components/PrivacyDialog.vue new file mode 100644 index 0000000..29eb948 --- /dev/null +++ b/src/views/privacy/components/PrivacyDialog.vue @@ -0,0 +1,104 @@ + + + diff --git a/src/views/privacy/index.vue b/src/views/privacy/index.vue new file mode 100644 index 0000000..c3946af --- /dev/null +++ b/src/views/privacy/index.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue new file mode 100644 index 0000000..47cad96 --- /dev/null +++ b/src/views/redirect/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/views/store/storeAccountList.vue b/src/views/store/storeAccountList.vue new file mode 100644 index 0000000..7745987 --- /dev/null +++ b/src/views/store/storeAccountList.vue @@ -0,0 +1,300 @@ + + + + + + + diff --git a/src/views/system/menu/components/Menu.vue b/src/views/system/menu/components/Menu.vue new file mode 100644 index 0000000..b6e91c1 --- /dev/null +++ b/src/views/system/menu/components/Menu.vue @@ -0,0 +1,450 @@ + + + diff --git a/src/views/system/menu/components/Perm.vue b/src/views/system/menu/components/Perm.vue new file mode 100644 index 0000000..fe44874 --- /dev/null +++ b/src/views/system/menu/components/Perm.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..b9df512 --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..61a3db1 --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,408 @@ + + + + + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..b76b351 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,841 @@ + + + + + + + diff --git a/src/views/user/archiveDetail.vue b/src/views/user/archiveDetail.vue new file mode 100644 index 0000000..9ab99df --- /dev/null +++ b/src/views/user/archiveDetail.vue @@ -0,0 +1,846 @@ + + + + + diff --git a/src/views/user/index.vue b/src/views/user/index.vue new file mode 100644 index 0000000..46d0bfb --- /dev/null +++ b/src/views/user/index.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/views/user/userAnalysis.vue b/src/views/user/userAnalysis.vue new file mode 100644 index 0000000..44517f7 --- /dev/null +++ b/src/views/user/userAnalysis.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/src/views/user/userDetection.vue b/src/views/user/userDetection.vue new file mode 100644 index 0000000..152c85a --- /dev/null +++ b/src/views/user/userDetection.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/views/user/userQuestion.vue b/src/views/user/userQuestion.vue new file mode 100644 index 0000000..493a718 --- /dev/null +++ b/src/views/user/userQuestion.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2d3ac31 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, + "allowSyntheticDefaultImports": true, // 默认导入 + "skipLibCheck": true, // 不对第三方依赖类型检查 ,element-plus 生产打包报错 + "types": ["element-plus/global"] + }, + "include": ["src/**/*.ts","src/**/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ab54e0a --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,44 @@ +import { UserConfig, ConfigEnv, loadEnv } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; +import path from 'path'; + +export default ({ mode }: ConfigEnv): UserConfig => { + // 获取 .env 环境配置文件 + const env = loadEnv(mode, process.cwd()); + + return { + define: { + 'process.env': env + }, + plugins: [ + vue(), + createSvgIconsPlugin({ + // 指定需要缓存的图标文件夹 + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], + // 指定symbolId格式 + symbolId: 'icon-[dir]-[name]' + }) + ], + // 本地反向代理解决浏览器跨域限制 + server: { + host: '0.0.0.0', + port: Number(env.VITE_APP_PORT), + open: true, // 运行自动打开浏览器 + proxy: { + [env.VITE_APP_BASE_API]: { + target: 'http://localhost:8001', + changeOrigin: true, + rewrite: path => + path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '') + } + } + }, + resolve: { + // Vite路径别名配置 + alias: { + '@': path.resolve('./src') + } + } + }; +};