这个需求妹子不会!哎,又要帮妹子做需求了......

背景

前两天,一妹子紧皱着眉头,走到我面前,轻轻地说:"哥,这个功能我不会弄,能不能帮帮忙?"

就这么简单的一声"哥",让我心中涌起了一股难以抗拒的责任感。没有多想,心一横,我答应了。这一忙,注定无法拒绝。

需求

  1. 要做一个时间轴 00:00 ~ 24:00 ,按照每 15 分钟一个刻度,每四个刻度一个大刻度。
  2. 需要展示两个选中的值,一个是接口默认的值,一个用户选择的值
  3. 接口默认的值为默认颜色,用户选择的值为其他颜色,区分开。
  4. 该项目技术栈:Vue3 + Element-Plus2 + Echarts5

设计图如下:

帮忙

看看妹子实现到什么程度了?总不至于一个 code 没写吧?😱

还好,看了妹子做出的效果,以及实现的代码,样式以及基础的能力都实现了,只是遇到一些解决不了的问题。还不错,"妹子可教也"!😄

妹子已经实现上述需求的第一点。是用 Element Plus 的 Slider 修改的

html 复制代码
<template>
  <el-slider
      class="time-slider"
      ref="sliderRef"
      range
      :marks="props.marksData"
      :min="props.min"
      :max="props.max"
      :format-tooltip="handleFormat" />
</template>
scss 复制代码
.time-slider {
  box-sizing: border-box;
  width: 100%;
  height: 32px;
  margin-top: 0;
  padding: 3px 20px 0;
  align-items: flex-start;

  // 修改 el-slider 默认样式
  :deep(.el-slider__runway) {
    width: 100%;
    height: 4px;
    border-bottom: 1px solid #01a59b;
    background-color: transparent;

    .el-slider__bar {
      background: transparent;
    }

     // 选中的样式
    .el-slider__button {
      height: 12px;
      width: 12px;
      border-radius: 50%;
      background-color: #01a59b;
      border-color: #01a59b;
    }

    .el-slider__marks-stop {
      box-sizing: border-box;
      width: 7px;
      height: 7px;
      background: #f2fbff;
      border: 1px solid #01a59b;
      border-radius: 50%;
    }

    // 每四个一个大刻度的样式
    .el-slider__marks-stop:nth-child(4n+1) {
      width: 12px;
      height: 12px;
      bottom: -6px;
    }

    .el-slider__marks-text {
      height: 20px;
      font-size: 16px;
      font-weight: 400;
      text-align: CENTER;
      color: #4f5566;
      line-height: 20px;
      margin-top: 14px;
    }
  }
}
js 复制代码
// 时间格式 转 刻度的方法
import { timeToScale } from '@/utils'

const model = defineModel()
const emits = defineEmits(['change'])
const props = defineProps({
  marksData: {
    type: Object,
    default() {
      let timeRange = timeToScale()
      let data = {}
      timeRange.forEach((time, index) => {
        if (index % 8 == 0 || index === 95) {
          data[index] = time
        } else {
          data[index] = ''
        }
      })
      return data
    }
  },
  min: {
    type: Number,
    default: 0
  },
  max: {
    type: Number,
    default: 95
  },
  highlight: {
    type: String,
    default: ''
  },
  format: {
    type: Boolean,
    default: true
  },
  formatData: {
    type: Object,
    default() {
      let timeRange = timeToScale()
      let data = {}
      timeRange.forEach((time, index) => {
        data[index] = time
      })
      return data
    }
  }
})

const handleFormat = (value) => {
  return props.format ? props.formatData[value] : value;
};

// 设置最小时刻
let sliderRef = ref()
watchPostEffect(() => {
  if (!props.highlight) return
  let index = null
  for (const key in props.formatData) {
    if (props.formatData[key] == props.highlight) {
      index = Number(key)
      break
    }
  }

  if (!Number.isNaN(index) && index != null) {
    let el = sliderRef.value.$el.children[0].children[2].children[index]
    el.classList.add('active')
  }
})
js 复制代码
export function timeToScale(timer = 15, length = 96) {
  const list = []
  let hour = 0
  let minutes = 0
  for (let i = 0; i < length; i++) {
    if (minutes >= 60) {
      minutes = 0
      hour += 1
    }
    list.push((hour > 9 ? hour : '0' + hour) + ':' + (minutes > 9 ? minutes : '0' + minutes))
    minutes += timer
  }
  return list
}

实现效果,整体上把样式实现了:

难点 1

因为接口给的默认是时间格式,而组件其实是按照数值计算的,那么怎么把时间格式转成对应的数值,对应到图上呢?

js 复制代码
export function timeToIndex(time, timer = 15) {
  const [hour, minutes] = time.split(':').map(Number);
  return (hour * 60 + minutes) / timer;
}

难点 2

妹子的问题就卡在了第二点需求:2. 需要展示两个选中的值,一个是接口默认的值,一个用户选择的值

其实就是怎么在图上标记一个固定点,不能更改,另外一个点用户可以随意选择。

我去了看了 Element Plus 的 Slider 官方组件的 API,element-plus.org/zh-CN/compo...

没有直接能支持的 API,但是间接支持的有:

  • range 是否开启选择范围,这样就能选择 2 个值了。

解题思路: 那么就需要想办法固定其中一个值,只让另外一个值变化就行。那么就是去干扰用户选择的值,重新赋值。那就 v-model 绑定一个计算属性的值。

经过几番调试,得出如下代码

js 复制代码
import { timeToScale, timeToIndex } from '@/utils'

// 接口默认的值
const fixedValue = ref(timeToIndex(model.value));
// 用户自己选的值
// 首先默认为和 接口默认值 相同
// 因为添加 range 属性后,值是一个数组 [low, hight]
const userValue = ref(timeToIndex(model.value));
// 计算属性,重新赋值
const modelFormat = computed({
  get() {
    // 组件展示的值,需要排一下大小顺序
    return [fixedValue.value, userValue.value].sort((a, b) => a - b);
  },
  set(val) {
    // 用户手动操作的值,和我想要的结果有点出入
    const [low, high] = val.sort((a, b) => a - b);

    // 进一步加工一下数据,得到预期的数据
    // 1. 如果固定值是小的,那么大的就是 用户选的
    // 2. 如果固定值是大的,则小的就是 用户选的
    // 本来以为到这就可以了,但是会有 bug,大家可以不加下边两句可以试试效果,
    // 固定值的左右都选选,固定值丢失了,都被用户选的覆盖了,所以...
    // 3. 上次用户值是小的,那么新选的用户值就是 大的
    // 4. 上次用户值是大的,则新选的用户值就是 小的
    if (fixedValue.value === low) {
      userValue.value = high;
    } else if (fixedValue.value === high) {
      userValue.value = low;
    } else if (userValue.value === low) {
      userValue.value = high;
    } else if (userValue.value === high) {
      userValue.value = low;
    }

    // 返会用户选的值
    emits("change", userValue.value);
  },
});

效果就是如下图了,固定值是 08:00, 另外一个值可以随意选择了,小于 08:00,大于 08:00,都可以!等于也可以,但是就没业务意义了。所以相同没必要了。

难点 3

选择的操作是可以了,但是选完了,看不出哪个是用户选的?哪个是原来固定值?那么就需要颜色区分开。

解题思路:打开浏览器控制台,看看怎么给他们加颜色,能不能固定的拿到对应的 Dom ? 给其动态增加颜色。

得出如下代码:

js 复制代码
const modelFormat = computed({
  get() {
    return [fixedValue.value, userValue.value].sort((a, b) => a - b);
  },
  set(val) {
    const [low, high] = val.sort((a, b) => a - b);
    if (fixedValue.value === low) {
      userValue.value = high;
    } else if (fixedValue.value === high) {
      userValue.value = low;
    } else if (userValue.value === low) {
      userValue.value = high;
    } else if (userValue.value === high) {
      userValue.value = low;
    }

    // 新加的代码
    // 需要加载更新后才能获得新 dom
    nextTick(() => {
       // 找到用户点的那个元素
      const parentElement = document.querySelector(`[aria-valuetext="${userValue.value}"]`);
      console.log(1111, userValue.value, parentElement)

      if (parentElement) {
        // 查找子元素 .el-slider__button
        const childElement = parentElement.querySelector('.el-slider__button');
        console.log(22222, childElement)

        if (childElement) {
          // 修改子元素的背景颜色和边框颜色
          childElement.style.backgroundColor = "#2D99FF"; // 示例背景颜色
          childElement.style.borderColor = "#2D99FF";    // 示例边框颜色
        }
      }
    });

    emits("change", userValue.value);
  },
});

这些代码其实是有 bug 的,点几下后,改完之后颜色都一起改了,两个点的颜色都是新颜色了。又不能区分了。

解题思路:固定的颜色跟着变了!不要让它跟着变,那就需要把它按在原地,或者变了再给它按回去。

刚才新加的代码删除掉,新增代码如下:

js 复制代码
// 监听 modelFormat 变化
watch(
  modelFormat,
  () => {
    // dom 更新后 
    nextTick(() => {
      // 整理一个通用的方法
      const applyStylesToSliderButton = (value, color) => {
        // 找到对应值的父级 dom 元素,因为只有这个能区分他们,可以控制台看它们元素区别
        const parentElement = document.querySelector(`[aria-valuetext="${value}"]`);
        if (parentElement) {
          const childElement = parentElement.querySelector('.el-slider__button');
          if (childElement) {
            childElement.style.backgroundColor = color; // 设置背景色
            childElement.style.borderColor = color; // 边框色也跟上
            childElement.style.width = "12px"; // 选中要变大
            childElement.style.height = "12px"; // 选中要变大
          }
        }
      };

      // 按住固定值的颜色,或者叫做按回去
      applyStylesToSliderButton(fixedValue.value, "#01a59b");
      
      // 两个值一样了,就当用户没有选择
      if (userValue.value === fixedValue.value) return;

      // 用户选择的值,设置颜色
      applyStylesToSliderButton(userValue.value, "#2D99FF");
    });
  },
  { immediate: true }
);

反反复复点了几次,测了一下,完美,至此终于解决了妹子的问题。一看表,时间过去了 2 个小时,该准备吃中午饭了...

效果

妹子凑过来看了一眼效果,眼睛顿时一亮,忍不住笑着夸道:"哥,你太棒了!"

她那一句赞美透着几分惊喜,我厚脸皮的也没忍住,脸上微微发热,挠了挠头,假装镇定地回她一个浅笑。

"哥,要不中午我请你吃饭吧?"妹子歪着头,声音里透着期待和一丝俏皮。

我嘴角扬起,心想这顿饭是推不掉了,便笑着应了她的邀请。

中午,跟着她去了家小店,点了排骨铁锅焖面,热腾腾的香气扑鼻而来。妹子看我吃得津津有味,忍不住问:"怎么样?"

我抬头冲她一笑:"真不错!这面比你夸我还带劲儿!"

妹子噗嗤笑了,眼睛弯成了月牙。那一瞬间,心里觉得这顿饭,不光味道好,还透着点说不清的甜。

小结

上述大概介绍了就是我用了 2 个小时,用技术换了一顿排骨铁锅焖面的故事。顺带用 1 个小时写成文章分享给大家。

此文只为告诉大家,没事多帮妹子写写代码,说不定会有一顿大餐哦......

代码如有什么问题,或者优化空间,也请评论区告知我,别让我的得瑟影响了我的技术进步......

题外话

掘金2024年度人气创作者打榜中,快来帮我打榜吧,目标 200 名之前,能有个标志就行 😄~ activity.juejin.cn/rank/2024/w...

相关推荐
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink4 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-6 小时前
验证码机制
前端·后端
燃先生._.7 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖8 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235248 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240259 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar9 小时前
纯前端实现更新检测
开发语言·前端·javascript