Bläddra i källkod

Merge branch 'feature/8-petsearch' of 1-bright/frontend-uniapp into main

依力 1 år sedan
förälder
incheckning
8c05f01293

+ 19 - 2
src/api/carousal.ts

@@ -1,9 +1,26 @@
+import type { PageResult } from '@/model/base'
+import type { Article, ArticleParams, Category, CreateCarousalsRequest, Recommends } from '@/model/pet-manual'
 import httpClient from '@/api/httpClient'
 
 function getCarousalList() {
-  return httpClient.get('/carousals/active-list')
+  return httpClient.get<CreateCarousalsRequest[]>('/carousals/active-list')
+}
+function getTypeList() {
+  return httpClient.get<Category[]>('/categories/list')
+}
+function getArticleList(data: ArticleParams) {
+  return httpClient.get<PageResult<Article>>('/articles', data)
+}
+function getSearchRecords() {
+  return httpClient.get<string[]>('/articles/search-records')
+}
+function getSearchRecommends() {
+  return httpClient.get<Recommends[]>('/articles/active-search-recommends')
 }
-
 export default {
   getCarousalList,
+  getTypeList,
+  getArticleList,
+  getSearchRecords,
+  getSearchRecommends,
 }

+ 32 - 0
src/model/base.ts

@@ -0,0 +1,32 @@
+import type { User } from './user'
+
+export interface ErrorResponse {
+  code: number
+  message: string
+}
+export interface BaseModel {
+  id: string
+  createdTime: string
+  updateTime: string
+}
+
+export interface AuditBaseModel extends BaseModel {
+  createBy: User
+  updateBy: User
+}
+
+export interface Paging {
+  page: number
+  size: number
+  total?: number
+}
+
+export interface BaseFilterRequest extends Partial<Paging> {
+}
+
+export interface PageResult<T> {
+  pagination: Paging
+  data: T[]
+}
+
+export interface ErrorResponseList extends Array<ErrorResponse> {}

+ 0 - 0
src/model/carousals.ts


+ 34 - 0
src/model/pet-manual.ts

@@ -0,0 +1,34 @@
+import type { Paging } from '@/model/base'
+
+export interface CreateCarousalsRequest {
+  imageUrl: string
+  targetType: string
+  targetUrl: string
+  targetId?: string
+}
+
+export interface Category {
+  id: string
+  code: string
+  name: string
+  isClick?: boolean
+}
+
+export interface ArticleParams extends Paging {
+  title: string
+  categoryId: string
+}
+
+export interface Article {
+  id: string
+  content: string
+  status: string
+  title: string
+  updatedTime?: string
+  imageUrl?: string
+}
+
+export interface Recommends {
+  id: string
+  keyword: string
+}

+ 11 - 0
src/model/user.ts

@@ -0,0 +1,11 @@
+import type { AuditBaseModel, BaseFilterRequest } from '@/model/base'
+
+export interface User extends AuditBaseModel {
+  username: string
+  locked: boolean
+  enabled: boolean
+}
+
+export interface UserSearchFilter extends BaseFilterRequest {
+  username?: string
+}

+ 38 - 11
src/pages/pet-manual/components/CardList.vue

@@ -1,13 +1,39 @@
 <script setup lang="ts">
-interface ListItem {
-  img: string
-  title: string
-  desc: string
-  time: string
+import type { Paging } from '@/model/base'
+import type { Article } from '@/model/pet-manual'
+import titleBg from '@/static/image/feed-plan/bg.png'
+
+interface ContentText {
+  contentdown: string
+  contentrefresh: string
+  contentnomore: string
+}
+const props = withDefaults(defineProps<{
+  cardList: Article[]
+  pagination: Paging
+}>(), {
+  pagination: {
+    page: 0,
+    size: 10,
+    total: 0,
+  },
+})
+const emit = defineEmits(['loadMore'])
+const loadMoreStatus = computed(() => {
+  return props.pagination.size > props.pagination.total ? 'nomore' : 'more'
+})
+const contentText = ref<ContentText>({
+  contentdown: '查看更多',
+  contentrefresh: '加载中',
+  contentnomore: '没有更多',
+})
+
+function handleLoadMore() {
+  if (props.pagination.size > props.pagination.total) {
+    return
+  }
+  emit('loadMore')
 }
-const props = defineProps<{
-  cardList: ListItem[]
-}>()
 </script>
 
 <template>
@@ -15,20 +41,21 @@ const props = defineProps<{
     v-for="(item, index) in props.cardList" :key="index" class="bg-[white] rounded-md shadow-lg mx-4 mb-4 w-[calc(100% - 32px)] mt-3"
   >
     <view class="flex">
-      <image class="w-28 h-28 rounded-l-md" :src="item.img" />
+      <image class="w-28 h-28 rounded-l-md" :src="item?.imageUrl || titleBg" />
       <view class="p-4">
         <view class="text-[14px]">
           {{ item.title }}
         </view>
         <view class="mt-2 text-[11px] text-[#999] whitespace-pre-line">
-          {{ item.desc }}
+          {{ item.content }}
         </view>
         <view class="mt-3 text-[10px] text-[#D8D8D8]">
-          {{ item.time }}
+          {{ item.updatedTime }}
         </view>
       </view>
     </view>
   </view>
+  <uni-load-more :status="loadMoreStatus" :content-text="contentText" @click-load-more="handleLoadMore" />
 </template>
 
 <style scoped>

+ 118 - 95
src/pages/pet-manual/index.vue

@@ -1,83 +1,31 @@
 <script setup lang="ts">
+import type { Paging } from '@/model/base'
+import type { Article, ArticleParams, Category, CreateCarousalsRequest, Recommends } from '@/model/pet-manual'
 import carousalApi from '@/api/carousal'
 import CardList from '@/pages/pet-manual/components/CardList.vue'
-// const searchValue = ref('')
-import bg from '@/static/image/feed-plan/bg.png'
 import circle from '@/static/image/feed-plan/circle.png'
 import message from '@/static/image/feed-plan/message.png'
 import ToolApi from '@/utils'
 
-interface ListItem {
-  img: string
-  title: string
-  desc: string
-  time: string
-}
-interface Item {
-  content: string
-}
 interface DotStyle {
   backgroundColor: string
   selectedBackgroundColor: string
   selectedBorder: string
   border: string
 }
-interface TypeItem {
-  name: string
-  isClick: boolean
-}
+
 const titleName = ref<string>('养宠手册')
-const listModel = ref<ListItem[]>([
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-  {
-    img: bg,
-    title: '猫咪食物禁忌',
-    desc: '1 饮食不慎 2 寄生虫\n 3 病毒感染 4 细菌和毒素感染',
-    time: '2025年1月1日',
-  },
-])
+const listModel = ref<Article[]>([])
 const safeHeight = ToolApi.getSafeHeight()
 const searchValue = ref<string>('')
-
-const info = ref<Item[]>([
-  { content: '内容 A' },
-  { content: '内容 B' },
-  { content: '内容 C' },
+const cardState = ref<'update' | 'updatePage'>('update')
+const info = ref<CreateCarousalsRequest[]>([
+  {
+    imageUrl: '',
+    targetId: '',
+    targetType: '',
+    targetUrl: '',
+  },
 ])
 
 const current = ref<number>(0)
@@ -93,38 +41,110 @@ function change(e: CustomEvent<{ current: number }>): void {
   current.value = e.detail.current
 }
 
-const typeList = ref<TypeItem[]>([
-  { name: '趣味科普', isClick: true },
-  { name: '科学喂养', isClick: false },
-  { name: '科学喂养', isClick: false },
-  { name: '科学喂养', isClick: false },
-])
+const typeList = ref<Category[]>([])
+const articleParams = ref<ArticleParams>({
+  page: 1,
+  size: 10,
+  title: '',
+  categoryId: '',
+})
+const articlePaging = ref<Paging>({
+  page: 1,
+  size: 10,
+  total: 0,
+})
 
-function handleClickType(item: TypeItem) {
+async function handleClickType(item: Category) {
   typeList.value.forEach(i => (i.isClick = false))
   item.isClick = true
+  articleParams.value.categoryId = item.id
+  cardState.value = 'update'
+  await updateArticleList()
 }
 const isSearch = ref<boolean>(true)
+const isSearchSource = ref<boolean>(false)
 function handleSearchFocus() {
   isSearch.value = false
+  isSearchSource.value = false
+  updateSearchList()
+}
+async function handleSearchBlur() {
+  if (searchValue.value !== '') {
+    isSearchSource.value = true
+    reset()
+    articleParams.value.title = searchValue.value
+    await updateArticleList()
+  }
+}
+function handleSearchKeyword(keyword: string): void {
+  searchValue.value = keyword
+  handleSearchBlur()
 }
-function handleCancel() {
+async function handleCancel() {
   isSearch.value = true
+  reset()
+  articleParams.value.categoryId = typeList.value.filter(item => item.isClick === true)[0].id
+  await updateArticleList()
+}
+function handleSearchClear() {
+  isSearchSource.value = false
+}
+const historyList = ref<string[]>([])
+const findList = ref<Recommends[]>([])
+async function updateArticleList() {
+  const articleListApi = await carousalApi.getArticleList(articleParams.value)
+  if (cardState.value === 'update') {
+    listModel.value = articleListApi.data
+  }
+  else if (cardState.value === 'updatePage') {
+    listModel.value.push(...articleListApi.data)
+  }
+  else {
+    listModel.value = articleListApi.data
+  }
+  articlePaging.value = articleListApi.pagination
 }
-const historyList = ref<{ name: string }[]>([
-  { name: '猫' },
-  { name: '狗' },
-  { name: '烘培粮' },
-])
 
-const findList = ref<{ name: string }[]>([
-  { name: '宠物吃饭慢' },
-  { name: '猫咪尿闭怎么办' },
-])
-function onLoad() {
-  carousalApi.getCarousalList()
+async function handleLoadArticle() {
+  articleParams.value.page += 1
+  cardState.value = 'updatePage'
+  await updateArticleList()
+}
+async function updateSearchList() {
+  historyList.value = await carousalApi.getSearchRecords()
+  findList.value = await carousalApi.getSearchRecommends()
+}
+async function init() {
+  info.value = await carousalApi.getCarousalList()
+  typeList.value = (await carousalApi.getTypeList()).map((item, index) => {
+    return {
+      name: item.name,
+      id: item.id,
+      code: item.code,
+      isClick: index === 0,
+    }
+  })
+  articleParams.value.categoryId = typeList.value[0].id
+  await updateArticleList()
+  await updateSearchList()
 }
-onLoad()
+function reset() {
+  articleParams.value = {
+    page: 1,
+    size: 10,
+    title: '',
+    categoryId: '',
+  }
+  listModel.value = []
+}
+function handleJumpUrl(item: CreateCarousalsRequest) {
+  if (item.targetType === 'url') {
+    uni.navigateTo({
+      url: item.targetUrl,
+    })
+  }
+}
+init()
 </script>
 
 <template>
@@ -140,16 +160,18 @@ onLoad()
         :radius="20"
         @focus="handleSearchFocus"
         @cancel="handleCancel"
+        @blur="handleSearchBlur"
+        @clear="handleSearchClear"
       />
     </view>
-    <view v-show="!isSearch" class="mx-4 mt-4">
-      <view>
+    <view v-show="!isSearch" class=" mt-4">
+      <view v-show="!isSearchSource" class="mx-4">
         <text class="text-[18px] font-600">
           历史记录
         </text>
         <view class="mt-4 flex gap-2 flex-wrap text-[12px] text-[#999] font-400 leading-5">
-          <view v-for="(item, index) in historyList" :key="index" class="px-2 py-0.5 flex justify-center items-center bg-[white] rounded-sm">
-            {{ item.name }}
+          <view v-for="(item, index) in historyList" :key="index" class="px-2 py-0.5 flex justify-center items-center bg-[white] rounded-sm" @click="handleSearchKeyword(item)">
+            {{ item }}
           </view>
         </view>
         <view class="mt-8">
@@ -157,17 +179,18 @@ onLoad()
             搜索发现
           </text>
           <view class="mt-4 flex gap-2 flex-wrap text-[12px] text-[#999] font-400 leading-5">
-            <view v-for="(item, index) in findList" :key="index" class="px-2 py-0.5 flex justify-center items-center bg-[white] rounded-sm gap-1">
+            <view v-for="(item, index) in findList" :key="index" class="px-2 py-0.5 flex justify-center items-center bg-[white] rounded-sm gap-1" @click="handleSearchKeyword(item.keyword)">
               <view class="flex items-center justify-center">
                 <uni-icons color="#999999" size="14" type="search" />
               </view>
               <view class="flex items-center justify-center">
-                {{ item.name }}
+                {{ item.keyword }}
               </view>
             </view>
           </view>
         </view>
       </view>
+      <CardList v-show="isSearchSource" :card-list="listModel" @load-more="handleLoadArticle" />
     </view>
     <view v-show="isSearch">
       <view class="ml-4 mt-4 text-[20px] font-600">
@@ -176,9 +199,9 @@ onLoad()
       <view class="mx-4 mt-4">
         <uni-swiper-dot :info="info" :current="current" field="content" mode="dot" :dots-styles="dotStyle">
           <swiper class="swiper-box" @change="change">
-            <swiper-item v-for="(item, index) in info" :key="index" style="background: rgba(0, 0, 0, 0.20)">
-              <view class="swiper-item">
-                {{ item.content }}
+            <swiper-item v-for="(item, index) in info" :key="index">
+              <view class="swiper-item w-full h-full flex items-center justify-center">
+                <image :src="item.imageUrl" mode="heightFix" class="w-full h-full" @tap="handleJumpUrl(item)" />
               </view>
             </swiper-item>
           </swiper>
@@ -187,7 +210,7 @@ onLoad()
       <view class="ml-4 mt-5 text-[20px] font-600">
         养宠贴士
       </view>
-      <view class="mx-4">
+      <view class="mx-4 sticky top-0 bg-[#f5f6f7]">
         <scroll-view class="scroll mt-2" scroll-x="auto">
           <view class="group h-[40px] mt-1">
             <view
@@ -205,7 +228,7 @@ onLoad()
           </view>
         </scroll-view>
       </view>
-      <CardList :card-list="listModel" />
+      <CardList :card-list="listModel" @load-more="handleLoadArticle" />
     </view>
   </view>
 </template>