index.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <template>
  2. <view class="article-card">
  3. <view @click="toDetail(localItem)" class="article-content">
  4. <view class="article-content">
  5. <view class="image-container">
  6. <image
  7. :src="localItem.images[0]"
  8. mode="aspectFill"
  9. class="main-image"
  10. ></image>
  11. <!-- {{ localItem.images[0] }} -->
  12. <!-- <view class="status-box" v-if="item.status !== 2">
  13. <view class="item-status">
  14. <uni-icons
  15. v-if="item.status === 1"
  16. color="#fff"
  17. type="eye"
  18. size="30"
  19. ></uni-icons>
  20. <uni-icons
  21. v-if="item.status === 3"
  22. color="#fff"
  23. type="eye-slash"
  24. size="30"
  25. ></uni-icons>
  26. <uni-icons
  27. v-if="item.status === 0"
  28. customPrefix="iconfont"
  29. color="#fff"
  30. type="icon-caogaoxiang"
  31. size="30"
  32. ></uni-icons>
  33. <view class="text">{{ articleStatusMap[item.status] }}</view>
  34. </view>
  35. </view> -->
  36. </view>
  37. <view class="content-box">
  38. <view class="title-container">
  39. <text class="title line2">{{ localItem.description }}</text>
  40. </view>
  41. </view>
  42. </view>
  43. <view class="article-footer">
  44. <view class="author-detail" @click="toPersonal">
  45. <image
  46. :src="`${localItem.contactAvatar}`"
  47. class="small-avatar"
  48. ></image>
  49. <text class="author-name">{{ localItem.contactName }}</text>
  50. </view>
  51. <view class="interaction" @click.stop="handleLike">
  52. <uni-icons
  53. customPrefix="iconfont"
  54. type="icon-dianzan"
  55. size="16"
  56. v-show="!localItem.likeMark"
  57. class="unlike"
  58. ></uni-icons>
  59. <uni-icons
  60. customPrefix="iconfont"
  61. type="icon-dianzanxuanzhong"
  62. size="16"
  63. color="#f73842"
  64. v-show="localItem.likeMark"
  65. :class="['liked', { 'animated heartBeat': showAnimation }]"
  66. ></uni-icons>
  67. <text class="like-count">{{ localItem.likeCount || 0 }}</text>
  68. </view>
  69. </view>
  70. </view>
  71. </view>
  72. </template>
  73. <script setup>
  74. import { ref, watch } from "vue";
  75. import { likeFundsAPI } from "@/api/find_fund";
  76. import { useDebounce } from "@/hooks/useDebounceThrottle";
  77. import { useSafeNavigate } from "@/hooks/useSafeNavigate";
  78. import { useAppStore } from "@/stores/app";
  79. // 头像baseUrl
  80. const baseUrl = "http://sb-admin.oss-cn-shenzhen.aliyuncs.com/";
  81. const { safeNavigateTo } = useSafeNavigate();
  82. const props = defineProps({
  83. item: { type: Object, required: true },
  84. likeAnimationIds: { type: Array, default: () => [] },
  85. });
  86. const emit = defineEmits(["like", "detail"]);
  87. const appStore = useAppStore();
  88. // 创建本地响应式副本
  89. const localItem = ref({ ...props.item });
  90. // 控制动画显示
  91. const showAnimation = ref(false);
  92. const articleStatusMap = {
  93. 0: "草稿",
  94. 1: "审核中",
  95. 2: "已发布",
  96. 3: "审核被拒绝",
  97. };
  98. // 监听外部 item 变化,同步到本地
  99. watch(
  100. () => props.item,
  101. (newItem) => {
  102. localItem.value = { ...newItem };
  103. },
  104. { deep: true }
  105. );
  106. // 跳转其它用户
  107. function toPersonal() {
  108. // 点自己的头像直接到我的tabbar页
  109. if (Number(localItem.value.uid) === Number(appStore.uid)) {
  110. return uni.switchTab({ url: "/pages/user/index" });
  111. }
  112. // 其它用户跳到 用户个人页
  113. uni.navigateTo({ url: `/pages/user/personal?id=${localItem.value.uid}` });
  114. }
  115. // 点赞
  116. async function handleLike() {
  117. try {
  118. const { code } = await likeFundsAPI(localItem.value.id);
  119. const wasLiked = localItem.value.likeMark;
  120. localItem.value.likeMark = !wasLiked;
  121. if (localItem.value.likeMark) {
  122. showAnimation.value = true;
  123. localItem.value.likeCount++;
  124. } else {
  125. showAnimation.value = false;
  126. localItem.value.likeCount--;
  127. }
  128. } catch (error) {
  129. console.error("handleLike", error);
  130. }
  131. }
  132. // 跳转详情
  133. const toDetail = (item) => {
  134. // 草稿和审核被拒绝跳到编辑页
  135. // if (item?.status === 0 || item?.status === 3) {
  136. // return safeNavigateTo(`/pages/article_create/edit?id=${item.id}`);
  137. // }
  138. console.log("点击详情");
  139. safeNavigateTo(`/pages/users/funds_detail/index?id=${item.id}`);
  140. };
  141. // 预览图片
  142. const previewImage = (item) => {
  143. console.log(item[0]);
  144. // 预览图片,支持多张图片传数组
  145. uni.previewImage({
  146. urls: item,
  147. current: item[0],
  148. indicator: "default",
  149. loop: true,
  150. });
  151. };
  152. </script>
  153. <style lang="scss" scoped>
  154. .article-card {
  155. width: 100%;
  156. background-color: #fff;
  157. border-radius: 12rpx;
  158. overflow: hidden;
  159. margin-bottom: 20rpx;
  160. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  161. .article-content {
  162. position: relative;
  163. .image-container {
  164. position: relative;
  165. width: 100%;
  166. .main-image {
  167. width: 100%;
  168. max-height: 400rpx;
  169. aspect-ratio: 4 / 3;
  170. object-fit: cover;
  171. display: block;
  172. position: relative;
  173. }
  174. .status-box {
  175. position: absolute;
  176. left: 0;
  177. top: 0;
  178. right: 0;
  179. bottom: 0;
  180. background-color: rgba(0, 0, 0, 0.4);
  181. display: flex;
  182. justify-content: center;
  183. align-items: center;
  184. z-index: 2;
  185. .item-status {
  186. color: #fff;
  187. display: flex;
  188. flex-direction: column;
  189. justify-content: center;
  190. align-items: center;
  191. }
  192. }
  193. }
  194. .content-box {
  195. padding: 16rpx;
  196. .title-container {
  197. margin-bottom: 8rpx;
  198. .title {
  199. font-size: 14px;
  200. color: #333;
  201. font-weight: bold;
  202. line-height: 1.4;
  203. }
  204. }
  205. }
  206. }
  207. .article-footer {
  208. display: flex;
  209. justify-content: space-between;
  210. align-items: center;
  211. padding: 16rpx;
  212. .author-detail {
  213. display: flex;
  214. align-items: center;
  215. .small-avatar {
  216. width: 40rpx;
  217. height: 40rpx;
  218. border-radius: 50%;
  219. margin-right: 8rpx;
  220. }
  221. .author-name {
  222. font-size: 12px;
  223. color: #666;
  224. white-space: nowrap;
  225. overflow: hidden;
  226. text-overflow: ellipsis;
  227. max-width: 120rpx;
  228. }
  229. }
  230. .interaction {
  231. display: flex;
  232. align-items: center;
  233. .like-count {
  234. font-size: 26rpx;
  235. color: #999;
  236. margin-left: 6rpx;
  237. }
  238. .unlike {
  239. }
  240. .liked {
  241. }
  242. }
  243. }
  244. }
  245. </style>