前言
最近写uniapp,遇到了产品的提的需求,需要做一个tag+input连用的功能,点击Tag进行选择,使用input可以进行tag的修改和创建,先来看看产品提的设计稿:
大概是一个这样的功能,我开始理解错了以为是在input中显示tag,类似element-ui中有的select选择器,但实际这只是进行tag和input的连用,看下面的设计稿应该能看懂逻辑
功能梳理
uniapp的组件库虽多,但或多或少都存在功能性局限或是兼容性的问题。我这里使用的是Uview-ui,它也只提供了最普通的input和tag基础功能,这里主要的难点就是实现样式的定制,比如输入框,点击,获取焦点,校验问题等,都会呈现不同的样式。还有手写input校验等等,过程中需要考虑用户各种输入的情况和输入框与用户的交互效果:
组件抽离实现
这里的功能的我抽离成为了一个组件,所以我就直接把代码贴出来,需要的朋友可以直接使用或者进行定制。
注:初代功能实现,代码粗糙,互相交流学习,大佬勿喷
template
<u-popup :show="label_popupShow" mode="bottom" :round="10">
<view class="label_popup">
<view class="label_popupTop">
<view style="width:120rpx ;">
<view class="label_popupDrop" @click="label_popupDrop">
<image src="../../static/Vector.png"></image>
</view>
</view>
<view class="label_popupTitle">标签</view>
<view class="label_popupbtn" @click="saveTags">完成</view>
</view>
<view class="label_popupContant" >
<view class="label_inputContant" :style="boxStyleDefault1">
<u--input
placeholder="请输入内容"
border="surround"
@blur="losefocusState"
@focus="focusState"
v-model="labelInputvalue"
@keyup.enter.native="deleteTags"
@change="change"
></u--input>
</view>
<view class="infoText" v-if="isWarning">标签长度不能超过8</view>
<view class="delete_contant">
<text >已有标签</text>
<image class="tagDelete_image" @click="tagDelete" src="@/static/notice/labeldelete.png" v-if="!tagClosable"> </image>
<image class="tagDeleteBack_image" @click="tagDeleteBack" src="@/static/notice/labeldeleteBack.png" v-if="tagClosable"> </image>
</view>
<view class="existing_label">
<view class="u-page__tag-item" v-for="(item, index) in allTags" :key="index">
<u-tag shape="circle" :text="item.name" :plain="!item.checked" :name="index"
@click="TagClick" borderColor='#fff' color='black' :closable='tagClosable'
@close="closelabelTag">
</u-tag>
</view>
</view>
</view>
</view>
</u-popup>
script
<script>
export default {
name:"chooselabel",
props: {
show: {
type:Boolean,
default: false
}
},
data(){
return {
isWarning:false,
boxStyleDefault1:'',
boxStyle:['',
'background:linear-gradient(to right,rgba(253, 157, 157, 1), rgba(244, 53, 53, 1))',
'background:linear-gradient(to right,rgba(93, 164, 247, 1), rgba(50, 133, 228, 1))'
],
// 标签弹窗是否弹出
label_popupShow:false,
// 标签弹窗的输入框数据
labelInputvalue: '',
// 存放所有的标签
allTags:[
// {
// name:'# 红头文件',
// checked: false
// },
],
// 标签输入框是否聚焦
isfocus:false,
// 标签删除按钮是否出现
tagClosable:false,
}
},
computed: {
focus() {
return !this.isfocus?'':'border-image:linear-gradient(to right,rgba(93, 164, 247, 1), rgba(50, 133, 228, 1))1;'
}
},
watch:{
label_popupShow: function (newVal, oldVal) {
// console.log(newVal);
if(newVal){
console.log('获取')
this.getlocalTags()
}
},
show: function (newVal, oldVal) {
// console.log(newVal);
if(newVal){
this.label_popupShow = newVal
}
},
},
methods:{
// 删除单个标签触发的时间
closelabelTag(e){
console.log(e)
console.log(list)
let list = []
list = uni.getStorageSync('userLabel')
list.splice(e, 1)
uni.setStorage({
key:'userLabel',
data:list
})
this.getlocalTags()
},
// 删除标签弹窗
tagDelete(){
this.tagClosable = true
},
tagDeleteBack() {
this.tagClosable = false
},
getlocalTags(){
let list = uni.getStorageSync('userLabel')
console.log(list)
if(list != '' || list.length == 0){
this.allTags = []
list.map((item, index) => {
let a = {}
console.log(item)
a.name = '#' + ' ' +item
a.checked = false
this.allTags.push(a)
})
}else{
console.log('第一次')
}
console.log(this.allTags)
},
// 标签保存
saveTags(){
if(this.labelInputvalue.length>8){
this.boxStyleDefault1 = this.boxStyle[1]
console.log('不通过')
}else{
console.log('通过啦啦')
// 如果没有输入值点击保存 则直接关闭,不作处理
if(this.labelInputvalue == ''){
this.label_popupShow = false
this.tagClosable = false
this.$emit('closeLabel_popup','')
}else {
// 输入值点击保存 判断标签是否存在,不存在在走 新建标签的流程
console.log('选择的标签是:',this.labelInputvalue)
let isSame = 0
this.allTags.map((item, index) => {
item.name.slice(2) == this.labelInputvalue ? isSame++ : ''
})
// if isSame==0则标签不存在要新建标签
if(isSame != 0) {
//存在
console.log('存在')
this.label_popupShow = false
this.tagClosable = false
this.$emit('closeLabel_popup',this.labelInputvalue)
}else {
// 不存在
console.log('不存在')
let list = []
list = uni.getStorageSync('userLabel')
if(list == ''){
list = []
list.push(this.labelInputvalue)
console.log(list)
uni.setStorage({
key:'userLabel',
data:list
})
} else {
list.push(this.labelInputvalue)
uni.setStorage({
key:'userLabel',
data:list
})
// console.log(isSame)
}
this.label_popupShow = false
this.tagClosable = false
this.$emit('closeLabel_popup',this.labelInputvalue)
}
}
}
},
TagClick(name) {
this.allTags.map((item, index) => {
item.checked = index === name ? true : false
})
this.labelInputvalue = this.allTags[name].name
this.labelInputvalue= this.labelInputvalue.slice(2)
},
change(e){
if(e.length>8){
this.boxStyleDefault1 = this.boxStyle[1]
this.isWarning = true
}else {
this.boxStyleDefault1 = this.boxStyle[2]
this.isWarning = false
}
},
// 输入框获取焦点
focusState() {
if(this.labelInputvalue.length>8){
this.boxStyleDefault1 = this.boxStyle[1]
}else{
this.boxStyleDefault1 = this.boxStyle[2]
}
},
// 输入框失去焦点
losefocusState() {
if(this.labelInputvalue.length>8){
this.boxStyleDefault1 = this.boxStyle[1]
}else{
this.boxStyleDefault1 = this.boxStyle[0]
}
},
// 标签弹出下拉隐藏按钮
label_popupDrop(){
this.label_popupShow = false
this.tagClosable = false
this.$emit('closeLabel_popup','')
this.labelInputvalue = ''
this.boxStyleDefault1 = this.boxStyle[0]
this.isWarning = false
},
}
}
</script>
CSS
<style lang="scss" scoped>
.label_popup {
height: 1040rpx;
display: flex;
flex-direction: column;
align-items: center;
.label_popupTop {
margin-top: 20px;
width: 640rpx;
height: 56rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
.label_popupDrop {
border-radius: 50%;
background-color:rgba(41, 41, 69, 0.05);
width: 48rpx;
height: 48rpx;
display: flex;
justify-content: center;
align-items: center;
image {
transform: rotate(90deg);
opacity: 0.7;
width: 5.5px;
height: 11px;
}
}
.label_popupTitle {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 40rpx;
line-height: 28px;
}
.label_popupbtn {
background: linear-gradient(95.13deg, #5DA4F7 3.75%, #3285E4 95.95%);
border-radius: 10px;
width: 120rpx;
height: 56rpx;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 32rpx;
line-height: 22px;
letter-spacing: -0.2176px;
}
}
.label_popupContant {
width: 640rpx;
.infoText{
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #FF5252;
margin-left: 5px;
}
.label_inputContant {
width: 640rpx;
border-radius: 4px;
padding: 2rpx;
background:linear-gradient(to right,rgba(220, 220, 220, 1), rgba(152, 152, 152, 1)) ;
margin-top: 25px;
display: flex;
align-items: center;
.labelTag {
margin-left: 10px;
border-radius: 5px;
}
/deep/.u-tag--mini {
border: 1px dashed rgba(41, 41, 69, 0.6)!important;
}
/deep/.u-input--square {
background: #fff!important;
height: 60rpx;
}
}
}
.delete_contant {
width: 640rpx;
height: 42rpx;
margin-top: 48px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
text {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: rgba(41, 41, 69, 0.6);
}
.tagDelete_image {
width: 42rpx;
height: 42rpx;
}
.tagDeleteBack_image {
width: 35rpx;
height: 29.74rpx;
}
}
.existing_label {
width: 640rpx;
margin-top: 10px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-content: space-between;
flex-wrap: wrap;
.u-page__tag-item {
margin-top: 15px;
// background: rgba(41, 41, 69, 0.05);
margin-right: 10px;
}
// .u-page__tag-item:nth-child(3n){
// margin-right: 0;
// }
.u-tag u-tag--circle u-tag--primary--plain u-tag--medium{
margin-right: 0px!important;
}
/deep/{
.u-tag--primary{
border-radius: 50px;
background-color:rgba(41, 41, 69, 0.05);
border-color: #3285E4!important;
// border-image:linear-gradient(to right,rgba(93, 164, 247, 1), rgba(50, 133, 228, 1))1!important;
}
.u-tag-wrapper {
// background: rgba(41, 41, 69, 0.05);
border-radius: 50px;
}
.u-tag u-tag--circle u-tag--primary u-tag--medium {
background-color:rgba(41, 41, 69, 0.05);
}
.u-tag--medium{
padding: 0 15px;
background: rgba(41, 41, 69, 0.05);
}
.u-tag__close--medium{
border: 1px solid rgba(122, 122, 140, 1);
background: #FFFFFF !important;
}
.u-tag__close{
// background-color: #FFFFFF!important;
top:12px;
right: 18px;
}
.u-icon__icon{
color:rgba(122, 122, 140, 1)!important;
}
// .u-tag u-tag--circle u-tag--primary--plain u-tag--medium {
// background: red;
// }
}
}
}
</style>