feat(ui): 实现ACG风格UI改造和动画效果

添加樱花飘落和表情点击特效组件,优化卡片动画和背景模糊效果
更新主题色为粉色并添加动漫风格背景图
引入圆角字体提升可爱风格
新增开发文档记录改造计划
This commit is contained in:
lirui
2026-01-11 12:59:41 +08:00
parent 9f4c61403e
commit b6abc32784
11 changed files with 452 additions and 10 deletions

View File

@@ -0,0 +1,24 @@
I will implement several interactive animations and visual enhancements to meet the user's request for a "prettier" and more "animated" ACG-style blog.
### 1. Emoji Click Burst Effect
I will create a new component `EmojiCursor.astro` that listens for click events and spawns floating emojis that fade out.
- **Implementation**: A client-side script that creates `span` elements with random emojis at the cursor position, animates them upwards, and removes them.
- **Emojis**: 🌸, 💖, ✨, 🍬, 🎀, 🍭, 🍡 (ACG/cute themed).
### 2. Falling Sakura (Cherry Blossom) Effect
I will create a `Sakura.astro` component using a lightweight canvas script to simulate falling cherry blossom petals.
- **Placement**: A fixed-position canvas covering the screen, with `pointer-events: none` so it doesn't interfere with clicks.
- **Visuals**: Pink/White petals gently drifting down.
### 3. Fade-in & Slide-up Animations
I will add global CSS keyframes to `src/styles/main.css` to animate elements when they appear.
- **Target**: `.card-base`, `.btn-card`
- **Effect**: `fade-in-up` (opacity 0 -> 1, translate-y 20px -> 0).
- **Stagger**: I'll try to use CSS nth-child selectors or a simple JS script to stagger the animation for a cascading effect.
### 4. Integration
I will import `EmojiCursor` and `Sakura` components into `src/layouts/Layout.astro` so they are available on every page.
### 5. Polishing
- **Live2D Suggestion**: I will add a comment/placeholder for Live2D in the layout, as a full implementation might be heavy, but I'll focus on the requested "emoji animations" first.
- **Link Hover**: Add a "rubber band" or "pulse" animation to navigation links.

View File

@@ -0,0 +1,20 @@
I will modify the project's configuration and CSS to implement an "ACG (Anime/Manga) Style" makeover. The changes will make the UI cuter, softer, and more translucent with an anime-style background.
### 1. Global Theme Configuration (`src/config.ts`)
- **Theme Color**: Change the primary hue to **Pink** (`340`) for a soft, cute look.
- **Background Image**: Enable the background and set it to a high-quality random anime wallpaper API (`https://t.alcy.cc/acg`) to ensure every visit feels fresh and "ACG".
- **Background Opacity**: Ensure the background is fully visible (`opacity: 1`).
### 2. Styling Overhaul (`src/styles/variables.styl` & `src/styles/main.css`)
- **Rounded Corners**: Increase the global border radius (`--radius-large`) to `1.75rem` to make all cards and elements look bubblier and softer.
- **Translucency (Glassmorphism)**:
- Reduce the opacity of the **Card Background** (`--card-bg`) and **Float Panel** (`--float-panel-bg`) to `0.6` (60% opacity) for light mode.
- This creates a "frosted glass" effect, allowing the anime background to show through the content.
- **Blur Effect**: Increase the `backdrop-filter` blur in `main.css` to `16px` to enhance the glass effect and ensure text readability.
### 3. Typography (`src/layouts/Layout.astro` & `tailwind.config.cjs`)
- **Cute Font**: Import the **"M PLUS Rounded 1c"** font from Google Fonts. This is a popular rounded font often used in anime/manga designs.
- **Font Integration**: Update Tailwind configuration to prioritize this new rounded font for the entire website.
### 4. Interactive Animations (`src/styles/main.css`)
- **Hover Effects**: Add a gentle "floating" animation (slight lift and shadow increase) to all cards (`.card-base`) when hovered, making the interface feel more alive and playful.

View File

@@ -0,0 +1,34 @@
<script>
function createEmoji(e: MouseEvent) {
const emojis = ["🌸", "💖", "✨", "🍬", "🎀", "🍭", "🍡", "🐱", "🍥"];
const emoji = document.createElement("div");
emoji.innerText = emojis[Math.floor(Math.random() * emojis.length)];
emoji.style.position = "fixed";
emoji.style.left = e.clientX + "px";
emoji.style.top = e.clientY + "px";
emoji.style.fontSize = Math.random() * 20 + 10 + "px";
emoji.style.pointerEvents = "none";
emoji.style.userSelect = "none";
emoji.style.zIndex = "9999";
emoji.style.transition = "all 1s ease-out";
// Initial randomness
const x = (Math.random() - 0.5) * 50;
const y = (Math.random() - 0.5) * 50;
document.body.appendChild(emoji);
// Animate
requestAnimationFrame(() => {
emoji.style.transform = `translate(${x}px, ${y - 50}px)`;
emoji.style.opacity = "0";
});
// Cleanup
setTimeout(() => {
emoji.remove();
}, 1000);
}
document.addEventListener("click", createEmoji);
</script>

View File

@@ -0,0 +1,104 @@
<canvas id="sakura-canvas"></canvas>
<style>
#sakura-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
</style>
<script>
// Sakura falling effect
const canvas = document.getElementById("sakura-canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
let width = 0;
let height = 0;
let petals = [];
const resize = () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
};
class Petal {
x: number;
y: number;
vx: number;
vy: number;
opacity: number;
size: number;
rotation: number;
rotationSpeed: number;
color: string;
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height - height;
this.vx = Math.random() * 1 + 0.5;
this.vy = Math.random() * 1 + 0.5;
this.opacity = Math.random() * 0.5 + 0.3;
this.size = Math.random() * 10 + 5;
this.rotation = Math.random() * 360;
this.rotationSpeed = (Math.random() - 0.5) * 2;
this.color = `rgba(255, 183, 197, ${this.opacity})`; // Sakura pink
}
update() {
this.x += this.vx;
this.y += this.vy;
this.rotation += this.rotationSpeed;
if (this.y > height) {
this.reset();
this.y = -20;
}
if (this.x > width) {
this.x = -20;
}
}
draw() {
if (!ctx) return;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate((this.rotation * Math.PI) / 180);
ctx.fillStyle = this.color;
ctx.beginPath();
// Draw a simple petal shape
ctx.moveTo(0, 0);
ctx.bezierCurveTo(this.size / 2, -this.size / 2, this.size, 0, 0, this.size);
ctx.bezierCurveTo(-this.size, 0, -this.size / 2, -this.size / 2, 0, 0);
ctx.fill();
ctx.restore();
}
}
const init = () => {
resize();
petals = Array.from({ length: 50 }, () => new Petal()); // 50 petals
animate();
};
const animate = () => {
if (!ctx) return;
ctx.clearRect(0, 0, width, height);
petals.forEach((petal) => {
petal.update();
petal.draw();
});
requestAnimationFrame(animate);
};
window.addEventListener("resize", resize);
init();
</script>

View File

@@ -19,25 +19,23 @@ export const siteConfig: SiteConfig = {
keywords: [], keywords: [],
lang: "zh_CN", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th' lang: "zh_CN", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th'
themeColor: { themeColor: {
hue: 361, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345 hue: 340, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
fixed: false, // Hide the theme color picker for visitors fixed: false, // Hide the theme color picker for visitors
forceDarkMode: false, // Force dark mode and hide theme switcher forceDarkMode: false, // Force dark mode and hide theme switcher
}, },
banner: { banner: {
enable: false, enable: false,
src: "/xinghui.avif", // Relative to the /src directory. Relative to the /public directory if it starts with '/' src: "/xinghui.avif", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
position: "center", // Equivalent to object-position, only supports 'top', 'center', 'bottom'. 'center' by default position: "center", // Equivalent to object-position, only supports 'top', 'center', 'bottom'. 'center' by default
credit: { credit: {
enable: true, // Display the credit text of the banner image enable: true, // Display the credit text of the banner image
text: "Pixiv @chokei", // Credit text to be displayed text: "Pixiv @chokei", // Credit text to be displayed
url: "https://www.pixiv.net/artworks/122782209", // (Optional) URL link to the original artwork or artist's page url: "https://www.pixiv.net/artworks/122782209", // (Optional) URL link to the original artwork or artist's page
}, },
}, },
background: { background: {
enable: true, // Enable background image enable: true, // Enable background image
src: "", // Background image URL (supports HTTPS) src: "https://t.alcy.cc/acg", // Background image URL (supports HTTPS)
position: "center", // Background position: 'top', 'center', 'bottom' position: "center", // Background position: 'top', 'center', 'bottom'
size: "cover", // Background size: 'cover', 'contain', 'auto' size: "cover", // Background size: 'cover', 'contain', 'auto'
repeat: "no-repeat", // Background repeat: 'no-repeat', 'repeat', 'repeat-x', 'repeat-y' repeat: "no-repeat", // Background repeat: 'no-repeat', 'repeat', 'repeat-x', 'repeat-y'

View File

@@ -0,0 +1,239 @@
---
title: 开发日记/SpringCloud项目配合maven动态启用不同配置文件设计
published: 2026-01-11T12:53:19
description: ''
image: ''
draft: false
lang: ''
category: '开发日记'
---
## 背景
在多模块(父工程 + 多个子模块)的 SpringCloud / SpringBoot 项目里我们通常会有多套环境配置dev/test/prod比如
- 数据库、Redis、MQ 地址不同
- Nacos / Config Server 的命名空间、group 不同
- 日志级别、监控开关不同
问题在于:**子模块是可独立启动的**,但它们的配置又希望能跟随父模块选择的 Maven Profile 自动切换,而不是每次都手动改 `application.yml` 或 IDE 的 VM Options。
## 目标
- 用 Maven Profile 统一管理环境dev/test/prod**一处配置,多模块复用**
- 打包时根据 `-Pdev/-Pprod` 自动写入激活的 Spring Profile
- 子模块的 `application.yml` 不硬编码环境,而是“动态注入”
- 本地运行、CI 打包、Docker 部署都能保持同一套切换逻辑
## 思路总览(核心点)
1. **父模块parent/pom.xml用 `<profiles>` 维护环境变量**,比如 `profile.active=dev``config.server.url=...`
2. **子模块开启资源过滤resource filtering**,让 `application.yml` 支持占位符替换
3. `application.yml` 用占位符写 `spring.profiles.active`,让它随 Maven Profile 注入(例如 `@profile.active@`
4. 运行 / 打包时只需要切换 Maven Profile`mvn -Pdev package``mvn -Pprod package`
下面重点讲两件事:**父模块 profiles 怎么写**,以及 **子模块 application.yml 怎么实现动态配置**
## 父模块Maven Profiles 统一管理环境
在父模块 `pom.xml` 中定义环境 Profiledev/test/prod每个 profile 只负责两类事情:
- 提供“环境变量”properties
- 参与资源过滤(让子模块能读到这些变量)
示例(父模块 `pom.xml`,只保留关键段落):
```xml
<project>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<properties>
<maven.resources.filtered>true</maven.resources.filtered>
<profile.active>dev</profile.active>
</properties>
<profiles>
<profile>
<id>dev</id>
<properties>
<profile.active>dev</profile.active>
<config.server.url>http://127.0.0.1:8888</config.server.url>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
<config.server.url>http://test-config:8888</config.server.url>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<profile.active>prod</profile.active>
<config.server.url>http://prod-config:8888</config.server.url>
</properties>
</profile>
</profiles>
</project>
```
### 为什么把 Profile 写在父模块
- 多模块下每个子模块都依赖 parent**配置天然继承**
- 切换环境只需控制父模块 profileCI/CD 更好统一
- 对团队协作友好:大家不需要各自维护一份“本地 dev 配置”
### 使用方式(命令)
```bash
# dev 打包
mvn -Pdev clean package
# prod 打包
mvn -Pprod clean package
```
在 IDE例如 IntelliJ IDEA也可以在 Maven 面板里勾选对应 profile然后运行子模块的启动类即可前提是子模块启用了资源过滤后面会讲
## 子模块application.yml 如何实现动态配置
核心就是一句话:**让 `application.yml` 里的值由 Maven 过滤替换**。
### 1子模块开启资源过滤resource filtering
在子模块(或统一放在父模块的 `<build><pluginManagement>` 里让子模块继承)配置 resources 过滤。
以子模块 `pom.xml` 为例(关键段落):
```xml
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
```
这样 Maven 在 `process-resources` 阶段会对 `src/main/resources` 下的文件做占位符替换。
### 2在 application.yml 里引用父模块 Profile 变量
在子模块 `src/main/resources/application.yml` 里写:
```yaml
spring:
profiles:
active: "@profile.active@"
app:
config-server-url: "@config.server.url@"
```
这里用的是 Maven 的 `@...@` 占位符格式(它比 `${...}` 在 YAML 中更不容易和 Spring 本身的占位符混淆)。
当你执行:
```bash
mvn -Pprod clean package
```
打包后的 `target/classes/application.yml` 会变成:
```yaml
spring:
profiles:
active: "prod"
app:
config-server-url: "http://prod-config:8888"
```
### 3配合多套配置文件application-{profile}.yml
建议把“环境差异”尽量放到 `application-dev.yml` / `application-prod.yml` 中,让 `application.yml` 只负责“选择环境”与公共配置。
结构示例:
```text
src/main/resources/
application.yml
application-dev.yml
application-test.yml
application-prod.yml
```
`application.yml`(只写公共 + 激活环境):
```yaml
spring:
profiles:
active: "@profile.active@"
server:
port: 8080
```
`application-dev.yml`(写 dev 差异):
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/app_dev
```
`application-prod.yml`(写 prod 差异):
```yaml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/app_prod
```
这样一来:**父模块 Maven Profile 决定 `spring.profiles.active`**SpringBoot 再根据 active profile 自动加载对应的 `application-{profile}.yml`
## 常见坑与建议
### 1本地直接运行为什么不生效
`@profile.active@` 只会在 **Maven 资源处理** 时被替换。
如果你直接用 IDE “运行启动类”,但没有触发 Maven 的 `process-resources`,就会出现占位符未替换的情况。
推荐做法:
- 在 IDE 使用 Maven Profile并确保运行前执行了 `process-resources`(常见方式是先 `mvn -Pdev -DskipTests package` 一次)
- 或者在 Run Configuration 里临时加 `-Dspring.profiles.active=dev`(但这会绕过“父模块统一管理”,不建议作为团队默认方案)
### 2过滤导致 application.yml 被破坏
如果你在 `application.yml` 里本身也使用 `${...}`(比如 Spring 的占位符Maven 过滤可能会误处理。
建议:
-`@...@` 作为 Maven 注入占位符
- Spring 自己的占位符继续用 `${...}`,避免混用
### 3CI/CD 建议
CI 里只需要:
```bash
mvn -Pprod clean package -DskipTests
```
产物里 profile 已经写死为 prod运行时不需要额外设置。
如果你希望“同一包多环境运行”,那就不要在构建期写死 `spring.profiles.active`,改为运行期用环境变量/启动参数控制(这是另一条路线,和本文目标不同)。
## 小结
- 父模块 `<profiles>` 用来统一维护环境变量(重点:`profile.active`
- 子模块开启资源过滤,让 `application.yml` 能读取父模块的变量
- `application.yml``spring.profiles.active: "@profile.active@"` 达到“动态切换配置”的效果

View File

@@ -6,6 +6,7 @@ image: ''
draft: false draft: false
lang: '' lang: ''
category: '开发日记'
--- ---
# 开发日记/记一次springcloud项目启动显示端口被占用但是查不到占用进程的问题 # 开发日记/记一次springcloud项目启动显示端口被占用但是查不到占用进程的问题

View File

@@ -6,6 +6,8 @@ import "@fontsource/roboto/700.css";
import { profileConfig, siteConfig } from "@/config"; import { profileConfig, siteConfig } from "@/config";
import ConfigCarrier from "@components/ConfigCarrier.astro"; import ConfigCarrier from "@components/ConfigCarrier.astro";
import Sakura from "@components/widget/Sakura.astro";
import EmojiCursor from "@components/widget/EmojiCursor.astro";
import { import {
AUTO_MODE, AUTO_MODE,
BANNER_HEIGHT, BANNER_HEIGHT,
@@ -91,6 +93,9 @@ const bannerOffset =
<!-- 手机端适配 --> <!-- 手机端适配 -->
<head> <head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@400;500;700&display=swap" rel="stylesheet">
<title>{pageTitle}</title> <title>{pageTitle}</title>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -357,6 +362,8 @@ const bannerOffset =
data-overlayscrollbars-initialize data-overlayscrollbars-initialize
> >
<div id="bg-box"></div> <div id="bg-box"></div>
<Sakura />
<EmojiCursor />
<ConfigCarrier /> <ConfigCarrier />
<slot /> <slot />

View File

@@ -2,9 +2,24 @@
@layer components { @layer components {
.card-base { .card-base {
@apply rounded-[var(--radius-large)] overflow-hidden bg-[var(--card-bg)] transition; @apply rounded-[var(--radius-large)] overflow-hidden bg-[var(--card-bg)] transition duration-300;
backdrop-filter: blur(12px); backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(16px);
animation: fade-in-up 0.6s ease-out backwards;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-base:hover {
@apply -translate-y-1 shadow-lg;
} }
h1, h2, h3, h4, h5, h6, p, a, span, li, ul, ol, blockquote, code, pre, table, th, td, strong { h1, h2, h3, h4, h5, h6, p, a, span, li, ul, ol, blockquote, code, pre, table, th, td, strong {
@apply transition; @apply transition;

View File

@@ -66,8 +66,8 @@ define({
--link-hover: oklch(0.95 0.025 var(--hue)) oklch(0.40 0.08 var(--hue)) --link-hover: oklch(0.95 0.025 var(--hue)) oklch(0.40 0.08 var(--hue))
--link-active: oklch(0.90 0.05 var(--hue)) oklch(0.35 0.07 var(--hue)) --link-active: oklch(0.90 0.05 var(--hue)) oklch(0.35 0.07 var(--hue))
--float-panel-bg: rgba(255, 255, 255, 0.9) unquote("oklch(0.19 0.015 var(--hue) / 0.9)") --float-panel-bg: rgba(255, 255, 255, 0.6) unquote("oklch(0.19 0.015 var(--hue) / 0.6)")
--float-panel-bg-transparent: rgba(255, 255, 255, 0.8) rgba(23, 23, 23, 0.8) --float-panel-bg-transparent: rgba(255, 255, 255, 0.6) rgba(23, 23, 23, 0.6)
--float-panel-bg-opaque: rgba(255, 255, 255, 1) unquote("oklch(0.19 0.015 var(--hue))") --float-panel-bg-opaque: rgba(255, 255, 255, 1) unquote("oklch(0.19 0.015 var(--hue))")
--scrollbar-bg-light: black(0.4) --scrollbar-bg-light: black(0.4)

View File

@@ -6,7 +6,7 @@ module.exports = {
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
sans: ["Roboto", "sans-serif", ...defaultTheme.fontFamily.sans], sans: ["'M PLUS Rounded 1c'", "Roboto", "sans-serif", ...defaultTheme.fontFamily.sans],
}, },
}, },
}, },