├── src ├── assets │ ├── images │ │ └── add-close.png │ ├── 401.png │ ├── 404.png │ ├── lock.jpg │ ├── logo.png │ ├── logo1.png │ ├── gateway.png │ ├── monitor.png │ ├── nil-img.png │ ├── rotate.png │ ├── bg-login.png │ └── gateway_d.png ├── theme │ ├── base.scss │ ├── mixins │ │ ├── function.scss │ │ ├── element-mixins.scss │ │ └── mixins.scss │ ├── media │ │ ├── home.scss │ │ ├── cityLinkage.scss │ │ ├── tagsView.scss │ │ ├── dialog.scss │ │ ├── pagination.scss │ │ ├── personal.scss │ │ ├── media.scss │ │ ├── form.scss │ │ ├── error.scss │ │ ├── index.scss │ │ ├── login.scss │ │ ├── layout.scss │ │ ├── scrollbar.scss │ │ └── chart.scss │ ├── index.scss │ ├── other.scss │ ├── loading.scss │ ├── common │ │ └── transition.scss │ ├── iconSelector.scss │ ├── dark.scss │ ├── listCard.scss │ └── waves.scss ├── stores │ ├── index.ts │ ├── requestOldRoutes.ts │ ├── tagsViewRoutes.ts │ ├── routesList.ts │ ├── keepAliveNames.ts │ ├── userInfos.ts │ └── interface │ │ └── index.ts ├── api │ ├── tool │ │ └── monitor.ts │ ├── menu │ │ └── index.ts │ ├── job │ │ ├── log.ts │ │ └── job.ts │ ├── log │ │ ├── job.ts │ │ ├── oper.ts │ │ └── login.ts │ ├── device │ │ ├── device_cmd.ts │ │ ├── device_alarm.ts │ │ ├── product_ota.ts │ │ ├── product_template.ts │ │ ├── product.ts │ │ ├── device_group.ts │ │ ├── product_category.ts │ │ └── device.ts │ ├── gen │ │ ├── gen.ts │ │ └── table.ts │ ├── login │ │ └── index.ts │ ├── system │ │ ├── notice.ts │ │ ├── post.ts │ │ ├── tenant.ts │ │ ├── dict │ │ │ ├── type.ts │ │ │ └── data.ts │ │ ├── config.ts │ │ ├── api.ts │ │ ├── organization.ts │ │ ├── menu.ts │ │ ├── role.ts │ │ └── user.ts │ └── resource │ │ ├── oss.ts │ │ └── email.ts ├── views │ ├── resource │ │ └── message │ │ │ └── index.vue │ ├── develop │ │ ├── form │ │ │ └── index.vue │ │ ├── api │ │ │ └── index.vue │ │ └── code │ │ │ └── component │ │ │ ├── basicInfoForm.vue │ │ │ └── genInfoForm.vue │ ├── login │ │ └── component │ │ │ ├── scan.vue │ │ │ └── mobile.vue │ ├── home │ │ └── mock.ts │ ├── personal │ │ └── mock.ts │ ├── device │ │ ├── product │ │ │ └── component │ │ │ │ ├── view.vue │ │ │ │ ├── product_info.vue │ │ │ │ └── product_tsl.vue │ │ └── device │ │ │ └── component │ │ │ ├── view.vue │ │ │ ├── device_info.vue │ │ │ └── allot.vue │ ├── tool │ │ └── notice │ │ │ └── component │ │ │ └── viewModule.vue │ ├── error │ │ ├── 404.vue │ │ └── 401.vue │ └── system │ │ └── tenant │ │ └── component │ │ └── editModule.vue ├── i18n │ ├── pages │ │ ├── formI18n │ │ │ ├── zh-cn.ts │ │ │ └── en.ts │ │ ├── home │ │ │ ├── zh-cn.ts │ │ │ └── en.ts │ │ └── login │ │ │ ├── zh-cn.ts │ │ │ └── en.ts │ └── index.ts ├── layout │ ├── main │ │ ├── transverse.vue │ │ ├── classic.vue │ │ ├── columns.vue │ │ └── defaults.vue │ ├── navBars │ │ ├── index.vue │ │ └── breadcrumb │ │ │ ├── closeFull.vue │ │ │ └── userNews.vue │ ├── footer │ │ └── index.vue │ ├── component │ │ ├── header.vue │ │ └── main.vue │ ├── navMenu │ │ ├── subItem.vue │ │ └── vertical.vue │ ├── routerView │ │ ├── link.vue │ │ ├── iframes.vue │ │ └── parent.vue │ ├── index.vue │ └── logo │ │ └── index.vue ├── utils │ ├── directive.ts │ ├── export.ts │ ├── authFunction.ts │ ├── viteBuild.ts │ ├── arrayOperation.ts │ ├── setIconfont.ts │ ├── authDirective.ts │ ├── storage.ts │ ├── wartermark.ts │ ├── loading.ts │ ├── zipdownload.ts │ ├── theme.ts │ ├── request.ts │ ├── commonFunction.ts │ ├── string.ts │ ├── getStyleSheets.ts │ └── other.ts ├── components │ ├── auth │ │ ├── auth.vue │ │ ├── authAll.vue │ │ └── auths.vue │ ├── screenShort │ │ └── index.vue │ ├── svgIcon │ │ └── index.vue │ ├── editor │ │ └── index.vue │ ├── panda │ │ └── MDInput.vue │ └── jessibuca │ │ └── index.vue ├── router │ └── route.ts ├── main.ts └── App.vue ├── public ├── decoder.wasm └── favicon.ico ├── plugins.d.ts ├── .env ├── Dockerfile ├── .eslintignore ├── .env.production ├── source.d.ts ├── .env.development ├── .gitignore ├── shim.d.ts ├── deploy └── default.conf ├── LICENSE ├── .prettierrc.js ├── index.html ├── vite.config.ts ├── .eslintrc.js └── package.json /src/assets/images/add-close.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/theme/base.scss: -------------------------------------------------------------------------------- 1 | @import 'common/transition.scss'; 2 | @import 'common/var.scss'; 3 | -------------------------------------------------------------------------------- /public/decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/public/decoder.wasm -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/401.png -------------------------------------------------------------------------------- /src/assets/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/404.png -------------------------------------------------------------------------------- /src/assets/lock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/lock.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/logo1.png -------------------------------------------------------------------------------- /src/assets/gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/gateway.png -------------------------------------------------------------------------------- /src/assets/monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/monitor.png -------------------------------------------------------------------------------- /src/assets/nil-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/nil-img.png -------------------------------------------------------------------------------- /src/assets/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/rotate.png -------------------------------------------------------------------------------- /src/assets/bg-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/bg-login.png -------------------------------------------------------------------------------- /src/assets/gateway_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaXGO/PandaXUi/HEAD/src/assets/gateway_d.png -------------------------------------------------------------------------------- /plugins.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-grid-layout'; 2 | declare module "splitpanes"; 3 | declare module "vite-plugin-vue-setup-extend"; -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | 3 | // 创建 4 | const pinia = createPinia(); 5 | 6 | // 导出 7 | export default pinia; -------------------------------------------------------------------------------- /src/theme/mixins/function.scss: -------------------------------------------------------------------------------- 1 | /* 颜色调用函数 2 | ------------------------------- */ 3 | @function set-color($key) { 4 | @return var(--color-#{$key}); 5 | } 6 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # port 端口号 2 | VITE_PORT = 7789 3 | 4 | # open 运行 npm run dev 时自动打开浏览器 5 | VITE_OPEN = true 6 | 7 | # public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可 8 | VITE_PUBLIC_PATH = ./ 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.25-alpine 2 | LABEL MAINTAINER="PandaX" 3 | 4 | COPY deploy/default.conf /etc/nginx/conf.d/default.conf 5 | COPY deploy/dist /usr/share/nginx/html/ 6 | 7 | #暴露容器8001端口 8 | EXPOSE 7789 9 | -------------------------------------------------------------------------------- /src/api/tool/monitor.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | // 查询服务器详细 3 | export function getServer() { 4 | return request({ 5 | url: '/system/server', 6 | method: 'get' 7 | }) 8 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | lib 5 | *.md 6 | *.scss 7 | *.woff 8 | *.ttf 9 | .vscode 10 | .idea 11 | dist 12 | mock 13 | public 14 | bin 15 | build 16 | config 17 | index.html 18 | src/assets -------------------------------------------------------------------------------- /src/views/resource/message/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 开发中。。。 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /src/theme/media/home.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于768px 4 | ------------------------------- */ 5 | @media screen and (max-width: $sm) { 6 | .home-warning-media, 7 | .home-dynamic-media { 8 | margin-top: 15px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/theme/media/cityLinkage.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于576px 4 | ------------------------------- */ 5 | @media screen and (max-width: $xs) { 6 | .el-cascader__dropdown.el-popper { 7 | overflow: auto; 8 | max-width: 100%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/theme/media/tagsView.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于768px 4 | ------------------------------- */ 5 | @media screen and (max-width: $sm) { 6 | .tags-view-form { 7 | .tags-view-form-col { 8 | margin-bottom: 20px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境 2 | ENV = 'production' 3 | 4 | # 线上环境接口地址 5 | VITE_API_URL = 'http://193.112.70.4:7788' 6 | 7 | VITE_SCREEN_URL = 'http://193.112.70.4:7790' 8 | VITE_RULE_URL = 'http://193.112.70.4:7791' 9 | VITE_REPORT_URL = 'http://193.112.70.4:9001' 10 | -------------------------------------------------------------------------------- /src/views/develop/form/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /src/i18n/pages/formI18n/zh-cn.ts: -------------------------------------------------------------------------------- 1 | // 定义内容 2 | export default { 3 | formI18nLabel: { 4 | name: '姓名', 5 | email: '用户归属组织', 6 | autograph: '登陆账户名', 7 | }, 8 | formI18nPlaceholder: { 9 | name: '请输入姓名', 10 | email: '请输入用户归属组织', 11 | autograph: '请输入登陆账户名', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/i18n/pages/home/zh-cn.ts: -------------------------------------------------------------------------------- 1 | // 定义内容 2 | export default { 3 | card: { 4 | title1: '消息数量监控', 5 | title2: '环境监测', 6 | title3: '预警信息', 7 | title4: '动态信息', 8 | title5: '履约超时预警', 9 | }, 10 | table: { 11 | th1: '时间', 12 | th2: '设备名称', 13 | th3: '报警内容', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/theme/media/dialog.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于800px 4 | ------------------------------- */ 5 | @media screen and (max-width: 800px) { 6 | .el-dialog { 7 | width: 90% !important; 8 | } 9 | .el-dialog.is-fullscreen { 10 | width: 100% !important; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json'; 2 | declare module '*.png'; 3 | declare module '*.jpg'; 4 | declare module '*.scss'; 5 | declare module '*.ts'; 6 | declare module '*.js'; 7 | declare module '@form-create/element-ui'; 8 | declare module 'codemirror-editor-vue3'; 9 | declare module 'element-plus'; 10 | 11 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 本地环境 2 | ENV = 'development' 3 | 4 | # 本地环境接口地址 5 | VITE_API_URL = 'http://127.0.0.1:7788' 6 | # 监控服务地址 7 | VITE_MEDIA_API_URL = 'http://127.0.0.1:8801' 8 | 9 | VITE_SCREEN_URL = 'http://127.0.0.1:7790' 10 | VITE_RULE_URL = 'http://127.0.0.1:7791' 11 | VITE_REPORT_URL = 'http://127.0.0.1:9001' 12 | -------------------------------------------------------------------------------- /src/theme/index.scss: -------------------------------------------------------------------------------- 1 | @import './app.scss'; 2 | @import './base.scss'; 3 | @import './other.scss'; 4 | @import './element.scss'; 5 | @import './iconSelector.scss'; 6 | @import './media/media.scss'; 7 | @import './waves.scss'; 8 | @import './dark.scss'; 9 | @import './listCard.scss'; 10 | @import './devicePtz.scss'; 11 | -------------------------------------------------------------------------------- /src/api/menu/index.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * 后端控制菜单模拟json,路径在 https://gitee.com/PandaAdmin/PandaX-images/tree/master/menu 5 | * 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由 6 | */ 7 | 8 | // 获取路由 9 | export const getRouters = () => { 10 | return request({ 11 | url: '/getRouters', 12 | method: 'get' 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/i18n/pages/formI18n/en.ts: -------------------------------------------------------------------------------- 1 | // 定义内容 2 | export default { 3 | formI18nLabel: { 4 | name: 'name', 5 | email: 'email', 6 | autograph: 'autograph', 7 | }, 8 | formI18nPlaceholder: { 9 | name: 'Please enter your name', 10 | email: 'Please enter the users Department', 11 | autograph: 'Please enter the login account name', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /deploy/dist 5 | 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/theme/media/pagination.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于576px 4 | ------------------------------- */ 5 | @media screen and (max-width: $xs) { 6 | .el-pager, 7 | .el-pagination__jump { 8 | display: none !important; 9 | } 10 | } 11 | 12 | // 默认居中对齐 13 | .el-pagination { 14 | justify-content: center; 15 | text-align: center !important; 16 | } 17 | -------------------------------------------------------------------------------- /src/theme/media/personal.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于768px 4 | ------------------------------- */ 5 | @media screen and (max-width: $sm) { 6 | .personal-info { 7 | padding-left: 0 !important; 8 | margin-top: 15px; 9 | } 10 | .personal-recommend-col { 11 | margin-bottom: 15px; 12 | &:last-of-type { 13 | margin-bottom: 0; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /shim.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // 声明文件,*.vue 后缀的文件交给 vue 模块来处理 4 | declare module '*.vue' { 5 | import { DefineComponent } from 'vue'; 6 | const component: DefineComponent<{}, {}, any>; 7 | export default component; 8 | } 9 | 10 | // 声明文件,定义全局变量。其它 app.config.globalProperties.xxx,使用 getCurrentInstance() 来获取 11 | interface Window { 12 | nextLoading: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/theme/media/media.scss: -------------------------------------------------------------------------------- 1 | @import './login.scss'; 2 | @import './error.scss'; 3 | @import './layout.scss'; 4 | @import './personal.scss'; 5 | @import './tagsView.scss'; 6 | @import './home.scss'; 7 | @import './chart.scss'; 8 | @import './form.scss'; 9 | @import './scrollbar.scss'; 10 | @import './pagination.scss'; 11 | @import './dialog.scss'; 12 | @import './cityLinkage.scss'; 13 | -------------------------------------------------------------------------------- /deploy/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 7789; 3 | server_name localhost; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html =404; 9 | } 10 | 11 | error_page 500 502 503 504 /50x.html; 12 | location = /50x.html { 13 | root html; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/i18n/pages/home/en.ts: -------------------------------------------------------------------------------- 1 | // 定义内容 2 | export default { 3 | card: { 4 | title1: 'Commodity sales', 5 | title2: 'environmental monitoring', 6 | title3: 'Early warning information', 7 | title4: 'dynamic information', 8 | title5: 'Performance overtime warning', 9 | }, 10 | table: { 11 | th1: 'time', 12 | th2: 'Laboratory name', 13 | th3: 'Alarm content', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/theme/media/form.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于576px 4 | ------------------------------- */ 5 | @media screen and (max-width: $xs) { 6 | .el-form-item__label { 7 | width: 100% !important; 8 | text-align: left !important; 9 | } 10 | .el-form-item__content { 11 | margin-left: 0 !important; 12 | } 13 | .el-form-item { 14 | display: unset !important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/layout/main/transverse.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/utils/directive.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { authDirective } from '@/utils/authDirective'; 3 | import { wavesDirective, dragDirective } from '@/utils/customDirective'; 4 | 5 | /** 6 | * 导出指令方法:v-xxx 7 | * @methods authDirective 用户权限指令,用法:v-auth 8 | * @methods wavesDirective 按钮波浪指令,用法:v-waves 9 | * @methods dragDirective 自定义拖动指令,用法:v-drag 10 | */ 11 | export function directive(app: App) { 12 | // 用户权限指令 13 | authDirective(app); 14 | // 按钮波浪指令 15 | wavesDirective(app); 16 | // 自定义拖动指令 17 | dragDirective(app); 18 | } 19 | -------------------------------------------------------------------------------- /src/api/job/log.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询参数列表分页 4 | export function listJobLog(query:any) { 5 | return request({ 6 | url: '/job/log/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除 13 | export function delJobLog(logId:any) { 14 | return request({ 15 | url: '/job/log/'+ logId, 16 | method: 'delete', 17 | }) 18 | } 19 | 20 | // 查询参数列表分页 21 | export function cleanJobLog() { 22 | return request({ 23 | url: '/job/log/all', 24 | method: 'delete', 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/api/log/job.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询参数列表分页 4 | export function listJobLog(query:any) { 5 | return request({ 6 | url: '/log/logJob/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除 13 | export function delJobLog(logId:any) { 14 | return request({ 15 | url: '/log/logJob/'+ logId, 16 | method: 'delete', 17 | }) 18 | } 19 | 20 | // 查询参数列表分页 21 | export function cleanJobLog() { 22 | return request({ 23 | url: '/log/logJob/all', 24 | method: 'delete', 25 | }) 26 | } -------------------------------------------------------------------------------- /src/api/log/oper.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询参数列表分页 4 | export function listOperInfo(query:any) { 5 | return request({ 6 | url: '/log/logOper/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除 13 | export function delOperInfo(operId:any) { 14 | return request({ 15 | url: '/log/logOper/'+ operId, 16 | method: 'delete', 17 | }) 18 | } 19 | 20 | // 查询参数列表分页 21 | export function cleanOpernfo() { 22 | return request({ 23 | url: '/log/logOper/all', 24 | method: 'delete', 25 | }) 26 | } -------------------------------------------------------------------------------- /src/api/log/login.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询参数列表分页 4 | export function listLoginInfo(query:any) { 5 | return request({ 6 | url: '/log/logLogin/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 删除 13 | export function delLoginInfo(infoId:any) { 14 | return request({ 15 | url: '/log/logLogin/'+ infoId, 16 | method: 'delete', 17 | }) 18 | } 19 | 20 | // 查询参数列表分页 21 | export function cleanLoginInfo() { 22 | return request({ 23 | url: '/log/logLogin/all', 24 | method: 'delete', 25 | }) 26 | } -------------------------------------------------------------------------------- /src/api/device/device_cmd.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询命令下发列表 4 | export function listCmdLog(query:any) { 5 | return request({ 6 | url: '/device/cmd/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | 13 | // 添加命令下发 14 | export function addCmd(data:any) { 15 | return request({ 16 | url: '/device/cmd', 17 | method: 'post', 18 | data: data 19 | }) 20 | } 21 | 22 | // 删除命令下发 23 | export function delCmd(id: string) { 24 | return request({ 25 | url: '/device/cmd/' + id, 26 | method: 'delete' 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/api/gen/gen.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | //视图 4 | export function preview(tableId:any) { 5 | return request({ 6 | url: '/develop/code/gen/preview/'+ tableId, 7 | method: 'get', 8 | }) 9 | } 10 | 11 | // 生成代码 12 | export function code(tableId:any) { 13 | return request({ 14 | url: '/develop/code/gen/code/' + tableId, 15 | method: 'get' 16 | }) 17 | } 18 | 19 | // 生成api菜单 20 | export function menuAndApi(tableId:any,menuId: any) { 21 | return request({ 22 | url: '/develop/code/gen/configure/' + tableId +"?menuId=" + menuId, 23 | method: 'get', 24 | }) 25 | } -------------------------------------------------------------------------------- /src/views/develop/api/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 23 | -------------------------------------------------------------------------------- /src/i18n/pages/login/zh-cn.ts: -------------------------------------------------------------------------------- 1 | // 定义内容 2 | export default { 3 | label: { 4 | one1: '账号密码登录', 5 | two2: '手机号登录', 6 | }, 7 | link: { 8 | one3: '第三方登录', 9 | two4: '友情链接', 10 | }, 11 | copyright: { 12 | one5: '版权所有:山东xxx软件科技有限公司 备案号: 鲁ICP备20000271号-1', 13 | two6: 'Copyright: ShanDong XXX Software Technology 鲁ICP备05010000号', 14 | }, 15 | account: { 16 | accountPlaceholder1: '用户名 admin 或不输均为 test', 17 | accountPlaceholder2: '密码:123456', 18 | accountPlaceholder3: '请输入验证码', 19 | accountBtnText: '登 录', 20 | }, 21 | mobile: { 22 | placeholder1: '请输入手机号', 23 | placeholder2: '请输入验证码', 24 | codeText: '获取验证码', 25 | btnText: '登 录', 26 | }, 27 | signInText: '欢迎回来!', 28 | }; 29 | -------------------------------------------------------------------------------- /src/stores/requestOldRoutes.ts: -------------------------------------------------------------------------------- 1 | import {defineStore} from 'pinia' 2 | import { RequestOldRoutesState} from 'storeInterface'; 3 | 4 | export const useRequestOldRoutesStateStore = defineStore( 5 | 'requestOldRoutes',{ 6 | state: (): RequestOldRoutesState => ({ 7 | requestOldRoutes: [], 8 | }), 9 | actions: { 10 | // 后端控制路由 11 | getBackEndControlRoutes(data: string[]) { 12 | this.requestOldRoutes=data; 13 | }, 14 | // 后端控制路由 15 | setBackEndControlRoutes(routes: Array ){ 16 | // 需要类型转换,先待定 17 | this.getBackEndControlRoutes( routes); 18 | }, 19 | } 20 | 21 | }) 22 | -------------------------------------------------------------------------------- /src/components/auth/auth.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | -------------------------------------------------------------------------------- /src/theme/other.scss: -------------------------------------------------------------------------------- 1 | /* wangeditor富文本编辑器 2 | ------------------------------- */ 3 | .w-e-toolbar { 4 | border: 1px solid #ebeef5 !important; 5 | border-bottom: 1px solid #ebeef5 !important; 6 | border-top-left-radius: 3px; 7 | border-top-right-radius: 3px; 8 | z-index: 2 !important; 9 | } 10 | .w-e-text-container { 11 | border: 1px solid #ebeef5 !important; 12 | border-top: none !important; 13 | border-bottom-left-radius: 3px; 14 | border-bottom-right-radius: 3px; 15 | z-index: 1 !important; 16 | } 17 | 18 | /* web端自定义截屏 19 | ------------------------------- */ 20 | #screenShotContainer { 21 | z-index: 9998 !important; 22 | } 23 | #toolPanel { 24 | height: 42px !important; 25 | } 26 | #optionPanel { 27 | height: 37px !important; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/auth/authAll.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 29 | -------------------------------------------------------------------------------- /src/api/login/index.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | 4 | /** 5 | * 获取验证码 6 | * @param params 要传的参数值 7 | * @returns 返回接口数据 8 | */ 9 | export function captcha() { 10 | return request({ 11 | url: '/system/user/getCaptcha', 12 | method: 'get', 13 | }); 14 | } 15 | 16 | /** 17 | * 用户登录 18 | * @param params 要传的参数值 19 | * @returns 返回接口数据 20 | */ 21 | export function signIn(params: object) { 22 | return request({ 23 | url: '/system/user/login', 24 | method: 'post', 25 | data: params, 26 | }); 27 | } 28 | 29 | /** 30 | * 用户退出登录 31 | * @param params 要传的参数值 32 | * @returns 返回接口数据 33 | */ 34 | export function signOut(params: object) { 35 | return request({ 36 | url: '/system/user/logout', 37 | method: 'post', 38 | data: params, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/api/device/device_alarm.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | 4 | export function getAlarmPanel() { 5 | return request({ 6 | url: '/device/alarm/panel', 7 | method: 'get', 8 | }) 9 | } 10 | 11 | // 查询告警列表 12 | export function listAlarm(query:any) { 13 | return request({ 14 | url: '/device/alarm/list', 15 | method: 'get', 16 | params: query 17 | }) 18 | } 19 | 20 | 21 | // 修改告警 22 | export function updateAlarm(data:any) { 23 | return request({ 24 | url: '/device/alarm', 25 | method: 'put', 26 | data: data 27 | }) 28 | } 29 | 30 | // 删除告警 31 | export function delAlarm(id: string) { 32 | return request({ 33 | url: '/device/alarm/' + id, 34 | method: 'delete' 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/theme/media/error.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于768px 4 | ------------------------------- */ 5 | @media screen and (max-width: $sm) { 6 | .error { 7 | .error-flex { 8 | flex-direction: column-reverse !important; 9 | height: auto !important; 10 | width: 100% !important; 11 | } 12 | .right, 13 | .left { 14 | flex: unset !important; 15 | display: flex !important; 16 | } 17 | .left-item { 18 | margin: auto !important; 19 | } 20 | .right img { 21 | max-width: 450px !important; 22 | @extend .left-item; 23 | } 24 | } 25 | } 26 | 27 | /* 页面宽度大于768px小于992px 28 | ------------------------------- */ 29 | @media screen and (min-width: $sm) and (max-width: $md) { 30 | .error { 31 | .error-flex { 32 | padding-left: 30px !important; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/api/system/notice.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询公告列表 4 | export function listNotice(query:any) { 5 | return request({ 6 | url: '/system/notice/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 新增公告 13 | export function addNotice(data:any) { 14 | return request({ 15 | url: '/system/notice', 16 | method: 'post', 17 | data: data 18 | }) 19 | } 20 | 21 | // 修改公告 22 | export function updateNotice(data:any) { 23 | return request({ 24 | url: '/system/notice', 25 | method: 'put', 26 | data: data 27 | }) 28 | } 29 | 30 | // 删除公告 31 | export function delNotice(noticeId: string) { 32 | return request({ 33 | url: '/system/notice/' + noticeId, 34 | method: 'delete' 35 | }) 36 | } -------------------------------------------------------------------------------- /src/components/auth/auths.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 34 | -------------------------------------------------------------------------------- /src/i18n/pages/login/en.ts: -------------------------------------------------------------------------------- 1 | // 定义内容 2 | export default { 3 | label: { 4 | one1: 'Account password', 5 | two2: 'Mobile number', 6 | }, 7 | link: { 8 | one3: 'Third party login', 9 | two4: 'Links', 10 | }, 11 | copyright: { 12 | one5: 'Copyright: Shenzhen XXX Software Technology Co., Ltd', 13 | two6: 'Copyright: Shenzhen XXX software technology Guangdong ICP preparation no.05010000', 14 | }, 15 | account: { 16 | accountPlaceholder1: 'The user name admin or not is test', 17 | accountPlaceholder2: 'Password: 123456', 18 | accountPlaceholder3: 'Please enter the verification code', 19 | accountBtnText: 'Sign in', 20 | }, 21 | mobile: { 22 | placeholder1: 'Please input mobile phone number', 23 | placeholder2: 'Please enter the verification code', 24 | codeText: 'Get code', 25 | btnText: 'Sign in', 26 | }, 27 | signInText: 'welcome back!', 28 | }; 29 | -------------------------------------------------------------------------------- /src/api/system/post.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询岗位列表 4 | export function listPost(query:any) { 5 | return request({ 6 | url: '/system/post/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询岗位详细 13 | export function getPost(postId:number) { 14 | return request({ 15 | url: '/system/post/' + postId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增岗位 21 | export function addPost(data:any) { 22 | return request({ 23 | url: '/system/post', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改岗位 30 | export function updatePost(data:any) { 31 | return request({ 32 | url: '/system/post', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除岗位 39 | export function delPost(postId: string) { 40 | return request({ 41 | url: '/system/post/' + postId, 42 | method: 'delete' 43 | }) 44 | } -------------------------------------------------------------------------------- /src/stores/tagsViewRoutes.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { TagsViewRoutesState } from 'storeInterface'; 3 | import { Session } from '@/utils/storage'; 4 | export const useTagsViewRoutesStore = defineStore('tagsViewRoutes', { 5 | state: (): TagsViewRoutesState => ({ 6 | tagsViewRoutes: [], 7 | isTagsViewCurrenFull: false, 8 | }), 9 | actions: { 10 | // 设置 TagsView 路由 11 | getTagsViewRoutes( data: Array) { 12 | this.tagsViewRoutes = data; 13 | }, 14 | // 设置卡片全屏 15 | getCurrenFullscreen( bool: boolean) { 16 | Session.set('isTagsViewCurrenFull', bool); 17 | this.isTagsViewCurrenFull = bool; 18 | }, 19 | // 设置 TagsView 路由 20 | async setTagsViewRoutes(data: Array) { 21 | this.getTagsViewRoutes( data); 22 | }, 23 | // 设置卡片全屏 24 | setCurrenFullscreen( bool: boolean) { 25 | this.getCurrenFullscreen( bool); 26 | }, 27 | }, 28 | }); -------------------------------------------------------------------------------- /src/theme/media/index.scss: -------------------------------------------------------------------------------- 1 | /* 栅格布局(媒体查询变量) 2 | * $xs <768px 响应式栅格 3 | * $sm ≥768px 响应式栅格 4 | * $md ≥992px 响应式栅格 5 | * $lg ≥1200px 响应式栅格 6 | * $xl ≥1920px 响应式栅格 7 | ------------------------------- */ 8 | $xs: 576px; 9 | $sm: 768px; 10 | $md: 992px; 11 | $lg: 1200px; 12 | $xl: 1920px; 13 | 14 | /* 页面宽度小于576px 15 | ------------------------------- */ 16 | @media screen and (max-width: $xs) { 17 | } 18 | 19 | /* 页面宽度小于768px 20 | ------------------------------- */ 21 | @media screen and (max-width: $sm) { 22 | } 23 | 24 | /* 页面宽度大于768px小于992px 25 | ------------------------------- */ 26 | @media screen and (min-width: $sm) and (max-width: $md) { 27 | } 28 | 29 | /* 页面宽度大于992px小于1200px 30 | ------------------------------- */ 31 | @media screen and (min-width: $md) and (max-width: $lg) { 32 | } 33 | 34 | /* 页面宽度大于1920px 35 | ------------------------------- */ 36 | @media screen and (min-width: $xl) { 37 | } 38 | -------------------------------------------------------------------------------- /src/theme/media/login.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于576px 4 | ------------------------------- */ 5 | @media screen and (max-width: $xs) { 6 | .login-container { 7 | background: none !important; 8 | .login-logo { 9 | display: none; 10 | } 11 | .login-content { 12 | width: 100% !important; 13 | height: 100% !important; 14 | padding: 20px 0 !important; 15 | border-radius: 0 !important; 16 | box-shadow: unset !important; 17 | border: none !important; 18 | } 19 | .login-copyright { 20 | display: none !important; 21 | } 22 | .el-form-item { 23 | display: flex !important; 24 | } 25 | } 26 | } 27 | 28 | /* 页面宽度小于375px 29 | ------------------------------- */ 30 | @media screen and (max-width: 376px) { 31 | .login-container { 32 | .login-content-title { 33 | font-size: 18px !important; 34 | transition: all 0.3s ease; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/theme/mixins/element-mixins.scss: -------------------------------------------------------------------------------- 1 | /* Button 按钮 2 | ------------------------------- */ 3 | @mixin Button($main, $c1, $c2) { 4 | color: set-color($main); 5 | background: set-color($c1); 6 | border-color: set-color($c2); 7 | } 8 | 9 | /* Radio 单选框、Checkbox 多选框 10 | ------------------------------- */ 11 | @mixin RadioCheckbox($name) { 12 | background-color: set-color($name); 13 | border-color: set-color($name); 14 | } 15 | 16 | /* Tag 标签 17 | ------------------------------- */ 18 | @mixin Tag($main, $c1, $c2) { 19 | color: set-color($main); 20 | background-color: set-color($c1); 21 | border-color: set-color($c2); 22 | } 23 | @mixin TagDark($main, $c1) { 24 | color: set-color($main); 25 | background-color: set-color($c1); 26 | } 27 | 28 | /* Alert 警告 29 | ------------------------------- */ 30 | @mixin Alert($main, $c1, $c2) { 31 | color: set-color($main); 32 | background: set-color($c1); 33 | border: 1px solid set-color($c2); 34 | } 35 | -------------------------------------------------------------------------------- /src/views/login/component/scan.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 28 | 29 | 37 | -------------------------------------------------------------------------------- /src/api/device/product_ota.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询Ota列表 4 | export function listOta(query:any) { 5 | return request({ 6 | url: '/device/ota/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询Ota详细 13 | export function getOta(id:number) { 14 | return request({ 15 | url: '/device/ota/' + id, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增Ota 21 | export function addOta(data:any) { 22 | return request({ 23 | url: '/device/ota', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改Ota 30 | export function updateOta(data:any) { 31 | return request({ 32 | url: '/device/ota', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除Ota 39 | export function delOta(id: string) { 40 | return request({ 41 | url: '/device/ota/' + id, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/export.ts: -------------------------------------------------------------------------------- 1 | import {ElMessage} from "element-plus"; 2 | 3 | export function handleFileError (res:any, fileName:any) { 4 | if (typeof (res.data) !== 'undefined') { 5 | if (res.data.type === 'application/json') { 6 | const reader:any = new FileReader() 7 | reader.onload = function() { 8 | const message = JSON.parse(reader.result).msg 9 | ElMessage({ 10 | showClose: true, 11 | message: message, 12 | type: 'error' 13 | }) 14 | } 15 | reader.readAsText(new Blob([res.data])) 16 | } 17 | } else { 18 | var downloadUrl = window.URL.createObjectURL(new Blob([res])) 19 | var a = document.createElement('a') 20 | a.style.display = 'none' 21 | a.href = downloadUrl 22 | a.download = fileName 23 | var event = new MouseEvent('click') 24 | a.dispatchEvent(event) 25 | } 26 | } -------------------------------------------------------------------------------- /src/utils/authFunction.ts: -------------------------------------------------------------------------------- 1 | 2 | import { judementSameArr } from '@/utils/arrayOperation'; 3 | import { useUserInfosState } from "@/stores/userInfos"; 4 | const userInfos = useUserInfosState(); 5 | /** 6 | * 单个权限验证 7 | * @param value 权限值 8 | * @returns 有权限,返回 `true`,反之则反 9 | */ 10 | export function auth(value: string): boolean { 11 | return userInfos.userInfos.authBtnList.some((v: string) => v === value); 12 | } 13 | 14 | /** 15 | * 多个权限验证,满足一个则为 true 16 | * @param value 权限值 17 | * @returns 有权限,返回 `true`,反之则反 18 | */ 19 | export function auths(value: Array): boolean { 20 | let flag = false; 21 | userInfos.userInfos.authBtnList.map((val: string) => { 22 | value.map((v: string) => { 23 | if (val === v) flag = true; 24 | }); 25 | }); 26 | return flag; 27 | } 28 | 29 | /** 30 | * 多个权限验证,全部满足则为 true 31 | * @param value 权限值 32 | * @returns 有权限,返回 `true`,反之则反 33 | */ 34 | export function authAll(value: Array): boolean { 35 | return judementSameArr(value, userInfos.userInfos.authBtnList); 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/viteBuild.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | 3 | // 定义接口类型声明 4 | export interface ViteEnv { 5 | VITE_PORT: number; 6 | VITE_OPEN: boolean; 7 | VITE_PUBLIC_PATH: string; 8 | } 9 | 10 | /** 11 | * vite 打包相关 12 | * @link 参考:https://cn.vitejs.dev/guide/env-and-mode.html 13 | * @returns 返回 `VITE_xxx` 环境变量和模式信息 14 | */ 15 | export function loadEnv(): ViteEnv { 16 | const env = process.env.NODE_ENV; 17 | const ret: any = {}; 18 | const envList = [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env']; 19 | envList.forEach((e) => { 20 | dotenv.config({ path: e }); 21 | }); 22 | for (const envName of Object.keys(process.env)) { 23 | let realName = (process.env as any)[envName].replace(/\\n/g, '\n'); 24 | realName = realName === 'true' ? true : realName === 'false' ? false : realName; 25 | if (envName === 'VITE_PORT') realName = Number(realName); 26 | if (envName === 'VITE_OPEN') realName = Boolean(realName); 27 | ret[envName] = realName; 28 | process.env[envName] = realName; 29 | } 30 | return ret; 31 | } 32 | -------------------------------------------------------------------------------- /src/stores/routesList.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { RoutesListState } from 'storeInterface'; 3 | export const useRoutesListStore = defineStore('routesList', { 4 | state: (): RoutesListState => ({ 5 | routesList: [], 6 | isColumnsMenuHover: false, 7 | isColumnsNavHover: false, 8 | }), 9 | actions: { 10 | // 设置路由,菜单中使用到 11 | getRoutesList(data: Array) { 12 | this.routesList = data; 13 | }, 14 | // 设置分栏布局,鼠标是否移入移出(菜单) 15 | getColumnsMenuHover(bool: Boolean) { 16 | this.isColumnsMenuHover = bool; 17 | }, 18 | // 设置分栏布局,鼠标是否移入移出(导航) 19 | getColumnsNavHover(bool: Boolean) { 20 | this.isColumnsNavHover = bool; 21 | }, 22 | // 设置路由,菜单中使用到 23 | async setRoutesList(data: any) { 24 | this.getRoutesList( data); 25 | }, 26 | // 设置分栏布局,鼠标是否移入移出(菜单) 27 | async setColumnsMenuHover(bool: Boolean) { 28 | this.getColumnsMenuHover( bool); 29 | }, 30 | // 设置分栏布局,鼠标是否移入移出(菜单) 31 | async setColumnsNavHover(bool: Boolean) { 32 | this.getColumnsNavHover( bool); 33 | }, 34 | }, 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /src/layout/navBars/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 29 | 30 | 38 | -------------------------------------------------------------------------------- /src/layout/footer/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PandaX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 一行最多多少个字符 3 | printWidth: 150, 4 | // 指定每个缩进级别的空格数 5 | tabWidth: 2, 6 | // 使用制表符而不是空格缩进行 7 | useTabs: true, 8 | // 在语句末尾打印分号 9 | semi: true, 10 | // 使用单引号而不是双引号 11 | singleQuote: true, 12 | // 更改引用对象属性的时间 可选值"" 13 | quoteProps: 'as-needed', 14 | // 在JSX中使用单引号而不是双引号 15 | jsxSingleQuote: false, 16 | // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"",默认none 17 | trailingComma: 'es5', 18 | // 在对象文字中的括号之间打印空格 19 | bracketSpacing: true, 20 | // jsx 标签的反尖括号需要换行 21 | jsxBracketSameLine: false, 22 | // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x 23 | arrowParens: 'always', 24 | // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 25 | rangeStart: 0, 26 | rangeEnd: Infinity, 27 | // 指定要使用的解析器,不需要写文件开头的 @prettier 28 | requirePragma: false, 29 | // 不需要自动在文件开头插入 @prettier 30 | insertPragma: false, 31 | // 使用默认的折行标准 always\never\preserve 32 | proseWrap: 'preserve', 33 | // 指定HTML文件的全局空格敏感度 css\strict\ignore 34 | htmlWhitespaceSensitivity: 'css', 35 | // Vue文件脚本和样式标签缩进 36 | vueIndentScriptAndStyle: false, 37 | // 换行符使用 lf 结尾是 可选值"" 38 | endOfLine: 'lf', 39 | }; 40 | -------------------------------------------------------------------------------- /src/layout/main/classic.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 37 | -------------------------------------------------------------------------------- /src/theme/media/layout.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于576px 4 | ------------------------------- */ 5 | @media screen and (max-width: $xs) { 6 | // MessageBox 弹框 7 | .el-message-box { 8 | width: 80% !important; 9 | } 10 | } 11 | 12 | /* 页面宽度小于768px 13 | ------------------------------- */ 14 | @media screen and (max-width: $sm) { 15 | // Breadcrumb 面包屑 16 | .layout-navbars-breadcrumb-hide { 17 | display: none; 18 | } 19 | // 外链视图 20 | .layout-view-link { 21 | a { 22 | max-width: 80%; 23 | text-align: center; 24 | } 25 | } 26 | // 菜单搜索 27 | .layout-search-dialog { 28 | .el-autocomplete { 29 | width: 80% !important; 30 | } 31 | } 32 | } 33 | 34 | /* 页面宽度小于1000px 35 | ------------------------------- */ 36 | @media screen and (max-width: 1000px) { 37 | // 布局配置 38 | .layout-drawer-content-flex { 39 | position: relative; 40 | &::after { 41 | content: '手机版不支持切换布局'; 42 | position: absolute; 43 | top: 0; 44 | right: 0; 45 | bottom: 0; 46 | left: 0; 47 | z-index: 1; 48 | text-align: center; 49 | height: 140px; 50 | line-height: 140px; 51 | background: rgba(255, 255, 255, 0.9); 52 | color: #666666; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/layout/component/header.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 39 | -------------------------------------------------------------------------------- /src/utils/arrayOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断两数组是否相同 3 | * @param news 新数据 4 | * @param old 源数据 5 | * @returns 两数组相同返回 `true`,反之则反 6 | */ 7 | export function judementSameArr(news: Array, old: Array): boolean { 8 | let count = 0; 9 | const leng = old.length; 10 | for (let i in old) { 11 | for (let j in news) { 12 | if (old[i] === news[j]) count++; 13 | } 14 | } 15 | return count === leng ? true : false; 16 | } 17 | 18 | /** 19 | * 判断两个对象是否相同 20 | * @param a 要比较的对象一 21 | * @param b 要比较的对象二 22 | * @returns 相同返回 true,反之则反 23 | */ 24 | export function isObjectValueEqual(a: { [key: string]: any }, b: { [key: string]: any }) { 25 | if (!a || !b) return false; 26 | let aProps = Object.getOwnPropertyNames(a); 27 | let bProps = Object.getOwnPropertyNames(b); 28 | if (aProps.length != bProps.length) return false; 29 | for (let i = 0; i < aProps.length; i++) { 30 | let propName = aProps[i]; 31 | let propA = a[propName]; 32 | let propB = b[propName]; 33 | if (!b.hasOwnProperty(propName)) return false; 34 | if (propA instanceof Object) { 35 | if (!isObjectValueEqual(propA, propB)) return false; 36 | } else if (propA !== propB) { 37 | return false; 38 | } 39 | } 40 | return true; 41 | } 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 15 | 16 | 17 | PandaUi 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/api/system/tenant.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询SysTenants列表分页 4 | export function listSysTenants(query:any) { 5 | return request({ 6 | url: '/system/tenant/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | export function allSysTenants() { 13 | return request({ 14 | url: '/system/tenant/lists', 15 | method: 'get' 16 | }) 17 | } 18 | 19 | // 查询SysTenants详细 20 | export function getSysTenants(tenantId:number) { 21 | return request({ 22 | url: '/system/tenant/' + tenantId, 23 | method: 'get' 24 | }) 25 | } 26 | 27 | // 新增SysTenants 28 | export function addSysTenants(data:any) { 29 | return request({ 30 | url: '/system/tenant', 31 | method: 'post', 32 | data: data 33 | }) 34 | } 35 | 36 | // 修改SysTenants 37 | export function updateSysTenants(data:any) { 38 | return request({ 39 | url: '/system/tenant', 40 | method: 'put', 41 | data: data 42 | }) 43 | } 44 | 45 | // 删除SysTenants 46 | export function delSysTenants(tenantId: string) { 47 | return request({ 48 | url: '/system/tenant/' + tenantId, 49 | method: 'delete' 50 | }) 51 | } -------------------------------------------------------------------------------- /src/utils/setIconfont.ts: -------------------------------------------------------------------------------- 1 | // 字体图标 url 2 | const cssCdnUrlList: Array = [ 3 | '//at.alicdn.com/t/font_2298093_y6u00apwst.css', 4 | '//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css', 5 | ]; 6 | // 第三方 js url 7 | const jsCdnUrlList: Array = []; 8 | 9 | // 动态批量设置字体图标 10 | export function setCssCdn() { 11 | if (cssCdnUrlList.length <= 0) return false; 12 | cssCdnUrlList.map((v) => { 13 | let link = document.createElement('link'); 14 | link.rel = 'stylesheet'; 15 | link.href = v; 16 | link.crossOrigin = 'anonymous'; 17 | document.getElementsByTagName('head')[0].appendChild(link); 18 | }); 19 | } 20 | 21 | // 动态批量设置第三方js 22 | export function setJsCdn() { 23 | if (jsCdnUrlList.length <= 0) return false; 24 | jsCdnUrlList.map((v) => { 25 | let link = document.createElement('script'); 26 | link.src = v; 27 | document.body.appendChild(link); 28 | }); 29 | } 30 | 31 | /** 32 | * 批量设置字体图标、动态js 33 | * @method cssCdn 动态批量设置字体图标 34 | * @method jsCdn 动态批量设置第三方js 35 | */ 36 | const setIntroduction = { 37 | // 设置css 38 | cssCdn: () => { 39 | setCssCdn(); 40 | }, 41 | // 设置js 42 | jsCdn: () => { 43 | setJsCdn(); 44 | }, 45 | }; 46 | 47 | // 导出函数方法 48 | export default setIntroduction; 49 | -------------------------------------------------------------------------------- /src/api/system/dict/type.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询字典类型列表 4 | export function listType(query: any) { 5 | return request({ 6 | url: '/system/dict/type/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询字典类型详细 13 | export function getType(dictId: number) { 14 | return request({ 15 | url: '/system/dict/type/' + dictId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增字典类型 21 | export function addType(data:any) { 22 | return request({ 23 | url: '/system/dict/type', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改字典类型 30 | export function updateType(data: any) { 31 | return request({ 32 | url: '/system/dict/type', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除字典类型 39 | export function delType(dictId: number) { 40 | return request({ 41 | url: '/system/dict/type/' + dictId, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | // 导出字典类型 47 | export function exportType(query: any) { 48 | return request({ 49 | url: '/system/dict/type/export', 50 | method: 'get', 51 | params: query 52 | }) 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/api/resource/oss.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询ResOsses列表 4 | export function listResOsses(query:any) { 5 | return request({ 6 | url: '/resource/oss/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询ResOsses详细 13 | export function getResOsses(ossId:number) { 14 | return request({ 15 | url: '/resource/oss/' + ossId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增ResOsses 21 | export function addResOsses(data:any) { 22 | return request({ 23 | url: '/resource/oss', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改ResOsses 30 | export function updateResOsses(data:any) { 31 | return request({ 32 | url: '/resource/oss', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除ResOsses 39 | export function delResOsses(ossId: string) { 40 | return request({ 41 | url: '/resource/oss/' + ossId, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | // 状态修改 47 | export function changeOssStatus(data:any) { 48 | return request({ 49 | url: '/resource/oss/changeStatus', 50 | method: 'put', 51 | data: data 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/api/device/product_template.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询Template列表 4 | export function listTemplate(query:any) { 5 | return request({ 6 | url: '/device/template/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询Template列表 13 | export function listTemplateAll(query:any) { 14 | return request({ 15 | url: '/device/template/list/all', 16 | method: 'get', 17 | params: query 18 | }) 19 | } 20 | 21 | // 查询Template详细 22 | export function getTemplate(id:number) { 23 | return request({ 24 | url: '/device/template/' + id, 25 | method: 'get' 26 | }) 27 | } 28 | 29 | // 新增Template 30 | export function addTemplate(data:any) { 31 | return request({ 32 | url: '/device/template', 33 | method: 'post', 34 | data: data 35 | }) 36 | } 37 | 38 | // 修改Template 39 | export function updateTemplate(data:any) { 40 | return request({ 41 | url: '/device/template', 42 | method: 'put', 43 | data: data 44 | }) 45 | } 46 | 47 | // 删除Template 48 | export function delTemplate(id: string) { 49 | return request({ 50 | url: '/device/template/' + id, 51 | method: 'delete' 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/api/system/config.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询参数列表 4 | export function listConfig(query:any) { 5 | return request({ 6 | url: '/system/config/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询参数详细 13 | export function getConfig(configId:any) { 14 | return request({ 15 | url: '/system/config/' + configId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 根据参数键名查询参数值 21 | export function getConfigKey(configKey:any) { 22 | return request({ 23 | url: '/system/config/configKey/' + configKey, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 新增参数配置 29 | export function addConfig(data:any) { 30 | return request({ 31 | url: '/system/config', 32 | method: 'post', 33 | data: data 34 | }) 35 | } 36 | 37 | // 修改参数配置 38 | export function updateConfig(data:any) { 39 | return request({ 40 | url: '/system/config', 41 | method: 'put', 42 | data: data 43 | }) 44 | } 45 | 46 | // 删除参数配置 47 | export function delConfig(configId:any) { 48 | return request({ 49 | url: '/system/config/' + configId, 50 | method: 'delete' 51 | }) 52 | } 53 | 54 | // 导出参数 55 | export function exportConfig(query:any) { 56 | return request({ 57 | url: '/system/config/export', 58 | method: 'get', 59 | params: query 60 | }) 61 | } -------------------------------------------------------------------------------- /src/views/home/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 最顶部 card 3 | * @returns 返回模拟数据 4 | */ 5 | export const topCardItemList = [ 6 | { 7 | title: '设备总数', 8 | titleNum: '123', 9 | tip: '在线总数', 10 | tipNum: '63', 11 | color: '#3DD2B4', 12 | iconColor: '#14DAB2', 13 | icon: 'iconfont icon-jinridaiban', 14 | }, 15 | { 16 | title: '产品总数', 17 | titleNum: '25', 18 | tip: '今日新增', 19 | tipNum: '2', 20 | color: '#8595F4', 21 | iconColor: '#92A1F4', 22 | icon: 'iconfont icon-AIshiyanshi', 23 | }, 24 | { 25 | title: '告警总数', 26 | titleNum: '256', 27 | tip: '告警中', 28 | tipNum: '12', 29 | color: '#E88662', 30 | iconColor: '#DE5C2C', 31 | icon: 'iconfont icon-shenqingkaiban', 32 | }, 33 | ]; 34 | 35 | /** 36 | * 环境监测 37 | * @returns 返回模拟数据 38 | */ 39 | export const environmentList = [ 40 | { 41 | icon: 'iconfont icon-yangan', 42 | label: '烟感', 43 | value: '2.1%OBS/M', 44 | iconColor: '#F72B3F', 45 | }, 46 | { 47 | icon: 'iconfont icon-wendu', 48 | label: '温度', 49 | value: '30℃', 50 | iconColor: '#91BFF8', 51 | }, 52 | { 53 | icon: 'iconfont icon-shidu', 54 | label: '湿度', 55 | value: '57%RH', 56 | iconColor: '#88D565', 57 | }, 58 | { 59 | icon: 'iconfont icon-zaosheng', 60 | label: '噪声', 61 | value: '57DB', 62 | iconColor: '#FBD4A0', 63 | }, 64 | ]; 65 | -------------------------------------------------------------------------------- /src/layout/navMenu/subItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ $t(val.meta.title) }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{ $t(val.meta.title) }} 14 | 15 | 16 | 17 | 18 | {{ $t(val.meta.title) }} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 46 | -------------------------------------------------------------------------------- /src/theme/mixins/mixins.scss: -------------------------------------------------------------------------------- 1 | /* 第三方图标字体间距/大小设置 2 | ------------------------------- */ 3 | @mixin generalIcon { 4 | font-size: 16px !important; 5 | display: inline-block; 6 | vertical-align: middle; 7 | margin-right: 5px; 8 | width: 24px; 9 | text-align: center; 10 | } 11 | 12 | /* 文本不换行 13 | ------------------------------- */ 14 | @mixin text-no-wrap() { 15 | text-overflow: ellipsis; 16 | overflow: hidden; 17 | white-space: nowrap; 18 | } 19 | 20 | /* 多行文本溢出 21 | ------------------------------- */ 22 | @mixin text-ellipsis($line: 2) { 23 | overflow: hidden; 24 | word-break: break-all; 25 | text-overflow: ellipsis; 26 | display: -webkit-box; 27 | -webkit-line-clamp: $line; 28 | -webkit-box-orient: vertical; 29 | } 30 | 31 | /* 滚动条(页面未使用) div 中使用: 32 | ------------------------------- */ 33 | // .test { 34 | // @include scrollBar; 35 | // } 36 | @mixin scrollBar { 37 | // 滚动条凹槽的颜色,还可以设置边框属性 38 | &::-webkit-scrollbar-track-piece { 39 | background-color: #f8f8f8; 40 | } 41 | // 滚动条的宽度 42 | &::-webkit-scrollbar { 43 | width: 9px; 44 | height: 9px; 45 | } 46 | // 滚动条的设置 47 | &::-webkit-scrollbar-thumb { 48 | background-color: #dddddd; 49 | background-clip: padding-box; 50 | min-height: 28px; 51 | } 52 | &::-webkit-scrollbar-thumb:hover { 53 | background-color: #bbb; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/api/job/job.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询定时任务调度列表 4 | export function listJob(query:any) { 5 | return request({ 6 | url: '/job/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询定时任务调度详细 13 | export function getJob(jobId:any) { 14 | return request({ 15 | url: '/job/' + jobId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增定时任务调度 21 | export function addJob(data:any) { 22 | return request({ 23 | url: '/job', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改定时任务调度 30 | export function updateJob(data:any) { 31 | return request({ 32 | url: '/job', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除定时任务调度 39 | export function delJob(jobId:any) { 40 | return request({ 41 | url: '/job/' + jobId, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | // 任务状态修改 47 | export function changeJobStatus(data:any) { 48 | return request({ 49 | url: '/job/changeStatus', 50 | method: 'put', 51 | data: data 52 | }) 53 | } 54 | 55 | 56 | // 开始执行 57 | export function runStartJob(jobId:any) { 58 | return request({ 59 | url: '/job/start/'+ jobId, 60 | method: 'get', 61 | }) 62 | } 63 | 64 | // 停止执行 65 | export function runStopJob(jobId:any) { 66 | return request({ 67 | url: '/job/stop/'+ jobId, 68 | method: 'get', 69 | }) 70 | } -------------------------------------------------------------------------------- /src/components/screenShort/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 49 | -------------------------------------------------------------------------------- /src/api/system/api.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询参数列表分页 4 | export function listApi(query:any) { 5 | return request({ 6 | url: '/system/api/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | // 查询参数列表 12 | export function listApiAll(query:any) { 13 | return request({ 14 | url: '/system/api/all', 15 | method: 'get', 16 | params: query 17 | }) 18 | } 19 | // 查询参数详细 20 | export function getApi(id:any) { 21 | return request({ 22 | url: '/system/api/' + id, 23 | method: 'get' 24 | }) 25 | } 26 | 27 | // 新增参数配置 28 | export function addApi(data:any) { 29 | return request({ 30 | url: '/system/api', 31 | method: 'post', 32 | data: data 33 | }) 34 | } 35 | 36 | // 修改参数配置 37 | export function updateApi(data:any) { 38 | return request({ 39 | url: '/system/api', 40 | method: 'put', 41 | data: data 42 | }) 43 | } 44 | 45 | // 删除参数配置 46 | export function delApi(id:any) { 47 | return request({ 48 | url: '/system/api/' + id, 49 | method: 'delete' 50 | }) 51 | } 52 | // 获取权限Api通过角色id 53 | export function getPolicyPathByRoleId(query:any) { 54 | return request({ 55 | url: '/system/api/getPolicyPathByRoleId', 56 | method: 'get', 57 | params: query 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/router/route.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | 3 | /** 4 | * 路由meta对象参数说明 5 | * meta: { 6 | * title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化) 7 | * isLink: 是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空` 8 | * isHide: 是否隐藏此路由 9 | * isKeepAlive: 是否缓存组件状态 10 | * isAffix: 是否固定在 tagsView 栏上 11 | * isIframe: 是否内嵌窗口,,开启条件,`1、isIframe:true 2、链接地址不为空` 12 | * auth: 当前路由权限标识(多个请用逗号隔开),最后转成数组格式,用于与当前用户权限进行对比,控制路由显示、隐藏 13 | * icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx` 14 | * } 15 | */ 16 | 17 | /** 18 | * 定义静态路由 19 | * @description 前端控制直接改 dynamicRoutes 中的路由,后端控制不需要修改,请求接口路由数据时,会覆盖 dynamicRoutes 第一个顶级 children 的内容(全屏,不包含 layout 中的路由出口) 20 | * @returns 返回路由菜单数据 21 | */ 22 | export const staticRoutes: Array = [ 23 | { 24 | path: '/login', 25 | name: 'login', 26 | component: () => import('@/views/login/index.vue'), 27 | meta: { 28 | title: '登录', 29 | }, 30 | }, 31 | { 32 | path: '/404', 33 | name: 'notFound', 34 | component: () => import('@/views/error/404.vue'), 35 | meta: { 36 | title: 'message.staticRoutes.notFound', 37 | }, 38 | }, 39 | { 40 | path: '/401', 41 | name: 'noPower', 42 | component: () => import('@/views/error/401.vue'), 43 | meta: { 44 | title: 'message.staticRoutes.noPower', 45 | }, 46 | }, 47 | ]; 48 | -------------------------------------------------------------------------------- /src/api/system/organization.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询组织列表 4 | export function listOrganization(query : any) { 5 | return request({ 6 | url: '/system/organization/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询组织详细 13 | export function getOrganization(organizationId: number) { 14 | return request({ 15 | url: '/system/organization/' + organizationId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 查询组织下拉树结构 21 | export function treeselect() { 22 | return request({ 23 | url: '/system/organization/organizationTree', 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 根据角色ID查询组织树结构 29 | export function roleOrganizationTreeselect(roleId: number) { 30 | return request({ 31 | url: '/system/organization/roleOrganizationTreeSelect/' + roleId, 32 | method: 'get' 33 | }) 34 | } 35 | 36 | // 新增组织 37 | export function addOrganization(data:any) { 38 | return request({ 39 | url: '/system/organization', 40 | method: 'post', 41 | data: data 42 | }) 43 | } 44 | 45 | // 修改组织 46 | export function updateOrganization(data:any) { 47 | return request({ 48 | url: '/system/organization', 49 | method: 'put', 50 | data: data 51 | }) 52 | } 53 | 54 | // 删除组织 55 | export function delOrganization(organizationId: number) { 56 | return request({ 57 | url: '/system/organization/' + organizationId, 58 | method: 'delete' 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /src/layout/routerView/link.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | {{ $t(currentRouteMeta.title) }}:{{ currentRouteMeta.isLink }} 9 | 10 | 11 | 12 | 48 | -------------------------------------------------------------------------------- /src/layout/main/columns.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 42 | -------------------------------------------------------------------------------- /src/api/system/menu.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function getRoutes(query: any) { 4 | return request({ 5 | url: '/api/menu/menuRole', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | // 查询菜单下拉树结构 12 | export function treeselect() { 13 | return request({ 14 | url: '/system/menu/menuTreeSelect', 15 | method: 'get' 16 | }) 17 | } 18 | 19 | // 根据角色ID查询菜单下拉树结构 20 | export function roleMenuTreeselect(roleId: number) { 21 | return request({ 22 | url: '/system/menu/roleMenuTreeSelect/' + roleId, 23 | method: 'get' 24 | }) 25 | } 26 | 27 | // 查询菜单列表 28 | export function listMenu(query: Array) { 29 | return request({ 30 | url: '/system/menu/list', 31 | method: 'get', 32 | params: query 33 | }) 34 | } 35 | 36 | // 查询菜单详细 37 | export function getMenu(menuId: number) { 38 | return request({ 39 | url: '/system/menu/' + menuId, 40 | method: 'get' 41 | }) 42 | } 43 | 44 | 45 | // 新增菜单 46 | export function addMenu(data: any) { 47 | return request({ 48 | url: '/system/menu', 49 | method: 'post', 50 | data: data 51 | }) 52 | } 53 | 54 | // 修改菜单 55 | export function updateMenu(data: any) { 56 | return request({ 57 | url: '/system/menu', 58 | method: 'put', 59 | data: data 60 | }) 61 | } 62 | 63 | // 删除菜单 64 | export function delMenu(menuId: number) { 65 | return request({ 66 | url: '/system/menu/' + menuId, 67 | method: 'delete' 68 | }) 69 | } -------------------------------------------------------------------------------- /src/api/device/product.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询Product列表 4 | export function listProduct(query:any) { 5 | return request({ 6 | url: '/device/product/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询Product所有列表 13 | export function listProductAll(query:any) { 14 | return request({ 15 | url: '/device/product/list/all', 16 | method: 'get', 17 | params: query 18 | }) 19 | } 20 | 21 | // 查询Product详细 22 | export function getProduct(id:number) { 23 | return request({ 24 | url: '/device/product/' + id, 25 | method: 'get' 26 | }) 27 | } 28 | 29 | // 查询Product详细 30 | export function getProductTsl(id:number) { 31 | return request({ 32 | url: '/device/product/' + id+'/tsl', 33 | method: 'get' 34 | }) 35 | } 36 | 37 | // 新增Product 38 | export function addProduct(data:any) { 39 | return request({ 40 | url: '/device/product', 41 | method: 'post', 42 | data: data 43 | }) 44 | } 45 | 46 | // 修改Product 47 | export function updateProduct(data:any) { 48 | return request({ 49 | url: '/device/product', 50 | method: 'put', 51 | data: data 52 | }) 53 | } 54 | 55 | // 删除Product 56 | export function delProduct(id: string) { 57 | return request({ 58 | url: '/device/product/' + id, 59 | method: 'delete' 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /src/api/system/dict/data.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询字典数据列表 4 | export function listData(query:any) { 5 | return request({ 6 | url: '/system/dict/data/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询字典数据详细 13 | export function getData(dictCode: string) { 14 | return request({ 15 | url: '/system/dict/data/' + dictCode, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 根据字典类型查询字典数据信息 21 | export function getDicts(dictType: string) { 22 | return request({ 23 | url: '/system/dict/data/type?dictType=' + dictType, 24 | method: 'get' 25 | }) 26 | } 27 | 28 | // 新增字典数据 29 | export function addData(data: any) { 30 | return request({ 31 | url: '/system/dict/data', 32 | method: 'post', 33 | data: data 34 | }) 35 | } 36 | 37 | // 修改字典数据 38 | export function updateData(data: any) { 39 | return request({ 40 | url: '/system/dict/data', 41 | method: 'put', 42 | data: data 43 | }) 44 | } 45 | 46 | // 删除字典数据 47 | export function delData(dictCode: number) { 48 | return request({ 49 | url: '/system/dict/data/' + dictCode, 50 | method: 'delete' 51 | }) 52 | } 53 | 54 | // 导出字典数据 55 | export function exportData(query: any) { 56 | return request({ 57 | url: '/system/dict/data/export', 58 | method: 'get', 59 | params: query 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/authDirective.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | 3 | import { judementSameArr } from '@/utils/arrayOperation'; 4 | import { useUserInfosState } from "@/stores/userInfos"; 5 | 6 | /** 7 | * 用户权限指令 8 | * @directive 单个权限验证(v-auth="xxx") 9 | * @directive 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") 10 | * @directive 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") 11 | */ 12 | export function authDirective(app: App) { 13 | 14 | // 单个权限验证(v-auth="xxx") 15 | app.directive('auth', { 16 | mounted(el, binding) { 17 | const userInfos = useUserInfosState(); 18 | if (!userInfos.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el); 19 | }, 20 | }); 21 | // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") 22 | app.directive('auths', { 23 | mounted(el, binding) { 24 | const userInfos = useUserInfosState(); 25 | let flag = false; 26 | userInfos.userInfos.authBtnList.map((val: string) => { 27 | binding.value.map((v: string) => { 28 | if (val === v) flag = true; 29 | }); 30 | }); 31 | if (!flag) el.parentNode.removeChild(el); 32 | }, 33 | }); 34 | // 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") 35 | app.directive('auth-all', { 36 | mounted(el, binding) { 37 | const userInfos = useUserInfosState(); 38 | const flag = judementSameArr(binding.value, userInfos.userInfos.authBtnList); 39 | if (!flag) el.parentNode.removeChild(el); 40 | }, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /src/theme/media/scrollbar.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于768px 4 | ------------------------------- */ 5 | @media screen and (max-width: $sm) { 6 | // 滚动条的宽度 7 | ::-webkit-scrollbar { 8 | width: 3px !important; 9 | height: 3px !important; 10 | } 11 | ::-webkit-scrollbar-track-piece { 12 | background-color: #f8f8f8; 13 | } 14 | // 滚动条的设置 15 | ::-webkit-scrollbar-thumb { 16 | background-color: rgba(144, 147, 153, 0.3); 17 | background-clip: padding-box; 18 | min-height: 28px; 19 | border-radius: 5px; 20 | transition: 0.3s background-color; 21 | } 22 | ::-webkit-scrollbar-thumb:hover { 23 | background-color: rgba(144, 147, 153, 0.5); 24 | } 25 | // element plus scrollbar 26 | .el-scrollbar__bar.is-vertical { 27 | width: 2px !important; 28 | } 29 | .el-scrollbar__bar.is-horizontal { 30 | height: 2px !important; 31 | } 32 | } 33 | 34 | /* 页面宽度大于768px 35 | ------------------------------- */ 36 | @media screen and (min-width: 769px) { 37 | // 滚动条的宽度 38 | ::-webkit-scrollbar { 39 | width: 7px; 40 | height: 7px; 41 | } 42 | ::-webkit-scrollbar-track-piece { 43 | background-color: #f8f8f8; 44 | } 45 | // 滚动条的设置 46 | ::-webkit-scrollbar-thumb { 47 | background-color: rgba(144, 147, 153, 0.3); 48 | background-clip: padding-box; 49 | min-height: 28px; 50 | border-radius: 5px; 51 | transition: 0.3s background-color; 52 | } 53 | ::-webkit-scrollbar-thumb:hover { 54 | background-color: rgba(144, 147, 153, 0.5); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/api/resource/email.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询ResEmails列表 4 | export function listResEmails(query:any) { 5 | return request({ 6 | url: '/resource/email/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询ResEmails详细 13 | export function getResEmails(mailId:number) { 14 | return request({ 15 | url: '/resource/email/' + mailId, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增ResEmails 21 | export function addResEmails(data:any) { 22 | return request({ 23 | url: '/resource/email', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改ResEmails 30 | export function updateResEmails(data:any) { 31 | return request({ 32 | url: '/resource/email', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除ResEmails 39 | export function delResEmails(mailId: string) { 40 | return request({ 41 | url: '/resource/email/' + mailId, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | // 状态修改 47 | export function changeMailStatus(data:any) { 48 | return request({ 49 | url: '/resource/email/changeStatus', 50 | method: 'put', 51 | data: data 52 | }) 53 | } 54 | 55 | // 状态修改 56 | export function debugMail(data:any) { 57 | return request({ 58 | url: '/resource/email/debugMail', 59 | method: 'post', 60 | data: data 61 | }) 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/stores/keepAliveNames.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import {KeepAliveNamesState} from 'storeInterface' 3 | 4 | /** 5 | * 路由缓存列表 6 | * @methods setCacheKeepAlive 设置要缓存的路由 names(开启 Tagsview) 7 | * @methods addCachedView 添加要缓存的路由 names(关闭 Tagsview) 8 | * @methods delCachedView 删除要缓存的路由 names(关闭 Tagsview) 9 | * @methods delOthersCachedViews 右键菜单`关闭其它`,删除要缓存的路由 names(关闭 Tagsview) 10 | * @methods delAllCachedViews 右键菜单`全部关闭`,删除要缓存的路由 names(关闭 Tagsview) 11 | */ 12 | export const useKeepAliveNamesStore = defineStore('keepAliveNames', { 13 | state: (): KeepAliveNamesState => ({ 14 | keepAliveNames: [], 15 | cachedViews: [], 16 | }), 17 | actions: { 18 | async setCacheKeepAlive(data: Array) { 19 | this.keepAliveNames = data; 20 | }, 21 | async addCachedView(view: any) { 22 | if (this.cachedViews.includes(view.name)) return; 23 | if (view.meta.isKeepAlive) this.cachedViews.push(view.name); 24 | }, 25 | async delCachedView(view: any) { 26 | const index = this.cachedViews.indexOf(view.name); 27 | index > -1 && this.cachedViews.splice(index, 1); 28 | }, 29 | async delOthersCachedViews(view: any) { 30 | if (view.meta.isKeepAlive) this.cachedViews = [view.name]; 31 | else this.cachedViews = []; 32 | }, 33 | async delAllCachedViews() { 34 | this.cachedViews = []; 35 | }, 36 | }, 37 | }) 38 | 39 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie'; 2 | 3 | /** 4 | * window.localStorage 浏览器永久缓存 5 | * @method set 设置永久缓存 6 | * @method get 获取永久缓存 7 | * @method remove 移除永久缓存 8 | * @method clear 移除全部永久缓存 9 | */ 10 | export const Local = { 11 | // 设置永久缓存 12 | set(key: string, val: any) { 13 | window.localStorage.setItem(key, JSON.stringify(val)); 14 | }, 15 | // 获取永久缓存 16 | get(key: string) { 17 | let json: any = window.localStorage.getItem(key); 18 | return JSON.parse(json); 19 | }, 20 | // 移除永久缓存 21 | remove(key: string) { 22 | window.localStorage.removeItem(key); 23 | }, 24 | // 移除全部永久缓存 25 | clear() { 26 | window.localStorage.clear(); 27 | }, 28 | }; 29 | 30 | /** 31 | * window.sessionStorage 浏览器临时缓存 32 | * @method set 设置临时缓存 33 | * @method get 获取临时缓存 34 | * @method remove 移除临时缓存 35 | * @method clear 移除全部临时缓存 36 | */ 37 | export const Session = { 38 | // 设置临时缓存 39 | set(key: string, val: any) { 40 | if (key === 'token') return Cookies.set(key, val); 41 | window.sessionStorage.setItem(key, JSON.stringify(val)); 42 | }, 43 | // 获取临时缓存 44 | get(key: string) { 45 | if (key === 'token') return Cookies.get(key); 46 | let json: any = window.sessionStorage.getItem(key); 47 | return JSON.parse(json); 48 | }, 49 | // 移除临时缓存 50 | remove(key: string) { 51 | if (key === 'token') return Cookies.remove(key); 52 | window.sessionStorage.removeItem(key); 53 | }, 54 | // 移除全部临时缓存 55 | clear() { 56 | Cookies.remove('token'); 57 | window.sessionStorage.clear(); 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/api/system/role.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | 4 | // 查询角色列表 5 | export function listRole(query: Array) { 6 | return request({ 7 | url: '/system/role/list', 8 | method: 'get', 9 | params: query 10 | }) 11 | } 12 | 13 | // 查询角色详细 14 | export function getRole(roleId: number) { 15 | return request({ 16 | url: '/system/role/' + roleId, 17 | method: 'get' 18 | }) 19 | } 20 | 21 | // 新增角色 22 | export function addRole(data: Array) { 23 | return request({ 24 | url: '/system/role', 25 | method: 'post', 26 | data: data 27 | }) 28 | } 29 | 30 | // 修改角色 31 | export function updateRole(data: Array) { 32 | return request({ 33 | url: '/system/role', 34 | method: 'put', 35 | data: data 36 | }) 37 | } 38 | 39 | // 角色数据权限 40 | export function dataScope(data: Array) { 41 | return request({ 42 | url: '/system/role/dataScope', 43 | method: 'put', 44 | data: data 45 | }) 46 | } 47 | 48 | // 角色状态修改 49 | export function changeRoleStatus(roleId: number, status: string) { 50 | const data = { 51 | roleId, 52 | status 53 | } 54 | return request({ 55 | url: '/system/role/changeStatus', 56 | method: 'put', 57 | data: data 58 | }) 59 | } 60 | 61 | // 删除角色 62 | export function delRole(roleId: number) { 63 | return request({ 64 | url: '/system/role/' + roleId, 65 | method: 'delete' 66 | }) 67 | } 68 | // 导出 69 | export function exportRole(query: any) { 70 | return request({ 71 | url: '/system/role/export', 72 | method: 'get', 73 | params: query 74 | }) 75 | } -------------------------------------------------------------------------------- /src/api/device/device_group.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询DeviceGroup列表 4 | export function listDeviceGroup(query:any) { 5 | return request({ 6 | url: '/device/group/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | export function listDeviceGroupTree() { 13 | return request({ 14 | url: '/device/group/list/tree', 15 | method: 'get', 16 | }) 17 | } 18 | 19 | export function listDeviceGroupTreeLabel() { 20 | return request({ 21 | url: '/device/group/list/tree/label', 22 | method: 'get', 23 | }) 24 | } 25 | export function listDeviceGroupAllList(query:any) { 26 | return request({ 27 | url: '/device/group/list/all', 28 | method: 'get', 29 | params: query 30 | }) 31 | } 32 | 33 | // 查询DeviceGroup详细 34 | export function getDeviceGroup(id:number) { 35 | return request({ 36 | url: '/device/group/' + id, 37 | method: 'get' 38 | }) 39 | } 40 | 41 | // 新增DeviceGroup 42 | export function addDeviceGroup(data:any) { 43 | return request({ 44 | url: '/device/group', 45 | method: 'post', 46 | data: data 47 | }) 48 | } 49 | 50 | // 修改DeviceGroup 51 | export function updateDeviceGroup(data:any) { 52 | return request({ 53 | url: '/device/group', 54 | method: 'put', 55 | data: data 56 | }) 57 | } 58 | 59 | // 删除DeviceGroup 60 | export function delDeviceGroup(id: string) { 61 | return request({ 62 | url: '/device/group/' + id, 63 | method: 'delete' 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /src/views/personal/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 消息通知 3 | * @returns 返回模拟数据 4 | */ 5 | export const newsInfoList: Array = [ 6 | { 7 | title: '[发布] 2021年11月28日发布基于 vue3.x + vite v1.0.0 版本', 8 | date: '02/28', 9 | link: 'https://gitee.com/PandaAdmin/PandaX', 10 | }, 11 | { 12 | title: '[发布] 2021年11月15日发布 vue2.x + webpack 重构版本', 13 | date: '04/15', 14 | link: 'https://gitee.com/PandaAdmin/PandaX/tree/vue-prev-admin/', 15 | }, 16 | { 17 | title: '[重构] 2021年04月10日 重构 vue2.x + webpack v1.0.0 版本', 18 | date: '04/10', 19 | link: 'https://gitee.com/PandaAdmin/PandaX/tree/vue-prev-admin/', 20 | }, 21 | { 22 | title: '[预览] 2020年12月08日,基于 vue3.x 版本后台模板的预览', 23 | date: '12/08', 24 | link: 'http://PandaGoAdmin.gitee.io/PandaUi-preview/#/login', 25 | }, 26 | { 27 | title: '[预览] 2020年11月15日,基于 vue2.x 版本后台模板的预览', 28 | date: '11/15', 29 | link: 'https://PandaGoAdmin.gitee.io/vue-prev-admin-preview/#/login', 30 | }, 31 | ]; 32 | 33 | /** 34 | * 营销推荐 35 | * @returns 返回模拟数据 36 | */ 37 | export const recommendList: Array = [ 38 | { 39 | title: 'Go语言', 40 | msg: '健壮的成年', 41 | icon: 'elementFood', 42 | bg: '#48D18D', 43 | iconColor: '#64d89d', 44 | }, 45 | { 46 | title: 'VUE', 47 | msg: '茁壮的成长', 48 | icon: 'elementShoppingCart', 49 | bg: '#F95959', 50 | iconColor: '#F86C6B', 51 | }, 52 | { 53 | title: 'JAVA', 54 | msg: '懵懂的孩子', 55 | icon: 'elementSchool', 56 | bg: '#8595F4', 57 | iconColor: '#92A1F4', 58 | }, 59 | { 60 | title: 'UNIAPP', 61 | msg: '牙牙学语', 62 | icon: 'elementAlarmClock', 63 | bg: '#FEBB50', 64 | iconColor: '#FDC566', 65 | }, 66 | ]; 67 | -------------------------------------------------------------------------------- /src/utils/wartermark.ts: -------------------------------------------------------------------------------- 1 | // 页面添加水印效果 2 | const setWatermark = (str: string) => { 3 | const id = '1.23452384164.123412416'; 4 | if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any); 5 | const can = document.createElement('canvas'); 6 | can.width = 200; 7 | can.height = 130; 8 | const cans: any = can.getContext('2d'); 9 | cans.rotate((-20 * Math.PI) / 180); 10 | cans.font = '12px Vedana'; 11 | cans.fillStyle = 'rgba(200, 200, 200, 0.30)'; 12 | cans.textBaseline = 'Middle'; 13 | cans.fillText(str, can.width / 10, can.height / 2); 14 | const div = document.createElement('div'); 15 | div.id = id; 16 | div.style.pointerEvents = 'none'; 17 | div.style.top = '15px'; 18 | div.style.left = '0px'; 19 | div.style.position = 'fixed'; 20 | div.style.zIndex = '10000000'; 21 | div.style.width = `${document.documentElement.clientWidth}px`; 22 | div.style.height = `${document.documentElement.clientHeight}px`; 23 | div.style.background = `url(${can.toDataURL('image/png')}) left top repeat`; 24 | document.body.appendChild(div); 25 | return id; 26 | }; 27 | 28 | /** 29 | * 页面添加水印效果 30 | * @method set 设置水印 31 | * @method del 删除水印 32 | */ 33 | const watermark = { 34 | // 设置水印 35 | set: (str: string) => { 36 | let id = setWatermark(str); 37 | if (document.getElementById(id) === null) id = setWatermark(str); 38 | }, 39 | // 删除水印 40 | del: () => { 41 | let id = '1.23452384164.123412416'; 42 | if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any); 43 | }, 44 | }; 45 | 46 | // 导出方法 47 | export default watermark; 48 | -------------------------------------------------------------------------------- /src/theme/loading.scss: -------------------------------------------------------------------------------- 1 | .loading-next { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | .loading-next .loading-next-box { 6 | position: absolute; 7 | top: 50%; 8 | left: 50%; 9 | transform: translate(-50%, -50%); 10 | } 11 | .loading-next .loading-next-box-warp { 12 | width: 80px; 13 | height: 80px; 14 | } 15 | .loading-next .loading-next-box-warp .loading-next-box-item { 16 | width: 33.333333%; 17 | height: 33.333333%; 18 | background: var(--color-primary); 19 | float: left; 20 | animation: loading-next-animation 1.2s infinite ease; 21 | border-radius: 1px; 22 | } 23 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(7) { 24 | animation-delay: 0s; 25 | } 26 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(4), 27 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(8) { 28 | animation-delay: 0.1s; 29 | } 30 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(1), 31 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(5), 32 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(9) { 33 | animation-delay: 0.2s; 34 | } 35 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(2), 36 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(6) { 37 | animation-delay: 0.3s; 38 | } 39 | .loading-next .loading-next-box-warp .loading-next-box-item:nth-child(3) { 40 | animation-delay: 0.4s; 41 | } 42 | @keyframes loading-next-animation { 43 | 0%, 44 | 70%, 45 | 100% { 46 | transform: scale3D(1, 1, 1); 47 | } 48 | 35% { 49 | transform: scale3D(0, 0, 1); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/api/gen/table.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 表列表 4 | export function getDbList(query:any) { 5 | return request({ 6 | url: '/develop/code/table/db/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | export function getTableList(query:any) { 13 | return request({ 14 | url: '/develop/code/table/list', 15 | method: 'get', 16 | params: query 17 | }) 18 | } 19 | 20 | // 获取表信息 21 | export function getTableInfo(tableId:any) { 22 | return request({ 23 | url: '/develop/code/table/info/' + tableId, 24 | method: 'get', 25 | }) 26 | } 27 | 28 | // 获取表信息 29 | export function getTableInfoByName(query:any) { 30 | return request({ 31 | url: '/develop/code/table/info/tableName', 32 | method: 'get', 33 | params: query 34 | }) 35 | } 36 | // 获取表树信息 37 | export function getTableTree() { 38 | return request({ 39 | url: '/develop/code/table/tableTree', 40 | method: 'get', 41 | }) 42 | } 43 | 44 | // 导入表 45 | export function insertTable(data:any) { 46 | return request({ 47 | url: '/develop/code/table', 48 | method: 'post', 49 | params: data 50 | }) 51 | } 52 | 53 | 54 | // 修改代码生成信息 55 | export function updateTable(data:any) { 56 | return request({ 57 | url: '/develop/code/table', 58 | method: 'put', 59 | data: data 60 | }) 61 | } 62 | 63 | 64 | // 删除表 65 | export function deleteTable(tableId:any) { 66 | return request({ 67 | url: '/develop/code/table/' + tableId, 68 | method: 'delete', 69 | }) 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/views/device/product/component/view.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 49 | 50 | 53 | -------------------------------------------------------------------------------- /src/utils/loading.ts: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'vue'; 2 | import loadingCss from '@/theme/loading.scss'; 3 | 4 | /** 5 | * 页面全局 Loading 6 | * @method setCss 载入 css 7 | * @method start 创建 loading 8 | * @method done 移除 loading 9 | */ 10 | export const NextLoading = { 11 | // 载入 css 12 | setCss: () => { 13 | let link = document.createElement('link'); 14 | link.rel = 'stylesheet'; 15 | link.href = loadingCss; 16 | link.crossOrigin = 'anonymous'; 17 | document.getElementsByTagName('head')[0].appendChild(link); 18 | }, 19 | // 创建 loading 20 | start: () => { 21 | const bodys: Element = document.body; 22 | const div = document.createElement('div'); 23 | div.setAttribute('class', 'loading-next'); 24 | const htmls = ` 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | `; 39 | div.innerHTML = htmls; 40 | bodys.insertBefore(div, bodys.childNodes[0]); 41 | window.nextLoading = true; 42 | }, 43 | // 移除 loading 44 | done: () => { 45 | nextTick(() => { 46 | setTimeout(() => { 47 | window.nextLoading = false; 48 | const el = document.querySelector('.loading-next'); 49 | el && el.parentNode?.removeChild(el); 50 | }, 1000); 51 | }); 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n'; 2 | import zhcnLocale from 'element-plus/dist/locale/zh-cn.mjs'; 3 | import enLocale from 'element-plus/dist/locale/en.mjs'; 4 | import nextZhcn from '@/i18n/lang/zh-cn'; 5 | import nextEn from '@/i18n/lang/en'; 6 | 7 | import pagesHomeZhcn from '@/i18n/pages/home/zh-cn'; 8 | import pagesHomeEn from '@/i18n/pages/home/en'; 9 | import pinia from '@/stores/index'; 10 | import { storeToRefs } from 'pinia'; 11 | import { useThemeConfigStateStore } from '@/stores/themeConfig'; 12 | 13 | import pagesLoginZhcn from '@/i18n/pages/login/zh-cn'; 14 | import pagesLoginEn from '@/i18n/pages/login/en'; 15 | import pagesFormI18nZhcn from '@/i18n/pages/formI18n/zh-cn'; 16 | import pagesFormI18nEn from '@/i18n/pages/formI18n/en'; 17 | 18 | 19 | 20 | // 定义语言国际化内容 21 | /** 22 | * 说明: 23 | * /src/i18n/lang 下的 ts 为框架的国际化内容 24 | * /src/i18n/pages 下的 ts 为各界面的国际化内容 25 | */ 26 | const messages = { 27 | [zhcnLocale.name]: { 28 | ...zhcnLocale, 29 | message: { 30 | ...nextZhcn, 31 | ...pagesHomeZhcn, 32 | ...pagesLoginZhcn, 33 | ...pagesFormI18nZhcn, 34 | }, 35 | }, 36 | [enLocale.name]: { 37 | ...enLocale, 38 | message: { 39 | ...nextEn, 40 | ...pagesHomeEn, 41 | ...pagesLoginEn, 42 | ...pagesFormI18nEn, 43 | }, 44 | }, 45 | }; 46 | const stores = useThemeConfigStateStore(pinia); 47 | const { themeConfig } = storeToRefs(stores); 48 | //导出语言国际化 49 | export const i18n = createI18n({ 50 | legacy: false, 51 | silentTranslationWarn: true, 52 | missingWarn: false, 53 | silentFallbackWarn: true, 54 | fallbackWarn: false, 55 | locale: themeConfig.value.globalI18n, 56 | fallbackLocale: zhcnLocale.name, 57 | messages, 58 | }); 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/api/device/product_category.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询ProductCategory列表 4 | export function listProductCategory(query:any) { 5 | return request({ 6 | url: '/device/product/category/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | export function listProductCategoryTree() { 13 | return request({ 14 | url: '/device/product/category/list/tree', 15 | method: 'get', 16 | }) 17 | } 18 | export function listProductCategoryLabel() { 19 | return request({ 20 | url: '/device/product/category/list/tree/label', 21 | method: 'get', 22 | }) 23 | } 24 | export function listProductCategoryAllList(query:any) { 25 | return request({ 26 | url: '/device/product/category/list/all', 27 | method: 'get', 28 | params: query 29 | }) 30 | } 31 | 32 | // 查询ProductCategory详细 33 | export function getProductCategory(id:number) { 34 | return request({ 35 | url: '/device/product/category/' + id, 36 | method: 'get' 37 | }) 38 | } 39 | 40 | // 新增ProductCategory 41 | export function addProductCategory(data:any) { 42 | return request({ 43 | url: '/device/product/category', 44 | method: 'post', 45 | data: data 46 | }) 47 | } 48 | 49 | // 修改ProductCategory 50 | export function updateProductCategory(data:any) { 51 | return request({ 52 | url: '/device/product/category', 53 | method: 'put', 54 | data: data 55 | }) 56 | } 57 | 58 | // 删除ProductCategory 59 | export function delProductCategory(id: string) { 60 | return request({ 61 | url: '/device/product/category/' + id, 62 | method: 'delete' 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /src/theme/common/transition.scss: -------------------------------------------------------------------------------- 1 | /* 页面切换动画 2 | ------------------------------- */ 3 | .slide-right-enter-active, 4 | .slide-right-leave-active, 5 | .slide-left-enter-active, 6 | .slide-left-leave-active { 7 | will-change: transform; 8 | transition: all 0.3s ease; 9 | } 10 | // slide-right 11 | .slide-right-enter-from { 12 | opacity: 0; 13 | transform: translateX(-20px); 14 | } 15 | .slide-right-leave-to { 16 | opacity: 0; 17 | transform: translateX(20px); 18 | } 19 | // slide-left 20 | .slide-left-enter-from { 21 | @extend .slide-right-leave-to; 22 | } 23 | .slide-left-leave-to { 24 | @extend .slide-right-enter-from; 25 | } 26 | // opacitys 27 | .opacitys-enter-active, 28 | .opacitys-leave-active { 29 | will-change: transform; 30 | transition: all 0.3s ease; 31 | } 32 | .opacitys-enter-from, 33 | .opacitys-leave-to { 34 | opacity: 0; 35 | } 36 | 37 | /* Breadcrumb 面包屑过渡动画 38 | ------------------------------- */ 39 | .breadcrumb-enter-active, 40 | .breadcrumb-leave-active { 41 | transition: all 0.3s; 42 | } 43 | .breadcrumb-enter-from, 44 | .breadcrumb-leave-active { 45 | opacity: 0; 46 | transform: translateX(20px); 47 | } 48 | .breadcrumb-leave-active { 49 | position: absolute; 50 | } 51 | 52 | /* logo 过渡动画 53 | ------------------------------- */ 54 | @keyframes logoAnimation { 55 | 0% { 56 | transform: scale(0); 57 | } 58 | 80% { 59 | transform: scale(1.2); 60 | } 61 | 100% { 62 | transform: scale(1); 63 | } 64 | } 65 | 66 | /* 404、401 过渡动画 67 | ------------------------------- */ 68 | @keyframes error-num { 69 | 0% { 70 | transform: translateY(60px); 71 | opacity: 0; 72 | } 73 | 100% { 74 | transform: translateY(0); 75 | opacity: 1; 76 | } 77 | } 78 | @keyframes error-img { 79 | 0% { 80 | opacity: 0; 81 | } 82 | 100% { 83 | opacity: 1; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/theme/iconSelector.scss: -------------------------------------------------------------------------------- 1 | /* Popover 弹出框(图标选择器) 2 | ------------------------------- */ 3 | .icon-selector-popper { 4 | padding: 0 !important; 5 | .icon-selector-warp { 6 | height: 260px; 7 | overflow: hidden; 8 | .icon-selector-warp-title { 9 | height: 40px; 10 | line-height: 40px; 11 | padding: 0 15px; 12 | .icon-selector-warp-title-tab { 13 | span { 14 | cursor: pointer; 15 | &:hover { 16 | color: var(--color-primary); 17 | text-decoration: underline; 18 | } 19 | } 20 | .span-active { 21 | color: var(--color-primary); 22 | text-decoration: underline; 23 | } 24 | } 25 | } 26 | .icon-selector-warp-row { 27 | height: 230px; 28 | overflow: hidden; 29 | border-top: var(--el-border-base); 30 | .el-row { 31 | padding: 15px; 32 | } 33 | .el-scrollbar__bar.is-horizontal { 34 | display: none; 35 | } 36 | .icon-selector-warp-item { 37 | display: flex; 38 | border: var(--el-border-base); 39 | padding: 5px; 40 | border-radius: 5px; 41 | margin-bottom: 10px; 42 | .icon-selector-warp-item-value { 43 | i { 44 | font-size: 20px; 45 | color: var(--el-text-color-regular); 46 | } 47 | } 48 | &:hover { 49 | cursor: pointer; 50 | background-color: var(--color-primary-light-9); 51 | border: 1px solid var(--color-primary-light-6); 52 | .icon-selector-warp-item-value { 53 | i { 54 | color: var(--color-primary); 55 | } 56 | } 57 | } 58 | } 59 | .icon-selector-active { 60 | background-color: var(--color-primary-light-9); 61 | border: 1px solid var(--color-primary-light-6); 62 | .icon-selector-warp-item-value { 63 | i { 64 | color: var(--color-primary); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/layout/main/defaults.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 52 | -------------------------------------------------------------------------------- /src/utils/zipdownload.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Session } from '@/utils/storage'; 3 | 4 | const mimeMap = { 5 | xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 6 | zip: 'application/zip' 7 | } 8 | 9 | const baseUrl = import.meta.env.VITE_API_URL 10 | export function downLoadZip(str: string, filename: string) { 11 | let url = baseUrl + str 12 | axios({ 13 | method: 'get', 14 | url: url, 15 | responseType: 'blob', 16 | headers: { 'Authorization': 'Bearer ' + `${Session.get('token')}` } 17 | }).then(res => { 18 | resolveBlob(res, mimeMap.zip) 19 | }) 20 | } 21 | 22 | export function downLoadFile(str: string) { 23 | let url = baseUrl + str 24 | const aLink = document.createElement('a') 25 | aLink.href = url 26 | document.body.appendChild(aLink) 27 | aLink.click() 28 | document.body.appendChild(aLink) 29 | } 30 | /** 31 | * 解析blob响应内容并下载 32 | * @param {*} res blob响应内容 33 | * @param {String} mimeType MIME类型 34 | */ 35 | export function resolveBlob(res:any, mimeType:string) { 36 | const aLink = document.createElement('a') 37 | let blob = new Blob([res.data], { type: mimeType }) 38 | // //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名; 39 | let patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*') 40 | let contentDisposition = decodeURI(res.headers['content-disposition']) 41 | let result:any = patt.exec(contentDisposition) 42 | let fileName = result[1] 43 | fileName = fileName.replace(/\"/g, '') 44 | aLink.href = URL.createObjectURL(blob) 45 | aLink.setAttribute('download', fileName) // 设置下载文件名称 46 | document.body.appendChild(aLink) 47 | aLink.click() 48 | document.body.appendChild(aLink) 49 | } -------------------------------------------------------------------------------- /src/views/tool/notice/component/viewModule.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | {{state.content.title}} 10 | 11 | 12 | {{noticeTypeFormat(state.content)}} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 50 | 51 | 56 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue'; 2 | import { resolve } from 'path'; 3 | import { UserConfig } from 'vite'; 4 | import { loadEnv } from './src/utils/viteBuild'; 5 | import vueSetupExtend from 'vite-plugin-vue-setup-extend'; 6 | 7 | const pathResolve = (dir: string): any => { 8 | return resolve(__dirname, dir); 9 | }; 10 | 11 | const { VITE_PORT, VITE_OPEN, VITE_PUBLIC_PATH } = loadEnv(); 12 | 13 | const alias: Record = { 14 | '@': pathResolve('src'), 15 | 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js', 16 | }; 17 | 18 | const viteConfig: UserConfig = { 19 | plugins: [vue(),vueSetupExtend()], 20 | root: process.cwd(), 21 | resolve: { alias }, 22 | base: process.env.NODE_ENV === 'production' ? VITE_PUBLIC_PATH : './', 23 | optimizeDeps: { 24 | include: ['element-plus/dist/locale/zh-cn.mjs', 'element-plus/dist/locale/en.mjs'], 25 | }, 26 | server: { 27 | host: '0.0.0.0', 28 | port: VITE_PORT, 29 | open: VITE_OPEN, 30 | proxy: { 31 | '/api': { 32 | target: 'http://127.0.0.1:7788', 33 | ws: true, 34 | changeOrigin: true, 35 | rewrite: (path) => path.replace(/^\/api/, '/'), 36 | }, 37 | }, 38 | }, 39 | build: { 40 | outDir: 'deploy/dist', 41 | minify: 'esbuild', 42 | sourcemap: false, 43 | chunkSizeWarningLimit: 1500, 44 | rollupOptions: { 45 | output: { 46 | entryFileNames: `assets/[name].${new Date().getTime()}.js`, 47 | chunkFileNames: `assets/[name].${new Date().getTime()}.js`, 48 | assetFileNames: `assets/[name].${new Date().getTime()}.[ext]`, 49 | }, 50 | }, 51 | }, 52 | css: { preprocessorOptions: { css: { charset: false } } }, 53 | define: { 54 | __VUE_I18N_LEGACY_API__: JSON.stringify(false), 55 | __VUE_I18N_FULL_INSTALL__: JSON.stringify(false), 56 | __INTLIFY_PROD_DEVTOOLS__: JSON.stringify(false), 57 | }, 58 | }; 59 | 60 | export default viteConfig; 61 | -------------------------------------------------------------------------------- /src/utils/theme.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage } from 'element-plus'; 2 | 3 | /** 4 | * hex颜色转rgb颜色 5 | * @param str 颜色值字符串 6 | * @returns 返回处理后的颜色值 7 | */ 8 | export function hexToRgb(str: any) { 9 | let hexs: any = ''; 10 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 11 | if (!reg.test(str)) return ElMessage.warning('输入错误的hex'); 12 | str = str.replace('#', ''); 13 | hexs = str.match(/../g); 14 | for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16); 15 | return hexs; 16 | } 17 | 18 | /** 19 | * rgb颜色转Hex颜色 20 | * @param r 代表红色 21 | * @param g 代表绿色 22 | * @param b 代表蓝色 23 | * @returns 返回处理后的颜色值 24 | */ 25 | export function rgbToHex(r: any, g: any, b: any) { 26 | let reg = /^\d{1,3}$/; 27 | if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning('输入错误的rgb颜色值'); 28 | let hexs = [r.toString(16), g.toString(16), b.toString(16)]; 29 | for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`; 30 | return `#${hexs.join('')}`; 31 | } 32 | 33 | /** 34 | * 加深颜色值 35 | * @param color 颜色值字符串 36 | * @param level 加深的程度,限0-1之间 37 | * @returns 返回处理后的颜色值 38 | */ 39 | export function getDarkColor(color: any, level: number) { 40 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 41 | if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值'); 42 | let rgb = hexToRgb(color); 43 | for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level)); 44 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 45 | } 46 | 47 | /** 48 | * 变浅颜色值 49 | * @param color 颜色值字符串 50 | * @param level 加深的程度,限0-1之间 51 | * @returns 返回处理后的颜色值 52 | */ 53 | export function getLightColor(color: any, level: number) { 54 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 55 | if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值'); 56 | let rgb = hexToRgb(color); 57 | for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]); 58 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 59 | } 60 | -------------------------------------------------------------------------------- /src/layout/navBars/breadcrumb/closeFull.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 37 | 38 | 70 | -------------------------------------------------------------------------------- /src/stores/userInfos.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { UserInfosState } from 'storeInterface'; 3 | import { Session } from '@/utils/storage'; 4 | import Cookies from 'js-cookie'; 5 | import {letterAvatar} from "@/utils/string"; 6 | import { authUser } from "@/api/system/user"; 7 | 8 | export const useUserInfosState = defineStore('userInfos', { 9 | state: (): UserInfosState => ({ 10 | userInfos: { 11 | username: "", 12 | photo: '', 13 | time: 0, 14 | authBtnList: [], 15 | 16 | userId: 0, 17 | roleId: 0, 18 | organizationId: 0, 19 | postId: 0, 20 | 21 | lastLoginTime: 0, 22 | lastLoginIp: "127.0.0.1", 23 | }, 24 | }), 25 | actions: { 26 | // 设置用户信息 27 | getUserInfos( data: object) { 28 | this.userInfos = data; 29 | }, 30 | // 设置用户信息 31 | async setUserInfos() { 32 | const userName = Cookies.get('userName'); 33 | 34 | let response = await authUser({"username": userName}) 35 | let loginRes = response.data 36 | Session.set("menus", loginRes.menus); 37 | let perms = loginRes.permissions; 38 | perms.push("base"); 39 | // 用户信息模拟数据 40 | const userInfos = { 41 | username: loginRes.user.username, 42 | userId: loginRes.user.userId, 43 | roleId: loginRes.user.roleId, 44 | organizationId: loginRes.user.organizationId, 45 | postId: loginRes.user.postId, 46 | // 头像 47 | photo: loginRes.user.avatar || letterAvatar(loginRes.user.username), 48 | time: new Date().getTime(), 49 | lastLoginTime: new Date().getTime(), 50 | lastLoginIp: "127.0.0.1", 51 | //authPageList: perms, 52 | authBtnList: perms, 53 | }; 54 | // 存储用户信息到浏览器缓存 55 | Session.set('userInfo', userInfos); 56 | 57 | 58 | if (Session.get('userInfo')) { 59 | this.getUserInfos(Session.get('userInfo')); 60 | } else { 61 | this.getUserInfos(userInfos); 62 | } 63 | }, 64 | async setUserInfo() { 65 | this.getUserInfos(Session.get('userInfo')); 66 | } 67 | 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { ElMessage, ElMessageBox } from 'element-plus'; 3 | import { Session } from '@/utils/storage'; 4 | 5 | // 配置新建一个 axios 实例 6 | const service = axios.create({ 7 | baseURL: import.meta.env.VITE_API_URL as any, 8 | timeout: 50000, 9 | headers: { 'Content-Type': 'application/json' }, 10 | }); 11 | 12 | // 添加请求拦截器 13 | service.interceptors.request.use( 14 | (config:any) => { 15 | // 在发送请求之前做些什么 token 16 | if (Session.get('token')) { 17 | config.headers!['X-TOKEN'] = `${Session.get('token')}`; 18 | //config.headers.common['X-TOKEN'] = `${Session.get('token')}`; 19 | } 20 | return config; 21 | }, 22 | (error) => { 23 | // 对请求错误做些什么 24 | return Promise.reject(error); 25 | } 26 | ); 27 | 28 | // 添加响应拦截器 29 | service.interceptors.response.use( 30 | (response) => { 31 | // 对响应数据做点什么 32 | const res = response.data; 33 | if (res.code && res.code !== 200) { 34 | // `token` 过期或者账号已在别处登录 35 | if (res.code === 401 || res.code === 4001) { 36 | Session.clear(); // 清除浏览器全部临时缓存 37 | window.location.href = '/'; // 去登录页 38 | ElMessageBox.alert('你已被登出,请重新登录', '提示', {}) 39 | .then(() => {}) 40 | .catch(() => {}); 41 | }else if((res.code === 403)){ 42 | ElMessage.error(res.msg) 43 | }else if (res.code === 400) { 44 | ElMessage.error(res.msg) 45 | } 46 | return response.data; 47 | //return Promise.reject(service.interceptors.response); 48 | } else { 49 | return response.data; 50 | } 51 | }, 52 | (error) => { 53 | // 对响应错误做点什么 54 | if (error.message.indexOf('timeout') != -1) { 55 | ElMessage.error('网络超时'); 56 | } else if (error.message == 'Network Error') { 57 | ElMessage.error('网络连接错误'); 58 | } else { 59 | console.log(error.response) 60 | if (error.response.data) ElMessage.error(error.response.statusText); 61 | else ElMessage.error('接口路径找不到'); 62 | } 63 | return Promise.reject(error); 64 | } 65 | ); 66 | 67 | // 导出 axios 实例 68 | export default service; 69 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import { createPinia } from 'pinia' 4 | import router from './router'; 5 | import { directive } from '@/utils/directive'; 6 | import { i18n } from '@/i18n/index'; 7 | import other from '@/utils/other'; 8 | import {addDateRange,parseTime,handleTree,selectDictLabel,download} from '@/utils' 9 | import { getDicts } from "@/api/system/dict/data" 10 | import { dateStrFormat } from "@/utils/formatTime" 11 | import ElementPlus from 'element-plus'; 12 | import 'element-plus/dist/index.css'; 13 | import '@/theme/index.scss'; 14 | import mitt from 'mitt'; 15 | // @ts-ignore 16 | import XmForm from 'pandax-form' 17 | import 'pandax-form/dist/designer.style.css' //引入XmForm样式 18 | import VueAMap, {initAMapApiLoader} from '@vuemap/vue-amap'; 19 | import '@vuemap/vue-amap/dist/style.css' 20 | 21 | initAMapApiLoader({ 22 | key: '86fc3e60687d0a24e4badd8c1b0f4ea0', 23 | securityJsCode: '4f809401e3babecd6bc49eb79a17086a', // 新版key需要配合安全密钥使用 24 | plugins: [ 25 | "AMap.Autocomplete", 26 | "AMap.PlaceSearch", 27 | "AMap.Scale", 28 | "AMap.OverView", 29 | "AMap.ToolBar", 30 | "AMap.MapType", 31 | "AMap.PolyEditor", 32 | "AMap.CircleEditor", 33 | "AMap.Geocoder", 34 | "AMap.Geolocation" 35 | ], 36 | }) 37 | 38 | const app = createApp(App); 39 | const pinia = createPinia(); 40 | 41 | directive(app); 42 | other.elSvg(app); 43 | app.use(VueAMap) 44 | 45 | app 46 | .use(ElementPlus) 47 | .use(i18n) 48 | .use(pinia) 49 | .use(router) 50 | .use(XmForm) 51 | .mount('#app'); 52 | 53 | // 全局方法挂载 54 | app.config.globalProperties.getDicts = getDicts 55 | app.config.globalProperties.mittBus = mitt(); 56 | app.config.globalProperties.addDateRange = addDateRange; 57 | app.config.globalProperties.parseTime = parseTime 58 | app.config.globalProperties.handleTree = handleTree 59 | app.config.globalProperties.selectDictLabel = selectDictLabel 60 | app.config.globalProperties.download = download 61 | app.config.globalProperties.dateStrFormat = dateStrFormat 62 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 58 | -------------------------------------------------------------------------------- /src/utils/commonFunction.ts: -------------------------------------------------------------------------------- 1 | // 通用函数 2 | import useClipboard from 'vue-clipboard3'; 3 | import { ElMessage } from 'element-plus'; 4 | import { formatDate } from '@/utils/formatTime'; 5 | import { useI18n } from 'vue-i18n'; 6 | 7 | export default function () { 8 | const { t } = useI18n(); 9 | const { toClipboard } = useClipboard(); 10 | //百分比格式化 11 | const percentFormat = (row: any, column: number, cellValue: any) => { 12 | return cellValue ? `${cellValue}%` : '-'; 13 | }; 14 | //列表日期时间格式化 15 | const dateFormatYMD = (row: any, column: number, cellValue: any) => { 16 | if (!cellValue) return '-'; 17 | return formatDate(new Date(cellValue), 'YYYY-mm-dd'); 18 | }; 19 | //列表日期时间格式化 20 | const dateFormatYMDHMS = (row: any, column: number, cellValue: any) => { 21 | if (!cellValue) return '-'; 22 | return formatDate(new Date(cellValue), 'YYYY-mm-dd HH:MM:SS'); 23 | }; 24 | //列表日期时间格式化 25 | const dateFormatHMS = (row: any, column: number, cellValue: any) => { 26 | if (!cellValue) return '-'; 27 | let time = 0; 28 | if (typeof row === 'number') time = row; 29 | if (typeof cellValue === 'number') time = cellValue; 30 | return formatDate(new Date(time * 1000), 'HH:MM:SS'); 31 | }; 32 | // 小数格式化 33 | const scaleFormat = (value: any = 0, scale: number = 4) => { 34 | return Number.parseFloat(value).toFixed(scale); 35 | }; 36 | // 小数格式化 37 | const scale2Format = (value: any = 0) => { 38 | return Number.parseFloat(value).toFixed(2); 39 | }; 40 | // 点击复制文本 41 | const copyText = (text: string) => { 42 | return new Promise((resolve, reject) => { 43 | try { 44 | //复制 45 | toClipboard(text); 46 | //下面可以设置复制成功的提示框等操作 47 | ElMessage.success(t('message.layout.copyTextSuccess')); 48 | resolve(text); 49 | } catch (e) { 50 | //复制失败 51 | ElMessage.error(t('message.layout.copyTextError')); 52 | reject(e); 53 | } 54 | }); 55 | }; 56 | return { 57 | percentFormat, 58 | dateFormatYMD, 59 | dateFormatYMDHMS, 60 | dateFormatHMS, 61 | scaleFormat, 62 | scale2Format, 63 | copyText, 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/theme/dark.scss: -------------------------------------------------------------------------------- 1 | /* 深色模式样式 2 | ------------------------------- */ 3 | [data-theme='dark'] { 4 | // 全局 5 | filter: invert(0.9) hue-rotate(180deg); 6 | img, 7 | .layout-lock-screen-img, 8 | .visualizing-demo2, 9 | .w-e-panel-tab-content { 10 | filter: invert(1) hue-rotate(180deg); 11 | } 12 | .error img { 13 | filter: unset; 14 | } 15 | // element plus 16 | .el-radio-button__original-radio:checked + .el-radio-button__inner, 17 | .el-image-viewer__close, 18 | .el-image-viewer__actions__inner, 19 | .el-image-viewer__next, 20 | .el-image-viewer__prev { 21 | color: #000000 !important; 22 | } 23 | .el-overlay { 24 | background-color: rgba(0, 0, 0, 0.05) !important; 25 | } 26 | .el-drawer { 27 | box-shadow: 0 8px 10px -5px rgb(0 0 0 / 1%), 0 16px 24px 2px rgb(0 0 0 / 2%), 0 6px 30px 5px rgb(0 0 0 / 1%); 28 | } 29 | // 数据可视化演示 30 | .visualizing-container-head { 31 | background: linear-gradient(to bottom, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.02)) !important; 32 | .visualizing-container-head-left-text-box { 33 | color: #000000 !important; 34 | } 35 | } 36 | .visualizing-container-content-left { 37 | background: linear-gradient(to right, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.01)) !important; 38 | } 39 | .visualizing-container-content-center { 40 | background: linear-gradient(to top, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.01)) !important; 41 | } 42 | .visualizing-container-content-right { 43 | background: linear-gradient(to left, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.01)) !important; 44 | } 45 | .cropper-modal { 46 | background-color: #ffffff; 47 | } 48 | // 其它菜单等 49 | --bg-menuBar: #ffffff !important; 50 | --bg-menuBarColor: #303133 !important; 51 | --bg-columnsMenuBar: #ffffff !important; 52 | --bg-columnsMenuBarColor: #303133 !important; 53 | --color-whites: #000000 !important; 54 | 55 | --bg-topBar: #ffffff !important; 56 | --bg-topBarColor: #000000 !important; 57 | } 58 | -------------------------------------------------------------------------------- /src/theme/listCard.scss: -------------------------------------------------------------------------------- 1 | .content_box{ 2 | width: 100%; 3 | display: flex; 4 | flex-wrap: wrap; 5 | 6 | .content_image_item{ 7 | position: relative; 8 | margin: 12px; 9 | width: 300px; 10 | height: 220px; 11 | box-sizing: border-box; 12 | flex-direction: column; 13 | } 14 | .content_table_item{ 15 | position: relative; 16 | margin: 12px; 17 | width: 400px; 18 | height: 220px; 19 | box-sizing: border-box; 20 | flex-direction: column; 21 | } 22 | } 23 | 24 | .el-card.ft-card { 25 | width: 100%; 26 | height: 100%; 27 | box-shadow: h-shadow v-shadow blur spread color inset; 28 | .ft-tag{ 29 | padding: 10px; 30 | height: 40px; 31 | .ml-3{ 32 | margin-left: 6px; 33 | } 34 | } 35 | .ft-head{ 36 | width: 100%; 37 | height: 170px; 38 | border-bottom: 1px solid var(--color-primary); 39 | background: linear-gradient(141.6deg,var(--color-primary-light-8) 0%,rgba(255,255,255,0) 70%); 40 | } 41 | .ft-body{ 42 | margin-top: 5px; 43 | width: 400px; 44 | height: 130px; 45 | padding: 10px; 46 | display: flex; 47 | justify-content: space-between; 48 | 49 | .ft-body-image{ 50 | width: 40%; 51 | height: 100px; 52 | text-align: center; 53 | margin-right: 10px; 54 | } 55 | .ft-body-item{ 56 | width: 60%; 57 | } 58 | .item-mb{ 59 | width: 100%; 60 | margin-bottom: 8px; 61 | text-overflow: ellipsis 62 | } 63 | } 64 | 65 | .ft-image{ 66 | width: 300px; 67 | height: 170px; 68 | border-bottom: 1px solid var(--color-primary) 69 | } 70 | 71 | .ft-foot{ 72 | padding: 0 10px; 73 | height: 50px; 74 | line-height: 50px; 75 | display: flex; 76 | justify-content: space-between; 77 | 78 | .ft-item-name{ 79 | font-size: 16px; 80 | font-weight: bold; 81 | overflow: hidden; 82 | white-space: nowrap; 83 | text-overflow: ellipsis 84 | } 85 | } 86 | } 87 | 88 | 89 | .image-slot { 90 | display: flex; 91 | justify-content: center; 92 | align-items: center; 93 | width: 100%; 94 | height: 100%; 95 | color: var(--el-text-color-secondary); 96 | font-size: 30px; 97 | } 98 | -------------------------------------------------------------------------------- /src/views/device/device/component/view.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/views/device/device/component/device_info.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{rowData.id}} 5 | 6 | 7 | {{rowData.linkStatus ==='inactive'? "未激活": rowData.linkStatus ==='online' ? '在线': "离线"}} 8 | 9 | 10 | {{rowData.name}} 11 | {{rowData.alias}} 12 | {{rowData.deviceGroup.name}} 13 | {{rowData.product.name}} 14 | {{rowData.deviceType ==='direct' ? '直连设备': rowData.deviceType ==='gateway' ? '网关设备': rowData.deviceType ==='gatewayS'?'网关子设备':'监控设备'}} 15 | 16 | {{ dateStrFormat(rowData.createTime) }} 17 | 18 | 19 | {{rowData.description}} 20 | 21 | 22 | 23 | 24 | {{rowData.token}} 25 | 26 | 27 | 28 | 29 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 模板字符串解析,如:template = 'hahaha{name}_{id}' ,param = {name: 'hh', id: 1} 3 | * 解析后为 hahahahh_1 4 | * @param template 模板字符串 5 | * @param param 参数占位符 6 | * @returns 7 | */ 8 | export function templateResolve(template: string, param: any) { 9 | return template.replace(/\{\w+\}/g, (word) => { 10 | const key = word.substring(1, word.length - 1); 11 | const value = param[key]; 12 | if (value != null || value != undefined) { 13 | return value; 14 | } 15 | return ""; 16 | }); 17 | } 18 | 19 | // 首字符头像 20 | export function letterAvatar(name: string, size = 60, color = '') { 21 | name = name || ''; 22 | size = size || 60; 23 | var colours = [ 24 | "#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50", 25 | "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d" 26 | ], 27 | nameSplit = String(name).split(' '), 28 | initials, charIndex, colourIndex, canvas, context, dataURI; 29 | 30 | if (nameSplit.length == 1) { 31 | initials = nameSplit[0] ? nameSplit[0].charAt(0) : '?'; 32 | } else { 33 | initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0); 34 | } 35 | if (window.devicePixelRatio) { 36 | size = (size * window.devicePixelRatio); 37 | } 38 | initials = initials.toLocaleUpperCase() 39 | charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64; 40 | colourIndex = charIndex % 20; 41 | canvas = document.createElement('canvas'); 42 | canvas.width = size; 43 | canvas.height = size; 44 | context = canvas.getContext("2d") as any; 45 | 46 | context.fillStyle = color ? color : colours[colourIndex - 1]; 47 | context.fillRect(0, 0, canvas.width, canvas.height); 48 | context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'"; 49 | context.textAlign = "center"; 50 | context.fillStyle = "#FFF"; 51 | context.fillText(initials, size / 2, size / 1.5); 52 | dataURI = canvas.toDataURL(); 53 | canvas = null; 54 | return dataURI; 55 | }; -------------------------------------------------------------------------------- /src/layout/routerView/iframes.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 15 | 16 | 17 | 18 | 83 | -------------------------------------------------------------------------------- /src/api/device/device.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询Device统计面板 4 | export function getDevicePanel() { 5 | return request({ 6 | url: '/device/panel', 7 | method: 'get', 8 | }) 9 | } 10 | 11 | // 查询Device列表 12 | export function listDevice(query:any) { 13 | return request({ 14 | url: '/device/list', 15 | method: 'get', 16 | params: query 17 | }) 18 | } 19 | // 查询Device列表 20 | export function listDeviceAll(query:any) { 21 | return request({ 22 | url: '/device/list/all', 23 | method: 'get', 24 | params: query 25 | }) 26 | } 27 | 28 | // 查询Device详细 29 | export function getDevice(id:number) { 30 | return request({ 31 | url: '/device/' + id, 32 | method: 'get' 33 | }) 34 | } 35 | 36 | // 查询Device详细 37 | export function getDeviceStatus(id:number,query:any) { 38 | return request({ 39 | url: '/device/' + id+'/status', 40 | method: 'get', 41 | params: query 42 | }) 43 | } 44 | 45 | // 查询Device属性历史 46 | export function getDevicePropertyHistory(id:number,query:any) { 47 | return request({ 48 | url: '/device/' + id+'/property/history', 49 | method: 'get', 50 | params: query 51 | }) 52 | } 53 | 54 | // 设备属性下发 55 | export function downAttribute(id:number,query:any) { 56 | return request({ 57 | url: '/device/' + id+'/attribute/down', 58 | method: 'get', 59 | params: query 60 | }) 61 | } 62 | 63 | 64 | // 新增Device 65 | export function addDevice(data:any) { 66 | return request({ 67 | url: '/device', 68 | method: 'post', 69 | data: data 70 | }) 71 | } 72 | 73 | // 修改Device 74 | export function updateDevice(data:any) { 75 | return request({ 76 | url: '/device', 77 | method: 'put', 78 | data: data 79 | }) 80 | } 81 | 82 | // 删除Device 83 | export function delDevice(id: string) { 84 | return request({ 85 | url: '/device/' + id, 86 | method: 'delete' 87 | }) 88 | } 89 | 90 | // 查询Device详细 91 | export function allotDevice(id:string,query:any) { 92 | return request({ 93 | url: '/device/' + id+'/allot/org', 94 | method: 'get', 95 | params: query 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /src/views/device/product/component/product_info.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | {{rowData.id}} 12 | {{rowData.name}} 13 | {{rowData.productCategory.name}} 14 | {{rowData.deviceType ==='direct' ? '直连设备': rowData.deviceType ==='gateway' ? '网关设备': '网关子设备'}} 15 | 16 | {{rowData.directConnection ? "直连": "非直连"}} 17 | 18 | 19 | {{rowData.protocolName}} 20 | 21 | 22 | {{rowData.ruleName}} 23 | 24 | 25 | {{ dateStrFormat(rowData.createTime) }} 26 | 27 | 28 | {{rowData.description}} 29 | 30 | 31 | 32 | 33 | 34 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /src/views/login/component/mobile.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{ $t('message.mobile.codeText') }} 21 | 22 | 23 | 24 | 25 | 26 | {{ $t('message.mobile.btnText') }} 27 | 28 | 29 | 30 | 31 | 32 | 41 | 42 | 80 | -------------------------------------------------------------------------------- /src/layout/logo/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ getThemeConfig.globalViceTitle }} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 44 | 45 | 84 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | parser: 'vue-eslint-parser', 9 | parserOptions: { 10 | ecmaVersion: 12, 11 | parser: '@typescript-eslint/parser', 12 | sourceType: 'module', 13 | }, 14 | extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'], 15 | plugins: ['vue', '@typescript-eslint'], 16 | rules: { 17 | // http://eslint.cn/docs/rules/ 18 | // https://eslint.vuejs.org/rules/ 19 | '@type-eslint/ban-ts-ignore': 'off', 20 | '@type-eslint/explicit-function-return-type': 'off', 21 | '@type-eslint/no-explicit-any': 'off', 22 | '@type-eslint/no-var-requires': 'off', 23 | '@type-eslint/no-empty-function': 'off', 24 | '@type-eslint/no-use-before-define': 'off', 25 | '@type-eslint/ban-ts-comment': 'off', 26 | '@type-eslint/ban-types': 'off', 27 | '@type-eslint/no-non-null-assertion': 'off', 28 | '@type-eslint/explicit-module-boundary-types': 'off', 29 | 'vue/custom-event-name-casing': 'off', 30 | 'vue/attributes-order': 'off', 31 | 'vue/one-component-per-file': 'off', 32 | 'vue/html-closing-bracket-newline': 'off', 33 | 'vue/max-attributes-per-line': 'off', 34 | 'vue/multiline-html-element-content-newline': 'off', 35 | 'vue/singleline-html-element-content-newline': 'off', 36 | 'vue/attribute-hyphenation': 'off', 37 | 'vue/html-self-closing': 'off', 38 | 'vue/no-multiple-template-root': 'off', 39 | 'vue/require-default-prop': 'off', 40 | 'vue/no-v-model-argument': 'off', 41 | 'vue/no-arrow-functions-in-watch': 'off', 42 | 'vue/no-template-key': 'off', 43 | 'vue/no-v-html': 'off', 44 | 'vue/comment-directive': 'off', 45 | 'vue/no-parsing-error': 'off', 46 | 'vue/no-deprecated-v-on-native-modifier': 'off', 47 | 'vue/multi-word-component-names': 'off', 48 | 'no-useless-escape': 'off', 49 | 'no-sparse-arrays': 'off', 50 | 'no-prototype-builtins': 'off', 51 | 'no-constant-condition': 'off', 52 | 'no-use-before-define': 'off', 53 | 'no-restricted-globals': 'off', 54 | 'no-restricted-syntax': 'off', 55 | 'generator-star-spacing': 'off', 56 | 'no-unreachable': 'off', 57 | 'no-multiple-template-root': 'off', 58 | 'no-unused-vars': 'error', 59 | 'no-v-model-argument': 'off', 60 | 'no-case-declarations': 'off', 61 | 'no-console': 'error', 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /src/theme/media/chart.scss: -------------------------------------------------------------------------------- 1 | @import './index.scss'; 2 | 3 | /* 页面宽度小于768px 4 | ------------------------------- */ 5 | @media screen and (max-width: $sm) { 6 | .big-data-down-left { 7 | width: 100% !important; 8 | flex-direction: unset !important; 9 | flex-wrap: wrap; 10 | .flex-warp-item { 11 | min-height: 196.24px; 12 | padding: 0 7.5px 15px 15px !important; 13 | .flex-warp-item-box { 14 | border: none !important; 15 | border-bottom: 1px solid #ebeef5 !important; 16 | } 17 | } 18 | } 19 | .big-data-down-center { 20 | width: 100% !important; 21 | .big-data-down-center-one, 22 | .big-data-down-center-two { 23 | min-height: 196.24px; 24 | padding-left: 15px !important; 25 | .big-data-down-center-one-content { 26 | border: none !important; 27 | border-bottom: 1px solid #ebeef5 !important; 28 | } 29 | .flex-warp-item-box { 30 | @extend .big-data-down-center-one-content; 31 | } 32 | } 33 | } 34 | .big-data-down-right { 35 | .flex-warp-item { 36 | .flex-warp-item-box { 37 | border: none !important; 38 | border-bottom: 1px solid #ebeef5 !important; 39 | } 40 | &:nth-of-type(2) { 41 | padding-left: 15px !important; 42 | } 43 | &:last-of-type { 44 | .flex-warp-item-box { 45 | border: none !important; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | /* 页面宽度大于768px小于1200px 53 | ------------------------------- */ 54 | @media screen and (min-width: $sm) and (max-width: $lg) { 55 | .chart-warp-bottom { 56 | .big-data-down-left { 57 | width: 50% !important; 58 | } 59 | .big-data-down-center { 60 | width: 50% !important; 61 | } 62 | .big-data-down-right { 63 | .flex-warp-item { 64 | width: 50% !important; 65 | &:nth-of-type(2) { 66 | padding-left: 7.5px !important; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | /* 页面宽度小于1200px 74 | ------------------------------- */ 75 | @media screen and (max-width: $lg) { 76 | .chart-warp-top { 77 | .up-left { 78 | display: none; 79 | } 80 | } 81 | .chart-warp-bottom { 82 | overflow-y: auto !important; 83 | flex-wrap: wrap; 84 | .big-data-down-right { 85 | width: 100% !important; 86 | flex-direction: unset !important; 87 | flex-wrap: wrap; 88 | .flex-warp-item { 89 | min-height: 196.24px; 90 | padding: 0 7.5px 15px 15px !important; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 7 | {{ $t('message.notFound.foundTitle') }} 8 | {{ $t('message.notFound.foundMsg') }} 9 | 10 | {{ $t('message.notFound.foundBtn') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 38 | 39 | 96 | -------------------------------------------------------------------------------- /src/api/system/user.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | 4 | // 查询用户列表 5 | export function authUser(query: any) { 6 | return request({ 7 | url: '/system/user/auth', 8 | method: 'get', 9 | params: query 10 | }) 11 | } 12 | 13 | // 查询用户列表 14 | export function listUser(query: any) { 15 | return request({ 16 | url: '/system/user/list', 17 | method: 'get', 18 | params: query 19 | }) 20 | } 21 | 22 | // 用户状态修改 23 | export function changeUserStatus(userId: number, status: string) { 24 | const data = { 25 | userId, 26 | status 27 | } 28 | return request({ 29 | url: '/system/user/changeStatus', 30 | method: 'put', 31 | data: data 32 | }) 33 | } 34 | 35 | // 查询用户详细 36 | export function getUser(userId: number) { 37 | return request({ 38 | url: '/system/user/getById/' + userId, 39 | method: 'get' 40 | }) 41 | } 42 | 43 | // 添加时查询用户详细 44 | export function getUserInit() { 45 | return request({ 46 | url: '/system/user/getInit', 47 | method: 'get' 48 | }) 49 | } 50 | 51 | // 添加时查询用户ROLE 52 | export function getRoPo() { 53 | return request({ 54 | url: '/system/user/getRoPo', 55 | method: 'get' 56 | }) 57 | } 58 | // 删除用户 59 | export function delUser(userId: number) { 60 | return request({ 61 | url: '/system/user/' + userId, 62 | method: 'delete' 63 | }) 64 | } 65 | 66 | // 新增用户 67 | export function addUser(data: any) { 68 | return request({ 69 | url: '/system/user', 70 | method: 'post', 71 | data: data 72 | }) 73 | } 74 | 75 | // 修改用户 76 | export function updateUser(data:any) { 77 | return request({ 78 | url: '/system/user', 79 | method: 'put', 80 | data: data 81 | }) 82 | } 83 | 84 | // 修改密码 85 | export function updateUserPwd(data:any) { 86 | return request({ 87 | url: '/system/user/pwd', 88 | method: 'put', 89 | data: data 90 | }) 91 | } 92 | 93 | 94 | // 导出 95 | export function exportUser(query: any) { 96 | return request({ 97 | url: '/system/user/export', 98 | method: 'get', 99 | params: query, 100 | responseType: 'blob' 101 | }) 102 | } 103 | 104 | // 下载用户导入模板 105 | export function importTemplate() { 106 | return request({ 107 | url: '/system/user/importTemplate', 108 | method: 'get' 109 | }) 110 | } 111 | 112 | // 用户头像上传 113 | export function uploadAvatar(data:any) { 114 | return request({ 115 | url: '/system/user/avatar', 116 | method: 'post', 117 | data: data 118 | }) 119 | } -------------------------------------------------------------------------------- /src/components/svgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PandaIotUi", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "vite --force", 6 | "build": "vite build", 7 | "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/" 8 | }, 9 | "dependencies": { 10 | "@element-plus/icons-vue": "^2.1.0", 11 | "@vuemap/vue-amap": "^2.0.22", 12 | "@wangeditor/editor": "^5.1.23", 13 | "@wangeditor/editor-for-vue": "^5.1.12", 14 | "axios": "^1.4.0", 15 | "codemirror": "^5.65.1", 16 | "codemirror-editor-vue3": "^2.1.7", 17 | "countup.js": "^2.6.0", 18 | "cropperjs": "^1.5.13", 19 | "echarts": "^5.4.2", 20 | "echarts-gl": "^2.0.9", 21 | "echarts-wordcloud": "^2.0.0", 22 | "element-plus": "^2.3.10", 23 | "file-saver": "^2.0.5", 24 | "js-cookie": "^3.0.1", 25 | "jsplumb": "^2.15.6", 26 | "mitt": "^3.0.0", 27 | "nprogress": "^0.2.0", 28 | "pandax-form": "^1.6.0", 29 | "pinia": "^2.0.35", 30 | "print-js": "^1.6.0", 31 | "qrcodejs2-fixes": "^0.0.2", 32 | "resize-detector": "^0.3.0", 33 | "screenfull": "^6.0.0", 34 | "sortablejs": "^1.15.0", 35 | "vue": "^3.3.0", 36 | "vue-clipboard3": "^2.0.0", 37 | "vue-i18n": "^9.2.2", 38 | "vue-router": "^4.2.2" 39 | }, 40 | "devDependencies": { 41 | "@types/axios": "^0.14.0", 42 | "@types/clipboard": "^2.0.1", 43 | "@types/js-cookie": "^3.0.2", 44 | "@types/node": "^17.0.24", 45 | "@types/nprogress": "^0.2.0", 46 | "@types/sortablejs": "^1.10.7", 47 | "@typescript-eslint/eslint-plugin": "^5.58.0", 48 | "@typescript-eslint/parser": "^5.58.0", 49 | "@vitejs/plugin-vue": "^4.2.1", 50 | "@vue/compiler-sfc": "^3.2.47", 51 | "dotenv": "^10.0.0", 52 | "eslint": "^8.13.0", 53 | "eslint-plugin-vue": "^8.6.0", 54 | "prettier": "^2.6.2", 55 | "sass": "^1.62.1", 56 | "sass-loader": "^12.4.0", 57 | "script-loader": "^0.7.2", 58 | "typescript": "^5.0.4", 59 | "vite": "^4.3.5", 60 | "vite-plugin-vue-setup-extend": "^0.4.0", 61 | "vue-eslint-parser": "^8.3.0" 62 | }, 63 | "browserslist": [ 64 | "> 1%", 65 | "last 2 versions", 66 | "not dead" 67 | ], 68 | "bugs": { 69 | "url": "https://github.com/PandaGoAdmin/PandaX/issues" 70 | }, 71 | "engines": { 72 | "node": ">=12.0.0", 73 | "npm": ">= 6.0.0" 74 | }, 75 | "keywords": [ 76 | "vue", 77 | "vue3", 78 | "vuejs/vue-next", 79 | "element-ui", 80 | "element-plus", 81 | "vue-next-admin", 82 | "next-admin" 83 | ], 84 | "repository": { 85 | "type": "git", 86 | "url": "https://github.com/PandaGoAdmin/PandaX" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/views/error/401.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 401 7 | {{ $t('message.noAccess.accessTitle') }} 8 | {{ $t('message.noAccess.accessMsg') }} 9 | 10 | {{ $t('message.noAccess.accessBtn') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 40 | 41 | 98 | -------------------------------------------------------------------------------- /src/components/editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 96 | -------------------------------------------------------------------------------- /src/layout/routerView/parent.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 86 | -------------------------------------------------------------------------------- /src/components/panda/MDInput.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/develop/code/component/basicInfoForm.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 71 | -------------------------------------------------------------------------------- /src/theme/waves.scss: -------------------------------------------------------------------------------- 1 | /* Waves v0.6.0 2 | * http://fian.my.id/Waves 3 | * 4 | * Copyright 2014 Alfiana E. Sibuea and other contributors 5 | * Released under the MIT license 6 | * https://github.com/fians/Waves/blob/master/LICENSE 7 | */ 8 | .waves-effect { 9 | position: relative; 10 | cursor: pointer; 11 | display: inline-block; 12 | overflow: hidden; 13 | -webkit-user-select: none; 14 | -moz-user-select: none; 15 | -ms-user-select: none; 16 | user-select: none; 17 | -webkit-tap-highlight-color: transparent; 18 | vertical-align: middle; 19 | z-index: 1; 20 | will-change: opacity, transform; 21 | transition: all 0.3s ease-out; 22 | } 23 | .waves-effect .waves-ripple { 24 | position: absolute; 25 | border-radius: 50%; 26 | width: 20px; 27 | height: 20px; 28 | margin-top: -10px; 29 | margin-left: -10px; 30 | opacity: 0; 31 | background: rgba(0, 0, 0, 0.2); 32 | transition: all 0.7s ease-out; 33 | transition-property: opacity, -webkit-transform; 34 | transition-property: transform, opacity; 35 | transition-property: transform, opacity, -webkit-transform; 36 | -webkit-transform: scale(0); 37 | transform: scale(0); 38 | pointer-events: none; 39 | } 40 | .waves-effect.waves-light .waves-ripple { 41 | background-color: rgba(255, 255, 255, 0.45); 42 | } 43 | .waves-effect.waves-red .waves-ripple { 44 | background-color: rgba(244, 67, 54, 0.7); 45 | } 46 | .waves-effect.waves-yellow .waves-ripple { 47 | background-color: rgba(255, 235, 59, 0.7); 48 | } 49 | .waves-effect.waves-orange .waves-ripple { 50 | background-color: rgba(255, 152, 0, 0.7); 51 | } 52 | .waves-effect.waves-purple .waves-ripple { 53 | background-color: rgba(156, 39, 176, 0.7); 54 | } 55 | .waves-effect.waves-green .waves-ripple { 56 | background-color: rgba(76, 175, 80, 0.7); 57 | } 58 | .waves-effect.waves-teal .waves-ripple { 59 | background-color: rgba(0, 150, 136, 0.7); 60 | } 61 | .waves-effect input[type='button'], 62 | .waves-effect input[type='reset'], 63 | .waves-effect input[type='submit'] { 64 | border: 0; 65 | font-style: normal; 66 | font-size: inherit; 67 | text-transform: inherit; 68 | background: none; 69 | } 70 | .waves-notransition { 71 | transition: none !important; 72 | } 73 | .waves-circle { 74 | -webkit-transform: translateZ(0); 75 | transform: translateZ(0); 76 | -webkit-mask-image: -webkit-radial-gradient(circle, #fff 100%, #000 100%); 77 | } 78 | .waves-input-wrapper { 79 | border-radius: 0.2em; 80 | vertical-align: bottom; 81 | } 82 | .waves-input-wrapper .waves-button-input { 83 | position: relative; 84 | top: 0; 85 | left: 0; 86 | z-index: 1; 87 | } 88 | .waves-circle { 89 | text-align: center; 90 | width: 2.5em; 91 | height: 2.5em; 92 | line-height: 2.5em; 93 | border-radius: 50%; 94 | -webkit-mask-image: none; 95 | } 96 | .waves-block { 97 | display: block; 98 | } 99 | a.waves-effect .waves-ripple { 100 | z-index: -1; 101 | } 102 | -------------------------------------------------------------------------------- /src/stores/interface/index.ts: -------------------------------------------------------------------------------- 1 | //这是pinia官方文档中仅有的定义接口方法 https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties 2 | //如此定义之后导入接口只需 import {接口名} from 'interface' 3 | //官方似乎推荐将接口嵌入pinia模块,为避免混淆不予使用 china12315 4 | declare module 'storeInterface' { 5 | export interface ThemeConfigState { 6 | themeConfig: { 7 | isDrawer: boolean; 8 | primary: string; 9 | success: string; 10 | info: string; 11 | warning: string; 12 | danger: string; 13 | topBar: string; 14 | menuBar: string; 15 | columnsMenuBar: string; 16 | topBarColor: string; 17 | menuBarColor: string; 18 | columnsMenuBarColor: string; 19 | isTopBarColorGradual: boolean; 20 | isMenuBarColorGradual: boolean; 21 | isColumnsMenuBarColorGradual: boolean; 22 | isMenuBarColorHighlight: boolean; 23 | isCollapse: boolean; 24 | isUniqueOpened: boolean; 25 | isFixedHeader: boolean; 26 | isFixedHeaderChange: boolean; 27 | isClassicSplitMenu: boolean; 28 | isLockScreen: boolean; 29 | lockScreenTime: number; 30 | isShowLogo: boolean; 31 | isShowLogoChange: boolean; 32 | isBreadcrumb: boolean; 33 | isTagsview: boolean; 34 | isBreadcrumbIcon: boolean; 35 | isTagsviewIcon: boolean; 36 | isCacheTagsView: boolean; 37 | isSortableTagsView: boolean; 38 | isShareTagsView: boolean; 39 | isFooter: boolean; 40 | isGrayscale: boolean; 41 | isInvert: boolean; 42 | isIsDark: boolean; 43 | isWartermark: boolean; 44 | wartermarkText: string; 45 | tagsStyle: string; 46 | animation: string; 47 | columnsAsideStyle: string; 48 | columnsAsideLayout: string; 49 | layout: string; 50 | isRequestRoutes: boolean; 51 | globalTitle: string; 52 | globalViceTitle: string; 53 | globalI18n: string; 54 | globalComponentSize: string; 55 | }; 56 | } 57 | 58 | // 路由列表 59 | export interface RoutesListState { 60 | routesList: Array; 61 | isColumnsMenuHover: Boolean; 62 | isColumnsNavHover: Boolean; 63 | } 64 | 65 | // 路由缓存列表 66 | export interface KeepAliveNamesState { 67 | keepAliveNames: string[]; 68 | cachedViews: string[]; 69 | } 70 | 71 | // TagsView 路由列表 72 | export interface TagsViewRoutesState { 73 | tagsViewRoutes: Array; 74 | isTagsViewCurrenFull: Boolean; 75 | } 76 | 77 | // 用户信息 78 | export interface UserInfosState { 79 | userInfos: any; 80 | } 81 | 82 | // 后端返回原始路由(未处理时) 83 | export interface RequestOldRoutesState { 84 | requestOldRoutes: Array; 85 | } 86 | } -------------------------------------------------------------------------------- /src/views/device/product/component/product_tsl.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 物模型TSL 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 导 出 25 | 取 消 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 95 | 96 | 99 | -------------------------------------------------------------------------------- /src/views/device/device/component/allot.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 25 | 26 | 27 | 28 | 29 | 取 消 30 | 分 配 31 | 32 | 33 | 34 | 35 | 36 | 37 | 100 | 101 | 104 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 101 | -------------------------------------------------------------------------------- /src/layout/component/main.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 90 | -------------------------------------------------------------------------------- /src/utils/getStyleSheets.ts: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'vue'; 2 | import * as svg from '@element-plus/icons-vue'; 3 | 4 | // 获取阿里字体图标 5 | const getAlicdnIconfont = () => { 6 | return new Promise((resolve, reject) => { 7 | nextTick(() => { 8 | const styles: any = document.styleSheets; 9 | let sheetsList = []; 10 | let sheetsIconList = []; 11 | for (let i = 0; i < styles.length; i++) { 12 | if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) { 13 | sheetsList.push(styles[i]); 14 | } 15 | } 16 | for (let i = 0; i < sheetsList.length; i++) { 17 | for (let j = 0; j < sheetsList[i].cssRules.length; j++) { 18 | if (sheetsList[i].cssRules[j].selectorText && sheetsList[i].cssRules[j].selectorText.indexOf('.icon-') > -1) { 19 | sheetsIconList.push( 20 | `${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}` 21 | ); 22 | } 23 | } 24 | } 25 | if (sheetsIconList.length > 0) resolve(sheetsIconList); 26 | else reject('未获取到值,请刷新重试'); 27 | }); 28 | }); 29 | }; 30 | 31 | // 初始化获取 css 样式,获取 element plus 自带 svg 图标,增加了 ele- 前缀,使用时:ele-Aim 32 | const getElementPlusIconfont = () => { 33 | return new Promise((resolve, reject) => { 34 | nextTick(() => { 35 | const icons = svg as any; 36 | const sheetsIconList = []; 37 | for (const i in icons) { 38 | sheetsIconList.push(`element${icons[i].name}`); 39 | } 40 | if (sheetsIconList.length > 0) resolve(sheetsIconList); 41 | else reject('未获取到值,请刷新重试'); 42 | }); 43 | }); 44 | }; 45 | 46 | // 初始化获取 css 样式,这里使用 fontawesome 的图标 47 | const getAwesomeIconfont = () => { 48 | return new Promise((resolve, reject) => { 49 | nextTick(() => { 50 | const styles: any = document.styleSheets; 51 | let sheetsList = []; 52 | let sheetsIconList = []; 53 | for (let i = 0; i < styles.length; i++) { 54 | if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) { 55 | sheetsList.push(styles[i]); 56 | } 57 | } 58 | for (let i = 0; i < sheetsList.length; i++) { 59 | for (let j = 0; j < sheetsList[i].cssRules.length; j++) { 60 | if ( 61 | sheetsList[i].cssRules[j].selectorText && 62 | sheetsList[i].cssRules[j].selectorText.indexOf('.fa-') === 0 && 63 | sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1 64 | ) { 65 | if (/::before/.test(sheetsList[i].cssRules[j].selectorText)) { 66 | sheetsIconList.push( 67 | `${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}` 68 | ); 69 | } 70 | } 71 | } 72 | } 73 | if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse()); 74 | else reject('未获取到值,请刷新重试'); 75 | }); 76 | }); 77 | }; 78 | 79 | /** 80 | * 获取字体图标 `document.styleSheets` 81 | * @method ali 获取阿里字体图标 `` 82 | * @method ele 获取 element plus 自带图标 `` 83 | * @method ali 获取 fontawesome 的图标 `` 84 | */ 85 | const initIconfont = { 86 | // iconfont 87 | ali: () => { 88 | return getAlicdnIconfont(); 89 | }, 90 | // element plus 91 | ele: () => { 92 | return getElementPlusIconfont(); 93 | }, 94 | // fontawesome 95 | awe: () => { 96 | return getAwesomeIconfont(); 97 | }, 98 | }; 99 | 100 | // 导出方法 101 | export default initIconfont; -------------------------------------------------------------------------------- /src/layout/navBars/breadcrumb/userNews.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ $t('message.user.newTitle') }} 5 | 6 | 7 | 8 | 9 | 10 | {{ v.type }} - {{ v.level ==='CRITICAL'? "危险": v.level ==='MAJOR' ? '重要': v.level ==='MINOR' ? '次要': v.level ==='WARNING' ? "警告": "不确定" }} 11 | 12 | 告警时间:{{ dateStrFormat(v.time) }} 13 | 14 | 15 | 16 | 17 | {{ $t('message.user.newGo') }} 18 | 19 | 20 | 21 | 66 | 67 | 123 | -------------------------------------------------------------------------------- /src/views/develop/code/component/genInfoForm.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 指定应用名 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 生成模块名 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 生成业务名 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 生成功能名 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 91 | -------------------------------------------------------------------------------- /src/utils/other.ts: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'vue'; 2 | import { App } from 'vue'; 3 | import * as svg from '@element-plus/icons-vue'; 4 | import router from '@/router/index'; 5 | import {useThemeConfigStateStore} from '@/stores/themeConfig' 6 | import { i18n } from '@/i18n/index'; 7 | import SvgIcon from '@/components/svgIcon/index.vue'; 8 | 9 | /** 10 | * 导出全局注册 element plus svg 图标 11 | * @param app vue 实例 12 | * @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html 13 | */ 14 | export function elSvg(app: App) { 15 | const icons = svg as any; 16 | for (const i in icons) { 17 | app.component(`element${icons[i].name}`, icons[i]); 18 | } 19 | app.component('SvgIcon', SvgIcon); 20 | } 21 | 22 | /** 23 | * 设置浏览器标题国际化 24 | * @method const title = useTitle(); ==> title() 25 | */ 26 | export function useTitle() { 27 | const theme = useThemeConfigStateStore(); 28 | nextTick(() => { 29 | let webTitle = ''; 30 | let globalTitle: string = theme.themeConfig.globalTitle; 31 | router.currentRoute.value.path === '/login' 32 | ? (webTitle = router.currentRoute.value.meta.title as any) 33 | : (webTitle = i18n.global.t(router.currentRoute.value.meta.title as any)); 34 | document.title = `${webTitle} - ${globalTitle}` || globalTitle; 35 | }); 36 | } 37 | 38 | /** 39 | * 图片懒加载 40 | * @param el dom 目标元素 41 | * @param arr 列表数据 42 | * @description data-xxx 属性用于存储页面或应用程序的私有自定义数据 43 | */ 44 | export const lazyImg = (el: any, arr: any) => { 45 | const io = new IntersectionObserver((res) => { 46 | res.forEach((v: any) => { 47 | if (v.isIntersecting) { 48 | const { img, key } = v.target.dataset; 49 | v.target.src = img; 50 | v.target.onload = () => { 51 | io.unobserve(v.target); 52 | arr[key]['loading'] = false; 53 | }; 54 | } 55 | }); 56 | }); 57 | nextTick(() => { 58 | document.querySelectorAll(el).forEach((img) => io.observe(img)); 59 | }); 60 | }; 61 | 62 | /** 63 | * 对象深克隆 64 | * @param obj 源对象 65 | * @returns 克隆后的对象 66 | */ 67 | export function deepClone(obj: any) { 68 | let newObj: any; 69 | try { 70 | newObj = obj.push ? [] : {}; 71 | } catch (error) { 72 | newObj = {}; 73 | } 74 | for (let attr in obj) { 75 | if (obj[attr] && typeof obj[attr] === 'object') { 76 | newObj[attr] = deepClone(obj[attr]); 77 | } else { 78 | newObj[attr] = obj[attr]; 79 | } 80 | } 81 | return newObj; 82 | } 83 | 84 | /** 85 | * 判断是否是移动端 86 | */ 87 | export function isMobile() { 88 | if ( 89 | navigator.userAgent.match( 90 | /('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i 91 | ) 92 | ) { 93 | return true; 94 | } else { 95 | return false; 96 | } 97 | } 98 | 99 | /** 100 | * 统一批量导出 101 | * @method elSvg 导出全局注册 element plus svg 图标 102 | * @method useTitle 设置浏览器标题国际化 103 | * @method lazyImg 图片懒加载 104 | * @method globalComponentSize element plus 全局组件大小 105 | * @method deepClone 对象深克隆 106 | * @method isMobile 判断是否是移动端 107 | */ 108 | const other = { 109 | elSvg: (app: App) => { 110 | elSvg(app); 111 | }, 112 | useTitle: () => { 113 | useTitle(); 114 | }, 115 | lazyImg: (el: any, arr: any) => { 116 | lazyImg(el, arr); 117 | }, 118 | deepClone: (obj: any) => { 119 | return deepClone(obj); 120 | }, 121 | isMobile: () => { 122 | return isMobile(); 123 | }, 124 | }; 125 | 126 | // 统一批量导出 127 | export default other; -------------------------------------------------------------------------------- /src/views/system/tenant/component/editModule.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | 7 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 取 消 30 | 保 存 31 | 32 | 33 | 34 | 35 | 36 | 37 | 112 | -------------------------------------------------------------------------------- /src/layout/navMenu/vertical.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 18 | {{ $t(val.meta.title) }} 19 | 20 | 21 | 22 | 23 | 24 | 28 | {{ $t(val.meta.title) }} 29 | 30 | 31 | {{ 32 | $t(val.meta.title) 33 | }} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 118 | -------------------------------------------------------------------------------- /src/components/jessibuca/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 166 | 167 | 174 | --------------------------------------------------------------------------------