赞
踩
本篇文章主要用于完全新手入门,实战操作使用 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', 'admin@clstech.cn', '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 类文件
在 utils 下 修改 R 统一返回结果类
/** * 统一返回结果类 */ @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 部分
到此就结束了,你得页面应该如下、并筛选查询、修改、删除、新增都可以正常使用
以上内容,仅仅是实现过程,更多的描述时间原因不做说明,可看注释或查阅资料
关于头像上传,可先自行实现逻辑,需要的可以代开发系统
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。