我是靠谱客的博主 正直摩托,最近开发中收集的这篇文章主要介绍PHP_Vue前后端分离项目(6),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

第0章 项目如何开始的

0.1 总体流程

需求调研-->需求转为需求文档-->将需求文档转为开发文档-->前端文档-->后台文档-->项目测试-->打包上线

0.2 数据服务器构建

0.2.1 技术栈

Vue+elementUI+NodeJS+MySQL

0.2.2 数据服务器准备

导入数据库数据:打开数据库服务器,新建名为 itcast 的库;

后台为我们提供了 /api-server/db/mydb.sql 数据文件,打开复制 sql 语句直接运行即可;

然后在 api-server 中执行 npm install 安装服务器所需扩展模块;

node app.js 将服务器启动起来;

0.3 接口测试

0.3.1 登录

后台已经写好接口文档,根据文档中的表述,我们测试登录接口:

0.3.2 获取用户信息

请求用户列表数据;但是,并没有返回相应的数据;

 

 

 

0.4 Vue项目初始化

使用 vue-cli 工具初始化项目:

 

初始化成功,使用 npm run dev 启动项目;

0.5 项目预览

解压 my-project(Vue项目).rar 后进入目录,使用 npm run dev 启动项目;

 

第1章 开始项目

1.1 添加用户登录路由组件

添加路由:myapp-code/src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/login/login'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path:'/login',
      name:'Login',
      component:Login
    }
  ]
})

添加组件:`myapp-code/src/components/login/login.vue`

<template>
  <div>{{msg}}</div>
</template>

<script>
  export default{
    data(){
      return {msg:'我是登录页面'}
    }
  }
</script>
<style>
</style>

修改Vue 项目运行端口: myapp-code/config/index.js

1.2 使用 ElementUI

http://element-cn.eleme.io/#/zh-CN

修改 src/main.js 代码,全局引入 ElementUI ; 

import Vue from 'vue'
import App from './App'
import router from './router'

// 引入 ElementUI 
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
// 将 ElementUI 注册为 vue的全局组件
Vue.use(ElementUI);


Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

在我们登录页面中尝试一下:` src/components/login/login.vue`

<template>
  <div>
    <el-button type="success">成功按钮</el-button>
    <el-button type="info">信息按钮</el-button>
    <el-button type="warning">警告按钮</el-button>
    <el-button type="danger">危险按钮</el-button>
  </div>
</template>

1.3 搭建登录页面

把公共样式写到 src/assets/css/style.cssForm 表单

html,
body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
}

然后在 src/main.js 加载公共样式:

// 代码略...

// 引入我们的公共样式
import './assets/css/style.css'

// 代码略...

为了让登陆组件的背景色撑满,所以我们需要让他们的父盒子 div#app 高度设置为 100%

所以我们在 src/App.vue

<style>
#app {
  height: 100%;
}
</style>

接下来我们开始调整 src/components/login/login.vue 组件样式:

  • 注意:这里遵循一个原则,不要直接去使用 Element 组件自带的类名

  • 如果你想为 Element 组件添加自定义样式,那么建议你给它加你自己的类名来控制

<template>
<div class="login-wrap">
  <el-form ref="form" :label-position="labelPosition" :model="form" label-width="80px" class="login-from" >
    <h2>用户登录</h2>
    <el-form-item  label="用户名">
    <el-input v-model="form.name"></el-input>
  </el-form-item>
  <el-form-item label-position="top" label="密码">
    <el-input v-model="form.pwd" ></el-input>
  </el-form-item>
  <el-form-item>
    <el-button type="primary" @click="onSubmit" class="login-btn" >登录</el-button>
  </el-form-item>
  </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      labelPosition:'top',
      form: {
        name: "",
        pwd: ""
      }
    };
  },
  methods:{
    onSubmit(){}
  }
};
</script>
<style>
.login-wrap {
  background-color: #324152;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.login-wrap .login-from {
  background-color: #fff;
  width: 400px;
  padding: 30px;
  border-radius: 5px;
}

.login-wrap .login-from .login-btn {
  width: 100%;
}
</style>

1.4 完成登录功能

1.4.1 封装axios

vue 插件语法: https://cn.vuejs.org/v2/guide/plugins.html

Axios : https://www.kancloud.cn/yunye/axios/234845

npm install axios ,将 axios 进行模块化封装,以 Vue 插件的方式,全局引入:

将插件的封装写入 src/assets/js/myaxios.js

// 引入axios
import Axios from 'axios';
// 自定义插件对象
var myaxios = {};
myaxios.install = function(vue){
  // 设置axios请求的URL,此后axios发送的请求全部执行本地址
  var axios_obj = Axios.create({
    baseURL:'http://localhost:8888/api/private/v1/'
  })
  // 将设置好的axios对象赋值给Vue实例的原型
  // 之后可以在Vue中直接只用 this.$myHttp 使用axios发送请求
  vue.prototype.$myHttp = axios_obj;
}
// 将插件以 模块 方式导出
export default myaxios;

在 main.js 引入axios插件,并注册为全局插件

// 导入 myaxios 模块
import myaxios from '@/assets/js/myaxios.js'
Vue.use(myaxios) // 注册使用 axios 插件

1.4.2 完成登录功能

发送post请求

export default {
  data() {
    return {
      labelPosition:'top',
      form: {
        username: "",
        password: ""
      }
    };
  },
  methods:{
    // 修改组件中绑定的按钮名称为 onLogin 
    onLogin(){
      // 使用axios 发送post 请求,传入data中的form数据
      this.$myHttp.post('login',this.form)
      .then(backdata=>{ // 异步执行成功后
        console.log(backdata);
      });
    }
  }
};

继续修改代码,完成登录逻辑

vue-router编程式导航: https://router.vuejs.org/zh/guide/essentials/navigation.html

onLogin(){
    // 使用axios 发送post 请求,传入data中的form数据
    this.$myHttp.post('login',this.form)
        .then(backdata=>{ // 异步执行成功后
        //console.log(backdata.data);
        // 结构赋值,获取返回的数据
        var {data,meta}  = backdata.data;
        // 判断数据状态
        if(meta.status == 200){
            alert('登录成功');
            // 使用vue-router编程式导航跳转到home
            this.$router.push('home');
        }
    });
}

修改提示弹窗

var { data, meta } = backdata.data;
// 判断数据状态
if (meta.status == 200) {
    this.$message({
        message: "恭喜你,登录成功",
        type: "success"
    });
    // 使用vue-router编程式导航跳转到home
    this.$router.push("home");
}else{
    this.$message.error('错了哦');
}

1.4.3 表单验证

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。

data() {
    return {
      labelPosition: "top",
      form: {
        username: "",
        password: ""
      },
      // 与 el-form 中的 :rules="rules"  对应
      rules: {
        //与 el-form-item 中的 prop="username" 对应
        username: [
          // 验证规则  是否必须        提示信息            触发时机
          { required: true, message: "请输入用户名", trigger: "blur" }
        ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
        ]
      }
    };
  },

1.4.4 阻止数据提交

onLogin() {
    // 因为要获取form表单的节点对象,
    // 所以 el-form 中要加入 ref="ruleForm" 
    this.$refs.ruleForm.validate(valid => {
        // elementUI 会将 validate 方法加入到节点对象,
        // 在提交是,如果表单的验证未通过,会将错误信息传入回调函数
        if (!valid) {
            // 如果有表单错误信息,则无反应
            this.$message.error("输入有误");
            return;
        }
        // 使用axios 发送post 请求,传入data中的form数据
        this.$myHttp.post("login", this.form).then(backdata => {
            // 结构赋值,获取返回的数据
            var { data, meta } = backdata.data;
            // 判断数据状态
            if (meta.status == 200) {
                this.$message({
                    message: "恭喜你,登录成功",
                    type: "success"
                });
                // 使用vue-router编程式导航跳转到home
                this.$router.push("home");
            } else {
                this.$message.error("错了哦");
            }
        });
    });
}

1.5 首页

1.5.1 添加路由及页面布局

修改登录成功后逻辑,使用路由名称表示进行跳转:

// 使用vue-router编程式导航跳转到home
this.$router.push({name:"home"});

导入组件,添加路由 `src/router/index.js`

import Home from '@/components/home/home'
 ……
{
    path:'/',
    name:'home',
    component:Home
}

添加一个home组件`src/components/home/home.vue`

<template>
  <div>{{msg}}</div>
</template>

<script>
export default {
  data(){
    return{
      msg:'we'
    }
  }
}
</script>

修改一个home组件src/components/home/home.vue ` Container 布局容器`

<template>
<el-container class="height100">
    <el-header>Header</el-header>
    <el-container>
        <el-aside width="200px">Aside</el-aside>
        <el-main>Main</el-main>
    </el-container>
</el-container>
</template>

.height100{
    height: 100%;
}
.el-header {
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
}
.el-aside {
    background-color: #D3DCE6;
    color: #333;
    text-align: center;
    line-height: 200px;
}
.el-main {
    background-color: #E9EEF3;
    color: #333;
    text-align: center;
    line-height: 160px;
}

1.5.2 头部样式

/src/components/home/home.vue

Layout 布局

<el-header>
    <el-row>
        <el-col :span="6">
            <div class="grid-content bg-purple">
                <img src="/static/logo.png" alt=""> 
            </div>
        </el-col>
        <el-col :span="12"><div class="grid-content bg-purple-light">电商后台管理系统</div></el-col>
        <el-col :span="6"><div class="grid-content bg-purple"><el-button type="warning">退出</el-button></div></el-col>
    </el-row>
</el-header>


……
// 标题文本样式
.bg-purple-light {
  font-size: 25px;
  color: white;
}

1.5.3 左侧样式

/src/components/home/home.vue

NavMenu 导航菜单 Icon 图标

 <el-container>
    <el-aside width="200px">
      <!-- 
        el-menu 侧边导航栏组件
			unique-opened="true" 只保持一个导航开启
            router="true" 开启导航路由
        el-submenu 导航栏的顶级项
        template 导航栏中需要展示的内容
            i 图标
            span 文字
        el-menu-item-group 次级导航组 内容与导航的组标识 可直接删除
        el-menu-item  导航栏选项

          index属性 控制收起展开+路由标识:
              在el-menu中加入router=“true”属性;
              index="1-1" 点击时路由跳转到1-1 ;
      -->
      <el-menu 
        :unique-opened="true"
        :router="true" 
        default-active="2"
        class="el-menu-vertical-demo"
        @open="handleOpen"
        @close="handleClose">
        <el-submenu index="1" >
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>用户管理</span>
          </template>
          <el-menu-item index="1-1">
            <i class="el-icon-menu"></i>
            用户列表
          </el-menu-item>
        </el-submenu>

        <el-submenu index="2" >
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>权限管理</span>
          </template>
          <el-menu-item index="2-1">
            <i class="el-icon-menu"></i>
            角色列表
          </el-menu-item>
          <el-menu-item index="2-2">
            <i class="el-icon-menu"></i>
            权限列表
          </el-menu-item>
        </el-submenu>
        
      </el-menu>
    </el-aside>
    <el-main>Main</el-main>
  </el-container>

我们发现,大部分组件,在浏览器渲染后,都会在标签内部自动添加一个标签名为名字的 class 属性 ,我们可以利用这个属性,设置样式:

.el-menu{
  width: 200px;
  height: 100%
}

.el-submenu{
    text-align: left;
}

1.5.4 右侧内容

添加组件内容 /src/components/home/home.vue

<el-main>
    <!-- 路由组件 -->
    <router-view></router-view>
</el-main>

添加组件:`src/components/index.vue`

<template>
  <p>我是首页内容</p>
</template>

<script>
export default {

}
</script>

<style>

</style>

添加路由:`/src/router/index.js`

{
    path:'/index',
    name:'index',
    component:Index
}

注意: 我们希望index.vue 组件的内容,展示到home组件的 <router-view></router-view>中

知识补充:

此时,我们需要借助嵌套路由: https://router.vuejs.org/zh/guide/essentials/nested-routes.html

嵌套路由(子路由的基本用法):

<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<div id="app">
    <router-view></router-view>
</div>
<script>
    // 1:定义路由组件
    var login = {
        template: `
            <div>
                <h2>我是登录页面</h2>
                <li><router-link to="/zi">子路由</router-link></li>
                <!--路由组件中继续使用路由组件-->
                <router-view></router-view>
            </div>
            `
    }
    var zi = {
        template: '<h4>我是嵌套路由的组件</h4>'
    }
    // 2:获取路由对象
    var router = new VueRouter({
        // 定义路由规则
        routes: [
            {
                name: 'login',
                path: "/login",
                component: login,
                // 路由中的 children 属性,定义嵌套路由(子路由)
                children: [
                    { name: 'zi', path: "/zi", component: zi }
                ]
            },
        ]
    })

    var app = new Vue({
        el: '#app',
        router
    })
</script>

*再谈 代码加载流程 *

(main.js->template: '<App/>')替换 (index.html->div#app);

(index.html-><App/>) --> (components: { App })

( components: { App }) --> (import App from './App' -> src/App.vue)

(App.vue -> <router-view/> -> 路由组件) --> (main.js-> router)
========此项决定了页面展示那个组件内容 ========

({path: '/',name: 'HelloWorld', component: HelloWorld }) --> (import HelloWorld from '@/components/HelloWorld')

(src/components/HelloWorld.vue) --> <router-view/>

因此,我们需要让 index 成为 home 的子路由组件 `src/router/index.js`

  routes: [
    {
      path:'/login',
      name:'Login',
      component:Login
    },
    {
      path:'/',
      name:'home',
      component:Home,
      // 添加子路由
      children:[
        {path:'index',name:'index',component:Index}
      ]
    },
  ]

登录完成后,跳转到 home-->index `src/components/login/login.vue`

if (meta.status == 200) {
    this.$message({
        message: "恭喜你,登录成功",
        type: "success"
    });
    // 使用vue-router编程式导航跳转到home->index
    this.$router.push({name:"index"});
}

1.6 验证首页登录

src/components/login/login.vue

if (meta.status == 200) {
    this.$message({
        message: "恭喜你,登录成功",
        type: "success"
    });

    // 登录成功后,将token信息保存到 localStorage 
    window.localStorage.setItem('token',data.token);

    // 使用vue-router编程式导航跳转到home->index
    this.$router.push({name:"index"});
}

src/components/home/home.vue 验证登录

export default {
  // 使用生命周期的钩子函数,判断token
  mounted() {
    // 获取token
    var token = window.localStorage.getItem("token");
    if (!token) {
      // 错误提示
      this.$message.error("请登录");
      // 跳转到登录页面
      this.$router.push({ name: "Login" });
    }
  },

  data() {
    return {
      msg: "we"
    };
  }
};

1.7 用户退出

绑定点击事件

<el-col :span="6">
    <div class="grid-content bg-purple">
    <el-button @click="loginOut" type="warning">退出</el-button>
    </div>
</el-col>

 

methods:{
    loginOut(){
        // 清楚token
        window.localStorage.removeItem('token')
        // 退出提示
        this.$message({
            message: "您已经退出,继续操作请重新登录",
            type: "success"
        });
        // 页面路由跳转
        this.$router.push({ name: "Login" });
    }
}

第2章 用户管理

2.1 路由及组件

/src/components/home/home.vue

 <el-menu-item index="users">
     <i class="el-icon-menu"></i>
     用户列表
</el-menu-item>

src/router/index.js

import Users from '@/components/users/users'

……

children:[
    {path:'index',name:'index',component:Index},
    {path:'users',name:'users',component:Users}
]

src/components/users/users.vue

<template>
  <div>展示用户列表表格</div>
</template>

<script>
export default {

}
</script>

<style>

</style>

2.2 面包屑导航及搜索框

src/components/users/users.vue Card 卡片 Breadcrumb 面包屑 Input 输入框 Button 按钮

<template>
<div>
  <!-- 面包鞋 -->
  <el-card>
  <el-breadcrumb separator-class="el-icon-arrow-right">
    <el-breadcrumb-item :to="{ path: '/index' }">首页</el-breadcrumb-item>
    <el-breadcrumb-item>用户管理</el-breadcrumb-item>
    <el-breadcrumb-item>用户列表</el-breadcrumb-item>
  </el-breadcrumb>
</el-card>

</div>
</template>
……
</el-card>

<el-row>
  <el-col :span="6" class="sou">
    <el-input placeholder="请输入内容" v-model="input5" class="input-with-select">
      <el-button slot="append" icon="el-icon-search"></el-button>
    </el-input>
  </el-col>
  <el-col :span="1" class="sou">
      <el-button type="success" plain>添加用户</el-button>
    </el-col>
</el-row>

</div>

……

<script>
export default {
  data(){
    // 不想看到报错
    return{input5:''}
  }
};
</script>

<style>
.sou{
  line-height:30px
}
</style>

2.3 展示用户列表

2.3.4 组件展示

src/components/users/users.vue Table 表格->自定义索引


<!-- 表格 自定义索引 -->
<el-table
    :data="tableData"
    style="width: 100% ;">
    <el-table-column
      type="index"
      :index="indexMethod">
    </el-table-column>
    <el-table-column
      prop="date"
      label="日期"
      width="180">
    </el-table-column>
    <el-table-column
      prop="name"
      label="姓名"
      width="180">
    </el-table-column>
  </el-table>

</div>
</template>

<script>
export default {
  data() {
    return {
      input5:'',
      tableData: [
        {
          date: "2016-05-03",
          name: "王小虎"
        }
      ]
    };
  }
};
</script>
<style>
.sou {
  line-height: 30px;
}

.el-main{
  line-height:30px;
}
</style>

2.3.5 获取数据

出登录接口,其他接口发送http请求,必须携带token值

Axios : https://www.kancloud.cn/yunye/axios/234845 ---> 请求配置


data() {
    return {
        input5:'',// 不想看到报错
        // 设置页码及条数
        pagenum:1,
        pagesize:5,
        tableData: []
    };
},
// 利用钩子函数,获取数据
mounted() {
    // 获取token
    let token = window.localStorage.getItem('token');
    // 通过配置选项发送请求
    // 携带token
    this.$myHttp({
        // 设置链接地址 es6新语法
        url:`users?pagenum=${this.pagenum}&pagesize=${this.pagesize}`,
        method:'get',
        // 配置token
        headers: {'Authorization': token}
    }).then(res=>{
        // 修改数据  展示页面
        this.tableData = res.data.data.users;
    })
},

修改组件参数,展示数据:

<el-table-column prop="username" label="姓名" > </el-table-column>

2.3.6 操作按钮

Button 按钮 Table 表格->自定义列模板

<el-table-column label="操作"  width="210">
      <template slot-scope="scope">
        <el-button type="primary" icon="el-icon-edit" size="mini"  plain></el-button>
        <el-button type="primary" icon="el-icon-check" size="mini"  plain></el-button>
        <el-button type="primary" icon="el-icon-delete" size="mini"  plain></el-button>
      </template>
    </el-table-column>

表格中加入按钮等元素时,需要使用 template 进行包裹:

<el-table-column label="用户状态" width="210">
      <template slot-scope="scope">
        <el-switch 
                   v-model="value2" 
                   active-color="#13ce66" 
                   inactive-color="#ff4949">
          </el-switch>
      </template>
</el-table-column>

2.3.7 状态显示

而在template 标签中有一个 slot-scope="scope" 属性,scope 的值就是本列中所有数据的值,参考: Table 表格->固定列

<el-table-column label="用户状态" width="210">
      <template slot-scope="scope">
        <!-- 利用scope 中的值,争取显示用户状态 -->
        <el-switch v-model="scope.row.mg_state" active-color="#13ce66"  inactive-color="#ff4949"></el-switch>
        <!-- 测试事件,查看 scope 数据 -->
        <el-button type="primary" size="mini" @click="showScope(scope)">显示scope</el-button>
      </template>
    </el-table-column>
methods:{
    // 测试 方法 显示scope
    showScope(scope){
      console.log(scope);
    }
  },

2.3.8 分页展示

Pagination 分页->附加功能

 
  <!-- 分页 -->
  <!-- 
      current-page  当前页码数 
      page-sizes  显示条数选项
      page-size 当前每页条数
  -->
  <el-pagination    
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page="pagenum" 
      :page-sizes="[2, 20, 40]"
      :page-size="pagesize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total">
    </el-pagination>
</div>
</template>

……
<script>
   ……
data() {
    return {
      input5:'',// 不想看到报错
      
      pagenum:1, //设置页码
      pagesize:2, // 设置页条数
      total:0,  //显示总条数
      
      tableData: []
    };
  },
      ……
      ……
     	// 获取总条数 修改数据展示
        this.total = res.data.data.total;

但点击页码时,会触发 size-change 事件

<script>
export default {
  data() {
    return {
      input5: "", // 不想看到报错
      pagenum: 1, //设置页码
      pagesize: 2, // 设置页条数
      total: 0, //显示总条数
      tableData: []
    };
  },

  methods: {
    // 获取用户数据
    getUserData() {
      // 获取token
      let token = window.localStorage.getItem("token");
      // 通过配置选项发送请求
      // 携带token
      this.$myHttp({
        // 设置链接地址 es6新语法
        url: `users?pagenum=${this.pagenum}&pagesize=${this.pagesize}`,
        method: "get",
        // 配置token
        headers: { Authorization: token }
      }).then(res => {
        // 修改数据  展示页面
        this.tableData = res.data.data.users;
        // 获取总条数 修改数据展示
        this.total = res.data.data.total;
      });
    },

    // 点击页码触发
    handleCurrentChange(pages) {
        // console.log(pages);
        // 修改data数据,重新发送请求
        this.pagenum = pages;
        this.getUserData();
    },
    // 改变显示条数时触发
    handleSizeChange(numbers){
      this.pagesize = numbers;
      this.getUserData();
    }
  },

  // 利用钩子函数,获取数据
  mounted() {
    this.getUserData();
  }
};
</script>

2.4 模糊搜索

请求地址中加入 query 请求参数,获取条件结果

<el-input placeholder="请输入内容" v-model="search" class="input-with-select">
      <el-button slot="append" 
                 @click="searchUsers" 
       icon="el-icon-search"></el-button>
</el-input>

……

<script>
data() {
    return {
      search: "", // 搜索关键字
    };
  },
      
      // 请求地址中加入关键字
      url: `users?pagenum=${this.pagenum}&pagesize=${this.pagesize}&query=${this.search}`,
      
          
      // 点击搜索事件
      searchUsers(){
          this.getUserData();
      }
  </script>

2.5 切换用户状态

<!-- 利用scope 中的值,争取显示用户状态 -->
<!-- 组件自带change事件 -->
<el-switch v-model="scope.row.mg_state" @change="change(scope)" active-color="#13ce66"  inactive-color="#ff4949"></el-switch>
// Switch 开关 组件自带事件
change(scope){
    // 接受本条全部信息
    // console.log(scope)
    let id = scope.row.id; // 获取id
    var state = scope.row.mg_state; // 获取修改后的状态
    // 请求接口
    this.$myHttp.put(`users/${id}/state/${state}`)
        .then(res=>{
        // 修改失败,将状态改为原始值
        if(!res.data.data){
            this.tableData[scope.$index].mg_state = !state;
            this.$message.error("修改失败");
        }
    })
}

 修改失败是因为没有token:

// Switch 开关 组件自带事件
change(scope){
    // 接受本条全部信息
    // console.log(scope)
    let id = scope.row.id; // 获取id
    var state = scope.row.mg_state; // 获取修改后的状态
    // 请求接口
    // 需要使用配置参数请求,设置token
    this.$myHttp({
        url:`users/${id}/state/${state}`,
        method:'put',
        headers: { Authorization: window.localStorage.getItem("token") }
    })
        .then(res=>{
        // 修改失败,将状态改为原始值
        if(!res.data.data){
            this.tableData[scope.$index].mg_state = !state;
            this.$message.error("修改失败");
        }
    })
}

2.6 删除用户

MessageBox 弹框->确认消息

// 组件中绑定点击按钮
<el-button type="primary" icon="el-icon-delete" size="mini" @click="deleteUser(scope.row.id)" plain></el-button>


// 删除用户
deleteUser(id) {
    //   this.$myHttp({
    //     url: `users/${id}`,
    //     method: "delete",
    //     headers: { Authorization: window.localStorage.getItem("token") }
    //   }).then(res => {
    //     this.getUserData();
    //     this.$message({
    //       message: "删除成功",
    //       type: "success"
    //     });
    //   });
    this.$confirm("此操作将永久删除该用户, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
    })
        .then(() => {
        this.$myHttp({
            url: `users/${id}`,
            method: "delete",
            headers: { Authorization: window.localStorage.getItem("token") }
        }).then(res => {
            this.getUserData();
            this.$message({
                message: "删除成功",
                type: "success"
            });
        });
    })
        .catch(() => {
        this.$message({
            type: "info",
            message: "已取消删除"
        });
    });
}

2.7 添加用户

Dialog对话框->自定义内容->打开嵌套表单的 Dialog Form 表单

表单弹窗:

<el-col :span="1" class="sou">
  <!-- 绑定按钮点击事件 直接将 dialogFormVisible值设置为true显示窗口  -->
  <el-button type="success" @click="dialogFormVisible = true" >添加用户</el-button>
  <!-- 
    :visible.sync属性 控制窗口显示隐藏
    -->
  <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>
    <div slot="footer" class="dialog-footer">
      <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
      <el-button @click="dialogFormVisible = false">取 消</el-button>
      <el-button type="primary" @click="dialogFormVisible = false">确 定</el-button>
    </div>
  </el-dialog>

  </el-col>
</el-row>

修改表单

<el-col :span="1" class="sou">
  <!-- 绑定按钮点击事件 直接将 dialogFormVisible值设置为true显示窗口  -->
  <el-button type="success" @click="dialogFormVisible = true" >添加用户</el-button>
  <!-- 
    :visible.sync属性 控制窗口显示隐藏
    -->
  <el-dialog title="添加用户" :visible.sync="dialogFormVisible">
    <el-form :model="form">
      <el-form-item label="姓名" label-width="90px">
        <el-input v-model="form.username" ></el-input>
      </el-form-item>
      <el-form-item label="密码" label-width="90px">
        <el-input v-model="form.password" ></el-input>
      </el-form-item>
      <el-form-item label="邮箱" label-width="90px">
        <el-input v-model="form.email" ></el-input>
      </el-form-item>
      <el-form-item label="电话" label-width="90px">
        <el-input v-model="form.mobile" ></el-input>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
      <el-button @click="dialogFormVisible = false">取 消</el-button>
       <!-- 修改点击事件,在数据入库成功后关闭窗口 -->
      <el-button type="primary" @click="addUser">确 定</el-button>
    </div>
  </el-dialog>
</el-col>

添加数据及方法

data() {
    return {
      dialogFormVisible: false,
      form: {
        username: '',
        password:'',
        email:'',
        mobile:''

      },
             
……
        
methods 方法
        
 // 添加用户
    addUser(){
      this.$myHttp({
        url:'users',
        method:'post',
        // post数据提交
        data:this.form,
        headers: { Authorization: window.localStorage.getItem("token") }
      }).then(res=>{
          let {data} = res;
          if(data.meta.status == 201){
              // 将数据更新到页面
              this.tableData.push(data.data);
              this.$message({message: "添加用户成功",type: "success"});
              // 关闭窗口 
              this.dialogFormVisible = false
          }
      })
    },

2.8 修改用户信息

绑定表单事件,传入 scope.row 以显示现有用户数据,做表单读入展示

<template slot-scope="scope">
        <el-button type="primary" icon="el-icon-edit" size="mini" @click="editUserShow(scope.row)" plain></el-button>
        <el-button type="primary" icon="el-icon-check" size="mini"  plain></el-button>
        <el-button type="primary" icon="el-icon-delete" size="mini" @click="deleteUser(scope.row.id)" plain></el-button>
      </template>

添加修改用户信息的弹窗,并在弹窗表单中展示用户信息

<!-- 修改用户弹窗 -->
<el-dialog title="添加用户" :visible.sync="editUser">
    <el-form :model="edit">
        <el-form-item label="姓名" label-width="90px">
            <el-input disabled v-model="edit.username" ></el-input>
        </el-form-item>
        <el-form-item label="邮箱" label-width="90px">
            <el-input v-model="edit.email" ></el-input>
        </el-form-item>
        <el-form-item label="电话" label-width="90px">
            <el-input v-model="edit.mobile" ></el-input>
        </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
        <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
        <el-button @click="editUser = false">取 消</el-button>
        <el-button type="primary" @click="editUserPut">确 定</el-button>
    </div>
</el-dialog>
// 弹窗并显示用户数据 用于修改表单
editUserShow(users){
    this.editUser = true; // 弹窗
    this.edit = users; // 直接使用表单数据
},

// 修改用户信息 入库
editUserPut(){
    var id = this.edit.id;
    var email = this.edit.email;
    var mobile = this.edit.mobile;

    this.$myHttp({
        url: `users/${id}`,
        method: "put",
        data:{email,mobile},
        headers: { Authorization: window.localStorage.getItem("token") }
    }).then(res=>{
        // console.log(res);
        if(res.data.meta.status == 200){
            this.editUser = false; // 关闭窗口
            this.getUserData(); // 重新获取数据
            this.$message({message: "修改用户成功",type: "success"});
        }
    })
}

2.9 修改用户角色

Select 选择器->基础用法 下拉框

<!-- 分配角色弹窗 -->
<el-dialog title="分配角色" :visible.sync="showRole">
    <el-form :model="role">
        <el-form-item label="当前用户" label-width="90px">
            <el-input disabled v-model="role.username" ></el-input>
        </el-form-item>
        <el-form-item label="活动区域">
            <el-select v-model="roleId"  placeholder="请选择活动区域">
                <el-option
                           v-for="item in roleList"
                           :key="item.key"
                           :label="item.roleName"
                           :value="item.id">
                </el-option>
            </el-select>
        </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
        {{roleId}}
        <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
        <el-button @click="showRole = false">取 消</el-button>
        <el-button type="primary" @click="roleUserPut">确 定</el-button>
    </div>
</el-dialog>

弹窗后,获取全部角色遍历到 el-option ,获取用户id及修改后的角色,请求接口即可;

第3章 权限管理

3.1 权限列表

添加路由及组件文件

import Rights from '@/components/rights/rights'

{path:'rights',name:'rights',component:Rights}
<template>
  <div>
    <el-table
    height="850"
    ref="singleTable"
    :data="tableData"
    highlight-current-row
    style="width: 100%">
    <el-table-column
      type="index"
      width="50">
    </el-table-column>
    <el-table-column
      property="authName"
      label="权限名称"
      width="120">
    </el-table-column>
    <el-table-column
      property="path"
      label="路径"
      width="120">
    </el-table-column>
    <el-table-column  property="一级"  label="层级">
    </el-table-column>
  </el-table>
  </div>
</template>

<script>
export default {
  data(){
    return {
      tableData:[]
    }
  },
  mounted() {
    this.getlist();
  },
  methods:{
    getlist(){
      this.$myHttp({
        url:'rights/list',
        method:'get',
        headers: { Authorization: window.localStorage.getItem("token") }
      }).then(backs=>{
        // console.log(backs);
        this.tableData = backs.data.data;
      })
    }
  }
}
</script>

<style>
.el-main {
  line-height: 30px;
}
</style>

只要在el-table元素中定义了height=500属性,即可实现固定表头的表格,而不需要额外的代码。

修改层级展示

<el-table-column  property="level"  label="层级">
    <template slot-scope="scope">
        <span v-if="scope.row.level==='0'">一级</span>
        <span v-else-if="scope.row.level==='1'">二级</span>
        <span v-if="scope.row.level==='2'">三级</span>
    </template>
</el-table-column>

3.2 角色列表

添加路由及组件

import Roles from '@/components/roles/roles'

{path:'roles',name:'roles',component:Roles},

 


<template>
  <el-table :data="tableData5"  style="width: 100%">

    <!-- 折叠内容 -->
    <el-table-column type="expand">
      <template slot-scope="props">
        <el-form label-position="left" inline class="demo-table-expand">
          <el-form-item label="商品名称">
            <span>{{ props.row.name }}</span>
          </el-form-item>
          <el-form-item label="所属店铺">
            <span>{{ props.row.shop }}</span>
          </el-form-item>
        </el-form>
      </template>
    </el-table-column>


    <!-- 表头及折叠按钮 -->
    <el-table-column
      label="角色名称"
      prop="id">
    </el-table-column>
    <el-table-column
      label="角色描述"
      prop="name">
    </el-table-column>
    <el-table-column
      label="操作"
      prop="desc">
    </el-table-column>

  </el-table>
</template>

<script>
  export default {
    data() {
      return {
        tableData5: [{
          id: '12987122',
          name: '好滋好味鸡蛋仔',
          category: '江浙小吃、小吃零食',
          desc: '荷兰优质淡奶,奶香浓而不腻',
          address: '上海市普陀区真北路',
          shop: '王小虎夫妻店',
          shopId: '10333'
        }, {
          id: '12987123',
          name: '好滋好味鸡蛋仔',
          category: '江浙小吃、小吃零食',
          desc: '荷兰优质淡奶,奶香浓而不腻',
          address: '上海市普陀区真北路',
          shop: '王小虎夫妻店',
          shopId: '10333'
        }]
      }
    }
  }
</script>

<style>
  .demo-table-expand {
    font-size: 0;
  }
  .demo-table-expand label {
    width: 90px;
    color: #99a9bf;
  }
  .demo-table-expand .el-form-item {
    margin-right: 0;
    margin-bottom: 0;
    width: 50%;
  }
  .el-main{
    line-height:20px;
  }
</style>
<!-- 表头及折叠按钮 -->
<el-table-column
                 label="角色名称"
                 prop="roleName">
</el-table-column>
<el-table-column
                 label="角色描述"
                 prop="roleDesc">
</el-table-column>
<el-table-column
                 label="操作"
                 prop="desc">
    <template slot-scope="scope">
        <el-button type="primary" icon="el-icon-edit" size="mini" circle></el-button>
        <el-button type="success" icon="el-icon-check" size="mini" circle></el-button>
    </template>
</el-table-column>
  data() {
    return {
      roleList: []
    };
  },
  mounted() {
    this.getrolelist();
  },
  methods: {
    getrolelist() {
      this.$myHttp({
        url: "roles",
        method: "get"
      }).then(back => {
        this.roleList = back.data.data;
      });
    }
  }

Tag 标签->可移除标签

<!-- 折叠内容 -->
<el-table-column type="expand">
    <template slot-scope="props">
	<el-tag closable>可移除</el-tag>
    </template>
</el-table-column>

分析角色数据,children 为上级角色中的子级角色;

<!-- 折叠内容 -->
<el-table-column type="expand">
    <template slot-scope="scope">
	{{scope.row.children}}
	<!-- <el-tag closable>{{scope.row.children}} </el-tag> -->
    </template>
</el-table-column>

<!-- 折叠内容 -->
    <el-table-column type="expand">
      <template slot-scope="scope">
        <!-- Layout 布局 -->
        <el-row>
          <!-- 一级区域 -->
          <el-col :span="6">
            <!-- 一级内容展示 -->
            <el-tag closable>{{scope.row.children[1].authName}} </el-tag> >
          </el-col>
          
          <el-col :span="18">
            <!-- 二级区域 -->
            <el-row>
              <el-col :span="6">
                  <!-- 二级内容 -->
                  <el-tag closable type="success">{{scope.row.children[0].children[0].authName}} </el-tag> >
              </el-col>
              <el-col :span="18">
                  <!-- 三级内容 -->
                  <el-tag closable type="warning">{{scope.row.children[1].children[0].children[0].authName}}</el-tag>
                  <el-tag closable type="warning">{{scope.row.children[1].children[0].children[1].authName}}</el-tag>
                  <el-tag closable type="warning">{{scope.row.children[1].children[0].children[2].authName}}</el-tag>
              </el-col>
            </el-row>
          </el-col>
        </el-row>
      </template>
    </el-table-column>

 循环遍历所有层级角色

  <!-- 折叠内容 -->
    <el-table-column type="expand">
      <template slot-scope="scope">
        <!-- Layout 布局 -->
        <el-row class="rowmargin" v-for="item1 in scope.row.children" :key="item1.id">
          <!-- 一级区域 -->
          <el-col :span="6">
            <!-- 一级内容展示 -->
            <el-tag closable>{{item1.authName}} </el-tag> >
          </el-col>
          
          <el-col :span="18">
            <!-- 二级区域 -->
            <el-row  v-for="item2 in item1.children" :key="item2.id">
              <el-col :span="6">
                  <!-- 二级内容 -->
                  <el-tag closable type="success">{{item2.authName}} </el-tag> >
              </el-col>
              <el-col :span="18">
                  <!-- 三级内容 -->
                  <el-tag v-for="item3 in item2.children" :key="item3.id" closable type="warning">{{item3.authName}} </el-tag>
              </el-col>
            </el-row>
          </el-col>
        </el-row>
	
		<!-- 判断没有权限 -->
	    <el-row v-if="scope.row.children.length==0">
          <template><el-tag type="danger">木有权限</el-tag></template>
        </el-row>	

      </template>
    </el-table-column>


……

.el-tag{
  margin-top: 10px;
  margin-right:5px; 
}
<style>

3.3 删除角色权限

绑定close事件

页面元素删除

<!-- 三级内容 -->
<el-tag @close="closeTag(item2,key3)"  v-for="(item3,key3) in item2.children" :key="item3.id" closable type="warning">
{{item3.authName}} 
</el-tag>
// 删除角色权限
closeTag(item,key){
    // 数组引用传递,直接删除即可
    // console.log(item,key)
    item.children.splice(key,1);
}

服务器删除

<el-col :span="18">
<!-- 三级内容 -->
<el-tag @close="closeTag(item2,key3,scope.row.id,item3.id)"  v-for="(item3,key3) in item2.children" :key="item3.id" closable type="warning">{{item3.authName}} </el-tag>
</el-col>
// 删除角色权限
closeTag(item,key,roleId,rightId){
    // item 要删除元素所在父级数组
    // key 要删除元素所在父级数组下标
    item.children.splice(key,1);

    // roleid 角色ID,rightId权限ID
    // console.log(roleId,rightId);
    this.$myHttp({
        url:`roles/${roleId}/rights/${rightId}`,
        method:'delete'
    }).then(back=>{
        let {meta}  = back.data;
        // console.log(meta);
        if(meta.status == 200){
            this.$message({message:meta.msg,type:'success'});
        }
    })
}

3.4 修改角色权限

展示面板:

<template slot-scope="scope">
            <el-button type="primary" icon="el-icon-edit" size="mini" circle></el-button>
            <el-button type="success" icon="el-icon-check" size="mini" @click="rightsShow" circle></el-button>
        </template>
<!-- 修改角色授权面板 -->
<el-dialog title="修改角色权限" :visible.sync="isrightsShow">
    <div slot="footer" class="dialog-footer">
        <el-tree show-checkbox="true" :data="rightsList" :props="defaultProps" ></el-tree>
        <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
        <el-button @click="isrightsShow = false">取 消</el-button>
        <el-button type="primary" @click="rightsPut">确 定</el-button>
    </div>
</el-dialog>
return {
    // 所有权限列表
    rightsList:[],
    // 设置展示内容
    defaultProps: {
        children: 'children',
        label: 'authName'
    },
// 展示修改角色权限面板
rightsShow() {
    // 获取所有角色权限
    this.$myHttp({
        url:'rights/tree',
        method:'get'
    }).then(back=>{
        let {data,meta} = back.data;
        this.rightsList= data;
    })
    this.isrightsShow = true;
},

选中角色拥有的权限:

在点击按钮式,将所有角色的所有信息传入展示面板事件中:

<template slot-scope="scope">
    <el-button type="primary" icon="el-icon-edit" size="mini" circle></el-button>
    <el-button @click="rightsShow(scope.row)" type="success" icon="el-icon-check" size="mini" circle></el-button>
</template>
    <!-- 修改角色授权面板 -->
    <el-dialog title="修改角色权限" :visible.sync="isrightsShow">
      <div slot="footer" class="dialog-footer">
        <!-- 
		  default-expand-all 默认展开所有节点
          node-key="id" 将id设置为节点的唯一主键
          :default-checked-keys=[] 被选中主键的数组
          :props="defaultProps" 设置显示的内容			
		  show-checkbox 节点可被选中
          -->
        <el-tree 
            default-expand-all
            node-key="id"  
            :default-checked-keys="defaultChecked"
            show-checkbox
            :data="rightsList" 
            :props="defaultProps" ></el-tree>
        <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
        <el-button @click="isrightsShow = false">取 消</el-button>
        <el-button type="primary" @click="rightsPut">确 定</el-button>
      </div>
    </el-dialog>
data() {
    return {
      // 所有权限列表
      rightsList: [],
      // 设置展示内容
      defaultProps: {
        children: "children",
        label: "authName"
      },
      // 默认选中的节点数组
      defaultChecked: [],

      // 控制角色权限面板
      isrightsShow: false,

      // 所有角色数据列表
      roleList: []
    };
  },  

…… 

// 展示修改角色权限面板
      rightsShow(row) {
          // 获取所有角色权限
          this.$myHttp({
              url: "rights/tree",
              method: "get"
          }).then(back => {
              let { data, meta } = back.data;
              // 显示所有权限
              this.rightsList = data;
          });

          // 遍历row,获取当前角色选中的所有权限,写入数组
          this.defaultChecked = [];
          // 在遍历赋值前,先清空数据,以免受其他数据影响
          var rr = row.children;
          rr.forEach(item1 => {
              item1.children.forEach(item2=>{
                  item2.children.forEach(item3=>{
                      // 只获取第三季选中即可
                      this.defaultChecked.push(item3.id);
                  })
              });
          });

          console.log(this.defaultChecked);
          // 控制显示窗口
          this.isrightsShow = true;
      },

提交数据入库:

    // 提交修改角色权限
    rightsPut() {
      // 在树形控件 中添加 ref="tree" 的属性,在此使用
      // elUI 中提供两个方法getCheckedKeys、getHalfCheckedKeys
      // 获取已选中的节点key 
      var arr1 = this.$refs.tree.getCheckedKeys();
      var arr2 = this.$refs.tree.getHalfCheckedKeys();
      // concat() 合并两个数组的元素
      // join() 将数组的值以逗号隔开转为字符串
      var checkedKeys = arr1.concat(arr2).join();
      this.$myHttp({
        // 点击打开窗口是,保存角色id,在此获取使用
        url:`roles/${this.roleId}/rights`,
        method:'post',
        data:{rids:checkedKeys}
      }).then(back=>{
        let {data,meta} = back.data;
        if(meta.status == 200){
          this.isrightsShow = false; // 关闭窗口
          this.getrolelist(); // 刷新数据
          this.$message({message:meta.msg,type:'success'}); // 提示成功
        }
      })
    },

3.5 权限限制

对角色分配了权限后,我们并没有做限制,其实接口文档中左侧菜单权限 已经提供了相应的接口:

src/components/home/home.vue

<el-menu 
        unique-opened
        :router="true"
        class="el-menu-vertical-demo"
        >
    <el-submenu 
                v-for="item in menusList" 
                :key="item.id" 
                :index="item.id.toString()" >
            <template slot="title">
            <i class="el-icon-location"></i>
            <span>{{item.authName}} {{item.id}}</span>
            </template>
        <el-menu-item 
                      v-for="item2 in item.children" 
                      :key="item2.id"  
                      :index="item2.path">
            <i class="el-icon-menu"></i>
            {{item2.authName}}  {{item2.path}}
        </el-menu-item>
    </el-submenu>
</el-menu>

<script>
export default {
  // 使用生命周期的钩子函数,判断token
  mounted() {
    // 获取token
    var token = window.localStorage.getItem("token");
    if (!token) {
      // 错误提示
      this.$message.error("请登录");
      // 跳转到登录页面
      this.$router.push({ name: "Login" });
    }else{
      // 登录后,获取左侧菜单权限
      this.$myHttp({
        url:'menus',
        method:'get',
      }).then(back=>{
        let {data,meta} = back.data;
        if(meta.status == 200){
          console.log(data);
          this.menusList = data
        }
      })
    }

  },

  data() {
    return {
      menusList:[],
      msg: "we"
    };
  },
  methods:{
    loginOut(){
      window.localStorage.removeItem('token')
      this.$message({
              message: "您已经退出,继续操作请重新登录",
              type: "success"
            });
      this.$router.push({ name: "Login" });
    }
  }
};
</script>

3.6 导航守卫

导航守卫: https://router.vuejs.org/zh/guide/advanced/navigation-guards.html

var router = new Router({……})

// 配置路由的导航守卫
router.beforeEach((to, from, next) => {
  // 如果访问登录的路由地址,放过
  if (to.name === 'Login') {
    next();
  } else {
    // 如果请求的不是登录页面,验证token
    // 1. 获取本地存储中的token
    const token = localStorage.getItem('token');
    if (!token) {
      // 2. 如果没有token,跳转到登录
      next({
        name: 'Login'
      });
    } else {
      // 3. 如果有token,继续往下执行
      next();
    }
  }
});

export default router;

 

项目打包及加载优化

打包命令:npm run build

打包完成后,直接将dist文件夹内容复制到服务器根目录即可;

我们的项目是很多组件组成的页面,但是,每次发送请求不管请求的是哪个路由的那个组件,很明显的都会将所有内容一次性全部加载出来,影响网站加载速度;如果我们可以在用户请求不同路由时,根据请求加载不同的页面,就会很大程度上提高页面的加载速度;

路由懒加载: https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

路由懒加载的工作就是在打包时,将路由文件分离出来,在请求时,需要哪个路由,再去请求相关文件;

用法:将路由引入的组件分别打包到不同的 js 文件;

 

打包完成后,很明显的在 JS 文件夹中多了一个js文件;

然后我们可以将所有的组件全部改为路由懒加载模式:

const Login = () => import('@/components/login/login');
const Home = () => import('@/components/home/home');
const UserList = () => import('@/components/userlist/user-list');
const RoleList = () => import('@/components/rolelist/role-list');
const RightsList = () => import('@/components/rightslist/rights-list');
const GoodsList = () => import('@/components/goodslist/goods-list');
const GoodsCategories = () => import('@/components/goodscategories/goods-categories');
const GoodsAdd = () => import('@/components/goodsadd/goods-add');
const Report = () => import('@/components/report/report');
const Order = () => import('@/components/orders/orders');
const Params = () => import('@/components/params/params');

 

但这是不够的,我们知道,很多组件都是可以用 CDN 加载的;

1:找到 cdn 地址,直接在index.html 中加入地址,注意,cdn 引入版本要和项目中的版本保持一致;

 <body>
    <div id="app">
    </div>
    <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
    <!-- built files will be auto injected -->
  </body>

2:修改 webpack 配置文件 https://www.webpackjs.com/configuration/externals/

 

最后

以上就是正直摩托为你收集整理的PHP_Vue前后端分离项目(6)的全部内容,希望文章能够帮你解决PHP_Vue前后端分离项目(6)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部