主要思路:基于elementplus ,并利用配置文件,生成表单控件(el-input,el-select,el-button等),设置栏栅布局,设置表单校验,提交按钮,placeholder,labelWidth,elRowGutter,labelPosition,slot插槽个性化内容等。
1.相关文件:
- testCaseConfig.js:配置表单控件的数据,按钮,校验数据等;
- FormItem.jsx:生成表单控件el-input,el-select,el-button等)
- FormButton.jsx:生成按钮
- TForm.vue:通用表单组件
- GenerateTestCase.vue:页面显示
2.config.js
- rules表单校验rules
- formItems生成表单控件的数据
- buttons需要生成的按钮
- elRowGutter每个单元格之间的间隔
- tableBorder表单是否需要边框
- colLayout栏栅布局配置(没用到)
// Object.freeze是可以冻结对象,对于不需要改变的对象使用,可以提升性能
const testCaseConfig = {
rules: {
sitFunctionName: [{ required: true, message: '请输入SIT功能列名', trigger: 'blur' }],
sitTestProject: [{ required: true, message: '请输入SIT测试项目', trigger: 'blur' }],
sitProductionTaskNumber: [{ required: true, message: '请输入SIT生产任务编号', trigger: 'blur' }],
sitBatch: [{ required: true, message: '请输入SIT批次', trigger: 'blur' }],
stExperimentalArchives: [{ required: true, message: '请输入ST实验档案', trigger: 'blur' }],
stAcceptancePerson: [{ required: true, message: '请选择ST验收人员', trigger: 'change' }],
},
formItems: [{
field: 'sitFunctionName',
prop: 'sitFunctionName',
label: 'SIT功能',
placeholder: 'SIT功能',
type: 'input',
// size: 'small',
span: 8,
},
{
field: 'sitTestProject',
prop: 'sitTestProject',
type: 'input',
label: 'SIT测试项目',
placeholder: 'SIT测试项目',
// editable: true,
// size: 'small',
span: 8,
},
{
field: 'sitProductionTaskNumber',
prop: 'sitProductionTaskNumber',
type: 'input',
label: 'SIT生产任务编号',
labelWidth: '150px',
placeholder: 'SIT生产任务编号',
isHidden: false,
span: 8,
},
{
field: 'sitBatch',
prop: 'sitBatch',
type: 'input',
label: 'SIT批次',
placeholder: 'SIT批次',
span: 8,
},
{
field: 'stExperimentalArchives',
prop: 'stExperimentalArchives',
type: 'input',
label: 'ST试验档案',
span: 8,
placeholder: 'ST试验档案',
},
{
field: 'stAcceptancePerson',
prop: 'stAcceptancePerson',
type: 'select',
label: 'ST验收人员',
span: 8,
labelWidth: '150px',
placeholder: '请选择ST验收人员',
options: []
}
],
// 按钮
buttons: [{
name: '生成案例',
title: 'generateTestCase',
type: 'primary',
size: 'default', //可以是default,small,large
icon: 'Edit',
// 按钮是否为朴素类型
// plain: true,
onClick: null
}, {
name: '重置',
type: 'info',
title: 'resetTestCase',
size: 'default',
icon: 'DocumentDelete',
// plain: true,
onClick: null
},
{
name: '下载测试案例',
type: 'success',
title: 'download',
size: 'default',
icon: 'Download',
isHidden: true,
// plain: true,
onClick: null
}
],
ref: 'testCaseFormRef',
labelWidth: '120px',
labelPosition: 'right',
inline: true,
editable: true,
// 单元列之间的间隔
elRowGutter: 10,
// size: 'small',
// 是否需要form边框
tableBorder: true,
colLayout: {
xl: 5, //2K屏等
lg: 8, //大屏幕,如大桌面显示器
md: 12, //中等屏幕,如桌面显示器
sm: 24, //小屏幕,如平板
xs: 24 //超小屏,如手机
}
}
export default testCaseConfig;
3.FormItem.jsx
import {
ElInput,
ElSelect,
ElOption,
ElButton
} from 'element-plus'
import { defineComponent } from 'vue'
// 普通显示
const Span = (form, data) => (
<span>{data}</span>
)
// 输入框
const Input = (form, data) => (
<ElInput
v-model={form[data.field]}
type={data.type}
size={data.size}
show-password={data.type == 'password'}
clearable
placeholder={data.placeholder}
autosize = {{
minRows: 3,
maxRows: 4,
}}
{...data.props}
>
</ElInput>
)
const setLabelValue = (_item, { optionsKey } = {}) => {
return {
label: optionsKey ? _item[optionsKey.label] : _item.label,
value: optionsKey ? _item[optionsKey.value] : _item.value,
}
}
// 选择框
const Select = (form, data) => (
<ElSelect
size={data.size}
v-model={form[data.field]}
filterable
clearable
placeholder={data.placeholder}
{...data.props}
>
{data.options.map((item) => {
return <ElOption {...setLabelValue(item, data)} />
})}
</ElSelect>
)
const Button = (form, data) =>{
<ElButton
type={data.type}
size={data.size}
icon={data.icon}
plain={data.plain}
click={data.clickBtn}
value={data.value}
></ElButton>
}
const setFormItem = (
form,
data,
editable,
) => {
if (!form) return null
if (!editable) return Span(form, data)
switch (data.type) {
case 'input':
return Input(form, data)
case 'textarea':
return Input(form, data)
case 'password':
return Input(form, data)
case 'inputNumber':
return InputNumber(form, data)
case 'select':
return Select(form, data)
case 'date':
case 'daterange':
return Date(form, data)
case 'time':
return Time(form, data)
case 'radio':
return Radio(form, data)
case 'checkbox':
return Checkbox(form, data)
case 'button':
return Button(form, data)
default:
return null
}
}
export default () =>
defineComponent({
props: {
data: Object,
formData: Object,
editable: Boolean,
},
setup(props) {
return () =>
props.data
? setFormItem(props.formData, props.data, props.editable)
: null
},
})
4.FormButton.jsx
emits:['click']需要声明,否则有警告,但是声明了原生的click会被覆盖,所以没有声明
import {
ElButton
} from 'element-plus'
import { defineComponent } from 'vue'
const Button = (form, data) =>(
!data.isHidden?<ElButton
type={data.type}
size={data.size}
icon={data.icon}
plain={data.plain}
click={data.onClick}
>{data.name}</ElButton>:''
)
const setBottonItem = (
form,
data,
editable,
) => {
if(!data) return null;
if (!editable) return Span(form, data)
return Button(form, data);
}
export default () =>
defineComponent({
props: {
data: Object,
formData: Object,
editable: Boolean,
},
// 这里必须要声明
// emits:['click'],
setup(props) {
return () =>
props.data
? setBottonItem(props.formData, props.data, props.editable)
: null
},
})
5.
<template>
<div class="testcase-box" v-loading="loading">
<t-form ref="testCaseFormRef"
:btnList="configData.buttons"
:modelForm="testCaseForm"
:formBorder="true"
:rules="configData.rules"
:data="formItems"
>
<template #footer v-if="testcasePath">
<div class="file-path">文件路径:{{ testcasePath }}</div>
</template>
</t-form >
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { getSTAndSITCase, downloadTestCase, getUsersExceptSelfAndVip } from '@/api/api'
import TForm from '@/components/form/TForm.vue'
import testCaseConfig from '@/config/form/testCaseConfig'
import { addFormOptions } from '@/tools/tools'
const configData = ref([]);
configData.value = testCaseConfig;
// data为必填项
const formItems = configData.value.formItems ? configData.value.formItems : {};
const loading = ref(false);
const testCaseFormRef = ref();
const allUser = ref([]);
const testcasePath = ref('');
const testCaseForm = reactive({
sitFunctionName: '', //SIT功能列名
sitTestProject: '', //SIT测试项目
sitProductionTaskNumber: '', //SIT生产任务编号
sitBatch: '', //SIT批次
stExperimentalArchives: '', //ST试验档案
stAcceptancePerson: '' //ST验收人员
});
const getStAcceptancePerson = async () => {
let res = await getUsersExceptSelfAndVip();
if (res.data.code === 200) {
allUser.value = res.data.data;
// 设置ST验收人员 选择框选项
let tempOptions = addFormOptions(allUser.value);
configData.value.formItems[5].options = tempOptions;
} else {
ElMessage.error("获取验收人员名单出错:" + res.data.errorMsg);
}
}
getStAcceptancePerson();
const generateTestCase = async () => {
if (!testCaseFormRef.value) return;
const result = await testCaseFormRef.value.validate()
if(result){
loading.value = true;
let res = await getSTAndSITCase(testCaseForm);
if (res.data.code === 200) {
loading.value = false;
testcasePath.value = res.data.data;
configData.value.buttons[2].isHidden = false;
ElMessage.success("案例生成成功");
} else {
loading.value = false;
ElMessage.error("案例生成失败:" + res.data.errorMsg);
}
}
}
const download = async () => {
loading.value = true;
let result = await downloadTestCase({ filePathAndName: testcasePath.value });
if (result.data.type == 'application/json') {
const reader = new FileReader();
reader.readAsText(result.data, 'utf-8');
reader.onload = function () {
const { code, errorMsg } = JSON.parse(reader.result);//reader.result里面含报错信息
ElMessage.error("案例下载失败:" + errorMsg);
}
} else if (result.data.type == 'application/octet-stream') {
const caseCronjob = document.createElement("a");
let blobCronjob = new Blob([result.data], { type: "application/zip, application/x-zip-compressed" }); //类型zip
caseCronjob.style.display = "none";
caseCronjob.href = URL.createObjectURL(blobCronjob);
let tempArr = testcasePath.value.split("\\");
let fileName = tempArr[tempArr.length-1];
// download属性,加上download后会指示浏览器下载而不是导航。但是这个属性是HTML5属性,仅兼容版本较高的浏览器
caseCronjob.setAttribute("download", fileName);
document.body.appendChild(caseCronjob);
caseCronjob.click();
document.body.removeChild(caseCronjob);
} else {
ElMessage.error("案例下载失败:下载数据类型有误");
}
loading.value = false;
}
const resetTestCase = () => {
if (!testCaseFormRef.value) return;
testCaseFormRef.value.resetFields();
}
/**
* 动态设置config中buttons 的点击方法
*/
configData.value.buttons.forEach((item,index)=>{
// 顺序必须和config中buttons顺序一致
[generateTestCase,resetTestCase,download].map((k,v)=>{
if(index === v) item.onClick = k;
});
});
</script>
<style scoped>
.testcase-box{
width:100%;
height: 100%;
}
.file-path{
margin: 0 auto;
}
</style>