Ver Fonte

Merge branch 'master' of http://www.sysuimars.cn:3000/feiniao/adopt-mini-h5

lxf há 1 semana atrás
pai
commit
510eef7819
38 ficheiros alterados com 3188 adições e 479 exclusões
  1. 1 0
      .gitignore
  2. 24 0
      index.html
  3. 882 0
      public/data/comments/video_id_7128686458763889956.json
  4. 882 0
      public/data/comments/video_id_7267478481213181238.json
  5. 0 4
      public/footer/tab-farm-active.svg
  6. 0 4
      public/footer/tab-farm.svg
  7. 0 6
      public/footer/tab-guard-active.svg
  8. 0 6
      public/footer/tab-guard.svg
  9. 0 5
      public/footer/tab-map-active.svg
  10. 0 5
      public/footer/tab-map.svg
  11. BIN
      public/video/portrait.mp4
  12. BIN
      public/video/portrait2.mp4
  13. 5 0
      src/App.vue
  14. BIN
      src/assets/img/tabbar/tab-1.png
  15. BIN
      src/assets/img/tabbar/tab-2.png
  16. BIN
      src/assets/img/tabbar/tab-3.png
  17. BIN
      src/assets/img/tabbar/tab-act-1.png
  18. BIN
      src/assets/img/tabbar/tab-act-2.png
  19. BIN
      src/assets/img/tabbar/tab-act-3.png
  20. 12 1
      src/assets/less/index.less
  21. 81 26
      src/components/BaseFooter.vue
  22. 422 100
      src/components/Comment.vue
  23. 117 107
      src/components/UserPanel.vue
  24. 190 0
      src/components/dialog/FromBottomDialog.vue
  25. 77 7
      src/components/video/BaseVideo.vue
  26. 36 7
      src/components/video/ItemDesc.vue
  27. 22 12
      src/components/video/ItemToolbar.vue
  28. 144 48
      src/mock/homeData.ts
  29. 2 0
      src/utils/bus.ts
  30. 66 0
      src/utils/dom.ts
  31. 17 0
      src/utils/hooks/useClick.ts
  32. 60 0
      src/utils/index.ts
  33. 55 25
      src/views/home/components/IndicatorHome.vue
  34. 14 11
      src/views/home/components/VideoFeed.vue
  35. 74 50
      src/views/home/index.vue
  36. 3 49
      src/views/home/slide/Slide0.vue
  37. 2 1
      src/views/home/slide/Slide4.vue
  38. 0 5
      src/views/my-guard/index.vue

+ 1 - 0
.gitignore

@@ -11,6 +11,7 @@
 *.user
 *.userosscache
 *.sln.docstates
+.DS_Store
 
 # User-specific files (MonoDevelop/Xamarin Studio)
 *.userprefs

+ 24 - 0
index.html

@@ -8,6 +8,30 @@
       content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
     />
     <title>飞鸟管家</title>
+    <style>
+      .fade-in {
+        animation: fade-in 0.3s;
+      }
+      .fade-out {
+        animation: fade-out 0.4s;
+      }
+      @keyframes fade-in {
+        from {
+          opacity: 0;
+        }
+        to {
+          opacity: 1;
+        }
+      }
+      @keyframes fade-out {
+        from {
+          opacity: 1;
+        }
+        to {
+          opacity: 0;
+        }
+      }
+    </style>
   </head>
   <body>
     <div id="app"></div>

+ 882 - 0
public/data/comments/video_id_7128686458763889956.json

@@ -0,0 +1,882 @@
+[
+  {
+    "comment_id": "7128700999460832031",
+    "create_time": 1659780051,
+    "ip_location": "天津",
+    "aweme_id": "7128686458763889956",
+    "content": "还是开场这件好",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "2440",
+    "user_id": "3196995034687451",
+    "sec_uid": "MS4wLjABAAAA8yM8RDVrRsKnVl3gW_k6VPw0xQ44yQAGfeZDDCrbFUQ0hZajjNTLsVs82-pY2_Br",
+    "short_user_id": "66457810832",
+    "user_unique_id": "66457810832",
+    "user_signature": "",
+    "nickname": "Ww",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_229fdc5613fb4e4e82c33296626bb6b7.jpeg?from=2956013662",
+    "sub_comment_count": "28",
+    "last_modify_ts": 1711644153754
+  },
+  {
+    "comment_id": "7128709634059535138",
+    "create_time": 1659782058,
+    "ip_location": "湖北",
+    "aweme_id": "7128686458763889956",
+    "content": "房间开关一般安装高度1.3M",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "620",
+    "user_id": "103058917050",
+    "sec_uid": "MS4wLjABAAAA_7kxEGaf1RbGsDJa2Pa51tvD2zuYIewFXD4lCBV87PU",
+    "short_user_id": "1279779883",
+    "user_unique_id": "",
+    "user_signature": "民工",
+    "nickname": "李戈戈",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_81f6bcffff6bc8b2f2734915f84c254d.jpeg?from=2956013662",
+    "sub_comment_count": "32",
+    "last_modify_ts": 1711644153756
+  },
+  {
+    "comment_id": "7128688316475671307",
+    "create_time": 1659777095,
+    "ip_location": "浙江",
+    "aweme_id": "7128686458763889956",
+    "content": "你的腿是真的长啊。[震惊][震惊][震惊]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "365",
+    "user_id": "3202257132459950",
+    "sec_uid": "MS4wLjABAAAA4jzGtWnBIuN4l1ifrRQlyM_zRDo20MwnBN_8NCPdkzHKkRnE_Xq71RnbMJIUzUql",
+    "short_user_id": "38078278252",
+    "user_unique_id": "38078278252",
+    "user_signature": "各有归舟,各有渡口。",
+    "nickname": "不丑温柔",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_767663457ff74128b92128315ead78b5.jpeg?from=2956013662",
+    "sub_comment_count": "9",
+    "last_modify_ts": 1711644153757
+  },
+  {
+    "comment_id": "7131110044033614624",
+    "create_time": 1660340976,
+    "ip_location": "辽宁",
+    "aweme_id": "7128686458763889956",
+    "content": "穿这露背装可要注意了。上次我去我朋友家他老婆新买的露背装非要我看。后来……穿反了[捂脸][捂脸][捂脸]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "130",
+    "user_id": "98821162329",
+    "sec_uid": "MS4wLjABAAAAAP9HGzLpB7Pnya2YHF61OZlyASjItY0Jb0hGa3ptaAA",
+    "short_user_id": "961604955",
+    "user_unique_id": "jx299517",
+    "user_signature": "每天晚上20:00直播\n搞笑到无能为力,逗比到感动自己\n星图➕V:私❤\n从不会主动加任何人微信。应聘主播➕15242216600!!15042216600",
+    "nickname": "于家村长(搞笑达人)团队招人",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_b04d01b051207316a65ca21932c32e83.jpeg?from=2956013662",
+    "sub_comment_count": "8",
+    "last_modify_ts": 1711644153759
+  },
+  {
+    "comment_id": "7128686838722888478",
+    "create_time": 1659776755,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "这条裙子终于穿了@朝阳的娃儿👾 [色][色]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "64",
+    "user_id": "2739632844317827",
+    "sec_uid": "MS4wLjABAAAAgNeGTBkRx8bgT3vy5vNEF_SOMSqlMeP1q8mID71z84RorrEme_-4L2mrBz2rfToG",
+    "short_user_id": "2977799237",
+    "user_unique_id": "12345xiaolaohu",
+    "user_signature": "每晚12:00直播 韩舞业余,专业蹦迪!\n虎的小号@杨巅峰🐑🐯 \n💜商务V:Joymedia7 💜\n💙这辈子人潮汹涌 遇到你  我很幸运💙",
+    "nickname": "杨老虎🐯(磕穿下巴掉牙版)",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_f14282e10099a4b436a9ca62c0902595.jpeg?from=2956013662",
+    "sub_comment_count": "12",
+    "last_modify_ts": 1711644153761
+  },
+  {
+    "comment_id": "7132513592851874591",
+    "create_time": 1660667749,
+    "ip_location": "新疆",
+    "aweme_id": "7128686458763889956",
+    "content": "一般般",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "62",
+    "user_id": "72662192803",
+    "sec_uid": "MS4wLjABAAAAS5ZkUNAi4PSM1ZWIK0O1hOMv4310R1REGtq7po2rUR0",
+    "short_user_id": "100529141",
+    "user_unique_id": "",
+    "user_signature": "MT的老公",
+    "nickname": "MT的老公",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_2cd8900042f798d10dd03.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153763
+  },
+  {
+    "comment_id": "7128663928885134087",
+    "create_time": 1659782554,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "你数钱的那个动作特别帅",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "8",
+    "user_id": "52885061495",
+    "sec_uid": "MS4wLjABAAAA76iUrLrsqC-DJughaAGwlTFLrioUdw5b5Lc5JwirTjo",
+    "short_user_id": "8838154",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "意义的旅行",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_2409c000673945dc8a4fa.jpeg?from=2956013662",
+    "sub_comment_count": "4",
+    "last_modify_ts": 1711644153765
+  },
+  {
+    "comment_id": "7132885084260582158",
+    "create_time": 1660754239,
+    "ip_location": "新疆",
+    "aweme_id": "7128686458763889956",
+    "content": "太会了",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "47",
+    "user_id": "94998935336",
+    "sec_uid": "MS4wLjABAAAAdbwFQ1ppeHJPfFsHOhjsHUtVqMdI7zO84j2TkKJR4-E",
+    "short_user_id": "582435671",
+    "user_unique_id": "",
+    "user_signature": "交朋友",
+    "nickname": "大卫",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_8a5d35e33ce8acfa933cd2f032aa91ec.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153767
+  },
+  {
+    "comment_id": "7137861404161475365",
+    "create_time": 1661912956,
+    "ip_location": "宁夏",
+    "aweme_id": "7128686458763889956",
+    "content": "我的女人",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "58",
+    "user_id": "82671710917",
+    "sec_uid": "MS4wLjABAAAAeDzWE-ueN-KTTEtlIPLYJXG-jX10HdXQ24R3NBqkVe0",
+    "short_user_id": "3222568880",
+    "user_unique_id": "dycqc3txh2m0",
+    "user_signature": "走自己的路,样别人无路可走...........",
+    "nickname": "好好走自己的",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_71ad000f8c81b289ece1.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153769
+  },
+  {
+    "comment_id": "7138279140779901726",
+    "create_time": 1662010229,
+    "ip_location": "黑龙江",
+    "aweme_id": "7128686458763889956",
+    "content": "怎么走道呢???",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "51",
+    "user_id": "1442200069350839",
+    "sec_uid": "MS4wLjABAAAASC2yyLvC02wq0oEi6ZyTsrDBVDjjhwL2440ryspFtD9fuHF9khtO1K8gGWHblgB-",
+    "short_user_id": "4182502908",
+    "user_unique_id": "dywh64zbetvb",
+    "user_signature": "",
+    "nickname": "笑看红尘",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_3793_3131589739.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153770
+  },
+  {
+    "comment_id": "7226321306987709236",
+    "create_time": 1682509053,
+    "ip_location": "河南",
+    "aweme_id": "7128686458763889956",
+    "content": "后面墙上的插座都成长方形了[看]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "65",
+    "user_id": "4107376814724707",
+    "sec_uid": "MS4wLjABAAAA_27V2cWYkmxzUiX6l-5R00EvgiMRE_t7v9AOtpbaaMK1uZswUNjuFF_qplfTXmI0",
+    "short_user_id": "3500065301",
+    "user_unique_id": "dyvm36wys59h",
+    "user_signature": "欲买桂花同载酒, 终不似,少年游。",
+    "nickname": "清风依旧",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_06a4644e65a6e19c29faea43fc29dea7.jpeg?from=2956013662",
+    "sub_comment_count": "1",
+    "last_modify_ts": 1711644153772
+  },
+  {
+    "comment_id": "7132318861181993763",
+    "create_time": 1660622398,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "后面那两件还没你第一套好看[捂脸]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "79",
+    "user_id": "85626162263",
+    "sec_uid": "MS4wLjABAAAArJhTP_gsXY9PmxmPQaXGH1rKaIJTsvjmDBJagfevKRk",
+    "short_user_id": "704415057",
+    "user_unique_id": "BBW1",
+    "user_signature": "你两手空空、为何心事重重。",
+    "nickname": "阿 O",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_06dd53765c3a415a9740e642c0ab437f.jpeg?from=2956013662",
+    "sub_comment_count": "1",
+    "last_modify_ts": 1711644153774
+  },
+  {
+    "comment_id": "7128702866088772365",
+    "create_time": 1659780483,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "还不如第一套",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "138",
+    "user_id": "96765937810",
+    "sec_uid": "MS4wLjABAAAAM5uMsxHpi-8L7Bg7571VKyLyQsJS4YXbNwCYlAyeQ0U",
+    "short_user_id": "784153794",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "-72。",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_6c636a9ec745591851f6adbcb416680c.jpeg?from=2956013662",
+    "sub_comment_count": "1",
+    "last_modify_ts": 1711644153775
+  },
+  {
+    "comment_id": "7137500167603209001",
+    "create_time": 1661838348,
+    "ip_location": "江西",
+    "aweme_id": "7128686458763889956",
+    "content": "这衣服确实蛮好看的",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "28",
+    "user_id": "3571653219466231",
+    "sec_uid": "MS4wLjABAAAANykWWT2Wo_dHU-oVVBg-hbg4F76hlSogFsPX3AyBTuJPgxmtPErT4ZD5zXThtIlC",
+    "short_user_id": "4174539047",
+    "user_unique_id": "ltr891116",
+    "user_signature": "123456",
+    "nickname": "舞梦江湖",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_f78a9a016b2040fa9bd51ea8c1b3ee2f.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153777
+  },
+  {
+    "comment_id": "7128709592737792799",
+    "create_time": 1659782051,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "黑白的两件还是没有第一件好",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "58",
+    "user_id": "93046141178",
+    "sec_uid": "MS4wLjABAAAAKisF0fEGnLCo5m3IARC23PaAvoN4zmMFrvl8mASni8k",
+    "short_user_id": "1111209068",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "逆流成河",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_2f93b0000bdf2fd4c0e4c.jpeg?from=2956013662",
+    "sub_comment_count": "1",
+    "last_modify_ts": 1711644153779
+  },
+  {
+    "comment_id": "7148005554803704576",
+    "create_time": 1664274744,
+    "ip_location": "安徽",
+    "aweme_id": "7128686458763889956",
+    "content": "怎么都是同一个房间。看着像酒店",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "28",
+    "user_id": "102288492031",
+    "sec_uid": "MS4wLjABAAAARzoj3bpm8TX2xntNzY4joiEhbS3_nRAwvfOZKklcYz0",
+    "short_user_id": "1221327846",
+    "user_unique_id": "gobgxifacai888",
+    "user_signature": "人防门安装!安全第一,质量为主。承接安徽人防门安装专业团队。质量好,让老板少操心",
+    "nickname": "楝龙兴人防门安装(合肥安装队)",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_4d3649e2ee874f4f89c7a54a85f71020.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153781
+  },
+  {
+    "comment_id": "7139718075007583009",
+    "create_time": 1662345161,
+    "ip_location": "山东",
+    "aweme_id": "7128686458763889956",
+    "content": "这个应该没有拉长腿",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "62",
+    "user_id": "111499912455",
+    "sec_uid": "MS4wLjABAAAAKoRjnAd2y13lhT-svJvZfbkSyASwtkzwPymX0Y0SaMg",
+    "short_user_id": "2332375848",
+    "user_unique_id": "pifubukeshiy",
+    "user_signature": "",
+    "nickname": "魏武遗风",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oMkzECIxlzAcuSApAFfDAeyACAxgyC40IuNYgh.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153783
+  },
+  {
+    "comment_id": "7137862948585063209",
+    "create_time": 1661913238,
+    "ip_location": "山西",
+    "aweme_id": "7128686458763889956",
+    "content": "好白[赞][赞][赞][赞][赞]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "54",
+    "user_id": "67743273615",
+    "sec_uid": "MS4wLjABAAAAp0_1FqtzoL941wJkpge2ExfpkuGPoDX0jHDkaqVOOVs",
+    "short_user_id": "627759073",
+    "user_unique_id": "Q15735088300",
+    "user_signature": "",
+    "nickname": "云游四方",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_24009000556d94db577ec.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153784
+  },
+  {
+    "comment_id": "7145395116960957220",
+    "create_time": 1663666952,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "好老婆[看]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "53",
+    "user_id": "68310694477",
+    "sec_uid": "MS4wLjABAAAA8y_qJPS255STse-gLflZ5lrHNGFUC5zUkX9X2bptPlY",
+    "short_user_id": "145325968",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "Lee大胆",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_ooC4AC0xZfCArA4AEyhzCpNUIA5YgVaJACPPDe.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153786
+  },
+  {
+    "comment_id": "7129362331688043305",
+    "create_time": 1659934028,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "不如第一套,毕竟不是每个人的体力都能坚持到你换第二套",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "32",
+    "user_id": "101544817847",
+    "sec_uid": "MS4wLjABAAAAQbUPLt3R3btM1_6zqy0vLTIlunQoO6aXkqvqymMV0Mc",
+    "short_user_id": "1143350686",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "没有姓名               ╲",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_osozAnBEyAeKBnJbBAU4taKEoB75eAq8DIeEAk.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644153788
+  },
+  {
+    "comment_id": "7348911002341229349",
+    "create_time": 1711051707,
+    "ip_location": "北京",
+    "aweme_id": "7128686458763889956",
+    "content": "[666][666][666]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "883645178981268",
+    "sec_uid": "MS4wLjABAAAAr2WiLGjMpTygBlfYy39k0up0wH3YKXTlUb1USRtdE98",
+    "short_user_id": "3699731620",
+    "user_unique_id": "dyv0itqbe6um",
+    "user_signature": "",
+    "nickname": "一直在心间",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_4aa9fa68099f4cc18fc7adedd07c0304.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154678
+  },
+  {
+    "comment_id": "7348516069798347572",
+    "create_time": 1710959775,
+    "ip_location": "福建",
+    "aweme_id": "7128686458763889956",
+    "content": "[赞]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "101394317255",
+    "sec_uid": "MS4wLjABAAAA8v4MzJFsemQynRCPFAIZJFVyX-cAfHSQHmhtC74hS20",
+    "short_user_id": "1130910983",
+    "user_unique_id": "",
+    "user_signature": "永远的神:",
+    "nickname": "锋喜哥",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oEMIg88EJWAQAIynjf6WAPhAnzICt1AOeACADN.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154684
+  },
+  {
+    "comment_id": "7345471814065226522",
+    "create_time": 1710250933,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "@[赞][赞][比心][比心]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "663705362105149",
+    "sec_uid": "MS4wLjABAAAA_3PepEDneo9e40ZOR3eTIqjUf-E86g4_OSgMmGiN6vs",
+    "short_user_id": "2814464165",
+    "user_unique_id": "cengjingyish44",
+    "user_signature": "曾经亦是过去!",
+    "nickname": "👩🏻‍🚀²²⁶曾经亦是过去",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oA8WpArmBAflASlAD7gSA9lCDYUbgfAAAJ4gQn.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154692
+  },
+  {
+    "comment_id": "7344237327789163305",
+    "create_time": 1709963510,
+    "ip_location": "甘肃",
+    "aweme_id": "7128686458763889956",
+    "content": "[图片表情]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "4503676407306728",
+    "sec_uid": "MS4wLjABAAAAv_TombEUE4RsR9GDJ4o2zF5rJG9XKFnU30_vIGoo4fUrjiTsIK4mmKQsU-2GkKjT",
+    "short_user_id": "3204005178",
+    "user_unique_id": "dyw6zv3imcf6",
+    "user_signature": "",
+    "nickname": "今生缘份561",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_4d370002c5a01b6711b0.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154701
+  },
+  {
+    "comment_id": "7338316748176098088",
+    "create_time": 1708585018,
+    "ip_location": "北京",
+    "aweme_id": "7128686458763889956",
+    "content": "[看][看][看]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "100446910339",
+    "sec_uid": "MS4wLjABAAAABqG604ES2cv_cTWsIogdP5KMoI-vwW9IwXP_vpCynaQ",
+    "short_user_id": "3666406553",
+    "user_unique_id": "dyg4kfrcdz78",
+    "user_signature": "等生死  齐万物",
+    "nickname": "执笔绘星河",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_288d89c9c46f909bf011965fef5f3135.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154712
+  },
+  {
+    "comment_id": "7337239242920608512",
+    "create_time": 1708334142,
+    "ip_location": "浙江",
+    "aweme_id": "7128686458763889956",
+    "content": "[比心][比心][比心][咖啡]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "105341520006",
+    "sec_uid": "MS4wLjABAAAAGeKD8VE6TlAM7ExCJ9HClI4-eM5NgrdoyFhf02-K2wM",
+    "short_user_id": "3476040766",
+    "user_unique_id": "laoye56819",
+    "user_signature": "",
+    "nickname": "老爷",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_e4816eb9acdb9cacded814de7f4868d5.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154724
+  },
+  {
+    "comment_id": "7335376690057610035",
+    "create_time": 1707900480,
+    "ip_location": "北京",
+    "aweme_id": "7128686458763889956",
+    "content": "[嘴唇]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "4344865903358080",
+    "sec_uid": "MS4wLjABAAAAjZQ9owxo7g252K1lqdkZ9GbGwYJ1LDkOZKzVQXopNkLberaouMJAetv03_Dr2MD0",
+    "short_user_id": "3824044352",
+    "user_unique_id": "beijingdeqik",
+    "user_signature": "色精病!😂🤣😆😄😃😜😚😘",
+    "nickname": "北京的七库昕",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_1eeaa241f96343b6b47f343cffba2794.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154737
+  },
+  {
+    "comment_id": "7333335109510562610",
+    "create_time": 1707425157,
+    "ip_location": "北京",
+    "aweme_id": "7128686458763889956",
+    "content": "[饺子][福][惊喜][爱心][飞吻][舔屏]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "106618694331",
+    "sec_uid": "MS4wLjABAAAAtPqBg04tz6KeuNOz4dWj2gc6nZKvDTH8m2zolS657Kw",
+    "short_user_id": "1728625566",
+    "user_unique_id": "",
+    "user_signature": "生命是场磨练和体验!",
+    "nickname": "冯主",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_f79a000976cb643ebb6f.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154748
+  },
+  {
+    "comment_id": "7331702910214325026",
+    "create_time": 1707045110,
+    "ip_location": "河南",
+    "aweme_id": "7128686458763889956",
+    "content": "[感谢][感谢][感谢]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "11820437026",
+    "sec_uid": "MS4wLjABAAAAgd-nEqa6gqYUXhOE5JjJC0PcflEVG1O7wh6rJYqcN8A",
+    "short_user_id": "3072731032",
+    "user_unique_id": "dy4lg06eqalu",
+    "user_signature": "",
+    "nickname": "李哥",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/hotsoon-avatar_4f5fb626c32f4297bab6010ea724d676.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154758
+  },
+  {
+    "comment_id": "7331582677348352809",
+    "create_time": 1707017117,
+    "ip_location": "河南",
+    "aweme_id": "7128686458763889956",
+    "content": "[图片表情]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "3",
+    "user_id": "101750487952",
+    "sec_uid": "MS4wLjABAAAATko-vbRMcjwX8qipYAUj24EOlhBK55-SLyLfT3O-_xs",
+    "short_user_id": "1238885940",
+    "user_unique_id": "",
+    "user_signature": "命里有时终须有,命里无时莫强求",
+    "nickname": "一生只为爱等你",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_1736000013968dd21b24f.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154768
+  },
+  {
+    "comment_id": "7331421854031627034",
+    "create_time": 1706979678,
+    "ip_location": "四川",
+    "aweme_id": "7128686458763889956",
+    "content": "多少缘分,私聊啊",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "3",
+    "user_id": "12787490502887",
+    "sec_uid": "MS4wLjABAAAAcVtZfnIgZe2G4MtYSkevX0D49HDWEsxDM0U3NeISw9I",
+    "short_user_id": "2324354084",
+    "user_unique_id": "zq704560525",
+    "user_signature": "",
+    "nickname": "钟Q",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_c25508ef96944ba9936cce9357702d09.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154778
+  },
+  {
+    "comment_id": "7330171790063502143",
+    "create_time": 1706688622,
+    "ip_location": "陕西",
+    "aweme_id": "7128686458763889956",
+    "content": "@🌸漩涡玖辛奈🌸 姐姐,想看你的约会战袍[送心]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "3609074079111376",
+    "sec_uid": "MS4wLjABAAAARbkRDVfZOFRPJSa0otVrB6p8yGpKjNLMBVP29Y4bt4dNR4XtHZtdoRxOeJUDNLay",
+    "short_user_id": "41008873454",
+    "user_unique_id": "100863345.",
+    "user_signature": "",
+    "nickname": "和光同尘",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_o0CDAkY5IfDAgAcAEyhzGltVIAu0gka6ANLt1e.jpeg?from=2956013662",
+    "sub_comment_count": "1",
+    "last_modify_ts": 1711644154784
+  },
+  {
+    "comment_id": "7329402200626250533",
+    "create_time": 1706509436,
+    "ip_location": "河南",
+    "aweme_id": "7128686458763889956",
+    "content": "[比心][比心][比心]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "71805177180",
+    "sec_uid": "MS4wLjABAAAAnaraMrUjyTGKevY9xTqTe4sUicM3lJ1DSRnKO04N-28",
+    "short_user_id": "3180788517",
+    "user_unique_id": "dy6fndl2laju",
+    "user_signature": "",
+    "nickname": "温暖如春",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_a69352634722453a9622d57ba08cb3cf.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154791
+  },
+  {
+    "comment_id": "7327598240160350987",
+    "create_time": 1706089418,
+    "ip_location": "山西",
+    "aweme_id": "7128686458763889956",
+    "content": "[赞][赞][赞]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "3380318885122536",
+    "sec_uid": "MS4wLjABAAAAiEKoY6SomcT38S5wVhbnT0aT13G_2_lWOWHD156cfRiod8Qe4TPMI1iFgSwzQpFF",
+    "short_user_id": "39736568403",
+    "user_unique_id": "39736568403",
+    "user_signature": "",
+    "nickname": "道德",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_3796_2975850990.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154797
+  },
+  {
+    "comment_id": "7326361096137507625",
+    "create_time": 1705801376,
+    "ip_location": "江苏",
+    "aweme_id": "7128686458763889956",
+    "content": "上小黄车啊",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "2430333590707924",
+    "sec_uid": "MS4wLjABAAAA9agGGGWPKVgEkMjvFnXTmfYSs9Q8Olm9TW3gbL5bzeYie2W06MhjQQE3naqMR66h",
+    "short_user_id": "50448980738",
+    "user_unique_id": "dykem6272678",
+    "user_signature": "",
+    "nickname": "牛阿哥 666",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oMEQA5IIZsATBTLEJekjBHDAinZebMAs7AEfEG.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154805
+  },
+  {
+    "comment_id": "7330587299070165796",
+    "create_time": 1706785364,
+    "ip_location": "山东",
+    "aweme_id": "7128686458763889956",
+    "content": "[图片表情]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "84110612270",
+    "sec_uid": "MS4wLjABAAAAKz4cpndVtQ-BSr6zr0_u9A1FaCCa85zWZ22eeUcrnIY",
+    "short_user_id": "183768389",
+    "user_unique_id": "Eros.995",
+    "user_signature": "纵使文章惊海内,纸上苍生而已",
+    "nickname": "琥珀L.",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_o0D9MfQ6WGbwAZniCgbMe5ADAcIBkAcjAW3lzA.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154812
+  },
+  {
+    "comment_id": "7325486022824542987",
+    "create_time": 1705597635,
+    "ip_location": "广东",
+    "aweme_id": "7128686458763889956",
+    "content": "",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "109134298283",
+    "sec_uid": "MS4wLjABAAAAAHzNw3U16wg2m0RfbInlnFcjroQrkngCvRbg9NHZyyo",
+    "short_user_id": "2247084694",
+    "user_unique_id": "",
+    "user_signature": "@可视化家居,转化,应用,落地方案🆚设计方案",
+    "nickname": "可沐木尔尔",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_4edf528bcc128cf71ea531d4c3ff55fb.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154819
+  },
+  {
+    "comment_id": "7325326409004811044",
+    "create_time": 1705560466,
+    "ip_location": "江苏",
+    "aweme_id": "7128686458763889956",
+    "content": "为什么,现实看不到这样的女人",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "697535389764931",
+    "sec_uid": "MS4wLjABAAAA3vCmfEYbsehDLgcx6Z-GZ88_r_1BD5QpUhmd4j1knwg",
+    "short_user_id": "80971320710",
+    "user_unique_id": "80971320710",
+    "user_signature": "",
+    "nickname": "用户9781596922326",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_3795_3033762272.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154831
+  },
+  {
+    "comment_id": "7325120948431422243",
+    "create_time": 1705512643,
+    "ip_location": "贵州",
+    "aweme_id": "7128686458763889956",
+    "content": "红十年是明星,红一年是网红,红几天是大姨妈[我想静静][我想静静][我想静静][我想静静]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "98840301240",
+    "sec_uid": "MS4wLjABAAAASxT7hez3Lip0bCg0xqu0rGJ-JXg3RiZHiGxDIukAZiw",
+    "short_user_id": "962141237",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "茜茜爸",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_e0c1ccf7a57348f29ed497213abcf6df.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154934
+  },
+  {
+    "comment_id": "7323385999815000895",
+    "create_time": 1705108682,
+    "ip_location": "贵州",
+    "aweme_id": "7128686458763889956",
+    "content": "这身材[捂脸]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "85228800137",
+    "sec_uid": "MS4wLjABAAAAUZw9T86FmSUiUnd1BYdhWtOmzAY27Nk-Isxflgr4L54",
+    "short_user_id": "967824491",
+    "user_unique_id": "5201314lhkm",
+    "user_signature": "嘻嘻",
+    "nickname": "生活能改变",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_54ed00021550f21174d9.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644154943
+  }
+]

+ 882 - 0
public/data/comments/video_id_7267478481213181238.json

@@ -0,0 +1,882 @@
+[
+  {
+    "comment_id": "7312915638987555593",
+    "create_time": 1702670873,
+    "ip_location": "浙江",
+    "aweme_id": "7267478481213181238",
+    "content": "最初做“东方美学”系列 是因为想通过自己小小的力量去宣传我们东方文化,没有想到会获得那多人的喜欢,“东方美学”这四个字 我斟酌了很久, “美学”不仅仅只是服饰 可以是文化交流 可以是关于对美的本质的探讨 ,“天地万物与我来唯一 ”这也是我最初起源用“东方美学”这个词的原因❤️所有视频都是我个人构思 剪辑 发布的 没有公司团队运营!服饰也是我个人喜好带去旅游的[太阳]从第一站到现在看到越来越多的人留学生以及路人穿国风的衣服自信的走在路上 我真的超级开心!!",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "3192",
+    "user_id": "24058267831",
+    "sec_uid": "MS4wLjABAAAAAAKy2_R6k-oFWT5E-97gbGZQ1laaweQMWImJDkDaef0",
+    "short_user_id": "6187453",
+    "user_unique_id": "elfin16",
+    "user_signature": "记录成长\n希望拥有向上生命力🐰☀️\n理想型:@六猪变美日记 \n🍠:彭十六elf \n商务v:MUXUAN16e(招摄影&剪辑)\n演出v:ME13919\n何其荣幸 何德何能💜",
+    "nickname": "彭十六elf",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_99d3a4923c94e1e27b16209743eaec24.jpeg?from=2956013662",
+    "sub_comment_count": "22",
+    "last_modify_ts": 1711644155992
+  },
+  {
+    "comment_id": "7267776615597359926",
+    "create_time": 1692161122,
+    "ip_location": "四川",
+    "aweme_id": "7267478481213181238",
+    "content": "他们的目光是欣赏不是打量[流泪][流泪]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "36536",
+    "user_id": "73695745581",
+    "sec_uid": "MS4wLjABAAAAkcsOgLNbp0KNT1wEEuu1Rmegjc_vCBibLWvTw19fTYE",
+    "short_user_id": "1921005886",
+    "user_unique_id": "FCFFMar1",
+    "user_signature": "@说",
+    "nickname": "🍟..",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_7dc55c7bc1b5ccf33cc87c314b6517ee.jpeg?from=2956013662",
+    "sub_comment_count": "165",
+    "last_modify_ts": 1711644156003
+  },
+  {
+    "comment_id": "7268321084943876919",
+    "create_time": 1692287896,
+    "ip_location": "山东",
+    "aweme_id": "7267478481213181238",
+    "content": "看啊,她们都是笑着的[泣不成声]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "4238",
+    "user_id": "1605699951676907",
+    "sec_uid": "MS4wLjABAAAAJ-Jw-Q93--Wo-ZWoTvp0Q3aHeYgMgryF4hIBudWekpG-0BGfBUDGfI3wy-ErQthw",
+    "short_user_id": "20608411512",
+    "user_unique_id": "20608411512",
+    "user_signature": "",
+    "nickname": "星陨",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_ooBsQ6ClAgAgfC9MADE4bAXAqHrnK853rC8e9A.jpeg?from=2956013662",
+    "sub_comment_count": "4",
+    "last_modify_ts": 1711644156014
+  },
+  {
+    "comment_id": "7276727044934124344",
+    "create_time": 1694245051,
+    "ip_location": "云南",
+    "aweme_id": "7267478481213181238",
+    "content": "从此,男孩的心中有了白月光[泣不成声]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "21532",
+    "user_id": "3773952187700740",
+    "sec_uid": "MS4wLjABAAAAk-9PcJ2aiHOVGqBg_HCV7pczqjgiQKt8lUy9jXGfSQSzTRWZStGIVsk4zCZ7sg_S",
+    "short_user_id": "45735179162",
+    "user_unique_id": "zf3344830ctx",
+    "user_signature": "我把错归结于自己,怪不了任何人。",
+    "nickname": "疏离.",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_o0AAy2HgQCAXalj2Mf5MMcO8Eefi4DA1PIP2Ae.jpeg?from=2956013662",
+    "sub_comment_count": "164",
+    "last_modify_ts": 1711644156025
+  },
+  {
+    "comment_id": "7268440521424519988",
+    "create_time": 1692315714,
+    "ip_location": "浙江",
+    "aweme_id": "7267478481213181238",
+    "content": "妈妈 我看见外国人了",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "15235",
+    "user_id": "92330274698",
+    "sec_uid": "MS4wLjABAAAA0-zN2uSSh6u6K5b6JUI_yqqeK1zVUjMkfYgCZ7FoKJA",
+    "short_user_id": "332295468",
+    "user_unique_id": "15355296033a",
+    "user_signature": "捡螺抓螃蟹+我",
+    "nickname": "赶海一哥",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_1430ccdfc00dcf2c954476942486d81f.jpeg?from=2956013662",
+    "sub_comment_count": "103",
+    "last_modify_ts": 1711644156034
+  },
+  {
+    "comment_id": "7267479160518820644",
+    "create_time": 1692091863,
+    "ip_location": "江苏",
+    "aweme_id": "7267478481213181238",
+    "content": "不是回国了[流泪]怎么还在法国",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "12453",
+    "user_id": "92657600038",
+    "sec_uid": "MS4wLjABAAAA1aANNflDuggG7wO0gEdoi8smRFZynUJ1glzini54yzc",
+    "short_user_id": "339088308",
+    "user_unique_id": "sky.0102",
+    "user_signature": "#",
+    "nickname": "早点睡 ̆̈",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_910231a0a37d95ab70cccbe66531bd15.jpeg?from=2956013662",
+    "sub_comment_count": "18",
+    "last_modify_ts": 1711644156045
+  },
+  {
+    "comment_id": "7267725549048218428",
+    "create_time": 1692149234,
+    "ip_location": "河南",
+    "aweme_id": "7267478481213181238",
+    "content": "好熟悉的穿搭[绿帽子]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "681",
+    "user_id": "2365752691137240",
+    "sec_uid": "MS4wLjABAAAAAgkGnKeITMLJvjSIMMD0GMxQNtGDOX0bjDaB5f_WTx9OgeMkcQJZStqBBtz-jzaN",
+    "short_user_id": "3518741138",
+    "user_unique_id": "zyhan070323",
+    "user_signature": "就爱学习",
+    "nickname": "香菜不感冒",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_b69b59418eef404185b2f1f7240b6858.jpeg?from=2956013662",
+    "sub_comment_count": "2",
+    "last_modify_ts": 1711644156066
+  },
+  {
+    "comment_id": "7267479730084840250",
+    "create_time": 1692091997,
+    "ip_location": "山东",
+    "aweme_id": "7267478481213181238",
+    "content": "这才是国人的审美![赞][赞]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "18032",
+    "user_id": "106660859294",
+    "sec_uid": "MS4wLjABAAAAJr174vM9QA9TyRbIqZB7fzfhYnZ0tfEb-HtYeNpW6to",
+    "short_user_id": "1731760481",
+    "user_unique_id": "",
+    "user_signature": "免费上门免费鉴定免费估价\n私信必回,没有套路!\n主打服务和性价比🤩\n目前已有200个城市开通上门服务\n在营线下门店400+家\n让用户放心买 省心卖❤️",
+    "nickname": "转转奢侈品",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_1bc0a1682b33fe2b3ee2cf79a7f40736.jpeg?from=2956013662",
+    "sub_comment_count": "203",
+    "last_modify_ts": 1711644156076
+  },
+  {
+    "comment_id": "7267737555989201664",
+    "create_time": 1692152023,
+    "ip_location": "辽宁",
+    "aweme_id": "7267478481213181238",
+    "content": "我有时候真的很想成为一个女孩,有那么多漂亮的裙子衣服可以穿,我现在出去买衣服除了苦菜子就是千篇一律的短袖外套,一点新意没有",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1415",
+    "user_id": "4037020336788471",
+    "sec_uid": "MS4wLjABAAAA11vg14vw2k8NcuO3dXwuESj3wGHjuPCI1r78ex1zGemSdOaT87bXuJQ0W-_0AmAa",
+    "short_user_id": "3595151507",
+    "user_unique_id": "zql182xxxx3397",
+    "user_signature": "_",
+    "nickname": "青衫啊",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_79fd50181770493589983922703b0272.jpeg?from=2956013662",
+    "sub_comment_count": "141",
+    "last_modify_ts": 1711644156088
+  },
+  {
+    "comment_id": "7267588863685919488",
+    "create_time": 1692117430,
+    "ip_location": "黑龙江",
+    "aweme_id": "7267478481213181238",
+    "content": "友情提示不想出国的朋友,这种质感,其实哈尔滨也能拍[看]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1991",
+    "user_id": "2348147786918477",
+    "sec_uid": "MS4wLjABAAAAZ02ZTbMeDSeX-b1Kjy7h9NTKKaVUjlOwGllaFiOyjTND-67d93LGjeTTC0oOx-A2",
+    "short_user_id": "3489070782",
+    "user_unique_id": "dynv018fzb5e",
+    "user_signature": "🏆2006年美国周刊年度风云人物\n🏆2008年感动中国组委会特别大奖\n🏆2009年年度地球卫士奖\n🏆2012年世界末日生存者\n🏆2022年奥林匹克杯获得者",
+    "nickname": "h💕",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_d402b73a316141dfaa23755acc38ca07.jpeg?from=2956013662",
+    "sub_comment_count": "74",
+    "last_modify_ts": 1711644156098
+  },
+  {
+    "comment_id": "7267479755153883961",
+    "create_time": 1692092001,
+    "ip_location": "四川",
+    "aweme_id": "7267478481213181238",
+    "content": "“先生,我认为文言文比白话文更加简洁。”\n“请举例。”\n“就好像沉鱼落雁,闭月羞花这句成语不是比白话文更加简洁吗?”\n“沉鱼落雁,闭月羞花是八个字,改用白话文三个字足矣。”\n“怎样讲?”\n“彭十六”",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1522",
+    "user_id": "2154686604127591",
+    "sec_uid": "MS4wLjABAAAAgyEwaCVZ4tDMzQnwfj58itSO8Xvi_cCUQ0m56EXgJzAPCurXGfYWsld7RtPPbiFr",
+    "short_user_id": "3697101218",
+    "user_unique_id": "lx14220",
+    "user_signature": "",
+    "nickname": "甫.",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oMEIN9ClHkSdbem5V8CnvgAACeAbAzxAcIAuDn.jpeg?from=2956013662",
+    "sub_comment_count": "35",
+    "last_modify_ts": 1711644156202
+  },
+  {
+    "comment_id": "7268196612386980643",
+    "create_time": 1692258909,
+    "ip_location": "广东",
+    "aweme_id": "7267478481213181238",
+    "content": "小孩:不惑之年还能碰上这种场面",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "2282",
+    "user_id": "105012996479",
+    "sec_uid": "MS4wLjABAAAA679ZNPxX03rvXuLLuq-my1iZAD2SwVvqa7OZST1cCJQ",
+    "short_user_id": "1695001000",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "RUN",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oUBeIIDnAqbCyToALUlfDjoQ5AemL2AfRaI4NA.jpeg?from=2956013662",
+    "sub_comment_count": "18",
+    "last_modify_ts": 1711644156214
+  },
+  {
+    "comment_id": "7267479869604250402",
+    "create_time": 1692092029,
+    "ip_location": "广东",
+    "aweme_id": "7267478481213181238",
+    "content": "连外国朋友都忍不住多看几眼[流泪][色]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1776",
+    "user_id": "106639074509",
+    "sec_uid": "MS4wLjABAAAAS2z1C9RgMjaSk8edw6Wpnmox1BYRMZzB3n7BOrBZP5M",
+    "short_user_id": "1730076543",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "a09y",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_9a4ab0d524b345f7a7625c6b36d2b158.jpeg?from=2956013662",
+    "sub_comment_count": "9",
+    "last_modify_ts": 1711644156227
+  },
+  {
+    "comment_id": "7267526222682719039",
+    "create_time": 1692102820,
+    "ip_location": "江西",
+    "aweme_id": "7267478481213181238",
+    "content": "现在很多博主去国外把中国的传统文化给外国人展示这是我们的传统文化,真的很好[流泪],很多外国人也不知道这是我们的文化,是要好好的宣传,同时也要文化自信",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "525",
+    "user_id": "1169480640367028",
+    "sec_uid": "MS4wLjABAAAAsvtPG2x9b0d9k4rz9bCgp8G6LtdekjkoQ295iBs8_N4PVPcbGWYy_q4GDvXgwshe",
+    "short_user_id": "3492281578",
+    "user_unique_id": "ajuhhhhh",
+    "user_signature": "",
+    "nickname": "iya^^",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_o4e2nbAPBDREQxzBRAKAeErYIv7MAArwDBfA2F.jpeg?from=2956013662",
+    "sub_comment_count": "7",
+    "last_modify_ts": 1711644156238
+  },
+  {
+    "comment_id": "7269037708086461225",
+    "create_time": 1692454738,
+    "ip_location": "湖北",
+    "aweme_id": "7267478481213181238",
+    "content": "东方美学这题我会,粉色是不一样的美[比心]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "8063",
+    "user_id": "84649980451",
+    "sec_uid": "MS4wLjABAAAA0w8tx-BC52VcwpJc5VSW62ofMADEPRL9NFDyqvaSCm8",
+    "short_user_id": "2183935207",
+    "user_unique_id": "",
+    "user_signature": "🌟做不被定义的女孩\n🌈爱自己才是终身浪漫的开始💗",
+    "nickname": "🍓草莓牛奶妹⁹⁸⁵",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_e5b5b673371c92dd7188fc0c7c823775.jpeg?from=2956013662",
+    "sub_comment_count": "335",
+    "last_modify_ts": 1711644156249
+  },
+  {
+    "comment_id": "7267502804549468989",
+    "create_time": 1692097369,
+    "ip_location": "贵州",
+    "aweme_id": "7267478481213181238",
+    "content": "她火的时候真的才16岁吗[流泪]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "2265",
+    "user_id": "107755253032",
+    "sec_uid": "MS4wLjABAAAABDYK_1shVLxNUSR4tjxvY6c_9QDq31vBB6BFRPSeDs8",
+    "short_user_id": "1759817599",
+    "user_unique_id": "131.45.20",
+    "user_signature": "听说喜欢一个人要喜欢很久",
+    "nickname": "城南旧事",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oMAAp8enhqwGnGGA9b0CRdAegNDwACAISkcGE5.jpeg?from=2956013662",
+    "sub_comment_count": "5",
+    "last_modify_ts": 1711644156263
+  },
+  {
+    "comment_id": "7269084905102279464",
+    "create_time": 1692465754,
+    "ip_location": "湖北",
+    "aweme_id": "7267478481213181238",
+    "content": "外国人的眼神里并不是鄙夷与不解,而是欣赏与赞美,心里不由得感叹中国文化的魅力和伟大[赞]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "3407",
+    "user_id": "987783797933308",
+    "sec_uid": "MS4wLjABAAAArpG9YhNycTaA7IDEYfif-KQzYesE82A9DQVo0ojNpGs",
+    "short_user_id": "63788840275",
+    "user_unique_id": "63788840275",
+    "user_signature": "-浪漫不一定非得是一束花💐 一个小礼物也是~\n以欢喜之心 慢度日常!",
+    "nickname": "羽宝儿",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oIe1APbnCQiC9HkADxfUMz7j4CmAGDGA8AAbgg.jpeg?from=2956013662",
+    "sub_comment_count": "30",
+    "last_modify_ts": 1711644156278
+  },
+  {
+    "comment_id": "7267507603301876521",
+    "create_time": 1692098483,
+    "ip_location": "浙江",
+    "aweme_id": "7267478481213181238",
+    "content": "[流泪][流泪]美惨",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "6056",
+    "user_id": "59150254410",
+    "sec_uid": "MS4wLjABAAAA3fehTGA-sn1J4lgu2R87BoOBTQsP0IaI2ap_HoLTKJU",
+    "short_user_id": "9031493",
+    "user_unique_id": "IAMCHEESE",
+    "user_signature": "@一只起司 \n𝐰𝐨𝐫𝐤:𝐂𝐇𝐄𝐄𝐒𝐄𝐍𝐙𝐖\n𝙬𝙚𝙞𝙗𝙤\\📕:起司姨太\n团队招人:cheesenzw(编导 内容运营备注)",
+    "nickname": "起司姨太",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_74d159d507d7a66cb8a15a7846941861.jpeg?from=2956013662",
+    "sub_comment_count": "37",
+    "last_modify_ts": 1711644156294
+  },
+  {
+    "comment_id": "7267579733327938340",
+    "create_time": 1692115293,
+    "ip_location": "广东",
+    "aweme_id": "7267478481213181238",
+    "content": "哇,是真美啊[流泪][流泪][流泪]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "53",
+    "user_id": "83789645036",
+    "sec_uid": "MS4wLjABAAAAfRwyIWhQ6K4moBX1DtgheAuog4MyApof4R5jSz9MJkw",
+    "short_user_id": "180771992",
+    "user_unique_id": "24827297199",
+    "user_signature": "",
+    "nickname": "180771992",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/aweme_default_avatar.png.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644156306
+  },
+  {
+    "comment_id": "7267492531611517753",
+    "create_time": 1692094972,
+    "ip_location": "西藏",
+    "aweme_id": "7267478481213181238",
+    "content": "马面裙真的给人一种国泰民安的感觉",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1316",
+    "user_id": "1023006402936851",
+    "sec_uid": "MS4wLjABAAAAe36RoBHrOZ77QMy8ZH6j4Ry0KjLdnR3YcuATOTE-hW37nD_-_bI8ekSDmXlLj4C1",
+    "short_user_id": "69186708516",
+    "user_unique_id": "VTJ0046",
+    "user_signature": "💫@ \n 落日弥漫的橘边透亮的星💝",
+    "nickname": "悸桃G",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_o4seAAgD9N9b8zDsjgAIgIA4EAPlZCnWpoOAeC.jpeg?from=2956013662",
+    "sub_comment_count": "5",
+    "last_modify_ts": 1711644156318
+  },
+  {
+    "comment_id": "7268289692562457356",
+    "create_time": 1692280578,
+    "ip_location": "湖南",
+    "aweme_id": "7267478481213181238",
+    "content": "早就想说了,啊啊其实我觉得这种汉元素的衣服,有东方韵味的就够了,那些全套的汉服真的感觉怪怪的,换位思考一下,一个外国人穿着什么全套的克里诺林裙啊巴洛克裙在故宫里逛,还要拍照,肯定会有人说的吧,有种中国人这么干就是弘扬传统文化,外国人干就会被骂的双标感[流泪][流泪]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": true,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "2964",
+    "user_id": "60675780312",
+    "sec_uid": "MS4wLjABAAAAll4Jqpxy6vNoCU0hi4GgltpuVpRocuatEGj1cjqLQSs",
+    "short_user_id": "619758400",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "叽里咕噜🎶",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_8c4fbe754aecfb95cbd26e53754bfdda.jpeg?from=2956013662",
+    "sub_comment_count": "48",
+    "last_modify_ts": 1711644157772
+  },
+  {
+    "comment_id": "7351414136725898023",
+    "create_time": 1711634490,
+    "ip_location": "江苏",
+    "aweme_id": "7267478481213181238",
+    "content": "1103",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "1834418105758604",
+    "sec_uid": "MS4wLjABAAAA6rX7IF9uPnHWKFz6AFwbNk5StuO6dmk7iwGGqrEu94G8i8CD2mqT7W3tRkDvRLgo",
+    "short_user_id": "63803157257",
+    "user_unique_id": "63803157257",
+    "user_signature": "我绝对自由",
+    "nickname": "Tevice",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_ogAVaLF9zIDQ2xHSAYfADh9A2Ct0lAAEhEgFAe.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157785
+  },
+  {
+    "comment_id": "7351177441938162468",
+    "create_time": 1711579382,
+    "ip_location": "安徽",
+    "aweme_id": "7267478481213181238",
+    "content": "[玫瑰][玫瑰][玫瑰]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "98158680089",
+    "sec_uid": "MS4wLjABAAAARKiTHfQjANY-A3d4ODuYIGtaREuDWfZ-1aTuCwOLRMk",
+    "short_user_id": "917056552",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "过往",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_8880001d857bf237e362.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157798
+  },
+  {
+    "comment_id": "7351086866722128667",
+    "create_time": 1711558297,
+    "ip_location": "广东",
+    "aweme_id": "7267478481213181238",
+    "content": "[图片表情]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "61560026749",
+    "sec_uid": "MS4wLjABAAAA55voQsQ62rysqeCdkYX326NFKAstGJOdNBe6ws9ikjc",
+    "short_user_id": "278970780",
+    "user_unique_id": "Gz808_CLUB",
+    "user_signature": "广州  网红夜店 808 CLUB酒吧\nKTV 商务接待 应酬 娱乐 生日派对道选🏌☎️18589266083(V❤️同步)\n点赞+关注👍,谢谢\n(免费赠送果盘,小食)",
+    "nickname": "💫广州808 CLUB酒吧📱",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oIDAQGfbeyZI5YB7S0BMmAemIAyn9AABAuGXHE.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157810
+  },
+  {
+    "comment_id": "7350986585507070720",
+    "create_time": 1711534943,
+    "ip_location": "山东",
+    "aweme_id": "7267478481213181238",
+    "content": "",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "94212630213",
+    "sec_uid": "MS4wLjABAAAA99GEddeqXBgvGJf9kiMr3RrzrBuIVqQC2pTDvqArlj8",
+    "short_user_id": "388966292",
+    "user_unique_id": "",
+    "user_signature": "世界上所有的惊喜和好运,都是你积累的温柔和善良",
+    "nickname": "yoyo🍓",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_1f9c51e6ab17475b94f81c1034bc7cec.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157823
+  },
+  {
+    "comment_id": "7350680261748687653",
+    "create_time": 1711463619,
+    "ip_location": "广东",
+    "aweme_id": "7267478481213181238",
+    "content": "把马面裙带火的女人[舔屏][舔屏][舔屏][舔屏]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "3532061659118715",
+    "sec_uid": "MS4wLjABAAAAmMvPWK5ewQ6XBE0Fu-bZPfLghU7Cqp55Y2Wz4Py583zyIo0dDuRxLXHgOUkVhsoc",
+    "short_user_id": "95840369705",
+    "user_unique_id": "919191dyqaz",
+    "user_signature": "人的能力是有限的,无法战胜时间生老病死",
+    "nickname": "宁死不给抖音一分钱",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_o4aA1ANhagAizQxNAICUeyAPAIEAfIAAzOAiSE.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157834
+  },
+  {
+    "comment_id": "7350679683316744979",
+    "create_time": 1711463485,
+    "ip_location": "西藏",
+    "aweme_id": "7267478481213181238",
+    "content": "酷",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "98559539092",
+    "sec_uid": "MS4wLjABAAAADjMuQS_xqyTK-V6V_4q--_984dFmDzA_qmaudUig1jg",
+    "short_user_id": "1223601920",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "留:白",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oM4CVlAGbACeAcnqAdIhrlQgbe9QK06ApNDIA4.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157847
+  },
+  {
+    "comment_id": "7350546958007026459",
+    "create_time": 1711432586,
+    "ip_location": "湖北",
+    "aweme_id": "7267478481213181238",
+    "content": "跪求马面裙[流泪][流泪][流泪][流泪][流泪][流泪]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "52228034662",
+    "sec_uid": "MS4wLjABAAAAEPcUTNTP35ctvquVvqzUu2uh3PTMJeMmHo_f83gLYvg",
+    "short_user_id": "63342780",
+    "user_unique_id": "",
+    "user_signature": "我就是我,做自己的烟火",
+    "nickname": "憨憨~hh",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_3308000c7151febaf4c2.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157860
+  },
+  {
+    "comment_id": "7350261141933261618",
+    "create_time": 1711366036,
+    "ip_location": "广东",
+    "aweme_id": "7267478481213181238",
+    "content": "好喜欢 十六 小鞠 卢昱晓 baby 的颜[摸头][摸头]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "2",
+    "user_id": "1454002547459002",
+    "sec_uid": "MS4wLjABAAAARpxPDLv797W2KFcGRug0gKPvIFhl5qCQ-MuADQrWpKbp37-jXBHatMBBb0XHq6kI",
+    "short_user_id": "88836736321",
+    "user_unique_id": "88836736321",
+    "user_signature": "",
+    "nickname": "爱笑的宝呀୧⍤⃝🌸",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oAAUAtTwzADyfA9AphhTKiEt8CDIH9PgJNeIdA.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157873
+  },
+  {
+    "comment_id": "7350158140174418728",
+    "create_time": 1711342054,
+    "ip_location": "江苏",
+    "aweme_id": "7267478481213181238",
+    "content": "后面几张照片觉了yyds",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "92813165686",
+    "sec_uid": "MS4wLjABAAAAa2TDECzeQRBcq6R-cEftFjNcr7yShocQT30_diI0TPk",
+    "short_user_id": "426553",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "吃包辣条压压惊。",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_owtBQQMGePADEsAfAC4uEsvAPHeAUQ0fg02xeH.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157886
+  },
+  {
+    "comment_id": "7350127919727510308",
+    "create_time": 1711335021,
+    "ip_location": "陕西",
+    "aweme_id": "7267478481213181238",
+    "content": "有宝贝知道衣服是哪家的吗[流泪][流泪][求求了][求求了][求求了]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "64652530230",
+    "sec_uid": "MS4wLjABAAAAABURFk82qgtNHym2gnkCkHM-1puKq6vqKAgD2KrKFFg",
+    "short_user_id": "52038434",
+    "user_unique_id": "",
+    "user_signature": "OMO",
+    "nickname": "zz是芝芝阿、",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_2db9a00000813ed798784.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157899
+  },
+  {
+    "comment_id": "7349958106322338560",
+    "create_time": 1711295488,
+    "ip_location": "海南",
+    "aweme_id": "7267478481213181238",
+    "content": "我想买这套衣服哪里买",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "62708442494",
+    "sec_uid": "MS4wLjABAAAAdDJSuiYO2DhLjOPB3LJ5SGW7cOqQGnym5wtEeIfK1AY",
+    "short_user_id": "30797357",
+    "user_unique_id": "ww5205201234",
+    "user_signature": "一個德藝雙馨、不務正業的中年美少女\n一個喜歡舉高高的健身女孩 不閒聊、不社交、不喝酒\n尤克里里、箜篌、古筝、卡林巴拇指琴、畫畫、唱歌、欣賞舞蹈、看戲都擅長一點的半全能宝藏女孩\n一半煙火以謀生,一半煙火以謀愛\n心有猛虎,细嗅蔷薇160746536\n感恩每个点赞的你",
+    "nickname": "寶藏女孩Bianca",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_ca9f10d41c37fcf87044d11ee7155618.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157911
+  },
+  {
+    "comment_id": "7349881481852289846",
+    "create_time": 1711277639,
+    "ip_location": "北京",
+    "aweme_id": "7267478481213181238",
+    "content": "大小姐驾到!",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "100705566778",
+    "sec_uid": "MS4wLjABAAAAwstRxVa5dqiCmzzz-nSeami6ajaGlym3F5qILh_mFKc",
+    "short_user_id": "1089569000",
+    "user_unique_id": "jianLeon666888",
+    "user_signature": "生在京城,心浮四海。",
+    "nickname": "纪安",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_o4AAkQAmENlAWK3kWeDWCewA9AUnBgiCXIbGDq.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157926
+  },
+  {
+    "comment_id": "7349775380415382308",
+    "create_time": 1711252938,
+    "ip_location": "山东",
+    "aweme_id": "7267478481213181238",
+    "content": "@Daisy. 马面裙就这个",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "104793188823",
+    "sec_uid": "MS4wLjABAAAAHbZZpVVLVVQjrPgWU9oyusNKFpIHUoSMYv6j_0hTzoM",
+    "short_user_id": "1540611514",
+    "user_unique_id": "",
+    "user_signature": "@晓宇_ \n\n@刘佳梁",
+    "nickname": "你是不是油饼√",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_oMCveAzi3CsNUANa9Gh9CqdhAvEfEAy1AIgAjg.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157940
+  },
+  {
+    "comment_id": "7349736805800051497",
+    "create_time": 1711243958,
+    "ip_location": "湖北",
+    "aweme_id": "7267478481213181238",
+    "content": "18年的夏天十六是我的女神,白月光的存在,当年我也只是个中考才毕业的小女孩[求求了]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "2365760049519534",
+    "sec_uid": "MS4wLjABAAAAWkVAvd-Nkzh5-fI6b8CnMdlNsJ4RFYsdjxWkaHNdOcEvIAEJdIUQ8yd7DtK-pwwA",
+    "short_user_id": "3725838642",
+    "user_unique_id": "Erinlpz",
+    "user_signature": "@🍂miss🍂",
+    "nickname": "濯缨",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oQAEAAnVhfASqhAgIeFzNFd7cSlAqNyLbAJNCQ.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157956
+  },
+  {
+    "comment_id": "7349583885716374299",
+    "create_time": 1711208352,
+    "ip_location": "湖南",
+    "aweme_id": "7267478481213181238",
+    "content": "[赞]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "1",
+    "user_id": "99270811894",
+    "sec_uid": "MS4wLjABAAAAYj226mLkUKHSRJYk8nfsupUm7uuf3gJQ8s7z7Ctq9_0",
+    "short_user_id": "2816991967",
+    "user_unique_id": "191118555douyin",
+    "user_signature": "奋斗的人们闪着心爱的光\n讲述生命的韵律\n用新的视角看待太阳的光明\n委屈我的歌",
+    "nickname": "一生一宁",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_34f25ae9870d4ddd8d7f98b64a70bb32.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157971
+  },
+  {
+    "comment_id": "7349559756581602058",
+    "create_time": 1711202733,
+    "ip_location": "四川",
+    "aweme_id": "7267478481213181238",
+    "content": "[调皮][调皮][调皮]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "149990827108499",
+    "sec_uid": "MS4wLjABAAAAlf1iVfoBMARYBYVaRswtWnBfueHsxBmcGbu5nZQDMcU",
+    "short_user_id": "61328566036",
+    "user_unique_id": "61328566036",
+    "user_signature": "",
+    "nickname": "七安🍃",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_o4jJAXNzCELU9MlAv8XRD54fnCjAuhkAfAAcIg.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644157989
+  },
+  {
+    "comment_id": "7349513164931924763",
+    "create_time": 1711191885,
+    "ip_location": "浙江",
+    "aweme_id": "7267478481213181238",
+    "content": "@途径她的盛放 [泣不成声]",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "95850971183",
+    "sec_uid": "MS4wLjABAAAACFvG1Row0WwgSbL-W9pm-rUnRgYE1dx5f7F5BdsxWlQ",
+    "short_user_id": "613835098",
+    "user_unique_id": "",
+    "user_signature": "……",
+    "nickname": "孤影照惊鸿",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813_f3c6dc3453f44ea0a3ed979949f9875a.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644158005
+  },
+  {
+    "comment_id": "7349505708705071899",
+    "create_time": 1711190149,
+    "ip_location": "福建",
+    "aweme_id": "7267478481213181238",
+    "content": "他们会不会是在觉得你这样穿不热嘛!",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "100910549274",
+    "sec_uid": "MS4wLjABAAAA8a5l8r-i5riodBgn6_KpzxH8D748YPS88sZuzeWcFnQ",
+    "short_user_id": "1102257929",
+    "user_unique_id": "",
+    "user_signature": "",
+    "nickname": "🌈小欻欻",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/mosaic-legacy_18aa0000190630a79e5a5.jpeg?from=2956013662",
+    "sub_comment_count": "0",
+    "last_modify_ts": 1711644158112
+  },
+  {
+    "comment_id": "7349430738121687834",
+    "create_time": 1711172695,
+    "ip_location": "山西",
+    "aweme_id": "7267478481213181238",
+    "content": "@谁点我一下 我也要买马面裙",
+    "is_author_digged": false,
+    "is_folded": false,
+    "is_hot": false,
+    "user_buried": false,
+    "user_digged": 0,
+    "digg_count": "0",
+    "user_id": "3661820193412716",
+    "sec_uid": "MS4wLjABAAAAwwFVm1Tu7Nhu8DYvVNyRdUnrKxhhuoMmUrjorM33YXRQgwdj3-spLZjrVVVmuDsr",
+    "short_user_id": "40274819678",
+    "user_unique_id": "336665up",
+    "user_signature": "",
+    "nickname": "烟雨倦",
+    "avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-i-0813c001_oI9CefAKAlRNAFFDylAGAaNVbAEAIArhzQDDhg.jpeg?from=2956013662",
+    "sub_comment_count": "3",
+    "last_modify_ts": 1711644158128
+  }
+]

+ 0 - 4
public/footer/tab-farm-active.svg

@@ -1,4 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
-  <path d="M24 6L10 40h28L24 6z" fill="#ffb400"/>
-  <path d="M24 16v20M17 34h14" stroke="#ffb400" stroke-width="2.5" stroke-linecap="round"/>
-</svg>

+ 0 - 4
public/footer/tab-farm.svg

@@ -1,4 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
-  <path d="M24 8L12 38h24L24 8z" fill="currentColor"/>
-  <path d="M24 14v24M18 32h12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
-</svg>

+ 0 - 6
public/footer/tab-guard-active.svg

@@ -1,6 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
-  <rect x="8" y="12" width="32" height="24" rx="3" stroke="#ffb400" stroke-width="2"/>
-  <path d="M8 20h32" stroke="#ffb400" stroke-width="2"/>
-  <circle cx="18" cy="28" r="3" fill="#ffb400"/>
-  <path d="M28 26l4 4 8-8" stroke="#ffb400" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>

+ 0 - 6
public/footer/tab-guard.svg

@@ -1,6 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
-  <rect x="8" y="12" width="32" height="24" rx="3" stroke="currentColor" stroke-width="2"/>
-  <path d="M8 20h32" stroke="currentColor" stroke-width="2"/>
-  <circle cx="18" cy="28" r="3" fill="currentColor"/>
-  <path d="M28 26l4 4 8-8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>

+ 0 - 5
public/footer/tab-map-active.svg

@@ -1,5 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
-  <path d="M8 28c4-8 10-12 16-12s12 4 16 12" stroke="#ffb400" stroke-width="2.5" stroke-linecap="round"/>
-  <path d="M14 26c2-4 6-6 10-6s8 2 10 6" stroke="#ffb400" stroke-width="2" stroke-linecap="round"/>
-  <circle cx="32" cy="18" r="3" fill="#ffb400"/>
-</svg>

+ 0 - 5
public/footer/tab-map.svg

@@ -1,5 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
-  <path d="M8 28c4-8 10-12 16-12s12 4 16 12" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
-  <path d="M14 26c2-4 6-6 10-6s8 2 10 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
-  <circle cx="32" cy="18" r="3" fill="currentColor"/>
-</svg>

BIN
public/video/portrait.mp4


BIN
public/video/portrait2.mp4


+ 5 - 0
src/App.vue

@@ -1,7 +1,12 @@
 <template>
   <router-view />
+  <BaseFooter />
 </template>
 
+<script setup lang="ts">
+import BaseFooter from '@/components/BaseFooter.vue'
+</script>
+
 <style lang="less">
 #app {
   height: 100%;

BIN
src/assets/img/tabbar/tab-1.png


BIN
src/assets/img/tabbar/tab-2.png


BIN
src/assets/img/tabbar/tab-3.png


BIN
src/assets/img/tabbar/tab-act-1.png


BIN
src/assets/img/tabbar/tab-act-2.png


BIN
src/assets/img/tabbar/tab-act-3.png


+ 12 - 1
src/assets/less/index.less

@@ -1,16 +1,27 @@
 :root {
   --home-header-height: 44rem;
   --footer-height: 56rem;
+  --footer-bottom-offset: 30rem;
+  --footer-safe-gap: 22rem;
+  --footer-safe-bottom: calc(
+    var(--footer-bottom-offset) + var(--footer-height) + var(--footer-safe-gap)
+  );
   --main-bg: rgb(21, 23, 36);
   --second-btn-color: rgb(58, 58, 70);
   --footer-color: black;
   --mask-dark: #000000bb;
-  --mask-light: transparent;
+  --mask-light: rgba(0, 0, 0, 0.45);
+  --second-text-color: #999;
   --mask-white: rgba(0, 0, 0, 0.4);
   --second-btn-color-tran: rgba(58, 58, 70, 0.4);
   --primary-btn-color: #fe2c55;
 }
 
+* {
+  user-select: none;
+  -webkit-tap-highlight-color: transparent;
+}
+
 html,
 body {
   width: 100%;

+ 81 - 26
src/components/BaseFooter.vue

@@ -7,16 +7,18 @@
           :key="item.id"
           class="footer-item"
           :class="{ active: currentTab === item.id }"
-          @click="tab(item.id)"
+          @click="refresh(item.id)"
         >
-          <div class="icon-placeholder">
-            <img
-              class="icon-img"
-              :src="currentTab === item.id ? item.iconActive : item.icon"
-              :alt="item.label"
-            />
-          </div>
-          <span class="label" :class="{ active: currentTab === item.id }">{{ item.label }}</span>
+          <template v-if="!isRefreshing(item.id)">
+            <div class="icon-placeholder">
+              <img
+                class="icon-img"
+                :src="currentTab === item.id ? item.iconActive : item.icon"
+                :alt="item.label"
+              />
+            </div>
+            <span class="label" :class="{ active: currentTab === item.id }">{{ item.label }}</span>
+          </template>
         </div>
       </nav>
     </div>
@@ -24,9 +26,15 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, onUnmounted, ref, watch } from 'vue'
+import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import bus, { EVENT_KEY } from '@/utils/bus'
+import tab1 from '@/assets/img/tabbar/tab-1.png'
+import tabAct1 from '@/assets/img/tabbar/tab-act-1.png'
+import tab2 from '@/assets/img/tabbar/tab-2.png'
+import tabAct2 from '@/assets/img/tabbar/tab-act-2.png'
+import tab3 from '@/assets/img/tabbar/tab-3.png'
+import tabAct3 from '@/assets/img/tabbar/tab-act-3.png'
 
 const props = defineProps({
   initTab: { type: Number, default: 1 },
@@ -36,37 +44,65 @@ const props = defineProps({
 const router = useRouter()
 const route = useRoute()
 const currentTab = ref(props.initTab)
-const visible = ref(true)
+/** douyin:bus 控制;扩展:仅 footerTab 路由 + 首页子层可隐藏 */
+const panelVisible = ref(true)
+const fullscreenHidden = ref(false)
+const hasFooterTab = computed(() => route.meta.footerTab != null)
+const visible = computed(
+  () => hasFooterTab.value && panelVisible.value && !fullscreenHidden.value,
+)
 
 const tabs = [
   {
     id: 1,
     path: '/',
     label: '农场美景',
-    icon: '/footer/tab-farm.svg',
-    iconActive: '/footer/tab-farm-active.svg',
+    icon: tab1,
+    iconActive: tabAct1,
   },
   {
     id: 2,
     path: '/adopt_map',
     label: '守护地图',
-    icon: '/footer/tab-map.svg',
-    iconActive: '/footer/tab-map-active.svg',
+    icon: tab2,
+    iconActive: tabAct2,
   },
   {
     id: 3,
     path: '/my-guard',
     label: '我的守护',
-    icon: '/footer/tab-guard.svg',
-    iconActive: '/footer/tab-guard-active.svg',
+    icon: tab3,
+    iconActive: tabAct3,
   },
 ]
 
 function syncTabFromRoute() {
+  const tab = route.meta.footerTab as number | undefined
+  if (tab) {
+    currentTab.value = tab
+    return
+  }
   const item = tabs.find((t) => t.path === route.path)
   if (item) currentTab.value = item.id
 }
 
+const isRefresh1 = ref(false)
+const isRefresh2 = ref(false)
+const isRefresh3 = ref(false)
+
+function isRefreshing(index: number) {
+  if (index === 1) return isRefresh1.value
+  if (index === 2) return isRefresh2.value
+  if (index === 3) return isRefresh3.value
+  return false
+}
+
+function setRefreshing(index: number, val: boolean) {
+  if (index === 1) isRefresh1.value = val
+  else if (index === 2) isRefresh2.value = val
+  else if (index === 3) isRefresh3.value = val
+}
+
 function tab(index: number) {
   const item = tabs.find((t) => t.id === index)
   if (!item) return
@@ -76,22 +112,42 @@ function tab(index: number) {
   }
 }
 
-watch(() => route.path, syncTabFromRoute)
+/** 与 douyin-vue 一致:当前 Tab 再点进入 refresh 态,否则切换 Tab */
+function refresh(index: number) {
+  if (currentTab.value === index) {
+    setRefreshing(index, !isRefreshing(index))
+    setTimeout(() => {
+      setRefreshing(index, !isRefreshing(index))
+    }, 2000)
+  } else {
+    tab(index)
+  }
+}
+
+watch(
+  () => route.path,
+  () => {
+    panelVisible.value = true
+    fullscreenHidden.value = false
+    syncTabFromRoute()
+  },
+)
 
 onMounted(() => {
   syncTabFromRoute()
   bus.on('setFooterVisible', (e) => {
-    visible.value = e as boolean
+    panelVisible.value = e as boolean
   })
   bus.on(EVENT_KEY.ENTER_FULLSCREEN, () => {
-    visible.value = false
+    fullscreenHidden.value = true
   })
   bus.on(EVENT_KEY.EXIT_FULLSCREEN, () => {
-    visible.value = true
+    fullscreenHidden.value = false
   })
 })
 
 onUnmounted(() => {
+  bus.off('setFooterVisible')
   bus.off(EVENT_KEY.ENTER_FULLSCREEN)
   bus.off(EVENT_KEY.EXIT_FULLSCREEN)
 })
@@ -101,7 +157,7 @@ onUnmounted(() => {
 .footer-wrap {
   position: fixed;
   left: 50%;
-  bottom: 16rem;
+  bottom: var(--footer-bottom-offset, 30rem);
   z-index: 10;
   transform: translateX(-50%);
   width: calc(100vw - 32rem);
@@ -119,10 +175,9 @@ onUnmounted(() => {
   height: 56rem;
   padding: 8rem 12rem;
   box-sizing: border-box;
-  background: #4a4a4a;
-  border: 1px solid rgba(255, 255, 255, 0.35);
-  border-radius: 999rem;
-  box-shadow: 0 2rem 8rem rgba(0, 0, 0, 0.25);
+  background: rgba(0, 0, 0, 0.61);
+  border: 1px solid rgba(255, 255, 255, 0.2);
+  border-radius: 25rem;
 
   &.isWhite {
     background: #f5f5f5;

+ 422 - 100
src/components/Comment.vue

@@ -1,160 +1,482 @@
 <template>
-  <div v-if="modelValue" class="comment-overlay" @click.self="close">
-    <div class="comment-panel">
+  <FromBottomDialog
+    :page-id="pageId"
+    :model-value="modelValue"
+    :show-heng-gang="false"
+    mask-mode="light"
+    :height="height"
+    tag="comment"
+    mode="white"
+    @update:model-value="(e) => emit('update:modelValue', e)"
+    @cancel="cancel"
+  >
+    <template #header>
       <div class="title">
-        <span>{{ comments.length }}条评论</span>
-        <Icon icon="ic:round-close" @click="close" />
+        <div class="num">{{ _formatNumber(comments.length) }}条评论</div>
+        <div class="right">
+          <Icon icon="ic:round-close" class="close-btn" @click.stop="cancel" />
+        </div>
       </div>
-      <div v-if="comments.length" class="list">
-        <div v-for="item in comments" :key="item.id" class="item">
-          <img :src="item.avatar" class="avatar" alt="" />
-          <div class="body">
-            <div class="name">{{ item.nickname }}</div>
-            <div class="content">{{ item.content }}</div>
-            <div class="footer">
-              <span class="time">{{ formatTime(item.create_time) }}</span>
-              <span class="like" :class="{ loved: item.user_digged }">
-                <Icon icon="icon-park-solid:like" />
-                {{ formatCount(item.digg_count) }}
-              </span>
+    </template>
+
+    <div class="comment">
+      <div v-if="loading" class="loading-tip">加载中...</div>
+      <div v-else-if="comments.length" class="wrapper">
+        <div class="items">
+          <div v-for="(item, i) in comments" :key="item.comment_id || i" class="item">
+            <div class="main">
+              <div class="content">
+                <img :src="_checkImgUrl(item.avatar)" alt="" class="head-image" />
+                <div class="comment-container">
+                  <div class="name">{{ item.nickname }}</div>
+                  <div class="detail" :class="{ gray: item.user_buried }">
+                    {{ item.user_buried ? '该评论已折叠' : item.content }}
+                  </div>
+                  <div class="time-wrapper">
+                    <div class="left">
+                      <div class="time">
+                        {{ _time(item.create_time)
+                        }}{{ item.ip_location ? ` · ${item.ip_location}` : '' }}
+                      </div>
+                      <div class="reply-text">回复</div>
+                    </div>
+                    <div class="right-actions">
+                      <div class="love" :class="{ loved: item.user_digged }" @click="loved(item)">
+                        <Icon
+                          v-show="item.user_digged"
+                          icon="icon-park-solid:like"
+                          class="love-image"
+                        />
+                        <Icon
+                          v-show="!item.user_digged"
+                          icon="icon-park-outline:like"
+                          class="love-image"
+                        />
+                        <span v-if="item.digg_count">{{ _formatNumber(item.digg_count) }}</span>
+                      </div>
+                      <div class="love" @click="item.user_buried = !item.user_buried">
+                        <Icon
+                          v-if="item.user_buried"
+                          icon="icon-park-solid:dislike-two"
+                          class="love-image"
+                        />
+                        <Icon v-else icon="icon-park-outline:dislike" class="love-image" />
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div v-if="item.sub_comment_count" class="replies">
+              <template v-if="item.showChildren">
+                <div v-for="(child, ci) in item.children" :key="ci" class="reply">
+                  <div class="content">
+                    <img :src="_checkImgUrl(child.avatar)" alt="" class="head-image small" />
+                    <div class="comment-container">
+                      <div class="name">{{ child.nickname }}</div>
+                      <div class="detail">{{ child.content }}</div>
+                      <div class="time-wrapper">
+                        <div class="left">
+                          <div class="time">
+                            {{ _time(child.create_time)
+                            }}{{ child.ip_location ? ` · ${child.ip_location}` : '' }}
+                          </div>
+                          <div class="reply-text">回复</div>
+                        </div>
+                        <div
+                          class="love"
+                          :class="{ loved: child.user_digged }"
+                          @click="loved(child)"
+                        >
+                          <Icon
+                            v-show="child.user_digged"
+                            icon="icon-park-solid:like"
+                            class="love-image"
+                          />
+                          <Icon
+                            v-show="!child.user_digged"
+                            icon="icon-park-outline:like"
+                            class="love-image"
+                          />
+                          <span>{{ _formatNumber(child.digg_count) }}</span>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              <div v-if="loadChildren && loadChildrenItemCId === item.comment_id" class="more">
+                加载中...
+              </div>
+              <div v-else class="more" @click="handShowChildren(item)">
+                <div class="gang" />
+                <span>展开{{ item.showChildren ? '更多' : `${item.sub_comment_count}条` }}回复</span>
+                <Icon icon="ep:arrow-down-bold" />
+              </div>
             </div>
           </div>
         </div>
+        <p class="no-more">暂时没有更多了</p>
       </div>
       <p v-else class="empty">暂无评论</p>
+
+      <div class="input-toolbar">
+        <div class="toolbar">
+          <div class="input-wrapper">
+            <input
+              v-model="commentText"
+              class="comment-input"
+              type="text"
+              placeholder="善语结善缘,恶言伤人心"
+            />
+            <Icon icon="mdi:emoticon-outline" class="emoji-icon" @click="_no" />
+          </div>
+          <Icon
+            v-if="commentText"
+            icon="mdi:arrow-up-circle"
+            class="send-btn"
+            @click="send"
+          />
+        </div>
+      </div>
     </div>
-  </div>
+  </FromBottomDialog>
 </template>
 
 <script setup lang="ts">
 import { Icon } from '@iconify/vue'
 import { ref, watch } from 'vue'
-import { formatCount, getVideoComments } from '@/mock/homeData'
+import FromBottomDialog from '@/components/dialog/FromBottomDialog.vue'
+import {
+  _checkImgUrl,
+  _formatNumber,
+  _no,
+  _sleep,
+  _time,
+  sampleSize,
+} from '@/utils'
+import { fetchVideoComments, type VideoCommentItem } from '@/mock/homeData'
 
 const props = defineProps({
-  pageId: { type: String, default: '' },
+  pageId: { type: String, default: 'home-index' },
   videoId: { type: String, default: '' },
   modelValue: { type: Boolean, default: false },
+  height: { type: String, default: 'calc(var(--vh, 1vh) * 70)' },
 })
 
 const emit = defineEmits<{ 'update:modelValue': [boolean]; close: [] }>()
 
-const comments = ref<
-  Array<{
-    id: string
-    nickname: string
-    avatar: string
-    content: string
-    create_time: number
-    digg_count: number
-    user_digged: boolean
-  }>
->([])
-
-function loadComments() {
-  const res = getVideoComments(props.videoId || '7267478481213181238')
-  comments.value = res.data
+const comments = ref<VideoCommentItem[]>([])
+const commentText = ref('')
+const loading = ref(false)
+const loadChildren = ref(false)
+const loadChildrenItemCId = ref('')
+
+function cancel() {
+  emit('update:modelValue', false)
+  emit('close')
+}
+
+async function getData() {
+  loading.value = true
+  const res = await fetchVideoComments(props.videoId)
+  loading.value = false
+  if (res.success) {
+    comments.value = res.data
+  } else {
+    comments.value = []
+  }
 }
 
-function formatTime(ts: number) {
-  const d = new Date(ts * 1000)
-  return `${d.getMonth() + 1}-${d.getDate()}`
+function loved(row: VideoCommentItem) {
+  if (row.user_digged) {
+    row.digg_count--
+  } else {
+    row.digg_count++
+  }
+  row.user_digged = !row.user_digged
 }
 
-function close() {
-  emit('update:modelValue', false)
-  emit('close')
+async function handShowChildren(item: VideoCommentItem) {
+  loadChildrenItemCId.value = item.comment_id
+  loadChildren.value = true
+  await _sleep(500)
+  loadChildren.value = false
+  if (item.showChildren) {
+    item.children = (item.children || []).concat(sampleSize(comments.value, 3))
+  } else {
+    item.children = sampleSize(comments.value, 3)
+    item.showChildren = true
+  }
+}
+
+function send() {
+  if (!commentText.value.trim()) return
+  comments.value.unshift({
+    comment_id: `local-${Date.now()}`,
+    create_time: Math.floor(Date.now() / 1000),
+    ip_location: '本地',
+    content: commentText.value,
+    user_buried: false,
+    user_digged: false,
+    digg_count: 0,
+    nickname: '我',
+    avatar: _checkImgUrl(''),
+    sub_comment_count: 0,
+    showChildren: false,
+    children: [],
+  })
+  commentText.value = ''
 }
 
 watch(
-  () => [props.modelValue, props.videoId],
-  ([visible]) => {
-    if (visible) loadComments()
+  () => props.modelValue,
+  (visible) => {
+    if (visible) getData()
+    else comments.value = []
   },
 )
 </script>
 
 <style scoped lang="less">
-.comment-overlay {
-  position: fixed;
-  inset: 0;
-  z-index: 10;
-  background: var(--mask-dark);
+.title {
+  box-sizing: border-box;
+  width: 100%;
+  height: 40rem;
+  padding: 0 15rem;
+  background: white;
   display: flex;
-  align-items: flex-end;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  border-radius: 10rem 10rem 0 0;
+
+  .num {
+    font-size: 12rem;
+    font-weight: bold;
+    text-align: center;
+    color: #000;
+  }
+
+  .right {
+    position: absolute;
+    right: 15rem;
+    z-index: 9;
+
+    .close-btn {
+      color: #000;
+      background: rgb(242, 242, 242);
+      padding: 4rem;
+      font-size: 16rem;
+      border-radius: 50%;
+      cursor: pointer;
+    }
+  }
 }
 
-.comment-panel {
+.comment {
+  color: #000;
   width: 100%;
-  max-height: 70%;
-  background: rgb(28, 30, 43);
-  border-radius: 12rem 12rem 0 0;
-  color: white;
-  display: flex;
-  flex-direction: column;
-
-  .title {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    font-size: 16rem;
-    padding: 16rem;
-    border-bottom: 1px solid rgb(45, 45, 55);
+  background: #fff;
+  min-height: 100%;
+  position: relative;
+
+  .loading-tip,
+  .empty {
+    padding: 40rem;
+    text-align: center;
+    color: #999;
+    font-size: 14rem;
   }
 
-  .list {
-    overflow-y: auto;
-    padding: 0 16rem 16rem;
-    flex: 1;
+  .wrapper {
+    width: 100%;
+    padding-bottom: 60rem;
+  }
+
+  .no-more {
+    text-align: center;
+    font-size: 12rem;
+    color: #ccc;
+    padding: 16rem 0 8rem;
+  }
+
+  .items {
+    width: 100%;
 
     .item {
-      display: flex;
-      gap: 10rem;
-      padding: 12rem 0;
-      border-bottom: 1px solid rgb(40, 40, 50);
-
-      .avatar {
-        width: 36rem;
-        height: 36rem;
-        border-radius: 50%;
-        flex-shrink: 0;
+      width: 100%;
+      margin-bottom: 15rem;
+
+      .main {
+        width: 100%;
+        padding: 5rem 0;
+        display: flex;
       }
 
-      .name {
-        font-size: 13rem;
-        opacity: 0.7;
-        margin-bottom: 4rem;
+      .replies {
+        padding-left: 55rem;
+
+        .reply {
+          padding: 5rem 0 5rem 5rem;
+          display: flex;
+        }
+
+        .more {
+          font-size: 13rem;
+          margin: 5rem;
+          display: flex;
+          align-items: center;
+          color: gray;
+          cursor: pointer;
+
+          .gang {
+            background: #d5d5d5;
+            width: 20rem;
+            margin-right: 10rem;
+            height: 1px;
+          }
+
+          span {
+            margin-right: 5rem;
+          }
+
+          svg {
+            font-size: 10rem;
+          }
+        }
       }
 
       .content {
+        width: 100%;
+        display: flex;
         font-size: 14rem;
-        line-height: 1.4;
-      }
 
-      .footer {
-        display: flex;
-        gap: 16rem;
-        margin-top: 8rem;
-        font-size: 12rem;
-        opacity: 0.5;
+        .head-image {
+          margin-left: 15rem;
+          margin-right: 10rem;
+          width: 37rem;
+          height: 37rem;
+          border-radius: 50%;
+          object-fit: cover;
+          flex-shrink: 0;
 
-        .like {
-          display: flex;
-          align-items: center;
-          gap: 4rem;
+          &.small {
+            width: 20rem;
+            height: 20rem;
+          }
+        }
+
+        .comment-container {
+          flex: 1;
+          margin-right: 20rem;
 
-          &.loved {
-            color: #fe2c55;
+          .name {
+            color: var(--second-text-color);
+            margin-bottom: 5rem;
+          }
+
+          .detail {
+            margin-bottom: 5rem;
+            line-height: 1.4;
+
+            &.gray {
+              color: var(--second-text-color);
+            }
+          }
+
+          .time-wrapper {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            font-size: 13rem;
+
+            .left {
+              display: flex;
+              align-items: center;
+
+              .time {
+                color: #c4c3c3;
+                margin-right: 10rem;
+              }
+
+              .reply-text {
+                color: var(--second-text-color);
+              }
+            }
+
+            .right-actions {
+              display: flex;
+              gap: 10rem;
+            }
+
+            .love {
+              color: gray;
+              display: flex;
+              align-items: center;
+              cursor: pointer;
+
+              &.loved {
+                color: rgb(231, 58, 87);
+              }
+
+              .love-image {
+                font-size: 17rem;
+                margin-right: 4rem;
+              }
+            }
           }
         }
       }
     }
   }
 
-  .empty {
-    padding: 24rem;
-    text-align: center;
-    opacity: 0.5;
+  .input-toolbar {
+    border-radius: 10rem 10rem 0 0;
+    background: white;
+    position: fixed;
+    width: 100%;
+    bottom: 0;
+    z-index: 3;
+    border-top: 1px solid #e2e1e1;
+
+    .toolbar {
+      display: flex;
+      align-items: center;
+      padding: 10rem 15rem;
+
+      .input-wrapper {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        box-sizing: border-box;
+        padding: 5rem 10rem;
+        background: #eee;
+        border-radius: 20rem;
+
+        .comment-input {
+          flex: 1;
+          border: none;
+          background: transparent;
+          outline: none;
+          font-size: 14rem;
+        }
+
+        .emoji-icon {
+          font-size: 22rem;
+          color: #666;
+          margin-left: 8rem;
+          cursor: pointer;
+        }
+      }
+
+      .send-btn {
+        font-size: 28rem;
+        color: #fe2c55;
+        margin-left: 12rem;
+        cursor: pointer;
+      }
+    }
   }
 }
 </style>

+ 117 - 107
src/components/UserPanel.vue

@@ -2,39 +2,20 @@
   <div id="UserPanel" class="user-panel">
     <div class="float">
       <Icon icon="eva:arrow-ios-back-fill" class="icon" @click="emit('back')" />
-      <span class="title">{{ author.nickname || '用户主页' }}</span>
+      <span class="title">{{ farmName }}</span>
     </div>
     <div class="main">
-      <div class="profile">
-        <img :src="avatarUrl" class="avatar" alt="" />
-        <div class="stats">
-          <div class="cell">
-            <b>{{ formatCount(author.following_count as number) }}</b>
-            <span>关注</span>
+      <div class="farm-card">
+        <div class="farm-header">
+          <img :src="avatarUrl" class="avatar" alt="" />
+          <div class="farm-info">
+            <div class="farm-name">{{ farmName }}</div>
+            <div class="farm-location">{{ location }}</div>
           </div>
-          <div class="cell">
-            <b>{{ formatCount(author.mplatform_followers_count as number) }}</b>
-            <span>粉丝</span>
-          </div>
-          <div class="cell">
-            <b>{{ formatCount(author.total_favorited as number) }}</b>
-            <span>获赞</span>
-          </div>
-        </div>
-      </div>
-      <p class="signature">{{ author.signature || '暂无简介' }}</p>
-      <p class="uid">抖音号:{{ author.unique_id || author.uid }}</p>
-      <div class="video-list">
-        <p class="section-title">作品 {{ author.aweme_count || 0 }}</p>
-        <div
-          v-for="v in userVideos"
-          :key="v.aweme_id"
-          class="video-card"
-          @click="openComments(v)"
-        >
-          <img :src="v.video?.cover?.url_list?.[0]" alt="" />
-          <span>{{ v.desc }}</span>
         </div>
+        <div class="farm-intro">{{ intro }}</div>
+        <div class="adopt-friends">{{ adoptFriendsText }}</div>
+        <div class="guard-btn" @click="onGuard">我要守护</div>
       </div>
     </div>
   </div>
@@ -43,8 +24,22 @@
 <script setup lang="ts">
 import { Icon } from '@iconify/vue'
 import { computed } from 'vue'
-import { formatCount, recommendVideos } from '@/mock/homeData'
-import bus, { EVENT_KEY } from '@/utils/bus'
+import { useRouter } from 'vue-router'
+import { _checkImgUrl, _notice } from '@/utils/index'
+
+type PanelItem = {
+  farmName?: string
+  desc?: string
+  city?: string
+  address?: string
+  adopt_friends_count?: number
+  author?: {
+    nickname?: string
+    signature?: string
+    avatar_168x168?: { url_list?: string[] }
+  }
+  video?: { cover?: { url_list?: string[] } }
+}
 
 const props = defineProps({
   currentItem: { type: Object, default: () => ({}) },
@@ -59,124 +54,139 @@ const emit = defineEmits<{
   'update:currentItem': [unknown]
 }>()
 
-const author = computed(() => (props.currentItem as { author?: Record<string, unknown> }).author || {})
+const router = useRouter()
+
+const item = computed(() => props.currentItem as PanelItem)
+const author = computed(() => item.value.author || {})
 
-const avatarUrl = computed(
-  () =>
-    (author.value.avatar_168x168 as { url_list?: string[] })?.url_list?.[0] ||
-    'https://p3-pc.douyinpic.com/img/aweme-avatar/tos-cn-avt-0015_99d3a4923c94e1e27b16209743eaec24~c5_168x168.jpeg?from=2956013662',
+const farmName = computed(
+  () => item.value.farmName || author.value.nickname || '从化荔博园',
 )
 
-const userVideos = computed(() => {
-  const uid = author.value.uid
-  return recommendVideos.filter((v) => v.author?.uid === uid).slice(0, 3)
+const location = computed(() => {
+  const parts = [item.value.city, item.value.address].filter(Boolean)
+  return parts.length ? parts.join(' ') : '农场位置农场位置'
+})
+
+const intro = computed(() => {
+  const sig = author.value.signature?.trim()
+  if (sig) return sig
+  const desc = item.value.desc?.replace(/\n/g, ' ').trim()
+  if (desc) return desc
+  return '果园介绍果园介绍果园介绍'
+})
+
+const adoptFriendsText = computed(() => {
+  const n = item.value.adopt_friends_count ?? 3
+  return `${n}个朋友认养`
+})
+
+const avatarUrl = computed(() => {
+  const cover = item.value.video?.cover?.url_list?.[0]
+  const avatar = author.value.avatar_168x168?.url_list?.[0]
+  return (
+    // _checkImgUrl(cover || avatar) ||
+    'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
+  )
 })
 
-function openComments(v: (typeof recommendVideos)[0]) {
-  bus.emit(EVENT_KEY.CURRENT_ITEM, v)
-  bus.emit(EVENT_KEY.OPEN_COMMENTS)
+function onGuard() {
+  _notice('守护功能开发中')
+  router.push('/my-guard')
 }
 
 defineExpose({
-  cancelFollow: () => {},
+  cancelFollow: () => { },
 })
 </script>
 
 <style scoped lang="less">
 .user-panel {
-  height: 100%;
-  background: rgb(22, 22, 22);
-  color: white;
+  height: 100vh;
+  background: #fff;
   overflow: auto;
 
   .float {
     display: flex;
     align-items: center;
-    gap: 12rem;
-    padding: 12rem 15rem;
+    justify-content: center;
     position: sticky;
     top: 0;
-    background: rgb(22, 22, 22);
+    padding: 12rem 48rem;
+    background: #fff;
     z-index: 1;
 
     .icon {
-      font-size: 28rem;
+      position: absolute;
+      left: 12rem;
+      font-size: 25rem;
       cursor: pointer;
     }
 
     .title {
-      font-size: 16rem;
-      font-weight: bold;
+      font-size: 17rem;
+      font-weight: 500;
+      text-align: center;
+      max-width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
     }
   }
 
   .main {
-    padding: 0 15rem 20rem;
-
-    .profile {
-      display: flex;
-      align-items: center;
-      gap: 20rem;
-      margin-bottom: 12rem;
-
-      .avatar {
-        width: 80rem;
-        height: 80rem;
-        border-radius: 50%;
-      }
+    padding: 6rem 18rem;
 
-      .stats {
-        flex: 1;
+    .farm-card {
+      .farm-header {
         display: flex;
-        justify-content: space-around;
-
-        .cell {
-          text-align: center;
+        align-items: center;
+        gap: 10rem;
+        margin-bottom: 16rem;
+
+        .avatar {
+          width: 66rem;
+          height: 66rem;
+          border-radius: 50%;
+          object-fit: cover;
+        }
 
-          b {
-            display: block;
-            font-size: 16rem;
-          }
+        .farm-name {
+          margin-bottom: 8rem;
+          font-size: 20rem;
+          font-weight: 500;
+          color: #1D2129;
+        }
 
-          span {
-            font-size: 12rem;
-            opacity: 0.6;
-          }
+        .farm-location {
+          font-size: 12rem;
+          color: #B0B0B0;
         }
       }
-    }
 
-    .signature,
-    .uid {
-      font-size: 13rem;
-      opacity: 0.7;
-      margin: 0 0 8rem;
-    }
-
-    .section-title {
-      font-size: 14rem;
-      margin: 16rem 0 10rem;
-    }
-
-    .video-card {
-      display: flex;
-      gap: 10rem;
-      margin-bottom: 10rem;
-      background: rgb(29, 29, 29);
-      border-radius: 8rem;
-      overflow: hidden;
-      cursor: pointer;
+      .farm-intro {
+        font-size: 12rem;
+        line-height: 1.5;
+        display: -webkit-box;
+        -webkit-line-clamp: 3;
+        -webkit-box-orient: vertical;
+        overflow: hidden;
+        margin-bottom: 8rem;
+      }
 
-      img {
-        width: 80rem;
-        height: 80rem;
-        object-fit: cover;
+      .adopt-friends {
+        margin-bottom: 16rem;
+        font-size: 12rem;
+        color: rgba(0, 0, 0, 0.5);
       }
 
-      span {
+      .guard-btn {
         padding: 10rem;
-        font-size: 13rem;
-        line-height: 1.4;
+        border-radius: 4rem;
+        background: #FFA617;
+        color: #fff;
+        text-align: center;
+        cursor: pointer;
       }
     }
   }

+ 190 - 0
src/components/dialog/FromBottomDialog.vue

@@ -0,0 +1,190 @@
+<template>
+  <Transition name="test">
+    <div
+      v-if="modelValue"
+      ref="dialog"
+      :class="['FromBottomDialog', mode, showHengGang ? '' : 'no-heng-gang']"
+      @touchstart="onStart"
+      @touchmove="onMove"
+      @touchend="onEnd"
+    >
+      <slot name="header" />
+      <div v-if="showHengGang" :class="['heng-gang', mode]">
+        <div class="content" />
+      </div>
+      <div ref="wrapper" class="wrapper">
+        <slot />
+      </div>
+    </div>
+  </Transition>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import Dom, { _css } from '@/utils/dom'
+import bus, { EVENT_KEY } from '@/utils/bus'
+import { _stopPropagation } from '@/utils'
+
+const props = withDefaults(
+  defineProps<{
+    modelValue?: boolean
+    mode?: 'dark' | 'light' | 'white'
+    maskMode?: 'dark' | 'light' | 'white'
+    height?: string
+    showHengGang?: boolean
+    pageId: string
+    tag?: string
+  }>(),
+  {
+    modelValue: false,
+    mode: 'dark',
+    maskMode: 'dark',
+    height: 'calc(var(--vh, 1vh) * 70)',
+    showHengGang: true,
+    tag: '',
+  },
+)
+
+const emit = defineEmits<{
+  'update:modelValue': [boolean]
+  cancel: []
+}>()
+
+const dialog = ref<HTMLElement | null>(null)
+const wrapper = ref<HTMLElement | null>(null)
+const scroll = ref(0)
+const startY = ref(0)
+const moveY = ref(0)
+const startTime = ref(0)
+const pagePosition = ref<string | null>(null)
+
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    const page = document.getElementById(props.pageId)
+    if (!page) return
+    if (newVal) {
+      pagePosition.value = String(_css(page, 'position') ?? '')
+      page.style.position = 'absolute'
+      scroll.value = document.documentElement.scrollTop
+      document.body.style.position = 'fixed'
+      document.body.style.top = `-${scroll.value}px`
+
+      const mask = new Dom().create(`<div class="Mask fade-in ${props.maskMode}"></div>`)
+      setTimeout(() => {
+        mask.on('click', (e: Event) => {
+          _stopPropagation(e)
+          onHide()
+        })
+      }, 200)
+      new Dom(page).append(mask)
+    } else {
+      page.style.position = pagePosition.value || 'fixed'
+      document.body.style.position = 'static'
+      document.documentElement.scrollTop = scroll.value
+      const mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out')
+      setTimeout(() => mask.remove(), 250)
+    }
+  },
+)
+
+function onHide() {
+  emit('update:modelValue', false)
+  emit('cancel')
+}
+
+function onStart(e: TouchEvent) {
+  if (wrapper.value?.scrollTop !== 0) return
+  startY.value = e.touches[0].clientY
+  startTime.value = Date.now()
+  if (dialog.value) _css(dialog.value, 'transition-duration', '0ms')
+}
+
+function onMove(e: TouchEvent) {
+  if (wrapper.value?.scrollTop !== 0) return
+  moveY.value = e.touches[0].pageY - startY.value
+  if (moveY.value > 0 && dialog.value) {
+    bus.emit(EVENT_KEY.DIALOG_MOVE, { tag: props.tag, e: moveY.value })
+    _css(dialog.value, 'transform', `translate3d(0, ${moveY.value}px, 0)`)
+  }
+}
+
+function onEnd() {
+  if (!dialog.value) return
+  if (Date.now() - startTime.value < 150 && Math.abs(moveY.value) < 30) return
+  const clientHeight = dialog.value.clientHeight
+  _css(dialog.value, 'transition-duration', '250ms')
+  if (Math.abs(moveY.value) > clientHeight / 2) {
+    _css(dialog.value, 'transform', 'translate3d(0,100%,0)')
+    bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: true })
+    setTimeout(onHide, 250)
+  } else {
+    _css(dialog.value, 'transform', 'translate3d(0,0,0)')
+    bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: false })
+  }
+  moveY.value = 0
+}
+</script>
+
+<style scoped lang="less">
+.test-enter-active,
+.test-leave-active {
+  transition-duration: 250ms !important;
+}
+
+.test-enter-from,
+.test-leave-to {
+  transform: translate3d(0, 101%, 0) !important;
+}
+
+.FromBottomDialog {
+  z-index: 9;
+  position: fixed;
+  width: 100%;
+  padding-top: 24rem;
+  bottom: 0;
+  left: 0;
+  box-sizing: border-box;
+  border-radius: 15rem 15rem 0 0;
+  transform: translate3d(0, 0, 0);
+  overflow: hidden;
+  display: flex;
+  height: v-bind(height);
+  max-height: v-bind(height);
+  flex-direction: column;
+
+  &.white {
+    background: white;
+  }
+
+  &.no-heng-gang {
+    padding-top: 0;
+  }
+
+  .heng-gang {
+    border-radius: 15rem 15rem 0 0;
+    z-index: 3;
+    width: 100%;
+    position: fixed;
+    height: 30rem;
+    display: flex;
+    transform: translateY(-24rem);
+    justify-content: center;
+    align-items: center;
+    touch-action: pan-y;
+    background: white;
+
+    .content {
+      border-radius: 2px;
+      height: 4rem;
+      width: 30rem;
+      background: darkgray;
+    }
+  }
+
+  .wrapper {
+    flex: 1;
+    overflow: auto;
+  }
+}
+</style>

+ 77 - 7
src/components/video/BaseVideo.vue

@@ -18,9 +18,11 @@
     <Icon v-if="!isPlaying && !loadFailed" icon="fluent:play-28-filled" class="pause-icon" />
     <p v-if="loadFailed" class="error-tip">视频加载失败,请检查网络</p>
     <div class="float">
-      <div class="normal">
-        <ItemToolbar :item="localItem" @update:item="onItemUpdate" />
-        <ItemDesc />
+      <div class="normal" :style="{ opacity: state.isMove ? 0 : 1 }">
+        <template v-if="!state.commentVisible">
+          <ItemToolbar :item="localItem" @update:item="onItemUpdate" />
+          <ItemDesc />
+        </template>
       </div>
     </div>
   </div>
@@ -32,6 +34,7 @@ import { computed, onMounted, onUnmounted, provide, reactive, ref, watch } from
 import bus, { EVENT_KEY } from '@/utils/bus'
 import { SlideItemPlayStatus } from '@/utils/const_var'
 import { _checkImgUrl } from '@/utils/index'
+import { _css } from '@/utils/dom'
 import ItemToolbar from './ItemToolbar.vue'
 import ItemDesc from './ItemDesc.vue'
 
@@ -58,8 +61,56 @@ const localItem = reactive({ ...(props.item as object) })
 
 const state = reactive({
   status: props.isPlay ? SlideItemPlayStatus.Play : SlideItemPlayStatus.Pause,
+  commentVisible: false,
+  isMove: false,
 })
 
+const COMMENT_VIDEO_HEIGHT = 'calc(var(--vh, 1vh) * 30)'
+
+function getAwemeId() {
+  return (localItem as { aweme_id?: string }).aweme_id
+}
+
+function setVideoHeight(height: string, duration = '300ms') {
+  const el = videoEl.value
+  if (!el) return
+  _css(el, 'transition-duration', duration)
+  _css(el, 'height', height)
+}
+
+function onDialogMove(val?: unknown) {
+  const { tag, e } = (val as { tag?: string; e?: number }) || {}
+  if (!state.commentVisible || tag !== 'comment' || e === undefined) return
+  state.isMove = true
+  setVideoHeight(`calc(var(--vh, 1vh) * 30 + ${e}px)`, '0ms')
+}
+
+function onDialogEnd(val?: unknown) {
+  const { tag, isClose } = (val as { tag?: string; isClose?: boolean }) || {}
+  if (!state.commentVisible || tag !== 'comment') return
+  state.isMove = false
+  if (isClose) {
+    state.commentVisible = false
+    setVideoHeight('100%')
+  } else {
+    setVideoHeight(COMMENT_VIDEO_HEIGHT)
+  }
+}
+
+function onOpenComments(id?: unknown) {
+  const awemeId = id as string | undefined
+  if (awemeId && awemeId !== getAwemeId()) return
+  state.commentVisible = true
+  setVideoHeight(COMMENT_VIDEO_HEIGHT)
+}
+
+function onCloseComments() {
+  if (!state.commentVisible) return
+  state.commentVisible = false
+  state.isMove = false
+  setVideoHeight('100%')
+}
+
 const playUrls = computed(() => {
   const video = (localItem as { video?: { play_addr?: { url_list?: string[] } } }).video
   return video?.play_addr?.url_list?.filter(Boolean) || []
@@ -190,6 +241,10 @@ function onRemoveMuted() {
 onMounted(() => {
   bus.on(EVENT_KEY.SINGLE_CLICK_BROADCAST, onBusClick)
   bus.on(EVENT_KEY.REMOVE_MUTED, onRemoveMuted)
+  bus.on(EVENT_KEY.DIALOG_MOVE, onDialogMove)
+  bus.on(EVENT_KEY.DIALOG_END, onDialogEnd)
+  bus.on(EVENT_KEY.OPEN_COMMENTS, onOpenComments)
+  bus.on(EVENT_KEY.CLOSE_COMMENTS, onCloseComments)
   if ((window as Window).isMuted === false) unmute()
   if (props.isPlay) play()
 })
@@ -197,22 +252,32 @@ onMounted(() => {
 onUnmounted(() => {
   bus.off(EVENT_KEY.SINGLE_CLICK_BROADCAST, onBusClick)
   bus.off(EVENT_KEY.REMOVE_MUTED, onRemoveMuted)
+  bus.off(EVENT_KEY.DIALOG_MOVE, onDialogMove)
+  bus.off(EVENT_KEY.DIALOG_END, onDialogEnd)
+  bus.off(EVENT_KEY.OPEN_COMMENTS, onOpenComments)
+  bus.off(EVENT_KEY.CLOSE_COMMENTS, onCloseComments)
   pause()
+  setVideoHeight('100%', '0ms')
 })
 </script>
 
 <style scoped lang="less">
 .video-wrapper {
   position: relative;
+  font-size: 14rem;
   width: 100%;
   height: 100%;
+  text-align: center;
   background: #000;
   overflow: hidden;
 
   .video {
-    width: 100%;
+    max-width: 100%;
     height: 100%;
-    object-fit: cover;
+    vertical-align: top;
+    transition:
+      height 0.3s,
+      margin-top 0.3s;
   }
 
   .pause-icon {
@@ -238,13 +303,18 @@ onUnmounted(() => {
 
   .float {
     position: absolute;
-    inset: 0;
+    left: 0;
+    top: 0;
+    height: 100%;
+    width: 100%;
     z-index: 2;
     pointer-events: none;
 
     .normal {
       position: absolute;
-      inset: 0;
+      bottom: 0;
+      width: 100%;
+      transition: all 0.3s;
       pointer-events: none;
     }
   }

+ 36 - 7
src/components/video/ItemDesc.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
 import { Icon } from '@iconify/vue'
-import { inject } from 'vue'
+import { computed, inject } from 'vue'
 import bus, { EVENT_KEY } from '@/utils/bus'
+import { _formatChineseDate } from '@/utils/index'
 
 defineProps({
   isMy: { type: Boolean, default: false },
@@ -12,9 +13,17 @@ const item = inject<{
   desc?: string
   city?: string
   address?: string
+  create_time?: number
+  farmName?: string
   author?: { nickname?: string }
 }>('item')
 
+const displayName = computed(
+  () => item?.farmName || item?.author?.nickname || '',
+)
+
+const publishDate = computed(() => _formatChineseDate(item?.create_time))
+
 function goUser() {
   bus.emit(EVENT_KEY.GO_USERINFO)
 }
@@ -33,7 +42,10 @@ function goUser() {
       </div>
     </div>
     <div v-if="isLive" class="live">直播中</div>
-    <div class="name" @click.stop="goUser">@{{ item.author?.nickname }}</div>
+    <div class="name-row" @click.stop="goUser">
+      <span class="name">@ {{ displayName }}</span>
+      <span v-if="publishDate" class="date">{{ publishDate }}</span>
+    </div>
     <div class="description">{{ item.desc }}</div>
   </div>
 </template>
@@ -41,10 +53,10 @@ function goUser() {
 <style scoped lang="less">
 .item-desc {
   position: absolute;
-  bottom: 0;
+  bottom: var(--footer-safe-bottom, 100rem);
   left: 0;
   width: 70%;
-  padding: 0 0 20rem 15rem;
+  padding: 0 0 8rem 15rem;
   box-sizing: border-box;
   pointer-events: auto;
   z-index: 4;
@@ -85,11 +97,28 @@ function goUser() {
     background: #fe2c55;
   }
 
-  .name {
-    font-size: 18rem;
-    font-weight: bold;
+  .name-row {
+    display: flex;
+    align-items: center;
+    flex-wrap: nowrap;
+    gap: 12rem;
     margin-bottom: 8rem;
     cursor: pointer;
+
+    .name {
+      font-size: 18rem;
+      font-weight: bold;
+      color: #fff;
+      flex-shrink: 0;
+    }
+
+    .date {
+      font-size: 14rem;
+      font-weight: normal;
+      color: rgba(255, 255, 255, 0.55);
+      flex-shrink: 0;
+      white-space: nowrap;
+    }
   }
 
   .description {

+ 22 - 12
src/components/video/ItemToolbar.vue

@@ -3,6 +3,9 @@ import { Icon } from '@iconify/vue'
 import { inject } from 'vue'
 import bus, { EVENT_KEY } from '@/utils/bus'
 import { _checkImgUrl, _formatNumber } from '@/utils/index'
+import { useClick } from '@/utils/hooks/useClick'
+
+const vClick = useClick()
 const props = defineProps({
   item: { type: Object, required: true },
   isMy: { type: Boolean, default: false },
@@ -13,6 +16,7 @@ const emit = defineEmits<{ 'update:item': [unknown] }>()
 const position = inject<{ uniqueId: string; index: number }>('position')
 
 type VideoItem = {
+  aweme_id?: string
   author?: { avatar_168x168?: { url_list?: string[] } }
   isAttention?: boolean
   isLoved?: boolean
@@ -64,7 +68,7 @@ function attention(e: Event) {
 }
 
 function showComments() {
-  bus.emit(EVENT_KEY.OPEN_COMMENTS)
+  bus.emit(EVENT_KEY.OPEN_COMMENTS, (props.item as VideoItem).aweme_id)
 }
 
 function goUser() {
@@ -85,25 +89,27 @@ const item = props.item as VideoItem
         class="avatar"
         :src="_checkImgUrl(item.author?.avatar_168x168?.url_list?.[0])"
         alt=""
-        @click.stop="goUser"
+        v-click.stop="goUser"
       />
-      <transition name="fade">
+      <!-- <transition name="fade">
         <div v-if="!item.isAttention" class="options" @click.stop="attention">
           <img class="no" src="@/assets/img/icon/add-light.png" alt="" />
           <img class="yes" src="@/assets/img/icon/ok-red.png" alt="" />
         </div>
-      </transition>
+      </transition> -->
     </div>
-    <div class="love" @click.stop="loved">
-      <img v-if="!item.isLoved" src="@/assets/img/icon/love.svg" class="action-icon" alt="" />
-      <img v-else src="@/assets/img/icon/loved.svg" class="action-icon" alt="" />
+    <div class="love" v-click.stop="loved">
+      <div>
+        <img v-if="!item.isLoved" src="@/assets/img/icon/love.svg" class="action-icon" alt="" />
+        <img v-else src="@/assets/img/icon/loved.svg" class="action-icon" alt="" />
+      </div>
       <span>{{ _formatNumber(item.statistics.digg_count) }}</span>
     </div>
-    <div class="message" @click.stop="showComments">
+    <div class="message" v-click.stop="showComments">
       <Icon icon="mage:message-dots-round-fill" class="icon" />
       <span>{{ _formatNumber(item.statistics.comment_count) }}</span>
     </div>
-    <div class="message" @click.stop="collected">
+    <div class="message" v-click.stop="collected">
       <Icon
         icon="ic:round-star"
         class="icon"
@@ -111,11 +117,11 @@ const item = props.item as VideoItem
       />
       <span>{{ _formatNumber(item.statistics.collect_count) }}</span>
     </div>
-    <div v-if="!isMy" class="share" @click.stop="showShare">
+    <div v-if="!isMy" class="share" v-click.stop="showShare">
       <img src="@/assets/img/icon/share-white-full.png" class="action-icon" alt="" />
       <span>{{ _formatNumber(item.statistics.share_count) }}</span>
     </div>
-    <div v-else class="share" @click.stop="showShare">
+    <div v-else class="share" v-click.stop="showShare">
       <Icon icon="ri:more-line" class="icon" />
     </div>
   </div>
@@ -124,7 +130,7 @@ const item = props.item as VideoItem
 <style scoped lang="less">
 .toolbar {
   position: absolute;
-  bottom: 0;
+  bottom: var(--footer-safe-bottom, 100rem);
   right: 10rem;
   color: #fff;
   display: flex;
@@ -197,10 +203,14 @@ const item = props.item as VideoItem
     align-items: center;
     margin-bottom: 20rem;
     cursor: pointer;
+    -webkit-tap-highlight-color: transparent;
+    tap-highlight-color: transparent;
+    user-select: none;
 
     .action-icon {
       width: 35rem;
       height: 35rem;
+      pointer-events: none;
     }
 
     .icon {

+ 144 - 48
src/mock/homeData.ts

@@ -6,25 +6,108 @@ export type RecommendVideo = (typeof posts6)[number] & { type: string }
 /** 本地演示视频,避免 douyin 跳转 CDN 连接被重置 */
 const LOCAL_MP4 = '/video/demo.mp4'
 
+/** 本地竖屏视频 1080×1920(从 posts6 抖音源下载) */
+const LOCAL_PORTRAIT_MP4 = '/video/portrait.mp4'
+const LOCAL_PORTRAIT_MP4_2 = '/video/portrait2.mp4'
+
 const REMOTE_MP4 = [
   'https://www.w3schools.com/html/mov_bbb.mp4',
   'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
 ]
 
-export const recommendVideos = posts6.map((v, i) => ({
-  ...v,
-  type: 'recommend-video',
-  isLoved: false,
-  isCollect: false,
-  isAttention: false,
-  video: {
-    ...v.video,
-    play_addr: {
-      ...v.video?.play_addr,
-      url_list: [LOCAL_MP4, REMOTE_MP4[i % REMOTE_MP4.length]],
+function applyPortraitVideo(
+  item: RecommendVideo,
+  postIndex: number,
+  portraitFile: string,
+  label = '竖屏',
+): RecommendVideo {
+  const raw = posts6[postIndex]
+  const w = raw?.video?.play_addr?.width ?? 1080
+  const h = raw?.video?.play_addr?.height ?? 1920
+  return {
+    ...item,
+    desc: `【${label}】${raw?.desc?.slice(0, 30) || ''}`,
+    video: {
+      ...item.video,
+      width: w,
+      height: h,
+      play_addr: {
+        ...item.video.play_addr,
+        width: w,
+        height: h,
+        url_list: [portraitFile],
+      },
     },
-  },
-})) as RecommendVideo[]
+  } as RecommendVideo
+}
+
+function buildVideoList(
+  source: typeof posts6,
+  options: { playIndexOffset?: number; primaryUrl?: string; fallbackUrl?: string } = {},
+): RecommendVideo[] {
+  const { playIndexOffset = 0, primaryUrl = LOCAL_MP4, fallbackUrl } = options
+  const farmNames = ['农场名称一', '农场名称二', '农场名称三', '农场名称四', '农场名称五', '农场名称六']
+  return source.map((v, i) => ({
+    ...v,
+    type: 'recommend-video',
+    farmName: farmNames[i] || `农场名称${i + 1}`,
+    adopt_friends_count: [3, 5, 8, 2, 6, 4][i] ?? 3,
+    isLoved: false,
+    isCollect: false,
+    isAttention: false,
+    video: {
+      ...v.video,
+      play_addr: {
+        ...v.video?.play_addr,
+        url_list: [
+          primaryUrl,
+          fallbackUrl ?? REMOTE_MP4[(i + playIndexOffset) % REMOTE_MP4.length],
+        ],
+      },
+    },
+  })) as RecommendVideo[]
+}
+
+/** 第 1 条:横屏 demo */
+function applyLandscapeDemo(item: RecommendVideo): RecommendVideo {
+  return {
+    ...item,
+    desc: `【横屏demo】${item.desc?.slice(0, 28) || ''}`,
+    video: {
+      ...item.video,
+      width: 1920,
+      height: 1080,
+      play_addr: {
+        ...item.video.play_addr,
+        width: 1920,
+        height: 1080,
+        url_list: [LOCAL_MP4],
+      },
+    },
+  } as RecommendVideo
+}
+
+/** 推荐 Tab:第1条横屏 demo;第2、3条竖屏 9:16 */
+export const recommendVideos = buildVideoList(posts6, { primaryUrl: LOCAL_MP4 }).map((v, i) => {
+  if (i === 0) return applyLandscapeDemo(v)
+  if (i === 1) return applyPortraitVideo(v, 1, LOCAL_PORTRAIT_MP4, '竖屏2')
+  if (i === 2) return applyPortraitVideo(v, 3, LOCAL_PORTRAIT_MP4_2, '竖屏3')
+  return v
+})
+
+/** 我的 Tab:另一段远程演示视频,便于区分 */
+export const myVideos = buildVideoList(posts6.slice(2), {
+  primaryUrl: REMOTE_MP4[1],
+  fallbackUrl: REMOTE_MP4[0],
+}).map((v, i) => {
+  if (i === 0) {
+    return { ...v, desc: `【我的频道】${v.desc || '竖屏视频'}` }
+  }
+  if (i === 1) {
+    return applyPortraitVideo(v, 3, LOCAL_PORTRAIT_MP4_2)
+  }
+  return v
+})
 
 const AVATAR =
   'https://p3-pc.douyinpic.com/img/aweme-avatar/tos-cn-avt-0015_99d3a4923c94e1e27b16209743eaec24~c5_168x168.jpeg?from=2956013662'
@@ -68,38 +151,41 @@ export const communityPosts = recommendVideos.slice(0, 2).map((v, i) => ({
 export const hotFeed = recommendVideos.slice(0, 2)
 export const followFeed = recommendVideos.slice(2, 3)
 
-export const videoComments: Record<
-  string,
-  Array<{
-    id: string
-    nickname: string
-    avatar: string
-    content: string
-    create_time: number
-    digg_count: number
-    user_digged: boolean
-  }>
-> = {
-  [recommendVideos[0]?.aweme_id]: [
-    {
-      id: 'c1',
-      nickname: '小果园',
-      avatar: AVATAR,
-      content: '认养链接在哪?',
-      create_time: 1692092000,
-      digg_count: 1280,
-      user_digged: false,
-    },
-    {
-      id: 'c2',
-      nickname: '旅行日记',
-      avatar: AVATAR,
-      content: '画面太美了',
-      create_time: 1692092500,
-      digg_count: 5600,
-      user_digged: true,
-    },
-  ],
+export type VideoCommentItem = {
+  comment_id: string
+  create_time: number
+  ip_location?: string
+  content: string
+  user_buried: boolean
+  user_digged: boolean
+  digg_count: number
+  nickname: string
+  avatar: string
+  sub_comment_count: number
+  showChildren?: boolean
+  children?: VideoCommentItem[]
+}
+
+const COMMENT_VIDEO_IDS = [
+  '7267478481213181238',
+  '7128686458763889956',
+]
+
+function normalizeComment(raw: Record<string, unknown>): VideoCommentItem {
+  return {
+    comment_id: String(raw.comment_id),
+    create_time: Number(raw.create_time),
+    ip_location: raw.ip_location as string | undefined,
+    content: String(raw.content || ''),
+    user_buried: Boolean(raw.user_buried),
+    user_digged: Boolean(raw.user_digged),
+    digg_count: Number(raw.digg_count) || 0,
+    nickname: String(raw.nickname || '用户'),
+    avatar: String(raw.avatar || AVATAR),
+    sub_comment_count: Number(raw.sub_comment_count) || 0,
+    showChildren: false,
+    children: [],
+  }
 }
 
 export function getRecommendedVideos(pageNo = 0, pageSize = 10) {
@@ -112,9 +198,19 @@ export function getRecommendedVideos(pageNo = 0, pageSize = 10) {
   }
 }
 
-export function getVideoComments(videoId: string) {
-  const list = videoComments[videoId] || videoComments[recommendVideos[0]?.aweme_id] || []
-  return { code: 200, data: list }
+/** 与 douyin-vue 一致:按视频 id 拉取评论 json */
+export async function fetchVideoComments(videoId: string) {
+  let id = videoId
+  if (!COMMENT_VIDEO_IDS.includes(String(id))) {
+    id = COMMENT_VIDEO_IDS[0]
+  }
+  try {
+    const res = await fetch(`/data/comments/video_id_${id}.json`)
+    const data = (await res.json()) as Record<string, unknown>[]
+    return { success: true, data: data.map(normalizeComment) }
+  } catch {
+    return { success: false, data: [] as VideoCommentItem[] }
+  }
 }
 
 export function formatCount(n: number) {

+ 2 - 0
src/utils/bus.ts

@@ -48,4 +48,6 @@ export const EVENT_KEY = {
   ITEM_STOP: 'ITEM_STOP',
   UPDATE_ITEM: 'UPDATE_ITEM',
   REMOVE_MUTED: 'REMOVE_MUTED',
+  DIALOG_MOVE: 'DIALOG_MOVE',
+  DIALOG_END: 'DIALOG_END',
 }

+ 66 - 0
src/utils/dom.ts

@@ -1,3 +1,69 @@
+export default class Dom {
+  els: Element[] = []
+
+  constructor(arg?: string | (() => void) | Element) {
+    if (typeof arg === 'string') {
+      return this.find(arg)
+    }
+    if (typeof arg === 'object' && arg instanceof Element) {
+      this.els.push(arg)
+    }
+    if (typeof arg === 'function') {
+      document.addEventListener('DOMContentLoaded', arg)
+    }
+    return this
+  }
+
+  replaceClass(class1: string, class2: string) {
+    this.els.forEach((el) => {
+      el.classList.replace(class1, class2)
+    })
+    return this
+  }
+
+  find(tag: string) {
+    let els: Element[] = []
+    if (this.els.length) {
+      els = Array.from(this.els[0].querySelectorAll(tag))
+    } else {
+      els = Array.from(document.querySelectorAll(tag))
+    }
+    if (els.length) {
+      this.els = els
+    }
+    return this
+  }
+
+  create(template: string) {
+    const tempNode = document.createElement('div')
+    tempNode.innerHTML = template.trim()
+    const first = tempNode.firstElementChild
+    if (first) this.els = [first]
+    return this
+  }
+
+  append(that: Dom) {
+    this.els.forEach((el) => {
+      that.els.forEach((child) => el.appendChild(child))
+    })
+    return this
+  }
+
+  on(eventName: string, fn: (e: Event) => void) {
+    eventName.split(' ').forEach((event) => {
+      this.els.forEach((el) => el.addEventListener(event, fn))
+    })
+    return this
+  }
+
+  remove() {
+    this.els.forEach((el) => {
+      el.parentNode?.removeChild(el)
+    })
+    return this
+  }
+}
+
 export function _css(el: HTMLElement, key: string, value?: string | number) {
   const reg = /^-?\d+.?\d*(px|pt|em|rem|vw|vh|%|rpx|ms)$/i
   if (value === undefined) {

+ 17 - 0
src/utils/hooks/useClick.ts

@@ -0,0 +1,17 @@
+import { _stopPropagation } from '@/utils/index'
+
+/** 与 douyin-vue 一致:用 pointer 事件代替 click,避免滑动后点击延迟与 tap 高亮遮罩 */
+export function useClick() {
+  return {
+    mounted(el: HTMLElement, binding: { value?: (e: Event) => void }) {
+      el.addEventListener('pointerdown', (e) => _stopPropagation(e))
+      el.addEventListener('pointerup', (e) => {
+        _stopPropagation(e)
+        binding.value?.(e)
+      })
+    },
+    unmounted() {
+      // directive cleanup handled by Vue when element is removed
+    },
+  }
+}

+ 60 - 0
src/utils/index.ts

@@ -28,6 +28,66 @@ export function _formatNumber(num?: number) {
   return String(num)
 }
 
+function _dateFormat(time: number, type = 'Y-M-D') {
+  const date = new Date(Number(time))
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const mStr = month < 10 ? `0${month}` : String(month)
+  const dayStr = day < 10 ? `0${day}` : String(day)
+  if (type === 'M_D') return `${mStr}-${dayStr}`
+  return `${year}-${mStr}-${dayStr}`
+}
+
+/** 2025年11月1日 */
+export function _formatChineseDate(time?: number | string): string {
+  if (time === undefined || time === null || time === '') return ''
+  let ts = Number(time)
+  if (String(time).length === 10) ts *= 1000
+  const d = new Date(ts)
+  if (Number.isNaN(d.getTime())) return ''
+  return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`
+}
+
+export function _time(time: number | string) {
+  let ts = Number(time)
+  if (String(time).length === 10) ts *= 1000
+  const date = new Date(ts)
+  const now = new Date()
+  const d = now.valueOf() - date.valueOf()
+  if (d < 1000 * 60) return '刚刚'
+  if (d < 1000 * 60 * 60) return `${Math.floor(d / (1000 * 60))}分钟前`
+  if (d < 1000 * 60 * 60 * 24) return `${Math.floor(d / (1000 * 60 * 60))}小时前`
+  if (d < 1000 * 60 * 60 * 24 * 2) return '1天前'
+  if (d < 1000 * 60 * 60 * 24 * 3) return '2天前'
+  if (d < 1000 * 60 * 60 * 24 * 4) return '3天前'
+  if (date.getFullYear() === now.getFullYear()) return _dateFormat(ts, 'M_D')
+  return _dateFormat(ts, 'D')
+}
+
+export function _sleep(duration: number) {
+  return new Promise<void>((resolve) => setTimeout(resolve, duration))
+}
+
+export function random(min: number, max: number) {
+  const minCeiled = Math.ceil(min)
+  const maxFloored = Math.floor(max)
+  return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled)
+}
+
+export function sampleSize<T>(arr: T[], num: number) {
+  const list: T[] = []
+  const indexs: number[] = []
+  while (list.length !== num && arr.length > 0) {
+    const j = random(0, arr.length - 1)
+    if (!indexs.includes(j)) {
+      indexs.push(j)
+      list.push(arr[j])
+    }
+  }
+  return list
+}
+
 export function _checkImgUrl(url?: string) {
   if (!url) return ''
   if (

+ 55 - 25
src/views/home/components/IndicatorHome.vue

@@ -8,7 +8,7 @@
         style="transform: rotateY(180deg)"
         @click="$emit('showSlidebar')"
       />
-      <div class="tab-ctn">
+      <div ref="tabCtn" class="tab-ctn">
         <div ref="tabs" class="tabs">
           <div
             v-for="(label, i) in tabLabels"
@@ -29,7 +29,7 @@
 
 <script setup lang="ts">
 import { Icon } from '@iconify/vue'
-import { computed, onMounted, onUnmounted, ref } from 'vue'
+import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
 import bus from '@/utils/bus'
 import { _css } from '@/utils/dom'
 import { _no } from '@/utils/index'
@@ -42,7 +42,8 @@ const props = defineProps({
 
 const emit = defineEmits<{ 'update:index': [number]; showSlidebar: [] }>()
 
-const tabLabels = ['热点', '长视频', '关注', '经验', '推荐']
+const tabLabels = ['我的', '推荐']
+const tabCtn = ref<HTMLElement | null>(null)
 const tabs = ref<HTMLElement | null>(null)
 const indicator = ref<HTMLElement | null>(null)
 const lefts = ref<number[]>([])
@@ -80,56 +81,66 @@ const noticeStyle = computed(() => {
   return { opacity: 0 }
 })
 
+function updateIndicatorLeft(index: number, animate = true) {
+  if (!indicator.value || !lefts.value.length) return
+  const safeIndex = Math.max(0, Math.min(index, lefts.value.length - 1))
+  _css(indicator.value, 'transition-duration', animate ? '300ms' : '0ms')
+  _css(indicator.value, 'left', lefts.value[safeIndex] + 'px')
+}
+
 function change(index: number) {
   emit('update:index', index)
-  if (!indicator.value) return
-  _css(indicator.value, 'transition-duration', '300ms')
-  _css(indicator.value, 'left', lefts.value[index] + 'px')
+  updateIndicatorLeft(index)
 }
 
 function initTabs() {
-  if (!tabs.value || !indicator.value) return
+  if (!tabs.value || !indicator.value || !tabCtn.value) return
   const indicatorWidth = _css(indicator.value, 'width') as number
+  const tabCtnLeft = tabCtn.value.getBoundingClientRect().left
   const positions: number[] = []
   for (let i = 0; i < tabs.value.children.length; i++) {
     const item = tabs.value.children[i] as HTMLElement
-    const tabWidth = _css(item, 'width') as number
-    positions.push(
-      item.getBoundingClientRect().x -
-        (tabs.value.children[0] as HTMLElement).getBoundingClientRect().x +
-        (tabWidth * 0.5 - indicatorWidth / 2),
-    )
+    const rect = item.getBoundingClientRect()
+    positions.push(rect.left - tabCtnLeft + (rect.width * 0.5 - indicatorWidth / 2))
   }
   lefts.value = positions
-  indicatorSpace.value = positions[1] - positions[0]
-  _css(indicator.value, 'transition-duration', '300ms')
-  _css(indicator.value, 'left', positions[props.index] + 'px')
+  indicatorSpace.value = positions.length > 1 ? positions[1] - positions[0] : 0
+  updateIndicatorLeft(props.index, false)
 }
 
 function move(val?: unknown) {
   const e = val as number
-  if (!indicator.value) return
+  if (!indicator.value || !indicatorSpace.value) return
+  const safeIndex = Math.max(0, Math.min(props.index, lefts.value.length - 1))
   _css(indicator.value, 'transition-duration', '0ms')
   _css(
     indicator.value,
     'left',
-    lefts.value[props.index] - e / (window.innerWidth / indicatorSpace.value) + 'px',
+    lefts.value[safeIndex] - e / (window.innerWidth / indicatorSpace.value) + 'px',
   )
 }
 
 function end(val?: unknown) {
   const index = val as number
   moveY.value = 0
-  if (!indicator.value) return
-  _css(indicator.value, 'transition-duration', '300ms')
-  _css(indicator.value, 'left', lefts.value[index] + 'px')
+  updateIndicatorLeft(index)
   setTimeout(() => {
     if (indicator.value) _css(indicator.value, 'transition-duration', '0ms')
   }, 300)
 }
 
+watch(
+  () => props.index,
+  (index) => {
+    nextTick(() => updateIndicatorLeft(index))
+  },
+)
+
 onMounted(() => {
-  initTabs()
+  nextTick(() => {
+    initTabs()
+    requestAnimationFrame(initTabs)
+  })
   bus.on(props.name + '-moveX', move)
   bus.on(props.name + '-moveY', (e) => {
     moveY.value = e as number
@@ -175,22 +186,26 @@ onUnmounted(() => {
     box-sizing: border-box;
     padding: 0 15rem;
     display: flex;
-    justify-content: space-between;
+    justify-content: center;
     align-items: center;
 
     .tab-ctn {
-      width: 80%;
       position: relative;
+      display: flex;
+      justify-content: center;
 
       .tabs {
         display: flex;
-        justify-content: space-between;
+        justify-content: center;
+        align-items: center;
+        gap: 40rem;
 
         .tab {
           transition: color 0.3s;
           color: rgba(255, 255, 255, 0.7);
           font-size: 17rem;
           cursor: pointer;
+          white-space: nowrap;
 
           &.active {
             color: white;
@@ -209,9 +224,24 @@ onUnmounted(() => {
     }
 
     .search {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      margin: auto 0;
+      height: 24rem;
+      display: flex;
+      align-items: center;
       color: white;
       font-size: 24rem;
       cursor: pointer;
+
+      &:first-of-type {
+        left: 15rem;
+      }
+
+      &:last-of-type {
+        right: 15rem;
+      }
     }
   }
 }

+ 14 - 11
src/views/home/components/VideoFeed.vue

@@ -2,7 +2,7 @@
   <SlideVertical
     v-model:index="currentIndex"
     class="video-feed"
-    name="home-feed"
+    :name="slideName"
   >
     <SlideItem
       v-for="(item, index) in list"
@@ -12,7 +12,7 @@
       <BaseVideo
         :item="item"
         :index="index"
-        :position="{ uniqueId: UNIQUE_ID, index }"
+        :position="{ uniqueId: feedId, index }"
         :is-play="active && currentIndex === index"
         @update:item="(val) => onItemUpdate(index, val)"
       />
@@ -21,20 +21,23 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, onUnmounted, ref, watch } from 'vue'
+import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
 import SlideVertical from '@/components/slide/SlideVertical.vue'
 import SlideItem from '@/components/slide/SlideItem.vue'
 import BaseVideo from '@/components/video/BaseVideo.vue'
-import { recommendVideos, type RecommendVideo } from '@/mock/homeData'
+import type { RecommendVideo } from '@/mock/homeData'
 import bus, { EVENT_KEY } from '@/utils/bus'
 
-const UNIQUE_ID = 'home'
-
 const props = defineProps({
   active: { type: Boolean, default: false },
+  /** 区分多个视频流,避免 bus 事件冲突 */
+  feedId: { type: String, required: true },
+  videos: { type: Array as () => RecommendVideo[], required: true },
 })
 
-const list = ref<RecommendVideo[]>([...recommendVideos])
+const slideName = computed(() => `home-feed-${props.feedId}`)
+
+const list = ref<RecommendVideo[]>([...props.videos])
 const currentIndex = ref(0)
 
 function onItemUpdate(index: number, val: unknown) {
@@ -46,14 +49,14 @@ function onUpdateItem(val?: unknown) {
     position?: { uniqueId: string; index: number }
     item?: RecommendVideo
   }
-  if (payload?.position?.uniqueId === UNIQUE_ID && payload.item) {
+  if (payload?.position?.uniqueId === props.feedId && payload.item) {
     list.value[payload.position.index] = payload.item
   }
 }
 
 function broadcastPlay(index: number) {
   bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
-    uniqueId: UNIQUE_ID,
+    uniqueId: props.feedId,
     index,
     type: EVENT_KEY.ITEM_PLAY,
   })
@@ -61,7 +64,7 @@ function broadcastPlay(index: number) {
 
 function broadcastStop(index: number) {
   bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
-    uniqueId: UNIQUE_ID,
+    uniqueId: props.feedId,
     index,
     type: EVENT_KEY.ITEM_STOP,
   })
@@ -70,7 +73,7 @@ function broadcastStop(index: number) {
 function togglePlay() {
   if (!props.active) return
   bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
-    uniqueId: UNIQUE_ID,
+    uniqueId: props.feedId,
     index: currentIndex.value,
     type: EVENT_KEY.ITEM_TOGGLE,
   })

+ 74 - 50
src/views/home/index.vue

@@ -92,7 +92,7 @@
           </div>
         </div>
       </SlideItem>
-      <SlideItem>
+      <SlideItem class="slide-main">
         <IndicatorHome
           v-if="!state.fullScreen"
           :loading="loading"
@@ -107,17 +107,9 @@
           :change-active-index-use-anim="false"
         >
           <Slide0 :active="state.navIndex === 0 && state.baseIndex === 1" />
-          <SlideItem>
-            <LongVideo :active="state.navIndex === 1 && state.baseIndex === 1" />
-          </SlideItem>
-          <Slide2 :active="state.navIndex === 2 && state.baseIndex === 1" />
-          <SlideItem>
-            <Community :active="state.navIndex === 3 && state.baseIndex === 1" />
-          </SlideItem>
-          <Slide4 :active="state.navIndex === 4 && state.baseIndex === 1" />
+          <Slide4 :active="state.navIndex === 1 && state.baseIndex === 1" />
         </SlideHorizontal>
 
-        <BaseFooter />
         <BaseMask
           v-if="state.baseIndex === 0"
           mode="white"
@@ -148,7 +140,8 @@
 
 <script setup lang="ts">
 import { Icon } from '@iconify/vue'
-import { onMounted, onUnmounted, reactive, ref } from 'vue'
+import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
+import { useRoute } from 'vue-router'
 import SlideHorizontal from '@/components/slide/SlideHorizontal.vue'
 import SlideItem from '@/components/slide/SlideItem.vue'
 import Comment from '@/components/Comment.vue'
@@ -156,16 +149,13 @@ import IndicatorHome from './components/IndicatorHome.vue'
 import bus, { EVENT_KEY } from '@/utils/bus'
 import { useNav } from '@/utils/hooks/useNav'
 import UserPanel from '@/components/UserPanel.vue'
-import Community from './slide/Community.vue'
 import Slide0 from './slide/Slide0.vue'
-import Slide2 from './slide/Slide2.vue'
 import Slide4 from './slide/Slide4.vue'
 import { _no, _notice } from '@/utils'
-import LongVideo from './slide/LongVideo.vue'
 import BaseMask from '@/components/BaseMask.vue'
-import BaseFooter from '@/components/BaseFooter.vue'
 import { miniPrograms, recentUsers, recommendVideos } from '@/mock/homeData'
 
+const route = useRoute()
 const nav = useNav()
 const loading = ref(false)
 
@@ -174,7 +164,7 @@ type VideoItem = (typeof recommendVideos)[number]
 const state = reactive({
   active: true,
   baseIndex: 1,
-  navIndex: 4,
+  navIndex: 1,
   canMove: true,
   showFollowSetting: false,
   showFollowSetting2: false,
@@ -206,43 +196,73 @@ function setCurrentItem(val?: unknown) {
   }
 }
 
+/** 与 douyin 一致:仅主视频层显示底栏,侧边栏/UserPanel 隐藏 */
+function syncFooterBySlide() {
+  if (route.path !== '/') return
+  bus.emit('setFooterVisible', state.baseIndex === 1)
+}
+
+watch(() => state.baseIndex, syncFooterBySlide)
+
+function onEnterFullscreen() {
+  if (!state.active) return
+  state.fullScreen = true
+}
+
+function onExitFullscreen() {
+  if (!state.active) return
+  state.fullScreen = false
+}
+
+function onOpenComments() {
+  if (!state.active) return
+  bus.emit(EVENT_KEY.ENTER_FULLSCREEN)
+  state.commentVisible = true
+}
+
+function onCloseComments() {
+  if (!state.active) return
+  bus.emit(EVENT_KEY.EXIT_FULLSCREEN)
+  state.commentVisible = false
+}
+
+function onShowShare() {
+  if (!state.active) return
+  _notice('分享面板待接入')
+}
+
+function onNav(val?: unknown) {
+  if (!state.active) return
+  const { path, query } = val as { path: string; query?: Record<string, string> }
+  nav(path, query || {})
+}
+
+function onGoUserinfo() {
+  if (!state.active) return
+  state.baseIndex = 2
+}
+
 onMounted(() => {
-  bus.on(EVENT_KEY.ENTER_FULLSCREEN, () => {
-    if (!state.active) return
-    state.fullScreen = true
-  })
-  bus.on(EVENT_KEY.EXIT_FULLSCREEN, () => {
-    if (!state.active) return
-    state.fullScreen = false
-  })
-  bus.on(EVENT_KEY.OPEN_COMMENTS, () => {
-    if (!state.active) return
-    bus.emit(EVENT_KEY.ENTER_FULLSCREEN)
-    state.commentVisible = true
-  })
-  bus.on(EVENT_KEY.CLOSE_COMMENTS, () => {
-    if (!state.active) return
-    bus.emit(EVENT_KEY.EXIT_FULLSCREEN)
-    state.commentVisible = false
-  })
-  bus.on(EVENT_KEY.SHOW_SHARE, () => {
-    if (!state.active) return
-    _notice('分享面板待接入')
-  })
-  bus.on(EVENT_KEY.NAV, (val?: unknown) => {
-    if (!state.active) return
-    const { path, query } = val as { path: string; query?: Record<string, string> }
-    nav(path, query || {})
-  })
-  bus.on(EVENT_KEY.GO_USERINFO, () => {
-    if (!state.active) return
-    state.baseIndex = 2
-  })
+  syncFooterBySlide()
+  bus.on(EVENT_KEY.ENTER_FULLSCREEN, onEnterFullscreen)
+  bus.on(EVENT_KEY.EXIT_FULLSCREEN, onExitFullscreen)
+  bus.on(EVENT_KEY.OPEN_COMMENTS, onOpenComments)
+  bus.on(EVENT_KEY.CLOSE_COMMENTS, onCloseComments)
+  bus.on(EVENT_KEY.SHOW_SHARE, onShowShare)
+  bus.on(EVENT_KEY.NAV, onNav)
+  bus.on(EVENT_KEY.GO_USERINFO, onGoUserinfo)
   bus.on(EVENT_KEY.CURRENT_ITEM, setCurrentItem)
 })
 
 onUnmounted(() => {
-  bus.offAll()
+  bus.off(EVENT_KEY.ENTER_FULLSCREEN, onEnterFullscreen)
+  bus.off(EVENT_KEY.EXIT_FULLSCREEN, onExitFullscreen)
+  bus.off(EVENT_KEY.OPEN_COMMENTS, onOpenComments)
+  bus.off(EVENT_KEY.CLOSE_COMMENTS, onCloseComments)
+  bus.off(EVENT_KEY.SHOW_SHARE, onShowShare)
+  bus.off(EVENT_KEY.NAV, onNav)
+  bus.off(EVENT_KEY.GO_USERINFO, onGoUserinfo)
+  bus.off(EVENT_KEY.CURRENT_ITEM, setCurrentItem)
 })
 
 function closeComments() {
@@ -358,10 +378,14 @@ function closeComments() {
   }
 }
 
+.slide-main {
+  height: calc(var(--vh, 1vh) * 100);
+  overflow: hidden;
+}
+
 .first-horizontal-item {
   width: 100%;
-  height: calc(var(--vh, 1vh) * 100 - var(--footer-height)) !important;
+  height: 100% !important;
   overflow: hidden;
-  border-radius: 10rem;
 }
 </style>

+ 3 - 49
src/views/home/slide/Slide0.vue

@@ -1,59 +1,13 @@
 <script setup lang="ts">
 import SlideItem from '@/components/slide/SlideItem.vue'
-import { formatCount, hotFeed } from '@/mock/homeData'
-import { _checkImgUrl } from '@/utils/index'
+import VideoFeed from '../components/VideoFeed.vue'
+import { myVideos } from '@/mock/homeData'
 
 defineProps({ active: { type: Boolean, default: false } })
 </script>
 
 <template>
   <SlideItem>
-    <div class="hot-feed">
-      <div v-for="item in hotFeed" :key="item.aweme_id" class="item">
-        <img :src="_checkImgUrl(item.video.cover.url_list[0])" class="cover" alt="" />
-        <div class="info">
-          <p class="desc">{{ item.desc }}</p>
-          <p class="meta">@{{ item.author.nickname }} · {{ formatCount(item.statistics.digg_count) }}赞</p>
-        </div>
-      </div>
-    </div>
+    <VideoFeed feed-id="my" :videos="myVideos" :active="active" />
   </SlideItem>
 </template>
-
-<style scoped lang="less">
-.hot-feed {
-  height: 100%;
-  overflow-y: auto;
-  background: #000;
-  padding: 10rem;
-
-  .item {
-    margin-bottom: 12rem;
-    border-radius: 8rem;
-    overflow: hidden;
-    background: rgb(29, 29, 29);
-
-    .cover {
-      width: 100%;
-      height: 160rem;
-      object-fit: cover;
-    }
-
-    .info {
-      padding: 10rem 12rem;
-      color: white;
-
-      .desc {
-        margin: 0 0 6rem;
-        font-size: 14rem;
-      }
-
-      .meta {
-        margin: 0;
-        font-size: 12rem;
-        opacity: 0.6;
-      }
-    }
-  }
-}
-</style>

+ 2 - 1
src/views/home/slide/Slide4.vue

@@ -1,12 +1,13 @@
 <script setup lang="ts">
 import SlideItem from '@/components/slide/SlideItem.vue'
 import VideoFeed from '../components/VideoFeed.vue'
+import { recommendVideos } from '@/mock/homeData'
 
 defineProps({ active: { type: Boolean, default: false } })
 </script>
 
 <template>
   <SlideItem>
-    <VideoFeed :active="active" />
+    <VideoFeed feed-id="recommend" :videos="recommendVideos" :active="active" />
   </SlideItem>
 </template>

+ 0 - 5
src/views/my-guard/index.vue

@@ -4,14 +4,9 @@
       <h1>我的守护</h1>
       <p class="desc">守护记录开发中</p>
     </div>
-    <BaseFooter />
   </div>
 </template>
 
-<script setup lang="ts">
-import BaseFooter from '@/components/BaseFooter.vue'
-</script>
-
 <style scoped lang="less">
 .tab-page {
   width: 100%;