Sfoglia il codice sorgente

📦 采集数据点赞/点踩功能

快乐的梦鱼 1 settimana fa
parent
commit
0e12beab64

+ 1 - 1
src/App.vue

@@ -86,7 +86,7 @@ function loadFontFace() {
 
 configAppTheme();
 
-IconUtils.loadDefaultIcons('https://mncdn.wenlvti.net/app_static/xiangyuan/data/DefaultIcon.json');
+IconUtils.loadDefaultIcons('https://xy.wenlvti.net/app_static/data/DefaultIcon.json');
 </script>
 
 <style lang="scss">

+ 26 - 0
src/api/inhert/VillageInfoApi.ts

@@ -126,8 +126,21 @@ export class CommonInfoModel extends DataModel<CommonInfoModel> {
   createdAt = new Date();
   updatedAt = new Date();
   publishAt = new Date();
+  /**
+   * 不喜欢数
+   */
   dislikeNum = 0;
+  /**
+   * 点赞数
+   */
   likeNum = 0;
+  /**
+   * 点赞状态
+   * * 1=已点赞
+   * * 2=踩
+   * * null=未操作
+   */
+  likeType = null as 1|2|null;
   lonlat?: number[];
   landforms = [] as string[];
   /** 类型:1=个人,2=团队,3=企业,4=匿名(多选,提交为逗号分隔) */
@@ -137,6 +150,7 @@ export class CommonInfoModel extends DataModel<CommonInfoModel> {
   customName = '';
   occurrenceDate = '';
   address = '';
+  statusText = '';
 }
 
 export class VillageListItem extends DataModel<VillageListItem> {
@@ -303,6 +317,18 @@ export class VillageInfoApi extends AppServerRequestModule<DataModel> {
     }, undefined, CommonInfoModel)).data as CommonInfoModel
   }
 
+  /**
+   * 点赞/不感兴趣/取消
+   * @param id 采集记录ID
+   * @param likeType 类型: 1=点赞, 2=不感兴趣
+   */
+  async collectLike(id: number, likeType: 1 | 2) {
+    return (await this.post<string>(`/village/collect/collectLike`, '点赞/不感兴趣', {
+      id,
+      like_type: likeType,
+    }));
+  }
+
   async deleteInfo(id: number) {
     return (await this.post(`/village/collect/del`, '删除信息详情', {
       id,

+ 98 - 0
src/pages/home/discover/components/LikeFooter.vue

@@ -0,0 +1,98 @@
+<template>
+  <FlexCol position="fixed" :bottom="0" :left="0" :right="0" backgroundColor="#f5ebe0" :zIndex="1000" :padding="[20,20,0,20]">
+    <FlexRow justify="space-between">
+      <FlexRow align="center">
+        <slot name="left" />
+      </FlexRow>
+      <FlexRow align="center">
+        <Touchable direction="row" align="center" :gap="10" :padding="[0,10]" @click="doLike">
+          <Icon icon="good" :color="isLiked ? 'primary' : 'text.content'" />
+          <Text :text="likes" :color="isLiked ? 'primary' : 'text.content'" />
+        </Touchable>
+        <Touchable direction="row" align="center" :gap="10" :padding="[0,10]" @click="doDisLike">
+          <Icon icon="good" :rotate="180" :color="isDisLiked ? 'primary' : 'text.content'" />
+          <Text :text="disLikes" :color="isDisLiked ? 'primary' : 'text.content'" />
+        </Touchable>
+        <!-- <Touchable direction="row" align="center" :gap="10" :padding="[0,10]" @click="doCollect">
+          <Icon icon="favorite" :color="content.isCollect ? 'warning' : 'text.content'" />
+          <Text text="收藏" :color="content.isCollect ? 'warning' : 'text.content'" />
+        </Touchable> -->
+        <button class="remove-button-style" open-type="share">
+          <FlexRow :gap="10" align="center" :padding="[0,10]" >
+            <Icon icon="share" color="text.content" />
+            <Text text="分享" />
+          </FlexRow>
+        </button>
+      </FlexRow>
+    </FlexRow>
+    <XBarSpace />
+
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import { computed, type PropType } from 'vue';
+import { toast } from "@/components/utils/DialogAction";
+import Icon from "@/components/basic/Icon.vue";
+import FlexRow from "@/components/layout/FlexRow.vue";
+import XBarSpace from "@/components/layout/space/XBarSpace.vue";
+import FlexCol from "@/components/layout/FlexCol.vue";
+import Touchable from "@/components/feedback/Touchable.vue";
+import Text from '@/components/basic/Text.vue';
+import type { CommonInfoModel } from '@/api/inhert/VillageInfoApi';
+import VillageInfoApi from '@/api/inhert/VillageInfoApi';
+
+const props = defineProps({
+  content: {
+    type: Object as PropType<CommonInfoModel>,
+    required: true,
+  }
+})
+
+const emit = defineEmits(['refresh']);
+
+const likes = computed(() => formatNumber(props.content.likeNum));
+const disLikes = computed(() => formatNumber(props.content.dislikeNum));
+const isLiked = computed(() => props.content.likeType === 1);
+const isDisLiked = computed(() => props.content.likeType === 2);
+
+async function doLike() {
+  const content = props.content;
+  if (!content) return;
+  try {
+    if (isLiked.value) {
+      toast('您已经点赞过啦');
+      return;
+    }
+    content.likeType = 1;
+    await VillageInfoApi.collectLike(content.id, 1);
+    emit('refresh');
+    toast('感谢点赞');
+  } catch (error) {
+    toast('操作失败' + error);
+  }
+}
+async function doDisLike() {
+  const content = props.content;
+  if (!content) return;
+  try {
+    if (isDisLiked.value) {
+      toast('您已经操作过啦');
+      return;
+    }
+    content.likeType = 2;
+    await VillageInfoApi.collectLike(content.id, 2);
+    emit('refresh');
+    toast('已点踩');
+  } catch (error) {
+    toast('操作失败' + error);
+  }
+}
+
+function formatNumber(num: number) {
+  num = Math.max(num, 0);
+  if (num >= 10000) 
+    return (num / 10000).toFixed(2) + '万';
+  return num.toString();
+}
+</script>

+ 31 - 4
src/pages/home/discover/details.vue

@@ -2,11 +2,23 @@
   <FlexCol :padding="[0,0,50,0]">
     <FlexCol position="absolute" :zIndex="100" :inset="{ l: 0, r: 0, t: 0 }">
       <StatusBarSpace />
-      <NavBar :title="loader.content.value?.title || '文章详情'" leftButton="back" />
+      <NavBar 
+        :title="loader.content.value?.title || '文章详情'"
+        leftButton="back"
+        :leftButtonProps="{
+          buttonStyle: {
+            marginLeft: '20rpx',
+          },
+          shape: 'round-full',
+          backgroundColor: 'background.primary',
+        }"
+        customBack 
+        @backPressed="handleBack" 
+      />
     </FlexCol>
     <SimplePageContentLoader :loader="loader">
       <template v-if="loader.content.value">
-        <FlexCol gap="games">
+        <FlexCol gap="gap.md">
           <swiper 
             v-if="loader.content.value.images.length > 0"
             circular 
@@ -100,6 +112,10 @@
               @click="goDetails(item.id)"
             />
           </FlexCol>
+          <LikeFooter 
+            :content="loader.content.value" 
+            @refresh="loader.reload(); isChangedStatus=true" 
+          />
         </FlexCol>
       </template>
     </SimplePageContentLoader>
@@ -107,11 +123,11 @@
 </template>
 
 <script setup lang="ts">
-import { computed } from "vue";
+import { computed, ref } from "vue";
 import { useSimpleDataLoader } from "@/components/composeabe/loader/SimpleDataLoader";
 import { useSwiperImagePreview } from "@/common/composeabe/SwiperImagePreview";
 import { useLoadQuerys } from "@/components/composeabe/LoadQuerys";
-import { backAndCallOnPageBack, navTo } from "@/components/utils/PageAction";
+import { backAndCallOnPageBack, navTo, back } from "@/components/utils/PageAction";
 import { onShareTimeline, onShareAppMessage } from "@dcloudio/uni-app";
 import { DataDateUtils } from "@imengyu/js-request-transform";
 import SimplePageContentLoader from "@/components/loader/SimplePageContentLoader.vue";
@@ -130,6 +146,7 @@ import Avatar from "@/components/display/Avatar.vue";
 import Touchable from "@/components/feedback/Touchable.vue";
 import StatusBarSpace from "@/components/layout/space/StatusBarSpace.vue";
 import NavBar from "@/components/nav/NavBar.vue";
+import LikeFooter from "./components/LikeFooter.vue";
 
 const { onPreviewImage } = useSwiperImagePreview(() => loader.content.value?.images || []);
 const emptyContent = computed(() => 
@@ -155,6 +172,8 @@ const { querys } = useLoadQuerys({
   keywords: '',
 }, (t) => loader.load(false, t));
 
+const isChangedStatus = ref(false);
+
 function goDetails(id: number) {
   navTo('details', { 
     id,
@@ -169,6 +188,14 @@ function handleGoVillageDetails(id: number) {
   backAndCallOnPageBack('goVillage', { id });
 }
 
+function handleBack() {
+  if (isChangedStatus.value) {
+    backAndCallOnPageBack('refresh', { needRefresh: true });
+    return;
+  }
+  back();
+}
+
 function getPageShareData() {
   if (!loader.content.value)
     return { title: '文章详情', imageUrl: '' }

+ 1 - 1
src/pages/home/village/introd/tree.vue

@@ -358,7 +358,7 @@ async function handlePickOrWaterOrFertilize(action: 'pick' | 'water' | 'fertiliz
   } catch (e) {
     uni.hideLoading();
     if (e instanceof RequestApiError && typeof e.data === 'string') {
-      showError(e.data);
+      toast(e.data);
       return;
     }
     showError(e);