由于最近业务到需要,我们TaaS平台需要实现一些需求,虽然号称前后分离但实际并不分离,而前端只是简单的学过一点,所以遇到了一些坑。在踩过这些坑的时候也想寻求google和百度的帮助,然而帮助实在很少,就索性写一篇博客,把这些坑都记录下来,为以后遇到同样问题的小伙伴提供参考,因为vue里面我们采用了eslint规范,所以都按照规范来书写代码。整篇代码的重点其实就是在v-if,slot-scope,v-model双向绑定的使用以及解决v-model双向绑定时,点击编辑按钮后,所有行的输入框都会弹出来等等因使用v-model双向绑定出现的相关bug。
首先提上来的代码是vue从后端接口拿到数据,并且渲染数据,然后制成一个表格,这个表格是可以展开的(为了后面描述方便,我们称之为母表格),展开前每一行的数据开头就是执行测试用例的ip,creator以及时间等等,展开后,里面则继续嵌套一个表格,里面每一列分别是这个用例的check点,status,detail等等。展开后大概是一个这样的部分效果,然后可以根据每个具体的信息然后点击查看日志或者获取更详细的一些信息:
check_pint | status |
---|---|
xxxxxxx | pass |
实现这些功能的代码也很简单,这些也不是我这里的重点,但是为了让读者明白后面关键的代码,还是先上代码,因为代码是已经写好了,为了方便就按照行数顺序编辑,遇到相关逻辑我会切开进行详细描述:
<template>
<section class="content">
<div class="row">
<div class="col-md-12">
<div class="box box-solid">
<div class="box-header with=border">
<i class="fa fa-inbox"></i>
<div class="box-tools">
<el-button-group>
<el-button
type="primary"
size="small"
@click="$route.push({path:'/check-point-service/new'})">
创建服务</el-button>
<el-button
type="primary"
size="small"
icon="el-icon-question">
帮助</el-button>
</el-button-group>
</div>
</div>
<div class="box-body>
<div>
<el-radio-group
v-model="show_all"
size="small">
<el-radio-button
:label="false">
我的任务</el-radio-button>
<el-radio-button
:label="true">
全部任务</el-radio-button>
</el-radio-group>
</div>
<el-row :gutter="12">
<el-col :span="4">
<div>
<el-input
placeholder="Creator"
v-model="creator"
clearable></el-input>
</div>
</el-col>
<el-col :span="3">
<div>
<template>
<el-select
v-model="choice"
clearable
placeholder="执行Status">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
</div>
</el-col>
<el-col :span="1">
<div>
<el-button
type="primary
icon="el-icon-search"
v-on:click="reload_pagination()">
搜索</el-button>
</div>
</el-col>
</el-row>
接下来的代码部分就是上面的子表格里的内容了(为了方便,后面都称这个表格为子表格),也是重点的开始,el-table绑定的:data的值就是在script里面定义的pagination的值,也是从后端返回的值,而前后端交互的代码则卸载和src同目录下的fetch/api.js里面,也就是后面方法里面的api.xxxx的接口调用规则
<el-table
:data="pagination.data"
style="width:100%
height="650px">
<el-table-column type="expand">
<template slot-scope="props">
<el-table
:data="convert_service_result_list(props.row.result)"
border
style="width:100%">
<el-table-column
prop="name"
label="check point"
width="200"
>
</el-table-column>
<el-table-column
prop="status"
label="status"
width="100"
>
// status有三种状态:pass,error,failed
<template slot-scope="scope">
<span
v-if="scope.row.status ==="Error"
class="label bg-red">
Error</span>
<span
v-else-if="scope.row.result.result"
class="label bg-green">
Pass</span>
<span
v-else
class="label bg-red">
Fail</span>
</template>
</el-table-column>
<el-table-column
label="Detail"
width="100">
<template
v-if="scope.row.status !== 'Error'"
slot-scope="scope">
<el-popover
placement="bottom"
width="50%"
trigger="click">
<div scrollContainer>//日志太多的情况下就可以以scroll滚动条的形式出现
<pre>{{convert_original_info(scope.row.result.original_info)}}</pre>
</div>
<el-button
type="text"
slot="reference">详细信息</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="Rule">
<template
v-if="scope.row.status !== 'Error'"
slot-scope="scope">
{{scope.row.result.rule}}
</template>
</el-table-column>
以上的重点其实就在里面用slot-scope=“props”绑定数据,而el-table-column就是每一列都有些什么,然后打开后嵌套的template里面又绑定了scope,而这里面的数据,就是一些具体的信息。
而下面部分的代码就是为子表格增加的一列描述标题,失败日志分析,功能则有为每一行的数据添加三个按钮(子表格可能有很多行,由mongodb里的数据决定),编辑、保存和返回,当用户点开母表格的一行时就会出现子表格的数据信息,此时如果mongodb里面的某个字段(flask后端代码部分会详解)有analyze描述,则会返回这个描述,没有的话则不返回。在描述的底部都只会有“编辑”这个按钮,当点开“编辑”的时候,编辑按钮则消失,保存按钮,返回按钮和与之关联的input输入框则出现,当点返回时,不保存任何信息回到编辑状态,当点保存的时候则将描述信息传递给后台再对mongodb进行字段添加更新操作。
<el-table-column
label="失败日志分析"
width="500">
<template slot-scope="scope">
<div v-show="!scope.row.isShow">
{{scope.row.analyze}}
</div>
<el-form-item>
<el-input
v-show="scope.row.isShow"
type="textarea
:autosize="{minRows:2, maxRows:4}"
placeholder="请输入内容"
v-model="scope.row.analyze">
</el-input>
<el-button
v-show="!scope.row.isShow"
type="success"
size="small"
@click="open_text(scope.row)">编辑</el-button>
<el-button
v-show="scope.row.isShow"
type="primary"
size="small"
@click="save_service(scope.row, props)">
保存</el-button>
<el-button
v-show="scope.row.isShow"
type="success"
size="small"
@click="out_text(scope.row)">
返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
</el--table-column>
</el-table>
</template>
</el-table-column>
以上代码为vue部分重中之重,为什么要用scope.row.isShow来绑定v-show,因为是按行数来敲的代码,所以可以看到后面script里面的convert_service_result_list方法里面,this.$set(value[1], ‘isShow’, false)在这里我将isShow这个值设为false,此时也就是不可见,所以只要是v-show="scope.row.isShow"的在此时都是隐藏的,只有当激活变成!scope.row.isShow的时候才会显示出来,而script里面其他几个方法里面看到相同的代码都是在做类似的操作,而为什么要用‘set’方法,因为在view中会自动监听observer对象,而如果只是普通的value[1].isShow=false的话,这个值并不是observer对象,从而vue不会去监听,导致不管怎么点击button,只会看到false, true来回变化但是并没有任何效果。而v-model用scope.row.analyze绑定则是因为点击某一行的按钮时,只能触发哪一行的事件,不能点击一个按钮,所有行的时间全部都触发了,这样用户会有意见的~
接下来的代码又开始滑水了:
<el-table-column
prop="env.ip"
label="IP"
width="180">
</el-table-column>
<el-table-column
prop="creator"
label="Creator">
</el-table-column>
<el-table-column
prop="created_time"
label="Start Time">
<template slot-scope="scope">
{{scope.row.created_time.slice(5,19).replace('T',' ')}}
</template>
</el-table-column>
<el-table-column
prop="status"
label="Status">
</el-table-column>
<el-table-column
prop="id"
label="Report">
<template slot-scope="scope"
<a
v-if="scope.row.status !=='CREATE"
// 出于保密,屏蔽网关后面的
:href="`http://8.46.xxx.xxx/apollo/log/${scope.row.pod_info.build_id}/files/`"
target="_blank">日志(点击查看)</a>
</template>
</el-table-column>
</el-table>
<el-pagination
background layout="total, sizes,prev, pager, next, jumper"
:page-size="[30]"
:total="pagination.page"
:page-size="pagination.per_page"
@current-change="reload_pagination">
</el-pagination>
</div>
</div>
</div>
</div>
</section>
</template>
终于到了大家喜欢的script的环节:
<script>
import api from '../fetch/api'
export default {
data() {
return {
pagination: {
total: 0,
page: 1,
per_page: 30,
data: []
},
show_all: false,
ip: ' ',
creator: ' ',
options: [{
value: 'RUNNING',
label: 'RUNNING'
}, {
value: 'Completed',
label: 'Completed'
}],
choice: ' ',
word = []
}
},
mounted() {
this.reload_pagination()
},
methods: {
reload_pagination(page) {
const params = {}
if (page) {
params.page = page
}
params.ip = this.ip
params.status = this.choice
if (!this.show_all){
params.creator =this.$store.state.userinfo._id
}else {
params.creator = this.creator
}
api.list_check_point_job(params).then(data => {
this.pagination = data
})
},
convert_service_result_list(result){
const ret = []
if (result.data) {
Object.entries(result.data).map(value => {
value[1].name = value[0]
this.$set(value[1], 'isShow', false)
ret.push(value[1])
return value
})
}
return ret
},
convert_original_info(data) {
const type = typeof data
if (type === 'string') {
return data
}
return data.map(value => {
if (typeof value === 'string')
{
return value.join('\n')
}).join('\n')
},
open_text(scope) {
scope.isShow = !scope.isShow
this.word.push(scope.analyze)
},
out_text(scope) {
scope.isShow = !scope.isShow
scope.analyze = this.word.pop()
},
save_service(scope, props) {
if (this.validate_device_list())
{
this.list = {
content: scope.analyze,
check_point: scope.name,
id: props.row.id
}
api.save_detail_analyze_content(this.list).then(returndata => {
if (returndata.result === 'success') {
this.$message({
message: '保存成功',
type: 'success'
})
}
scope.isShow = !scope.isShow
)}
}
},
validate_device_list() {
let flag =true
if (this.content === 0) {
flag =false
}
return flag
}
},
watch: {
show_all() {
this.$nextTick(() => {
this.reload_pagination()
})
}
}
}
</script>
上面唯一要将一下的是data里面的word和open_text以及out_text函数, 这里实现的功能是如果在输入框输入了文字后再点击返回按钮,不会因为v-model双向绑定而使原来显示的界面上的数值有所改变,当然这里还有点小bug,因为点open_text里面push了值后word里面还存在值,多点几次后会把其他地方的界面显示给影响,我的处理方案是把word设成字典,用键值对去取,有兴趣的朋友可以试试。
最后是后台python带部分一并贴出来,原理就简单很多了,找出id等于传过来的id的那个object,然后取出相应的check point,再用mongoengine的update方法进行操作至此就完成所有操作。
@ns.route('/fail-analyze')
class DetailPost(Resource):
def post(self):
data = request.json
content = data['content']
check_point = data['check_point']
job = Job.objects(id=data['id'])
kwargs = {'set__result__data__%s__analyze' % check_point: content}
job.update(**kwargs)
return {"result": "success"}
写了五个小时的文章,代码都是一个一个敲下来的,可能会有一些输入错误不过不影响理解,如果有什么意见或者建议欢迎提出来,大家一起讨论。