开发助手
This commit is contained in:
parent
165a74d398
commit
9e8fa86a10
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -3,6 +3,7 @@
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/dev-assistant.iml" filepath="$PROJECT_DIR$/dev-assistant.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dev-assistant-service.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dev-assistant-service.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/dev-assistant-web.iml" filepath="$PROJECT_DIR$/dev-assistant-web.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
8
.idea/modules/dev-assistant-service.main.iml
generated
Normal file
8
.idea/modules/dev-assistant-service.main.iml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../dev-assistant-service/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../dev-assistant-service/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
Binary file not shown.
@ -25,7 +25,6 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
|
||||
implementation 'cn.hutool:hutool-all:5.8.35'
|
||||
|
@ -0,0 +1,44 @@
|
||||
package com.devassistant.authentication.controller;
|
||||
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.devassistant.authentication.service.AuthenticationService;
|
||||
import com.devassistant.framework.common.IgnoreLogin;
|
||||
import com.devassistant.framework.common.RestResult;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 权限认证接口
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("authentication")
|
||||
public class AuthenticationController {
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @return token
|
||||
*/
|
||||
@IgnoreLogin
|
||||
@PostMapping("login")
|
||||
public RestResult<?> login(@RequestBody JSONObject body, HttpServletRequest request) {
|
||||
String password = body.getString("password");
|
||||
boolean loginResult = authenticationService.login(password, request);
|
||||
if (loginResult) {
|
||||
return RestResult.genSuccessResult();
|
||||
} else {
|
||||
return RestResult.genErrorResult("密码错误");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.devassistant.authentication.service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 用户身份权限认证服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AuthenticationService {
|
||||
|
||||
@Value("${file.password}")
|
||||
private String loginPassword;
|
||||
|
||||
public boolean login(String password, HttpServletRequest request) {
|
||||
if (loginPassword.equals(password)) {
|
||||
request.getSession().setAttribute("isLogin", true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -60,12 +60,10 @@ public class FileTransmissionController {
|
||||
/**
|
||||
* 下载文件
|
||||
*
|
||||
* @param body 查询条件
|
||||
* @return 文件
|
||||
*/
|
||||
@GetMapping("/downloadFile")
|
||||
public RestResult<?> downloadFile(@RequestBody JSONObject body, HttpServletResponse response) {
|
||||
Integer id = body.getInteger("id");
|
||||
public RestResult<?> downloadFile(@RequestParam Integer id, HttpServletResponse response) {
|
||||
fileService.downloadFile(id, response);
|
||||
return RestResult.genSuccessResult();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.devassistant.file.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
@ -20,5 +21,6 @@ public class FileRecord {
|
||||
private Long fileSize;
|
||||
private String filePath;
|
||||
private String fileType;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date uploadTime;
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@ -118,7 +120,7 @@ public class FileService {
|
||||
|
||||
// 设置响应头
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8) + "\"");
|
||||
response.setContentLengthLong(file.length());
|
||||
|
||||
try (InputStream inputStream = new FileInputStream(file);
|
||||
|
@ -0,0 +1,60 @@
|
||||
package com.devassistant.framework.config;
|
||||
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.devassistant.framework.common.ResponseCode;
|
||||
import com.devassistant.framework.common.RestResult;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.annotation.WebFilter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
@WebFilter(filterName = "loginFilter", urlPatterns = "/*")
|
||||
public class LoginFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
String requestURI = ((HttpServletRequest) request).getRequestURI();
|
||||
|
||||
if("/".equals(requestURI)){
|
||||
httpResponse.sendRedirect("/index.html");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("/authentication/login".equals(requestURI) || requestURI.startsWith("/h2-console") || requestURI.startsWith("/index.html") || requestURI.startsWith("/assets")) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpSession session = httpRequest.getSession();
|
||||
Object isLogin = session.getAttribute("isLogin");
|
||||
if (isLogin != null && (boolean) isLogin) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
RestResult<Object> result = RestResult.genErrorResult(ResponseCode.NEED_LOGIN);
|
||||
String responseBody = JSONObject.toJSONString(result);
|
||||
httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
|
||||
httpResponse.addHeader("Access-Control-Allow-Origin", "*");
|
||||
httpResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
|
||||
|
||||
httpResponse.setCharacterEncoding("UTF-8");
|
||||
httpResponse.setContentType("application/json;charset=UTF-8");
|
||||
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
ServletOutputStream outputStream = response.getOutputStream();
|
||||
outputStream.write(responseBody.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package com.devassistant.framework.security;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.devassistant.framework.common.ResponseCode;
|
||||
import com.devassistant.framework.common.RestResult;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* spring security 未认证请求拦截器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
|
||||
String method = httpServletRequest.getMethod();
|
||||
log.warn("security 未认证请求拦截 {} [{}]: {}", method, httpServletRequest.getRequestURI(), e.getMessage());
|
||||
RestResult<Object> result = RestResult.genErrorResult(ResponseCode.NEED_LOGIN);
|
||||
String responseBody = JSONObject.toJSONString(result);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
ServletOutputStream outputStream = response.getOutputStream();
|
||||
outputStream.write(responseBody.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package com.devassistant.framework.security;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
@Configuration
|
||||
public class FixedPasswordAuthProvider implements AuthenticationProvider {
|
||||
|
||||
@Value("${file.password}")
|
||||
private String loginPassword;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) {
|
||||
String password = (String) authentication.getCredentials();
|
||||
if (loginPassword.equals(password)) {
|
||||
return new UsernamePasswordAuthenticationToken("admin", password);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package com.devassistant.framework.security;
|
||||
|
||||
import com.devassistant.framework.common.IgnoreLogin;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
/**
|
||||
* spring security 安全配置
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
|
||||
|
||||
private final AuthEntryPoint authEntryPoint;
|
||||
private final RequestMappingHandlerMapping handlerMapping;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// 禁用 csrf
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
|
||||
// 允许跨域
|
||||
.cors(Customizer.withDefaults())
|
||||
// 禁用 httpBasic 登录
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
// 禁用 logout
|
||||
.logout(AbstractHttpConfigurer::disable)
|
||||
|
||||
// 禁用 session
|
||||
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
// 异常处理
|
||||
.exceptionHandling(e -> e.authenticationEntryPoint(authEntryPoint))
|
||||
// 访问控制
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**")
|
||||
.permitAll().requestMatchers("/error")
|
||||
.permitAll().requestMatchers("/h2-console/**")
|
||||
.permitAll().anyRequest().access((authentication, object) -> getAuthorizationDecision(object)));
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前请求是否放行
|
||||
*
|
||||
* @param object 请求信息
|
||||
* @return 放行策略
|
||||
*/
|
||||
private AuthorizationDecision getAuthorizationDecision(RequestAuthorizationContext object) {
|
||||
// 已认证非匿名用户放行请求
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (!(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated()) {
|
||||
return new AuthorizationDecision(true);
|
||||
}
|
||||
|
||||
// 未认证判断接口是否需要认证
|
||||
try {
|
||||
HandlerExecutionChain handlerChain = handlerMapping.getHandler(object.getRequest());
|
||||
if (handlerChain != null) {
|
||||
Object handlerObj = handlerChain.getHandler();
|
||||
if (handlerObj instanceof HandlerMethod handler) {
|
||||
IgnoreLogin annotationMethod = handler.getMethodAnnotation(IgnoreLogin.class);
|
||||
if (annotationMethod != null) {
|
||||
return new AuthorizationDecision(true);
|
||||
}
|
||||
IgnoreLogin annotationClass = handler.getBeanType().getAnnotation(IgnoreLogin.class);
|
||||
if (annotationClass != null) {
|
||||
return new AuthorizationDecision(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("接口权限判断异常", e);
|
||||
return null;
|
||||
}
|
||||
// 拦截其他请求
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
}
|
@ -35,6 +35,20 @@ public class NoteController {
|
||||
return RestResult.genSuccessResult(noteRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改笔记
|
||||
*
|
||||
* @param body 笔记内容
|
||||
* @return 添加结果
|
||||
*/
|
||||
@PostMapping("/updateNote")
|
||||
public RestResult<?> updateNote(@RequestBody JSONObject body, HttpServletRequest request) {
|
||||
Integer id = body.getInteger("id");
|
||||
String content = body.getString("content");
|
||||
noteService.updateNote(id, content, request);
|
||||
return RestResult.genSuccessResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔记列表
|
||||
*
|
||||
|
@ -1,9 +1,7 @@
|
||||
package com.devassistant.note.model;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
@ -15,8 +13,11 @@ public class NoteRecord {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Integer id;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String content;
|
||||
private String updateIp;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ public class NoteService {
|
||||
return (root, query, cb) -> {
|
||||
Predicate predicate = cb.conjunction();
|
||||
if (StrUtil.isNotBlank(searchText)) {
|
||||
predicate = cb.and(predicate, cb.or(cb.like(root.get("content"), "%" + searchText + "%"), cb.like(root.get("createIp"), "%" + searchText + "%")));
|
||||
predicate = cb.and(predicate, cb.or(cb.like(root.get("content"), "%" + searchText + "%"), cb.like(root.get("updateIp"), "%" + searchText + "%")));
|
||||
}
|
||||
Objects.requireNonNull(query).orderBy(cb.desc(root.get("updateTime")));
|
||||
return predicate;
|
||||
|
@ -1,3 +1,5 @@
|
||||
server:
|
||||
port: 80
|
||||
spring:
|
||||
application:
|
||||
name: dev-assistant-service
|
||||
@ -6,12 +8,19 @@ spring:
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password: 123456
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2-console
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10GB
|
||||
max-request-size: 10GB
|
||||
|
||||
file:
|
||||
upload-dir: ./data/files
|
||||
password: 0105
|
||||
password: "0105"
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
14
dev-assistant-service/src/main/resources/static/index.html
Normal file
14
dev-assistant-service/src/main/resources/static/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>开发助手</title>
|
||||
<script type="module" crossorigin src="./assets/index-km1bwIfZ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-F-bWWWv-.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
BIN
dev-assistant-service/src/main/resources/static/logo.png
Normal file
BIN
dev-assistant-service/src/main/resources/static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -2,7 +2,7 @@
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>开发助手</title>
|
||||
</head>
|
||||
|
BIN
dev-assistant-web/public/logo.png
Normal file
BIN
dev-assistant-web/public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -11,6 +11,24 @@ import {zhCn} from "element-plus/es/locale/index";
|
||||
<style>
|
||||
html, body {
|
||||
color: #444;
|
||||
background-color: #eee;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.back-img {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
5
dev-assistant-web/src/api/file.js
Normal file
5
dev-assistant-web/src/api/file.js
Normal file
@ -0,0 +1,5 @@
|
||||
import http from '~/axios/index.js';
|
||||
|
||||
export const getFileListApi = data => http.post("/file/getFileList", data)
|
||||
export const delFileApi = data => http.post("/file/delFile", data)
|
||||
export const downloadFileApi = data => http.post("/note/downloadFile", data)
|
3
dev-assistant-web/src/api/login.js
Normal file
3
dev-assistant-web/src/api/login.js
Normal file
@ -0,0 +1,3 @@
|
||||
import http from '~/axios/index.js';
|
||||
|
||||
export const loginApi = data => http.post("/authentication/login", data)
|
6
dev-assistant-web/src/api/note.js
Normal file
6
dev-assistant-web/src/api/note.js
Normal file
@ -0,0 +1,6 @@
|
||||
import http from '~/axios/index.js';
|
||||
|
||||
export const addNoteApi = data => http.post("/note/addNote", data)
|
||||
export const updateNoteApi = data => http.post("/note/updateNote", data)
|
||||
export const delNoteApi = data => http.post("/note/delNote", data)
|
||||
export const getNoteListApi = data => http.post("/note/getNoteList", data)
|
40
dev-assistant-web/src/assets/background.svg
Normal file
40
dev-assistant-web/src/assets/background.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
baseProfile="full"
|
||||
width="100%" height="100%"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1400 800"
|
||||
>
|
||||
|
||||
<rect x="1300" y="400" rx="40" ry="40" width="300" height="300" stroke="rgb(129, 201, 149)" fill="rgb(129, 201, 149)">
|
||||
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 1450 550" to="360 1450 550" repeatCount="indefinite" />
|
||||
</rect>
|
||||
|
||||
<path d="M 100 350 A 150 150 0 1 1 400 350 Q400 370 380 370 L 250 370 L 120 370 Q100 370 100 350" stroke="rgb(253, 214, 99)" fill="rgb(253, 214, 99)">
|
||||
<animateMotion path="M 800 -200 L 800 -300 L 800 -200" dur="20s" begin="0s" repeatCount="indefinite" />
|
||||
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 210 530 ; -30 210 530 ; 0 210 530" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite" />
|
||||
</path>
|
||||
|
||||
<circle cx="200" cy="150" r="20" stroke="#1a73e8" fill="#1a73e8">
|
||||
<animateMotion path="M 0 0 L 40 20 Z" dur="5s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<!-- 三角形 -->
|
||||
<path d="M 165 580 L 270 580 Q275 578 270 570 L 223 483 Q220 480 217 483 L 165 570 Q160 578 165 580" stroke="rgb(238, 103, 92)" fill="rgb(238, 103, 92)">
|
||||
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 210 530" to="360 210 530" repeatCount="indefinite" />
|
||||
</path>
|
||||
|
||||
<circle cx="1200" cy="600" r="30" stroke="rgb(241, 243, 244)" fill="rgb(241, 243, 244)">
|
||||
<animateMotion path="M 0 0 L -20 40 Z" dur="9s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<path d="M 100 350 A 40 40 0 1 1 180 350 L 180 430 A 40 40 0 1 1 100 430 Z" stroke="rgb(241, 243, 244)" fill="rgb(241, 243, 244)">
|
||||
<animateMotion path="M 140 390 L 180 360 L 140 390" dur="20s" begin="0s" repeatCount="indefinite" />
|
||||
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 140 390; -60 140 390; 0 140 390" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite" />
|
||||
</path>
|
||||
|
||||
|
||||
<rect x="900" y="600" rx="30" ry="30" width="90" height="90" stroke="rgb(129, 201, 149)" fill="rgb(129, 201, 149)">
|
||||
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="50s" type="rotate" from="0 900 550" to="360 900 550" repeatCount="indefinite" />
|
||||
</rect>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
57
dev-assistant-web/src/axios/index.js
Normal file
57
dev-assistant-web/src/axios/index.js
Normal file
@ -0,0 +1,57 @@
|
||||
import axios from 'axios';
|
||||
import {ElNotification} from "element-plus";
|
||||
import router from "~/router";
|
||||
|
||||
const http = axios.create();
|
||||
|
||||
// 添加请求拦截器
|
||||
http.interceptors.request.use(function (config) {
|
||||
return config;
|
||||
}, function (error) {
|
||||
console.log('error.response1', error.response)
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
// 添加响应拦截器
|
||||
http.interceptors.response.use(function (response) {
|
||||
if (response.data.code === 400) {
|
||||
let content = response.data.message
|
||||
ElNotification({
|
||||
title: '请求参数错误',
|
||||
message: content,
|
||||
position: 'top-right',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
return response;
|
||||
}, function (error) {
|
||||
if (error.response.status === 401) {
|
||||
ElNotification({
|
||||
title: '请重新登陆',
|
||||
message: '请重新登陆',
|
||||
position: 'top-right',
|
||||
type: 'error',
|
||||
})
|
||||
router.push({path: '/login'})
|
||||
} else if (error.response.status === 403) {
|
||||
let content = error.response.data.message
|
||||
ElNotification({
|
||||
title: '操作错误',
|
||||
message: content,
|
||||
position: 'top-right',
|
||||
type: 'error',
|
||||
})
|
||||
} else if (error.response.status === 500) {
|
||||
let content = error.response.data.message
|
||||
ElNotification({
|
||||
title: '系统错误',
|
||||
message: content,
|
||||
position: 'top-right',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
export default http
|
||||
|
@ -1,8 +1,12 @@
|
||||
<script setup>
|
||||
|
||||
import {Timer, UploadFilled, User} from "@element-plus/icons-vue";
|
||||
import {Coin, Timer, UploadFilled, User} from "@element-plus/icons-vue";
|
||||
import {addNoteApi, delNoteApi, getNoteListApi, updateNoteApi} from "~/api/note.js";
|
||||
import {ElNotification} from "element-plus";
|
||||
import {delFileApi, getFileListApi} from "~/api/file.js";
|
||||
|
||||
const files = ref([])
|
||||
const searchText = ref('')
|
||||
const notes = ref([])
|
||||
|
||||
const alertInfo = ref({
|
||||
@ -19,217 +23,351 @@ const addNote = () => {
|
||||
alertInfo.value.visible = true
|
||||
}
|
||||
|
||||
const updateNote = (note) => {
|
||||
alertInfo.value.form.id = note.id
|
||||
alertInfo.value.form.content = note.content
|
||||
alertInfo.value.visible = true
|
||||
}
|
||||
|
||||
const delNote = (note) => {
|
||||
delNoteApi({id: note.id}).then(() => {
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
position: 'top-right',
|
||||
type: 'success',
|
||||
})
|
||||
|
||||
getNoteList()
|
||||
})
|
||||
}
|
||||
|
||||
const alertClose = () => {
|
||||
alertInfo.value.form.id = ''
|
||||
alertInfo.value.form.content = ''
|
||||
}
|
||||
|
||||
const saveNote = () => {
|
||||
if (!alertInfo.value.form.id) {
|
||||
addNoteApi({content: alertInfo.value.form.content}).then(() => {
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '保存成功',
|
||||
position: 'top-right',
|
||||
type: 'success',
|
||||
})
|
||||
alertInfo.value.visible = false;
|
||||
alertInfo.value.form.id = ''
|
||||
alertInfo.value.form.content = ''
|
||||
getNoteList()
|
||||
})
|
||||
} else {
|
||||
updateNoteApi({id: alertInfo.value.form.id, content: alertInfo.value.form.content}).then(() => {
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '修改成功',
|
||||
position: 'top-right',
|
||||
type: 'success',
|
||||
})
|
||||
alertInfo.value.visible = false;
|
||||
alertInfo.value.form.id = ''
|
||||
alertInfo.value.form.content = ''
|
||||
getNoteList()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getNoteList = () => {
|
||||
getNoteListApi({searchText: searchText.value}).then(res => {
|
||||
notes.value = res.data.data
|
||||
})
|
||||
}
|
||||
|
||||
const getFileList = () => {
|
||||
getFileListApi().then(res => {
|
||||
files.value = res.data.data
|
||||
})
|
||||
}
|
||||
|
||||
const icons = [
|
||||
{
|
||||
type: ['html', 'htm'],
|
||||
type: ['.html', '.htm'],
|
||||
icon: 'icon-HTML'
|
||||
},
|
||||
{
|
||||
type: ['css'],
|
||||
type: ['.css'],
|
||||
icon: 'icon-CSS'
|
||||
},
|
||||
{
|
||||
type: ['mp3'],
|
||||
type: ['.mp3'],
|
||||
icon: 'icon-MP'
|
||||
},
|
||||
{
|
||||
type: ['csv'],
|
||||
type: ['.csv'],
|
||||
icon: 'icon-CSV'
|
||||
},
|
||||
{
|
||||
type: ['json'],
|
||||
type: ['.json'],
|
||||
icon: 'icon-JSON'
|
||||
},
|
||||
{
|
||||
type: ['js'],
|
||||
type: ['.js'],
|
||||
icon: 'icon-JS'
|
||||
},
|
||||
{
|
||||
type: ['jar'],
|
||||
type: ['.jar'],
|
||||
icon: 'icon-JAR'
|
||||
},
|
||||
{
|
||||
type: ['pdf'],
|
||||
type: ['.pdf'],
|
||||
icon: 'icon-PDF'
|
||||
},
|
||||
{
|
||||
type: ['txt'],
|
||||
type: ['.txt'],
|
||||
icon: 'icon-TXT'
|
||||
},
|
||||
{
|
||||
type: ['xls', 'xlsx'],
|
||||
type: ['.xls', '.xlsx'],
|
||||
icon: 'icon-XLS'
|
||||
},
|
||||
{
|
||||
type: ['doc', 'docx'],
|
||||
type: ['.doc', '.docx'],
|
||||
icon: 'icon-DOC'
|
||||
},
|
||||
{
|
||||
type: ['ppt', 'pptx'],
|
||||
type: ['.ppt', '.pptx'],
|
||||
icon: 'icon-PPT'
|
||||
},
|
||||
{
|
||||
type: ['gif'],
|
||||
type: ['.gif'],
|
||||
icon: 'icon-GIF'
|
||||
},
|
||||
{
|
||||
type: ['sql'],
|
||||
type: ['.sql'],
|
||||
icon: 'icon-SQL'
|
||||
},
|
||||
{
|
||||
type: ['exe'],
|
||||
type: ['.exe'],
|
||||
icon: 'icon-exe'
|
||||
},
|
||||
{
|
||||
type: ['png', 'jpg', 'jpeg'],
|
||||
type: ['.png', '.jpg', '.jpeg'],
|
||||
icon: 'icon-image'
|
||||
},
|
||||
{
|
||||
type: ['zip', 'rar', '7z'],
|
||||
type: ['.zip', '.rar', '.7z'],
|
||||
icon: 'icon-zip'
|
||||
},
|
||||
{
|
||||
type: ['xml'],
|
||||
type: ['.xml'],
|
||||
icon: 'icon-xml'
|
||||
},
|
||||
{
|
||||
type: ['log'],
|
||||
type: ['.log'],
|
||||
icon: 'icon-log'
|
||||
}
|
||||
]
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
const index = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const size = (bytes / Math.pow(1024, index)).toFixed(2);
|
||||
|
||||
return `${size} ${units[index]}`;
|
||||
}
|
||||
|
||||
const getIcon = (file) => {
|
||||
for (let i = 0; i < icons.length; i++) {
|
||||
if (icons[i].type.includes(file.type)) {
|
||||
if (icons[i].type.includes(file.fileType)) {
|
||||
return icons[i].icon
|
||||
}
|
||||
}
|
||||
return 'icon-weizhiwenjian'
|
||||
}
|
||||
|
||||
const fileUploadSuccess = () => {
|
||||
getFileList()
|
||||
}
|
||||
|
||||
const downFile = (file) => {
|
||||
window.open('/file/downloadFile?id=' + file.id)
|
||||
}
|
||||
|
||||
const delFile = (file) => {
|
||||
delFileApi({id: file.id}).then(res => {
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
position: 'top-right',
|
||||
type: 'success',
|
||||
})
|
||||
getFileList()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getNoteList()
|
||||
getFileList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img alt="" class="back-img" src="../assets/background.svg">
|
||||
<el-row justify="center">
|
||||
<el-col :span="18">
|
||||
<el-card>
|
||||
<el-col :span="12" style="padding: 5px">
|
||||
<el-card class="main-card">
|
||||
<template #header>
|
||||
<el-text size="large">
|
||||
笔记
|
||||
</el-text>
|
||||
</template>
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<el-link size="small" type="primary" :underline="false" plain icon="Plus" @click="addNote">新建笔记</el-link>
|
||||
<el-input size="small" placeholder="搜索..." clearable @clear="getNoteList" @keyup.enter="getNoteList"
|
||||
v-model="searchText"
|
||||
style="width: 35%;min-width: 250px">
|
||||
<template #append>
|
||||
<el-button @click="getNoteList" icon="Search"/>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-divider style="margin-top: 20px;margin-bottom: 8px"></el-divider>
|
||||
<el-row>
|
||||
<el-col :span="12" style="padding-right: 10px;margin-top: 25px">
|
||||
<div class="search-div">
|
||||
<el-input size="large" placeholder="搜索..." style="width: 350px">
|
||||
<template #append>
|
||||
<el-button size="large" icon="Search"/>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div style="display: flex;justify-content: flex-end;margin-top: 10px">
|
||||
<el-link size="small" type="primary" :underline="false" plain icon="Plus" @click="addNote">新建笔记</el-link>
|
||||
</div>
|
||||
<el-divider style="margin-bottom: 8px;margin-top: 8px"></el-divider>
|
||||
<el-row>
|
||||
<el-scrollbar height="calc(100vh - 160px)" style="width: 100%">
|
||||
<el-col class="note-col" v-for="note in notes">
|
||||
<el-card>
|
||||
{{ note.content }}
|
||||
<template #footer>
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<el-text type="info">
|
||||
<el-icon>
|
||||
<User/>
|
||||
</el-icon>
|
||||
{{ note.updateIp }}
|
||||
</el-text>
|
||||
<el-text type="info" style="margin-left: 10px">
|
||||
<el-icon>
|
||||
<Timer/>
|
||||
</el-icon>
|
||||
{{ note.updateTime }}
|
||||
</el-text>
|
||||
</div>
|
||||
<div>
|
||||
<el-button size="small" type="primary" plain>修改</el-button>
|
||||
<el-button size="small" type="danger" plain>删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-scrollbar>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="12" style="padding: 10px">
|
||||
<el-upload
|
||||
drag
|
||||
action="/file/uploadFile"
|
||||
multiple
|
||||
>
|
||||
<el-icon style="font-size: 30px;color: #999">
|
||||
<upload-filled/>
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖动文件到这里 或 <em>点击上传</em>
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<el-divider style="margin-bottom: 8px;margin-top: 10px"></el-divider>
|
||||
<el-scrollbar height="calc(100vh - 160px)" style="width: 100%">
|
||||
<el-col class="note-col" v-for="file in files">
|
||||
<el-card>
|
||||
<div>
|
||||
<span :class="['iconfont', getIcon(file)]"></span>
|
||||
</div>
|
||||
<div>
|
||||
{{ file.fileName }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<el-text type="info">
|
||||
<el-icon>
|
||||
<User/>
|
||||
</el-icon>
|
||||
{{ file.uploadIp }}
|
||||
</el-text>
|
||||
<el-text type="info" style="margin-left: 10px">
|
||||
<el-icon>
|
||||
<Timer/>
|
||||
</el-icon>
|
||||
{{ file.uploadTime }}
|
||||
</el-text>
|
||||
</div>
|
||||
<div>
|
||||
<el-button size="small" type="primary" plain>下载</el-button>
|
||||
<el-button size="small" type="danger" plain>删除</el-button>
|
||||
</div>
|
||||
<el-scrollbar height="calc(100vh - 190px)" style="width: 100%">
|
||||
<el-col class="note-col" v-for="note in notes">
|
||||
<el-card>
|
||||
<el-scrollbar max-height="300px">
|
||||
<el-text>{{ note.content }}</el-text>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<el-text type="info">
|
||||
<el-icon>
|
||||
<User/>
|
||||
</el-icon>
|
||||
{{ note.updateIp }}
|
||||
</el-text>
|
||||
<el-text type="info" style="margin-left: 10px">
|
||||
<el-icon>
|
||||
<Timer/>
|
||||
</el-icon>
|
||||
{{ note.updateTime }}
|
||||
</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="updateNote(note)" plain>修改</el-button>
|
||||
<el-popconfirm title="确认删除吗?" @confirm="delNote(note)">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger" plain>删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-scrollbar>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12" style="padding: 5px">
|
||||
<el-card class="main-card">
|
||||
<template #header>
|
||||
<el-text size="large">
|
||||
文件
|
||||
</el-text>
|
||||
</template>
|
||||
<el-upload
|
||||
drag
|
||||
action="/file/upload"
|
||||
multiple
|
||||
:on-success="fileUploadSuccess"
|
||||
>
|
||||
<el-icon class="el-icon--upload">
|
||||
<upload-filled/>
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖动文件到这里 或 <em>点击上传</em>
|
||||
</div>
|
||||
</el-upload>
|
||||
<el-divider style="margin-bottom: 8px;margin-top: 10px"></el-divider>
|
||||
<el-scrollbar height="calc(100vh - 280px)" style="width: 100%">
|
||||
<el-row>
|
||||
<el-col class="note-col" v-for="file in files" :span="12">
|
||||
<el-card>
|
||||
<div style="display: flex;justify-content: start">
|
||||
<div>
|
||||
<svg style="width: 80px;height: 80px" class="icon" aria-hidden="true">
|
||||
<use :href="'#' + getIcon(file)"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div style="margin-left: 15px">
|
||||
<div>
|
||||
<el-text size="large" tag="b">
|
||||
{{ file.fileName }}
|
||||
</el-text>
|
||||
</div>
|
||||
<div style="margin-top: 5px">
|
||||
<el-text type="info">
|
||||
<el-icon>
|
||||
<Coin/>
|
||||
</el-icon>
|
||||
{{ formatFileSize(file.fileSize) }}
|
||||
</el-text>
|
||||
</div>
|
||||
<div style="margin-top: 5px">
|
||||
<el-text type="info">
|
||||
<el-icon>
|
||||
<User/>
|
||||
</el-icon>
|
||||
{{ file.uploadIp }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<el-text type="info">
|
||||
<el-icon>
|
||||
<Timer/>
|
||||
</el-icon>
|
||||
{{ file.uploadTime }}
|
||||
</el-text>
|
||||
</div>
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="downFile(file)" plain>下载</el-button>
|
||||
<el-popconfirm title="确认删除吗?" @confirm="delFile(file)">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger" plain>删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog v-model="alertInfo.visible" title="笔记" draggable>
|
||||
<el-input v-model="alertInfo.form.content" type="textarea" placeholder="请输入内容" :rows="10"></el-input>
|
||||
<el-dialog :close-on-click-modal="false" v-model="alertInfo.visible" title="笔记" @closed="alertClose" draggable>
|
||||
<el-input v-model="alertInfo.form.content" type="textarea" placeholder="请输入内容" :rows="12"></el-input>
|
||||
<template #footer>
|
||||
<div style="display:flex;justify-content: end">
|
||||
<el-button type="warning" plain>取消</el-button>
|
||||
<el-button type="primary" plain>保存</el-button>
|
||||
<el-button type="primary" plain @click="saveNote">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.note-col {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -238,6 +376,12 @@ const getIcon = (file) => {
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
padding: 20px;
|
||||
padding: 16px 20px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.main-card {
|
||||
backdrop-filter: blur(60px);
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
</style>
|
@ -1,11 +1,125 @@
|
||||
<script setup>
|
||||
|
||||
import {useRouter} from "vue-router";
|
||||
import {loginApi} from "~/api/login.js";
|
||||
|
||||
const loginFormRef = ref()
|
||||
const router = useRouter()
|
||||
|
||||
const loginForm = ref({
|
||||
password: ''
|
||||
})
|
||||
|
||||
const loging = ref(false)
|
||||
const message = ref('')
|
||||
|
||||
// 表单校验规则
|
||||
const loginFormRules = reactive({
|
||||
password: [{required: true, message: '请输入口令', trigger: 'blur'}],
|
||||
})
|
||||
|
||||
const login = async (loginFormRef) => {
|
||||
await loginFormRef.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
loging.value = true
|
||||
loginApi(loginForm.value).then((res) => {
|
||||
let data = res.data
|
||||
loging.value = false
|
||||
if (data.code === 200) {
|
||||
router.push('/')
|
||||
} else {
|
||||
message.value = data.message
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(JSON.stringify(err))
|
||||
loging.value = false
|
||||
message.value = '系统错误 请稍后再试'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<img alt="" class="back-img" src="~~/assets/background.svg">
|
||||
<div class="login-container">
|
||||
<el-card class="login-card" v-loading="loging" element-loading-text="正在登录...">
|
||||
<div class="title">
|
||||
通用登陆页
|
||||
</div>
|
||||
<el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" class="login-form">
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="loginForm.password" autocomplete="new-password" clearable type="password" size="large"
|
||||
placeholder="请输入口令"></el-input>
|
||||
</el-form-item>
|
||||
<el-button type="primary" size="large" @click="login(loginFormRef)" style="width: 100%">
|
||||
登录
|
||||
</el-button>
|
||||
<div class="message">
|
||||
<el-text type="danger"> {{ message }}</el-text>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.back-img {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background-color: rgba(83, 91, 242, 0.4);
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1
|
||||
}
|
||||
|
||||
.login-container {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
backdrop-filter: blur(40px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background-color: white;
|
||||
width: 320px;
|
||||
height: 260px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 16px;
|
||||
background-color: #409EFF;
|
||||
position: relative;
|
||||
margin-left: -40px;
|
||||
color: white;
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -10px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 20px solid transparent;
|
||||
border-top: 10px solid #555;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
@ -14,8 +14,16 @@ export default defineConfig({
|
||||
'~/': `${path.resolve(__dirname, 'src')}/`,
|
||||
}
|
||||
}, server: {
|
||||
host: '127.0.0.1', port: 80
|
||||
}, plugins: [vue(), AutoImport({
|
||||
host: '127.0.0.1', port: 80,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [vue(), AutoImport({
|
||||
imports: ['vue'], resolvers: [ElementPlusResolver(), IconsResolver({
|
||||
prefix: 'Icon',
|
||||
})]
|
||||
|
Loading…
x
Reference in New Issue
Block a user