这一节,我们将通过一个实战案例 :
来加深对 vuex 的理解
element-ui 是由国内饿了么团队开发的一套vue组件, 相比于其他的一些组件库,如
iview
( GitHub: https://github.com/iview/iview )
( 官网文档: https://www.iviewui.com/docs/guide )
它提供了更丰富的组件内容, 更友好的UI风格和API。
你可以使用或者不使用它, 这里仅提供一种选择。
在任意目录下打开控制台, 依次完成以下操作
1. 使用vue-cli + webpack 初始化项目结构
vue init webpack news
2. 安装项目依赖
cnpm i
3. 安装vuex & element-ui
cnpm i vuex element-ui -S
4. 启动项目服务
npm start
根据需求
我们先来分析一下前后台交互的数据有哪些 :
如果筛选类型是动态返回的, 还需要添加一个
关于 筛选 的执行,这里有两种方案
在当前网络性能已不成障碍的前提下,一般采用第二种方案。
由此分析可得与后台交互的接口 :
axios.get('/searchMode').then(res => {}).catch(err => {})
axios.get('/news', { type, keyword }).then(res => {}).catch(err => {})
由于案例没有和后台连接,所以我们先来伪造一下数据接口
src/ajax/news.js
const _news = [
{
id: 0,
title: '习近平论和平',
content: '\n' +
' <p style="text-align:center;">\n' +
' </p>\n' +
' <p> 今年(2017年)12月13日,是第四个南京大屠杀死难者国家公祭日,也是南京大屠杀死难同胞80周年祭。80年来,曾经的苦难和血泪不敢忘,也不能忘。那沉重哭墙之上镌刻的每一个名字,都是我们刻骨铭心的历史。</p>\n' +
' <p> 为了永不忘却的纪念,2017年国家公祭日来临之际,荔枝新闻和我苏客户端推出特别策划“为历史拂尘,祈和平永续”互动。愿擦亮的名字守护历史的真相,愿和平的祈愿温暖寒冬的殇城。</p>\n' +
' <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/8/20171281512721396004_214.png" border="0" alt=""></p>\n' +
' <p> “为历史拂尘,祈和平永续”互动页面以哭墙为背景。侵华日军南京大屠杀遇难同胞纪念馆原馆长朱成山曾说:“在当年惨绝人寰的南京大屠杀中,30万遇难同胞,没有坟墓,只有一个名字,哭墙就是他们共同的墓碑。这样的祭奠,其实是一种情感的表达,南京大屠杀的历史,我们应该永远铭记”。</p>\n' +
' <p> 的确,一切苦难皆有名字,每个名字都是一部历史。哭墙是情感的表达、个体的史书。它无声,可那嵌进时间的笔画里是30万个体的愤怒、呼喊和哀叫;它无泪,可那饱蘸生命的笔墨里是30万个体的血泪与凋萎。</p>\n' +
' <p> <strong>进入“为历史拂尘,祈和平永续”互动页面后,刻有万余遇难者名字的哭墙会持续滚动。</strong></p>\n' +
' <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/6/20171261512526500996_214.gif" border="0" alt=""></p>\n' +
' <p> 尽管灰暗,但却是最真最痛的历史;尽管沉重,但却是必须永远铭记的真相。</p>\n' +
' <p> 对于这面沉重的史书,我们的每一次触碰即是一次告慰。我们的每一次擦拭即是一次纪念。</p>\n' +
' <p> 从擦亮每一个遇难者的名字开始,为历史拂尘,守护真相。<strong>轻触哭墙,现在就可以践行你的那份力量——</strong></p>\n' +
' <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/6/20171261512549549335_214.png" border="0" alt=""></p>\n' +
' <p> 昭昭前事,惕惕后人。这一擦亮历史的寓意寄望提醒:无穷的远方,无数的人们,都与你我有关。这样擦亮名字的行动希望告慰那些逝去的生命,铭记就是最好的纪念。</p>\n' +
' <p> 每一个逝去的生命都是一颗星星。<strong>擦亮名字的行动完成后,页面上属于逝者的那颗星星会升入银河。</strong></p>\n' +
' <p></p>\n' +
' <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/8/20171281512721645459_214.gif" border="0" alt=""></p>\n' +
' <p> <strong>页面上同时会生成独属于你的那张海报。长按即可保存海报</strong>,永久纪念这份刻骨铭心的历史,永久记录这份守护真相的行动。</p>\n' +
' <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/8/20171281512721406459_214.png" border="0" alt=""></p>\n' +
' <p> 此刻,距离2017年12月13日不到24小时。呜咽的警报将一再提醒我们那一年冬天的殇城有多寒冷、多黑暗。</p>\n' +
' <p> 这份铭记历史、守护真相、传递和平的使命,与你我有关。</p><p> 现在,就开始你的<strong>【擦亮历史】</strong>行动\n' +
' </p>\n',
from: '环球军事',
active: false,
createAt: '2017-12-13 08:15:00'
},
{
id: 1,
title: '以史为鉴 才能面向未来',
content: '<p style="font-size: 16px;"> 原标题:以史为鉴,才能面向未来(国际论坛)</p>\n' +
' <p style="font-size: 16px;"> 村山富市</p>\n' +
' <p style="font-size: 16px;"> 在思考日中关系时,最令我难忘的是1995年——日本战败50周年之际,我作为日本首相发表了“村山谈话”。“村山谈话”指出,日本在不久的过去一段时期,国策有错误,走了战争的道路,殖民统治和侵略给许多国家,特别是亚洲各国人民带来了巨大的损害和痛苦,对此表示深刻的反省和由衷的歉意,并向在这段历史中受到灾难的所有国内外人士表示沉痛的哀悼。我认为,日本必须正确认识日中关系的历史事实,并在深刻反省的基础上,与中国开展交往。</p>\n' +
' <p style="font-size: 16px;"> 二战结束之后,日本一些政客不断在历史问题上作出错误言行,伤害了中国等亚洲国家人民的感情,使日本无法取信于亚洲各国。“村山谈话”得到包括亚洲国家在内的世界各国的高度评价,如果“村山谈话”的精神能够得到日本政界人士的继承,相信日本与亚洲各国将会更好地构建友好共处的关系。为此,在辞任日本首相两年后,我在1998年参观了侵华日军南京大屠杀遇难同胞纪念馆,并敬献了写有“前事不忘,后事之师”的花圈,发出正视历史、以史为鉴的信号,希望推动日中关系向前发展。</p>\n' +
' <p style="font-size: 16px;"> 2014年,中国全国人大常委会通过决定,将每年的12月13日设立为南京大屠杀死难者国家公祭日。近日,加拿大安大略省议会通过了有关“设立南京大屠杀纪念日”的动议。日本军国主义的侵略,给中国人民造成了巨大伤害。包括中国、加拿大在内的世界各地纪念包括南京大屠杀在内的历史惨案,具有重要的意义。</p>\n' +
' <br/>\n' +
' <p style="font-size: 16px;"> 历史是一面镜子。只有以史为鉴,才能面向未来。日本军国主义发动的侵略战争,给中国等亚洲各国人民带来深重灾难,对于包括南京大屠杀在内的历史事实,我们应该永远铭记于心。日本政府和政界人士应该始终保持“以史为鉴,才能面向未来”的姿态,这非常重要。</p>\n' +
' <p style="font-size: 16px;"> 当前,日本执政党正在推进修改宪法的进程,试图修改宪法第九条。“放弃发动战争权利”的日本宪法第九条,是日本向全世界发出的和平宣言。修改宪法第九条,会给包括亚洲在内的世界各国发出危险信号,损害日本作为和平国家的信用,百害而无一利。日本应该放弃这种篡改宪法第九条的愚蠢行径。</p>\n' +
' <p style="font-size: 16px;"> 对日本政府来说,与中国约定好的事情,必须要切实遵守。如果不遵守相关约定,两国间就无法建立起信赖关系,外交工作也难以开展。日中之间的四个政治文件和四点原则共识并没有过时,对现在来说依然非常重要。应该在日中四个政治文件和四点原则共识的基础上,本着以史为鉴、面向未来的精神发展两国关系。</p>\n' +
' <p style="font-size: 16px;"> 2017年不仅是日中邦交正常化45周年,同时也是标志着日本发动全面侵华战争的“卢沟桥事变”80周年。我们不应该只纪念邦交正常化,对日中两国来说,更应该去了解从卢沟桥事变到邦交正常化这段时间里发生了什么,去了解中国人民经历了多少苦难。</p>\n' +
' <p style="font-size: 16px;"> 像去年访问美国珍珠港一样,如果安倍首相访问中国,理所当然应该去访问当年侵华日军给中国人民造成巨大灾难的场所。</p>\n' +
' <p style="font-size: 16px;"> (作者为日本前首相)</p>',
from: '人民日报',
active: false,
createAt: '2017-12-13 05:49:00'
},
{
id: 2,
title: '用在钓鱼岛?美媒称日本承认对远程导弹的兴趣',
content:
' <p style="text-align: center; font-size: 16px;">\n' +
' <img alt="" src="http://himg2.huanqiu.com/attachment2010/2017/1213/08/03/20171213080338993.jpg" style="width: 570px; height: 294px;">\n' +
' </p>\n' +
' <p style="font-size: 16px;"> 【环球网军事12月13日报道】据美国《防务新闻》11日报道,日本方面“确认了对远程导弹的兴趣”,日本的42架F-35A隐形战斗机将获得最先进的“联合攻击导弹(JSM)”,此外日本还将认真研究为现役F-15J战斗机配备远程导弹的可能性。报道特别提到,日本官方否认采购该导弹是为对付朝鲜,而是用来防卫包括钓鱼岛在内的“偏远岛屿”。</p>\n' +
' <p style="font-size: 16px;"> 报道称,日本防卫大臣小野寺五典证实,防卫省将在明年4月提交的2018年预算中预留额外资金,以购买美国的先进导弹。他表示,防卫省还计划资助研究将洛克希德·马丁公司研制的AGM-158B及在此基础上发展的AGM-158C远程反舰导弹整合到日本F-15J战斗机上的可行性。据称,JSM导弹是目前唯一能在F-35内部武器舱内携带的远程隐身巡航导弹。</p>\n' +
' <p style="font-size: 16px;"> 报道称,小野寺五典的最新表态与他三天前在新闻发布会上的说法大相径庭,当时他宣称日本“尚未决定引进远程导弹,也没有在2018财政年度预算中预留出相关资金”。</p>\n' +
' <p style="font-size: 16px;"> 此前有日本媒体称,如果朝鲜向日本发射弹道导弹,日本可能会用这种巡航导弹对朝鲜目标进行打击。但《防务新闻》称,日本官方明确表示,“这些导弹‘将专门用于防卫日本’,特别是那些所谓的‘偏远岛屿’——后者经常被视为对中日纷争的钓鱼岛的委婉说法”。</p>\n' +
' <p style="font-size: 16px;"> 美国“外交学者”网站11日也强调,日本防卫大臣坚持认为,购买新型导弹不是为了对敌方基地进行攻击。这些新型导弹将专门用于保卫自卫队的宙斯盾驱逐舰,因为日本防御朝鲜弹道导弹的第一道防线就是6艘装备宙斯盾系统的导弹驱逐舰。</p>\n' +
' <p style="font-size: 16px;"> 不过接受采访的中国专家认为,上述巡航导弹是典型的进攻性武器,用来保护日本驱逐舰的说法根本经不住推敲。日本引进上述导弹将极大提高自卫队对地对海攻击能力。这些先进导弹将日本战机挂载导弹的攻击范围从300公里猛增到500甚至1000公里,可在对方防空区以外发射,而且它们均可采用低空隐身突防方式,较难防御和拦截。▲ (张亦驰)</p>\n' +
' ',
from: '环球时报',
active: false,
createAt: '2017-12-13 08:01:00'
}
]
const _searchMode = [
{
id: 1,
label: '内容',
value: 'content'
},
{
id: 0,
label: '来源',
value: 'from'
}
]
export default {
getSearchMode (cb) {
setTimeout(() => {
cb(_searchMode)
}, 100)
},
getNews (type, keyword, cb) {
setTimeout(() => {
switch (_searchMode.indexOf(_searchMode.find(m => m.value === type))) {
case 0:
cb(_news.filter(n => n.title.indexOf(keyword) >= 0 || n.content.indexOf(keyword) >= 0))
break
case 1:
cb(_news.filter(n => n.from === keyword))
break
default:
cb(_news)
break
}
}, 100)
}
}
配置 mutations 名称常量
src/store/types/news.js
export const SET_NEWS = 'SET_NEWS'
export const SET_SEARCH_MODE = 'SET_SEARCH_MODE'
创建 store.news
src/store/modules/news.js
import * as types from '../types/news'
import news from '../../ajax/news'
const state = {
keyword: '',
type: '',
modes: [],
now: []
}
const getters = {
keyword: state => state.keyword,
type: state => state.type,
modes: state => state.modes,
now: state => state.now
}
const actions = {
getSearchMode ({commit}) {
news.getSearchMode((modes) => commit(types.SET_SEARCH_MODE, {modes}))
},
getNews ({commit, state}) {
news.getNews(state.type, state.keyword, (news) => commit(types.SET_NEWS, {news}))
}
}
const mutations = {
[types.SET_NEWS] (state, {news}) {
state.now = news
},
[types.SET_SEARCH_MODE] (state, {modes}) {
state.modes = modes
state.type = modes[0].value
},
updateType (state, type) {
state.type = type
},
updateKeyword (state, keyword) {
state.keyword = keyword
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
创建 store
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import news from './modules/news'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
news
}
})
src/assets/stylesheets/base.css
body {
margin: 0;
}
/* 设置webkit内核滚动条样式 */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
border-radius: 5px;
background-color: #F5F5F5;
}
::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(119, 119, 119, 0.7);
}
src/main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
// 引入element-ui 组件和样式文件
import element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/stylesheets/base.css'
Vue.config.productionTip = false
Vue.use(element)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
src/components/News.vue
这里用到了 element-ui (by 饿了么团队) 中的 :
<template>
<div class="container" :style="{height: innerHeight}">
<div class="container-header">
<el-input placeholder="输入关键字.." v-model="keyword" @keyup.native="getNews">
<el-select class="el-select" v-model="type" slot="prepend" placeholder="请选择.." @change="getNews">
<el-option v-for="mode in modes" :key="mode.id" :label="mode.label" :value="mode.value"></el-option>
</el-select>
</el-input>
</div>
<div class="container-body">
<el-collapse accordion>
<el-collapse-item v-for="item in now" :key="item.id">
<template slot="title">
<div class="collapse-item-profile">
<label>创建时间: <span>{{item.createAt}}</span></label>
<label>来源: <span>{{item.from}}</span></label>
</div>
<span class="collapse-item-title">{{item.title}}</span>
</template>
<div v-html="item.content"></div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
import {mapGetters, mapActions, mapMutations} from 'vuex'
export default {
name: 'News',
mounted () {
this.$nextTick(function () {
this.getSearchMode()
this.getNews()
})
},
methods: {
...mapActions('news', [
'getSearchMode',
'getNews'
]),
...mapMutations('news', [
'updateType',
'updateKeyword'
])
},
computed: {
...mapGetters('news', [ 'now', 'modes' ]),
type: {
// 修改计算属性的 getter & setter 使之可以双向数据绑定
get () {
return this.$store.state.news.type
},
set (value) {
// 提交 mutation 改变 store.state
this.updateType(value)
}
},
keyword: {
get () {
return this.$store.state.news.keyword
},
set (value) {
this.updateKeyword(value)
}
},
innerHeight () {
return window.innerHeight - 16 + 'px'
}
}
}
</script>
<style>
.el-collapse-item__wrap {
background-color: #fed;
}
</style>
<style scoped>
.el-select {
width:130px;
}
.container {
width: 1068px;
margin: 8px auto;
overflow: auto;
}
.container-header,.container-body {
width: 1048px;
}
.collapse-item-title {
font-size: 1.25rem;
font-weight: 600;
padding-left: 15px;
}
.collapse-item-profile {
display: inline-block;
float: right;
margin-right: 100px;
}
.collapse-item-profile>label {
font-size: .75rem;
font-weight: 400;
color: #555;
}
.collapse-item-profile>label>span {
color: #777;
}
</style>
src/App.vue
<template>
<div id="app">
<news></news>
</div>
</template>
<script>
import News from './components/News.vue'
export default {
components: { News }
}
</script>
本节源码 :