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 diff is collapsed.
{
"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
This diff is collapsed.
<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
This diff is collapsed.
<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