├── static └── .gitkeep ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── src ├── assets │ ├── logo.png │ ├── star.png │ └── imgLoading.svg ├── store │ ├── mutation-types.js │ ├── actions.js │ ├── index.js │ └── mutations.js ├── base │ └── rem.js ├── main.js ├── components │ ├── common │ │ ├── loading-copy.svg │ │ ├── Toast.js │ │ ├── Loading.vue │ │ ├── loading.svg │ │ └── vfooter.vue │ ├── Search.vue │ ├── More.vue │ ├── Home.vue │ ├── Login.vue │ ├── Me.vue │ └── Detail.vue ├── App.vue ├── router │ └── index.js ├── data │ └── fetchData.js └── style │ ├── home.scss │ ├── reset.scss │ ├── detail.scss │ └── me.scss ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── .babelrc ├── index.html ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wunci/vue-video/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wunci/vue-video/HEAD/src/assets/star.png -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ADD_USER = 'ADD_USER'; 2 | export const INIT_VDIEO_DATA = 'INIT_VDIEO_DATA'; 3 | export const INIT_COMMENT_DATA = 'INIT_COMMENT_DATA'; 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | createUser({ commit }, user) { 3 | commit('ADD_USER', user); 4 | }, 5 | initVideoData({ commit }, data) { 6 | commit('INIT_VDIEO_DATA', data); 7 | }, 8 | initMeCommentData({ commit }, data) { 9 | commit('INIT_COMMENT_DATA', data); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import actions from './actions'; 4 | import mutations from './mutations'; 5 | 6 | Vue.use(Vuex); 7 | 8 | const state = { 9 | userInfo: '', 10 | videoData: null, 11 | meCommentDatas: null, 12 | }; 13 | 14 | export default new Vuex.Store({ 15 | state, 16 | actions, 17 | mutations, 18 | }); 19 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as type from './mutation-types.js'; 2 | 3 | export default { 4 | [type.ADD_USER](state, user) { 5 | state.userInfo = user.userName; 6 | }, 7 | [type.INIT_VDIEO_DATA](state, data) { 8 | state.videoData = data['initVideoData']; 9 | }, 10 | [type.INIT_COMMENT_DATA](state, data) { 11 | state.meCommentDatas = { ...state.meCommentDatas, ...data }; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/base/rem.js: -------------------------------------------------------------------------------- 1 | !(function(doc, win) { 2 | var docEle = doc.documentElement, 3 | evt = 'onorientationchange' in window ? 'orientationchange' : 'resize', 4 | fn = function() { 5 | var width = docEle.clientWidth; 6 | width = width > 375 ? 375 : width; 7 | width && (docEle.style.fontSize = width / 7.5 + 'px'); 8 | }; 9 | win.addEventListener(evt, fn, false); 10 | doc.addEventListener('DOMContentLoaded', fn, false); 11 | })(document, window); 12 | document.body.addEventListener('touchstart', function() {}, false); 13 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | import "./base/rem"; 6 | import VueLazyload from "vue-lazyload"; 7 | import Toast from "./components/common/Toast.js"; 8 | import promise from "es6-promise"; 9 | import Img from "./assets/imgLoading.svg"; 10 | promise.polyfill(); 11 | Vue.use(Toast); 12 | Vue.use(VueLazyload, { 13 | loading: Img 14 | }); 15 | Vue.config.productionTip = false; 16 | 17 | new Vue({ 18 | el: "#app", 19 | router, 20 | store, 21 | template: "", 22 | components: { App } 23 | }); 24 | -------------------------------------------------------------------------------- /src/components/common/loading-copy.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | vue-video 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/common/Toast.js: -------------------------------------------------------------------------------- 1 | var Toast = {}; 2 | Toast.install = function(Vue, options) { 3 | Vue.prototype.$toast = options => { 4 | let icon = options.icon == 'success' ? 'icon-chenggong' : 'icon-shibai'; 5 | 6 | if (document.getElementsByClassName('dialog').length) return; 7 | 8 | let toastCpl = Vue.extend({ 9 | template: `
10 |
11 | 12 |

${options.message}

13 |
14 |
`, 15 | }); 16 | let tpl = new toastCpl().$mount().$el; 17 | document.body.appendChild(tpl); 18 | setTimeout(function() { 19 | document.body.removeChild(tpl); 20 | options.success && options.success(); 21 | }, 1500); 22 | }; 23 | }; 24 | module.exports = Toast; 25 | -------------------------------------------------------------------------------- /src/components/common/Loading.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 35 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /src/components/common/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 15 | 16 | 17 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/common/vfooter.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | 26 | 63 | -------------------------------------------------------------------------------- /src/assets/imgLoading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report, 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8000, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 53 | 54 | 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "video", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "wclimb <875246904@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "vue": "^2.3.3", 14 | "vue-router": "^2.6.0", 15 | "vuex": "^2.3.1" 16 | }, 17 | "devDependencies": { 18 | "autoprefixer": "^7.1.2", 19 | "axios": "^0.18.0", 20 | "babel-core": "^6.22.1", 21 | "babel-loader": "^7.1.1", 22 | "babel-plugin-transform-runtime": "^6.22.0", 23 | "babel-preset-env": "^1.3.2", 24 | "babel-preset-stage-2": "^6.22.0", 25 | "babel-register": "^6.22.0", 26 | "chalk": "^2.0.1", 27 | "connect-history-api-fallback": "^1.3.0", 28 | "copy-webpack-plugin": "^4.0.1", 29 | "css-loader": "^0.28.4", 30 | "cssnano": "^3.10.0", 31 | "es6-promise": "^4.2.4", 32 | "eventsource-polyfill": "^0.9.6", 33 | "express": "^4.14.1", 34 | "extract-text-webpack-plugin": "^2.0.0", 35 | "file-loader": "^0.11.1", 36 | "friendly-errors-webpack-plugin": "^1.1.3", 37 | "html-webpack-plugin": "^2.28.0", 38 | "http-proxy-middleware": "^0.17.3", 39 | "node-sass": "^4.5.3", 40 | "opn": "^5.1.0", 41 | "optimize-css-assets-webpack-plugin": "^2.0.0", 42 | "ora": "^1.2.0", 43 | "rimraf": "^2.6.0", 44 | "sass-loader": "^6.0.6", 45 | "semver": "^5.3.0", 46 | "shelljs": "^0.7.6", 47 | "style-loader": "^0.18.2", 48 | "url-loader": "^0.5.8", 49 | "vue-lazyload": "^1.1.3", 50 | "vue-loader": "^12.1.0", 51 | "vue-style-loader": "^3.0.1", 52 | "vue-template-compiler": "^2.3.3", 53 | "webpack": "^2.6.1", 54 | "webpack-bundle-analyzer": "^2.2.1", 55 | "webpack-dev-middleware": "^1.10.0", 56 | "webpack-hot-middleware": "^2.18.0", 57 | "webpack-merge": "^4.1.0" 58 | }, 59 | "engines": { 60 | "node": ">= 4.0.0", 61 | "npm": ">= 3.0.0" 62 | }, 63 | "browserslist": [ 64 | "> 1%", 65 | "last 2 versions", 66 | "last 10 Chrome versions", 67 | "last 5 Firefox versions", 68 | "Safari >= 6", 69 | "ie > 8" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 写在前面 2 | 3 | 该项目有两个版本,vue和react 4 | 5 | > 前端项目地址 https://github.com/wunci/vue-video   6 | 7 | > 后端项目地址 https://github.com/wunci/video-admin 8 | 9 | > API接口地址 https://github.com/wunci/video-admin/blob/master/API.md (未更新,接口现在重写过) 10 | 11 | react版现在已经完成 12 | 13 | > react版项目地址 https://github.com/wunci/react-video 14 | 15 | ## 技术栈(Vue2.js + Node.js 全栈项目) 16 | 17 | > 由于页面不是很多,vuex用的不多,关键掌握怎么实现就好了 18 | 19 | `vue2` + `vuex` + `vue-router` + `webpack` + `fetch/axios` + `sass` + `flex` + `svg` + `阿里字体图标` 20 | 21 | ## 运行 22 | 23 | ``` 24 | git clone https://github.com/wunci/vue-video.git 25 | 26 | cd vue-video 27 | 28 | npm install 建议使用淘宝镜像(https://npm.taobao.org/) => cnpm i 29 | 30 | npm run dev (运行项目) 31 | 32 | npm run build (打包项目) 33 | 34 | ps: 如果打包之后文件运行不了,请打包之前把路由的 mode:'history'注释掉,该功能去掉了url中丑陋的 # 号 35 | 36 | ``` 37 | ## 功能 38 | 39 | * 1. 注册登录登出 + 验证码 密码检测,如果用户不存在则自动创建 40 | * 2. 检测是否登录,如果没有登录则不允许评论和评价 41 | * 3. 可以上传影片到后台,进行前台展示 42 | * 4. 评分功能,初始化评分可以自由设置,如果没有人like则默认显示原始评分,如果有则计算当前vide的评分 43 | * 5. 修改用户名,检测用户名是否跟其他人重复 44 | * 6. 上传头像,默认没有头像 45 | * 7. 评论功能,评论之后可以在个人中心展示,并且可以删除 46 | * 8. 搜索功能,可以搜索存在的影片,如果没有则显示无结果 47 | * 9. 自己喜欢的video和评论的内容会在个人中心显示 48 | 49 | 综上: 50 | 51 | - [x] 注册 52 | - [x] 登录 53 | - [x] 登出 54 | - [x] 验证码 55 | - [x] 详情页 56 | - [x] 分类 57 | - [x] 分类影视列表 58 | - [x] 修改用户名 59 | - [x] 上传头像 60 | - [x] 评论 61 | - [x] 删除评论(可以左滑评论删除) 62 | - [x] 搜索 63 | - [x] 个人中心数据 64 | 65 | 如果觉得对你有帮助还望关注一下,有问题可以及时提哟,觉得不错的话`star`一下也是可以的哟 66 | 67 | ## 前端线上地址 68 | 69 | 项目是手机端的,请使用谷歌浏览器手机预览模式 70 | 71 | > 首页默认一种类别只显示10个,可以查看更多显示全部 72 | 73 | 74 | ## 后端线上地址 75 | 76 | 技术栈:`Node` + `Koa2` + `Mysql` 77 | GitHub: [管理后台](https://github.com/wunci/video-admin) 78 | 79 | 80 | ## 目录结构 81 | 82 | ``` 83 | |-- build // webpack配置文件 84 | |-- config // 项目打包路径 85 | |-- src // 源码目录 86 | | |-- assets // 图片文件 87 | | |-- base // 移动端适配 88 | | |-- components // 组件 89 | | |-- data // 接口 90 | | |-- router // 路由配置 91 | | |-- store // 状态管理 92 | | |-- style // 样式 93 | | App.vue // 页面入口文件 94 | | main.js // 程序入口文件 95 | |-- static // 静态资源 96 | |-- .babelrc // ES6语法编译配置 97 | |-- .editorconfig // 代码编写规格 98 | |-- .gitignore // git忽略的文件 99 | |-- .postcssrc.js // post-loader的插件配置文件 100 | |-- index.html // 入口html文件 101 | |-- package.json // 项目及工具的依赖配置文件 102 | 103 | ``` 104 | 105 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | import Search from "@/components/Search"; 4 | import Home from "@/components/Home"; 5 | import More from "@/components/More"; 6 | import Me from "@/components/Me"; 7 | import Login from "@/components/Login"; 8 | import Detail from "@/components/Detail"; 9 | // const Home = r => require.ensure([], () => r(require('../components/Home')), 'Home') 10 | // const More = r => require.ensure([], () => r(require('../components/More')), 'More') 11 | // const Me = r => require.ensure([], () => r(require('../components/Me')), 'Me') 12 | // const Login = r => require.ensure([], () => r(require('../components/Login')), 'Login') 13 | // const Search = r => require.ensure([], () => r(require('../components/Search')), 'Search') 14 | // const Detail = r => require.ensure([], () => r(require('../components/Detail')), 'Detail') 15 | 16 | Vue.use(Router); 17 | 18 | export default new Router({ 19 | mode: "history", 20 | routes: [ 21 | { 22 | path: "*", 23 | redirect: "/", 24 | meta: { 25 | index: 0 26 | } 27 | }, 28 | { 29 | path: "/", 30 | name: "home", 31 | component: Home, 32 | meta: { 33 | index: 1 34 | } 35 | // children:[ 36 | // { 37 | // name:'search', 38 | // path: '/home/search', 39 | // component: Search 40 | // } 41 | // ], 42 | // meta: { 43 | // keepAlive: true // 需要被缓存 44 | // } 45 | }, 46 | { 47 | path: "/search", 48 | name: "search", 49 | component: Search, 50 | meta: { 51 | index: 2 52 | } 53 | }, 54 | { 55 | path: "/video/:id", 56 | name: "video", 57 | component: Detail, 58 | meta: { 59 | index: 3 60 | } 61 | }, 62 | { 63 | path: "/all", 64 | name: "all", 65 | component: More, 66 | meta: { 67 | index: 2 68 | } 69 | }, 70 | { 71 | path: "/movie", 72 | name: "movie", 73 | component: More, 74 | meta: { 75 | index: 2 76 | } 77 | }, 78 | { 79 | path: "/tv", 80 | name: "tv", 81 | component: More, 82 | meta: { 83 | index: 2 84 | } 85 | }, 86 | { 87 | path: "/zy", 88 | name: "zy", 89 | component: More, 90 | meta: { 91 | index: 2 92 | } 93 | }, 94 | { 95 | path: "/login", 96 | name: "login", 97 | component: Login, 98 | meta: { 99 | index: 4 100 | } 101 | }, 102 | { 103 | path: "/me", 104 | name: "me", 105 | component: Me, 106 | meta: { 107 | index: 2 108 | } 109 | } 110 | ], 111 | scrollBehavior(to, from, savedPosition) { 112 | return { x: 0, y: 0 }; 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /src/data/fetchData.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | export const url = "http://vue.wclimb.site"; 3 | // export const url = 'http://localhost:3000'; 4 | let $axios = axios.create({ 5 | baseURL: url + "/vi/" 6 | }); 7 | function getCookie(name) { 8 | var arr, 9 | reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); 10 | if ((arr = document.cookie.match(reg))) return unescape(arr[2]); 11 | else return ""; 12 | } 13 | 14 | function $fetch(method, url, data) { 15 | return new Promise((reslove, reject) => { 16 | $axios({ 17 | method, 18 | url, 19 | data: data, 20 | headers: { 21 | token: getCookie("token") 22 | } 23 | }) 24 | .then(res => { 25 | let body = res.data; 26 | if (body.code == 200 || body.code == 201) { 27 | reslove(body); 28 | } else { 29 | reject(body); 30 | } 31 | }) 32 | .catch(err => { 33 | reject(err); 34 | }); 35 | }); 36 | } 37 | 38 | // 首页初始化数据 39 | export const initHome = () => $fetch("get", "list"); 40 | 41 | // 验证码 42 | export const yzmChange = () => $fetch("get", "getYzm"); 43 | 44 | // 注册登录 45 | export const signin = (userName, password) => 46 | $fetch("post", "signin", { userName, password }); 47 | 48 | // 个人评论 49 | export const meComment = userName => 50 | $fetch("post", "getUserComment", { userName }); 51 | 52 | // 获取用户喜欢不喜欢数据 53 | export const meLike = userName => 54 | $fetch("post", "getUserLikeData", { userName }); 55 | 56 | // 删除评论--- 57 | export const meDelete = (commentId, userName) => 58 | $fetch("post", "deleteComment", { userName, commentId }); 59 | 60 | // 上传头像---- 61 | export const uploadAvator = (userName, avator) => 62 | $fetch("post", "uploadAvator", { avator, userName }); 63 | 64 | // 获取头像 65 | export const getAvator = userName => 66 | $fetch("post", "getUserAvator", { userName }); 67 | 68 | // 编辑用户名 69 | export const editNameData = (oldName, newName) => 70 | $fetch("post", "editUserName", { newName, userName: oldName }); 71 | 72 | // 搜索 73 | export const search = val => $fetch("post", "search", { val }); 74 | 75 | // 获取单个video数据 76 | export const singleVideoData = videoId => 77 | $fetch("post", "getVideoById", { videoId }); 78 | 79 | // 获取评论 80 | export const getVideoComment = videoId => 81 | $fetch("post", "getVideoComment", { videoId }); 82 | 83 | // 初始化单个video的like信息(判断用户当前的选项) 84 | export const getInitVideoLikeData = (videoId, userName) => 85 | $fetch("post", "getUserSingleLike", { userName, videoId }); 86 | 87 | // 提交用户选择like数据 88 | export const postVideoLikeData = ( 89 | videoId, 90 | like, 91 | userName, 92 | videoName, 93 | videoImg, 94 | star 95 | ) => 96 | $fetch("post", "postUserLike", { 97 | like, 98 | userName, 99 | videoName, 100 | videoImg, 101 | star, 102 | videoId 103 | }); 104 | 105 | // 发表评论 106 | export const reportComment = (videoId, userName, content, videoName, avator) => 107 | $fetch("post", "postComment", { 108 | videoId, 109 | userName, 110 | content, 111 | videoName, 112 | avator 113 | }); 114 | -------------------------------------------------------------------------------- /src/style/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | a { 3 | color: #444; 4 | } 5 | 6 | padding-bottom: 1.2rem; 7 | font-size: 12px; 8 | 9 | #search { 10 | padding: 0.15rem 0; 11 | background: #0fce0f; 12 | 13 | a { 14 | display: block; 15 | display: flex; 16 | width: 100%; 17 | justify-content: center; 18 | 19 | .search_input { 20 | width: 95%; 21 | border: 1px solid #0fce0f; 22 | height: 0.6rem; 23 | border-radius: 0.2rem; 24 | padding-left: 0.15rem; 25 | background: #fff; 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | color: #333; 30 | 31 | i { 32 | font-size: 20px; 33 | } 34 | 35 | &:active { 36 | background: #f2f2f2; 37 | } 38 | } 39 | } 40 | } 41 | 42 | .video_list { 43 | & + .video_list { 44 | border-top: 0.2rem solid #f2f2f2; 45 | margin-top: 0.3rem; 46 | } 47 | 48 | padding-top: 0.3rem; 49 | 50 | .video_list_header { 51 | display: flex; 52 | justify-content: space-between; 53 | padding-right: 0.3rem; 54 | font-size: 17px; 55 | margin-bottom: 0.3rem; 56 | 57 | h3 { 58 | font-weight: bold; 59 | border-left: 3px solid #0fce0f; 60 | padding-left: 0.2rem; 61 | color: #666; 62 | 63 | span { 64 | font-weight: normal; 65 | margin-left: 0.05rem; 66 | font-size: 15px; 67 | } 68 | } 69 | 70 | a { 71 | font-size: 14px; 72 | color: #0fce0f; 73 | 74 | i { 75 | margin-left: -5px; 76 | } 77 | } 78 | } 79 | 80 | ul { 81 | display: flex; 82 | margin-top: 0.2rem; 83 | width: 100%; 84 | overflow-x: scroll; 85 | overflow-y: hidden; 86 | white-space: nowrap; 87 | -webkit-overflow-scrolling: touch; 88 | 89 | li { 90 | flex: none; 91 | margin-left: 0.2rem; 92 | width: 2.3rem; 93 | 94 | h3 { 95 | font-size: 16px; 96 | margin: 0.1rem 0; 97 | height: 0.5rem; 98 | line-height: 0.5rem; 99 | white-space: nowrap; 100 | overflow: hidden; 101 | text-overflow: ellipsis; 102 | } 103 | 104 | img { 105 | width: 100%; 106 | &[lazy="loaded"] { 107 | object-fit: cover; 108 | } 109 | &[lazy="loading"] { 110 | object-fit: contain; 111 | } 112 | } 113 | 114 | a { 115 | > div:nth-child(1) { 116 | width: 100%; 117 | height: 3.4rem; 118 | overflow: hidden; 119 | } 120 | } 121 | 122 | .color { 123 | color: yellow; 124 | } 125 | 126 | div { 127 | display: flex; 128 | 129 | span { 130 | margin-left: 0.1rem; 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | #search_main { 139 | position: fixed; 140 | left: 0; 141 | top: 0; 142 | width: 100%; 143 | height: 100%; 144 | background: #fff; 145 | z-index: 9999999; 146 | } 147 | 148 | .router-in-enter-active, 149 | .router-in-leave-active { 150 | transition: all 0.5s; 151 | } 152 | 153 | .router-in-enter, 154 | .router-in-leave-active { 155 | transform: translateX(100%); 156 | } 157 | -------------------------------------------------------------------------------- /src/components/Search.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 78 | 79 | 152 | -------------------------------------------------------------------------------- /src/components/More.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 84 | 85 | 159 | -------------------------------------------------------------------------------- /src/style/reset.scss: -------------------------------------------------------------------------------- 1 | html { 2 | color: #000; 3 | background: #fff; 4 | overflow-y: scroll; 5 | -webkit-text-size-adjust: 100%; 6 | -ms-text-size-adjust: 100%; 7 | font-size: 16px; 8 | } 9 | 10 | html * { 11 | outline: 0; 12 | -webkit-text-size-adjust: none; 13 | -webkit-tap-highlight-color: transparent; 14 | } 15 | 16 | body { 17 | font-size: 16px; 18 | width: 7.5rem; 19 | margin: 0 auto; 20 | // position: relative; 21 | // min-height: 100%; 22 | // /* 解决 iOS safari 卡顿问题 */ 23 | // overflow-y: scroll; 24 | // -webkit-overflow-scrolling: touch; 25 | // /* 设置 iOS 设备点击元素高亮为透明色 */ 26 | // -webkit-tap-highlight-color: transparent; 27 | } 28 | 29 | body, 30 | html { 31 | font-family: sans-serif; 32 | } 33 | 34 | article, 35 | aside, 36 | blockquote, 37 | body, 38 | button, 39 | code, 40 | dd, 41 | details, 42 | div, 43 | dl, 44 | dt, 45 | fieldset, 46 | figcaption, 47 | figure, 48 | footer, 49 | form, 50 | h1, 51 | h2, 52 | h3, 53 | h4, 54 | h5, 55 | h6, 56 | header, 57 | hgroup, 58 | hr, 59 | input, 60 | legend, 61 | li, 62 | menu, 63 | nav, 64 | ol, 65 | p, 66 | pre, 67 | section, 68 | td, 69 | textarea, 70 | th, 71 | ul { 72 | margin: 0; 73 | padding: 0; 74 | box-sizing: border-box; 75 | } 76 | 77 | input, 78 | select, 79 | textarea { 80 | font-size: 100%; 81 | } 82 | 83 | table { 84 | border-collapse: collapse; 85 | border-spacing: 0; 86 | } 87 | 88 | fieldset, 89 | img { 90 | border: 0; 91 | } 92 | 93 | abbr, 94 | acronym { 95 | border: 0; 96 | font-variant: normal; 97 | } 98 | 99 | del { 100 | text-decoration: line-through; 101 | } 102 | 103 | address, 104 | caption, 105 | cite, 106 | code, 107 | dfn, 108 | em, 109 | th, 110 | var { 111 | font-style: normal; 112 | font-weight: 500; 113 | } 114 | 115 | ol, 116 | ul, 117 | li { 118 | list-style: none; 119 | } 120 | 121 | caption, 122 | th { 123 | text-align: left; 124 | } 125 | 126 | h1, 127 | h2, 128 | h3, 129 | h4, 130 | h5, 131 | h6 { 132 | font-size: 100%; 133 | font-weight: 500; 134 | } 135 | 136 | q:after, 137 | q:before { 138 | content: ""; 139 | } 140 | 141 | sub, 142 | sup { 143 | font-size: 75%; 144 | line-height: 0; 145 | position: relative; 146 | vertical-align: baseline; 147 | } 148 | 149 | sup { 150 | top: -0.5em; 151 | } 152 | 153 | sub { 154 | bottom: -0.25em; 155 | } 156 | 157 | a:hover { 158 | text-decoration: none; 159 | } 160 | 161 | a, 162 | ins { 163 | text-decoration: none; 164 | } 165 | 166 | button, 167 | input[type="button"], 168 | input[type="file"], 169 | input[type="submit"] { 170 | cursor: pointer; 171 | -webkit-appearance: none; 172 | } 173 | 174 | body { 175 | font-size: 12px; 176 | height: 100%; 177 | } 178 | 179 | .icon { 180 | width: 1em; 181 | height: 1em; 182 | vertical-align: -0.15em; 183 | fill: currentColor; 184 | overflow: hidden; 185 | } 186 | 187 | .icon-wjx { 188 | color: rgb(255, 172, 45); 189 | } 190 | 191 | .icon-wjx-copy { 192 | color: #ccc; 193 | } 194 | 195 | .loading { 196 | width: 100%; 197 | height: 100%; 198 | text-align: center; 199 | position: fixed; 200 | top: 0; 201 | left: 0; 202 | 203 | div { 204 | width: 100px; 205 | height: 100px; 206 | border-radius: 50%; 207 | position: absolute; 208 | left: 50%; 209 | top: 50%; 210 | transform: translate(-50%, -50%); 211 | background: rgba($color: #1abc9c, $alpha: 0.8); 212 | } 213 | 214 | img { 215 | margin-top: 5px; 216 | } 217 | 218 | z-index: 99999; 219 | } 220 | 221 | .mini-loading { 222 | text-align: center; 223 | height: 44px; 224 | line-height: 44px; 225 | vertical-align: middle; 226 | transition: all 0.5s; 227 | overflow: hidden; 228 | 229 | img { 230 | height: 44px; 231 | } 232 | } 233 | 234 | .icon-chenggong { 235 | color: #1abc9c; 236 | } 237 | 238 | .icon-shibai { 239 | color: rgb(250, 84, 84); 240 | } 241 | 242 | .starList { 243 | height: 15px; 244 | width: 75px; 245 | background: url(assets/star.png); 246 | } 247 | 248 | .fade-enter-active, 249 | .fade-leave-active { 250 | transition: all 0.3s; 251 | } 252 | 253 | .fade-enter, 254 | .fade-leave-active { 255 | opacity: 0; 256 | } 257 | 258 | .slide-in-enter-active, 259 | .slide-in-leave-active { 260 | transition: all 0.6s; 261 | } 262 | 263 | .slide-in-enter, 264 | .slide-in-leave-active { 265 | transform: translateX(100%); 266 | } 267 | 268 | @keyframes tipMove { 269 | 0% { 270 | transform: scale(1); 271 | } 272 | 273 | 35% { 274 | transform: scale(0.8); 275 | } 276 | 277 | 70% { 278 | transform: scale(1.1); 279 | } 280 | 281 | 100% { 282 | transform: scale(1); 283 | } 284 | } 285 | 286 | .dialog { 287 | position: fixed; 288 | top: 50%; 289 | left: 50%; 290 | min-width: 2rem; 291 | transform: translate(-50%, -50%); 292 | z-index: 999999; 293 | text-align: center; 294 | 295 | .dialog_wrap { 296 | padding: 0.2rem 0.25rem; 297 | background: rgba(0, 0, 0, 0.5); 298 | border-radius: 5px; 299 | 300 | i { 301 | font-size: 50px; 302 | position: relative; 303 | border-radius: 50%; 304 | } 305 | 306 | p { 307 | font-size: 18px; 308 | margin-top: 0.1rem; 309 | color: #fff; 310 | } 311 | 312 | b { 313 | display: block; 314 | width: 0.8rem; 315 | height: 0.8rem; 316 | background: #fff; 317 | position: absolute; 318 | top: 0; 319 | z-index: -1; 320 | } 321 | } 322 | } 323 | 324 | .aniDialog { 325 | animation: tipMove 0.4s; 326 | } 327 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 125 | 126 | 176 | 177 | 178 | 181 | -------------------------------------------------------------------------------- /src/style/detail.scss: -------------------------------------------------------------------------------- 1 | .detail { 2 | padding-bottom: 1rem; 3 | min-height: 100%; 4 | overflow-y: auto; 5 | overflow-x: hidden; 6 | } 7 | 8 | header { 9 | height: 3rem; 10 | position: relative; 11 | display: flex; 12 | background: #4ebf60; 13 | 14 | .wrap { 15 | display: flex; 16 | color: #fff; 17 | 18 | img { 19 | width: 1.7rem; 20 | height: 2.4rem; 21 | object-fit: cover; 22 | margin-left: 0.4rem; 23 | position: relative; 24 | bottom: -1rem; 25 | } 26 | 27 | .video_name { 28 | margin: 1.4rem 0.5rem; 29 | 30 | h3 { 31 | font-size: 24px; 32 | white-space: nowrap; 33 | width: 4.5rem; 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | } 37 | 38 | .score_wrap { 39 | display: flex; 40 | 41 | strong { 42 | font-size: 28px; 43 | color: #fff; 44 | margin-right: 0.2rem; 45 | font-weight: normal; 46 | } 47 | 48 | .score { 49 | font-size: 14px; 50 | color: #f2f2f2; 51 | } 52 | } 53 | } 54 | 55 | .back { 56 | position: absolute; 57 | top: 0.2rem; 58 | left: 0.3rem; 59 | font-size: 13px; 60 | 61 | i { 62 | font-size: 15px; 63 | } 64 | 65 | border: 1px solid #fff; 66 | padding: 0.06rem 0.14rem; 67 | border-radius: 4px; 68 | 69 | &:active { 70 | background: rgba(0, 0, 0, 0.1); 71 | } 72 | } 73 | } 74 | 75 | & > a { 76 | position: absolute; 77 | right: 0.2rem; 78 | top: 0.2rem; 79 | } 80 | } 81 | 82 | .video_txt { 83 | margin-top: 0.8rem; 84 | font-size: 16px; 85 | 86 | .video_txt_wrap { 87 | margin: 0 0.4rem; 88 | color: #111; 89 | 90 | p:nth-child(2) { 91 | margin: 0.05rem 0; 92 | } 93 | } 94 | } 95 | 96 | .video_about { 97 | padding: 0 0.4rem 0.8rem; 98 | 99 | h3 { 100 | margin: 0.5rem 0 0.3rem 0; 101 | color: #afafaf; 102 | font-size: 15px; 103 | } 104 | 105 | p { 106 | font-size: 14px; 107 | text-indent: 2em; 108 | word-wrap: break-word; 109 | word-break: break-all; 110 | } 111 | } 112 | 113 | .video_comments { 114 | margin-left: 0.4rem; 115 | 116 | h3 { 117 | font-size: 16px; 118 | margin-bottom: 0.2rem; 119 | color: #afafaf; 120 | } 121 | 122 | ul { 123 | li { 124 | & + li { 125 | margin-top: 0.3rem; 126 | } 127 | 128 | display: flex; 129 | 130 | .avator { 131 | width: 0.6rem; 132 | height: 0.6rem; 133 | margin: 0.05rem 0.2rem 0 0; 134 | border-radius: 50%; 135 | background: #4ebf60; 136 | text-align: center; 137 | line-height: 0.6rem; 138 | color: #fff; 139 | font-size: 16px; 140 | overflow: hidden; 141 | flex: none; 142 | 143 | img { 144 | width: 100%; 145 | height: 100%; 146 | &[lazy="loaded"] { 147 | object-fit: cover; 148 | } 149 | &[lazy="loading"] { 150 | object-fit: contain; 151 | } 152 | } 153 | } 154 | 155 | .comments_detail { 156 | font-size: 16px; 157 | width: 95%; 158 | margin-right: 0.5rem; 159 | 160 | p { 161 | font-size: 14px; 162 | color: #afafaf; 163 | } 164 | 165 | h4 { 166 | width: 70%; 167 | white-space: nowrap; 168 | text-overflow: ellipsis; 169 | overflow: hidden; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | .like_list { 177 | width: 80%; 178 | margin: 0.5rem auto; 179 | display: flex; 180 | justify-content: space-between; 181 | position: relative; 182 | 183 | .like { 184 | width: 50%; 185 | text-align: center; 186 | border: 1px solid #4ebf60; 187 | margin: 0 0.3rem; 188 | border-radius: 8px; 189 | padding: 0.1rem 0; 190 | color: #4ebf60; 191 | font-size: 14px; 192 | 193 | &:active { 194 | background: #4ebf60; 195 | color: #fff; 196 | } 197 | } 198 | 199 | .like.like_active { 200 | background: #4ebf60; 201 | color: #fff; 202 | } 203 | 204 | .like.likeDisable { 205 | background: #f5f5f5; 206 | color: #4ebf60; 207 | 208 | &:active { 209 | background: #f5f5f5; 210 | color: #4ebf60; 211 | } 212 | } 213 | 214 | p { 215 | position: absolute; 216 | bottom: -0.4rem; 217 | left: 50%; 218 | transform: translateX(-50%); 219 | color: #ec5051; 220 | } 221 | } 222 | 223 | .fixed_comment { 224 | position: fixed; 225 | bottom: 0; 226 | left: 50%; 227 | transform: translateX(-50%); 228 | width: 7.5rem; 229 | 230 | display: flex; 231 | justify-content: space-between; 232 | padding: 0 0.1rem 0.05rem 0.1rem; 233 | background: #fff; 234 | 235 | input { 236 | border-radius: 5px; 237 | border: none; 238 | border: 1px solid #4ebf60; 239 | padding: 0 0 0 0.2rem; 240 | width: 100%; 241 | height: 0.75rem; 242 | } 243 | 244 | button { 245 | border: none; 246 | width: 1.5rem; 247 | padding: 0.15rem 0.25rem; 248 | border-radius: 5px; 249 | margin-left: 0.1rem; 250 | background: #63bb71; 251 | color: #fff; 252 | font-size: 14px; 253 | } 254 | 255 | button.disabled { 256 | background: #ccc; 257 | } 258 | } 259 | 260 | .page { 261 | margin: 0.2rem 0; 262 | display: flex; 263 | justify-content: center; 264 | align-items: center; 265 | 266 | div { 267 | display: inline; 268 | background: #4ebf60; 269 | color: #fff; 270 | text-align: center; 271 | margin: 0 0.05rem; 272 | border-radius: 5px; 273 | padding: 0.08rem 0.14rem; 274 | 275 | &.pageNum { 276 | background: #ccc; 277 | } 278 | } 279 | 280 | div:active { 281 | background: #89e498; 282 | } 283 | } 284 | 285 | .page-scale-enter-active, 286 | .page-scale-leave-active { 287 | transition: all 0.6s; 288 | } 289 | 290 | .page-scale-enter, 291 | .page-scale-leave-active { 292 | transform: scale(0); 293 | } 294 | -------------------------------------------------------------------------------- /src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 151 | 152 | 153 | 218 | -------------------------------------------------------------------------------- /src/style/me.scss: -------------------------------------------------------------------------------- 1 | .me { 2 | padding-bottom: 1.2rem; 3 | 4 | .me_deatil { 5 | .avator { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | color: #fff; 10 | background: rgba(95, 113, 206, 0.7); 11 | padding: 1rem 0 0.5rem 0; 12 | position: relative; 13 | 14 | #upload { 15 | position: absolute; 16 | left: 50%; 17 | top: 1.22rem; 18 | transform: translateX(-50%); 19 | width: 1.3rem; 20 | height: 1.3rem; 21 | opacity: 0; 22 | z-index: 999999; 23 | } 24 | 25 | div.avator_border { 26 | width: 1.3rem; 27 | height: 1.3rem; 28 | border: 2px solid #fff; 29 | border-radius: 50%; 30 | text-align: center; 31 | line-height: 1.2rem; 32 | overflow: hidden; 33 | font-size: 14px; 34 | 35 | img { 36 | width: 100%; 37 | height: 100%; 38 | } 39 | } 40 | 41 | i { 42 | font-size: 80px; 43 | } 44 | 45 | div.name { 46 | font-size: 24px; 47 | margin-top: 0.2rem; 48 | position: relative; 49 | 50 | i { 51 | font-size: 18px; 52 | position: absolute; 53 | right: -0.5rem; 54 | top: 0.1rem; 55 | } 56 | 57 | input { 58 | width: 1.5rem; 59 | border: none; 60 | background: transparent; 61 | border-bottom: 1px solid #fff; 62 | color: #fff; 63 | font-size: 20px; 64 | padding: 0.05rem 0; 65 | text-align: center; 66 | } 67 | 68 | input + i { 69 | font-size: 30px; 70 | right: -0.8rem; 71 | } 72 | } 73 | 74 | .logout { 75 | position: absolute; 76 | top: 0.2rem; 77 | right: 0.3rem; 78 | font-size: 13px; 79 | 80 | i { 81 | font-size: 15px; 82 | } 83 | 84 | border: 1px solid #fff; 85 | padding: 0.06rem 0.14rem; 86 | border-radius: 4px; 87 | 88 | &:active { 89 | background: rgba(0, 0, 0, 0.1); 90 | } 91 | } 92 | } 93 | } 94 | 95 | .list { 96 | & + .list { 97 | margin-top: 0.3rem; 98 | border-top: 0.2rem solid #f2f2f2; 99 | } 100 | 101 | h3 { 102 | font-size: 20px; 103 | margin: 0.1rem 0 0 0.4rem; 104 | color: #4ebf60; 105 | position: relative; 106 | 107 | // padding-left: 0.2rem; 108 | i { 109 | color: #d81e06; 110 | font-size: 22px; 111 | } 112 | 113 | span { 114 | font-size: 15px; 115 | margin-left: 0.1rem; 116 | display: inline-block; 117 | transform: translateY(-0.05rem); 118 | } 119 | } 120 | 121 | ul { 122 | margin-top: 0.3rem; 123 | padding: 0 0.4rem; 124 | display: flex; 125 | overflow-x: auto; 126 | -webkit-overflow-scrolling: touch; 127 | 128 | li { 129 | flex: none; 130 | 131 | & + li { 132 | margin-left: 0.2rem; 133 | } 134 | 135 | h4 { 136 | color: #333; 137 | font-size: 16px; 138 | margin: 0.1rem 0; 139 | height: 0.4rem; 140 | line-height: 0.4rem; 141 | width: 1.8rem; 142 | white-space: nowrap; 143 | overflow: hidden; 144 | text-overflow: ellipsis; 145 | } 146 | 147 | div { 148 | display: flex; 149 | } 150 | 151 | img { 152 | width: 1.8rem; 153 | height: 2.7rem; 154 | &[lazy="loaded"] { 155 | object-fit: cover; 156 | } 157 | &[lazy="loading"] { 158 | object-fit: contain; 159 | } 160 | } 161 | 162 | div { 163 | color: #333; 164 | display: flex; 165 | 166 | span { 167 | margin-left: 0.1rem; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | .list.comment { 175 | h3 { 176 | i { 177 | color: #4ebf60; 178 | margin-top: 0.06rem; 179 | } 180 | } 181 | 182 | ul { 183 | display: flex; 184 | flex-direction: column; 185 | padding: 0; 186 | 187 | li { 188 | width: 100%; 189 | height: 1rem; 190 | position: relative; 191 | border-top: 1px solid #ccc; 192 | overflow: hidden; 193 | transition: all 0.5s; 194 | 195 | & + li { 196 | margin-left: 0; 197 | } 198 | 199 | h5 { 200 | font-size: 16px; 201 | width: 4.5rem; 202 | overflow: hidden; 203 | text-overflow: ellipsis; 204 | white-space: nowrap; 205 | } 206 | 207 | .commentWrap { 208 | height: 1rem; 209 | display: flex; 210 | flex-direction: column; 211 | justify-content: center; 212 | padding-left: 0.5rem; 213 | position: absolute; 214 | left: 0; 215 | top: 0; 216 | width: 100%; 217 | background: #fff; 218 | transition: all 0.5s; 219 | box-sizing: border-box; 220 | z-index: 999; 221 | 222 | .time { 223 | position: absolute; 224 | top: 0.1rem; 225 | right: 0.2rem; 226 | // transform: translateY(-50%); 227 | } 228 | 229 | section { 230 | font-size: 16px; 231 | display: flex; 232 | flex: none; 233 | 234 | span { 235 | flex: none; 236 | font-size: 16px; 237 | } 238 | 239 | p { 240 | white-space: nowrap; 241 | text-overflow: ellipsis; 242 | width: 75%; 243 | overflow: hidden; 244 | } 245 | } 246 | } 247 | 248 | .delete { 249 | display: block; 250 | height: 1rem; 251 | width: 1rem; 252 | background: #ec5051; 253 | color: #fff; 254 | font-size: 16px; 255 | text-align: center; 256 | line-height: 1rem; 257 | position: absolute; 258 | right: 0; 259 | top: 0; 260 | z-index: 1; 261 | } 262 | } 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/components/Me.vue: -------------------------------------------------------------------------------- 1 | 115 | 116 | 412 | 413 | 416 | -------------------------------------------------------------------------------- /src/components/Detail.vue: -------------------------------------------------------------------------------- 1 | 173 | 174 | 463 | 464 | 465 | 468 | --------------------------------------------------------------------------------