记账app_8_组件封装
Contents
1 Button组件
1.1 Icon
Icon组件接受svg的id作为参数,所以需要让definedComponent方法与TypeScript都知道此组件的参数及类型
|
|
小细节:如果在使用Icon组件时添加了class属性,那么会将这个class赋值给内部的svg标签,而name属性则自动赋值给了svg标签内部的use标签,这些都是vue自动区分的,如果使用react则需我们自行区分
1.2 Button
- 若要自定义样式,可以在Button组件添加;若要调整布局相关样式,可以在Button外包裹一层div.button_wrapper
|
|
-
总之为了方便Button应用于不同场景,所以只在Button组件中规定基本样式即可
-
在Button组件中并未定义onClick事件,但在使用时可以直接传入onClick事件,且能够成功触发
-
这是因为在Vue中,如果给组件绑定了onClick事件,那么就会默认透传给组件内部的第一层元素
-
此时的报错是TypeScript报错,只需在defineComponent传入props类型即可解决:
-
|
|
1.3 FloatButton
直接在Icon组件的基础上包装一下即可
实现自定义图标,因此需要接受iconName参数,其类型与Icon的name属性一致,为避免重复,我们在Icon中将类型导出以复用
|
|
2 Center组件
-
Center组件实质上就是让父组件传入的元素水平垂直居中(flex)
-
支持多元素居中方向的配置(flex的方向)
|
|
Center.module.scss
|
|
3 Navbar组件
- Navbar设置两个插槽,分别表示图标与标题
|
|
-
样式方面,由于不方便给插槽内部的图标写样式,索性就规定icon_wrapper的大小,具体样式在父组件使用插槽传递具体图标时再定义
-
使用
|
|
4 Overlay组件
- Overlay组件主要由主体和遮罩两部分构成
|
|
- 给Icon绑定onClick事件,如果在props中声明了就只能手动绑定了(与按钮不同)
|
|
-
单击菜单按钮触发Overlay的显示,单击遮罩层隐藏,利用props实现子父组件通信(too React)
-
基本样式
-
mask与overlay定位均是
absolute
,left为0,距离top保留一段安全高度 -
宽度
100%
,高度100%-顶部安全距离
(摄像头) -
overlay的z-index要大于mask
-
5 Tabs组件
5.1 创建items相关路由
-
/items/create
:/components/item/ItemCreate.tsx -
/item
:/components/item/ItemList.tsx -
views/ItemPage.tsx
|
|
5.2 封装MainLayout组件
绝大部分页面的结构都是上下结构,上部Navbar,下部是页面内容,因此可以将其抽离为MainLayout组件,以StartPage为例,将title、icon和主体设置为三个插槽
|
|
5.3 设计Tabs Api
分为外部的Tabs组件和其内部的Tab组件
Tabs组件接收selected与onChange属性,且内部元素只允许为Tab组件
Tab组件接收name属性
|
|
5.4 完成Tabs
- Tab比较简单,将传入的元素作为插槽返回即可。记得声明name属性
- Tabs通过
context.slots.default()
拿到内部元素(数组),遍历并判断其type
属性是否为Tab,若不是,throw 一个运行时错误
|
|
-
实现点击切换tab名称
- 给li元素绑定onClick和selected样式,onClick事件即调用父组件传来的props.onChange方法,将新的TabName传给父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
return ( // 这里没有返回函数是因为已经将tsx与逻辑放入setup return的函数中了 <div class={s.tabs}> <ol class={s.tabs_nav}> {nodeArr.map((item) => ( <li class={item.props?.name === props.selected ? s.selected : ""} onClick={() => props.onChange?.(item.props?.name)} > {item.props?.name} </li> ))} </ol> <div></div> </div> );
- v-model实现,区别在于使用时无需定义onChange的回调函数,以及tab的单击事件所调用的方法(props => context.emit)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Tabs.tsx触发v-model的update <ol class={s.tabs_nav}> {nodeArr.map((item) => ( <li class={item.props?.name === props.selected ? s.selected : ""} onClick={() => context.emit("update:selected", item.props?.name)} > {item.props?.name} </li> ))} </ol> // 使用v-model const refSelected = ref("支出"); ... <Tabs v-model:selected={refSelected.value}> <Tab name="支出">icon列表</Tab> <Tab name="收入">icon列表2</Tab> </Tabs> ...
-
展示Tab内容
- 重点在于获取到子组件(Tab)的子组件(插槽)
- 通过log大法,根据item.props.name找到并直接渲染子组件即可
1
<div>{nodeArr.find((item) => item.props?.name === props.selected)}</div>
-
完善样式
- tabs_nav左右布局并居中
1 2 3 4 5 6 7 8 9 10 11
&_nav { // 左右并居中 display: flex; justify-content: space-between; align-items: center; text-align: center; color: var(--nav-text-color); li { flex-shrink: 0; flex-grow: 1; ...
- 选中状态,下部有白色横线标识
1 2 3 4 5 6 7 8 9 10 11 12 13
&.selected { position: relative; // 选中 下边框变白 &::after { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 4px; background-color: var(--tabs-indicator-bg); } }
6 数字按键
- 下方数字按键包括两部分,上半部分是日期选择器及数字显示,下半部分是数字按键
6.1 grid布局制作数字按键
- 数字按键采用grid布局,通过子类选择器
&:nth-child(n)
进行area的指定
|
|
- tsx中,利用map遍历buttons
|
|
6.2 利用Vant UI制作日期选择器
- 安装Vant与Vite按需引入配置
|
|
- 完善日期选择器功能
|
|
- 样式
|
|
6.3 完善数字按键功能
- 数字输入的逻辑主要是围绕
0
和.
这两个字符- 先判断位数(能提前return的逻辑就放到前面)
- 如果输入的是
.
,则去看前面存不存在.
,如果存在则不生效 - 如果输入的是0,则去看值是不是0(初始状态),如果值为0则不生效
- 如果输入的是0-9,则将初始值置为空(去掉0)
- 最后完成赋值操作即可
|
|
7 表单组件
7.1 创建Tag页面
- 快速创建100个li,内容为1~100
li{$}*100
- routes
|
|
- views/TagPage.tsx
|
|
- components/tag/TagCreate.tsx
- 使用MainLayout.tsx,主体分为四部分,利用四个div完成布局
|
|
7.2 TagCreate样式
- 控制emoji显示行数为12,只需规定外面ol的行高(line-height)与高度(height)的关系即可
|
|
- form表单项元素在命名class时要拼接上formItem关键字,方便样式定义
- 例如输入框及错误提示
|
|
-
CSS变量排序
-
对CSS变量进行排序,以防止变量重名等bug
-
选中全部变量,cmd+shift+p 输入sort,选择按升序/降序排列
-
7.3 利用js爬取页面数据
获取全部emojiList数据
主要是利用DOM Api以及数组的方法来分析及处理emoji表格数据,主要用到的API如下
|
|
7.4 封装EmojiList组件
- 利用
reactive
与v-model
关联input与表单数据
|
|
-
创建EmojiList组件,原来父元素的样式保留,将内部元素抽取为tsx组件
-
获取到emojiList数据后,遍历渲染在页面中即可
-
这里有个细节,如果将emoji的遍历过程抽离出来,那么遍历渲染的方法所处的位置很关键
-
如果放在return函数前,由于
setup
中除return函数之外,其余代码只运行一次,所以emoji只会计算一次,不会随着系列的改变而改变 -
可以将方法放在return函数中,这样每次渲染都会重新计算渲染
1 2 3 4 5 6
setup(){ return () => { const emojis = {...} return <div>{emojis}</div> } }
- 可以将方法用computed包裹,vue会自动跟踪其依赖(性能更好)
1 2 3 4
setup(){ const emojis = computed({...}) return () => <div>{emojis.value}</div> }
-
-
绑定emoji的导航栏选中事件及选中样式
|
|
- 绑定emoji选中事件及选中样式
- 选中后需要与父组件的表单数据进行交互,因此需要进行双向数据绑定
|
|
7.5 设计表单验证器
定义规则与校验函数,在onSubmit回调中校验
|
|
7.6 实现表单验证器
-
初步定义类型
-
定义表单数据的类型
|
|
- 定义规则数据的类型
|
|
- 实际上
FData
中的key和Rule与Errors
中的key都是有关联的 - 通过泛型添加
FData
、Rule
与Errors
的关联
|
|
- 实现校验逻辑
|
|
- 使用校验方法,注意类型定义与占位的细节
|
|
8 页面开发
8.1 制作ItemCreate页面
- 将布局改为flex(原来的inputPad是fixed布局),nav与inputPad固定高度,中间Tab页自适应,Tab组件的nav的布局使用sticky,防止其跟随内容滑动
|
|
8.2 制作TagEdit页面
-
由于TagEdit页面与TagCreate页面都有form表单,区别仅在于页面标题与下部按钮
-
两个页面的公共样式抽离为Tag.module.scss
-
将form抽离为TagForm组件
|
|
- 扩展Button组件,支持按钮类型指定
|
|
- 扩展MainLayout组件,保证nav不随页面滚动(
position: sticky
)
|
|
8.3 制作ItemList页面
- 使用MainLayout制作列表页
8.3.1 Tabs组件支持classPrefix
-
设计稿中的Tabs是靠左对齐的,而Tabs组件默认是居中分布的
-
索性让Tabs组件支持类名前缀(classPrefix),开发者可以自定义样式来覆盖原始样式
|
|
由于使用的是CSS Modules,会导致在拼接类名时会存在随机字符串,使得自定义样式无法准确匹配到类名
|
|
-
所以可以使传入的classPrefix为固定的字符串,而不是s.xxx(放弃使用CSS Module)
-
此时自定义样式就必须写在全局样式中(App.scss)
|
|
8.3.2 重构Time类
值得一提的就是js对于月份加1,也就是所谓下个月的概念与我们正常生活中提到的下个月不一致,解决思路如下:
|
|
8.3.3 itemSummary组件
-
组件设计
- 接受startDate与endDate字段,展示页面内容
-
组件实现 略
8.3.4 封装Form和FormItem组件
-
自定义时间Tab页内部弹窗表单开发
-
设计Form Api
|
|
- 实现Form与FormItem组件
|
|
- 应用Form
|
|
8.4 封装弹出菜单
8.4.1 更新v-model的使用
-
详情见Vue3组件通信一文
-
改造Tabs组件、等对于v-model的使用方案
8.4.2 封装OverlayIcon组件
- 单击菜单图标会弹出浮窗组件,我们将图标、浮窗及相关逻辑抽离为OverlayIcon组件
- 先在StartPage页面下手,将浮窗与图标放在一起,再进行相关视图与逻辑的抽离
|
|
- 在StartPage中Mainlayout的icon插槽中引入并使用即可
- 当在ItemList中使用时,由于
position: sticky
属性会影响z-index,因此我们需要重新设计Overlay组件的z-index
|
|
8.5 制作登录页面
写路由,MainLayout写页面,过程略
封装validationCode表单项,在FormItem组件中添加此分支
8.6 制作echarts图表
8.6.1 封装TimeTabsLayout组件
统计图表页面与ItemList页面唯一的区别就在于每个Tab中的组件内容不同,其他均相同
- 在定义传入组件的类型时,可以在Tabs中声明一个子组件的类型(TImeTabsProps)供父组件完善TS支持
|
|
- 此时ItemList页面就可以重构为
|
|
8.6.2 实现select控件
设计Select Api(作为FormItem的类型之一)
|
|
实现
|
|
8.6.3 引入Echarts
- 安装
pnpm i echarts@5.3.2
- 使用
|
|
Author gsemir
LastMod 2022-06-09