11<template >
2- <header class =" site-top-bar" role =" banner" >
3- <div class =" site-top-bar-inner" >
4- <div class =" site-top-actions" >
2+ <div >
3+ <header class =" site-top-bar" role =" banner" >
4+ <div class =" site-top-bar-inner" >
5+ <div class =" site-top-actions" >
56 <a
67 class =" top-action-btn"
78 href =" https://github.com/yyyCode/OpenBlog.git"
1819 </span >
1920 项目源码
2021 </a >
21- <button type =" button" class =" top-action-btn" aria-label =" 面试内容 " @click =" onInterviewClick " >
22+ <button type =" button" class =" top-action-btn" aria-label =" 问题反馈 " @click =" openFeedback " >
2223 <span class =" top-action-ico" aria-hidden =" true" >
2324 <svg width =" 16" height =" 16" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" >
24- <path d =" M9 2h6v3H9z" />
25- <path d =" M7 5h10a2 2 0 0 1 2 2v12a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3V7a2 2 0 0 1 2-2z" />
26- <path d =" M9 11h6M9 15h6" />
25+ <path d =" M4 4h16v16H4z" />
26+ <path d =" M22 6l-10 7L2 6" />
2727 </svg >
2828 </span >
29- 面试内容
29+ 问题反馈
3030 </button >
3131 </div >
3232 <button
3737 >
3838 <span class =" theme-toggle-icon" aria-hidden =" true" >{{ isDark ? '☀' : '☽' }}</span >
3939 <span class =" theme-toggle-text" >{{ isDark ? '浅色' : '深色' }}</span >
40- </button >
41- </div >
42- </header >
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 >
4375</template >
4476
4577<script setup>
46- import { onMounted , ref } from ' vue'
78+ import { computed , onMounted , ref } from ' vue'
4779import { toggleTheme as applyToggle } from ' ../theme'
4880import { showMessage } from ' ../utils/message'
81+ import { createFeedback } from ' ../api/feedback'
4982
5083const isDark = ref (false )
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+ })
5194
5295function sync () {
5396 isDark .value = document .documentElement .getAttribute (' data-theme' ) === ' dark'
@@ -62,7 +105,68 @@ function onToggle() {
62105 sync ()
63106}
64107
65- function onInterviewClick () {
66- showMessage (' 功能暂未开放' )
108+ function openFeedback () {
109+ feedbackOpen .value = true
110+ }
111+
112+ function closeFeedback () {
113+ if (fbLoading .value ) return
114+ feedbackOpen .value = false
115+ }
116+
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+ }
67137}
68138< / script>
139+
140+ < style scoped>
141+ .ob - dialog- overlay {
142+ position: fixed;
143+ inset: 0 ;
144+ z- index: 10060 ;
145+ display: flex;
146+ align- items: center;
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 );
152+ }
153+
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 );
158+ }
159+
160+ .ob - dialog- title {
161+ font- size: 18px ;
162+ font- weight: 950 ;
163+ color: var (-- text);
164+ }
165+
166+ .ob - dialog- desc {
167+ margin- top: 8px ;
168+ font- size: 13px ;
169+ line- height: 1.6 ;
170+ color: var (-- muted);
171+ }
172+ < / style>
0 commit comments