Introduce my name Fadli, I work as a bug bounty hunter, I found a vulnerability in an existing gitlab system which allows me to change the gitlab administrator/root password and also be able to view all source code and secret credentials. This finding is Critical, get shell to access server/linux operating system. please reply my email bfadliyanto@gmail.com

Commit 0ad4ef4b authored by 林探宇's avatar 林探宇

Initial commit

parents
Pipeline #222 failed with stages
> 1%
last 2 versions
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 280
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'@vue/airbnb',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'array-element-newline': ['error', 'consistent'],
'indent': ['error', 4, { 'MemberExpression': 0, 'SwitchCase': 1 }],
'quotes': ['error', 'single'],
'comma-dangle': ['error', 'always-multiline'],
'semi': ['error', 'never'],
'object-curly-spacing': ['error', 'always'],
'max-len': ['error', 280],
'no-new': 'off',
'linebreak-style': 'off',
'import/extensions': 'off',
'eol-last': 'off',
'no-shadow': 'off',
'no-unused-vars': 'warn',
'import/no-cycle': 'off',
'arrow-parens': 'off',
'eqeqeq': 'off',
'no-param-reassign': 'off',
'import/prefer-default-export': 'off',
'no-use-before-define': 'off',
'no-continue': 'off',
'prefer-destructuring': 'off',
'no-plusplus': 'off',
'prefer-const': 'off',
'global-require': 'off',
'no-prototype-builtins': 'off',
'consistent-return': 'off',
'vue/require-component-is': 'off',
'prefer-template': 'off',
'one-var-declaration-per-line': 'off',
'one-var': 'off',
'import/named': 'off',
'object-curly-newline': 'off',
'default-case': 'off',
'import/no-dynamic-require': 'off',
},
parserOptions: {
parser: 'babel-eslint',
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)',
],
env: {
jest: true,
},
},
],
}
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
MIT License
Copyright (c) 2020 bin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Vue 轻量级后台管理系统基础模板
### [在线预览](https://woai3c.github.io/vue-admin-template)
### [更新日志](https://github.com/woai3c/vue-admin-template/blob/master/update.md)
### 相关依赖
* [vue-router](https://router.vuejs.org/zh/)
* [iview](https://www.iviewui.com/docs/guide/install)
* [axios](https://www.kancloud.cn/yunye/axios/234845)
* [vuex](https://vuex.vuejs.org/zh/)
### 功能
#### 登录页
* 一周七天自动切换不同的壁纸(建议自己配置)
#### 标签栏
* 点击标签切换页面
* 刷新当前标签页
* 关闭其他标签/关闭所有标签
**注意:** 组件的名称和路由的名称一定要一致,例如 `Home.vue` 组件名称 `name: home`,则在路由文件中也要给它设置为 `name: home`,否则页面内容不能缓存
```js
// 在router文件中
{
path: 'home',
name: 'home',
component: () => import('../views/Home.vue')
}
// 在Home.vue中
export default {
name: 'home'
}
```
#### 侧边栏
* 伸展/收缩
* 页面宽度过小自动收缩
* 多级菜单(利用iView组件)
#### 用户相关
* 消息通知
* 用户头像
* 基本资料
#### 动态菜单栏
* 根据数据动态生成菜单
* 在菜单项上添加 hidden 属性可以隐藏该菜单项,但还是可以正常访问页面,具体请看 DEMO 及其相关代码
#### 面包屑
* 展示当前页面的路径
#### 权限控制
* 如果在未登陆的情况下访问指定页面 将会重定向到登陆页
#### [eslint + vscode 自动格式化代码](https://github.com/woai3c/Front-end-articles/blob/master/eslint-vscode-format.md)
具体配置方法请点击上面的链接,如果不需要 eslint,请将相关依赖卸载以及根目录下的 `.eslintrc.js` 删除。
#### [jest 单元测试](https://vue-test-utils.vuejs.org/zh/guides/testing-single-file-components-with-jest.html)
如果不需要,请卸载相关依赖及删除根目录下的 `tests` 目录
#### 页面标题 `document.title`
`src/utils/index` 下可设置默认的 `title`,在每个路由配置项上可设置对应的 `title`,具体示例请看代码
#### 其它
* 利用`axios`拦截器 实现了`ajax`请求前展示`loading` 请求结束关闭`loading`
### 注意
* 源码可见 并且添加了必要的注释 可以自行更改
`Index`组件一般情况下只需要传数据就行 其他不用关注
市面上有大量的vue后台管理系统模板 但是功能都太丰富了 而且有很多组件用不上 所以写了这么一个最基础的 只有必要功能的模板
UI库使用的是`iView` 有大量的组件可用
### 使用
#### 下载
```
git clone https://github.com/woai3c/vue-admin-template.git
cd vue-admin-template
npm i
```
#### 开发
```
npm run serve
```
#### 打包
````
npm run build
````
打包后的文件不能放在服务器根目录,否则会出现空白页面。
如果确实要把文件放在服务器根目录则需要更改打包的路径,打开 `vue.config.js` 文件,将如下代码删去即可。
```js
publicPath: './',
```
### 如果对你有帮助,请给个Star
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
}
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "vue-admin-template",
"version": "1.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test": "vue-cli-service test:unit"
},
"dependencies": {
"axios": "^0.19.0",
"core-js": "^3.4.3",
"echarts": "^4.9.0",
"install": "^0.13.0",
"npm": "^6.14.8",
"view-design": "^4.0.2",
"vue": "^2.6.10",
"vue-router": "^3.0.6",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-unit-jest": "^4.1.0",
"@vue/cli-service": "^4.5.0",
"@vue/eslint-config-airbnb": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>华大智农算法平台</title>
</head>
<body>
<noscript>
<strong>We're sorry but new doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div id="app">
<router-view/>
<div class="global-loading" v-show="isShowLoading">
<Spin size="large"></Spin>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'App',
data() {
return {
keepAliveData: ['manage'],
}
},
computed: {
...mapState([
'isShowLoading',
]),
},
}
</script>
<style>
body {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
li, ul, p, div, body, html, table {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
}
li {
list-style: none;
}
#app {
height: 100%;
}
/* loading */
.global-loading {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(255,255,255,.5);
display: flex;
justify-content: center;
align-items: center;
}
</style>
import request from '@/utils/request'
export function fetchUserData() {
return request.get('https://api.github.com/users/woai3c')
}
\ No newline at end of file
<template>
<div>
<img :src="img">
<p>未到找指定页面</p>
<Button class="back" @click="back">返回页面</Button>
</div>
</template>
<script>
export default {
name: 'error',
data() {
return {
img: require('../assets/imgs/404.jpg'),
}
},
methods: {
back() {
this.$router.back()
},
},
}
</script>
<style scoped>
div {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #f8f5ec;
}
img {
display: block;
margin: auto;
}
p {
font-size: 40px;
text-align: center;
margin-bottom: 100px;
}
.back {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
}
</style>
\ No newline at end of file
<template>
<div class="index-vue">
<!-- 侧边栏 -->
<aside :class="asideClassName">
<!-- logo -->
<div class="logo-c">
<img src="../assets/imgs/logo.png" alt="logo" class="logo">
<span v-show="isShowAsideTitle">华大智农算法平台</span>
</div>
<!-- 菜单栏 -->
<Menu class="menu" ref="asideMenu" theme="dark" width="100%" @on-select="selectMenuCallback"
accordion :open-names="openMenus" :active-name="currentPage" @on-open-change="menuChange">
<!-- 动态菜单 -->
<div v-for="(item, index) in menuItems" :key="index">
<Submenu :class="isShowAsideTitle? '' : 'shrink'" v-if="item.children" :name="index">
<template slot="title">
<Icon :size="item.size" :type="item.type"/>
<span v-show="isShowAsideTitle">{{item.text}}</span>
</template>
<div v-for="(subItem, i) in item.children" :key="index + i">
<Submenu :class="isShowAsideTitle? '' : 'shrink'" v-if="subItem.children" :name="index + '-' + i">
<template slot="title">
<Icon :size="subItem.size" :type="subItem.type"/>
<span v-show="isShowAsideTitle">{{subItem.text}}</span>
</template>
<template v-for="(threeItem, k) in subItem.children">
<a href="https://www.baidu.com" target="_blank" :key="index + i + k" v-if="threeItem.isExternal">
<MenuItem :class="isShowAsideTitle? '' : 'shrink'" class="menu-level-3"
:name="'external-link-' + index + i + k">
<template v-if="!threeItem.hidden">
<a :href="threeItem.url" target="_blank" class="external">
<Icon :size="threeItem.size" :type="threeItem.type"/>
<span v-show="isShowAsideTitle">{{threeItem.text}}</span>
</a>
</template>
</MenuItem>
</a>
<MenuItem v-else :class="isShowAsideTitle? '' : 'shrink'" class="menu-level-3"
:name="threeItem.name" :key="index + i + k">
<template v-if="!threeItem.hidden">
<Icon :size="threeItem.size" :type="threeItem.type"/>
<span v-show="isShowAsideTitle">{{threeItem.text}}</span>
</template>
</MenuItem>
</template>
</Submenu>
<template v-else-if="!subItem.hidden">
<a :href="subItem.url" v-if="subItem.isExternal" target="_blank" class="external">
<MenuItem :class="isShowAsideTitle? '' : 'shrink'"
:name="'external-link-' + index + '-' + i">
<Icon :size="subItem.size" :type="subItem.type"/>
<span v-show="isShowAsideTitle">{{subItem.text}}</span>
</MenuItem>
</a>
<MenuItem v-else :class="isShowAsideTitle? '' : 'shrink'" :name="subItem.name">
<Icon :size="subItem.size" :type="subItem.type"/>
<span v-show="isShowAsideTitle">{{subItem.text}}</span>
</MenuItem>
</template>
</div>
</Submenu>
<template v-else-if="!item.hidden">
<a :href="item.url" v-if="item.isExternal" target="_blank" class="external">
<MenuItem :class="isShowAsideTitle? '' : 'shrink'" :name="'external-link-' + index">
<Icon :size="item.size" :type="item.type"/>
<span v-show="isShowAsideTitle">{{item.text}}</span>
</MenuItem>
</a>
<MenuItem v-else :class="isShowAsideTitle? '' : 'shrink'" :name="item.name">
<Icon :size="item.size" :type="item.type" />
<span v-show="isShowAsideTitle">{{item.text}}</span>
</MenuItem>
</template>
</div>
</Menu>
</aside>
<!-- 右侧部分 -->
<section class="sec-right">
<!-- 头部 -->
<div class="top-c">
<header>
<div class="h-left">
<div class="pointer" @click="isShrinkAside" title="收缩/展开">
<Icon type="ios-apps" />
</div>
<!-- 面包屑功能 -->
<p class="crumbs">{{crumbs}}</p>
</div>
<div class="h-right">
<!-- 消息 -->
<div class="notice-c" @click="info" title="查看新消息">
<div :class="{newMsg: hasNewMsg}"></div>
<Icon type="ios-notifications-outline" />
</div>
<!-- 用户头像 -->
<div class="user-img-c">
<img :src="userImg">
</div>
<!-- 下拉菜单 -->
<Dropdown trigger="click" @on-click="userOperate" @on-visible-change="showArrow">
<div class="pointer">
<span>{{userName}}</span>
<Icon v-show="arrowDown" type="md-arrow-dropdown"/>
<Icon v-show="arrowUp" type="md-arrow-dropup"/>
</div>
<DropdownMenu slot="list">
<!-- name标识符 -->
<DropdownItem name="1">修改密码</DropdownItem>
<DropdownItem name="2">基本资料</DropdownItem>
<DropdownItem divided name="3">退出登陆</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</header>
<!-- 标签栏 -->
<div class="div-tags">
<ul class="ul-c">
<li v-for="(item, index) in tagsArry" :key="index" :class="{active: isActive(item.name)}" @click="activeTag(index)">
<a class="li-a">
{{item.text}}
</a>
<Icon size="16" @click="closeTag(index)" type="md-close" />
</li>
</ul>
<!-- 标签栏相关功能 -->
<div class="div-icons">
<div class="refresh-c" @click="reloadPage" title="刷新当前标签页">
<Icon type="md-refresh" />
</div>
<div class="tag-options" title="关闭标签">
<Dropdown trigger="click" @on-click="closeTags">
<Icon type="ios-options" />
<DropdownMenu slot="list">
<DropdownItem name="1">关闭其他标签</DropdownItem>
<DropdownItem name="2">关闭所有标签</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
</div>
</div>
<!-- 页面主体 -->
<div class="main-content">
<div class="view-c">
<keep-alive :include="keepAliveData">
<!-- 子页面 -->
<router-view v-if="isShowRouter"/>
</keep-alive>
</div>
</div>
</section>
</div>
</template>
<script>
import { resetTokenAndClearUser } from '../utils'
export default {
name: 'index',
data() {
return {
// 用于储存页面路径
paths: {},
// 当前显示页面
currentPage: '',
openMenus: [], // 要打开的菜单名字 name属性
menuCache: [], // 缓存已经打开的菜单
hasNewMsg: true, // 是否有新消息
isShowRouter: true,
msgNum: '10', // 新消息条数
// 标签栏 标签标题 路由名称
// 数据格式 {text: '首页', name: 'home'}
// 用于缓存打开的路由 在标签栏上展示
tagsArry: [],
arrowUp: false, // 用户详情向上箭头
arrowDown: true, // 用户详情向下箭头
isShowAsideTitle: true, // 是否展示侧边栏内容
main: null, // 页面主要内容区域
asideClassName: 'aside-big', // 控制侧边栏宽度变化
asideArrowIcons: [], // 缓存侧边栏箭头图标 收缩时用
// 面包屑
crumbs: '主页',
userName: '',
userImg: '',
// 主页路由名称
home: 'home',
}
},
mounted() {
// 第一个标签
const name = this.$route.name
this.currentPage = name
this.tagsArry.push({
text: this.nameToTitle[name],
name,
})
// 根据路由打开对应的菜单栏
this.openMenus = this.getMenus(name)
this.$nextTick(() => {
this.$refs.asideMenu.updateOpened()
})
// 设置用户信息
this.userName = localStorage.getItem('userName')
this.userImg = localStorage.getItem('userImg')
this.main = document.querySelector('.sec-right')
this.asideArrowIcons = document.querySelectorAll('aside .ivu-icon-ios-arrow-down')
// 监听窗口大小 自动收缩侧边栏
this.monitorWindowSize()
},
watch: {
$route(to) {
const name = to.name
this.currentPage = name
if (name == 'error') {
this.crumbs = '404'
return
}
if (!this.keepAliveData.includes(name)) {
// 如果标签超过8个 则将第一个标签删除
if (this.tagsArry.length == 8) {
this.tagsArry.shift()
}
this.tagsArry.push({ name, text: this.nameToTitle[name] })
}
setTimeout(() => {
this.crumbs = this.paths[name]
}, 0)
},
},
computed: {
// 菜单栏
menuItems() {
return this.$store.state.menuItems
},
// 需要缓存的路由
keepAliveData() {
return this.tagsArry.map(item => item.name)
},
// 由于iView的导航菜单比较坑 只能设定一个name参数
// 所以需要在这定义组件名称和标签栏标题的映射表 有多少个页面就有多少个映射条数
nameToTitle() {
const obj = {}
this.menuItems.forEach(e => {
this.processNameToTitle(obj, e)
})
return obj
},
},
methods: {
getMenus(name) {
let menus
const tagTitle = this.nameToTitle[name]
for (let i = 0, l = this.menuItems.length; i < l; i++) {
const item = this.menuItems[i]
menus = []
menus[0] = i
if (item.text == tagTitle) {
return menus
}
if (item.children) {
for (let j = 0, ll = item.children.length; j < ll; j++) {
const child = item.children[j]
menus[1] = i + '-' + j
menus.length = 2
if (child.text == tagTitle) {
return menus
}
if (child.children) {
for (let k = 0, lll = child.children.length; k < lll; k++) {
const grandson = child.children[k]
menus[2] = i + '-' + j + '-' + k
if (grandson.text == tagTitle) {
return menus
}
}
}
}
}
}
},
monitorWindowSize() {
let w = document.documentElement.clientWidth || document.body.clientWidth
if (w < 1300) {
this.shrinkAside()
}
window.onresize = () => {
// 可视窗口宽度太小 自动收缩侧边栏
if (w < 1300 && this.isShowAsideTitle
&& w > (document.documentElement.clientWidth || document.body.clientWidth)) {
this.shrinkAside()
}
w = document.documentElement.clientWidth || document.body.clientWidth
}
},
// 判断当前标签页是否激活状态
isActive(name) {
return this.$route.name === name
},
// 跳转页面 路由名称和参数
gotoPage(name, params) {
this.currentPage = name
this.crumbs = this.paths[name]
this.$router.push({ name, params })
if (!this.keepAliveData.includes(name)) {
// 如果标签超过8个 则将第一个标签删除
if (this.tagsArry.length == 8) {
this.tagsArry.shift()
}
this.tagsArry.push({ name, text: this.nameToTitle[name] })
}
},
// 选择菜单回调函数
selectMenuCallback(name) {
if (name.includes('external-link')) return
this.gotoPage(name)
this.expandAside()
setTimeout(() => {
this.isShowAsideTitle = true
}, 200)
},
// 用户操作
userOperate(name) {
switch (name) {
case '1':
// 修改密码
this.gotoPage('password')
break
case '2':
// 基本资料
this.gotoPage('userinfo')
break
case '3':
resetTokenAndClearUser()
this.$router.push({ name: 'login' })
break
}
},
// 控制用户三角箭头显示状态
showArrow(flag) {
this.arrowUp = flag
this.arrowDown = !flag
},
// 判断
isShrinkAside() {
if (this.isShowAsideTitle) {
this.shrinkAside()
} else {
this.expandAside()
}
},
// 收缩
shrinkAside() {
for (let i = 0, len = this.asideArrowIcons.length; i < len; i++) {
this.asideArrowIcons[i].style.display = 'none'
}
this.isShowAsideTitle = false
this.openMenus = []
this.$nextTick(() => {
if (this.$refs.asideMenu) {
this.$refs.asideMenu.updateOpened()
}
})
setTimeout(() => {
this.asideClassName = ''
this.main.style.marginLeft = '90px'
}, 0)
},
// 展开
expandAside() {
setTimeout(() => {
this.isShowAsideTitle = true
for (let i = 0, len = this.asideArrowIcons.length; i < len; i++) {
this.asideArrowIcons[i].style.display = 'block'
}
this.openMenus = this.menuCache
if (this.$refs.asideMenu) {
this.$refs.asideMenu.updateOpened()
}
}, 200)
this.asideClassName = 'aside-big'
this.main.style.marginLeft = '220px'
},
// 刷新当前标签页
reloadPage() {
let name = this.$route.name
let index = this.keepAliveData.indexOf(name)
this.$nextTick(() => {
if (this.tagsArry.length) {
this.isShowRouter = false
this.tagsArry.splice(index, 1)
this.$nextTick(() => {
this.tagsArry.splice(index, 0, { name, text: this.nameToTitle[name] })
this.gotoPage(name)
this.isShowRouter = true
})
} else {
this.isShowRouter = false
this.$nextTick(() => {
this.tagsArry.push({ name, text: this.nameToTitle[name] })
this.gotoPage(name)
this.isShowRouter = true
})
}
})
},
// 关闭单个标签
closeTag(i) {
let name = this.tagsArry[i].name
this.tagsArry.splice(i, 1)
window.event.stopPropagation()
// 如果关闭的是当前标签 则激活前一个标签
// 如果关闭的是第一个标签 则激活后一个标签
if (this.tagsArry.length) {
if (this.isActive(name)) {
if (i != 0) {
this.gotoPage(this.tagsArry[i - 1].name)
} else {
this.gotoPage(this.tagsArry[i].name)
}
}
} else if (name != this.home) {
// 如果没有标签则跳往首页
this.gotoPage(this.home)
} else {
this.reloadPage()
}
},
// 根据路由名称关闭页面
closeName(name) {
for (let i = 0, len = this.tagsArry.length; i < len; i++) {
if (this.tagsArry[i].name == name) {
this.closeTag(i)
break
}
}
},
// 批量关闭标签
closeTags(flag) {
if (flag == 1) {
// 关闭其他标签
this.tagsArry = []
this.gotoPage(this.$route.name)
} else {
// 关闭所有标签
this.tagsArry = []
this.gotoPage(this.home)
this.reloadPage()
}
},
// 激活标签
activeTag(i) {
this.gotoPage(this.tagsArry[i].name)
},
// 消息通知
info() {
const self = this
this.$Notice.info({
title: `您有${this.msgNum}条消息`,
render(h) {
return h('Button', {
attrs: {
type: 'info',
size: 'small',
},
on: {
click() {
// 点击查看跳转到消息页
self.gotoPage('msg')
self.hasNewMsg = false
self.msgNum = 0
},
},
}, [
'点击查看',
])
},
})
},
// 菜单栏改变事件
menuChange(data) {
this.menuCache = data
},
processNameToTitle(obj, data, text) {
if (data.name) {
obj[data.name] = data.text
this.paths[data.name] = text ? `${text} / ${data.text}` : data.text
}
if (data.children) {
data.children.forEach(e => {
this.processNameToTitle(obj, e, text ? `${text} / ${data.text}` : data.text)
})
}
},
},
}
</script>
<style scoped>
.index-vue {
height: 100%;
color: #666;
}
/* 侧边栏 */
aside {
position: fixed;
top: 0;
left: 0;
width: 90px;
background: #20222A;
height: 100%;
transition: width .3s;
overflow: auto;
}
.logo-c {
display: flex;
align-items: center;
color: rgba(255,255,255,.8);
font-size: 16px;
margin: 20px 0;
justify-content: center;
}
.logo {
width: 40px;
margin-right: 10px;
}
.aside-big {
width: 220px;
}
/* 主体页面 */
.sec-right {
height: 100%;
margin-left: 220px;
transition: margin-left .3s;
overflow: hidden;;
background: #f3f7fd;
}
/* 主体页面头部 */
header {
height: 50px;
border-bottom: none;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 40px;
padding-left: 10px;
font-size: 14px;
}
header .ivu-icon {
font-size: 24px;
}
.refresh-c {
margin: 0 30px;
cursor: pointer;
}
.h-right {
display: flex;
align-items: center;
}
.h-left {
display: flex;
align-items: center;
}
.user-img-c img {
width: 100%;
}
.notice-c {
cursor: pointer;
position: relative;
}
.newMsg {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #FF5722;
right: 0;
top: 0;
}
.user-img-c {
width: 34px;
height: 34px;
background: #ddd;
border-radius: 50%;
margin: 0 40px;
overflow: hidden;
}
.tag-options {
cursor: pointer;
position: relative;
}
.div-tags {
display: flex;
align-items: center;
justify-content: space-between;
margin: 4px 0;
}
.div-icons {
display: flex;
justify-content: flex-start;
align-items: center;
background: #fff;
height: 34px;
width: 160px;
font-size: 18px;
}
/* 标签栏 */
.ul-c {
height: 34px;
background: #fff;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 10px;
overflow: hidden;
width: calc(100% - 160px);
}
.ul-c li {
border-radius: 3px;
cursor: pointer;
font-size: 12px;
height: 24px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: center;
margin: 3px 5px 2px 3px;
border: 1px solid #e6e6e6;
}
a {
color: #666;
transition: none;
}
.li-a {
max-width: 80px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ul-c .ivu-icon {
margin-left: 6px;
}
.active {
background: #409eff;
border: 1px solid #409eff;
}
.active a {
color: #fff;
}
.active .ivu-icon {
color: #fff;
}
/* 主要内容区域 */
.main-content {
height: calc(100% - 88px);
overflow: hidden;
}
.view-c {
position: relative;
height: 100%;
overflow: hidden;
}
.pointer {
cursor: pointer;
}
.crumbs {
margin-left: 10px;
color: #97a8be;
cursor: default;
}
.menu-level-3 .ivu-icon {
font-size: 18px;
}
.shrink {
text-align: center;
}
.external {
color: rgba(255,255,255,.7);
}
.external > i {
margin-right: 6px;
}
</style>
<template>
<div class="login-vue" :style="bg">
<div class="container">
<p class="title">WELCOME</p>
<div class="input-c">
<Input prefix="ios-contact" v-model="account" placeholder="用户名" clearable @on-blur="verifyAccount" />
<p class="error">{{accountError}}</p>
</div>
<div class="input-c">
<Input type="password" v-model="pwd" prefix="md-lock" placeholder="密码" clearable @on-blur="verifyPwd"
@keyup.enter.native="submit" />
<p class="error">{{pwdError}}</p>
</div>
<Button :loading="isShowLoading" class="submit" type="primary" @click="submit">登陆</Button>
<p class="account"><span @click="register">注册账号</span> | <span @click="forgetPwd">忘记密码</span></p>
</div>
</div>
</template>
<script>
export default {
name: 'login',
data() {
return {
account: 'admin',
pwd: 'admin',
accountError: '',
pwdError: '',
isShowLoading: false,
bg: {},
}
},
created() {
this.bg.backgroundImage = 'url(' + require('../assets/imgs/bg0' + new Date().getDay() + '.jpg') + ')'
},
watch: {
$route: {
handler(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true,
},
},
methods: {
verifyAccount() {
if (this.account !== 'admin') {
this.accountError = '账号为admin'
} else {
this.accountError = ''
}
},
verifyPwd() {
if (this.pwd !== 'admin') {
this.pwdError = '密码为admin'
} else {
this.pwdError = ''
}
},
register() {
},
forgetPwd() {
},
submit() {
if (this.account === 'admin' && this.pwd === 'admin') {
this.isShowLoading = true
// 登陆成功 设置用户信息
localStorage.setItem('userImg', 'https://avatars3.githubusercontent.com/u/22117876?s=460&v=4')
localStorage.setItem('userName', '小明')
// 登陆成功 假设这里是后台返回的 token
localStorage.setItem('token', 'i_am_token')
this.$router.push({ path: this.redirect || '/' })
} else {
if (this.account !== 'admin') {
this.accountError = '账号为admin'
}
if (this.pwd !== 'admin') {
this.pwdError = '密码为admin'
}
}
},
},
}
</script>
<style>
.login-vue {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
.login-vue .container {
background: rgba(255, 255, 255, .5);
width: 300px;
text-align: center;
border-radius: 10px;
padding: 30px;
}
.login-vue .ivu-input {
background-color: transparent;
color: #fff;
outline: #fff;
border-color: #fff;
}
.login-vue ::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: rgba(255, 255, 255, .8);
}
.login-vue :-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: rgba(255, 255, 255, .8);
}
.login-vue ::-moz-placeholder { /* Mozilla Firefox 19+ */
color: rgba(255, 255, 255, .8);
}
.login-vue :-ms-input-placeholder { /* Internet Explorer 10-11 */
color: rgba(255, 255, 255, .8);
}
.login-vue .title {
font-size: 16px;
margin-bottom: 20px;
}
.login-vue .input-c {
margin: auto;
width: 200px;
}
.login-vue .error {
color: red;
text-align: left;
margin: 5px auto;
font-size: 12px;
padding-left: 30px;
height: 20px;
}
.login-vue .submit {
width: 200px;
}
.login-vue .account {
margin-top: 30px;
}
.login-vue .account span {
cursor: pointer;
}
.login-vue .ivu-icon {
color: #eee;
}
.login-vue .ivu-icon-ios-close-circle {
color: #777;
}
</style>
import Vue from 'vue'
import axios from 'axios'
import ViewUI from 'view-design'
import echarts from 'echarts'
import App from './App'
import store from './store'
import router from './router'
import 'view-design/dist/styles/iview.css'
import './permission'
import 'view-design/dist/iview.min.js'
Vue.config.productionTip = false
Vue.use(ViewUI)
Vue.prototype.$axios = axios
Vue.prototype.$echarts = echarts
new Vue({
el: '#app',
router,
store,
render: h => h(App),
})
import { LoadingBar } from 'view-design'
import router from './router'
import store from './store'
import createRoutes from '@/utils/createRoutes'
import { getDocumentTitle, resetTokenAndClearUser } from './utils'
// 是否有菜单数据
let hasMenus = false
router.beforeEach(async (to, from, next) => {
document.title = getDocumentTitle(to.meta.title)
LoadingBar.start()
if (localStorage.getItem('token')) {
if (to.path === '/login') {
next({ path: '/' })
} else if (hasMenus) {
next()
} else {
try {
// 这里可以用 await 配合请求后台数据来生成路由
// const data = await axios.get('xxx')
// const routes = createRoutes(data)
const routes = createRoutes(store.state.menuItems)
// 动态添加路由
router.addRoutes(routes)
hasMenus = true
next({ path: to.path || '/' })
} catch (error) {
resetTokenAndClearUser()
next(`/login?redirect=${to.path}`)
}
}
} else {
hasMenus = false
if (to.path === '/login') {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
router.afterEach(() => {
LoadingBar.finish()
})
\ No newline at end of file
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const commonRoutes = [
{
path: '/login',
name: 'login',
meta: { title: '登录' },
component: () => import('../components/Login.vue'),
},
{
path: '/other', // 点击侧边栏跳到一个单独的路由页面,需要定义,层级和其他顶级路由一样
name: 'other',
meta: { title: '单独的路由' },
component: () => import('../views/Other.vue'),
},
{
path: '/404',
name: '404',
meta: { title: '404' },
component: () => import('../components/404.vue'),
},
{ path: '/', redirect: '/home' },
]
// 本地所有的页面 需要配合后台返回的数据生成页面
export const asyncRoutes = {
home: {
path: 'home',
name: 'home',
meta: { title: '主页' },
component: () => import('../views/Home.vue'),
},
t1: {
path: 't1',
name: 't1',
meta: { title: '表格' },
component: () => import('../views/T1.vue'),
},
password: {
path: 'password',
name: 'password',
meta: { title: '修改密码' },
component: () => import('../views/Password.vue'),
},
msg: {
path: 'msg',
name: 'msg',
meta: { title: '通知消息' },
component: () => import('../views/Msg.vue'),
},
userinfo: {
path: 'userinfo',
name: 'userinfo',
meta: { title: '用户信息' },
component: () => import('../views/UserInfo.vue'),
},
bhfxDetail: {
path: 'bhfxDetail',
name: 'bhfxDetail',
meta: { title: '病害详情' },
component: () => import('../views/BhfxDetail.vue'),
},
bhfx: {
path: 'bhfx',
name: 'bhfx',
meta: { title: '病害分析' },
component: () => import('../views/Bhfx.vue'),
},
chfx: {
path: 'chfx',
name: 'chfx',
meta: { title: '虫害分析' },
component: () => import('../views/Chfx.vue'),
},
zsfx: {
path: 'zsfx',
name: 'zsfx',
meta: { title: '用户信息' },
component: () => import('../views/Zsfx.vue'),
},
gjzx: {
path: 'gjzx',
name: 'gjzx',
meta: { title: '告警中心' },
component: () => import('../views/Gjzx.vue'),
},
}
const createRouter = () => new Router({
routes: commonRoutes,
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isShowLoading: false, // 全局 loading
// 左侧菜单栏数据
menuItems: [
{
name: 'home', // 要跳转的路由名称 不是路径
size: 18, // icon大小
type: 'md-home', // icon类型
text: '主页', // 文本内容
},
{
name: 'other', // 要跳转的路由名称 不是路径
size: 18, // icon大小
type: 'ios-egg-outline', // icon类型
text: '单独的路由', // 点击侧边栏跳到一个单独的路由页面,需要提前在 router.js 定义
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
text: '外链',
url: 'https://www.baidu.com',
isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
text: '二级菜单',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 't1',
text: '表格',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
text: '外链',
url: 'https://www.baidu.com',
isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
text: '三级菜单',
type: 'ios-paper',
children: [
{
type: 'ios-notifications-outline',
name: 'msg',
text: '查看消息',
},
{
type: 'md-lock',
name: 'password',
text: '修改密码',
},
{
type: 'md-person',
name: 'userinfo',
text: '基本资料',
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
text: '外链',
url: 'https://www.baidu.com',
isExternal: true, // 外链 跳到一个外部的 URL 页面
},
],
},
],
},
],
},
mutations: {
setMenus(state, items) {
state.menuItems = [...items]
},
setLoading(state, isShowLoading) {
state.isShowLoading = isShowLoading
},
},
})
export default store
\ No newline at end of file
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isShowLoading: false, // 全局 loading
// 左侧菜单栏数据
menuItems: [
// {
// name: 'home', // 要跳转的路由名称 不是路径
// size: 18, // icon大小
// type: 'md-home', // icon类型
// text: '主页', // 文本内容
// },
// {
// name: 'other', // 要跳转的路由名称 不是路径
// size: 18, // icon大小
// type: 'ios-egg-outline', // icon类型
// text: '单独的路由', // 点击侧边栏跳到一个单独的路由页面,需要提前在 router.js 定义
// },
// {
// size: 18, // icon大小
// type: 'md-arrow-forward', // icon类型
// text: '外链',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
// },
// {
// text: '二级菜单',
// type: 'ios-paper',
// children: [
// {
// type: 'ios-grid',
// name: 't1',
// text: '表格',
// // hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// // hidden: true,
// },
// {
// size: 18, // icon大小
// type: 'md-arrow-forward', // icon类型
// text: '外链',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
// },
// {
// text: '三级菜单',
// type: 'ios-paper',
// children: [
{
name: 'home', // 要跳转的路由名称 不是路径
size: 18, // icon大小
type: 'md-home', // icon类型
text: '主页', // 文本内容
},
{
type: 'md-person',
name: 'userinfo',
text: '实时检测',
},
{
type: 'ios-notifications-outline',
name: 'msg',
text: '气象分析',
},
{
type: 'md-lock',
name: 'password',
text: '土壤分析',
},
{
type: 'md-person', // icon类型
name: 'bhfx',
text: '病害分析',
},
{
type: 'md-person', // icon类型
name: 'chfx',
text: '虫害分析',
},
{
type: 'md-person', // icon类型
name: 'zsfx',
text: '长势分析',
},
{
type: 'md-person', // icon类型
name: 'gjzx',
text: '告警中心',
},
// ],
// },
// ],
// },
],
},
mutations: {
setMenus(state, items) {
state.menuItems = [...items]
},
setLoading(state, isShowLoading) {
state.isShowLoading = isShowLoading
},
},
})
export default store
import { asyncRoutes } from '@/router'
// 将菜单信息转成对应的路由信息 动态添加
export default function createRoutes(data) {
const result = []
const children = []
result.push({
path: '/',
component: () => import('../components/Index.vue'),
children,
})
data.forEach(item => {
generateRoutes(children, item)
})
// 最后添加404页面 否则会在登陆成功后跳到404页面
result.push(
{ path: '*', redirect: '/404' },
)
return result
}
function generateRoutes(children, item) {
console.log(children)
if (item.name) {
console.log(item.name)
console.log(asyncRoutes[item.name])
if (asyncRoutes[item.name]) children.push(asyncRoutes[item.name])
} else if (item.children) {
console.log('children')
console.log(item.children)
item.children.forEach(e => {
generateRoutes(children, e)
})
}
console.log('result')
console.log(children)
}
import { resetRouter } from '@/router'
export function resetTokenAndClearUser() {
// 退出登陆 清除用户资料
localStorage.setItem('token', '')
localStorage.setItem('userImg', '')
localStorage.setItem('userName', '')
// 重设路由
resetRouter()
}
export const defaultDocumentTitle = 'vue-admin-template'
export function getDocumentTitle(pageTitle) {
if (pageTitle) return `${defaultDocumentTitle} - ${pageTitle}`
return `${defaultDocumentTitle}`
}
\ No newline at end of file
import store from '@/store'
let loadingCounter = 0
export function showLoading() {
if (loadingCounter === 0) {
store.commit('setLoading', true)
}
loadingCounter++
}
export function closeLoading() {
loadingCounter--
if (loadingCounter <= 0) {
loadingCounter = 0
store.commit('setLoading', false)
}
}
\ No newline at end of file
import axios from 'axios'
import { Message } from 'view-design'
import router from '@/router'
import { showLoading, closeLoading } from '@/utils/loading'
import { resetTokenAndClearUser } from '@/utils'
const service = axios.create({
baseURL: window.location.origin,
timeout: 60000,
})
service.interceptors.request.use(config => {
showLoading()
if (localStorage.getItem('token')) {
config.headers.Authorization = localStorage.getItem('token')
}
return config
}, (error) => Promise.reject(error))
service.interceptors.response.use(response => {
closeLoading()
const res = response.data
// 这里是接口处理的一个示范,可以根据自己的项目需求更改
// 错误处理
if (res.code != 0 && res.msg) {
Message.error({
content: res.msg,
})
// token 无效,清空路由,退出登录
if (res.code == 2) {
resetTokenAndClearUser()
router.push('login')
}
return Promise.reject()
}
// 如果接口正常,直接返回数据
return res
}, (error) => {
closeLoading()
if (error.name == 'Error') {
Message.error({
content: error.msg,
})
} else {
Message.error({
content: error.response.data.data || error.message,
})
}
return Promise.reject(error)
})
export default service
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
<h1>病害分析</h1>
<!-- Example single danger button -->
<div style="margin-top:2vh">
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部种植基地
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>基地1</Dropdown-item>
<Dropdown-item>基地2</Dropdown-item>
<Dropdown-item>基地3</Dropdown-item>
</Dropdown-menu>
</Dropdown>
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部类型
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>正常</Dropdown-item>
<Dropdown-item>严重</Dropdown-item>
<Dropdown-item>非常严重</Dropdown-item>
</Dropdown-menu>
</Dropdown>
</div>
<!-- <Input search placeholder="请输入查询内容" style="width: auto" /> -->
</div>
<br>
<div>
<div v-for="(item,i) in datas" :key=i>
<div class="out_box_t row_dsp" @click="onClick">
<div class="word_left" @click="onClick">
<span>{{item.content}}</span>
</div>
<div class="word_right_t" @click="onClick">
<span class="word_right_t">{{item.date}}</span>
</div>
</div>
</div>
</div>
<br>
<Page :total="100" show-sizer show-elevator/>
</div>
</div>
</template>
<script>
export default {
name: 'bhfx',
data() {
return {
columns1: [
{
title: '分析结果',
key: 'name',
},
],
visible: false,
datas: [
{
content: 'John Brown',
date: '2016-10-03 10:19',
},
{
content: 'John',
date: '2016-10-03 10:19',
},
{
content: 'Brown',
date: '2016-10-03 10:19',
},
],
methods: {
handleOpen() {
this.visible = true
},
handleClose() {
this.visible = false
},
onClick() {
this.$router.push({ path: '/' })
},
},
}
},
}
</script>
<style scoped>
.btn_t{
background: none;
}
.dropdown-menu {
/* min-width:1px; */
}
.out_box_t{
padding: 1vw;
}
.arrow-down {
border-left: 1px solid #000000;
border-bottom: 1px solid #000000;
height: 5px;
width: 5px;
transform: translate(1px, -1px) rotate(-45deg);
-webkit-transform: translate(1px, -1px) rotate(-45deg);
border-right: 1px solid transparent;
border-top: 1px solid transparent;
display: inline-block;
-moz-transform: translate(1px, -1px) rotate(-45deg);
-ms-transform: translate(1px, -1px) rotate(-45deg);
-o-transform: translate(1px, -1px) rotate(-45deg);
}
.out_box_t{
border:solid 1px rgb(157, 151, 151);
}
.word_center_t{
text-align:center;
}
.word_left_t{
text-align:left;
}
.word_right_t{
text-align:right;
}
.row_dsp{
display: flex;
align-items: center;
/* justify-content: space-around; */
justify-content: space-between;
}
.list_box_t{
}
</style>
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
<h1>预警来源</h1>
<!-- Example single danger button -->
<div style="margin-top:2vh" class="row_dsp out_box_t">
<div style="margin-top:2vh" class="col_dsp">
<h1>{{date}}</h1>
<div class="row_dsp">
<div class="interval_t">
<img v-bind:src="picUrl"/>
</div>
<div class="interval_t word_center_t">
<span>
60%
</span>
<br>
<span>
疑似真菌
</span>
</div>
</div>
</div>
<div style="margin-top:2vh">
<div ref="chart" id="chart-user" style="width:600px; height:300px"></div>
</div>
</div>
<!-- <Input search placeholder="请输入查询内容" style="width: auto" /> -->
</div>
</div>
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
<h1>农事指导</h1>
<!-- Example single danger button -->
<div class="interval_t word_center_t out_box_t" style="margin-top:2vh">
<span>{{guideContent}}</span>
</div>
<!-- <Input search placeholder="请输入查询内容" style="width: auto" /> -->
</div>
<br>
</div>
</div>
</template>
<script>
export default {
name: 'bhfxDetail',
data() {
return {
date: '12月23日',
picUrl: require('../assets/imgs/user.jpg'),
guideContent: '农事建议:针对本次识别结果,物候期为{生长期},注意浇灌巡园',
}
},
mounted() {
this.getEchartData()
},
methods: {
getEchartData() {
const chart = this.$refs.chart
if (chart) {
const myChart = this.$echarts.init(chart)
const option = {
title: {
text: '感染趋势',
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['2018', '2019', '2020'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
toolbox: {
feature: {
saveAsImage: {},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['9.17', '9.18', '9.19', '9.20', '9.21', '9.22', '9.23'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '2018',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '2019',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '2020',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
],
}
myChart.setOption(option)
window.addEventListener('resize', () => {
myChart.resize()
})
}
},
},
}
</script>
<style scoped>
.btn_t{
background: none;
}
.arrow-down {
border-left: 1px solid #000000;
border-bottom: 1px solid #000000;
height: 5px;
width: 5px;
transform: translate(1px, -1px) rotate(-45deg);
-webkit-transform: translate(1px, -1px) rotate(-45deg);
border-right: 1px solid transparent;
border-top: 1px solid transparent;
display: inline-block;
-moz-transform: translate(1px, -1px) rotate(-45deg);
-ms-transform: translate(1px, -1px) rotate(-45deg);
-o-transform: translate(1px, -1px) rotate(-45deg);
}
.row_dsp{
display: flex;
align-items: center;
justify-content: space-around;
}
.content_top{
width:30vh;
}
.col_dsp{
display: flex;
align-items: center;
justify-content: space-around;
flex-direction:column;
}
.interval_t{
padding: 1vw;
}
.word_center_t{
text-align:center;
}
.out_box_t{
border:solid 1px rgb(157, 151, 151);
border-radius:1vw;
}
</style>
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
<h1>虫害分析<Icon type="arrow-down-a"></Icon></h1>
<!-- Example single danger button -->
<div style="margin-top:2vh">
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部种植基地
<i></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>基地1</Dropdown-item>
<Dropdown-item>基地2</Dropdown-item>
<Dropdown-item>基地3</Dropdown-item>
</Dropdown-menu>
</Dropdown>
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部类型
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>正常</Dropdown-item>
<Dropdown-item>严重</Dropdown-item>
<Dropdown-item>非常严重</Dropdown-item>
</Dropdown-menu>
</Dropdown>
</div>
<!-- <Input search placeholder="请输入查询内容" style="width: auto" /> -->
</div>
<br>
<Table max-height="670" border stripe :columns="columns1" :data="data1"></Table>
<br>
<Page :total="100" show-sizer show-elevator/>
</div>
</div>
</template>
<script>
export default {
name: 'chfx',
data() {
return {
columns1: [
{
title: '分析结果',
key: 'name',
},
],
visible: false,
data1: [
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
],
methods: {
handleOpen() {
this.visible = true
},
handleClose() {
this.visible = false
},
},
}
},
}
</script>
<style scoped>
.btn_t{
background: none;
}
.dropdown-menu {
/* min-width:1px; */
}
.out_box_t{
padding: 1vw;
}
.arrow-down {
border-left: 1px solid #000000;
border-bottom: 1px solid #000000;
height: 5px;
width: 5px;
transform: translate(1px, -1px) rotate(-45deg);
-webkit-transform: translate(1px, -1px) rotate(-45deg);
border-right: 1px solid transparent;
border-top: 1px solid transparent;
display: inline-block;
-moz-transform: translate(1px, -1px) rotate(-45deg);
-ms-transform: translate(1px, -1px) rotate(-45deg);
-o-transform: translate(1px, -1px) rotate(-45deg);
}
</style>
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
<h1>告警中心<Icon type="arrow-down-a"></Icon></h1>
<!-- Example single danger button -->
<div style="margin-top:2vh">
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部种植基地
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>基地1</Dropdown-item>
<Dropdown-item>基地2</Dropdown-item>
<Dropdown-item>基地3</Dropdown-item>
</Dropdown-menu>
</Dropdown>
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部类型
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>正常</Dropdown-item>
<Dropdown-item>严重</Dropdown-item>
<Dropdown-item>非常严重</Dropdown-item>
</Dropdown-menu>
</Dropdown>
</div>
<!-- <Input search placeholder="请输入查询内容" style="width: auto" /> -->
</div>
<br>
<Table max-height="670" border stripe :columns="columns1" :data="data1"></Table>
<br>
<Page :total="100" show-sizer show-elevator/>
</div>
</div>
</template>
<script>
export default {
name: 'gjzx',
data() {
return {
columns1: [
{
title: '分析结果',
key: 'name',
},
],
visible: false,
data1: [
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
],
methods: {
handleOpen() {
this.visible = true
},
handleClose() {
this.visible = false
},
},
}
},
}
</script>
<style scoped>
.btn_t{
background: none;
}
.dropdown-menu {
/* min-width:1px; */
}
.out_box_t{
padding: 1vw;
}
.arrow-down {
border-left: 1px solid #000000;
border-bottom: 1px solid #000000;
height: 5px;
width: 5px;
transform: translate(1px, -1px) rotate(-45deg);
-webkit-transform: translate(1px, -1px) rotate(-45deg);
border-right: 1px solid transparent;
border-top: 1px solid transparent;
display: inline-block;
-moz-transform: translate(1px, -1px) rotate(-45deg);
-ms-transform: translate(1px, -1px) rotate(-45deg);
-o-transform: translate(1px, -1px) rotate(-45deg);
}
</style>
<template>
<div class="home-container">
<div class="home-content">
<Button @click="getUserData">ajax 测试</Button>
<Input :rows="30" style="margin-top: 20px" v-model="userInfo" type="textarea" />
</div>
</div>
</template>
<script>
import { fetchUserData } from '@/api'
export default {
name: 'home',
data() {
return {
userInfo: '',
}
},
methods: {
getUserData() {
fetchUserData().then(res => {
this.userInfo = JSON.stringify(res, null, 4)
})
},
},
}
</script>
<style scoped>
.home-container {
padding: 10px;
padding-top: 5px;
}
.home-content {
padding: 10px;
border-radius: 5px;
background: #fff;
}
</style>
\ No newline at end of file
<template>
<div>
查看消息
<input type="text">
</div>
</template>
<script>
export default {
name: 'msg',
}
</script>
<style scoped>
</style>
<template>
<div>
单独的路由
<div>
<Button @click="reback" type="primary" style="margin-left: 100px">返回</Button>
</div>
</div>
</template>
<script>
export default {
name: 'other',
methods: {
reback() {
this.$router.back()
},
},
}
</script>
<style>
</style>
\ No newline at end of file
<template>
<div>
修改密码
<input type="text">
</div>
</template>
<script>
export default {
name: 'password',
data() {
return {
}
},
}
</script>
<style scoped>
</style>
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
查询:
<Input search placeholder="请输入查询内容" style="width: auto" />
</div>
<br>
<Table max-height="670" border stripe :columns="columns1" :data="data1"></Table>
<br>
<Page :total="100" show-sizer show-elevator/>
</div>
</div>
</template>
<script>
export default {
name: 't1',
data() {
return {
columns1: [
{
title: 'Name',
key: 'name',
},
{
title: 'Age',
key: 'age',
},
{
title: 'Address',
key: 'address',
},
],
data1: [
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
],
}
},
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div>
基本资料
<input type="text">
</div>
</template>
<script>
export default {
name: 'userinfo',
data() {
return {
}
},
}
</script>
<style scoped>
</style>
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
<h1>长势分析<Icon type="arrow-down-a"></Icon></h1>
<!-- Example single danger button -->
<div style="margin-top:2vh">
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部种植基地
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>基地1</Dropdown-item>
<Dropdown-item>基地2</Dropdown-item>
<Dropdown-item>基地3</Dropdown-item>
</Dropdown-menu>
</Dropdown>
<Dropdown trigger="click" style="margin-left: 20px">
<a href="javascript:void(0)" style="color:#515a6e;border:solid 1px #CCC">
全部类型
<i class="arrow-down"></i>
</a>
<Dropdown-menu slot="list">
<Dropdown-item></Dropdown-item>
<Dropdown-item>正常</Dropdown-item>
<Dropdown-item>严重</Dropdown-item>
<Dropdown-item>非常严重</Dropdown-item>
</Dropdown-menu>
</Dropdown>
</div>
<!-- <Input search placeholder="请输入查询内容" style="width: auto" /> -->
</div>
<br>
<Table max-height="670" border stripe :columns="columns1" :data="data1"></Table>
<br>
<Page :total="100" show-sizer show-elevator/>
</div>
</div>
</template>
<script>
export default {
name: 'zsfx',
data() {
return {
columns1: [
{
title: '分析结果',
key: 'name',
},
],
visible: false,
data1: [
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
],
methods: {
handleOpen() {
this.visible = true
},
handleClose() {
this.visible = false
},
},
}
},
}
</script>
<style scoped>
.btn_t{
background: none;
}
.dropdown-menu {
/* min-width:1px; */
}
.out_box_t{
padding: 1vw;
}
.arrow-down {
border-left: 1px solid #000000;
border-bottom: 1px solid #000000;
height: 5px;
width: 5px;
transform: translate(1px, -1px) rotate(-45deg);
-webkit-transform: translate(1px, -1px) rotate(-45deg);
border-right: 1px solid transparent;
border-top: 1px solid transparent;
display: inline-block;
-moz-transform: translate(1px, -1px) rotate(-45deg);
-ms-transform: translate(1px, -1px) rotate(-45deg);
-o-transform: translate(1px, -1px) rotate(-45deg);
}
</style>
import { defaultDocumentTitle, getDocumentTitle } from '@/utils'
it('getDocumentTitle test', () => {
const title = '这是一个测试'
expect(getDocumentTitle(title)).toMatch(`${defaultDocumentTitle} - ${title}`)
})
## 更新日志
### 2020.8.30 更新
* build: 打包后的文件从绝对路径改成相对路径,也就是说打包后的文件不能放在服务器根目录下。
### 2020.8.14 更新
* new: loading 从 `components/Index.vue` 挪到了 `App.vue`。axios 从 `components/Index.vue` 挪到了 `utils/request.js`,并对其进行了封装,方便复用。
* new: 重构了 loading 和 axios 拦截器使用方式,并提供了一个 ajax DEMO 放在首页。
### 2020.6.5 更新
* new: 新增外链功能,点击菜单可以跳到一个新页面,地址为指定的 URL。
* new: 新增独立路由页面功能,点击侧边栏可以跳转到单独的路由页面(铺满屏幕,顶级路由)。
具体示例请查看源码 `src/store/index`[demo](https://woai3c.github.io/)
### 2019.12.21 更新
* refactor: 将 `404` 页面独立出来,单独展示(占满屏幕)
* new: [新增 eslint,配合 vscode 可以自动格式化代码](https://github.com/woai3c/Front-end-articles/blob/master/eslint-vscode-format.md)
* new: 新增 jest 单元测试
* new: 页面标题 `document.title`,在 `src/utils/index` 下可设置默认的 `title`,在每个路由配置项上可设置对应的 `title`,具体示例请看代码
### 2019.12.13 更新
* fix: 修复在IE下关闭标签栏时,页面抖动的问题
* refactor: 同时将左右两栏的布局方式从 flex 布局更改为 fixed + margin 的方式
### 2019.10.30 更新
* new: 在对应的菜单项上添加 `hidden` 属性,即可隐藏对应的菜单项,但还是可以在地址栏上输入对应的 URL 来访问页面。
使用方法
```js
{
type: 'ios-grid',
name: 't1',
text: '表格',
hidden: true, // 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
}
```
### 2019.10.14 更新
* fix: 修复窗口宽度过小不会收缩侧边栏的问题
* new: 打开页面时,默认展开和路由对应的菜单栏
### 2019.8.19 更新
* fix: `components/Index.vue` 文件第 31 行代码的 `v-show="isShowAsideTitle"` 会造成侧边栏收缩时二级菜单隐藏,目前已修复。
### 2019.7.24 更新
* new: 增加页面进度条,跳转时显示
### 2019.6.25 更新
* fix: 修复路由表冲突问题
退出当前用户,换账号重新登陆时,上个账号和现在的账号路由表会有冲突的问题,解决办法是在退出登陆时重置路由表。
具体实现请查看 `router/index.js``Login.vue``Index.vue` 的退出登陆回调方法。
### 2019.6.18 更新
* new: 优化动态添加路由功能
以前的动态路由功能并不完善,首先要将所有的路由都添加到路由表里,然后根据后台返回的菜单栏数据来生成菜单。
导致的问题就是,虽然有页面在菜单栏上不显示,但由于已经添加到路由表里了,所以可以在地址栏上手动输入在菜单栏上不存在(但在路由表存在)的页面,进而可以越权访问。
现在除了必要的页面需要在一开始添加到路由表里,其他的页面都可以根据后台数据来自动生成。而且菜单栏上没有的页面,在地址栏上输入地址也是访问不了的。
另外,如果在未登陆时要访问某一指定页面,会重定向到登陆页,登陆成功后会自动跳到这个指定页面。
具体实现请看 `permission.js``util` 目录下的 `index.js` 文件
### 2019.3.14 更新
* new: 增加404页面
假如跳转到一个不存在的页面时会重定向到404页面
### 2019.3.8 更新
* new: 增加面包屑功能 用于展示当前页面的路径
* new: 增加权限控制功能,如果未登陆,访问所有页面都重定向到登陆页
### 2019.3.1 更新
* new: 增加动态菜单栏功能
菜单项中的 `icon` 使用的是 `iview` 组件的 `icon` 组件。
数据格式:
```js
// 左侧菜单栏数据
menuItems: [
{
name: 'Home', // 要跳转的路由名称 不是路径
size: 18, // icon大小 非必填
type: 'md-home', // icon类型 非必填
text: '主页' // 文本内容
},
{
text: '二级菜单',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 'T1',
text: '表格',
hidden: true, // 可以在菜单中隐藏此菜单项,但还是可以访问此页面,只是不能在菜单栏中看见。
},
{
text: '三级菜单',
type: 'ios-paper',
children: [
{
type: 'ios-notifications-outline',
name: 'Msg',
text: '查看消息'
},
{
type: 'md-lock',
name: 'Password',
text: '修改密码'
},
{
type: 'md-person',
name: 'UserInfo',
text: '基本资料',
}
]
}
]
}
]
```
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://xxxx/device/', // 对应自己的接口
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': '',
},
},
},
},
publicPath: './',
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment