新增:新增上传头像 API,采用本地存储方式存储头像
This commit is contained in:
parent
e77c77419b
commit
5252c54c48
22
README.md
22
README.md
@ -109,7 +109,8 @@ continew-admin # 全局通用项目配置及依赖版本管理
|
|||||||
│ │ └─ cnadmin
|
│ │ └─ cnadmin
|
||||||
│ │ ├─ webapi
|
│ │ ├─ webapi
|
||||||
│ │ │ └─ controller
|
│ │ │ └─ controller
|
||||||
│ │ │ └─ auth # 认证相关 API
|
│ │ │ ├─ auth # 认证相关 API
|
||||||
|
│ │ │ └─ system # 系统管理相关 API
|
||||||
│ │ └─ ContinewAdminApplication.java # 启动入口
|
│ │ └─ ContinewAdminApplication.java # 启动入口
|
||||||
│ └─ resources # 工程配置目录
|
│ └─ resources # 工程配置目录
|
||||||
│ └─ db.changelog.v0.0.1 # 数据库脚本文件
|
│ └─ db.changelog.v0.0.1 # 数据库脚本文件
|
||||||
@ -153,7 +154,8 @@ continew-admin # 全局通用项目配置及依赖版本管理
|
|||||||
│ │ └─ system # 系统管理相关业务及配置
|
│ │ └─ system # 系统管理相关业务及配置
|
||||||
│ │ ├─ mapper # 系统管理相关 Mapper
|
│ │ ├─ mapper # 系统管理相关 Mapper
|
||||||
│ │ ├─ model # 系统管理相关模型
|
│ │ ├─ model # 系统管理相关模型
|
||||||
│ │ │ └─ entity # 系统管理相关实体对象
|
│ │ │ ├─ entity # 系统管理相关实体对象
|
||||||
|
│ │ │ └─ vo # 系统管理相关 VO(View Object)
|
||||||
│ │ └─ service # 系统管理相关业务接口及实现类
|
│ │ └─ service # 系统管理相关业务接口及实现类
|
||||||
│ │ └─ impl # 系统管理相关业务实现类
|
│ │ └─ impl # 系统管理相关业务实现类
|
||||||
│ └─ resources # 工程配置目录
|
│ └─ resources # 工程配置目录
|
||||||
@ -181,7 +183,8 @@ continew-admin # 全局通用项目配置及依赖版本管理
|
|||||||
│ │ └─ vo # 公共 VO(View Object)
|
│ │ └─ vo # 公共 VO(View Object)
|
||||||
│ └─ util # 公共工具类
|
│ └─ util # 公共工具类
|
||||||
│ ├─ helper # 公共 Helper(助手)
|
│ ├─ helper # 公共 Helper(助手)
|
||||||
│ └─ holder # 公共 Holder(持有者)
|
│ ├─ holder # 公共 Holder(持有者)
|
||||||
|
│ └─ validate # 公共校验器(参数校验,业务校验)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 前端
|
### 前端
|
||||||
@ -189,12 +192,14 @@ continew-admin # 全局通用项目配置及依赖版本管理
|
|||||||
```bash
|
```bash
|
||||||
continew-admin
|
continew-admin
|
||||||
└─ continew-admin-ui # 前端项目
|
└─ continew-admin-ui # 前端项目
|
||||||
|
├─ public # 公共静态资源(favicon.ico、logo.svg)
|
||||||
├─ src
|
├─ src
|
||||||
│ ├─ api # 请求接口
|
│ ├─ api # 请求接口
|
||||||
│ │ └─ auth # 认证模块
|
│ │ ├─ auth # 认证模块
|
||||||
│ ├─ assets # 静态资源
|
│ │ └─ system # 系统管理模块
|
||||||
│ │ └─ style # 全局样式
|
|
||||||
│ ├─ assets # 静态资源
|
│ ├─ assets # 静态资源
|
||||||
|
│ │ ├─ images # 图片资源
|
||||||
|
│ │ └─ style # 样式资源
|
||||||
│ ├─ components # 通用业务组件
|
│ ├─ components # 通用业务组件
|
||||||
│ ├─ config # 全局配置(包含 echarts 主题)
|
│ ├─ config # 全局配置(包含 echarts 主题)
|
||||||
│ │ └─ settings.json # 配置文件
|
│ │ └─ settings.json # 配置文件
|
||||||
@ -205,11 +210,12 @@ continew-admin
|
|||||||
│ ├─ mock # 模拟数据
|
│ ├─ mock # 模拟数据
|
||||||
│ ├─ router # 路由配置
|
│ ├─ router # 路由配置
|
||||||
│ ├─ store # 状态管理中心
|
│ ├─ store # 状态管理中心
|
||||||
│ ├─ types # Typescript 类型
|
│ ├─ types # TypeScript 类型
|
||||||
│ ├─ utils # 工具库
|
│ ├─ utils # 工具库
|
||||||
│ ├─ views # 页面模板
|
│ ├─ views # 页面模板
|
||||||
│ │ ├─ login # 登录模块
|
│ │ ├─ login # 登录模块
|
||||||
│ │ └─ user # 用户模块(用户设置、用户中心)
|
│ │ └─ system # 系统管理模块
|
||||||
|
│ │ └─ user # 用户模块(用户中心)
|
||||||
│ ├─ App.vue # 视图入口
|
│ ├─ App.vue # 视图入口
|
||||||
│ └─ main.ts # 入口文件
|
│ └─ main.ts # 入口文件
|
||||||
├─ .env.development
|
├─ .env.development
|
||||||
|
@ -35,6 +35,7 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
|
|||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
import top.charles7c.cnadmin.common.config.properties.CorsProperties;
|
import top.charles7c.cnadmin.common.config.properties.CorsProperties;
|
||||||
|
import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web MVC 配置
|
* Web MVC 配置
|
||||||
@ -48,6 +49,7 @@ import top.charles7c.cnadmin.common.config.properties.CorsProperties;
|
|||||||
public class WebMvcConfiguration implements WebMvcConfigurer {
|
public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
private final CorsProperties corsProperties;
|
private final CorsProperties corsProperties;
|
||||||
|
private final LocalStorageProperties localStorageProperties;
|
||||||
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
|
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,6 +57,13 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
LocalStorageProperties.LocalStoragePath path = localStorageProperties.getPath();
|
||||||
|
String avatarUtl = "file:" + path.getAvatar().replace("\\", "/");
|
||||||
|
String fileUrl = "file:" + path.getFile().replace("\\", "/");
|
||||||
|
registry.addResourceHandler(localStorageProperties.getFilePattern()).addResourceLocations(fileUrl)
|
||||||
|
.setCachePeriod(0);
|
||||||
|
registry.addResourceHandler(localStorageProperties.getAvatarPattern()).addResourceLocations(avatarUtl)
|
||||||
|
.setCachePeriod(0);
|
||||||
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
||||||
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/")
|
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/")
|
||||||
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
|
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.common.config.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import cn.hutool.system.OsInfo;
|
||||||
|
import cn.hutool.system.SystemUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地存储配置属性
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/1/2 19:43
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "local-storage")
|
||||||
|
public class LocalStorageProperties {
|
||||||
|
|
||||||
|
/** 文件模式 */
|
||||||
|
private String filePattern;
|
||||||
|
|
||||||
|
/** 头像模式 */
|
||||||
|
private String avatarPattern;
|
||||||
|
|
||||||
|
/** 文件大小限制 */
|
||||||
|
private Long maxSizeInMb;
|
||||||
|
|
||||||
|
/** 头像大小限制 */
|
||||||
|
private Long avatarMaxSizeInMb;
|
||||||
|
|
||||||
|
/** Windows 系统本地存储路径 */
|
||||||
|
private LocalStoragePath windows;
|
||||||
|
|
||||||
|
/** Linux 系统本地存储路径 */
|
||||||
|
private LocalStoragePath linux;
|
||||||
|
|
||||||
|
/** MAC 系统本地存储路径 */
|
||||||
|
private LocalStoragePath mac;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取存储路径
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public LocalStoragePath getPath() {
|
||||||
|
OsInfo osInfo = SystemUtil.getOsInfo();
|
||||||
|
if (osInfo.isWindows()) {
|
||||||
|
return windows;
|
||||||
|
}
|
||||||
|
if (osInfo.isMac()) {
|
||||||
|
return mac;
|
||||||
|
}
|
||||||
|
return linux;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地存储路径
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class LocalStoragePath {
|
||||||
|
|
||||||
|
/** 文件存储路径 */
|
||||||
|
private String file;
|
||||||
|
|
||||||
|
/** 头像存储路径 */
|
||||||
|
private String avatar;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.common.consts;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件常量
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/1/2 21:19
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class FileConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像支持的图片类型
|
||||||
|
*/
|
||||||
|
public static final String[] AVATAR_SUPPORTED_IMG_TYPES = {"jpg", "png", "gif", "jpeg"};
|
||||||
|
|
||||||
|
}
|
@ -33,8 +33,10 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||||
|
|
||||||
import cn.dev33.satoken.exception.NotLoginException;
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
import top.charles7c.cnadmin.common.exception.BadRequestException;
|
import top.charles7c.cnadmin.common.exception.BadRequestException;
|
||||||
@ -164,6 +166,18 @@ public class GlobalExceptionHandler {
|
|||||||
return R.fail(HttpStatus.UNAUTHORIZED.value(), "认证失败,无法访问系统资源");
|
return R.fail(HttpStatus.UNAUTHORIZED.value(), "认证失败,无法访问系统资源");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截文件上传异常-超过上传大小限制
|
||||||
|
*/
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||||
|
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
|
||||||
|
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
|
||||||
|
log.error("请求地址'{}',上传文件失败,文件大小超过限制", request.getRequestURI(), e);
|
||||||
|
return R.fail(HttpStatus.BAD_REQUEST.value(),
|
||||||
|
String.format("请上传小于 %s MB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作日志保存异常信息
|
* 操作日志保存异常信息
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.common.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.io.file.FileNameUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件工具类
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/1/2 21:34
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*
|
||||||
|
* @param multipartFile
|
||||||
|
* 源文件对象
|
||||||
|
* @param filePath
|
||||||
|
* 文件路径
|
||||||
|
* @param isKeepOriginalFilename
|
||||||
|
* 是否保留原文件名
|
||||||
|
* @return 目标文件对象
|
||||||
|
*/
|
||||||
|
public static File upload(MultipartFile multipartFile, String filePath, boolean isKeepOriginalFilename) {
|
||||||
|
String originalFilename = multipartFile.getOriginalFilename();
|
||||||
|
String extensionName = FileNameUtil.extName(originalFilename);
|
||||||
|
|
||||||
|
String filename;
|
||||||
|
if (isKeepOriginalFilename) {
|
||||||
|
filename = String.format("%s-%s.%s", FileNameUtil.getPrefix(originalFilename),
|
||||||
|
DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmssS"), extensionName);
|
||||||
|
} else {
|
||||||
|
filename = String.format("%s.%s", IdUtil.fastSimpleUUID(), extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String pathname = filePath + filename;
|
||||||
|
File dest = new File(pathname).getCanonicalFile();
|
||||||
|
// 如果父路径不存在,自动创建
|
||||||
|
if (!dest.getParentFile().exists()) {
|
||||||
|
if (!dest.getParentFile().mkdirs()) {
|
||||||
|
log.error("Create upload file parent path failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 文件写入
|
||||||
|
multipartFile.transferTo(dest);
|
||||||
|
return dest;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -65,6 +65,17 @@ public class LoginHelper {
|
|||||||
return loginUser;
|
return loginUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新登录用户信息
|
||||||
|
*
|
||||||
|
* @param loginUser
|
||||||
|
* 登录用户信息
|
||||||
|
*/
|
||||||
|
public static void updateLoginUser(LoginUser loginUser) {
|
||||||
|
SaHolder.getStorage().set(CacheConstants.LOGIN_USER_CACHE_KEY, loginUser);
|
||||||
|
StpUtil.getTokenSession().set(CacheConstants.LOGIN_USER_CACHE_KEY, loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取登录用户 ID
|
* 获取登录用户 ID
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.common.util.validate;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import top.charles7c.cnadmin.common.exception.ServiceException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务检查工具类(抛出 500 ServiceException)
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @see ServiceException
|
||||||
|
* @since 2023/1/2 22:12
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class CheckUtils extends Validator {
|
||||||
|
|
||||||
|
private static final Class<ServiceException> EXCEPTION_TYPE = ServiceException.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果为空,抛出异常
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* 被检测的对象
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
public static void exIfNull(Object obj, String message) {
|
||||||
|
exIfNull(obj, message, EXCEPTION_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果为空,抛出异常
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* 被检测的字符串
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
public static void exIfBlank(CharSequence str, String message) {
|
||||||
|
exIfBlank(str, message, EXCEPTION_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果相同,抛出异常
|
||||||
|
*
|
||||||
|
* @param obj1
|
||||||
|
* 要比较的对象1
|
||||||
|
* @param obj2
|
||||||
|
* 要比较的对象2
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
public static void exIfEqual(Object obj1, Object obj2, String message) {
|
||||||
|
exIfEqual(obj1, obj2, message, EXCEPTION_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果不相同,抛出异常
|
||||||
|
*
|
||||||
|
* @param obj1
|
||||||
|
* 要比较的对象1
|
||||||
|
* @param obj2
|
||||||
|
* 要比较的对象2
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
public static void exIfNotEqual(Object obj1, Object obj2, String message) {
|
||||||
|
exIfNotEqual(obj1, obj2, message, EXCEPTION_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果条件成立,抛出异常
|
||||||
|
*
|
||||||
|
* @param conditionSupplier
|
||||||
|
* 条件
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
public static void exIfCondition(java.util.function.BooleanSupplier conditionSupplier, String message) {
|
||||||
|
exIfCondition(conditionSupplier, message, EXCEPTION_TYPE);
|
||||||
|
}
|
||||||
|
}
|
@ -14,26 +14,26 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package top.charles7c.cnadmin.common.util;
|
package top.charles7c.cnadmin.common.util.validate;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
|
|
||||||
import top.charles7c.cnadmin.common.exception.BadRequestException;
|
import top.charles7c.cnadmin.common.exception.BadRequestException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查工具类
|
* 校验工具类(抛出 400 BadRequestException)
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2022/12/21 20:56
|
* @since 2022/12/21 20:56
|
||||||
|
* @see BadRequestException
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class CheckUtils {
|
public class ValidationUtils extends Validator {
|
||||||
|
|
||||||
|
private static final Class<BadRequestException> EXCEPTION_TYPE = BadRequestException.class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果为空,抛出异常
|
* 如果为空,抛出异常
|
||||||
@ -44,10 +44,7 @@ public class CheckUtils {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
public static void exIfNull(Object obj, String message) {
|
public static void exIfNull(Object obj, String message) {
|
||||||
if (obj == null) {
|
exIfNull(obj, message, EXCEPTION_TYPE);
|
||||||
log.error(message);
|
|
||||||
throw new BadRequestException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,10 +56,7 @@ public class CheckUtils {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
public static void exIfBlank(CharSequence str, String message) {
|
public static void exIfBlank(CharSequence str, String message) {
|
||||||
if (StrUtil.isBlank(str)) {
|
exIfBlank(str, message, EXCEPTION_TYPE);
|
||||||
log.error(message);
|
|
||||||
throw new BadRequestException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,10 +70,7 @@ public class CheckUtils {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
public static void exIfEqual(Object obj1, Object obj2, String message) {
|
public static void exIfEqual(Object obj1, Object obj2, String message) {
|
||||||
if (ObjectUtil.equals(obj1, obj2)) {
|
exIfEqual(obj1, obj2, message, EXCEPTION_TYPE);
|
||||||
log.error(message);
|
|
||||||
throw new BadRequestException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,10 +84,7 @@ public class CheckUtils {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
public static void exIfNotEqual(Object obj1, Object obj2, String message) {
|
public static void exIfNotEqual(Object obj1, Object obj2, String message) {
|
||||||
if (ObjectUtil.notEqual(obj1, obj2)) {
|
exIfNotEqual(obj1, obj2, message, EXCEPTION_TYPE);
|
||||||
log.error(message);
|
|
||||||
throw new BadRequestException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,9 +96,6 @@ public class CheckUtils {
|
|||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
public static void exIfCondition(java.util.function.BooleanSupplier conditionSupplier, String message) {
|
public static void exIfCondition(java.util.function.BooleanSupplier conditionSupplier, String message) {
|
||||||
if (conditionSupplier != null && conditionSupplier.getAsBoolean()) {
|
exIfCondition(conditionSupplier, message, EXCEPTION_TYPE);
|
||||||
log.error(message);
|
|
||||||
throw new BadRequestException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.common.util.validate;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/1/2 22:12
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
public class Validator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果为空,抛出异常
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* 被检测的对象
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
* @param exceptionType
|
||||||
|
* 异常类型
|
||||||
|
*/
|
||||||
|
protected static void exIfNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) {
|
||||||
|
if (obj == null) {
|
||||||
|
log.error(message);
|
||||||
|
throw ReflectUtil.newInstance(exceptionType, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果为空,抛出异常
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* 被检测的字符串
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
* @param exceptionType
|
||||||
|
* 异常类型
|
||||||
|
*/
|
||||||
|
public static void exIfBlank(CharSequence str, String message, Class<? extends RuntimeException> exceptionType) {
|
||||||
|
if (StrUtil.isBlank(str)) {
|
||||||
|
log.error(message);
|
||||||
|
throw ReflectUtil.newInstance(exceptionType, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果相同,抛出异常
|
||||||
|
*
|
||||||
|
* @param obj1
|
||||||
|
* 要比较的对象1
|
||||||
|
* @param obj2
|
||||||
|
* 要比较的对象2
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
* @param exceptionType
|
||||||
|
* 异常类型
|
||||||
|
*/
|
||||||
|
public static void exIfEqual(Object obj1, Object obj2, String message,
|
||||||
|
Class<? extends RuntimeException> exceptionType) {
|
||||||
|
if (ObjectUtil.equals(obj1, obj2)) {
|
||||||
|
log.error(message);
|
||||||
|
throw ReflectUtil.newInstance(exceptionType, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果不相同,抛出异常
|
||||||
|
*
|
||||||
|
* @param obj1
|
||||||
|
* 要比较的对象1
|
||||||
|
* @param obj2
|
||||||
|
* 要比较的对象2
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
* @param exceptionType
|
||||||
|
* 异常类型
|
||||||
|
*/
|
||||||
|
public static void exIfNotEqual(Object obj1, Object obj2, String message,
|
||||||
|
Class<? extends RuntimeException> exceptionType) {
|
||||||
|
if (ObjectUtil.notEqual(obj1, obj2)) {
|
||||||
|
log.error(message);
|
||||||
|
throw ReflectUtil.newInstance(exceptionType, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果条件成立,抛出异常
|
||||||
|
*
|
||||||
|
* @param conditionSupplier
|
||||||
|
* 条件
|
||||||
|
* @param message
|
||||||
|
* 错误信息
|
||||||
|
* @param exceptionType
|
||||||
|
* 异常类型
|
||||||
|
*/
|
||||||
|
public static void exIfCondition(java.util.function.BooleanSupplier conditionSupplier, String message,
|
||||||
|
Class<? extends RuntimeException> exceptionType) {
|
||||||
|
if (conditionSupplier != null && conditionSupplier.getAsBoolean()) {
|
||||||
|
log.error(message);
|
||||||
|
throw ReflectUtil.newInstance(exceptionType, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,9 +26,9 @@ import cn.hutool.core.bean.BeanUtil;
|
|||||||
import top.charles7c.cnadmin.auth.service.LoginService;
|
import top.charles7c.cnadmin.auth.service.LoginService;
|
||||||
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
|
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
|
||||||
import top.charles7c.cnadmin.common.model.dto.LoginUser;
|
import top.charles7c.cnadmin.common.model.dto.LoginUser;
|
||||||
import top.charles7c.cnadmin.common.util.CheckUtils;
|
|
||||||
import top.charles7c.cnadmin.common.util.SecureUtils;
|
import top.charles7c.cnadmin.common.util.SecureUtils;
|
||||||
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
|
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
|
||||||
|
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
|
||||||
import top.charles7c.cnadmin.system.model.entity.SysUser;
|
import top.charles7c.cnadmin.system.model.entity.SysUser;
|
||||||
import top.charles7c.cnadmin.system.service.UserService;
|
import top.charles7c.cnadmin.system.service.UserService;
|
||||||
|
|
||||||
@ -50,10 +50,11 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
SysUser sysUser = userService.getByUsername(username);
|
SysUser sysUser = userService.getByUsername(username);
|
||||||
|
|
||||||
// 校验
|
// 校验
|
||||||
CheckUtils.exIfNull(sysUser, "用户名或密码错误");
|
ValidationUtils.exIfNull(sysUser, "用户名或密码错误");
|
||||||
Long userId = sysUser.getUserId();
|
Long userId = sysUser.getUserId();
|
||||||
CheckUtils.exIfNotEqual(sysUser.getPassword(), SecureUtils.md5Salt(password, userId.toString()), "用户名或密码错误");
|
ValidationUtils.exIfNotEqual(sysUser.getPassword(), SecureUtils.md5Salt(password, userId.toString()),
|
||||||
CheckUtils.exIfEqual(DisEnableStatusEnum.DISABLE, sysUser.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
|
"用户名或密码错误");
|
||||||
|
ValidationUtils.exIfEqual(DisEnableStatusEnum.DISABLE, sysUser.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
LoginUser loginUser = BeanUtil.copyProperties(sysUser, LoginUser.class);
|
LoginUser loginUser = BeanUtil.copyProperties(sysUser, LoginUser.class);
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.system.model.vo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像信息
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/1/2 16:29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Schema(description = "头像信息")
|
||||||
|
public class AvatarVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像地址
|
||||||
|
*/
|
||||||
|
@Schema(description = "头像地址")
|
||||||
|
private String avatar;
|
||||||
|
}
|
@ -34,4 +34,14 @@ public interface UserService {
|
|||||||
* @return 用户信息
|
* @return 用户信息
|
||||||
*/
|
*/
|
||||||
SysUser getByUsername(String username);
|
SysUser getByUsername(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改头像
|
||||||
|
*
|
||||||
|
* @param avatar
|
||||||
|
* 头像路径
|
||||||
|
* @param userId
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
void updateAvatar(String avatar, Long userId);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
|
||||||
import top.charles7c.cnadmin.system.mapper.UserMapper;
|
import top.charles7c.cnadmin.system.mapper.UserMapper;
|
||||||
@ -42,4 +43,10 @@ public class UserServiceImpl implements UserService {
|
|||||||
public SysUser getByUsername(String username) {
|
public SysUser getByUsername(String username) {
|
||||||
return userMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
|
return userMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAvatar(String avatar, Long userId) {
|
||||||
|
userMapper.update(null,
|
||||||
|
new LambdaUpdateWrapper<SysUser>().set(SysUser::getAvatar, avatar).eq(SysUser::getUserId, userId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
continew-admin-ui/src/api/system/user-center.ts
Normal file
18
continew-admin-ui/src/api/system/user-center.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export interface BasicInfoModel {
|
||||||
|
username: string;
|
||||||
|
nickname: string;
|
||||||
|
gender: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvatarRes {
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
export function uploadAvatar(data: FormData) {
|
||||||
|
return axios.post<AvatarRes>('/system/user/center/avatar', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveUserInfo() {
|
||||||
|
return axios.get('/api/user/save-info');
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
export interface MyProjectRecord {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
peopleNumber: number;
|
|
||||||
contributors: {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
avatar: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
export function queryMyProjectList() {
|
|
||||||
return axios.get('/api/user/my-project/list');
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MyTeamRecord {
|
|
||||||
id: number;
|
|
||||||
avatar: string;
|
|
||||||
name: string;
|
|
||||||
peopleNumber: number;
|
|
||||||
}
|
|
||||||
export function queryMyTeamList() {
|
|
||||||
return axios.get('/api/user/my-team/list');
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LatestActivity {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
avatar: string;
|
|
||||||
}
|
|
||||||
export function queryLatestActivity() {
|
|
||||||
return axios.get<LatestActivity[]>('/api/user/latest-activity');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveUserInfo() {
|
|
||||||
return axios.get('/api/user/save-info');
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BasicInfoModel {
|
|
||||||
username: string;
|
|
||||||
nickname: string;
|
|
||||||
gender: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function userUploadApi(
|
|
||||||
data: FormData,
|
|
||||||
config: {
|
|
||||||
controller: AbortController;
|
|
||||||
onUploadProgress?: (progressEvent: any) => void;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
// const controller = new AbortController();
|
|
||||||
return axios.post('/api/user/upload', data, config);
|
|
||||||
}
|
|
@ -8,6 +8,5 @@ export default {
|
|||||||
'messageBox.noContent': 'No Content',
|
'messageBox.noContent': 'No Content',
|
||||||
'messageBox.switchRoles': 'Switch Roles',
|
'messageBox.switchRoles': 'Switch Roles',
|
||||||
'messageBox.userCenter': 'User Center',
|
'messageBox.userCenter': 'User Center',
|
||||||
'messageBox.userSettings': 'User Settings',
|
|
||||||
'messageBox.logout': 'Logout',
|
'messageBox.logout': 'Logout',
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,6 @@ export default {
|
|||||||
'messageBox.viewMore': '查看更多',
|
'messageBox.viewMore': '查看更多',
|
||||||
'messageBox.noContent': '暂无内容',
|
'messageBox.noContent': '暂无内容',
|
||||||
'messageBox.switchRoles': '切换角色',
|
'messageBox.switchRoles': '切换角色',
|
||||||
'messageBox.userCenter': '用户中心',
|
'messageBox.userCenter': '个人中心',
|
||||||
'messageBox.userSettings': '用户设置',
|
|
||||||
'messageBox.logout': '退出登录',
|
'messageBox.logout': '退出登录',
|
||||||
};
|
};
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
:size="32"
|
:size="32"
|
||||||
:style="{ marginRight: '8px', cursor: 'pointer' }"
|
:style="{ marginRight: '8px', cursor: 'pointer' }"
|
||||||
>
|
>
|
||||||
<img alt="avatar" :src="loginStore.avatar ?? getAvatar(loginStore.gender)" />
|
<img alt="avatar" :src="getAvatar(loginStore)" />
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption>
|
<a-doption>
|
||||||
@ -156,18 +156,10 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</a-doption>
|
</a-doption>
|
||||||
<a-doption>
|
<a-doption>
|
||||||
<a-space @click="$router.push({ name: 'Info' })">
|
<a-space @click="$router.push({ name: 'UserCenter' })">
|
||||||
<icon-user />
|
|
||||||
<span>
|
|
||||||
{{ $t('messageBox.userCenter') }}
|
|
||||||
</span>
|
|
||||||
</a-space>
|
|
||||||
</a-doption>
|
|
||||||
<a-doption>
|
|
||||||
<a-space @click="$router.push({ name: 'Setting' })">
|
|
||||||
<icon-settings />
|
<icon-settings />
|
||||||
<span>
|
<span>
|
||||||
{{ $t('messageBox.userSettings') }}
|
{{ $t('messageBox.userCenter') }}
|
||||||
</span>
|
</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-doption>
|
</a-doption>
|
||||||
|
12
continew-admin-ui/src/hooks/axios.d.ts
vendored
Normal file
12
continew-admin-ui/src/hooks/axios.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import axios, { Axios, AxiosResponse, AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
|
declare module "axios" {
|
||||||
|
interface AxiosResponse<T = any> {
|
||||||
|
success: boolean; // 是否成功
|
||||||
|
code: number; // 状态码
|
||||||
|
msg: string; // 状态信息
|
||||||
|
timestamp: string; // 时间戳
|
||||||
|
data: T; // 返回数据
|
||||||
|
}
|
||||||
|
export function create(config?: AxiosRequestConfig): AxiosInstance;
|
||||||
|
}
|
@ -23,8 +23,7 @@ import locale403 from '@/views/exception/403/locale/en-US';
|
|||||||
import locale404 from '@/views/exception/404/locale/en-US';
|
import locale404 from '@/views/exception/404/locale/en-US';
|
||||||
import locale500 from '@/views/exception/500/locale/en-US';
|
import locale500 from '@/views/exception/500/locale/en-US';
|
||||||
|
|
||||||
import localeUserInfo from '@/views/user/info/locale/en-US';
|
import localeUserCenter from '@/views/system/user/center/locale/en-US';
|
||||||
import localeUserSetting from '@/views/user/setting/locale/en-US';
|
|
||||||
|
|
||||||
import localeSettings from './en-US/settings';
|
import localeSettings from './en-US/settings';
|
||||||
|
|
||||||
@ -62,6 +61,5 @@ export default {
|
|||||||
...locale403,
|
...locale403,
|
||||||
...locale404,
|
...locale404,
|
||||||
...locale500,
|
...locale500,
|
||||||
...localeUserInfo,
|
...localeUserCenter,
|
||||||
...localeUserSetting,
|
|
||||||
};
|
};
|
||||||
|
@ -23,8 +23,7 @@ import locale403 from '@/views/exception/403/locale/zh-CN';
|
|||||||
import locale404 from '@/views/exception/404/locale/zh-CN';
|
import locale404 from '@/views/exception/404/locale/zh-CN';
|
||||||
import locale500 from '@/views/exception/500/locale/zh-CN';
|
import locale500 from '@/views/exception/500/locale/zh-CN';
|
||||||
|
|
||||||
import localeUserInfo from '@/views/user/info/locale/zh-CN';
|
import localeUserCenter from '@/views/system/user/center/locale/zh-CN';
|
||||||
import localeUserSetting from '@/views/user/setting/locale/zh-CN';
|
|
||||||
|
|
||||||
import localeSettings from './zh-CN/settings';
|
import localeSettings from './zh-CN/settings';
|
||||||
|
|
||||||
@ -62,6 +61,5 @@ export default {
|
|||||||
...locale403,
|
...locale403,
|
||||||
...locale404,
|
...locale404,
|
||||||
...locale500,
|
...locale500,
|
||||||
...localeUserInfo,
|
...localeUserCenter,
|
||||||
...localeUserSetting,
|
|
||||||
};
|
};
|
||||||
|
@ -17,8 +17,7 @@ import '@/views/profile/basic/mock';
|
|||||||
import '@/views/visualization/data-analysis/mock';
|
import '@/views/visualization/data-analysis/mock';
|
||||||
import '@/views/visualization/multi-dimension-data-analysis/mock';
|
import '@/views/visualization/multi-dimension-data-analysis/mock';
|
||||||
|
|
||||||
import '@/views/user/info/mock';
|
import '@/views/system/user/center/mock';
|
||||||
import '@/views/user/setting/mock';
|
|
||||||
|
|
||||||
Mock.setup({
|
Mock.setup({
|
||||||
timeout: '600-1000',
|
timeout: '600-1000',
|
||||||
|
@ -13,7 +13,7 @@ export default function setupUserLoginInfoGuard(router: Router) {
|
|||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await loginStore.info();
|
await loginStore.getInfo();
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await loginStore.logout();
|
await loginStore.logout();
|
||||||
|
@ -2,7 +2,7 @@ import { DEFAULT_LAYOUT } from '../base';
|
|||||||
import { AppRouteRecordRaw } from '../types';
|
import { AppRouteRecordRaw } from '../types';
|
||||||
|
|
||||||
const USER: AppRouteRecordRaw = {
|
const USER: AppRouteRecordRaw = {
|
||||||
path: '/user',
|
path: '/system/user',
|
||||||
name: 'user',
|
name: 'user',
|
||||||
component: DEFAULT_LAYOUT,
|
component: DEFAULT_LAYOUT,
|
||||||
meta: {
|
meta: {
|
||||||
@ -13,21 +13,11 @@ const USER: AppRouteRecordRaw = {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'info',
|
path: 'center',
|
||||||
name: 'Info',
|
name: 'UserCenter',
|
||||||
component: () => import('@/views/user/info/index.vue'),
|
component: () => import('@/views/system/user/center/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.user.info',
|
locale: 'menu.user.center',
|
||||||
requiresAuth: true,
|
|
||||||
roles: ['*'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'setting',
|
|
||||||
name: 'Setting',
|
|
||||||
component: () => import('@/views/user/setting/index.vue'),
|
|
||||||
meta: {
|
|
||||||
locale: 'menu.user.setting',
|
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
roles: ['*'],
|
roles: ['*'],
|
||||||
},
|
},
|
||||||
|
@ -75,7 +75,7 @@ const useLoginStore = defineStore('user', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
async info() {
|
async getInfo() {
|
||||||
const res = await getUserInfo();
|
const res = await getUserInfo();
|
||||||
this.setInfo(res.data);
|
this.setInfo(res.data);
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
|
import { UserState } from '@/store/modules/login/types';
|
||||||
import Unknown from '../assets/images/avatar/unknown.png';
|
import Unknown from '../assets/images/avatar/unknown.png';
|
||||||
import Male from '../assets/images/avatar/male.png';
|
import Male from '../assets/images/avatar/male.png';
|
||||||
import Female from '../assets/images/avatar/female.png';
|
import Female from '../assets/images/avatar/female.png';
|
||||||
|
|
||||||
export default function getAvatar(gender: number | undefined) {
|
export default function getAvatar(loginStore: UserState) {
|
||||||
if (gender === 1) {
|
const userAvatar = loginStore.avatar;
|
||||||
|
if (userAvatar) {
|
||||||
|
const baseUrl = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
return `${baseUrl}/avatar/${userAvatar}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userGender = loginStore.gender;
|
||||||
|
if (userGender === 1) {
|
||||||
return Male;
|
return Male;
|
||||||
}
|
}
|
||||||
if (gender === 2) {
|
if (userGender === 2) {
|
||||||
return Female;
|
return Female;
|
||||||
}
|
}
|
||||||
return Unknown;
|
return Unknown;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<div v-if="userInfo">
|
<div v-if="userInfo">
|
||||||
<a-space :size="12">
|
<a-space :size="12">
|
||||||
<a-avatar :size="24">
|
<a-avatar :size="24">
|
||||||
<img :src="userInfo.avatar ?? getAvatar(userInfo.gender)" />
|
<img :src="getAvatar(userInfo)" />
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
<a-typography-text>
|
<a-typography-text>
|
||||||
{{ userInfo.nickname }} {{ $t('monitor.studioPreview.studio') }}
|
{{ userInfo.nickname }} {{ $t('monitor.studioPreview.studio') }}
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'Dashboard', // If you want the include property of keep-alive to take effect, you must name the component
|
name: 'Workplace', // If you want the include property of keep-alive to take effect, you must name the component
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -7,44 +7,38 @@
|
|||||||
:wrapper-col-props="{ span: 16 }"
|
:wrapper-col-props="{ span: 16 }"
|
||||||
>
|
>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="$t('userSetting.basicInfo.form.label.username')"
|
:label="$t('userCenter.basicInfo.form.label.username')"
|
||||||
:rules="[
|
:rules="[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: $t('userSetting.form.error.username.required'),
|
message: $t('userCenter.form.error.username.required'),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
disabled
|
disabled
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model="formData.username"
|
v-model="formData.username"
|
||||||
:placeholder="$t('userSetting.basicInfo.placeholder.username')"
|
:placeholder="$t('userCenter.basicInfo.placeholder.username')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="nickname"
|
field="nickname"
|
||||||
:label="$t('userSetting.basicInfo.form.label.nickname')"
|
:label="$t('userCenter.basicInfo.form.label.nickname')"
|
||||||
:rules="[
|
:rules="[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: $t('userSetting.form.error.nickname.required'),
|
message: $t('userCenter.form.error.nickname.required'),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model="formData.nickname"
|
v-model="formData.nickname"
|
||||||
:placeholder="$t('userSetting.basicInfo.placeholder.nickname')"
|
:placeholder="$t('userCenter.basicInfo.placeholder.nickname')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="gender"
|
field="gender"
|
||||||
:label="$t('userSetting.basicInfo.form.label.gender')"
|
:label="$t('userCenter.basicInfo.form.label.gender')"
|
||||||
:rules="[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: $t('userSetting.form.error.gender.required'),
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<a-radio-group v-model="formData.gender">
|
<a-radio-group v-model="formData.gender">
|
||||||
<a-radio :value="1">男</a-radio>
|
<a-radio :value="1">男</a-radio>
|
||||||
@ -55,10 +49,10 @@
|
|||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="validate">
|
<a-button type="primary" @click="validate">
|
||||||
{{ $t('userSetting.save') }}
|
{{ $t('userCenter.save') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="secondary" @click="reset">
|
<a-button type="secondary" @click="reset">
|
||||||
{{ $t('userSetting.reset') }}
|
{{ $t('userCenter.reset') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -69,7 +63,7 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useLoginStore } from '@/store';
|
import { useLoginStore } from '@/store';
|
||||||
import { FormInstance } from '@arco-design/web-vue/es/form';
|
import { FormInstance } from '@arco-design/web-vue/es/form';
|
||||||
import { BasicInfoModel } from '@/api/user-center';
|
import { BasicInfoModel } from '@/api/system/user-center';
|
||||||
|
|
||||||
const loginStore = useLoginStore();
|
const loginStore = useLoginStore();
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
@ -4,7 +4,7 @@
|
|||||||
<a-list-item-meta>
|
<a-list-item-meta>
|
||||||
<template #avatar>
|
<template #avatar>
|
||||||
<a-typography-paragraph>
|
<a-typography-paragraph>
|
||||||
{{ $t('userSetting.SecuritySettings.form.label.password') }}
|
{{ $t('userCenter.SecuritySettings.form.label.password') }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
@ -13,12 +13,12 @@
|
|||||||
已设置
|
已设置
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
<a-typography-paragraph v-else class="tip">
|
<a-typography-paragraph v-else class="tip">
|
||||||
{{ $t('userSetting.SecuritySettings.placeholder.password') }}
|
{{ $t('userCenter.SecuritySettings.placeholder.password') }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</div>
|
</div>
|
||||||
<div class="operation">
|
<div class="operation">
|
||||||
<a-link>
|
<a-link>
|
||||||
{{ $t('userSetting.SecuritySettings.button.update') }}
|
{{ $t('userCenter.SecuritySettings.button.update') }}
|
||||||
</a-link>
|
</a-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<a-list-item-meta>
|
<a-list-item-meta>
|
||||||
<template #avatar>
|
<template #avatar>
|
||||||
<a-typography-paragraph>
|
<a-typography-paragraph>
|
||||||
{{ $t('userSetting.SecuritySettings.form.label.phone') }}
|
{{ $t('userCenter.SecuritySettings.form.label.phone') }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
@ -37,12 +37,12 @@
|
|||||||
已绑定:{{ loginStore.phone }}
|
已绑定:{{ loginStore.phone }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
<a-typography-paragraph v-else class="tip">
|
<a-typography-paragraph v-else class="tip">
|
||||||
{{ $t('userSetting.SecuritySettings.placeholder.phone') }}
|
{{ $t('userCenter.SecuritySettings.placeholder.phone') }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</div>
|
</div>
|
||||||
<div class="operation">
|
<div class="operation">
|
||||||
<a-link>
|
<a-link>
|
||||||
{{ $t('userSetting.SecuritySettings.button.update') }}
|
{{ $t('userCenter.SecuritySettings.button.update') }}
|
||||||
</a-link>
|
</a-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<a-list-item-meta>
|
<a-list-item-meta>
|
||||||
<template #avatar>
|
<template #avatar>
|
||||||
<a-typography-paragraph>
|
<a-typography-paragraph>
|
||||||
{{ $t('userSetting.SecuritySettings.form.label.email') }}
|
{{ $t('userCenter.SecuritySettings.form.label.email') }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
@ -61,12 +61,12 @@
|
|||||||
已绑定:{{ loginStore.email }}
|
已绑定:{{ loginStore.email }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
<a-typography-paragraph v-else class="tip">
|
<a-typography-paragraph v-else class="tip">
|
||||||
{{ $t('userSetting.SecuritySettings.placeholder.email') }}
|
{{ $t('userCenter.SecuritySettings.placeholder.email') }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</div>
|
</div>
|
||||||
<div class="operation">
|
<div class="operation">
|
||||||
<a-link>
|
<a-link>
|
||||||
{{ $t('userSetting.SecuritySettings.button.update') }}
|
{{ $t('userCenter.SecuritySettings.button.update') }}
|
||||||
</a-link>
|
</a-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
@ -2,19 +2,19 @@
|
|||||||
<a-card :bordered="false">
|
<a-card :bordered="false">
|
||||||
<a-space :size="54">
|
<a-space :size="54">
|
||||||
<a-upload
|
<a-upload
|
||||||
:custom-request="customRequest"
|
:custom-request="handleUpload"
|
||||||
list-type="picture-card"
|
list-type="picture-card"
|
||||||
:file-list="fileList"
|
:file-list="avatarList"
|
||||||
:show-upload-button="true"
|
:show-upload-button="true"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
@change="uploadChange"
|
@change="changeAvatar"
|
||||||
>
|
>
|
||||||
<template #upload-button>
|
<template #upload-button>
|
||||||
<a-avatar :size="100" class="info-avatar">
|
<a-avatar :size="100" class="info-avatar">
|
||||||
<template #trigger-icon>
|
<template #trigger-icon>
|
||||||
<icon-camera />
|
<icon-camera />
|
||||||
</template>
|
</template>
|
||||||
<img v-if="fileList.length" :src="fileList[0].url" />
|
<img v-if="avatarList.length" :src="avatarList[0].url" />
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
</template>
|
</template>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
>
|
>
|
||||||
<template #label="{ label }">{{ $t(label) }} :</template>
|
<template #label="{ label }">{{ $t(label) }} :</template>
|
||||||
<template #value="{ value, data }">
|
<template #value="{ value, data }">
|
||||||
<div v-if="data.label === 'userSetting.label.gender'">
|
<div v-if="data.label === 'userCenter.label.gender'">
|
||||||
<div v-if="loginStore.gender === 1">
|
<div v-if="loginStore.gender === 1">
|
||||||
男
|
男
|
||||||
<icon-man style="color: #19BBF1" />
|
<icon-man style="color: #19BBF1" />
|
||||||
@ -61,74 +61,69 @@
|
|||||||
RequestOption,
|
RequestOption,
|
||||||
} from '@arco-design/web-vue/es/upload/interfaces';
|
} from '@arco-design/web-vue/es/upload/interfaces';
|
||||||
import { useLoginStore } from '@/store';
|
import { useLoginStore } from '@/store';
|
||||||
import { userUploadApi } from '@/api/user-center';
|
import { uploadAvatar } from '@/api/system/user-center';
|
||||||
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
|
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
|
||||||
import getAvatar from "@/utils/avatar";
|
import getAvatar from "@/utils/avatar";
|
||||||
|
import { Message } from "@arco-design/web-vue";
|
||||||
|
|
||||||
const loginStore = useLoginStore();
|
const loginStore = useLoginStore();
|
||||||
const file = {
|
const avatar = {
|
||||||
uid: '-2',
|
uid: '-2',
|
||||||
name: 'avatar.png',
|
name: 'avatar.png',
|
||||||
url: loginStore.avatar ?? getAvatar(loginStore.gender),
|
url: getAvatar(loginStore),
|
||||||
};
|
};
|
||||||
const renderData = [
|
const renderData = [
|
||||||
{
|
{
|
||||||
label: 'userSetting.label.nickname',
|
label: 'userCenter.label.nickname',
|
||||||
value: loginStore.nickname,
|
value: loginStore.nickname,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'userSetting.label.gender',
|
label: 'userCenter.label.gender',
|
||||||
value: loginStore.gender,
|
value: loginStore.gender,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'userSetting.label.phone',
|
label: 'userCenter.label.phone',
|
||||||
value: loginStore.phone,
|
value: loginStore.phone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'userSetting.label.email',
|
label: 'userCenter.label.email',
|
||||||
value: loginStore.email,
|
value: loginStore.email,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'userSetting.label.registrationDate',
|
label: 'userCenter.label.registrationDate',
|
||||||
value: loginStore.registrationDate,
|
value: loginStore.registrationDate,
|
||||||
},
|
},
|
||||||
] as DescData[];
|
] as DescData[];
|
||||||
const fileList = ref<FileItem[]>([file]);
|
const avatarList = ref<FileItem[]>([avatar]);
|
||||||
const uploadChange = (fileItemList: FileItem[], fileItem: FileItem) => {
|
|
||||||
fileList.value = [fileItem];
|
|
||||||
};
|
|
||||||
const customRequest = (options: RequestOption) => {
|
|
||||||
// docs: https://axios-http.com/docs/cancellation
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
|
// 切换头像
|
||||||
|
const changeAvatar = (fileItemList: FileItem[], currentFile: FileItem) => {
|
||||||
|
avatarList.value = [currentFile];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传头像
|
||||||
|
const handleUpload = (options: RequestOption) => {
|
||||||
|
const controller = new AbortController();
|
||||||
(async function requestWrap() {
|
(async function requestWrap() {
|
||||||
const {
|
const {
|
||||||
onProgress,
|
onProgress,
|
||||||
onError,
|
onError,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
fileItem,
|
fileItem,
|
||||||
name = 'file',
|
name = 'avatarFile',
|
||||||
} = options;
|
} = options;
|
||||||
onProgress(20);
|
onProgress(20);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(name as string, fileItem.file as Blob);
|
formData.append(name as string, fileItem.file as Blob);
|
||||||
const onUploadProgress = (event: ProgressEvent) => {
|
|
||||||
let percent;
|
|
||||||
if (event.total > 0) {
|
|
||||||
percent = (event.loaded / event.total) * 100;
|
|
||||||
}
|
|
||||||
onProgress(parseInt(String(percent), 10), event);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// https://github.com/axios/axios/issues/1630
|
const res = await uploadAvatar(formData);
|
||||||
// https://github.com/nuysoft/Mock/issues/127
|
|
||||||
|
|
||||||
const res = await userUploadApi(formData, {
|
|
||||||
controller,
|
|
||||||
onUploadProgress,
|
|
||||||
});
|
|
||||||
onSuccess(res);
|
onSuccess(res);
|
||||||
|
Message.success({
|
||||||
|
content: res.msg || '网络错误',
|
||||||
|
duration: 3 * 1000,
|
||||||
|
});
|
||||||
|
// 更换头像
|
||||||
|
loginStore.avatar = res.data.avatar;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError(error);
|
onError(error);
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Breadcrumb :items="['menu.user', 'menu.user.setting']" />
|
<Breadcrumb :items="['menu.user.center']" />
|
||||||
<a-row style="margin-bottom: 16px">
|
<a-row style="margin-bottom: 16px">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<UserPanel />
|
<UserPanel />
|
||||||
@ -9,10 +9,10 @@
|
|||||||
<a-row class="wrapper">
|
<a-row class="wrapper">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-tabs default-active-key="1" type="rounded">
|
<a-tabs default-active-key="1" type="rounded">
|
||||||
<a-tab-pane key="1" :title="$t('userSetting.tab.basicInformation')">
|
<a-tab-pane key="1" :title="$t('userCenter.tab.basicInformation')">
|
||||||
<BasicInformation />
|
<BasicInformation />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" :title="$t('userSetting.tab.securitySettings')">
|
<a-tab-pane key="2" :title="$t('userCenter.tab.securitySettings')">
|
||||||
<SecuritySettings />
|
<SecuritySettings />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'Setting',
|
name: 'UserCenter',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -0,0 +1,31 @@
|
|||||||
|
export default {
|
||||||
|
'menu.user.center': 'User Center',
|
||||||
|
'userCenter.label.nickname': 'Nick Name',
|
||||||
|
'userCenter.label.gender': 'Gender',
|
||||||
|
'userCenter.label.phone': 'Phone',
|
||||||
|
'userCenter.label.email': 'Email',
|
||||||
|
'userCenter.label.registrationDate': 'Registration Date',
|
||||||
|
|
||||||
|
'userCenter.tab.basicInformation': 'Basic Information',
|
||||||
|
'userCenter.basicInfo.form.label.username': 'Username',
|
||||||
|
'userCenter.basicInfo.placeholder.username': 'Please enter username',
|
||||||
|
'userCenter.form.error.username.required': 'Please enter username',
|
||||||
|
'userCenter.basicInfo.form.label.nickname': 'Nickname',
|
||||||
|
'userCenter.basicInfo.placeholder.nickname': 'Please enter nickname',
|
||||||
|
'userCenter.form.error.nickname.required': 'Please enter nickname',
|
||||||
|
'userCenter.basicInfo.form.label.gender': 'Gender',
|
||||||
|
'userCenter.save': 'Save',
|
||||||
|
'userCenter.reset': 'Reset',
|
||||||
|
|
||||||
|
'userCenter.tab.securitySettings': 'Security Settings',
|
||||||
|
'userCenter.SecuritySettings.form.label.password': 'Login Password',
|
||||||
|
'userCenter.SecuritySettings.placeholder.password':
|
||||||
|
'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
|
||||||
|
'userCenter.SecuritySettings.form.label.phone': 'Phone',
|
||||||
|
'userCenter.SecuritySettings.placeholder.phone':
|
||||||
|
'You have not set a phone yet. The phone binding can be used to retrieve passwords and receive notifications and SMS login.',
|
||||||
|
'userCenter.SecuritySettings.form.label.email': 'Email',
|
||||||
|
'userCenter.SecuritySettings.placeholder.email':
|
||||||
|
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
|
||||||
|
'userCenter.SecuritySettings.button.update': 'Update',
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
export default {
|
||||||
|
'menu.user.center': '个人中心',
|
||||||
|
'userCenter.label.nickname': '昵称',
|
||||||
|
'userCenter.label.gender': '性别',
|
||||||
|
'userCenter.label.phone': '手机号码',
|
||||||
|
'userCenter.label.email': '邮箱',
|
||||||
|
'userCenter.label.registrationDate': '注册日期',
|
||||||
|
|
||||||
|
'userCenter.tab.basicInformation': '基础信息',
|
||||||
|
'userCenter.basicInfo.form.label.username': '用户名',
|
||||||
|
'userCenter.basicInfo.placeholder.username': '请输入您的用户名',
|
||||||
|
'userCenter.form.error.username.required': '请输入用户名',
|
||||||
|
'userCenter.basicInfo.form.label.nickname': '昵称',
|
||||||
|
'userCenter.basicInfo.placeholder.nickname': '请输入您的昵称',
|
||||||
|
'userCenter.form.error.nickname.required': '请输入昵称',
|
||||||
|
'userCenter.basicInfo.form.label.gender': '性别',
|
||||||
|
'userCenter.save': '保存',
|
||||||
|
'userCenter.reset': '重置',
|
||||||
|
|
||||||
|
'userCenter.tab.securitySettings': '安全设置',
|
||||||
|
'userCenter.SecuritySettings.form.label.password': '登录密码',
|
||||||
|
'userCenter.SecuritySettings.placeholder.password':
|
||||||
|
'您暂未设置密码,密码至少6位字符,支持数字、字母和除空格外的特殊字符。',
|
||||||
|
'userCenter.SecuritySettings.form.label.phone': '安全手机',
|
||||||
|
'userCenter.SecuritySettings.placeholder.phone':
|
||||||
|
'您暂未设置手机号,绑定手机号可以用来找回密码、接收通知、短信登录等。',
|
||||||
|
'userCenter.SecuritySettings.form.label.email': '安全邮箱',
|
||||||
|
'userCenter.SecuritySettings.placeholder.email':
|
||||||
|
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
|
||||||
|
'userCenter.SecuritySettings.button.update': '修改',
|
||||||
|
};
|
@ -1,88 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-card class="general-card" :title="$t('userInfo.title.latestActivity')">
|
|
||||||
<template #extra>
|
|
||||||
<a-link>{{ $t('userInfo.viewAll') }}</a-link>
|
|
||||||
</template>
|
|
||||||
<a-list :bordered="false">
|
|
||||||
<a-list-item
|
|
||||||
v-for="activity in activityList"
|
|
||||||
:key="activity.id"
|
|
||||||
action-layout="horizontal"
|
|
||||||
>
|
|
||||||
<a-skeleton
|
|
||||||
v-if="loading"
|
|
||||||
:loading="loading"
|
|
||||||
:animation="true"
|
|
||||||
class="skeleton-item"
|
|
||||||
>
|
|
||||||
<a-row :gutter="6">
|
|
||||||
<a-col :span="2">
|
|
||||||
<a-skeleton-shape shape="circle" />
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="22">
|
|
||||||
<a-skeleton-line :widths="['40%', '100%']" :rows="2" />
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-skeleton>
|
|
||||||
<a-list-item-meta
|
|
||||||
v-else
|
|
||||||
:title="activity.title"
|
|
||||||
:description="activity.description"
|
|
||||||
>
|
|
||||||
<template #avatar>
|
|
||||||
<a-avatar>
|
|
||||||
<img :src="activity.avatar" />
|
|
||||||
</a-avatar>
|
|
||||||
</template>
|
|
||||||
</a-list-item-meta>
|
|
||||||
</a-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { queryLatestActivity, LatestActivity } from '@/api/user-center';
|
|
||||||
import useLoading from '@/hooks/loading';
|
|
||||||
|
|
||||||
const { loading, setLoading } = useLoading(true);
|
|
||||||
const activityList = ref<LatestActivity[]>(new Array(7).fill({}));
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const { data } = await queryLatestActivity();
|
|
||||||
activityList.value = data;
|
|
||||||
} catch (err) {
|
|
||||||
// you can report use errorHandler or other
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchData();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.latest-activity {
|
|
||||||
&-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.general-card :deep(.arco-list-item) {
|
|
||||||
padding-left: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
.arco-list-item-meta-content {
|
|
||||||
flex: 1;
|
|
||||||
padding-bottom: 27px;
|
|
||||||
border-bottom: 1px solid var(--color-neutral-3);
|
|
||||||
}
|
|
||||||
.arco-list-item-meta-avatar {
|
|
||||||
padding-bottom: 27px;
|
|
||||||
}
|
|
||||||
.skeleton-item {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 1px solid var(--color-neutral-3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,27 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-card class="general-card" :title="$t('userInfo.title.latestNotification')">
|
|
||||||
<a-skeleton v-if="loading" :animation="true">
|
|
||||||
<a-skeleton-line :rows="3" />
|
|
||||||
</a-skeleton>
|
|
||||||
<a-result v-else status="404">
|
|
||||||
<template #subtitle>
|
|
||||||
{{ $t('userInfo.nodata') }}
|
|
||||||
</template>
|
|
||||||
</a-result>
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import useLoading from '@/hooks/loading';
|
|
||||||
|
|
||||||
const { loading, setLoading } = useLoading(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
}, 500);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
:deep(.arco-result) {
|
|
||||||
padding: 40px 32px 108px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-card class="general-card" :title="$t('userInfo.title.myProject')">
|
|
||||||
<template #extra>
|
|
||||||
<a-link>{{ $t('userInfo.showMore') }}</a-link>
|
|
||||||
</template>
|
|
||||||
<a-row :gutter="16">
|
|
||||||
<a-col
|
|
||||||
v-for="(project, index) in projectList"
|
|
||||||
:key="index"
|
|
||||||
:xs="12"
|
|
||||||
:sm="12"
|
|
||||||
:md="12"
|
|
||||||
:lg="12"
|
|
||||||
:xl="8"
|
|
||||||
:xxl="8"
|
|
||||||
class="my-project-item"
|
|
||||||
>
|
|
||||||
<a-card>
|
|
||||||
<a-skeleton v-if="loading" :loading="loading" :animation="true">
|
|
||||||
<a-skeleton-line :rows="3" />
|
|
||||||
</a-skeleton>
|
|
||||||
<a-space v-else direction="vertical">
|
|
||||||
<a-typography-text bold>{{ project.name }}</a-typography-text>
|
|
||||||
<a-typography-text type="secondary">
|
|
||||||
{{ project.description }}
|
|
||||||
</a-typography-text>
|
|
||||||
<a-space>
|
|
||||||
<a-avatar-group :size="24">
|
|
||||||
{{ project.contributors }}
|
|
||||||
<a-avatar
|
|
||||||
v-for="(contributor, idx) in project.contributors"
|
|
||||||
:key="idx"
|
|
||||||
:size="32"
|
|
||||||
>
|
|
||||||
<img alt="avatar" :src="contributor.avatar" />
|
|
||||||
</a-avatar>
|
|
||||||
</a-avatar-group>
|
|
||||||
<a-typography-text type="secondary">
|
|
||||||
等{{ project.peopleNumber }}人
|
|
||||||
</a-typography-text>
|
|
||||||
</a-space>
|
|
||||||
</a-space>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { queryMyProjectList, MyProjectRecord } from '@/api/user-center';
|
|
||||||
import useRequest from '@/hooks/request';
|
|
||||||
|
|
||||||
const defaultValue = Array(6).fill({} as MyProjectRecord);
|
|
||||||
const { loading, response: projectList } = useRequest<MyProjectRecord[]>(
|
|
||||||
queryMyProjectList,
|
|
||||||
defaultValue
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
:deep(.arco-card-body) {
|
|
||||||
min-height: 128px;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
.my-project {
|
|
||||||
&-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-title {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
margin-bottom: 18px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
// padding-right: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-card
|
|
||||||
class="general-card"
|
|
||||||
:title="$t('userInfo.tab.title.team')"
|
|
||||||
:header-style="{ paddingBottom: '18px' }"
|
|
||||||
:body-style="{ paddingBottom: '12px' }"
|
|
||||||
>
|
|
||||||
<a-list :bordered="false">
|
|
||||||
<a-list-item
|
|
||||||
v-for="team in teamList"
|
|
||||||
:key="team.id"
|
|
||||||
action-layout="horizontal"
|
|
||||||
>
|
|
||||||
<a-skeleton v-if="loading" :loading="loading" :animation="true">
|
|
||||||
<a-row :gutter="6">
|
|
||||||
<a-col :span="6">
|
|
||||||
<a-skeleton-shape shape="circle" />
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="16">
|
|
||||||
<a-skeleton-line :widths="['100%', '40%']" :rows="2" />
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-skeleton>
|
|
||||||
<a-list-item-meta v-else :title="team.name">
|
|
||||||
<template #avatar>
|
|
||||||
<a-avatar>
|
|
||||||
<img :src="team.avatar" />
|
|
||||||
</a-avatar>
|
|
||||||
</template>
|
|
||||||
<template #description> 共{{ team.peopleNumber }}人 </template>
|
|
||||||
</a-list-item-meta>
|
|
||||||
</a-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { queryMyTeamList, MyTeamRecord } from '@/api/user-center';
|
|
||||||
import useRequest from '@/hooks/request';
|
|
||||||
|
|
||||||
const defaultValue: MyTeamRecord[] = new Array(4).fill({});
|
|
||||||
const { loading, response: teamList } = useRequest<MyTeamRecord[]>(
|
|
||||||
queryMyTeamList,
|
|
||||||
defaultValue
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.general-card {
|
|
||||||
height: 356px;
|
|
||||||
.arco-list-item {
|
|
||||||
height: 72px;
|
|
||||||
padding-left: 0;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
border-bottom: 1px solid var(--color-neutral-3);
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.arco-list-item-meta {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,70 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="header">
|
|
||||||
<a-space :size="12" direction="vertical" align="center">
|
|
||||||
<a-avatar :size="64">
|
|
||||||
<template #trigger-icon>
|
|
||||||
<icon-camera />
|
|
||||||
</template>
|
|
||||||
<img :src="userInfo.avatar ?? getAvatar(userInfo.gender)" />
|
|
||||||
</a-avatar>
|
|
||||||
<a-typography-title :heading="6" style="margin: 0">
|
|
||||||
{{ userInfo.nickname }}
|
|
||||||
</a-typography-title>
|
|
||||||
<div class="user-msg">
|
|
||||||
<a-space :size="18">
|
|
||||||
<div>
|
|
||||||
<icon-user />
|
|
||||||
<a-typography-text>{{ userInfo.jobName }}</a-typography-text>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<icon-home />
|
|
||||||
<a-typography-text>
|
|
||||||
{{ userInfo.organizationName }}
|
|
||||||
</a-typography-text>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<icon-location />
|
|
||||||
<a-typography-text>{{ userInfo.locationName }}</a-typography-text>
|
|
||||||
</div>
|
|
||||||
</a-space>
|
|
||||||
</div>
|
|
||||||
</a-space>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useLoginStore } from '@/store';
|
|
||||||
import getAvatar from "@/utils/avatar";
|
|
||||||
|
|
||||||
const userInfo = useLoginStore();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 204px;
|
|
||||||
color: var(--gray-10);
|
|
||||||
background: url(//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/41c6b125cc2e27021bf7fcc9a9b1897c.svg~tplv-49unhts6dw-image.image)
|
|
||||||
no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
:deep(.arco-avatar-trigger-icon-button) {
|
|
||||||
color: rgb(var(--arcoblue-6));
|
|
||||||
|
|
||||||
:deep(.arco-icon) {
|
|
||||||
vertical-align: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.user-msg {
|
|
||||||
.arco-icon {
|
|
||||||
color: rgb(var(--gray-10));
|
|
||||||
}
|
|
||||||
.arco-typography {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<Breadcrumb :items="['menu.user', 'menu.user.info']" />
|
|
||||||
<UserInfoHeader />
|
|
||||||
<div class="content">
|
|
||||||
<div class="content-left">
|
|
||||||
<a-grid :cols="24" :col-gap="16" :row-gap="16">
|
|
||||||
<a-grid-item :span="24">
|
|
||||||
<MyProject />
|
|
||||||
</a-grid-item>
|
|
||||||
<a-grid-item :span="24">
|
|
||||||
<LatestActivity />
|
|
||||||
</a-grid-item>
|
|
||||||
</a-grid>
|
|
||||||
</div>
|
|
||||||
<div class="content-right">
|
|
||||||
<a-grid :cols="24" :row-gap="16">
|
|
||||||
<a-grid-item :span="24">
|
|
||||||
<MyTeam />
|
|
||||||
</a-grid-item>
|
|
||||||
<a-grid-item class="panel" :span="24">
|
|
||||||
<LatestNotification />
|
|
||||||
</a-grid-item>
|
|
||||||
</a-grid>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import UserInfoHeader from './components/user-info-header.vue';
|
|
||||||
import LatestNotification from './components/latest-notification.vue';
|
|
||||||
import MyProject from './components/my-project.vue';
|
|
||||||
import LatestActivity from './components/latest-activity.vue';
|
|
||||||
import MyTeam from './components/my-team.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'Info',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.container {
|
|
||||||
padding: 0 20px 20px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
&-left {
|
|
||||||
flex: 1;
|
|
||||||
margin-right: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
// background-color: var(--color-bg-2);
|
|
||||||
|
|
||||||
:deep(.arco-tabs-nav-tab) {
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-right {
|
|
||||||
width: 332px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-pane-wrapper {
|
|
||||||
padding: 0 16px 16px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.mobile {
|
|
||||||
.content {
|
|
||||||
display: block;
|
|
||||||
&-left {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
&-right {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,15 +0,0 @@
|
|||||||
export default {
|
|
||||||
'menu.user.info': 'User Info',
|
|
||||||
'userInfo.editUserInfo': 'Edit Info',
|
|
||||||
'userInfo.tab.title.overview': 'Overview',
|
|
||||||
'userInfo.tab.title.project': 'Project',
|
|
||||||
'userInfo.tab.title.team': 'My Team',
|
|
||||||
'userInfo.title.latestActivity': 'Latest Activity',
|
|
||||||
'userInfo.title.latestNotification': 'In-site Notification',
|
|
||||||
'userInfo.title.myProject': 'My Project',
|
|
||||||
'userInfo.showMore': 'Show More',
|
|
||||||
'userInfo.viewAll': 'View All',
|
|
||||||
'userInfo.nodata': 'No Data',
|
|
||||||
'userInfo.visits.unit': 'times',
|
|
||||||
'userInfo.visits.lastMonth': 'Last Month',
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
export default {
|
|
||||||
'menu.user.info': '用户信息',
|
|
||||||
'userInfo.editUserInfo': '编辑信息',
|
|
||||||
'userInfo.tab.title.overview': '总览',
|
|
||||||
'userInfo.tab.title.project': '项目',
|
|
||||||
'userInfo.tab.title.team': '我的团队',
|
|
||||||
'userInfo.title.latestActivity': '最新动态',
|
|
||||||
'userInfo.title.latestNotification': '站内通知',
|
|
||||||
'userInfo.title.myProject': '我的项目',
|
|
||||||
'userInfo.showMore': '查看更多',
|
|
||||||
'userInfo.viewAll': '查看全部',
|
|
||||||
'userInfo.nodata': '暂无数据',
|
|
||||||
'userInfo.visits.unit': '人次',
|
|
||||||
'userInfo.visits.lastMonth': '较上月',
|
|
||||||
};
|
|
@ -1,162 +0,0 @@
|
|||||||
import Mock from 'mockjs';
|
|
||||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
|
||||||
|
|
||||||
setupMock({
|
|
||||||
setup() {
|
|
||||||
// 最新项目
|
|
||||||
Mock.mock(new RegExp('/api/user/my-project/list'), () => {
|
|
||||||
const contributors = [
|
|
||||||
{
|
|
||||||
name: '秦臻宇',
|
|
||||||
email: 'qingzhenyu@arco.design',
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '于涛',
|
|
||||||
email: 'yuebao@arco.design',
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '宁波',
|
|
||||||
email: 'ningbo@arco.design',
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '郑曦月',
|
|
||||||
email: 'zhengxiyue@arco.design',
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/8361eeb82904210b4f55fab888fe8416.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '宁波',
|
|
||||||
email: 'ningbo@arco.design',
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const units = [
|
|
||||||
{
|
|
||||||
name: '企业级产品设计系统',
|
|
||||||
description: 'Arco Design System',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '火山引擎智能应用',
|
|
||||||
description: 'The Volcano Engine',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'OCR文本识别',
|
|
||||||
description: 'OCR text recognition',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '内容资源管理',
|
|
||||||
description: 'Content resource management ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '今日头条内容管理',
|
|
||||||
description: 'Toutiao content management',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '智能机器人',
|
|
||||||
description: 'Intelligent Robot Project',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return successResponseWrap(
|
|
||||||
new Array(6).fill(null).map((_item, index) => ({
|
|
||||||
id: index,
|
|
||||||
name: units[index].name,
|
|
||||||
description: units[index].description,
|
|
||||||
peopleNumber: Mock.Random.natural(10, 1000),
|
|
||||||
contributors,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最新动态
|
|
||||||
Mock.mock(new RegExp('/api/user/latest-activity'), () => {
|
|
||||||
return successResponseWrap(
|
|
||||||
new Array(7).fill(null).map((_item, index) => ({
|
|
||||||
id: index,
|
|
||||||
title: '发布了项目 Arco Design System',
|
|
||||||
description: '企业级产品设计系统',
|
|
||||||
avatar:
|
|
||||||
'//lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png',
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 访问量
|
|
||||||
Mock.mock(new RegExp('/api/user/visits'), () => {
|
|
||||||
return successResponseWrap([
|
|
||||||
{
|
|
||||||
name: '主页访问量',
|
|
||||||
visits: 5670,
|
|
||||||
growth: 206.32,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '项目访问量',
|
|
||||||
visits: 5670,
|
|
||||||
growth: 206.32,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 项目和团队列表
|
|
||||||
Mock.mock(new RegExp('/api/user/project-and-team/list'), () => {
|
|
||||||
return successResponseWrap([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
content: '他创建的项目',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
content: '他参与的项目',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
content: '他创建的团队',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
content: '他加入的团队',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 团队列表
|
|
||||||
Mock.mock(new RegExp('/api/user/my-team/list'), () => {
|
|
||||||
return successResponseWrap([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
name: '火山引擎智能应用团队',
|
|
||||||
peopleNumber: Mock.Random.natural(10, 100),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
name: '企业级产品设计团队',
|
|
||||||
peopleNumber: Mock.Random.natural(5000, 6000),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
name: '前端/UE小分队',
|
|
||||||
peopleNumber: Mock.Random.natural(10, 5000),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
avatar:
|
|
||||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/8361eeb82904210b4f55fab888fe8416.png~tplv-uwbnlip3yd-webp.webp',
|
|
||||||
name: '内容识别插件小分队',
|
|
||||||
peopleNumber: Mock.Random.natural(10, 100),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,47 +0,0 @@
|
|||||||
export default {
|
|
||||||
'menu.user.setting': 'User Setting',
|
|
||||||
'userSetting.menu.title.info': 'Personal Information',
|
|
||||||
'userSetting.menu.title.account': 'Account Setting',
|
|
||||||
'userSetting.menu.title.password': 'Password',
|
|
||||||
'userSetting.menu.title.message': 'Message Notification',
|
|
||||||
'userSetting.menu.title.result': 'Result',
|
|
||||||
'userSetting.menu.title.data': 'Export Data',
|
|
||||||
'userSetting.saveSuccess': 'Save Success',
|
|
||||||
'userSetting.title.basicInfo': 'Basic Information',
|
|
||||||
'userSetting.title.socialInfo': 'Social Information',
|
|
||||||
'userSetting.label.avatar': 'Avatar',
|
|
||||||
'userSetting.label.nickname': 'Nick Name',
|
|
||||||
'userSetting.label.location': 'Office Location',
|
|
||||||
'userSetting.label.introduction': 'Introduction',
|
|
||||||
'userSetting.label.personalWebsite': 'Website',
|
|
||||||
'userSetting.save': 'Save',
|
|
||||||
'userSetting.cancel': 'Cancel',
|
|
||||||
'userSetting.reset': 'Reset',
|
|
||||||
// new
|
|
||||||
'userSetting.label.phone': 'Phone',
|
|
||||||
'userSetting.label.email': 'Email',
|
|
||||||
'userSetting.label.gender': 'Gender',
|
|
||||||
'userSetting.label.registrationDate': 'Registration Date',
|
|
||||||
'userSetting.tab.basicInformation': 'Basic Information',
|
|
||||||
'userSetting.tab.securitySettings': 'Security Settings',
|
|
||||||
'userSetting.tab.certification': 'Certification',
|
|
||||||
'userSetting.basicInfo.form.label.username': 'Username',
|
|
||||||
'userSetting.basicInfo.placeholder.username': 'Please enter username',
|
|
||||||
'userSetting.form.error.username.required': 'Please enter username',
|
|
||||||
'userSetting.basicInfo.form.label.nickname': 'Nickname',
|
|
||||||
'userSetting.basicInfo.placeholder.nickname': 'Please enter nickname',
|
|
||||||
'userSetting.form.error.nickname.required': 'Please enter nickname',
|
|
||||||
'userSetting.basicInfo.form.label.gender': 'Gender',
|
|
||||||
'userSetting.form.error.gender.required': 'Please select gender',
|
|
||||||
'userSetting.SecuritySettings.form.label.password': 'Login Password',
|
|
||||||
'userSetting.SecuritySettings.placeholder.password':
|
|
||||||
'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
|
|
||||||
'userSetting.SecuritySettings.form.label.phone': 'Phone',
|
|
||||||
'userSetting.SecuritySettings.placeholder.phone':
|
|
||||||
'You have not set a phone yet. The phone binding can be used to retrieve passwords and receive notifications and SMS login.',
|
|
||||||
'userSetting.SecuritySettings.form.label.email': 'Email',
|
|
||||||
'userSetting.SecuritySettings.placeholder.email':
|
|
||||||
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
|
|
||||||
'userSetting.SecuritySettings.button.settings': 'Settings',
|
|
||||||
'userSetting.SecuritySettings.button.update': 'Update',
|
|
||||||
};
|
|
@ -1,47 +0,0 @@
|
|||||||
export default {
|
|
||||||
'menu.user.setting': '用户设置',
|
|
||||||
'userSetting.menu.title.info': '个人信息',
|
|
||||||
'userSetting.menu.title.account': '账号设置',
|
|
||||||
'userSetting.menu.title.password': '密码',
|
|
||||||
'userSetting.menu.title.message': '消息通知',
|
|
||||||
'userSetting.menu.title.result': '结果页',
|
|
||||||
'userSetting.menu.title.data': '导出数据',
|
|
||||||
'userSetting.saveSuccess': '保存成功',
|
|
||||||
'userSetting.title.basicInfo': '基本信息',
|
|
||||||
'userSetting.title.socialInfo': '社交信息',
|
|
||||||
'userSetting.label.avatar': '头像',
|
|
||||||
'userSetting.label.nickname': '昵称',
|
|
||||||
'userSetting.label.location': '办公地点',
|
|
||||||
'userSetting.label.introduction': '个人简介',
|
|
||||||
'userSetting.label.personalWebsite': '个人网站',
|
|
||||||
'userSetting.save': '保存',
|
|
||||||
'userSetting.cancel': '取消',
|
|
||||||
'userSetting.reset': '重置',
|
|
||||||
// new
|
|
||||||
'userSetting.label.phone': '手机号码',
|
|
||||||
'userSetting.label.email': '邮箱',
|
|
||||||
'userSetting.label.gender': '性别',
|
|
||||||
'userSetting.label.registrationDate': '注册日期',
|
|
||||||
'userSetting.tab.basicInformation': '基础信息',
|
|
||||||
'userSetting.tab.securitySettings': '安全设置',
|
|
||||||
'userSetting.tab.certification': '实名认证',
|
|
||||||
'userSetting.basicInfo.form.label.username': '用户名',
|
|
||||||
'userSetting.basicInfo.placeholder.username': '请输入您的用户名',
|
|
||||||
'userSetting.form.error.username.required': '请输入用户名',
|
|
||||||
'userSetting.basicInfo.form.label.nickname': '昵称',
|
|
||||||
'userSetting.basicInfo.placeholder.nickname': '请输入您的昵称',
|
|
||||||
'userSetting.form.error.nickname.required': '请输入昵称',
|
|
||||||
'userSetting.basicInfo.form.label.gender': '性别',
|
|
||||||
'userSetting.form.error.gender.required': '请选择性别',
|
|
||||||
'userSetting.SecuritySettings.form.label.password': '登录密码',
|
|
||||||
'userSetting.SecuritySettings.placeholder.password':
|
|
||||||
'您暂未设置密码,密码至少6位字符,支持数字、字母和除空格外的特殊字符。',
|
|
||||||
'userSetting.SecuritySettings.form.label.phone': '安全手机',
|
|
||||||
'userSetting.SecuritySettings.placeholder.phone':
|
|
||||||
'您暂未设置手机号,绑定手机号可以用来找回密码、接收通知、短信登录等。',
|
|
||||||
'userSetting.SecuritySettings.form.label.email': '安全邮箱',
|
|
||||||
'userSetting.SecuritySettings.placeholder.email':
|
|
||||||
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
|
|
||||||
'userSetting.SecuritySettings.button.settings': '设置',
|
|
||||||
'userSetting.SecuritySettings.button.update': '修改',
|
|
||||||
};
|
|
@ -60,7 +60,7 @@ public class CaptchaController {
|
|||||||
Captcha captcha = captchaProperties.getCaptcha();
|
Captcha captcha = captchaProperties.getCaptcha();
|
||||||
|
|
||||||
// 保存验证码
|
// 保存验证码
|
||||||
String uuid = IdUtil.simpleUUID();
|
String uuid = IdUtil.fastSimpleUUID();
|
||||||
String captchaKey = RedisUtils.formatKey(captchaProperties.getKeyPrefix(), uuid);
|
String captchaKey = RedisUtils.formatKey(captchaProperties.getKeyPrefix(), uuid);
|
||||||
RedisUtils.setCacheObject(captchaKey, captcha.text(),
|
RedisUtils.setCacheObject(captchaKey, captcha.text(),
|
||||||
Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
|
Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
|
||||||
|
@ -39,11 +39,11 @@ import top.charles7c.cnadmin.auth.service.LoginService;
|
|||||||
import top.charles7c.cnadmin.common.config.properties.RsaProperties;
|
import top.charles7c.cnadmin.common.config.properties.RsaProperties;
|
||||||
import top.charles7c.cnadmin.common.model.dto.LoginUser;
|
import top.charles7c.cnadmin.common.model.dto.LoginUser;
|
||||||
import top.charles7c.cnadmin.common.model.vo.R;
|
import top.charles7c.cnadmin.common.model.vo.R;
|
||||||
import top.charles7c.cnadmin.common.util.CheckUtils;
|
|
||||||
import top.charles7c.cnadmin.common.util.ExceptionUtils;
|
import top.charles7c.cnadmin.common.util.ExceptionUtils;
|
||||||
import top.charles7c.cnadmin.common.util.RedisUtils;
|
import top.charles7c.cnadmin.common.util.RedisUtils;
|
||||||
import top.charles7c.cnadmin.common.util.SecureUtils;
|
import top.charles7c.cnadmin.common.util.SecureUtils;
|
||||||
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
|
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
|
||||||
|
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录 API
|
* 登录 API
|
||||||
@ -67,14 +67,14 @@ public class LoginController {
|
|||||||
// 校验验证码
|
// 校验验证码
|
||||||
String captchaKey = RedisUtils.formatKey(captchaProperties.getKeyPrefix(), loginRequest.getUuid());
|
String captchaKey = RedisUtils.formatKey(captchaProperties.getKeyPrefix(), loginRequest.getUuid());
|
||||||
String captcha = RedisUtils.getCacheObject(captchaKey);
|
String captcha = RedisUtils.getCacheObject(captchaKey);
|
||||||
CheckUtils.exIfBlank(captcha, "验证码已失效");
|
ValidationUtils.exIfBlank(captcha, "验证码已失效");
|
||||||
RedisUtils.deleteCacheObject(captchaKey);
|
RedisUtils.deleteCacheObject(captchaKey);
|
||||||
CheckUtils.exIfCondition(() -> !captcha.equalsIgnoreCase(loginRequest.getCaptcha()), "验证码错误");
|
ValidationUtils.exIfCondition(() -> !captcha.equalsIgnoreCase(loginRequest.getCaptcha()), "验证码错误");
|
||||||
|
|
||||||
// 用户登录
|
// 用户登录
|
||||||
String rawPassword = ExceptionUtils
|
String rawPassword = ExceptionUtils
|
||||||
.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(loginRequest.getPassword(), RsaProperties.PRIVATE_KEY));
|
.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(loginRequest.getPassword(), RsaProperties.PRIVATE_KEY));
|
||||||
CheckUtils.exIfBlank(rawPassword, "密码解密失败");
|
ValidationUtils.exIfBlank(rawPassword, "密码解密失败");
|
||||||
String token = loginService.login(loginRequest.getUsername(), rawPassword);
|
String token = loginService.login(loginRequest.getUsername(), rawPassword);
|
||||||
return R.ok(new LoginVO().setToken(token));
|
return R.ok(new LoginVO().setToken(token));
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ public class LoginController {
|
|||||||
in = ParameterIn.HEADER)
|
in = ParameterIn.HEADER)
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public R logout() {
|
public R logout() {
|
||||||
CheckUtils.exIfCondition(() -> !StpUtil.isLogin(), "Token 无效");
|
ValidationUtils.exIfCondition(() -> !StpUtil.isLogin(), "Token 无效");
|
||||||
StpUtil.logout();
|
StpUtil.logout();
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.charles7c.cnadmin.webapi.controller.system;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.file.FileNameUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties;
|
||||||
|
import top.charles7c.cnadmin.common.consts.FileConstants;
|
||||||
|
import top.charles7c.cnadmin.common.model.dto.LoginUser;
|
||||||
|
import top.charles7c.cnadmin.common.model.vo.R;
|
||||||
|
import top.charles7c.cnadmin.common.util.FileUtils;
|
||||||
|
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
|
||||||
|
import top.charles7c.cnadmin.common.util.validate.CheckUtils;
|
||||||
|
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
|
||||||
|
import top.charles7c.cnadmin.system.model.vo.AvatarVO;
|
||||||
|
import top.charles7c.cnadmin.system.service.UserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人中心 API
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/1/2 11:41
|
||||||
|
*/
|
||||||
|
@Tag(name = "个人中心 API")
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping(value = "/system/user/center", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public class UserCenterController {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final LocalStorageProperties localStorageProperties;
|
||||||
|
|
||||||
|
@Operation(summary = "上传头像", description = "用户上传个人头像")
|
||||||
|
@PostMapping("/avatar")
|
||||||
|
public R<AvatarVO> uploadAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) {
|
||||||
|
// 校验
|
||||||
|
ValidationUtils.exIfCondition(avatarFile::isEmpty, "头像不能为空");
|
||||||
|
Long avatarMaxSizeInMb = localStorageProperties.getAvatarMaxSizeInMb();
|
||||||
|
ValidationUtils.exIfCondition(() -> avatarFile.getSize() > avatarMaxSizeInMb * 1024 * 1024,
|
||||||
|
String.format("请上传小于 %s MB 的图片", avatarMaxSizeInMb));
|
||||||
|
String avatarImageType = FileNameUtil.extName(avatarFile.getOriginalFilename());
|
||||||
|
String[] avatarSupportImgTypes = FileConstants.AVATAR_SUPPORTED_IMG_TYPES;
|
||||||
|
ValidationUtils.exIfCondition(() -> !StrUtil.equalsAnyIgnoreCase(avatarImageType, avatarSupportImgTypes),
|
||||||
|
String.format("头像仅支持 %s 格式的图片", String.join(",", avatarSupportImgTypes)));
|
||||||
|
|
||||||
|
// 上传新头像
|
||||||
|
String avatarPath = localStorageProperties.getPath().getAvatar();
|
||||||
|
File newAvatarFile = FileUtils.upload(avatarFile, avatarPath, false);
|
||||||
|
CheckUtils.exIfNull(newAvatarFile, "上传头像失败");
|
||||||
|
|
||||||
|
// 更新用户头像
|
||||||
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
String newAvatar = newAvatarFile.getName();
|
||||||
|
userService.updateAvatar(newAvatar, loginUser.getUserId());
|
||||||
|
|
||||||
|
// 删除原头像
|
||||||
|
String oldAvatar = loginUser.getAvatar();
|
||||||
|
if (StrUtil.isNotBlank(loginUser.getAvatar())) {
|
||||||
|
FileUtil.del(avatarPath + oldAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新登录用户信息
|
||||||
|
loginUser.setAvatar(newAvatar);
|
||||||
|
LoginHelper.updateLoginUser(loginUser);
|
||||||
|
return R.ok("上传成功", new AvatarVO().setAvatar(newAvatar));
|
||||||
|
}
|
||||||
|
}
|
@ -73,6 +73,12 @@ spring:
|
|||||||
security:
|
security:
|
||||||
# 排除路径配置
|
# 排除路径配置
|
||||||
excludes:
|
excludes:
|
||||||
|
# 静态资源
|
||||||
|
- /*.html
|
||||||
|
- /**/*.html
|
||||||
|
- /**/*.css
|
||||||
|
- /**/*.js
|
||||||
|
- /webSocket/**
|
||||||
# 接口文档相关资源
|
# 接口文档相关资源
|
||||||
- /favicon.ico
|
- /favicon.ico
|
||||||
- /doc.html
|
- /doc.html
|
||||||
@ -80,6 +86,9 @@ security:
|
|||||||
- /swagger-ui/**
|
- /swagger-ui/**
|
||||||
- /swagger-resources/**
|
- /swagger-resources/**
|
||||||
- /*/api-docs/**
|
- /*/api-docs/**
|
||||||
|
# 本地存储资源
|
||||||
|
- /avatar/**
|
||||||
|
- /file/**
|
||||||
|
|
||||||
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对:http://web.chacuo.net/netrsakeypair)
|
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对:http://web.chacuo.net/netrsakeypair)
|
||||||
rsa:
|
rsa:
|
||||||
@ -106,6 +115,39 @@ springdoc:
|
|||||||
swagger-ui:
|
swagger-ui:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
--- ### 文件上传配置
|
||||||
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
enabled: true
|
||||||
|
# 单文件上传大小限制
|
||||||
|
max-file-size: 10MB
|
||||||
|
# 单次总上传文件大小限制
|
||||||
|
max-request-size: 20MB
|
||||||
|
|
||||||
|
--- ### 本地存储配置
|
||||||
|
local-storage:
|
||||||
|
# 文件模式
|
||||||
|
filePattern: /file/**
|
||||||
|
# 头像模式
|
||||||
|
avatarPattern: /avatar/**
|
||||||
|
# 文件上传大小限制
|
||||||
|
maxSizeInMb: 10
|
||||||
|
# 头像上传大小限制
|
||||||
|
avatarMaxSizeInMb: 5
|
||||||
|
## Windows 系统本地存储配置
|
||||||
|
windows:
|
||||||
|
file: C:\continew-admin\data\file\
|
||||||
|
avatar: C:\continew-admin\data\avatar\
|
||||||
|
## Linux 系统本地存储配置
|
||||||
|
linux:
|
||||||
|
file: /data/file/
|
||||||
|
avatar: /data/avatar/
|
||||||
|
## Mac 系统本地存储配置
|
||||||
|
mac:
|
||||||
|
file: ~/data/file/
|
||||||
|
avatar: ~/data/avatar/
|
||||||
|
|
||||||
--- ### 跨域配置
|
--- ### 跨域配置
|
||||||
cors:
|
cors:
|
||||||
# 配置允许跨域的域名
|
# 配置允许跨域的域名
|
||||||
|
@ -69,6 +69,20 @@ spring:
|
|||||||
# 是否开启 SSL
|
# 是否开启 SSL
|
||||||
ssl: false
|
ssl: false
|
||||||
|
|
||||||
|
--- ### 安全配置
|
||||||
|
security:
|
||||||
|
# 排除路径配置
|
||||||
|
excludes:
|
||||||
|
# 静态资源
|
||||||
|
- /*.html
|
||||||
|
- /**/*.html
|
||||||
|
- /**/*.css
|
||||||
|
- /**/*.js
|
||||||
|
- /webSocket/**
|
||||||
|
# 本地存储资源
|
||||||
|
- /avatar/**
|
||||||
|
- /file/**
|
||||||
|
|
||||||
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对:http://web.chacuo.net/netrsakeypair)
|
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对:http://web.chacuo.net/netrsakeypair)
|
||||||
rsa:
|
rsa:
|
||||||
# 私钥
|
# 私钥
|
||||||
@ -94,6 +108,39 @@ springdoc:
|
|||||||
swagger-ui:
|
swagger-ui:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
--- ### 文件上传配置
|
||||||
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
enabled: true
|
||||||
|
# 单文件上传大小限制
|
||||||
|
max-file-size: 10MB
|
||||||
|
# 单次总上传文件大小限制
|
||||||
|
max-request-size: 20MB
|
||||||
|
|
||||||
|
--- ### 本地存储配置
|
||||||
|
local-storage:
|
||||||
|
# 文件模式
|
||||||
|
filePattern: /file/**
|
||||||
|
# 头像模式
|
||||||
|
avatarPattern: /avatar/**
|
||||||
|
# 文件上传大小限制
|
||||||
|
maxSizeInMb: 10
|
||||||
|
# 头像上传大小限制
|
||||||
|
avatarMaxSizeInMb: 5
|
||||||
|
## Windows 系统本地存储配置
|
||||||
|
windows:
|
||||||
|
file: C:\continew-admin\data\file\
|
||||||
|
avatar: C:\continew-admin\data\avatar\
|
||||||
|
## Linux 系统本地存储配置
|
||||||
|
linux:
|
||||||
|
file: /data/file/
|
||||||
|
avatar: /data/avatar/
|
||||||
|
## Mac 系统本地存储配置
|
||||||
|
mac:
|
||||||
|
file: ~/data/file/
|
||||||
|
avatar: ~/data/avatar/
|
||||||
|
|
||||||
--- ### 跨域配置
|
--- ### 跨域配置
|
||||||
cors:
|
cors:
|
||||||
# 配置允许跨域的域名
|
# 配置允许跨域的域名
|
||||||
|
@ -92,17 +92,6 @@ sa-token:
|
|||||||
# JWT秘钥
|
# JWT秘钥
|
||||||
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
|
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
|
||||||
|
|
||||||
--- ### 安全配置
|
|
||||||
security:
|
|
||||||
# 排除路径配置
|
|
||||||
excludes:
|
|
||||||
# 静态资源
|
|
||||||
- /*.html
|
|
||||||
- /**/*.html
|
|
||||||
- /**/*.css
|
|
||||||
- /**/*.js
|
|
||||||
- /webSocket/**
|
|
||||||
|
|
||||||
--- ### MyBatis Plus 配置
|
--- ### MyBatis Plus 配置
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
# Mapper 接口扫描包配置(该配置为自定义配置,非 MP 配置,不支持多包,如有需要可通过注解配置或提升扫描包层级)
|
# Mapper 接口扫描包配置(该配置为自定义配置,非 MP 配置,不支持多包,如有需要可通过注解配置或提升扫描包层级)
|
||||||
|
@ -48,7 +48,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- '18000:18000'
|
- '18000:18000'
|
||||||
volumes:
|
volumes:
|
||||||
- /docker/continew-admin/server/logs:/logs
|
- /docker/continew-admin/logs:/logs
|
||||||
|
- /docker/continew-admin/data/file:/data/file
|
||||||
|
- /docker/continew-admin/data/avatar:/data/avatar
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- mariadb
|
- mariadb
|
||||||
|
Loading…
Reference in New Issue
Block a user