<template>
|
<div>
|
<div class="choose-material" :class="title ? '' : 'has-title'">
|
<div class="add-group">
|
<div v-if="title">*</div>
|
<span v-if="title">{{ title }}</span>
|
<el-button
|
@click="showAddDialog = true"
|
class="el-icon-plus"
|
type="primary"
|
>
|
添加组件</el-button
|
>
|
</div>
|
|
<!-- 选择组件弹窗 -->
|
<AddComponentDialog
|
:visible="showAddDialog"
|
@confirm="addComponent"
|
@close="showAddDialog = false"
|
/>
|
|
<!-- 动态渲染组件 -->
|
<div
|
v-for="(item, idx) in components"
|
:key="item.id"
|
class="dynamic-component"
|
>
|
<!-- 富文本 -->
|
<div v-if="item.type == 'richText'">
|
<AiEditor
|
:ref="`editor_${item.id}`"
|
v-model="item.data.content"
|
height="200px"
|
placeholder="请输入内容..."
|
/>
|
</div>
|
<!-- 自定义表格 -->
|
<div v-else-if="item.type == 'customTable'" style="flex: 1">
|
<div class="table-actions">
|
<el-button size="mini" @click="showTableHeaderDialog(idx)"
|
>添加表头</el-button
|
>
|
<el-button
|
size="mini"
|
type="primary"
|
@click="showAddRowDialog(idx, item.data.headers)"
|
>添加数据</el-button
|
>
|
</div>
|
<Table
|
:data="item.data.rows"
|
:total="null"
|
:height="null"
|
class="groupTable"
|
>
|
<!-- <el-table-column
|
type="index"
|
label="序号"
|
width="80"
|
></el-table-column> -->
|
|
<el-table-column
|
v-for="(header, hidx) in item.data.headers"
|
:key="hidx"
|
:label="header.name"
|
:prop="header.name"
|
/>
|
<el-table-column
|
label="更新时间"
|
prop="updateTime"
|
min-width="180"
|
></el-table-column>
|
<el-table-column label="操作" min-width="200">
|
<template slot-scope="scope">
|
<el-button type="text" @click="handleEditRow(idx, scope.$index)"
|
>编辑</el-button
|
>
|
<el-button
|
type="text"
|
@click="handleDeleteRow(idx, scope.$index)"
|
>删除</el-button
|
>
|
</template>
|
</el-table-column>
|
</Table>
|
</div>
|
<!-- 文件上传 -->
|
<div v-else-if="item.type == 'fileUpload'">
|
<el-upload
|
action="#"
|
:file-list="item.data.fileList"
|
:on-change="(file, fileList) => handleFileChange(idx, fileList)"
|
list-type="text"
|
>
|
<el-button size="small" icon="el-icon-upload2">点击上传</el-button>
|
</el-upload>
|
</div>
|
<!-- 图片上传 -->
|
<div v-else-if="item.type == 'imageUpload'">
|
<el-upload
|
action="#"
|
:file-list="item.data.imageList"
|
:on-change="(file, fileList) => handleImageChange(idx, fileList)"
|
:on-success="
|
(res, file, fileList) =>
|
handleImageSuccess(res, file, fileList, idx)
|
"
|
list-type="picture-card"
|
>
|
<i class="el-icon-plus"></i>
|
<div class="upload-text">上传图片</div>
|
</el-upload>
|
<div class="uploaf-notice">支持.jpg .png格式</div>
|
</div>
|
<img
|
src="@/assets/public/delete.png"
|
@click="removeComponent(idx)"
|
alt="删除"
|
class="delete-icon"
|
/>
|
</div>
|
</div>
|
|
<!-- 添加表头弹窗 -->
|
<el-dialog
|
:visible.sync="tableHeaderDialog.visible"
|
title="添加表头"
|
width="300px"
|
>
|
<el-input
|
v-model="tableHeaderDialog.header"
|
placeholder="请输入表头"
|
></el-input>
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="tableHeaderDialog.visible = false">取消</el-button>
|
<el-button type="primary" @click="confirmAddHeader">确定</el-button>
|
</span>
|
</el-dialog>
|
|
<addTableHeader
|
:visible.sync="tableHeaderDialog.visible"
|
@confirm="confirmAddHeader"
|
></addTableHeader>
|
<addTableData
|
:visible.sync="rowDialog.visible"
|
:headerList="rowDialog.headers"
|
:editData="rowDialog.form"
|
:isEdit="rowDialog.isEdit"
|
@success="confirmAddRow"
|
>
|
</addTableData>
|
</div>
|
</template>
|
|
<script>
|
import AddComponentDialog from "../AddComponentDialog/index.vue";
|
import AiEditor from "../AiEditor/index.vue";
|
import Table from "../Table/index.vue";
|
import addTableHeader from "./addTableHeader.vue";
|
import addTableData from "./addTableData.vue";
|
|
export default {
|
name: "DynamicComponent",
|
components: {
|
AddComponentDialog,
|
AiEditor,
|
Table,
|
addTableHeader,
|
addTableData,
|
},
|
props: {
|
title: {
|
type: String,
|
default: "",
|
},
|
},
|
data() {
|
return {
|
showAddDialog: false,
|
components: [],
|
tableHeaderDialog: {
|
visible: false,
|
idx: null,
|
header: "",
|
},
|
rowDialog: {
|
visible: false,
|
idx: null,
|
rowIndex: null,
|
isEdit: false,
|
headers: [],
|
form: {},
|
},
|
headerList: [], //编辑的表头列表
|
};
|
},
|
methods: {
|
addComponent(type) {
|
this.showAddDialog = false;
|
const id = Date.now() + Math.random();
|
let data = {};
|
if (type === "richText") data = { content: "" };
|
if (type === "customTable") data = { headers: [], rows: [] };
|
if (type === "fileUpload") data = { fileList: [] };
|
if (type === "imageUpload") data = { imageList: [] };
|
console.log(type, "111111111111111", this.components);
|
|
this.components.push({ id, type, data });
|
},
|
removeComponent(idx) {
|
this.components.splice(idx, 1);
|
},
|
showTableHeaderDialog(idx) {
|
this.tableHeaderDialog.visible = true;
|
this.tableHeaderDialog.idx = idx;
|
this.tableHeaderDialog.header = "";
|
},
|
confirmAddHeader(data) {
|
console.log("data", data);
|
const { idx } = this.tableHeaderDialog;
|
// 添加新表头
|
this.components[idx].data.headers.push({ ...data });
|
|
// 为已有行数据添加新表头对应的默认值
|
this.components[idx].data.rows.forEach(row => {
|
// 如果行数据是对象,直接添加新属性
|
if (typeof row === 'object' && row !== null) {
|
if (data.type === 'user') {
|
this.$set(row, data.name, []);
|
} else {
|
this.$set(row, data.name, '');
|
}
|
} else {
|
// 如果行数据不是对象,转换为对象
|
const newRow = {};
|
this.components[idx].data.headers.forEach(header => {
|
if (header.name === data.name) {
|
if (header.type === 'user') {
|
newRow[header.name] = [];
|
} else {
|
newRow[header.name] = '';
|
}
|
} else {
|
newRow[header.name] = row[header.name] || '';
|
}
|
});
|
// 替换原行数据
|
const rowIndex = this.components[idx].data.rows.indexOf(row);
|
this.components[idx].data.rows.splice(rowIndex, 1, newRow);
|
}
|
});
|
|
// 关闭弹窗并重置数据
|
this.tableHeaderDialog.visible = false;
|
this.tableHeaderDialog = {
|
visible: false,
|
idx: null,
|
header: "",
|
};
|
},
|
showAddRowDialog(idx, headerList) {
|
this.headerList = headerList;
|
this.rowDialog.visible = true;
|
this.rowDialog.idx = idx;
|
this.rowDialog.isEdit = false;
|
this.rowDialog.headers = this.components[idx].data.headers;
|
this.rowDialog.form = {};
|
// 初始化表单数据
|
this.rowDialog.headers.forEach((header) => {
|
if (header.type === "user") {
|
this.rowDialog.form[header.name] = [];
|
} else {
|
this.rowDialog.form[header.name] = "";
|
}
|
});
|
},
|
handleEditRow(idx, rowIndex) {
|
this.rowDialog.visible = true;
|
this.rowDialog.idx = idx;
|
this.rowDialog.rowIndex = rowIndex;
|
this.rowDialog.isEdit = true;
|
this.rowDialog.headers = this.components[idx].data.headers;
|
// 深拷贝行数据,避免直接修改原数据
|
this.rowDialog.form = JSON.parse(
|
JSON.stringify(this.components[idx].data.rows[rowIndex])
|
);
|
},
|
handleDeleteRow(idx, rowIndex) {
|
this.$confirm("确认删除该行数据吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
this.components[idx].data.rows.splice(rowIndex, 1);
|
this.$message.success("删除成功");
|
})
|
.catch(() => {});
|
},
|
handleFileChange(idx, fileList) {
|
this.components[idx].data.fileList = fileList;
|
},
|
handleImageChange(idx, fileList) {
|
this.components[idx].data.imageList = fileList;
|
},
|
handleImageSuccess(res, file, fileList, idx) {
|
// 假设后端返回的图片地址在 res.url
|
file.url = res.url;
|
this.components[idx].data.imageList = fileList;
|
},
|
},
|
};
|
</script>
|
|
<style scoped lang="less">
|
.choose-material {
|
background: #eff8fa;
|
padding: 20px;
|
margin-top: 37px;
|
}
|
.has-title{
|
margin-top: 0px !important;
|
}
|
.add-group {
|
display: flex;
|
align-items: center;
|
margin-bottom: 19px;
|
|
div {
|
color: #f56c6c;
|
}
|
|
span {
|
font-weight: 500;
|
font-size: 14px;
|
color: #222222;
|
line-height: 21px;
|
margin: 0 32px 0 8px;
|
}
|
}
|
.dynamic-component {
|
background: #ffffff;
|
padding: 15px 20px;
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 20px;
|
.delete-icon {
|
width: 16px;
|
height: 16px;
|
cursor: pointer;
|
}
|
}
|
::v-deep .el-upload {
|
width: 104px;
|
height: 104px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
flex-direction: column;
|
.upload-text {
|
font-weight: 400;
|
font-size: 14px;
|
color: rgba(0, 0, 0, 0.85);
|
line-height: 22px;
|
margin-top: 13px;
|
}
|
}
|
.uploaf-notice {
|
font-weight: 400;
|
font-size: 14px;
|
color: rgba(0, 0, 0, 0.85);
|
line-height: 22px;
|
margin-top: 8px;
|
}
|
.table-actions {
|
margin-bottom: 10px;
|
display: flex;
|
gap: 10px;
|
}
|
.groupTable {
|
width: 100%;
|
margin-top: 10px;
|
::v-deep .el-input__inner {
|
width: unset !important;
|
}
|
}
|
|
</style>
|