本篇文章主要记录菜鸟自己对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);
});
}
}
};
注意
vueUse
的until
方法只能监听浅层的响应式对象,ref里面的ref是不能监听的,深度监听需要添加deep
参数,eg:await until(dictData).toMatch((v) => unref(v).sample_fridge_class.length > 0, { deep: true });
然后上面展示的储位编号又只需要冰箱的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-cascader
的presentText
,也是需要使用到vueUse
的until
方法
代码
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();
……
}
});
注意
如果是
Dom对象的ref
就不用使用.value
,或者更保险就是直接使用unref
菜鸟发现一个问题,就是
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();