Selectbox component
component .vue파일
<template>
<div class="m_selectbox_cm">
<button class="multi_del" v-if="info.multiple && info.multitxt.length > 1"
@mouseenter="info.multidel = true" @mouseleave="info.multidel = false"
@click="multiDel">
<img src="@/assets/images/icon_close.svg" alt="">
</button>
<a role="button" tabindex="1" :id="id" class="select"
@click="showList" @blur="optionHide"
@mouseenter="info.blurbo = false" @mouseleave="info.blurbo = true">
{{ info.multiple ? info.selecmulti : info.selecone }}
</a>
<div class="optionwrap" v-if="info.showopt">
<div class="optionslist">
<button class="opt" :class="{ on: seleclass(list.value) }" v-for="(list, idx) in props.options" :key="idx"
@click="changeVal(list)" @blur.self="optionHideSend"
@mouseenter="info.blurbo = false" @mouseleave="info.blurbo = true">
{{ list.txt }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const props = defineProps({
options: Object,
id: String,
val: undefined,
multi: false,
closeoption: false
})
const emit = defineEmits(['change'])
const info = ref({
showopt: false,// 옵션 목록 보이기 여부
multiple: false,// 여러개 선택 여부
multitxt: [],// 여러개 선택 시 문구
// 선택한 옵션
selecone: computed(() => {
return props?.options?.filter(d => d.value === props?.val)[0]?.txt
}),
selecmulti: computed(() => {
return info.value.multitxt.length > 1 ?
info.value.multitxt[0] + ", + " + (info.value.multitxt.length - 1) :
info.value.multitxt[0]
}),
// 여러개 옵션일 때 옵션 영역 바깥 클릭 시 옵션 닫기
blurbo: true,
// 옵션 닫기
selectoption: false,
// 선택한 옵션 초기화
multidel: false,
})
info.value.selectoption = props.closeoption
// 선택된 옵션(단일, 여러개)
const selectoption = ref([])
selectoption.value = props.val
// 여러개 선택
info.value.multiple = props.multi
// 여러개 선택일 때 내용 입력
if(info.value.multiple) {
selectoption?.value.filter((val) => {
props.options.filter((val2) => {
if(val === val2.value) {
info.value.multitxt.push(val2.txt);
}
})
})
}
// 선택된 옵션. 클래스 적용
const seleclass = computed(() => {
return (val) => {
return info.value.multiple ?
selectoption.value.filter((va) => va === val).length : // 옵션이 여러개 일 때
selectoption.value === val ? true : false // 단일 옵션일 때
}
})
watch(props, (e) => {
if(e.val) {
selectoption.value = e.val
}
if(e.multi) {
info.value.multiple = true
}
if(e.closeoption) {
info.value.selectoption = true;
info.value.showopt = false
}
})
watch(() => info.value.selectoption, (nv, ov) => {
if(info.value.selectoption) {
info.value.selectoption = false;
info.value.showopt = false
}
})
// 옵션 목록 보기(true / false)
const showList = () => {
info.value.showopt = !info.value.showopt
}
// 옵션 선택
const changeVal = (n) => {
info.value.selectclose = true;
// 여러개 선택 시
if(info.value.multiple) {
// 옵션의 값이 0, '0', '', null 이 아닐 때
if(n.value !== 0 && n.value !== '0' && n.value !== '' && n.value !== null) {
// value, text
if(selectoption.value.indexOf(n.value) < 0) {//(info.value.multitxt.indexOf(n.txt) < 0)
selectoption.value.push(n.value)
info.value.multitxt.push(n.txt)
} else {
selectoption.value.splice(selectoption.value.indexOf(n.value), 1)
info.value.multitxt.splice(info.value.multitxt.indexOf(n.txt), 1)
}
}
// 옵션 선택 시 기본 옵션 제거
info.value.multitxt.some((ele, idx) => {
if(props.options[0].txt === ele) {
info.value.multitxt.splice(idx, 1);
selectoption.value.splice(idx, 1);
}
})
// // 선택된 옵션이 다 없어지지 않게 마지막 옵션이 선택되게
// if(selectoption.value.length === 0) {
// selectoption.value.push(n.value)
// info.value.multitxt.push(n.txt)
// }
// 선택된 옵션이 다 없어지면 기본 옵션이 선택되게
if(selectoption.value.length === 0) {
selectoption.value.push(props.options[0].value);
info.value.multitxt.push(props.options[0].txt);
}
}
// 단일 선택 시
else {
info.value.showopt = false
emit('change', n)
}
}
// 옵션(버튼) 밖 클릭 시
const optionHideSend = () => {
if(info.value.multiple && info.value.blurbo && !info.value.multidel) {
info.value.showopt = false;
emit('change', selectoption.value)
}
}
// 선택 버튼 밖 클릭 시
const optionHide = () => {
if(info.value.blurbo) {
info.value.showopt = false;
}
}
// 여러개 선택 시 두개 이상 선택했으면 선택목록 초기화 버튼 보이기
const multiDel = () => {
selectoption.value = [props.options[0].value];
info.value.multitxt = [props.options[0].txt];
info.value.multidel = false;
optionHideSend();
}
</script>
<style scoped src="./SelectboxCm.scss"></style>
<!--
// iOS에서는 <button>에 focus가 안되어서 <a role="button" tabindex="1"> 이런식으로 변경
// 부모 컴포넌트에서 사용 시
# options : selectbox의 option 값. options.value, options.txt
# val : 보여지는 option 값
# multi : 여러 옵션 선택( :multi="true" )
##== multi ref: ["opt1", "opt2", ...]
<SelectboxCm :options="[{value: 'value값', txt:'txt문구'}]" :val="미리 보여지는 option값" />
ex : <SelectboxCm :options="[{ value: 'm1', txt: 'ㅇㅇㅇ' }, { value: 'm2', txt: 'ㅍㅍㅍ' }, { value: 'm3', txt: 'ㅎㅎㅎ' }]" @change="selecManager" />
-->
불러오는 .vue 파일에
<template>
...
<Selectbox :options="info.opt" :val="info.selectedoption" @change="selec" />
...
<SelectboxCm :multi="true" :options="info.optmulti" :val="info.selecmulti" @change="selecMulti" />
...
</template>
<script setup>
import { ref } from "vue";
import Selectbox from "@/components/Selectbox.vue";
//
const info = ref({
opt: [
{ value: "", txt: "전체" },
{ value: "opt1", txt: "내용1" },
{ value: "opt2", txt: "내용2" },
{ value: "opt3", txt: "내용3" }
],
selectedoption: "opt1",
...
optmulti: [
{ value: "", txt: "전체" },
{ value: "optm1", txt: "내용11" },
{ value: "optm2", txt: "내용22" },
{ value: "optm3", txt: "내용33" }
],
selecmulti: ["optm1"]
})
//
const selec = (e) => {
info.value.selectedoption = e.value
console.log(info.value.selectedoption, e.txt);
}
...
//
const selecMulti = (e) => {
info.value.selecmulti = e;
console.log(info.value.selecmulti);
}
참고용 css
.m_selectbox_cm {
position: relative;
min-width: 12rem;
height: 3.2rem;
border: 1px solid $c_dc;
border-radius: 0.4rem;
&::after {
content: "";
margin: auto;
position: absolute;
top: 0.3rem;
right: 1.2rem;
bottom: 0;
width: 0.9rem;
height: 0.5rem;
background: url(@/assets/images/icon_select.svg) no-repeat center;
pointer-events: none;
}
.select {
padding: 0 3.2rem 0 1.6rem;
width: 100%;
height: 100%;
line-height: 3rem;
font-size: 1.2rem;
color: $c_42;
text-align: left;
font-weight: 500;
letter-spacing: -0.02em;
cursor: pointer;
}
.optionwrap {
position: absolute;
top: 4.2rem;
left: -1px;
display: flex;
flex-direction: column;
align-items: flex-start;
width: calc(100% + 2px); // m_selectbox_cm의 border값 포함하려고
box-shadow: 0 0 1.2rem rgba(20, 82, 143, 0.2);
border-radius: 0.8rem;
z-index: 9;
.optionslist {
width: 100%;
max-height: 20rem;
overflow-y: auto;
background-color: #fff;
border-radius: 0.8rem;
}
.opt {
padding-left: 2rem;
position: relative;
width: 100%;
height: 3.2rem;
font-size: 1.4rem;
font-weight: 500;
color: $c_82;
line-height: 3.2rem;
text-align: left;
~ .opt {
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background-color: $c_f1;
}
}
&.on {
color: $c_pri;
background-color: #f6faff;
}
&:hover {
background-color: #f6faff;
}
}
}
&.middle {
height: 4.4rem;
border-radius: 0.8rem;
.select {
padding: 0 3rem 0 2rem;
line-height: 4.2rem;
font-size: 1.4rem;
}
.optionwrap {
top: 5.4rem;
.opt {
height: 4.4rem;
line-height: 4.4rem;
}
}
}
&.middle48 {
min-width: 17rem;
height: 4.8rem;
border-radius: 0.8rem;
.select {
padding: 0 3rem 0 2rem;
line-height: 4.6rem;
font-size: 1.4rem;
}
.optionwrap {
top: 5.8rem;
.opt {
height: 4.8rem;
line-height: 4.8rem;
}
}
}
~ .m_selectbox {
margin-left: 1rem;
}
&.big {
height: 5.2rem;
min-width: 20rem;
border-radius: 0.8rem;
.select {
padding: 0 3.5rem 0 1.9rem;
}
&::after {
right: 2rem;
}
&.more {
height: 5.6rem;
.optionwrap {
top: 6.6rem;
.opt {
height: 5.6rem;
line-height: 5.6rem;
}
}
}
.optionwrap {
top: 6.2rem;
.opt {
height: 5.2rem;
line-height: 5.2rem;
}
}
}
&.noact {
background-color: $c_f1;
.select {
color: $c_82;
}
&::after {
color: $c_d9;
}
}
&.noborder {
min-width: 8.4rem;
border: none;
&::after {
right: 0.4rem;
}
.select {
padding-left: 0;
padding-right: 1.6rem;
line-height: 3.2rem;
}
.optionwrap {
left: -0.8rem;
top: initial;
bottom: 4.4rem;
.optionslist {
min-width: 9.4rem;
max-height: 16rem;
}
.opt {
padding-left: 1rem;
font-size: 1.2rem;
letter-spacing: -0.24px;
}
}
}
.multi_del {
margin: auto;
padding: 0 1rem;
position: absolute;
top: 0;
bottom: 0;
right: 0.2rem;
z-index: 1;
}
}
Selectbox component
끝.