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. api.dynamic.getLikedList(userInfo.userId, 1, 10).then(res => {
  358. if (res) {
  359. // 处理返回数据,支持多种可能的数据结构
  360. const records = res.records || res.list || (Array.isArray(res) ? res : []);
  361. this.interactionList = records || [];
  362. } else {
  363. this.interactionList = [];
  364. }
  365. }).catch(err => {
  366. console.error('获取用户点赞列表失败:', err);
  367. this.interactionList = [];
  368. uni.showToast({
  369. title: '加载点赞列表失败',
  370. icon: 'none'
  371. });
  372. });
  373. } else if (this.activeSubTab === 'collect') {
  374. // 获取用户收藏的动态列表
  375. api.dynamic.getFavoritesList(userInfo.userId, 1, 10).then(res => {
  376. if (res) {
  377. // 处理返回数据,支持多种可能的数据结构
  378. const records = res.records || res.list || (Array.isArray(res) ? res : []);
  379. this.interactionList = records || [];
  380. } else {
  381. this.interactionList = [];
  382. }
  383. }).catch(err => {
  384. console.error('获取用户收藏列表失败:', err);
  385. this.interactionList = [];
  386. uni.showToast({
  387. title: '加载收藏列表失败',
  388. icon: 'none'
  389. });
  390. });
  391. }
  392. },
  393. // 加载浏览记录数据
  394. loadBrowseData() {
  395. const userInfo = uni.getStorageSync('userInfo');
  396. if (userInfo && userInfo.userId) {
  397. // 获取用户浏览记录列表
  398. api.dynamic.getBrowseHistoryList(userInfo.userId, 1, 10).then(res => {
  399. if (res && res.records) {
  400. this.browseList = res.records;
  401. }
  402. }).catch(err => {
  403. console.error('获取用户浏览记录失败:', err);
  404. });
  405. }
  406. },
  407. // 跳转到发布动态页面
  408. goPublish() {
  409. uni.navigateTo({
  410. url: '/pages/plaza/publish'
  411. });
  412. },
  413. // 跳转到动态详情页
  414. goToDetail(dynamicId) {
  415. uni.navigateTo({
  416. url: `/pages/plaza/detail?id=${dynamicId}`
  417. });
  418. },
  419. // 格式化时间
  420. formatTime(timeStr) {
  421. if (!timeStr) return '';
  422. const date = new Date(timeStr);
  423. const now = new Date();
  424. const diff = now - date;
  425. const minute = 60 * 1000;
  426. const hour = minute * 60;
  427. const day = hour * 24;
  428. const month = day * 30;
  429. const year = day * 365;
  430. if (diff < minute) {
  431. return '刚刚';
  432. } else if (diff < hour) {
  433. return Math.floor(diff / minute) + '分钟前';
  434. } else if (diff < day) {
  435. return Math.floor(diff / hour) + '小时前';
  436. } else if (diff < month) {
  437. return Math.floor(diff / day) + '天前';
  438. } else if (diff < year) {
  439. return Math.floor(diff / month) + '个月前';
  440. } else {
  441. return Math.floor(diff / year) + '年前';
  442. }
  443. },
  444. // 处理媒体URL,支持数组、JSON字符串、逗号分隔等多种格式
  445. getMediaUrls(mediaUrls) {
  446. if (!mediaUrls) return [];
  447. const isLikelyImage = (u) => {
  448. if (typeof u !== 'string' || !u.trim()) return false;
  449. const url = u.trim();
  450. const hasExt = /(\.png|\.jpg|\.jpeg|\.gif|\.webp|\.bmp)(\?|$)/i.test(url);
  451. const isHttp = /^https?:\/\//i.test(url);
  452. return hasExt || isHttp;
  453. };
  454. try {
  455. if (Array.isArray(mediaUrls)) {
  456. return mediaUrls.filter(isLikelyImage);
  457. }
  458. if (typeof mediaUrls === 'string') {
  459. const s = mediaUrls.trim();
  460. // JSON数组字符串: ["url1", "url2"]
  461. if (s.startsWith('[')) {
  462. const arr = JSON.parse(s);
  463. return Array.isArray(arr) ? arr.filter(isLikelyImage) : [];
  464. }
  465. // 逗号分隔或带引号
  466. return s.split(',')
  467. .map(x => x.trim().replace(/^\[|\]$/g, '').replace(/^['"]|['"]$/g, ''))
  468. .filter(isLikelyImage);
  469. }
  470. } catch (e) {
  471. // ignore parse error
  472. console.error('解析媒体URL失败:', e);
  473. }
  474. return [];
  475. },
  476. // 预览图片
  477. previewImage(urls, current) {
  478. uni.previewImage({
  479. urls: urls,
  480. current: current,
  481. longPressActions: {
  482. itemList: ['保存图片'],
  483. success: function(data) {
  484. },
  485. fail: function(err) {
  486. console.error('长按图片操作失败:', err);
  487. }
  488. }
  489. });
  490. },
  491. // 显示操作菜单
  492. showActionMenu(item) {
  493. uni.showActionSheet({
  494. itemList: ['编辑动态', '删除动态'],
  495. success: (res) => {
  496. if (res.tapIndex === 0) {
  497. // 编辑动态
  498. this.editDynamic(item);
  499. } else if (res.tapIndex === 1) {
  500. // 删除动态
  501. this.deleteDynamic(item);
  502. }
  503. }
  504. });
  505. },
  506. // 编辑动态
  507. editDynamic(item) {
  508. // 跳转到编辑页面,传递动态信息
  509. uni.navigateTo({
  510. 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}`
  511. });
  512. },
  513. // 删除动态
  514. deleteDynamic(item) {
  515. uni.showModal({
  516. title: '确认删除',
  517. content: '确定要删除这条动态吗?删除后无法恢复。',
  518. confirmText: '删除',
  519. confirmColor: '#E91E63',
  520. success: async (res) => {
  521. if (res.confirm) {
  522. try {
  523. const userInfo = uni.getStorageSync('userInfo');
  524. if (!userInfo || !userInfo.userId) {
  525. uni.showToast({
  526. title: '请先登录',
  527. icon: 'none'
  528. });
  529. return;
  530. }
  531. uni.showLoading({
  532. title: '删除中...'
  533. });
  534. await api.dynamic.deleteUserDynamic(item.dynamicId, userInfo.userId);
  535. uni.hideLoading();
  536. uni.showToast({
  537. title: '删除成功',
  538. icon: 'success'
  539. });
  540. // 重新加载动态列表
  541. this.loadDynamicData();
  542. } catch (error) {
  543. uni.hideLoading();
  544. console.error('删除动态失败:', error);
  545. uni.showToast({
  546. title: error.message || '删除失败',
  547. icon: 'none'
  548. });
  549. }
  550. }
  551. }
  552. });
  553. },
  554. // 显示互动操作菜单
  555. showInteractionMenu(item) {
  556. const menuItems = [];
  557. if (this.activeSubTab === 'like') {
  558. menuItems.push('取消点赞');
  559. } else if (this.activeSubTab === 'collect') {
  560. menuItems.push('取消收藏');
  561. }
  562. if (menuItems.length === 0) return;
  563. uni.showActionSheet({
  564. itemList: menuItems,
  565. success: (res) => {
  566. if (res.tapIndex === 0) {
  567. if (this.activeSubTab === 'like') {
  568. this.cancelLike(item);
  569. } else if (this.activeSubTab === 'collect') {
  570. this.cancelFavorite(item);
  571. }
  572. }
  573. }
  574. });
  575. },
  576. // 取消点赞
  577. async cancelLike(item) {
  578. try {
  579. const userInfo = uni.getStorageSync('userInfo');
  580. if (!userInfo || !userInfo.userId) {
  581. uni.showToast({
  582. title: '请先登录',
  583. icon: 'none'
  584. });
  585. return;
  586. }
  587. uni.showLoading({
  588. title: '处理中...'
  589. });
  590. await api.dynamic.unlike(item.dynamicId, userInfo.userId);
  591. uni.hideLoading();
  592. uni.showToast({
  593. title: '已取消点赞',
  594. icon: 'success'
  595. });
  596. // 通知其他页面更新状态(如首页、详情页)
  597. uni.$emit('dynamic-updated', {
  598. dynamicId: item.dynamicId,
  599. isLiked: false,
  600. likeCount: Math.max(0, (item.likeCount || 0) - 1)
  601. });
  602. // 从列表中移除该项
  603. const index = this.interactionList.findIndex(i => i.dynamicId === item.dynamicId);
  604. if (index !== -1) {
  605. this.interactionList.splice(index, 1);
  606. }
  607. } catch (error) {
  608. uni.hideLoading();
  609. console.error('取消点赞失败:', error);
  610. uni.showToast({
  611. title: error.message || '操作失败',
  612. icon: 'none'
  613. });
  614. }
  615. },
  616. // 取消收藏
  617. async cancelFavorite(item) {
  618. try {
  619. const userInfo = uni.getStorageSync('userInfo');
  620. if (!userInfo || !userInfo.userId) {
  621. uni.showToast({
  622. title: '请先登录',
  623. icon: 'none'
  624. });
  625. return;
  626. }
  627. uni.showLoading({
  628. title: '处理中...'
  629. });
  630. await api.dynamic.unfavorite(item.dynamicId, userInfo.userId);
  631. uni.hideLoading();
  632. uni.showToast({
  633. title: '已取消收藏',
  634. icon: 'success'
  635. });
  636. // 通知其他页面更新状态(如首页、详情页)
  637. uni.$emit('dynamic-updated', {
  638. dynamicId: item.dynamicId,
  639. isFavorited: false,
  640. favoriteCount: Math.max(0, (item.favoriteCount || 0) - 1)
  641. });
  642. // 从列表中移除该项
  643. const index = this.interactionList.findIndex(i => i.dynamicId === item.dynamicId);
  644. if (index !== -1) {
  645. this.interactionList.splice(index, 1);
  646. }
  647. } catch (error) {
  648. uni.hideLoading();
  649. console.error('取消收藏失败:', error);
  650. uni.showToast({
  651. title: error.message || '操作失败',
  652. icon: 'none'
  653. });
  654. }
  655. }
  656. }
  657. }
  658. </script>
  659. <style lang="scss" scoped>
  660. .my-dynamics-page {
  661. min-height: 100vh;
  662. background: #F5F5F5;
  663. padding-bottom: 100rpx;
  664. }
  665. /* 顶部导航栏 */
  666. .top-nav {
  667. display: flex;
  668. align-items: center;
  669. justify-content: space-between;
  670. padding: 40rpx 30rpx 20rpx;
  671. background: #FFFFFF;
  672. position: relative;
  673. z-index: 10;
  674. .back-btn {
  675. width: 60rpx;
  676. height: 60rpx;
  677. display: flex;
  678. align-items: center;
  679. justify-content: center;
  680. .back-icon {
  681. font-size: 48rpx;
  682. color: #333333;
  683. font-weight: bold;
  684. }
  685. }
  686. .nav-title {
  687. font-size: 36rpx;
  688. font-weight: bold;
  689. color: #333333;
  690. }
  691. .nav-right {
  692. width: 60rpx;
  693. }
  694. }
  695. /* 用户信息区域 */
  696. .user-info-section {
  697. display: flex;
  698. align-items: center;
  699. padding: 30rpx;
  700. background: #FFFFFF;
  701. margin-bottom: 20rpx;
  702. .avatar {
  703. width: 120rpx;
  704. height: 120rpx;
  705. border-radius: 60rpx;
  706. margin-right: 20rpx;
  707. background-color: #E0E0E0;
  708. }
  709. .user-details {
  710. display: flex;
  711. flex-direction: column;
  712. justify-content: center;
  713. .nickname {
  714. font-size: 32rpx;
  715. font-weight: bold;
  716. color: #333333;
  717. margin-bottom: 8rpx;
  718. }
  719. .dynamic-count {
  720. font-size: 24rpx;
  721. color: #999999;
  722. }
  723. }
  724. }
  725. /* 标签页导航 */
  726. .tab-nav {
  727. display: flex;
  728. background: #FFFFFF;
  729. border-bottom: 2rpx solid #F0F0F0;
  730. margin-bottom: 20rpx;
  731. .tab-item {
  732. flex: 1;
  733. text-align: center;
  734. padding: 30rpx 0;
  735. position: relative;
  736. text {
  737. font-size: 30rpx;
  738. color: #666666;
  739. }
  740. &.active {
  741. text {
  742. color: #E91E63;
  743. font-weight: bold;
  744. }
  745. &::after {
  746. content: '';
  747. position: absolute;
  748. bottom: 0;
  749. left: 50%;
  750. transform: translateX(-50%);
  751. width: 60rpx;
  752. height: 6rpx;
  753. background: #E91E63;
  754. border-radius: 3rpx;
  755. }
  756. }
  757. }
  758. }
  759. /* 互动子标签导航 */
  760. .sub-tab-nav {
  761. display: flex;
  762. background: #FFFFFF;
  763. padding: 0 30rpx;
  764. margin-bottom: 20rpx;
  765. .sub-tab-item {
  766. padding: 20rpx 30rpx;
  767. margin-right: 40rpx;
  768. text {
  769. font-size: 26rpx;
  770. color: #999999;
  771. }
  772. &.active {
  773. text {
  774. color: #E91E63;
  775. font-weight: bold;
  776. }
  777. }
  778. }
  779. }
  780. /* 内容区域 */
  781. .content-area {
  782. padding: 0 30rpx;
  783. }
  784. /* 列表通用样式 */
  785. .dynamic-list,
  786. .interaction-list,
  787. .browse-list {
  788. background: #FFFFFF;
  789. border-radius: 12rpx;
  790. overflow: hidden;
  791. }
  792. .dynamic-item,
  793. .interaction-item,
  794. .browse-item {
  795. padding: 30rpx;
  796. border-bottom: 2rpx solid #F5F5F5;
  797. position: relative;
  798. &:last-child {
  799. border-bottom: none;
  800. }
  801. }
  802. /* 动态操作菜单 */
  803. .dynamic-menu {
  804. position: absolute;
  805. top: 30rpx;
  806. right: 30rpx;
  807. width: 60rpx;
  808. height: 60rpx;
  809. display: flex;
  810. align-items: center;
  811. justify-content: center;
  812. z-index: 10;
  813. .menu-icon {
  814. font-size: 40rpx;
  815. color: #999999;
  816. font-weight: bold;
  817. line-height: 1;
  818. }
  819. }
  820. /* 互动操作菜单 */
  821. .interaction-menu {
  822. position: absolute;
  823. top: 30rpx;
  824. right: 30rpx;
  825. width: 60rpx;
  826. height: 60rpx;
  827. display: flex;
  828. align-items: center;
  829. justify-content: center;
  830. z-index: 10;
  831. .menu-icon {
  832. font-size: 40rpx;
  833. color: #999999;
  834. font-weight: bold;
  835. line-height: 1;
  836. }
  837. }
  838. /* 用户信息 */
  839. .user-info {
  840. display: flex;
  841. align-items: center;
  842. margin-bottom: 20rpx;
  843. .avatar {
  844. width: 80rpx;
  845. height: 80rpx;
  846. border-radius: 40rpx;
  847. margin-right: 20rpx;
  848. background-color: #E0E0E0;
  849. }
  850. .user-details {
  851. display: flex;
  852. flex-direction: column;
  853. .nickname {
  854. font-size: 28rpx;
  855. font-weight: bold;
  856. color: #333333;
  857. margin-bottom: 4rpx;
  858. }
  859. }
  860. }
  861. /* 内容文本 */
  862. .dynamic-content,
  863. .interaction-content,
  864. .browse-content {
  865. margin-bottom: 20rpx;
  866. .dynamic-text,
  867. .interaction-text,
  868. .browse-text {
  869. font-size: 28rpx;
  870. color: #333333;
  871. line-height: 44rpx;
  872. }
  873. }
  874. /* 图片列表 */
  875. .dynamic-images,
  876. .interaction-images,
  877. .browse-images {
  878. display: flex;
  879. flex-wrap: wrap;
  880. gap: 15rpx;
  881. margin-bottom: 20rpx;
  882. .dynamic-image,
  883. .interaction-image,
  884. .browse-image {
  885. width: calc((100% - 30rpx) / 3);
  886. height: 200rpx;
  887. border-radius: 8rpx;
  888. background-color: #E0E0E0;
  889. }
  890. }
  891. /* 信息栏 */
  892. .dynamic-info,
  893. .interaction-info,
  894. .browse-info {
  895. display: flex;
  896. align-items: center;
  897. justify-content: space-between;
  898. .dynamic-time,
  899. .interaction-time,
  900. .browse-time {
  901. font-size: 22rpx;
  902. color: #999999;
  903. }
  904. .dynamic-stats,
  905. .interaction-stats,
  906. .browse-stats {
  907. display: flex;
  908. align-items: center;
  909. .stat-item {
  910. font-size: 22rpx;
  911. color: #999999;
  912. margin-left: 30rpx;
  913. }
  914. }
  915. }
  916. /* 空状态提示 */
  917. .empty-tip {
  918. padding: 100rpx 0;
  919. text-align: center;
  920. text {
  921. font-size: 28rpx;
  922. color: #999999;
  923. }
  924. }
  925. /* 发布动态按钮 */
  926. .publish-btn {
  927. position: fixed;
  928. bottom: 80rpx;
  929. right: 40rpx;
  930. width: 100rpx;
  931. height: 100rpx;
  932. background: #E91E63;
  933. border-radius: 50rpx;
  934. display: flex;
  935. align-items: center;
  936. justify-content: center;
  937. box-shadow: 0 8rpx 20rpx rgba(233, 30, 99, 0.3);
  938. z-index: 999;
  939. .publish-icon {
  940. font-size: 60rpx;
  941. color: #FFFFFF;
  942. font-weight: bold;
  943. line-height: 1;
  944. }
  945. }
  946. /* 页面背景色 */
  947. .my-dynamics-page {
  948. background-color: #F5F5F5;
  949. }
  950. </style>