09_Vue前端执行后端传递过来的JS代码块
一、前言
在 Vue 中执行后端传递过来的 JS 代码块可以使用 new Function() 构造函数,但是这样做存在安全风险,因为这允许执行任意代码。在实际应用中,你应该避免直接执行未知或不受信任的代码,因为这可能导致 XSS 攻击或其他安全问题。然而,如果你确信后端提供的代码是安全的并且在工作中确实有这样的需求,你可以按照以下方式执行它。
二、需求
付款时需要生成凭证,后端会处理一部分逻辑,前端需要根据 后端提供的JS代码 去执行,然后将返回结果以接口传参的形式 传递给后端。例如,如果列表页当前行的数据中 ‘是否职能部门’ (isFunction) 这个字段为 ture 的时候,则 ‘摘要’(abstractInfo)的值应该为 ‘科目编码’ + ’ / ’ +‘科目名称’ ,如果这个字段为 false 的时候 ,则 ‘摘要’ ()的值 应该为 ‘科目编码’ + ’ / ’ +‘科目名称’ + ’ / ’ +‘方向’ , 如下 JS
//JS 代码块的 内容
if (codeBlockItem.isFunction === true) {
codeBlockItem.abstractInfo = codeBlockItem.subjectCode + "/" + codeBlockItem.subjectName
} else if (item.isFunction === false) {
codeBlockItem.abstractInfo = codeBlockItem.subjectCode + "/" + codeBlockItem.subjectName + "/" + codeBlockItem.borrowDirect
}
因为需求 是需要 前端 定义 JS代码块 通过接口 传递给后端,由后端存储到数据库中,在之后的流程,通过接口获取 JS 代码块,在前端执行,执行结果再传递后端 。 实现 JS 代码块可自定义的功能。 所以我们需要 搭建 输入 JS 代码块 的输入框。
三、实操步骤
1、前端生成 JS 代码块
1.1、创建子组件 publicZYPZ
<template>
<div>
<el-dialog ref="publicZYPZ" :title="dialogTitle" class="dDetails" :visible.sync="dialogDetails" width="50%"
:before-close="handleDetailsClose">
<div style="height: 600px;">
<el-button size="mini" type="primary" style="margin-bottom: 10px;" @click="submit">保存</el-button>
<!-- 输入 JS 代码块 -->
<el-input type="textarea" v-model="jsDmk"></el-input>
</div>
</el-dialog>
</div>
</template>
并修改其默认样式修改为自己想要的如下 css
/deep/ .el-textarea__inner {
height: 550px !important;//设置默认高度
max-height: 550px !important; //设置最大高度
}
2、父子组件通信
2.1、 引入自定义组件
import publicZYPZ from '@/views/xxx/dialog/publicZYPZ.vue';
//局部注册
export default {
components: {
publicZYPZ,
},
}
2.2、 在页面中使用子组件
<template>
<div>
<!--@chang-jsDmk="changjsCode" 将子组件 通过保存按钮得到的数据 传递给父组件 表格中的 ‘摘要’ 字段-->
<publicZYPZ ref="publicZYPZ" @chang-jsDmk="changjsCode" />
</div>
</template>
2.3、 父组件 页面如下
<template>
<div v-show="tableDataShow" style="width: 83.5%;margin-left: .625rem;">
<div style="margin-bottom: .625rem; text-align: left;">
<el-button size="mini" type="success" icon="el-icon-plus" @click="addOne"></el-button>
<el-button size="mini" type="primary" :disabled="isSubmitted" :icon="submitIcon"
@click="submit">提交</el-button>
</div>
<el-table :data="tableDataRight" border style="width: 100%;" max-height="620px">
<el-table-column type="index" label="序号" width="50" align="center" show-overflow-tooltip />
<el-table-column label="科目编码" width="170px" align="center" prop="subjectCode" show-overflow-tooltip>
<template #default="{ row }">
<el-input-group>
<el-input :disabled="inputDisabled" v-model="row.subjectCode" size="small">
<template #append>
<el-button size="mini" style="background-color: #66B1FF;color: white;" type="primary"
@click="selectsubjectCode(row)">查看</el-button>
</template>
</el-input>
</el-input-group>
</template>
</el-table-column>
<el-table-column label="科目名称" width="170px" align="center" prop="subjectName" show-overflow-tooltip>
<template #default="{ row }">
<el-tooltip class="item" effect="dark" :content="row.subjectName" placement="top-start">
<el-input :disabled="inputDisabled" v-model="row.subjectName" size="small"></el-input>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="方向" width="120px" align="center" prop="borrowDirect" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.borrowDirect" size="small" placeholder="请选择">
<el-option v-for="item in borrowDirectOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="辅助核算" width="120px" align="center" prop="accountTypeName" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.accountTypeName" size="small" placeholder="请辅助">
<el-option v-for="item in accountTypeNameOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="是否职能部门" width="120px" align="center" prop="isFunction" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.isFunction" size="small" placeholder="请选择">
<el-option v-for="item in isFunctionOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="行号" width="100px" align="center" prop="inid" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.inid" size="small"></el-input>
</template>
</el-table-column>
<el-table-column label="供应商编码" width="150px" align="center" prop="supplierCode" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.supplierCode" size="small"></el-input>
</template>
</el-table-column>
<el-table-column label="摘要" width="150px" align="center" prop="abstractInfo" show-overflow-tooltip>
<template #default="{ row }">
<el-input-group>
<el-input placeholder="${key}/${key1}" :disabled="inputDisabled" v-model="row.abstractInfo"
size="small">
<!--点击配置 时调用子组件 -->
<template #append>
<el-button size="mini" style="background-color: #66B1FF;color: white;" type="primary"
@click="seePz(row)">配置</el-button>
</template>
</el-input>
</el-input-group>
</template>
</el-table-column>
<el-table-column label="借贷计算" width="150px" align="center" prop="formula" show-overflow-tooltip>
<template #default="{ row }">
<el-input placeholder="${key}/${key1}" v-model="row.formula" size="small"></el-input>
</template>
</el-table-column>
<el-table-column label="备注" width="200px" align="center" prop="remarks" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.remarks" size="small"></el-input>
</template>
</el-table-column>
</el-table>
</div>
</template>
并在 methods 中写 调用子组件的 函数
data(){
return{
JShangRow: undefined,
}
}
methods:{
seePz(row) {
this.JShangRow = row
const title = '摘要配置JS代码块'
//传递 弹窗的标题 和 当前行 的 存放 JS 代码的参数
this.$refs["publicZYPZ"].open(title, row.abstractInfo);
},
}
2.4、在子组件中编写逻辑,保存时传给父组件
methods: {
//接收参数
open(title, params) {
this.dialogDetails = true
this.dialogTitle = title
//创建一个代码块模板
const codeBanks = function () {
if (codeBlockItem.isFunction === true) {
codeBlockItem.abstractInfo = codeBlockItem.subjectCode + "/" + codeBlockItem.subjectName
} else if (item.isFunction === false) {
codeBlockItem.abstractInfo = codeBlockItem.subjectCode + "/" + codeBlockItem.subjectName + "/" + codeBlockItem.borrowDirect
}
//将创建的 JS 代码块 交给vue 实例管理
this.jsDmk = codeBanks
}
},
submit() {
this.dialogDetails = false
// 传递给父组件 根据父组件中的逻辑 赋值给当前行的 摘要 (abstractInfo )
this.$emit('chang-jsDmk', this.jsDmk)
},
handleDetailsClose() {
this.dialogDetails = false
},
}
2.5、父组件接收传递过来的JS 代码块 ,返给后端
methods:{
//1、将数据 给到 表格中的 abstractInfo
changjsCode(jsCodeBlock) {
this.JShangRow.abstractInfo = jsCodeBlock
},
// 2、在提交的时候 将 数据提交给后端
3、运行JS代码块
3.1、获取后端返回的 JS代码块,并运行
在 Vue 中执行后端传递过来的 JS 代码块可以使用 new Function() 构造函数,
// 假设 sysVitem.abstractInfo 包含从后端接收到的 JS 代码字符串
const codeBlock = sysVitem.abstractInfo;
// 创建一个新的 Function 对象
const func = new Function(‘codeBlockItem’, codeBlock);
// 将 sysVitem 作为参数传递给函数并执行
try {
func(sysVitem);
} catch (error) {
console.error(‘Error executing the code block:’, error);
}
// 注意: 如果 codeBlock 中引用了外部变量或函数,你需要确保这些变量或函数在全局作用域中是可访问的。
methods:{
async scpz() {
//1、传参 发请求 拿需要的详情 和 JS代码块
const params = this.multipleSelection.map(item => ({
id: item.id,
relatedId: item.relatedId,
orgId: item.orgId,
relatedType: item.relatedType,
voucherFlag: item.voucherFlag,
}));
if (params.length == 0) {
this.$message.warning('请选择需要生成凭证的数据');
} else {
const res = await judgmentAndConfiguration(params)
if (res.code == 200) {
if (res.data.length == 0) {
this.$message.error('返回data数据为空 无法拿到详情,和JS代码块');
} else {
// 查询费用详情数据 并拿 JS代码块
res.data.forEach(item => {
const params = {
id: item.relatedId,
relatedType: item.relatedType
}
//2、 查询费用详情并调取运行JS代码块的函数
this.selectAll(params, item)
});
this.$message({
message: res.msg,
type: 'success',
duration: 5000
});
this.select()
}
} else {
this.$message({
message: res.msg,
type: 'error',
duration: 5000
});
}
}
},
// 查费用详情
async selectAll(params, item) {
const ress = await getCostRelatedBankPageById(params)
//3、运行JS代码块
this.runJS(ress.data, item)
},
// 4、跑JS代码
runJS(detailsInfo, item) {
item.sysVoucherConfigureSubList.forEach(sysVitem => {
// 执行 JS 代码块的逻辑
try {
const codeBlock = sysVitem.abstractInfo
const func = new Function('codeBlockItem', codeBlock);
// 执行函数
func(sysVitem);
} catch (error) {
this.$message({
message: 'JS代码块不正确,请重新配置',
type: 'error',
duration: 5000
});
}
//5、执行完毕 将后端所需要的参数 传递过去
const params = [{
'voucherConfigureResponse': item,
'cdigest': sysVitem.abstractInfo
}]
this.JSvoucherInfoAndInster(params)
})
},
}
四、总结
这部分需求并不是很难,在实际开发中,我自认为需要考虑的有两点
1、需要效验 后端返回的JS代码块 是否符合自己的逻辑,以及 通过接口拿到的数据,是否包含 JS 代码块中的参数
如果你们有更高的方式 欢迎分享一起讨论。
2、也是最重要的一点,将实际的参数 与JS代码块中用到的参数 对应。
注:不建议把逻辑处理放在前端执行!
存在安全风险,因为这允许执行任意代码。在实际应用中,你应该避免直接执行未知或不受信任的代码,因为这可能导致 XSS 攻击或其他安全问题