ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

从0到1 前后分离项目开发实战

2021-12-20 22:31:37  阅读:159  来源: 互联网

标签:实战 前后 name err vue 分离 Book type id


前后端分离项目开发

文章目录

前言

前后端分离就是一个应用的前端代码和后端代码分开写,为什么这么做?

如果不使用前后端分离的方式,会有那些问题?

传统java web开发中,前端使用JSP开发,后端使用servlet。

前端 — 》HTML 静态文件 --》 后端 – 》 JSP

这种开发方式效率极低,可以使用前后端分离的方式进行开发,就可以完

美解决这一问题。

前端只需要独立编写客户端代码,后端也只需要独立编写服务端代码提供

数据接口即可。前端通过Ajax请求来访问后端的数据接口,将Model展示

到View中即可。

前后端开发者只需要提前约定好接口文档(URL、参数、数据类型…),然

后分别独立开发即可,前端可以造假数据进行测试,完全不需要依赖于后

端。完全不依赖于后端,前后端相互独立。前后端应用解耦,极大提升了开发效率。

单体 – 》 前端应用 + 后端应用

前端应用 : 负责数据展示和用户交互

后端应用: 负责提供数据处理接口。

前端HTML --》 Ajax --》 Restful 后端开发

在这里插入图片描述

技术栈

前端

1.vue.js
2.axios
3.element-ui

后端

1.springboot
2.mybatis-plus

数据库

mysql

安装 前端环境

node.js 查看是否安装成功

node -v

没有就得安装

安装淘宝镜像

npm install -g cnpm --registry=https://registry.npm.taobao.org

npm 查看是否安装成功

npm -v

安装vue vue-cli 脚手架

cnpm install vue-cli  或者安装最新版 cnpm i -g @vue/cli

查看 vue-cli是否安装成功

vue --version

可能出现的问题是
’Set-ExecutionPolicy’ 不是内部或外部命令,也不是可运行的程序

解决办法

用管理员的身份打开 power shell,执行以下命令:

set-ExecutionPolicy RemoteSigned

get-ExecutionPolicy

创建vue项目

vue ui

在这里插入图片描述

可能出现的问题

vue ui 命令弹出空白页面

解决办法

在Chrome浏览器的设置中允许所有cookie

在这里插入图片描述

解决后执行命令

vue ui

出现的页面是如下所示:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用vscode打开创建好的vue项目;

在这里插入图片描述

1)Book.vue

src/views 下创建 Book.vue

 <template>
     <div>
         <table>
             <tr>
                 <td>编号</td>
                 <td>书名</td>
                 <td>作者</td>
             </tr>
             <tr>
                 {{msg}}
             </tr>
         </table>
     </div>
 </template>

 <script>
     export default {
         name: "Book",
         data(){
             return{
                 msg: 'Hello'
             }
         }
     }
 </script>

 <style scoped>

 </style>

2)index.js配置

打开 src/router 下的 index.js

index.js

  • 导入Book.vue

import Book from “…/views/Book”

  • 配置(一定要在前带 逗号(,)

,{ path: ‘/book’, component: Book }

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Book from '../views/Book.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/book',
    name: 'Book',
    component: Book
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

3)测试

Terminal 中输入 npm run serve

访问

在这里插入图片描述

显示数据

Book.vue

  • 遍历

    books:被遍历的数组 ,item:每次遍历的对象

    < tr v-for="item in books ">
       < td >{{item.id}}< /td >
       < td >{{item.name}}< /td >
       < td >{{item.author}}< /td >
      < /tr >

  • books中添加数据

    books: [ { id: 1, name: ‘Java’, author: ‘哈哈’ }, { id: 2, name: ‘C++’, author: ‘啦啦’ }, { id: 3, name: ‘Python’, author: ‘嘿嘿’ } ]

<template>
     <div>
         <table>
             <tr>
                 <td>编号</td>
                 <td>书名</td>
                 <td>作者</td>
             </tr>
             <!--books:被遍历的数组 ,item:每次遍历的对象-->
             <tr v-for="item in books">
                 <td>{{item.id}}</td>
                 <td>{{item.name}}</td>
                 <td>{{item.author}}</td>
             </tr>
         </table>
     </div>
 </template>
 <script>
     export default {
         name: "Book",
         data() {
             return {
                 msg: 'Hello',
                 books: [
                     {
                         id: 1,
                         name: 'Java',
                         author: '哈哈'
                     },
                     {
                         id: 2,
                         name: 'C++',
                         author: '啦啦'
                     },
                     {
                         id: 3,
                         name: 'Python',
                         author: '嘿嘿'
                     }
                 ]
             }
         }

     }
 </script>

 <style scoped>

 </style>

在这里插入图片描述

永远的CRUD

从上面的demo可以看出,数据是在前端页面构造的,而不是数据库获取的数据。因此,需要前端发送ajax请求得到数据.

安装axios

vue add axios

查看是否安装成功
在这里插入图片描述

安装element-ui

1. 在项目下 输入 npm install element-ui -S

2.查看配置文件package.json,是否有element-ui组件的版本号 如下图

在这里插入图片描述
4.在main.js文件中 引入 element 组件 :

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)

在这里插入图片描述

5.使用elementui组件开发Student.vue页面

element-ui

demo展示
在这里插入图片描述

6.此时使用axios发送http请求访问后台页面数据会因为浏览器的同源策略造成跨域问题

解决办法

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET","HEAD","POST","DELETE","OPTIONS","PUT")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
}

开发步骤

(1)创建数据库 test 和表 student

CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `student` */

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'ID自增',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `gender` tinyint(1) DEFAULT NULL COMMENT '性别 0女1男',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `headImageFilePath` varchar(100) DEFAULT NULL COMMENT '头像路径 学生头像相对路径',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8mb4;

/*Data for the table `student` */

insert  into `student`(`id`,`name`,`gender`,`birthday`,`headImageFilePath`) values 

(3,'wang',0,'2002-03-21','https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'),

(25,'cs',1,'2021-12-15','https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'),

(49,'15',0,'2021-12-14','');

(2)构建springboot项目 并按照mvc模式开发好 curd接口,使用postman发送请求保证每个接口正常执行

在这里插入图片描述

项目结构

在这里插入图片描述

(3)前端页面对应操作放置 对应的后端接口请求。

for example

后端对应

    @RequestMapping("/selectAll")
    public List<Student> selectAll(){
        return studentService.selectAll();
    }

由前台页面发送查询请求

            loadData(){
                const _this = this;
                axios.get('http://127.0.0.1:8081/selectAll').then(function(resp){
                    _this.students = resp.data;
                }).catch((err) =>{
                    console.log(err);
                });   
            },

源码 :页面Student.vue

<template>
    <div>
        <el-dialog title="学生信息" :visible.sync="dialogFormVisible">
            <el-form :model="form">
                <el-form-item label="名称" :label-width="formLabelWidth">
                    <el-input v-model="form.name" autocomplete="off"></el-input>
                </el-form-item>
                <el-form-item label="性别" :label-width="formLabelWidth">
                    <el-select v-model="form.gender" placeholder="请选择性别">
                        <!-- 通过循环的形式展示出下拉菜单 key必须添加,否则可能会出错,相当于唯一性标识 -->
                        <el-option v-for="sextype in sexType"
                            :key="sextype.type"
                            :label="sextype.name"
                            :value="sextype.type">
                        </el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="生日" :label-width="formLabelWidth">
                    <el-date-picker type="date" placeholder="日期" value-format="yyyy-MM-dd" v-model="form.birthday"></el-date-picker>
                </el-form-item>
                <el-form-item label="头像" :label-width="formLabelWidth">
                    <!-- <el-input v-model="form.headImageFilePath"></el-input>          -->
                    <!-- 文件上传 -->
                    <single-upload v-model="form.headImageFilePath"></single-upload>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="cancel">取 消</el-button>
                <el-button type="primary" @click="update">确 定</el-button>
            </div>
        </el-dialog>

        <el-form :model="queryParam" ref="form" label-width="100px" class="demo-ruleForm" size="mini">
            <el-row>
                <el-col :span="8">
                    <el-form-item label="姓名">
                        <el-input v-model="queryParam.name"></el-input>
                    </el-form-item>
                </el-col>
                <el-col :span="8">
                    <el-form-item label="性别">
                        <el-select v-model="queryParam.gender" placeholder="性别">
                        <el-option label="男" value="1"></el-option>
                        <el-option label="女" value="0"></el-option>
                        </el-select>
                    </el-form-item> 
                </el-col>
                <el-col :span="8">
                    <el-form-item>
                        <el-button type="primary" @click="add">新增</el-button>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="16">
                    <el-form-item label="生日">
                        <el-date-picker type="date" placeholder="开始日期" value-format="yyyy-MM-dd" v-model="queryParam.startDate"></el-date-picker>
                        --
                        <el-date-picker type="date" placeholder="结束日期" value-format="yyyy-MM-dd" v-model="queryParam.endDate" ></el-date-picker>
                    </el-form-item>
                </el-col>
                <el-col :span="8">
                    <el-form-item>
                        <el-button type="primary" @click="searchQuery" icon="el-icon-search">查询</el-button>
                        <el-button type="warning" @click="resetForm" icon="el-icon-search" plain>重置</el-button>
                    </el-form-item>
                </el-col>                
            </el-row>            
        </el-form>                         
        <el-table
            :data="students"
            style="width: 100%">
            <el-table-column
                prop="id"
                label="ID"
                v-if="false"
                width="180">
            </el-table-column>
            <el-table-column
                prop="headImageFilePath"
                label="头像"
                width="180">
                <template slot-scope="scope">            
                    <img :src="scope.row.headImageFilePath"  min-width="70" height="70" />
                </template>                               
            </el-table-column>
            <el-table-column
                prop="name"
                label="姓名"
                width="180">
            </el-table-column>
                           
            <el-table-column
                prop="gender"
                label="性别"
                width="180">
                <template slot-scope="scope">
                    <!-- 通过 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据
                        用|分割,前面获取传给后面,然后通过filters获取 -->
                    <span>{{scope.row.gender | sexTypeFilter}}</span>
                </template> 
            </el-table-column>           
            <el-table-column
                prop="birthday"
                value-format="yyyy-MM-dd"
                label="生日">
            </el-table-column>
            <el-table-column label="操作">
                <template slot-scope="scope">
                    <el-button
                    size="mini"
                    @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
                    <el-button
                    size="mini"
                    type="danger"
                    @click="handleDelete(scope.$index, scope.row)">删除</el-button>
                </template>
            </el-table-column>
        </el-table>        
    </div>
</template>

<script>
// @ is an alias to /src
import SingleUpload from '@/components/upload/singleUpload.vue'
    // 过滤器的数据写在data外面,因为过滤器不能调用this.
    const sexType = [
       { "type":1, "name":'男'},
       { "type":0, "name":'女'}
    ]
    export default {       
        name: "Student",
        components:{
            SingleUpload
        },     
        data(){
            return {
                sexType,
                input: '',
                students: [

                ],
                fits: ['fill'],
                url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
                imageUrl: '',
                dialogFormVisible: false,
                formLabelWidth: '80px',
                flag: '', // 因为编辑和新增共用一个弹框元素,因此用flag加以区分
                form: {
                    name: '',
                    gender: '',
                    birthday:'',
                    headImageFilePath:''
                },
                queryParam: {
                    name: '',
                    gender: '',
                    birthday:'',
                    starDate: '',
                    endDate: ''
                }                                
            }
        },
        //过滤器,数字类型和汉字的转换
        //两种写法都可以,type是传入的数字,与过滤器数据对比
        // sexType.find(obj=>{
        // return obj.type === type})
        filters:{
            sexTypeFilter(type){
                const sexTy = sexType.find(obj => obj.type === type)
                return sexTy ? sexTy.name : null
            },
        },                      
        created(){
            // this.loadData();
            this.searchQuery();  
        },
        methods: {
            loadData(){
                const _this = this;
                axios.get('http://127.0.0.1:8081/selectAll').then(function(resp){
                    _this.students = resp.data;
                }).catch((err) =>{
                    console.log(err);
                });   
            },
            add(){
                this.flag = 1;
                this.form = {
                    name: '',
                    gender: '',
                    birthday:'',
                    headImageFilePath:''
                },
                //   设置点击按钮之后进行显示对话框
                this.dialogFormVisible = true;
            },
            cancel(){
                this.dialogFormVisible = false;
            },
            update(){
                if(this.flag == 1){
                    // 将我们添加的信息提交到总数据里面
                    this.students.push(this.form);
                    this.save();
                }else{
                    this.updateOne();
                }
                this.dialogFormVisible = false;
            },
            save(){
                axios.post('http://127.0.0.1:8081/add',this.form).then((resp) =>{
                    
                }).catch((err) =>{
                    console.log(err);
                });
            },
            updateOne(){
                var s = this.form;
                axios.post('http://127.0.0.1:8081/update',this.form).then((resp) =>{

                }).catch((err) =>{
                    console.log(err);
                });                
            },
            searchQuery(){
                var that = this;
                axios.post('http://127.0.0.1:8081/selectAllbyCondition',this.queryParam).then((resp) =>{
                     that.students = resp.data;
                }).catch((err) =>{
                    console.log(err);
                });
            },
            resetForm(){
                this.flag = '';
                this.queryParam = {};
            },            
            handleEdit(index, row) {
                this.flag = 2;
                // 将数据的index传递过来用于实现数据的回显
                this.form = this.students[index];
                // 设置对话框的可见
                this.dialogFormVisible = true;

            },
            handleDelete(index, row) {
                var id = row.id;   
                axios.delete('http://127.0.0.1:8081/delete'+'/'+id)
                .then(() => {
                    this.students.splice(index, 1);
                    this.$message({
                        type: "success",
                        message: "删除成功!"
                    }); 
                }).catch((err) =>{
                    console.log(err);
                });
            }          
        },
    }
</script>

<style>

</style>

这里面有很多细节,需要读者实操体会

项目结构

在这里插入图片描述
单文件上传 singleUpload.vue

<template>
  <el-upload
  class="avatar-uploader"
  action="https://jsonplaceholder.typicode.com/posts/"
  :show-file-list="false"
  :on-success="handleAvatarSuccess"
  :before-upload="beforeAvatarUpload"
>
  <img v-if="imageUrl" :src="imageUrl" class="avatar" />
  <i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</template>

<script>
  export default {
    data() {
      return {
        imageUrl: '',
        file:'',
        headers: {
            'Content-Type': 'multipart/form-data', // 默认值
        },
        // 图片上传参数
        // actionUrl: this.$axios.defaults.baseURL + '/file/uploadImage',

      }
    },
    methods: {
      handleAvatarSuccess(res, file) {
        debugger;
        this.imageUrl = URL.createObjectURL(file.raw)
      },
      beforeAvatarUpload(file) {
        debugger;
        var s = file;

        const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg'
        const isLt2M = file.size / 1024 / 1024 < 2

        if (!isJPG) {
          this.$message.error('上传头像图片只能是 JPG 格式!')
        }
        if (!isLt2M) {
          this.$message.error('上传头像图片大小不能超过 2MB!')
        }
        return isJPG && isLt2M
      },
    },
  }
</script>
<style>
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409eff;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
</style>

Show

页面加载
在这里插入图片描述

编辑
在这里插入图片描述
添加
在这里插入图片描述

条件查询
在这里插入图片描述
删除看不到效果就不展示了。

标签:实战,前后,name,err,vue,分离,Book,type,id
来源: https://blog.csdn.net/Austin_/article/details/122050045

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有