JavaWeb03-Vue&Ajax

简介: Vue 是一个渐进式 JavaScript 框架,用于构建用户界面。它通过数据绑定和指令简化 DOM 操作,支持组件化开发,结合 Axios 可实现异步数据获取,配合生命周期钩子可自动加载数据,提升用户体验。

什么是Vue

Vue是一款用于构建用户界面的渐进式的JavaScript框架。官方网站:https://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进行了封装,官网:https://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

https://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属性进行前端过滤。

目录
相关文章
|
2月前
|
监控 安全 Unix
iOS 崩溃排查不再靠猜!这份分层捕获指南请收好
从 Mach 内核异常到 NSException,从堆栈遍历到僵尸对象检测,阿里云 RUM iOS SDK 基于 KSCrash 构建了一套完整、异步安全、生产可用的崩溃捕获体系,让每一个线上崩溃都能被精准定位。
620 72
|
23天前
|
数据采集 人工智能 IDE
告别碎片化日志:一套方案采集所有主流 AI 编程工具
本文介绍了一套基于MCP架构的轻量化、多AI工具代码采集方案,支持CLI、IDE等多类工具,实现用户无感、可扩展的数据采集,已对接Aone日志平台,助力AI代码采纳率分析与研发效能提升。
396 46
告别碎片化日志:一套方案采集所有主流 AI 编程工具
|
29天前
|
SQL 人工智能 分布式计算
从工单、文档到结构化知识库:一套可复用的 Agent 知识采集方案
我们构建了一套“自动提取 → 智能泛化 → 增量更新 → 向量化同步”的全链路自动化 pipeline,将 Agent 知识库建设中的收集、提质与维护难题转化为简单易用的 Python 工具,让知识高效、持续、低门槛地赋能智能体。
311 36
|
22天前
|
存储 缓存 数据建模
StarRocks + Paimon: 构建 Lakehouse Native 数据引擎
12月10日,Streaming Lakehouse Meetup Online EP.2重磅回归,聚焦StarRocks与Apache Paimon深度集成,探讨Lakehouse Native数据引擎的构建。活动涵盖架构统一、多源联邦分析、性能优化及可观测性提升,助力企业打造高效实时湖仓一体平台。
298 39
|
15天前
|
机器学习/深度学习 编解码 算法
YOLO26改进 - C2PSA | C2PSA融合TSSA(Token Statistics Self-Attention)令牌统计自注意力,优化遮挡目标感知
本文提出Token统计自注意力(TSSA),通过动态分组与低秩投影实现线性复杂度注意力机制。基于MCR²目标推导,摒弃传统成对相似度计算,显著提升效率。集成于YOLO26的C2PSA模块后,实验验证其在目标检测中性能优越,代码已开源。
|
23天前
|
人工智能 运维 监控
进阶指南:BrowserUse + AgentRun Sandbox 最佳实践
本文将深入讲解 BrowserUse 框架集成、提供类 Manus Agent 的代码示例、Sandbox 高级生命周期管理、性能优化与生产部署策略。涵盖连接池设计、安全控制、可观测性建设及成本优化方案,助力构建高效、稳定、可扩展的 AI 浏览器自动化系统。
419 47
|
8天前
|
弹性计算 运维 应用服务中间件
ECS和轻量应用服务器选哪个?阿里云轻量和ECS有什么区别?2026新手实测
阿里云ECS与轻量应用服务器核心区别:ECS功能全面、弹性强,适合企业级高负载场景;轻量服务器开箱即用、操作简单、性价比高,专为个人开发者、学生及低流量网站(博客、测试环境等)设计。2026实测对比涵盖场景、配置、带宽、计费、运维等维度,助你一键选对!
|
24天前
|
数据采集 监控 数据可视化
快速上手:LangChain + AgentRun 浏览器沙箱极简集成指南
AgentRun Browser Sandbox 是基于云原生函数计算的浏览器沙箱服务,为 AI Agent 提供安全、免运维的浏览器环境。通过 Serverless 架构与 CDP 协议支持,实现网页抓取、自动化操作等能力,并结合 VNC 实时可视化,助力大模型“上网”交互。
427 43
|
存储 缓存 NoSQL
阿里云 Tair KVCache 仿真分析:高精度的计算和缓存模拟设计与实现
阿里云 Tair 推出 KVCache-HiSim,首个高保真 LLM 推理仿真工具。在 CPU 上实现<5%误差的性能预测,成本仅为真实集群的1/39万,支持多级缓存建模与 SLO 约束下的配置优化,助力大模型高效部署。
|
1月前
|
Kubernetes 应用服务中间件 API
应对 Nginx Ingress 退役,是时候理清这些易混淆的概念了
本文希望提供一种更简单的方式,来理解这些容易混淆的技术概念:Nginx、Ingress、Ingress Controller、Ingress API、Nginx Ingress、Higress、Gateway API。
738 70