最近在编写前端界面,硬是一人一周时间加班加点写完了一个项目的前端界面(一级菜单有12个页面+一个控制台大屏,二三级界面有N个),之前预估前端界面的编写需要一个月,我是自己把自己卷死了(没有办法,项目经理说项目要1周写界面,2周发版,我这个项目前端只我1个人,后端有3个人...).......下面要说的是某一个界面中的一个小模块。

  产品原型

需求说明:

【站点配置】

1、站点配置,需要线路属性配置完后才可以配置,编辑的时候做校验,线路属性没有配置完,不可以编辑,禁用状态。

2、上下行:需要选择配置上行还是下行的站点,单选。是否有上下行,需要根据线路属性来判断。

2、统计:统计上行或下行站点数量,大站数量

3、站点配置:

3.1、场站:根据线路属性配置的上下行场站,如果为上行,上行场站在前下行场站在后,在图中显示场站名称。反之,下行场站在前上行场站在后。

3.2、场站显示:上行场站,标记“首”,无“--”里程信息。下行场站,标记“末 ”,无“--”里程信息。反之,下行场站,标记“首”,无“--”里程信息。上行场站,标记“末 ”,无“--”里程信息

3.3、场站操作:“首”场站选中后,左侧显示“+”,可以添加第一个站点。“末”场站选中后,由侧显示“+”,可以添加倒数第一个站点。

3.4、站点显示:显示序号、站间距、时长。站间距根据地图计算,时长需要输入,3位整数,单位分钟。

3.5、站点移动:鼠标选中,移动鼠标可以拖动排序。

3.6、站点操作:鼠标选中后,可以往前或往后添加站点,也可以删除站点,并自动排序。

3.7、添加站点:添加空白站点,点击可以添加站点,按照站点名字搜索和选择,数据来源站点管理。

3.8、时长修改:不需要选中站点,可以直接修改时长。

3.9、大站设置:鼠标选中后,选中左上角图标,设置为大站点

3.10、 站点修改:不做站点的编辑操作,只可以删除在添加。

3.11、如果有添加操作,但未添加站点信息。不可以保存。提示“请添加站点信息或删除空白站点”

  我做出来的界面效果

  代码实现

  分析:要能拖拽,要能动态编辑。拖拽,用第三方插件vuedraggable,布局用flex。注意安装的是vue3版本,yarn add vuedraggable@next,一开始有考虑过vue-grid-layout,后面感觉grid布局操作会更复杂,所以果断放弃。

  前端项目技术栈:vue3+ts+vite3.x+element plus+wujie微前端。

  Dom代码结构:

<template>
<div class="site-set">
<div class="operator-box">
<div class="tag-box">
<el-tag
v-for="tag in appTags"
:key="tag.name"
size="large"
:closable="false"
round
:type="getTagType(tag)"
@click="onClickTag(tag)"
class="cursor-pointer"
>
{{ tag.name }}
</el-tag>
</div>
<div class="tools-box">
<xdEditCancleBtn v-model="isEditSiteInfo" @onOK="onEditInfo"></xdEditCancleBtn>
<el-link type="primary" :underline="false" @click="onLineView()">
<i class="iconfont icon-xianlu"></i>线路预览
</el-link>
</div>
</div>
<div class="total-info-box">
<span class="total-num">站点总数:{{ totalSiteNums }}站</span>
<span>大站数量:{{ bigSiteNums }}个</span>
</div>
<div class="site-list-box xd-scrollBar">
<draggable
class="wrapper-site"
v-model="siteList"
@start="drag = true"
@end="drag = false"
item-key="index"
filter=".fixed"
>
<template #item="{ element, index }">
<div
class="site-item"
:class="{ actived: activedIndex == index, fixed: getFixedClass(index) }"
@click="onClickSiteItem(index)"
>
<span class="text">
<template v-if="isStartSite(index)"> 首 </template>
<template v-else-if="isEndSite(index)"> 末 </template>
<template v-else>
<input class="time" />
</template>
</span>
<span class="distance">
<template v-if="isStartOrEndSite(index)"> - </template>
</span>
<span class="content">
<el-popover
v-if="element.id == -1 && activedIndex == index"
placement="bottom"
:width="196"
trigger="click"
:visible="visible"
>
<template #reference>
<span class="site-name" @click="visible = !visible"
>{{ element.name }}
</span>
</template>
<el-select
placeholder="请输入站点信息"
class="select-block-mini"
popper-class="popper-block"
filterable
@change="onChangeSite"
>
<el-option
v-for="item in allSiteList"
:key="item.code"
:label="item.name"
:value="item.code"
:disabled="item.disabled"
>
<div class="select-item">
<div class="row">
<span class="name">
{{ item.name }}
</span>
<span class="line-name">{{ item.lineName }}</span>
</div>
<div class="row">
<span class="code">{{ item.code }}</span>
</div>
</div>
</el-option>
</el-select>
</el-popover>
<span v-else class="site-name">{{ element.name }} </span>
<span class="site-index">{{ index + 1 }}</span>
<span
v-if="element.isBigSite && activedIndex != index"
class="triangle-block"
></span>
<span
@click="onChangeBigSite(index)"
title="切换大小站"
v-if="element.id != -1 && activedIndex == index"
class="triangle-block edit"
></span>
</span>
<span
v-if="!isStartOrEndSite(index)"
class="del-btn"
@click="delSiteItem(index)"
title="移除"
>
<i class="iconfont icon-shanchu"> </i>
</span>
<span
@click="onLeftAddSite(index)"
class="plus left"
v-if="!isStartSite(index)"
title="左边添加"
>
<i class="iconfont icon-jia"></i>
</span>
<span
@click="onRightAddSite(index)"
class="plus right"
v-if="!isEndSite(index)"
title="右边添加"
>
<i class="iconfont icon-jia"></i>
</span>
</div>
</template>
</draggable>
</div>
</div>
</template>

ts代码:

<script setup lang="ts">
import draggable from "vuedraggable";
const siteList = ref<any>([
{ name: "火车站广场", id: 0, isBigSite: false }, //首站
]);
for (let i = 0; i < 30; i++) {
siteList.value.push({ name: "站点" + i, id: i + 1, isBigSite: false });
}
siteList.value.push({ name: "汽车西站", id: siteList.length + 1, isBigSite: false }); //末站
const appTags = [
{ name: "上行", id: 1 },
{ name: "下行", id: 2 },
];
const state = reactive({
drag: false,
activedIndex: -1, //当前激活项索引
activedApp: "",
totalSiteNums: 0, //总站数
bigSiteNums: 0, //大站数量
});
//获取tab类型样式
const getTagType = (tag: any) => {
return tag.id == state.activedApp ? "" : "info";
};
//点击标签
const onClickTag = (tag: any) => {
state.activedApp = tag.id;
};
const isEditSiteInfo = ref(false); //是否是编辑
//编辑信息
const onEditInfo = () => {
isEditSiteInfo.value = true;
};
//线路预览
const onLineView = () => {};
//获取固定样式
const getFixedClass = (index: number) => {
return index == 0 || index == siteList.value.length - 1;
};
//点击站点项
const onClickSiteItem = (index: number) => {
console.log("onClickSiteItem");
state.activedIndex = index;
};
//是否是首站
const isStartSite = (index: number) => {
return index == 0;
};
//是否是末站
const isEndSite = (index: number) => {
return index == siteList.value.length - 1;
};
//是否是首末站
const isStartOrEndSite = (index: number) => {
return isStartSite(index) || isEndSite(index);
};
const addSiteItem = {
name: "填写站点名称",
id: -1,
isBigSite: false,
};
//左边添加站点
const onLeftAddSite = (index: number) => {
siteList.value.splice(index, 0, addSiteItem);
};
//右边添加站点
const onRightAddSite = (index: number) => {
siteList.value.splice(index + 1, 0, addSiteItem);
};
//删除站点项
const delSiteItem = (index: number) => {
siteList.value.splice(index, 1);
};
const visible = ref(false);
const allSiteList = ref<any>([]);
for (let i = 0; i < 20; i++) {
let item: any = { name: "张三" + i, code: "00c" + i, lineName: i + 1 + "路" };
// let disabled = driverTags.value.some((s: any) => s.code == item.code);
// item.disabled = disabled;
allSiteList.value.push(item);
}
//选择站点
const onChangeSite = (val: any) => {
const item = allSiteList.value.find((f: any) => f.code == val);
siteList.value.splice(activedIndex.value, 0, item);
};
//切换大站小站
const onChangeBigSite = (index: number) => {
siteList.value[index].isBigSite = !siteList.value[index].isBigSite;
};
const { totalSiteNums, bigSiteNums, drag, activedIndex } = toRefs(state);
</script>

  css代码较多,已独立css文件,css代码如下:

.operator-box {
display: flex;
width: 100%;
justify-content: space-between;
height: 36px;
align-items: center;
.tag-box {
.el-tag {
margin-right: 15px;
padding: 0px 15px;
}
}
.tools-box {
display: flex;
align-items: center;
.iconfont {
padding-right: 4px;
}
}
}
.total-info-box {
width: 100%;
display: flex;
align-items: center;
height: 44px;
color: var(--sub-title-color);
.total-num {
padding-right: 10px;
}
}
.site-list-box {
height: 488px; //2行的高度
overflow-y: auto;
}
.wrapper-site {
display: flex;
width: 100%;
flex-wrap: wrap;
gap: 20px;
margin-bottom: -4px;
}
.site-item {
width: 45px;
height: 220px;
color: var(--sub-title-color);
display: flex;
flex-direction: column; //220 - 160 -22
align-items: center;
position: relative;
margin-bottom: 4px;
.text {
width: 45px;
height: 22px;
display: flex;
justify-content: center;
align-items: center;
.time {
width: 45px;
height: 22px;
border: 1px solid #cbcdd3;
border-radius: 2px;
}
}
&.fixed {
.text {
background-color: var(--hover-font-color);
color: #f7f9f7;
font-size: 12px;
}
}
.distance {
height: 38px;
color: #4f5a68;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
}
.content {
height: 160px;
background-color: #f7f9f7;
writing-mode: vertical-rl;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
position: relative;
.site-name {
writing-mode: vertical-rl;
text-orientation: upright; /* 数字垂直展示 */
// position:absolute;left:50%;top:50%;--webkit-transform:translate(-50%,-50%);
}
.site-index {
width: 20px;
height: 20px;
border-radius: 2px;
background: #e4e6ec;
color: var(--sub-title-color);
writing-mode: horizontal-tb;
text-align: center;
line-height: 20px;
}
$bw: 10px;
.triangle-block {
display: inline-block;
height: 0;
width: 0;
border-top: $bw solid #faad14;
border-bottom: $bw solid transparent;
border-left: $bw solid transparent;
border-right: $bw solid transparent;
transform: rotate(135deg);
transform-origin: top;
position: absolute;
top: 6px;
left: -4px;
&.edit {
border-top: $bw solid #004eff;
}
}
}
.del-btn {
display: flex;
justify-content: center;
height: 24px;
align-items: center;
width: 24px;
position: absolute;
bottom: -24px;
left: calc(50% - 12px);
&:hover {
.iconfont {
color: red;
}
}
}
//左右加号
.plus {
display: none;
position: absolute;
z-index: 9;
top: calc(50% + 16px);
width: 16px;
height: 16px;
justify-content: center;
align-items: center;
background-color: var(--main-btn-color);
.iconfont {
font-size: 14px;
color: white;
}
&.left {
left: -16px;
}
&.right {
right: -16px;
}
}
.iconfont {
font-size: 14px;
display: none;
&.icon-shanchu {
color: #f53f3f;
}
}
&.actived {
.content {
border: 1px solid var(--main-btn-color);
color: var(--main-btn-color);
background: #f7faff;
}
.iconfont,
.plus {
display: flex;
}
}
}

  vue组件中引入css代码:

<style lang="scss" scoped>
@import "./scss/siteSet.scss";
</style>
<style lang="scss">
.el-select {
&.select-block-mini {
width: 171.27px;
}
}
</style>

  其实我花了1.5h,主要是按UI的稿子调样式,因为很赶,所以代码很糙,我早就有了后面重构的觉悟,那么短的时间内,几乎不可能想得很周到,我只能想办法在最短的时间内实现需求。里面其实有部分功能没有实现,因为后端一期不现实。最近写代码真是写得手都快抽搐了,即便是各种复制粘贴也累啊,界面实在太多了.....

vue3项目,记录我是如何用1h实现产品预估1天工作量的界面需求的更多相关文章

  1. Vue(1):用Vue-cli构建Vue3项目

    使用Vue-cli构建Vue3项目 1.检查node版本 node -v 以上node版本位14.15.0满足Vue3项目的创建条件(Vu3需要node 版本8以上) 为什么需要安装node? vue ...

  2. 从零搭建基于webpack的Electron-Vue3项目(1)——基于webpack的Vue3项目搭建

    从零搭建基于webpack的Electron-Vue3项目(1)--基于webpack的Vue3项目搭建 前言 本篇文章内容,主要是基于webpack的Vue3项目开发环境进行搭建,暂时还不涉及到El ...

  3. Vue3项目的简单搭建与项目结构的简单介绍

    Vue3项目的创建与运行 本文记录下自己近期学习的Vue3项目的创建,以及如何去运行一个Vue应用,同时包括对Vue项目结构进行一个简单的介绍. 一.node与npm的安装 通常平常进行开发的同学应该 ...

  4. IOS客户端Coding项目记录导航

    IOS客户端Coding项目记录(一) a:UITextField设置出现清除按键 b:绘画一条下划线  表格一些设置 c:可以定义表头跟底部视图(代码接上面) d:隐藏本页的导航栏 e:UIEdge ...

  5. Unity Project Wizard (最近打开的项目记录)

    最近打开工程列表 当用Unity打开过的项目越来越多之后,在最近打开项目记录框中就会变的很长,那么如何才能删除最近打开的记录呢? Unity4.x最近打开的工程记录 Unity5.x最近打开的工程记录 ...

  6. jenkins构建项目记录2(tag)

    与jenkins构建项目记录1不同的是通过tag拉去对应版本代码 1.先安装创建(git parameter) 2.general设置 name可任意命名,下面源码管理设置时变量会引用到. 3.源码管 ...

  7. Vue3项目搭建规范

    Vue3项目搭建规范 一. 代码规范 1.1 集成editorconfig配置 EditorConfig有助于为不同IDE编辑器上维护一致的编码风格 安装插件:EditorConfig for VS ...

  8. 【Parcel 2 + Vue 3】从0到1搭建一款极快,零配置的Vue3项目构建工具

    前言 一周时间,没见了,大家有没有想我啊!哈哈!我知道肯定会有的.言归正传,我们切入正题.上一篇文章中我主要介绍了使用Vite2+Vue3+Ts如何更快的入手项目.那么,今天我将会带领大家认识一个新的 ...

  9. Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性

    简介 Tengine是由淘宝网发起的Web服务器项目.它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性.Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很 ...

  10. <p>在我们的实际软件项目中,管理团队事实上比写代码或者实现一个客户的需求更为的有挑战性。由于编程实际上是和机器打交道,而和机器打交道,仅仅要你符合机器预定的逻辑,</p>

    在我们的实际软件项目中,管理团队事实上比写代码或者实现一个客户的需求更为的有挑战性. 由于编程实际上是和机器打交道.而和机器打交道,仅仅要你符合机器预定的逻辑, 一步步迈向解决这个问题的道路上一点都不 ...

随机推荐

  1. JS 模块化- 05 ES Module & 4 大规范总结

    1 ES Module 规范 ES Module 是目前使用较多的模块化规范,在 Vue.React 中大量使用,大家应该非常熟悉.TypeScript 中的模块化与 ES 类似. 1.1 导出模块 ...

  2. PHP全栈开发(六):PHP与HTML页面交互

    之前我们在HTML表单学习这篇文章里面创建了一个HTML页面下的表单. 这个表单是用户用来输入数据的 具体代码如下 <!DOCTYPE html> <html> <hea ...

  3. POJ1639 Picnic Planning (限制入度最小生成树)

    节点1是有度数限制的,把节点1去掉,就会形成若干个连通块,在每个连通块内部求最小生成树(prim算法实现),并求出每个连通块与1相连的最短的边,这样形成了初始状态的生成树. 假设(1,x)这条边没在生 ...

  4. Hive之安装

    Hive安装 1. 依赖hadoop(另有hadoop安装文档) 执行程序运行在yarn上面,需要启动start-yarn.sh 2. 先安装MySQL MySQL安装详见数据库MySQL之安装:ht ...

  5. 【python】Ubuntu中多条命令的运行

    在模型训练时,往往需要消融实验,通常都是在一个程序运行结束后才再次手动运行下一个,不及时在电脑前可能无端浪费时间,因此需要让程序自动一个接一个去执行.受此启发,特了解了Ubuntu中多命令运行的内容. ...

  6. 【linux】 第1回 linux运维基础

    目录 1. 运维的本质 2. 电脑与服务器 2.1 电脑的种类 2.2 服务器种类 2.3 服务器品牌 2.4 服务器尺寸 2.5 服务器内部组成 3. 磁盘阵列 4. 系统简介 5. 虚拟化 6. ...

  7. 有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。

    第一题 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数 ...

  8. 齐博X1-栏目的调用2

    fun('sort@fathers',$fid,'cms')  获取上层多级栏目这样的,比如我们现在所属第三级栏目,现在可以利用这个函数获取第二级和第一级的栏目,当然自身也会被调用出来,所以此函数用的 ...

  9. offset新探索:双管齐下,加速大数据量查询

    摘要:随着offset的增加,查询的时长也会越来越长.当offset达到百万级别的时候查询时长通常是业务所不能容忍的. 本文分享自华为云社区<offset新探索:双管齐下,加速大数据量查询> ...

  10. 十七、Job与Cronjob

    Job 与 Cronjob 一.Job ​Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束. 特殊说明: 1.spec.template 格式同 Pod ​2 ...