落笔前,先期望疫情快快过去,都要生锈了都~
场景一
模拟接口请求,对请求头的参数进行处理,如下图:
嗯,我是用的vue
版本的ant design
,然后实现之后是这样的:
相关代码:
<template>
<div class="mock-info">
<a-form :form="form">
<!-- 基本信息 -->
<a-divider orientation="left" style="color: #1890ff;">基本信息</a-divider>
<a-form-item
v-bind="formItemLayout"
label="期待名称">
<a-input
v-decorator="[
'name',
{rules: [{ required: true, message: '请输入环境域名'}]}
]"
placeholder="请输入期待名称"/>
</a-form-item>
<a-form-item
style="margin-bottom: 0;"
v-bind="formItemLayoutWithOutLabel">
<a-switch checkedChildren="JSON" unCheckedChildren="JSON" v-decorator="['is_json', {valuePropName: 'checked', initialValue: false }]" />
</a-form-item>
<div class="paramsArr" v-show="!form.getFieldValue('is_json')"> <!--非json展示输入框-->
<a-form-item
v-for="(k, index) in form.getFieldValue('baseKeys')"
:key="k"
v-bind="index === 0 ? formItemLayout : formItemLayoutWithOutLabel"
:label="index === 0 ? '参数名称' : ''"
:required="false"
style="margin-bottom: 0;">
<a-input
v-decorator="[`paramNames[${k}]`]"
placeholder="参数过滤"
style="width: 40%; margin-right: 8px"/>
<a-input
v-decorator="[`paramValues[${k}]`]"
placeholder="参数值"
style="width: 40%; margin-right: 8px"/>
<a-icon
v-if="form.getFieldValue('baseKeys').length > 1"
class="dynamic-delete-button"
type="minus-circle-o"
:disabled="form.getFieldValue('baseKeys').length === 1"
@click="() => removeParam(k)"/>
</a-form-item>
<a-form-item v-bind="formItemLayoutWithOutLabel">
<a-button type="primary" style="width: 60%" @click="addParam">
<a-icon type="plus" /> 添加参数
</a-button>
</a-form-item>
</div>
<div v-show="form.getFieldValue('is_json')">
<a-form-item
v-bind="formItemLayout"
label="参数名称">
<v-jsoneditor
style="margin-top: 12px;"
:options="options"
v-model="request_params" />
</a-form-item>
</div>
<!-- 响应信息 -->
<a-divider orientation="left" style="color: #1890ff;">响应</a-divider>
<a-form-item
v-bind="formItemLayout"
label="HTTP Code">
<a-select
v-decorator="[
'http_code',
{rules: [{ required: false, message: '请选择'}]}
]"
showSearch
placeholder="请选择">
<a-select-option v-for="(item, index) in codes" :key="index" :value="item">{{item}}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
v-bind="formItemLayout"
label="延时">
<a-input-number
v-decorator="['delay_time', { initialValue: 0 }]"
:min="0"/> ms
</a-form-item>
<div class="httpArr">
<a-form-item
v-for="(k, index) in form.getFieldValue('httpKeys')"
:key="k"
v-bind="index === 0 ? formItemLayout : formItemLayoutWithOutLabel"
:label="index === 0 ? 'HTTP头' : ''"
:required="false"
style="margin-bottom: 0;">
<a-select
mode="combobox"
v-decorator="[`httpNames[${k}]`]"
showSearch
placeholder="请选择"
style="width: 40%; margin-right: 8px">
<a-select-option v-for="(item, index) in http_headers" :key="index" :value="item">{{item}}</a-select-option>
</a-select>
<a-input
v-decorator="[`httpValues[${k}]`]"
placeholder="参数值"
style="width: 40%; margin-right: 8px"/>
<a-icon
v-if="form.getFieldValue('httpKeys').length > 1"
class="dynamic-delete-button"
type="minus-circle-o"
:disabled="form.getFieldValue('httpKeys').length === 1"
@click="() => removeHttp(k)"/>
</a-form-item>
<a-form-item v-bind="formItemLayoutWithOutLabel">
<a-button type="primary" style="width: 60%" @click="addHttp">
<a-icon type="plus" /> 添加HTTP头
</a-button>
</a-form-item>
</div>
<a-form-item
v-bind="formItemLayout"
label="Body">
<v-jsoneditor
style="margin-top: 12px;"
:options="options"
v-model="response_body" />
</a-form-item>
</a-form>
</div>
</template>
复制代码
<script>
import VJsoneditor from 'v-jsoneditor'
export default {
name: 'mock-info',
components: {
VJsoneditor, // json编辑器
},
props: {
row: Object, // 回填信息
},
data() {
const formItemLayout = {
labelCol: {
span: 5
},
wrapperCol: {
span: 18
},
};
const formItemLayoutWithOutLabel = {
wrapperCol: {
span: 18,
offset: 5
},
};
const options = {
mainMenuBar: false,
mode: 'code'
};
// 可以考虑后端返回,也允许用户自己添加
const codes = [100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 307, 308, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 422, 423, 424, 426, 428, 429, 431, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511]
// 可以考虑后端返回,也允许用户自己添加
const http_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding', 'Accept-Language', 'Accept-Datetime', 'Authorization', 'Cache-Control', 'Connection', 'Cookie', 'Content-Disposition', 'Content-Length', 'Content-MD5', 'Content-Type', 'Date', 'Expect', 'From', 'Host', 'If-Match', 'If-Modified-Since',
'If-None-Match', 'If-Range', 'If-Unmodified-Since', 'Max-Forwards', 'Origin', 'Pragma', 'Proxy-Authorization', 'Range', 'Referer', 'TE', 'User-Agent', 'Upgrade', 'Via', 'Warning', 'X-Requested-With', 'DNT', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'Front-End-Https', 'X-Http-Method-Override',
'X-ATT-DeviceId', 'X-Wap-Profile', 'Proxy-Connection', 'X-UIDH', 'X-Csrf-Token']
return {
formItemLayout,
formItemLayoutWithOutLabel,
options,
baseId: 0, // 基本信息的ID
httpId: 0, // 响应信息得ID
request_params: {}, // 请求参数
response_body: {}, // 响应参数
codes,
http_headers,
itemVal: ''
};
},
beforeCreate() {
// 创建form
this.form = this.$form.createForm(this, { name: 'form' });
this.form.getFieldDecorator('baseKeys', { initialValue: [0], preserve: true });
this.form.getFieldDecorator('httpKeys', { initialValue: [0], preserve: true });
},
mounted() {
let vm = this
// 信息回填
vm.form.setFieldsValue({
name: vm.row.name,
is_json: vm.row.is_json == '1' ? true : false, // 这里考虑'0','1','2'之类的
http_code: vm.row.resp_code,
delay_time: vm.row.delay || 0
})
if(vm.row.id) {
vm.response_body = JSON.parse(vm.row.resp_body)
vm.form.id = vm.row.id
// 回填请求参数
if(vm.row.is_json == '0') { // 非JSON
vm.rollbackKeyValue(JSON.parse(vm.row.request_params), 'paramNames', 'paramValues', 'baseKeys', 'baseId')
}
if(vm.row.is_json == '1') { // JSON格式
vm.request_params = JSON.parse(vm.row.request_params)
}
// 回填响应参数
vm.rollbackKeyValue(JSON.parse(vm.row.resp_header), 'httpNames', 'httpValues', 'httpKeys', 'httpId')
}
},
methods: {
// 回填keyValues值
rollbackKeyValue(objectData, names, values, keys, seq) {
let vm = this
let temp_names = [],
temp_values = [],
temp_keys = [];
let objKeys = Object.keys(objectData)
if(objKeys.length > 0) {
objKeys.map((name, index) => {
temp_names.push(name)
temp_values.push(objectData[name])
temp_keys.push(index)
vm[seq] = index
})
// 回填
vm.form.setFieldsValue({
[keys]: temp_keys, // 这个要先出来,保证UI被渲染出来了
})
vm.$nextTick(() => { // nextTick保证dom被渲染好之后进行下一步操作
vm.form.setFieldsValue({
[names]: temp_names,
[values]: temp_values
})
})
}
},
// 移除参数
removeParam(k) {
const { form } = this;
const baseKeys = form.getFieldValue('baseKeys');
if (baseKeys.length === 1) {
return;
}
form.setFieldsValue({
baseKeys: baseKeys.filter(key => key !== k),
});
},
// 移除http
removeHttp(k) {
const { form } = this;
const httpKeys = form.getFieldValue('httpKeys');
if (httpKeys.length === 1) {
return;
}
form.setFieldsValue({
httpKeys: httpKeys.filter(key => key !== k),
});
},
// 添加参数
addParam() {
const { form } = this;
const baseKeys = form.getFieldValue('baseKeys');
const nextKeys = baseKeys.concat(++this.baseId);
form.setFieldsValue({
baseKeys: nextKeys,
});
},
// 添加Http
addHttp() {
const { form } = this;
const httpKeys = form.getFieldValue('httpKeys');
const nextKeys = httpKeys.concat(++this.httpId);
form.setFieldsValue({
httpKeys: nextKeys,
});
}
},
};
</script>
复制代码
<style lang="less">
.mock-info {
.dynamic-delete-button {
cursor: pointer;
position: relative;
top: 4px;
font-size: 24px;
color: #999;
transition: all 0.3s;
}
.dynamic-delete-button:hover {
color: #777;
}
.dynamic-delete-button[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
}
</style>
复制代码
嗯~这种实现的方式还是和舒服的,不用自己布局,不用自己再次思考逻辑;如果你想自己捣鼓一个,那你是真的闲,还不如花点时间捣鼓其他非编程的东西。
注意:能用react版本的ant design尽量用react版本的~
场景二
根据后台接口返回的字段来渲染。类型值对应不同的组件,如下:
-
类型值1:单行文本组件
-
类型值2:多行文本组件
-
类型值3:单选组件
-
类型值4:多选组件
-
类型值5:文件上传组件
每种类型出现的次数是大于等于0,而且后端可配置必填或者非必填。嗯,下面实现它~
因为是移动端的业务,肯定是选UI框架帮我干活啊,这里我选了有赞的vant
。用的还是vue
去搭建工程,别问为啥不用react
,公司给我时间,我就用react
~这是业务线啊,想得倒是美,而且还是疫情期间,不压你时间就很好了。所以做完后,乖乖申请回去中台~
下面实现的思路,效果和关键代码~
- 动态组件,那么每个字段都要有一个字段标识该组件,这里后端没有配,那么我自己创建一个
uuid
(能叫得动后端,就叫后端配吧...)
<!-- 进行字段的遍历 -->
<div v-for="(type, type_index) in alterFields">
<!-- 单行文本和多行文本区域 -->
<div class="advertise part" v-if="type.fieldType===1 || type.fieldType ===2">
<!-- 单行文本内容 -->
<!-- 多行文本内容 -->
</div>
<!-- 单选和多选区域 -->
<div class="advertise part" v-if="type.fieldType===3 || type.fieldType ===4">
<!-- 单选内容 -->
<!-- 多选内容 -->
</div>
<!-- 资源上传的区域 -->
<div class="advertise part" v-if="type.fieldType===5">
<!--文件上传内容-->
</div>
</div>
复制代码
- 后端返回类型(优先)/前端写死类型(备选),对后端返回的动态数据进行遍历,以展示不同类型的组件
我这里前端写死了,蓝瘦香菇,三个字"人真懒"。
types: [{
code: 1,
text: '单行文本'
}, {
code: 2,
text: '多行文本'
}, {
code: 3,
text: '单选'
}, {
code: 4,
text: '多选'
}, {
code: 5,
text: '文件上传'
}],
复制代码
- 编辑的时候,信息回填前要考虑动态数据时候已经发生改动(时刻以后端返回的动态数据为准来回填)
// 将返回的字段和编辑的字段进行配对,回填
let _alterFields = []
for (let i = 0; i < vm.alterFields.length; i++) {
let _item = vm.alterFields[i]
_alterFields.push(_item)
for (let j = 0; j < vm.editFileds.length; j++) {
if (_item.id === vm.editFileds[i].id) {
// 替换值
_alterFields.splice(i, 1, Object.assign(_item, {
fieldValue: vm.editFileds[i].fieldValue,
[_item.uuid]: vm.editFileds[i].fieldValue,
}))
}
}
}
vm.alterFields = _alterFields
复制代码
- 前端限制文件上传的大小,有必要自己做什么压缩文件之类的骚操作,这里是内嵌到app里面的,app里面已经对图片处理。上传文件不要直接调公司的服务,直接调上传到云的操作就行,不然公司服务会崩溃的~
// 文件资源的限制
prompt_for_oversize () {
this.$dialog({
title: '提 示',
text: '单个文件大小不应该大于10M',
confirmText: '了解',
showCancelBtn: false,
confirm () { }
})
}
复制代码
- 文件上传使用
async await
来操作,更加直观明了
realUploadFile (item) {
let vm = this
let temp_valueFiled = []
let be_upload = false
return new Promise(resolve => {
// 上传到云
let formData = new FormData();
for (let i = 0; i < item[item.uuid].length; i++) {
let row = item[item.uuid][i]
if (row.fileName) { // 编辑的时候存在文件就不用再上传到服务器了
temp_valueFiled.push(row)
if (temp_valueFiled.length === item[item.uuid].length) { resolve(temp_valueFiled) }
} else {
be_upload = true
console.log('row.file', row.file.size)
formData.append("files", row.file);
}
}
if (!be_upload) { // 不需要上传的时候直接返回
return
}
vm.api.apply.uploadMultiFiles(formData).then(res => {
console.log(res)
if (res.code === '00000') {
temp_valueFiled.push(...res.data)
resolve(temp_valueFiled)
} else {
vm.$toast({ msg: res.message || '上传失败,请重试!' })
vm.forbidden = false
}
})
})
}
复制代码
- 对单选组件进行处理,非必填的状态下,要允许取消勾选
// 处理单选框(如果是非必填字段,允许用户取消)
handleRadio (type, type_index, item_title) { // type是整个项目,type_index是类型遍历的索引, item_title是选中项目的名称
if (type.isRequired) { return } // 必选的单选框,啥都不做
let vm = this
let union = `${type['uuid']}_${item_title}` // 唯一的标识
if (vm.radioSet.has(union)) { // 存在集合中
vm.radioSet.delete(union)
vm.alterFields.splice(type_index, 1, Object.assign(type, {
[type.uuid]: ''
}))
} else {
if (vm.radioSet.size > 0) {
vm.radioSet.forEach(function (val) {
if (val.indexOf(`${type['uuid']}_`) >= 0) {
vm.radioSet.delete(val) // 移除当前组件的唯一标识的所有值
}
})
}
vm.radioSet.add(union) // 每个单选的组件只维护一个数据
}
},
复制代码
...
效果如下:
公司业务,我怂,不敢放全部代码
后话
谢谢各位看官的捧场~
更多的内容请看下面的链接
本文同步分享在 博客“Jimmy”(JueJin)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。