工作中突然发现零宽字符串的作用了!

本篇文章主要记录菜鸟自己对el-cascader的使用问题,以及其中用到了零宽字符串,对大佬来说就是小儿科,对初学者可能有点帮助!请酌情观看!

最近菜鸟都在忙着做公司的新系统,以及解决生活琐事,都没空更新。不过做系统的过程中也遇见了很多很多的问题,感觉自己太菜了,很多都要请教公司另一位6年经验的大佬,真的受益匪浅!

零宽字符串不懂的可以看渡一:https://www.bilibili.com/video/BV1CN411m74S/

零宽字符串算是我自己想到的一个巧妙的解决办法,具体需要如下:

后端数据

后端返回的数据

[
  {
    "createUserId": "1",
    "createTime": "2025-04-02 17:46:08",
    "updateUserId": "1",
    "updateTime": "2025-04-02 17:46:08",
    "fridgeId": "1907368944074145793",
    "fridgeTemperatureType": "A",
    "fridgeNum": 1,
    "version": "1",
    "location": "31231",
    "layerNum": "25"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-02 17:46:15",
    "updateUserId": "1",
    "updateTime": "2025-04-02 17:46:15",
    "fridgeId": "1907368971165155330",
    "fridgeTemperatureType": "B",
    "fridgeNum": 1,
    "version": "1",
    "location": "123"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-02 17:46:19",
    "updateUserId": "1",
    "updateTime": "2025-04-02 17:46:19",
    "fridgeId": "1907368987678130178",
    "fridgeTemperatureType": "C",
    "fridgeNum": 1,
    "version": "1",
    "location": "12312",
    "layerNum": "7"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-02 17:46:26",
    "updateUserId": "1",
    "updateTime": "2025-04-02 17:46:26",
    "fridgeId": "1907369020548890625",
    "fridgeTemperatureType": "D",
    "fridgeNum": 1,
    "version": "1",
    "location": "12",
    "layerNum": "3"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-02 17:46:30",
    "updateUserId": "1",
    "updateTime": "2025-04-02 17:46:30",
    "fridgeId": "1907369036319473665",
    "fridgeTemperatureType": "R",
    "fridgeNum": 1,
    "version": "1",
    "location": "123123",
    "layerNum": "3"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-02 17:56:40",
    "updateUserId": "1",
    "updateTime": "2025-04-02 17:56:40",
    "fridgeId": "1907371593846669314",
    "fridgeTemperatureType": "A",
    "fridgeNum": 100,
    "version": "1",
    "location": "111",
    "layerNum": "4"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-03 09:09:36",
    "updateUserId": "1",
    "updateTime": "2025-04-03 09:09:36",
    "fridgeId": "1907601340705529857",
    "fridgeTemperatureType": "A",
    "fridgeNum": 51,
    "version": "1",
    "location": "asda",
    "layerNum": "47"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-03 10:23:51",
    "updateUserId": "1",
    "updateTime": "2025-04-03 10:23:51",
    "fridgeId": "1907620025964945409",
    "fridgeTemperatureType": "A",
    "fridgeNum": 21,
    "version": "1",
    "location": "12"
  },
  {
    "createUserId": "1",
    "createTime": "2025-04-17 14:29:10",
    "updateUserId": "1",
    "updateTime": "2025-04-17 14:29:10",
    "fridgeId": "1912755191600173057",
    "fridgeTemperatureType": "A",
    "fridgeNum": 12,
    "version": "1",
    "location": "22"
  }
]

前端

html 代码比较简单

<el-cascader v-model="fridgeData" clearable ref="fridgeCascader" @change="fridgeChange" :props="props" />

前端的展示效果

在这里插入图片描述

数据处理

这里需要通过fridgeTemperatureType对冰箱进行分类,然后还要通过后端返回的字典,去确定分类的冰箱是什么类型的。

字典数据

{
  "sample_fridge_class":[
    {"label":"-(84°C~80°C)","value":"A","elTagType":"primary"},
    {"label":"-40°C","value":"B","elTagType":"primary"},
    {"label":"-20°C","value":"C","elTagType":"primary"},
    {"label":"4°C","value":"D","elTagType":"warning"},
    {"label":"常温","value":"R","elTagType":"info"}
  ]
}

具体代码

const props = {
  lazy: true,
  lazyLoad(node, resolve) {
    console.log(node);
    const { level } = node;
    if (level === 0) {
      // 请求上面的json
      listStorageFridge(queryParams)
        .then(async (res) => {
          if (+res.code === 200) {
            // 将res.data.records按照fridgeTemperatureType分类
            const fridgeTemperatureTypeMap = {};
            res.data.records.forEach((item) => {
              if (!fridgeTemperatureTypeMap[item.fridgeTemperatureType]) {
                fridgeTemperatureTypeMap[item.fridgeTemperatureType] = [];
              }
              fridgeTemperatureTypeMap[item.fridgeTemperatureType].push(item);
            });
            // vueUse的until方法,可以等待某个数据有值再执行后面
            await until(sample_fridge_class).toMatch((v) => unref(v).length > 0);
            let nodes = Object.entries(fridgeTemperatureTypeMap).map(([k, v]) => {
              // 循环sample_fridge_class(字典),通过判断value和k是否相等,取出lable
              const label = dictData.value.sample_fridge_class.find(
                (item) => item.value == k
              ).label;
              // 处理第二层
              // 将v的fridgeNum作为lable,fridgeId作为value
              v = v.map((item) => {
                return {
                  label: item.fridgeNum,
                  value: item.fridgeId,
                  // 通过下一层数量来判断
                  disabled: item.layerNum ? false : true,
                  leaf: item.layerNum ? false : true,
                  ...item
                };
              });
              // 第一层
              return {
                label: k + "\u200d" + label, // 用到了零宽字符串
                value: k,
                children: v,
                leaf: false
              };
            });
            resolve(nodes);
          } else {
            ElMessage.error(res.message);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    }
    // 第三层 -- 因为第二层其实已经有了
    if (level === 2) {
      listStorageFridgeLayer({
        fridgeId: node.data.fridgeId,
        ...queryParams
      })
        .then((res) => {
          if (res.data.records.length === 0) {
            // 修改为使用 disabled 属性
            resolve([
              {
                label: "无数据",
                value: null,
                disabled: true,
                leaf: true
              }
            ]);
            return;
          }
          let temp = res.data.records.map((item) => {
            return {
              label: item.layerValue,
              value: item.fridgeLayerId,
              leaf: true,
              ...item
            };
          });
          resolve(temp);
        })
        .catch((err) => {
          console.log(err);
        });
    }
  }
};

注意

  1. vueUseuntil方法只能监听浅层的响应式对象,ref里面的ref是不能监听的,深度监听需要添加deep参数,eg:
await until(dictData).toMatch((v) => unref(v).sample_fridge_class.length > 0, {
  deep: true
});
  1. 更多见:https://vueuse.nodejs.cn/shared/until/

  2. 源码见:https://github.com/vueuse/vueuse/blob/main/packages/shared/until/index.ts

然后上面展示的储位编号又只需要冰箱的fridgeTemperatureType,不需要label,所以就变成了这样

// 监听冰箱选择
const fridgeChange = (value) => {
  if (value) {
    // 获取layerClass,判断是年就只要后两位
    layerClass.value = fridgeCascader.value.getCheckedNodes()[0].data.layerClass;
    // 获取el-cascader框框里面的内容
    const presentText = fridgeCascader.value.presentText;
    const tempArr = presentText.split("/");
    const last = layerClass.value == "3" ? tempArr[2].slice(-2) : tempArr[2];
    const tempArr2 = tempArr[0].split("\u200d"); // 通过零宽字符串进行分割,就避免界面上展示,也更好分割
    // 对fridgeName.value.storage进行处理展示
    fridgeName.value.storage = tempArr2[0].trim() + " - " + tempArr[1].trim() + " - " + last.trim();
    ……
};

回显

这里冰箱回显也是挺难的,因为虽然设置了v-model的值,但是渲染到界面也是异步的方法,无法马上获取到el-cascaderpresentText,也是需要使用到vueUseuntil方法

代码

onMounted(async () => {
  const res = await orderSampleListByOrderId({
    orderId: porps.orderId
  });
  if (+res.code == 200) {
    // 判断res.data里面的对象的keys是否都有大于0,有才进行后面的步骤,没有就不进行后面的步骤
    if (
      Object.keys(res.data.storageFridgeLayerDTO || {}).length > 0 &&
      Object.keys(res.data.storageFridgeDTO || {}).length > 0 &&
      Object.keys(res.data.storageLocationsDTO || {}).length > 0 &&
      Object.keys(res.data.storageLocationsBatchDTO || {}).length > 0
    ) {
      let layerClass = res.data.storageFridgeLayerDTO.layerClass;

      fridgeData.value = [
        res.data.storageFridgeDTO.fridgeTemperatureType,
        res.data.storageFridgeDTO.fridgeId,
        res.data.storageFridgeLayerDTO.fridgeLayerId
      ];
      // 请求第四层
      fridgeLayerId.value = fridgeData.value[fridgeData.value.length - 1];
      await listStorageLocationsFunFour(fridgeLayerId.value);
      // 请求第五层
      await listStorageLocationsFunFive(res.data.storageLocationsDTO.locationId);
      
      // 菜鸟2025.4.23重新试了一下,发现这个是对的,昨天错误是因为v后面多加了.value,保险还是用unref或者tovalue包一下
      await until(fridgeCascader).toMatch((v) => {
        console.log(v);
        if (v) {
          return unref(v).presentText.length > 0;
        }
      });

      const presentText = fridgeCascader.value.presentText;
      const tempArr = presentText.split("/");
      const last = layerClass == "3" ? tempArr[2].slice(-2) : tempArr[2];
      const tempArr2 = tempArr[0].split("\u200d");
      // 对fridgeName.value.storage进行处理
      fridgeName.value.storage =
        tempArr2[0].trim() + " - " + tempArr[1].trim() + " - " + last.trim();
      ……
  }
});

注意

  1. 如果是Dom对象的ref就不用使用.value,或者更保险就是直接使用unref

  2. 菜鸟发现一个问题,就是onMounted里面执行的await until,在组件卸载的时候也会执行,所以菜鸟只能加一个if(v)的判断,这个菜鸟就不是很清楚,懂的读者可以指点江山,激扬文字!

协程

昨天菜鸟这里的解决办法是使用协程,每一帧去看是否可以往下执行,具体代码为

await waitUntil(() => {
    return fridgeCascader.value?.presentText?.length > 0;
});

注意

大佬建议:这个还是少用,不是迫不得已不要用,因为有性能问题!

这里waitUntil的封装是大佬封装的

export const waitUntil = async (predicate) => {
  while (1) {
    if (predicate()) {
      break;
    }
    await CoroutineManager.WaitForEndOfFrame();
  }
};

export class CoroutineManager {
  constructor() {
    /**
     * @type {Set<AsyncGenerator | Generator>}
     */
    this.coPool = new Set();
  }

  /**
   * @param co {AsyncGenerator | Generator}
   * @returns {Promise<void>}
   */
  async startCoroutine(co) {
    if (this.coPool.has(co)) {
      console.warn("co has been started");
    }

    this.coPool.add(co);

    /**
     * @type { IteratorYieldResult | null}
     */
    let res = null;

    while ((res = await co.next(res?.value ?? null))) {
      const { done } = res;

      if (done) {
        break;
      }
    }
  }

  /**
   * @description 如果返回done => true 那么说明co已经执行完成,未能成功取消
   * @param co {AsyncGenerator | Generator}
   * @returns {Promise<IteratorYieldResult>}
   */
  stopCoroutine(co) {
    this.coPool.delete(co);
    return co.return(null);
  }

  async stopAllCoroutines() {
    return Promise.allSettled([...this.coPool].map((co) => this.stopCoroutine(co)));
  }

  static WaitForMillSeconds = sleep;

  static WaitForEndOfFrame = raf;

  static WaitUntil = waitUntil;

  static async *CreateNoopCo() {}
}

export const sleep = (ms) => createSleep()({ ms });
export const raf = () => new Promise((resolve) => requestAnimationFrame(resolve));

// createSleep() 是一个工厂函数,提供不同模式的 sleep 实现
export const createSleep = ({ isRejectMode = false, withRetVal = false } = {}) => {
  return ({ ms, v }) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        Reflect.apply(
          isRejectMode ? reject : resolve,
          null,
          Array.of(v).flatMap((v) => (withRetVal ? [v] : []))
        );
      }, ms);
    });
  };
};

export const GCM = new CoroutineManager();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PBitW

可以去掘金看更完善版本

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值