瑶都万能墙
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
7.9 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <template>
  2. <view class="waterfall-item" @click="handleClick">
  3. <!-- 主媒体 -->
  4. <view class="main-image" v-if="mainMedia">
  5. <!-- 图片 -->
  6. <image v-if="mainMedia.type === 'image'" :src="mainMedia.url" mode="aspectFill"></image>
  7. <!-- 视频 -->
  8. <view v-else-if="mainMedia.type === 'video'" class="video-container">
  9. <video :src="mainMedia.url" :poster="mainMedia.url" :show-center-play-btn="false" :controls="false" muted></video>
  10. <!-- <view class="video-overlay">
  11. <uv-icon name="play-circle" size="60rpx" color="rgba(255,255,255,0.8)"></uv-icon>
  12. </view> -->
  13. </view>
  14. <!-- 分类标签 -->
  15. <view class="category-tag" v-if="item.classId_dictText">
  16. #{{ item.classId_dictText }}
  17. </view>
  18. </view>
  19. <!-- 内容区域 -->
  20. <view class="content">
  21. <!-- 标题/内容 -->
  22. <view class="title" v-if="item.title" v-html="formatContent(item.title)"></view>
  23. <!-- 地址信息 -->
  24. <view class="address" v-if="item.address">
  25. <uv-icon name="map-pin" size="24rpx" color="#999"></uv-icon>
  26. <text>{{ item.address }}</text>
  27. </view>
  28. <!-- 用户信息 -->
  29. <view class="user-info">
  30. <view class="user-avatar">
  31. <image :src="item.userImage" mode="aspectFill" @click.stop="previewImage([item.userImage])"></image>
  32. </view>
  33. <view class="user-details">
  34. <view class="username">{{ item.userName }}</view>
  35. <view class="user-tags">
  36. <text class="tag" v-if="item.sex" :style="{'background-color': sexColors[item.sex] || '#999'}">{{ item.sex }}</text>
  37. <text class="tag" v-if="item.yearDate">{{ item.yearDate }}</text>
  38. <text class="auth-tag" v-if="item.isContent">{{ item.isContent }}</text>
  39. </view>
  40. </view>
  41. </view>
  42. <!-- 互动数据 -->
  43. <view class="interaction">
  44. <view class="interaction-item">
  45. <uv-icon name="eye" size="24rpx" color="#999"></uv-icon>
  46. <text>{{ item.isBrowse || 0 }}</text>
  47. </view>
  48. <view class="interaction-item">
  49. <uv-icon name="chat" size="24rpx" color="#999"></uv-icon>
  50. <text>{{ item.isComment || 0 }}</text>
  51. </view>
  52. <view class="interaction-item" @click.stop="handleLike">
  53. <uv-icon name="thumb-up" size="24rpx" :color="isLiked ? '#ff4757' : '#999'"></uv-icon>
  54. <text :style="{color: isLiked ? '#ff4757' : '#999'}">{{ item.isUp || 0 }}</text>
  55. </view>
  56. <!-- 发布时间 -->
  57. <view class="publish-time">
  58. {{ formatTime(item.createTime) }}
  59. </view>
  60. </view>
  61. </view>
  62. </view>
  63. </template>
  64. <script>
  65. export default {
  66. props: {
  67. item: {
  68. type: Object,
  69. default: () => ({})
  70. }
  71. },
  72. data() {
  73. return {
  74. isLiked: false,
  75. sexColors: {
  76. '男': '#4A90E2',
  77. '女': '#FF69B4',
  78. '其他': '#999'
  79. }
  80. }
  81. },
  82. computed: {
  83. // 主媒体 - 优先显示微信图片,然后是第一个文件(图片或视频)
  84. mainMedia() {
  85. if (this.item.wxImage) {
  86. return {
  87. url: this.item.wxImage,
  88. type: 'image'
  89. }
  90. }
  91. if (this.item.image) {
  92. const files = this.item.image.split(',').filter(file => file.trim())
  93. if (files.length > 0) {
  94. const firstFile = files[0].trim()
  95. return {
  96. url: firstFile,
  97. type: this.isVideoFile(firstFile) ? 'video' : 'image'
  98. }
  99. }
  100. }
  101. return null
  102. }
  103. },
  104. methods: {
  105. // 处理点击事件
  106. handleClick() {
  107. this.$emit('click', this.item)
  108. },
  109. // 处理点赞事件
  110. handleLike() {
  111. this.isLiked = !this.isLiked
  112. this.$emit('like', this.item)
  113. },
  114. // 预览媒体
  115. previewMedia(media) {
  116. if (media.type === 'image') {
  117. // 图片预览
  118. this.previewImage([media.url])
  119. } else if (media.type === 'video') {
  120. // 视频播放
  121. uni.navigateTo({
  122. url: `/pages/video/videoPlayer?url=${encodeURIComponent(media.url)}`
  123. })
  124. }
  125. },
  126. // 预览图片
  127. previewImage(urls, current = 0) {
  128. if (!urls || urls.length === 0) return
  129. uni.previewImage({
  130. urls: urls,
  131. current: current
  132. })
  133. },
  134. // 判断是否为视频文件
  135. isVideoFile(url) {
  136. const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.m4v']
  137. const lowerUrl = url.toLowerCase()
  138. return videoExtensions.some(ext => lowerUrl.includes(ext))
  139. },
  140. // 格式化内容
  141. formatContent(content) {
  142. if (!content) return ''
  143. // 尝试使用全局utils,如果不存在则返回原内容
  144. if (this.$utils && this.$utils.stringFormatHtml) {
  145. return this.$utils.stringFormatHtml(content)
  146. }
  147. // 简单的HTML处理
  148. return content.replace(/\n/g, '<br/>')
  149. },
  150. // 格式化时间
  151. formatTime(timeStr) {
  152. if (!timeStr) return ''
  153. // 如果已经包含"发布",直接返回
  154. if (timeStr.includes('发布')) {
  155. return timeStr
  156. }
  157. // 简单的时间格式处理
  158. const now = new Date()
  159. const time = new Date(timeStr)
  160. const diff = now - time
  161. const days = Math.floor(diff / (1000 * 60 * 60 * 24))
  162. if (days === 0) {
  163. return '今天'
  164. } else if (days === 1) {
  165. return '昨天'
  166. } else if (days < 7) {
  167. return `${days}天前`
  168. } else {
  169. // 返回月-日格式
  170. const month = time.getMonth() + 1
  171. const day = time.getDate()
  172. return `${month}-${day}`
  173. }
  174. }
  175. }
  176. }
  177. </script>
  178. <style scoped lang="scss">
  179. .waterfall-item {
  180. background-color: #fff;
  181. border-radius: 16rpx;
  182. overflow: hidden;
  183. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  184. margin-bottom: 20rpx;
  185. .main-image {
  186. position: relative;
  187. width: 100%;
  188. image {
  189. width: 100%;
  190. height: 400rpx;
  191. object-fit: cover;
  192. }
  193. .video-container {
  194. position: relative;
  195. width: 100%;
  196. height: 400rpx;
  197. video {
  198. width: 100%;
  199. height: 100%;
  200. object-fit: cover;
  201. }
  202. .video-overlay {
  203. position: absolute;
  204. top: 0;
  205. left: 0;
  206. right: 0;
  207. bottom: 0;
  208. display: flex;
  209. align-items: center;
  210. justify-content: center;
  211. background: rgba(0, 0, 0, 0.3);
  212. }
  213. }
  214. .category-tag {
  215. position: absolute;
  216. top: 16rpx;
  217. right: 16rpx;
  218. background: rgba(255, 215, 0, 0.9);
  219. color: #333;
  220. padding: 8rpx 16rpx;
  221. border-radius: 20rpx;
  222. font-size: 22rpx;
  223. font-weight: 500;
  224. }
  225. }
  226. .content {
  227. padding: 24rpx;
  228. .title {
  229. font-size: 28rpx;
  230. line-height: 1.4;
  231. color: #333;
  232. margin-bottom: 16rpx;
  233. display: -webkit-box;
  234. -webkit-box-orient: vertical;
  235. -webkit-line-clamp: 3;
  236. overflow: hidden;
  237. }
  238. .address {
  239. display: flex;
  240. align-items: center;
  241. margin-bottom: 20rpx;
  242. text {
  243. font-size: 24rpx;
  244. color: #666;
  245. margin-left: 8rpx;
  246. }
  247. }
  248. .user-info {
  249. display: flex;
  250. align-items: center;
  251. margin-bottom: 20rpx;
  252. .user-avatar {
  253. width: 60rpx;
  254. height: 60rpx;
  255. border-radius: 30rpx;
  256. overflow: hidden;
  257. margin-right: 16rpx;
  258. image {
  259. width: 100%;
  260. height: 100%;
  261. }
  262. }
  263. .user-details {
  264. flex: 1;
  265. .username {
  266. font-size: 26rpx;
  267. color: #333;
  268. font-weight: 500;
  269. margin-bottom: 6rpx;
  270. }
  271. .user-tags {
  272. display: flex;
  273. align-items: center;
  274. flex-wrap: wrap;
  275. .tag {
  276. font-size: 20rpx;
  277. color: white;
  278. background-color: #999;
  279. padding: 4rpx 12rpx;
  280. border-radius: 12rpx;
  281. margin-right: 8rpx;
  282. margin-bottom: 4rpx;
  283. }
  284. .auth-tag {
  285. font-size: 20rpx;
  286. color: white;
  287. background-color: #ffd036;
  288. padding: 4rpx 12rpx;
  289. border-radius: 12rpx;
  290. margin-right: 8rpx;
  291. margin-bottom: 4rpx;
  292. }
  293. }
  294. }
  295. }
  296. .interaction {
  297. display: flex;
  298. align-items: center;
  299. justify-content: space-between;
  300. .interaction-item {
  301. display: flex;
  302. align-items: center;
  303. text {
  304. font-size: 24rpx;
  305. color: #999;
  306. margin-left: 6rpx;
  307. }
  308. }
  309. .publish-time {
  310. font-size: 24rpx;
  311. color: #999;
  312. margin-left: auto;
  313. }
  314. }
  315. }
  316. }
  317. </style>