Skip to content

Commit c0cd851

Browse files
yyyCodeclaude
andcommitted
feat: add homepage hero image with admin upload control
- Add hero_image_url to site_config for storing hero image URL - Add hero image display section (16:9) on homepage above hero text - Add image upload preview and remove button in admin site settings - Update CSS styles for both homepage and admin panel Co-Authored-By: Claude <noreply@anthropic.com>
1 parent dcdbbae commit c0cd851

7 files changed

Lines changed: 425 additions & 2 deletions

File tree

OpenBlog-business/src/main/java/com/yqz/openblog/article/controller/ArticleController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.yqz.openblog.article.controller;
2-
2+
1
33
import com.yqz.openblog.article.dto.ArticleCreateRequest;
44
import com.yqz.openblog.article.dto.ArticleDetailResponse;
55
import com.yqz.openblog.article.dto.ArticleListItemResponse;

OpenBlog-business/src/main/resources/sql/site-config.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ INSERT IGNORE INTO site_config (config_key, config_value) VALUES
2020
('about_text', '这里是个人博客,用来记录设计、技术与思考。'),
2121
('default_avatar_url','https://via.placeholder.com/120x120.png?text=OpenBlog'),
2222
('site_start_date', '2026-03-20'),
23-
('footer_copyright', '© 2026 OpenBlog');
23+
('footer_copyright', '© 2026 OpenBlog'),
24+
('hero_image_url', '');
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# 首页 Hero 图片功能 Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** 在首页顶部增加 16:9 独立图片展示区存放网站主旨视觉图,并在后台站点设置页提供上传更换入口
6+
7+
**Architecture:** 复用现有 `site_config` key-value 机制存储图片 URL,`uploadMedia` 接口上传,`siteConfig` provide/inject 传递至首页渲染。零新增后端接口。
8+
9+
**Tech Stack:** Vue 3 (Composition API), Spring Boot, MySQL
10+
11+
---
12+
13+
### Task 1: 数据库添加初始配置行
14+
15+
**Files:**
16+
- Modify: `OpenBlog-business/src/main/resources/sql/site-config.sql`
17+
18+
- [ ] **Step 1: 在 site-config.sql 中新增 `hero_image_url` 初始数据**
19+
20+
`INSERT IGNORE INTO site_config` 的 values 列表末尾追加一行:
21+
22+
```sql
23+
('hero_image_url', ''),
24+
```
25+
26+
- [ ] **Step 2: Commit**
27+
28+
```bash
29+
git add OpenBlog-business/src/main/resources/sql/site-config.sql
30+
git commit -m "feat: add hero_image_url site config key"
31+
```
32+
33+
---
34+
35+
### Task 2: 后台站点设置页增加 Hero 图片上传控件
36+
37+
**Files:**
38+
- Modify: `vue/src/views/ConsoleSiteConfigView.vue`
39+
40+
- [ ] **Step 1: 在 form 对象中添加 `hero_image_url` 字段**
41+
42+
找到第 94-107 行的 `form` 对象,在 `site_start_date` 后面追加:
43+
44+
```js
45+
hero_image_url: '',
46+
```
47+
48+
- [ ] **Step 2: 在模板「站点展示」区块添加上传控件**
49+
50+
`site_start_date` 字段后面(约第 66 行附近)、`保存配置` 按钮上方,插入以下内容:
51+
52+
```html
53+
<div class="field">
54+
<div class="label">首页 Hero 图片(16:9)</div>
55+
<div class="hero-image-upload-row">
56+
<div class="hero-image-preview" v-if="form.hero_image_url">
57+
<img :src="form.hero_image_url" alt="hero preview" />
58+
</div>
59+
<div v-else class="hero-image-preview hero-image-preview--empty">
60+
<span class="hero-image-placeholder">未设置首页图片</span>
61+
</div>
62+
<div class="hero-image-upload-actions">
63+
<input
64+
ref="heroFileInput"
65+
class="input"
66+
type="file"
67+
accept="image/*"
68+
@change="onPickHeroImage"
69+
/>
70+
<button
71+
v-if="form.hero_image_url"
72+
type="button"
73+
class="btn"
74+
style="margin-top: 6px"
75+
@click="form.hero_image_url = ''"
76+
>
77+
移除图片
78+
</button>
79+
</div>
80+
</div>
81+
</div>
82+
```
83+
84+
- [ ] **Step 3: 添加上传处理函数和 import**
85+
86+
`<script setup>` 中找到 `onPickAvatar` 函数(约第 86 行),在其附近添加 `onPickHeroImage``uploadMedia` import:
87+
88+
先确认 `uploadMedia` 已在文件顶部导入,如果没有,添加:
89+
90+
```js
91+
import { uploadMedia } from '../api/media'
92+
```
93+
94+
然后在 `onPickAvatar` 之后添加:
95+
96+
```js
97+
const heroFileInput = ref(null)
98+
99+
async function onPickHeroImage(e) {
100+
const file = e.target.files?.[0]
101+
if (!file) return
102+
error.value = ''
103+
try {
104+
const resp = await uploadMedia(file)
105+
form.value.hero_image_url = resp.url
106+
// 清除 file input 以便重复选择同一文件
107+
if (heroFileInput.value) heroFileInput.value.value = ''
108+
} catch (e) {
109+
error.value = e.message || '上传失败'
110+
}
111+
}
112+
```
113+
114+
- [ ] **Step 4: Commit**
115+
116+
```bash
117+
git add vue/src/views/ConsoleSiteConfigView.vue
118+
git commit -m "feat: add hero image upload control in site settings"
119+
```
120+
121+
---
122+
123+
### Task 3: 首页新增 Hero 图片展示区
124+
125+
**Files:**
126+
- Modify: `vue/src/views/HomeView.vue`
127+
128+
- [ ] **Step 1: 在 Hero 文字区域上方插入图片区块**
129+
130+
找到 `HomeView.vue` 第 3-8 行,在 `<section class="home-hero">``<div class="home-hero-inner">` 之前插入:
131+
132+
```html
133+
<div v-if="heroImageUrl" class="home-hero-image-wrap">
134+
<img class="home-hero-image" :src="heroImageUrl" alt="网站主旨图" />
135+
</div>
136+
```
137+
138+
修改后的模板结构应如下:
139+
140+
```html
141+
<section class="home-hero">
142+
<!-- 新增:Hero 图片 -->
143+
<div v-if="heroImageUrl" class="home-hero-image-wrap">
144+
<img class="home-hero-image" :src="heroImageUrl" alt="网站主旨图" />
145+
</div>
146+
<div class="home-hero-inner">
147+
<h1 class="home-hero-title">{{ heroTitleLines[0] || '' }}<br />{{ heroTitleLines[1] || '' }}</h1>
148+
<p class="home-hero-sub" v-html="heroSubtitleHtml"></p>
149+
</div>
150+
</section>
151+
```
152+
153+
- [ ] **Step 2: 添加 `heroImageUrl` 计算属性**
154+
155+
`<script setup>` 中,在 `heroTitle` 计算属性之前(约第 106 行),添加:
156+
157+
```js
158+
const heroImageUrl = computed(() => {
159+
return (siteConfig && siteConfig.value && siteConfig.value.hero_image_url) || ''
160+
})
161+
```
162+
163+
- [ ] **Step 3: Commit**
164+
165+
```bash
166+
git add vue/src/views/HomeView.vue
167+
git commit -m "feat: display hero image on homepage"
168+
```
169+
170+
---
171+
172+
### Task 4: 添加 Hero 图片样式
173+
174+
**Files:**
175+
- Modify: `vue/src/assets/blog.css`
176+
177+
- [ ] **Step 1: 在 `.home-hero` 相关样式区域添加图片样式**
178+
179+
在现有 `.home-hero` 样式块后面(约第 426 行,`@media` 之后),追加:
180+
181+
```css
182+
/* 首页 Hero 图片 */
183+
.home-hero-image-wrap {
184+
width: 100%;
185+
max-width: 720px;
186+
border-radius: 10px;
187+
overflow: hidden;
188+
background: var(--placeholder-tint);
189+
aspect-ratio: 16 / 9;
190+
margin-bottom: 32px;
191+
}
192+
193+
.home-hero-image {
194+
width: 100%;
195+
height: 100%;
196+
display: block;
197+
object-fit: cover;
198+
}
199+
```
200+
201+
- [ ] **Step 2: 确认自适应样式**
202+
203+
检查 `@media (max-width: 680px)` 中是否需要微调:
204+
- `.home-hero-image-wrap` 不需要额外调整,因为 `width: 100%` 已自适应
205+
- 圆角和间距在移动端表现良好
206+
207+
- [ ] **Step 3: 提交**
208+
209+
```bash
210+
git add vue/src/assets/blog.css
211+
git commit -m "style: add hero image styles"
212+
```
213+
214+
---
215+
216+
### Task 5: Checkstyle / 验证与冒烟测试
217+
218+
**Files:**
219+
- Modify: none(验证性操作)
220+
221+
- [ ] **Step 1: 验证前端构建**
222+
223+
```bash
224+
cd vue && npx vite build 2>&1 | tail -20
225+
```
226+
227+
预期结果:无报错,构建成功,输出 `dist/` 目录
228+
229+
- [ ] **Step 2: 验证 SQL 语法**
230+
231+
确认 SQL 插入语句语法正确:`('hero_image_url', '')` 末尾无遗漏逗号。
232+
233+
- [ ] **Step 3: 功能验证 checklist**
234+
235+
| 检查项 | 预期 |
236+
|--------|------|
237+
| 无图时首页不显示图片区域 | `.home-hero-image-wrap` 不渲染 |
238+
| 有图时显示 16:9 图片 | `aspect-ratio: 16/9` 生效 |
239+
| 后台设置页显示预览 | 上传后预览图可见 |
240+
| 移除图片功能可清空 | 点击"移除图片"后 `hero_image_url` 清空 |
241+
| 保存配置后刷新首页 | 图片展示与后台设置一致 |
242+
243+
- [ ] **Step 4: 提交最终验证
244+
245+
```bash
246+
git add -A
247+
git commit -m "feat: add homepage hero image with admin upload control"
248+
```
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# 首页 Hero 图片功能设计
2+
3+
## 概述
4+
在首页顶部(导航栏下方)增加一个 16:9 比例的独立图片展示区,用于展示网站主旨/理念的视觉图。后台提供上传更换入口。
5+
6+
## 涉及页面
7+
- **前台首页**`vue/src/views/HomeView.vue`
8+
- **后台站点设置**`vue/src/views/ConsoleSiteConfigView.vue`
9+
- **样式**`vue/src/assets/blog.css`
10+
11+
## 数据方案
12+
利用现有 `site_config` 表,新增配置键:
13+
| key | value 示例 | 说明 |
14+
|-----|-----------|------|
15+
| `hero_image_url` | `https://.../hero.png` | 首页 Hero 图片 URL,空串表示无图 |
16+
17+
后台通过现有 `PUT /api/v1/site/config` 保存,前台通过 `siteConfig.hero_image_url` 读取。
18+
图片上传复用现有 `POST /api/v1/media/upload` 接口(`src/api/media.js``uploadMedia`)。
19+
20+
## 首页布局变化
21+
22+
```
23+
[BlogHeader 导航栏]
24+
┌─────────────────────────────────────┐
25+
│ 16:9 Hero Banner 图片 │ ← 新增
26+
│ (网站主旨/理念视觉图) │
27+
│ 有图时显示图片,无图时不占位 │
28+
└─────────────────────────────────────┘
29+
[Home Hero 标题 + 副标题] ← 保留现有
30+
[精选文章]
31+
[最新文章]
32+
[订阅区]
33+
```
34+
35+
图片区样式要求:
36+
- 16:9 宽高比(`aspect-ratio: 16/9`
37+
- 全宽显示(与内容区同宽,max-width: 720px 居中对齐,或与 Hero 文字对齐)
38+
- 圆角 10px
39+
- 图片 `object-fit: cover`
40+
- 无图时不渲染该区域
41+
42+
## 后台配置页面
43+
在 ConsoleSiteConfigView.vue 的「站点展示」区块增加「首页 Hero 图片」行:
44+
- 当前图片预览(16:9 缩略图,圆角)
45+
- 文件选择 `<input type="file" accept="image/*">`
46+
- 上传后自动填充 URL 并刷新预览
47+
- 有图时显示预览 + 更换按钮,无图时显示占位文字
48+
49+
## 实现步骤
50+
1.`site-config.sql` 中新增 `hero_image_url` 初始数据行
51+
2. `ConsoleSiteConfigView.vue` — 添加表单字段 `hero_image_url` + 文件上传控件
52+
3. `HomeView.vue` — 在 Hero 文字上方插入图片区,条件渲染
53+
4. `blog.css` — 新增 `.home-hero-image` 样式类
54+
55+
## 涉及文件清单
56+
| 文件 | 改动 |
57+
|------|------|
58+
| `vue/src/views/HomeView.vue` | 新增 `heroImageUrl` 计算属性 + 图片模板 |
59+
| `vue/src/views/ConsoleSiteConfigView.vue` | 新增 `hero_image_url` 字段 + 上传控件 |
60+
| `vue/src/assets/blog.css` | 新增 `.home-hero-image` 相关样式 |
61+
| `OpenBlog-business/src/main/resources/sql/site-config.sql` | 新增初始数据行 |

vue/src/assets/blog.css

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,24 @@ body {
425425
.home-hero-sub { font-size: 14px; }
426426
}
427427

428+
/* 首页 Hero 图片 */
429+
.home-hero-image-wrap {
430+
width: 100%;
431+
max-width: 720px;
432+
border-radius: 10px;
433+
overflow: hidden;
434+
background: var(--placeholder-tint);
435+
aspect-ratio: 16 / 9;
436+
margin-bottom: 32px;
437+
}
438+
439+
.home-hero-image {
440+
width: 100%;
441+
height: 100%;
442+
display: block;
443+
object-fit: cover;
444+
}
445+
428446
.home-featured {
429447
padding: 18px;
430448
}
@@ -1737,6 +1755,47 @@ body {
17371755
font-size: 13px;
17381756
}
17391757

1758+
/* 后台 Hero 图片上传控件 */
1759+
.hero-image-upload-row {
1760+
display: flex;
1761+
gap: 16px;
1762+
align-items: flex-start;
1763+
}
1764+
1765+
.hero-image-preview {
1766+
width: 260px;
1767+
aspect-ratio: 16 / 9;
1768+
border-radius: 10px;
1769+
overflow: hidden;
1770+
border: 1px solid var(--console-border, var(--border));
1771+
background: var(--console-bg, var(--surface-soft));
1772+
flex-shrink: 0;
1773+
}
1774+
1775+
.hero-image-preview img {
1776+
width: 100%;
1777+
height: 100%;
1778+
object-fit: cover;
1779+
display: block;
1780+
}
1781+
1782+
.hero-image-preview--empty {
1783+
display: flex;
1784+
align-items: center;
1785+
justify-content: center;
1786+
}
1787+
1788+
.hero-image-placeholder {
1789+
font-size: 13px;
1790+
font-weight: 600;
1791+
color: var(--console-muted, var(--muted));
1792+
}
1793+
1794+
.hero-image-upload-actions {
1795+
flex: 1;
1796+
min-width: 0;
1797+
}
1798+
17401799
.grid2 {
17411800
display: grid;
17421801
grid-template-columns: 1fr 1fr;

0 commit comments

Comments
 (0)