## Day18 Web前端实战(案例)：修改删除员工、登录打包

---
* 员工管理-修改
* 员工管理-删除
* 登录退出
* 打包部署

  

### 1. 员工管理-修改

#### 修改员工-查询回显

* 点击 “编辑” 按钮，执行根据ID查询员工信息，页面回显。
* 点击 “保存” 按钮，执行修改操作。

<figure><img src="/AiJavaWeb/imgs/jwai18-01.png"><figcaption>图1 修改员工-查询回显</figcaption></figure>

##### src\api\emp.js  添加如下代码

```javascript
//根据ID查询员工
export const queryByIdApi = (id) => request.get(`/emps/${id}`);
```

##### src\views\emp\index.vue   添加或修改如下代码

```vue
<script setup>
  import {queryPageApi, addApi, queryByIdApi} from "@/api/emp";
  ...
  //编辑员工对话框-打开
  const editEmp = async (id) => { 
    dialogVisible.value = true;
    dialogTitle.value = '修改员工';
    //根据ID查询员工
    const result = await queryByIdApi(id);
    if(result.code){//成功
      employee.value = result.data;
    } else {
      ElMessage.error(result.msg);
    }
    //重置表单的校验规则-提示信息
    if (empFormRef.value){
      empFormRef.value.resetFields();
    }
    //对工作经历进行处理
    let exprList = employee.value.exprList;
    if(exprList && exprList.length > 0){
      exprList.forEach((expr) => {
        expr.exprDate=[expr.begin,expr.end];
      })
    }
  }

</script>

      <el-table-column label="操作" align="center">
        <template #default="scope">
          <el-button type="primary" size ="small" @click="editEmp(scope.row.id)"><el-icon><EditPen/></el-icon>编辑</el-button>
          ...
        </template>
      </el-table-column>
```



<figure><img src="/AiJavaWeb/imgs/jwai18-02.png"><figcaption>图2 <mark>修改员工-查询回显验证</mark></figcaption></figure>



#### 修改员工-保存数据

* 点击保存按钮，执行修改数据操作。
* 备注：添加员工与修改员工使用的是同一个表单，需要根据id判断到底执行新增还是修改。

<figure><img src="/AiJavaWeb/imgs/jwai18-03.png"><figcaption>图3 修改员工-保存数据</figcaption></figure>

##### src\api\emp.js  添加如下代码

```javascript
//修改员工
export const updateApi = (emp) => request.put('/emps',emp);
```

##### src\views\emp\index.vue   添加或修改如下代码

```vue
<script setup>
  ...
  import {queryPageApi, addApi, queryByIdApi, updateApi} from "@/api/emp";
  
  //保存员工
  const save = async () => { 
    //表单校验
    if(!empFormRef.value) return;
    empFormRef.value.validate(async(valid)=>{//valid表示是否校验通过：true通过/false不通过
      if(valid){//通过
        let result;
        if(employee.value.id){//修改
          result = await updateApi(employee.value);
        } else {//新增
          result = await addApi(employee.value);
        }
     ...
</script>
```



<figure><img src="/AiJavaWeb/imgs/jwai18-04.png"><figcaption>图4 <mark>修改员工-保存数据验证</mark></figcaption></figure>



### 2. 员工管理-删除

* 点击每条记录之后的“删除”按钮，删除当前这条记录；
* 选择前面的复选框，选中要删除的员工，点击“批量删除”之后，会批量删除员工信息；

<figure><img src="/AiJavaWeb/imgs/jwai18-05.png"><figcaption>图5 员工管理-删除</figcaption></figure>

#### 删除员工-删除单个

* 为 "删除" 按钮绑定事件，触发事件，调用函数
* 发送异步请求到服务端，根据id删除员工信息

##### src\api\emp.js  添加如下代码

```javascript
//删除员工
export const delApi = (ids) => request.delete(`/emps?ids=${ids}`);
```

##### src\views\emp\index.vue   添加或修改如下代码

```vue
<script setup>
  ...
  import {ElMessage, ElMessageBox} from "element-plus";
  import {queryPageApi, addApi, queryByIdApi, updateApi, delApi} from "@/api/emp";
  
  //删除单个员工
  const delEmp = async (id,name) => { 
    ElMessageBox.confirm(`确定删除员工【${name}】吗？`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(async () => {
      const result = await delApi(id);
      if(result.code){
        ElMessage.success('删除成功');
        search();
      } else {
        ElMessage.error(result.msg);
      }
    }).catch(() => {
      ElMessage({
        type: 'info',
        message: '已取消删除'
      });
    });
  }
</script>

      <el-table-column label="操作" align="center">
          ...
          <el-button type="danger" size ="small" @click="delEmp(scope.row.id,scope.row.name)"><el-icon><Delete/></el-icon> 删除</el-button>
        </template>
      </el-table-column>
```



<figure><img src="/AiJavaWeb/imgs/jwai18-06.png"><figcaption>图6 <mark>删除员工-删除单个验证</mark></figcaption></figure>



#### 删除员工-批量删除

* 为表格的复选框绑定事件，点击复选框之后，获取到目前选中的条件的id（多个id可以封装到数组中）。
* 为 "批量删除" 按钮绑定事件, 发送异步请求到服务端，根据id批量删除员工信息。

<figure><img src="/AiJavaWeb/imgs/jwai18-07.png"><figcaption>图7 删除员工-批量删除</figcaption></figure>



##### src\views\emp\index.vue   添加或修改如下代码

```vue
<script>
  //记录勾选的员工的id
  const selectedIds = ref([]);

  //复选框勾选发生变化时触发 selection：当前选中的记录（数组）
  const handleSelectionChange = (selection) => {
    selectedIds.value = selection.map(item => item.id);
  }
  //批量删除员工
  const delBatch = async () => {
    if(selectedIds.value.length === 0){
      ElMessage.warning('请选择要删除的记录');
      return;
    }
    ElMessageBox.confirm(`确定删除选中的${selectedIds.value.length}条员工吗？`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(async () => {
      const result = await delApi(selectedIds.value);
      if(result.code){
        ElMessage.success('删除成功');
        search();
      } else {
        ElMessage.error(result.msg);
      }
    }).catch(() => {
      ElMessage({
        type: 'info',
        message: '已取消删除'
      });
    });
  }
</script>
...
    <el-button type="danger" @click="delBatch">-批量删除</el-button>
...
<el-table :data="empList" border style="width: 100%" @selection-change="handleSelectionChange">
    
```



<figure><img src="/AiJavaWeb/imgs/jwai18-08.png"><figcaption>图8 <mark>删除员工-批量删除验证</mark></figcaption></figure>



### 3. 登录退出

#### 3.1 登录

<figure><img src="/AiJavaWeb/imgs/jwai18-09.png"><figcaption>图9 登录需求</figcaption></figure>

##### src\api\login.js

```js
import request from '@/utils/request'

// 登录
export const loginApi = (data) => request.post('/login',data)
```

##### src\views\login\index.vue  添加或修改代码如下

```vue
<script setup>
  import { ref } from 'vue'
  import { loginApi } from '@/api/login'
  import { ElMessage } from 'element-plus'
  import { useRouter } from 'vue-router'

  let loginForm = ref({username:'', password:''})
  let router = useRouter()

  // 登录
  const login = async () => { 
    const result = await loginApi(loginForm.value)
    if(result.code){
      //提示信息
      ElMessage.success('登录成功')
      //保存登录信息
   
      //跳转
      router.push('/index')
    } else {
      ElMessage.error(result.msg)
    }
  }

  // 重置
  const clear = () => { 
    loginForm.value = {username:'', password:''}
  }
</script>

          <el-button class="button" type="primary" @click="login">登 录</el-button>
          <el-button class="button" type="info" @click="clear">重 置</el-button>
```



<figure><img src="/AiJavaWeb/imgs/jwai18-10.png"><figcaption>图10 <mark>登录验证</mark> </figcaption></figure>



#### 登录

* 问题：目前执行登录操作，登录成功之后，并没有将令牌信息起来，在后续的每次操作中，也就拿不到登录时的令牌信息了。
* 方案：需要在登录成功后，将令牌等信息存储起来。 在后续的请求中，再将令牌取出来，携带到服务端。

<figure><img src="/AiJavaWeb/imgs/jwai18-11.png"><figcaption>图11 登录后令牌处理</figcaption></figure>

#### localStorage

* localStorage是浏览器提供的本地存储机制（5MB）。
* 存储形式为key-value形式，键和值都是字符串类型。
* API方法：
  * localStorage.setItem(key,value)
  * localStorage.getItem(key)
  * localStorage.removeItem(key)
  * localStorage.clear()



##### src\views\login\index.vue  添加或修改代码如下

```js
      //保存登录信息
      localStorage.setItem('loginUser',JSON.stringify(result.data))
```

<figure><img src="/AiJavaWeb/imgs/jwai18-12.png"><figcaption>图12 <mark>登录成功后保存用户信息验证</mark> </figcaption></figure>



#### 携带令牌访问服务端

* 在后续的每一次Ajax请求中都获取localStorage中的令牌，在请求头中将令牌携带到服务端。

<figure><img src="/AiJavaWeb/imgs/jwai18-13.png"><figcaption>图13 请求头中将令牌携带到服务端</figcaption></figure>

<figure><img src="/AiJavaWeb/imgs/jwai18-14.png"><figcaption>图14 请求头中将令牌携带到服务端</figcaption></figure>

##### 服务器端设置权限过滤，如

* cn/dzj/filter/TokenFilter.java  开启 @WebFilter("/*")
* cn/dzj/TliasWebManagementApplication.java 开启 @ServletComponentScan ///开启了SpringBoot对Servlet组件的支持

##### src\utils\request.js 添加如下代码

```js
//axios的请求request拦截器-获取localstorage中的token，在请求头中增加token请求头
request.interceptors.request.use(
  (config) => { //成功回调
    const loginUser = JSON.parse(localStorage.getItem('loginUser'));
    if(loginUser && loginUser.token)
      config.headers.token = loginUser.token;
      return config
  },
  (error) => { //失败回调
    return Promise.reject (error)
  }
)
```

<figure><img src="/AiJavaWeb/imgs/jwai18-15.png"><figcaption>图15 <mark>携带令牌访问服务端验证</mark> </figcaption></figure>

#### 文件上传携带令牌

##### src\views\emp\index.vue  添加或修改下列代码

```vue
<script>
...
  //获取token
  const token = ref('');

  //初始化查询
  onMounted(() => {
    search();
    queryAllDepts();
    getToken(); //获取token
  })

  //获取token
  const getToken= () => {
    const loginUser = JSON.parse(localStorage.getItem('loginUser'));
    if(loginUser && loginUser.token){
      token.value = loginUser.token;
    }
  }
...
</script>

      <!-- 第五行 -->
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item label="头像">
            <el-upload
              class="avatar-uploader"
              action="/api/upload"
              :headers="{'token':token}"
              :show-file-list="false"
              :on-success="handleAvatarSuccess"
              :before-upload="beforeAvatarUpload"
              >
              <img v-if="employee.image" :src="employee.image" class="avatar" />
              <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
            </el-upload>
          </el-form-item>
        </el-col>
      </el-row>
```

<figure><img src="/AiJavaWeb/imgs/jwai18-16.png"><figcaption>图16 <mark>文件上传携带令牌验证</mark> </figcaption></figure>

#### 功能完善

* 目前，即使用户未登录的情况下访问服务器，服务器会响应401状态码，但是前端并不会跳转到登录页面。

  

<figure><img src="/AiJavaWeb/imgs/jwai18-17.png"><figcaption>图17 响应401状态码跳转到登录页面</figcaption></figure>

##### src\utils\request.js 添加或修改如下代码

```js
...
import { ElMessage } from 'element-plus'
import router from '../router'

...
//axios的响应 response 拦截器
request.interceptors.response.use(
  (response) => { //成功回调
    return response.data
  },
  (error) => { //失败回调
    if(error.response.status === 401) { //全等
      ElMessage.error('登录已过期，请重新登录！')
      localStorage.removeItem('loginUser')
      //跳转到登录页面
      router.push('/login')
    } else
      ElMessage.error("接口访问异常")
    return Promise.reject(error)
  }
)

export default request
```

<figure><img src="/AiJavaWeb/imgs/jwai18-18.png"><figcaption>图18 <mark>响应401状态码</mark> </figcaption></figure>



#### 3.2 退出

<figure><img src="/AiJavaWeb/imgs/jwai18-19.png"><figcaption>图19 退出需求</figcaption></figure>

* 展示当前登录用户。
* 退出登录。

#### 展示当前登录用户

##### src\views\layout\index.vue  添加、修改代码如下

```vue
<script setup>
  import { ref, onMounted } from 'vue'
  // 获取登录用户名
  const loginName = ref('')
  onMounted(() => {
  onMounted(() => {
    const loginUser = JSON.parse(localStorage.getItem('loginUser'));
    if(loginUser && loginUser.name){
      loginName.value = loginUser.name;
    }
  })
</script>
。。。
            <el-icon><SwitchButton /></el-icon> 退出登录 【{{loginName}}】
```

<figure><img src="/AiJavaWeb/imgs/jwai18-20.png"><figcaption>图20 <mark>登录展示当前登录用户验证</mark> </figcaption></figure>

#### 退出登录

##### src\views\layout\index.vue  添加、修改代码如下

```vue
<script setup>
  import { ref, onMounted } from 'vue'
  import { ElMessage, ElMessageBox } from 'element-plus'
  import { useRouter } from 'vue-router' 

  const router = useRouter()

  // 获取登录用户名
  ....
  //退出登录
  const logout = () => {
    //弹出确认框
    ElMessageBox.confirm('您确认退出登录吗?', '提示', {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(async () =>  {//确认
      ElMessage.success('退出成功');
      localStorage.removeItem('loginUser');
      //跳转页面-登录
      router.push('/login');
    }).catch( () => {
      ElMessage.info('您已取消退出登录');
    })
  }
</script>
...
          <a href="javascript:void(0)" @click="logout">
            <el-icon><SwitchButton /></el-icon> 退出登录 【{{loginName}}】
          </a>
```

<figure><img src="/AiJavaWeb/imgs/jwai18-21.png"><figcaption>图21 <mark>登录退出验证</mark> </figcaption></figure>

#### 修改密码





### 4. 项目打包部署

#### 4.1 打包

<figure><img src="/AiJavaWeb/imgs/jwai18-22.png"><figcaption>图22 前端部署</figcaption></figure>

<figure><img src="/AiJavaWeb/imgs/jwai18-23.png"><figcaption>图23 前端打包</figcaption></figure>



#### 4.2 部署（nginx）

* 介绍：Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件（IMAP/POP3）代理服务器。其特点是占有内存少，并发能力强，在各大型互联网公司都有非常广泛的使用。
* 官网：https://nginx.org/

<figure><img src="/AiJavaWeb/imgs/jwai18-24.png"><figcaption>图24 Nginx安装、前端部署</figcaption></figure>

* 部署：将打包好的 dist 目录下的文件，复制到nginx安装目录的html目录下。
* 启动：双击 nginx.exe 文件即可，Nginx服务器默认占用 80 端口号。
* Nginx默认占用80端口号，如果80端口号被占用，可以在nginx.conf中修改端口号。(netstat -ano | findStr  80)

* <mark>vite.config.js是项目开发时的配置文件、项目打包是将src下的源代码混淆打包、所以项目部署后运行时该配置文件不起作用</mark>

##### G:\develop\nginx-1.22.1\conf\nginx.conf 

```

#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        client_max_body_size 10m;

        location / {
           root   html;
           index  index.html index.htm;
           try_files $uri $uri/ /index.html;
        }
        
       location ^~ /api/ {
          rewrite ^/api/(.*)$ /$1 break;
          proxy_pass http://localhost:8080;
       }
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
           root   html;
        }
   }
}

```

<figure><img src="/AiJavaWeb/imgs/jwai18-25.png"><figcaption>图25 <mark>前端部署验证</mark> </figcaption></figure>

#### 小结

前端项目的打包部署

* 打包：运行 build ---> dist
* 部署：将dist目录下的打包好的文件 ---> nginx/html 目录中
* nginx命令:
  * 启动：nginx.exe
  * 重载：nginx.exe -s reload
  * 停止：nginx.exe -s stop



<figure><img src="/AiJavaWeb/imgs/jwai18-26.png"><figcaption>图26  小结</figcaption></figure>

#### 班级、学员管理自行完成

<figure><img src="/AiJavaWeb/imgs/jwai18-27.png"><figcaption>图27 班级管理、学员管理自行完成</figcaption></figure>



