frontend backend

This commit is contained in:
Captain.B 2020-02-03 14:01:28 +08:00
parent de8e29f4f9
commit c27167a3ba
64 changed files with 1664 additions and 232 deletions

29
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Created by .ignore support plugin (hsz.mobi)
.DS_Store
node_modules
node/
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.iml
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
src/main/resources/static
src/main/resources/templates
target

258
backend/pom.xml Normal file
View File

@ -0,0 +1,258 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>backend</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<shiro.version>1.4.0</shiro.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
<exclusion>
<artifactId>hibernate-validator</artifactId>
<groupId>org.hibernate.validator</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-collections</artifactId>
<groupId>commons-collections</groupId>
</exclusion>
<exclusion>
<artifactId>commons-beanutils</artifactId>
<groupId>commons-beanutils</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<addResources>true</addResources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- Overlay guacamole-common-js (zip) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>main-class-placement</id>
<phase>generate-resources</phase>
<configuration>
<target>
<move todir="src/main/resources/static">
<fileset dir="../frontend/dist">
<exclude name="*.html"/>
</fileset>
</move>
<move todir="src/main/resources/templates">
<fileset dir="../frontend/dist">
<include name="*.html"/>
</fileset>
</move>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

14
frontend/.editorconfig Normal file
View File

@ -0,0 +1,14 @@
# https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

29
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
.DS_Store
node_modules
node/
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.iml
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.setting
.project
.classpath
yarn.lock
package-lock.json

5
frontend/babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

49
frontend/package.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "vue-demo",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-regular-svg-icons": "^5.12.0",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/vue-fontawesome": "^0.1.9",
"axios": "^0.19.0",
"core-js": "^3.4.3",
"element-ui": "^2.13.0",
"vue": "^2.6.10",
"vue-i18n": "^8.15.3",
"vue-router": "^3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

68
frontend/pom.xml Normal file
View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>vue-demo</artifactId>
<groupId>com.fit2cloud</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>frontend</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<frontend-maven-plugin.version>1.9.1</frontend-maven-plugin.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<executions>
<execution>
<!-- optional: you don't really need execution ids, but it looks nice in your build log. -->
<id>install node and yarn</id>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
<!-- optional: default phase is "generate-resources" -->
<phase>generate-resources</phase>
<configuration>
<nodeVersion>v12.14.1</nodeVersion>
<yarnVersion>v1.21.1</yarnVersion>
</configuration>
</execution>
<!-- Install all project dependencies -->
<execution>
<id>yarn install</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<!-- optional: The default argument is actually
"install", so unless you need to run some other yarn command,
you can remove this whole <configuration> section.
-->
<arguments>install</arguments>
</configuration>
</execution>
<!-- Build and minify static files -->
<execution>
<id>yarn build</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>build</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

BIN
frontend/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

View File

@ -0,0 +1,82 @@
import {Message, MessageBox} from 'element-ui';
import axios from "axios";
export default {
install(Vue) {
if (!axios) {
window.console.error('You have to install axios');
return
}
if (!Message) {
window.console.error('You have to install Message of ElementUI');
return
}
let login = function () {
MessageBox.alert("认证信息已过期,请重新登录。", {
callback: () => {
window.location.href = "/login"
}
});
};
axios.defaults.withCredentials = true;
axios.interceptors.response.use(response => {
if (response.headers["authentication-status"] === "invalid") {
login();
}
return response;
}, error => {
return Promise.reject(error);
});
Vue.prototype.$get = function (url, success) {
if (!success) {
return axios.get(url);
} else {
axios.get(url).then(response => {
if (!response.data) {
return success(response);
}
if (response.data.success) {
return success(response.data);
} else {
window.console.warn(response.data);
Message.warning(response.data.message);
}
}).catch(error => {
window.console.error(error.response || error.message);
Message.error({message: error.message, showClose: true});
})
}
};
Vue.prototype.$post = function (url, data, success) {
if (!success) {
return axios.post(url, data);
} else {
axios.post(url, data).then(response => {
if (!response.data) {
return success(response);
}
if (response.data.success) {
return success(response.data);
} else {
window.console.warn(response.data);
Message.warning(response.data.message);
}
}).catch(error => {
window.console.error(error.response || error.message);
Message.error({message: error.message, showClose: true});
})
}
};
Vue.prototype.$all = function (array, callback) {
if (array.length < 1) return;
axios.all(array).then(axios.spread(callback));
};
}
}

View File

@ -0,0 +1,24 @@
const options = function (value, array) {
if (!value) return '';
if (array) {
for (let i = 0; i < array.length; i++) {
if (value === array[i].key) {
return array[i].value
}
}
}
return value;
};
const filters = {
"options": options,
};
export default {
install(Vue) {
// 注册公用过滤器
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
});
}
}

View File

@ -0,0 +1,11 @@
import {library} from '@fortawesome/fontawesome-svg-core'
import {fas} from '@fortawesome/free-solid-svg-icons'
import {far} from '@fortawesome/free-regular-svg-icons'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
export default {
install(Vue) {
library.add(fas, far);
Vue.component('font-awesome-icon', FontAwesomeIcon);
}
}

View File

@ -0,0 +1,7 @@
const en_US = {
i18n: {
'home': 'Home',
}
};
export default en_US

26
frontend/src/i18n/i18n.js Normal file
View File

@ -0,0 +1,26 @@
import Vue from 'vue';
import VueI18n from "vue-i18n";
import enLocale from "element-ui/lib/locale/lang/en";
import zh_CNLocale from "element-ui/lib/locale/lang/zh-CN";
import en_US from "./en_US";
import zh_CN from "./zh_CN";
Vue.use(VueI18n);
const messages = {
'en_US': {
...en_US,
...enLocale
},
'zh_CN': {
...zh_CN,
...zh_CNLocale
}
};
const i18n = new VueI18n({
locale: 'en_US',
messages,
});
export default i18n;

View File

@ -0,0 +1,7 @@
const zh_CN = {
i18n: {
'home': '首页',
}
};
export default zh_CN

View File

@ -0,0 +1,200 @@
<template>
<div class="container" v-if="ready">
<el-row type="flex">
<el-col :span="12">
<el-form :model="form" :rules="rules" ref="form">
<div class="logo">
<img src="../assets/MeterSphere-彩色.png" style="width: 224px" alt="">
</div>
<div class="title">
<span id="s1">登录</span>
<span id="s2">MeterSphere</span>
</div>
<div class="border"></div>
<div class="welcome">
欢迎回来请输入用户名和密码登录MeterSphere
</div>
<div class="form">
<el-form-item prop="username">
<el-input v-model="form.username" placeholder="邮箱" autocomplete="off" maxlength="100"
show-word-limit/>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" placeholder="密码" show-password autocomplete="off"
maxlength="20" show-word-limit/>
</el-form-item>
</div>
<div class="btn">
<el-button type="primary" class="submit" @click="submit('form')">登录
</el-button>
</div>
<div class="msg">
{{msg}}
</div>
</el-form>
</el-col>
<el-col :span="12" class="image">
<div></div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "Login",
data() {
let validateEmail = (rule, value, callback) => {
// eslint-disable-next-line no-useless-escape
let EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (!EMAIL_REGEX.test(value)) {
callback(new Error('邮箱格式不正确'));
} else {
callback();
}
};
return {
form: {
username: '',
password: ''
},
rules: {
username: [
{required: true, message: '请输入邮箱', trigger: 'blur'},
{validator: validateEmail, trigger: 'blur'}
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur'}
]
},
msg: '',
ready: false
}
},
beforeCreate() {
this.$get("/isLogin").then(response => {
if (!response.data.success) {
this.ready = true;
} else {
window.location.href = "/"
}
});
},
methods: {
submit(form) {
this.$refs[form].validate((valid) => {
if (valid) {
this.$post("signin", this.form, function () {
window.location.href = "/"
});
} else {
return false;
}
});
}
}
}
</script>
<style scoped>
.container {
min-width: 800px;
max-width: 1440px;
height: 560px;
margin: calc((100vh - 560px) / 2) auto 0;
background-color: #FFFFFF;
}
.logo {
margin: 30px 30px 0;
}
.title {
margin-top: 50px;
font-size: 32px;
letter-spacing: 0;
text-align: center;
}
.title > #s1 {
color: #999999;
}
.title > #s2 {
color: #151515;
}
.border {
height: 2px;
margin: 20px auto 20px;
position: relative;
width: 80px;
background: #8B479B;
}
.welcome {
margin-top: 50px;
font-size: 14px;
color: #999999;
letter-spacing: 0;
line-height: 18px;
text-align: center;
}
.form {
margin-top: 60px;
padding: 0 40px;
}
.btn {
margin-top: 40px;
padding: 0 40px;
}
.btn > .submit {
width: 100%;
border-radius: 0;
border-color: #8B479B;
background-color: #8B479B;
}
.btn > .submit:hover {
border-color: rgba(139, 71, 155, 0.9);
background-color: rgba(139, 71, 155, 0.9);
}
.btn > .submit:active {
border-color: rgba(139, 71, 155, 0.8);
background-color: rgba(139, 71, 155, 0.8);
}
.msg {
margin-top: 10px;
padding: 0 40px;
color: red;
text-align: center;
}
.image {
background: url(../assets/info.png);
height: 560px;
}
</style>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Neue Haas Grotesk Text Pro", "Arial Nova", "Segoe UI", "Helvetica Neue", ".PingFang SC", "PingFang SC", "Source Han Sans SC", "Noto Sans CJK SC", "Source Han Sans CN", "Noto Sans SC", "Source Han Sans TC", "Noto Sans CJK TC", "Hiragino Sans GB", sans-serif;
font-size: 14px;
background-color: #F5F5F5;
line-height: 26px;
color: #2B415C;
-webkit-font-smoothing: antialiased;
margin: 0;
}
.form .el-input > .el-input__inner {
border-radius: 0;
}
</style>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh">
<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.png">
<title>MeterSphere</title>
</head>
<body>
<div id="login"></div>
</body>
</html>

View File

@ -0,0 +1,19 @@
import Vue from 'vue';
import {Button, Col, Form, FormItem, Input, Row} from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Login from "./Login.vue";
import Ajax from "../common/ajax";
Vue.config.productionTip = false;
Vue.use(Row);
Vue.use(Col);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Input);
Vue.use(Button);
Vue.use(Ajax);
new Vue({
el: '#login',
render: h => h(Login)
});

View File

@ -0,0 +1,105 @@
<template>
<el-col v-if="auth">
<el-row id="header-top" type="flex" justify="space-between" align="middle">
<a class="logo"/>
<ms-user/>
</el-row>
<el-row id="header-bottom" type="flex" justify="space-between" align="middle">
<el-col :span="10">
<ms-menus/>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="center" align="middle">
<el-button type="primary" size="small">创建测试</el-button>
</el-row>
</el-col>
<el-col :span="10">
<ms-setting/>
</el-col>
</el-row>
<ms-view/>
<ms-web-socket/>
</el-col>
</template>
<script>
import MsMenus from "./components/HeaderMenus";
import MsSetting from "./components/HeaderSetting";
import MsView from "./components/router/View";
import MsUser from "./components/HeaderUser";
import MsWebSocket from "./components/websocket/WebSocket";
export default {
name: 'app',
data() {
return {
auth: false,
}
},
beforeCreate() {
this.$get("/isLogin").then(response => {
if (response.data.success) {
this.auth = true;
} else {
window.location.href = "/login"
}
}).catch(() => {
window.location.href = "/login"
});
},
components: {MsWebSocket, MsUser, MsMenus, MsSetting, MsView}
}
</script>
<style>
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-size: 14px;
margin: 0;
}
</style>
<style scoped>
#header-top {
width: 100%;
height: 40px;
padding: 0 10px;
background-color: rgb(44, 42, 72);
color: rgb(245, 245, 245);
font-size: 14px;
}
.logo {
width: 156px;
margin-right: 20px;
display: inline-block;
background-size: 156px 30px;
height: 40px;
background-repeat: no-repeat;
background-position: 50% center;
background-image: url("../assets/MeterSphere-反白.png");
}
#header-bottom {
height: 40px;
padding: 0 15px;
border-bottom: 1px solid #E6E6E6;
cursor: default;
color: #404040;
}
.menus > * {
color: inherit;
padding: 0;
max-width: 180px;
white-space: pre;
cursor: pointer;
line-height: 40px;
}
.menus > a {
padding-right: 15px;
text-decoration: none;
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<el-menu class="header-menu" :unique-opened="true" mode="horizontal"
menu-trigger="click">
<el-menu-item index="1"><a href="/" style="text-decoration: none;">{{ $t("i18n.home") }}</a></el-menu-item>
<el-submenu index="2" popper-class="submenu">
<template slot="title">工作空间</template>
<el-menu-item index="2-1">工作空间1</el-menu-item>
<el-menu-item index="2-2">工作空间2</el-menu-item>
<el-menu-item index="2-3">显示全部</el-menu-item>
</el-submenu>
<el-submenu index="3" popper-class="submenu">
<template slot="title">项目</template>
<el-menu-item index="3-1">项目1</el-menu-item>
<el-menu-item index="3-2">项目2</el-menu-item>
<el-menu-item index="3-3">显示全部</el-menu-item>
<el-menu-item index="create-project">
<el-button type="text">创建项目</el-button>
</el-menu-item>
</el-submenu>
<el-submenu index="4" popper-class="submenu">
<template slot="title">测试</template>
<el-menu-item index="4-1">测试1</el-menu-item>
<el-menu-item index="4-2">测试2</el-menu-item>
<el-menu-item index="4-3">显示全部</el-menu-item>
<el-menu-item index="create-test" route="{path:'test'}">
<el-button type="text">创建测试</el-button>
</el-menu-item>
</el-submenu>
<el-submenu index="5" popper-class="submenu">
<template slot="title">报告</template>
<el-menu-item index="5-1">报告1</el-menu-item>
<el-menu-item index="5-2">报告2</el-menu-item>
<el-menu-item index="5-3">显示全部</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name: "MsMenus"
}
</script>
<style>
.header-menu.el-menu--horizontal > li.el-menu-item {
padding-left: 0;
}
.header-menu.el-menu--horizontal > li {
height: 39px;
line-height: 40px;
color: inherit;
}
.header-menu.el-menu--horizontal > li.el-submenu > * {
height: 39px;
line-height: 40px;
color: inherit;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<el-row class="settings" type="flex" justify="end" align="middle">
<router-link to="/content">
<font-awesome-icon :icon="['fas', 'user-plus']" size="lg"/>
</router-link>
<router-link to="/content">
<font-awesome-icon :icon="['fas', 'cog']" size="lg"/>
</router-link>
</el-row>
</template>
<script>
export default {
name: "MsSetting"
}
</script>
<style scoped>
.settings > * {
padding-left: 15px;
cursor: pointer;
line-height: 40px;
color: inherit;
}
.settings > * :hover {
opacity: 0.7;
}
.settings > * :active {
opacity: 0.8;
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<el-dropdown size="medium" @command="handleCommand">
<span class="dropdown-link">
<i class="el-icon-caret-bottom el-icon--right"/>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="personal">个人信息</el-dropdown-item>
<el-dropdown-item command="logout">退出系统</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
name: "MsUser",
methods: {
handleCommand(command) {
switch (command) {
case "personal":
this.$i18n.locale = "zh_CN";
break;
case "logout":
this.$get("/signout", function () {
window.location.href = "/login";
});
break;
default:
break;
}
}
}
}
</script>
<style scoped>
.dropdown-link {
cursor: pointer;
font-size: 12px;
color: rgb(245, 245, 245);
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<div>
adfdsaf
</div>
</template>
<script>
export default {
name: "RouterSidebar"
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,33 @@
<template>
<div id="body">
<router-view name="sidebar" class="sidebar"/>
<router-view name="content" class="content"/>
</div>
</template>
<script>
export default {
name: "MsView"
}
</script>
<style scoped>
#body {
width: 100%;
height: calc(100vh - 80px);
background-color: #F5F5F5;
}
.sidebar {
position: relative;
width: 330px;
height: 100%;
max-width: 750px;
min-width: 250px;
}
.content {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,28 @@
import Vue from "vue";
import VueRouter from 'vue-router'
import RouterSidebar from "./RouterSidebar";
import Setting from "../settings/Setting";
import Workspace from "../settings/Workspace";
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: "/sidebar", components: {
sidebar: RouterSidebar
}
},
{
path: "/content", components: {
content: Setting
}, children: [
{
path: 'workspace',
component: Workspace
}
]
}]
});
export default router

View File

@ -0,0 +1,36 @@
<template>
<div class="create-box">
<el-tooltip class="item" effect="dark" :content="tips" placement="left">
<el-button @click="exec()" type="primary" size="mini" circle>
<font-awesome-icon :icon="['fas', 'plus']"/>
</el-button>
</el-tooltip>
</div>
</template>
<script>
export default {
name: "MsCreateBox",
props: {
tips: String,
exec: Function
}
}
</script>
<style scoped>
.create-box {
width: 100%;
background: #FCFCFC;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
box-shadow: 0 5px 20px 0 rgba(0, 0, 0, .1);
height: 60px;
line-height: 60px;
text-align: center;
}
.create-box .el-button--mini.is-circle {
width: 28.5px;
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<el-row type="flex" align="middle" class="current-user">
<el-avatar shape="square" size="small" :src="squareUrl"/>
<span class="username">kun@fit2cloud.com</span>
<el-button class="edit" type="primary" icon="el-icon-edit" size="mini"
circle @click="editVisible = true"/>
<el-dialog :title="title" :visible.sync="editVisible" width="30%">
<el-form :model="form" label-position="top" size="small">
<el-form-item label="姓名">
<el-input v-model="form.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="手机号码">
<el-input v-model="form.mobile" autocomplete="off"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="editVisible = false" size="medium">更新</el-button>
</span>
</el-dialog>
</el-row>
</template>
<script>
export default {
name: "MsCurrentUser",
data() {
return {
editVisible: false,
id: "123456",
squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
form: {
name: "kun@fit2cloud.com",
mobile: ""
}
}
}, computed: {
title: function () {
return "编辑账号(id: " + this.id + ")";
}
}
}
</script>
<style scoped>
.current-user .username {
display: inline-block;
font-size: 16px;
font-weight: 500;
margin: 0 5px;
overflow-x: hidden;
padding-bottom: 0;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
width: 180px;
}
.current-user .edit {
opacity: 0;
}
.current-user:hover .edit {
opacity: 1;
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<el-row type="flex">
<div class="menus">
<ms-current-user/>
<el-divider/>
<h1>设置</h1>
<ms-setting-menu/>
</div>
<div class="container">
<router-view class="main-content"/>
</div>
</el-row>
</template>
<script>
import MsCurrentUser from "./CurrentUser";
import MsSettingMenu from "./SettingMenu";
export default {
name: "MsSetting",
components: {MsSettingMenu, MsCurrentUser},
}
</script>
<style scoped>
.menus {
width: 280px;
height: 100%;
border-right: 1px solid #E6E6E6;
padding: 20px;
box-sizing: border-box;
background-color: #FFF;
}
h1 {
font-size: 20px;
font-weight: 500;
}
.container {
padding: 15px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.main-content {
margin: 0 auto;
width: 100%;
max-width: 1200px;
}
</style>
<style>
.main-content span.title {
font-size: 16px;
font-weight: 500;
margin-top: 0;
text-overflow: ellipsis;
overflow: hidden;
word-wrap: break-word;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<el-menu menu-trigger="click" :default-active="$route.path" router>
<el-submenu index="1">
<template slot="title">
<font-awesome-icon class="icon account" :icon="['far', 'address-card']" size="lg"/>
<span>账号</span>
</template>
<el-menu-item>用户</el-menu-item>
<el-menu-item index="/content/workspace">工作空间</el-menu-item>
<el-menu-item>API Keys</el-menu-item>
</el-submenu>
<el-submenu index=2>
<template slot="title">
<font-awesome-icon class="icon workspace" :icon="['far', 'clone']" size="lg"/>
<span>工作空间</span>
</template>
<el-menu-item>成员</el-menu-item>
<el-menu-item>证书</el-menu-item>
<el-menu-item>测试计划</el-menu-item>
<el-menu-item>警告</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template slot="title">
<font-awesome-icon class="icon" :icon="['far', 'user']" size="lg"/>
<span>个人</span>
</template>
<el-menu-item>个人设置</el-menu-item>
<el-menu-item>API Keys</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name: "MsSettingMenu"
}
</script>
<style scoped>
.el-menu {
border-right: 0;
}
.el-menu-item {
height: 40px;
line-height: 40px;
}
.icon {
width: 24px;
margin-right: 10px;
}
.account {
color: #5a78f0;
}
.workspace {
color: #44b349;
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<div v-loading="loading">
<el-card>
<div slot="header">
<el-row type="flex" justify="space-between" align="middle">
<span class="title">工作空间</span>
<span class="search">
<el-input type="text" size="small" placeholder="根据ID名称搜索" prefix-icon="el-icon-search"
maxlength="60" v-model="condition" clearable/>
</span>
</el-row>
</div>
<el-table :data="items" style="width: 100%">
<el-table-column prop="id" label="ID" width="180"/>
<el-table-column prop="name" label="名称"/>
<el-table-column prop="owner" label="创建者"/>
<el-table-column prop="enable" label="启用"/>
<el-table-column width="50">
<template slot-scope="scope">
<el-button @click="edit(scope.row)" type="primary" icon="el-icon-edit" size="mini" circle
class="edit"/>
</template>
</el-table-column>
</el-table>
</el-card>
<ms-create-box :tips="btnTips" :exec="create"/>
<el-dialog title="创建工作空间" :visible.sync="createVisible" width="30%">
<el-form :model="form" label-position="left" label-width="100px" size="small">
<el-form-item label="名称">
<el-input v-model="form.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="form.enable"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="createVisible = false" size="medium">创建</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import MsCreateBox from "./CreateBox";
export default {
name: "MsWorkspace",
components: {MsCreateBox},
methods: {
create() {
this.createVisible = true;
this.$get("/test/user", function (response) {
window.console.log(response);
});
},
edit(row) {
window.console.log(row);
this.loading = true;
let self = this;
let getUser1 = this.$get("/test/user");
let getUser2 = this.$get("/test/sleep");
this.$all([getUser1, getUser2], function (r1, r2) {
window.console.log(r1.data.data, r2.data.data);
self.loading = false;
});
}
},
data() {
return {
loading: false,
createVisible: false,
btnTips: "添加工作空间",
condition: "",
items: [{
id: '123456',
name: 'Default workspace',
owner: 'mk',
enable: "是"
}],
form: {
name: "",
enable: false
}
}
}
}
</script>
<style scoped>
.search {
width: 240px;
}
.edit {
opacity: 0;
}
.el-table__row:hover .edit {
opacity: 1;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="test">
</div>
</template>
<script>
export default {
name: "MsWebSocket",
data() {
return {
websocket: null,
}
},
created() {
this.initWebSocket();
},
destroyed() {
// this.websocket.close() //websocket
},
methods: {
initWebSocket() {
window.console.log("init WebSocket");
const uri = "ws://" + window.location.host + "/socket";
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
onOpen() {
let actions = {"test": "12345"};
this.send(JSON.stringify(actions));
},
onError(e) {
window.console.error(e)
},
onMessage(e) {
window.console.log(e.data)
},
onClose(e) {
window.console.log('断开连接', e);
},
send(Data) {
this.websocket.send(Data);
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh">
<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.png">
<title>MeterSphere</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,24 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import icon from "../common/icon";
import filters from "../common/filter";
import ajax from "../common/ajax";
import App from './App.vue';
import router from "./components/router/router";
import i18n from "../i18n/i18n";
Vue.config.productionTip = false;
Vue.use(icon);
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value)
});
Vue.use(filters);
Vue.use(ajax);
new Vue({
el: '#app',
router,
i18n,
render: h => h(App)
});

26
frontend/vue.config.js Normal file
View File

@ -0,0 +1,26 @@
module.exports = {
productionSourceMap: true,
configureWebpack: {
devtool: 'source-map'
},
devServer: {
proxy: {
['^(?!/login)']: {
target: 'http://localhost:8888',
ws: true,
}
}
},
pages: {
performance: {
entry: "src/performance/main.js",
template: "src/performance/index.html",
filename: "index.html"
},
login: {
entry: "src/login/login.js",
template: "src/login/login.html",
filename: "login.html"
}
}
};

257
pom.xml
View File

@ -1,196 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fit2cloud</groupId>
<artifactId>metersphere-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>root</name>
<modules>
<module>frontend</module>
<module>backend</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<shiro.version>1.4.0</shiro.version>
<java.version>1.8</java.version>
<junit.version>4.12</junit.version>
<jmeter.version>5.2.1</jmeter.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- jmeter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_http</artifactId>
<version>${jmeter.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
<exclusion>
<artifactId>hibernate-validator</artifactId>
<groupId>org.hibernate.validator</groupId>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-collections</artifactId>
<groupId>commons-collections</groupId>
</exclusion>
<exclusion>
<artifactId>commons-beanutils</artifactId>
<groupId>commons-beanutils</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
<groupId>org.influxdb</groupId>
<artifactId>influxdb-java</artifactId>
<version>2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.29</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<addResources>true</addResources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@ -199,60 +86,6 @@
<target>1.8</target>
</configuration>
</plugin>
<!-- Overlay guacamole-common-js (zip) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>main-class-placement</id>
<phase>generate-resources</phase>
<configuration>
<target>
<move todir="src/main/resources/static">
<fileset dir="../frontend/dist">
<exclude name="*.html"/>
</fileset>
</move>
<move todir="src/main/resources/templates">
<fileset dir="../frontend/dist">
<include name="*.html"/>
</fileset>
</move>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>