快速开始
在项目根目录下打开终端(包含package.json的目录)
# 导入项目所需依赖npm install# 访问项目npm run dev# 打包项目npm run build
目录结构分析
fateboard基于vue2,使用了vue生态中vuex、vue-router、element-ui等插件
src目录下,核心目录:
- views:包括了不同的页面组件
- components:页面组件中用到的重复的组件
- utils:封装了一些公共的方法,其中request.js导出了一个axios实例,访问服务器的具体路径需要在此处修改
- api:通过Promise封装了所需要的api请求函数,函数内都用到了utils工具包下的request.js,为保持项目代码风格的一致性,后续开发的所有ajax请求也需要在此处定义
- store:vuex目录,用于管理不同组件的公共状态,后续开发也需要使用
- router:定义了路由,需要在开发时根据需要更改
- style:使用sass定义了一些样式
src目录下,核心文件:
- main.js:项目的入口文件,导入相关插件
- App.js:根组件
- App.css(自己创建的):全局样式

开发流程
1. 创建页面组件
2. 更改路由
首先打开 src/router 下的 index.js 文件
根据二次开发的需求,我们把最初默认跳转到 running 目录改成默认跳转到我们之前定义的联邦训练页面,并且暂时注释了默认的路由守卫(用于鉴权,默认返回到登陆页面)
export const constantRouterMap = [{path: '/',component: Layout,redirect: '/federalTrain',name: 'Dashboard',hidden: true,children: [{path: '/federalTrain',redirect: '/federalTrain/chosePartner',component: () => import('@/views/federal-train'),children: [{path: '/federalTrain/chosePartner',component: () => import('@/views/federal-train/chose-partner')}, {path: '/federalTrain/choseParams-normal',component: () => import('@/views/federal-train/chose-params-normal')}, {path: '/federalTrain/choseParams-advance',component: () => import('@/views/federal-train/chose-params-advance')}]},........]},// { path: '/login', component: () => import('@/views/job-login/index'), hidden: true },{ path: '/404', component: () => import('@/views/404'), hidden: true },{ path: '*', redirect: '/404', hidden: true }]const router = new Router({// mode: 'history',scrollBehavior: () => ({ y: 0 }),routes: constantRouterMap})// let preUrl = null// router.beforeEach((to, from, next) => {// if (getLocal('CurrentUser')) {// if (preUrl) {// const mid = preUrl// preUrl = null// next(mid)// } else if (to.path === '/history') {// if (from.query.page || from.params.page) {// to.params.page = from.query.page// to.params.search_job_id = from.query.search_job_id// to.params.search_party_id = from.query.search_party_id// to.params.search_role = from.query.search_role// to.params.search_status = from.query.search_status// }// // console.log(to, from)// next()// } else if (to.name === 'login') {// next({// name: 'RUNNINNG'// })// } else {// next()// }// } else if (to.name !== 'login') {// if (!preUrl) {// preUrl = Object.assign({}, to)// }// next({// name: 'login'// })// } else {// if (from.name !== 'login') {// if (!preUrl) {// preUrl = Object.assign({}, from)// }// }// next()// }// })export default router
3. 相关api开发
打开 src/api 目录,新建一个 federalTrain.js 用于封装我们自定义的ajax请求
具体代码如下,由于request是axios的一个实例,其中封装了bashurl,我们在指定访问路径时只需要指定path部分
import request from '@/utils/request'export function submitAssignment(data) {return request({url: '/v1/client/submit/job/general',method: 'post',data})}export function submitAssignmentByFile(data) {return request({url: '/v1/client/submit/job/high',method: 'post',data})}
4. 导航栏二次开发
打开 src/views/layout/components 目录下的 Navbar.vue
更改导航
根据 style 标签下的导入找到样式文件,src/styles/common/layout/navbar.scss
根据 scss 规则,为刚刚添加的导航定义样式
.middle-router-btns {align-self: flex-start;justify-self: start;margin-left: 28px;> span {margin: 0 8px;padding: 0 15px;$h: 28px;color: rgba(255, 255, 255, 0.6);height: $h;line-height: $h;border-radius: $h;cursor: pointer;&:hover {color: #fff;}}.active {color: #fff;}}
5. 联邦训练组件开发
根据第一步和第二步,我们已经可以访问到我们自定义的页面组件了,并且根据第4步,我们更是可以通过导航栏访问到我们自定义的页面
接下来我们要具体开发联邦训练组件
- 定义框架
首先我们要根据之前定义的二级路由,创建相应的 steps 组件和添加用于显示具体内容的 route -view ,用于配置联邦训练参与者和相关参数的页面组件都会显示在route -view 中
最终效果:
其中的卡片效果在全局样式 App.css 下
代码实现:
index.vue
<template><div class="site-layout-content"><el-steps :active="stepActive" style="height: 10%"><el-step icon="el-icon-upload" title="任务方选择"/><el-step icon="el-icon-download" title="参数配置"/></el-steps><div class="content-container" style="height: 90%"><router-view @stepActive="changeStep"/></div></div></template><script>export default {data() {return {stepActive: 1}},methods: {changeStep(step) {this.stepActive = step}}}</script><style scoped>.content-container {width: 100%;overflow-y: scroll;}</style>
全局样式
/* 全局卡片样式 */.site-layout-content {padding: 24px;border-radius: 7px;-moz-border-radius: 7px;-webkit-box-shadow: 0 20px 15px #9B7468;-moz-box-shadow: 0 20px 15px #9B7468;box-shadow: 0 8px 8px rgba(96, 185, 234, .24), 0 0 8px rgba(96, 185, 234, .12);text-decoration: none;background: #fff;height: 88vh;width: 90vw;margin-top: 3vh;}
- 编写参与方组件
我们需要指定联邦训练的参与方,以及用于训练的数据源和命名空间
代码实现:
<template><div><el-form ref="form" :inline="true" :model="form"><!-- 单行数据 --><el-row v-for="(item, index) in form.items" :key="index" type="flex" justify="space-around"><el-form-item:prop="'items.'+index+'.role'":rules="[{ required: true, message: '请输入角色类型'}]"label="角色类型:"><el-input v-model="item.role" readonly/></el-form-item><el-form-item:prop="'items.'+index+'.id'":rules="[{ required: true, message: '请输入成员ID' }]"label="成员ID:"><el-input v-model="item.id"/></el-form-item><el-form-item:prop="'items.'+index+'.tableName'":rules="[{ required: true, message: '请输入数据表名称' }]"label="数据表名称:"><el-input v-model="item.tableName"/></el-form-item><el-form-item:prop="'items.'+index+'.nameSpace'":rules="[{ required: true, message: '请输入命名空间' }]"label="命名空间:"><el-input v-model="item.nameSpace"/></el-form-item></el-row><el-row type="flex" justify="center"><el-form-item><el-buttontype="primary"style="width: 83vw"@click="addPartner">新增参与方</el-button></el-form-item></el-row></el-form><el-row style="margin: 5vh 10vw;" type="flex" justify="space-around"><el-button type="info" plain round @click="resetForm">重置</el-button><el-button type="primary" plain round @click="toNext">继续</el-button></el-row></div></template><script>import { mapMutations } from '_vuex@3.0.1@vuex'export default {data() {return {form: {items: [{ role: '主导方', id: '', tableName: '', nameSpace: '' },{ role: '参与方', id: '', tableName: '', nameSpace: '' }]}}},mounted() {this.$emit('stepActive', 1)},methods: {addPartner() {this.form.items.push({role: '参与方',id: '',tableName: '',nameSpace: ''})},resetForm() {this.$refs.form.resetFields()this.form.items = this.form.items.slice(0, 2)},toNext() {this.$refs.form.validate((valid) => {if (valid) {const guest = {party_id: this.form.items[0].id,table_name: this.form.items[0].tableName,namespace: this.form.items[0].nameSpace}const host = this.form.items.slice(1).map(item => ({party_id: item.id,table_name: item.tableName,namespace: item.nameSpace}))this.UPDATE_PARTNER({ host, guest })this.$router.push('/federalTrain/choseParams-normal')} else {console.log('error submit!!')return false}})},...mapMutations('federalTrain', ['UPDATE_PARTNER'])}}</script>
- 编写根据参数训练和根据配置文件训练的组件
3.1 根据参数训练

代码实现
<template><div><div class="form-container"><el-formref="form":rules="rules":model="form"label-position="right"size="small"style="width: 50vw"label-width="15vw"><el-form-item prop="assignmentName" label="任务名称:"><el-input v-model="form.assignmentName" class="form-input"/></el-form-item><el-form-item :rules="[{ required: true, message: '请输入任务类型'}]" prop="assignmentType" label="任务类型:"><el-radio-group v-model="form.assignmentType"><el-radio :label="0">单机</el-radio><el-radio :label="1">多机</el-radio></el-radio-group></el-form-item><el-form-item prop="algorithmName" label="采用算法:"><el-input v-model="form.algorithmName" class="form-input" readonly/></el-form-item><el-form-item prop="algorithmParams" label="算法参数:"><el-input v-model="form.algorithmParams" class="form-input" type="textarea"/></el-form-item><el-form-item prop="isScale" label="isScale:"><el-radio-group v-model="form.isScale"><el-radio :label="true">是</el-radio><el-radio :label="false">否</el-radio></el-radio-group></el-form-item><el-form-item prop="percent" label="测试数据集百分比(%):"><el-input v-model.number="form.percent" class="form-input" max="1" min="0" type="number" step="0.1"/></el-form-item><el-form-item prop="postScript" label="备注信息:"><el-input v-model="form.postScript" class="form-input" type="textarea"/></el-form-item><el-form-item><el-link style="color:blue" @click="$router.push('/federalTrain/choseParams-advance')">高级</el-link></el-form-item></el-form></div><el-row style="margin: 5vh 10vw;" type="flex" justify="space-around"><el-button type="info" plain round @click="toLast">返回</el-button><el-button :loading="submitLoading" type="primary" plain round @click="handleFinish">提交</el-button></el-row></div></template><script>import { mapState } from 'vuex'import { submitAssignment } from '../../../api/federalTrain'export default {data() {const algorithmValidator = (rules, value, callback) => {if (!value || value === '') {callback()}const jsonVal = '{' + value + '}'try {if (typeof JSON.parse(jsonVal) === 'object') {this.paramType = 0callback()}} catch (e) {try {if (typeof JSON.parse(value) === 'object') {this.paramType = 1callback()}} catch (e2) {console.log(2222)callback(new Error())}}callback(new Error())}return {form: {assignmentName: '',assignmentType: 0,algorithmName: 'hetero_lr',algorithmParams: '',isScale: true,percent: null,postScript: '',paramType: 0},submitLoading: false,rules: {assignmentName: [{ required: true, message: '请输入任务名称' }],assignmentType: [{ required: true, message: '请输入任务类型' }],algorithmParams: [{ validator: algorithmValidator, message: '算法参数格式不正确' }],percent: [{ required: true, message: '请输入测试数据百分比' }, { type: 'number', max: 1, min: 0, message: '必须在0和1之间' }]}}},computed: {...mapState('federalTrain', ['guest', 'host'])},mounted() {this.$emit('stepActive', 2)if (this.guest === null || this.host === null) {this.$router.push('/federalTrain/chosePartner')}},methods: {toLast() {this.$router.push('/federalTrain/chosePartner')},handleFinish() {this.$refs.form.validate((valid) => {if (valid) {this.submitLoading = truelet algorithmParamsif (this.paramType === 0) {algorithmParams = this.form.algorithmParams? JSON.parse('{' + this.form.algorithmParams + '}'): {}} else {algorithmParams = this.form.algorithmParams? JSON.parse(this.form.algorithmParams): {}}const { form } = thisconst data = {algorithm_parameters: algorithmParams,guest: this.guest,host: this.host,isScale: form.isScale,job_description: form.postScript,job_name: form.assignmentName,test_size: form.percent,train_algorithm_name: form.algorithmName,work_mode: form.assignmentType,config_type: 0}console.log(data)submitAssignment(data).then((res) => {console.log(res)}, (err) => {console.log(err)}).finally(() => {this.submitLoading = false})// this.$router.push('/running')} else {console.log('error submit!!')return false}})}}}</script><style scoped>.form-container {width: 100%;display: flex;flex-direction: column;align-items: center;}.form-input {width: 35vw;}</style>
3.2 根据配置文件训练
代码实现
<template><div><div class="form-container"><el-formref="form":rules="rules":model="form"label-position="right"size="small"style="width: 50vw"label-width="15vw"><el-form-item prop="train_name" label="任务名称:"><el-input v-model="form.train_name" class="form-input" readonly/></el-form-item><el-form-item prop="config_file" label="config_file"><el-upload:limit="1":auto-upload="true":before-upload="resetConfigFile":multiple="false":file-list="form.config_file"action="#"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button size="small" type="primary">下载模版</el-button></el-upload></el-form-item><el-form-item prop="dsl_file" label="dsl_file"><el-upload:limit="1":auto-upload="true":before-upload="resetDslFile":multiple="false":file-list="form.dsl_file"action="#"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button size="small" type="primary">下载模版</el-button></el-upload></el-form-item><el-form-item><el-link style="color:blue" @click="$router.push('/federalTrain/choseParams-normal')">普通</el-link></el-form-item></el-form><el-row style="position: absolute;bottom: 35vh;width: 50vw" type="flex" justify="space-around"><el-button type="info" plain round @click="toLast">返回</el-button><el-button :loading="submitLoading" type="primary" plain round @click="handleFinish">提交</el-button></el-row></div></div></template><script>import { mapState } from 'vuex'import { submitAssignmentByFile } from '../../../api/federalTrain'export default {data() {return {form: {train_name: 'homo_logistic_regression',config_file: [],dsl_file: []},submitLoading: false,rules: {train_name: [{ required: true, message: '请输入任务名称' }]}}},computed: {...mapState('federalTrain', ['guest', 'host'])},mounted() {this.$emit('stepActive', 2)if (this.guest === null || this.host === null) {this.$router.push('/federalTrain/chosePartner')}},methods: {toLast() {this.$router.push('/federalTrain/chosePartner')},resetConfigFile(file) {this.form.config_file = [file]return false},resetDslFile(file) {this.form.dsl_file = [file]return false},handleFinish() {console.log(this.form)if (this.form.config_file.length === 0 || this.form.dsl_file.length === 0) {this.$message({ message: '请选择config文件和dsl文件', type: 'warning' })return}this.$refs.form.validate((valid) => {if (valid) {this.submitLoading = trueconst { form } = thisconsole.log(form)const formData = new FormData()formData.append('train_algorithm_name', form.train_name)formData.append('config_type', 1)formData.append('config_file', form.config_file[0])formData.append('dsl_file', form.dsl_file[0])submitAssignmentByFile(formData).then((res) => {console.log(res)}, (err) => {console.log(err)}).finally(() => {this.submitLoading = false})// this.$router.push('/running')} else {console.log('error submit!!')return false}})}}}</script><style scoped>.form-container {width: 100%;display: flex;flex-direction: column;align-items: center;}.form-input {width: 20vw;}</style>
