my-dynamics.vue 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. <template>
  2. <view class="my-dynamics-page">
  3. <!-- 顶部导航栏 -->
  4. <view class="top-nav">
  5. <view class="back-btn" @click="goBack">
  6. <text class="back-icon">‹</text>
  7. </view>
  8. <view class="nav-title">我的动态</view>
  9. <view class="nav-right"></view>
  10. </view>
  11. <!-- 用户信息区域 -->
  12. <view class="user-info-section">
  13. <image class="avatar" :src="userInfo.avatar" mode="aspectFill"></image>
  14. <view class="user-details">
  15. <text class="nickname">{{ userInfo.nickname }}</text>
  16. <text class="dynamic-count">{{ userInfo.dynamicCount }} 动态</text>
  17. </view>
  18. </view>
  19. <!-- 标签页导航 -->
  20. <view class="tab-nav">
  21. <view
  22. class="tab-item"
  23. :class="{ active: activeTab === 'dynamic' }"
  24. @click="switchTab('dynamic')"
  25. >
  26. <text>动态</text>
  27. </view>
  28. <view
  29. class="tab-item"
  30. :class="{ active: activeTab === 'interaction' }"
  31. @click="switchTab('interaction')"
  32. >
  33. <text>互动</text>
  34. </view>
  35. <view
  36. class="tab-item"
  37. :class="{ active: activeTab === 'browse' }"
  38. @click="switchTab('browse')"
  39. >
  40. <text>浏览记录</text>
  41. </view>
  42. </view>
  43. <!-- 互动子标签 -->
  44. <view class="sub-tab-nav" v-if="activeTab === 'interaction'">
  45. <view
  46. class="sub-tab-item"
  47. :class="{ active: activeSubTab === 'like' }"
  48. @click="switchSubTab('like')"
  49. >
  50. <text>点赞</text>
  51. </view>
  52. <view
  53. class="sub-tab-item"
  54. :class="{ active: activeSubTab === 'collect' }"
  55. @click="switchSubTab('collect')"
  56. >
  57. <text>收藏</text>
  58. </view>
  59. </view>
  60. <!-- 内容区域 -->
  61. <view class="content-area">
  62. <!-- 动态列表 -->
  63. <view v-if="activeTab === 'dynamic'" class="dynamic-list">
  64. <view class="empty-tip" v-if="dynamicList.length === 0">
  65. <text>暂无动态</text>
  66. </view>
  67. <view class="dynamic-item" v-for="item in dynamicList" :key="item.dynamicId">
  68. <!-- 操作菜单按钮 -->
  69. <view class="dynamic-menu" @click.stop="showActionMenu(item)">
  70. <text class="menu-icon">⋯</text>
  71. </view>
  72. <view @click="goToDetail(item.dynamicId)">
  73. <!-- 动态内容 -->
  74. <view class="dynamic-content">
  75. <text class="dynamic-text">{{ item.content }}</text>
  76. </view>
  77. <!-- 动态图片 -->
  78. <view class="dynamic-images" v-if="item.mediaUrls">
  79. <image
  80. class="dynamic-image"
  81. v-for="(img, index) in getMediaUrls(item.mediaUrls)"
  82. :key="index"
  83. :src="img"
  84. mode="aspectFill"
  85. @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
  86. ></image>
  87. </view>
  88. <!-- 动态信息 -->
  89. <view class="dynamic-info">
  90. <text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
  91. <view class="dynamic-stats">
  92. <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
  93. <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
  94. </view>
  95. </view>
  96. </view>
  97. </view>
  98. </view>
  99. <!-- 互动列表 -->
  100. <view v-else-if="activeTab === 'interaction'" class="interaction-list">
  101. <view class="empty-tip" v-if="interactionList.length === 0">
  102. <text>暂无互动</text>
  103. </view>
  104. <view class="interaction-item" v-for="item in interactionList" :key="item.dynamicId">
  105. <!-- 操作菜单按钮 -->
  106. <view class="interaction-menu" @click.stop="showInteractionMenu(item)">
  107. <text class="menu-icon">⋯</text>
  108. </view>
  109. <view @click="goToDetail(item.dynamicId)">
  110. <!-- 用户信息 -->
  111. <view class="user-info">
  112. <image class="avatar" :src="getAvatar(item)" mode="aspectFill"></image>
  113. <view class="user-details">
  114. <text class="nickname">{{ getNickname(item) }}</text>
  115. </view>
  116. </view>
  117. <!-- 互动内容 -->
  118. <view class="interaction-content">
  119. <text class="interaction-text">{{ item.content }}</text>
  120. </view>
  121. <!-- 互动图片 -->
  122. <view class="interaction-images" v-if="item.mediaUrls">
  123. <image
  124. class="interaction-image"
  125. v-for="(img, index) in getMediaUrls(item.mediaUrls)"
  126. :key="index"
  127. :src="img"
  128. mode="aspectFill"
  129. @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
  130. ></image>
  131. </view>
  132. <!-- 互动信息 -->
  133. <view class="interaction-info">
  134. <text class="interaction-time">{{ formatTime(item.createdAt) }}</text>
  135. <view class="interaction-stats">
  136. <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
  137. <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
  138. </view>
  139. </view>
  140. </view>
  141. </view>
  142. </view>
  143. <!-- 浏览记录列表 -->
  144. <view v-else-if="activeTab === 'browse'" class="browse-list">
  145. <view class="empty-tip" v-if="browseList.length === 0">
  146. <text>暂无浏览记录</text>
  147. </view>
  148. <view class="browse-item" v-for="item in browseList" :key="item.dynamicId" @click="goToDetail(item.dynamicId)">
  149. <!-- 用户信息 -->
  150. <view class="user-info">
  151. <image class="avatar" :src="getAvatar(item)" mode="aspectFill"></image>
  152. <view class="user-details">
  153. <text class="nickname">{{ getNickname(item) }}</text>
  154. </view>
  155. </view>
  156. <!-- 浏览内容 -->
  157. <view class="browse-content">
  158. <text class="browse-text">{{ item.content }}</text>
  159. </view>
  160. <!-- 浏览图片 -->
  161. <view class="browse-images" v-if="item.mediaUrls">
  162. <image
  163. class="browse-image"
  164. v-for="(img, index) in getMediaUrls(item.mediaUrls)"
  165. :key="index"
  166. :src="img"
  167. mode="aspectFill"
  168. @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
  169. ></image>
  170. </view>
  171. <!-- 浏览信息 -->
  172. <view class="browse-info">
  173. <text class="browse-time">{{ formatTime(item.createdAt) }}</text>
  174. <view class="browse-stats">
  175. <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
  176. <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
  177. </view>
  178. </view>
  179. </view>
  180. </view>
  181. </view>
  182. <!-- 发布动态按钮 -->
  183. <view class="publish-btn" @click="goPublish">
  184. <text class="publish-icon">+</text>
  185. </view>
  186. </view>
  187. </template>
  188. <script>
  189. import api from '../../utils/api.js'
  190. export default {
  191. data() {
  192. return {
  193. // 用户信息
  194. userInfo: {
  195. avatar: 'https://via.placeholder.com/100',
  196. nickname: '用户',
  197. dynamicCount: 0
  198. },
  199. // 活跃标签页
  200. activeTab: 'dynamic',
  201. // 活跃子标签(互动下的点赞/收藏)
  202. activeSubTab: 'like',
  203. // 动态列表数据
  204. dynamicList: [],
  205. // 互动列表数据
  206. interactionList: [],
  207. // 浏览记录列表数据
  208. browseList: [],
  209. // 默认头像
  210. defaultAvatar: 'https://via.placeholder.com/100x100.png?text=头像',
  211. // 我的动态分页相关
  212. dynamicPageNum: 1,
  213. dynamicPageSize: 10,
  214. dynamicLoading: false,
  215. dynamicNoMore: false
  216. }
  217. },
  218. onLoad() {
  219. // 页面加载时获取用户信息和动态数据
  220. this.loadUserInfo();
  221. // 初始化分页状态并加载第一页数据
  222. this.dynamicPageNum = 1;
  223. this.dynamicNoMore = false;
  224. this.dynamicList = [];
  225. this.loadDynamicData();
  226. },
  227. // 页面触底加载更多
  228. onReachBottom() {
  229. // 仅在“动态”标签下并且还有更多数据时加载
  230. if (this.activeTab === 'dynamic' && !this.dynamicNoMore && !this.dynamicLoading) {
  231. this.loadDynamicData();
  232. }
  233. },
  234. methods: {
  235. // 返回上一页
  236. goBack() {
  237. uni.navigateBack();
  238. },
  239. // 获取头像
  240. getAvatar(item) {
  241. if (item && item.user && item.user.avatarUrl) {
  242. return item.user.avatarUrl;
  243. }
  244. return this.defaultAvatar;
  245. },
  246. // 获取昵称
  247. getNickname(item) {
  248. if (item && item.user && item.user.nickname) {
  249. return item.user.nickname;
  250. }
  251. return '匿名用户';
  252. },
  253. // 切换标签页
  254. switchTab(tab) {
  255. this.activeTab = tab;
  256. // 根据标签页加载对应数据
  257. if (tab === 'dynamic') {
  258. // 切换回“动态”时,如果还没有数据或之前已经加载完一部分,继续使用当前分页状态
  259. if (this.dynamicList.length === 0) {
  260. this.dynamicPageNum = 1;
  261. this.dynamicNoMore = false;
  262. }
  263. this.loadDynamicData();
  264. } else if (tab === 'interaction') {
  265. this.loadInteractionData();
  266. } else if (tab === 'browse') {
  267. this.loadBrowseData();
  268. }
  269. },
  270. // 切换互动子标签
  271. switchSubTab(subTab) {
  272. if (this.activeSubTab === subTab) {
  273. return; // 如果点击的是当前标签,不重复加载
  274. }
  275. this.activeSubTab = subTab;
  276. // 先清空列表
  277. this.interactionList = [];
  278. // 根据子标签加载对应数据
  279. this.loadInteractionData();
  280. },
  281. // 加载用户信息
  282. loadUserInfo() {
  283. // 从本地存储获取用户信息
  284. const userInfo = uni.getStorageSync('userInfo');
  285. if (userInfo) {
  286. this.userInfo = {
  287. avatar: userInfo.avatarUrl || userInfo.avatar || 'https://via.placeholder.com/100',
  288. nickname: userInfo.nickname || '用户',
  289. dynamicCount: 0 // 后续从接口获取
  290. };
  291. // 加载用户动态数量
  292. this.loadUserDynamicCount();
  293. }
  294. },
  295. // 加载用户动态数量
  296. loadUserDynamicCount() {
  297. const userInfo = uni.getStorageSync('userInfo');
  298. if (userInfo && userInfo.userId) {
  299. // 获取用户动态数量
  300. api.dynamic.getUserDynamics(userInfo.userId, { pageNum: 1, pageSize: 1 }).then(res => {
  301. if (res && res.total) {
  302. this.userInfo.dynamicCount = res.total;
  303. }
  304. }).catch(err => {
  305. console.error('获取用户动态数量失败:', err);
  306. });
  307. }
  308. },
  309. // 加载动态数据
  310. async loadDynamicData() {
  311. const userInfo = uni.getStorageSync('userInfo');
  312. if (!userInfo || !userInfo.userId) {
  313. return;
  314. }
  315. if (this.dynamicLoading || this.dynamicNoMore) {
  316. return;
  317. }
  318. this.dynamicLoading = true;
  319. try {
  320. // 获取用户发布的动态列表(分页)
  321. const res = await api.dynamic.getUserDynamics(userInfo.userId, {
  322. pageNum: this.dynamicPageNum,
  323. pageSize: this.dynamicPageSize
  324. });
  325. if (res && res.records) {
  326. const records = res.records || [];
  327. if (this.dynamicPageNum === 1) {
  328. this.dynamicList = records;
  329. } else {
  330. this.dynamicList = [...this.dynamicList, ...records];
  331. }
  332. // 根据返回数量判断是否还有更多
  333. if (!records || records.length < this.dynamicPageSize) {
  334. this.dynamicNoMore = true;
  335. } else {
  336. this.dynamicPageNum += 1;
  337. }
  338. }
  339. } catch (err) {
  340. console.error('获取用户动态列表失败:', err);
  341. } finally {
  342. this.dynamicLoading = false;
  343. }
  344. },
  345. // 加载互动数据
  346. loadInteractionData() {
  347. const userInfo = uni.getStorageSync('userInfo');
  348. if (!userInfo || !userInfo.userId) {
  349. console.warn('用户信息不存在,无法加载互动数据');
  350. this.interactionList = [];
  351. return;
  352. }
  353. // 先清空列表,避免显示旧数据
  354. this.interactionList = [];
  355. if (this.activeSubTab === 'like') {
  356. // 获取用户点赞的动态列表
  357. console.log('开始加载点赞列表,userId:', userInfo.userId);
  358. api.dynamic.getLikedList(userInfo.userId, 1, 10).then(res => {
  359. console.log('点赞列表API返回数据:', res);
  360. if (res) {
  361. // 处理返回数据,支持多种可能的数据结构
  362. const records = res.records || res.list || (Array.isArray(res) ? res : []);
  363. this.interactionList = records || [];
  364. console.log('设置点赞列表,数量:', this.interactionList.length);
  365. } else {
  366. this.interactionList = [];
  367. console.log('点赞列表返回数据为空');
  368. }
  369. }).catch(err => {
  370. console.error('获取用户点赞列表失败:', err);
  371. this.interactionList = [];
  372. uni.showToast({
  373. title: '加载点赞列表失败',
  374. icon: 'none'
  375. });
  376. });
  377. } else if (this.activeSubTab === 'collect') {
  378. // 获取用户收藏的动态列表
  379. console.log('开始加载收藏列表,userId:', userInfo.userId);
  380. api.dynamic.getFavoritesList(userInfo.userId, 1, 10).then(res => {
  381. console.log('收藏列表API返回数据:', res);
  382. if (res) {
  383. // 处理返回数据,支持多种可能的数据结构
  384. const records = res.records || res.list || (Array.isArray(res) ? res : []);
  385. this.interactionList = records || [];
  386. console.log('设置收藏列表,数量:', this.interactionList.length);
  387. } else {
  388. this.interactionList = [];
  389. console.log('收藏列表返回数据为空');
  390. }
  391. }).catch(err => {
  392. console.error('获取用户收藏列表失败:', err);
  393. this.interactionList = [];
  394. uni.showToast({
  395. title: '加载收藏列表失败',
  396. icon: 'none'
  397. });
  398. });
  399. }
  400. },
  401. // 加载浏览记录数据
  402. loadBrowseData() {
  403. const userInfo = uni.getStorageSync('userInfo');
  404. if (userInfo && userInfo.userId) {
  405. // 获取用户浏览记录列表
  406. api.dynamic.getBrowseHistoryList(userInfo.userId, 1, 10).then(res => {
  407. if (res && res.records) {
  408. this.browseList = res.records;
  409. }
  410. }).catch(err => {
  411. console.error('获取用户浏览记录失败:', err);
  412. });
  413. }
  414. },
  415. // 跳转到发布动态页面
  416. goPublish() {
  417. uni.navigateTo({
  418. url: '/pages/plaza/publish'
  419. });
  420. },
  421. // 跳转到动态详情页
  422. goToDetail(dynamicId) {
  423. uni.navigateTo({
  424. url: `/pages/plaza/detail?id=${dynamicId}`
  425. });
  426. },
  427. // 格式化时间
  428. formatTime(timeStr) {
  429. if (!timeStr) return '';
  430. const date = new Date(timeStr);
  431. const now = new Date();
  432. const diff = now - date;
  433. const minute = 60 * 1000;
  434. const hour = minute * 60;
  435. const day = hour * 24;
  436. const month = day * 30;
  437. const year = day * 365;
  438. if (diff < minute) {
  439. return '刚刚';
  440. } else if (diff < hour) {
  441. return Math.floor(diff / minute) + '分钟前';
  442. } else if (diff < day) {
  443. return Math.floor(diff / hour) + '小时前';
  444. } else if (diff < month) {
  445. return Math.floor(diff / day) + '天前';
  446. } else if (diff < year) {
  447. return Math.floor(diff / month) + '个月前';
  448. } else {
  449. return Math.floor(diff / year) + '年前';
  450. }
  451. },
  452. // 处理媒体URL,支持数组、JSON字符串、逗号分隔等多种格式
  453. getMediaUrls(mediaUrls) {
  454. if (!mediaUrls) return [];
  455. const isLikelyImage = (u) => {
  456. if (typeof u !== 'string' || !u.trim()) return false;
  457. const url = u.trim();
  458. const hasExt = /(\.png|\.jpg|\.jpeg|\.gif|\.webp|\.bmp)(\?|$)/i.test(url);
  459. const isHttp = /^https?:\/\//i.test(url);
  460. return hasExt || isHttp;
  461. };
  462. try {
  463. if (Array.isArray(mediaUrls)) {
  464. return mediaUrls.filter(isLikelyImage);
  465. }
  466. if (typeof mediaUrls === 'string') {
  467. const s = mediaUrls.trim();
  468. // JSON数组字符串: ["url1", "url2"]
  469. if (s.startsWith('[')) {
  470. const arr = JSON.parse(s);
  471. return Array.isArray(arr) ? arr.filter(isLikelyImage) : [];
  472. }
  473. // 逗号分隔或带引号
  474. return s.split(',')
  475. .map(x => x.trim().replace(/^\[|\]$/g, '').replace(/^['"]|['"]$/g, ''))
  476. .filter(isLikelyImage);
  477. }
  478. } catch (e) {
  479. // ignore parse error
  480. console.error('解析媒体URL失败:', e);
  481. }
  482. return [];
  483. },
  484. // 预览图片
  485. previewImage(urls, current) {
  486. uni.previewImage({
  487. urls: urls,
  488. current: current,
  489. longPressActions: {
  490. itemList: ['保存图片'],
  491. success: function(data) {
  492. console.log('长按图片操作结果:', data);
  493. },
  494. fail: function(err) {
  495. console.error('长按图片操作失败:', err);
  496. }
  497. }
  498. });
  499. },
  500. // 显示操作菜单
  501. showActionMenu(item) {
  502. uni.showActionSheet({
  503. itemList: ['编辑动态', '删除动态'],
  504. success: (res) => {
  505. if (res.tapIndex === 0) {
  506. // 编辑动态
  507. this.editDynamic(item);
  508. } else if (res.tapIndex === 1) {
  509. // 删除动态
  510. this.deleteDynamic(item);
  511. }
  512. }
  513. });
  514. },
  515. // 编辑动态
  516. editDynamic(item) {
  517. // 跳转到编辑页面,传递动态信息
  518. uni.navigateTo({
  519. url: `/pages/plaza/publish?edit=true&dynamicId=${item.dynamicId}&content=${encodeURIComponent(item.content || '')}&mediaUrls=${encodeURIComponent(JSON.stringify(item.mediaUrls || []))}&mediaType=${item.mediaType || 0}&visibility=${item.visibility || 1}`
  520. });
  521. },
  522. // 删除动态
  523. deleteDynamic(item) {
  524. uni.showModal({
  525. title: '确认删除',
  526. content: '确定要删除这条动态吗?删除后无法恢复。',
  527. confirmText: '删除',
  528. confirmColor: '#E91E63',
  529. success: async (res) => {
  530. if (res.confirm) {
  531. try {
  532. const userInfo = uni.getStorageSync('userInfo');
  533. if (!userInfo || !userInfo.userId) {
  534. uni.showToast({
  535. title: '请先登录',
  536. icon: 'none'
  537. });
  538. return;
  539. }
  540. uni.showLoading({
  541. title: '删除中...'
  542. });
  543. await api.dynamic.deleteUserDynamic(item.dynamicId, userInfo.userId);
  544. uni.hideLoading();
  545. uni.showToast({
  546. title: '删除成功',
  547. icon: 'success'
  548. });
  549. // 重新加载动态列表
  550. this.loadDynamicData();
  551. } catch (error) {
  552. uni.hideLoading();
  553. console.error('删除动态失败:', error);
  554. uni.showToast({
  555. title: error.message || '删除失败',
  556. icon: 'none'
  557. });
  558. }
  559. }
  560. }
  561. });
  562. },
  563. // 显示互动操作菜单
  564. showInteractionMenu(item) {
  565. const menuItems = [];
  566. if (this.activeSubTab === 'like') {
  567. menuItems.push('取消点赞');
  568. } else if (this.activeSubTab === 'collect') {
  569. menuItems.push('取消收藏');
  570. }
  571. if (menuItems.length === 0) return;
  572. uni.showActionSheet({
  573. itemList: menuItems,
  574. success: (res) => {
  575. if (res.tapIndex === 0) {
  576. if (this.activeSubTab === 'like') {
  577. this.cancelLike(item);
  578. } else if (this.activeSubTab === 'collect') {
  579. this.cancelFavorite(item);
  580. }
  581. }
  582. }
  583. });
  584. },
  585. // 取消点赞
  586. async cancelLike(item) {
  587. try {
  588. const userInfo = uni.getStorageSync('userInfo');
  589. if (!userInfo || !userInfo.userId) {
  590. uni.showToast({
  591. title: '请先登录',
  592. icon: 'none'
  593. });
  594. return;
  595. }
  596. uni.showLoading({
  597. title: '处理中...'
  598. });
  599. await api.dynamic.unlike(item.dynamicId, userInfo.userId);
  600. uni.hideLoading();
  601. uni.showToast({
  602. title: '已取消点赞',
  603. icon: 'success'
  604. });
  605. // 通知其他页面更新状态(如首页、详情页)
  606. uni.$emit('dynamic-updated', {
  607. dynamicId: item.dynamicId,
  608. isLiked: false,
  609. likeCount: Math.max(0, (item.likeCount || 0) - 1)
  610. });
  611. // 从列表中移除该项
  612. const index = this.interactionList.findIndex(i => i.dynamicId === item.dynamicId);
  613. if (index !== -1) {
  614. this.interactionList.splice(index, 1);
  615. }
  616. } catch (error) {
  617. uni.hideLoading();
  618. console.error('取消点赞失败:', error);
  619. uni.showToast({
  620. title: error.message || '操作失败',
  621. icon: 'none'
  622. });
  623. }
  624. },
  625. // 取消收藏
  626. async cancelFavorite(item) {
  627. try {
  628. const userInfo = uni.getStorageSync('userInfo');
  629. if (!userInfo || !userInfo.userId) {
  630. uni.showToast({
  631. title: '请先登录',
  632. icon: 'none'
  633. });
  634. return;
  635. }
  636. uni.showLoading({
  637. title: '处理中...'
  638. });
  639. await api.dynamic.unfavorite(item.dynamicId, userInfo.userId);
  640. uni.hideLoading();
  641. uni.showToast({
  642. title: '已取消收藏',
  643. icon: 'success'
  644. });
  645. // 通知其他页面更新状态(如首页、详情页)
  646. uni.$emit('dynamic-updated', {
  647. dynamicId: item.dynamicId,
  648. isFavorited: false,
  649. favoriteCount: Math.max(0, (item.favoriteCount || 0) - 1)
  650. });
  651. // 从列表中移除该项
  652. const index = this.interactionList.findIndex(i => i.dynamicId === item.dynamicId);
  653. if (index !== -1) {
  654. this.interactionList.splice(index, 1);
  655. }
  656. } catch (error) {
  657. uni.hideLoading();
  658. console.error('取消收藏失败:', error);
  659. uni.showToast({
  660. title: error.message || '操作失败',
  661. icon: 'none'
  662. });
  663. }
  664. }
  665. }
  666. }
  667. </script>
  668. <style lang="scss" scoped>
  669. .my-dynamics-page {
  670. min-height: 100vh;
  671. background: #F5F5F5;
  672. padding-bottom: 100rpx;
  673. }
  674. /* 顶部导航栏 */
  675. .top-nav {
  676. display: flex;
  677. align-items: center;
  678. justify-content: space-between;
  679. padding: 40rpx 30rpx 20rpx;
  680. background: #FFFFFF;
  681. position: relative;
  682. z-index: 10;
  683. .back-btn {
  684. width: 60rpx;
  685. height: 60rpx;
  686. display: flex;
  687. align-items: center;
  688. justify-content: center;
  689. .back-icon {
  690. font-size: 48rpx;
  691. color: #333333;
  692. font-weight: bold;
  693. }
  694. }
  695. .nav-title {
  696. font-size: 36rpx;
  697. font-weight: bold;
  698. color: #333333;
  699. }
  700. .nav-right {
  701. width: 60rpx;
  702. }
  703. }
  704. /* 用户信息区域 */
  705. .user-info-section {
  706. display: flex;
  707. align-items: center;
  708. padding: 30rpx;
  709. background: #FFFFFF;
  710. margin-bottom: 20rpx;
  711. .avatar {
  712. width: 120rpx;
  713. height: 120rpx;
  714. border-radius: 60rpx;
  715. margin-right: 20rpx;
  716. background-color: #E0E0E0;
  717. }
  718. .user-details {
  719. display: flex;
  720. flex-direction: column;
  721. justify-content: center;
  722. .nickname {
  723. font-size: 32rpx;
  724. font-weight: bold;
  725. color: #333333;
  726. margin-bottom: 8rpx;
  727. }
  728. .dynamic-count {
  729. font-size: 24rpx;
  730. color: #999999;
  731. }
  732. }
  733. }
  734. /* 标签页导航 */
  735. .tab-nav {
  736. display: flex;
  737. background: #FFFFFF;
  738. border-bottom: 2rpx solid #F0F0F0;
  739. margin-bottom: 20rpx;
  740. .tab-item {
  741. flex: 1;
  742. text-align: center;
  743. padding: 30rpx 0;
  744. position: relative;
  745. text {
  746. font-size: 30rpx;
  747. color: #666666;
  748. }
  749. &.active {
  750. text {
  751. color: #E91E63;
  752. font-weight: bold;
  753. }
  754. &::after {
  755. content: '';
  756. position: absolute;
  757. bottom: 0;
  758. left: 50%;
  759. transform: translateX(-50%);
  760. width: 60rpx;
  761. height: 6rpx;
  762. background: #E91E63;
  763. border-radius: 3rpx;
  764. }
  765. }
  766. }
  767. }
  768. /* 互动子标签导航 */
  769. .sub-tab-nav {
  770. display: flex;
  771. background: #FFFFFF;
  772. padding: 0 30rpx;
  773. margin-bottom: 20rpx;
  774. .sub-tab-item {
  775. padding: 20rpx 30rpx;
  776. margin-right: 40rpx;
  777. text {
  778. font-size: 26rpx;
  779. color: #999999;
  780. }
  781. &.active {
  782. text {
  783. color: #E91E63;
  784. font-weight: bold;
  785. }
  786. }
  787. }
  788. }
  789. /* 内容区域 */
  790. .content-area {
  791. padding: 0 30rpx;
  792. }
  793. /* 列表通用样式 */
  794. .dynamic-list,
  795. .interaction-list,
  796. .browse-list {
  797. background: #FFFFFF;
  798. border-radius: 12rpx;
  799. overflow: hidden;
  800. }
  801. .dynamic-item,
  802. .interaction-item,
  803. .browse-item {
  804. padding: 30rpx;
  805. border-bottom: 2rpx solid #F5F5F5;
  806. position: relative;
  807. &:last-child {
  808. border-bottom: none;
  809. }
  810. }
  811. /* 动态操作菜单 */
  812. .dynamic-menu {
  813. position: absolute;
  814. top: 30rpx;
  815. right: 30rpx;
  816. width: 60rpx;
  817. height: 60rpx;
  818. display: flex;
  819. align-items: center;
  820. justify-content: center;
  821. z-index: 10;
  822. .menu-icon {
  823. font-size: 40rpx;
  824. color: #999999;
  825. font-weight: bold;
  826. line-height: 1;
  827. }
  828. }
  829. /* 互动操作菜单 */
  830. .interaction-menu {
  831. position: absolute;
  832. top: 30rpx;
  833. right: 30rpx;
  834. width: 60rpx;
  835. height: 60rpx;
  836. display: flex;
  837. align-items: center;
  838. justify-content: center;
  839. z-index: 10;
  840. .menu-icon {
  841. font-size: 40rpx;
  842. color: #999999;
  843. font-weight: bold;
  844. line-height: 1;
  845. }
  846. }
  847. /* 用户信息 */
  848. .user-info {
  849. display: flex;
  850. align-items: center;
  851. margin-bottom: 20rpx;
  852. .avatar {
  853. width: 80rpx;
  854. height: 80rpx;
  855. border-radius: 40rpx;
  856. margin-right: 20rpx;
  857. background-color: #E0E0E0;
  858. }
  859. .user-details {
  860. display: flex;
  861. flex-direction: column;
  862. .nickname {
  863. font-size: 28rpx;
  864. font-weight: bold;
  865. color: #333333;
  866. margin-bottom: 4rpx;
  867. }
  868. }
  869. }
  870. /* 内容文本 */
  871. .dynamic-content,
  872. .interaction-content,
  873. .browse-content {
  874. margin-bottom: 20rpx;
  875. .dynamic-text,
  876. .interaction-text,
  877. .browse-text {
  878. font-size: 28rpx;
  879. color: #333333;
  880. line-height: 44rpx;
  881. }
  882. }
  883. /* 图片列表 */
  884. .dynamic-images,
  885. .interaction-images,
  886. .browse-images {
  887. display: flex;
  888. flex-wrap: wrap;
  889. gap: 15rpx;
  890. margin-bottom: 20rpx;
  891. .dynamic-image,
  892. .interaction-image,
  893. .browse-image {
  894. width: calc((100% - 30rpx) / 3);
  895. height: 200rpx;
  896. border-radius: 8rpx;
  897. background-color: #E0E0E0;
  898. }
  899. }
  900. /* 信息栏 */
  901. .dynamic-info,
  902. .interaction-info,
  903. .browse-info {
  904. display: flex;
  905. align-items: center;
  906. justify-content: space-between;
  907. .dynamic-time,
  908. .interaction-time,
  909. .browse-time {
  910. font-size: 22rpx;
  911. color: #999999;
  912. }
  913. .dynamic-stats,
  914. .interaction-stats,
  915. .browse-stats {
  916. display: flex;
  917. align-items: center;
  918. .stat-item {
  919. font-size: 22rpx;
  920. color: #999999;
  921. margin-left: 30rpx;
  922. }
  923. }
  924. }
  925. /* 空状态提示 */
  926. .empty-tip {
  927. padding: 100rpx 0;
  928. text-align: center;
  929. text {
  930. font-size: 28rpx;
  931. color: #999999;
  932. }
  933. }
  934. /* 发布动态按钮 */
  935. .publish-btn {
  936. position: fixed;
  937. bottom: 80rpx;
  938. right: 40rpx;
  939. width: 100rpx;
  940. height: 100rpx;
  941. background: #E91E63;
  942. border-radius: 50rpx;
  943. display: flex;
  944. align-items: center;
  945. justify-content: center;
  946. box-shadow: 0 8rpx 20rpx rgba(233, 30, 99, 0.3);
  947. z-index: 999;
  948. .publish-icon {
  949. font-size: 60rpx;
  950. color: #FFFFFF;
  951. font-weight: bold;
  952. line-height: 1;
  953. }
  954. }
  955. /* 页面背景色 */
  956. .my-dynamics-page {
  957. background-color: #F5F5F5;
  958. }
  959. </style>