Skip to content

Commit 83e5233

Browse files
author
yqz
committed
Merge branch 'refs/heads/master' into web-refactor
# Conflicts: # frontend/src/components/BlogHeader.vue
2 parents 02fee6f + 5f5d9f2 commit 83e5233

21 files changed

Lines changed: 758 additions & 154 deletions

.cursor/rules/rule.mdc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
alwaysApply: true
3+
---
4+
5+
任何前端代码的编写都要求和之前项目整体的前端的风格,颜色搭配保持一致

frontend/src/api/feedback-admin.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { request } from './http'
2+
3+
export function fetchPendingFeedback(page = 0, size = 50) {
4+
return request(`/api/v1/admin/feedback/pending?page=${page}&size=${size}`, {
5+
method: 'GET',
6+
withAuth: true
7+
})
8+
}
9+

frontend/src/api/feedback.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { request } from './http'
2+
3+
export function createFeedback(payload) {
4+
return request('/api/v1/feedback', {
5+
method: 'POST',
6+
body: JSON.stringify(payload)
7+
})
8+
}
9+

frontend/src/api/http.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ function resolveApiBase() {
1010
const v = import.meta.env.VITE_API_BASE
1111
if (v === '') return ''
1212
if (v != null && v !== '') return v
13-
return import.meta.env.DEV ? 'http://8.138.32.217:8082' : ''
13+
// 开发环境默认走同域 /api(由 Vite proxy 转到本机后端),避免误连远端导致 token/权限不一致
14+
return ''
1415
}
1516

1617
export const API_BASE = resolveApiBase()
@@ -69,12 +70,14 @@ export async function request(path, options = {}) {
6970
const err = new Error(msg)
7071
err.code = json.code
7172
err.httpStatus = res.status
73+
err.traceId = json.traceId
7274
throw err
7375
}
7476

7577
if (!res.ok) {
7678
const err = new Error(`HTTP ${res.status}`)
7779
err.httpStatus = res.status
80+
err.traceId = json?.traceId
7881
throw err
7982
}
8083

Lines changed: 128 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,96 @@
11
<template>
2-
<header class="site-top-bar" role="banner">
3-
<div class="site-top-bar-inner">
4-
<router-link class="site-brand" to="/" aria-label="烧仙草冰室,返回首页">
5-
<span class="site-brand-logo" aria-hidden="true">
6-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
7-
<!-- cup -->
8-
<path d="M7 6h10l-1.2 14.2a2 2 0 0 1-2 1.8H10.2a2 2 0 0 1-2-1.8L7 6z" />
9-
<!-- rim -->
10-
<path d="M6.2 6h11.6" />
11-
<!-- ice cubes -->
12-
<path d="M10 9.2h2.6v2.6H10z" />
13-
<path d="M13.6 10.6h2.6v2.6h-2.6z" />
14-
<!-- grass jelly layer -->
15-
<path d="M9.2 15.2c1.1-.8 2.4-.8 3.5 0s2.4.8 3.5 0" />
16-
</svg>
17-
</span>
18-
<span class="site-brand-text">烧仙草冰室</span>
19-
</router-link>
20-
21-
<nav class="site-nav" aria-label="主导航">
22-
<button
23-
type="button"
24-
class="site-nav-link site-nav-link-btn site-nav-widgets-btn"
25-
@click="emit('toggle-widgets')"
26-
aria-label="打开个人信息侧栏"
27-
title="个人信息"
28-
>
29-
<span class="site-nav-ico" aria-hidden="true"><Menu /></span>
30-
<span class="site-nav-widgets-text">个人</span>
31-
</button>
32-
<router-link class="site-nav-link" to="/" :class="{ active: isActive('/') }">
33-
<span class="site-nav-ico" aria-hidden="true"><House /></span>
34-
首页
35-
</router-link>
36-
<router-link class="site-nav-link" to="/all" :class="{ active: isActive('/all') }">
37-
<span class="site-nav-ico" aria-hidden="true"><Notebook /></span>
38-
博客
39-
</router-link>
40-
<router-link class="site-nav-link" to="/about" :class="{ active: isActive('/about') }">
41-
<span class="site-nav-ico" aria-hidden="true"><InfoFilled /></span>
42-
关于
43-
</router-link>
44-
<div ref="dropdownRoot" class="site-nav-dropdown">
45-
<button
46-
type="button"
47-
class="site-nav-link site-nav-link-btn site-nav-dropdown-trigger"
48-
:aria-expanded="dropdownOpen"
49-
aria-haspopup="menu"
50-
@click.stop="toggleDropdown"
51-
>
52-
<span class="site-nav-ico" aria-hidden="true"><MagicStick /></span>
53-
AI实验室 <span class="site-nav-dropdown-caret" aria-hidden="true">▾</span>
54-
</button>
55-
56-
<div v-if="dropdownOpen" class="site-nav-dropdown-menu" role="menu">
57-
<a
58-
class="site-nav-dropdown-item"
59-
href="http://ai.wecode.xin/"
60-
target="_blank"
61-
rel="noopener noreferrer"
62-
role="menuitem"
63-
@click="closeDropdown"
64-
>
65-
AI工作平台
66-
</a>
67-
</div>
68-
</div>
2+
<div>
3+
<header class="site-top-bar" role="banner">
4+
<div class="site-top-bar-inner">
5+
<div class="site-top-actions">
696
<a
70-
class="site-nav-link"
7+
class="top-action-btn"
718
href="https://github.com/yyyCode/OpenBlog.git"
729
target="_blank"
7310
rel="noopener noreferrer"
11+
aria-label="项目源码(GitHub)"
7412
>
75-
<span class="site-nav-ico" aria-hidden="true"><Link /></span>
76-
源码
13+
<span class="top-action-ico" aria-hidden="true">
14+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
15+
<path
16+
d="M12 .5C5.73.5.75 5.67.75 12.09c0 5.15 3.28 9.52 7.83 11.06.57.11.78-.25.78-.55 0-.27-.01-1.17-.02-2.12-3.18.71-3.85-1.39-3.85-1.39-.52-1.36-1.28-1.72-1.28-1.72-1.05-.74.08-.73.08-.73 1.16.08 1.77 1.23 1.77 1.23 1.03 1.8 2.7 1.28 3.36.98.1-.77.4-1.28.73-1.57-2.54-.3-5.21-1.3-5.21-5.79 0-1.28.44-2.33 1.16-3.15-.12-.29-.5-1.48.11-3.08 0 0 .95-.31 3.11 1.2.9-.26 1.86-.39 2.82-.39.96 0 1.92.13 2.82.39 2.16-1.51 3.11-1.2 3.11-1.2.61 1.6.23 2.79.11 3.08.72.82 1.16 1.87 1.16 3.15 0 4.5-2.68 5.49-5.23 5.79.41.36.78 1.07.78 2.17 0 1.57-.02 2.83-.02 3.22 0 .3.21.67.79.55 4.55-1.54 7.83-5.91 7.83-11.06C23.25 5.67 18.27.5 12 .5z"
17+
/>
18+
</svg>
19+
</span>
20+
项目源码
7721
</a>
78-
<button type="button" class="site-nav-link site-nav-link-btn" @click="onToggle">
79-
<span class="site-nav-ico" aria-hidden="true">
80-
<Sunny v-if="isDark" />
81-
<Moon v-else />
22+
<button type="button" class="top-action-btn" aria-label="问题反馈" @click="openFeedback">
23+
<span class="top-action-ico" aria-hidden="true">
24+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
25+
<path d="M4 4h16v16H4z" />
26+
<path d="M22 6l-10 7L2 6" />
27+
</svg>
8228
</span>
83-
{{ isDark ? '浅色' : '深色' }}
29+
问题反馈
8430
</button>
85-
</nav>
86-
</div>
87-
</header>
31+
</div>
32+
<button
33+
type="button"
34+
class="theme-toggle"
35+
:aria-label="isDark ? '切换到浅色模式' : '切换到深色模式'"
36+
@click="onToggle"
37+
>
38+
<span class="theme-toggle-icon" aria-hidden="true">{{ isDark ? '☀' : '☽' }}</span>
39+
<span class="theme-toggle-text">{{ isDark ? '浅色' : '深色' }}</span>
40+
</button>
41+
</div>
42+
</header>
43+
44+
<Teleport to="body">
45+
<div v-if="feedbackOpen" class="ob-dialog-overlay" role="dialog" aria-modal="true" aria-label="问题反馈">
46+
<div class="ob-dialog card">
47+
<div class="ob-dialog-title">问题反馈</div>
48+
<div class="ob-dialog-desc">欢迎提交问题与建议(同一 IP 每天仅可提交一次)。</div>
49+
50+
<div class="field" style="margin-top: 14px">
51+
<div class="label">提交者名字</div>
52+
<input v-model="fbName" class="input" type="text" maxlength="50" placeholder="例如:张三" />
53+
</div>
54+
<div class="field">
55+
<div class="label">内容</div>
56+
<textarea
57+
v-model="fbContent"
58+
class="textarea"
59+
rows="6"
60+
maxlength="2000"
61+
placeholder="请描述你遇到的问题或建议"
62+
></textarea>
63+
</div>
64+
65+
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px">
66+
<button type="button" class="btn" :disabled="fbLoading" @click="closeFeedback">取消</button>
67+
<button type="button" class="btn primary" :disabled="fbLoading || !canSubmit" @click="submitFeedback">
68+
{{ fbLoading ? '提交中…' : '提交' }}
69+
</button>
70+
</div>
71+
</div>
72+
</div>
73+
</Teleport>
74+
</div>
8875
</template>
8976

9077
<script setup>
91-
import { onBeforeUnmount, onMounted, ref } from 'vue'
92-
import { useRoute } from 'vue-router'
78+
import { computed, onMounted, ref } from 'vue'
9379
import { toggleTheme as applyToggle } from '../theme'
94-
import { House, InfoFilled, Link, MagicStick, Menu, Moon, Notebook, Sunny } from '@element-plus/icons-vue'
80+
import { showMessage } from '../utils/message'
81+
import { createFeedback } from '../api/feedback'
9582
9683
const isDark = ref(false)
97-
const route = useRoute()
98-
const emit = defineEmits(['toggle-widgets'])
99-
100-
const dropdownOpen = ref(false)
101-
const dropdownRoot = ref(null)
84+
const feedbackOpen = ref(false)
85+
const fbName = ref('')
86+
const fbContent = ref('')
87+
const fbLoading = ref(false)
88+
89+
const canSubmit = computed(() => {
90+
const n = (fbName.value || '').trim()
91+
const c = (fbContent.value || '').trim()
92+
return n.length > 0 && c.length > 0
93+
})
10294
10395
function sync() {
10496
isDark.value = document.documentElement.getAttribute('data-theme') === 'dark'
@@ -108,88 +100,73 @@ onMounted(() => {
108100
sync()
109101
})
110102
111-
function toggleDropdown() {
112-
dropdownOpen.value = !dropdownOpen.value
113-
}
114-
115-
function closeDropdown() {
116-
dropdownOpen.value = false
103+
function onToggle() {
104+
applyToggle()
105+
sync()
117106
}
118107
119-
function onDocClick(e) {
120-
const el = dropdownRoot.value
121-
if (!el) return
122-
// 点击下拉触发器或菜单内部:不收起
123-
const target = e?.target
124-
if (target && el.contains(target)) return
125-
closeDropdown()
108+
function openFeedback() {
109+
feedbackOpen.value = true
126110
}
127111
128-
onMounted(() => {
129-
document.addEventListener('click', onDocClick)
130-
})
131-
132-
onBeforeUnmount(() => {
133-
document.removeEventListener('click', onDocClick)
134-
})
135-
136-
function onToggle() {
137-
applyToggle()
138-
sync()
112+
function closeFeedback() {
113+
if (fbLoading.value) return
114+
feedbackOpen.value = false
139115
}
140116
141-
function isActive(path) {
142-
return route.path === path
117+
async function submitFeedback() {
118+
if (!canSubmit.value || fbLoading.value) return
119+
fbLoading.value = true
120+
try {
121+
await createFeedback({
122+
submitterName: fbName.value.trim(),
123+
content: fbContent.value.trim()
124+
})
125+
showMessage('提交成功,感谢反馈')
126+
fbName.value = ''
127+
fbContent.value = ''
128+
feedbackOpen.value = false
129+
} catch (e) {
130+
const traceId = e?.traceId ? `(traceId ${e.traceId}` : ''
131+
const prefix = e?.code ? `错误码 ${e.code}` : e?.httpStatus ? `HTTP ${e.httpStatus}` : ''
132+
const msg = e?.message || '提交失败'
133+
showMessage(`${prefix ? prefix + '' : ''}${msg}${traceId}`)
134+
} finally {
135+
fbLoading.value = false
136+
}
143137
}
144138
</script>
145139
146140
<style scoped>
147-
.site-nav-dropdown {
148-
position: relative;
149-
display: inline-flex;
150-
}
151-
152-
.site-nav-dropdown-trigger {
153-
display: inline-flex;
141+
.ob-dialog-overlay {
142+
position: fixed;
143+
inset: 0;
144+
z-index: 10060;
145+
display: flex;
154146
align-items: center;
155-
gap: 6px;
156-
white-space: nowrap;
147+
justify-content: center;
148+
padding: 24px;
149+
background: rgba(0, 0, 0, 0.42);
150+
backdrop-filter: blur(6px);
151+
-webkit-backdrop-filter: blur(6px);
157152
}
158153
159-
.site-nav-dropdown-caret {
160-
font-size: 12px;
161-
opacity: 0.85;
162-
transform: translateY(-1px);
154+
.ob-dialog {
155+
width: min(520px, 100%);
156+
padding: 20px 18px 18px;
157+
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.18);
163158
}
164159
165-
.site-nav-dropdown-menu {
166-
position: absolute;
167-
left: 0;
168-
top: calc(100% + 8px);
169-
background: var(--bg);
170-
border: 1px solid var(--border);
171-
border-radius: 10px;
172-
padding: 6px;
173-
min-width: 160px;
174-
box-shadow: var(--shadow);
175-
z-index: 1000;
160+
.ob-dialog-title {
161+
font-size: 18px;
162+
font-weight: 950;
163+
color: var(--text);
176164
}
177165
178-
.site-nav-dropdown-item {
179-
display: block;
180-
text-decoration: none;
181-
color: var(--muted);
182-
font-weight: 600;
166+
.ob-dialog-desc {
167+
margin-top: 8px;
183168
font-size: 13px;
184-
padding: 10px 12px;
185-
border-radius: 8px;
186-
transition:
187-
background 0.15s ease,
188-
color 0.15s ease;
189-
}
190-
191-
.site-nav-dropdown-item:hover {
192-
color: var(--text);
193-
background: var(--accent-bg);
169+
line-height: 1.6;
170+
color: var(--muted);
194171
}
195172
</style>

frontend/src/layouts/ConsoleLayout.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@
7474
</span>
7575
评论
7676
</router-link>
77+
<router-link to="/console/feedback" class="console-nav-item" active-class="active">
78+
<span class="console-nav-ico" aria-hidden="true">
79+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
80+
<path d="M4 4h16v16H4z" />
81+
<path d="M22 6l-10 7L2 6" />
82+
</svg>
83+
</span>
84+
问题反馈
85+
</router-link>
7786
<router-link to="/console/attachments" class="console-nav-item" active-class="active">
7887
<span class="console-nav-ico" aria-hidden="true">
7988
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">

0 commit comments

Comments
 (0)