最近的新项目有个需求需要合并单元列表。elementplus 的 table 提供了合并行或列的方法,可以参考一下https://element-plus.org/zh-cn/component/table.html
但项目中,后台数据返回格式和指定合并是动态且没有规律的,element 的示例过于简单,因此记录下来,大家可以参考一下!
效果图

后台返回的数据结构

代码详解
实操中,需要合并的代码通常就是 list_cnt 数据需要进行合并,因为后台返回的格式都是data 数据中包裹着 list_cnt 数据,这种格式看起来也是比较清晰。由 element 文档可知:el-table 组件主要靠 :span-method 方法实现合并。

完整代码
<template>
<div class="app-container">
<div class="search-bar">
<el-form :inline="true" :model="formdata" class="common-form-inline">
<el-form-item label="名称搜索">
<el-input v-model="formdata.name" clearable @clear="queryandroidlist(true)" placeholder="请输入" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="queryandroidlist(true)">搜索</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="list" :stripe="true" fit highlight-current-row :show-overflow-tooltip="true"
style="width: 100%; margin-top: 20px" v-loading="loading" @selection-change="handleselectionchange"
:span-method="objectspanmethod" border>
<el-table-column type="selection" align="center" width="55" />
<el-table-column align="center" label="实例" prop="idx" width="220px">
<template #default="scope">
<el-button size="small" round>实例: {{ scope.row.idx }}</el-button>
<el-button type="primary" size="small" @click="handleedit(scope.row)">编辑</el-button>
<el-button type="primary" size="small" @click="handlecreate(scope.row)">创建</el-button>
<div class="time-line">
<span>到期时间: {{ scope.row.update_time || '未授权' }}</span>
</div>
</template>
</el-table-column>
<el-table-column align="center" label="ip" prop="ip" width="180px">
</el-table-column>
<el-table-column align="center" label="adb/api端口" prop="levelname" width="180px">
<template #default="scope">
<span v-if="scope.row.adb_port || scope.row.sdk_port">{{ scope.row.adb_port || '-' }} - {{ scope.row.sdk_port
|| '-' }}</span>
<span v-else> - </span>
</template>
</el-table-column>
<el-table-column align="center" label="名称" prop="name" width="120px">
<template #default="scope">
<span v-if="scope.row.name">{{ scope.row.name }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column align="center" label="状态" prop="status" width="150px">
<template #default="scope">
<el-button plain :style="{
backgroundcolor: scope.row.status === 20 ? '#fef0f0' : scope.row.status === 10 ? '#f0f9eb' : '',
bordercolor: scope.row.status === 20 ? '#fde2e2' : scope.row.status === 10 ? '#e1f3d8' : '',
color: scope.row.status === 20 ? '#f56c6c' : scope.row.status === 10 ? '#67c23a' : ''
}" v-if="scope.row.status">
{{ scope.row.status === 10 ? '运行中' : scope.row.status === 20 ? '关机' : '空闲' }}
</el-button>
</template>
</el-table-column>
<el-table-column align="center" label="系统版本" prop="serial_no" width="150px">
<template #default="scope">
<span>版本1.0</span>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="false" align="center" label="操作">
<template #default="scope">
<div class="cell">
<el-button :type="scope.row.status === 20 ? 'success' : 'danger'" size="small"
@click="handlepoweraction(scope.row)">
{{ scope.row.status === 20 ? '开机' : '关机' }}
</el-button>
<div class="el-dropdown flex flex-wrap items-center">
<el-dropdown>
<el-button type="info">
更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleoperate('restart', scope.row)">重启云机
</el-dropdown-item>
<el-dropdown-item @click="handleoperate('edit', scope.row)">修改名称</el-dropdown-item>
<el-dropdown-item @click="handleoperate('remark', scope.row)">设置备注</el-dropdown-item>
<el-dropdown-item @click="handleoperate('random', scope.row)">随机设备信息</el-dropdown-item>
<el-dropdown-item @click="handleoperate('mirror', scope.row)">切换镜像</el-dropdown-item>
<el-dropdown-item @click="handleoperate('reset', scope.row)">重置云机</el-dropdown-item>
<el-dropdown-item @click="handleoperate('copy', scope.row)">复制云机</el-dropdown-item>
<el-dropdown-item @click="handleoperate('delete', scope.row)">删除云机</el-dropdown-item>
<el-dropdown-item @click="handleoperate('terminal', scope.row)">终端窗口</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="flex flex-wrap items-center">
<el-dropdown>
<el-button type="primary">
选择网络<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleselectvpc">(旧)选择vpc网络</el-dropdown-item>
<el-dropdown-item>(新)选择vpc网络</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
</el-table-column>
</el-table>
<!-- 创建 -->
<el-dialog v-model="createdvisible" title="创建安卓" width="500">
<el-form :model="formcreate">
<el-form-item label="云机数量" :label-width="formlabelwidth">
<el-input-number v-model="formcreate.num" autocomplete="off" :min="1" :max="12" />
</el-form-item>
<el-form-item label="镜像类型" :label-width="formlabelwidth">
<el-radio-group v-model="formcreate.img_type">
<el-radio label="10">基础镜像</el-radio>
<el-radio label="20">gms镜像</el-radio>
</el-radio-group>
<el-button type="primary" size="small" style="margin-left: 10px" @click="handleswitchimage">
<el-icon>
<refresh />
</el-icon> 切换
</el-button>
</el-form-item>
<el-form-item label="dns设置" :label-width="formlabelwidth">
<el-select v-model="formcreate.dns" @change="selectdns" placeholder="请选择dns" class="w130" filterable>
<el-option v-for="item in setdns" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="屏幕刷新率" :label-width="formlabelwidth">
<el-select v-model="formcreate.fps" placeholder="请选择刷新率">
<el-option label="60 fps" value="60" />
<el-option label="90 fps" value="90" />
<el-option label="120 fps" value="120" />
</el-select>
</el-form-item>
<!-- <el-form-item label="vpc网络" :label-width="formlabelwidth">
<el-select v-model="formcreate.vpc" placeholder="请选择vpc">
<el-option label="vpc网络 1" value="vpc1" />
<el-option label="vpc网络 2" value="vpc2" />
<el-option label="vpc网络 3" value="vpc3" />
</el-select>
</el-form-item> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="createdvisible = false">取消</el-button>
<el-button type="primary" @click="createddialog(row)">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 修改 -->
<el-dialog v-model="dialogformvisible" title="修改云机名称" width="500">
<el-form :model="formedit">
<el-form-item label="名称" :label-width="formlabelwidth">
<el-input v-model="formedit.new_name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogformvisible = false">取消</el-button>
<el-button type="primary" @click="editdialog(row)">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 设置备注 -->
<el-dialog v-model="remarkvisible" title="设置云机备注" width="500">
<el-form :model="formremark">
<el-form-item label="云机备注" :label-width="formlabelwidth">
<el-input v-model="formremark.name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="remarkvisible = false">取消</el-button>
<el-button type="primary" @click="remarkvisible = false">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 切换云机镜像 -->
<el-dialog v-model="mirrorvisible" title="切换云机镜像" width="500">
<el-form :model="formmirror">
<el-form-item label="云机镜像" :label-width="formlabelwidth">
<el-select v-model="formmirror.mirror" multiple placeholder="请选择" style="width: 240px">
<el-option v-for="item in mirrorlist" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<p>说明:如何切换的镜像不存在,系统会先拉取镜像,这个过程比较耗时请耐心等待。</p>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="mirrorvisible = false">取消</el-button>
<el-button type="primary" @click="mirrorvisible = false">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 随机设备信息 -->
<el-dialog v-model="randomvisible" title="随机设备信息" width="500"> </el-dialog>
<!-- 复制云机 -->
<el-dialog v-model="copyvisible" title="复制云机" width="500">
<el-form :model="formcopy">
<span>云机复制数量
</span><el-input-number v-model="formcopy.num" :min="1" :max="10" />
</el-form>
<span>说明:复制请先关闭云机。相同实例号的云机,同时只能有一台为开机状态。复制云机比较耗时请耐心等待</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="copyvisible = false">取消</el-button>
<el-button type="primary" @click="handlecopy(row)">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 重置云机 -->
<el-dialog v-model="resetvisible" title="提示" width="500">
<el-icon>
<warningfilled />
</el-icon> <span>确定要重置此云机?</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="resetvisible = false">取消</el-button>
<el-button type="primary" @click="handlereset(row)">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 删除云机 -->
<el-dialog v-model="deletevisible" title="提示" width="500">
<el-icon>
<warningfilled />
</el-icon> <span>确定要删除此云机?</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="deletevisible = false">取消</el-button>
<el-button type="primary" @click="handledele(row)">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 终端窗口 -->
<el-dialog v-model="terminalvisible" title="终端窗口" width="500">
<iframe src="http://192.168.1.100:8080" frameborder="0" width="100%" height="500px"></iframe>
<template #footer> </template>
</el-dialog>
<!-- 选择网络 -->
<el-dialog v-model="networkvisible" title="选择网络" width="500">
<el-form :model="formnetwork">
<el-form-item label="vpc网络" :label-width="formlabelwidth">
<el-select v-model="formnetwork.network" placeholder="请选择">
<el-option label="vpc网络1" value="1" />
<el-option label="vpc网络2" value="2" />
<el-option label="vpc网络3" value="3" />
</el-select>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, nexttick } from 'vue'
import andriodlist from '@/network/andriodlist'
import { elmessage } from 'element-plus'
// 搜索条件
const formdata = reactive({
name: '',
})
const loading = ref(false); // 列表的加载中
const list = ref([{}]) // 列表的数据
const queryandroidlist = async (flag) => {
// 根据搜索条件设置查询参数
if (flag) {
formdata.name = formdata.name.trim()
}
loading.value = true
try {
const res = await andriodlist.getallandroidlist({
name: formdata.name
})
if (res.code === 200) {
let alldatalist = [];
res.data && res.data.length > 0 && res.data.foreach((item, index) => {
item.list_cnt && item.list_cnt.length > 0 && item.list_cnt.foreach((item2, index2) => {
alldatalist.push({
...item,
// ...item2, 看具体需求 处理列表所需字段, 将list_cnt里的数据平铺开
idx: item2.idx,
name: item2.name,
status: item2.status,
data_dir: item2.data_dir,
update_time: item2.update_time,
sdk_port1: item2.sdk_port,
adb_port1: item2.adb_port,
cnt_id1: item2.cnt_id,
})
})
})
list.value = alldatalist;
} else {
list.value = []
}
} catch (error) {
list.value = []
} finally {
loading.value = false
}
}
// 初始化获取列表
queryandroidlist()
const selectids = ref([]);
// 行复选框选中项变化
function handleselectvpc(selection) {
selectids.value = selection.map(item => item.id);
}
function handlequery() {
loading.value = true;
}
// 多选框选中数据
function handleselectionchange(selection) {
selectids.value = selection.map(item => item.id);
}
/**
* 合并行或列
* @param row 行号
* @param col 列号
* @param rowspan 行合并数
* @param colspan 列合并数
* @param rowindex 当前行号
* @param columnindex 当前列号
*
*/
const objectspanmethod = ({
row,
column,
rowindex,
columnindex,
}) => {
if (column.property === 'idx') {
if (rowindex > 0 && list.value[rowindex].idx === list.value[rowindex - 1].idx) {
return {
rowspan: 0,
colspan: 0,
}
}
return {
rowspan: getrowspan('idx', rowindex),
colspan: 1,
}
}
}
// 获取行合并数
const getrowspan = (key, rowindex) => {
let rowspan = 1; //默认合并1行
let curval = list.value[rowindex][key]; //存储了当前值
for (let i = rowindex + 1; i < list.value.length; i++) {
if (list.value[i][key] === curval) {
rowspan++;
} else {
break;
}
}
return rowspan;
}
// 选择dns
const setdns = ref([{
id: '1',
name: 'dns1'
},
{
id: '2',
name: 'dns2'
}])
const selectdns = async () => { }
//创建弹窗
const formcreate = reactive({
idx: '',
num: 1,
img_type: "10",
dns: '',
fps: '',
})
const createdvisible = ref(false)
// 创建实例
function handlecreate(row) {
createdvisible.value = true;
formcreate.idx = row.idx; // 保存idx到formcreate中
}
const handlecreateandroid = async () => {
try {
const res = await andriodlist.createapi({
idx: formcreate.idx, // 使用保存的idx
num: formcreate.num,
img_type: formcreate.img_type,
dns: formcreate.dns,
fps: formcreate.fps,
})
if (res.code === 200) {
elmessage.success(res.msg)
createdvisible.value = false
// 刷新列表
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
//确定创建弹窗
function createddialog() {
handlecreateandroid()
}
// 编辑实例
function handleedit(row) {
console.log('编辑实例:', row);
}
//切换镜像
const handleswitchimage = () => {
console.log('切换镜像');
}
//开机--关机 --status 容器状态 10 运行中 20 关机
let runid = null
async function handlepoweraction(row) {
runid = row.cnt_id1
const status = row.status
try {
let res
if (status === 10) {
res = await andriodlist.stopapi({
cnt_id: runid
})
} else {
res = await andriodlist.runapi({
cnt_id: runid
})
}
if (res.code === 200) {
elmessage.success(res.msg)
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
//重启云机
const restarcnt = async () => {
try {
const res = await andriodlist.restart({
cnt_id: publicid
})
if (res.code === 200) {
elmessage.success(res.msg)
// 重启成功后重新获取列表
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
const formedit = reactive({
cnt_id: '',
new_name: '',
})
const dialogformvisible = ref(false)
const formlabelwidth = '140px'
const handleeditname = async () => {
try {
const res = await andriodlist.renameapi({
cnt_id: formedit.cnt_id,
new_name: formedit.new_name
})
if (res.code === 200) {
elmessage.success(res.msg)
dialogformvisible.value = false
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
//修改弹窗
function editdialog() {
handleeditname()
}
//设置备注
const formremark = reactive({
name: '',
})
const remarkvisible = ref(false)
//切换
const formmirror = reactive({
mirror: '',
})
const mirrorlist = [{
value: '1',
label: '镜像1'
}]
const mirrorvisible = ref(false)
//随机
const randomvisible = ref(false)
//复制
const formcopy = reactive({
num: 1,
src_cnt_id: "",
target_cnt_idx: "",
target_cnt_name: "",
})
const copyvisible = ref(false)
//复制弹窗
function handlecopy() {
handlecopyapi()
}
const handlecopyapi = async () => {
try {
const res = await andriodlist.copyapi({
num: formcopy.num,
src_cnt_id: formcopy.src_cnt_id,
target_cnt_idx: formcopy.target_cnt_idx,
target_cnt_name: formcopy.target_cnt_name,
})
if (res.code === 200) {
elmessage.success(res.msg)
copyvisible.value = false
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
//重置
const resetvisible = ref(false)
const handleresetid = async () => {
try {
const res = await andriodlist.resetapi({
cnt_id: publicid
})
if (res.code === 200) {
elmessage.success(res.msg)
deletevisible.value = false
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
//重置弹窗
function handlereset() {
handleresetid()
}
//删除
const deletevisible = ref(false)
const handledeleid = async () => {
try {
const res = await andriodlist.deletecntapi({
cnt_id: publicid
})
if (res.code === 200) {
elmessage.success(res.msg)
deletevisible.value = false
queryandroidlist()
} else {
elmessage.error(res.msg)
}
} catch (error) {
elmessage.error(res.msg)
}
}
//删除
function handledele() {
handledeleid()
}
//终端
const terminalvisible = ref(false)
//选择网络
const formnetwork = reactive({
network: '',
})
const networkvisible = ref(false) //选择网络弹窗
//操作
let publicid = null // 公共id
function handleoperate(type, row) {
publicid = row.cnt_id1 // 获取第一个云机的cnt_id
switch (type) {
case 'restart':
restarcnt(row)
break;
case 'edit':
dialogformvisible.value = true;
formedit.cnt_id = publicid;
formedit.new_name = row.name;
break;
case 'remark':
remarkvisible.value = true;
break;
case 'random':
randomvisible.value = true;
break;
case 'mirror':
mirrorvisible.value = true;
break;
case 'copy':
copyvisible.value = true;
formcopy.src_cnt_id = publicid;
formcopy.target_cnt_name = row.name;
formcopy.target_cnt_idx = row.idx;
break;
case 'terminal':
terminalvisible.value = true;
break;
case 'reset':
// 处理重置操作
resetvisible.value = true;
break;
case 'delete':
// 处理删除操作
deletevisible.value = true;
break;
default:
break;
}
}
</script>
<style lang="scss" scoped>
.app-container {
.search-bar {
.el-icon {
color: #fff;
}
}
.cell {
display: flex;
justify-content: center;
align-items: center;
}
.el-dropdown {
margin-left: 10px;
}
.examples {
display: flex;
justify-content: center;
}
.time-line {
margin-top: 10px;
color: rgb(235, 0, 0);
font-size: 14px;
}
}
</style>代码中会有一些注释,根据个人需求可以进行参考,此需求也涉及到按钮操作的,如果没有次需求可以忽略不看。
以上就是列表的合并单元格,如果对你有帮助,麻烦点个赞呗~
到此这篇关于vue3 + elementplus动态合并数据相同的单元格的文章就介绍到这了,更多相关vue3 elementplus动态合并单元格内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论