场景一
模拟接口请求,对请求头的参数进行处理,如下图:
嗯,我是用的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) // 每个单选的组件只维护一个数据 } }, 复制代码
...
效果如下: