duqing2026 commited on
Commit
a30d178
·
1 Parent(s): f6413ef

Fix Jinja2 error, add auto-save & JSON export, optimize Dockerfile

Browse files
Files changed (2) hide show
  1. Dockerfile +4 -11
  2. templates/builder.html +71 -3
Dockerfile CHANGED
@@ -11,20 +11,13 @@ COPY . /app
11
 
12
  # Create a non-root user and switch to it
13
  RUN useradd -m -u 1000 user
14
- USER user
15
- ENV PATH="/home/user/.local/bin:$PATH"
16
-
17
- # Ensure the user owns the application files
18
- # (Although COPY . /app creates files owned by root, for read-only it's usually fine,
19
- # but for safety and potential write needs, we should fix permissions or COPY with chown)
20
- # However, standard practice in HF Spaces is often just running as user 1000.
21
- # Let's use the --chown flag in COPY if possible, but standard COPY followed by chmod is also an option.
22
- # The simplest way for this file:
23
 
24
- USER root
25
  RUN chown -R user:user /app
 
26
  USER user
 
27
 
28
  EXPOSE 7860
29
 
30
- CMD ["python", "app.py"]
 
11
 
12
  # Create a non-root user and switch to it
13
  RUN useradd -m -u 1000 user
 
 
 
 
 
 
 
 
 
14
 
15
+ # Set ownership
16
  RUN chown -R user:user /app
17
+
18
  USER user
19
+ ENV PATH="/home/user/.local/bin:$PATH"
20
 
21
  EXPOSE 7860
22
 
23
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
templates/builder.html CHANGED
@@ -17,6 +17,7 @@
17
  <body class="bg-gray-50 text-gray-800 h-screen flex flex-col overflow-hidden">
18
 
19
  <div id="app" class="flex-1 flex flex-col h-full">
 
20
  <!-- Header -->
21
  <header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 shrink-0 z-10">
22
  <div class="flex items-center gap-3">
@@ -25,11 +26,22 @@
25
  </div>
26
  <h1 class="text-xl font-bold text-gray-900 tracking-tight">Interactive Checklist Pro</h1>
27
  </div>
28
- <div class="flex items-center gap-4">
 
 
 
 
 
 
 
 
 
 
 
29
  <button @click="downloadHTML" :disabled="isExporting" class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-5 py-2 rounded-lg font-medium transition-colors shadow-sm disabled:opacity-50">
30
  <i class="ph ph-download-simple" v-if="!isExporting"></i>
31
  <i class="ph ph-spinner animate-spin" v-else></i>
32
- {{ isExporting ? '生成中...' : '导出单文件 HTML' }}
33
  </button>
34
  </div>
35
  </header>
@@ -186,9 +198,10 @@
186
  </div>
187
  </div>
188
  </div>
 
189
 
190
  <script>
191
- const { createApp, ref, computed } = Vue;
192
 
193
  createApp({
194
  setup() {
@@ -217,6 +230,22 @@
217
  ]
218
  });
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  const isExporting = ref(false);
221
 
222
  const themeColors = [
@@ -333,4 +362,43 @@
333
  }).mount('#app');
334
  </script>
335
  </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  </html>
 
17
  <body class="bg-gray-50 text-gray-800 h-screen flex flex-col overflow-hidden">
18
 
19
  <div id="app" class="flex-1 flex flex-col h-full">
20
+ {% raw %}
21
  <!-- Header -->
22
  <header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 shrink-0 z-10">
23
  <div class="flex items-center gap-3">
 
26
  </div>
27
  <h1 class="text-xl font-bold text-gray-900 tracking-tight">Interactive Checklist Pro</h1>
28
  </div>
29
+ <div class="flex items-center gap-2">
30
+ <div class="flex items-center gap-1 mr-2 border-r border-gray-200 pr-3">
31
+ <button @click="importJSON" class="text-gray-500 hover:text-indigo-600 p-2 rounded-lg transition" title="导入配置 (Import JSON)">
32
+ <i class="ph ph-upload-simple text-lg"></i>
33
+ </button>
34
+ <button @click="exportJSON" class="text-gray-500 hover:text-indigo-600 p-2 rounded-lg transition" title="备份配置 (Export JSON)">
35
+ <i class="ph ph-floppy-disk text-lg"></i>
36
+ </button>
37
+ <button @click="clearStorage" class="text-gray-400 hover:text-red-600 p-2 rounded-lg transition" title="重置 (Reset)">
38
+ <i class="ph ph-arrow-counter-clockwise text-lg"></i>
39
+ </button>
40
+ </div>
41
  <button @click="downloadHTML" :disabled="isExporting" class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-5 py-2 rounded-lg font-medium transition-colors shadow-sm disabled:opacity-50">
42
  <i class="ph ph-download-simple" v-if="!isExporting"></i>
43
  <i class="ph ph-spinner animate-spin" v-else></i>
44
+ <span v-text="isExporting ? '生成中...' : '导出单文件 HTML'"></span>
45
  </button>
46
  </div>
47
  </header>
 
198
  </div>
199
  </div>
200
  </div>
201
+ {% endraw %}
202
 
203
  <script>
204
+ const { createApp, ref, computed, watch, onMounted } = Vue;
205
 
206
  createApp({
207
  setup() {
 
230
  ]
231
  });
232
 
233
+ // Auto-save logic
234
+ onMounted(() => {
235
+ const saved = localStorage.getItem('checklist_pro_draft');
236
+ if (saved) {
237
+ try {
238
+ checklist.value = JSON.parse(saved);
239
+ } catch (e) {
240
+ console.error('Failed to load draft', e);
241
+ }
242
+ }
243
+ });
244
+
245
+ watch(checklist, (newVal) => {
246
+ localStorage.setItem('checklist_pro_draft', JSON.stringify(newVal));
247
+ }, { deep: true });
248
+
249
  const isExporting = ref(false);
250
 
251
  const themeColors = [
 
362
  }).mount('#app');
363
  </script>
364
  </body>
365
+ </html>
366
+ const file = e.target.files[0];
367
+ if (!file) return;
368
+ const reader = new FileReader();
369
+ reader.readAsText(file, 'UTF-8');
370
+ reader.onload = readerEvent => {
371
+ try {
372
+ const content = readerEvent.target.result;
373
+ checklist.value = JSON.parse(content);
374
+ } catch (e) {
375
+ alert('Invalid JSON file');
376
+ }
377
+ }
378
+ }
379
+ input.click();
380
+ };
381
+
382
+ const clearStorage = () => {
383
+ if(confirm('确定要清空本地缓存并重置为默认值吗?此操作无法撤销。')) {
384
+ localStorage.removeItem('checklist_pro_draft');
385
+ location.reload();
386
+ }
387
+ };
388
+
389
+ return {
390
+ checklist,
391
+ themeColors,
392
+ currentThemeClass,
393
+ currentThemeTextClass,
394
+ totalItems,
395
+ addGroup, removeGroup, moveGroup,
396
+ addItem, removeItem, moveItem,
397
+ downloadHTML, isExporting,
398
+ exportJSON, importJSON, clearStorage
399
+ };
400
+ }
401
+ }).mount('#app');
402
+ </script>
403
+ </body>
404
  </html>