my-dynamics.vue 27 KB

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