【山竹记账前端-vue】13.制作两个页面:记一笔 ItemCreate、TagEdit
大纲链接 §
[toc]
1. 创建列表 ⇧
创建列表数据结构 ⇧
- 记账项目数据格式
{id: 1, name: 'xx', sign: '💰', category: 'incomes'}- 类型尽量不用
type作为属性,它是很多编程语言的内置关键字
- 类型尽量不用
@/views/ItemCreate.tsx
|
|
完善 TagList 页面样式 ⇧
@/views/ItemCreate.tsx
|
|
- 添加tags标签数量,查看是否样式重叠
解决重叠问题 ⇧
@/components/tab/SwitchTabs.tsx
- 添加根标签
- 使用黏着定位
<nav class="sticky left-0 top-0 z-1"> 父元素添加样式
<div class="grow-1 shrink-1 overflow-auto scrollbar-hide relative">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60import { defineComponent, type PropType } from 'vue' import { ItemTab } from '@/components/tab/ItemTab.tsx' import { cn } from 'cn' export const SwitchTabs = defineComponent({ name: 'SwitchTabs', props: { selected: { type: String as PropType<string>, }, }, setup(_props, _ctx) { const selectedClass = (tabName: string) => _props.selected === tabName ? cn( 'relative', `after:(content-[""] block absolute bottom-0 left-0 w-full h-4px bg-[var(--tabs-indicator-bg)])`, ) : '' return () => { const defaultSlotTabs = _ctx.slots?.default?.() ?? [] const ifAllItemTab = defaultSlotTabs.every( (slot) => slot.type === ItemTab && !!slot.props?.tabName, ) if (!ifAllItemTab) { throw new Error(`SwitchTabs: 子组件必须是 ItemTab,且必须有 tabName 属性`) } return ( <div class="grow-1 shrink-1 overflow-auto scrollbar-hide relative"> {/* 切换标签 */} <nav class="sticky left-0 top-0 z-1"> <menu class="flex items-center text-color-[var(--navbar-text-color)]"> {defaultSlotTabs.map((slot, index) => ( <li key={index + slot?.props?.tabName} class={cn( 'grow-1 shrink-0 text-center', 'p-y-12px bg-[var(--navbar-bg-end)]', selectedClass(slot?.props?.tabName), )} onClick={() => { _ctx.emit('update:selected', slot?.props?.tabName) }}> {slot?.props?.tabName} </li> ))} </menu> </nav> {/* 标签内容-插槽-数组 */} <section> {defaultSlotTabs.find((slot) => slot.props?.tabName === _props.selected)} </section> </div> ) } }, })
封装记账图标标签组件组件 ⇧
@src/modules/tags/IconTag.tsx
|
|
重构创建标签页面
@/views/ItemCreate.tsx
|
|
@/views/ItemCreate.tsx使用数组循环遍历,生成节点
|
|
- 报错
SwitchTabs: 子组件必须是 ItemTab,且必须有 tabName 属性 - 原因是使用数组循环遍历的节点都是包一层
fragment,内部不是直接ItemTab组件了 - 在
SwitchTabs组件内部需要额外判断是否为文档片段,是则返回拍平后的VNode[]
添加生成默认插槽标签函数,兼容可遍历的文档片段 ⇧
@/views/genDefaultSlotTabs.ts
|
|
- 使用
Symbol.keyFor(slot?.type as symbol) === 'v-fgt'判断是否为文档片段 - 递归判断节点是否为文档片段,并返回拍平后的节点数组
重构
@/components/tab/SwitchTabs.tsx
- 处理报错
- 原组件内部必须使用默认插槽接收 ItemTab 组件,ItemTab 组件必须有 tabName 属性
拿到默认插槽的子组件数组,遍历判断是否为
ItemTab或是v-fgt,递归拍平返回子组件数组并渲染defaultSlotTabs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66import { defineComponent, type PropType, } from 'vue' import { ItemTab } from '@/components/tab/ItemTab.tsx' import { cn } from 'cn' import { genDefaultSlotTabs } from '@/views/genDefaultSlotTabs.ts' export const SwitchTabs = defineComponent({ name: 'SwitchTabs', props: { selected: { type: String as PropType<string>, }, }, setup(_props, _ctx) { const selectedClass = (tabName: string) => _props.selected === tabName ? cn( 'relative', `after:(content-[""] block absolute bottom-0 left-0 w-full h-4px bg-[var(--tabs-indicator-bg)])`, ) : '' return () => { const defaultSlotTabs = genDefaultSlotTabs(_ctx.slots?.default?.() ?? []) const ifAllItemTab = defaultSlotTabs .every((slot) => slot.type === ItemTab && !!slot.props?.tabName) if (!ifAllItemTab) { throw new Error(`SwitchTabs: 子组件必须是 ItemTab,且必须有 tabName 属性`) } console.log('SwitchTabs_______________________') console.log(_ctx.slots?.default?.()) console.log(defaultSlotTabs) console.log('_______________________SwitchTabs') return ( <div class="grow-1 shrink-1 overflow-auto scrollbar-hide relative"> {/* 切换标签 */} <nav class="sticky left-0 top-0 z-1"> <menu class="flex items-center text-color-[var(--navbar-text-color)]"> {defaultSlotTabs.map((slot, index) => ( <li key={index + slot?.props?.tabName} class={cn( 'grow-1 shrink-0 text-center', 'p-y-12px bg-[var(--navbar-bg-end)]', selectedClass(slot?.props?.tabName), )} onClick={() => { _ctx.emit('update:selected', slot?.props?.tabName) }}> {slot?.props?.tabName} </li> ))} </menu> </nav> {/* 标签内容-插槽-数组 */} <section> {defaultSlotTabs.find((slot) => slot.props?.tabName === _props.selected)} </section> </div> ) } }, })
重构创建标签页面
@/views/ItemCreate.tsx,使用数组数据循环遍历生成
|
|
微信、手机屏幕样式测试
2. 制作 TagEdit 页面 ⇧
完成 TagDetailEditView 组件样式结构 ⇧
@/router/routes.ts
|
|
@/views/TagDetailEditView.tsx
|
|
- 页面样式基本与创建页面一直,已抽取组件
@/modules/tags/TagForm.tsx复用,需要添加插槽 - 逻辑上需要一开始就获取数据,暂时不做
- 异同点
- 数据源不同
- 错误验证展示逻辑相同
- 提交逻辑不同
- 创建的api 与 更新的api
TagForm.tsx仅做验证逻辑,展示验证错误结;- 仅当编辑tag时,有表单默认数据,通过 props 传入,暂时不做,现在只完成样式;
- 提交逻辑抛出事件,暂时不做,现在只完成样式;
为 BarButton 组件添加 level 属性支持多风格样式 ⇧
@/assets/styles/vars.scss
|
|
@/components/BarButton.tsx
|
|
3. 修改SwitchTabs内部子项样式,使用 :where() 降低优先级 ⇧
@/components/tab/SwitchTabs.tsx
|
|
- 默认子项样式优先级降为0
'where:(text-center grow-1 shrink-0 px-1em py-12px)'
@/views/ItemList.tsx
|
|
- 使用父级样式覆盖
'[&>nav>menu>li]:(text-start grow-0)'
参考文章
- 记一笔最终代码为:https://github.com/FrankFang/0r2jdkiNO681/commits/master
- TagEdit最终代码为:https://github.com/FrankFang/fNCJ4z6s3HGe/commits/master
相关文章
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名