Skip to content

用Hooks封装业务逻辑

加载第一页的Tags内容

定义分页的TS类型。

tsx
type Resources<T = any> = {
  resources: T[]
  pager: {
    page: number,
    per_page: number,
    count: number
  }
}

然后重构mock.tsx中的mockTagIndex方法,增加了构造分页的参数为page(页码数)、per_page(每页个数)、count(总个数)。并将createTagcreatePager的两个方法合并为createBody

tsx

export const mockTagIndex: Mock = (config) => {
  const { kind, page } = config.params
  const per_page = 25
  const count = 26
  let id = 0
  const createId = () => {
    id += 1
    return id
  }
  const createPaper = (page = 1) => ({
    page, per_page, count
  })
  const createTag = (n = 1, attrs?: any) =>
    Array.from({ length: n }).map(() => ({
      id: createId(),
      name: faker.lorem.word(),
      sign: faker.internet.emoji(),
      kind: config.params.kind,
      ...attrs
    }))
  const createBody = (n = 1, attrs?: any) => ({
    resources: createTag(n),
    pager: createPaper(page)
  })
  if (kind === 'expenses' && (page === 1 || !page)) {
    return [200, createBody(25)]
  } else if (kind === 'expenses' && page === 2){
    return [200, createBody(1)]
  } else {
    return [200, createBody(20)]
  }
}

然后在ItemCreate.tsx页面请求TagIndex的参数上带上_mock的参数,详细内容见链接

tsx
const response = await http.get<Resources<Tag>>('/tags', {
	kind: 'income',
	_mock: 'tagIndex'
})

封装Tags

封装了Tags用于接受页面数,返回请求分页的得到的响应值,还定义了一个Fetcher的类型。

tsx
import { AxiosResponse } from "axios"
import { onMounted, ref } from "vue"

type Fetcher = (page: number) => Promise<AxiosResponse<Resources<Tag>>>
export const useTags = (fetcher: Fetcher) => {
  const page = ref(0)
  const hasMore = ref(false)
  const tags = ref<Tag[]>([])
  const fetchTag = async () => {
    const response = await fetcher(page.value)
    const { resources, pager } = response.data
    tags.value.push(...resources)
    hasMore.value = (pager.page - 1) * pager.per_page + resources.length < pager.count
    page.value += 1
  }
  onMounted(fetchTag)
  return { page, hasMore, tags, fetchTag }
}

然后修改ItemCreate.tsx的支出和收入,使用useTags的Hooks方法,详细内容见链接

tsx
const { tags: expensesTags, hasMore: expensesHasMore , fetchTag: expensesFetchTag } = useTags((page)=> {
  return http.get<Resources<Tag>>('/tags', {
    kind: 'expenses',
    page: page + 1,
    _mock: 'tagIndex'
  })
})
const { tags: incomeTags, hasMore: incomeHasMore, fetchTag: incomeFetchTag } = useTags((page)=> {
  return http.get<Resources<Tag>>('/tags', {
    kind: 'expenses',
    page: page + 1,
    _mock: 'tagIndex'
  })
})

封装useTags组件

再次封装一个Tags.tsx组件,用于ItemCreate.tsx页面,具体详细内容见链接

tsx
import { defineComponent, onUpdated, PropType } from 'vue';
import { Button } from '../../shared/Button';
import { http } from '../../shared/Http';
import { Icon } from '../../shared/Icon';
import { useTags } from '../../shared/useTags';
import s from './Tags.module.scss';
export const Tags = defineComponent({
  props: {
    kind: {
      type: String as PropType<string>,
      required: true
    }
  },
  setup: (props, context) => {
    const { tags, hasMore, page, fetchTags } = useTags((page) => {
      return http.get<Resources<Tag>>('/tags', {
        kind: props.kind,
        page: page + 1,
        _mock: 'tagIndex'
      })
    })
    return () => <>
      <div class={s.tags_wrapper}>
        <div class={s.tag}>
          <div class={s.sign}>
            <Icon name="add" class={s.createTag} />
          </div>
          <div class={s.name}>
            新增
          </div>
        </div>
        {tags.value.map(tag =>
          <div class={[s.tag, s.selected]}>
            <div class={s.sign}>
              {tag.sign}
            </div>
            <div class={s.name}>
              {tag.name}
            </div>
          </div>
        )}
      </div>
      <div class={s.more}>
        {hasMore.value ?
          <Button class={s.loadMore} onClick={fetchTags}>加载更多</Button> :
          <span class={s.noMore}>没有更多</span>
        }
      </div>
    </>
  }
})

然后修改Tabs组件,通过选中的name来显示收入和支出的Icon。切换收入和支出的时候,只是节点隐藏。

tsx
<div>
  {tabs.map(item => 
		<div v-show={item.props?.name === props.selected}>{item}</div>
	)}
</div>

其实还有一个bug,即当前在支出点击加载更多滚动到底部然后切换到收入,会发现收入页面已经滚动到底部了,要解决这个bug,可以通过计算支出滚动到高度,在切换收入时,将高度复原。