开发助手

This commit is contained in:
huangyp 2025-02-13 17:58:37 +08:00
parent 165a74d398
commit 9e8fa86a10
41 changed files with 769 additions and 312 deletions

1
.idea/modules.xml generated
View File

@ -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>

View 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.

View File

@ -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'

View File

@ -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("密码错误");
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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);

View 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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
/**
* 获取笔记列表
*

View File

@ -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;
}

View File

@ -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;

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -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>

View 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)

View File

@ -0,0 +1,3 @@
import http from '~/axios/index.js';
export const loginApi = data => http.post("/authentication/login", data)

View 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)

View 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

View 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

View File

@ -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>

View File

@ -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>

View File

@ -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',
})]