├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico └── index.html ├── screenshot ├── demo-1.gif └── demo-2.gif ├── src ├── App.vue ├── api │ ├── category.js │ └── live.js ├── assets │ ├── images │ │ ├── icon_play.png │ │ └── menu.png │ ├── logo.png │ └── styles │ │ ├── index.scss │ │ └── reset.css ├── components │ ├── DyLiveItem.vue │ ├── DyMoreButton.vue │ ├── DyNavbar.vue │ ├── DySidebar.vue │ └── DySwiper.vue ├── filters │ └── index.js ├── icons │ ├── SvgIcon.vue │ ├── index.js │ └── svg │ │ ├── menu.svg │ │ ├── right.svg │ │ └── tv.svg ├── main.js ├── router │ └── index.js ├── store │ ├── index.js │ └── modules │ │ ├── ajax.js │ │ └── app.js └── views │ ├── category │ └── index.vue │ ├── detail │ └── index.vue │ ├── error │ └── 404.vue │ ├── home │ └── index.vue │ └── room │ └── index.vue ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Vuex版的斗鱼 (vuex-douyu) 2 | 3 | 使用技术栈: webpack + vuejs+ vuex + axios + vue-router 4 | 5 | ## 更改使用本地Proxy代理,解决跨域问题 6 | 7 | ```js 8 | proxyTable: { 9 | '/api': { 10 | target: 'http://open.douyucdn.cn/api/RoomApi', 11 | changeOrigin: true, 12 | pathRewrite: { 13 | '^/api': '' 14 | } 15 | }, 16 | '/category': { 17 | target: 'https://m.douyu.com/category', 18 | changeOrigin: true, 19 | pathRewrite: { 20 | '^/category': '' 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | ## 动图演示 27 | ![demo-1](https://github.com/axhello/vuex-douyu/blob/master/screenshot/demo-1.gif) 28 | ![demo-2](https://github.com/axhello/vuex-douyu/blob/master/screenshot/demo-2.gif) 29 | 30 | ## 本地运行 31 | 32 | ``` bash 33 | # install dependencies 34 | npm install or yarn install 35 | # serve with hot reload at localhost:8080 36 | npm run dev or yarn run dev 37 | # build for production with minification 38 | npm run build 39 | ``` -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuex-douyu", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "fastclick": "^1.0.6", 13 | "lib-flexible": "^0.3.2", 14 | "vue": "^2.5.17", 15 | "vue-awesome-swiper": "^3.1.3", 16 | "vue-router": "^3.0.1", 17 | "vuex": "^3.0.1" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "^3.0.5", 21 | "@vue/cli-plugin-eslint": "^3.0.5", 22 | "@vue/cli-service": "^3.0.5", 23 | "node-sass": "^4.9.3", 24 | "sass-loader": "^7.1.0", 25 | "svg-sprite-loader": "^4.1.2", 26 | "vue-template-compiler": "^2.5.17" 27 | }, 28 | "eslintConfig": { 29 | "root": true, 30 | "env": { 31 | "node": true 32 | }, 33 | "extends": [ 34 | "plugin:vue/essential", 35 | "eslint:recommended" 36 | ], 37 | "rules": {}, 38 | "parserOptions": { 39 | "parser": "babel-eslint" 40 | } 41 | }, 42 | "postcss": { 43 | "plugins": { 44 | "autoprefixer": {} 45 | } 46 | }, 47 | "browserslist": [ 48 | "> 1%", 49 | "last 2 versions", 50 | "not ie <= 8" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhello/vuex-douyu/25ced89d1cd1088405e8e5f212cfafff9f414367/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vuex-douyu 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /screenshot/demo-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhello/vuex-douyu/25ced89d1cd1088405e8e5f212cfafff9f414367/screenshot/demo-1.gif -------------------------------------------------------------------------------- /screenshot/demo-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhello/vuex-douyu/25ced89d1cd1088405e8e5f212cfafff9f414367/screenshot/demo-2.gif -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /src/api/category.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | // 创建axios实例 4 | const service = axios.create({ 5 | baseURL: '/category/', // api的base_url 6 | timeout: 15000 // 请求超时时间 7 | }) 8 | 9 | export function category(query) { 10 | return service({ 11 | url: '', 12 | method: 'get', 13 | params: query 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/api/live.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | // 创建axios实例 4 | const service = axios.create({ 5 | baseURL: '/api/', // api的base_url 6 | timeout: 15000 // 请求超时时间 7 | }) 8 | 9 | export function liveLists(data) { 10 | return service({ 11 | url: `live`, 12 | method: 'get', 13 | params: data 14 | }) 15 | } 16 | 17 | export function roomLists(id, data) { 18 | return service({ 19 | url: `live/${id}`, 20 | method: 'get', 21 | params: data 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/images/icon_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhello/vuex-douyu/25ced89d1cd1088405e8e5f212cfafff9f414367/src/assets/images/icon_play.png -------------------------------------------------------------------------------- /src/assets/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhello/vuex-douyu/25ced89d1cd1088405e8e5f212cfafff9f414367/src/assets/images/menu.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhello/vuex-douyu/25ced89d1cd1088405e8e5f212cfafff9f414367/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | body.slide-overflow { 2 | overflow: hidden; 3 | } 4 | .view{ 5 | margin-top: 1.233rem; 6 | } 7 | html body { 8 | line-height: 1.5; 9 | font-family: "Helvetica Neue","Arial","PingFang SC","Hiragino Sans GB","STHeiti","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; 10 | color: #333; 11 | background-color: #F4F4F4; 12 | } 13 | * { 14 | box-sizing: border-box; 15 | } 16 | .clearfix { 17 | &:before, 18 | &:after{ 19 | content: " "; 20 | display: table; 21 | } 22 | &:after { 23 | clear: both; 24 | } 25 | } 26 | 27 | .fade-enter-active, .fade-leave-active { 28 | transition: opacity .5s; 29 | } 30 | .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { 31 | opacity: 0; 32 | } -------------------------------------------------------------------------------- /src/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | KISSY CSS Reset 3 | 理念:清除和重置是紧密不可分的 4 | 特色:1.适应中文 2.基于最新主流浏览器 5 | 维护:玉伯(lifesinger@gmail.com), 正淳(ragecarrier@gmail.com) 6 | */ 7 | 8 | /* 清除内外边距 */ 9 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 10 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 11 | pre, /* text formatting elements 文本格式元素 */ 12 | fieldset, lengend, button, input, textarea, /* form elements 表单元素 */ 13 | th, td { /* table elements 表格元素 */ 14 | margin: 0; 15 | padding: 0; 16 | } 17 | a, input, textarea, select, button { 18 | outline: 0; 19 | } 20 | 21 | /* 设置默认字体 */ 22 | body, 23 | button, input, select, textarea { /* for ie */ 24 | /*font: 12px/1 Tahoma, Helvetica, Arial, "宋体", sans-serif;*/ 25 | font: 12px/1 Tahoma, Helvetica, Arial, "\5b8b\4f53", sans-serif; /* 用 ascii 字符表示,使得在任何编码下都无问题 */ 26 | } 27 | 28 | h1 { font-size: 18px; /* 18px / 12px = 1.5 */ } 29 | h2 { font-size: 16px; } 30 | h3 { font-size: 14px; } 31 | h4, h5, h6 { font-size: 100%; } 32 | 33 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 34 | code, kbd, pre, samp, tt { font-family: "Courier New", Courier, monospace; } /* 统一等宽字体 */ 35 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 36 | 37 | /* 重置列表元素 */ 38 | ul, ol { list-style: none; } 39 | 40 | /* 重置文本格式元素 */ 41 | a { color: #0894ec; text-decoration: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } 42 | a { background-color: transparent; } 43 | a:hover { text-decoration: none; } 44 | 45 | abbr[title], acronym[title] { /* 注:1.ie6 不支持 abbr; 2.这里用了属性选择符,ie6 下无效果 */ 46 | border-bottom: 1px dotted; 47 | cursor: help; 48 | } 49 | 50 | q:before, q:after { content: ''; } 51 | 52 | /* 重置表单元素 */ 53 | legend { color: #000; } /* for ie6 */ 54 | fieldset, img { border: none; } /* img 搭车:让链接里的 img 无边框 */ 55 | /* 注:optgroup 无法扶正 */ 56 | button, input, select, textarea { 57 | font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */ 58 | } 59 | 60 | /* 重置表格元素 */ 61 | table { 62 | border-collapse: collapse; 63 | border-spacing: 0; 64 | } 65 | 66 | /* 重置 hr */ 67 | hr { 68 | border: none; 69 | height: 1px; 70 | } 71 | 72 | /* 让非ie浏览器默认也显示垂直滚动条,防止因滚动条引起的闪烁 */ 73 | // html { overflow-y: scroll; } 74 | 75 | * {box-sizing: border-box;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);-webkit-touch-callout: none;} 76 | -------------------------------------------------------------------------------- /src/components/DyLiveItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 26 | -------------------------------------------------------------------------------- /src/components/DyMoreButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | 82 | -------------------------------------------------------------------------------- /src/components/DyNavbar.vue: -------------------------------------------------------------------------------- 1 | 12 | 23 | 60 | -------------------------------------------------------------------------------- /src/components/DySidebar.vue: -------------------------------------------------------------------------------- 1 | 20 | 38 | 106 | -------------------------------------------------------------------------------- /src/components/DySwiper.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 57 | 68 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | export function fixed(value) { 2 | if (value < 1) { 3 | return 0 4 | } else if (value >= 1e6) { 5 | return (value / 1e4).toFixed(0) + '万' 6 | } else if (value >= 1e4) { 7 | return ((value / 1e4).toFixed(1) + '万').replace('.0', '') 8 | } else { 9 | return value 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/icons/SvgIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from './SvgIcon'// svg组件 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | const req = require.context('./svg', false, /\.svg$/) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /src/icons/svg/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/tv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import 'lib-flexible' 4 | import Vue from 'vue' 5 | import App from './App' 6 | import store from './store' 7 | import router from './router' 8 | import FastClick from 'fastclick' 9 | import VueAwesomeSwiper from 'vue-awesome-swiper' 10 | import * as filters from './filters' 11 | import './assets/styles/reset.css' 12 | import './assets/styles/index.scss' 13 | import 'swiper/dist/css/swiper.css' 14 | import '@/icons' // icon 15 | 16 | window.addEventListener('load', () => { 17 | FastClick.attach(document.body) 18 | }) 19 | Object.keys(filters).forEach(key => { 20 | Vue.filter(key, filters[key]) 21 | }) 22 | 23 | Vue.use(VueAwesomeSwiper) 24 | Vue.config.productionTip = false 25 | 26 | /* eslint-disable no-new */ 27 | new Vue({ 28 | el: '#app', 29 | router, 30 | store, 31 | render: h => h(App) 32 | }) 33 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | export default new Router({ 7 | routes: [ 8 | { 9 | path: '/404', 10 | component: () => import('@/views/error/404') 11 | }, 12 | { 13 | path: '/', 14 | name: 'index', 15 | component: () => import('@/views/home') 16 | }, 17 | { 18 | path: '/category/:type', 19 | name: 'category', 20 | component: () => import('@/views/category') 21 | }, 22 | { 23 | path: '/rooms/:name', 24 | name: 'rooms', 25 | component: () => import('@/views/room') 26 | }, 27 | { 28 | path: '/detail/:id', 29 | name: 'detail', 30 | component: () => import('@/views/detail') 31 | }, 32 | { 33 | path: '*', 34 | redirect: '/404' 35 | } 36 | ], 37 | scrollBehavior(to, from, savedPosition) { 38 | if (savedPosition) { 39 | return savedPosition 40 | } else { 41 | return { x: 0, y: 0 } 42 | } 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import ajax from './modules/ajax' 5 | // import getters from './getters' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | app, 12 | ajax 13 | } 14 | }) 15 | 16 | export default store 17 | -------------------------------------------------------------------------------- /src/store/modules/ajax.js: -------------------------------------------------------------------------------- 1 | import { liveLists, roomLists } from '@/api/live' 2 | import { category } from '@/api/category' 3 | 4 | const ajax = { 5 | state: { 6 | livelists: [], 7 | roomlists: [], 8 | categories: [], 9 | catelists: [] 10 | }, 11 | 12 | mutations: { 13 | // 获取热门直播 14 | FETCH_LIVE_LIST: (state, data) => { 15 | state.livelists = data 16 | }, 17 | // 获取分类房间列表 18 | FETCH_ROOM_LIST: (state, data) => { 19 | state.roomlists = data 20 | }, 21 | // 获取所有目录 22 | FETCH_All_CATEGORY: (state, data) => { 23 | state.categories = data 24 | }, 25 | // 获取目录列表 26 | FETCH_CATEGORY_LIST: (state, data) => { 27 | state.catelists = data 28 | } 29 | }, 30 | 31 | actions: { 32 | fetchLiveLists({ commit }, payload) { 33 | const query = payload 34 | return new Promise((resolve, reject) => { 35 | liveLists(query) 36 | .then(response => { 37 | const result = response.data 38 | commit('FETCH_LIVE_LIST', result.data) 39 | resolve(response) 40 | }) 41 | .catch(error => { 42 | reject(error) 43 | }) 44 | }) 45 | }, 46 | fetchRoomLists({ commit }, payload) { 47 | const query = { offset: payload.offset, limit: payload.limit } 48 | return new Promise((resolve, reject) => { 49 | roomLists(payload.name, query) 50 | .then(response => { 51 | const result = response.data 52 | commit('FETCH_ROOM_LIST', result.data) 53 | resolve(response) 54 | }) 55 | .catch(error => { 56 | reject(error) 57 | }) 58 | }) 59 | }, 60 | fetchCategory({ commit }) { 61 | return new Promise((resolve, reject) => { 62 | category() 63 | .then(response => { 64 | const result = response.data 65 | // console.log(result.cate1Info) 66 | commit('FETCH_All_CATEGORY', result.cate1Info) 67 | resolve(response) 68 | }) 69 | .catch(error => { 70 | reject(error) 71 | }) 72 | }) 73 | }, 74 | fetchCateList({ commit }, payload) { 75 | return new Promise((resolve, reject) => { 76 | category(payload) 77 | .then(response => { 78 | const result = response.data 79 | // console.log(result.cate2Info) 80 | commit('FETCH_CATEGORY_LIST', result.cate2Info) 81 | localStorage.setItem('cateName', result.cate1Info.cate1Name) 82 | resolve(response) 83 | }) 84 | .catch(error => { 85 | reject(error) 86 | }) 87 | }) 88 | } 89 | }, 90 | 91 | getters: { 92 | livelists: state => state.livelists, 93 | roomlists: state => state.roomlists, 94 | categories: state => state.categories, 95 | catelists: state => state.catelists 96 | } 97 | } 98 | 99 | export default ajax 100 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const app = { 2 | state: { 3 | loading: false, 4 | leftNavState: false 5 | }, 6 | 7 | mutations: { 8 | CHANGE_LEFTNAV_STATE: (state, data) => { 9 | state.leftNavState = data 10 | }, 11 | TOGGLE_LOADING_STATE: (state, data) => { 12 | state.loading = data 13 | } 14 | }, 15 | 16 | actions: { 17 | changeLeftNavState({ commit }, payload) { 18 | return commit('CHANGE_LEFTNAV_STATE', payload) 19 | }, 20 | toggleLoadingState({ commit }, payload) { 21 | return commit('TOGGLE_LOADING_STATE', payload) 22 | } 23 | }, 24 | 25 | getters: { 26 | loading: state => state.loading, 27 | leftNavState: state => state.leftNavState 28 | } 29 | } 30 | 31 | export default app 32 | -------------------------------------------------------------------------------- /src/views/category/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 69 | -------------------------------------------------------------------------------- /src/views/detail/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 14 | 22 | -------------------------------------------------------------------------------- /src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 13 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 39 | 40 | 114 | -------------------------------------------------------------------------------- /src/views/room/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 79 | 157 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | module.exports = { 3 | chainWebpack: config => { 4 | const svgRule = config.module.rule('svg') 5 | svgRule.uses.clear() 6 | svgRule 7 | .use('file-loader') 8 | .loader('svg-sprite-loader') 9 | .options({ symbolId: 'icon-[name]' }) 10 | }, 11 | devServer: { 12 | proxy: { 13 | '/api': { 14 | target: 'http://open.douyucdn.cn/api/RoomApi', 15 | changeOrigin: true, 16 | pathRewrite: { 17 | '^/api': '' 18 | } 19 | }, 20 | '/category': { 21 | target: 'https://m.douyu.com/category', 22 | changeOrigin: true, 23 | pathRewrite: { 24 | '^/category': '' 25 | } 26 | } 27 | } 28 | } 29 | } --------------------------------------------------------------------------------