本篇文章主要用于完全新手入门,实战操作使用 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两款工具的安装,请自行安装开发工具和环境
所需开发软件下载地址以及代码在本章末尾提供下载
这里创建一个名为 give 的数据库,在本地数据库上右键 新建数据库,按如图所示选择对应字符集与排序规则。
为方便大家节省时间一个个字段创建,这里提供一个简单的用户表示例,可以用于直接创建,在give上右键新建查询,将SQL语句粘贴进去点击运行即可。或在提供的资料代码中找到 give.sql 在give上右键导入运行SQL文件,导入前请先确保数据库创建好。
/*
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;
可能由于网络原因、此步骤部分学友可能无法操作,可以使用文章末尾提供的网盘中 give-init.zip 压缩包,将其解压后,点击Open选项打开即可。
如图所示,打开IDEA后,点击 New Project 在弹出框中选择 Spring Initializr 之后点击Next下一步操作。填写项目信息,这里仅供参考。
根据如图所示选择Lombok、Spring Web、 MySQL Driver 后点击Next 之后 Finish 即可创建SpringBoot项目的基本模板。
在 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两个文件夹
在 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
插件名称 mybatisCodeHelperPro 可在 IDEA 插件中心下载安装。
配置IDEA中数据库连接,如图所示,选择连接数据源 MySQL
填写我们的 数据库地址、账号和密码以及数据库名称give即可
在 schemas 下 选中 cls_user 右键选择 MyBatis generator
按照如下填写与选择 其中 com.example.give 为项目包名
确认无误后,点击OK得到如下 文件
在 com.example.give 下创建 controller 、 utils两个目录,包含 UserController 与 R 两个 Java 类文件
/**
* 统一返回结果类
*/
@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
按照提示选择如下选项
修改 .env.development 文件中 VITE_API_BASE_URL 后端接口请求地址,端口在 后端 application.yaml 配置文件配置为 9001
修改 src\store\modules\user 目录下 index.ts 与 types.ts
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;
}
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
修改 src\api 下 user.ts 的 getUserInfo方法请求方式POST 修改为 Get
修改 src\api 下的 interceptor.ts 在请求时附带 Token
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
2、修改 src\store\modules\user\index.ts 中的setToken(res.data.token); 修改为 setToken(res.data.tokenValue);
后端使用IDEA启动方式很简单,点击绿色播放小按钮即可启动,祝你一路不爆红。
前端启动 在终端中输入 yarn dev 即可启动成功。
到此为止你就可以尝试 访问 http://localhost:3000进行登录了,账号密码 就 创建数据库时候插入的 一条数据 都是 admin
如果没有安装yarn 在终端命令行中输入 如下 进行安装即可
npm install -g yarn --registry=https://registry.npm.taobao.org
在 src\router\routes\modules\dashboard.ts 文件中 增加如下路由配置
{
path: 'users',
name: 'users',
component: () => import('@/views/dashboard/users/index.vue'),
meta: {
locale: 'menu.dashboard.users', // 二级菜单名(语言包键名)
requiresAuth: true, // 是否需要鉴权
roles: ['admin'], // 权限角色
},
},
为 src\views\dashboard\workplace\locale 目录下的 en-US.ts 和 zh-CN.ts 分别增加国际化配置
'menu.dashboard.users': 'User Manage',
'menu.dashboard.users': '用户管理',
export interface ResponseResult<T = any> {
code: number;
status: string;
msg: string;
data: T;
}
在文件开始部分加入引用
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);
}
<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>
到此你的页面应该是这样的,可以看出各个字段地方国际化没有显示出来,别急,我们来添加关于用户的国际化配置文件
在 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 部分
en-US.ts 部分
到此就结束了,你得页面应该如下、并筛选查询、修改、删除、新增都可以正常使用
以上内容,仅仅是实现过程,更多的描述时间原因不做说明,可看注释或查阅资料
关于头像上传,可先自行实现逻辑,也可以先咨询我
以下是关于全部 代码 + 数据库文件 + 安装软件 的下载地址
下载地址:点我下载使用 SpringBoot + Redis …Pro 开发管理系统
提取码:获取提取码可以发私信获取 实在不好意思,哈哈哈。麻烦各位了
以下是更改记录
本文 发布于 2022年3月28日