duqing2026 commited on
Commit
05c3aec
·
1 Parent(s): 40ff5dd

升级优化

Browse files
Files changed (4) hide show
  1. Dockerfile +0 -3
  2. app.py +56 -7
  3. requests.json.bak +1 -0
  4. templates/index.html +29 -8
Dockerfile CHANGED
@@ -12,9 +12,6 @@ COPY . .
12
  # For this demo, local file is fine (it resets on restart unless persistent storage is configured in HF).
13
  RUN chmod -R 777 /app
14
 
15
- # Create an empty data file if it doesn't exist and make it writable
16
- RUN echo "[]" > requests.json && chmod 666 requests.json
17
-
18
  EXPOSE 7860
19
 
20
  CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
 
12
  # For this demo, local file is fine (it resets on restart unless persistent storage is configured in HF).
13
  RUN chmod -R 777 /app
14
 
 
 
 
15
  EXPOSE 7860
16
 
17
  CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
app.py CHANGED
@@ -5,20 +5,69 @@ from datetime import datetime
5
  from flask import Flask, render_template, request, jsonify
6
 
7
  app = Flask(__name__)
8
- DATA_FILE = "requests.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  def load_data():
11
  if not os.path.exists(DATA_FILE):
12
- return []
 
13
  try:
14
  with open(DATA_FILE, "r", encoding="utf-8") as f:
15
- return json.load(f)
16
- except Exception:
17
- return []
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  def save_data(data):
20
- with open(DATA_FILE, "w", encoding="utf-8") as f:
21
- json.dump(data, f, ensure_ascii=False, indent=2)
 
 
 
22
 
23
  @app.route("/")
24
  def index():
 
5
  from flask import Flask, render_template, request, jsonify
6
 
7
  app = Flask(__name__)
8
+ DATA_FILE = os.environ.get("DATA_FILE", "requests.json")
9
+
10
+ DEFAULT_REQUESTS = [
11
+ {
12
+ "id": "1",
13
+ "title": "增加暗色模式",
14
+ "description": "建议增加暗色模式支持,方便夜间使用。",
15
+ "category": "界面设计 (UI/UX)",
16
+ "votes": 15,
17
+ "status": "planned",
18
+ "created_at": datetime.now().isoformat(),
19
+ "comments": []
20
+ },
21
+ {
22
+ "id": "2",
23
+ "title": "支持图片上传",
24
+ "description": "在提交反馈时,希望可以上传截图,这样能更直观地说明问题。",
25
+ "category": "功能建议 (Feature)",
26
+ "votes": 8,
27
+ "status": "pending",
28
+ "created_at": datetime.now().isoformat(),
29
+ "comments": []
30
+ },
31
+ {
32
+ "id": "3",
33
+ "title": "移动端适配优化",
34
+ "description": "目前在手机上查看列表时,排版有些拥挤,建议优化移动端显示。",
35
+ "category": "界面设计 (UI/UX)",
36
+ "votes": 12,
37
+ "status": "in_progress",
38
+ "created_at": datetime.now().isoformat(),
39
+ "comments": []
40
+ }
41
+ ]
42
 
43
  def load_data():
44
  if not os.path.exists(DATA_FILE):
45
+ save_data(DEFAULT_REQUESTS)
46
+ return DEFAULT_REQUESTS
47
  try:
48
  with open(DATA_FILE, "r", encoding="utf-8") as f:
49
+ data = json.load(f)
50
+ if not isinstance(data, list):
51
+ # If data is corrupted (not a list), backup and reset
52
+ print(f"Warning: {DATA_FILE} contains invalid data (not a list). Resetting.")
53
+ os.rename(DATA_FILE, DATA_FILE + ".bak")
54
+ save_data(DEFAULT_REQUESTS)
55
+ return DEFAULT_REQUESTS
56
+ return data
57
+ except Exception as e:
58
+ print(f"Error loading data: {e}. Resetting.")
59
+ # If file is corrupted, reset
60
+ if os.path.exists(DATA_FILE):
61
+ os.rename(DATA_FILE, DATA_FILE + ".bak")
62
+ save_data(DEFAULT_REQUESTS)
63
+ return DEFAULT_REQUESTS
64
 
65
  def save_data(data):
66
+ try:
67
+ with open(DATA_FILE, "w", encoding="utf-8") as f:
68
+ json.dump(data, f, ensure_ascii=False, indent=2)
69
+ except Exception as e:
70
+ print(f"Error saving data: {e}")
71
 
72
  @app.route("/")
73
  def index():
requests.json.bak ADDED
@@ -0,0 +1 @@
 
 
1
+ {"some": "object"}
templates/index.html CHANGED
@@ -82,13 +82,24 @@
82
 
83
  <!-- Feed -->
84
  <div class="md:col-span-3 space-y-4">
 
 
 
 
 
 
 
 
 
 
 
85
  <!-- Loading -->
86
  <div v-if="loading" class="flex justify-center py-12">
87
  <i class="fa-solid fa-circle-notch fa-spin text-indigo-500 text-2xl"></i>
88
  </div>
89
 
90
  <!-- Empty State -->
91
- <div v-else-if="filteredRequests.length === 0" class="text-center py-12 bg-white rounded-xl border border-dashed border-slate-300">
92
  <div class="w-16 h-16 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-4">
93
  <i class="fa-regular fa-folder-open text-slate-400 text-2xl"></i>
94
  </div>
@@ -98,19 +109,19 @@
98
  </div>
99
 
100
  <!-- List -->
101
- <div v-else v-for="req in filteredRequests" :key="req.id" class="bg-white rounded-xl p-5 shadow-sm border border-slate-100 hover:shadow-md transition-shadow group">
102
- <div class="flex gap-5">
103
  <!-- Vote Button -->
104
- <button @click="vote(req.id)" class="vote-btn flex flex-col items-center justify-center w-12 h-14 rounded-lg border border-slate-200 bg-slate-50 hover:border-indigo-300 hover:bg-indigo-50 transition-all group/vote flex-shrink-0" :class="{'border-indigo-500 bg-indigo-50 text-indigo-600': hasVoted(req.id)}">
105
  <i class="fa-solid fa-caret-up text-slate-400 group-hover/vote:text-indigo-500" :class="{'text-indigo-600': hasVoted(req.id)}"></i>
106
- <span class="font-bold text-sm" :class="{'text-indigo-700': hasVoted(req.id), 'text-slate-600': !hasVoted(req.id)}">[[ req.votes ]]</span>
107
  </button>
108
 
109
  <!-- Content -->
110
  <div class="flex-1">
111
  <div class="flex items-start justify-between mb-1">
112
- <h3 class="font-bold text-slate-900 text-lg leading-snug">[[ req.title ]]</h3>
113
- <span :class="['px-2.5 py-1 rounded-full text-xs font-semibold capitalize whitespace-nowrap ml-2', statusClass(req.status)]">
114
  [[ statusLabel(req.status) ]]
115
  </span>
116
  </div>
@@ -137,6 +148,12 @@
137
  </div>
138
  </main>
139
 
 
 
 
 
 
 
140
  <!-- Modal -->
141
  <div v-if="showModal" class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-50 flex items-center justify-center p-4 fade-enter-active">
142
  <div class="bg-white rounded-2xl shadow-xl max-w-lg w-full overflow-hidden transform transition-all">
@@ -185,6 +202,7 @@
185
  setup() {
186
  const requests = ref([]);
187
  const loading = ref(true);
 
188
  const showModal = ref(false);
189
  const submitting = ref(false);
190
  const isAdmin = ref(false);
@@ -208,11 +226,14 @@
208
 
209
  const fetchRequests = async () => {
210
  loading.value = true;
 
211
  try {
212
  const res = await fetch('/api/requests');
 
213
  requests.value = await res.json();
214
  } catch (e) {
215
  console.error(e);
 
216
  } finally {
217
  loading.value = false;
218
  }
@@ -334,7 +355,7 @@
334
  });
335
 
336
  return {
337
- requests, loading, showModal, submitting, newRequest,
338
  submitRequest, vote, hasVoted, openModal, isValid,
339
  statusLabel, statusClass, statusColor, formatDate,
340
  isAdmin, adminStatuses, updateStatus,
 
82
 
83
  <!-- Feed -->
84
  <div class="md:col-span-3 space-y-4">
85
+ <!-- Error State -->
86
+ <div v-if="error" class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-xl flex items-center justify-between shadow-sm">
87
+ <div class="flex items-center gap-2">
88
+ <i class="fa-solid fa-triangle-exclamation"></i>
89
+ <span>[[ error ]]</span>
90
+ </div>
91
+ <button @click="fetchRequests" class="text-sm bg-white border border-red-200 hover:bg-red-50 px-3 py-1 rounded-lg transition-colors">
92
+ 重试
93
+ </button>
94
+ </div>
95
+
96
  <!-- Loading -->
97
  <div v-if="loading" class="flex justify-center py-12">
98
  <i class="fa-solid fa-circle-notch fa-spin text-indigo-500 text-2xl"></i>
99
  </div>
100
 
101
  <!-- Empty State -->
102
+ <div v-else-if="!error && filteredRequests.length === 0" class="text-center py-12 bg-white rounded-xl border border-dashed border-slate-300">
103
  <div class="w-16 h-16 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-4">
104
  <i class="fa-regular fa-folder-open text-slate-400 text-2xl"></i>
105
  </div>
 
109
  </div>
110
 
111
  <!-- List -->
112
+ <div v-else-if="!error" v-for="req in filteredRequests" :key="req.id" class="bg-white rounded-xl p-4 sm:p-5 shadow-sm border border-slate-100 hover:shadow-md transition-shadow group">
113
+ <div class="flex gap-3 sm:gap-5">
114
  <!-- Vote Button -->
115
+ <button @click="vote(req.id)" class="vote-btn flex flex-col items-center justify-center w-10 h-12 sm:w-12 sm:h-14 rounded-lg border border-slate-200 bg-slate-50 hover:border-indigo-300 hover:bg-indigo-50 transition-all group/vote flex-shrink-0" :class="{'border-indigo-500 bg-indigo-50 text-indigo-600': hasVoted(req.id)}">
116
  <i class="fa-solid fa-caret-up text-slate-400 group-hover/vote:text-indigo-500" :class="{'text-indigo-600': hasVoted(req.id)}"></i>
117
+ <span class="font-bold text-xs sm:text-sm" :class="{'text-indigo-700': hasVoted(req.id), 'text-slate-600': !hasVoted(req.id)}">[[ req.votes ]]</span>
118
  </button>
119
 
120
  <!-- Content -->
121
  <div class="flex-1">
122
  <div class="flex items-start justify-between mb-1">
123
+ <h3 class="font-bold text-slate-900 text-base sm:text-lg leading-snug">[[ req.title ]]</h3>
124
+ <span :class="['px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-full text-[10px] sm:text-xs font-semibold capitalize whitespace-nowrap ml-2', statusClass(req.status)]">
125
  [[ statusLabel(req.status) ]]
126
  </span>
127
  </div>
 
148
  </div>
149
  </main>
150
 
151
+ <footer class="bg-white border-t border-slate-200 py-6 mt-auto">
152
+ <div class="max-w-5xl mx-auto px-4 text-center text-slate-400 text-sm">
153
+ <p>&copy; 2026 Feature Request Hub. All rights reserved.</p>
154
+ </div>
155
+ </footer>
156
+
157
  <!-- Modal -->
158
  <div v-if="showModal" class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-50 flex items-center justify-center p-4 fade-enter-active">
159
  <div class="bg-white rounded-2xl shadow-xl max-w-lg w-full overflow-hidden transform transition-all">
 
202
  setup() {
203
  const requests = ref([]);
204
  const loading = ref(true);
205
+ const error = ref(null);
206
  const showModal = ref(false);
207
  const submitting = ref(false);
208
  const isAdmin = ref(false);
 
226
 
227
  const fetchRequests = async () => {
228
  loading.value = true;
229
+ error.value = null;
230
  try {
231
  const res = await fetch('/api/requests');
232
+ if (!res.ok) throw new Error('Failed to load requests');
233
  requests.value = await res.json();
234
  } catch (e) {
235
  console.error(e);
236
+ error.value = '加载数据失败,请稍后重试。';
237
  } finally {
238
  loading.value = false;
239
  }
 
355
  });
356
 
357
  return {
358
+ requests, loading, error, showModal, submitting, newRequest,
359
  submitRequest, vote, hasVoted, openModal, isValid,
360
  statusLabel, statusClass, statusColor, formatDate,
361
  isAdmin, adminStatuses, updateStatus,