├── .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 | 
28 | 
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 |
2 |
9 |
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 |
2 |
3 |
4 | {{film.room_name}}
5 |
6 | {{film.nickname}}
7 | {{film.online | fixed}}
8 |
9 |
10 |
11 |
26 |
--------------------------------------------------------------------------------
/src/components/DyMoreButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
82 |
--------------------------------------------------------------------------------
/src/components/DyNavbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
23 |
60 |
--------------------------------------------------------------------------------
/src/components/DySidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
38 |
106 |
--------------------------------------------------------------------------------
/src/components/DySwiper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
2 |
5 |
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 |
2 |
8 |
9 |
10 |
36 |
37 |
69 |
--------------------------------------------------------------------------------
/src/views/detail/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
22 |
--------------------------------------------------------------------------------
/src/views/error/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
404
4 |
5 |
6 |
11 |
13 |
--------------------------------------------------------------------------------
/src/views/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{livelist.room_name}}
9 |
10 | {{livelist.nickname}}
11 | {{livelist.online | fixed}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
39 |
40 |
114 |
--------------------------------------------------------------------------------
/src/views/room/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{roomlist.room_name}}
12 |
13 | {{roomlist.nickname}}
14 | {{roomlist.online | fixed}}
15 |
16 |
17 |
18 |
19 |
27 |
28 |
29 |
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 | }
--------------------------------------------------------------------------------