vue移动端项目-mall_phone(面向接口开发)

318 阅读6分钟

项目首页

image.png

一、项目技术栈

  • phone:
    • vue-cli + vue-router + vuex +sass、less + es6^ +webpack
  • server
    • express +mysql + doc

二、接口

三、项目引入vant

1.创建项目

  • 在cmd中,进入到桌面(desktop),输入命令:vue create mall_phone image.png
  • 选择第二个vue-ui,因为需要用到babel、router、eslint image.png
  • 创建成功

image.png

  • 通过VSCode打开创建的项目,并运行
    • npm run serve 或
    • npm start

image.png

image.png

2.vant

youzan.github.io/vant/#/zh-C…

  • yarn add vant
  • 重启:npm run serve 每次修改文件后需要重启项目

三、项目常见结构

  • public 静态资源
  • src 源码
    • main.js 入口
    • App.vue 根组件
    • assets common 图片、css
    • components 存放普通的组件
    • view 存放路由组件
    • plugins 存放插件、ui
    • api 存放接口
    • utils 工具
    • mixins 混入
    • router 路由
    • filters 过滤器
    • directives 自定义过滤器

四、手动配置路由

1.yarn add vue-router

2.新建router文件夹,下面有一个index.js文件

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    // 还未写
  ]
})


// 1. 引入安装好的vue-router
// 2. 注册路由 Vue.use()
// 3. 实例化路由对象
// 4. 定义路由规则
// 设置 hash地址 和组件的对应关系
// 5. 挂载路由
// 6. 渲染路由
  • 挂载路由 image.png
  • 渲染路由
    • 移动端中间部分都是渲染的路由 image.png

五、项目主要路由配置

  • 配置路由时,需要把根元素写好,不然会报错:
<template>
  <div class="home">
   this is home
  </div>
</template>
  • 在views文件夹下创建四个主要组件(Home.vue、Friends.vue、Cart.vue、Search.vue),这四个组件是处于移动端页面底部的导航

1.Home组件

1.Home.vue

<template>
  <div>home</div>
</template>
<script>
export default {
  data: () => ({})
}
</script>
<style lang="less" scoped>
</style>

2.配置路由

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Friends from '../views/Friends.vue'
import Cart from '../views/Cart.vue'
import Search from '../views/Search.vue'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {
      path: '/',
      redirect: '/home'
    },
    {
      path: '/home',
      component: Home
    },
    {
      path: '/friends',
      component: Friends
    },
    {
      path: '/cart',
      component: Cart
    },
    {
      path: '/search',
      component: Search
    }
  ]
})

export default router

3.渲染到页面上(App.vue)

  • 需要用到的组件直接到Vant官网找到,并引入到项目的./plugins/vant.js(按需引入)(也可以全部引入)
import Vue from 'vue'
import { Button, Tabbar, TabbarItem, NavBar, Swipe, SwipeItem, Toast, Grid, GridItem } from 'vant'

Vue.use(Button)
Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.use(NavBar)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Grid)
Vue.use(GridItem)

Vue.prototype.$Toast = Toast
  • (App.vue)
<template>
  <div>
    <van-nav-bar
      :title="title"
      left-text="返回"
      left-arrow
    />
    <router-view></router-view>
    <van-tabbar v-model="active">
      <van-tabbar-item
        icon="home-o"
        to="/home"
      ></van-tabbar-item>
      <van-tabbar-item
        icon="friends-o"
        to="/friends"
      ></van-tabbar-item>
      <van-tabbar-item
        icon="cart-o"
        to="/cart"
      ></van-tabbar-item>
      <van-tabbar-item
        icon="search"
        to="/search"
      ></van-tabbar-item>
    </van-tabbar>
  </div>
</template>
<script>
export default {
  data: () => ({
    title: '首页',
    active: 0
  }),
  methods: {}
}
</script>
<style lang="less" scoped>
</style>

六、返回上一页(App.vue)

<template>
  <div>
    <van-nav-bar
      :title="title"
      left-text="返回"
      left-arrow
      @click-left="onClickLeft"
    />
    <router-view></router-view>
    <van-tabbar v-model="active">
      <van-tabbar-item
        icon="home-o"
        to="/home"
      ></van-tabbar-item>
      <van-tabbar-item
        icon="friends-o"
        to="/friends"
      ></van-tabbar-item>
      <van-tabbar-item
        icon="cart-o"
        to="/cart"
      ></van-tabbar-item>
      <van-tabbar-item
        icon="search"
        to="/search"
      ></van-tabbar-item>
    </van-tabbar>
  </div>
</template>
<script>
export default {
  data: () => ({
    title: '首页',
    active: 0
  }),
  methods: {
    onClickLeft() {
      this.$router.go(-1)
    }
  }
}
</script>
<style lang="less" scoped>
</style>

image.png

七、eslint报错处理

  • 一. Too many blank lines at the end of file. Max of 0 allowed. (no-multiple-empty-lines)

问题分析:

解决方案

1.文件末尾的空行太多。最大值为0不允许有多个空行。

  删除多余的空行。

2.安装 ESLint

我用的开发工具vscode,直接安装插件ESLint

  • 二、space-before-function-paren 函数后面需要有空格

  • 三、quotes 使用单引号

  • 四、comma-dangle 对象中结尾不能有,

  • 五、no-multiple-empty-lines 文件末尾的空行太多。最大值为0不允许有多个空行

  • 六、vue/valid-template-root 定义的组件需要有div根组件

  • 七、semi 语句结尾不能有分号;

  • 八、key-spacing 对象中键值对 key后面不能有空格

八、托管项目到git

  • 1.创建仓库

image.png

  • 2.在终端查看状态

image.png

  • 若之前做了修改,需要:
    • git add .
    • git commit -m 'xxx'
    • git status
    • git log(查看刚才所做的操作‘xxx’是否提交了)

image.png

  • 3.连接远程仓库
git remote add origin http
  • http是你的运程仓库地址 需要你在 github 或者 码云上创建
  • 4.提交到主分支上去
git push -u origin master  

image.png

  • 5.回到gitee,刷新页面,可以看到代码

image.png

  • 6.可以创建分支(home分支)

image.png

  • git操作
git 

git init 初始化git本地仓库

git status 查看监听的状态  (可选)

git add .  全部添加到可操作的状态 (当前状态应该为绿色)

git commit -m 'first'  把监听的文件添加到本地仓库       

git log    查看修改日志   (可选)

git remote add origin http  http是你的运程仓库地址  需要你在 github 或者 码云上创建    

git push -u origin master  提交到主分支上   

git checkout -b 分支的名称  创建并切换到新分支   

git branch     查看分支  

git checkout 分支名称   切换分支  

git push -u origin  分支名称

git reset --hard commit_id   代码回退  

合并

git merge 分支名称      要确保你是在master 分支上操作

删除

git branch -d 分支名称 删除本地分支

git branch -D 分支名称 强制删除本地分支

git push origin --delete 分支名称 删除远程分支

重命名

git branch -m 要改的本地分支名 修改后的分支名(修改本地分支)

git push origin :远程修改前的分支名(删除远程分支)

git push origin 修改后的分支名:修改后的分支名(push 到远程分支)

git branch --set-upstream  修改后的分支名 origin/修改后的分支名(绑定远程分支)

代码拉取  

git pull origin master 

强制删除远程分支 

git push origin --delete 分支名称   

出现这个问题是因为gitee中的文件不在本地代码目录中,可以通过如下命令进行代码合并,之后在提交
代码合并 

git merge 分支名称     本地合并   

git push              提交到远程  

git pull --rebase origin master   同步远程和本地的代码  



AppID(小程序ID)	wx2739bc5dddb04a70

每次操作完后记得查看状态   在git add .   和git commit -m ' ' 后再进行下一步操作

九、home组件-轮播图请求渲染

1.引入轮播组件

image.png

2.需要用到axios(下载)

cnpm i axios -S

image.png

3.写请求

1.轮播函数(函数需要在created()函数中调用)

image.png 写完请求后,先打印一下结果,看是否获取到了数据

  • 查看接口(可以在此了解数据的组成,有哪些内容、属性)

image.png

2.在轮播组件上循环

<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
      <van-swipe-item v-for="item in lunbolist" :key="item.id">
        <img :src="item.img" alt="" />
      </van-swipe-item>
    </van-swipe>
  • 可以为轮播图设置样式
<style lang="less" >
// lang 设置使用哪个预编译语言
// scoped 限制当前样式之能在当前组件使用
.home {
  .my-swipe {
    height: 200px;
    // background-color: red;
    img {
      width: 100%;
      height: 100%;
    }
  }
  .van-grid-item__icon {
    font-size: 60px;
  }
}
</style>

Home.vue完整代码

<template>
  <div class="home">
    <van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
      <van-swipe-item v-for="item in lunbolist" :key="item.id">
        <img :src="item.img" alt="" />
      </van-swipe-item>
    </van-swipe>

    <van-grid :column-num="3">
      <van-grid-item
        v-for="grid in grids"
        :key="grid.id"
        :icon="grid.src"
        :text="grid.title"
      />
    </van-grid>
  </div>
</template>
<script>
export default {
  data: () => ({
    lunbolist: [],
    grids: []
  }),
  methods: {
    async getLunbo() {
      const { data: { message } } = await this.$http.getLunbo()
      this.lunbolist = message
    },
    async getGrids() {
      const { data: { message } } = await this.$http.getGrids()
      this.grids = message
    }
  },
  created() {
    this.getLunbo()
    this.getGrids()
  }
}
</script>

<style lang="less" >
// lang 设置使用哪个预编译语言
// scoped 限制当前样式之能在当前组件使用
.home {
  .my-swipe {
    height: 200px;
    // background-color: red;
    img {
      width: 100%;
      height: 100%;
    }
  }
  .van-grid-item__icon {
    font-size: 60px;
  }
}
</style>
  • 如果遇到如下问题: image.png
  • 解决: 删除node_modules包,再在终端执行:npm i
  • 引入axios
import axios from 'axios'

若没有引入则会报如下错误: image.png

十、home组件-grid请求渲染

1.设置好接口

<script>
export default {
  data: () => ({
    lunbolist: [],
    grids: []
  }),
  methods: {
    async getLunbo() {
      const { data: { message } } = await this.$http.getLunbo()
      this.lunbolist = message
    },
    async getGrids() {
      const { data: { message } } = await this.$http.getGrids()
      this.grids = message
    }
  },
  created() {
    this.getLunbo()
    this.getGrids()
  }
}
</script>

2.引入组件

<van-grid :column-num="3">
      <van-grid-item
        v-for="grid in grids"
        :key="grid.id"
        :icon="grid.src"
        :text="grid.title"
      />
    </van-grid>
  • 引入组件
import Vue from 'vue'
import { Button, Tabbar, TabbarItem, NavBar, Swipe, SwipeItem, Toast, Grid, GridItem } from 'vant'

Vue.use(Button)
Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.use(NavBar)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Grid)
Vue.use(GridItem)

Vue.prototype.$Toast = Toast

十一、优化axios请求 单独抽离接口模块

  • ./api/index.js
// import Vue from 'vue'
// import axios from 'axios'

// axios.defaults.baseURL = 'http://itfly.vip:8888'

// Vue.prototype.$http = axios

import Vue from 'vue'
import axios from 'axios'
import { Toast } from 'vant'

// axios.defaults.baseURL = 'http://itfly.vip:8888'
const http = axios.create({
  baseURL: 'http://itfly.vip:8888'
})

// 响应拦截
http.interceptors.response.use(response => {
  // Toast('succss')
  return response
}, err => {
  Toast(err)
})

//
Vue.prototype.$http = {
  async getLunbo() {
    return await http.get('/api/getlunbo')
  },
  async getGrids() {
    return await http.get('/api/grids')
  }
}

十二、newlist组件-路由跳转-页面请求渲染

image.png

  • newlist.vue
<template>
  <div class="news-list">
    <van-card
      v-for="item in newslist"
      :key="item.id"
      :title="item.title"
      :thumb="item.img_url"
      @click="goDatil(item.id)"
    >
      <template #price>
        <div>
          {{ item.add_time }}
        </div>
      </template>
      <template #num>
        <div>点击{{ item.click }}次</div>
      </template>
    </van-card>
  </div>
</template>
<script>
export default {
  data: () => ({
    newslist: []
  }),
  methods: {
    async getNewsList() {
      const { data: { message } } = await this.$http.getNewsList()
      this.newslist = message
      console.log(this.newslist)
    },
    goDatil(id) {
      // 跳转的同时传递id
      this.$router.push('/home/newsinfo/' + id)
    }
  },
  created() {
    this.getNewsList()
  }
}
</script>
<style lang="less">
.news-list {
  .van-card__thumb {
    height: 55px;
  }
  .van-card__content {
    min-height: 55px;
  }
  .van-card__price {
    color: red;
  }
  .van-card__title {
    // 用于省略
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 1;
    overflow: hidden;
  }
}
</style>

  • router/index.js
import Newslist from '../views/Home/news/Newslist'
{
    path: '/home/newslist',
    component: Newslist
  }
  • 在vant.js中引入需要的组件

  • api/index.js

async getNewsList() {
    return await http.get('/api/getnewslist')
  }

十三、newsinfo组件

  • 在newslist.vue点击跳转到newsinfo
van-card
      v-for="item in newslist"
      :key="item.id"
      :title="item.title"
      :thumb="item.img_url"
      @click="goDatil(item.id)"//跳转到newsinfo
    >
    
    。。。。
    
    methods: {
    async getNewsList() {
      const { data: { message } } = await this.$http.getNewsList()
      this.newslist = message
      console.log(this.newslist)
    },
    goDatil(id) {
      // 跳转的同时传递id
      this.$router.push('/home/newsinfo/' + id)
    }
  }

1.newsinfo头部请求渲染

  • api/index.js
 async getNewsInfo(id) {
    return await http.get('/api/getnew/' + id)
  }
  • router/index.js
import Newsinfo from '../views/Home/news/Newsinfo'



{
    path: '/home/newsinfo/:id',
    component: Newsinfo,
    props: true
  }
  • newsinfo.vue
<template>
  <div class="news-info">
    <van-panel
      :title="newsinfo.zhaiyao"
      :status="'阅读' + newsinfo.click + '次'"
    >
      <div class="content">{{ newsinfo.content }}</div>
    </van-panel>
    <Comment :id="id"></Comment>
  </div>
</template>
<script>
// @相当于src
import Comment from '@/components/Comment.vue'
// import Comment from '../../../../components/Comment.vue'
export default {
  components: { Comment },
  props: ['id'],
  data: () => ({
    newsinfo: {}
  }),
  methods: {
    async getNewsInfo() {
      const res = await this.$http.getNewsInfo(this.id)
      this.newsinfo = res.data.message
    }
  },
  created() {
    // 接受id
    // this.id = this.$route.params.id
    this.getNewsInfo()
  },
  conponents: {
    Comment
  }
}
</script>
<style lang="less" scoped>
.news-info {
  .van-panel__header {
    font-size: 12px;
    .van-cell__title {
      flex: 2;
      font-size: 16px;
      font-weight: 600;
      color: red;
    }
  }
  .content {
    text-align: center;
    text-indent: 2em;
  }
}
</style>

2.comment组件-页面布局

  • 评论以下的归为一个组件
  • 新建一个文件夹:conponents,下面有一个Comments.js组件
  • 在newsinfo.vue中引入Comments.js,并注册、渲染
// 引入Comments.js
import Comment from '@/components/Comment.js'


// 注册
conponents: {
    Comment
  }
  
  
  
  
// 渲染
<Comment></Comment>
  • api/index.js
/**
   *
   * @param {number} id 评论id
   * @param {number} pageNo 页码
   * @param {number} pageSize 每页的总条数
   * @returns Promise
   */
  async getComments({ id, pageNo, pageSize }) {
    return await http.get(`/api/getComments/${id}?pageindex=${pageNo}&limit=${pageSize}`)
  }
  • comments.js
<template>
  <div class="comment">
    <van-field
      class="my-filed"
      v-model="message"
      rows="2"
      label="留言"
      type="textarea"
      maxlength="50"
      :placeholder="placeholder"
      show-word-limit
    />
    <van-button class="my-post" type="primary" block @click="postComments"
      >发表评论</van-button
    >
    <van-card
      v-for="(comment, index) in comments"
      :key="index"
      :desc="comment.content"
    >
      <template #thumb>
        <div class="box">
          <van-icon
            name="https://b.yzcdn.cn/vant/icon-demo-1126.png"
            class="name"
          />
          <div>{{ comment.user_name }}</div>
        </div>
      </template>
      <template #title>
        <van-rate v-model="comment.rate" readonly />
      </template>
      <template #price> {{ comment.add_time | datefmt }} </template>
    </van-card>
    <van-button type="warning" block @click="goMore">{{ moreText }}</van-button>
  </div>
</template>
<script>
export default {
  props: ['id'],
  data: () => ({
    message: '',
    placeholder: '请输入bb的内容',
    pageNo: 2,
    pageSize: 4,
    comments: [],
    hasMore: false
  }),
  methods: {
    async getComments() {
      // 节流
      if (this.hasMore !== false) return
      ++this.pageNo
      const res = await this.$http.getComments({ id: this.id, pageNo: this.pageNo, pageSize: this.pageSize })
      // 上一页拼接下一页的数据
      this.comments = this.comments.concat(res.data.message)
      // 计算是否已经加载完毕
      this.hasMore = this.pageNo * this.pageSize > res.data.count
    },
    async postComments() {
      await this.$http.postComments({ id: this.id, content: this.message })
      this.comments.unshift({
        add_time: new Date().getTime(),
        content: this.message,
        rate: 5,
        user_name: 'Svip'
      })
      this.message = ''
    },
    goMore() {
      this.getComments()
    }
  },
  created() {
    this.getComments()
  },
  computed: {
    moreText() {
      return this.hasMore ? '被掏空了' : '加载更多'
    }
  }
}
</script>
<style lang="less" scoped>
.comment {
  margin-top: 10px;
  .my-field {
    border: 1px solid #ddd;
  }
  .my-post {
    margin-top: 10px;
  }
  .box {
    .name {
      font-size: 40px;
    }
  }
  .van-card__thumb {
    height: 55px;
  }
  .van-card__content {
    min-height: 55px;
  }
}
</style>

  • 在newsinfo.vue传递id
<Comment :id="id"></Comment>
  • 提交评论
    • api/index.js
    /**
    *
    * @param {number} id 评论id
    * @param {String} content 评论内容
    * @returns Promise
    */
    async postComments({ id, content }) {
    return await http.post(`/api/postcomment/${id}`, { content })
    }
    
  • 格式化时间
    • 1.创建文件夹filters,下有datefmt.js
    • 2.yarn add moment
    • 3.在main.js引入该文件:import from './filters/datefmt'
    • 4.对想要格式化的时间进行格式化:{{comment.add_time|datefmt}}
    • datefmt.js
    import Vue from 'vue'
    import moment from 'moment'
    
    Vue.filter('datefmt', arg => {
         return moment(arg).format('YYYY-MM-DD HH:mm:ss')
    })
    
    • main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import './plugins/vant.js'
    import './api'
    import './filter/datefmt'
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app')
    
  • 设置评分为只读
<template #title>
      <van-rate v-model="comment.rate" readonly />
    </template>