分享-2023年资深前端进阶:前端登顶之巅-最全面的前端知识点梳理总结
*分享一个使用比较久的
技术框架公司的选型:uni-app + uni-ui + vue3 + vite4 + ts
需求分析:微信小程序-uni-ui内容
1、创建一个自定义的下拉,支持多个内容的同时多选
2、定义好出入参数,支持回显内容等
3、绑定对应的v-model数据响应
<template>
<view tabindex="1" ref="customSelectRef" class="uni-select" @click.stop="handleClickDiv">
<view>
<template v-if="modelLabel.length">
<span class="custom-tag" :key="index" v-for="(item, index) in modelLabel">
<span>{{ item }}</span>
</span>
</template>
<span class="custom-tag" v-if="modelLabel.length && checkList.length - maxLength > 0">
+ {{ checkList.length - maxLength }}
</span>
<span v-if="!modelLabel.length" class="cus_placeholder">{{ placeholder }}</span>
<img
class="icon-delete"
v-if="modelLabel.length"
@click.stop="handleRemove"
:src="'../../static/icons/delete.png'"
/>
</view>
<transition>
<view class="cus_select_background" ref="cusSelectDropdown" v-if="isShowDropdown" @click="handleMemory">
<view class="cus_tabs" :key="index" v-for="(item, index) in cusDataListChecked">
<template v-if="item.children">
<view class="cus_tabs_title">{{ item.text }}</view>
<view class="cus_tabs_body">
<uni-data-checkbox
mode="tag"
:multiple="multiple"
v-model="item.checkList"
:localdata="item.children"
@change="(val) => handleCheckedChange(val, item)"
></uni-data-checkbox>
</view>
</template>
</view>
</view>
</transition>
<view v-if="isShowDropdown" class="custom_mask"></view>
</view>
</template>
<script setup lang="ts">
import { watch } from "vue";
import { toRaw } from "vue";
import { ref, onMounted, nextTick, onBeforeMount } from "vue";
const props = withDefaults(
defineProps<{
dataSource: any;
modelValue?: any;
placeholder?: string;
multiple?: boolean;
maxLength?: number;
}>(),
{
multiple: true,
dataSource: [],
modelValue: [],
maxLength: 3,
placeholder: "请选择",
}
);
const emit = defineEmits(["update:modelValue", 'change']);
const customSelectRef = ref();
const cusSelectDropdown = ref();
const modelLabel = ref<Record<string, any>[]>([]);
const checkList = ref<string[]>([]);
const cusDataListChecked = ref<Record<string, any>[]>([]);
const isShowDropdown = ref<boolean>(false);
const isShowDownMemory = ref<boolean>(false);
const handleClickDiv = () => {
isShowDropdown.value = isShowDownMemory.value ? true : !isShowDropdown.value;
isShowDownMemory.value = false;
};
const handleMemory = () => {
isShowDownMemory.value = true;
};
const handleCheckedChange = (e: Record<string, any>, row: Record<string, any>) => {
const { data } = e.detail;
row.checkLabel = data.map((opt) => opt.text);
getModelVal();
};
const getModelVal = () => {
const newValue = toRaw(cusDataListChecked.value);
const newLabel = newValue.map((item) => item.checkLabel);
const newModelVal = newValue.map((item) => item.checkList);
const deconstructLabel = newLabel?.flat();
const deconstructVal = newModelVal?.flat();
modelLabel.value = deconstructLabel.slice(0, props.maxLength);
checkList.value = deconstructVal;
emit("update:modelValue", newModelVal);
};
const handleRemove = (e) => {
modelLabel.value = [];
checkList.value = [];
if (isShowDropdown.value) {
isShowDropdown.value = false;
}
if (props.multiple) {
cusDataListChecked.value = addCheckProperties(props.dataSource);
}
emit("update:modelValue", []);
};
const addCheckProperties = (treeData) => {
let result = [];
result = JSON.parse(JSON.stringify(treeData));
result.forEach((node) => {
const child = node.children;
node.checkList = [];
node.checkLabel = [];
if (child && child.length > 0) {
addCheckProperties(child);
}
});
return result;
};
const findTreeChecked = (treeData) => {
const newLabel = [];
const val = toRaw(props.modelValue);
treeData.forEach((node, index) => {
if (node.children?.length) {
const child = node.children;
const bool = child.some((opt) => {
const isExist = val[index] && val[index].includes(opt.value);
isExist ? newLabel.push(opt.text) : void null;
return isExist;
});
if (bool) {
node.checkLabel = newLabel;
node.checkList = val[index];
}
}
});
return treeData;
};
watch(isShowDropdown, (newVal) => {
emit('change', newVal, props.modelValue)
})
onBeforeMount(() => {
if (props.multiple) {
cusDataListChecked.value = addCheckProperties(props.dataSource);
}
});
onMounted(async () => {
await nextTick();
if (props.multiple && props.modelValue.length) {
cusDataListChecked.value = findTreeChecked(cusDataListChecked.value);
getModelVal();
}
});
</script>
<style lang="scss" scoped>
.uni-select {
font-size: 14px;
border: 1px solid #e5e5e5;
box-sizing: border-box;
border-radius: 4px;
padding: 0 5px 0 10px;
position: relative;
display: flex;
user-select: none;
flex-direction: row;
align-items: center;
border-bottom: solid 1px #e5e5e5;
width: 100%;
flex: 1;
height: 35px;
position: relative;
}
.cus_placeholder {
color: #6a6a6a;
font-size: 12px;
}
.icon-delete {
position: absolute;
width: 18px;
height: 18px;
right: 5px;
margin-top: 3.5px;
z-index: 10;
}
.cus_select_background {
width: 95vw;
max-height: 260px;
box-sizing: border-box;
border-radius: 4px;
font-size: 13px;
color: #606266;
background: #ffffff;
border: 1px solid #e4e7ed;
position: absolute;
top: 40px;
left: 0px;
padding: 5px 8px;
z-index: 10;
}
.cus_tabs {
margin-bottom: 8px;
.cus_tabs_title {
font-weight: 600;
margin-bottom: 4px;
}
.cus_tabs_body {
margin-left: 12px;
}
}
.custom-tag {
color: #909399;
display: inline-flex;
justify-content: center;
align-items: center;
height: 24px;
padding: 0 9px;
line-height: 1;
border-radius: 4px;
white-space: nowrap;
font-size: 13px;
margin-right: 5px;
background-color: #f0f2f5;
}
.custom_mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
}
</style>
1、树形结构入参:
dataSource=[{ ext: "服务器状态", children: [{ text: "在线", value: 0}]}]
,
2、标签引用:
3、相关api说明文档在文章底部
参数 | 说明 | 类型 | 默认值 | 必填项 |
---|---|---|---|---|
dataSource | [{}]-label,value;树形结构 | Array[] | [] | 是 |
modelValue | 当前选中项内容 | Array | [] | 否 |
placeholder | 输入框内容 | String | 请输入 | 否 |
multiple | 是否开启多选 | Boolean | false | 否 |
maxLength | 输入框最大标签长度 | Number | 3 | 否 |