diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/config/WebConfig.java b/src/main/java/uk/ac/cf/spring/demo/sports/config/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..4bd1f4302b749e201d9d8f90819e33f9ae8043ec --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/config/WebConfig.java @@ -0,0 +1,14 @@ +package uk.ac.cf.spring.demo.sports.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/uploads/**") + .addResourceLocations("file:./uploads/"); // 指定静态资源的实际路径 + } +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageController.java b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageController.java new file mode 100644 index 0000000000000000000000000000000000000000..c137de20530d933d88f72b4ff075e905d877e7ff --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageController.java @@ -0,0 +1,39 @@ +package uk.ac.cf.spring.demo.sports.images; +import java.io.IOException; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/images") +public class ImageController { + private final ImageService imageService; + + @Autowired + public ImageController(ImageService imageService) { + this.imageService = imageService; + } + + @GetMapping + public List<Image> getImages() { + List<Image> images = imageService.getAllImages(); + System.out.println("Returned JSON data: " + images); + return imageService.getAllImages(); + } + + @PostMapping + public ResponseEntity<Void> uploadImage(@RequestParam("image") MultipartFile file) { + try { + imageService.saveImage(file); + List<Image> images = imageService.getAllImages(); + System.out.println("Returned JSON data: " + images); + return ResponseEntity.ok().build(); + } catch (IOException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} + diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageRepository.java b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..3feedb21d13c98ddd40abc5592b4e2436dc5e50c --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageRepository.java @@ -0,0 +1,42 @@ +package uk.ac.cf.spring.demo.sports.images; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +@Repository +public class ImageRepository { + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ImageRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + + public int save(String url) { + String sql = "INSERT INTO images (url) VALUES (?)"; + return jdbcTemplate.update(sql, url); + } + // 获取所有图片记录 + public List<Image> findAll() { + String sql = "SELECT * FROM images"; + return jdbcTemplate.query(sql, new ImageRowMapper()); + } + //映射方法 + private static class ImageRowMapper implements RowMapper<Image> { + @Override + public Image mapRow(ResultSet rs, int rowNum) throws SQLException { + Image image = new Image(); + image.setId(rs.getInt("id")); + image.setUrl(rs.getString("url")); + image.setUploadedAt(rs.getTimestamp("uploaded_at")); + return image; + } + } + +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageService.java b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageService.java new file mode 100644 index 0000000000000000000000000000000000000000..7ea853716315f773b269a6b96fb220e54dbc5f19 --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageService.java @@ -0,0 +1,11 @@ +package uk.ac.cf.spring.demo.sports.images; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +public interface ImageService { + List<Image> getAllImages(); + void saveImage(MultipartFile file) throws IOException; +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageServiceImpl.java b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..bac30bb083bd67189a56cfbd86f3fd4bb8373572 --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/images/ImageServiceImpl.java @@ -0,0 +1,50 @@ +package uk.ac.cf.spring.demo.sports.images; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; + +@Service +public class ImageServiceImpl implements ImageService { + + private final ImageRepository imageRepository; + + @Value("${image.upload.dir}") + private String uploadDir; + + @Autowired + public ImageServiceImpl(ImageRepository imageRepository) { + this.imageRepository = imageRepository; + } + + @Override + public List<Image> getAllImages() { + return imageRepository.findAll(); + } + + @Override + public void saveImage(MultipartFile file) throws IOException { + // 生成唯一文件名 + String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename(); + Path filePath = Paths.get(uploadDir, fileName); + + // 确保目录存在 + Files.createDirectories(filePath.getParent()); + + // 将文件写入目标目录 + Files.write(filePath, file.getBytes()); + + // 保存图片路径到数据库 + String url = "/uploads/" + fileName; + imageRepository.save(url); + } + +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java b/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java index 77c1947120a65521f76bcece1485890c00989625..e401b0ff6a5f35c377ab513f4f56e65afceddf82 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java @@ -31,6 +31,8 @@ public class SecurityConfig { "/html/login.html" , // login "/rankings", //ranking data path "/rankings/**", + "/uploads", //images + "/uploads/**", }; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a025cc6041e1be9ce644d6cdb23ec401ff2e70e6..59586e3f5bac34c5411ad3b7cfa4e13e2aebf1e4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,5 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver #spring.mvc.view.suffix=.html # ???? spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html -spring.thymeleaf.mode=HTML \ No newline at end of file +spring.thymeleaf.mode=HTML +image.upload.dir=./uploads \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 4e0ea9ec9070dc0cd41f4bb326cbf4ecf9d64ad5..32ef80c21843a90af4ef0ae5c8aed599fcf28c77 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -40,7 +40,6 @@ CREATE TABLE IF NOT EXISTS ranking ( ); CREATE TABLE images ( id INT AUTO_INCREMENT PRIMARY KEY, - filename VARCHAR(255) NOT NULL, -- 图片文件名或路径 - uploader VARCHAR(100) NOT NULL, -- 上传者名称 - upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 上传时间 + url VARCHAR(255) NOT NULL, -- 图片文件名或路径 + uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP-- 上传时间 ); diff --git a/src/main/resources/static/css/gallery.css b/src/main/resources/static/css/gallery.css new file mode 100644 index 0000000000000000000000000000000000000000..0404d3e5d594bcdb5f44fe1ba4397b4f29cf5a9e --- /dev/null +++ b/src/main/resources/static/css/gallery.css @@ -0,0 +1,89 @@ +/* General Styles for Gallery */ +.gallery-container { + max-width: 1200px; + margin: 20px auto; + padding: 20px; + text-align: center; + font-family: Arial, sans-serif; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.gallery-container h1 { + margin-bottom: 20px; + font-size: 2em; + color: #333; +} + +/* Carousel Styles */ +.carousel { + position: relative; + overflow: hidden; + width: 80%; + margin: 0 auto; + max-height: 400px; +} + +#carousel-images { + display: flex; + transition: transform 0.5s ease-in-out; +} + +#carousel-images img { + max-width: 100%; + max-height: 400px; + object-fit: cover; + border-radius: 8px; +} + +.carousel-button { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 10px; + cursor: pointer; + border-radius: 50%; + font-size: 1.5em; +} + +.carousel-button.prev { + left: 10px; +} + +.carousel-button.next { + right: 10px; +} + +.carousel-button:hover { + background-color: rgba(0, 0, 0, 0.8); +} + +/* Upload Section */ +.upload-section { + margin-top: 20px; +} + +.upload-btn { + background-color: #007bff; + color: white; + border: none; + padding: 10px 20px; + font-size: 1em; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.upload-btn:hover { + background-color: #0056b3; +} +.carousel img { + display: block; /* Ensure the image displays as a block-level element */ + margin: 0 auto; /* Horizontally centers the image */ + max-width: 100%; /* Keep image responsive */ + height: auto; +} \ No newline at end of file diff --git a/src/main/resources/static/html/gallery.html b/src/main/resources/static/html/gallery.html new file mode 100644 index 0000000000000000000000000000000000000000..71a86f2db25eec323ba3cd86bcc15170d7d08dbd --- /dev/null +++ b/src/main/resources/static/html/gallery.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>image gallery</title> + <link rel="stylesheet" href="/css/navBar.css"> + <link rel="stylesheet" href="/css/footer.css"> + <link rel="stylesheet" href="/css/gallery.css"> +</head> +<body> +<div id="navbar-container"></div> + +<!-- Main Content --> +<main class="gallery-container"> + <h1>Image Gallery</h1> + + <!-- Carousel --> + <div class="carousel"> + <div id="carousel-images"> + <!-- Images will be dynamically inserted here --> + </div> + <button class="carousel-button prev" onclick="prevImage()">❮</button> + <button class="carousel-button next" onclick="nextImage()">❯</button> + </div> + + <!-- Upload Section --> + <div class="upload-section"> + <button class="upload-btn" onclick="document.getElementById('file-input').click()">Upload Image</button> + <input type="file" id="file-input" accept="image/*" style="display: none;" onchange="uploadImage(event)"> + </div> +</main> + +<!-- Footer Section --> +<div id="footer-container"></div> + +<!-- JavaScript --> +<script src="/js/navBar.js"></script> +<script src="/js/footer.js"></script> +<script src="/js/gallery.js"></script> +<script> + // Load header and footer + loadNavbar('navbar-container'); + loadFooter('footer-container'); +</script> +</body> +</html> \ No newline at end of file diff --git a/src/main/resources/static/html/ranking.html b/src/main/resources/static/html/ranking.html index c9ff9753da94074d46005e75bb706310c07c6d9c..691ed42b53246422fa502a51bbbd800363ca12f8 100644 --- a/src/main/resources/static/html/ranking.html +++ b/src/main/resources/static/html/ranking.html @@ -52,6 +52,5 @@ loadFooter('footer-container'); </script> <script src="/js/rankingTable.js"></script> -<script src="/js/matchSchedule.js"></script> </body> </html> \ No newline at end of file diff --git a/src/main/resources/static/js/gallery.js b/src/main/resources/static/js/gallery.js new file mode 100644 index 0000000000000000000000000000000000000000..68949a4ed82cabdaf0cfb421cc27e5db464190d6 --- /dev/null +++ b/src/main/resources/static/js/gallery.js @@ -0,0 +1,70 @@ +let currentIndex = 0; +let images = []; // This will hold the URLs of the images + +// Load images into the carousel +function loadImages() { + const carouselImages = document.getElementById("carousel-images"); + carouselImages.innerHTML = ""; // Clear current images + + images.forEach((imageUrl, index) => { + const img = document.createElement("img"); + img.src = imageUrl; // Correctly use the URL for the image src + console.log(img.src) + img.alt = `Image ${index + 1}`; + img.style.display = index === currentIndex ? "block" : "none"; + carouselImages.appendChild(img); + }); +} + +// Show previous image +function prevImage() { + currentIndex = (currentIndex - 1 + images.length) % images.length; + loadImages(); +} + +// Show next image +function nextImage() { + currentIndex = (currentIndex + 1) % images.length; + loadImages(); +} + +// Upload image +function uploadImage(event) { + const file = event.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append("image", file); + + fetch("/api/images", { + method: "POST", + body: formData, + }) + .then(response => { + if (!response.ok) { + throw new Error("Failed to upload image"); + } + return response.json(); + }) + .then(data => { + images.push(data.url); // Assuming the server returns the image URL + loadImages(); + }) + .catch(error => { + console.error("Error uploading image:", error); + }); +} + +// Initialize the gallery on page load +window.onload = () => { + fetch("/api/images") + .then(response => response.json()) + .then(data => { + images = data.map(image => image.url); // Extract URLs from the server response + loadImages(); + }) + .catch(error => { + console.error("Error loading images:", error); + }); +}; +