diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb410c9 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# system-project + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Lints and fixes files +``` +npm run lint +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..e955840 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..4aafc5f --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "esnext", + "baseUrl": "./", + "moduleResolution": "node", + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d681847 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "system-project", + "version": "0.1.0", + "private": true, + "scripts": { + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "lint": "vue-cli-service lint" + }, + "dependencies": { + "axios": "^1.7.2", + "compression-webpack-plugin": "^6.1.2", + "core-js": "^3.8.3", + "css-loader": "^7.1.2", + "element-ui": "^2.15.14", + "js-cookie": "^3.0.5", + "js-md5": "^0.8.3", + "node-sass": "^9.0.0", + "normalize.css": "^8.0.1", + "nprogress": "^0.2.0", + "sass-loader": "^14.2.1", + "style-loader": "^4.0.0", + "vue": "^2.6.14", + "vue-router": "^2.8.1", + "vuex": "^3.6.2" + }, + "devDependencies": { + "@babel/core": "^7.12.16", + "@babel/eslint-parser": "^7.12.16", + "@vue/cli-plugin-babel": "~5.0.0", + "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-service": "~5.0.0", + "eslint": "^7.32.0", + "eslint-plugin-vue": "^8.0.3", + "vue-template-compiler": "^2.6.14" + }, + "eslintConfig": { + "root": true, + "env": { + "node": true + }, + "extends": [ + "plugin:vue/essential", + "eslint:recommended" + ], + "parserOptions": { + "parser": "@babel/eslint-parser" + }, + "rules": {} + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not dead" + ] +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..df36fcf --- /dev/null +++ b/public/favicon.ico diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3e5a139 --- /dev/null +++ b/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..b02130c --- /dev/null +++ b/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/api/area.js b/src/api/area.js new file mode 100644 index 0000000..e3de28f --- /dev/null +++ b/src/api/area.js @@ -0,0 +1,12 @@ +// 公司信息 +import request from "@/utils/request"; + +// 获取系统类型 +export async function getAreaSelect(data) { + return await request({ + url: '/extend/systeminformation/GetRegionList', + method: 'get', + params: data + }); +} + diff --git a/src/api/common.js b/src/api/common.js new file mode 100644 index 0000000..6a65429 --- /dev/null +++ b/src/api/common.js @@ -0,0 +1,23 @@ +// 公司信息 +import request from "@/utils/request"; + +// 获取系统类型 +export async function getSystemTypeList() { + return await request({ + url: '/extend/systeminformation/GetSystemTypeList', + method: 'get', + }); +} + +// 上传文件 +export async function uploader({ type, file }) { + return await request({ + headers: { + 'Content-Type': 'multipart/form-data', + }, + url: `/file/Uploader/${type}`, + method: 'post', + data: { file } + }); +} + diff --git a/src/api/company.js b/src/api/company.js new file mode 100644 index 0000000..83fec3e --- /dev/null +++ b/src/api/company.js @@ -0,0 +1,18 @@ +// 公司信息 +import request from "@/utils/request"; + +// 获取公司信息列表 +export async function getCompanyInfoList() { + return await request({ + url: '/Extend/basecomapnyinfo', + method: 'get', + }); +} +// 获取公司信息 +export async function getCompanyInfoById(id) { + return await request({ + url: `/Extend/basecomapnyinfo/${id}`, + method: 'get', + }); +} + diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..f9722d9 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,45 @@ +import request from "@/utils/request"; + + +// 登录 +export async function login(data) { + return await request({ + url: '/oauth/Login', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded',//类型设置 + }, + method: 'post', + data: data + }); +} +// 获取用户信息 +export async function getInfo() { + return await request({ + url: '/oauth/CurrentUser', + method: 'get', + }); +} +// 退出登录 +export async function logout() { + return await request({ + url: '/oauth/Logout', + method: 'get', + }); +} +// 修改密码 +export async function updateUserPwd(id, userPassword, validatePassword) { + return await request({ + url: `/permission/users/${id}/Actions/ResetPassword`, + method: 'post', + data: {userPassword, validatePassword} + }); +} + +// 注册账号 +export async function register(data) { + return await request({ + url: `/permission/users/CreateSimple`, + method: 'post', + data + }); +} diff --git a/src/api/info.js b/src/api/info.js new file mode 100644 index 0000000..a224893 --- /dev/null +++ b/src/api/info.js @@ -0,0 +1,33 @@ +// 公司信息 +import request from "@/utils/request"; + +// 新增应用 +export async function addSystem(data) { + return await request({ + url: '/Extend/basesysteminfo', + method: 'post', + data + }); +} +export async function getInfoList(data) { + return await request({ + url: '/Extend/basesysteminfo/SearchSystemList', + method: 'get', + params: data + }); +} +export async function updataSystem(data) { + let id = data.id; + return await request({ + url: `/Extend/basesysteminfo/${id}`, + method: 'put', + params: data + }); +} +export async function getSystemDetail(id) { + return await request({ + url: `/Extend/basesysteminfo/${id}`, + method: 'get', + }); +} + diff --git a/src/api/systemClass.js b/src/api/systemClass.js new file mode 100644 index 0000000..142c2c0 --- /dev/null +++ b/src/api/systemClass.js @@ -0,0 +1,12 @@ +// 公司信息 +import request from "@/utils/request"; + +// 获取系统类型 +export async function getSystemClassSelect(data) { + return await request({ + url: '/Extend/basesystemclassification/GetSystemClassificationlist', + method: 'get', + params: data + }); +} + diff --git a/src/assets/images/bg.jpg b/src/assets/images/bg.jpg new file mode 100644 index 0000000..b2ad737 --- /dev/null +++ b/src/assets/images/bg.jpg diff --git a/src/assets/images/login-box-container.png b/src/assets/images/login-box-container.png new file mode 100644 index 0000000..fe80aba --- /dev/null +++ b/src/assets/images/login-box-container.png diff --git a/src/assets/images/login-btn.png b/src/assets/images/login-btn.png new file mode 100644 index 0000000..132757f --- /dev/null +++ b/src/assets/images/login-btn.png diff --git a/src/assets/images/login_bg.png b/src/assets/images/login_bg.png new file mode 100644 index 0000000..1c9e516 --- /dev/null +++ b/src/assets/images/login_bg.png diff --git a/src/assets/images/user.jpg b/src/assets/images/user.jpg new file mode 100644 index 0000000..8421569 --- /dev/null +++ b/src/assets/images/user.jpg diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 --- /dev/null +++ b/src/assets/logo.png diff --git a/src/assets/mockdata/demodata.json b/src/assets/mockdata/demodata.json new file mode 100644 index 0000000..71acf5d --- /dev/null +++ b/src/assets/mockdata/demodata.json @@ -0,0 +1,36 @@ +{ + "todoObj": { + "teskName": "任务名称1", + "teskCode": "任务编号", + "teskStatus": "任务状态", + "teskTime": "任务期限", + "teskUser": "分配者" + }, + "tipObj": { + "content": "任务名称1", + "time": "2022-01-01 20:00:00" + }, + "areaObj": { + "actName": "行动名称", + "child1": "8", + "child2": "8", + "startTime": "2024-07-01", + "endTime": "2024-07-31", + "type": "1" + }, + "chObj": { + "actName": "行动名称", + "startTime": "2024-07-01", + "endTime": "2024-07-31", + "type": "2" + }, + "navArr": [ + {"title": "专项行动"}, + {"title": "侦查处置"}, + {"title": "网信执法"}, + {"title": "考核评估"}, + {"title": "清单管理"}, + {"title": "查询上报"}, + {"title": "数字大屏"} + ] +} \ No newline at end of file diff --git a/src/assets/style/common.scss b/src/assets/style/common.scss new file mode 100644 index 0000000..69fa1ad --- /dev/null +++ b/src/assets/style/common.scss @@ -0,0 +1,62 @@ +html, +body, +#app { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +::-webkit-scrollbar-track-piece { + -webkit-border-radius: 0 +} +::-webkit-scrollbar { + width: 5px; + height: 10px +} +::-webkit-scrollbar-thumb { + height: 50px; + background-color: #b8b8b8; + -webkit-border-radius: 6px; + outline-offset: -2px; + filter: alpha(opacity = 50); + -moz-opacity: 0.5; + -khtml-opacity: 0.5; + opacity: 0.5 +} +::-webkit-scrollbar-thumb:hover { + height: 50px; + background-color: #878987; + -webkit-border-radius: 6px +} + +.item-box { + width: 100%; + border-radius: 10px; + background-color: rgba(244, 244, 245, 0.38); + margin-bottom: 15px; + .item-title { + color: rgba(255, 255, 255, 1); + font-size: 16px; + line-height: 23px; + padding: 10px; + } + .item-body { + height: calc(100% - 68px); + margin: 0 13px; + padding-bottom: 28px; + .el-table { + border-radius: 10px; + height: 100%; + } + } +} + +.router-link-active { + text-decoration: none +} + +a { + text-decoration: none + +} \ No newline at end of file diff --git a/src/assets/style/homePage.scss b/src/assets/style/homePage.scss new file mode 100644 index 0000000..ec4240f --- /dev/null +++ b/src/assets/style/homePage.scss @@ -0,0 +1,119 @@ +.HomePage { + width: 100%; + height: 100%; + background-image: url("@/assets/images/bg.jpg"); /* 替换为你的图片路径 */ + background-size: cover; /* 背景图片覆盖整个元素 */ + background-position: center; /* 背景图片居中 */ + background-repeat: no-repeat; /* 不重复背景图片 */ +} +.content { + margin-top: 56px; + position: relative; + .navs { + position: absolute; + left: 0; + box-sizing: border-box; + width: 122px; + height: 80vh; + background-color: rgba(228, 231, 237, 0.23); + border-radius: 0px 10px 10px 0px; + padding: 25px; + border-right: unset; + .el-menu-item { + display: flex; + flex-direction: column; + align-items: center; + color: #fff; + margin-bottom: 15px; + &.is-active { + border-radius: 5px; + background-color: #67c23a; + } + &:hover { + background-color: #dfdada56; + } + i { + font-size: 30px; + color: #fff; + } + span { + color: #fff; + line-height: 36px; + } + } + } + .table-box { + position: absolute; + right: 122px; + width: calc(100% - 122px - 122px - 26px - 26px); + margin: 0 26px; + .search { + border-radius: 10px; + background-color: rgba(244, 244, 245, 0.38); + display: flex; + flex-direction: row; + align-items: center; + padding: 12px; + .ipt-box { + display: flex; + flex-direction: row; + align-items: center; + .el-input { + width: 300px; + :deep(.el-input__inner) { + border-radius: 4px 0 0 4px; + } + } + .el-button { + height: 40px; + background: rgb(164, 173, 179); + color: #fff; + border-radius: 0 4px 4px 0; + margin-right: 10px; + border: unset; + } + } + } + .info { + margin-top: 14px; + border-radius: 10px; + height: calc(100vh - 195px); + } + } + .news { + position: absolute; + right: 0; + box-sizing: border-box; + width: 122px; + height: 200px; + background-color: rgba(228, 231, 237, 0.23); + border-radius: 10px 0px 0px 10px; + padding: 20px; + .news-item { + display: flex; + flex-direction: column; + align-items: center; + color: #fff; + cursor: pointer; + margin-bottom: 8px; + .icon-item { + position: relative; + width: 40px; + height: 40px; + text-align: center; + line-height: 40px; + font-size: 40px; + margin: 8px; + .red-spot { + position: absolute; + right: -6px; + top: -6px; + width: 12px; + height: 12px; + background-color: red; + border-radius: 50%; + } + } + } + } + } \ No newline at end of file diff --git a/src/components/CompanyForm/index.vue b/src/components/CompanyForm/index.vue new file mode 100644 index 0000000..e3c0246 --- /dev/null +++ b/src/components/CompanyForm/index.vue @@ -0,0 +1,100 @@ + + + + diff --git a/src/components/Header.vue b/src/components/Header.vue new file mode 100644 index 0000000..dadd99e --- /dev/null +++ b/src/components/Header.vue @@ -0,0 +1,110 @@ + + + + diff --git a/src/components/InfoForm/index.vue b/src/components/InfoForm/index.vue new file mode 100644 index 0000000..0cc99d7 --- /dev/null +++ b/src/components/InfoForm/index.vue @@ -0,0 +1,485 @@ + + + + diff --git a/src/components/InfoList.vue b/src/components/InfoList.vue new file mode 100644 index 0000000..588504d --- /dev/null +++ b/src/components/InfoList.vue @@ -0,0 +1,128 @@ + + + + diff --git a/src/components/Overview.vue b/src/components/Overview.vue new file mode 100644 index 0000000..b0df934 --- /dev/null +++ b/src/components/Overview.vue @@ -0,0 +1,260 @@ + + + + diff --git a/src/components/ParentView/index.vue b/src/components/ParentView/index.vue new file mode 100644 index 0000000..7bf6148 --- /dev/null +++ b/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/PasswordForm/index.vue b/src/components/PasswordForm/index.vue new file mode 100644 index 0000000..0128ce2 --- /dev/null +++ b/src/components/PasswordForm/index.vue @@ -0,0 +1,142 @@ + + + + diff --git a/src/components/RegisterForm/index.vue b/src/components/RegisterForm/index.vue new file mode 100644 index 0000000..b94906a --- /dev/null +++ b/src/components/RegisterForm/index.vue @@ -0,0 +1,170 @@ + + + + diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..c7ae740 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,12 @@ +import InfoForm from '@/components/InfoForm' +import CompanyForm from '@/components/CompanyForm' +import PasswordForm from '@/components/PasswordForm' +import RegisterForm from '@/components/RegisterForm' +export default { + install(Vue, options) { + Vue.component('InfoForm', InfoForm) + Vue.component('CompanyForm', CompanyForm) + Vue.component('PasswordForm', PasswordForm) + Vue.component('RegisterForm', RegisterForm) + } + } \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..c37ef8a --- /dev/null +++ b/src/main.js @@ -0,0 +1,33 @@ +import Vue from 'vue' +import App from './App.vue' +import router from './router' +import 'normalize.css/normalize.css' +import '@/assets/style/common.scss' +import ElementUI from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +import store from './store' +import './permission' + +Vue.config.productionTip = false +Vue.use(ElementUI); +// 批量引入组件 +import components from './components' +Vue.use(components) +Vue.directive('loadMore', { + bind(el, binding) { + // 获取element-ui定义好的scroll盒子 + const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap') + SELECTWRAP_DOM.addEventListener('scroll', function () { + + const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight + if (CONDITION) { + binding.value() + } + }) + } +}) +new Vue({ + router, + store, + render: h => h(App), +}).$mount('#app') diff --git a/src/permission.js b/src/permission.js new file mode 100644 index 0000000..4d52e94 --- /dev/null +++ b/src/permission.js @@ -0,0 +1,58 @@ +import router from './router' +import store from './store' +import { Message } from 'element-ui' +import { getToken } from '@/utils/auth' +import { isRelogin } from '@/utils/request' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' + +NProgress.configure({ showSpinner: false }) +const whiteList = ['/login', '/register'] + +router.beforeEach((to, from, next) => { + NProgress.start() + if (getToken()) { + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + if (store.getters.roles.length === 0) { + isRelogin.show = true + + // 判断当前用户是否已拉取完user_info信息 + store.dispatch('GetInfo').then(() => { + isRelogin.show = false + next({ ...to, replace: true }) + // store.dispatch('GenerateRoutes').then(accessRoutes => { + // // 根据roles权限生成可访问的路由表 + // router.addRoutes(accessRoutes) // 动态添加可访问路由表 + // next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + // }) + }).catch(err => { + store.dispatch('LogOut').then(() => { + Message.error(err) + next({ path: '/login' }) + }) + }) + } else { + next() + } + } + } else { + // 没有token + if (whiteList.indexOf(to.path) !== -1) { + // 在免登录白名单,直接进入 + next() + } else { + next(`/login`) // 否则全部重定向到登录页 + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..3ce4a52 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,46 @@ + +import Vue from "vue"; +import VueRouter from "vue-router"; +import Layout from '@/views/HomePage.vue' + + +Vue.use(VueRouter); + +const router = new VueRouter({ + mode: 'hash', // 使用 hash 模式 + routes: [ + { + path: '', + component: Layout, + name: 'homePage', + children: [ + { + path: '/homePage', + name: 'homePage', + component: () => import('@/components/Overview.vue'), + meta: { title: '首页', icon: 'dashboard', affix: true } + }, + ] + }, + { + path: '', + component: Layout, + name: 'infoList', + children: [ + { + path: '/infoList', + name: 'infoList', + component: () => import('@/components/InfoList.vue'), + meta: { title: '系统', icon: 'dashboard', affix: true } + }, + ] + }, + { + path: '/login', + name: 'login', + component: () => import('@/views/Login.vue'), + }, + ] +}); + +export default router; \ No newline at end of file diff --git a/src/store/getters.js b/src/store/getters.js new file mode 100644 index 0000000..f52c5a8 --- /dev/null +++ b/src/store/getters.js @@ -0,0 +1,20 @@ +const getters = { + sidebar: state => state.app.sidebar, + size: state => state.app.size, + device: state => state.app.device, + dict: state => state.dict.dict, + visitedViews: state => state.tagsView.visitedViews, + cachedViews: state => state.tagsView.cachedViews, + token: state => state.user.token, + avatar: state => state.user.avatar, + name: state => state.user.name, + introduction: state => state.user.introduction, + roles: state => state.user.roles, + permissions: state => state.user.permissions, + permission_routes: state => state.permission.routes, + topbarRouters:state => state.permission.topbarRouters, + defaultRoutes:state => state.permission.defaultRoutes, + sidebarRouters:state => state.permission.sidebarRouters, + } + export default getters + \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..ca26d60 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,19 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import user from './modules/user' +import meta from './modules/meta' +import getters from './getters' +import permission from './modules/permission' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + modules: { + user, + meta, + permission, + }, + getters +}) + +export default store diff --git a/src/store/modules/meta.js b/src/store/modules/meta.js new file mode 100644 index 0000000..f14094f --- /dev/null +++ b/src/store/modules/meta.js @@ -0,0 +1,34 @@ +// 获取公司列表 +import { register } from "@/api"; +import { getCompanyInfoList } from "@/api/company"; + +const meta = { + state: { + metaData: {} + }, + + mutations: { + ADD_MATA_DATA_ITEMS: (state, data) => { + Object.keys(data).forEach(key => { + let value = data[key].map(item => { + let {dictCode, dictValue, dictLabel} = item + return {dictCode, dictValue, dictLabel} + }) + Vue.set(state.metaData, key, value) + }) + } + }, + + actions: { + registerMateDataItems({ commit, state }, items) { + let types = items.filter(item => !state.metaData[item]) + if(types && types.length) { + getCompanyInfoList(types.join(',')).then(res => { + commit('ADD_MATA_DATA_ITEMS', res.data) + }) + } + } + } +} + +export default meta diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js new file mode 100644 index 0000000..b85665c --- /dev/null +++ b/src/store/modules/permission.js @@ -0,0 +1,135 @@ +// import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' +// import { getRouters } from '@/api/menu' +import Layout from '@/views/HomePage.vue' +import ParentView from '@/components/ParentView' + +const permission = { + state: { + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [] + }, + mutations: { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes + state.routes = constantRoutes.concat(routes) + }, + SET_DEFAULT_ROUTES: (state, routes) => { + state.defaultRoutes = constantRoutes.concat(routes) + }, + SET_TOPBAR_ROUTES: (state, routes) => { + state.topbarRouters = routes + }, + SET_SIDEBAR_ROUTERS: (state, routes) => { + state.sidebarRouters = routes + }, + }, + actions: { + // 生成路由 + GenerateRoutes({ commit }) { + return new Promise(resolve => { + // 向后端请求路由数据 + // getRouters().then(res => { + // let data = res.data.filter(v => !(v.name && v.name == 'System')); + // const sdata = JSON.parse(JSON.stringify(data)) + // const rdata = JSON.parse(JSON.stringify(data)) + // const sidebarRoutes = filterAsyncRouter(sdata) + // const rewriteRoutes = filterAsyncRouter(rdata, false, true) + // const asyncRoutes = filterDynamicRoutes(dynamicRoutes); + // rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) + // router.addRoutes(asyncRoutes); + // commit('SET_ROUTES', rewriteRoutes) + // commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) + // commit('SET_DEFAULT_ROUTES', sidebarRoutes) + // commit('SET_TOPBAR_ROUTES', sidebarRoutes) + // resolve(rewriteRoutes) + // }) + }) + } + } +} + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { + return asyncRouterMap.filter(route => { + if (type && route.children) { + route.children = filterChildren(route.children) + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout + } else if (route.component === 'ParentView') { + route.component = ParentView + } else { + route.component = loadView(route.component) + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type) + } else { + delete route['children'] + delete route['redirect'] + } + return true + }) +} + +function filterChildren(childrenMap, lastRouter = false) { + var children = [] + childrenMap.forEach((el, index) => { + if (el.children && el.children.length) { + if (el.component === 'ParentView' && !lastRouter) { + el.children.forEach(c => { + c.path = el.path + '/' + c.path + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)) + return + } + children.push(c) + }) + return + } + } + if (lastRouter) { + el.path = lastRouter.path + '/' + el.path + if (el.children && el.children.length) { + children = children.concat(filterChildren(el.children, el)) + return + } + } + children = children.concat(el) + }) + return children +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = [] + // routes.forEach(route => { + // if (route.permissions) { + // if (auth.hasPermiOr(route.permissions)) { + // res.push(route) + // } + // } else if (route.roles) { + // if (auth.hasRoleOr(route.roles)) { + // res.push(route) + // } + // } + // }) + return res +} + +export const loadView = (view) => { + if (process.env.NODE_ENV === 'development') { + return (resolve) => require([`@/views/${view}`], resolve) + } else { + // 使用 import 实现生产环境的路由懒加载 + return () => import(`@/views/${view}`) + } +} + +export default permission diff --git a/src/store/modules/user.js b/src/store/modules/user.js new file mode 100644 index 0000000..3231305 --- /dev/null +++ b/src/store/modules/user.js @@ -0,0 +1,99 @@ +import { login, getInfo, logout } from '@/api/index' +import { getToken, setToken, removeToken } from '@/utils/auth' +import md5 from "js-md5"; // 密码加密 + +const user = { + state: { + token: getToken(), + id: '', + name: '', + avatar: '', + roles: [], + address: '', + }, + + mutations: { + SET_TOKEN: (state, token) => { + state.token = token + }, + SET_ID: (state, id) => { + state.id = id + }, + SET_NAME: (state, name) => { + state.name = name + }, + SET_AVATAR: (state, avatar) => { + state.avatar = avatar + }, + SET_ROLES: (state, roles) => { + state.roles = roles + }, + SET_ADDRESS: (state, address) => { + state.address = address + } + }, + + actions: { + // 登录 + Login({ commit }, data) { + return new Promise((resolve, reject) => { + let password = md5(data.password); `` + let account = data.account; + login({ account, password }).then(res => { + setToken(res.data.token) + commit('SET_TOKEN', res.data.token) + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 获取用户信息 + GetInfo({ commit, state }) { + return new Promise((resolve, reject) => { + getInfo().then((res) => { + let { data } = res; + const user = data.userInfo + const avatar = (user.headIcon == "" || user.headIcon == null) || require("@/assets/images/user.jpg"); + if (user.roleIds && user.roleIds.length > 0) { // 验证返回的roles是否是一个非空数组 + commit('SET_ROLES', user.roleIds) + } else { + commit('SET_ROLES', ['ROLE_DEFAULT']) + } + commit('SET_ID', user.userId) + commit('SET_NAME', user.userName) + commit('SET_AVATAR', avatar) + commit('SET_ADDRESS', user.loginIPAddressName) + resolve(res) + }).catch(error => { + reject(error) + }) + }) + }, + + // 退出系统 + LogOut({ commit, state }) { + return new Promise((resolve, reject) => { + logout().then(() => { + commit('SET_TOKEN', '') + commit('SET_ROLES', []) + removeToken() + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + // 前端退出 + FedLogOut({ commit, state }) { + return new Promise((resolve, reject) => { + commit('SET_TOKEN', '') + removeToken() + resolve() + }) + }, + } +} + +export default user diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..84acfad --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Account-Token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} diff --git a/src/utils/errorCode.js b/src/utils/errorCode.js new file mode 100644 index 0000000..d2111ee --- /dev/null +++ b/src/utils/errorCode.js @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..ef25042 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,234 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str + } + + // 表单重置 + export function resetForm(refName) { + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } + } + + // 添加日期范围 + export function addDateRange(params, dateRange, propName) { + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; + } + + // 回显数据字典 + export function selectDictLabel(datas, value) { + if (value === undefined) { + return ""; + } + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); + } + + // 回显数据字典(字符串、数组) + export function selectDictLabels(datas, value, separator) { + if (value === undefined || value.length ===0) { + return ""; + } + if (Array.isArray(value)) { + value = value.join(","); + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + var match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }) + return actions.join('').substring(0, actions.join('').length - 1); + } + + // 字符串格式化(%s ) + export function sprintf(str) { + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; + } + + // 转换字符串,undefined,null等转化为"" + export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; + } + + // 数据合并 + export function mergeRecursive(source, target) { + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; + }; + + /** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ + export function handleTree(data, id, parentId, children) { + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; + + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (let t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; + } + + /** + * 参数处理 + * @param {*} params 参数 + */ + export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result + } + + // 验证是否为blob格式 + export function blobValidate(data) { + return data.type !== 'application/json' + } + \ No newline at end of file diff --git a/src/utils/mixin/metaMixin.js b/src/utils/mixin/metaMixin.js new file mode 100644 index 0000000..7353cfa --- /dev/null +++ b/src/utils/mixin/metaMixin.js @@ -0,0 +1,12 @@ +import store from "@/store"; +const metaMixin = { + created() {}, + computed: { + }, + methods: { + + }, +} +export { + metaMixin +} \ No newline at end of file diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..6a04407 --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,73 @@ +import axios from "axios"; +import { getToken, removeToken } from "@/utils/auth"; +import { Notification, MessageBox, Message, Loading } from 'element-ui' +import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from "@/utils/index"; + +// 是否显示重新登录 +export let isRelogin = { show: false }; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' + +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: '/api', + // 超时 + // timeout: 20000 +}) + +//拦截器(请求,响应) +//请求 +service.interceptors.request.use(config => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + // const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + // 判断token + if (getToken() && !isToken) { + config.headers['Authorization'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + return config; +}) +//响应 +service.interceptors.response.use(res => { + const code = res.data.code || 200; + const msg = errorCode[code] || res.data.msg || errorCode['default'] + + if (code === 401 || code == 600) { + if (!isRelogin.show) { + isRelogin.show = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false; + removeToken(); + location.href = '#/login'; + }).catch(() => { + isRelogin.show = false; + }); + } + } else if (code === 500) { + Message({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + Message({ message: msg, type: 'warning' }) + return Promise.reject('error') + } else if (code !== 200) { + Notification.error({ title: msg }) + return Promise.reject('error') + } else { + return res.data + } +}, err => { + //失败 + return Promise.reject(err) +}) + + +export default service; \ No newline at end of file diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue new file mode 100644 index 0000000..ece6ae2 --- /dev/null +++ b/src/views/HomePage.vue @@ -0,0 +1,96 @@ +r + + + + + diff --git a/src/views/Login.vue b/src/views/Login.vue new file mode 100644 index 0000000..bb351ae --- /dev/null +++ b/src/views/Login.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/src/views/Systeminfo/AddDialog.vue b/src/views/Systeminfo/AddDialog.vue new file mode 100644 index 0000000..b92539a --- /dev/null +++ b/src/views/Systeminfo/AddDialog.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..40d7cb9 --- /dev/null +++ b/vue.config.js @@ -0,0 +1,51 @@ +const { defineConfig } = require('@vue/cli-service') +const path = require('path') +function resolve(dir) { + return path.join(__dirname, dir) +} +const CompressionPlugin = require('compression-webpack-plugin') + +module.exports = defineConfig({ + // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 + productionSourceMap: false, + transpileDependencies: true, + lintOnSave: false, + devServer: { + port: 8080, + open: true, + proxy: { + '/api': { + // 跨域请求的地址 + target: 'http://8.130.38.56:8043/api', + changeOrigin: true, // 是否允许跨域请求,在本地会创建一个虚拟服务端,发送接收请求数据,这样服务端和服务端进行数据的交互就不会有跨域问题 + // 路径重写,替换请求地址 + pathRewrite: { + '^/api': '' + } + } + } + }, + configureWebpack: { + name: '属地', + resolve: { + alias: { + '@': resolve('src') + } + }, + plugins: [ + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + new CompressionPlugin({ + cache: false, // 不启用文件缓存 + test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式 + filename: '[path][base].gz[query]', // 压缩后的文件名 + algorithm: 'gzip', // 使用gzip压缩 + minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩 + deleteOriginalAssets: false // 压缩后删除原文件 + }) + ], + }, + // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) + outputDir: 'dist', + // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) + assetsDir: 'static', +})