소스 검색

使用 elementui 替换 vuetify

reghao 2 년 전
부모
커밋
5e11e23aa2
100개의 변경된 파일4325개의 추가작업 그리고 10224개의 파일을 삭제
  1. 1 1
      Dockerfile
  2. 21 0
      LICENSE
  3. 67 10
      README.md
  4. BIN
      img/image-20200817121128341.png
  5. 1190 262
      package-lock.json
  6. 19 43
      package.json
  7. BIN
      public/favicon.ico
  8. BIN
      public/images/head.png
  9. 4 9
      public/index.html
  10. BIN
      public/logo.png
  11. 62 6
      src/App.vue
  12. 63 0
      src/api/account.js
  13. 0 14
      src/api/account/user.js
  14. 0 22
      src/api/comment/comment.js
  15. 0 33
      src/api/config/api-config.js
  16. 2 2
      src/api/file.js
  17. 0 42
      src/api/impl/my-content.js
  18. 0 118
      src/api/index.js
  19. 0 22
      src/api/mblog/status.js
  20. 0 22
      src/api/media/audio.js
  21. 0 22
      src/api/media/collection.js
  22. 0 16
      src/api/post/article.js
  23. 0 16
      src/api/search/search.js
  24. 0 44
      src/api/user/account.js
  25. 0 22
      src/api/user/auth.js
  26. 0 50
      src/api/user/user.js
  27. 24 23
      src/api/video.js
  28. 20 0
      src/assets/css/base.css
  29. 0 0
      src/assets/debug-data/data/content.js
  30. 0 0
      src/assets/debug-data/data/friendship.js
  31. 0 0
      src/assets/debug-data/data/notify.js
  32. 0 5409
      src/assets/debug-data/data/status.js
  33. 0 95
      src/assets/debug-data/data/userInfo.js
  34. 0 32
      src/assets/debug-data/getData.js
  35. BIN
      src/assets/img/icon/avatar.png
  36. BIN
      src/assets/img/icon/backtop.png
  37. BIN
      src/assets/img/icon/erweima.png
  38. BIN
      src/assets/img/icon/exit.png
  39. BIN
      src/assets/img/icon/github.png
  40. BIN
      src/assets/img/icon/history.png
  41. BIN
      src/assets/img/icon/like.png
  42. BIN
      src/assets/img/icon/logo.png
  43. BIN
      src/assets/img/icon/mylike.png
  44. BIN
      src/assets/img/icon/not-collection.png
  45. BIN
      src/assets/img/icon/not-history.png
  46. BIN
      src/assets/img/icon/not-result.png
  47. BIN
      src/assets/img/icon/play-icon.png
  48. BIN
      src/assets/img/icon/profile.png
  49. BIN
      src/assets/img/icon/recommmand-icon.png
  50. BIN
      src/assets/img/icon/search-result.png
  51. BIN
      src/assets/img/icon/weixin.png
  52. BIN
      src/assets/img/moviecover/py.jpg
  53. 4 0
      src/assets/js/const.js
  54. 272 0
      src/assets/js/mixin.js
  55. 96 0
      src/assets/js/utils.js
  56. BIN
      src/assets/logo.png
  57. 0 1
      src/assets/logo.svg
  58. 9 114
      src/components/VideoPlayer.vue
  59. 145 0
      src/components/card/ArticleCard.vue
  60. 160 0
      src/components/card/AudioCard.vue
  61. 164 0
      src/components/card/ImageCard.vue
  62. 156 0
      src/components/card/StatusCard.vue
  63. 61 0
      src/components/card/TextCard.vue
  64. 164 0
      src/components/card/VideoCard.vue
  65. 0 51
      src/components/card/answer-card.vue
  66. 0 42
      src/components/card/audio-card.vue
  67. 0 84
      src/components/card/audio-card1.vue
  68. 0 54
      src/components/card/collection-card.vue
  69. 0 331
      src/components/card/comment-card.vue
  70. 0 53
      src/components/card/history-card.vue
  71. 0 59
      src/components/card/item-card.vue
  72. 0 59
      src/components/card/live-card.vue
  73. 0 37
      src/components/card/question-card.vue
  74. 0 53
      src/components/card/reply-card.vue
  75. 0 145
      src/components/card/status-card.vue
  76. 0 67
      src/components/card/video-card.vue
  77. 215 0
      src/components/categorybtn/CategoryBtn.vue
  78. 197 0
      src/components/comment/Comment.vue
  79. 0 449
      src/components/comment/components/CommentForm.vue
  80. 0 303
      src/components/comment/components/CommentItem.vue
  81. 0 12
      src/components/comment/components/CommentList.js
  82. 0 264
      src/components/comment/components/EmojiSelector.vue
  83. 0 11
      src/components/comment/index.js
  84. 0 541
      src/components/comment/index.vue
  85. 0 11
      src/components/dialog/examine-dialog.vue
  86. 221 0
      src/components/hotlist/HotList.vue
  87. 49 0
      src/components/layout/FooterBar.vue
  88. 307 0
      src/components/layout/NavBar.vue
  89. 213 0
      src/components/layout/Profile.vue
  90. 240 0
      src/components/layout/SearchBox.vue
  91. 0 179
      src/components/login-form-mobile.vue
  92. 0 32
      src/components/no-login-show.vue
  93. 0 78
      src/components/player/live-player.vue
  94. 179 0
      src/components/recommend/Recommend.vue
  95. 0 203
      src/components/register-form.vue
  96. 0 213
      src/components/reset-password-form.vue
  97. 0 121
      src/components/setting/user-base-setting.vue
  98. 0 154
      src/components/setting/user-head-setting.vue
  99. 0 10
      src/components/setting/user-login-log.vue
  100. 0 158
      src/components/setting/user-password-setting.vue

+ 1 - 1
Dockerfile

@@ -1,4 +1,4 @@
-FROM docker.reghao.cn/nginx/tnb:1.21
+FROM docker.reghao.cn/nginx/npm:1.21
 
 RUN sed -i 's/8080/8082/' /etc/nginx/conf.d/http.conf
 COPY ./dist/ /opt/webroot/

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 phk422
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 67 - 10
README.md

@@ -1,24 +1,81 @@
-# web
+# 基于Vue,ElementUI的在现视频点播系统
+
+[项目演示](http://182.92.148.170:8080)
+
+**使用到的技术**
+
+```
+UI框架:EelementUI
+网络请求:axios
+视频播放组件:vue-player
+```
+
+**基本功能**
+
+```
+1.视频浏览
+2.视频搜索
+3.推荐视频
+4.视频排行榜
+5.视频收藏,为视频添加标签
+6.播放历史记录
+7.用户注册登录
+8.视频评论
+```
+
+**后端接口**
+
+打开vue.config.js配置服务接口地址:
+
+```javascript
+devServer: {
+	port: 8000, // 项目的端口号
+	// 跨域代理配置
+	proxy: {
+		'/api': {
+			target: 'http://localhost:8888', //这里是接口地址,接口地址联系作者获取
+			ws: true,//是否代理websockets
+			changeOrigin: true,   // 设置同源  默认false,是否需要改变原始主机头为目标URL
+			pathRewrite: {
+				'^/api': ''
+			}		
+		}
+	}
+},
+```
+
+
+
+```
+接口地址可添加微信/QQ获取:
+邮箱地址:1769476788@qq.com
+微信账号/QQ:1769476788
+```
+
+**个人微信**
+
+<img src="img/image-20200817121128341.png" alt="image-20200817121128341" width="260px" />
+
+后面会将后端接口代码push上来!
+
+### 安装依赖
 
-## Project setup
 ```
 npm install
 ```
 
-### Compiles and hot-reloads for development
+### 启动服务
+
 ```
 npm run serve
 ```
 
-### Compiles and minifies for production
+启动服务后打开浏览器,访问http://localhost:8000
+
+### 项目打包
+
 ```
 npm run build
 ```
 
-### Lints and fixes files
-```
-npm run lint
-```
 
-### Customize configuration
-See [Configuration Reference](https://cli.vuejs.org/config/).

BIN
img/image-20200817121128341.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1190 - 262
package-lock.json


+ 19 - 43
package.json

@@ -1,6 +1,6 @@
 {
-  "name": "bili",
-  "version": "1.0.0",
+  "name": "videoproject",
+  "version": "0.1.0",
   "private": true,
   "scripts": {
     "serve": "vue-cli-service serve",
@@ -9,11 +9,11 @@
   },
   "dependencies": {
     "@liripeng/vue-audio-player": "^1.5.0",
-    "core-js": "^3.6.5",
-    "crypto-js": "^4.1.1",
+    "axios": "^0.19.2",
+    "core-js": "^3.30.1",
     "dashjs": "^4.2.0",
     "dplayer": "^1.26.0",
-    "element-ui": "2.13.2",
+    "element-ui": "^2.15.8",
     "filepond": "^4.20.1",
     "filepond-plugin-file-validate-size": "^2.2.1",
     "filepond-plugin-file-validate-type": "^1.2.5",
@@ -22,53 +22,29 @@
     "filepond-plugin-image-preview": "^4.6.4",
     "flv.js": "^1.6.2",
     "hls.js": "^1.1.2",
-    "js-cookie": "2.2.0",
     "jsencrypt": "^3.2.1",
+    "mavon-editor": "^2.10.4",
     "shaka-player": "^3.2.1",
     "v-viewer": "^1.6.4",
-    "vue": "^2.6.12",
-    "vue-cookies": "^1.7.4",
-    "vue-filepond": "^6.0.3",
-    "vue-infinite-scroll": "^2.0.2",
+    "vue": "^2.7.14",
+    "vue-cookies": "^1.8.3",
     "vue-quill-editor": "^3.0.6",
     "vue-router": "^3.4.5",
     "vue-simple-uploader": "^0.7.6",
-    "vuetify": "^2.3.12",
-    "vuex": "^3.4.0",
-    "vuex-persistedstate": "^4.1.0"
+    "vuex": "^3.4.0"
   },
   "devDependencies": {
-    "@vue/cli-plugin-babel": "^4.5.6",
-    "@vue/cli-plugin-eslint": "^4.5.6",
-    "@vue/cli-plugin-router": "^4.5.6",
-    "@vue/cli-plugin-vuex": "^4.5.6",
-    "@vue/cli-service": "^4.5.6",
+    "@vue/cli-plugin-babel": "^4.5.19",
+    "@vue/cli-plugin-eslint": "^4.5.19",
+    "@vue/cli-service": "^4.5.19",
+    "@vue/eslint-config-standard": "^5.1.2",
     "babel-eslint": "^10.1.0",
     "eslint": "^6.7.2",
+    "eslint-plugin-import": "^2.27.5",
+    "eslint-plugin-node": "^11.1.0",
+    "eslint-plugin-promise": "^4.2.1",
+    "eslint-plugin-standard": "^4.0.0",
     "eslint-plugin-vue": "^6.2.2",
-    "sass": "^1.26.11",
-    "sass-loader": "^8.0.0",
-    "vue-cli-plugin-vuetify": "^2.0.7",
-    "vue-template-compiler": "^2.6.11",
-    "vuetify-loader": "^1.3.0"
-  },
-  "eslintConfig": {
-    "root": true,
-    "env": {
-      "node": true
-    },
-    "extends": [
-      "plugin:vue/essential",
-      "eslint:recommended"
-    ],
-    "parserOptions": {
-      "parser": "babel-eslint"
-    },
-    "rules": {}
-  },
-  "browserslist": [
-    "> 1%",
-    "last 2 versions",
-    "not dead"
-  ]
+    "vue-template-compiler": "^2.7.14"
+  }
 }

BIN
public/favicon.ico


BIN
public/images/head.png


+ 4 - 9
public/index.html

@@ -1,23 +1,18 @@
 <!DOCTYPE html>
-<html lang="zh-CN">
+<html lang="en">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="/favicon.ico">
+    <link rel="icon" href="<%= BASE_URL %>logo.png">
+<!--    <title><%= htmlWebpackPlugin.options.title %></title>-->
     <title>bili</title>
-<!--    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">-->
-    <link rel="stylesheet" href="https://fonts.loli.net/css?family=Roboto:100,300,400,500,700,900">
-    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
-
   </head>
   <body>
     <noscript>
-      <strong>We're sorry but bili doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
     </noscript>
     <div id="app"></div>
     <!-- built files will be auto injected -->
-    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
-    <script src="https://cdn.jsdelivr.net/npm/flv.js@1.5.0/dist/flv.min.js"></script>
   </body>
 </html>

BIN
public/logo.png


+ 62 - 6
src/App.vue

@@ -1,14 +1,70 @@
 <template>
-  <v-app>
-    <router-view :key="$route.fullPath"/>
-  </v-app>
+  <div id="app">
+    <!--导航栏-->
+    <nav-bar />
+    <!--手机屏幕下显示的搜索框-->
+    <div class="hidden-sm-and-up searchd"><search-box /></div>
+    <!--下面的部分通过路由动态决定渲染与否-->
+    <!--exclude,其值为正则,匹配到的组件的名称会被排除在keep-alive之外-->
+    <keep-alive exclude="Collection,History">
+      <router-view />
+    </keep-alive>
+    <!--页脚-->
+    <footer-bar />
+    <!--回到顶部按钮-->
+    <el-backtop :visibility-height="100" :bottom="60" />
+  </div>
 </template>
 
 <script>
+import NavBar from 'components/layout/NavBar'
+import SearchBox from 'components/layout/SearchBox'
+import FooterBar from 'components/layout/FooterBar'
+
 export default {
   name: 'App',
-  data: () => ({
-    //
-  })
+  components: {
+    FooterBar,
+    SearchBox,
+    NavBar
+  },
+  created() {
+    /**
+     * 防止vuex中的state在界面刷新后丢失
+     */
+    // 在页面加载时读取sessionStorage里的状态信息
+    if (sessionStorage.getItem('store')) {
+      this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem('store'))))
+    }
+
+    // 在页面刷新时将vuex里的信息保存到sessionStorage里
+    window.addEventListener('beforeunload', () => {
+      sessionStorage.setItem('store', JSON.stringify(this.$store.state))
+    })
+  },
+  methods: {
+
+  }
+
 }
 </script>
+
+<style>
+@import "assets/css/base.css";
+
+#app {
+  overflow-y: scroll;
+}
+.searchd {
+  margin-top: 15px;
+  text-align: center;
+}
+
+/*.fade-enter-active,.fade-leave-active {
+  -webkit-transition:opacity 1s;
+  transition:opacity 1s
+}
+.fade-enter,.fade-leave-to {
+  opacity:0
+}*/
+</style>

+ 63 - 0
src/api/account.js

@@ -0,0 +1,63 @@
+import { get, post } from '@/utils/request'
+
+const accountApi = {
+  checkUsernameApi: '/api/user/account/check/username',
+  selectUsernameApi: '/api/user/account/select/username',
+  checkEmailApi: '/api/user/account/check/email',
+  verifyCodeApi: '/api/account/code/verify',
+  captchaCodeApi: '/api/account/code/captcha',
+  registerApi: '/api/account/user/register',
+  resetPasswordApi: '/api/account/user/reset',
+  pubkeyApi: '/api/account/code/pubkey',
+  loginApi: '/api/account/auth/signin',
+  logoutApi: '/api/account/auth/signout'
+}
+
+export function getBase64Captcha(captchaUrl) {
+  return get(captchaUrl)
+}
+
+export function isUsernameExist(username) {
+  return get(accountApi.checkUsernameApi + '?username=' + username)
+}
+
+export function selectUsername(username) {
+  return post(accountApi.selectUsernameApi + '/' + username)
+}
+
+export function isEmailExist(email) {
+  return get(accountApi.checkEmailApi + '/' + email)
+}
+
+export function register(userRegistry) {
+  return post(accountApi.registerApi, userRegistry)
+}
+
+export function resetPassword(resetPasswordData) {
+  return post(accountApi.resetPasswordApi, resetPasswordData)
+}
+
+// 获取公钥
+export function getPubkey() {
+  return get(accountApi.pubkeyApi)
+}
+
+// 获取图形验证码
+export function getCaptchaCode(captchaCodeApi) {
+  return get(accountApi.captchaCodeApi)
+}
+
+// 获取短信验证码
+export function getVerifyCode(verifyCode) {
+  return post(accountApi.verifyCodeApi, verifyCode)
+}
+
+// 登录
+export function login(loginData) {
+  return post(accountApi.loginApi, loginData)
+}
+
+// 注销
+export function logout() {
+  return get(accountApi.logoutApi)
+}

+ 0 - 14
src/api/account/user.js

@@ -1,14 +0,0 @@
-import $axios from '../index'
-
-const userApi = {
-  myInfoApi: '/api/user/info/my',
-  userInfoApi: '/api/user/info'
-}
-
-export function getMyInfo() {
-  return $axios.get(userApi.myInfoApi)
-}
-
-export function getUserInfo(userId) {
-  return $axios.get(userApi.userInfoApi + '?userId' + userId)
-}

+ 0 - 22
src/api/comment/comment.js

@@ -1,22 +0,0 @@
-import $axios from '../index'
-
-const commentApi = {
-  commentSubmitApi: '/api/comment/video',
-  videoCommentApi: '/api/comment/video',
-  childCommentApi: '/api/comment/child'
-}
-
-// 发布评论
-export function submitComment(data) {
-  return $axios.post(commentApi.commentSubmitApi, data)
-}
-
-// 视频评论
-export function videoComment(videoId, page) {
-  return $axios.get(commentApi.videoCommentApi + '?videoId=' + videoId + '&page=' + page)
-}
-
-// 评论的子评论
-export function childComment(commentId, page) {
-  return $axios.get(commentApi.childCommentApi + '?commentId=' + commentId + '&page=' + page)
-}

+ 0 - 33
src/api/config/api-config.js

@@ -1,33 +0,0 @@
-const key_config = {
-  app_key: '3436445128',
-  app_secret: '6a1d41d969da490d9a37d4457184ea0b',
-  redirect_uri: 'http://127.0.0.1:8089/splash'
-}
-
-const host_config = {
-  local: 'http://127.0.0.1:8089/',
-  host: 'https://pixel-api.herokuapp.com/',
-  oauth: 'https://open.weibo.cn/oauth2/authorize'
-}
-
-const api_router_config = {
-  oauth_post: '/oauth2/access_token',
-  home_timeline: '/statuses/home_timeline.json',
-  public_timeline: '/statuses/public_timeline.json',
-  my_content: '/statuses/user_timeline.json',
-  userinfo: '/users/show.json',
-  content_comments: '/comments/show.json',
-  send_post_text: '/statuses/update.json',
-  send_post_image: '/statuses/upload.json',
-  at_me_statue: '/statuses/mentions.json',
-  at_me_comment: '/comments/mentions.json',
-  receive_comment: '/comments/to_me.json',
-  send_comment: '/comments/by_me.json',
-  my_follower: '/friendships/followers.json',
-  my_friend: '/friendships/friends.json'
-}
-
-export const HOST_CONCIG = host_config
-export const KEY_CONFIG = key_config
-export const API_ROUTER_CONFIG = api_router_config
-export const DEBUG = true

+ 2 - 2
src/api/media/file.js → src/api/file.js

@@ -1,4 +1,4 @@
-import $axios from '../index'
+import { post } from '@/utils/request'
 
 const fileApi = {
   videoIdApi: '/api/file/id/video'
@@ -6,5 +6,5 @@ const fileApi = {
 
 // 获取视频文件 ID 和 URL ID
 export function getVideoId() {
-  return $axios.post(fileApi.videoIdApi)
+  return post(fileApi.videoIdApi)
 }

+ 0 - 42
src/api/impl/my-content.js

@@ -1,42 +0,0 @@
-import axios from 'axios'
-import { HOST_CONCIG, API_ROUTER_CONFIG, DEBUG } from '@/api/config/api-config'
-import { logger } from '@/utils/logger'
-import store from '../../store/'
-import * as data from '../../assets/debug-data/getData'
-
-export const getMyContent = (page, okCallback, errorCallback) => {
-  if (DEBUG) {
-    setTimeout(function() {
-      okCallback(data.metimelime)
-    }, 1500)
-    return
-  }
-
-  const accesstoken = store.getters.token.access_token
-
-  var request_data = {
-    access_token: accesstoken,
-    count: 30,
-    page: page
-  }
-
-  var config = {
-    method: 'get',
-    url: API_ROUTER_CONFIG.my_content,
-    baseURL: HOST_CONCIG.host,
-    params: request_data,
-    headers: {
-      'Content-Type': 'application/json'
-    }
-  }
-
-  axios(config)
-    .then(function(response) {
-      logger('oauthPost-ok', 'getMyContent response succeed')
-      okCallback(response.data)
-    })
-    .catch(function(error) {
-      console.log(error)
-      errorCallback(error)
-    })
-}

+ 0 - 118
src/api/index.js

@@ -1,118 +0,0 @@
-import axios from 'axios'
-import store from '@/store'
-import router from '@/router'
-import Vue from 'vue'
-
-const $axios = axios.create({
-  // 5 分钟超时
-  timeout: 300000,
-  // 域名
-  baseURL: process.env.VUE_APP_BASE_API,
-  headers: {
-    'Content-Type': 'application/json; charset=UTF-8'
-  }
-})
-Vue.prototype.$http = axios // 并发请求
-// 在全局请求和响应拦截器中添加请求状态
-const loading = null
-$axios.defaults.withCredentials = true
-
-// 请求拦截器
-$axios.interceptors.request.use(
-  config => {
-    const token = store.getters.token
-    if (token) {
-      // 在请求的 Authorization 首部添加 token
-      config.headers.Authorization = 'Bearer ' + token
-    }
-    return config
-  },
-  error => {
-    return Promise.reject(error)
-  }
-)
-
-// 响应拦截器
-$axios.interceptors.response.use(
-  response => {
-    if (loading) {
-      loading.close()
-    }
-    const code = response.status
-    if ((code >= 200 && code < 300) || code === 304) {
-      return Promise.resolve(response.data)
-    } else {
-      return Promise.reject(response)
-    }
-  },
-  error => {
-    if (loading) {
-      loading.close()
-    }
-    if (error.response) {
-      const statusCode = error.response.status
-      switch (statusCode) {
-        case 401:
-          // 清除用户信息和 token 并跳转到登录页面
-          store.dispatch('user/resetToken').then(() => {
-            router.push('/login')
-          }).catch(() => {
-          })
-
-          // store.commit('setUserInfo', null)
-          // commit('RESET_STATE')
-          /* store.user.userInfo = null
-          store.user.token = null
-          console.log(store.user)*/
-          break
-        case 404:
-          console.error('网络请求不存在')
-          break
-        default:
-          console.error(error.response)
-      }
-      return error.response
-    } else {
-      // 请求超时或者网络有问题
-      if (error.message.includes('timeout')) {
-        console.error('请求超时!请检查网络是否正常')
-      } else {
-        console.error(error.message)
-        console.error('请求失败,请检查服务器是否已启动')
-      }
-    }
-    return Promise.reject(error)
-  }
-)
-
-// HTTP 请求
-export default {
-  get(url, params) {
-    return $axios({
-      method: 'get',
-      url,
-      params
-    })
-  },
-  post(url, data) {
-    return $axios({
-      method: 'post',
-      url,
-      data: JSON.stringify(data)
-    })
-  },
-  put(url, data) {
-    return $axios({
-      method: 'put',
-      url,
-      data: JSON.stringify(data)
-    })
-  },
-  delete(url, data) {
-    return $axios({
-      method: 'delete',
-      url,
-      data: JSON.stringify(data)
-    })
-  }
-}

+ 0 - 22
src/api/mblog/status.js

@@ -1,22 +0,0 @@
-import $axios from '../index'
-
-const statusApi = {
-  statusPubApi: '/api/mblog/status',
-  statusRecommendApi: '/api/mblog/status',
-  userStatusApi: '/api/mblog/status/user'
-}
-
-// 状态发布接口
-export function pubStatus(statusPost) {
-  return $axios.post(statusApi.statusPubApi, statusPost)
-}
-
-// 状态获取接口
-export function statusRecommend(page) {
-  return $axios.get(statusApi.statusRecommendApi + '/' + page)
-}
-
-// 用户状态获取接口
-export function userStatus(userId, page) {
-  return $axios.get(statusApi.userStatusApi + '?userId=' + userId + '&page=' + page)
-}

+ 0 - 22
src/api/media/audio.js

@@ -1,22 +0,0 @@
-import $axios from '../index'
-
-const audioApi = {
-  audioPostSubmitApi: '/api/media/audio/post/submit',
-  userAudioListApi: '/api/media/audio/post/user',
-  audioInfoApi: '/api/media/audio/post'
-}
-
-// 音频发布接口
-export function submitAudioPost(audioPost) {
-  return $axios.post(audioApi.audioPostSubmitApi, audioPost)
-}
-
-// 用户音频列表
-export function userAudioList(page, userId) {
-  return $axios.get(audioApi.userAudioListApi + '?page=' + page + '&userId=' + userId)
-}
-
-// 用户音频列表
-export function audioInfo(audioId) {
-  return $axios.get(audioApi.audioInfoApi + '/' + audioId)
-}

+ 0 - 22
src/api/media/collection.js

@@ -1,22 +0,0 @@
-import $axios from '../index'
-
-const videoApi = {
-  videoCollectApi: '/api/media/video/collect',
-  videoCollectionApi: '/api/media/video/collection',
-  videoRecordApi: '/api/media/video/play/record'
-}
-
-// 收藏视频
-export function collectVideo(data) {
-  return $axios.post(videoApi.videoCollectApi, data)
-}
-
-// 返回收藏的视频
-export function videoCollection(page) {
-  return $axios.get(videoApi.videoCollectionApi + '?page=' + page)
-}
-
-// 返回视频播放记录
-export function videoRecord(page) {
-  return $axios.get(videoApi.videoRecordApi + '?page=' + page)
-}

+ 0 - 16
src/api/post/article.js

@@ -1,16 +0,0 @@
-import $axios from '../index'
-
-const articlePostApi = {
-  articlePostApi: '/api/media/article/post/submit',
-  articleInfoApi: '/api/media/article/post'
-}
-
-// 发布文章贴
-export function submitArticlePost(data) {
-  return $axios.post(articlePostApi.articlePostApi, data)
-}
-
-// 获取文章贴
-export function getArticleInfo(articleId) {
-  return $axios.get(articlePostApi.articleInfoApi + '/' + articleId)
-}

+ 0 - 16
src/api/search/search.js

@@ -1,16 +0,0 @@
-import $axios from '../index'
-
-const searchApi = {
-  keywordSuggestApi: '/api/search/suggest',
-  videoSearchApi: '/api/search/query'
-}
-
-// 关键词建议
-export function keywordSuggest(keyword) {
-  return $axios.get(searchApi.keywordSuggestApi + '?k=' + keyword)
-}
-
-// 视频搜索
-export function videoQuery(keyword, page) {
-  return $axios.get(searchApi.videoSearchApi + '?keyword=' + keyword + '&page=' + page)
-}

+ 0 - 44
src/api/user/account.js

@@ -1,44 +0,0 @@
-import $axios from '../index'
-
-const accountApi = {
-  checkUsernameApi: '/api/user/account/check/username',
-  selectUsernameApi: '/api/user/account/select/username',
-  checkEmailApi: '/api/user/account/check/email',
-  verifyCodeApi: '/api/account/code/verify',
-  captchaCodeApi: '/api/account/code/captcha',
-  registerApi: '/api/account/user/register',
-  resetPasswordApi: '/api/account/user/reset'
-}
-
-export function getBase64Captcha(captchaUrl) {
-  return $axios.get(captchaUrl)
-}
-
-export function isUsernameExist(username) {
-  return $axios.get(accountApi.checkUsernameApi + '?username=' + username)
-}
-
-export function selectUsername(username) {
-  return $axios.post(accountApi.selectUsernameApi + '/' + username)
-}
-
-export function isEmailExist(email) {
-  return $axios.get(accountApi.checkEmailApi + '/' + email)
-}
-
-// 获取验证码
-export function getVerifyCode(verifyCode) {
-  return $axios.post(accountApi.verifyCodeApi, verifyCode)
-}
-
-export function getCaptchaCode(captchaCodeApi) {
-  return $axios.get(accountApi.captchaCodeApi)
-}
-
-export function register(userRegistry) {
-  return $axios.post(accountApi.registerApi, userRegistry)
-}
-
-export function resetPassword(resetPasswordData) {
-  return $axios.post(accountApi.resetPasswordApi, resetPasswordData)
-}

+ 0 - 22
src/api/user/auth.js

@@ -1,22 +0,0 @@
-import $axios from '../index'
-
-const authApi = {
-  pubkeyApi: '/api/account/code/pubkey',
-  loginApi: '/api/account/auth/signin',
-  logoutApi: '/api/account/auth/signout'
-}
-
-// 登录
-export function getPubkey() {
-  return $axios.get(authApi.pubkeyApi)
-}
-
-// 登录
-export function login(loginData) {
-  return $axios.post(authApi.loginApi, loginData)
-}
-
-// 注销
-export function logout() {
-  return $axios.get(authApi.logoutApi)
-}

+ 0 - 50
src/api/user/user.js

@@ -1,50 +0,0 @@
-import $axios from '../index'
-
-const userApi = {
-  myInfoApi: '/api/user/info/my',
-  userInfoApi: '/api/user/info',
-  followUserApi: '/api/user/relation/follow',
-  unfollowUserApi: '/api/user/relation/unfollow',
-  checkRelationApi: '/api/user/relation/check',
-  vipPlanApi: '/api/user/vip/plan',
-  vipOrderApi: '/api/user/vip/order',
-  vipPayApi: '/api/user/vip/pay'
-}
-
-export function getMyInfo() {
-  return $axios.get(userApi.myInfoApi)
-}
-
-export function getUserInfo(userId) {
-  return $axios.get(userApi.userInfoApi + '?userId=' + userId)
-}
-
-// 关注用户
-export function followUser(followingId) {
-  return $axios.post(userApi.followUserApi + '/' + followingId)
-}
-
-// 取消关注用户
-export function unfollowUser(followingId) {
-  return $axios.post(userApi.unfollowUserApi + '/' + followingId)
-}
-
-export function checkRelation(userId) {
-  return $axios.get(userApi.checkRelationApi + '/' + userId)
-}
-
-export function getVipPlan() {
-  return $axios.get(userApi.vipPlanApi)
-}
-
-export function getVipOrder() {
-  return $axios.get(userApi.vipOrderApi)
-}
-
-export function createVipOrder(planId) {
-  return $axios.post(userApi.vipOrderApi + '/' + planId)
-}
-
-export function payVip(orderId) {
-  return $axios.post(userApi.vipPayApi + '/' + orderId)
-}

+ 24 - 23
src/api/media/video.js → src/api/video.js

@@ -1,13 +1,14 @@
-import $axios from '../index'
+import { get, post } from '@/utils/request'
 
 const videoApi = {
+  videoRecommendApi: '/api/content/video/recommend',
+  similarVideoApi: '/api/content/video/similar',
+  videoInfoApi: '/api/content/video/detail',
+  videoCategoryApi: '/api/content/video/category',
+  videoTagApi: '/api/content/video/tag',
+  videoUrlApi: '/api/content/video/url',
+
   videoTimelineApi: '/api/media/video/post/timeline',
-  videoRecommendApi: '/api/media/video/post/recommend',
-  videoTagApi: '/api/media/video/post/tag',
-  similarVideoApi: '/api/media/video/post/similar',
-  videoInfoApi: '/api/media/video/post/detail',
-  videoUrlApi: '/api/media/video/url',
-  videoCategoryApi: '/api/media/video/category',
   videoPostSubmitApi: '/api/media/video/post/submit',
   playerRecordApi: '/api/media/video/play/record',
   userVideoListApi: '/api/media/video/post/user',
@@ -15,59 +16,59 @@ const videoApi = {
   testVideoApi: '/api/media/video/post/display'
 }
 
-// 视频时间线
-export function videoTimeline(page) {
-  return $axios.get(videoApi.videoTimelineApi + '/' + page)
-}
-
 // 视频推荐接口
 export function videoRecommend(page) {
-  return $axios.get(videoApi.videoRecommendApi + '/' + page)
+  return get(videoApi.videoRecommendApi + '/' + page)
 }
 
 // 视频标签接口
 export function videoTag(tag, page) {
-  return $axios.get(videoApi.videoTagApi + '?tag=' + tag + '&page=' + page)
+  return get(videoApi.videoTagApi + '?tag=' + tag + '&page=' + page)
 }
 // 相似视频接口
 export function similarVideo(videoId) {
-  return $axios.get(videoApi.similarVideoApi + '?videoId=' + videoId)
+  return get(videoApi.similarVideoApi + '?videoId=' + videoId)
 }
 
 // 视频详情接口
 export function videoInfo(videoId) {
-  return $axios.get(videoApi.videoInfoApi + '/' + videoId)
+  return get(videoApi.videoInfoApi + '/' + videoId)
 }
 
 // 视频 URL 接口
 export function videoUrl(videoId) {
-  return $axios.get(videoApi.videoUrlApi + '/' + videoId)
+  return get(videoApi.videoUrlApi + '/' + videoId)
 }
 
 // 视频 URL 接口
 export function videoCategory() {
-  return $axios.get(videoApi.videoCategoryApi)
+  return get(videoApi.videoCategoryApi)
 }
 
 export function submitVideoPost(videoPost) {
-  return $axios.post(videoApi.videoPostSubmitApi, videoPost)
+  return post(videoApi.videoPostSubmitApi, videoPost)
 }
 
 // 播放记录
 export function submitPlayRecord(playerRecord) {
-  return $axios.post(videoApi.playerRecordApi, playerRecord)
+  return post(videoApi.playerRecordApi, playerRecord)
 }
 
 // 用户视频列表
 export function userVideoList(page, userId) {
-  return $axios.get(videoApi.userVideoListApi + '?page=' + page + '&userId=' + userId)
+  return get(videoApi.userVideoListApi + '?page=' + page + '&userId=' + userId)
 }
 
 // 用户最近投稿的视频
 export function userRecentlyVideoList(userId) {
-  return $axios.get(videoApi.userRecentlyVideoListApi + '?userId=' + userId)
+  return get(videoApi.userRecentlyVideoListApi + '?userId=' + userId)
 }
 
 export function getDisplayedVideoList() {
-  return $axios.get(videoApi.testVideoApi)
+  return get(videoApi.testVideoApi)
+}
+
+// 视频时间线
+export function videoTimeline(page) {
+  return get(videoApi.videoTimelineApi + '/' + page)
 }

+ 20 - 0
src/assets/css/base.css

@@ -0,0 +1,20 @@
+body {
+    width: 100vw;
+    overflow: hidden;
+    padding: 0px;
+    margin: 0px;
+}
+
+/*解决滚动条消失*/
+html {
+    overflow-y: scroll;
+}
+
+:root {
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+:root body {
+    position: absolute;
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
src/assets/debug-data/data/content.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
src/assets/debug-data/data/friendship.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
src/assets/debug-data/data/notify.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 5409
src/assets/debug-data/data/status.js


+ 0 - 95
src/assets/debug-data/data/userInfo.js

@@ -1,95 +0,0 @@
-export const userInfo = {
-    "id": 3024601477,
-    "idstr": "3024601477",
-    "class": 1,
-    "screen_name": "wanbo-",
-    "name": "wanbo-",
-    "province": "11",
-    "city": "1000",
-    "location": "北京",
-    "description": "我想做一名音乐人。",
-    "url": "",
-    "profile_image_url": "http://tvax3.sinaimg.cn/crop.0.0.750.750.50/b447c185ly8fdocwsavc6j20ku0kuaba.jpg",
-    "cover_image_phone": "http://ww1.sinaimg.cn/crop.0.0.640.640.640/6ce2240djw1e9snuksa5nj20hs0hs78b.jpg",
-    "profile_url": "singerwannber",
-    "domain": "singerwannber",
-    "weihao": "",
-    "gender": "m",
-    "followers_count": 675,
-    "friends_count": 397,
-    "pagefriends_count": 1,
-    "statuses_count": 192,
-    "favourites_count": 91,
-    "created_at": "Sun Feb 17 22:51:23 +0800 2013",
-    "following": false,
-    "allow_all_act_msg": false,
-    "geo_enabled": true,
-    "verified": false,
-    "verified_type": 220,
-    "remark": "",
-    "insecurity": {
-        "sexual_content": false
-    },
-    "status": {
-        "created_at": "Mon Jun 26 00:28:34 +0800 2017",
-        "id": 4122684797467015,
-        "mid": "4122684797467015",
-        "idstr": "4122684797467015",
-        "text": "前排举手🙋我是你的小迷弟。🤗",
-        "source_allowclick": 0,
-        "source_type": 1,
-        "source": "<a href=\"http://app.weibo.com/t/feed/3jskmg\" rel=\"nofollow\">iPhone 6s</a>",
-        "favorited": false,
-        "truncated": false,
-        "in_reply_to_status_id": "",
-        "in_reply_to_user_id": "",
-        "in_reply_to_screen_name": "",
-        "pic_urls": [],
-        "geo": null,
-        "annotations": [
-            {
-                "mapi_request": true
-            }
-        ],
-        "reposts_count": 0,
-        "comments_count": 0,
-        "attitudes_count": 0,
-        "isLongText": false,
-        "mlevel": 0,
-        "visible": {
-            "type": 0,
-            "list_id": 0
-        },
-        "biz_feature": 0,
-        "hasActionTypeCard": 0,
-        "darwin_tags": [],
-        "hot_weibo_tags": [],
-        "text_tag_tips": [],
-        "userType": 0,
-        "positive_recom_flag": 0,
-        "gif_ids": "",
-        "is_show_bulletin": 2
-    },
-    "ptype": 0,
-    "allow_all_comment": true,
-    "avatar_large": "http://tvax3.sinaimg.cn/crop.0.0.750.750.180/b447c185ly8fdocwsavc6j20ku0kuaba.jpg",
-    "avatar_hd": "http://tvax3.sinaimg.cn/crop.0.0.750.750.1024/b447c185ly8fdocwsavc6j20ku0kuaba.jpg",
-    "verified_reason": "",
-    "verified_trade": "",
-    "verified_reason_url": "",
-    "verified_source": "",
-    "verified_source_url": "",
-    "follow_me": false,
-    "online_status": 0,
-    "bi_followers_count": 163,
-    "lang": "zh-cn",
-    "star": 0,
-    "mbtype": 0,
-    "mbrank": 0,
-    "block_word": 0,
-    "block_app": 0,
-    "credit_score": 80,
-    "user_ability": 1024,
-    "urank": 28,
-    "story_read_state": -1
-}

+ 0 - 32
src/assets/debug-data/getData.js

@@ -1,32 +0,0 @@
-import * as user from './data/userInfo'
-import * as status from './data/status'
-import * as notify from './data/notify'
-import * as friendship from './data/friendship'
-import * as content from './data/content'
-
-
-var userInfo = user.userInfo
-var hometimeline = status.hometimeline
-var publictimeline = status.publictimeline
-var metimelime = status.metimeline
-var atmestatus = notify.atmestatus
-var atmecomment = notify.atmecomment
-var receivecomment = notify.receivecomment
-var sendcomment = notify.sendcomment
-var myfollowers = friendship.myfollowers
-var myfriends = friendship.myfriends
-var contentcomment = content.comment
-
-export {
-    userInfo,
-    hometimeline,
-    publictimeline,
-    metimelime,
-    atmestatus,
-    atmecomment,
-    receivecomment,
-    sendcomment,
-    myfollowers,
-    myfriends,
-    contentcomment
-}

BIN
src/assets/img/icon/avatar.png


BIN
src/assets/img/icon/backtop.png


BIN
src/assets/img/icon/erweima.png


BIN
src/assets/img/icon/exit.png


BIN
src/assets/img/icon/github.png


BIN
src/assets/img/icon/history.png


BIN
src/assets/img/icon/like.png


BIN
src/assets/img/icon/logo.png


BIN
src/assets/img/icon/mylike.png


BIN
src/assets/img/icon/not-collection.png


BIN
src/assets/img/icon/not-history.png


BIN
src/assets/img/icon/not-result.png


BIN
src/assets/img/icon/play-icon.png


BIN
src/assets/img/icon/profile.png


BIN
src/assets/img/icon/recommmand-icon.png


BIN
src/assets/img/icon/search-result.png


BIN
src/assets/img/icon/weixin.png


BIN
src/assets/img/moviecover/py.jpg


+ 4 - 0
src/assets/js/const.js

@@ -0,0 +1,4 @@
+// 常量
+
+// 服务器地址
+export const URL = 'http://localhost:8080'

+ 272 - 0
src/assets/js/mixin.js

@@ -0,0 +1,272 @@
+/**
+ * 混入对象,抽取Vue中公共的部分
+ */
+import { getPubkey, getCaptchaCode, getVerifyCode, login, logout } from '@/api/account'
+import { setUserToken, removeAll } from '@/utils/auth'
+import { JSEncrypt } from 'jsencrypt'
+
+export const userMixin = {
+  data() {
+    return {
+      pubkey: '',
+      pubkeyR: '',
+      captchaCode: '',
+      userLogin: {
+        accountType: 1,
+        loginType: 1,
+        username: null,
+        password: null,
+        captchaCode: null,
+        plat: 1
+      },
+      loginDialog: false,
+      registerDialog: false,
+      dialogVisible: false,
+      dialogVisible2: false,
+      nickname: '', // 用户昵称
+      phone: '', // 电话
+      mobile: '', // 电话
+      username: '', // 电话
+      password: '', // 用户密码
+      repassword: '', // 确认密码
+      avatarurl: '', // 用户头像路径
+      rcode: '', // 注册验证码
+      isLoading: false, // 登录加载效果
+      user: this.$user, // 登录用户对象
+      code: '获取验证码',
+      isBtn: false,
+      imageUrl: require('assets/img/icon/avatar.png')
+    }
+  },
+  methods: {
+    fetchPubkey() {
+      getPubkey().then(res => {
+        if (res.code === 0) {
+          this.pubkey = res.data.pubkey
+          this.pubkeyR = res.data.r
+
+          this.getCaptcha()
+        } else {
+          this.message = res.msg
+          this.showMessage = true
+        }
+      }).catch(error => {
+        this.message = error.message
+        this.showMessage = true
+      })
+    },
+    getCaptcha() {
+      getCaptchaCode().then(res => {
+        if (res.code === 0) {
+          this.captchaCode = 'data:image/jpeg;base64,' + res.data
+          this.dialogVisible = true
+        } else {
+          this.message = '获取图形验证码失败, 请重新刷新页面'
+          this.showMessage = true
+        }
+      })
+    },
+    encryptPassword(password, pubkey, pubkeyR) {
+      var encryptor = new JSEncrypt()
+      encryptor.setPublicKey(pubkey)
+      return encryptor.encrypt(pubkeyR + password)
+    },
+    fetchVerifyCode() {
+      if (this.userLogin.username === null || this.userLogin.username === '') {
+        this.$message.success('请填写手机号')
+        return
+      }
+
+      this.isBtn = true
+      let time = 60
+      const timeout = setInterval(() => {
+        if (time !== 0) {
+          time--
+          this.code = time + 's后重新获取'
+        } else {
+          this.isBtn = false
+          this.code = '重新获取验证码'
+          clearTimeout(timeout)
+        }
+      }, 1000)
+
+      const verifyCodeReq = {}
+      verifyCodeReq.receiver = this.userLogin.username
+      verifyCodeReq.notifyType = 2
+      getVerifyCode(verifyCodeReq).then(res => {
+        if (res.code === 0) {
+          this.$message.success('验证码已发送, 请注意查收')
+        } else {
+          this.$message.warning(res.msg)
+        }
+      }).catch(error => {
+        this.$message.error(error)
+      })
+    },
+    // 昵称校验
+    nickNameBlur() {
+      if (this.nickname.length <= 0) {
+        this.$message.warning('昵称不能为空')
+      }
+    },
+    // 电话校验
+    phoneBlur() {
+      if (!(/^1[3456789]\d{9}$/.test(this.mobile))) {
+        this.$message.warning('电话号码格式有误')
+      }
+    },
+    loginBtn() {
+      // 显示加载效果
+      this.isLoading = true
+      if (this.userLogin.username === '') {
+        this.$message.warning('手机号不能为空')
+        return
+      }
+      if (this.userLogin.password === '' || this.userLogin.password === null) {
+        this.$message.warning('短信验证码不能为空')
+        return
+      }
+      if (this.userLogin.captchaCode === '') {
+        this.$message.warning('图形验证码不能为空')
+        return
+      }
+
+      this.userLogin.password = this.encryptPassword(this.userLogin.password, this.pubkey, this.pubkeyR)
+      login(this.userLogin).then(res => {
+        if (res.code === 0) {
+          const resData = res.data
+          const userInfo = resData.userInfo
+          const userToken = resData.userToken
+          // 保存授权信息到本地缓存
+          setUserToken(userToken)
+          this.$store.commit('UPDATE_USER_INFO', userInfo)
+
+          // 关闭弹窗并刷新页面
+          this.dialogVisible = false
+          this.$router.go(0)
+        } else {
+          // 登录失败
+          this.$message.warning(res.msg)
+        }
+      }).catch(error => {
+        // 登录请求错误
+        this.$message.error(error)
+      }).finally(() => {
+        this.userLogin = {
+          accountType: 1,
+          loginType: 1,
+          username: null,
+          password: null,
+          captchaCode: null,
+          plat: 1
+        }
+      })
+    },
+    goToLogout() {
+      this.$confirm('退出登录, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        logout().then(res => {
+          if (res.code === 0) {
+            this.$store.commit('USER_LOGOUT')
+            removeAll()
+          } else {
+            this.$notify.error({
+              title: '提示',
+              message: res.msg
+            })
+          }
+        }).catch((e) => {
+          console.log(e)
+          this.$notify({
+            message: '网络错误,请稍后再试...'
+          })
+        }).finally(() => {
+          // 刷新当前页面
+          this.$router.go(0)
+          // this.$router.push('/')
+          this.isLoading = false
+        })
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消'
+        })
+      })
+    },
+    goToProfile() {
+      if (this.$route.path === '/user/profile') {
+        this.$router.go(0)
+        return
+      }
+      this.$router.push('/user/profile')
+    },
+    goToCollection() {
+      if (this.$route.path === '/user/collection') {
+        this.$router.go(0)
+        return
+      }
+      this.$router.push('/user/collection')
+    },
+    goToHistory() {
+      if (this.$route.path === '/user/history') {
+        this.$router.go(0)
+        return
+      }
+      this.$router.push('/user/history')
+    },
+    // 点击注册
+    register() {
+      if (this.password !== this.repassword) {
+        this.$message.warning('密码输入不一致')
+        return
+      }
+      // 先判断验证码是否正确
+      /* checkCode(this.rcode).then(res => {
+        if (res === 1) {
+          // 验证码正确,提交头像,在成功的回调中注册用户
+          this.$refs.uploadImg.submit()
+        } else if (res === -1) {
+          this.$message.warning('验证码已失效,请重新获取!')
+        } else {
+          this.$message.warning('验证码错误!')
+        }
+      })*/
+    },
+    // 选择图片时
+    imgChange(file) {
+      this.imageUrl = URL.createObjectURL(file.raw)
+    },
+    // 上传成功时
+    handleAvatarSuccess(res, file) {
+      this.avatarurl = res
+      // console.log(res);// 服务器返回的文件名称
+      // 注册
+      /* register(this.phone, this.password, this.nickname, this.avatarurl, this.phone).then(res => {
+        // console.log(res);
+        if (res === 1) {
+          this.$message.success('注册成功!')
+          this.dialogVisible2 = false
+          this.password = ''
+        } else if (res === -1) {
+          this.$message.warning('该手机号码已经被注册!如有疑问,请联系管理员!')
+        }
+      })*/
+    },
+    // 上传之前
+    beforeAvatarUpload(file) {
+      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
+      const isLt2M = file.size / 1024 / 1024 < 2
+
+      if (!isJPG) {
+        this.$message.error('上传头像图片格式错误!')
+      }
+      if (!isLt2M) {
+        this.$message.error('上传头像图片大小不能超过 2MB!')
+      }
+      return isJPG && isLt2M
+    }
+  }
+}

+ 96 - 0
src/assets/js/utils.js

@@ -0,0 +1,96 @@
+// 工具类
+
+// 随机获取数组中的num个元素并返回新数组
+export function getArrayItems(arr, num) {
+  const newArr = []
+  for (let i = 0; i <= num - 1; i++) {
+    // 生成数组长度范围内的随机数
+    const index = Math.floor(Math.random() * arr.length)
+    newArr[i] = arr.splice(index, 1)[0]
+  }
+  return newArr
+}
+
+// 23000 =》 2.3万
+export function handleVisited(visited) {
+  // console.log(visited);
+  if (visited >= 10000) {
+    var s = Math.floor(visited / 10000)
+    var s2 = Math.floor(visited % 10000 / 1000)
+    visited = s + '.' + s2 + '万'
+  }
+  return visited
+}
+
+// 时间的转换
+export function getDate(pretime) {
+  const minute = 1000 * 60
+  const hour = minute * 60
+  const day = hour * 24
+  const month = day * 30
+  // 将时间转化为时间戳
+  pretime = pretime.replace(/\-/g, '/')
+  const time = new Date(pretime).getTime()
+  // 获取当前时间戳
+  const now = new Date().getTime()
+  const subTime = now - time
+  const monthC = subTime / month
+  const weekC = subTime / (7 * day)
+  const dayC = subTime / day
+  const hourC = subTime / hour
+  const minC = subTime / minute
+
+  if (monthC >= 1) {
+    return parseInt(monthC) + '个月前'
+  } else if (weekC >= 1) {
+    return parseInt(weekC) + '周前'
+  } else if (dayC >= 1) {
+    return parseInt(dayC) + '天前'
+  } else if (hourC >= 1) {
+    return parseInt(hourC) + '小时前'
+  } else if (minC >= 1) {
+    return parseInt(minC) + '分钟前'
+  } else {
+    return '刚刚'
+  }
+}
+
+// 获取唯一的id
+export function getUUid() {
+  var s = []
+  var hexDigits = '0123456789abcdef'
+  for (var i = 0; i < 36; i++) {
+    s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
+  }
+  s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
+  s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
+  s[8] = s[13] = s[18] = s[23] = '-'
+
+  var uuid = s.join('')
+  return uuid
+}
+
+// 时间格式化
+export function formatDate(date, fmt) {
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+  }
+  const o = {
+    'M+': date.getMonth() + 1,
+    'd+': date.getDate(),
+    'h+': date.getHours(),
+    'm+': date.getMinutes(),
+    's+': date.getSeconds()
+  }
+  for (const k in o) {
+    if (new RegExp(`(${k})`).test(fmt)) {
+      const str = o[k] + ''
+      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))
+    }
+  }
+  return fmt
+}
+
+function padLeftZero(str) {
+  return ('00' + str).substr(str.length)
+}

BIN
src/assets/logo.png


+ 0 - 1
src/assets/logo.svg

@@ -1 +0,0 @@
-<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

+ 9 - 114
src/components/player/player.vue → src/components/VideoPlayer.vue

@@ -1,86 +1,9 @@
 <template>
-  <div id="dplayer" ref="dplayer" style="width: 800px; height: 450px;">
-    <v-dialog
-      v-model="permissionDialog"
-      max-width="290"
-    >
-      <v-card>
-        <v-card-title class="headline">VIP 观看</v-card-title>
-        <v-card-text>
-          <span>您还不是 VIP, 本视频仅供 VIP 观看</span>
-        </v-card-text>
-
-        <v-card-actions>
-          <v-spacer />
-          <v-btn
-            color="green darken-1"
-            text
-            @click="gotoHomePage"
-          >
-            好的呢
-          </v-btn>
-          <v-btn
-            color="green darken-1"
-            text
-            @click="gotoVipPage"
-          >
-            成为 VIP
-          </v-btn>
-        </v-card-actions>
-      </v-card>
-    </v-dialog>
-    <v-dialog
-      v-model="authCodeDialog"
-      max-width="290"
-    >
-      <v-form
-        ref="form"
-        lazy-validation
-      >
-        <v-text-field
-          v-model="authCode"
-          label="验证码"
-          required
-        />
-        <v-btn
-          color="success"
-          class="mr-4"
-          @click="submitAuthCode"
-        >
-          提交
-        </v-btn>
-        <v-btn
-          color="error"
-          class="mr-4"
-          @click="cancelSubmitAuthCode"
-        >
-          取消
-        </v-btn>
-      </v-form>
-    </v-dialog>
-    <v-snackbar
-      v-model="showMessage"
-      :top="true"
-      :timeout="1000"
-    >
-      {{ message }}
-      <template v-slot:action="{ attrs }">
-        <v-btn
-          color="pink"
-          text
-          v-bind="attrs"
-          @click="showMessage = false"
-        >
-          关闭
-        </v-btn>
-      </template>
-    </v-snackbar>
-  </div>
+  <div id="dplayer" ref="dplayer" style="width: 800px; height: 450px;" />
 </template>
 
 <script>
-import { videoUrl } from '@/api/media/video'
-import SocketInstance from '@/utils/ws/socket-instance'
+import { videoUrl } from '@/api/video'
 
 const DPlayer = require('dplayer')
 const flv = require('flv.js')
@@ -117,13 +40,14 @@ export default {
     }
   },
   created() {
-    const userInfo = this.$store.state.user.userInfo
+    /* const userInfo = this.$store.state.user.userInfo
     if (userInfo !== null) {
       this.userPermission.userId = userInfo.userId
       this.userPermission.vip = userInfo.vip
-    }
+    }*/
 
-    const vidScope = this.videoProp.scope
+    this.getUrl = true
+    /* const vidScope = this.videoProp.scope
     if (vidScope === 1) {
       this.getUrl = true
     } else if (vidScope === 2) {
@@ -136,7 +60,7 @@ export default {
       this.authCodeDialog = true
     } else if (vidScope === 4) {
       alert('私有视频, 只有本人可以观看')
-    }
+    }*/
   },
   mounted() {
     // SocketInstance.connect()
@@ -201,11 +125,11 @@ export default {
       // 跳转到上次看到的位置
       player.seek(pos)
       player.on('progress', function() {
-        SocketInstance.send({ videoId: videoId, currentTime: player.video.currentTime })
+        // SocketInstance.send({ videoId: videoId, currentTime: player.video.currentTime })
       })
 
       player.on('ended', () => {
-        SocketInstance.send({ videoId: videoId, currentTime: player.video.currentTime })
+        // SocketInstance.send({ videoId: videoId, currentTime: player.video.currentTime })
         // TODO 当前视频播放完成后判断是否继续播放相关推荐中的视频
         console.log('视频播放完成')
         if (this.autoPlayList) {
@@ -265,35 +189,6 @@ export default {
           type: 'dash'
         }
       })
-    },
-    gotoHomePage() {
-      this.permissionDialog = false
-      if (this.$route.path === '/') {
-        return
-      }
-      this.$router.push('/')
-    },
-    gotoVipPage() {
-      this.permissionDialog = false
-      if (this.$route.path === '/vip/plan') {
-        return
-      }
-      this.$router.push('/vip/plan')
-    },
-    submitAuthCode() {
-      this.authCodeDialog = false
-      console.log('发送视频认证码')
-      console.log(this.videoProp.videoId)
-      console.log(this.authCode)
-
-      this.getVideoUrl(this.videoProp.videoId)
-    },
-    cancelSubmitAuthCode() {
-      this.authCodeDialog = false
-      if (this.$route.path === '/') {
-        return
-      }
-      this.$router.push('/')
     }
   }
 }

+ 145 - 0
src/components/card/ArticleCard.vue

@@ -0,0 +1,145 @@
+<template>
+  <router-link target="_blank" :to="`/video/${video.videoId}`">
+    <el-row>
+      <el-col :md="6">
+        <el-image
+            lazy
+            fit="cover"
+            :src="video.coverUrl"
+            class="coverImg"
+        />
+      </el-col>
+      <el-col :md="12">
+        <div style="padding: 14px">
+          <span style="left: 0;margin-bottom: 0px;color: black;">{{ video.title | ellipsis }}</span>
+        </div>
+      </el-col>
+    </el-row>
+    <el-divider />
+  </router-link>
+</template>
+
+<script>
+import { handleVisited } from 'assets/js/utils'
+
+export default {
+  name: 'ArticleCard',
+  filters: {
+    ellipsis(value) {
+      if (!value) return ''
+      const max = 20
+      if (value.length > max) {
+        return value.slice(0, max) + '...'
+      }
+      return value
+    }
+  },
+  props: {
+    video: {
+      type: Object,
+      default: null
+    },
+    // 时间前的描述
+    dateTit: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    getVisited(visited) {
+      return handleVisited(visited)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.time {
+  font-size: 15px;
+  color: #999;
+}
+
+.bottom {
+  margin-top: 13px;
+  line-height: 12px;
+}
+
+.tit {
+  font-weight: 700;
+  font-size: 18px;
+
+  height: 50px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2; /*行数*/
+  -webkit-box-orient: vertical;
+}
+
+.num {
+  position: relative;
+  font-size: 15px;
+  padding-top: 9px;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  .tit {
+    font-weight: 600;
+    font-size: 12px;
+    height: 32px;
+  }
+  .time {
+    font-size: 10px;
+    color: #999;
+  }
+  .num {
+    font-size: 9px;
+    padding-top: 3px;
+  }
+  .bottom {
+    margin-top: 2px;
+    line-height: 7px;
+  }
+  .coverImg {
+    height: 120px !important;
+  }
+}
+
+.coverImg {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.card {
+  margin-bottom: 20px;
+  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+}
+
+/*.card:hover {
+  !*鼠标放上之后元素变成1.06倍大小*!
+  transform: scale(1.06);
+}*/
+.imgs {
+  position: relative;
+}
+.play-icon {
+  position: absolute;
+  /*top: -15px;*/
+  right: 2%;
+  bottom: 5px;
+  z-index: 7;
+  width: 40px;
+}
+</style>

+ 160 - 0
src/components/card/AudioCard.vue

@@ -0,0 +1,160 @@
+<template>
+  <el-col style="padding-right: 7px; padding-left: 7px">
+    <div style="cursor: pointer" :title="video.title">
+      <el-card :body-style="{ padding: '0px' }" class="card">
+        <router-link target="_blank" :to="`/video/${video.videoId}`">
+          <div class="imgs">
+            <el-image
+              lazy
+              fit="cover"
+              :src="video.coverUrl"
+              class="coverImg"
+            />
+            <span style="position: absolute; bottom: 0; left: 0; color:red">
+              <i class="el-icon-video-play">{{ getVisited(video.viewCount) }}</i>
+            </span>
+            <span style="position: absolute; bottom: 0; left: 15%; color:red">
+              <i class="el-icon-s-comment">{{ getVisited(video.commentCount) }}</i>
+            </span>
+            <span style="position: absolute; bottom: 0; right: 0; color:red"> 12:11 </span>
+          </div>
+        </router-link>
+        <div style="padding: 14px">
+          <router-link target="_blank" :to="`/video/${video.videoId}`">
+            <span style="left: 0;margin-bottom: 0px;color: black;">{{ video.title | ellipsis }}</span>
+          </router-link>
+        </div>
+        <div style="padding: 14px">
+          <span style="left: 0;margin-bottom: 0px;color: black;">
+            <router-link target="_blank" :to="`/u/${video.userId}`"><i class="el-icon-user"> {{ video.username }} </i></router-link> · {{ video.pubDate }}
+          </span>
+        </div>
+      </el-card>
+    </div>
+  </el-col>
+</template>
+
+<script>
+import { handleVisited } from 'assets/js/utils'
+
+export default {
+  name: 'AudioCard',
+  filters: {
+    ellipsis(value) {
+      if (!value) return ''
+      const max = 20
+      if (value.length > max) {
+        return value.slice(0, max) + '...'
+      }
+      return value
+    }
+  },
+  props: {
+    video: {
+      type: Object,
+      default: null
+    },
+    // 时间前的描述
+    dateTit: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    getVisited(visited) {
+      return handleVisited(visited)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.time {
+  font-size: 15px;
+  color: #999;
+}
+
+.bottom {
+  margin-top: 13px;
+  line-height: 12px;
+}
+
+.tit {
+  font-weight: 700;
+  font-size: 18px;
+
+  height: 50px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2; /*行数*/
+  -webkit-box-orient: vertical;
+}
+
+.num {
+  position: relative;
+  font-size: 15px;
+  padding-top: 9px;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  .tit {
+    font-weight: 600;
+    font-size: 12px;
+    height: 32px;
+  }
+  .time {
+    font-size: 10px;
+    color: #999;
+  }
+  .num {
+    font-size: 9px;
+    padding-top: 3px;
+  }
+  .bottom {
+    margin-top: 2px;
+    line-height: 7px;
+  }
+  .coverImg {
+    height: 120px !important;
+  }
+}
+
+.coverImg {
+  width: 100%;
+  height: 175px;
+  display: block;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.card {
+  margin-bottom: 20px;
+  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+}
+
+/*.card:hover {
+  !*鼠标放上之后元素变成1.06倍大小*!
+  transform: scale(1.06);
+}*/
+.imgs {
+  position: relative;
+}
+.play-icon {
+  position: absolute;
+  /*top: -15px;*/
+  right: 2%;
+  bottom: 5px;
+  z-index: 7;
+  width: 40px;
+}
+</style>

+ 164 - 0
src/components/card/ImageCard.vue

@@ -0,0 +1,164 @@
+<template>
+  <el-col style="padding-right: 7px; padding-left: 7px">
+    <div style="cursor: pointer" :title="video.title">
+      <el-card :body-style="{ padding: '0px' }" class="card">
+        <router-link target="_blank" :to="`/image/${video.videoId}`">
+          <div class="imgs">
+            <el-image
+              lazy
+              fit="cover"
+              :src="video.coverUrl"
+              class="coverImg"
+            />
+            <span style="position: absolute; bottom: 0; left: 0; color:red">
+              <i v-if="video.viewCount === 0" class="el-icon-mobile-phone" />
+              <i v-else class="el-icon-monitor" />
+            </span>
+            <span style="position: absolute; bottom: 0; left: 10%; color:red">
+              <i class="el-icon-video-play">{{ getVisited(video.viewCount) }}</i>
+            </span>
+            <span style="position: absolute; bottom: 0; left: 30%; color:red">
+              <i class="el-icon-s-comment">{{ getVisited(video.commentCount) }}</i>
+            </span>
+            <span style="position: absolute; bottom: 0; right: 0; color:red"> {{ video.duration }} </span>
+          </div>
+        </router-link>
+        <div style="padding: 14px">
+          <router-link target="_blank" :to="`/video/${video.videoId}`">
+            <span style="left: 0;margin-bottom: 0px;color: black;">{{ video.title | ellipsis }}</span>
+          </router-link>
+        </div>
+        <div style="padding: 14px">
+          <span style="left: 0;margin-bottom: 0px;color: black;">
+            <router-link target="_blank" :to="`/u/${video.userId}`"><i class="el-icon-user"> {{ video.username }} </i></router-link> · {{ video.pubDate }}
+          </span>
+        </div>
+      </el-card>
+    </div>
+  </el-col>
+</template>
+
+<script>
+import { handleVisited } from 'assets/js/utils'
+
+export default {
+  name: 'ImageCard',
+  filters: {
+    ellipsis(value) {
+      if (!value) return ''
+      const max = 20
+      if (value.length > max) {
+        return value.slice(0, max) + '...'
+      }
+      return value
+    }
+  },
+  props: {
+    video: {
+      type: Object,
+      default: null
+    },
+    // 时间前的描述
+    dateTit: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    getVisited(visited) {
+      return handleVisited(visited)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.time {
+  font-size: 15px;
+  color: #999;
+}
+
+.bottom {
+  margin-top: 13px;
+  line-height: 12px;
+}
+
+.tit {
+  font-weight: 700;
+  font-size: 18px;
+
+  height: 50px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2; /*行数*/
+  -webkit-box-orient: vertical;
+}
+
+.num {
+  position: relative;
+  font-size: 15px;
+  padding-top: 9px;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  .tit {
+    font-weight: 600;
+    font-size: 12px;
+    height: 32px;
+  }
+  .time {
+    font-size: 10px;
+    color: #999;
+  }
+  .num {
+    font-size: 9px;
+    padding-top: 3px;
+  }
+  .bottom {
+    margin-top: 2px;
+    line-height: 7px;
+  }
+  .coverImg {
+    height: 120px !important;
+  }
+}
+
+.coverImg {
+  width: 100%;
+  height: 175px;
+  display: block;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.card {
+  margin-bottom: 20px;
+  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+}
+
+/*.card:hover {
+  !*鼠标放上之后元素变成1.06倍大小*!
+  transform: scale(1.06);
+}*/
+.imgs {
+  position: relative;
+}
+.play-icon {
+  position: absolute;
+  /*top: -15px;*/
+  right: 2%;
+  bottom: 5px;
+  z-index: 7;
+  width: 40px;
+}
+</style>

+ 156 - 0
src/components/card/StatusCard.vue

@@ -0,0 +1,156 @@
+<template>
+  <el-card :body-style="{ padding: '0px' }" class="card">
+    <router-link target="_blank" :to="`/video/${video.videoId}`">
+      <div class="imgs">
+        <el-image
+          lazy
+          fit="cover"
+          :src="video.coverUrl"
+          class="coverImg"
+        />
+        <span style="position: absolute; bottom: 0; left: 0; color:red">
+          <i class="el-icon-video-play">{{ getVisited(video.viewCount) }}</i>
+        </span>
+        <span style="position: absolute; bottom: 0; left: 15%; color:red">
+          <i class="el-icon-s-comment">{{ getVisited(video.commentCount) }}</i>
+        </span>
+        <span style="position: absolute; bottom: 0; right: 0; color:red"> 12:11 </span>
+      </div>
+    </router-link>
+    <div style="padding: 14px">
+      <router-link target="_blank" :to="`/video/${video.videoId}`">
+        <span style="left: 0;margin-bottom: 0px;color: black;">{{ video.title | ellipsis }}</span>
+      </router-link>
+    </div>
+    <div style="padding: 14px">
+      <span style="left: 0;margin-bottom: 0px;color: black;">
+        <router-link target="_blank" :to="`/u/${video.userId}`"><i class="el-icon-user"> {{ video.username }} </i></router-link> · {{ video.pubDate }}
+      </span>
+    </div>
+  </el-card>
+</template>
+
+<script>
+import { handleVisited } from 'assets/js/utils'
+
+export default {
+  name: 'StatusCard',
+  filters: {
+    ellipsis(value) {
+      if (!value) return ''
+      const max = 20
+      if (value.length > max) {
+        return value.slice(0, max) + '...'
+      }
+      return value
+    }
+  },
+  props: {
+    video: {
+      type: Object,
+      default: null
+    },
+    // 时间前的描述
+    dateTit: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    getVisited(visited) {
+      return handleVisited(visited)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.time {
+  font-size: 15px;
+  color: #999;
+}
+
+.bottom {
+  margin-top: 13px;
+  line-height: 12px;
+}
+
+.tit {
+  font-weight: 700;
+  font-size: 18px;
+
+  height: 50px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2; /*行数*/
+  -webkit-box-orient: vertical;
+}
+
+.num {
+  position: relative;
+  font-size: 15px;
+  padding-top: 9px;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  .tit {
+    font-weight: 600;
+    font-size: 12px;
+    height: 32px;
+  }
+  .time {
+    font-size: 10px;
+    color: #999;
+  }
+  .num {
+    font-size: 9px;
+    padding-top: 3px;
+  }
+  .bottom {
+    margin-top: 2px;
+    line-height: 7px;
+  }
+  .coverImg {
+    height: 120px !important;
+  }
+}
+
+.coverImg {
+  width: 100%;
+  height: 175px;
+  display: block;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.card {
+  margin-bottom: 20px;
+  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+}
+
+/*.card:hover {
+  !*鼠标放上之后元素变成1.06倍大小*!
+  transform: scale(1.06);
+}*/
+.imgs {
+  position: relative;
+}
+.play-icon {
+  position: absolute;
+  /*top: -15px;*/
+  right: 2%;
+  bottom: 5px;
+  z-index: 7;
+  width: 40px;
+}
+</style>

+ 61 - 0
src/components/card/TextCard.vue

@@ -0,0 +1,61 @@
+<template>
+  <el-card :body-style="{ padding: '0px' }" class="card">
+    <el-input
+      v-model="textarea"
+      type="textarea"
+      :rows="3"
+      placeholder="有什么新鲜事想分享给大家?"
+    />
+    <el-upload
+      action="https://jsonplaceholder.typicode.com/posts/"
+    >
+      <el-button><i class="el-icon-picture-outline">图片</i></el-button>
+    </el-upload>
+    <el-dialog :visible.sync="dialogVisible">
+      <img width="100%" :src="dialogImageUrl" alt="">
+    </el-dialog>
+    <el-button round type="submit">发送</el-button>
+  </el-card>
+</template>
+
+<script>
+export default {
+  name: 'TextCard',
+  data() {
+    return {
+      textarea: '',
+      dialogImageUrl: '',
+      dialogVisible: false
+    }
+  },
+  methods: {
+    handleRemove(file, fileList) {
+      console.log(file, fileList)
+    },
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url
+      this.dialogVisible = true
+    }
+  }
+}
+</script>
+
+<style scoped>
+.el-button--submit.is-active,
+.el-button--submit:active {
+  background: #20B2AA;
+  border-color: #20B2AA;
+  color: #fff;
+}
+.el-button--submit:focus,
+.el-button--submit:hover {
+  background: #48D1CC;
+  border-color: #48D1CC;
+  color: #fff;
+}
+.el-button--submit {
+  color: #FFF;
+  background-color: #20B2AA;
+  border-color: #20B2AA;
+}
+</style>

+ 164 - 0
src/components/card/VideoCard.vue

@@ -0,0 +1,164 @@
+<template>
+  <el-col style="padding-right: 7px; padding-left: 7px">
+    <div style="cursor: pointer" :title="video.title">
+      <el-card :body-style="{ padding: '0px' }" class="card">
+        <router-link target="_blank" :to="`/video/${video.videoId}`">
+          <div class="imgs">
+            <el-image
+              lazy
+              fit="cover"
+              :src="video.coverUrl"
+              class="coverImg"
+            />
+            <span style="position: absolute; bottom: 0; left: 0; color:red">
+              <i v-if="video.viewCount === 0" class="el-icon-mobile-phone" />
+              <i v-else class="el-icon-monitor" />
+            </span>
+            <span style="position: absolute; bottom: 0; left: 10%; color:red">
+              <i class="el-icon-video-play">{{ getVisited(video.viewCount) }}</i>
+            </span>
+            <span style="position: absolute; bottom: 0; left: 30%; color:red">
+              <i class="el-icon-s-comment">{{ getVisited(video.commentCount) }}</i>
+            </span>
+            <span style="position: absolute; bottom: 0; right: 0; color:red"> {{ video.duration }} </span>
+          </div>
+        </router-link>
+        <div style="padding: 14px">
+          <router-link target="_blank" :to="`/video/${video.videoId}`">
+            <span style="left: 0;margin-bottom: 0px;color: black;">{{ video.title | ellipsis }}</span>
+          </router-link>
+        </div>
+        <div style="padding: 14px">
+          <span style="left: 0;margin-bottom: 0px;color: black;">
+            <router-link target="_blank" :to="`/u/${video.userId}`"><i class="el-icon-user"> {{ video.username }} </i></router-link> · {{ video.pubDate }}
+          </span>
+        </div>
+      </el-card>
+    </div>
+  </el-col>
+</template>
+
+<script>
+import { handleVisited } from 'assets/js/utils'
+
+export default {
+  name: 'VideoCard',
+  filters: {
+    ellipsis(value) {
+      if (!value) return ''
+      const max = 20
+      if (value.length > max) {
+        return value.slice(0, max) + '...'
+      }
+      return value
+    }
+  },
+  props: {
+    video: {
+      type: Object,
+      default: null
+    },
+    // 时间前的描述
+    dateTit: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    getVisited(visited) {
+      return handleVisited(visited)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.time {
+  font-size: 15px;
+  color: #999;
+}
+
+.bottom {
+  margin-top: 13px;
+  line-height: 12px;
+}
+
+.tit {
+  font-weight: 700;
+  font-size: 18px;
+
+  height: 50px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2; /*行数*/
+  -webkit-box-orient: vertical;
+}
+
+.num {
+  position: relative;
+  font-size: 15px;
+  padding-top: 9px;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  .tit {
+    font-weight: 600;
+    font-size: 12px;
+    height: 32px;
+  }
+  .time {
+    font-size: 10px;
+    color: #999;
+  }
+  .num {
+    font-size: 9px;
+    padding-top: 3px;
+  }
+  .bottom {
+    margin-top: 2px;
+    line-height: 7px;
+  }
+  .coverImg {
+    height: 120px !important;
+  }
+}
+
+.coverImg {
+  width: 100%;
+  height: 175px;
+  display: block;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.card {
+  margin-bottom: 20px;
+  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+}
+
+/*.card:hover {
+  !*鼠标放上之后元素变成1.06倍大小*!
+  transform: scale(1.06);
+}*/
+.imgs {
+  position: relative;
+}
+.play-icon {
+  position: absolute;
+  /*top: -15px;*/
+  right: 2%;
+  bottom: 5px;
+  z-index: 7;
+  width: 40px;
+}
+</style>

+ 0 - 51
src/components/card/answer-card.vue

@@ -1,51 +0,0 @@
-<template>
-  <v-card
-    class-name="mx-auto"
-    color="white"
-    width="720"
-  >
-    <v-card-title>
-      <v-avatar>
-        <img
-          :src="answer.avatarUrl"
-          alt="social"
-        >
-      </v-avatar>
-      <span class="text-body-1 font-weight-light">{{ answer.username }}</span>
-      <span class="text-body-1 font-weight-light">{{ answer.intro }}</span>
-    </v-card-title>
-
-    <v-card-text class-name="text-h5 font-weight-bold">
-      <span v-html="answer.content" />
-    </v-card-text>
-    <v-icon
-      small
-      left
-    >
-      mdi-twitter
-    </v-icon>
-  </v-card>
-</template>
-
-<script>
-
-export default {
-  name: 'AnswerCard',
-  props: {
-    answer: {
-      type: Object,
-      default: () => {
-      }
-    }
-  },
-  data() {
-    return {
-    }
-  },
-  methods: {
-  }
-}
-</script>
-
-<style lang="scss">
-</style>

+ 0 - 42
src/components/card/audio-card.vue

@@ -1,42 +0,0 @@
-<template>
-  <div style="width: 640px">
-    <v-card
-      color="white"
-    >
-      <v-card-title>
-        <router-link target="_blank" :to="`/audio/${item.audioId}`">
-          <div style="position: relative; width: 200px; height: 60px;">
-            <span style="position: absolute; right: 0; color:deepskyblue">{{ item.title }}</span>
-          </div>
-        </router-link>
-      </v-card-title>
-      <v-card-text>
-        <span class="text-body-1 font-weight-light">{{ item.pubDate }}</span>
-      </v-card-text>
-    </v-card>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'AudioCard',
-  props: {
-    item: {
-      type: Object,
-      default: () => {
-      }
-    }
-  },
-  data() {
-    return {
-    }
-  },
-  created() {
-  },
-  methods: {
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 84
src/components/card/audio-card1.vue

@@ -1,84 +0,0 @@
-<template>
-  <div style="width: 640px">
-    <v-card
-      color="white"
-    >
-      <v-card-title>
-        <!--        <v-avatar>
-          <img
-            :src="item.avatarUrl"
-            alt="social"
-          >
-        </v-avatar>-->
-        <span class="text-body-1 font-weight-light">{{ item.userId }}</span>
-        <span class="text-body-1 font-weight-light">{{ item.pubDate }}</span>
-      </v-card-title>
-
-      <v-card-text class-name="text-h5 font-weight-bold">
-        <span v-html="item.title" />
-        <div>
-          <audio-player
-            ref="audioPlayer"
-            :show-prev-button="false"
-            :show-next-button="false"
-            :show-playback-rate="false"
-            :audio-list="audioList.map(elm => elm.url)"
-            :before-play="handleBeforePlay"
-            theme-color="#87CEFA"
-          />
-        </div>
-      </v-card-text>
-      <v-icon
-        small
-        left
-      >
-        mdi-twitter
-      </v-icon>
-    </v-card>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'AudioCard',
-  props: {
-    item: {
-      type: Object,
-      default: () => {
-      }
-    }
-  },
-  data() {
-    return {
-      currentAudioName: '',
-      audioList: []
-    }
-  },
-  created() {
-    this.audioList = [
-      { name: this.item.title, url: this.item.audioUrl }
-    ]
-  },
-  methods: {
-    // 播放前做的事
-    handleBeforePlay(next) {
-      // 这里可以做一些事情...
-      this.currentAudioName = this.audioList[this.$refs.audioPlayer.currentPlayIndex].name
-
-      next() // 开始播放
-    },
-    handlePlaySpecify() {
-      this.$refs.audioPlayer.currentPlayIndex = 1
-      this.$nextTick(() => {
-        this.$refs.audioPlayer.play()
-        this.title = this.audioList[
-          this.$refs.audioPlayer.currentPlayIndex
-        ].name
-      })
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 54
src/components/card/collection-card.vue

@@ -1,54 +0,0 @@
-<template>
-  <div style="width: 240px; height: 180px">
-    <router-link :to="`/video/${videoInfo.videoId}`">
-      <div style="position: relative;">
-        <v-img
-          :src="videoInfo.coverUrl"
-          outlined
-          aspect-ratio="1.77"
-        />
-        <span style="position: absolute; bottom: 0; right: 0; color:red">{{ videoInfo.duration }}</span>
-      </div>
-    </router-link>
-    <v-row>
-      <v-col cols="10">
-        <p class="text-over" style="font-size: 15px; margin-bottom: 0px;color: black;">
-          <router-link :to="`/video/${videoInfo.videoId}`" style="color: black;"> {{ videoInfo.title }} </router-link>
-        </p>
-        <p>
-          收藏于: {{ videoInfo.createAt }}
-        </p>
-      </v-col>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import TimeUtil from '@/utils/time-util.vue'
-export default {
-  name: 'CollectionCard',
-  props: {
-    video: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      TimeUtil,
-      videoInfo: this.video
-    }
-  },
-  created() {
-  }
-}
-</script>
-
-<style>
-.text-over {
-  /* 指定宽度 */
-  white-space: nowrap;  /* 不换行,如果是span就可以不用这个 */
-  overflow: hidden;     /* 溢出内容隐藏 */
-  text-overflow: ellipsis; /* 文本溢出时显示省略号 */
-}
-</style>

+ 0 - 331
src/components/card/comment-card.vue

@@ -1,331 +0,0 @@
-<template>
-  <div v-infinite-scroll="loadMore" infinite-scroll-disabled="true" infinite-scroll-distance="10">
-    <v-row>
-      <v-col>
-        {{ video.commentCount }} 条评论
-      </v-col>
-      <v-col>
-        <span @click="getNewestComments">最新</span>
-      </v-col>
-      <v-col>
-        <span @click="getHotComments">最热</span>
-      </v-col>
-    </v-row>
-    <v-divider />
-    <v-row>
-      <div ref="comment" :style="wrapStyle" class="comment-wrap" style="width: 720px; height: 1080px">
-        <Comment
-          v-model="videoComments"
-          :user="currentUser"
-          :props="props"
-          :before-submit="submit"
-          :before-like="like"
-          :before-delete="deleteComment"
-          :upload-img="uploadImg"
-        />
-      </div>
-    </v-row>
-    <v-snackbar
-      v-model="showMessage"
-      :top="true"
-      :timeout="1000"
-    >
-      {{ message }}
-      <template v-slot:action="{ attrs }">
-        <v-btn
-          color="pink"
-          text
-          v-bind="attrs"
-          @click="showMessage = false"
-        >
-          关闭
-        </v-btn>
-      </template>
-    </v-snackbar>
-  </div>
-</template>
-
-<script>
-import Comment from '@/components/comment'
-import { submitComment, videoComment } from '@/api/comment/comment'
-
-export default {
-  name: 'CommentCard',
-  components: {
-    Comment
-  },
-  props: {
-    video: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      commentCount: this.count,
-      videoComments: [],
-      wrapStyle: '',
-      props: {
-        id: 'commentId',
-        content: 'content',
-        imgSrc: 'imgSrc',
-        children: 'childrenComments',
-        likes: 'likes',
-        liked: 'liked',
-        reply: 'reply',
-        createAt: 'createAt',
-        user: 'user'
-      },
-      currentUser: null,
-      busy: false,
-      page: 1,
-      showMessage: false,
-      message: ''
-    }
-  },
-  created() {
-    const userInfo = this.$store.state.user.userInfo
-    if (userInfo !== null) {
-      this.currentUser = {
-        userId: userInfo.userId,
-        name: userInfo.username,
-        avatar: userInfo.avatarUrl
-      }
-    }
-    this.getVideoComment(this.video.videoId, 1)
-  },
-  mounted() {
-    // const header = this.$refs.header
-    const header = 10
-    this.wrapStyle = `height: calc(100vh - ${header.clientHeight + 20}px)`
-  },
-  methods: {
-    loadMore: function() {
-      this.busy = true
-      setTimeout(() => {
-        this.getVideoComment(this.video.videoId, this.page)
-      }, 1000)
-    },
-    getHotComments() {
-      console.log('获取热门评论')
-    },
-    getNewestComments() {
-      console.log('获取最新评论')
-    },
-    getVideoComment(videoId, page) {
-      videoComment(videoId, page).then(res => {
-        if (res.code === 0) {
-          this.page += 1
-          this.busy = false
-
-          for (const item of res.data.list) {
-            this.videoComments.push(item)
-          }
-        } else {
-          console.error(res.msg)
-        }
-      })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    // 评论相关方法
-    async submit(newComment, parent, add) {
-      const res = await new Promise((resolve) => {
-        setTimeout(() => {
-          var parentId = null
-          if (parent !== null) {
-            parentId = parent.id
-          }
-
-          const commentSbtData = {}
-          commentSbtData.videoId = this.video.videoId
-          commentSbtData.userId = newComment.user.userId
-          commentSbtData.parentId = parentId
-          commentSbtData.content = newComment.content
-          commentSbtData.imgSrc = newComment.imgSrc
-          commentSbtData.createAt = newComment.createAt
-          submitComment(commentSbtData).then(res => {
-            if (res.code !== 0) {
-              this.message = '评论发送失败'
-              this.showMessage = true
-            }
-          })
-
-          resolve({ newComment, parent })
-        }, 300)
-      })
-      add(Object.assign(res.newComment, { _id: new Date().getTime() }))
-    },
-    async like(comment) {
-      const res = await new Promise((resolve) => {
-        setTimeout(() => {
-          resolve(comment)
-        }, 0)
-      })
-
-      console.log('likeComment: ', res)
-    },
-    async uploadImg({ file, callback }) {
-      const res = await new Promise((resolve, reject) => {
-        const formData = new FormData()
-        formData.append('file', file)
-        fetch(`//api.reghao.cn/api/file/upload/image`, {
-          headers: {
-            'Authorization': 'Bearer ' + this.$store.getters.token
-          },
-          method: 'POST',
-          credentials: 'include',
-          body: formData
-        }).then(response => response.json())
-          .then(json => {
-            if (json.code === 0) {
-              /* this.videoPost.coverFileId = json.data.imageFileId
-              this.videoPost.imageUrl = json.data.imageUrl*/
-              resolve(json.data.imageUrl)
-            } else {
-              this.message = '图片上传失败,请重试!' + json.message
-              this.showMessage = true
-            }
-          })
-          .catch(e => {
-            reject(e)
-          })
-
-        /* 读取本地文件为 base64 编码并显示
-        const reader = new FileReader()
-        reader.readAsDataURL(file)
-        reader.onload = () => {
-          resolve(reader.result)
-        }
-        reader.onerror = () => {
-          reject(reader.error)
-        }*/
-      })
-
-      callback(res)
-    },
-    async deleteComment(comment, parent) {
-      const res = await new Promise((resolve) => {
-        setTimeout(() => {
-          resolve({ comment, parent })
-        }, 300)
-      })
-      console.log('deleteComment: ', res)
-    }
-  }
-}
-</script>
-
-<style lang="scss">
-  .category-link {
-    color: #999;
-  }
-  a {
-    text-decoration: none;
-  }
-  @mixin scroll-style(
-    $thumb: rgba(255, 255, 255, 0.6),
-    $track: rgba(255, 255, 255, 0)
-  ) {
-    &::-webkit-scrollbar,
-    &::-webkit-scrollbar-thumb,
-    &::-webkit-scrollbar-track {
-      border: none;
-      box-shadow: none;
-    }
-    &::-webkit-scrollbar {
-      width: 4px;
-    }
-    &::-webkit-scrollbar-thumb {
-      border-radius: 2px;
-      background: $thumb;
-    }
-    &::-webkit-scrollbar-track {
-      background: $track;
-    }
-  }
-  * {
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-  }
-
-  html {
-    font-size: 14px;
-  }
-
-  html,
-  body,
-  #app {
-    height: 100%;
-  }
-
-  @media screen and (min-width: 320px) {
-    html {
-      font-size: calc(14px + 4 * ((100vw - 320px) / (1200 - 320)));
-    }
-  }
-
-  @media screen and (min-width: 1200px) {
-    html {
-      font-size: 18px;
-    }
-  }
-
-  .change-role {
-    background: #1c2433;
-    color: #eee;
-    padding: 1rem;
-    display: flex;
-    justify-content: center;
-    align-content: center;
-    .change {
-      cursor: pointer;
-      padding: 0.4rem;
-      margin-right: 2rem;
-      font-size: 0.9rem;
-      border: 1px solid #e99210;
-      border-radius: 5px;
-      user-select: none;
-      &:hover {
-        opacity: 0.9;
-      }
-    }
-    .current-role {
-      min-width: 15rem;
-      color: #e99210;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      border: 1px dashed #e99210;
-      padding: 0 1rem;
-      img {
-        width: 1.5rem;
-        height: 1.5rem;
-        margin-right: 0.5rem;
-        border: 1px solid #eee;
-        border-radius: 50%;
-      }
-    }
-  }
-
-  .comment-wrap {
-    overflow: auto;
-    @include scroll-style(#db8f1c, #b9b9b9);
-  }
-
-  @media screen and (min-width: 760px) {
-    body {
-      margin: 0 10%;
-      border: 1px dashed #eee;
-    }
-  }
-
-  @media screen and (max-width: 500px) {
-    .change-role .current-role {
-      min-width: 5rem;
-      padding: 0 0.5rem;
-    }
-  }
-</style>

+ 0 - 53
src/components/card/history-card.vue

@@ -1,53 +0,0 @@
-<template>
-  <div style="width: 320px">
-    <router-link :to="`/video/${videoInfo.videoId}`">
-      <div style="position: relative; width: 320px; height: 180px;">
-        <v-img
-          :src="videoInfo.coverUrl"
-          outlined
-          aspect-ratio="1.77"
-        />
-        <span style="position: absolute; bottom: 0; right: 0; color:red">{{ videoInfo.duration }}</span>
-      </div>
-    </router-link>
-    <v-row>
-      <v-col cols="2">
-        <router-link :to="`/u/${videoInfo.userId}`">
-          <v-avatar size="48">
-            <v-img :src="videoInfo.avatarUrl" />
-          </v-avatar>
-        </router-link>
-      </v-col>
-      <v-col cols="10">
-        <p class="text-over" style="font-size: 15px; margin-bottom: 0px;color: black;">
-          <router-link :to="`/video/${videoInfo.videoId}`" style="color: black;"> {{ videoInfo.title }} </router-link>
-        </p>
-        <p style="font-size: 10px; color: #606060;">
-          <router-link :to="`/u/${videoInfo.userId}`"> {{ videoInfo.username }}</router-link>
-        </p>
-      </v-col>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import TimeUtil from '@/utils/time-util.vue'
-export default {
-  name: 'History',
-  props: {
-    video: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      TimeUtil,
-      videoInfo: this.video
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 59
src/components/card/item-card.vue

@@ -1,59 +0,0 @@
-<template>
-  <!-- padding-left: 10px; padding-right: 10px; -->
-  <div style="width: 320px">
-    <router-link :to="`/video/${videoInfo.videoId}`">
-      <div style="position: relative; width: 320px; height: 180px;">
-        <v-img
-          :src="videoInfo.coverUrl"
-          outlined
-          aspect-ratio="1.77"
-        />
-        <span style="position: absolute; bottom: 0; right: 0; color:red">{{ videoInfo.duration }}</span>
-      </div>
-    </router-link>
-    <v-row>
-      <v-col cols="2">
-        <router-link v-if="videoInfo.userId !== null" :to="`/u/${videoInfo.userId}`">
-          <v-avatar size="48">
-            <v-img :src="videoInfo.avatarUrl" />
-          </v-avatar>
-        </router-link>
-      </v-col>
-      <v-col cols="10">
-        <p style="font-size: 15px; margin-bottom: 0px;color: black;">
-          <router-link :to="`/video/${videoInfo.videoId}`" style="color: black;"> {{ videoInfo.title }} </router-link>
-        </p>
-        <p style="font-size: 10px; color: #606060;">
-          <router-link v-if="videoInfo.userId !== null" :to="`/u/${videoInfo.userId}`"> {{ videoInfo.username }}</router-link>
-          <br>
-          {{ videoInfo.viewCount }} 观看 <span v-html="`&nbsp;&nbsp;`" />
-          {{ videoInfo.commentCount }} 评论 <span v-html="`&nbsp;&nbsp;`" />
-        </p>
-      </v-col>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import TimeUtil from '@/utils/time-util.vue'
-export default {
-  name: 'ItemCard',
-  props: {
-    video: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      TimeUtil,
-      videoInfo: this.video
-    }
-  },
-  created() {
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 59
src/components/card/live-card.vue

@@ -1,59 +0,0 @@
-<template>
-  <!-- padding-left: 10px; padding-right: 10px; -->
-  <div style="width: 320px">
-    <router-link :to="`/video/${videoInfo.videoId}`">
-      <div style="position: relative; width: 320px; height: 180px;">
-        <v-img
-          :src="videoInfo.coverUrl"
-          outlined
-          aspect-ratio="1.77"
-        />
-        <span style="position: absolute; bottom: 0; right: 0; color:red">{{ videoInfo.duration }}</span>
-      </div>
-    </router-link>
-    <v-row>
-      <v-col cols="2">
-        <router-link v-if="videoInfo.userId !== null" :to="`/u/${videoInfo.userId}`">
-          <v-avatar size="48">
-            <v-img :src="videoInfo.avatarUrl" />
-          </v-avatar>
-        </router-link>
-      </v-col>
-      <v-col cols="10">
-        <p style="font-size: 15px; margin-bottom: 0px;color: black;">
-          <router-link :to="`/video/${videoInfo.videoId}`" style="color: black;"> {{ videoInfo.title }} </router-link>
-        </p>
-        <p style="font-size: 10px; color: #606060;">
-          <router-link v-if="videoInfo.userId !== null" :to="`/u/${videoInfo.userId}`"> {{ videoInfo.username }}</router-link>
-          <br>
-          {{ videoInfo.viewCount }} 观看 <span v-html="`&nbsp;&nbsp;`" />
-          {{ videoInfo.commentCount }} 评论 <span v-html="`&nbsp;&nbsp;`" />
-        </p>
-      </v-col>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import TimeUtil from '@/utils/time-util.vue'
-export default {
-  name: 'LiveCard',
-  props: {
-    video: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      TimeUtil,
-      videoInfo: this.video
-    }
-  },
-  created() {
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 37
src/components/card/question-card.vue

@@ -1,37 +0,0 @@
-<template>
-  <v-card
-    class-name="mx-auto"
-    color="white"
-    width="720"
-  >
-    <v-card-title>
-      <span class="text-body-1 font-weight-light">{{ question.title }}</span>
-    </v-card-title>
-
-    <v-card-text class-name="text-h5 font-weight-bold">
-      <span v-html="question.detail" />
-    </v-card-text>
-  </v-card>
-</template>
-
-<script>
-export default {
-  name: 'QuestionCard',
-  props: {
-    question: {
-      type: Object,
-      default: () => {
-      }
-    }
-  },
-  data() {
-    return {
-    }
-  },
-  methods: {
-  }
-}
-</script>
-
-<style lang="scss">
-</style>

+ 0 - 53
src/components/card/reply-card.vue

@@ -1,53 +0,0 @@
-<template>
-  <div>
-    <v-row>
-      <v-col>
-        <router-link v-if="this.$store.state.user.userInfo" :to="`/u/${this.$store.state.user.userInfo.userId}`">
-          <v-avatar size="32">
-            <v-img
-              :src="this.$store.state.user.userInfo.avatarUrl"
-              :alt="this.$store.state.user.userInfo.username"
-              :title="this.$store.state.user.userInfo.username"
-            />
-          </v-avatar>
-        </router-link>
-      </v-col>
-      <v-col>
-        <span>{{ reply.content }}</span>
-      </v-col>
-    </v-row>
-    <v-divider />
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'ReplyCard',
-  props: {
-    reply: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      commentCount: this.count,
-      videoComment: ''
-    }
-  },
-  created() {
-    this.getComments()
-  },
-  methods: {
-    getComments() {
-      console.log('获取评论')
-    },
-    submitComment() {
-      console.log('发布评论: ' + this.videoComment)
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 145
src/components/card/status-card.vue

@@ -1,145 +0,0 @@
-<template>
-  <div style="width: 640px">
-    <v-card
-      color="white"
-    >
-      <v-card-title>
-        <v-avatar>
-          <img
-            :src="item.avatarUrl"
-            alt="social"
-          >
-        </v-avatar>
-        <span class="text-body-1 font-weight-light">{{ item.username }}</span>
-        <span class="text-body-1 font-weight-light">{{ item.createdAt }}</span>
-      </v-card-title>
-
-      <v-card-text class-name="text-h5 font-weight-bold">
-        <span v-html="item.text" />
-        <v-row v-if="item.imageUrls.length !== 0">
-          <v-col
-            v-for="imageUrl in item.imageUrls"
-            :key="imageUrl.thumbnailUrl"
-            class="d-flex child-flex"
-            cols="4"
-          >
-            <v-img
-              :src="imageUrl.thumbnailUrl"
-              :lazy-src="imageUrl.thumbnailUrl"
-              aspect-ratio="1"
-              class="grey lighten-2"
-              @click="showImage(item.imageUrls)"
-            >
-              <template v-slot:placeholder>
-                <v-row
-                  class="fill-height ma-0"
-                  align="center"
-                  justify="center"
-                >
-                  <v-progress-circular
-                    indeterminate
-                    color="grey lighten-5"
-                  />
-                </v-row>
-              </template>
-            </v-img>
-          </v-col>
-        </v-row>
-      </v-card-text>
-      <v-icon
-        small
-        left
-      >
-        mdi-twitter
-      </v-icon>
-    </v-card>
-  </div>
-</template>
-
-<script>
-import * as DateUtils from '@/utils/date-utils'
-import * as StringUtils from '@/utils/string-utils'
-import { mapActions } from 'vuex'
-
-export default {
-  name: 'StatusCard',
-  props: {
-    item: {
-      type: Object,
-      default: () => {
-      }
-    }
-  },
-  data() {
-    return {
-    }
-  },
-  methods: {
-    ...mapActions([
-      'setImageZoom',
-      'setDetailContent'
-    ]),
-    showImage(imageUrls) {
-      const imgs = []
-      for (const i of imageUrls) {
-        imgs.push(i.originalUrl)
-      }
-      this.$viewerApi({
-        images: imgs,
-        options: {
-          movable: true,
-          fullscreen: false,
-          keyboard: true
-        }
-      })
-    },
-    goDetailContent() {
-      this.setDetailContent(this.item)
-      this.$router.push({ name: 'detail-content' })
-    },
-    formatTime(time) {
-      return DateUtils.format(time)
-    },
-    compareImgSize(size) {
-      const count = size / 3
-      if (count >= 1) {
-        return true
-      } else {
-        return false
-      }
-    },
-    formatThumbImg(img) {
-      return StringUtils.formatImgThumb(img)
-    },
-    formatMidImg(img) {
-      return StringUtils.formatImgMiddle(img)
-    },
-    formatNum(num) {
-      return StringUtils.formatNum(num)
-    },
-    imageZoom(url) {
-      this.setImageZoom(this.formatMidImg(url))
-      this.$router.push({ name: 'imageZoom' })
-    },
-    imgClass(size) {
-      let clazz = ''
-      switch (size) {
-        case 1:
-          clazz = 'img-li-one'
-          break
-        case 2:
-        case 4:
-          clazz = 'img-li-two'
-          break
-        default:
-          clazz = 'img-li-other'
-          break
-      }
-      return clazz
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 67
src/components/card/video-card.vue

@@ -1,67 +0,0 @@
-<!-- TODO 使用 item-card 页面替换 -->
-<template>
-  <!-- padding-left: 10px; padding-right: 10px; -->
-  <div style="width: 320px">
-    <router-link :to="`/video/${videoInfo.videoId}`">
-      <div style="position: relative; width: 320px; height: 180px;">
-        <v-img
-          :src="videoInfo.coverUrl"
-          outlined
-          aspect-ratio="1.77"
-        />
-        <span style="position: absolute; bottom: 0; right: 0; color:red">{{ videoInfo.duration }}</span>
-      </div>
-    </router-link>
-    <v-row>
-      <v-col cols="2">
-        <router-link :to="`/u/${videoInfo.userId}`">
-          <v-avatar size="48">
-            <v-img :src="videoInfo.avatarUrl" />
-          </v-avatar>
-        </router-link>
-      </v-col>
-      <v-col cols="10">
-        <p class="text-over" style="font-size: 15px; margin-bottom: 0px;color: black;">
-          <router-link :to="`/video/${videoInfo.videoId}`" style="color: black;"> {{ videoInfo.title }} </router-link>
-        </p>
-        <p style="font-size: 10px; color: #606060;">
-          <router-link :to="`/u/${videoInfo.userId}`"> {{ videoInfo.username }}</router-link>
-          <br>
-          {{ videoInfo.viewCount }} 观看 <span v-html="`&nbsp;&nbsp;`" />
-          {{ videoInfo.commentCount }} 评论 <span v-html="`&nbsp;&nbsp;`" />
-          <span v-text="TimeUtil.timeToNowStrning(videoInfo.pubDate)" />
-        </p>
-      </v-col>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import TimeUtil from '@/utils/time-util.vue'
-export default {
-  name: 'UserVideoCard',
-  props: {
-    video: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      TimeUtil,
-      videoInfo: this.video
-    }
-  },
-  created() {
-  }
-}
-</script>
-
-<style>
-.text-over {
-  /* 指定宽度 */
-  white-space: nowrap;  /* 不换行,如果是span就可以不用这个 */
-  overflow: hidden;     /* 溢出内容隐藏 */
-  text-overflow: ellipsis; /* 文本溢出时显示省略号 */
-}
-</style>

+ 215 - 0
src/components/categorybtn/CategoryBtn.vue

@@ -0,0 +1,215 @@
+<template>
+  <div id="category-btn">
+    <el-button
+      v-for="(item, index) in videoCategory"
+      :key="index"
+      type="primary"
+      size="medium"
+      :plain="currentIndex != index"
+      circle
+      @click="btnClick(item.cid, index)"
+    >{{ item.cname }}
+    </el-button>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'CategoryBtn',
+  data() {
+    return {
+      currentIndex: 0,
+      // 电影分类
+      videoCategory: [
+        { cname: '电影', cid: 11011 },
+        { cname: '音乐', cid: 11012 },
+        { cname: '新闻', cid: 11013 }
+      ]
+    }
+  },
+  created() {
+    // getCategory().then((res) => (this.videoCategory = res))
+    const pageBean = {
+      totalCount: 20,
+      list: [
+        {
+          vname: '哈哈哈哈哈哈哈哈哈哈哈哈1111111111111111111111111111111111',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈1',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈哈哈哈哈哈哈哈哈哈哈哈1111111111111111111111111111111111',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈1',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10,
+          keyword: '芒果,草莓,西瓜'
+        }
+      ]
+    }
+    this.$store.commit('getPageBean', pageBean)
+
+    // 当组件加载的时候默认当前活跃页
+    /*    getPageBean(this.currentIndex + 1, 1, 6).then((res) => {
+      this.$store.commit('getPageBean', res)
+    })*/
+  },
+  methods: {
+    btnClick(cid, index) {
+      this.currentIndex = index
+      // 页码变为1
+      this.$store.commit('updatePage', 1)
+      this.$store.commit('saveCid', cid)
+      this.$store.dispatch('getPageBean')
+    }
+  }
+}
+</script>
+
+<style scoped>
+#category-btn {
+  padding-left: 14%;
+  padding-right: 6%;
+  padding-top: 20px;
+}
+el-btn {
+  background: #409eff;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  #category-btn {
+    padding-left: 0.5%;
+    padding-right: 0.5%;
+    padding-top: 3%;
+    text-align: center;
+  }
+}
+</style>

+ 197 - 0
src/components/comment/Comment.vue

@@ -0,0 +1,197 @@
+<template>
+  <div id="comment">
+    <!--评论条数-->
+    <el-row
+      style="font-size: 20px; padding-top: 10px"
+    >{{ comments.length }}条评论</el-row>
+    <!--评论框-->
+    <el-row type="flex" align="middle" class="content">
+      <el-col :span="2">
+        <img
+          v-if="!$user"
+          class="u-avatar"
+          src="@/assets/img/icon/avatar.png"
+          alt=""
+        >
+        <img v-else class="u-avatar" :src="user.avatarurl" alt="">
+      </el-col>
+      <el-col :span="15" :offset="1">
+        <el-input
+          v-model="content"
+          type="textarea"
+          :rows="3"
+          :disabled="!$user"
+          :placeholder="$user ? '请输入评论' : '登陆后才可评论!'"
+        />
+      </el-col>
+      <el-col :span="6" :offset="1">
+        <el-button
+          type="primary"
+          :disabled="!$user"
+          @click.native="addComment"
+        >发布评论</el-button>
+      </el-col>
+    </el-row>
+    <!--评论内容-->
+    <el-row
+      v-if="comments.length === 0"
+      style="height: 100px"
+      type="flex"
+      align="middle"
+      justify="center"
+    >暂无评论!</el-row>
+    <el-row v-for="(comment, index) in comments" :key="index" class="content">
+      <el-col :span="2">
+        <img class="u-avatar" :src="comment.avatarurl">
+      </el-col>
+      <el-col :span="21" :offset="1">
+        <div class="u-nickname">{{ comment.nickname }}</div>
+        <span class="time">{{ getDate(comment.date) }}</span>
+        <!--	<el-popover
+								placement="top"
+								width="150"
+								trigger="hover">
+					<p>确定删除该条评论吗?</p>
+					<div style="text-align: right; margin: 0">
+						<el-button type="primary" size="mini" @click="deleteComment(comment.commentId)">确定</el-button>
+					</div>
+					<i class="el-icon-delete" slot="reference">删除</i>
+				</el-popover>-->
+        <div class="comment">
+          {{ comment.content }}
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { formatDate } from 'assets/js/utils'
+
+export default {
+  name: 'Comment',
+  components: {},
+  props: {
+    vid: Number
+  },
+  data() {
+    return {
+      content: '',
+      comments: [],
+      user: {
+        uid: 11011,
+        nickname: '大西瓜',
+        avatarurl: 'https://picx.zhimg.com/v2-6fd63e002c1b9b7ee99cf635608f9467_xl.jpg'
+      }
+    }
+  },
+  watch: {
+    vid(newVal) {
+      // 加载当前视频的评论
+      /* getComments(newVal).then((res) => {
+        this.comments = res
+      })*/
+    }
+  },
+  created() {
+    this.comments = [
+      {
+        uid: 11011,
+        nickname: '大西瓜',
+        avatarurl: 'https://picx.zhimg.com/v2-6fd63e002c1b9b7ee99cf635608f9467_xl.jpg',
+        content: 'hhhhhhhhhhhhhhh',
+        date: new Date()
+      }
+    ]
+
+    // 加载当前视频的评论
+    /* getComments(this.vid).then((res) => {
+      this.comments = res
+    })*/
+  },
+  methods: {
+    getDate(date) {
+      return formatDate(new Date(date), 'yyyy-MM-dd hh:mm:ss')
+    },
+    // 添加评论
+    addComment() {
+      this.$message.info('发布评论成功!')
+      this.comments.splice(0, 0, {
+        nickname: '大芒果',
+        avatarurl: 'https://picx.zhimg.com/v2-6fd63e002c1b9b7ee99cf635608f9467_xl.jpg',
+        content: '一扫而散',
+        date: new Date()
+      })
+      this.content = ''
+
+      /* const user = this.$user
+      if (user) {
+        if (this.content === '' || this.content === null) {
+          this.$message.warning('评论内容不能为空!')
+          return
+        }
+        addComment(user.uid, this.vid, this.content).then((res) => {
+          if (res === 1) {
+            this.$message.info('发布评论成功!')
+            this.comments.splice(0, 0, {
+              avatarurl: user.avatarurl,
+              nickname: user.nickname,
+              content: this.content,
+              date: new Date()
+            })
+            this.content = ''
+          }
+        })
+      }*/
+    }
+    // 删除当前评论
+    // deleteComment(contentId) {
+    // 	deleteCommentById(contentId) // 删除评论
+    //
+    // }
+  }
+}
+</script>
+
+<style scoped>
+#comment {
+  border-top: 1px solid #9d9d9d;
+  margin-top: 20px;
+}
+
+.u-avatar {
+  width: 90%;
+  border-radius: 50%;
+}
+
+.u-nickname {
+  display: inline;
+  cursor: pointer;
+  color: #66b1ff;
+}
+
+.time {
+  margin-left: 10px;
+  font-size: 15px;
+  color: #9d9d9d;
+}
+
+.comment {
+  padding-top: 5px;
+  font-size: 20vm;
+  line-height: 1.7;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #9d9d9d;
+}
+
+.content {
+  margin-top: 20px;
+  padding-bottom: 20px;
+}
+
+.el-icon-delete {
+  cursor: pointer;
+  margin-left: 10px;
+  color: #9d9d9d;
+}
+</style>

+ 0 - 449
src/components/comment/components/CommentForm.vue

@@ -1,449 +0,0 @@
-<template>
-  <div :class="`${className} comment-form`">
-    <div class="avatar-box">
-      <slot />
-    </div>
-
-    <div class="form-box">
-      <div class="rich-input" :class="{ focus: focus || value }">
-        <div class="grow-wrap" :data-replicated-value="value">
-          <textarea
-            ref="input"
-            rows="1"
-            :value="value"
-            :placeholder="placeholder"
-            @input="(e) => (value = e.target.value)"
-            @focus="focus = true"
-            @blur="handleBlur"
-            @mousedown="closeEmojiSelector"
-          />
-        </div>
-        <div v-show="imgSrc" ref="image-preview-box" class="image-preview-box">
-          <div
-            v-show="imgSrc"
-            :style="`background-image: url(${imgSrc})`"
-            class="image"
-          />
-          <div class="clean-btn" @mousedown.prevent="removeImg">
-            <svg
-              aria-hidden="true"
-              width="15"
-              height="15"
-              viewBox="0 0 21 21"
-              class="icon close-icon"
-            >
-              <g fill="none" fill-rule="evenodd" transform="translate(1 1)">
-                <circle
-                  cx="9.5"
-                  cy="9.5"
-                  r="9.5"
-                  fill="#000"
-                  stroke="#FFF"
-                  opacity=".5"
-                />
-                <path
-                  fill="#FFF"
-                  d="M13.743 5.964L10.207 9.5l3.536 3.536-.707.707L9.5 10.207l-3.536 3.536-.707-.707L8.793 9.5 5.257 5.964l.707-.707L9.5 8.793l3.536-3.536z"
-                />
-              </g>
-            </svg>
-          </div>
-        </div>
-      </div>
-      <div
-        v-show="focus || value || imgSrc"
-        class="option-box"
-        @mousedown.prevent="closeEmojiSelector($refs.input.focus())"
-      >
-        <div
-          class="emoji emoji-btn"
-          @mousedown.prevent.stop="openEmojiSelector"
-        >
-          <div class="emoji-box">
-            <div class="icon" />
-            <span>表情</span>
-          </div>
-          <EmojiSelector
-            v-show="showEmojiSelector"
-            @choose="(v) => (value += v)"
-          />
-        </div>
-        <div class="image-btn" @mousedown.prevent="triggerUpload">
-          <svg
-            aria-hidden="true"
-            width="22"
-            height="22"
-            viewBox="0 0 22 22"
-            class="icon image-icon"
-          >
-            <g fill="none" fill-rule="evenodd">
-              <path d="M1 1h20v20H1z" />
-              <g transform="translate(2 3)">
-                <path
-                  stroke="#027FFF"
-                  stroke-width=".9"
-                  d="M2.28.667h13.44c1.075 0 1.947.871 1.947 1.946v10.774a1.947 1.947 0 0 1-1.947 1.946H2.28a1.947 1.947 0 0 1-1.947-1.946V2.613c0-1.075.872-1.946 1.947-1.946zM.333 12.499L5 8l9.01 7.333m-6.343-4.842L10.333 8l7.136 5.914"
-                />
-                <circle cx="13.5" cy="4.5" r="1.5" fill="#027FFF" />
-              </g>
-            </g>
-          </svg>
-          <span>图片</span>
-          <input
-            ref="upload"
-            class="upload-file"
-            type="file"
-            @change="handleChange"
-            @click="onUpload = true"
-          />
-        </div>
-        <slot name="submitBtn">
-          <button
-            class="submit-btn"
-            :disabled="!value && !imgSrc"
-            @click.stop="handleSubmit"
-          >
-            评论
-          </button>
-        </slot>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import EmojiSelector from './EmojiSelector'
-export default {
-  name: 'CommentForm',
-  components: { EmojiSelector },
-  props: {
-    placeholder: {
-      type: String,
-      default: '输入评论...',
-    },
-    id: {
-      type: [String, Number],
-      default: 'comment-root',
-    },
-    comment: {
-      type: Object,
-      default: () => {},
-    },
-    parent: {
-      type: Object,
-      default: () => {},
-    },
-    uploadImg: {
-      type: Function,
-      default: null,
-    },
-  },
-  data() {
-    return {
-      focus: false, // * 聚焦状态
-      value: '', // * 输入框值
-      imgSrc: '', // * 粘贴的图片src
-      showEmojiSelector: false, // * 表情选择框状态
-    }
-  },
-  computed: {
-    // 是否为顶部评论表单
-    isRoot() {
-      return this.id === 'comment-root'
-    },
-    // 是否为回复中的表单
-    isSub() {
-      return this.id.split('-').length === 3
-    },
-    className() {
-      return this.isRoot
-        ? 'comment-root'
-        : this.isSub
-        ? 'reply sub-reply'
-        : 'reply'
-    },
-  },
-  mounted() {
-    const richInput = this.$refs.input
-    !this.isRoot && richInput.focus()
-
-    richInput.addEventListener('paste', this.handlePaste)
-    this.$once('hook:beforeDestroy', () =>
-      richInput.removeEventListener('paste', this.handlePaste)
-    )
-  },
-  methods: {
-    // * 选择要上传的图片
-    handleChange(e) {
-      const files = e.target.files
-      if (!(files && files[0])) return
-      this.beforeSetImg(files[0])
-    },
-    // * 处理图片
-    async beforeSetImg(file) {
-      if (!/^image/.test(file.type)) {
-        throw new Error("file type must contain 'image'.")
-      }
-
-      if (typeof this.uploadImg === 'function') {
-        const callback = (src) => {
-          this.imgSrc = src
-        }
-        await this.uploadImg({ file, callback })
-        return
-      }
-
-      const reader = new FileReader()
-      reader.readAsDataURL(file)
-      reader.onload = () => {
-        this.imgSrc = reader.result
-      }
-      reader.onerror = () => {
-        throw new Error(
-          `read file errored, the error code is ${reader.error.code}.`
-        )
-      }
-    },
-    // * 点击图片触发上传
-    triggerUpload() {
-      this.$refs.upload.click()
-    },
-    // * 点击图片上的删除按钮
-    removeImg() {
-      this.imgSrc = ''
-      this.closeEmojiSelector()
-    },
-    // * textarea blur 事件
-    handleBlur(e) {
-      this.showEmojiSelector = false
-
-      if (this.onUpload) {
-        this.$nextTick(() => {
-          this.onUpload = false
-        })
-        return
-      }
-
-      if (this.value || this.imgSrc) return
-
-      this.focus = false
-
-      if (!this.isRoot) {
-        this.close()
-      }
-    },
-    // * textarea paste 事件
-    handlePaste(e) {
-      const file = e.clipboardData.files[0]
-      if (file) {
-        // 只处理复制图片
-        this.beforeSetImg(file)
-        e.preventDefault()
-      }
-    },
-    // * 点击评论
-    handleSubmit() {
-      if (!this.value.trim() && !this.imgSrc) return
-      const user = (this.comment && this.comment.user) || null
-
-      const data = {
-        id: this.id,
-        content: this.value,
-        imgSrc: this.imgSrc,
-        reply: (this.isSub && JSON.parse(JSON.stringify(user))) || null,
-        createAt: new Date().getTime(),
-        likes: 0,
-        callback: () => {
-          this.isRoot ? this.reset() : this.close()
-        },
-      }
-
-      if (!this.isSub) {
-        data.children = []
-      }
-
-      this.$emit('form-submit', { newComment: data, parent: this.parent })
-    },
-    // * 重置组件状态
-    reset() {
-      this.value = ''
-      this.imgSrc = ''
-      this.$refs.input.blur()
-    },
-    // * 销毁组件
-    close() {
-      this.$emit('form-delete', this.id)
-    },
-    // * 选择表情
-    openEmojiSelector() {
-      this.showEmojiSelector = !this.showEmojiSelector
-
-      if (document.activeElement === document.body) {
-        this.$refs.input.focus()
-      }
-      if (this.showEmojiSelector) {
-        // 移动光标到末尾
-        const input = this.$refs.input
-        input.selectionStart = input.selectionEnd = this.value.length
-      }
-    },
-    // * 关闭选择表情组件
-    closeEmojiSelector() {
-      if (this.showEmojiSelector) {
-        this.showEmojiSelector = false
-      }
-    },
-  },
-}
-</script>
-
-<style lang="scss" scoped>
-.comment-form {
-  max-width: 100%;
-  padding: 0.8rem 1.0664rem;
-  display: flex;
-  background-color: #fafbfc;
-  border-radius: 3px;
-  .avatar-box {
-    flex: 0 0 auto;
-    img {
-      margin: 0 0.8rem 0 0;
-    }
-  }
-  .form-box {
-    flex: 1 1 auto;
-    .rich-input {
-      border-radius: 3px;
-      border: 1px solid #f1f1f1;
-      background-color: #fff;
-      overflow: hidden;
-      &.focus {
-        border-color: #007fff;
-      }
-      .grow-wrap {
-        display: grid;
-        &::after {
-          content: attr(data-replicated-value) ' ';
-          white-space: pre-wrap;
-          visibility: hidden;
-        }
-        textarea {
-          outline: none;
-          border: none;
-          resize: none;
-          touch-action: none;
-          overflow: hidden;
-          &::placeholder {
-            color: #c2c2c2;
-          }
-        }
-        & > textarea,
-        &::after {
-          font: inherit;
-          grid-area: 1 / 1 / 2 / 2;
-          padding: 0.48rem 0.8rem;
-          min-height: 1.04rem;
-          line-height: 1.7;
-          font-size: 0.8664rem;
-          color: #17181a;
-          box-sizing: border-box;
-          word-break: break-all;
-        }
-      }
-
-      .image-preview-box {
-        display: inline-block;
-        position: relative;
-        margin: 0 0.8rem 0.4rem;
-        .image {
-          width: 5.3336rem;
-          height: 5.3336rem;
-          background-repeat: no-repeat;
-          background-size: cover;
-          background-position: 50%;
-        }
-        .clean-btn {
-          position: absolute;
-          top: 0.15rem;
-          right: 0.2rem;
-          cursor: pointer;
-        }
-      }
-    }
-    .option-box {
-      margin-top: 0.52rem;
-      display: flex;
-      align-items: center;
-      color: #027fff;
-      font-size: 0.8664rem;
-      .emoji {
-        position: relative;
-        .emoji-box {
-          display: flex;
-          align-items: center;
-          cursor: pointer;
-          .icon {
-            width: 1.2rem;
-            height: 1.2rem;
-            background-repeat: no-repeat;
-            background-size: cover;
-            background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMiIgaGVpZ2h0PSIyMiIgdmlld0JveD0iMCAwIDIyIDIyIj4KICAgIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZD0iTTEgMWgyMHYyMEgxeiIvPgogICAgICAgIDxwYXRoIGZpbGw9IiMwMjdGRkYiIGZpbGwtcnVsZT0ibm9uemVybyIgZD0iTTExIDE4LjQzOGE3LjQzOCA3LjQzOCAwIDEgMCAwLTE0Ljg3NiA3LjQzOCA3LjQzOCAwIDAgMCAwIDE0Ljg3NnptMCAxLjA2MmE4LjUgOC41IDAgMSAxIDAtMTcgOC41IDguNSAwIDAgMSAwIDE3ek03LjgxMiA5LjkzN2ExLjA2MiAxLjA2MiAwIDEgMCAwLTIuMTI0IDEuMDYyIDEuMDYyIDAgMCAwIDAgMi4xMjV6bTYuMzc1IDBhMS4wNjMgMS4wNjMgMCAxIDAgMC0yLjEyNSAxLjA2MyAxLjA2MyAwIDAgMCAwIDIuMTI1ek0xMSAxNi4yMzJhMy4yNyAzLjI3IDAgMCAwIDMuMjctMy4yN0g3LjczYTMuMjcgMy4yNyAwIDAgMCAzLjI3IDMuMjd6Ii8+CiAgICA8L2c+Cjwvc3ZnPgo=');
-          }
-          &:hover {
-            opacity: 0.8;
-          }
-        }
-      }
-      .image-btn {
-        flex: 0 0 auto;
-        display: flex;
-        align-items: center;
-        margin-left: 20px;
-        cursor: pointer;
-        .icon {
-          margin-right: 0.2664rem;
-          width: 1.2rem;
-          height: 1.2rem;
-        }
-        &:hover {
-          opacity: 0.8;
-        }
-      }
-      .upload-file {
-        display: none;
-      }
-      .submit-btn {
-        flex: 0 0 auto;
-        margin-left: auto;
-        padding: 0.4rem 1.04rem;
-        font-size: 1rem;
-        color: #fff;
-        background-color: #027fff;
-        border-radius: 2px;
-        outline: none;
-        border: none;
-        cursor: pointer;
-        transition: all 0.3s;
-        &:hover {
-          background-color: #0371df;
-        }
-        &:disabled {
-          opacity: 0.4;
-          cursor: default;
-        }
-      }
-    }
-  }
-  &.reply {
-    margin-top: 0.8664rem;
-    padding: 0.8rem;
-    &.sub-reply {
-      background-color: #fff;
-      border: 1px solid #f1f1f2;
-    }
-    .avatar-box {
-      display: none;
-    }
-  }
-}
-</style>

+ 0 - 303
src/components/comment/components/CommentItem.vue

@@ -1,303 +0,0 @@
-<template>
-  <div class="comment-item" :class="{ 'sub-comment-item': isSubComment }">
-    <div class="comment">
-      <!-- 评论或回复人头像 -->
-      <router-link :to="`/u/${comment.user.userId}`">
-        <img
-          class="avatar"
-          :src="comment.user.avatar || ''"
-          @error="(e) => e.target.classList.add('error')"
-        >
-      </router-link>
-      <div class="content-box">
-        <router-link :to="`/u/${comment.user.userId}`">
-          <!-- 评论或回复人具体信息 -->
-          <div class="meta-box">
-            <slot name="userMeta">
-              <div class="user-popover-box">
-              <span v-if="comment.user">{{
-                comment.user.name +
-                  (comment.user.author === true ? '(作者)' : '')
-              }}</span>
-              </div>
-            </slot>
-          </div>
-        </router-link>
-
-        <!-- 评论或回复内容 -->
-        <div class="content">
-          <span
-            v-if="comment.reply"
-            class="reply"
-          >回复
-            <span class="reply-target" :title="comment.reply.email">{{
-              comment.reply.name + ':'
-            }}</span>
-          </span>
-          {{ comment.content }}
-          <div v-if="comment.imgSrc" class="img-box">
-            <img
-              :src="comment.imgSrc || ''"
-              @error="(e) => e.target.classList.add('error')"
-            >
-          </div>
-        </div>
-
-        <!-- 评论或回复时间及操作 -->
-        <div class="reply-stat">
-          <time
-            :title="formatTime(comment.createAt, true)"
-            :datetime="comment.createAt"
-          >{{ formatTime(comment.createAt) }}</time>
-          <div
-            v-if="user.author === true"
-            class="delete"
-            @click.stop="$emit('comment-delete', { id, comment, parent })"
-          >
-            <span>·</span>删除
-          </div>
-          <div class="action-box">
-            <div
-              class="like-action action"
-              :class="{ active: comment.liked }"
-              @click.stop="$emit('comment-like', { id, comment })"
-            >
-              <svg
-                aria-hidden="true"
-                viewBox="0 0 20 20"
-                class="icon like-icon"
-              >
-                <g fill="none" fill-rule="evenodd">
-                  <path d="M0 0h20v20H0z" />
-                  <path
-                    :stroke="comment.liked ? '#37C700' : '#8A93A0'"
-                    stroke-linejoin="round"
-                    :fill="comment.liked ? '#37c700' : 'none'"
-                    d="M4.58 8.25V17h-1.4C2.53 17 2 16.382 2 15.624V9.735c0-.79.552-1.485 1.18-1.485h1.4zM11.322 2c1.011.019 1.614.833 1.823 1.235.382.735.392 1.946.13 2.724-.236.704-.785 1.629-.785 1.629h4.11c.434 0 .838.206 1.107.563.273.365.363.84.24 1.272l-1.86 6.513A1.425 1.425 0 0 1 14.724 17H6.645V7.898C8.502 7.51 9.643 4.59 9.852 3.249A1.47 1.47 0 0 1 11.322 2z"
-                  />
-                </g>
-              </svg>
-              <span v-show="comment.likes" class="action-title">{{
-                comment.likes
-              }}</span>
-            </div>
-            <div
-              class="comment-action action"
-              @mousedown.prevent="$emit('comment-reply', id)"
-              @click.prevent
-            >
-              <svg
-                aria-hidden="true"
-                viewBox="0 0 20 20"
-                class="icon comment-icon"
-              >
-                <g fill="none" fill-rule="evenodd">
-                  <path d="M0 0h20v20H0z" />
-                  <path
-                    stroke="#8A93A0"
-                    stroke-linejoin="round"
-                    d="M10 17c-4.142 0-7.5-2.91-7.5-6.5S5.858 4 10 4c4.142 0 7.5 2.91 7.5 6.5 0 1.416-.522 2.726-1.41 3.794-.129.156.41 3.206.41 3.206l-3.265-1.134c-.998.369-2.077.634-3.235.634z"
-                  />
-                </g>
-              </svg>
-              <span class="action-title">回复</span>
-            </div>
-          </div>
-        </div>
-
-        <!-- 评论表单组件 -->
-        <slot :id="id" />
-
-        <!-- 回复列表 -->
-        <slot name="subList" :parentId="id" />
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'CommentItem',
-  props: {
-    comment: {
-      type: Object,
-      default: () => {},
-      required: true
-    },
-    id: {
-      type: [String, Number],
-      required: true
-    },
-    parent: {
-      type: Object,
-      default: () => {}
-    },
-    user: {
-      type: Object,
-      default: () => {}
-    }
-  },
-  computed: {
-    isSubComment() {
-      return this.id.split('-').length === 3
-    }
-  },
-  methods: {
-    formatTime(time, local = false) {
-      const d = new Date(time)
-
-      if (local) {
-        return d.toString()
-      }
-
-      const now = Date.now()
-      const diff = (now - d) / 1000
-
-      switch (true) {
-        case diff < 30:
-          return '刚刚'
-        case diff < 3600:
-          return Math.ceil(diff / 60) + '分钟前'
-        case diff < 3600 * 24:
-          return Math.ceil(diff / 3600) + '小时前'
-        case diff < 3600 * 24 * 30:
-          return Math.floor(diff / 3600 / 24) + '天前'
-        case diff < 3600 * 24 * 365:
-          return Math.floor(diff / 3600 / 24 / 30) + '月前'
-        default:
-          return Math.floor(diff / 3600 / 24 / 365) + '年前'
-      }
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.comment-item {
-  margin-bottom: 1.0664rem;
-  &:not(:last-child) {
-    .content-box {
-      border-bottom: 1px solid #f1f1f1;
-    }
-  }
-  &:hover {
-    .comment .reply-stat .delete {
-      visibility: visible;
-    }
-  }
-  .comment {
-    display: flex;
-    .content-box {
-      margin-left: 0.6664rem;
-      flex: 1 1 auto;
-      &.focus {
-        padding-bottom: 0.4rem;
-      }
-      .meta-box {
-        display: flex;
-        align-items: center;
-        font-size: 0.8664rem;
-        line-height: 1.25;
-        white-space: nowrap;
-        .user-popover-box {
-          cursor: pointer;
-        }
-      }
-      .content {
-        margin-top: 0.44rem;
-        font-size: 0.8664rem;
-        line-height: 1.4664rem;
-        white-space: pre-line;
-        word-break: break-all;
-        color: #505050;
-        overflow: hidden;
-        .img-box {
-          margin-top: 0.5rem;
-          img {
-            max-width: 100%;
-            max-height: 20rem;
-            object-fit: cover;
-          }
-        }
-        .reply {
-          vertical-align: top;
-        }
-        .reply-target {
-          cursor: pointer;
-          color: #406599;
-        }
-      }
-    }
-    .reply-stat {
-      display: flex;
-      margin-top: 7px;
-      font-weight: 400;
-      time,
-      .delete {
-        font-size: 0.8664rem;
-        color: #8a9aa9;
-      }
-      .delete {
-        visibility: hidden;
-        cursor: pointer;
-        span {
-          margin: 0 0.2rem;
-        }
-      }
-      .action-box {
-        flex: 0 0 auto;
-        display: flex;
-        justify-content: space-between;
-        margin-left: auto;
-        min-width: 7.04rem;
-        color: #8a93a0;
-        user-select: none;
-        .action {
-          display: flex;
-          align-items: center;
-          margin-left: 0.4rem;
-          cursor: pointer;
-          &:hover {
-            opacity: 0.8;
-          }
-          &.active {
-            color: #37c700;
-          }
-          .icon {
-            min-width: 16.5px;
-            min-height: 16.5px;
-            width: 0.8rem;
-            height: 0.8rem;
-          }
-          .action-title {
-            margin-left: 0.2rem;
-            font-size: 0.8rem;
-          }
-        }
-      }
-    }
-  }
-}
-
-.sub-comment-list {
-  margin: 0.8rem 0;
-  padding: 0 0 0 0.8rem;
-  background-color: #fafbfc;
-  border-radius: 3px;
-  .comment-item {
-    margin-bottom: 0;
-    &:last-child .content-box {
-      border-bottom: none;
-    }
-    .comment {
-      position: relative;
-      padding: 0.8rem 0 0;
-
-      .content-box {
-        margin-right: 0.8rem;
-        padding-bottom: 0.8rem;
-      }
-    }
-  }
-}
-</style>

+ 0 - 12
src/components/comment/components/CommentList.js

@@ -1,12 +0,0 @@
-export default {
-  props: {
-    sub: {
-      type: Boolean,
-      default: false
-    }
-  },
-  render(h) {
-    const className = this.sub ? 'sub-comment-list' : 'comment-list'
-    return h('div', { class: className }, this.$slots.default)
-  }
-}

+ 0 - 264
src/components/comment/components/EmojiSelector.vue

@@ -1,264 +0,0 @@
-<template>
-  <div class="emoji-selector" @mousedown.prevent @mousedown.stop>
-    <div class="triangle" />
-    <div class="emoji-content">
-      <div class="category">
-        <div
-          v-for="(item, i) in currentEmojis"
-          :key="`emoji-${i}`"
-          class="item"
-          @click="$emit('choose', item)"
-        >
-          {{ item }}
-        </div>
-      </div>
-    </div>
-    <div class="next-page">
-      <div
-        v-for="cat in Object.keys(emojis)"
-        :key="cat"
-        :class="{ active: currentCat === cat }"
-        @click="currentCat = cat"
-      />
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'ImojiSelector',
-  data() {
-    return {
-      currentCat: 'FrequentlyUsed',
-      emojis: {
-        FrequentlyUsed: [
-          '😃',
-          '😘',
-          '😂',
-          '😳',
-          '😍',
-          '👏',
-          '👍',
-          '👎',
-          '😁',
-          '😉',
-          '😠',
-          '😞',
-          '😥',
-          '😭',
-          '😝',
-          '😡',
-          '❤',
-          '💔',
-          '😣',
-          '😔',
-          '😄',
-          '😷',
-          '😚',
-          '😓',
-          '😊',
-          '😢',
-          '😜',
-          '😨',
-          '😰',
-          '😲',
-          '😏',
-          '😱',
-          '😪',
-          '😖',
-          '😌',
-          '😒',
-          '👻',
-          '🎅',
-          '👧',
-          '👦',
-          '👩',
-          '👨',
-          '🐶',
-          '🐱',
-          '👊',
-          '✊',
-          '✌',
-          '💪',
-          '👆',
-          '👇',
-          '👉',
-          '👈',
-          '👌',
-          '💩'
-        ],
-        Symbols0: [
-          '🤗',
-          '😎',
-          '🤓',
-          '👩‍💻',
-          '👨‍💻',
-          '🙄',
-          '😭',
-          '😨',
-          '🤪',
-          '🎉',
-          '🤔',
-          '🐵',
-          '😇',
-          '🤬',
-          '🐈',
-          '😹',
-          '🙀',
-          '🇨🇳',
-          '👮',
-          '🐕',
-          '✅',
-          '👋',
-          '🔥',
-          '🐛',
-          '🍉',
-          '👽',
-          '🤖',
-          '⌚',
-          '🤝',
-          '🏳️‍🌈',
-          '🚩',
-          '💤',
-          '®',
-          '©',
-          '💯',
-          '™',
-          '💻',
-          '📅',
-          '📌',
-          '✉',
-          '⌨',
-          '📗',
-          '🤳',
-          '🛌',
-          '🎣',
-          '🎨',
-          '🎧',
-          '🎸',
-          '🎤',
-          '🏸',
-          '🏀',
-          '⚽',
-          '🎮',
-          '🏊'
-        ],
-        Symbols1: [
-          '🍗',
-          '🦄',
-          '🔞',
-          '🙏',
-          '☀',
-          '🌙',
-          '🌟',
-          '⚡',
-          '☁',
-          '☔',
-          '🍁',
-          '🌻',
-          '🍃',
-          '👗',
-          '🎀',
-          '👄',
-          '🌹',
-          '☕',
-          '🎂',
-          '🕙',
-          '🍺',
-          '🔍',
-          '📱',
-          '🏠',
-          '🚗',
-          '🎁',
-          '⚽',
-          '💣',
-          '💎',
-          '💊',
-          '🤮',
-          '🏆',
-          '👿'
-        ]
-      }
-    }
-  },
-  computed: {
-    currentEmojis() {
-      return this.emojis[this.currentCat]
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.emoji-selector {
-  padding: 0.8rem;
-  position: absolute;
-  top: 2.24rem;
-  z-index: 1;
-  bottom: 0;
-  width: 19rem;
-  height: 14rem;
-  border-radius: 2px;
-  background-color: #fff;
-  box-shadow: 0 5px 18px 0 rgba(0, 0, 0, 0.16);
-  box-sizing: content-box;
-  .triangle {
-    position: absolute;
-    top: -0.56rem;
-    left: 14%;
-    width: 0;
-    height: 0;
-    transform: translate(-50%, -50%);
-    border: 0.64rem solid transparent;
-    border-bottom-color: #fff;
-  }
-  .emoji-content {
-    height: 100%;
-    overflow: auto;
-    margin-bottom: 10px;
-    .category {
-      max-width: 19rem;
-      max-height: 13rem;
-      display: flex;
-      flex-wrap: wrap;
-      align-items: center;
-      justify-content: flex-start;
-      overflow: hidden;
-      .item {
-        width: calc(19rem / 9);
-        height: calc(13rem / 6);
-        font-size: 1.25rem;
-        text-align: center;
-        line-height: calc(10rem / 6);
-        cursor: pointer;
-        &:hover {
-          font-size: 1.6rem;
-        }
-      }
-    }
-  }
-  .next-page {
-    list-style: none;
-    position: absolute;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    bottom: 10px;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    div {
-      list-style: none;
-      margin: 0 5px;
-      width: 6px;
-      height: 6px;
-      border-radius: 100%;
-      background-color: #f0f0f0;
-      cursor: pointer;
-      &.active {
-        cursor: default;
-        background-color: #d8d8d8;
-      }
-    }
-  }
-}
-</style>

+ 0 - 11
src/components/comment/index.js

@@ -1,11 +0,0 @@
-import JuejinComment from './index.vue'
-
-JuejinComment.install = function(Vue) {
-  Vue.component(JuejinComment.name, JuejinComment)
-}
-
-if (typeof window !== 'undefined' && window.Vue) {
-  JuejinComment.install(window.Vue)
-}
-
-export default JuejinComment

+ 0 - 541
src/components/comment/index.vue

@@ -1,541 +0,0 @@
-<template>
-  <div id="comment" ref="comment">
-    <!-- 顶部评论表单 -->
-    <comment-form :upload-img="uploadImg" @form-submit="formSubmit">
-      <img
-        class="avatar"
-        :src="user.avatar || ''"
-        @error="(e) => e.target.classList.add('error')"
-      >
-    </comment-form>
-
-    <!-- 底部评论列表 -->
-    <comment-list v-if="cacheData.length > 0" ref="comment-list">
-      <!-- 单条评论 -->
-      <comment-item
-        v-for="(comment, i) in cacheData"
-        :id="`comment-${i}`"
-        :key="`comment-${i}`"
-        :ref="`comment-${i}`"
-        :user="user"
-        :comment="comment"
-        @comment-reply="hasForm"
-        @comment-like="handleCommentLike"
-        @comment-delete="handleCommentDelete"
-      >
-        <!-- 回复表单 -->
-        <template #default="{ id }">
-          <comment-form
-            v-if="forms.includes(id)"
-            :id="id"
-            :parent="comment"
-            :placeholder="`回复${comment.user.name}...`"
-            :upload-img="uploadImg"
-            @form-submit="formSubmit"
-            @form-delete="deleteForm"
-          />
-        </template>
-
-        <!-- 单条评论下的回复列表 -->
-        <template #subList="{ parentId }">
-          <div>
-            <comment-list sub>
-              <!-- 单条回复 -->
-              <comment-item
-                v-for="(child, j) in comment.children"
-                :id="`${parentId}-${j}`"
-                :key="`${parentId}-${j}`"
-                :ref="`${parentId}-${j}`"
-                :comment="child"
-                :user="user"
-                :parent="comment"
-                @comment-reply="hasForm"
-                @comment-like="handleCommentLike"
-                @comment-delete="handleCommentDelete"
-              >
-                <!-- 单条回复的回复表单 -->
-                <comment-form
-                  v-if="forms.includes(`${parentId}-${j}`)"
-                  :id="`${parentId}-${j}`"
-                  :comment="child"
-                  :parent="comment"
-                  :placeholder="`回复${child.user && child.user.name}...`"
-                  :upload-img="uploadImg"
-                  @form-delete="deleteForm"
-                  @form-submit="formSubmit"
-                />
-              </comment-item>
-            </comment-list>
-            <v-pagination
-              v-model="page"
-              :length="length"
-              :total-visible="7"
-              @input="pageChange"
-            />
-          </div>
-        </template>
-      </comment-item>
-    </comment-list>
-  </div>
-</template>
-
-<script>
-import CommentForm from './components/CommentForm'
-import CommentList from './components/CommentList'
-import CommentItem from './components/CommentItem'
-import { childComment } from '@/api/comment/comment'
-
-export default {
-  name: 'JuejinComment',
-  components: { CommentList, CommentItem, CommentForm },
-  inheritAttrs: false,
-  // 接收父组件通过 v-model 绑定的值
-  model: {
-    prop: 'videoComments',
-    event: 'input'
-  },
-  props: {
-    /* 数据 */
-    // model 中的 videoComments prop
-    videoComments: {
-      type: Array,
-      default: () => [],
-      required: true
-    },
-    /* 当前用户 */
-    user: {
-      type: Object,
-      default: () => {},
-      required: true
-    },
-    /* 配置属性 */
-    props: {
-      type: Object,
-      default: () => {}
-    },
-    /* 提交表单前事件 */
-    beforeSubmit: {
-      type: Function,
-      required: true
-    },
-    /* 执行点赞前事件 */
-    beforeLike: {
-      type: Function,
-      required: true
-    },
-    /* 执行删除前事件 */
-    beforeDelete: {
-      type: Function,
-      required: true
-    },
-    /* 上传图片 */
-    uploadImg: {
-      type: Function,
-      required: true
-    }
-  },
-  data() {
-    return {
-      forms: [], // 显示在视图上的所有表单 id
-      cacheData: [],
-      page: 1,
-      currentPage: 1,
-      length: 0
-    }
-  },
-  computed: {
-    computedProps({ props }) {
-      if (!props) return null
-      const entries = Object.entries(props)
-      return entries.length > 0 ? entries : null
-    }
-  },
-  watch: {
-    videoComments: {
-      immediate: true,
-      handler(value) {
-        // 数据发生变化时加载新数据
-        this.processVideoComments()
-      }
-    }
-  },
-  created() {
-    // 监听并执行一次
-    const cancel = this.$watch('data', () => {
-      this.processData()
-      cancel && cancel()
-    })
-  },
-  methods: {
-    /**
-     * 处理初始数据
-     */
-    processData() {
-      this.cacheData = this.data.map(this.comparePropsAndValues)
-    },
-    processVideoComments() {
-      this.cacheData = this.videoComments.map(this.comparePropsAndValues)
-    },
-    /** 对比和检查每条评论对象字段值 */
-    comparePropsAndValues(comment) {
-      // 初始对象
-      const originObj = {
-        videoId: '',
-        id: '',
-        content: '',
-        imgSrc: '',
-        children: [],
-        likes: 0,
-        reply: null,
-        createAt: null,
-        user: {},
-        liked: false
-      }
-
-      // 赋值
-      for (const key in originObj) {
-        originObj[key] =
-          comment[this.props[key]] || comment[key] || originObj[key]
-
-        // 校验
-        this.validate({ key, value: originObj[key] })
-      }
-
-      if (originObj.children.length > 0) {
-        originObj.children = originObj.children.map(this.comparePropsAndValues)
-      }
-
-      return originObj
-    },
-
-    /** 校验数据 */
-    validate({ key, value }) {
-      const map = {
-        user: {
-          validate: function(v) {
-            return (
-              (typeof v !== 'object' || JSON.stringify(v) === '{}') &&
-              this.message
-            )
-          },
-          message: 'User must be an object with props.'
-        },
-        reply: {
-          validate: function(v) {
-            return typeof v !== 'object' && this.message
-          },
-          message: 'Reply must be an object'
-        },
-        children: {
-          validate: function(v) {
-            return !Array.isArray(v) && this.message
-          },
-          message: 'Children must be an array'
-        },
-        createAt: {
-          validate: function() {
-            return new Date(value).toString() === 'Invalid Date' && this.message
-          },
-          message: 'CreateAt is not a valid date.'
-        }
-      }
-
-      const target = map[key]
-      if (!target) return
-
-      const res = target.validate(value)
-      if (res) {
-        throw new Error(`validate(): ${res}`)
-      }
-    },
-
-    /**
-     * 将更新后的数组中的对象数据转换为初始对象结构
-     */
-    transformToOriginObj(comment) {
-      try {
-        const _comment = JSON.parse(JSON.stringify(comment))
-
-        if (_comment.children && _comment.children.length > 0) {
-          _comment.children = _comment.children.map(this.transformToOriginObj)
-        }
-
-        // 返回 props 中自定义的字段名
-        if (!this.computedProps) return _comment
-
-        for (const [key, value] of this.computedProps) {
-          if (key !== value && Object.hasOwnProperty.call(_comment, key)) {
-            _comment[value] = JSON.parse(JSON.stringify(_comment[key]))
-            delete _comment[key]
-          }
-        }
-        return _comment
-      } catch (e) {
-        console.error(e)
-      }
-    },
-
-    /**
-     * 判断是否已存在该id的表单,存在删除该表单,不存在则新增该表单,并触发其他表单blur事件
-     */
-    hasForm(id) {
-      this.forms.includes(id) ? this.deleteForm(id) : this.addForm(id)
-      this.broadcastBlur(this.$refs['comment-list'].$children, id)
-    },
-
-    /**
-     * 增加新表单
-     */
-    addForm(id) {
-      this.forms.push(id)
-      // this.scrollIntoView(`${id}-form`)
-    },
-
-    /** 删除表单 */
-    deleteForm(id) {
-      const index = this.forms.indexOf(id)
-      index > -1 && this.forms.splice(index, 1)
-    },
-
-    /**
-     * 评论或回复
-     */
-    async formSubmit({
-      newComment: { id, callback, ...params },
-      parent = null
-    }) {
-      const _params = Object.assign(params, { user: this.user })
-
-      // 等待外部提交事件执行
-      if (typeof this.beforeSubmit === 'function') {
-        try {
-          const data = this.transformToOriginObj(_params)
-
-          const add = (data) => {
-            this.addComment(id, this.comparePropsAndValues(data))
-            callback()
-          }
-
-          await this.beforeSubmit(data, parent, add)
-        } catch (e) {
-          console.error(e)
-        }
-      }
-    },
-
-    async handleCommentLike({ id, comment: { children, ...params }}) {
-      const _params = Object.assign(params, { user: this.user })
-      if (typeof this.beforeLike === 'function') {
-        try {
-          await this.beforeLike(this.transformToOriginObj(_params))
-
-          this.storeLikes(id)
-        } catch (e) {
-          console.error(e)
-        }
-      }
-    },
-
-    /**
-     * 删除评论或回复
-     */
-    async handleCommentDelete({ id, comment, parent = null }) {
-      if (typeof this.beforeDelete === 'function') {
-        try {
-          const data = this.transformToOriginObj(comment)
-          await this.beforeDelete(data, parent)
-
-          this.deleteComment(id)
-        } catch (e) {
-          console.error(e)
-        }
-      }
-    },
-
-    /**
-     * 保存点赞
-     */
-    storeLikes(id) {
-      const { commentIndex, replyIndex } = this.getIndex(id)
-
-      let comment = this.cacheData[commentIndex]
-
-      if (!isNaN(replyIndex)) {
-        comment = comment.children[replyIndex]
-      }
-
-      comment.liked = !comment.liked
-
-      if (comment.likes) {
-        comment.liked ? comment.likes++ : comment.likes--
-      } else {
-        comment.likes = 1
-      }
-
-      const data = this.cacheData.map(this.transformToOriginObj)
-      this.$emit('input', data)
-    },
-
-    /**
-     * 存储新评论或回复
-     */
-    addComment(id, rawData) {
-      const { commentIndex } = this.getIndex(id)
-
-      // 更新视图
-      if (commentIndex === 'root') {
-        this.cacheData.push(rawData)
-      } else {
-        const comment = this.cacheData[commentIndex]
-        comment.children.push(rawData)
-      }
-
-      // 滚动至可见视图上
-      const signal =
-        commentIndex === 'root'
-          ? this.cacheData.length - 1
-          : `${commentIndex}-${
-            this.cacheData[commentIndex].children.length - 1
-          }`
-      this.scrollIntoView(`comment-${signal}`)
-
-      // 更新外部数据
-      const data = this.cacheData.map(this.transformToOriginObj)
-      this.$emit('input', data)
-    },
-
-    /**
-     * 删除评论或回复
-     */
-    deleteComment(id) {
-      const { commentIndex, replyIndex } = this.getIndex(id)
-
-      this.cacheData = this.cacheData.filter((c, i) => {
-        if (isNaN(replyIndex)) {
-          return i !== commentIndex
-        } else {
-          c.children = c.children.filter((r, j) => j !== replyIndex)
-          return c
-        }
-      })
-
-      const data = this.cacheData.map(this.transformToOriginObj)
-      this.$emit('input', data)
-    },
-
-    /**
-     * 向下递归触发表单blur事件
-     */
-    broadcastBlur(target, id) {
-      if (id && target.id === id) return
-
-      if (Array.isArray(target)) {
-        target.map((c) => this.broadcastBlur(c, id))
-      } else {
-        const children = target.$children
-        children && this.broadcastBlur(children, id)
-
-        const richInput = target.$refs['rich-input']
-        richInput && richInput.blur()
-      }
-    },
-
-    /**
-     * 从id中提取出序号
-     */
-    getIndex(id) {
-      const [, c, r] = id.split('-')
-      return { commentIndex: c === 'root' ? c : +c, replyIndex: +r }
-    },
-
-    /**
-     * 将子组件滚动到视图可见区域
-     */
-    scrollIntoView(ref) {
-      this.$nextTick(() => {
-        this.$refs[ref][0].$el.scrollIntoView(false)
-      })
-    },
-    pageChange(page) {
-      if (page !== this.currentPage) {
-        this.currentPage = page
-        console.log('获取下一页子评论')
-      }
-    },
-    getChildComments(parentId) {
-      childComment(parentId, this.page).then(res => {
-        if (res.code === 0) {
-          this.page += 1
-        } else {
-          console.error(res.msg)
-        }
-      })
-        .catch(error => {
-          console.error(error.message)
-        })
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-#comment {
-  // border-top: 1px solid #ebebeb;
-  padding-top: 1.0664rem;
-  & > .comment-form {
-    margin: 0 1.3328rem 1.0664rem;
-  }
-  & > .comment-list {
-    margin: 0 1.3328rem 0 5.2rem;
-    background-color: #fff;
-  }
-
-  ::v-deep {
-    img {
-      user-select: none;
-      -webkit-user-drag: none;
-      &.avatar {
-        width: 2.1336rem;
-        height: 2.1336rem;
-        border-radius: 50%;
-        cursor: pointer;
-      }
-      &.error {
-        display: inline-block;
-        transform: scale(0.5);
-        content: '';
-        color: transparent;
-        &::before {
-          content: '';
-          position: absolute;
-          left: 0;
-          top: 0;
-          width: 100%;
-          height: 100%;
-          border-radius: 50%;
-          border: 1px solid #e7e7e7;
-          box-sizing: border-box;
-          transform: scale(2);
-          background: #f5f5f5
-            url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M304.128 456.192c48.64 0 88.064-39.424 88.064-88.064s-39.424-88.064-88.064-88.064-88.064 39.424-88.064 88.064 39.424 88.064 88.064 88.064zm0-116.224c15.36 0 28.16 12.288 28.16 28.16s-12.288 28.16-28.16 28.16-28.16-12.288-28.16-28.16 12.288-28.16 28.16-28.16z' fill='%23e6e6e6'/%3E%3Cpath d='M887.296 159.744H136.704C96.768 159.744 64 192 64 232.448v559.104c0 39.936 32.256 72.704 72.704 72.704h198.144L500.224 688.64l-36.352-222.72 162.304-130.56-61.44 143.872 92.672 214.016-105.472 171.008h335.36C927.232 864.256 960 832 960 791.552V232.448c0-39.936-32.256-72.704-72.704-72.704zm-138.752 71.68v.512H857.6c16.384 0 30.208 13.312 30.208 30.208v399.872L673.28 408.064l75.264-176.64zM304.64 792.064H165.888c-16.384 0-30.208-13.312-30.208-30.208v-9.728l138.752-164.352 104.96 124.416-74.752 79.872zm81.92-355.84l37.376 228.864-.512.512-142.848-169.984c-3.072-3.584-9.216-3.584-12.288 0L135.68 652.8V262.144c0-16.384 13.312-30.208 30.208-30.208h474.624L386.56 436.224zm501.248 325.632c0 16.896-13.312 30.208-29.696 30.208H680.96l57.344-93.184-87.552-202.24 7.168-7.68 229.888 272.896z' fill='%23e6e6e6'/%3E%3C/svg%3E")
-            no-repeat center / 50% 50%;
-        }
-      }
-    }
-  }
-}
-
-@media screen and (max-width: 600px) {
-  #comment {
-    & > .comment-list {
-      margin: 0 1.6rem;
-    }
-    & > .comment-form {
-      margin: 1rem 1.6rem;
-    }
-    & > ::v-deep .comment-root .avatar-box {
-      display: none;
-    }
-  }
-}
-</style>

+ 0 - 11
src/components/dialog/examine-dialog.vue

@@ -1,11 +0,0 @@
-<template />
-
-<script>
-export default {
-  name: 'Examine'
-}
-</script>
-
-<style>
-
-</style>

+ 221 - 0
src/components/hotlist/HotList.vue

@@ -0,0 +1,221 @@
+<template>
+  <el-card class="box-card" :body-style="{ paddingTop: '0px' }">
+    <div slot="header" class="clearfix">
+      <img src="@/assets/img/icon/like.png" alt="" class="recommand-icon">
+      <span>热播排行</span>
+    </div>
+    <div
+      v-for="(video, index) in videos"
+      :key="index"
+      :title="video.vname"
+      class="item"
+    >
+      <div :class="rank(index + 1)">{{ index + 1 }}</div>
+      {{ video.vname }}
+    </div>
+  </el-card>
+</template>
+
+<script>
+export default {
+  name: 'HotList',
+  data() {
+    return {
+      videos: []
+    }
+  },
+  computed: {
+    rank() {
+      return (index) => {
+        // index是使用时的参数
+        // console.log(index)
+        if (index === 1) {
+          return 'first'
+        }
+        if (index === 2) {
+          return 'second'
+        }
+        if (index === 3) {
+          return 'third'
+        }
+        if (index > 3) {
+          return 'other'
+        }
+      }
+    }
+  },
+  created() {
+    this.videos = [
+      {
+        vname: '哈1',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈2',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈3',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈4',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈5',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈1',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈2',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈3',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈4',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈5',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      }
+    ]
+    /* getHotTen().then((res) => {
+      this.videos = res
+    })*/
+  },
+  methods: {}
+}
+</script>
+
+<style scoped>
+.item {
+  height: 25px;
+  margin-top: 4px;
+  margin-bottom: 16px;
+  cursor: pointer;
+  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 1; /*行数*/
+  -webkit-box-orient: vertical;
+}
+.item:hover {
+  transform: scale(1.1); /*鼠标放上之后元素变成1.1倍大小*/
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both;
+}
+
+.clearfix {
+  position: relative;
+}
+.clearfix span {
+  font-size: 20px;
+  position: absolute;
+  bottom: 8px;
+  left: 35px;
+}
+.box-card {
+  width: 100%;
+}
+
+.recommand-icon {
+  width: 30px;
+}
+
+@media screen and (max-width: 768px) {
+  .clearfix span {
+    font-size: 15px;
+    position: absolute;
+    bottom: 8px;
+    left: 35px;
+  }
+  .text {
+    font-size: 13px;
+  }
+}
+
+.other {
+  display: inline-block;
+  background-color: rgb(176, 183, 194);
+  width: 22px;
+  height: 22px;
+  line-height: 20px;
+  text-align: center;
+  color: #ffffff;
+  font-size: 15px;
+  border-radius: 5px;
+}
+.first {
+  display: inline-block;
+  background-color: rgb(255, 74, 74);
+  width: 22px;
+  height: 22px;
+  line-height: 20px;
+  text-align: center;
+  color: #ffffff;
+  font-size: 15px;
+  border-radius: 5px;
+}
+.second {
+  display: inline-block;
+  background-color: rgb(255, 119, 1);
+  width: 22px;
+  height: 22px;
+  line-height: 20px;
+  text-align: center;
+  color: #ffffff;
+  font-size: 15px;
+  border-radius: 5px;
+}
+.third {
+  display: inline-block;
+  background-color: rgb(255, 180, 0);
+  width: 22px;
+  height: 22px;
+  line-height: 20px;
+  text-align: center;
+  color: #ffffff;
+  font-size: 15px;
+  border-radius: 5px;
+}
+.el-card__body {
+  padding-top: 0px;
+}
+</style>

+ 49 - 0
src/components/layout/FooterBar.vue

@@ -0,0 +1,49 @@
+<template>
+  <div id="footer-bar">
+    <img class="logo" src="@/assets/img/icon/logo.png" alt="">
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FooterBar'
+}
+</script>
+
+<style scoped>
+#footer-bar {
+  position: relative;
+  bottom: 0;
+  left: 0;
+  right: 0;
+
+  padding-left: 6%;
+  padding-right: 6%;
+  margin-top: 30px;
+  height: 60px;
+  border-top: 2px solid rgba(34, 36, 38, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
+  text-align: center;
+}
+
+.content {
+  padding-top: 10px;
+  font-size: 14px;
+}
+.logo {
+  width: 30px;
+  cursor: pointer;
+}
+.author,
+.statement {
+  padding-top: 5px;
+}
+
+.weixin {
+  width: 30px;
+  cursor: pointer;
+}
+.er {
+  width: 150px;
+}
+</style>

+ 307 - 0
src/components/layout/NavBar.vue

@@ -0,0 +1,307 @@
+<template>
+  <div>
+    <el-row id="navbar" class="main">
+      <!--logo-->
+      <el-col :sm="4" class="right">
+        <a href="/" class="tit">
+          <img src="@/assets/img/icon/logo.png" class="logo" alt="img">
+          <span>bili</span>
+        </a>
+      </el-col>
+      <el-col :sm="1" class="right">
+        <a href="/status/11011" class="tit">
+          <span>状态</span>
+        </a>
+      </el-col>
+      <el-col :sm="1" class="right">
+        <a href="/video" class="tit">
+          <span>视频</span>
+        </a>
+      </el-col>
+      <el-col :sm="1" class="right">
+        <a href="/audio" class="tit">
+          <span>音频</span>
+        </a>
+      </el-col>
+      <el-col :sm="1" class="right">
+        <a href="/image" class="tit">
+          <span>图片</span>
+        </a>
+      </el-col>
+      <el-col :sm="1" class="right">
+        <a href="/article" class="tit">
+          <span>文章</span>
+        </a>
+      </el-col>
+      <!--搜索框-->
+      <el-col :sm="8" class="search">
+        <search-box />
+      </el-col>
+      <el-col :sm="1" class="right logo">
+        <a href="/message" class="tit logo">
+          <span class="el-icon-bell logo" />
+        </a>
+      </el-col>
+      <el-col :sm="1" class="right logo">
+        <a href="/user/post/publish" class="tit logo">
+          <span class="el-icon-upload logo" />
+        </a>
+      </el-col>
+      <!--用户信息-->
+      <el-col :sm="5" class="user-info">
+        <!--头像-->
+        <img
+          v-if="!user"
+          src="@/assets/img/icon/avatar.png"
+          title="点击登录"
+          class="el-avatar--medium"
+          style="cursor: pointer"
+          alt=""
+          @click="login"
+        >
+        <img
+          v-else
+          :src="user.avatarUrl"
+          class="el-avatar--circle el-avatar--medium"
+          alt=""
+        >
+        <!--下拉菜单-->
+        <el-dropdown class="drop">
+          <span class="el-dropdown-link">
+            <span v-if="user">{{ user.username }}</span>
+            <span
+              v-else
+              title="点击登录"
+              @click="login"
+            >登录</span>
+            <i class="el-icon-arrow-down el-icon--right" />
+          </span>
+          <el-dropdown-menu /><!--防止该标签隐藏的时候控制台会报错-->
+          <el-dropdown-menu v-if="user" slot="dropdown" class="iconsize">
+            <el-dropdown-item
+              icon="el-icon-user-solid"
+              class="size"
+              @click.native="goToProfile"
+            >个人资料</el-dropdown-item>
+            <el-dropdown-item
+              icon="el-icon-star-on"
+              class="size"
+              @click.native="goToCollection"
+            >我的收藏
+            </el-dropdown-item>
+            <el-dropdown-item
+              icon="el-icon-video-camera-solid"
+              class="size"
+              @click.native="goToHistory"
+            >播放历史
+            </el-dropdown-item>
+            <el-dropdown-item
+              icon="el-icon-error"
+              class="size"
+              @click.native="goToLogout"
+            >退出</el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
+      </el-col>
+    </el-row>
+    <!--防止下面的盒子上移动,nav-bar脱离了标准流-->
+    <div style="height: 50px" />
+    <!--登录弹窗-->
+    <el-dialog
+      title="用户登录"
+      append-to-body
+      :visible.sync="dialogVisible"
+      width="30%"
+      center
+    >
+      <el-input
+        v-model="userLogin.username"
+        placeholder="请输入手机号或邮箱"
+        style="width: 70%; padding-right: 2px"
+        clearable
+      />
+      <br>
+      <br>
+      <el-input
+        v-model="userLogin.password"
+        placeholder="请输入验证码"
+        style="width: 45%; padding-right: 2px"
+      />
+      <el-button :disabled="isBtn" @click="fetchVerifyCode">{{ code }}</el-button>
+      <br>
+      <br>
+      <el-image :src="captchaCode" @click="getCaptcha" />
+      <el-input
+        v-model="userLogin.captchaCode"
+        placeholder="请输入图形验证码"
+        style="width: 45%; padding-right: 2px"
+      />
+      <br>
+      <br>
+      <span
+        class="register"
+      >账号不存在会自动注册</span>
+      <span slot="footer" class="dialog-footer">
+        <el-button
+          type="primary"
+          :loading="isLoading"
+          @click.native="loginBtn"
+        >登 录</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import SearchBox from '@/components/layout/SearchBox'
+import { userMixin } from 'assets/js/mixin'
+import { getUserInfo } from '@/utils/auth'
+
+export default {
+  name: 'NavBar',
+  components: { SearchBox },
+  mixins: [userMixin],
+  data() {
+    return {
+      user: null,
+      drawer: false
+    }
+  },
+  created() {
+    this.user = getUserInfo()
+  },
+  methods: {
+    login() {
+      this.fetchPubkey()
+    },
+    register() {
+      this.fetchPubkey()
+    },
+    login1() {
+      this.dialogVisible2 = false
+      this.dialogVisible = true
+    },
+    register1() {
+      this.dialogVisible2 = false
+      this.dialogVisible = true
+    },
+    goToStatus() {
+      this.$router.push('/status')
+    }
+  }
+}
+</script>
+
+<style scoped>
+#navbar {
+  height: 50px;
+  border-bottom: 1px solid rgba(34, 36, 38, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
+
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  background-color: #ffffff;
+  z-index: 9;
+}
+
+.navmenu {
+  height: 50px;
+  border-bottom: 1px solid rgba(34, 36, 38, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
+
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  background-color: #ffffff;
+  z-index: 9;
+}
+
+.main {
+  padding-left: 1%;
+  padding-right: 1%;
+}
+
+.search-icon {
+  display: none;
+}
+
+@media screen and (max-width: 768px) {
+  .main {
+    padding-left: 1%;
+    padding-right: 1%;
+  }
+
+  .search,
+  .user-info {
+    display: none;
+  }
+
+  .search-icon {
+    display: inline;
+    cursor: pointer;
+    font-size: 30px;
+    align-content: flex-end;
+  }
+}
+
+.right {
+  word-break: keep-all; /* 不换行 */
+  white-space: nowrap; /* 不换行 */
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.tit {
+  display: block;
+  font-weight: 700;
+  height: 45px;
+  text-decoration: none;
+  color: #333;
+}
+
+.tit span {
+  margin-left: 5px;
+}
+
+.logo {
+  width: 30px;
+  position: relative;
+  top: 5px;
+}
+
+.search {
+  text-align: center;
+  padding-top: 6px;
+}
+
+.user-info {
+  padding-top: 6px;
+  word-break: keep-all; /* 不换行 */
+  white-space: nowrap; /* 不换行 */
+}
+
+.drop {
+  cursor: pointer;
+  position: relative;
+  font-size: 16px;
+  bottom: 10px;
+  left: 5px;
+}
+
+.size {
+  font-size: 16px;
+}
+.avatar-name {
+  text-align: center;
+}
+
+.avatar {
+  width: 70px;
+  height: 70px;
+  border-radius: 50%;
+}
+</style>

+ 213 - 0
src/components/layout/Profile.vue

@@ -0,0 +1,213 @@
+<template>
+  <div id="profile">
+    <!--登录弹窗-->
+    <el-dialog
+      title="用户登录"
+      append-to-body
+      :visible.sync="dialogVisible"
+      width="85%"
+      center
+    >
+      <el-input v-model="username" placeholder="请输入登录账号" clearable />
+      <br>
+      <br>
+      <el-input
+        v-model="password"
+        placeholder="请输入密码"
+        show-password
+      />
+      <br>
+      <br>
+      <span
+        class="register"
+      >没有账号?<a
+        href="javascript:void(0)"
+        @click="dialogVisible2 = true"
+      >点击注册</a></span>
+
+      <span slot="footer" class="dialog-footer">
+        <el-button
+          type="primary"
+          :loading="isLoading"
+          @click.native="loginBtn"
+        >登 录</el-button>
+      </span>
+    </el-dialog>
+    <!--注册弹窗-->
+    <el-dialog
+      title="用户注册"
+      append-to-body
+      :visible.sync="dialogVisible2"
+      width="85%"
+      center
+    >
+      <el-col class="avatar-name" style="padding-bottom: 10px">
+        <img :src="imageUrl" class="avatar"><br>
+        <el-upload
+          ref="uploadImg"
+          class="avatar-uploader"
+          :auto-upload="false"
+          :action="baseURL + '/upload'"
+          :show-file-list="false"
+          :on-change="imgChange"
+          :on-success="handleAvatarSuccess"
+          :before-upload="beforeAvatarUpload"
+        >
+          <el-button
+            type="primary"
+            size="medium"
+            class="el-icon-plus avatar-uploader-icon"
+          >上传头像</el-button>
+        </el-upload>
+      </el-col>
+
+      <el-input
+        v-model="nickname"
+        placeholder="请输入您的昵称"
+        clearable
+        @blur="nickNameBlur"
+      />
+      <br>
+      <br>
+      <el-input
+        v-model="phone"
+        placeholder="请输入手机号码"
+        clearable
+        @blur="phoneBlur"
+      />
+      <br>
+      <br>
+      <el-input
+        v-model="password"
+        placeholder="请输入密码"
+        show-password
+      />
+      <br>
+      <br>
+      <el-input
+        v-model="repassword"
+        placeholder="确认密码"
+        show-password
+      />
+      <br>
+      <br>
+      <el-input
+        v-model="rcode"
+        placeholder="请输入验证码"
+        style="width: 45%; padding-right: 2px"
+      />
+      <el-button type="primary" :disabled="isBtn" @click="fetchVerifyCode">{{ code }}</el-button>
+
+      <span slot="footer" class="dialog-footer">
+        <el-button
+          type="primary"
+          :loading="isLoading"
+          @click.native="register"
+        >注 册</el-button>
+      </span>
+    </el-dialog>
+
+    <el-row justify="center">
+      <el-col class="avatar-name">
+        <img v-if="user" :src="user.avatarurl" class="avatar">
+        <img v-else src="@/assets/img/icon/avatar.png" class="avatar"><br>
+        <el-button
+          v-if="!user"
+          type="danger"
+          size="small"
+          @click="dialogVisible = true"
+        >登录/注册</el-button>
+        <span v-else>{{ user.nickname }}</span>
+      </el-col>
+    </el-row>
+
+    <div class="graySpace" />
+    <el-row :gutter="3">
+      <el-col
+        v-for="(item, index) in items"
+        :key="index"
+        :span="8"
+        class="item"
+        @click.native="itemClick(item.item)"
+      >
+        <img :src="item.img" alt="" class="icon-img">
+        <div class="detail">{{ item.item }}</div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { userMixin } from 'assets/js/mixin'
+
+export default {
+  name: 'Profile',
+  mixins: [userMixin],
+  data() {
+    return {
+      items: [
+        { img: require('assets/img/icon/history.png'), item: '历史记录' },
+        { img: require('assets/img/icon/mylike.png'), item: '我的收藏' },
+        { img: require('assets/img/icon/profile.png'), item: '个人资料' },
+        { img: require('assets/img/icon/exit.png'), item: '退出登录' }
+      ]
+    }
+  },
+  methods: {
+    itemClick(item) {
+      if (this.user === null) {
+        this.$message({
+          message: '尚未登录!',
+          type: 'warning'
+        })
+      } else {
+        if (item === '我的收藏') {
+          this.goToCollection()
+        } else if (item === '历史记录') {
+          this.goToHistory()
+        } else if (item === '个人资料') {
+          // todo
+        } else {
+          this.exitLogin()
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+#profile {
+  padding-top: 10px;
+  /*background-color: #9acfea;*/
+}
+
+.graySpace {
+  background: #eee;
+  height: 20px;
+}
+
+.avatar-name {
+  text-align: center;
+}
+
+.avatar {
+  width: 70px;
+  height: 70px;
+  border-radius: 50%;
+}
+
+.item {
+  margin-top: 5px;
+  cursor: pointer;
+  text-align: center;
+}
+
+.detail {
+  font-size: 15px;
+}
+
+.icon-img {
+  width: 50%;
+}
+</style>

+ 240 - 0
src/components/layout/SearchBox.vue

@@ -0,0 +1,240 @@
+<template>
+  <!--搜索框-->
+  <el-autocomplete
+    v-model="state"
+    :fetch-suggestions="querySearchAsync"
+    placeholder="想要搜点神马呢"
+    clearable
+    suffix-icon="el-icon-search"
+    size="medium"
+    :debounce="1000"
+    :style="sw"
+    @select="handleSelect"
+    @focus="userFocus"
+    @keyup.enter.native="onSearch"
+  />
+</template>
+
+<script>
+export default {
+  name: 'SearchBox',
+  props: {
+    sw: {
+      type: String,
+      default: 'width:60%'
+    }
+  },
+  data() {
+    return {
+      restaurants: [],
+      state: '',
+      timer: null
+    }
+  },
+  methods: {
+    // 跳转搜索页面,传递搜索框的参数
+    toSearchList() {
+      this.$store.commit('saveValue', this.state)
+      this.$store.dispatch('getPageBeanByValue')
+      this.$router.push('/search')
+    },
+    // 回车事件
+    onSearch() {
+      console.log('回车事件')
+      // 正则去空格
+      if (this.state.replace(/\s*/g, '')) {
+        this.toSearchList()
+      } else {
+        this.$message({
+          showClose: true,
+          message: '不能为空!',
+          type: 'warning'
+        })
+      }
+    },
+
+    loadAll() {
+      const res = [
+        {
+          vname: '哈1',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈1',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        }
+      ]
+      this.restaurants = res.map((item) => {
+        return {
+          value: item.vname
+        }
+      })
+
+      /* getNumVideo(10).then((res) => {
+        // map可以改变原json
+        this.restaurants = res.map((item) => {
+          return {
+            value: item.vname
+          }
+        })
+      })*/
+    },
+
+    // 重点:当框中的改变时触发该方法,elementui自动设置了防抖,参见debounce属性
+    // queryString 为输入框中的值。cb为返回显示列表的回调函数
+    querySearchAsync(queryString, cb) {
+      const res = [
+        {
+          vname: '哈1',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈1',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈2',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈3',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈4',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        },
+        {
+          vname: '哈5',
+          coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+          releasetime: '2023-04-24 16:47:00',
+          visited: 10
+        }
+      ]
+      this.restaurants = res.map((item) => {
+        return {
+          value: item.vname
+        }
+      })
+      cb(this.restaurants)
+
+      /* if (queryString) {
+        getUserInput(queryString).then((res) => {
+          this.restaurants = res.map((item) => {
+            return {
+              value: item.vname
+            }
+          })
+          cb(this.restaurants)
+        })
+      } else {
+        cb(this.restaurants)
+      }*/
+    },
+
+    createStateFilter(queryString) {
+      return (state) => {
+        return (
+          state.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
+        )
+      }
+    },
+    handleSelect(item) {
+      this.toSearchList()
+      // console.log(item);
+    },
+    userFocus() {
+      this.loadAll()
+    }
+  }
+}
+</script>
+
+<style scoped>
+</style>

+ 0 - 179
src/components/login-form-mobile.vue

@@ -1,179 +0,0 @@
-<template>
-  <div>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userLogin.username"
-          placeholder="请输入手机号"
-          label="手机号"
-          :rules="[() => userLogin.username != null || '手机号不能为空']"
-          clearable
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-row justify="center">
-          <v-col cols="5">
-            <v-text-field
-              v-model="userLogin.password"
-              placeholder="请输入短信验证码"
-              label="短信验证码"
-              :rules="[() => userLogin.password != null || '短信验证码不能为空']"
-              clearable
-            />
-          </v-col>
-          <v-col cols="5">
-            <v-btn color="primary" @click="getVerifyCode">获取验证码</v-btn>
-          </v-col>
-        </v-row>
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="5">
-        <img :src="captchaCode" alt="图形验证码" title="点击刷新" style="cursor:pointer;" @click="getCaptcha">
-      </v-col>
-      <v-col cols="5">
-        <v-text-field
-          v-model="userLogin.captchaCode"
-          placeholder="请输入图形验证码"
-          label="图形验证码"
-          :rules="[() => userLogin.captchaCode != null || '图形验证码不能为空']"
-          clearable
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-btn color="primary" @click="submitLogin">登录/注册</v-btn>
-    </v-row>
-
-    <v-snackbar
-      v-model="showMessage"
-      :top="true"
-      :timeout="1000"
-    >
-      {{ message }}
-      <template v-slot:action="{ attrs }">
-        <v-btn
-          color="pink"
-          text
-          v-bind="attrs"
-          @click="showMessage = false"
-        >
-          关闭
-        </v-btn>
-      </template>
-    </v-snackbar>
-  </div>
-</template>
-
-<script>
-import { getVerifyCode, getCaptchaCode } from '@/api/user/account'
-import { getPubkey } from '@/api/user/auth'
-import { JSEncrypt } from 'jsencrypt'
-
-export default {
-  name: 'Register',
-  data() {
-    return {
-      captchaCode: '',
-      userLogin: {
-        accountType: 1,
-        loginType: 1,
-        username: null,
-        password: null,
-        captchaCode: null,
-        r: null,
-        plat: 1
-      },
-      pubkey: '',
-      pubkeyR: '',
-      showMessage: false,
-      message: ''
-    }
-  },
-  created() {
-    this.fetchPubkey()
-  },
-  methods: {
-    submitLogin() {
-      this.userLogin.password = this.$options.methods.encryptPassword(this.userLogin.password, this.pubkey, this.pubkeyR)
-      if (this.userLogin.username === '') {
-        this.showMessage = true
-        this.message = '手机号不能为空'
-        return
-      }
-      if (this.userLogin.password === '' || this.userLogin.password === null) {
-        this.showMessage = true
-        this.message = '短信验证码不能为空'
-        return
-      }
-      if (this.userLogin.captchaCode === '') {
-        this.showMessage = true
-        this.message = '图形验证码不能为空'
-        return
-      }
-
-      // 将数据返回给父组件
-      this.$emit('login', this.userLogin)
-    },
-    getCaptcha() {
-      getCaptchaCode().then(res => {
-        if (res.code === 0) {
-          this.captchaCode = 'data:image/jpeg;base64,' + res.data
-        } else {
-          this.message = '获取图形验证码失败, 请重新刷新页面'
-          this.showMessage = true
-        }
-      })
-    },
-    fetchPubkey() {
-      getPubkey().then(res => {
-        if (res.code === 0) {
-          this.pubkey = res.data.pubkey
-          this.pubkeyR = res.data.r
-
-          this.getCaptcha()
-        } else {
-          this.message = res.msg
-          this.showMessage = true
-        }
-      }).catch(error => {
-        this.message = error.message
-        this.showMessage = true
-      })
-    },
-    encryptPassword(password, pubkey, pubkeyR) {
-      var encryptor = new JSEncrypt()
-      encryptor.setPublicKey(pubkey)
-      return encryptor.encrypt(pubkeyR + password)
-    },
-    getVerifyCode() {
-      if (this.userLogin.username === null || this.userLogin.username === '') {
-        this.showMessage = true
-        this.message = '请填写手机号'
-        return
-      }
-
-      const verifyCodeReq = {}
-      verifyCodeReq.receiver = this.userLogin.username
-      verifyCodeReq.notifyType = 2
-      getVerifyCode(verifyCodeReq).then(res => {
-        if (res.code === 0) {
-          this.showMessage = true
-          this.message = '验证码已发送, 请注意查收'
-        } else {
-          this.showMessage = true
-          this.message = res.msg
-        }
-      }).catch(error => {
-        this.showMessage = true
-        this.message = error
-      })
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 32
src/components/no-login-show.vue

@@ -1,32 +0,0 @@
-<template>
-  <v-row justify="center" align="center">
-    <v-col cols="12">
-      <v-card
-        class="mx-auto"
-        max-width="500"
-      >
-        <v-row justify="center">
-          <v-col cols="10" style="text-align: center">
-            你还没有登录,请登录后查看
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10" style="text-align: center">
-            <v-btn x-large outlined color="indigo" @click="()=>this.$router.push('/login')">登录</v-btn>
-          </v-col>
-        </v-row>
-
-      </v-card>
-    </v-col>
-  </v-row>
-</template>
-
-<script>
-export default {
-  name: 'NoLoginShow'
-
-}
-</script>
-
-<style>
-</style>

+ 0 - 78
src/components/player/live-player.vue

@@ -1,78 +0,0 @@
-<template>
-  <video
-    id="videoElement"
-    ref="videoElement"
-    controls
-    muted
-    width="60%"
-    height="30%"
-  />
-</template>
-
-<script>
-import { videoUrl } from '@/api/media/video'
-const flvjs = require('flv.js')
-
-export default {
-  name: 'LivePlayer',
-  data() {
-    return {
-      flvjs,
-      videoId: ''
-    }
-  },
-  mounted() {
-    /* const userInfo = this.$store.state.user.userInfo
-    if (userInfo != null) {
-      this.userId = userInfo.userId.toString()
-    } else {
-      this.userId = 111222333
-    }
-
-    this.videoId = this.$route.params.id
-    this.getVideoUrl(this.videoId)*/
-    const videoUrl = 'http://127.0.0.1:9090/live/first'
-    this.initFlvPlayer(videoUrl)
-  },
-  methods: {
-    getVideoUrl(videoId) {
-      videoUrl(videoId)
-        .then(res => {
-          if (res.code === 0) {
-            // TODO 返回一个 dplayer 播放器对象,包含一些常用的属性
-            var coverUrl = res.data.coverUrl
-            var videoUrl = res.data.videoUrl
-            var urlType = res.data.urlType
-            if (urlType === 'flv') {
-              this.initFlvPlayer(this.videoId, coverUrl, videoUrl)
-            } else {
-              console.log('无法识别 url 类型')
-            }
-          } else {
-            console.error(res.msg)
-          }
-        })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    initFlvPlayer(videoUrl) {
-      var videoElement = document.getElementById('videoElement')
-      const flvPlayer = flvjs.createPlayer({
-        type: 'flv',
-        isLive: true,
-        url: videoUrl
-      })
-      flvPlayer.attachMediaElement(videoElement)
-      flvPlayer.load()
-      flvPlayer.play()
-    }
-  }
-}
-</script>
-
-<style>
-#dplayer {
-  height: 500px;
-}
-</style>

+ 179 - 0
src/components/recommend/Recommend.vue

@@ -0,0 +1,179 @@
+<template>
+  <div v-if="$user && videos.length > 0" id="recommend">
+    <el-tag style="font-size: 15px" type="danger" effect="light">
+      猜您喜欢
+    </el-tag>
+    <div class="text-container">
+      <transition class="inner-container" name="slide" mode="out-in">
+        <div
+          v-if="text.val !== undefined"
+          :key="text.id"
+          class="text"
+        >
+          {{ text.val.vname }}
+        </div>
+      </transition>
+    </div>
+  </div>
+</template>
+
+<script>
+import { videoRecommend } from '@/api/video'
+
+export default {
+  name: 'Recommend',
+  data() {
+    return {
+      videos: [],
+      number: 0
+    }
+  }, // 混入toPlayer()方法
+  computed: {
+    text() {
+      return {
+        id: this.number,
+        val: this.videos[this.number]
+      }
+    }
+  },
+  mounted() {
+    this.startMove()
+  },
+  created() {
+    videoRecommend(1).then(res => {
+      console.log(res)
+    })
+
+    this.videos = [
+      {
+        vname: '哈1',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈2',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈3',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈4',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈5',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈1',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈2',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈3',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈4',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      },
+      {
+        vname: '哈5',
+        coverurl: 'https://picx.zhimg.com/v2-ea15e9203a1d95a465a78da043a9315d_xl.jpg',
+        releasetime: '2023-04-24 16:47:00',
+        visited: 10
+      }
+    ]
+
+    /* const user = this.$user
+    if (user) {
+      getRecommendVideos(user.uid).then((res) => {
+        this.videos = res
+      })
+    }*/
+  },
+
+  methods: {
+    startMove() {
+      setTimeout(() => {
+        if (this.number === this.videos.length - 1) {
+          this.number = 0
+        } else {
+          this.number += 1
+        }
+        this.startMove()
+      }, 3000)
+    }
+  }
+}
+</script>
+
+<style scoped>
+#recommend {
+  padding-top: 20px;
+  padding-right: 6%;
+  display: flex;
+  align-items: center;
+}
+
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  #recommend {
+    padding-left: 0.5%;
+    padding-right: 0.5%;
+    padding-top: 3%;
+  }
+}
+
+.text-container {
+  height: 30px;
+  overflow: hidden;
+}
+.text {
+  color: #66b1ff;
+}
+.text-container div {
+  margin-top: 5px;
+  cursor: pointer;
+  height: 20px;
+  overflow: hidden;
+  width: 450px;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 1; /*行数*/
+  -webkit-box-orient: vertical;
+}
+
+.slide-enter-active,
+.slide-leave-active {
+  transition: all 0.5s linear;
+}
+.slide-leave-to {
+  transform: translateY(-20px);
+}
+.slide-enter {
+  transform: translateY(20px);
+}
+</style>

+ 0 - 203
src/components/register-form.vue

@@ -1,203 +0,0 @@
-<template>
-  <div>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userRegistry.username"
-          placeholder="请输入用户名"
-          label="用户名"
-          :rules="[() => userRegistry.username != null || '用户名不能为空']"
-          clearable
-          @input="checkUsername"
-          @blur="selectUsername"
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userRegistry.email"
-          placeholder="请输入邮箱"
-          label="邮箱"
-          :rules="[() => userRegistry.email != null || '邮箱不能为空']"
-          type="email"
-          clearable
-          @blur="checkEmail"
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-row justify="center">
-          <v-col cols="5">
-            <v-text-field
-              v-model="userRegistry.regVerifyCode"
-              placeholder="请输入邮件验证码"
-              label="邮件验证码"
-              :rules="[() => userRegistry.regVerifyCode != null || '邮件验证码不能为空']"
-              type="email"
-              clearable
-            />
-          </v-col>
-          <v-col cols="5">
-            <v-btn color="primary" @click="getVerifyCode">获取邮件验证码</v-btn>
-          </v-col>
-        </v-row>
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userRegistry.password"
-          placeholder="请输入密码"
-          label="密码"
-          :rules="[() => userRegistry.password != null || '密码不能为空']"
-          clearable
-          type="password"
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="5">
-        <img :src="captchaCode" alt="图形验证码" title="点击刷新" style="cursor:pointer;" @click="getCaptcha">
-      </v-col>
-      <v-col cols="5">
-        <v-text-field
-          v-model="userRegistry.captchaCode"
-          placeholder="图形验证码"
-          label="图形验证码"
-          :rules="[() => userRegistry.captchaCode != null || '图形验证码不能为空']"
-          clearable
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-btn color="primary" @click="submitRegister">注册</v-btn>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import { getVerifyCode, isUsernameExist, selectUsername, isEmailExist, getCaptchaCode } from '@/api/user/account'
-
-export default {
-  name: 'Register',
-  data() {
-    return {
-      captchaCode: '',
-      userRegistry: {
-        regType: 1,
-        email: '',
-        password: '',
-        regVerifyCode: '',
-        captchaCode: '',
-        r: ''
-      }
-    }
-  },
-  created() {
-    this.getCaptcha()
-  },
-  methods: {
-    checkUsername() {
-      isUsernameExist(this.userRegistry.username)
-        .then(res => {
-          if (res.code === 0) {
-            this.showMessage = true
-            this.message = res.msg
-          } else {
-            this.showMessage = true
-            this.message = res.data
-          }
-        })
-        .catch(error => {
-          this.showMessage = true
-          this.message = error.message
-        })
-    },
-    selectUsername() {
-      selectUsername(this.userRegistry.username).then(res => {
-        if (res.code === 0) {
-          console.log(res.msg)
-        } else {
-          alert(res.data)
-        }
-      })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    checkEmail() {
-      const email = this.userRegistry.email
-      if (email === null || email === '') {
-        return
-      }
-
-      isEmailExist(this.userRegistry.email)
-        .then(res => {
-          if (res.code === 0) {
-            console.log(res.msg)
-          } else {
-            alert(res.data)
-          }
-        })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    submitRegister() {
-      var re = /^(([^()[\]\\.,;:\s@\"]+(\.[^()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
-      if (!re.test(this.userRegistry.email)) {
-        console.log('email 字段不符合要求')
-        return
-      } else if (this.userRegistry.password === '' || this.userRegistry.password.length < 6) {
-        console.log('password 字段不符合要求')
-        return
-      } else if (this.userRegistry.captchaCode === '') {
-        alert('captchaCode 或 username 字段不符合要求')
-        return
-      }
-
-      if (this.$store.state.webInfo.openInvitationRegister === 1 && this.userRegistry.invitationCode === '') {
-        console.log('openInvitationRegister 或 invitationCode 字段不符合要求')
-        return
-      }
-      // 返回到父组件
-      this.$emit('register', this.userRegistry)
-    },
-    getCaptcha() {
-      getCaptchaCode().then(res => {
-        if (res.code === 0) {
-          this.captchaCode = 'data:image/jpeg;base64,' + res.data
-        } else {
-          this.showMessage = true
-          this.message = '获取图形验证码失败, 请重新刷新页面'
-        }
-      })
-    },
-    getVerifyCode() {
-      const email = this.userRegistry.email
-      if (email == null || email === '') {
-        alert('请输入邮箱地址')
-        return
-      }
-
-      const verifyCodeReq = {}
-      verifyCodeReq.receiver = email
-      verifyCodeReq.notifyType = 1
-      getVerifyCode(verifyCodeReq).then(res => {
-        if (res.code === 0) {
-          alert(res.msg)
-        } else {
-          console.error(res.msg)
-        }
-      }).catch(error => {
-        console.error(error.message)
-      })
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 213
src/components/reset-password-form.vue

@@ -1,213 +0,0 @@
-<template>
-  <div>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userRegistry.username"
-          placeholder="请输入用户名"
-          label="用户名"
-          :rules="[() => userRegistry.username != null || '用户名不能为空']"
-          clearable
-          @input="checkUsername"
-          @blur="selectUsername"
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userRegistry.email"
-          placeholder="请输入邮箱"
-          label="邮箱"
-          :rules="[() => userRegistry.email != null || '邮箱不能为空']"
-          type="email"
-          clearable
-          @blur="checkEmail"
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-row justify="center">
-          <v-col cols="5">
-            <v-text-field
-              v-model="userRegistry.regVerifyCode"
-              placeholder="请输入邮件验证码"
-              label="邮件验证码"
-              :rules="[() => userRegistry.regVerifyCode != null || '邮件验证码不能为空']"
-              type="email"
-              clearable
-            />
-          </v-col>
-          <v-col cols="5">
-            <v-btn color="primary" @click="getVerifyCode">获取邮件验证码</v-btn>
-          </v-col>
-        </v-row>
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="10">
-        <v-text-field
-          v-model="userRegistry.password"
-          placeholder="请输入密码"
-          label="密码"
-          :rules="[() => userRegistry.password != null || '密码不能为空']"
-          clearable
-          type="password"
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-col cols="5">
-        <img :src="captchaUrl" alt="图形验证码" title="点击刷新" style="cursor:pointer;" @click="getCaptcha">
-        <!--<img :src="captchaBase64" alt="验证码" title="点击刷新" style="cursor:pointer;" @click="getCaptcha">-->
-      </v-col>
-      <v-col cols="5">
-        <v-text-field
-          v-model="userRegistry.captchaCode"
-          placeholder="图形验证码"
-          label="图形验证码"
-          :rules="[() => userRegistry.captchaCode != null || '图形验证码不能为空']"
-          clearable
-        />
-      </v-col>
-    </v-row>
-    <v-row justify="center">
-      <v-btn color="primary" @click="submitRegister">注册</v-btn>
-    </v-row>
-  </div>
-</template>
-
-<script>
-import { randomString, getCaptchaUrl } from '@/utils'
-import { getVerifyCode, isUsernameExist, selectUsername, isEmailExist } from '@/api/user/account'
-
-export default {
-  name: 'ResetPasswordForm',
-  data() {
-    return {
-      captchaUrl: '',
-      captchaBase64: '',
-      showMessage: false,
-      userRegistry: {
-        regType: 1,
-        email: '',
-        password: '',
-        regVerifyCode: '',
-        captchaCode: '',
-        r: ''
-      }
-    }
-  },
-  created() {
-    this.getCaptcha()
-  },
-  methods: {
-    checkUsername() {
-      isUsernameExist(this.userRegistry.username)
-        .then(res => {
-          if (res.code === 0) {
-            console.log(res.msg)
-          } else {
-            alert(res.data)
-          }
-        })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    selectUsername() {
-      selectUsername(this.userRegistry.username).then(res => {
-        if (res.code === 0) {
-          console.log(res.msg)
-        } else {
-          alert(res.data)
-        }
-      })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    checkEmail() {
-      const email = this.userRegistry.email
-      if (email === null || email === '') {
-        return
-      }
-
-      isEmailExist(this.userRegistry.email)
-        .then(res => {
-          if (res.code === 0) {
-            console.log(res.msg)
-          } else {
-            alert(res.data)
-          }
-        })
-        .catch(error => {
-          console.error(error.message)
-        })
-    },
-    submitRegister() {
-      var re = /^(([^()[\]\\.,;:\s@\"]+(\.[^()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
-      if (!re.test(this.userRegistry.email)) {
-        console.log('email 字段不符合要求')
-        return
-      } else if (this.userRegistry.password === '' || this.userRegistry.password.length < 6) {
-        console.log('password 字段不符合要求')
-        return
-      } else if (this.userRegistry.captchaCode === '') {
-        alert('captchaCode 或 username 字段不符合要求')
-        return
-      }
-
-      if (this.$store.state.webInfo.openInvitationRegister === 1 && this.userRegistry.invitationCode === '') {
-        console.log('openInvitationRegister 或 invitationCode 字段不符合要求')
-        return
-      }
-      // 返回到父组件
-      this.$emit('register', this.userRegistry)
-    },
-    getCaptcha() {
-      const randomStr = randomString(10)
-      this.userRegistry.r = randomStr
-      // 图片上发送点击事件时,captchaUrl 的值发生变化,然后才去请求后端
-      this.captchaUrl = getCaptchaUrl(randomStr)
-      /* getBase64Captcha(this.captchaUrl)
-        .then(res => {
-          if (res.code === 0) {
-            this.captchaBase64 = 'data:image/jpg;base64,' + res.data
-          } else {
-            alert(res.data)
-          }
-        })
-        .catch(error => {
-          console.error(error.message)
-        })*/
-    },
-    getVerifyCode() {
-      const email = this.userRegistry.email
-      if (email == null || email === '') {
-        alert('请输入邮箱地址')
-        return
-      }
-
-      const verifyCodeReq = {}
-      verifyCodeReq.receiver = email
-      verifyCodeReq.notifyType = 1
-      getVerifyCode(verifyCodeReq)
-        .then(res => {
-          if (res.code === 0) {
-            alert(res.msg)
-          } else {
-            console.error(res.msg)
-          }
-        })
-        .catch(error => {
-          console.error(error.message)
-        })
-    }
-  }
-}
-</script>
-
-<style>
-</style>

+ 0 - 121
src/components/setting/user-base-setting.vue

@@ -1,121 +0,0 @@
-<template>
-  <v-row justify="center" align="center">
-    <v-col>
-      <v-card
-        class="mx-auto"
-        outlined
-      >
-        <v-row justify="center">
-          <v-col cols="10">
-            <h2>基本信息修改</h2>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="8">
-            <v-text-field
-              v-model="userInfo.username"
-              placeholder="用户名"
-              label="用户名(25字以内)"
-              clearable
-              :rules="[() => userInfo.username != null || '标题不能为空']"
-              :disabled="usernameAlter"
-            />
-          </v-col>
-          <v-col cols="2">
-            <v-btn color="primary" @click="usernameAlter = !usernameAlter">修改</v-btn>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="8">
-            <v-textarea
-              v-model="userInfo.intro"
-              label="简介"
-              clearable
-              placeholder="填写个人简介,让更多人认识你!"
-              :disabled="introductionAlter"
-            />
-          </v-col>
-          <v-col cols="2">
-            <v-btn color="primary" @click="introductionAlter = !introductionAlter">修改</v-btn>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="2">
-            <v-btn color="primary" @click="save">保存</v-btn>
-          </v-col>
-          <v-col cols="8">
-            说明: 点击保存后才会最终修改成功
-          </v-col>
-        </v-row>
-      </v-card>
-    </v-col>
-    <v-snackbar
-      v-model="showMessage"
-      :top="true"
-      :timeout="3000"
-    >
-      {{ message }}
-
-      <template v-slot:action="{ attrs }">
-        <v-btn
-          color="pink"
-          text
-          v-bind="attrs"
-          @click="showMessage = false"
-        >
-          关闭
-        </v-btn>
-      </template>
-    </v-snackbar>
-  </v-row>
-</template>
-
-<script>
-export default {
-  name: 'UserSetting',
-  data() {
-    return {
-      userInfo: {
-        username: ''
-      },
-      usernameAlter: true,
-      introductionAlter: true,
-      message: '',
-      showMessage: false
-    }
-  },
-  created() {
-    this.userInfo = this.$store.state.user.userInfo
-  },
-  methods: {
-    save() {
-      fetch(`/api/user/update/info`, {
-        headers: {
-          'Content-Type': 'application/json; charset=UTF-8',
-          'X-XSRF-TOKEN': this.$cookies.get('XSRF-TOKEN')
-        },
-        method: 'POST',
-        credentials: 'include',
-        body: JSON.stringify(this.userInfo)
-      }).then(response => response.json())
-        .then(json => {
-          if (json.status === 200) {
-            this.$store.commit('user/SET_USER_INFO', this.userInfo)
-            this.message = '修改成功'
-            this.showMessage = true
-          } else {
-            this.message = '修改失败!' + json.message
-            this.showMessage = true
-          }
-        })
-        .catch(e => {
-          return null
-        })
-    }
-  }
-}
-</script>
-
-<style>
-
-</style>

+ 0 - 154
src/components/setting/user-head-setting.vue

@@ -1,154 +0,0 @@
-<template>
-  <v-row justify="center" align="center">
-    <v-col>
-      <v-card
-        class="mx-auto"
-        outlined
-      >
-        <v-row justify="center">
-          <v-col cols="10">
-            <h2>头像修改</h2>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10">
-            <v-avatar size="100">
-              <v-img :src="userInfo.avatarUrl" />
-            </v-avatar>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10">
-            <v-file-input
-              :rules="rules"
-              accept="image/png, image/jpeg, image/bmp"
-              placeholder="选择头像"
-              prepend-icon="mdi-camera"
-              label="头像"
-              @change="setFile"
-            />
-            <v-btn color="primary" @click="uploadFile">
-              上传
-            </v-btn>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10">
-            <v-btn color="primary" @click="save">
-              保存
-            </v-btn>
-          </v-col>
-        </v-row>
-      </v-card>
-    </v-col>
-    <v-snackbar
-      v-model="showMessage"
-      :top="true"
-      :timeout="3000"
-    >
-      {{ message }}
-
-      <template v-slot:action="{ attrs }">
-        <v-btn
-          color="pink"
-          text
-          v-bind="attrs"
-          @click="showMessage = false"
-        >
-          关闭
-        </v-btn>
-      </template>
-    </v-snackbar>
-  </v-row>
-</template>
-
-<script>
-
-export default {
-  name: 'UserSetting',
-  data() {
-    return {
-      userInfo: {
-        username: ''
-      },
-      avatarFile: null,
-      files: [],
-      rules: [
-        value => !value || value.size < 2000000 || '头像大小必须在2MB以内!'
-      ],
-      showMessage: false,
-      message: ''
-    }
-  },
-  created() {
-    this.userInfo = this.$store.state.user.userInfo
-  },
-  methods: {
-    setFile(value) {
-      this.avatarFile = value
-      this.files = []
-      this.files.push(value)
-    },
-    uploadFile() {
-      if (this.files.length === 0) {
-        this.message = '请先选择图片,然后上传!'
-        this.showMessage = true
-        return
-      }
-      const formData = new FormData()
-      formData.append('videoFileId', 'test')
-      formData.append('file', this.avatarFile)
-      fetch(`//api.reghao.cn/api/file/upload/image`, {
-        headers: {
-          'X-XSRF-TOKEN': this.$cookies.get('XSRF-TOKEN')
-        },
-        method: 'POST',
-        credentials: 'include',
-        body: formData
-      }).then(response => response.json())
-        .then(json => {
-          if (json.code === 0) {
-            this.userInfo.avatarUrl = json.data.imageUrl
-            this.message = '上传成功,请点击保存,保存头像设置!'
-            this.showMessage = true
-          } else {
-            this.message = '上传失败,请重试!' + json.message
-            this.showMessage = true
-          }
-        })
-        .catch(e => {
-          return null
-        })
-    },
-    save() {
-      fetch(`/api/user/update/avatar`, {
-        headers: {
-          'Content-Type': 'application/json; charset=UTF-8',
-          'X-XSRF-TOKEN': this.$cookies.get('XSRF-TOKEN')
-        },
-        method: 'POST',
-        credentials: 'include',
-        body: JSON.stringify(this.userInfo)
-      }).then(response => response.json())
-        .then(json => {
-          if (json.status === 200) {
-            this.$store.commit('user/SET_USER_INFO', this.userInfo)
-            this.message = '保存成功!'
-            this.showMessage = true
-          } else {
-            //
-            this.message = '保存失败!' + json.message
-            this.showMessage = true
-          }
-        })
-        .catch(e => {
-          return null
-        })
-    }
-  }
-}
-</script>
-
-<style>
-
-</style>

+ 0 - 10
src/components/setting/user-login-log.vue

@@ -1,10 +0,0 @@
-<template>
-  <span><h2>手机换绑</h2></span>
-</template>
-
-<script>
-</script>
-
-<style>
-
-</style>

+ 0 - 158
src/components/setting/user-password-setting.vue

@@ -1,158 +0,0 @@
-<template>
-  <v-row justify="center" align="center">
-    <v-col>
-      <v-card
-        class="mx-auto"
-        outlined
-      >
-        <v-row justify="center">
-          <v-col cols="10">
-            <h2>密码修改</h2>
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10">
-            <v-text-field
-              v-model="passoword.oldPassword"
-              placeholder="原始密码"
-              label="原始密码"
-              clearable
-              type="password"
-              :rules="[() => passoword.oldPassword != null || '原始密码不能为空']"
-            />
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10">
-            <v-text-field
-              v-model="passoword.newPassword"
-              placeholder="新密码"
-              label="新密码"
-              clearable
-              type="password"
-              :rules="[() => passoword.newPassword != null || '新密码不能为空']"
-            />
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="10">
-            <v-text-field
-              v-model="temp"
-              placeholder="请再输入一遍"
-              label="请再输入一遍"
-              clearable
-              type="password"
-              :rules="[() => temp == passoword.newPassword || '两次密码不相同']"
-            />
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="5">
-            <v-img :src="captchaUrl" alt="验证码" title="点击刷新" style="cursor:pointer;" max-width="200" @click="getCaptcha" />
-          </v-col>
-          <v-col cols="5">
-            <v-text-field
-              v-model="passoword.verifyCode"
-              placeholder="验证码"
-              label="验证码"
-              :rules="[() => passoword.verifyCode != null || '验证码不能为空']"
-              clearable
-            />
-          </v-col>
-        </v-row>
-        <v-row justify="center">
-          <v-col cols="2">
-            <v-btn color="primary" @click="save">保存</v-btn>
-          </v-col>
-          <v-col cols="8">
-            说明: 修改密码后需要重新登录
-          </v-col>
-        </v-row>
-      </v-card>
-    </v-col>
-    <v-snackbar
-      v-model="showMessage"
-      :top="true"
-      :timeout="3000"
-    >
-      {{ message }}
-
-      <template v-slot:action="{ attrs }">
-        <v-btn
-          color="pink"
-          text
-          v-bind="attrs"
-          @click="showMessage = false"
-        >
-          关闭
-        </v-btn>
-      </template>
-    </v-snackbar>
-  </v-row>
-</template>
-
-<script>
-export default {
-  name: 'UserSetting',
-  data() {
-    return {
-      passoword: {
-        oldPassword: '',
-        newPassword: '',
-        verifyCode: ''
-      },
-      temp: '',
-      captchaUrl: process.env.VUE_APP_BASE_API + '/api/account/code/captcha',
-      showMessage: false,
-      message: ''
-    }
-  },
-  created() {
-  },
-  methods: {
-    getCaptcha() {
-      this.captchaUrl = process.env.VUE_APP_BASE_API + '/api/account/code/captcha?t=' + new Date().getTime()
-    },
-    save() {
-      if (this.passoword.oldPassword === '') {
-        return
-      }
-      if (this.passoword.newPassword === '') {
-        return
-      }
-      if (this.passoword.newPassword !== this.temp) {
-        return
-      }
-      if (this.passoword.verifyCode === '') {
-        return
-      }
-      fetch(`/api/user/update/password`, {
-        headers: {
-          'Content-Type': 'application/json; charset=UTF-8',
-          'X-XSRF-TOKEN': this.$cookies.get('XSRF-TOKEN')
-        },
-        method: 'POST',
-        credentials: 'include',
-        body: JSON.stringify(this.passoword)
-      }).then(response => response.json())
-        .then(json => {
-          if (json.status === 200) {
-            this.message = '修改成功,即将跳转,请重新登录!'
-            this.showMessage = true
-            this.$store.commit('user/SET_USER_INFO', null)
-            this.$router.push('/login')
-          } else {
-            this.message = '修改失败!' + json.message
-            this.showMessage = true
-          }
-        })
-        .catch(e => {
-          return null
-        })
-    }
-  }
-}
-</script>
-
-<style>
-</style>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.