NavBar.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <template>
  2. <div class="header-wrapper">
  3. <div class="nav-bar-container">
  4. <el-row type="flex" align="middle" class="nav-content">
  5. <el-col :xs="3" :sm="2" :md="3" class="logo-wrapper">
  6. <a href="/" class="logo-link">
  7. <img src="@/assets/img/logo.png" class="logo-img" alt="logo">
  8. <span class="logo-text hidden-xs-only">TNBAPP</span>
  9. </a>
  10. </el-col>
  11. <el-col :sm="6" :md="7" class="hidden-xs-only">
  12. <div class="nav-links">
  13. <router-link to="/region" class="nav-item-link">分区</router-link>
  14. <router-link to="/shortvideo" class="nav-item-link">短视频</router-link>
  15. <router-link to="/playlist" class="nav-item-link">播放列表</router-link>
  16. </div>
  17. </el-col>
  18. <el-col :xs="15" :sm="8" :md="6">
  19. <div class="search-wrapper">
  20. <el-autocomplete
  21. v-model="keyword"
  22. :fetch-suggestions="querySearchAsync"
  23. :placeholder="searchHint"
  24. prefix-icon="el-icon-search"
  25. class="custom-search"
  26. size="small"
  27. @keyup.enter.native="onSearch"
  28. />
  29. </div>
  30. </el-col>
  31. <el-col :xs="6" :sm="8" :md="8" class="user-actions">
  32. <div class="action-icons hidden-xs-only">
  33. <div class="icon-item-wrapper" @click="goToTimeline">
  34. <el-badge :value="statusCount" :max="99" :hidden="statusCount <= 0" class="badge-item">
  35. <i class="el-icon-video-camera-solid custom-icon"></i>
  36. </el-badge>
  37. <span class="icon-label">动态</span>
  38. </div>
  39. <div class="icon-item-wrapper" @click="goToMessage">
  40. <el-badge :value="msgCount" :max="99" :hidden="msgCount <= 0" class="badge-item">
  41. <i class="el-icon-message-solid custom-icon"></i>
  42. </el-badge>
  43. <span class="icon-label">消息</span>
  44. </div>
  45. </div>
  46. <div class="user-profile-wrapper">
  47. <el-dropdown v-if="user" trigger="hover" placement="bottom" class="user-dropdown-trigger">
  48. <div class="avatar-wrapper">
  49. <img :src="user.avatarUrl" class="user-avatar" alt="avatar">
  50. </div>
  51. <el-dropdown-menu slot="dropdown" class="user-dropdown-menu">
  52. <div class="user-name-header">{{ user.screenName || '用户' }}</div>
  53. <el-dropdown-item icon="el-icon-collection" @click.native="goToPlaylist">收藏列表</el-dropdown-item>
  54. <el-dropdown-item icon="el-icon-collection" @click.native="goToHistory">历史记录</el-dropdown-item>
  55. <el-dropdown-item icon="el-icon-collection" @click.native="goToBackground">进入后台</el-dropdown-item>
  56. <el-dropdown-item icon="el-icon-switch-button" divided @click.native="goToLogout">退出登录</el-dropdown-item>
  57. </el-dropdown-menu>
  58. </el-dropdown>
  59. <el-button v-else type="text" class="login-btn-text" @click="login">登录</el-button>
  60. <el-button
  61. type="primary"
  62. class="upload-btn-blue hidden-xs-only"
  63. @click="goToPublish"
  64. >
  65. <i class="el-icon-upload"></i>
  66. <span>投稿</span>
  67. </el-button>
  68. </div>
  69. </el-col>
  70. </el-row>
  71. </div>
  72. <nav class="bottom-nav hidden-sm-and-up">
  73. <router-link to="/" class="bottom-nav-item" exact-active-class="is-active">
  74. <i class="el-icon-house"></i>
  75. <span>首页</span>
  76. </router-link>
  77. <router-link to="/region" class="bottom-nav-item" active-class="is-active">
  78. <i class="el-icon-menu"></i>
  79. <span>分区</span>
  80. </router-link>
  81. <router-link to="/shortvideo" class="bottom-nav-item" active-class="is-active">
  82. <i class="el-icon-video-camera"></i>
  83. <span>短视频</span>
  84. </router-link>
  85. <router-link :to="user ? `/timeline` : '/login'" class="bottom-nav-item" active-class="is-active">
  86. <i class="el-icon-wind-power"></i>
  87. <span>动态</span>
  88. </router-link>
  89. <router-link :to="user ? `/user/${user.userIdStr}` : '/login'" class="bottom-nav-item" active-class="is-active">
  90. <i class="el-icon-user"></i>
  91. <span>我的</span>
  92. </router-link>
  93. </nav>
  94. </div>
  95. </template>
  96. <script>
  97. import { userMixin } from 'assets/js/mixin'
  98. import { keywordSuggest } from '@/api/search'
  99. import { getAuthedUser } from '@/utils/auth'
  100. import { getUnreadCount } from '@/api/user'
  101. export default {
  102. name: 'NavBar',
  103. mixins: [userMixin],
  104. data() {
  105. return {
  106. user: null,
  107. restaurants: [],
  108. searchHint: '想要搜点神马呢',
  109. keyword: '',
  110. statusCount: 0,
  111. msgCount: 0
  112. }
  113. },
  114. created() {
  115. const userInfo = getAuthedUser()
  116. if (userInfo !== null) {
  117. this.user = userInfo
  118. getUnreadCount().then(resp => {
  119. if (resp.code === 0) {
  120. this.msgCount = resp.data.total
  121. }
  122. })
  123. }
  124. },
  125. methods: {
  126. querySearchAsync(queryString, cb) {
  127. if (!queryString) return
  128. setTimeout(() => {
  129. keywordSuggest(queryString).then(res => {
  130. if (res.code === 0) {
  131. const results = res.data.map(item => ({ value: item.keyword }))
  132. cb(results)
  133. }
  134. })
  135. }, 300)
  136. },
  137. onSearch() {
  138. if (this.keyword.trim()) {
  139. this.toSearchPage()
  140. } else {
  141. this.$message.warning('不能为空!')
  142. }
  143. },
  144. toSearchPage() {
  145. const query = { searchType: 1, keyword: this.keyword, pn: 1 }
  146. if (this.$route.path === '/search') {
  147. this.$router.push({ path: '/search', query }).catch(() => {})
  148. } else {
  149. const routeUrl = this.$router.resolve({ path: '/search', query })
  150. window.open(routeUrl.href, '_blank')
  151. }
  152. },
  153. login() {
  154. this.$router.push('/login').catch(() => {})
  155. },
  156. goToUserHome() { this.$router.push('/user/' + this.user.userIdStr) },
  157. goToPlaylist() { this.$router.push('/bg/my/album') },
  158. goToHistory() { this.$router.push('/bg/my/history') },
  159. goToBackground() { this.$router.push('/bg') },
  160. goToTimeline() { this.$router.push('/timeline') },
  161. goToMessage() { this.$router.push('/bg/account/message') },
  162. goToPublish() { this.$router.push('/bg/post/video') }
  163. }
  164. }
  165. </script>
  166. <style lang="scss" scoped>
  167. /* 变量定义 */
  168. $primary-blue: #00a1d6;
  169. $hover-blue: #00b5e5;
  170. $text-main: #18191c;
  171. $text-second: #61666d;
  172. $bg-grey: #f1f2f3;
  173. /* 1. 顶部主容器 */
  174. .nav-bar-container {
  175. position: fixed; /* 强制固定 */
  176. top: 0;
  177. left: 0;
  178. right: 0;
  179. z-index: 2000; /* 提高层级,确保在弹窗之下但在内容之上 */
  180. height: 64px;
  181. background: #fff;
  182. /* 如果想要毛玻璃效果,取消下面两行的注释 */
  183. /* background: rgba(255, 255, 255, 0.9); */
  184. /* backdrop-filter: blur(10px); */
  185. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  186. display: flex;
  187. align-items: center;
  188. padding: 0 20px;
  189. }
  190. .nav-content {
  191. width: 100%;
  192. max-width: 1400px;
  193. margin: 0 auto;
  194. }
  195. /* 2. Logo 样式 */
  196. .logo-link {
  197. display: flex;
  198. align-items: center;
  199. text-decoration: none;
  200. .logo-img { width: 30px; height: 30px; }
  201. .logo-text {
  202. color: $primary-blue;
  203. font-weight: bold;
  204. font-size: 18px;
  205. margin-left: 8px;
  206. }
  207. }
  208. /* 3. PC端菜单链接 */
  209. .nav-links {
  210. display: flex;
  211. gap: 20px;
  212. .nav-item-link {
  213. text-decoration: none;
  214. color: $text-main;
  215. font-size: 15px;
  216. transition: color 0.2s;
  217. &:hover { color: $primary-blue; }
  218. }
  219. }
  220. /* 4. 搜索框美化 */
  221. .custom-search {
  222. width: 100%;
  223. ::v-deep .el-input__inner {
  224. border-radius: 20px;
  225. background-color: $bg-grey;
  226. border: 1px solid transparent;
  227. padding-left: 35px;
  228. transition: all 0.3s ease;
  229. &:focus {
  230. background-color: #fff;
  231. border-color: $primary-blue;
  232. box-shadow: 0 0 8px rgba(0, 161, 214, 0.15);
  233. }
  234. }
  235. }
  236. /* 5. 右侧操作区总布局 */
  237. .user-actions {
  238. display: flex;
  239. align-items: center;
  240. justify-content: flex-end;
  241. height: 100%;
  242. }
  243. /* 图标组 (动态/消息) */
  244. .action-icons {
  245. display: flex;
  246. align-items: center;
  247. margin-right: 20px;
  248. .icon-item-wrapper {
  249. display: flex;
  250. flex-direction: column;
  251. align-items: center;
  252. margin-left: 18px;
  253. cursor: pointer;
  254. transition: transform 0.2s ease;
  255. .custom-icon {
  256. font-size: 20px;
  257. color: $text-second;
  258. transition: color 0.2s;
  259. }
  260. .icon-label {
  261. font-size: 11px;
  262. color: $text-second;
  263. margin-top: 2px;
  264. }
  265. &:hover {
  266. transform: translateY(-2px);
  267. .custom-icon, .icon-label { color: $primary-blue; }
  268. }
  269. }
  270. }
  271. /* 头像与按钮包装 */
  272. .user-profile-wrapper {
  273. display: flex;
  274. align-items: center;
  275. }
  276. .avatar-wrapper {
  277. display: flex;
  278. align-items: center;
  279. padding: 0 8px;
  280. cursor: pointer;
  281. }
  282. .user-avatar {
  283. width: 36px;
  284. height: 36px;
  285. border-radius: 50%;
  286. object-fit: cover;
  287. border: 1px solid transparent;
  288. transition: all 0.3s;
  289. &:hover {
  290. transform: scale(1.1);
  291. border-color: $primary-blue;
  292. }
  293. }
  294. .login-btn-text {
  295. color: $primary-blue;
  296. font-weight: 500;
  297. margin-right: 15px;
  298. }
  299. /* 蓝色投稿按钮 */
  300. .upload-btn-blue {
  301. background-color: $primary-blue !important;
  302. border: none !important;
  303. border-radius: 8px !important;
  304. height: 34px;
  305. padding: 0 16px !important;
  306. display: flex;
  307. align-items: center;
  308. margin-left: 10px !important;
  309. transition: all 0.3s !important;
  310. i { font-size: 16px; margin-right: 4px; }
  311. span { font-size: 14px; }
  312. &:hover {
  313. background-color: $hover-blue !important;
  314. box-shadow: 0 4px 12px rgba(0, 161, 214, 0.2);
  315. }
  316. }
  317. /* 6. 移动端底部导航 */
  318. .bottom-nav {
  319. position: fixed;
  320. bottom: 0;
  321. left: 0;
  322. right: 0;
  323. height: 56px;
  324. background: rgba(255, 255, 255, 0.96);
  325. backdrop-filter: blur(15px);
  326. display: flex;
  327. border-top: 1px solid #f1f2f3;
  328. z-index: 2000;
  329. padding-bottom: env(safe-area-inset-bottom);
  330. .bottom-nav-item {
  331. flex: 1;
  332. display: flex;
  333. flex-direction: column;
  334. align-items: center;
  335. justify-content: center;
  336. text-decoration: none;
  337. color: $text-second;
  338. i { font-size: 20px; margin-bottom: 2px; }
  339. span { font-size: 10px; }
  340. &.is-active { color: $primary-blue; i { font-weight: bold; } }
  341. }
  342. }
  343. /* 7. 移动端适配补丁 */
  344. @media screen and (max-width: 768px) {
  345. .nav-bar-container { padding: 0 10px; height: 56px; }
  346. .user-profile-wrapper { margin-right: 5px; }
  347. .logo-wrapper { flex: 0 0 auto; width: 40px; }
  348. .custom-search {
  349. ::v-deep .el-input__inner { height: 32px; line-height: 32px; }
  350. }
  351. }
  352. /* 下拉菜单修饰 */
  353. .user-dropdown-menu {
  354. .user-name-header {
  355. padding: 12px 20px;
  356. font-weight: bold;
  357. color: $text-main;
  358. border-bottom: 1px solid #f1f2f3;
  359. margin-bottom: 5px;
  360. text-align: center;
  361. }
  362. }
  363. </style>