嘿,今天是怎样?
都没有人交作业,是不是昨天的太小菜一叠了!
今天是昨天的延伸,
但说难也难不到哪里去啦~
因为相信经过前面的两天,
应该已经很清楚步骤了吧?
跟前两天的相同,兔兔还是重新建立了一个专案,你们就看自己的决定罗,前置准备跳过!
首先,在专案里的 ./src/components
资料夹中新增一个 AccordionMenuItem.vue
的元件:
完成後,增添以下内容:
<template>
</template>
<script>
export default {
name: "AccordionMenuItem",
}
</script>
一样,把元件新增到画面中,不过因为最後呈现出来的效果差异,所以我 App.vue
的样式稍微修改了一下:
<template>
<div :class="[
'w-screen h-screen',
'flex flex-col',
'items-center',
'pt-5'
]"
>
<AccordionMenuItem />
</div>
</template>
<script>
import AccordionMenuItem from './components/AccordionMenuItem.vue'
export default {
data() {
return {
}
},
components: {
AccordionMenuItem,
}
}
</script>
OK,前面不重要的部分终於完成了
我们快速前进下一步骤!
在开始之前,我们可以先参考一下一般的手风琴选单是怎麽设计的:
(找不到好的图,这张很小很模糊,抱歉。)
经过眼睛一眨 (?) 可以立马归纳出几点,就是选单项目左边是字
,右边是箭头 icon
,然後项目的内容是可以被展开来显示的
,也可以再收起来
。
那我们一步骤一步骤来!
其实我们只要完成一个项目就好了,
其他的项目用回圈来完成。
首先,先来做出打开後的样子:
<template>
<div class="w-80">
<label
:class="[
'px-4 py-2',
'border border-gray-300',
'hover:bg-gray-100',
'flex justify-between items-center',
'cursor-pointer',
'transition-all',
]"
>
<div>
选项
</div>
<div
:class="[
'w-9 h-9',
'hover:bg-gray-200',
'rounded-full',
'flex justify-center items-center',
'transition-all',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor"
:class="[
'h-6 w-6',
'transition-all'
]"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</label>
<div
:class="[
'border border-t-0 border-gray-300',
'overflow-hidden transition-all'
]"
>
<div class="p-4 text-gray-600">
内容
</div>
</div>
</div>
</template>
<script>
export default {
name: "AccordionMenuItem",
}
</script>
这样就有选单的雏形了!
资讯量可能较庞大了点,但是仔细看就会发现其实结构并不复杂~
实际上我们简化一下,结构是这样的:
<!-- 整个元件包裹起来 -->
<div>
<!-- 选单的选项 -->
<label>
<!-- 选项标题 -->
<div>
选项
</div>
<!-- 右边的 icon 区块 -->
<div>
<svg />
</div>
</label>
<!-- 选单内容区域 -->
<div>
<!-- 选单实际内容 -->
<div>
内容
</div>
</div>
</div>
是不是其实不复杂呢?
没有问题的话,我们准备开始让它动起来罗!
我们要先来完成的,就是选单收合的问题。
选单收合的实现,我们可以依靠 <input>
元素来完成。运用 <input>
元素且把类型设定成 checkbox
,我们就可以简单的记录开启 / 关闭的状况,也可以用 <label>
来触发 <input>
元素的状态改变,这样就可以少写很多 JS 的 onclick 了~
所以最基本的,把 <input>
加在 <label>
元素之中,且用运 tailwind 所提供的特别样式 sr-only
来隐藏踪迹:
<div class="w-80">
<label
:class="[
'px-4 py-2',
'border border-gray-300',
'hover:bg-gray-100',
'flex justify-between items-center',
'cursor-pointer',
'transition-all',
]"
>
+ <input type="checkbox" class="sr-only" />
<div>
选项
</div>
<div
:class="[
'w-9 h-9',
'hover:bg-gray-200',
'rounded-full',
'flex justify-center items-center',
'transition-all',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor"
:class="[
'h-6 w-6',
'transition-all'
]"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</label>
<div
:class="[
'border border-t-0 border-gray-300',
'overflow-hidden transition-all'
]"
>
<div class="p-4 text-gray-600">
内容
</div>
</div>
</div>
加好之後,我们要在 vue 中使用 v-model 同步 <input>
的状态并用变数记录起来:
<template>
<div class="w-80">
<label
:class="[
'px-4 py-2',
'border border-gray-300',
'hover:bg-gray-100',
'flex justify-between items-center',
'cursor-pointer',
'transition-all',
]"
>
+ <input type="checkbox" class="sr-only" v-model="checked" />
<div>
选项
</div>
<div
:class="[
'w-9 h-9',
'hover:bg-gray-200',
'rounded-full',
'flex justify-center items-center',
'transition-all',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor"
:class="[
'h-6 w-6',
'transition-all'
]"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</label>
<div
:class="[
'border border-t-0 border-gray-300',
'overflow-hidden transition-all'
]"
>
<div class="p-4 text-gray-600">
内容
</div>
</div>
</div>
</template>
<script>
export default {
name: "AccordionMenuItem",
+ data() {
+ return {
+ checked: false,
+ }
+ },
}
</script>
加好之後,我们把 checked 变数的状态应用到以下三处:
checked && 'text-green-500'
和 transition-all
checked && '-rotate-180'
checked ? 'max-h-[300px]' : 'max-h-0'
(要使用最大高度或宽度,这样不定宽度长度的内容才可以有过渡效果。)
那,就会是这个样子:
<div class="w-80">
<label
:class="[
'px-4 py-2',
'border border-gray-300',
'hover:bg-gray-100',
'flex justify-between items-center',
'cursor-pointer',
'transition-all',
]"
>
<input type="checkbox" class="sr-only" v-model="checked" />
<div
:class="[
checked && 'text-green-500',
'transition-all'
]"
>
选项
</div>
<div
:class="[
'w-9 h-9',
'hover:bg-gray-200',
'rounded-full',
'flex justify-center items-center',
'transition-all',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor"
:class="[
'h-6 w-6',
checked && '-rotate-180',
'transition-all'
]"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</label>
<div
:class="[
checked ? 'max-h-[300px]' : 'max-h-0',
'border border-t-0 border-gray-300',
'overflow-hidden transition-all'
]"
>
<div class="p-4 text-gray-600">
内容
</div>
</div>
</div>
有~ 非常好的效果~
但回想起在制作按钮时的状况就会不禁的觉得 ...
没错,选项中的内容替换还不方便!
所以我们当然就要用到 props 和 slot 啦!
我们一样先来解决 slot 的部分吧~
为了达到超灵活的替换,我们要来做巢状 slot!
这是目前的样子:
<!-- 选项内容区块 -->
<div
:class="[
checked ? 'max-h-[300px]' : 'max-h-0',
'border border-t-0 border-gray-300',
'overflow-hidden transition-all'
]"
>
<div class="p-4 text-gray-600">
内容
</div>
</div>
那现在终於可以来解释为什麽选项内容区块内又有一个 div,而且内距还是加在里面那个 div 上了。
因为啊 ...
「因为什麽啦 ... ? 兔兔快说!」
因为这是我为了 slot 而预留的!
如果今天我们要做的是像原本的参考图这样子的话:
你可以注意到选单中的子选项是和外部没有空隙的
,所以如果把内距加在外层,那麽里面若是用 slot 插入其他元件时,就会留一个空隙
在那边; 相反的,如果是直接写文字在其中
,为了还要能插入元件而去掉内距,文字和边框看起来又会太过接近
,很丑。
所以我们这边要用具名的巢状 slot
!
废话不多说,我直接上范例:
<div
:class="[
checked ? 'max-h-[300px]' : 'max-h-0',
'border border-t-0 border-gray-300',
'overflow-hidden transition-all'
]"
>
<slot name="itemContent">
<div class="p-4 text-gray-600">
<slot name="itemText">
内容
</slot>
</div>
</slot>
</div>
这样做很有趣哦!
如果我们只是想要在选项中加入纯文字内容时,使用时只需要指名插槽 itemText
:
<!-- 使用时 -->
<AccordionMenuItem>
<template v-slot:itemText>
纯文字选项内容
</template>
</AccordionMenuItem>
实际上渲染出来的内容就是这样:
<div class="max-h-[300px] border border-t-0 border-gray-300 overflow-hidden transition-all">
<div class="p-4 text-gray-600">
纯文字选项内容
</div>
</div>
但是,
如果今天我们要加入的是其他元件,我们只需要指定插槽名称为 itemContent
:
<!-- 使用时 -->
<AccordionMenuItem>
<template v-slot:itemContent>
<v-link>连结 1</v-link>
<v-link>连结 2</v-link>
<v-link>连结 3</v-link>
</template>
</AccordionMenuItem>
实际上渲染出来的内容就是这样:
<div class="max-h-[300px] border border-t-0 border-gray-300 overflow-hidden transition-all">
<v-link>连结 1</v-link>
<v-link>连结 2</v-link>
<v-link>连结 3</v-link>
</div>
这样,不就能无痛解决那个空隙的问题了吗?
是不是超级好玩的!
我每次写起来都觉得很兴奋呢~
那内容替换的问题解决了,我们处理 props 的部分啦!
那 Props 部分我们目前只需要传入项目名称而已,所以就增加吧!然後记得,要有预设内容哦:
<script>
export default {
name: "AccordionMenuItem",
props: {
itemName: {
default: "选项",
}
},
data() {
return {
checked: false,
}
},
}
</script>
然後,记得把 props 的内容应用到 template 上:
<div class="w-80">
<label
:class="[
'px-4 py-2',
'border border-gray-300',
'hover:bg-gray-100',
'flex justify-between items-center',
'cursor-pointer',
'transition-all',
]"
>
<input type="checkbox" class="sr-only" v-model="checked" />
<div
:class="[
checked && 'text-green-500',
'transition-all'
]"
>
{{ itemName }}
</div>
<div
:class="[
'w-9 h-9',
'hover:bg-gray-200',
'rounded-full',
'flex justify-center items-center',
'transition-all',
]"
>
...
那这样,感觉都完成了~
我们就快来测试吧!
为了测试,兔兔这边已经先写好两组资料了~大胆的拿去用吧!
list: {
groupName: "Abouts",
items: [
{ name: "关於兔兔教", content: "不是邪教,但不太正常 (?)。 不过可以为你在此献上教义 ... (住嘴!)"},
{ name: "关於 Tailwind CSS", content: "超赞了啦,不用真是太可惜了!"},
{ name: "关於手风琴", content: "流浪到淡水时有机会可以看到。"},
{ name: "关於兔兔", content: "你想知道的太多了,去掷筊问神吧!!!"}
]
},
faq: {
groupName: "FAQ",
links: [
{ name: "四大超商", contents: [
"7-11","全家","莱尔富","OK",
]},
{ name: "付款方式", contents: [
"现金","ATM","信用卡","LINE PAY","五倍券",
]},
{ name: "取货方式", contents: [
"宅配","超商取货"
]},
]
}
加到 App.vue
的 data 中之後,我们就来开心快乐测试元件吧!
首先是用关於我们
的资料,看资料的内容会发现只是纯文字,所以我们就用 v-for 来遍历内容,然後把 content 差在指定插槽 itemText
吧:
<AccordionMenuItem
v-for="item in list.items"
:key="list.groupName.concat('-',item.name)"
:itemName="item.name"
>
<template v-slot:itemText>
{{ item.content }}
</template>
</AccordionMenuItem>
有,马上就像样了~
趁着手感还没消失把第二个也做出来吧!
第二个我们就需要自己写一个像清单的外框,然後指定插槽到 itemContent
:
<AccordionMenuItem
v-for="link in faq.links"
:key="faq.groupName.concat('-',link.name)"
:itemName="link.name"
>
<template v-slot:itemContent>
<div
:class="[
'p-3',
'first:border-0 border-t',
'hover:bg-gray-100 transition-all'
]"
v-for="content in link.contents"
:key="content"
>
{{ content }}
</div>
</template>
</AccordionMenuItem>
就会像这样了! 完成~
超级方便的对吧? 对吧?
我想你一定也觉得很方便,那就拿去用吧~
(欸你这兔,少强迫推销了)
好,那麽今天的部分结束罗~
下一个元件是 ... 简易日历!
有没有觉得越来越难呀?
「没有!」
没有吗 ... 非常好!
那就把作业交上来吧 XD
关於兔兔们:
( # 兔兔小声说 )
听说兔兔这礼拜要去聚会,
不知道有看到兔兔真面目的朋友会不会失望?
来找设计师一起 side project,前後端 / UIUX 皆可ㄛ。配对单连结: https:...
连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...
D1-第零周:心态培养 这周是通知录取课程到课程正式开始之前的准备时间,这时间点只能看到第五期课...
问题回答 懒加载路由或元件的意思是当访问该路由,或需要显示该元件时,才载入该路由或元件。这做法会提升...
现在不管是学校课程规划或是同学主动想要了解职场,对於实习其实是一个可以看清自己的能力跟业界之间的差距...