使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统

文章目录

  • 使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统
  • 前言
  • 一、后端开发
    • 1. 数据库创建
    • 2. 项目创建
    • 3. 增加依赖包与修改配置
    • 3. 使用插件快速创建 mapper、xml、service
    • 4. 创建 controller
  • 二、前端开发
    • 1.使用VSCode 打开修改参数配置等
    • 2. 用户管理CRUD添加
      • (1)、添加用户管理路由配置
      • (2) 、添加用户管理国际化配置
      • (3) 、添加用户管理页面内容


前言

本篇文章主要用于完全新手入门,实战操作使用 SpringBoot + Redis + Vue3 + ArcoPro 等开发一个简单管理系统的CRUD(增删改查)操作。并不做过多深入的讲解。

本文将分为两部分:后端开发、前端开发。

其中涉及到的环境安装需自行安装好 JDK1.8、MySQL5.7+、Redis 5.0、Node 14.18.1、IDEA、VSCode、Navicat premium 数据管理工具

如果已安装过,就无需在重复安装。切记Node版本最好一致,以免出现各种奇奇怪怪的问题

后面会继续写 搭配 ElasticSearch 、RabbitMQ 完成搜索推荐、位置距离推荐使用的,包含小程序、APP等 结合本套 教程以及完善如密码、安全、OSS存储 等功能案例

内容有不明之处或错误之处可指出,看到第一时间更正

适用于Windows 操作系统,这里以 Windows11 为例。其中不包含IDEA与VSCode两款工具的安装,请自行安装开发工具和环境
所需开发软件下载地址以及代码在本章末尾提供下载

最终效果图
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第1张图片

一、后端开发

1. 数据库创建

这里创建一个名为 give 的数据库,在本地数据库上右键 新建数据库,按如图所示选择对应字符集与排序规则。
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第2张图片
为方便大家节省时间一个个字段创建,这里提供一个简单的用户表示例,可以用于直接创建,在give上右键新建查询,将SQL语句粘贴进去点击运行即可。或在提供的资料代码中找到 give.sql 在give上右键导入运行SQL文件,导入前请先确保数据库创建好。
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第3张图片

/*
 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 50737
 Source Host           : localhost:3306
 Source Schema         : give

 Target Server Type    : MySQL
 Target Server Version : 50737
 File Encoding         : 65001

 Date: 28/03/2022
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for cls_user
-- ----------------------------
DROP TABLE IF EXISTS `cls_user`;
CREATE TABLE `cls_user`  (
  `id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `nickname` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
  `age` int(3) NULL DEFAULT NULL COMMENT '年龄',
  `sex` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '性别',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号码',
  `role` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of cls_user
-- ----------------------------
INSERT INTO `cls_user` VALUES ('1498674755007102976', 'admin', 'admin', '小可', 'https://www.proyy.com/api/pictures/random/index.php?type=tx', 18, '1', '[email protected]', '19911110000', 'admin', '2022-03-14 21:56:21', '2022-03-14 21:56:25');

SET FOREIGN_KEY_CHECKS = 1;

2. 项目创建

可能由于网络原因、此步骤部分学友可能无法操作,可以使用文章末尾提供的网盘中 give-init.zip 压缩包,将其解压后,点击Open选项打开即可。

如图所示,打开IDEA后,点击 New Project 在弹出框中选择 Spring Initializr 之后点击Next下一步操作。填写项目信息,这里仅供参考。
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第4张图片
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第5张图片
根据如图所示选择Lombok、Spring Web、 MySQL Driver 后点击Next 之后 Finish 即可创建SpringBoot项目的基本模板。
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第6张图片

3. 增加依赖包与修改配置

在 pom.xml 的 dependencies 中 增加如下依赖

<!--Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.29.0</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.21</version>
</dependency>

<!-- Dynamic datasource 动态数据源 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

<!-- JSON 解 析 工 具 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>

<!-- Sa-Token 整合 Redis (使用jdk默认序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis</artifactId>
    <version>1.29.0</version>
</dependency>

<!-- 提供Redis连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

由于个人习惯yaml风格、所以这里 application.properties 修改为 application.yaml ,并删除static、templates两个文件夹
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第7张图片
在 application.yaml 中加入如下配置参数

server:
  # 端口
  port: 9001

# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: token
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: -1
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: false
  # token风格
  token-style: uuid
  # 是否输出操作日志
  is-log: false


spring:
  # redis配置
  redis:
    # Redis数据库索引(默认为0)
    database: 2
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:give}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
          username: ${MYSQL_USER:root}
          password: ${MYSQL_PWD:123456}
  servlet:
    multipart:
      # 配置上传文件大小限制
      max-file-size: 10MB
      max-request-size: 100MB

3. 使用插件快速创建 mapper、xml、service

插件名称 mybatisCodeHelperPro 可在 IDEA 插件中心下载安装。

配置IDEA中数据库连接,如图所示,选择连接数据源 MySQL
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第8张图片
填写我们的 数据库地址、账号和密码以及数据库名称give即可
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第9张图片
在 schemas 下 选中 cls_user 右键选择 MyBatis generator
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第10张图片
按照如下填写与选择 其中 com.example.give 为项目包名
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第11张图片
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第12张图片
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第13张图片
确认无误后,点击OK得到如下 文件

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第14张图片

4. 创建 controller

在 com.example.give 下创建 controller 、 utils两个目录,包含 UserController 与 R 两个 Java 类文件

在 utils 下 修改 R 统一返回结果类
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第15张图片

/**
 * 统一返回结果类
 */
@Data
@AllArgsConstructor
public class R {

    // 返回状态码
    private int code;

    // 返回消息
    private String msg;

    // 状态
    private String status;

    // 返回数据
    private Object data;

    // 成功(无数据)
    public static R success(String message) {
        return new R(20000, message, "ok", null);
    }

    // 成功
    public static R success(String message, Object data) {
        return new R(20000, message,"ok",  data);
    }

    // 失败(无数据)
    public static R fail(int code, String message) {
        return new R(code, message, "fail", null);
    }

}

在 新建的 UserController 中加入如下代码,实际业务中 主要密码加密解密判断等操作,这里就不做密码判断校验

/**
 * 用户管理
 */
@RestController
@RequestMapping(value = "/api/user")
public class UserController {


    @Autowired
    private ClsUserService clsUserService;


    /**
     * 用户登录
     * @param accouut 账号和密码
     * @return 登录结果
     */
    @PostMapping(value = "/login")
    public R login(@RequestBody Map<String, Object> accouut) {
        String username = String.valueOf(accouut.get("username"));
        String password = String.valueOf(accouut.get("password"));

        final ClsUser clsUser = clsUserService.getOne(new QueryWrapper<ClsUser>().eq("username", username));

        // 登录
        // 实际业务中 主要密码加密解密判断等操作
        StpUtil.login(clsUser.getUsername());
        return R.success("登录成功", StpUtil.getTokenInfo());

    }


    /**
     * 注销登录
     */
    @PostMapping(value = "/logout")
    public R login() {
        StpUtil.logout();
        return R.success("注销成功");
    }

    /**
     * 获取所有用户数据
     *
     * @return 所有用户数据
     */
    @GetMapping(value = "/list")
    public R list(ClsUser clsUser, @RequestParam(value = "current") int current, @RequestParam(value = "pageSize") int pageSize) {
        IPage page = clsUserService.page(new Page<>(current, pageSize), Wrappers.query(clsUser));
        return R.success("获取用户列表", page);
    }


    /**
     * 获取个人信息
     *
     * @return
     */
    @GetMapping(value = "/info")
    @SaCheckLogin
    public R info() {
        final ClsUser clsUser = clsUserService.getOne(new QueryWrapper<ClsUser>().eq("username", StpUtil.getLoginIdAsString()));
        return R.success("获取用户信息成功", clsUser);
    }



    @PostMapping(value ="/save")
    public R save(@RequestBody ClsUser clsUser) {
        boolean save = false;

        // 实际业务中 主要密码加密解密判断等操作

        if(ObjectUtil.isNotNull(clsUser.getPassword()) && ObjectUtil.isNotEmpty(clsUser.getPassword()) && ObjectUtil.isNotEmpty(clsUser.getUsername())) {
            clsUser.setId(IdUtil.getSnowflakeNextIdStr());
            clsUser.setCreateTime(DateUtil.date());
            save = clsUserService.save(clsUser);
        }
        if (save) {
            return R.success("新增用户成功", clsUser);
        } else {
            return R.fail(20001, "新增用户失败");
        }
    }

    @PutMapping(value = "/update")
    public R update(@RequestBody ClsUser clsUser) {
        clsUser.setUpdateTime(DateUtil.date());
        boolean b = clsUserService.updateById(clsUser);
        if (b) {
            return R.success("更新成功");
        }else {
            return R.fail(20001, "更新失败");
        }
    }

    @DeleteMapping(value = "/delete")
    public R delUser(@RequestParam(value = "id") String id) {
        final boolean res = clsUserService.removeById(id);
        if (res) {
            return R.success("删除用户成功", 20000);
        } else {
            return R.fail(20001, "删除用户失败");
        }
    }


}

二、前端开发

安装项目模版的工具,在命令行中 执行 如下命令

npm i -g arco-cli

在任意盘符下,创建一个文件夹, 这里是在D盘VueProjects文件夹下执行如下命令新建项目

arco init arco-give


按照提示选择如下选项

1.使用VSCode 打开修改参数配置等

修改 .env.development 文件中 VITE_API_BASE_URL 后端接口请求地址,端口在 后端 application.yaml 配置文件配置为 9001
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第16张图片
修改 src\store\modules\user 目录下 index.ts 与 types.ts

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第17张图片

export type RoleType = '' | '*' | 'admin' | 'user';
export interface UserState {
  id?: string;
  username?: string;
  password?: string;
  nickname?: string;
  avatar?: string;
  age?: number;
  sex?: string;
  idcard?: string;
  name?: string;
  email?: string;
  phone?: string;
  tel?: string;
  enable?: string;
  createTime?: string;
  updateTime?: string;
  roleId?: string;
  role: RoleType;
}

修改红框部分
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第18张图片

state: (): UserState => ({
    id: undefined,
    username: undefined,
    password: undefined,
    nickname: undefined,
    avatar: undefined,
    age: undefined,
    sex: undefined,
    idcard: undefined,
    name: undefined,
    email: undefined,
    phone: undefined,
    tel: undefined,
    enable: undefined,
    createTime: undefined,
    updateTime: undefined,
    roleId: undefined,
    role: '',
  }),

在 src\mock 目录下 修改 user.ts 增加 mock: false
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第19张图片
修改 src\api 下 user.ts 的 getUserInfo方法请求方式POST 修改为 Get

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第20张图片
修改 src\api 下的 interceptor.ts 在请求时附带 Token
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第21张图片

axios.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // let each request carry token
    // this example using the JWT token
    // Authorization is a custom headers key
    // please modify it according to the actual situation
    const token = getToken();
    if (token) {
      config.headers = {
        token,
      };
      // if (!config.headers) {
      //   config.headers = {
      //     token,
      //   };
      // }
      // config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    // do something
    return Promise.reject(error);
  }
);

修改ArcoPro在登录时候,获取token值进行保存相关配置
1、 修改 src\api\user.ts 文件中 LoginRes 接口中 token 更改为 tokenValue
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第22张图片
2、修改 src\store\modules\user\index.ts 中的setToken(res.data.token); 修改为 setToken(res.data.tokenValue);
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第23张图片
后端使用IDEA启动方式很简单,点击绿色播放小按钮即可启动,祝你一路不爆红。
前端启动 在终端中输入 yarn dev 即可启动成功。
到此为止你就可以尝试 访问 http://localhost:3000进行登录了,账号密码 就 创建数据库时候插入的 一条数据 都是 admin

如果没有安装yarn 在终端命令行中输入 如下 进行安装即可

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

2. 用户管理CRUD添加

(1)、添加用户管理路由配置

在 src\router\routes\modules\dashboard.ts 文件中 增加如下路由配置

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第24张图片

{
   path: 'users',
   name: 'users',
   component: () => import('@/views/dashboard/users/index.vue'),
   meta: {
     locale: 'menu.dashboard.users', // 二级菜单名(语言包键名)
     requiresAuth: true, // 是否需要鉴权
     roles: ['admin'], // 权限角色
   },
 },

(2) 、添加用户管理国际化配置

为 src\views\dashboard\workplace\locale 目录下的 en-US.ts 和 zh-CN.ts 分别增加国际化配置
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第25张图片

'menu.dashboard.users': 'User Manage',

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第26张图片

'menu.dashboard.users': '用户管理',

此时后台中就出现 用户管理
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第27张图片

(3) 、添加用户管理页面内容

  1. 在 src\api 增加 base.ts 文件用于统一请求后返回结果
    使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第28张图片
export interface ResponseResult<T = any> {
  code: number;
  status: string;
  msg: string;
  data: T;
}

  1. 在 src\api\user.ts 文件中 添加如下 接口请求封装

在文件开始部分加入引用

import qs from 'query-string';
import { UserState } from '@/store/modules/user/types';
import { ResponseResult } from './base';

在末尾处增加如下内容


export interface UserParams extends Partial<UserState> {
  current: number;
  pageSize: number;
}

export interface UserListRes {
  records: UserState[];
  total: number;
}

export function queryUserList(params: UserParams) {
  return axios.get<UserListRes>('/api/user/list', {
    params,
    paramsSerializer: (obj) => {
      return qs.stringify(obj, {
        // 如果为Null或undefined、不参与拼接
        skipNull: true,
        // 如果为空字符串、不参与拼接
        skipEmptyString: true,
      });
    },
  });
}

/// 更新操作
export function addUser(params: {
  id: string;
  username: string;
  password: string;
  nickname?: string;
  avatar?: string;
  age?: number;
  sex: string;
  email?: string;
  phone?: string;
  role?: string;
  createTime?: string;
  updateTime?: string;
}) {
  return axios.post<ResponseResult, ResponseResult>('/api/user/save', params);
}

/// 删除账号
export function delUser(params: { id: string }) {
  return axios.delete<ResponseResult, ResponseResult>('/api/user/delete', {
    params,
    paramsSerializer: (obj) => {
      return qs.stringify(obj);
    },
  });
}

/// 更新操作
export function updateUser(params: {
  id: string;
  username: string;
  nickname?: string;
  avatar?: string;
  age?: number;
  sex: string;
  email?: string;
  phone?: string;
  role?: string;
  updateTime?: string;
}) {
  return axios.put<ResponseResult, ResponseResult>('/api/user/update', params);
}

  1. 在 src\views\dashboard 目录下新建 users 目录 并且在 该目录下新建 index.vue 文件
    使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第29张图片
    加入如下代码即可、关于什么意思,那就只能麻烦大家去多多学习了,本文只描述搭建开发过程
<template>
  <div class="container">
    <Breadcrumb :items="['menu.system', 'menu.system.user']" />
    <a-card class="general-card" :title="$t('menu.system.user')">
      <a-row>
        <a-col :flex="1">
          <a-form
            :model="formModel"
            :label-col-props="{ span: 6 }"
            :wrapper-col-props="{ span: 18 }"
            label-align="left"
          >
            <a-row :gutter="16">
              <a-col :span="8">
                <a-form-item
                  field="number"
                  :label="$t('searchTable.form.number')"
                >
                  <a-input
                    v-model="formModel.id"
                    :placeholder="$t('searchTable.form.number.placeholder')"
                  />
                </a-form-item>
              </a-col>
              <a-col :span="8">
                <a-form-item
                  field="username"
                  :label="$t('searchTable.form.username')"
                >
                  <a-input
                    v-model="formModel.username"
                    :placeholder="$t('searchTable.form.username.placeholder')"
                  />
                </a-form-item>
              </a-col>
              <a-col :span="8">
                <a-form-item
                  field="nickname"
                  :label="$t('searchTable.form.nickname')"
                >
                  <a-input
                    v-model="formModel.nickname"
                    :placeholder="$t('searchTable.form.nickname.placeholder')"
                  />
                </a-form-item>
              </a-col>
            </a-row>
          </a-form>
        </a-col>
        <a-divider style="height: 84px" direction="vertical" />
        <a-col :flex="'86px'" style="text-align: right">
          <a-space direction="vertical" :size="18">
            <a-button type="primary" @click="search">
              <template #icon>
                <icon-search />
              </template>
              {{ $t('searchTable.form.search') }}
            </a-button>
            <a-button @click="reset">
              <template #icon>
                <icon-refresh />
              </template>
              {{ $t('searchTable.form.reset') }}
            </a-button>
          </a-space>
        </a-col>
      </a-row>
      <a-divider style="margin-top: 0" />
      <a-row style="margin-bottom: 16px">
        <a-col :span="16">
          <a-space>
            <a-button type="primary" @click="addClick">
              <template #icon>
                <icon-plus />
              </template>
              {{ $t('searchTable.operation.create') }}
            </a-button>
            <a-upload action="/">
              <template #upload-button>
                <a-button>
                  {{ $t('searchTable.operation.import') }}
                </a-button>
              </template>
            </a-upload>
          </a-space>
        </a-col>
        <a-col :span="8" style="text-align: right">
          <a-button>
            <template #icon>
              <icon-download />
            </template>
            {{ $t('searchTable.operation.download') }}
          </a-button>
        </a-col>
      </a-row>
      <a-table
        row-key="id"
        :loading="loading"
        :pagination="pagination"
        :data="renderData"
        :bordered="false"
        @page-change="onPageChange"
      >
        <template #columns>
          <a-table-column
            :title="$t('searchTable.columns.number')"
            data-index="id"
          />
          <a-table-column
            :title="$t('searchTable.columns.username')"
            data-index="username"
          />
          <a-table-column
            :title="$t('searchTable.columns.nickname')"
            data-index="nickname"
          />
          <a-table-column
            :title="$t('searchTable.columns.avatar')"
            data-index="avatar"
          >
            <template #cell="{ record }">
              <a-space>
                <a-avatar>
                  <img :src="record.avatar" alt="" />
                </a-avatar>
              </a-space>
            </template>
          </a-table-column>
          <a-table-column
            :title="$t('searchTable.columns.age')"
            data-index="age"
          />
          <a-table-column
            :title="$t('searchTable.columns.sex')"
            data-index="sex"
          >
            <template #cell="{ record }">
              {{ $t(`searchTable.form.sex.${record.sex}`) }}
            </template>
          </a-table-column>
          <a-table-column
            :title="$t('searchTable.columns.email')"
            data-index="email"
          />
          <a-table-column
            :title="$t('searchTable.columns.phone')"
            data-index="phone"
          />
          <a-table-column
            :title="$t('searchTable.columns.role')"
            data-index="role"
          >
            <template #cell="{ record }">
              <span v-if="record.role === 'admin'">管理员</span>
              <span v-if="record.role === 'user'">用户</span>
            </template>
          </a-table-column>
          <a-table-column
            :title="$t('searchTable.columns.createTime')"
            data-index="createTime"
          />
          <a-table-column
            :title="$t('searchTable.columns.updateTime')"
            data-index="updateTime"
          />
          <a-table-column
            :title="$t('searchTable.columns.operations')"
            data-index="operations"
          >
            <template #cell="{ record }">
              <!-- <a-button type="text" size="small" @click="showData(record)">
                {{ $t('searchTable.columns.operations.view') }}
              </a-button> -->
              <a-button type="text" size="small" @click="addClick(record)">
                {{ $t('searchTable.columns.operations.update') }}
              </a-button>
              <a-popconfirm
                position="lt"
                :content="
                  $t('searchTable.columns.operations.confirm.delete') +
                  record.username
                "
                @cancel="onCancel()"
                @ok="deleteUser({ id: record.id })"
              >
                <a-button type="text" size="small">
                  {{ $t('searchTable.columns.operations.delete') }}
                </a-button>
              </a-popconfirm>
            </template>
          </a-table-column>
        </template>
      </a-table>
    </a-card>

    <!-- 新增弹窗 -->
    <a-modal
      v-model:visible="addVisible"
      width="auto"
      :mask-closable="false"
      @before-ok="addOk"
      @cancel="addCancel"
    >
      <template #title>
        {{ $t('searchTable.columns.modal.add.title') }}
        <a-progress
          v-if="percent != 0.0"
          type="circle"
          size="mini"
          :percent="percent"
        />
      </template>
      <div>
        <a-form :ref="formRef" :model="form" :style="{ width: '600px' }">
          <a-form-item field="username" label="账号" validate-trigger="input">
            <a-input v-model="form.username" placeholder="请输入用户名称..." />
          </a-form-item>
          <a-form-item
            v-if="isShow"
            field="password"
            label="密码"
            validate-trigger="input"
          >
            <a-input-password
              v-model="form.password"
              placeholder="请输入用户密码..."
            />
          </a-form-item>
          <a-form-item field="nickname" label="昵称" validate-trigger="input">
            <a-input v-model="form.nickname" placeholder="请输入昵称..." />
          </a-form-item>

          <!-- 头像 -->
          <a-form-item field="avatar" label="头像" validate-trigger="input">
            <a-upload
              list-type="picture"
              action="/api/file/upload"
              :limit="1"
              :file-list="file ? [file] : []"
              @success="onSuccess"
            />
          </a-form-item>

          <a-form-item field="age" label="年龄" validate-trigger="input">
            <a-input-number v-model="form.age" placeholder="请输入年龄..." />
          </a-form-item>
          <a-form-item field="sex" label="性别" validate-trigger="input">
            <a-select v-model="form.sex" placeholder="请选择性别 ...">
              <a-option value="0"></a-option>
              <a-option value="1"></a-option>
            </a-select>
          </a-form-item>
          <a-form-item field="email" label="邮箱" validate-trigger="input">
            <a-input v-model="form.email" placeholder="请输入邮箱..." />
          </a-form-item>
          <a-form-item field="phone" label="联系方式" validate-trigger="input">
            <a-input v-model="form.phone" placeholder="请输入联系方式..." />
          </a-form-item>
          <a-form-item field="role" label="角色" validate-trigger="input">
            <a-select v-model="form.role" placeholder="请选择角色 ...">
              <a-option value="admin">管理员</a-option>
              <a-option value="user">用户</a-option>
            </a-select>
          </a-form-item>
        </a-form>
      </div>
    </a-modal>
  </div>
</template>

<script lang="ts">
  import { defineComponent, ref, reactive } from 'vue';
  import { useI18n } from 'vue-i18n';
  import useLoading from '@/hooks/loading';
  import { UserState } from '@/store/modules/user/types';
  import { Pagination } from '@/types/global';
  import {
    UserParams,
    queryUserList,
    delUser,
    updateUser,
    addUser,
  } from '@/api/user';
  import { Message, Notification } from '@arco-design/web-vue';
  import { FormInstance } from '@arco-design/web-vue/es/form';
  import IconPlus from '@arco-design/web-vue/es/icon/icon-plus';

  const generateFormModel = () => {
    return {
      id: '',
      username: '',
      password: '',
      nickname: '',
      avatar: '',
      age: 0,
      sex: '',
      email: '',
      phone: '',
      role: '',
      createTime: '',
      updateTime: '',
    };
  };

  const serarchModel = () => {
    return {
      id: '',
      username: '',
      nickname: '',
      sex: '',
      email: '',
      phone: '',
      role: '',
      createTime: '',
      updateTime: '',
    };
  };

  export default defineComponent({
    components: { IconPlus },
    setup() {
      const { loading, setLoading } = useLoading(true);
      const { t } = useI18n();
      const renderData = ref<UserState[]>([]);
      const formModel = ref(serarchModel());
      const form = ref(generateFormModel());
      const addVisible = ref(false);
      const percent = ref(0.0);
      const formRef = ref<FormInstance>();
      const isShow = ref(true);
      const file = ref('');

      const basePagination: Pagination = {
        current: 1,
        pageSize: 20,
      };

      const pagination = reactive({
        ...basePagination,
      });

      const onSuccess = (currentFile: any) => {
        form.value.avatar = currentFile.response.data.fileName;
        Message.success(currentFile.response.msg);
      };

      const onCancel = () => {
        Message.success(t('searchTable.columns.operations.confirm.cancel'));
      };

      // 获取数据
      const fetchData = async (
        params: UserParams = { current: 1, pageSize: 20 }
      ) => {
        setLoading(true);
        try {
          const { data } = await queryUserList(params);
          renderData.value = data.records;
          pagination.current = params.current;
          pagination.total = data.total;
        } catch (err) {
          // you can report use errorHandler or other
        } finally {
          setLoading(false);
        }
      };

      // 删除账号
      const deleteUser = async (params: { id: string }) => {
        setLoading(true);
        try {
          const { code } = await delUser(params);
          if (code === 20000) {
            Message.success(t('searchTable.operation.success'));
            fetchData();
          } else {
            Message.error(t('searchTable.operation.fail'));
          }
        } catch (err) {
          // console.log(err);
        } finally {
          setLoading(false);
        }
      };

      // 搜索
      const search = () => {
        fetchData({
          ...basePagination,
          ...formModel.value,
        } as unknown as UserParams);
      };

      // 添加弹窗
      const addClick = (params: {
        id: string;
        username: string;
        password: string;
        nickname: string;
        avatar: string;
        age: number;
        sex: string;
        email: string;
        phone: string;
        role: string;
        createTime: string;
        updateTime: string;
      }) => {
        form.value = params;
        if (form.value.id !== undefined) {
          isShow.value = false;
          addVisible.value = true;
        } else {
          isShow.value = true;
          addVisible.value = true;
        }
      };

      // 确认添加和修改
      const addOk = async (done: () => void) => {
        done();

        if (form.value.id === undefined) {
          const { code } = await addUser(form.value);
          if (code === 20001) {
            Notification.error('添加失败!');
          }
          fetchData();
          Notification.success('添加成功!');
        } else {
          const { code } = await updateUser(form.value);
          if (code === 20001) {
            Notification.error('更新失败!');
          }
          fetchData();
          Notification.success('更新成功!');
        }
      };

      // 确认取消
      const addCancel = () => {
        Message.info(t('searchTable.columns.modal.add.confirm.cancel'));
      };

      const onPageChange = (current: number) => {
        fetchData({ ...basePagination, current });
      };

      fetchData();

      // 重置
      const reset = () => {
        formModel.value = serarchModel();
        fetchData();
      };

      return {
        loading,
        search,
        onPageChange,
        renderData,
        pagination,
        formModel,
        reset,
        deleteUser,
        addVisible,
        addClick,
        addOk,
        addCancel,
        onCancel,
        percent,
        form,
        formRef,
        isShow,
        file,
        onSuccess,
      };
    },
  });
</script>

<style scoped lang="less">
  :deep(.arco-table-th) {
    &:last-child {
      .arco-table-th-item-title {
        margin-left: 16px;
      }
    }
  }
</style>

到此你的页面应该是这样的,可以看出各个字段地方国际化没有显示出来,别急,我们来添加关于用户的国际化配置文件
使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第30张图片
在 src\views\dashboard\users 目录下 新建一个 locale 目录,创建两个文件 en-US.ts 和 zh-CN.ts 并在各个文件中加入国际化配置参数

en-US.ts 部分

export default {
  'menu.system.user': 'User Manage',
  'searchTable.form.number': 'User Number',
  'searchTable.form.number.placeholder': 'Please enter Set User Number',
  'searchTable.form.username': 'Username',
  'searchTable.form.username.placeholder': 'Please enter Set Username',
  'searchTable.form.nickname': 'Nickname',
  'searchTable.form.nickname.placeholder': 'Please enter Set Nickname',
  'searchTable.form.status': 'Status',
  'searchTable.form.status.placeholder': 'Please enter Set Status',
  'searchTable.form.selectDefault': 'All',

  'searchTable.form.search': 'Search',
  'searchTable.form.reset': 'Reset',
  'searchTable.form.sex.0': 'Female',
  'searchTable.form.sex.1': 'Male',
  'searchTable.form.status.0': 'Enable',
  'searchTable.form.status.1': 'Disable',
  'searchTable.operation.create': 'Create',
  'searchTable.operation.import': 'Import',
  'searchTable.operation.download': 'Download',
  'searchTable.operation.success': 'Success',
  'searchTable.operation.fail': 'Fail',
  // columns
  'searchTable.columns.number': 'User Number',
  'searchTable.columns.username': 'Username',
  'searchTable.columns.password': 'Password',
  'searchTable.columns.nickname': 'Nickname',
  'searchTable.columns.avatar': 'Avatar',
  'searchTable.columns.age': 'Age',
  'searchTable.columns.sex': 'Sex',
  'searchTable.columns.email': 'E-Mail',
  'searchTable.columns.phone': 'Phone',
  'searchTable.columns.role': 'Role',
  'searchTable.columns.createTime': 'Create Time',
  'searchTable.columns.updateTime': 'Update Time',

  'searchTable.columns.operations': 'Operations',
  'searchTable.columns.operations.view': 'View',
  'searchTable.columns.operations.update': 'Edit',
  'searchTable.columns.operations.delete': 'Delete',
  'searchTable.columns.operations.confirm.delete': 'Confirm deletion',
  'searchTable.columns.operations.confirm.cancel': 'Cancel deletion',

  'searchTable.columns.modal.add.title': 'Add User',
  'searchTable.columns.modal.add.confirm.determine': 'Confirm add',
  'searchTable.columns.modal.add.confirm.cancel': 'Cancel adding',
  'searchTable.columns.modal.update.title': 'Update information',
  'searchTable.columns.modal.update.confirm.determine': 'Confirm update',
  'searchTable.columns.modal.update.confirm.cancel': 'Cancel update',
};

zh-CN.ts 部分

export default {
  'menu.system.user': '用户管理',
  'searchTable.form.number': '用户编号',
  'searchTable.form.number.placeholder': '请输入用户编号',
  'searchTable.form.username': '账号',
  'searchTable.form.username.placeholder': '请输入账号',
  'searchTable.form.nickname': '昵称',
  'searchTable.form.nickname.placeholder': '请输入昵称',
  'searchTable.form.status': '状态',
  'searchTable.form.status.placeholder': '请选择状态',
  'searchTable.form.selectDefault': '全部',

  'searchTable.form.search': '查询',
  'searchTable.form.reset': '重置',
  'searchTable.form.sex.0': '女',
  'searchTable.form.sex.1': '男',
  'searchTable.form.status.0': '启用',
  'searchTable.form.status.1': '禁用',
  'searchTable.operation.create': '新建',
  'searchTable.operation.import': '批量导入',
  'searchTable.operation.download': '下载',
  'searchTable.operation.success': '成功',
  'searchTable.operation.fail': '失败',
  // columns
  'searchTable.columns.number': '编号',
  'searchTable.columns.username': '名称',
  'searchTable.columns.password': '密码',
  'searchTable.columns.nickname': '昵称',
  'searchTable.columns.avatar': '头像',
  'searchTable.columns.age': '年龄',
  'searchTable.columns.sex': '性别',
  'searchTable.columns.email': '邮箱',
  'searchTable.columns.phone': '手机号',
  'searchTable.columns.role': '角色',
  'searchTable.columns.enable': '状态',
  'searchTable.columns.createTime': '创建时间',
  'searchTable.columns.updateTime': '更新时间',

  'searchTable.columns.operations': '操作',
  'searchTable.columns.operations.view': '查看',
  'searchTable.columns.operations.update': '修改',
  'searchTable.columns.operations.delete': '删除',
  'searchTable.columns.operations.confirm.delete': '确认删除',
  'searchTable.columns.operations.confirm.cancel': '取消删除',

  'searchTable.columns.modal.add.title': '添加用户',
  'searchTable.columns.modal.add.confirm.determine': '确定添加',
  'searchTable.columns.modal.add.confirm.cancel': '取消添加',
  'searchTable.columns.modal.update.title': '更新信息',
  'searchTable.columns.modal.update.confirm.determine': '确定更新',
  'searchTable.columns.modal.update.confirm.cancel': '取消更新',
};

添加完成后,在加入这两个文件的引用
在 src\locale 目录下中 有两个 en-US.ts 和 zh-CN.ts 分别在其中增加 引用

zh-CN.ts 部分

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第31张图片

en-US.ts 部分

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第32张图片

到此就结束了,你得页面应该如下、并筛选查询、修改、删除、新增都可以正常使用

使用 SpringBoot + Redis + Vue3 + ArcoPro 开发管理系统_第33张图片

以上内容,仅仅是实现过程,更多的描述时间原因不做说明,可看注释或查阅资料

关于头像上传,可先自行实现逻辑,也可以先咨询我

以下是关于全部 代码 + 数据库文件 + 安装软件 的下载地址

下载地址:点我下载使用 SpringBoot + Redis …Pro 开发管理系统
提取码:获取提取码可以发私信获取 实在不好意思,哈哈哈。麻烦各位了

以下是更改记录
本文 发布于 2022年3月28日

你可能感兴趣的:(技术分享,SpringBoot,Vue,java,spring,springboot,vue.js,mysql)