前端vue2+elementui帮我生成一个前端页面,一下需求1.2.3.4.5.6部分已经完成完成的代码在下边,仔细分析现有代码,优化一下第三部分的部件和接口选项的宽度问题,完善7.8部分的需求,尤其是接口选项多选和主体中间部分的跨容器拖拽功能实现
1.竖向分为头部和主体部分,头部宽度100%,高度60px,头部左侧两个input框,lable分别为:用例名称,脚本编号,右侧3个按钮是生成用例,生成脚本,调试
2.主体部分占据剩下高度,分为左,中,右三部分,宽度占比分别为40%,40%,20%,
3.主体左侧为左右两部分,分别为部件和接口选项,两个均为可以手动打开关闭的抽屉(使用elementui中的el-drawer),重要一点:我需要的是将部件用el-drawer包裹和接口选项整体用el-drawer包裹,有一个箭头的按钮在整体的右侧部分控制el-drawer的开关,部件宽度为35%,接口选项宽度65%,两个抽屉没有层级关系,独立开启关闭,不会相互影响,无论开启或关闭组件在左,接口选项在右
4.部件部分:竖向一个搜索框,输入关键字可对下面具体组件进行筛选,搜索框下是组件名称和图标,组件名称分别为:车门,车窗,座椅,前舱盖,雨刮器,光机幕布,后视镜这部分可使用
列表渲染,每个组件展示为一行,左侧组件图标,右侧组件名称,,一行一列,依次向下
5.接口选项部分,竖向一个搜索框,输入关键字可对下面具体接口进行筛选,接口选项和左侧部件是有联动的,两者存在跨容器拖拽关系,拖动左侧部件中的一个(每次只能拖动一个),到右侧接口选项区域,会触发事件,调取后端接口获取对应组件的接口列表(这里可以使用模拟数据),展示在接口选项部分,
接口内容为:打开所有车窗。open_all_wins,控制车辆上电。power_on_off_control...,(可以放点模拟数据)
6.拖动组件到接口选项部分,每一个组件对应的接口展示都是一个独立的二级树形结构,树形结构一级lable为组件名称,右侧有一个可以X,可以删除当前组件,树形结构二级内容就为每个具体的接口,每个接口头部都有一个多选按钮,可以展示多个,依次竖向向下展示
7.主体中间部分竖向依次为3部分,预置条件,测试步骤,环境恢复,宽度均占据主体中间部分宽度100%,高度分别为30%,40%,20%,左侧接口选项部分通过多选框勾选接口,勾选的接口组成一个list,通过拖拽将接口选项中的接口拖拽到右侧主体中间部分展示,支多选批量拖拽
每个区域可视为独立区域,相互不影响,若一个区域拖入多个接口,根据先后依次排序 ,拖拽至中间主体部分的接口,在各自独立区域内,接口排序可以通过上下拖拽调换,
8.主体右侧为参数配置:由上至下第一行两个单选按钮,Machine,Relay,剩下为3个输入框,各占一行,lable分别为:机械控制踏板踩下去和抬起来之间的时间间隔(秒),踏板踩下去和抬起来的时间间隔(秒),踏板踩下去或抬起来需要继电器打开的时间(秒)
<template>
<div id="app">
<!-- 头部区域 -->
<div class="header">
<div class="header-left">
<el-input
placeholder="请输入用例名称"
class="header-input"
v-model="caseName"
>
<template slot="prepend">用例名称</template>
</el-input>
<el-input
placeholder="请输入脚本编号"
class="header-input"
v-model="scriptId"
>
<template slot="prepend">脚本编号</template>
</el-input>
</div>
<div class="header-right">
<el-button type="primary" class="header-button">生成用例</el-button>
<el-button type="success" class="header-button">生成脚本</el-button>
<el-button type="warning" class="header-button">调试</el-button>
</div>
</div>
<!-- 主体区域 -->
<div class="main-container">
<!-- 左侧区域 -->
<div class="left-section">
<!-- 部件抽屉 -->
<div class="drawer-container">
<div class="drawer-wrapper" :style="{width: componentsDrawerOpen ? '280px' : '0'}">
<div class="components-drawer" v-show="componentsDrawerOpen">
<div class="components-container">
<div class="components-header">
<i class="el-icon-s-grid"></i> 车辆部件列表
</div>
<el-input
placeholder="搜索部件..."
v-model="componentSearch"
prefix-icon="el-icon-search"
clearable
></el-input>
<div style="margin-top: 15px; overflow-y: auto; height: calc(100% - 100px);">
<div
class="component-item"
v-for="component in filteredComponents"
:key="component.id"
draggable="true"
@dragstart="dragStart(component)"
>
<div class="icon">
<i :class="component.icon"></i>
</div>
<div class="name">{{ component.name }}</div>
</div>
</div>
</div>
</div>
</div>
<div
class="drawer-toggle"
:class="{collapsed: !componentsDrawerOpen}"
@click="componentsDrawerOpen = !componentsDrawerOpen"
>
<i class="el-icon-arrow-left"></i>
</div>
</div>
<!-- 接口抽屉 -->
<div class="drawer-container">
<div class="drawer-wrapper" :style="{width: interfacesDrawerOpen ? '500px' : '0'}">
<div class="interfaces-drawer" v-show="interfacesDrawerOpen">
<div class="interfaces-container">
<div class="interfaces-header">
<i class="el-icon-connection"></i> 接口选项
</div>
<el-input
placeholder="搜索接口..."
v-model="interfaceSearch"
prefix-icon="el-icon-search"
clearable
></el-input>
<div class="tree-container"
@dragover.prevent
@drop="dropComponent"
>
<div v-if="interfaceGroups.length === 0" class="drop-area">
<i class="el-icon-upload"></i>
<div class="drop-hint">请从左侧拖动部件到此处以加载接口</div>
</div>
<div v-else>
<transition-group name="fade">
<div
class="tree-node"
v-for="group in filteredInterfaceGroups"
:key="group.id"
>
<div class="tree-header">
<div>{{ group.componentName }} 接口</div>
<div class="delete-btn" @click="removeGroup(group.id)">
<i class="el-icon-close"></i>
</div>
</div>
<div class="tree-content">
<div
class="interface-item"
v-for="item in group.interfaces"
:key="item.id"
>
<el-checkbox v-model="item.selected"></el-checkbox>
<div class="interface-name">{{ item.name }}</div>
<div class="interface-id">{{ item.id }}</div>
</div>
</div>
</div>
</transition-group>
</div>
</div>
</div>
</div>
</div>
<div
class="drawer-toggle"
:class="{collapsed: !interfacesDrawerOpen}"
@click="interfacesDrawerOpen = !interfacesDrawerOpen"
>
<i class="el-icon-arrow-left"></i>
</div>
</div>
</div>
<!-- 中间区域 -->
<div class="center-section">
<div class="center-placeholder">
<i class="el-icon-s-marketing"></i>
<div>用例执行区域</div>
<div style="font-size: 14px; margin-top: 10px;">此处将展示用例执行流程和结果</div>
</div>
</div>
<!-- 右侧区域 -->
<div class="right-section">
<div class="right-placeholder">
<i class="el-icon-setting"></i>
<div>参数配置区域</div>
<div style="font-size: 14px; margin-top: 10px;">此处将展示脚本参数配置选项</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import { } from "@/api/api.js";
import draggable from "vuedraggable";
import Papa from "papaparse";
import * as echarts from "echarts";
import { Message } from "element-ui";
export default {
components: { draggable },
data() {
return {
caseName: '',
scriptId: '',
componentsDrawerOpen: true,
interfacesDrawerOpen: true,
componentSearch: '',
interfaceSearch: '',
draggedComponent: null,
// 部件数据
components: [
{ id: 1, name: '车门', icon: 'el-icon-s-help' },
{ id: 2, name: '车窗', icon: 'el-icon-s-promotion' },
{ id: 3, name: '座椅', icon: 'el-icon-s-opportunity' },
{ id: 4, name: '前舱盖', icon: 'el-icon-s-flag' },
{ id: 5, name: '雨刮器', icon: 'el-icon-s-release' },
{ id: 6, name: '光机幕布', icon: 'el-icon-s-platform' },
{ id: 7, name: '后视镜', icon: 'el-icon-s-claim' }
],
// 接口分组数据
interfaceGroups: [],
nextGroupId: 1,
// 模拟接口数据
mockInterfaces: {
1: [ // 车门接口
{ id: 'door_lock', name: '车门锁定', selected: false },
{ id: 'door_unlock', name: '车门解锁', selected: false },
{ id: 'door_open', name: '打开车门', selected: false },
{ id: 'door_close', name: '关闭车门', selected: false }
],
2: [ // 车窗接口
{ id: 'window_open_all', name: '打开所有车窗', selected: false },
{ id: 'window_close_all', name: '关闭所有车窗', selected: false },
{ id: 'window_open_driver', name: '打开驾驶员车窗', selected: false },
{ id: 'window_open_passenger', name: '打开乘客车窗', selected: false }
],
3: [ // 座椅接口
{ id: 'seat_heat', name: '座椅加热', selected: false },
{ id: 'seat_cool', name: '座椅通风', selected: false },
{ id: 'seat_adjust', name: '座椅调节', selected: false },
{ id: 'seat_massage', name: '座椅按摩', selected: false }
],
4: [ // 前舱盖接口
{ id: 'hood_open', name: '打开前舱盖', selected: false },
{ id: 'hood_close', name: '关闭前舱盖', selected: false }
],
5: [ // 雨刮器接口
{ id: 'wiper_on', name: '开启雨刮器', selected: false },
{ id: 'wiper_off', name: '关闭雨刮器', selected: false },
{ id: 'wiper_speed', name: '调节雨刮速度', selected: false }
],
6: [ // 光机幕布接口
{ id: 'screen_up', name: '升起幕布', selected: false },
{ id: 'screen_down', name: '降下幕布', selected: false },
{ id: 'screen_brightness', name: '调节亮度', selected: false }
],
7: [ // 后视镜接口
{ id: 'mirror_adjust', name: '调节后视镜', selected: false },
{ id: 'mirror_fold', name: '折叠后视镜', selected: false },
{ id: 'mirror_heat', name: '后视镜加热', selected: false }
]
}
};
},
mounted() {
},
computed: {
// 过滤后的部件列表
filteredComponents() {
if (!this.componentSearch) return this.components;
return this.components.filter(comp =>
comp.name.toLowerCase().includes(this.componentSearch.toLowerCase())
);
},
// 过滤后的接口分组
filteredInterfaceGroups() {
if (!this.interfaceSearch) return this.interfaceGroups;
return this.interfaceGroups.map(group => {
const filteredInterfaces = group.interfaces.filter(item =>
item.name.toLowerCase().includes(this.interfaceSearch.toLowerCase()) ||
item.id.toLowerCase().includes(this.interfaceSearch.toLowerCase())
);
// 只返回包含匹配接口的分组
if (filteredInterfaces.length > 0) {
return { ...group, interfaces: filteredInterfaces };
}
return null;
}).filter(group => group !== null);
}
},
methods: {
// 开始拖拽
dragStart(component) {
this.draggedComponent = component;
},
// 放置组件
dropComponent() {
if (!this.draggedComponent) return;
console.log( '11',JSON.stringify(this.draggedComponent.id));
// 检查是否已存在该组件的接口组
const exists = this.interfaceGroups.some(group =>
group.componentId === this.draggedComponent.id
);
if (exists) {
this.$message.warning(`已加载过${this.draggedComponent.name}的接口`);
return;
}
// 模拟API请求获取接口数据
this.$message.success(`正在加载${this.draggedComponent.name}接口...`);
// 模拟API延迟
setTimeout(() => {
const interfaces = [...this.mockInterfaces[this.draggedComponent.id]];
this.interfaceGroups.push({
id: this.nextGroupId++,
componentId: this.draggedComponent.id,
componentName: this.draggedComponent.name,
interfaces: interfaces
});
this.$message.success(`成功加载${this.draggedComponent.name}接口`);
}, 500);
//this.draggedComponent = null;
},
// 删除接口组
removeGroup(groupId) {
this.interfaceGroups = this.interfaceGroups.filter(group => group.id !== groupId);
this.$message.success('接口组已移除');
}
},
watch: {
},
beforeDestroy() {
},
async created() { },
};
</script>
<style scoped lang="less">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Microsoft YaHei', sans-serif;
background-color: #f5f7fa;
overflow: hidden;
}
#app {
height: 100%;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
height: 60px;
background: linear-gradient(135deg, #1e3c72, #2a5298);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
padding: 0 20px;
color: white;
z-index: 1000;
}
.header-left {
display: flex;
align-items: center;
flex: 1;
}
.header-input {
width: 280px;
margin-right: 20px;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 4px;
border: none;
}
.header-input .el-input__inner {
background-color: transparent;
color: white;
border: none;
}
.header-input .el-input__inner::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.header-right {
display: flex;
}
.header-button {
margin-left: 15px;
background: linear-gradient(to right, #4facfe, #00f2fe);
border: none;
color: white;
font-weight: bold;
transition: all 0.3s;
}
.header-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
/* 主体样式 */
.main-container {
flex: 1;
display: flex;
overflow: hidden;
background-color: #f0f2f5;
}
/* 左侧区域 */
.left-section {
flex: 0 0 40%;
display: flex;
position: relative;
background-color: #fff;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
z-index: 1;
}
/* 中间区域 */
.center-section {
flex: 0 0 40%;
background-color: #fff;
margin: 10px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
font-size: 18px;
position: relative;
overflow: hidden;
}
.center-placeholder {
text-align: center;
}
.center-placeholder i {
font-size: 48px;
margin-bottom: 20px;
color: #c0c4cc;
}
/* 右侧区域 */
.right-section {
flex: 0 0 20%;
background-color: #fff;
margin: 10px 10px 10px 0;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
font-size: 18px;
}
.right-placeholder {
text-align: center;
}
.right-placeholder i {
font-size: 48px;
margin-bottom: 20px;
color: #c0c4cc;
}
/* 抽屉样式 */
.drawer-container {
height: 100%;
display: flex;
position: relative;
}
.drawer-wrapper {
height: 100%;
overflow: hidden;
transition: all 0.3s;
}
.components-drawer {
width: 280px;
height: 100%;
border-right: 1px solid #ebeef5;
background-color: #fff;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
}
.interfaces-drawer {
width: 500px;
height: 100%;
background-color: #fff;
border-right: 1px solid #ebeef5;
}
.drawer-toggle {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: -15px;
width: 30px;
height: 60px;
background-color: #1e3c72;
color: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 15px 15px 0;
cursor: pointer;
z-index: 10;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.drawer-toggle:hover {
background-color: #2a5298;
}
.drawer-toggle i {
transition: transform 0.3s;
}
.drawer-toggle.collapsed i {
transform: rotate(180deg);
}
/* 部件区域样式 */
.components-container {
height: 100%;
display: flex;
flex-direction: column;
padding: 15px;
}
.components-header {
margin-bottom: 15px;
font-weight: bold;
color: #1e3c72;
border-bottom: 1px solid #ebeef5;
padding-bottom: 10px;
font-size: 16px;
}
.component-item {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 8px;
border-radius: 4px;
cursor: move;
transition: all 0.2s;
border: 1px solid #ebeef5;
background-color: #f9fafc;
}
.component-item:hover {
background-color: #ecf5ff;
border-color: #c6e2ff;
transform: translateX(5px);
}
.component-item .icon {
width: 24px;
height: 24px;
margin-right: 10px;
background-color: #1e3c72;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
}
.component-item .name {
font-size: 14px;
color: #606266;
}
/* 接口选项区域 */
.interfaces-container {
height: 100%;
display: flex;
flex-direction: column;
padding: 15px;
}
.interfaces-header {
margin-bottom: 15px;
font-weight: bold;
color: #1e3c72;
border-bottom: 1px solid #ebeef5;
padding-bottom: 10px;
font-size: 16px;
}
.tree-container {
flex: 1;
overflow-y: auto;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #f9fafc;
}
.tree-node {
margin-bottom: 10px;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.tree-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background: linear-gradient(to right, #4facfe, #00f2fe);
color: white;
font-weight: bold;
}
.delete-btn {
cursor: pointer;
transition: all 0.2s;
}
.delete-btn:hover {
transform: scale(1.2);
}
.tree-content {
padding: 10px;
background-color: white;
}
.interface-item {
display: flex;
align-items: center;
padding: 8px 10px;
border-bottom: 1px dashed #ebeef5;
}
.interface-item:last-child {
border-bottom: none;
}
.interface-name {
margin-left: 8px;
font-size: 14px;
color: #606266;
}
.interface-id {
margin-left: 8px;
font-size: 12px;
color: #909399;
font-family: monospace;
}
/* 拖拽区域 */
.drop-area {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 16px;
flex-direction: column;
}
.drop-area i {
font-size: 48px;
margin-bottom: 15px;
color: #c0c4cc;
}
.drop-hint {
text-align: center;
max-width: 300px;
}
/* 动画效果 */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
最新发布