477 lines
10 KiB
Vue
477 lines
10 KiB
Vue
<template>
|
||
<view class="_select">
|
||
<view class="_mask" @touchmove.stop.prevent="() => { }" v-if="popclass" @tap="hidepop"></view>
|
||
<view class="_pop" :animation="anidatapop" v-if="popclass" :class="popclass">
|
||
<view class="ciy-hr"></view>
|
||
<view class="_search" v-if="range.length > minselectsearch">
|
||
<view class="itm">
|
||
<input v-model="searchval" :placeholder="lang('placeholder.key')" @confirm="searchconfirm" />
|
||
</view>
|
||
</view>
|
||
<view class="_list" :class="rows==1?'_flex':'_grid'" :style="'grid-template-columns: repeat('+rows+', 1fr);text-align:' + itemalign">
|
||
<slot name="list" :data="{range:mrange,searchkey:searchval,popclass:popclass,onselect:Selitem}">
|
||
<view class="_item" :class="{_select:tvalue.id==item.id}" v-for="(item,index) in mrange" :key="index" @tap="Selitem(index)">
|
||
{{item.name}}
|
||
</view>
|
||
<template v-if="mrange.length == 0">
|
||
<view v-if="searchval" class="_nodata">
|
||
{{lang('select.nosearch')}}
|
||
<text class="_code">{{searchval}}</text>
|
||
</view>
|
||
<view v-else class="_nodata">{{lang('select.nodata')}}</view>
|
||
</template>
|
||
</slot>
|
||
</view>
|
||
<view class="ciy-hr"></view>
|
||
<view style="flex:1;pointer-events: none;width:100%;" @touchmove.stop.prevent="() => { }" @tap="hidepop"></view>
|
||
</view>
|
||
<view class="_show" :class="{_left:left}" @tap="showpop">
|
||
<view class="_txt" :style="ciystyle">
|
||
<view v-if="tvalue.name" :style="{color:disabled?'var(--bg6)':'var(--txt9)'}">{{tvalue.name}}</view>
|
||
<view v-else-if="placeholder !== ''" style="color:var(--txt1);">{{placeholder?placeholder:lang('placeholder.select')}}</view>
|
||
</view>
|
||
<view v-if="!noarrow" class="_arrow ciy-icon-arrow" :class="{_sel:popclass}"></view>
|
||
</view>
|
||
|
||
<input type="hidden" :name="name" :value="tvalue.id" style="display:none;" />
|
||
<input v-if="hasmore" type="hidden" :name="name+'_name'" :value="tvalue.name" style="display:none;" />
|
||
</view>
|
||
</template>
|
||
<script>
|
||
export default {
|
||
behaviors: ['uni://form-field-group'],
|
||
emits: ['change', 'update:modelValue', 'search'],
|
||
props: {
|
||
name: {
|
||
type: String
|
||
},
|
||
modelValue: {
|
||
type: [String, Number],
|
||
default: ''
|
||
},
|
||
value: {
|
||
type: [String, Number],
|
||
default: ''
|
||
},
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
initevent: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
ciystyle: {
|
||
type: [String, Object]
|
||
},
|
||
left: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
hasmore: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
chkuse: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
range: {
|
||
type: Array,
|
||
default: []
|
||
},
|
||
placeholder: {
|
||
type: String
|
||
},
|
||
diastema: { //上下间隙
|
||
type: Number,
|
||
default: 16
|
||
},
|
||
itemalign: { //item对齐
|
||
type: String,
|
||
default: ''
|
||
},
|
||
all: { //0值选项
|
||
type: String,
|
||
default: ''
|
||
},
|
||
noarrow: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
rows: {
|
||
type: Number,
|
||
default: 1
|
||
},
|
||
minselectsearch: { //选项少于n,则不显示搜索
|
||
type: Number,
|
||
default: 5
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
v: '',
|
||
searchval: '', //搜索键入内容
|
||
popclass: '', //值包含[btm/top],制定弹出在上方还是下方
|
||
anidatapop: {}, //弹出的选项动画
|
||
addrange: [] //动态新增的选项,不能污染属性,保持单向性
|
||
};
|
||
},
|
||
watch: {
|
||
searchval: {
|
||
handler(newD, oldD) {
|
||
this.$emit('search', {
|
||
name: this.name,
|
||
from: 'input',
|
||
com: this,
|
||
value: newD
|
||
});
|
||
}
|
||
},
|
||
value: {
|
||
handler(newD, oldD) {
|
||
if (newD || oldD)
|
||
this.v = 'value';
|
||
},
|
||
immediate: true
|
||
},
|
||
modelValue: {
|
||
handler(newD, oldD) {
|
||
if (newD || oldD)
|
||
this.v = 'modelValue';
|
||
},
|
||
immediate: true
|
||
},
|
||
},
|
||
computed: {
|
||
mrange() {
|
||
var mr = [];
|
||
if (this.all)
|
||
mr.push({
|
||
id: 0,
|
||
name: this.all
|
||
});
|
||
for (var i in this.range) {
|
||
if(this.chkuse && this.range[i].isuse == 2)
|
||
continue;
|
||
if (this.range[i].name.indexOf(this.searchval) > -1)
|
||
mr.push(this.range[i]);
|
||
}
|
||
for (var i in this.addrange) {
|
||
if(this.chkuse && this.range[i].isuse == 2)
|
||
continue;
|
||
if (this.addrange[i].name.indexOf(this.searchval) > -1)
|
||
mr.push(this.addrange[i]);
|
||
}
|
||
return mr;
|
||
},
|
||
tvalue() {
|
||
var val = '';
|
||
if (this.v == 'modelValue') {
|
||
if (typeof(this.modelValue) == 'number')
|
||
val = this.modelValue;
|
||
else if (this.modelValue)
|
||
val = this.modelValue;
|
||
} else if (this.v == 'value') {
|
||
if (typeof(this.value) == 'number')
|
||
val = this.value;
|
||
else if (this.value)
|
||
val = this.value;
|
||
} else if (this.v instanceof Object) {
|
||
return this.v;
|
||
}
|
||
for (var i in this.range) {
|
||
if (this.range[i].id == val)
|
||
return {
|
||
_index: i,
|
||
...this.range[i]
|
||
};
|
||
}
|
||
if (this.range.length > 0 && this.placeholder === '')
|
||
return {
|
||
_index: 0,
|
||
...this.range[0]
|
||
};
|
||
return {
|
||
id: 0,
|
||
_index: -1,
|
||
name: ''
|
||
};
|
||
}
|
||
},
|
||
mounted() {
|
||
if (this.initevent) {
|
||
this.$emit('change', {
|
||
name: this.name,
|
||
from: 'init',
|
||
value: {
|
||
...this.tvalue
|
||
}
|
||
});
|
||
}
|
||
this.lastvalue = {
|
||
...this.tvalue
|
||
};
|
||
},
|
||
methods: {
|
||
searchconfirm(e) {
|
||
this.$emit('search', {
|
||
name: this.name,
|
||
from: 'confirm',
|
||
com: this,
|
||
value: e.detail.value
|
||
});
|
||
},
|
||
Addrange(newrange) {
|
||
if (!(newrange instanceof Array))
|
||
return 'ERR: Not Array.';
|
||
var addarr = [];
|
||
for (var i in newrange) {
|
||
var bfind = false;
|
||
for (var r in this.range) {
|
||
if (newrange[i].id == this.range[r].id) {
|
||
bfind = true;
|
||
break;
|
||
}
|
||
}
|
||
if (bfind)
|
||
continue;
|
||
for (var r in this.addrange) {
|
||
if (newrange[i].id == this.addrange[r].id) {
|
||
bfind = true;
|
||
break;
|
||
}
|
||
}
|
||
if (bfind)
|
||
continue;
|
||
for (var r in addarr) {
|
||
if (newrange[i].id == addarr[r].id) {
|
||
bfind = true;
|
||
break;
|
||
}
|
||
}
|
||
if (bfind)
|
||
continue;
|
||
addarr.push(newrange[i]);
|
||
}
|
||
this.addrange.push(...addarr);
|
||
return true;
|
||
},
|
||
Selitem(index) {
|
||
this.v = this.mrange[index];
|
||
this.$emit('update:modelValue', this.v.id);
|
||
this.$emit('change', {
|
||
name: this.name,
|
||
from: 'select',
|
||
value: {
|
||
_index: index,
|
||
...this.v
|
||
},
|
||
lastvalue: this.lastvalue
|
||
});
|
||
this.lastvalue = {
|
||
_index: index,
|
||
...this.v
|
||
};
|
||
this.hidepop();
|
||
},
|
||
hidepop() {
|
||
var animation = uni.createAnimation({});
|
||
animation.translateX('100vw');
|
||
animation.opacity(0);
|
||
animation.step({
|
||
duration: 200
|
||
});
|
||
animation.height('auto');
|
||
this.anidatapop = animation.export();
|
||
setTimeout(() => {
|
||
this.popclass = '';
|
||
}, 300);
|
||
},
|
||
async showpop() {
|
||
if (this.disabled)
|
||
return;
|
||
var app = getApp();
|
||
app.globalData.scrollcbs['com_select'] = e => {
|
||
delete app.globalData.scrollcbs['com_select'];
|
||
this.hidepop();
|
||
};
|
||
const {
|
||
headerheight,
|
||
footerheight
|
||
} = await this.com_gethdft();
|
||
var fixtop = 0;
|
||
|
||
this.searchval = '';
|
||
this.popclass = 'temp';
|
||
var mainrect = await this.getrect('._pop');
|
||
if (!mainrect)
|
||
return;
|
||
var showrect = await this.getrect('._show');
|
||
if (!showrect)
|
||
return;
|
||
var toph = showrect.top - headerheight - this.diastema;
|
||
var btmh = app.globalData._sysinfo.windowHeight - footerheight - showrect.bottom - this.diastema;
|
||
var topcls = '_btm';
|
||
var syheight, sytop;
|
||
if (toph > btmh) {
|
||
topcls = '_top';
|
||
syheight = mainrect.height;
|
||
if (syheight > toph)
|
||
syheight = toph;
|
||
sytop = headerheight + fixtop + toph - syheight;
|
||
} else {
|
||
syheight = mainrect.height;
|
||
if (syheight > btmh)
|
||
syheight = btmh;
|
||
sytop = showrect.bottom + this.diastema - fixtop;
|
||
}
|
||
this.popclass = topcls;
|
||
this.$nextTick(() => {
|
||
var animation = uni.createAnimation({});
|
||
animation.translateX(0);
|
||
animation.top(sytop);
|
||
animation.height(syheight);
|
||
animation.opacity(1);
|
||
animation.step({
|
||
duration: 200
|
||
});
|
||
this.anidatapop = animation.export();
|
||
});
|
||
}
|
||
|
||
}
|
||
}
|
||
</script>
|
||
<style scoped>
|
||
._select ._pop {
|
||
position: fixed;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
opacity: 0;
|
||
left: 0;
|
||
right: 0;
|
||
transform: translateX(100vw);
|
||
transform: translateY(38px);
|
||
z-index: 992;
|
||
}
|
||
|
||
._select ._pop ._search {
|
||
background: var(--bg2);
|
||
box-sizing: border-box;
|
||
padding: 0.5em;
|
||
width: 100%;
|
||
}
|
||
|
||
._select ._pop ._search>.itm {
|
||
background: var(--bg1);
|
||
border: 1px solid var(--bg4);
|
||
border-radius: 0.5em;
|
||
padding: 0.5em;
|
||
text-align: left;
|
||
}
|
||
|
||
._select ._pop._top {
|
||
justify-content: flex-end;
|
||
flex-direction: column-reverse;
|
||
}
|
||
|
||
._select ._pop._top ._list._flex {
|
||
/* flex-direction: column-reverse; */
|
||
}
|
||
|
||
._select ._pop ._list._flex {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
overflow: auto;
|
||
text-align: left;
|
||
overscroll-behavior-y: contain !important;
|
||
background: var(--bg2);
|
||
}
|
||
|
||
._select ._item._select {
|
||
font-weight: bold;
|
||
color: var(--man6);
|
||
}
|
||
|
||
._select ._pop ._list._flex ._item {
|
||
padding: 1em 1em;
|
||
width: 100%;
|
||
border-top: 1px solid var(--bg4);
|
||
}
|
||
|
||
._select ._pop ._list._grid {
|
||
display: grid;
|
||
width: 100%;
|
||
overflow: auto;
|
||
text-align: left;
|
||
background: var(--bg2);
|
||
}
|
||
|
||
._select ._pop ._list._grid ._item {
|
||
padding: 1em 1em;
|
||
width: 100%;
|
||
border-top: 1px solid var(--bg4);
|
||
}
|
||
|
||
._select ._pop._top ._list._grid {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
._select ._pop._top ._list._grid ._item {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
._select ._pop ._list ._nodata {
|
||
background: var(--bg2);
|
||
text-align: center;
|
||
padding: 2em 0 1em 0;
|
||
color: var(--txt1);
|
||
}
|
||
|
||
._select {
|
||
position: relative;
|
||
}
|
||
|
||
._select ._show {
|
||
padding: 0;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
}
|
||
|
||
._select ._show._left {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
._select ._show._left ._txt {
|
||
text-align: left;
|
||
}
|
||
|
||
._select ._show ._arrow {
|
||
width: 1.5em;
|
||
height: 1.5em;
|
||
margin-left: 0.3em;
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
._select ._show ._arrow._sel {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
._select ._mask {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
backgroundx: #f3abec99;
|
||
z-index: 992;
|
||
backdrop-filterx: blur(10px);
|
||
}
|
||
</style> |