项目预览
主要包含主页资讯,圈子俩大模块
主页
资讯详情
圈子
相关代码
网络请求
import wx from 'wx'
import Fly from 'flyio'
const request = new Fly()
request.interceptors.request.use((request) => {
wx.showNavigationBarLoading()
return request
})
request.interceptors.response.use(
(response, promise) => {
wx.hideNavigationBarLoading()
return promise.resolve(response.data)
},
(err, promise) => {
wx.hideNavigationBarLoading()
wx.showToast({
title: err.message,
icon: 'none'
})
return promise.resolve()
}
)
export default request
部分 api 列表
- 新闻列表 https://api.ithome.com/json/newslist/news?r=0
- 文章详情 https://api.ithome.com/xml/newscontent/350/412.xml
- 相关文章 https://api.ithome.com/json/tags/0350/350362.json
- 最热评论 https://dyn.ithome.com/json/hotcommentlist/350/87a8e5b144d81938.json
- 评论列表 https://dyn.ithome.com/json/commentlist/350/87a8e5b144d81938.json
- 评论详情 https://dyn.ithome.com/json/commentcontent/d739ee8f2ceb0a27.json
- 轮播新闻 https://api.ithome.com/xml/slide/slide.xml
- 圈子列表 https://apiquan.ithome.com/api/post?categoryid=0&type=0&orderTime=&visistCount&pageLength
- 圈子详情 https://apiquan.ithome.com/api/post/236076
- 圈子评论 https://apiquan.ithome.com/api/reply?postid=236076&replyidlessthan=3241294
api数据获取
import request from './request'
const baseUrlApi = 'https://api.ithome.com'
const baseUrlDyn = 'https://dyn.ithome.com'
const baseUrlQuan = 'https://apiquan.ithome.com'
const api = {
getNewsList: (r) => request.get('/json/newslist/news', null, {
baseURL: baseUrlApi
}),
getNews: (id) => request.get(`/xml/newscontent/${id}.xml`, null, {
baseURL: baseUrlApi
}),
getRelatedNews: (id) => request.get(`/json/tags/0${id.slice(0, 3)}/${id}.json`, null, {
baseURL: baseUrlApi,
parseJson: false
}),
getNewsComments: (id) => request.get(`/json/commentlist/350/87a8e5b144d81938.json`, null, {
baseURL: baseUrlDyn
}),
getSlides: () => request.get('/xml/slide/slide.xml', null, {
baseURL: baseUrlApi
}),
getTopics: (r) => request.get('/api/post', {
categoryid: 0,
type: 0,
orderTime: r,
visistCount: '',
pageLength: ''
}, {
baseURL: baseUrlQuan
}),
getTopic: (id) => request.get(`/api/post/${id}`, null, {
baseURL: baseUrlQuan
}),
getTopicComments: (id, last) => request.get('/api/reply', {
postid: id,
replyidlessthan: last
}, {
baseURL: baseUrlQuan
})
}
export default api
数据存取
import Vue from 'vue'
import Vuex from 'vuex'
import xml2json from 'xmlstring2json'
import { formatSlideList, formatNewsList, formatTopicList } from '@/utils'
import api from '@/utils/api'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
slides: [],
news: [],
topics: []
},
mutations: {
slides (state, data) {
state.slides = data
},
news (state, data) {
state.news = data
},
topics (state, data) {
state.topics = data
}
},
actions: {
async getSlides ({ commit }) {
const slides = await api.getSlides()
if (!slides) return
const parsedSlides = xml2json(slides).rss.channel.item
const filtedSlides = parsedSlides.filter(
slide => slide.opentype['#text'] === '1'
)
const formatedSlides = filtedSlides.map(formatSlideList)
commit('slides', formatedSlides)
},
async getNewsList ({ state, commit }, init) {
const news = await api.getNewsList()
if (!news) return
const formatedNews = news.newslist.map(formatNewsList)
if (init) {
commit('news', formatedNews)
} else {
commit('news', state.news.concat(formatedNews))
}
},
async getTopics ({ state, commit }, init) {
let replytime = Date.now()
if (!init) {
const lastTopic = state.topics[state.topics.length - 1]
replytime = lastTopic.replytime.replace(/[^0-9]/ig, '')
}
const topics = await api.getTopics(replytime)
if (!topics) return
const formatedTopics = topics.map(formatTopicList)
if (init) {
commit('topics', formatedTopics)
} else {
commit('topics', state.topics.concat(formatedTopics))
}
}
}
})
export default store
新闻资讯列表
<template lang="pug">
.container
swiper.slider-wrap(
autoplay,
indicator-dots,
circular,
indicator-color="rgba(255, 255, 255, .3)",
indicator-active-color="rgba(210, 34, 34, .7)")
swiper-item(
v-for="slide of slides",
:key="slide.title")
.slider-item(@click="$router.push(slide.link)")
.slider-title {{slide.title}}
img.slider-img(:src="slide.image", mode="aspectFill")
.news-wrap
news-item(
v-for="item of news",
:news="item"
:key="item.newsid")
.nomore 只给看这么多
</template>
<script>
import wx from 'wx'
import { mapState, mapActions } from 'vuex'
import newsItem from '@/components/news-item'
export default {
components: {
newsItem
},
computed: {
...mapState([
'slides',
'news'
])
},
mounted () {
this.refresh()
},
onPullDownRefresh () {
this.refresh()
},
// onReachBottom () {
// this.loadmore()
// },
methods: {
...mapActions([
'getSlides',
'getNewsList'
]),
async refresh () {
await Promise.all([
this.getNewsList(true),
this.getSlides()
])
wx.stopPullDownRefresh()
}
// loadmore () {
// this.getNewsList()
// }
}
}
</script>
<style lang="less" scoped>
.slider-wrap {
width: 100%;
height: 200px;
}
.slider-item {
position: relative;
display: block;
width: 100%;
height: 100%;
}
.slider-title {
max-width: 90vw;
position: absolute;
top: 10px;
right: 0;
background-color: rgba(0, 0, 0, .3);
color: #fff;
padding: 2px 6px;
font-size: 18px;
}
.slider-img {
width: 100%;
height: 100%;
}
.news-wrap {
padding: 0 10px;
}
.nomore {
width: 100%;
line-height: 50px;
text-align: center;
font-size: 14px;
color: #ddd;
}
</style>
新闻资讯详情
<template lang="pug">
.container
.header
h1.news-title {{title}}
.auth-info {{news.newssource}}({{news.newsauthor}})
.news-content(v-html="news.detail")
h2.related-title(v-if="relatedNews.length") 相关文章
.news-wrap
news-item(
v-for="news of relatedNews",
:news="news"
:key="news.newsid")
//- .comment-btn(@click="turnToComment")
//- img.comment-icon(src="/static/assets/comment.png")
</template>
<script>
import xml2json from 'xmlstring2json'
import api from '@/utils/api'
import newsItem from '@/components/news-item'
const dataArr = []
export default {
components: {
newsItem
},
data () {
return {
id: null,
title: '',
news: {},
relatedNews: []
}
},
async mounted () {
Object.assign(this.$data, this.$options.data())
this.id = this.$route.query.id
this.title = this.$route.query.title
await Promise.all([
this.getNews(),
this.getRelatedNews()
])
dataArr.push({ ...this.$data })
},
onUnload () {
dataArr.pop()
const dataNum = dataArr.length
if (!dataNum) return
Object.assign(this.$data, dataArr[dataNum - 1])
},
methods: {
turnToComment () {
this.$router.push({
path: '/pages/news/comment',
query: {
id: this.id
}
})
},
async getNews () {
let { id } = this
id = `${id.slice(0, 3)}/${id.slice(3, 6)}`
const news = await api.getNews(id)
if (!news) return
const parsedNews = xml2json(news).rss.channel.item
this.news = {
newssource: parsedNews.newssource['#text'],
detail: parsedNews.detail['#text'].replace(/<img/g, '<img width="100%"'),
newsauthor: parsedNews.newsauthor['#text']
}
},
async getRelatedNews () {
const newslist = await api.getRelatedNews(this.id)
if (!newslist) return
const parsedNews = JSON.parse(newslist.replace('var tag_jsonp =', ''))
this.relatedNews = parsedNews.slice(0, 3).map(news => {
return {
title: news.newstitle,
image: news.img,
link: `/pages/news/detail?id=${news.newsid}&title=${news.newstitle}`,
postdate: news.postdate
}
})
}
}
}
</script>
<style lang="less">
@import url("~@/styles/index.less");
.header {
display: flex;
flex-direction: column;
width: 100%;
color: #fff;
background-color: @primary-color;
padding: 10px;
box-sizing: border-box;
}
.news-title {
font-size: 22px;
}
.auth-info {
font-size: 12px;
margin-top: 10px;
align-self: flex-end;
}
.news-content {
width: 100%;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
line-height: 1.6;
}
.related-title {
font-size: 18px;
font-weight: 600;
align-self: flex-start;
border-left: 4px solid @primary-color;
padding: 2px 5px;
}
.news-wrap {
width: 100%;
box-sizing: border-box;
padding: 0 10px;
}
.comment-btn {
width: 55px;
height: 55px;
border-radius: 50%;
background-color: @primary-color;
position: fixed;
right: 20px;
bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.comment-icon {
width: 40px;
height: 40px;
}
</style>
新闻资讯组件
<template lang="pug">
.news-item(
@click="turn",
v-if="!news.lapinid")
img.news-img(:src="news.image", mode="aspectFill")
.news-text
.news-title {{news.title}}
.news-info
text {{news.postdate}}
text(v-if="news.commentcount") {{news.commentcount}}评
</template>
<script>
export default {
props: {
news: {
type: Object,
default () {
return {}
}
}
},
methods: {
turn () {
const { link } = this.news
this.$router.push(link)
}
}
}
</script>
<style lang="less" scoped>
.news-item {
display: flex;
height: 90px;
align-items: center;
border-bottom: 1px solid #eee;
}
.news-img {
width: 100px;
height: 75px;
margin-right: 10px;
}
.news-text {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.news-title {
font-size: 15px;
}
.news-info {
color: #aaa;
font-size: 12px;
display: flex;
justify-content: space-between;
}
</style>
圈子列表
<template lang="pug">
.container
.topic-wrap
topicItem(
v-for="topic of topics",
:topic="topic"
:key="topic.id")
</template>
<script>
import wx from 'wx'
import { mapState, mapActions } from 'vuex'
import topicItem from '@/components/topic-item'
export default {
components: {
topicItem
},
computed: {
...mapState([
'topics'
])
},
mounted () {
this.refresh()
},
onPullDownRefresh () {
this.refresh()
},
onReachBottom () {
this.loadmore()
},
methods: {
...mapActions([
'getTopics'
]),
async refresh () {
await this.getTopics(true)
wx.stopPullDownRefresh()
},
loadmore () {
this.getTopics()
}
}
}
</script>
<style lang="less">
</style>
圈子详情
<template lang="pug">
.container
.topic-title {{topic.title}}
.topic-num 1楼
.topic-info
.topic-info-item
img.topic-info-icon(src="/static/assets/quan_hit.png")
span.topic-info-text {{topic.vc}}
.topic-info-item
img.topic-info-icon(src="/static/assets/quan_comment.png")
span.topic-info-text {{topic.rc}}
.topic-content(v-html="topic.content")
.comment-wrap
comment-item(
v-for="comment of topic.reply",
:key="comment.id",
:comment="comment")
</template>
<script>
import api from '@/utils/api'
import commentItem from '@/components/comment-item'
import { formatComment } from '@/utils'
export default {
components: {
commentItem
},
data () {
return {
loading: false,
topic: {}
}
},
mounted () {
Object.assign(this.$data, this.$options.data())
this.getTopic()
},
onReachBottom () {
this.getComments()
},
methods: {
async getTopic () {
const { query } = this.$route
const topic = await api.getTopic(query.id)
if (!topic) return
topic.content = topic.content.replace('!--IMG_1--', `img src="${topic.imgs[0]}" width="100%" /`)
topic.reply = topic.reply.map(formatComment)
this.topic = Object.assign({
title: query.title,
vc: query.vc
}, topic)
},
async getComments () {
if (this.loading) return
this.loading = true
const { query } = this.$route
const comments = this.topic.reply
const lastComment = comments[comments.length - 1]
const newComments = await api.getTopicComments(query.id, lastComment.id)
if (!newComments) return
const formatedComments = newComments.map(formatComment)
this.topic.reply = this.topic.reply.concat(formatedComments)
this.loading = false
}
}
}
</script>
<style lang="less">
.topic-title {
width: 100%;
padding: 10px;
box-sizing: border-box;
font-size: 22px;
}
.topic-num {
font-size: 12px;
position: absolute;
right: 10px;
top: 10px;
}
.topic-info {
display: flex;
align-items: center;
width: 100%;
padding: 0 10px 10px;
box-sizing: border-box;
border-bottom: 1px solid #eee;
}
.topic-info-item {
margin-right: 10px;
font-size: 12px;
color: #aaa;
display: flex;
align-items: center;
}
.topic-info-icon {
width: 15px;
height: 15px;
margin-right: 4px;
}
.topic-content {
width: 100%;
font-size: 16px;
padding: 10px;
line-height: 1.6;
border-bottom: 1px solid #eee;
box-sizing: border-box;
min-height: 100px;
}
.comment-wrap {
width: 100%;
}
</style>
圈子组件
<template lang="pug">
.topic-item(@click="turn")
img.topic-headimg(src="/static/assets/avatar_default.png")
img.topic-headimg(:src="topic.author.headimg")
.topic-title {{topic.tag}} {{topic.title}}
.topic-info
.topic-info-item {{topic.author.nickname}}
.topic-info-item {{topic.type}}
.topic-info-item
img.topic-info-icon(src="/static/assets/quan_hit.png")
span.topic-info-text {{topic.viewcount}}
.topic-info-item
img.topic-info-icon(src="/static/assets/quan_comment.png")
span.topic-info-text {{topic.replycount}}
</template>
<script>
export default {
props: {
topic: {
type: Object,
default () {
return {}
}
}
},
methods: {
turn () {
const { id, title, author, viewcount } = this.topic
this.$router.push({
path: '/pages/quanzi/detail',
query: {
id,
title,
author,
vc: viewcount
}
})
}
}
}
</script>
<style lang="less" scoped>
.topic-item {
padding: 10px 10px 10px 60px;
border-bottom: 1px solid #eee;
position: relative;
}
.topic-headimg {
width: 40px;
height: 40px;
border-radius: 50%;
position: absolute;
left: 10px;
top: 15px;
}
.topic-title {
font-size: 16px;
}
.topic-info {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 10px;
}
.topic-info-item {
margin-left: 10px;
font-size: 12px;
color: #aaa;
display: flex;
align-items: center;
}
.topic-info-icon {
width: 15px;
height: 15px;
margin-right: 4px;
}
</style>
相关依赖
- flyio - 同时支持浏览器、小程序、Node、Weex 及快应用的基于 Promise 的跨平台请求库
- mpvue-entry - 集中式页面配置,不再需要重复编辑各页面的 main.js 文件
- mpvue-router-patch - 在 mpvue 中使用 vue-router 兼容的路由写法
- xmlstring2json - xml字符串转换 json 格式,适用于微信小程序
Tips
- flyio 使用方法
具体内容参见 微信小程序中使用flyio,这里提示下小程序中需要引入的是 flyio/dist/npm/wx.js
这个文件,可以配置下 webpack 的 alias 方便调用
alias: {
'@': resolve('src'),
vue: 'mpvue',
flyio: 'flyio/dist/npm/wx',
wx: resolve('src/utils/wx')
}
- vuex 使用方法
建立 src/store/index.js
文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
},
mutations: {
},
actions: {
}
})
export default store
在 src/main.js
中引用
import Vue from 'vue'
import store from '@/store'
import App from '@/App'
const app = new Vue({
store,
...App
}).$mount()
最后在需要使用 vuex 的页面相对应的 main.js
文件中像 src/main.js
一样引用即可
说明
如果本项目对您有帮助,欢迎 “点赞,关注” 支持一下 谢谢~
源码获取关注公众号「码农园区」,回复 【uniapp源码】