├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── api
│ └── index.ts
├── assets
│ ├── icon-loading.gif
│ ├── icon-search.svg
│ ├── icon-star-empty.png
│ ├── icon-star-full.png
│ ├── icon-star-half.png
│ └── logo.png
├── common
│ ├── font
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ └── style
│ │ ├── iconfont.css
│ │ ├── reset.less
│ │ └── variable.less
├── components
│ ├── ArticleListComponent.vue
│ ├── ArticleTabComponent.vue
│ ├── ClassifyHeaderComponent.vue
│ ├── CommentListComponent.vue
│ ├── MoreHandleComponent.vue
│ ├── MovieListComponent.vue
│ ├── MovieTabComponent.vue
│ ├── MyTabComponent.vue
│ ├── VideoListComponent.vue
│ └── VideoTabComponent.vue
├── hooks
│ ├── useArticleDetailEffect.ts
│ ├── useArticleListEffect.ts
│ ├── useArticleTabEffect.ts
│ ├── useCommentEffect.ts
│ ├── useMoreHandleEffect.ts
│ ├── useMovieDetailEffect.ts
│ ├── useMovieTabEffect.ts
│ ├── useMyTabEffect.ts
│ ├── useVideoListEffect.ts
│ └── useVideoTabEffect.ts
├── main.ts
├── pages
│ ├── ArticleDetailPage.vue
│ ├── HomePage.vue
│ ├── MovieDetaiPage.vue
│ └── RecordListPage.vue
├── router
│ └── index.ts
├── service
│ ├── appService.ts
│ ├── articleDetailService.ts
│ ├── handleService.ts
│ ├── homeService.ts
│ ├── movieDetailService.ts
│ ├── movieService.ts
│ ├── myService.ts
│ ├── recordService.ts
│ └── videoService.ts
├── shims-vue.d.ts
├── store
│ ├── actions.ts
│ ├── getters.ts
│ ├── index.ts
│ ├── mapActions.ts
│ ├── mapGetters.ts
│ ├── mutation-types.ts
│ ├── mutations.ts
│ ├── state.ts
│ └── stateInterface.ts
├── types
│ └── index.ts
└── utils
│ ├── emitter.ts
│ ├── index.ts
│ └── setAxios
├── tsconfig.json
├── vue.config.js
├── 明日头条app整体效果图(待更新).jpg
├── 界面预览1.png
├── 界面预览10.png
├── 界面预览2.png
├── 界面预览3.png
├── 界面预览4.png
├── 界面预览5.png
├── 界面预览6.png
├── 界面预览7.png
├── 界面预览8.png
└── 界面预览9.png
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 4
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: false,
3 | env: {
4 | node: false
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | '@vue/standard',
9 | '@vue/typescript/recommended'
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020
13 | },
14 | rules: {
15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue3-toutiao-app-ui
2 | 使用最新流行的vue3+typescript+hook搭建的明日头条webapp项目,数据来自于python自动化爬虫,自动抓取今日头条的数据,存入自己的数据中,包含文章。博客,视频,电影,详情,用户中心,登录等模块,功能完善齐全,持续更新中...
3 |
4 | ================================界面预览================================
5 | 
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 
16 | ================================界面预览================================
17 |
18 |
19 | 后端接口和sql参见:https://github.com/wuyuanwuhui99/springboot-app-service
20 |
21 | flutter在线电影项目:https://github.com/wuyuanwuhui99/flutter-movie-app-ui
22 |
23 | react-native在线电影项目:https://github.com/wuyuanwuhui99/react-native-app-ui
24 |
25 | 在线音乐项目:https://github.com/wuyuanwuhui99/vue-music-app-ui
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "muvue3",
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 | "ansi-regex": "^6.0.0",
12 | "axios": "^0.21.1",
13 | "better-scroll": "^2.4.1",
14 | "fork-ts-checker-webpack-plugin-v5": "^0.0.1-security",
15 | "mitt": "^3.0.0",
16 | "vue": "^3.0.0",
17 | "vue-router": "^4.0.0-0",
18 | "vuex": "^4.0.0-0"
19 | },
20 | "devDependencies": {
21 | "@typescript-eslint/eslint-plugin": "^4.18.0",
22 | "@typescript-eslint/parser": "^4.18.0",
23 | "@vue/cli-plugin-eslint": "~4.5.0",
24 | "@vue/cli-plugin-router": "~4.5.0",
25 | "@vue/cli-plugin-typescript": "~4.5.0",
26 | "@vue/cli-plugin-vuex": "~4.5.0",
27 | "@vue/cli-service": "~4.5.0",
28 | "@vue/compiler-sfc": "^3.0.0",
29 | "@vue/eslint-config-standard": "^5.1.2",
30 | "@vue/eslint-config-typescript": "^7.0.0",
31 | "eslint": "^6.7.2",
32 | "eslint-plugin-import": "^2.20.2",
33 | "eslint-plugin-node": "^11.1.0",
34 | "eslint-plugin-promise": "^4.2.1",
35 | "eslint-plugin-standard": "^4.0.0",
36 | "eslint-plugin-vue": "^7.0.0",
37 | "less": "^3.13.1",
38 | "less-loader": "^5.0.0",
39 | "typescript": "~4.1.5"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
36 |
57 |
--------------------------------------------------------------------------------
/src/api/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | getUserData: "/service/toutiao/getUserData",//获取用户信息
3 | getFavoriteChannels: "/service/toutiao-getway/getArticleFavoriteChannels",//获取用户收藏的频道
4 | getAllChannels: "/service/toutiao/getFavoriteChannels",//获取所有频道
5 | getArticleList: "/service/toutiao/getArticleList",//获取文章列表
6 | getVideoFavoriteChannels:"/service/video-getway/getFavoriteChannels",//获取视频分类
7 | getVideoList: "/service/video/getVideoList",//获取视频列表
8 | getMovieList: "/service/movie/search",//获取视频列表
9 | getArticleDetail:"/service/toutiao/getArticleDetail",//获取文章详情
10 | getRecordList:"/service/{type}-getway/getRecordList",//获取文章浏览记录
11 | isFavorite:"/service/{type}-getway/isFavorite",//获取是否收藏过的文章和视频
12 | insertFavorite:"/service/{type}-getway/insertFavorite",//插入收藏
13 | deleteFavorite:"/service/{type}-getway/deleteFavorite",//删除收藏
14 | isLike:"/service/{type}-getway/isLike",//获取是是否点赞过的文章和视频
15 | insertLike:"/service/{type}-getway/insertLike",//插入点赞
16 | deleteLike:"/service/{type}-getway/deleteLike",//删除点赞
17 | getFavorite:"/service/{type}-getway/getFavorite",//获取文章浏览记录或视频播放记录或电影观看记录
18 | isFocus:"/service/{type}-getway/isFocus",//查询是否已经关注
19 | insertFocus:"/service/{type}-getway/insertFocus",//新增关注
20 | deleteFocus:"/service/{type}-getway/deleteFocus",//取消关注
21 | getCommentCount:"/service/{type}/getCommentCount",//获取评论总条数
22 | getTopCommentList:"/service/{type}/getTopCommentList",//获取一级评论列表
23 | insertComment:"/service/{type}-getway/insertComment",//新增评论
24 | deleteComment:"/service/{type}-getway/deleteComment/{id}",//删除评论
25 | getReplyCommentList:"/service/{type}/getReplyCommentList",//获取回复列表
26 | getCommentItem:"/service/{type}/getCommentItem",//获取新增的单条评论或者回复
27 | getMovieDetail:"/service/movie/getMovieDetail/",//获取电影详情
28 | getStar:"/service/movie/getStar/",//获取演员表
29 | getYourLikes:"/service/movie/getYourLikes",//获取猜你喜欢的电影
30 | getMovieListByType:"/service/movie/getMovieListByType",//按类型获取获取电影
31 | getRecommend:"/service/movie/getRecommend",//获取推荐的电影
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/icon-loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/assets/icon-loading.gif
--------------------------------------------------------------------------------
/src/assets/icon-search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon-star-empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/assets/icon-star-empty.png
--------------------------------------------------------------------------------
/src/assets/icon-star-full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/assets/icon-star-full.png
--------------------------------------------------------------------------------
/src/assets/icon-star-half.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/assets/icon-star-half.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/assets/logo.png
--------------------------------------------------------------------------------
/src/common/font/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/common/font/iconfont.ttf
--------------------------------------------------------------------------------
/src/common/font/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/common/font/iconfont.woff
--------------------------------------------------------------------------------
/src/common/font/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/src/common/font/iconfont.woff2
--------------------------------------------------------------------------------
/src/common/style/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "iconfont"; /* Project id 87269 */
3 | src: url('../font/iconfont.woff2?t=1624079947675') format('woff2'),
4 | url('../font/iconfont.woff?t=1624079947675') format('woff'),
5 | url('../font/iconfont.ttf?t=1624079947675') format('truetype');
6 | }
7 |
8 | .iconfont {
9 | font-family: "iconfont" !important;
10 | font-size: 1.6rem;
11 | font-style: normal;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | .icon-edit:before {
17 | content: "\e63c";
18 | }
19 |
20 | .icon-edit:before {
21 | content: "\e63c";
22 | }
23 |
24 | .iconfont-arrow:before{
25 | content: "\e615";
26 | }
27 |
28 | .iconfont-arrow{
29 | color: #888;
30 | font-size:0.5rem;
31 | padding: 0 0.3rem;
32 | }
33 |
34 | .iconfont-has-collection:before{
35 | content: "\e62a";
36 | color: #ff9900;
37 | }
38 |
39 | .iconfont-set:before{
40 | content: "\e605";
41 | }
42 |
43 | .iconfont-play:before {
44 | content: "\e701";
45 | color: #fff;
46 | }
47 |
48 | .iconfont-tab-my:before {
49 | content: "\e763";
50 | }
51 |
52 | .iconfont-tab-home:before {
53 | content: "\e603";
54 | }
55 |
56 | .iconfont-search:before {
57 | content: "\e7d2";
58 | }
59 |
60 | .iconfont-tab-play:before {
61 | content: "\e61b";
62 | }
63 |
64 | .iconfont-tab-video:before {
65 | content: "\e6f8";
66 | }
67 |
68 | .iconfont-user:before {
69 | content: "\e646";
70 | }
71 |
72 | .iconfont-user:before {
73 | content: "\e646";
74 | }
75 |
76 | .iconfont-comment:before {
77 | content: "\e604";
78 | }
79 |
80 | .iconfont-no-collection:before {
81 | content: "\e629";
82 | }
83 |
84 | .iconfont-like:before {
85 | content: "\e61c";
86 | }
87 |
88 | .iconfont-like-active:before {
89 | content: "\e60c";
90 | color: #ff9900;
91 | }
92 |
93 | .iconfont-views:before {
94 | content: "\e68c";
95 | }
96 |
97 | .iconfont-more:before {
98 | content: "\e639";
99 | color: #bbb;
100 | }
101 |
102 | .iconfont-jiantou:before {
103 | content: "\e616";
104 | }
105 |
--------------------------------------------------------------------------------
/src/common/style/reset.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
3 | * http://cssreset.com
4 | */
5 | html, body, div, span, applet, object, iframe, h1,
6 | h2, h3, h4, h5, h6, p, blockquote, pre, a,
7 | abbr, acronym, address, big, cite, code, del,
8 | dfn, em, img, ins, kbd, q, s, samp, small,
9 | strike, strong, sub, sup, tt, var, b,
10 | u, i, center, dl,
11 | dt, dd, ol, ul, li, fieldset,
12 | form, label, legend, table,
13 | caption, tbody, tfoot, thead, tr, th, td, article,
14 | aside, canvas, details, embed, figure,
15 | figcaption, footer, header, menu,
16 | nav, output, ruby, section, summary, time,
17 | mark, audio, video, input {
18 | margin: 0;
19 | padding: 0;
20 | border: 0;
21 | font-size: 100%;
22 | font-weight: normal;
23 | vertical-align: baseline;
24 | }
25 |
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure, footer,
28 | header, menu, nav, section {
29 | display: block;
30 | }
31 | body,html{
32 | width: 100%;
33 | height: 100%;
34 | }
35 | blockquote, q {
36 | quotes: none;
37 | }
38 |
39 | blockquote:before, blockquote:after,
40 | q:before, q:after {
41 | content: none;
42 | }
43 |
44 | table {
45 | border-collapse: collapse;
46 | border-spacing: 0;
47 | }
48 |
49 | /* custom */
50 |
51 | a {
52 | color: #7e8c8d;
53 | -webkit-backface-visibility: hidden;
54 | text-decoration: none;
55 | }
56 |
57 | li {
58 | list-style: none;
59 | }
60 |
61 | body {
62 | -webkit-text-size-adjust: none;
63 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
64 | }
65 |
--------------------------------------------------------------------------------
/src/common/style/variable.less:
--------------------------------------------------------------------------------
1 | @color-active: #ff9900;
2 | @color-icon:#bbb;
3 | @iconfont-size: 1.5rem;
4 | @iconfont-margin:0 0.5rem;
5 | @article-title-font-size:1.3rem;
6 | @article-footer-font-size:0.9rem;
7 | @article-footer-color:#bbb;
8 | @border:1px solid #eee;
9 | @small-margin: 0.5rem;
10 | @big-margin:1rem;
11 | @border-raduis:0.3rem;
12 | @big-avater-size:4rem;
13 | @middle-avater-size:2rem;
14 | @small-avater-size:1rem;
15 | @circle-avater:50%;
16 | @img-border-raduis:0.5rem;
17 |
--------------------------------------------------------------------------------
/src/components/ArticleListComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
{{item.title}}
6 |
7 |
8 |
11 |
18 |
19 |
20 |
21 |
22 |
51 |
52 |
103 |
--------------------------------------------------------------------------------
/src/components/ArticleTabComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
45 |
46 |
131 |
--------------------------------------------------------------------------------
/src/components/ClassifyHeaderComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
28 |
29 |
49 |
--------------------------------------------------------------------------------
/src/components/CommentListComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
45 |
46 |
47 |
48 |
70 |
71 |
184 |
--------------------------------------------------------------------------------
/src/components/MoreHandleComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
41 |
42 |
80 |
--------------------------------------------------------------------------------
/src/components/MovieListComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
![]()
6 |
{{item.viewingState.replace(/\s/g,"")}}
7 |
8 | {{item.movieName}}
9 |
10 |
11 |
12 |
13 |
41 |
42 |
103 |
--------------------------------------------------------------------------------
/src/components/MovieTabComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
44 |
45 |
172 |
--------------------------------------------------------------------------------
/src/components/MyTabComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
80 |
81 |
82 |
99 |
100 |
356 |
--------------------------------------------------------------------------------
/src/components/VideoListComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
{{item.title}}
6 |
![]()
7 |
8 |
9 |
10 |
![]()
11 |
{{item.authorInfo.authorDesc}}
12 |
{{fomatTime(item.publishTime)}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
41 |
42 |
176 |
--------------------------------------------------------------------------------
/src/components/VideoTabComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
44 |
45 |
261 |
--------------------------------------------------------------------------------
/src/hooks/useArticleDetailEffect.ts:
--------------------------------------------------------------------------------
1 | import {reactive,ref} from "vue"
2 | import {useRoute} from "vue-router";
3 | import {
4 | getArticleService,
5 | isFocusService,
6 | insertFocusService,
7 | deleteFocusService,
8 | } from "../service/articleDetailService"
9 | import {ArticleInterface} from "../types";
10 |
11 | export default ()=> {
12 |
13 | const articleDetail = reactive({});
14 | const isFocus = ref(false);
15 | const route:any = useRoute();
16 |
17 | const useInitArticleDetail = async () => {
18 | const result = await getArticleService(route.params.id);
19 | Object.assign(articleDetail,result);
20 | isFocusService(articleDetail.authorId,"toutiao").then((res)=>{
21 | isFocus.value = res > 0;
22 | })
23 | };
24 |
25 | const useHandleFocus =()=>{
26 | if(isFocus.value){
27 | deleteFocusService(articleDetail.authorId,"toutiao").then((res)=>{
28 | isFocus.value = !(res > 0)
29 | });
30 | }else{
31 | insertFocusService(articleDetail.authorId,"toutiao").then((res)=>{
32 | isFocus.value = res > 0;
33 | });
34 | }
35 | };
36 |
37 | return {
38 | useInitArticleDetail,
39 | articleDetail,
40 | isFocus,
41 | useHandleFocus
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/hooks/useArticleListEffect.ts:
--------------------------------------------------------------------------------
1 | import {useRouter} from "vue-router";
2 | import {ref,onUnmounted} from "vue";
3 | import emitter from "../utils/emitter"
4 | import {fomatTime,getImgHtml} from "../utils";
5 | export default ()=> {
6 | const showHandleIndex = ref(-1);
7 | const router = useRouter();
8 | const useGoArticleDetail = (id:number)=>{
9 | router.push(`/articleDetail/${id}`);
10 | };
11 | /**
12 | * @author: wuwenqiang
13 | * @description: 显示点赞评论收藏的操作框
14 | * @date: 2021-08-15 16:20
15 | */
16 | const useShowHandle = (index:number)=>{
17 | showHandleIndex.value = index;
18 | };
19 | /**
20 | * @author: wuwenqiang
21 | * @description: 点击了其他地方,因此点赞评论操作框
22 | * @date: 2021-08-15 16:20
23 | */
24 | const useHandleArticle = ()=>{
25 | showHandleIndex.value = -1;
26 | };
27 |
28 | emitter.$on("bodyClick",useHandleArticle);
29 |
30 | onUnmounted(()=>{
31 | emitter.$off("bodyClick",useHandleArticle)
32 | });
33 |
34 | return {fomatTime,useGoArticleDetail,showHandleIndex,useShowHandle,getImgHtml}
35 | }
36 |
--------------------------------------------------------------------------------
/src/hooks/useArticleTabEffect.ts:
--------------------------------------------------------------------------------
1 | import {ref,reactive,nextTick,toRefs,onUnmounted} from "vue"
2 | import {
3 | ArticleStateInterface,
4 | ArticleChannelInterface
5 | } from "../types";
6 | import {
7 | getFavoriteChannelsListService,
8 | getArticleListService
9 | } from "../service/homeService";
10 | import BScroll from "better-scroll";
11 | import { useRouter } from "vue-router";
12 | import emitter from "../utils/emitter";
13 |
14 | export default ()=> {
15 | const articleState = reactive({
16 | isInit: true,
17 | activeId:-1,
18 | isEnd: false,
19 | loading:false,
20 | params:{
21 | pageNum: 1,
22 | pageSize: 20,
23 | channelId: "",
24 | },
25 | channels:[],
26 | list:[],
27 | bscroll:null
28 | });
29 | const articleNavScroll = ref(null);
30 | const articleScrollWrapper = ref(null);
31 |
32 | /**
33 | * @author: wuwenqiang
34 | * @description: 切换频道
35 | * @date: 2020-06-27 21:29
36 | */
37 | const useTabArticleChannel = async (navItem: ArticleChannelInterface) => {
38 | articleState.activeId = navItem.id;
39 | articleState.params = {
40 | pageNum: 1,
41 | pageSize: 20,
42 | channelId: navItem.channelId,
43 | };
44 | articleState.isEnd = false;
45 | articleState.list.splice(0, articleState.list.length);
46 | if (navItem.channelName == "西瓜视频") articleState.params.type = "video";
47 | let res = await getArticleListService(articleState.params);
48 | articleState.list.push(...res);
49 | nextTick(()=>{
50 | articleState.bscroll.refresh();
51 | })
52 | };
53 |
54 | /**
55 | * @author: wuwenqiang
56 | * @description: 初始化方法
57 | * @date: 2020-06-30 23:28
58 | */
59 | const useInitArticle = async ()=>{
60 | const res = await getFavoriteChannelsListService();//获取频道信息
61 | articleState.channels.push(...res);
62 | const activeChannel = articleState.channels.find((item: ArticleChannelInterface) => item.status == 1);
63 | const {channelId,id} = activeChannel as ArticleChannelInterface;
64 | articleState.params.channelId = channelId;
65 | articleState.activeId = id;
66 | const reuslt = await getArticleListService(articleState.params).finally(()=>{
67 | articleState.isInit = true
68 | });
69 | articleState.list.push(...reuslt);
70 | setTimeout(()=>{
71 | new BScroll(articleNavScroll.value as HTMLElement, {
72 | probeType: 1,
73 | click: true,
74 | scrollX: true,
75 | scrollY: false,
76 | eventPassthrough: "vertical"
77 | });
78 |
79 | articleState.bscroll = new BScroll(articleScrollWrapper.value as HTMLElement, {
80 | probeType: 1,
81 | click: true,
82 | });
83 | articleState.bscroll.on("scroll",()=>{
84 | emitter.emit("scroll");
85 | });
86 | articleState.bscroll.on('scrollEnd', async () => {
87 | if (articleState.bscroll.y <= (articleState.bscroll.maxScrollY + 100) && !articleState.isEnd && !articleState.loading) {
88 | articleState.params.pageNum++;
89 | let result = await getArticleListService(articleState.params).finally(()=>{
90 | articleState.isInit = true
91 | });
92 | if(result.length == 0){
93 | articleState.isEnd = true;
94 | return;
95 | }else{
96 | articleState.isEnd = false;
97 | }
98 | articleState.list.push(...reuslt);
99 | nextTick(() => {
100 | articleState.bscroll.refresh();
101 | });
102 | }
103 | })
104 | },500);
105 | };
106 |
107 | const router = useRouter();
108 |
109 | const useGoArticleDetail = (id:number)=>{
110 | router.push(`/articleDetail/${id}`);
111 | };
112 |
113 | /**
114 | * @author: wuwenqiang
115 | * @description: 弹出评论对话框禁止滚动页面
116 | * @date: 2020-08-23 23:11
117 | */
118 | const disableScroll =()=>{
119 | articleState.bscroll.disable()
120 | };
121 |
122 | /**
123 | * @author: wuwenqiang
124 | * @description: 弹出评论对话框禁止滚动页面
125 | * @date: 2020-08-23 23:11
126 | */
127 | const enableScroll = ()=>{
128 | articleState.bscroll.enable()
129 | };
130 |
131 | emitter.on("disable-scroll",disableScroll);
132 | emitter.on("enable-scroll",enableScroll);
133 |
134 | onUnmounted(()=>{
135 | emitter.off("disable-scroll",disableScroll);
136 | emitter.off("enable-scroll",disableScroll);
137 | });
138 |
139 | return {
140 | ...toRefs(articleState),
141 | useTabArticleChannel,
142 | articleNavScroll,
143 | articleScrollWrapper,
144 | useInitArticle,
145 | useGoArticleDetail
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/hooks/useCommentEffect.ts:
--------------------------------------------------------------------------------
1 | import {ReplyCommentInterface, TopCommentInterface} from "../types";
2 | import {ref,reactive} from "vue";
3 | import {
4 | getCommentCountService,
5 | getTopCommentListService,
6 | insertCommentService,
7 | getCommentItemService,
8 | getReplyCommentListService
9 | } from "../service/handleService";
10 | export default (type:string,id:number)=> {
11 | const commentCount = ref(0);//评论条数
12 | const isDone = ref(true);//是否已经加载所有与评论
13 | const topCommentList = reactive>([]);//一级评论
14 | const placeholder = ref("有爱评论,说点好听的~");
15 | const content = ref("");
16 | let replyCommentItem:ReplyCommentInterface = {};
17 | let pageNum:number = 1;//评论的分页
18 | let pageSize:number = 20;
19 |
20 | /**
21 | * @author: wuwenqiang
22 | * @description: 显示评论
23 | * @date: 2021-08-21 14:16
24 | */
25 | getCommentCountService(type,id).then((res)=>{
26 | commentCount.value = res;
27 | });
28 | getTopCommentListService(type,id,pageNum,pageSize).then((res)=>{
29 | topCommentList.push(...res);
30 | if(res.length < pageSize)isDone.value = true;
31 | });
32 |
33 | /**
34 | * @author: wuwenqiang
35 | * @description: 发送评论
36 | * @date: 2021-08-22 14:10
37 | */
38 | const sendComment = async () => {
39 | const commentItem: ReplyCommentInterface = {};
40 | commentItem.parentId = replyCommentItem.id;
41 | commentItem.topId = replyCommentItem.topId || replyCommentItem.id;
42 | commentItem.content = content.value;
43 | if(type == "toutiao"){
44 | commentItem.articleId = id;
45 | }else if(type == "video"){
46 | commentItem.videoId = id;
47 | }else if(type == "movie"){
48 | commentItem.movieId = id;
49 | }
50 | commentItem.replyUserId = replyCommentItem.userId;
51 | let commmentId:number = -1;//新增之后返回的评论的id
52 | await insertCommentService(type, commentItem).then((res) => {
53 | if (res) content.value = "";
54 | commmentId = res;
55 | replyCommentItem = {};
56 | });
57 | getCommentItemService(type,commmentId).then((res)=>{
58 | if(res.topId){//如果是回复的评论
59 | let topCommentItem:any = topCommentList.find((item)=>item.id == res.topId);
60 | topCommentItem.replyList = topCommentItem.replyList || [];
61 | topCommentItem.replyList.push(res);
62 | }else{//如果是一级评论
63 | topCommentList.push(res);
64 | }
65 | placeholder.value = "有爱评论,说点好听的~";
66 | });
67 | };
68 |
69 | /**
70 | * @author: wuwenqiang
71 | * @description: 发送评论
72 | * @date: 2021-08-22 17:59
73 | */
74 | const useReply = (currentReplyComment:TopCommentInterface)=>{
75 | replyCommentItem = currentReplyComment;
76 | placeholder.value = `回复:${currentReplyComment.username}`
77 | };
78 |
79 |
80 | const useGetReplyCommentList = (commentItem:TopCommentInterface)=>{
81 | commentItem.pageNum = commentItem.pageNum || 1;
82 | commentItem.pageSize = commentItem.pageSize || 20;
83 | commentItem.replyList = commentItem.replyList || [];
84 | getReplyCommentListService(type,commentItem.id as number,commentItem.pageNum,commentItem.pageSize).then((res)=>{
85 | commentItem.isLoadAllReply = res.length < 20;
86 | commentItem.replyList.push(...res);
87 | });
88 | };
89 |
90 | return {
91 | commentCount,isDone,topCommentList,placeholder,sendComment,content,useReply,useGetReplyCommentList
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/hooks/useMoreHandleEffect.ts:
--------------------------------------------------------------------------------
1 | import {MixinInterface} from "../types";
2 | import {ref,onUnmounted,watch,nextTick} from "vue";
3 | import {
4 | isFavoriteService,
5 | isLikeService,
6 | deleteLikeService,
7 | insertLikeService,
8 | deleteFavoriteService,
9 | insertFavoriteService
10 | } from "../service/handleService";
11 | import emitter from "../utils/emitter";
12 | export default (item:MixinInterface,type:string)=> {
13 | const isFavorite = ref(false);
14 | const isLike = ref(false);
15 | const showComment = ref(false);
16 | const commentContainer = ref(null);
17 | let loading:boolean = false;
18 | /**
19 | * @author: wuwenqiang
20 | * @description: 显示点赞评论收藏的操作框
21 | * @date: 2021-08-15 16:20
22 | */
23 | const useShowHandle = ()=>{
24 | isFavoriteService(type,item.id).then(res=> isFavorite.value = res > 0);
25 | isLikeService(type,item.id).then(res => isLike.value = res > 0);
26 | };
27 |
28 | /**
29 | * @author: wuwenqiang
30 | * @description: 点赞
31 | * @date: 2021-08-15 16:20
32 | */
33 | const useHandleLike =()=>{
34 | if(loading)return;
35 | loading = true;
36 | if(isLike.value){
37 | deleteLikeService(type,item.id).then(res=> isLike.value = !(res > 0)).finally(()=>loading = false);
38 | }else{
39 | insertLikeService(type,item.id).then(res=> isLike.value = res > 0).finally(()=>loading = false);
40 | }
41 | };
42 |
43 | /**
44 | * @author: wuwenqiang
45 | * @description: 点赞
46 | * @date: 2021-08-18 19:57
47 | */
48 | const useHandleFavorite = ()=>{
49 | if(loading)return;
50 | loading = true;
51 | if(isFavorite.value){
52 | deleteFavoriteService(type,item.id).then(res=> isFavorite.value = !(res > 0)).finally(()=>loading = false);
53 | }else{
54 | insertFavoriteService(type,item.id).then(res=> isFavorite.value = res > 0).finally(()=>loading = false);
55 | }
56 | };
57 |
58 | /**
59 | * @author: wuwenqiang
60 | * @description: 显示评论
61 | * @date: 2021-08-21 14:16
62 | */
63 | const useShowComment = ()=>{
64 | showComment.value = true;
65 | };
66 |
67 | /**
68 | * @author: wuwenqiang
69 | * @description: 显示评论
70 | * @date: 2021-08-21 14:16
71 | */
72 | const useHideComment = ()=>{
73 | showComment.value = false;
74 | };
75 |
76 | useShowHandle();
77 |
78 | emitter.on("scroll",useHideComment);
79 |
80 | onUnmounted(()=>{
81 | emitter.off("scroll",useHideComment);
82 | });
83 |
84 | watch(showComment,(value)=>{
85 | if(value){
86 | nextTick(()=>{
87 | document.body.appendChild(commentContainer.value)
88 | });
89 | }
90 |
91 | });
92 |
93 | return {
94 | useHandleFavorite,useHandleLike,isFavorite,isLike,useShowComment,showComment,useHideComment,commentContainer
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/hooks/useMovieDetailEffect.ts:
--------------------------------------------------------------------------------
1 | import {reactive,toRefs,ref} from "vue";
2 | import {getMovieDetailService,getStarService,getMovieListByTypeService,getYourLikesService,getRecommendService} from "../service/movieDetailService";
3 | import {useRoute} from "vue-router";
4 | import {MovieInterface,StarInterface} from "@/types";
5 | export default async () => {
6 | const scores = reactive>([]);
7 | const stars = reactive>([]);
8 | const route: any = useRoute();
9 | const movieDetail = reactive({});
10 | const youLikes = reactive>([]);
11 | const recommendList = reactive>([]);
12 | const sameTypeList = reactive>([]);
13 | const showMoreHandle = ref(false);
14 | /**
15 | * @author: wuwenqiang
16 | * @description: 显示点赞评论收藏的操作框
17 | * @date: 2021-08-25 21:44
18 | */
19 | const getStar = (score: number) => {
20 | for (let i = 0; i < 5; i++) {
21 | if ((i + 1) * 2 < score) {
22 | scores.push("full");
23 | } else if ((i + 1) * 2 > score && i * 2 < score) {
24 | scores.push("half");
25 | } else {
26 | scores.push("empty");
27 | }
28 | }
29 | };
30 |
31 | const onMoreHandle = ()=>{
32 | showMoreHandle.value = true;
33 | };
34 |
35 | await getMovieDetailService(route.params.movieId).then((res) => {
36 | Object.assign(movieDetail, res);
37 | getStar(res.score);
38 | });
39 |
40 | getStarService(route.params.movieId).then((res)=>{
41 | stars.push(...res);
42 | });
43 |
44 | if(movieDetail.label){
45 | getYourLikesService(movieDetail.label,movieDetail.classify).then(res=>{
46 | youLikes.push(...res);
47 | });
48 | }
49 |
50 | if(movieDetail.type){
51 | getMovieListByTypeService(movieDetail.type,movieDetail.classify).then(res=>{
52 | sameTypeList.push(...res);
53 | });
54 | }
55 |
56 | getRecommendService(movieDetail.classify).then(res=>{
57 | recommendList.push(...res);
58 | });
59 |
60 | return {
61 | scores,
62 | stars,
63 | youLikes,
64 | sameTypeList,
65 | recommendList,
66 | showMoreHandle,
67 | movieDetail,
68 | onMoreHandle,
69 | ...toRefs(movieDetail)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/hooks/useMovieTabEffect.ts:
--------------------------------------------------------------------------------
1 | import {ref,reactive,nextTick,Ref,toRefs} from "vue"
2 | import {
3 | MovieInterface,
4 | MovieStateInterface,
5 | } from "../types";
6 | import BScroll from "better-scroll";
7 | import {getMovieListService} from "../service/movieService";
8 |
9 | export default ()=> {
10 |
11 | const movieState = reactive({
12 | isInit: false,
13 | activeClassify:"电影",
14 | isEnd: false,
15 | loading:false,
16 | params: {
17 | pageNum:1,
18 | pageSize:20,
19 | classify:"电影",
20 | keyword:""
21 | },
22 | classifies:["电影","电视剧","动漫","综艺","新片场","福利","恐怖"],
23 | list:[],
24 | bscroll:null
25 | });
26 | const movieNavScroll:Ref = ref(null);
27 | const movieScrollWrapper: Ref = ref(null);
28 |
29 |
30 | /**
31 | * @author: wuwenqiang
32 | * @description: 切换频道
33 | * @date: 2020-06-27 21:29
34 | */
35 | const useTabMovieChannel = async (classify:string) => {
36 | movieState.params = {
37 | pageNum: 1,
38 | pageSize: 20,
39 | classify
40 | };
41 | movieState.isEnd = false;
42 | movieState.activeClassify = classify;
43 | movieState.list.splice(0, movieState.list.length);
44 | const result:Array = await getMovieListService(movieState.params);
45 | movieState.list.push(...result);
46 | nextTick(()=>{
47 | movieState.bscroll.refresh();
48 | })
49 | };
50 |
51 | /**
52 | * @author: wuwenqiang
53 | * @description: 初始化视频方法
54 | * @date: 2020-07-02 00:11
55 | */
56 | const useInitMovie = async ()=>{
57 | movieState.params.classify = "电影";
58 | const result:Array = await getMovieListService(movieState.params);
59 | movieState.list.push(...result);
60 | movieState.isInit = true;
61 | movieState.loading = false;
62 | setTimeout(()=>{
63 | new BScroll(movieNavScroll.value as HTMLElement, {
64 | probeType: 1,
65 | click: true,
66 | scrollX: true,
67 | scrollY: false,
68 | eventPassthrough: "vertical"
69 | });
70 | movieState.bscroll = new BScroll(movieScrollWrapper.value as HTMLElement, {
71 | probeType: 1,
72 | click: true
73 | });
74 | movieState.bscroll.on('scrollEnd', async () => {
75 | if (movieState.bscroll.y <= (movieState.bscroll.maxScrollY + 100) && !movieState.isEnd && !movieState.loading) {
76 | movieState.params.pageNum++
77 | let result:Array = await getMovieListService(movieState.params);
78 | movieState.list.push(...result);
79 | nextTick(() => {
80 | movieState.bscroll.refresh();
81 | })
82 | }
83 | })
84 | },100)
85 | }
86 |
87 |
88 | return {
89 | ...toRefs(movieState),
90 | useTabMovieChannel,
91 | movieNavScroll,
92 | movieScrollWrapper,
93 | useInitMovie
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/hooks/useMyTabEffect.ts:
--------------------------------------------------------------------------------
1 | import {useRouter} from "vue-router";
2 |
3 | export default ()=> {
4 | const router = useRouter();
5 | const goRouter =(name:string)=>{
6 | router.push(`recordList?type=${name}`);
7 | };
8 |
9 | return {
10 | goRouter
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/hooks/useVideoListEffect.ts:
--------------------------------------------------------------------------------
1 | import {ref,onUnmounted} from "vue";
2 | import emitter from "../utils/emitter";
3 | import {fomatTime} from "../utils";
4 | export default ()=> {
5 | const showHandleIndex = ref(-1);
6 |
7 | /**
8 | * @author: wuwenqiang
9 | * @description: 显示点赞评论收藏的操作框
10 | * @date: 2021-08-15 16:20
11 | */
12 | const useShowHandle = (index:number)=>{
13 | showHandleIndex.value = index;
14 | };
15 |
16 | /**
17 | * @author: wuwenqiang
18 | * @description: 点击了其他地方,因此点赞评论操作框
19 | * @date: 2021-08-15 16:20
20 | */
21 | const useHandleVideo = ()=>{
22 | showHandleIndex.value = -1;
23 | };
24 |
25 | emitter.$on("bodyClick",useHandleVideo);
26 |
27 | onUnmounted(()=>{
28 | emitter.$off("bodyClick",useHandleVideo)
29 | });
30 |
31 | return {useShowHandle,showHandleIndex,fomatTime}
32 | }
33 |
--------------------------------------------------------------------------------
/src/hooks/useVideoTabEffect.ts:
--------------------------------------------------------------------------------
1 | import {ref,reactive,nextTick,toRefs,Ref} from "vue"
2 | import {
3 | VideoInterface,
4 | VideoChannelInterface,
5 | VideoStateInterface,
6 | } from "../types";
7 | import {
8 | getVideoListService,
9 | getVideoFavoriteChannelsService
10 | } from "../service/videoService";
11 | import BScroll from "better-scroll";
12 | import emitter from "../utils/emitter";
13 |
14 | export default ()=> {
15 |
16 | const videoState = reactive({
17 | isInit: false,
18 | activeChannelId:"",
19 | isEnd: false,
20 | loading:false,
21 | showHandleIndex:-1,
22 | params: {
23 | pageSize:20,
24 | pageNum:1,//页码
25 | star:"",//主演
26 | channelId:"",//分类
27 | type:"",//类型
28 | label:"",//标签
29 | authorId:"",//用户
30 | keyword:"",//关键字
31 | },
32 | channels:[],
33 | list:[],
34 | bscroll:null
35 | });
36 | const videoNavScroll:Ref = ref(null);
37 | const videoScrollWrapper = ref();
38 |
39 |
40 | /**
41 | * @author: wuwenqiang
42 | * @description: 切换频道
43 | * @date: 2020-06-27 21:29
44 | */
45 | const useTabVideoChannel = async (navItem: VideoChannelInterface) => {
46 | videoState.activeChannelId = navItem.channelId;
47 | videoState.params = {
48 | pageNum: 1,
49 | pageSize: 20,
50 | channelId: navItem.channelId,
51 | };
52 | videoState.isEnd = false;
53 | videoState.list.splice(0, videoState.list.length);
54 | let res = await getVideoListService(videoState.params);
55 | videoState.list.push(...res as Array);
56 | nextTick(()=>{
57 | videoState.bscroll.refresh()
58 | })
59 | };
60 |
61 | /**
62 | * @author: wuwenqiang
63 | * @description: 初始化视频方法
64 | * @date: 2020-07-02 00:11
65 | */
66 | const useInitVideo = async ()=>{
67 | let res = await getVideoFavoriteChannelsService();
68 | videoState.channels.push(...res as Array);
69 | videoState.params.channelId = videoState.channels[0].channelId;
70 | let result = await getVideoListService(videoState.params);
71 | videoState.list.push(...result as Array);
72 | videoState.isInit = true;
73 | videoState.loading = false;
74 | videoState.activeChannelId = videoState.channels[0].channelId;
75 | setTimeout(()=>{
76 | new BScroll(videoNavScroll.value as HTMLElement, {
77 | probeType: 1,
78 | click: true,
79 | scrollX: true,
80 | scrollY: false,
81 | eventPassthrough: "vertical"
82 | });
83 | videoState.bscroll = new BScroll(videoScrollWrapper.value as HTMLElement, {
84 | probeType: 1,
85 | click: true,
86 | });
87 | videoState.bscroll.on("scroll",()=>{
88 | emitter.emit("scroll");
89 | });
90 | videoState.bscroll.on('scrollEnd', async () => {
91 | if (videoState.bscroll.y <= (videoState.bscroll.maxScrollY + 100) && !videoState.isEnd && !videoState.loading) {
92 | videoState.params.pageNum++;
93 | let result:Array = await getVideoListService(videoState.params);
94 | videoState.list.push(...result);
95 | nextTick(() => {
96 | videoState.bscroll.refresh()
97 | });
98 | }
99 | })
100 | },100)
101 | };
102 |
103 |
104 | return {
105 | ...toRefs(videoState),
106 | useTabVideoChannel,
107 | videoNavScroll,
108 | videoScrollWrapper,
109 | useInitVideo
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue,{ createApp } from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import store from './store';
5 | import "./utils/setAxios";
6 | import "./common/style/iconfont.css";
7 | import "./common/style/reset.less";
8 |
9 | createApp(App).use(store).use(router).mount('#app');
10 |
--------------------------------------------------------------------------------
/src/pages/ArticleDetailPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{articleDetail.title}}
5 |
6 |
7 |
![]()
8 |
{{articleDetail.authorInfo && articleDetail.authorInfo.name ? articleDetail.authorInfo.name: articleDetail.authorId}}
9 |
{{fomatTime(articleDetail.createTime)}}
10 |
11 |
{{isFocus?"已关注":"关注"}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
37 |
45 |
91 |
--------------------------------------------------------------------------------
/src/pages/HomePage.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
63 |
119 |
153 |
--------------------------------------------------------------------------------
/src/pages/MovieDetaiPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
![]()
9 |
10 |
11 |
{{movieName}}
12 |
{{star}}
13 |
14 |
15 |
16 |
17 |
18 |
{{score}}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
38 |
39 | -
40 |
41 | {{item.starName}}
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
68 |
69 |
84 |
85 |
214 |
--------------------------------------------------------------------------------
/src/pages/RecordListPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router'
2 |
3 | const routes: Array = [
4 | {
5 | path: '/',
6 | name: 'Home',
7 | meta: {keepAlive: true},
8 | component: () => import('../pages/HomePage.vue')
9 | },
10 | {
11 | path: '/articleDetail/:id',
12 | name: 'detail',
13 | meta: {keepAlive: false},
14 | component: () => import('../pages/ArticleDetailPage.vue')
15 | },
16 | {
17 | path: '/recordList',
18 | name: 'recordList',
19 | meta: {keepAlive: false},
20 | component: () => import('../pages/RecordListPage.vue')
21 | },
22 | {
23 | path: '/movieDetail/:movieId',
24 | name: 'movieDetail/:movieId',
25 | meta: {keepAlive: false},
26 | component: () => import('../pages/MovieDetaiPage.vue')
27 | }
28 | ];
29 |
30 | const router = createRouter({
31 | history: createWebHashHistory(),
32 | routes
33 | });
34 |
35 | export default router
36 |
--------------------------------------------------------------------------------
/src/service/appService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import store from "../store";
4 | import {UserDataInterface} from "../types";
5 | import {USER_DATA} from "../store/mutation-types";
6 | export const getUserDataService = async () => {
7 | return new Promise((async (resolve) => {
8 | let data = await axios.get(api.getUserData).then((res: AxiosResponse) => res.data)
9 | .catch(()=>resolve({}));
10 | store.commit(USER_DATA, data);
11 | resolve(data);
12 | }));
13 | }
14 |
--------------------------------------------------------------------------------
/src/service/articleDetailService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {ArticleInterface} from "../types";
4 | import {getUrl} from "../utils"
5 |
6 | export const getArticleService = async (id:number) => {
7 | return axios.get(`${api.getArticleDetail}/${id}`).then((res:AxiosResponse)=>res.data);
8 | };
9 |
10 | export const isFocusService = async (authorId:string,type:string) => {
11 | return axios.get(getUrl(api.isFocus,type),{params:{authorId}}).then((res:AxiosResponse)=>res.data);
12 | };
13 |
14 | export const insertFocusService = async (authorId:string,type:string) => {
15 | return axios.post(getUrl(api.insertFocus,type),{authorId}).then((res:AxiosResponse)=>res.data);
16 | };
17 |
18 | export const deleteFocusService = async (authorId:string,type:string) => {
19 | return axios.delete(getUrl(api.deleteFocus,type),{params:{authorId}}).then((res:AxiosResponse)=>res.data);
20 | };
21 |
--------------------------------------------------------------------------------
/src/service/handleService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {getUrl,getParams} from "../utils"
4 | import {TopCommentInterface,ReplyCommentInterface} from "@/types";
5 |
6 | export const isFavoriteService = (type:string,id:number) => {
7 | return axios.get(getUrl(api.isFavorite,type), {params:getParams(type,id)}).then((res:AxiosResponse)=>res.data);
8 | };
9 |
10 | export const insertFavoriteService = (type:string,id:number) => {
11 | return axios.post(getUrl(api.insertFavorite,type), getParams(type,id)).then((res:AxiosResponse)=>res.data);
12 | };
13 |
14 | export const deleteFavoriteService = (type:string,id:number) => {
15 | return axios.delete(getUrl(api.deleteFavorite,type), {params:getParams(type,id)}).then((res:AxiosResponse)=>res.data);
16 | };
17 |
18 | export const isLikeService = (type:string,id:number) => {
19 | return axios.get(getUrl(api.isLike,type), { params:getParams(type,id)}).then((res:AxiosResponse)=>res.data);
20 | };
21 |
22 | export const insertLikeService = (type:string,id:number) => {
23 | return axios.post(getUrl(api.insertLike,type), getParams(type,id)).then((res:AxiosResponse)=>res.data);
24 | };
25 |
26 | export const deleteLikeService = (type:string,id:number) => {
27 | return axios.delete(getUrl(api.deleteLike,type), {params:getParams(type,id)}).then((res:AxiosResponse)=>res.data);
28 | };
29 |
30 | export const getCommentCountService = (type:string,id:number) => {
31 | return axios.get(getUrl(api.getCommentCount,type), {params:getParams(type,id)}).then((res:AxiosResponse)=>res.data);
32 | };
33 |
34 | export const getTopCommentListService = (type:string,id:number,pageNum:number,pageSize:number) => {
35 | return axios.get(getUrl(api.getTopCommentList,type), {params:getParams(type,id,{pageNum,pageSize})}).then((res:AxiosResponse>)=>res.data);
36 | };
37 |
38 | export const insertCommentService = (type:string,params:ReplyCommentInterface) => {
39 | return axios.post(getUrl(api.insertComment,type), params).then((res:AxiosResponse)=>res.data);
40 | };
41 |
42 | export const deleteCommentService = (type:string,id:number) => {
43 | return axios.delete(getUrl(api.insertComment,type).replace("{id}",id+"")).then((res:AxiosResponse)=>res.data);
44 | };
45 |
46 | export const getReplyCommentListService = (type:string,topId:number,pageNum:number,pageSize:number) => {
47 | return axios.get(getUrl(api.getReplyCommentList,type),{params:{topId,pageNum,pageSize}}).then((res:AxiosResponse>)=>res.data);
48 | };
49 |
50 | export const getCommentItemService = (type:string,id:number) => {
51 | return axios.get(getUrl(api.getCommentItem,type), {params:{id}}).then((res:AxiosResponse)=>res.data);
52 | };
53 |
--------------------------------------------------------------------------------
/src/service/homeService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import { ArticleParamsInterface, ArticleInterface, ArticleChannelInterface} from "../types";
4 | import {getImg} from "../utils";
5 |
6 | export const getFavoriteChannelsListService = async () => {
7 | return axios.get(api.getFavoriteChannels).then((res:AxiosResponse>)=>res.data);
8 | };
9 |
10 | export const getArticleListService = (params:ArticleParamsInterface) => {
11 | return axios.get(api.getArticleList, {
12 | params
13 | }).then((res:AxiosResponse>)=>{
14 | return res.data.map((item:ArticleInterface)=>{
15 | item.imgList = getImg(item);
16 | return item;
17 | });
18 | })
19 | };
20 |
--------------------------------------------------------------------------------
/src/service/movieDetailService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {MovieInterface, StarInterface} from "../types";
4 |
5 | export const getMovieDetailService = async (movieId:number) => {
6 | return axios.get(api.getMovieDetail+movieId).then((res:AxiosResponse)=>res.data);
7 | };
8 |
9 | export const getStarService = async (movieId:number)=>{
10 | return axios.get(api.getStar+movieId).then((res:AxiosResponse>)=>res.data);
11 | };
12 |
13 | export const getYourLikesService = async (labels:string,classify:string)=>{
14 | return axios.get(api.getYourLikes,{params:{labels,classify}}).then((res:AxiosResponse>)=>res.data);
15 | };
16 |
17 | export const getMovieListByTypeService = async (types:string,classify:string)=>{
18 | return axios.get(api.getMovieListByType,{params:{types,classify}}).then((res:AxiosResponse>)=>res.data);
19 | };
20 |
21 | export const getRecommendService = async (classify:string)=>{
22 | return axios.get(api.getRecommend,{params:{classify}}).then((res:AxiosResponse>)=>res.data);
23 | };
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/service/movieService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {MovieParamsInterface,MovieInterface} from "../types";
4 |
5 | export const getMovieListService = (params:MovieParamsInterface) => {
6 | return axios.get(api.getMovieList, {
7 | params
8 | }).then((res:AxiosResponse>)=>res.data);
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/src/service/myService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {MixinInterface} from "../types";
4 |
5 | export const getRecordListService = (type:string) => {
6 | return axios.get(api.getRecordList,{params:{type}}).then((res:AxiosResponse>)=>res.data);
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/service/recordService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {ArticleInterface, MixinInterface} from "../types";
4 | import {getImg,getUrl} from "../utils";
5 |
6 | export const getRecordListService = (type:string) => {
7 | return axios.get(getUrl(api.getRecordList,type)).then((res:AxiosResponse>)=>{
8 | if(type == "toutiao"){
9 | return res.data.map((item:any)=>{
10 | item.imgList = getImg(item as ArticleInterface);
11 | return item;
12 | });
13 | }
14 | return res.data
15 | })
16 | };
17 |
18 |
--------------------------------------------------------------------------------
/src/service/videoService.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosResponse} from "axios";
2 | import api from "../api";
3 | import {VideoParamsInterface, VideoInterface, VideoChannelInterface, ArticleInterface, MovieInterface} from "../types";
4 |
5 | export const getVideoFavoriteChannelsService = ()=>{
6 | return axios.get(api.getVideoFavoriteChannels).then((res:AxiosResponse>)=>res.data)
7 | }
8 |
9 | export const getVideoListService = (params:VideoParamsInterface) => {
10 | return axios.get(api.getVideoList, {
11 | params
12 | }).then((res:AxiosResponse>)=>res.data);
13 | };
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | declare module '*.vue' {
3 | import type { DefineComponent } from 'vue'
4 | const component: DefineComponent<{}, {}, any>
5 | export default component
6 | }
7 |
--------------------------------------------------------------------------------
/src/store/actions.ts:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types'
2 | import {UserDataInterface} from "../types";
3 |
4 | export const setToken = ({ commit }:any, myData:string):void => {
5 | commit(types.TOKEN, myData)
6 | }
7 |
8 | export const setUserData = ({ commit }:any, userData:UserDataInterface):void => {
9 | commit(types.USER_DATA, userData)
10 | }
11 |
--------------------------------------------------------------------------------
/src/store/getters.ts:
--------------------------------------------------------------------------------
1 | import stateInterface from './stateInterface'
2 |
3 | export const token = (state: stateInterface) => state.token;
4 | export const userData = (state: stateInterface) => state.userData;
5 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 | import * as getters from "./getters";
3 | import * as actions from "./actions";
4 | import mutations from "./mutations";
5 | import state from "./state";
6 |
7 | export default createStore({
8 | state,
9 | mutations,
10 | actions,
11 | getters
12 | })
13 |
--------------------------------------------------------------------------------
/src/store/mapActions.ts:
--------------------------------------------------------------------------------
1 | import {useStore} from "vuex"
2 |
3 | export default (arr:Array):any=>{
4 | const data: any = {};
5 | const state = useStore();
6 | arr.forEach((item: string) => {
7 | data[item] = (payload:any) => {
8 | state.dispatch(item,payload)
9 | }
10 | });
11 | return data;
12 | }
13 |
--------------------------------------------------------------------------------
/src/store/mapGetters.ts:
--------------------------------------------------------------------------------
1 | import {computed} from "vue";
2 | import store from "../store"
3 |
4 | export default (arr:Array):any => {
5 | const data:any = {};
6 | arr.forEach((item:string)=>{
7 | data[item] = computed(()=>{
8 | return store.getters[item];
9 | })
10 | })
11 | return data;
12 | }
13 |
--------------------------------------------------------------------------------
/src/store/mutation-types.ts:
--------------------------------------------------------------------------------
1 | export const TOKEN = 'TOKEN'
2 | export const USER_DATA = 'USER_DATA'
3 |
--------------------------------------------------------------------------------
/src/store/mutations.ts:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types'
2 | import stateInterface from "./stateInterface";
3 | import {UserDataInterface} from "../types"
4 |
5 | export default {
6 | [types.TOKEN](state: stateInterface, token: string): void {
7 | state.token = token
8 | },
9 | [types.USER_DATA](state: stateInterface, userData: UserDataInterface): void {
10 | state.userData = userData
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/src/store/state.ts:
--------------------------------------------------------------------------------
1 | import stateInterface from './stateInterface'
2 |
3 | export default {
4 | token: '',
5 | userData: null,
6 | }
7 |
--------------------------------------------------------------------------------
/src/store/stateInterface.ts:
--------------------------------------------------------------------------------
1 | import {UserDataInterface} from "../types"
2 |
3 | export default interface StateInterface {
4 | token: string,
5 | userData: UserDataInterface | null,
6 | }
7 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface UserDataInterface {
2 | userId:string,
3 | create_date:string,
4 | update_date:string,
5 | username:string,
6 | telephone:string,
7 | email:string,
8 | avater:string,
9 | birthday:string,
10 | sex:string,
11 | role:string,
12 | sign:string,
13 | region:string
14 | }
15 |
16 | export interface ArticleInterface {
17 | id:number,//主键
18 | channelId:string,//频道id
19 | content:string,//内容
20 | duration:string,//视频播放时长
21 | href:string,//链接地址
22 | img:string,//视频图片地址
23 | imgNum:string,//图片长度
24 | type:string,//类型,视频:video, 文章: article,博客:blog
25 | isTop:string,//是否置顶,0表示否,1表示是
26 | title:string,//主标题
27 | createTime:string,//创建时间
28 | updateTime:string,//更新时间
29 | source:string,//来源
30 | commentId:string,//评论id
31 | labels:string,//标签
32 | authorId:string,//用户id
33 | authorAvatar:string,//头像
34 | authorHref:string,//用户主页
35 | ranks:string//排名
36 | imgList?:Array,//预览图片列表
37 | }
38 |
39 | export interface ArticleChannelInterface {
40 | id:number,//主键
41 | channelId:string,//频道id
42 | channelName:string,//频道名称
43 | href:string,//链接地址
44 | createTime:string,//创建时间
45 | updateTime:string,//最近更新时间
46 | userId:string,//用户id
47 | disabled:number,//是否禁用
48 | status:number,//状态,公开:0,推荐:1,默认:2
49 | sequence:number,//主标题
50 | }
51 |
52 | export interface ArticleParamsInterface {
53 | channelId?:string,//频道id
54 | authorId?:string,//用户id
55 | keyword?:string,
56 | pageNum:number,//页码
57 | pageSize: number,//每页长度
58 | isTop?:string //是否置顶
59 | type?:string
60 | }
61 |
62 | export interface ArticleStateInterface {
63 | isInit: boolean,
64 | activeId:number,
65 | isEnd: boolean,
66 | loading:boolean,
67 | params:ArticleParamsInterface,
68 | channels:Array,
69 | list:Array,
70 | bscroll:any
71 | }
72 |
73 | export interface VideoInterface {
74 | id:number, //主键,
75 | albumId:string,//视频id,
76 | channelId:string,//频道id,
77 | title:string,//电影名称,
78 | director:string,//导演,
79 | star:string,//主演,
80 | type:string,//类型,
81 | countryLanguage: string,//国家/语言,
82 | publishTime:string,//上映时间,
83 | plot:string,//剧情,
84 | isRecommend:string //是否推荐,0:不推荐,1:推荐,
85 | classify:string ,//分类 电影,电视剧,动漫,综艺,新片库,福利,午夜,恐怖,其他,
86 | sourceName: string,//来源名称,本地,骑士影院,爱奇艺,
87 | sourceUrl:string,//来源地址,
88 | label:string,//标签,
89 | originaHref: string,//源地址,
90 | description :string,//简单描述,
91 | targetHref:string,//链接地址,
92 | status:string,//0代表未使用,1表示正在使用,是banner和carousel图的才有,
93 | score: number,//评分,
94 | category:string,//类目,值为banner首屏,carousel:滚动轮播,
95 | ranks:string,//排名,
96 | authorId:string,//用户名,这这个表不需要,为了跟记录叫和收藏表的结构一致,
97 | duration:string,//时长,
98 | img:string,// 图片地址,
99 | createTime:string,//创建时间,
100 | updateTime:string//更新时间,
101 | authorInfo:AuthorInfo,//用户信息
102 | }
103 |
104 | export interface AuthorInfo {
105 | id:number,//主键
106 | authorId:string,//用户id
107 | name:string,//名称
108 | authorDesc:string,//描述
109 | avatarUrl:string,//头像地址
110 | description:string,//描述
111 | followersCount:string,//粉丝数量
112 | verifiedContent:string,//主要内容
113 | createTime:string,//创建时间
114 | updateTime:string,//更新时间
115 | }
116 |
117 | export interface VideoParamsInterface {
118 | pageSize:number,//每页显示条数
119 | pageNum:number,//页码
120 | star?:string,//主演
121 | channelId?:string,//分类
122 | type?:string,//类型
123 | label?:string,//标签
124 | authorId?:string,//用户
125 | keyword?:string,//关键字
126 | }
127 |
128 | export interface VideoChannelInterface {
129 | id:number,
130 | channelName:string,
131 | channelId:string,
132 | createTime:string,
133 | updateTime:string,
134 | sequence:number,
135 | status:number,
136 | userId:string
137 | }
138 |
139 | export interface VideoStateInterface {
140 | isInit: boolean,
141 | activeChannelId:string,
142 | isEnd: boolean,
143 | loading:boolean,
144 | params: VideoParamsInterface,
145 | channels:Array,
146 | list:Array,
147 | bscroll:any
148 | showHandleIndex:number,//显示点赞评论收藏操作框的下标
149 | }
150 |
151 | export interface MovieStateInterface {
152 | isInit: boolean,
153 | activeClassify:string,
154 | isEnd: boolean,
155 | loading:boolean,
156 | params: MovieParamsInterface,
157 | classifies:Array,
158 | list:Array,
159 | bscroll:any
160 | }
161 | export interface MovieInterface {
162 | id:number,//主键',
163 | movieId:number,//电影id
164 | movieName:string,//电影名称
165 | director:string,//导演
166 | star:string,//主演
167 | type:string,//类型
168 | countryLanguage:string,//国家/语言
169 | viewingState:string,//观看状态
170 | releaseTime:string,//上映时间
171 | plot:string,//剧情
172 | updateTime:string,//更新时间
173 | isRecommend:number,//是否推荐,0:不推荐,1:推荐
174 | img:number,//电影海报
175 | classify:string,//分类 电影,电视剧,动漫,综艺,新片库,福利,午夜,恐怖,其他
176 | sourceName:string,//来源名称,本地,骑士影院,爱奇艺
177 | sourceUrl:string,//来源地址
178 | createTime:string,//创建时间
179 | localImg:string,//本地图片
180 | label:string,//标签
181 | originalHref:string,//源地址
182 | description:string,//简单描述
183 | targetHref:string,//链接地址
184 | useStatus:string,//0代表未使用,1表示正在使用,是banner和carousel图的才有
185 | score:number,//评分
186 | category:string,//类目,值为banner首屏,carousel:滚动轮播
187 | ranks:string,//排名
188 | userId:string,//用户名,这这个表不需要,为了跟记录叫和收藏表的结构一致
189 | doubanUrl:string,//对应豆瓣网的地址
190 | }
191 | export interface MovieParamsInterface {
192 | id?:number,
193 | pageNum:number,
194 | pageSize:number
195 | classify?:string,
196 | star?:string,
197 | type?:string,
198 | label?:string,
199 | director?:string,//导员
200 | keyword?:string
201 | }
202 |
203 | export interface HandlePropsInterface {
204 | type:string,
205 | item:MixinInterface
206 | }
207 |
208 | export type MixinInterface = ArticleInterface|MovieInterface|VideoInterface
209 |
210 | export interface ReplyCommentInterface {
211 | id?:number,
212 | content:string,
213 | parentId?:number,
214 | topId?:number,
215 | articleId?:number,
216 | videoId?:number,
217 | movieId?:number,
218 | userId:string,
219 | username?:string,
220 | avater?:string,
221 | replyUserId?:string,
222 | replyUserName?:string,
223 | createTime?:string,
224 | updateTime?:string,
225 | }
226 |
227 | export interface TopCommentInterface extends ReplyCommentInterface{
228 | replyCount:number,
229 | pageNum?:number,//第某页回复
230 | pageSize?:number,//每页回复数量
231 | isLoadAllReply?:boolean,//是否已经加载了所有回复
232 | replyList:Array
233 | }
234 |
235 | export interface CommentListPropsInterface {
236 | type:string,
237 | id:number
238 | }
239 |
240 | export interface StarInterface {
241 | id:number,
242 | starName:string,
243 | img:string,
244 | localImg:string,
245 | createTime:string,
246 | update_time:string,
247 | movie_id:number,
248 | role:string,
249 | href:string
250 | works:string
251 | }
252 |
253 |
254 |
255 |
--------------------------------------------------------------------------------
/src/utils/emitter.ts:
--------------------------------------------------------------------------------
1 | import Mitt from 'mitt';
2 |
3 | const emitter = new Mitt();
4 |
5 | emitter.$on = emitter.on;
6 | emitter.$off = emitter.off;
7 | emitter.$emit = emitter.emit;
8 |
9 | export default emitter;
10 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import {ArticleInterface} from "../types";
2 |
3 | export const zorefull=(value:number):string|number=>{
4 | return value < 9 ? "0"+value:value
5 | }
6 |
7 | export const fomatTime=(value:any):string=>{
8 | var date =new Date(value);
9 | var nowDate = new Date()
10 | let diff = Math.ceil((nowDate.getTime()-date.getTime())/1000);
11 | if(diff < 60){
12 | return diff + "秒前"
13 | }else if(diff < 60*60){
14 | return Math.ceil(diff/60) + "分前"
15 | }else if(diff < 60*60*24){
16 | return Math.ceil(diff/(60*60))+"小时前"
17 | }else if(diff < 60*60*24*30){
18 | return Math.ceil(diff/(60*60*24))+"天前"
19 | }else if(diff < 60*60*24*30*12){
20 | return Math.ceil(diff/(60*60*24*30))+"个月前"
21 | }
22 | const year = zorefull(date.getFullYear());
23 | const month = zorefull(date.getMonth()+1);
24 | const dates = zorefull(date.getDate());
25 | const hour = zorefull(date.getHours());
26 | const minutes = zorefull(date.getMinutes());
27 | const seconds = zorefull(date.getSeconds());
28 | return `${year}-${month}-${dates} ${hour}:${minutes}:${seconds}`
29 | };
30 |
31 | /**
32 | * @author: wuwenqiang
33 | * @description: 获取图片html
34 | * @date: 2020-06-27 21:29
35 | */
36 | export const getImg = (article:ArticleInterface)=>{
37 | if (article.type == "video")return article.img ? [`
${article.duration}
`] : []
38 | return article.content.match(/
]+>/g) || []
39 | };
40 |
41 | export const isMobile = ()=> {
42 | var sUserAgent = navigator.userAgent.toLowerCase();
43 | if (/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile/.test(sUserAgent)) {
44 | return true;//手机端
45 | } else {
46 | return false;//pc端
47 | }
48 | };
49 |
50 | /**
51 | * @author: wuwenqiang
52 | * @description: 获取图片数量
53 | * @date: 2020-06-27 21:29
54 | */
55 | export const getImgHtml = (htmlStr:string,length:number,index:number) =>{
56 | if (index == 3 && length > 4){
57 | return `+${length-index-1}
${htmlStr}`;
58 | }else{
59 | return htmlStr;
60 | }
61 | };
62 |
63 | /**
64 | * @author: wuwenqiang
65 | * @description: 根据类型获取api地址
66 | * @date: 2020-06-27 21:29
67 | */
68 | export const getUrl = (url:string,type:string) => url.replace("{type}",type);
69 |
70 | /**
71 | * @author: wuwenqiang
72 | * @description: 根据类型转换参数
73 | * @date: 2020-06-27 21:29
74 | */
75 | export const getParams = (type:string,id:number,params?:object) => {
76 | switch (type) {
77 | case "toutiao":
78 | return {articleId:id,...params};
79 | case "video":
80 | return {videoId:id,...params};
81 | case "movieId":
82 | return {movieId:id,...params};
83 | }
84 | };
85 |
--------------------------------------------------------------------------------
/src/utils/setAxios:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import store from "../store";
3 | import {TOKEN} from "../store/mutation-types"
4 |
5 | axios.interceptors.request.use(config => {
6 | //config是axios配置对象
7 | //每次发送请求前都会进入此拦截器处理函数,可以在此处统一携带上token,每次请求都会带有
8 | config.headers.common.Authorization = store.state.token || localStorage.getItem('token');
9 | return config
10 | },err => {
11 | //请求出错的处理函数
12 | return Promise.reject(err)
13 | });
14 |
15 | axios.interceptors.response.use((res)=> {
16 | if(res.data.status=="SUCCESS" && res.data.token){
17 | store.commit(TOKEN, res.data.token);
18 | localStorage.setItem("token",res.data.token);
19 | }
20 | return res.data;
21 | },err => {
22 | // 在请求错误时要做的事儿
23 | // 该返回的数据则是axios.catch(err)中接收的数据
24 | return Promise.reject(err)
25 | });
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ]
21 | },
22 | "lib": [
23 | "esnext",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "src/**/*.ts",
31 | "src/**/*.tsx",
32 | "src/**/*.vue",
33 | "tests/**/*.ts",
34 | "tests/**/*.tsx"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | lintOnSave: false,
3 | devServer: {
4 | overlay: {
5 | warning: false,
6 | errors: false
7 | },
8 | port: '8080',
9 | open: true,
10 | proxy: {
11 | '^/service/toutiao': {
12 | target: 'http://localhost:8000',
13 | },
14 | '^/service/video': {
15 | target: 'http://localhost:8001',
16 | },
17 | '^/service/movie': {
18 | target: 'http://localhost:5000',
19 | },
20 | '/static': {
21 | target: 'http://localhost:5001',
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/明日头条app整体效果图(待更新).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/明日头条app整体效果图(待更新).jpg
--------------------------------------------------------------------------------
/界面预览1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览1.png
--------------------------------------------------------------------------------
/界面预览10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览10.png
--------------------------------------------------------------------------------
/界面预览2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览2.png
--------------------------------------------------------------------------------
/界面预览3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览3.png
--------------------------------------------------------------------------------
/界面预览4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览4.png
--------------------------------------------------------------------------------
/界面预览5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览5.png
--------------------------------------------------------------------------------
/界面预览6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览6.png
--------------------------------------------------------------------------------
/界面预览7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览7.png
--------------------------------------------------------------------------------
/界面预览8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览8.png
--------------------------------------------------------------------------------
/界面预览9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyuanwuhui99/vue3-ts-toutiao-app-ui/a5c2409f027113d9ce1bff4776141a77be28fcb0/界面预览9.png
--------------------------------------------------------------------------------
6 |-
7 |
8 |
9 | {{tItem.username}}
10 | {{tItem.content}}
11 |
12 | {{tItem.createTime.replace(/^\d{1,4}-/,"")}}
13 |
14 |
15 |
16 |
33 |
34 |
35 |
36 |
37 |