4

vue3项目,记录我是如何用1h实现产品预估1天工作量的界面需求 - 邹琼俊

 1 year ago
source link: https://www.cnblogs.com/jiekzou/p/16989037.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

  最近在编写前端界面,硬是一人一周时间加班加点写完了一个项目的前端界面(一级菜单有12个页面+一个控制台大屏,二三级界面有N个),之前预估前端界面的编写需要一个月,我是自己把自己卷死了(没有办法,项目经理说项目要1周写界面,2周发版,我这个项目前端只我1个人,后端有3个人...).......我只想说的是,即便你编码速度再快,也无可避免地面临加班,即便你编码的速度能赶上产品做界面的速度,你也赶不上产品改界面需求的速度。因为很多公司就是给你直接先定时间,然后反推,又不加人又不减需求,能咋办呢?下面要说的是某一个界面中的一个站点配置模块。

413851-20221217145344176-672067861.png

需求说明:

【站点配置】

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、如果有添加操作,但未添加站点信息。不可以保存。提示“请添加站点信息或删除空白站点”

我做出来的界面效果

413851-20221217152833830-561663902.png
413851-20221217145946706-598028898.png

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

  拿到产品需求时,先通读一遍需求,然后看一遍UI界面,再根据需求,脑袋里面快速闪现技术方案,如果要高效,尽量避免自己造轮子,通用技术通常都有现成轮子,只有定制化的功能需要自己动手,先把整理的解决思路和自己即将采用的技术在脑子里面推演一遍,当在脑子里开发完成之后,你就可以心无旁骛,然后一气呵成,快速编码了。写代码也是一鼓作气,再而衰,三而竭!

  前端项目技术栈: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: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.push(item);
}
//选择站点
const onChangeSite = (val: any) => {
const item = allSiteList.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代码如下:

ContractedBlock.gifExpandedBlockStart.gif

View Code

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

  注意:高德webapi2.0跟mockjs有冲突,启用mock会导致地图显示空白,但是浏览器控制又不会报错的问题!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK