花了两周,看了许久的文档和案例,还是要实践一下,于是做了这个demo,设计就这样看吧,我的设计水平真心不好T^T,一周目的demo是静态的,二周目再搭建数据层。
项目仓库:https://github.com/gknpezgssb...
项目简介
鉴于最近的pokemon大热,这次的demo也选择了pokemon主题的(其实我是想选守望屁股来着的)。
主体
项目结构:
Demo:
主显示区域(这里由vue-router控制)可进行三个界面的切换;
功能区域(由logo按钮,搜索栏,pokemon列表,精灵捕获记录四个部分组成)
各部分功能
点击捕获按钮,主界面切换,可以记录pokemon信息,点击捕获成功后列表会自动更新(原谅科幻迷的我 (/≥▽≤/))
可以在搜索栏中用你给精灵起的名字寻找你的pokemon
点击具体精灵会显示他的信息
一周目制作
脚手架
vue-cli是一款脚手架工具,使用他能帮你自动生成模版,免去繁杂的配置工作,这里我选用了webpack的模版。
首先安装vue-cli:
npm install -g vue-cli
之后进入项目目录,安装依赖的包:
vue init webpack vue-time-tracker
cd vue-time-tracker
npm install
此时,执行 npm run dev,在地址栏输入localhost:8080
,如果成功看到下图就说明你已经搭好框架了
main.js 与 App.vue 配置
首先将 index.html 改写成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>yourpokemon</title>
</head>
<body>
<div id="app">
</div>
<!-- built files will be auto injected -->
</body>
</html>
安装vue-router
npm install vue-router --save
在main.js加入如下代码:
import Vue from 'vue'
import App from './App'
import Home from './components/Home'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter()
router.map({
'/home': {
component: Home
}
})
router.redirect({
'*': '/home'
})
router.start(App, '#app')
这里暂时先引用Home组件。
我们引用了vue与vue-router,并且通过map配置了路由,这样在任何标签(不限于a)上使用v-link都能实现路由变化。
<template>
<div id="pokemon">
<div class="game-boy">
<div class="gb-main">
<router-view ></router-view>
</div>
<asidebar :items="monsters"></asidebar>
</div>
</div>
</template>
这样App.vue中的内容会替换掉到index里的div,这里左部的主题区域用vue-router来控制,右边的功能区域自定义组件asidebar。
接下来我们在App.vue中添加
import Asidebar from './components/Asidebar.vue'
export default {
components: {
'asidebar': Asidebar
},
data () {
let monstersData = [
{
name: '艾萨克·阿西莫夫',
type: 'thunder',
species: '皮卡丘',
height: '0.4m',
weight: '6.0kg ',
sex: 'male'
},
{
name: '亚瑟·克拉克',
type: 'grass',
species: '妙蛙种子',
height: '0.7m',
weight: '6.9kg ',
sex: 'female'
}
]
return {
monsters: monstersData
}
},
events: {
}
}
这里引用组件Asidebar,伪造两组数据,通过v-bind进行父子组件通信,详见使用props传递数据。
最后往App.vue添加样式
*{
margin: 0;
padding: 0;
box-sizing:border-box;
}
#pokemon{
width: 100vw;
height: 100vh;
background-image: url(./assets/bg.jpg);
background-position: center center;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: flex;
justify-content: center;
-ms-align-items: center;
align-items: center;
}
.gb-main{
width: 500px;
border: 20px solid #444;
float: left;
height: 100%;
}
.game-boy{
box-shadow: 0 0 15px #333;
width: 800px;
height: 450px;
background-color: #fff;
border: 25px solid #fc0;
border-radius: 18px 18px 13px 13px;
}
倒腾 Sidebar.vue
首先添加html:
<template>
<div class="gb-aside">
<div class="gb-logo" v-link="'/home'"></div>
<input type="text" class="gb-search" placeholder="寻找你的梦可宝" v-model="search">
<ol class="gb-list">
<li class="monster" v-for="item in items | search" v-link="'/detail'" @click="show(item)">
<div class="m-img" :class="item.type">
</div>
<h2 class="m-name">{{item.species}}:<span>{{item.name}}</span></h2>
</li>
</ol>
<button v-link="'/new'" class="gb-catch">捕捉</button>
</div>
</template>
这里设置了v-link使
logo按钮能访问home页面
li标签能访问detail页面
button按钮能访问new页面
可以看到搜索栏绑定了v-model,通过他结合li上的search函数即可实现列表的搜索
在li上的click事件,绑定了show函数,他实现了一个事件派发,传递到App.vue后再由其进行广播。js代码如下:
export default {
data () {
return {search: ''}
},
props: ['items'],
filters: {
search (name) {
return name.filter(item => item.name.indexOf(this.search) > -1)
}
},
methods: {
show (item) {
let index = this.items.indexOf(item)
this.$dispatch('findMonster', index)
}
}
}
css代码如下:
.gb-aside{
position: relative;
float: right;
width: 250px;
background-color: #FC0;
height: 100%;
padding-left: 25px;
}
.gb-list{
list-style: none;
background-color: #fff;
width: 100%;
margin-top: 20px;
margin-bottom: 20px;
height: 200px;
overflow-y: scroll;
overflow-x:hidden;
}
.monster{
cursor: pointer;
width: 100%;
height: 40px;
border-bottom: 1px solid #dadada;
position: relative;
padding-left: 43px;
position: relative;
}
.m-name{
width: 150px;
line-height: 40px;
font-size: 12px;
color: #666;
white-space: nowrap;
overflow: hidden;
-ms-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.m-name span{
font-size: 14px;
color: #333;
}
.m-img.grass{
background-image: url(../assets/grass.png);
}
.m-img.thunder{
background-image: url(../assets/thunder.png);
}
.m-img.water{
background-image: url(../assets/water.png);
}
.m-img.fire{
background-image: url(../assets/fire.png);
}
.m-img.normal{
background-image: url(../assets/normal.png);
}
.m-img.chonoryoku{
background-image: url(../assets/chonoryoku.png);
}
.m-img.wrestle{
background-image: url(../assets/wrestle.png);
}
.m-img{
background-position: center center;
-webkit-background-size: 23px 23px;
background-size: 23px 23px;
background-repeat: no-repeat;
width: 30px;
height: 40px;
position: absolute;
left: 5px;
top: 0;
}
.m-img img{
width: 25px;
height: 25px;
}
.gb-logo{
height: 60px;
background-image: url(../assets/logo.png);
background-repeat: no-repeat;
background-position: center top;
-webkit-background-size: auto 20px;
background-size: auto 40px;
cursor: pointer;
}
.gb-search{
width: 100%;
display: block;
border: 0px;
height: 40px;
padding-left: 1em;
font-size: 18px;
color: #333;
/*margin-top: 60px;*/
}
.gb-catch{
background-color: #fff;
cursor: pointer;
position: absolute;
bottom: 10px;
left: 25px;
right: 0;
margin: 0px auto;
border: 0;
width: 150px;
height: 50px;
border-radius: 25px;
padding-left: 70px;
font-size: 18px;
line-height: 50px;
color: #666;
text-align: left;
}
.gb-catch::before{
background-image: url(../assets/ball.png);
background-position: center center;
background-repeat: no-repeat;
-webkit-background-size:auto 40px ;
background-size:auto 40px ;
content:"";
position: absolute;
left: 3px;
top: 0;
width: 50px;
height: 50px;
animation-name: catch-pokemon;
animation-duration:2s;
animation-timing-function:linear;
animation-iteration-count:infinite;
animation-direction:normal;
}
@keyframes catch-pokemon {
from {
transform:rotate(0deg);
}
to {
transform:rotate(360deg);
}
}
我门还需对App.vue添加事件
events: {
findMonster (index) {
let monster = this.monsters[index]
this.$broadcast('showMonster', monster)
}
}
这里接受到事件派发的App.vue又进行了一次事件广播实现了子组件的通信,当然还有更好的方式这里纯粹是为了使用dispatch和broadcast.( ̄▽ ̄")
关于这块详见vue api broadcast和dispatch。
主显示区
我们在main.js中先引用另外两个组件
import New from './components/NewMonster'
import Detail from './components/Detail'
//并且修改路由配置
router.map({
'/home': {
component: Home
},
'/new': {
component: New
},
'/detail': {
component: Detail
}
})
NewMonster 组件
<template>
<div class="detail">
<div class="d-info">
<label for="">姓名:</label>
<input type="text" v-model="monster.name">
<label for="">属性:</label>
<select name="" id="" v-model="monster.type">
<option class="water" selected>water</option>
<option class="fire" >fire</option>
<option class="grass" >grass</option>
<option class="thunder" >thunder</option>
<option class="chonoryoku" >chonoryoku</option>
<option class="wrestle" >wrestle</option>
<option class="normal" >normal</option>
</select>
</div>
<div class="d-info">
<label for="">性别:</label>
<select name="" id="" v-model="monster.sex">
<option selected>male</option>
<option >female</option>
</select>
<label for="">种类:</label>
<input type="text" v-model="monster.species">
</div>
<div class="d-info">
<label for="">身高:</label>
<input type="text" v-model="monster.height">
<label for="">体重:</label>
<input type="text" v-model="monster.weight">
</div>
<button class="d-catch" @click="save()">捕获成功</button>
</div>
</template>
<script>
export default {
data () {
return {
monster: {
name: '',
type: '',
species: '',
height: '',
weight: '',
sex: ''
}
}
},
methods: {
save () {
if (this.monster.name && this.monster.species) {
let monster = this.monster
this.$dispatch('catch', monster)
this.monster = {}
} else {
window.alert('请填入完整精灵信息')
}
}
}
}
</script>
<style scoped>
.detail {
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
background-image: url(../assets/sightseeing.png);
background-repeat: no-repeat;
-webkit-background-size: 130% 100%;
background-size: 130% 100%;
background-position: center bottom;
padding-top: 30px;
}
.d-info{
width: 100%;
height: 35px;
position: relative;
margin-top: 30px;
margin-bottom: 30px;
text-align: center;
}
.d-catch{
width: 120px;
height: 35px;
border: 3px solid #666;
display: block;
margin: 0 auto;
background-color: #fff;
border-radius: 10px;
cursor: pointer;
}
option{
height: 25px;
line-height: 25px;
display: block;
font-size: 14px;
background-repeat: no-repeat;
background-position: right center;
-webkit-background-size: 20px 20px;
background-size: 20px 20px;
padding-left: 10px;
}
option.water{
background-image: url(../assets/water.png);
}
option.fire{
background-image: url(../assets/fire.png);
}
option.grass{
background-image: url(../assets/grass.png);
}
option.thunder{
background-image: url(../assets/thunder.png);
}
option.normal{
background-image: url(../assets/normal.png);
}
option.wrestle{
background-image: url(../assets/wrestle.png);
}
option.chonoryoku{
background-image: url(../assets/chonoryoku.png);
}
.detail label{
width: 80px;
text-align: right;
height: 35px;
line-height: 35px;
font-size: 14px;
color: #666;
display: inline-block;
background-repeat: no-repeat;
background-position: center left;
-webkit-background-size: auto 35px;
background-size: auto 35px;
}
.d-info input,.d-info select{
display: inline-block;
width: 120px;
height: 100%;
border: 1px solid #999;
font-size: 14px;
color: #444;
padding-left: 10px;
background-color: #fff;
}
.d-info:nth-of-type(1) label:nth-of-type(1){
background-image: url(../assets/Eevee.png);
}
.d-info:nth-of-type(1) label:nth-of-type(2){
background-image: url(../assets/Flareon.png);
}
.d-info:nth-of-type(2) label:nth-of-type(1){
background-image: url(../assets/Jolteon.png);
}
.d-info:nth-of-type(2) label:nth-of-type(2){
background-image: url(../assets/Vaporeon.png);
}
.d-info:nth-of-type(3) label:nth-of-type(1){
background-image: url(../assets/Espeon.png);
}
.d-info:nth-of-type(3) label:nth-of-type(2){
background-image: url(../assets/Umbreon.png);
}
</style>
通过save方法派发一个事件,用来保存新捕获的精灵;
对于App需添加events
events: {
catch (monster) {
this.monsters.push(monster)
},
findMonster (index) {
let monster = this.monsters[index]
this.$broadcast('showMonster', monster)
}
}
并且设置名字与种类未填写时无法提交。
Detail 组件
<template>
<div class="detail" :class="monster.type">
<h1 class="name"><span>{{monster.name}}</span></h1>
<h1 class="species"><span>{{monster.species}}</span></h1>
<h1 class="height"><span>{{monster.height}}</span></h1>
<h1 class="weight"><span>{{monster.weight}}</span></h1>
<h1 class="sex"><span>{{monster.sex}}</span></h1>
</div>
</template>
<script>
export default {
data () {
return {
monster: {
name: '??',
species: '??',
height: '??',
weight: '??',
sex: '??',
type: ''
}
}
},
events: {
showMonster (info) {
this.monster.name = info.name
this.monster.species = info.species
this.monster.height = info.height === '' ? '??' : info.height
this.monster.weight = info.weight === '' ? '??' : info.weight
this.monster.sex = info.sex
this.monster.type = info.type
}
}
}
</script>
App.vue广播的时间被Detail收到从而实现右侧列表点击左侧内容切换的效果。
Home 组件
这是一个纯粹静态的组件,代码也很简短:
<template>
<div class="cover">
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: '打造你的梦可宝图鉴!'
}
}
}
</script>
<style scoped>
h2 {
color: #4E3A13;
text-align: center;
line-height: 3.5em;
font-size: 25px;
position: absolute;
bottom: 0px;
width: 100%;
}
.cover{
position: relative;
width: 100%;
height: 100%;
background-image: url(../assets/cover.jpg);
background-position: center center;
-webkit-background-size: 500px auto;
background-size: 500px auto;
}
</style>
总结
这只是一个很粗糙的试手Demo,有很多代码需要优化,会在二周目进行,二周目也会搭建真实的数据层;
demo真的没有什么实际的用处╮(╯_╰)╭ ,但是vue却是实用且精致的典范
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。