3-Vue&Ajax

0 阅读15分钟

什么是Vue

Vue是一款用于构建用户界面的渐进式的JavaScript框架。官方网站:cn.vuejs.org/

前端负责将数据以美观的样式呈现出来,而数据最终又要在数据库服务器中存储并管理。前端想要拿到数据,就需要请求服务器。然后服务器将数据返回给前端。Vue可以将这些原始的数据渲染展示在页面中,转换为用户可以看懂的网页,这就是构建用户界面,即基于数据渲染出用户看到的界面。

准备工作:

1.引入Vue模块

2.创建Vue程序的应用实例,控制视图的元素

3.准备元素(div),被Vue控制

数据驱动视图:

1.准备数据

2.通过插值表达式渲染页面

不需要去记忆语法,可以看官方文档或直接用AI生成。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <div id="app">
    <h1>{{ message }}</h1> 
    <h1>{{ count }}</h1>
    <!-- 插值表达式 -->
  </div>
​
  <script type = "module">
    import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';//引入Vue模块
    createApp({//创建实例
      data() {
        return {
          message: "Hello Vue!",//return要返回js的对象,对象里装的是Vue实例中的数据
          count: 100
        }
      },
    }).mount('#app'); //创建的实例接管上面的app
    //如果把count那一行写在div的外面,那么展示的就会是{{100}}而不是100,因为Vue接管的只有div下的内容
  </script>
</body>
</html>

Vue常用指令

指令:html标签上带有v-前缀的特殊属性

常用指令:

指令作用
v-for列表渲染,遍历容器的元素或对象的属性
v-bind为HTML标签绑定属性值,如设置href,css样式等
v-if/v-else-if/v-else条件性地渲染某元素,判定为true时渲染,否则不渲染
v-show根据条件展示某元素,区别在于切换的是display属性的值
v-model在表单元素上创建双向数据绑定
v-on为HTML标签绑定事件

依然用之前的例子。我们知道这些指令即可,代码让AI生成。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>进击!巨人中学校</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
        /* 原有的CSS样式保持不变 */
        .top-nav {
            background-color: #333;
            padding: 15px;
            overflow: hidden;
        }
        
        .nav-title {
            float: left;
            font-size: 24px;
            font-weight: bold;
            margin: 0;
            color:#f0f0f0;
        }
        
        .nav-logout {
            float: right;
            font-size: 16px;
            margin-top: 8px;
        }
        
        .nav-logout a {
            color: white;
            text-decoration: none;
        }
        
        .nav-logout a:hover {
            text-decoration: underline;
        }
​
        .search-form {
            padding: 20px;
            background-color: #f8f9fa;
            border: 1px solid #e9ecef;
            margin: 20px;
            border-radius: 5px;
        }
        
        .search-form form {
            display: flex;
            align-items: center;
            gap: 15px;
            flex-wrap: wrap;
        }
        
        .form-group {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        .form-group label {
            font-weight: 500;
            min-width: 60px;
        }
        
        .form-group input,
        .form-group select {
            padding: 8px 12px;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 14px;
        }
        
        .form-group input {
            width: 150px;
        }
        
        .form-group select {
            width: 120px;
        }
        
        .form-buttons {
            margin-left: auto;
            display: flex;
            gap: 10px;
        }
        
        .btn {
            padding: 8px 20px;
            border: none;
            border-radius: 4px;
            font-size: 14px;
            cursor: pointer;
            font-weight: 500;
        }
        
        .btn-primary {
            background-color: #007bff;
            color: white;
        }
        
        .btn-primary:hover {
            background-color: #0056b3;
        }
        
        .btn-secondary {
            background-color: #6c757d;
            color: white;
        }
        
        .btn-secondary:hover {
            background-color: #545b62;
        }
​
        .table-container {
            margin: 20px;
            border-radius: 5px;
            overflow: hidden;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        
        .data-table {
            width: 100%;
            border-collapse: collapse;
            background-color: white;
        }
        
        .data-table th,
        .data-table td {
            padding: 12px 15px;
            text-align: center;
            border-bottom: 1px solid #e9ecef;
        }
        
        .data-table th {
            background-color: #f8f9fa;
            font-weight: 600;
            color: #495057;
            font-size: 14px;
        }
        
        .data-table tbody tr {
            transition: background-color 0.2s;
        }
        
        .data-table tbody tr:hover {
            background-color: #f8f9fa;
        }
        
        .btn-edit {
            background-color: #28a745;
            color: white;
            padding: 5px 12px;
            margin-right: 5px;
        }
        
        .btn-edit:hover {
            background-color: #218838;
        }
        
        .btn-delete {
            background-color: #dc3545;
            color: white;
            padding: 5px 12px;
        }
        
        .btn-delete:hover {
            background-color: #c82333;
        }
​
        .avatar {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            object-fit: cover;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- 顶部导航栏 -->
        <div class="top-nav">
            <h1 class="nav-title">进击!巨人中学校</h1>
            <div class="nav-logout">
                <a href="#">退出登录</a>
            </div>
        </div>
​
        <!-- 搜索表单区域 -->
        <div class="search-form">
            <form @submit.prevent="searchEmployees">
                <div class="form-group">
                    <label for="name">姓名:</label>
                    <input type="text" id="name" v-model="searchForm.name" placeholder="请输入姓名">
                    <!-- 现在不通过原生的方式操作了,所以name属性可有可无 -->
                </div>
                
                <div class="form-group">
                    <label for="gender">性别:</label>
                    <select id="gender" v-model="searchForm.gender">
                        <option value="">请选择</option>
                        <option value="男"></option>
                        <option value="女"></option>
                    </select>
                </div>
​
                <div class="form-group">
                    <label for="avatar">头像:</label>
                    <input type="image" id="avatar" v-model="searchForm.avatar" placeholder="请输入头像地址">    
                </div>
                
                <div class="form-group">
                    <label for="position">职位:</label>
                    <select id="position" v-model="searchForm.position">
                        <option value="">请选择</option>
                        <option value="班主任">班主任</option>
                        <option value="讲师">讲师</option>
                        <option value="学工主管">学工主管</option>
                        <option value="教导主任">教导主任</option>
                        <option value="校长">校长</option>
                        <option value="后勤部长">后勤部长</option>
                        <option value="教研主管">教研主管</option>
                    </select>
                </div>
                
                <div class="form-buttons">
                    <button type="submit" class="btn btn-primary">查询</button>
                    <button type="button" @click="clearSearch" class="btn btn-secondary">清空</button>
                    <!-- @符号是v-on的简写 -->
                </div>
            </form>
        </div>
​
        <!-- 表格展示区 -->
        <div class="table-container">
            <table class="data-table">
                <thead> 
                    <tr>
                        <th>序号</th>
                        <th>姓名</th>
                        <th>性别</th>
                        <th>头像</th>
                        <th>职位</th>
                        <th>入职日期</th>
                        <th>最后操作时间</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(emp, index) in filteredEmpList" :key="emp.id"> 
                        <td>{{ index + 1 }}</td>
                        <td>{{ emp.name }}</td>
                        <td>{{ emp.gender }}</td>
                        <!-- Vue中,标签内部不能有插值表达式 -->
                         <td><img class="avatar" v-bind:src="emp.avatar" :alt="emp.name"></td>
                        <!-- 冒号是v-bind:的缩写形式 -->
                        <td>{{ emp.position }}</td>
                        <td>{{ emp.entryDate }}</td>
                        <td>{{ emp.lastOperationTime }}</td>
                        <td>
                            <button class="btn btn-edit" @click="editEmployee(emp)">编辑</button>
                            <button class="btn btn-delete" @click="deleteEmployee(emp, index)">删除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
​
    <script>
        const { createApp } = Vue;
​
​
        createApp({
            data() {
                return {
                    empList: [
                        {
                            name: '艾伦',
                            gender: '男',
                            avatar:'demo-avatar/eren.jpg',
                            position: '讲师',
                            entryDate: '2023-01-15',
                            lastOperationTime: '2023-12-10 14:30:25'
                        },
                        {
                            name: '三笠',
                            gender: '女',
                            avatar:'demo-avatar/mikasa.jpg',
                            position: '学工主管',
                            entryDate: '2023-02-20',
                            lastOperationTime: '2023-12-09 09:15:42'
                        },
                        {
                            name: '阿尔敏',
                            gender: '男',
                            avatar:'demo-avatar/armin.jpg',
                            position: '教研主管',
                            entryDate: '2023-03-05',
                            lastOperationTime: '2023-12-08 16:45:18'
                        },
                        {
                            name: '利威尔',
                            gender: '男',
                            avatar:'demo-avatar/levi.jpg',
                            position: '班主任',
                            entryDate: '2023-01-10',
                            lastOperationTime: '2023-12-10 11:20:30'
                        },
                        {
                            name: '韩吉',
                            gender: '女',
                            avatar:'demo-avatar/hange.jpg',
                            position: '教导主任',
                            entryDate: '2023-02-15',
                            lastOperationTime: '2023-12-07 15:50:45'
                        },
                        {
                            name:"埃尔文",
                            gender:"男",
                            avatar:"demo-avatar/erwin.jpg",
                            position:"校长",
                            entryDate:"2023-03-20",
                            lastOperationTime:"2023-12-06 10:30:00"
                        },
                        {
                            name:"萨莎",
                            gender:"女",
                            avatar:"demo-avatar/sasha.jpg",
                            position:"后勤部长",
                            entryDate:"2023-04-01",
                            lastOperationTime:"2023-12-05 18:00:00"
                        }
                    ],
                    searchForm: {
                        name: '',
                        gender: '',
                        position: ''
                    } //数据模型,封装用户输入的查询条件
                }
            },
            computed: {
                filteredEmpList() {
                    return this.empList.filter(emp => {
                        const matchesName = !this.searchForm.name || 
                            emp.name.toLowerCase().includes(this.searchForm.name.toLowerCase());
                        const matchesGender = !this.searchForm.gender || 
                            emp.gender === this.searchForm.gender;
                        const matchesPosition = !this.searchForm.position || 
                            emp.position === this.searchForm.position;
                         
                        return matchesName && matchesGender && matchesPosition;
                    });
                }
            },
            methods: {
                searchEmployees() {
                    // 搜索逻辑已经在computed中实现
                    console.log('搜索条件:', this.searchForm);
                },
                clearSearch() {
                    this.searchForm = {
                        name: '',
                        gender: '',
                        position: ''
                    };
                },
                editEmployee(employee) {
                    alert(`编辑员工: ${employee.name}`);
                    // 这里可以实现编辑逻辑
                },
                deleteEmployee(employee, index) {
                    if (confirm(`确定要删除员工 "${employee.name}" 吗?`)) {
                        this.empList.splice(index, 1);
                    }
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

解释一下v-model:

输入->数据:用户在输入框中输入内容,searchForm对象的相应属性自动更新

数据->输入:searchForm对象值变化,输入框内容自动更新

解释一下filteredEmpList函数:

filteredEmpList() {
    return this.empList.filter(emp => {
        const matchesName = !this.searchForm.name || 
            emp.name.toLowerCase().includes(this.searchForm.name.toLowerCase());
        const matchesGender = !this.searchForm.gender || 
            emp.gender === this.searchForm.gender;
        const matchesPosition = !this.searchForm.position || 
            emp.position === this.searchForm.position;
         
        return matchesName && matchesGender && matchesPosition;
    });
}
this.empList.filter(emp => {
    // 对每个员工执行这个函数
    // 如果函数返回 true,该员工会被包含在结果中
    // 如果函数返回 false,该员工会被过滤掉
})

姓名过滤逻辑:

const matchesName = !this.searchForm.name || 
    emp.name.toLowerCase().includes(this.searchForm.name.toLowerCase());

!this.searchForm.name 表示搜索框为空

逻辑或:只要前面为true,就不执行后面

emp.name.toLowerCase().includes(this.searchForm.toLowerCase()):检查员工姓名是否包含搜索关键词

也就是说,如果搜索框为空,则matchesName=true;如果搜索框不为空,检查员工姓名是否包含输入的关键词。如果包含,matchesName也为true

const matchesGender = !this.searchForm.gender || 
    emp.gender === this.searchForm.gender;
const matchesPosition = !this.searchForm.position || 
    emp.position === this.searchForm.position;

性别过滤逻辑和职位过滤逻辑一样,如果没有选择性别或职位,就匹配所有员工。选择了,就匹配相同性别/职位的员工。

后面的AND逻辑好理解,三个都满足才会被保留。最后返回empList中经过过滤器筛选后剩下来的emp

用具体例子解释一下computed:

用户在姓名输入框里输入”艾伦“,在v-model的加持下searchForm.name变为”艾伦“,computed检测到依赖变化,执行过滤逻辑,经过过滤后只剩下艾伦,就返回艾伦这个emp,最后通过v-for输出:

<tr v-for="(emp, index) in filteredEmpList" :key="emp.id"> 

关于依赖:

当 Vue 首次计算 filteredEmpList 时: 1. 开始执行 filteredEmpList 函数 ; 2. 记录:this.searchForm.name 被访问了;3. 记录:this.searchForm.gender 被访问了 ;4. 记录:this.searchForm.position 被访问了 ; 5. 记录:this.empList 被访问了 ; 6. 建立依赖关系:filteredEmpList 依赖于以上所有属性

那为什么我们不把过滤函数放在methods里面呢?因为computed有缓存性,只有当依赖项变化时才重新计算。如果放在methods里,那每次渲染都要执行。

Ajax

全称:Asynchronous JavaScript And XML(异步的JavaScript和XML)

XML全称:Extensible Makrup Language(可扩展的标记语言)。本质是一种数据格式,可以用来存储复杂的数据结构

Ajax作用:

1.数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据

2.异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。例如:搜索联想(在搜索引擎中输入谷,就弹出谷歌相关的词条,这个过程中没有页面的刷新)、用户名可用性的校验(设置用户名后,在不刷新界面的同时提示用户名已被占用,实际上是在不重新加载页面的情况下已经与服务器完成了数据的交换)

同步:客户端请求服务器后,只等待

异步:客户端请求服务器后,可以执行其它操作

Axios

Axios对原生的Ajax进行了封装,官网:www.axios-http.cn/

步骤:

1.引入Axios的js文件(参照官网)

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

2.使用Axios发送请求,并获取响应结果

axios({
    method:'GET',//请求方式 GET/POST
    url:'https://...' //请求路径:url
    //data:请求数据(POST)
    //params:发送请求时携带的url参数
}).then((result) => {//成功回调函数。因为是异步的,所以前端不会等待服务器返回。等到服务器返回了,再回头调用这个函数,所以叫回调。result是服务器响应回来的数据
    console.log(result.data);
}).catch((err) => {//失败回调函数
    alert(err);
});

也可以使用简单方式,axios.get和axios.post,写完url之后直接在后面写thenc再回车就可以补全两个回调函数的框架。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="button" value="获取数据GET" id="btnGet">
  <input type="button" value="提交数据POST" id="btnPost">
​
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    //发送GET请求
    document.querySelector('#btnGet').addEventListener('click', () => {
      axios.get('https://jsonplaceholder.typicode.com/posts/1')
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error('发生错误:', error);
        });
    });
    //发送POST请求
    document.querySelector('#btnPost').addEventListener('click', () => {
      axios.post('https://jsonplaceholder.typicode.com/posts', {
        title: '新标题',
        body: '新内容',
        userId: 1
      })
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error('发生错误:', error);
        });
    });
  </script>
</body>
</html>

练习:从服务器端动态获取数据渲染展示列表

在前面的巨人中学校例子里,渲染员工列表时的七条数据都是被写死的,现在我们需要在点击查询按钮时动态地根据查询条件来查询员工信息。在此之前我们先创建一个服务URL以供练习。

1.安装Node.js

nodejs.org/en

检查:

node --version
npm --version

2.安装JSON Server

可以使用以下任一方式安装JSON Server

CMD或PowerShell或Git Bash:

npm install -g json-server

发现安装失败,原因是json-server依赖的一个子包在npm仓库中找不到了,尝试安装一个依赖包还存在的历史版本:

npm install -g json-server@0.17.4

成功安装。

3.创建数据文件db.json(放在任意目录)

{
  "empList": [
    {
      "id": 1,
      "name": "艾伦",
      "gender": "男",
      "avatar": "demo-avatar/eren.jpg",
      "position": "讲师",
      "entryDate": "2023-01-15",
      "lastOperationTime": "2023-12-10 14:30:25"
    },
    {
      "id": 2,
      "name": "三笠",
      "gender": "女",
      "avatar": "demo-avatar/mikasa.jpg",
      "position": "学工主管",
      "entryDate": "2023-02-20",
      "lastOperationTime": "2023-12-09 09:15:42"
    },
    {
      "id": 3,
      "name": "阿尔敏",
      "gender": "男",
      "avatar": "demo-avatar/armin.jpg",
      "position": "教研主管",
      "entryDate": "2023-03-05",
      "lastOperationTime": "2023-12-08 16:45:18"
    },
    {
      "id": 4,
      "name": "利威尔",
      "gender": "男",
      "avatar": "demo-avatar/levi.jpg",
      "position": "班主任",
      "entryDate": "2023-01-10",
      "lastOperationTime": "2023-12-10 11:20:30"
    },
    {
      "id": 5,
      "name": "韩吉",
      "gender": "女",
      "avatar": "demo-avatar/hange.jpg",
      "position": "教导主任",
      "entryDate": "2023-02-15",
      "lastOperationTime": "2023-12-07 15:50:45"
    },
    {
      "id": 6,
      "name": "埃尔文",
      "gender": "男",
      "avatar": "demo-avatar/erwin.jpg",
      "position": "校长",
      "entryDate": "2023-03-20",
      "lastOperationTime": "2023-12-06 10:30:00"
    },
    {
      "id": 7,
      "name": "萨莎",
      "gender": "女",
      "avatar": "demo-avatar/sasha.jpg",
      "position": "后勤部长",
      "entryDate": "2023-04-01",
      "lastOperationTime": "2023-12-05 18:00:00"
    }
  ]
}

4.启动JSON Server

在包含db.json文件的目录中打开命令提示符,运行:

json-server --watch db.json --port 3000

看到Hi之后,意味着我们成功在本地运行了一个JSON Server服务器,从Loading db.json可以看到,服务器加载了我们的db.json文件,并且正在监听3000端口

在浏览器中输入http://localhost:3000/empList,即可看到员工数据。

如果我们想查询,可以在url后面加一个问号,输入查询参数即可。例如想查询名字为艾伦的员工:

http://localhost:3000/empList?name=艾伦

回车后即可筛选出艾伦的信息。

现在我们将empList中的内容清空。由于要向服务器请求数据,所以应该在searchEmployees函数做手脚。我们不再将搜索条件输出到控制台,而是发送Ajax请求获取数据

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>进击!巨人中学校</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
        /* 原有的CSS样式保持不变 */
        .top-nav {
            background-color: #333;
            padding: 15px;
            overflow: hidden;
        }
        
        .nav-title {
            float: left;
            font-size: 24px;
            font-weight: bold;
            margin: 0;
            color:#f0f0f0;
        }
        
        .nav-logout {
            float: right;
            font-size: 16px;
            margin-top: 8px;
        }
        
        .nav-logout a {
            color: white;
            text-decoration: none;
        }
        
        .nav-logout a:hover {
            text-decoration: underline;
        }
​
        .search-form {
            padding: 20px;
            background-color: #f8f9fa;
            border: 1px solid #e9ecef;
            margin: 20px;
            border-radius: 5px;
        }
        
        .search-form form {
            display: flex;
            align-items: center;
            gap: 15px;
            flex-wrap: wrap;
        }
        
        .form-group {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        .form-group label {
            font-weight: 500;
            min-width: 60px;
        }
        
        .form-group input,
        .form-group select {
            padding: 8px 12px;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 14px;
        }
        
        .form-group input {
            width: 150px;
        }
        
        .form-group select {
            width: 120px;
        }
        
        .form-buttons {
            margin-left: auto;
            display: flex;
            gap: 10px;
        }
        
        .btn {
            padding: 8px 20px;
            border: none;
            border-radius: 4px;
            font-size: 14px;
            cursor: pointer;
            font-weight: 500;
        }
        
        .btn-primary {
            background-color: #007bff;
            color: white;
        }
        
        .btn-primary:hover {
            background-color: #0056b3;
        }
        
        .btn-secondary {
            background-color: #6c757d;
            color: white;
        }
        
        .btn-secondary:hover {
            background-color: #545b62;
        }
​
        .table-container {
            margin: 20px;
            border-radius: 5px;
            overflow: hidden;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        
        .data-table {
            width: 100%;
            border-collapse: collapse;
            background-color: white;
        }
        
        .data-table th,
        .data-table td {
            padding: 12px 15px;
            text-align: center;
            border-bottom: 1px solid #e9ecef;
        }
        
        .data-table th {
            background-color: #f8f9fa;
            font-weight: 600;
            color: #495057;
            font-size: 14px;
        }
        
        .data-table tbody tr {
            transition: background-color 0.2s;
        }
        
        .data-table tbody tr:hover {
            background-color: #f8f9fa;
        }
        
        .btn-edit {
            background-color: #28a745;
            color: white;
            padding: 5px 12px;
            margin-right: 5px;
        }
        
        .btn-edit:hover {
            background-color: #218838;
        }
        
        .btn-delete {
            background-color: #dc3545;
            color: white;
            padding: 5px 12px;
        }
        
        .btn-delete:hover {
            background-color: #c82333;
        }
​
        .avatar {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            object-fit: cover;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- 顶部导航栏 -->
        <div class="top-nav">
            <h1 class="nav-title">进击!巨人中学校</h1>
            <div class="nav-logout">
                <a href="#">退出登录</a>
            </div>
        </div>
​
        <!-- 搜索表单区域 -->
        <div class="search-form">
            <form @submit.prevent="searchEmployees">
                <div class="form-group">
                    <label for="name">姓名:</label>
                    <input type="text" id="name" v-model="searchForm.name" placeholder="请输入姓名">
                    <!-- 现在不通过原生的方式操作了,所以name属性可有可无 -->
                </div>
                
                <div class="form-group">
                    <label for="gender">性别:</label>
                    <select id="gender" v-model="searchForm.gender">
                        <option value="">请选择</option>
                        <option value="男"></option>
                        <option value="女"></option>
                    </select>
                </div>
                
                <div class="form-group">
                    <label for="position">职位:</label>
                    <select id="position" v-model="searchForm.position">
                        <option value="">请选择</option>
                        <option value="班主任">班主任</option>
                        <option value="讲师">讲师</option>
                        <option value="学工主管">学工主管</option>
                        <option value="教导主任">教导主任</option>
                        <option value="校长">校长</option>
                        <option value="后勤部长">后勤部长</option>
                        <option value="教研主管">教研主管</option>
                    </select>
                </div>
                
                <div class="form-buttons">
                    <button type="submit" class="btn btn-primary">查询</button>
                    <button type="button" @click="clearSearch" class="btn btn-secondary">清空</button>
                    <!-- @符号是v-on的简写 -->
                </div>
            </form>
        </div>
​
        <!-- 表格展示区 -->
        <div class="table-container">
            <table class="data-table">
                <thead> 
                    <tr>
                        <th>序号</th>
                        <th>姓名</th>
                        <th>性别</th>
                        <th>头像</th>
                        <th>职位</th>
                        <th>入职日期</th>
                        <th>最后操作时间</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(emp, index) in filteredEmpList" :key="emp.id"> 
                        <td>{{ index + 1 }}</td>
                        <td>{{ emp.name }}</td>
                        <td>{{ emp.gender }}</td>
                        <!-- Vue中,标签内部不能有插值表达式 -->
                         <td><img class="avatar" v-bind:src="emp.avatar" :alt="emp.name"></td>
                        <!-- 冒号是v-bind:的缩写形式 -->
                        <td>{{ emp.position }}</td>
                        <td>{{ emp.entryDate }}</td>
                        <td>{{ emp.lastOperationTime }}</td>
                        <td>
                            <button class="btn btn-edit" @click="editEmployee(emp)">编辑</button>
                            <button class="btn btn-delete" @click="deleteEmployee(emp, index)">删除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
​
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        const { createApp } = Vue;
​
​
        createApp({
            data() {
                return {
                    empList: [
​
                    ],
                    searchForm: {
                        name: '',
                        gender: '',
                        position: ''
                    } //数据模型,封装用户输入的查询条件
                }
            },
            computed: {
                filteredEmpList() {
                    return this.empList.filter(emp => {
                        const matchesName = !this.searchForm.name || 
                            emp.name.toLowerCase().includes(this.searchForm.name.toLowerCase());
                        const matchesGender = !this.searchForm.gender || 
                            emp.gender === this.searchForm.gender;
                        const matchesPosition = !this.searchForm.position || 
                            emp.position === this.searchForm.position;
                         
                        return matchesName && matchesGender && matchesPosition;
                    });
                }
            },
            methods: {
              searchEmployees() {
                const params = {};
                if (this.searchForm.name) {
                  params.name_like = this.searchForm.name;
                }
                if (this.searchForm.gender) {
                  params.gender = this.searchForm.gender;
                }
                if (this.searchForm.position) {
                  params.position = this.searchForm.position;
                }
                
                axios.get('http://localhost:3000/empList', { params })
                  .then(response => {
                    console.log('原始响应数据', response.data);
​
                    //如果返回的是数组,则直接赋值给empList
                    if (Array.isArray(response.data)) {
                      this.empList = response.data;
                    }
​
                    //如果返回的是对象
                    else if (typeof response.data === 'object') {
                      //如果返回的是对象,则将data.data赋值给empList
                      this.empList = response.data.data;
                    }
​
                    //其它情况
                    else {
                      //将response.data赋值给empList
                      this.empList = response.data;
                    }
​
                    console.log('过滤后的员工列表:', this.empList);
                  })
                  .catch(error => {
                    console.error('获取员工数据失败:', error);
                    if(error.response) {
                      console.error('响应状态:', error.response.status);
                      console.error('响应数据:', error.response.data);
                    }
                  });
              },
​
              clearSearch() {
                this.searchForm = {
                  name: '',
                  gender: '',
                  position: ''
                };
                //清空后重新搜索
                this.searchEmployees();
            },
​
              editEmployee(emp) {
                alert(`编辑员工:${emp.name}`);
              },
              deleteEmployee(emp, index) {
                if (confirm(`确定要删除员工:${emp.name} 吗?`)) {
                  axios.delete(`http://localhost:3000/empList/${emp.id}`)
                    .then(() => {
                      this.empList.splice(index, 1);
                      alert('员工删除成功');
                    })
                    .catch(error => {
                      console.error('删除员工失败:', error);
                      alert('删除员工失败,请稍后重试');
                    });
                }
              }
            },
        }).mount('#app');
    </script>
</body>
</html>

这里的searchEmployees的防御性编程比较多,实际上只要确定了db.json究竟是数组还是对象即可。

async & await

可以通过async\await让异步变为同步操作,async用来声明一个异步方法,await用来等待异步任务执行。await关键字只在async函数内有效,await关键字取代then函数(成功回调函数),等待获取到请求成功的结果值。

            methods: {
              async searchEmployees() {
                let res = await axios.get('http://localhost:3000/empList');
                this.empList = res.data;
              },
Vue生命周期

现在还有一个问题:当页面一刷新,页面又变回了不展示任何数据的状态。因为只有点击查询按钮时才查询数据。我们希望在页面加载完毕后马上发送请求查询服务器。

Vue的生命周期可以分为八个阶段,每触发一个生命周期事件,会自动执行一个生命周期方法(钩子方法)

beforeCreate:创建前

created:创建后

beforeMount:载入前

mounted:挂载完成

beforeUpdate:数据更新前

updated:数据更新后

beforeUnmount:组件销毁前

unmounted:组件销毁后

            methods: {
              async searchEmployees() {
                let res = await axios.get('http://localhost:3000/empList');
                this.empList = res.data;
              },
 
​
              clearSearch() {
                this.searchForm = {
                  name: '',
                  gender: '',
                  position: ''
                };
                //清空后重新搜索
                this.searchEmployees();
            },
​
              editEmployee(emp) {
                alert(`编辑员工:${emp.name}`);
              },
              deleteEmployee(emp, index) {
                if (confirm(`确定要删除员工:${emp.name} 吗?`)) {
                  axios.delete(`http://localhost:3000/empList/${emp.id}`)
                    .then(() => {
                      this.empList.splice(index, 1);
                      alert('员工删除成功');
                    })
                    .catch(error => {
                      console.error('删除员工失败:', error);
                      alert('删除员工失败,请稍后重试');
                    });
                }
              }
            },
            //钩子函数
            mounted() {
              //页面加载完成后,发送ajax请求,获取员工列表数据
              this.searchEmployees();
            }
        }).mount('#app');

钩子函数要与methods平齐

我们捋一下逻辑:

一上来,Vue接管页面,执行钩子函数mounted.钩子函数里执行searchEmployees.searchEmployees利用Ajax服务器拿到数据,交给前端能处理的empList. 然后computed检测到依赖发生变化,更新filteredEmpList,最后在v-for那里将每个emp对象的属性渲染出来。

如果用户进行查找,相当于改变searchForm的属性(因为v-model将input的内容与searchForm绑定了),一改变computed就能检测到,就调用filterEmpList进行筛选。这样就比点击查询按钮的实时性更高。

对于Vue,一个很直观的感受是:它让代码变得更加灵活了,很多原来写死的东西变得模板化了,这样不仅利于解耦合,也方便维护。

需要注意的是,这个查询按钮实际上是可有可无的,因为我们并没有实现真正的查询,而是利用computed属性进行前端过滤。