Trae Assistant commited on
Commit
7e13234
·
0 Parent(s):

Update: Enhance UI, add upload feature, localize content

Browse files
Files changed (5) hide show
  1. Dockerfile +17 -0
  2. README.md +21 -0
  3. app.py +44 -0
  4. requirements.txt +2 -0
  5. templates/index.html +561 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ # Create a non-root user for security
11
+ RUN useradd -m -u 1000 user
12
+ USER user
13
+
14
+ ENV FLASK_APP=app.py
15
+ ENV PORT=7860
16
+
17
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Aura UI Component Library
3
+ emoji: 🎨
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: 现代 UI 组件库展示 (Modern UI Library Showcase)
9
+ ---
10
+
11
+ # Aura UI Prototype
12
+
13
+ 这是一个模拟现代 UI 组件库(如 shadcn/ui)的雏形展示项目。
14
+ 使用 Flask + Vue 3 + Tailwind CSS 构建。
15
+
16
+ ## 功能
17
+
18
+ - 现代化的深色/科技感 UI 设计
19
+ - 组件展示与代码预览
20
+ - 响应式侧边栏导航
21
+ - 包含 Button, Card, Input, Badge 等基础组件演示
app.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, render_template, request, jsonify
3
+
4
+ app = Flask(__name__)
5
+
6
+ # Configure upload folder and max size (e.g., 100MB)
7
+ UPLOAD_FOLDER = 'uploads'
8
+ if not os.path.exists(UPLOAD_FOLDER):
9
+ os.makedirs(UPLOAD_FOLDER)
10
+
11
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
12
+ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit
13
+
14
+ @app.route('/')
15
+ def index():
16
+ return render_template('index.html')
17
+
18
+ @app.route('/upload', methods=['POST'])
19
+ def upload_file():
20
+ if 'file' not in request.files:
21
+ return jsonify({'error': '没有文件部分'}), 400
22
+
23
+ file = request.files['file']
24
+
25
+ if file.filename == '':
26
+ return jsonify({'error': '未选择文件'}), 400
27
+
28
+ if file:
29
+ filename = file.filename
30
+ # Ensure safe filename if needed, but for now just save it
31
+ # In a real app, use werkzeug.utils.secure_filename
32
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
33
+ try:
34
+ file.save(file_path)
35
+ return jsonify({
36
+ 'message': '文件上传成功',
37
+ 'filename': filename,
38
+ 'size': os.path.getsize(file_path)
39
+ }), 200
40
+ except Exception as e:
41
+ return jsonify({'error': str(e)}), 500
42
+
43
+ if __name__ == '__main__':
44
+ app.run(host='0.0.0.0', port=7860, debug=True)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ flask
2
+ gunicorn
templates/index.html ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Aura UI - 现代组件库展示</title>
7
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script>
10
+ tailwind.config = {
11
+ darkMode: 'class',
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ border: "hsl(var(--border))",
16
+ input: "hsl(var(--input))",
17
+ ring: "hsl(var(--ring))",
18
+ background: "hsl(var(--background))",
19
+ foreground: "hsl(var(--foreground))",
20
+ primary: {
21
+ DEFAULT: "hsl(var(--primary))",
22
+ foreground: "hsl(var(--primary-foreground))",
23
+ },
24
+ secondary: {
25
+ DEFAULT: "hsl(var(--secondary))",
26
+ foreground: "hsl(var(--secondary-foreground))",
27
+ },
28
+ destructive: {
29
+ DEFAULT: "hsl(var(--destructive))",
30
+ foreground: "hsl(var(--destructive-foreground))",
31
+ },
32
+ muted: {
33
+ DEFAULT: "hsl(var(--muted))",
34
+ foreground: "hsl(var(--muted-foreground))",
35
+ },
36
+ accent: {
37
+ DEFAULT: "hsl(var(--accent))",
38
+ foreground: "hsl(var(--accent-foreground))",
39
+ },
40
+ popover: {
41
+ DEFAULT: "hsl(var(--popover))",
42
+ foreground: "hsl(var(--popover-foreground))",
43
+ },
44
+ card: {
45
+ DEFAULT: "hsl(var(--card))",
46
+ foreground: "hsl(var(--card-foreground))",
47
+ },
48
+ },
49
+ borderRadius: {
50
+ lg: "var(--radius)",
51
+ md: "calc(var(--radius) - 2px)",
52
+ sm: "calc(var(--radius) - 4px)",
53
+ },
54
+ }
55
+ }
56
+ }
57
+ </script>
58
+ <style>
59
+ @layer base {
60
+ :root {
61
+ --background: 240 10% 3.9%;
62
+ --foreground: 0 0% 98%;
63
+ --card: 240 10% 3.9%;
64
+ --card-foreground: 0 0% 98%;
65
+ --popover: 240 10% 3.9%;
66
+ --popover-foreground: 0 0% 98%;
67
+ --primary: 0 0% 98%;
68
+ --primary-foreground: 240 5.9% 10%;
69
+ --secondary: 240 3.7% 15.9%;
70
+ --secondary-foreground: 0 0% 98%;
71
+ --muted: 240 3.7% 15.9%;
72
+ --muted-foreground: 240 5% 64.9%;
73
+ --accent: 240 3.7% 15.9%;
74
+ --accent-foreground: 0 0% 98%;
75
+ --destructive: 0 62.8% 30.6%;
76
+ --destructive-foreground: 0 0% 98%;
77
+ --border: 240 3.7% 15.9%;
78
+ --input: 240 3.7% 15.9%;
79
+ --ring: 240 4.9% 83.9%;
80
+ --radius: 0.5rem;
81
+ }
82
+ }
83
+
84
+ body {
85
+ @apply bg-background text-foreground;
86
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
87
+ -webkit-font-smoothing: antialiased;
88
+ }
89
+
90
+ /* Custom Scrollbar */
91
+ ::-webkit-scrollbar {
92
+ width: 8px;
93
+ height: 8px;
94
+ }
95
+ ::-webkit-scrollbar-track {
96
+ background: transparent;
97
+ }
98
+ ::-webkit-scrollbar-thumb {
99
+ @apply bg-muted rounded-full;
100
+ }
101
+ ::-webkit-scrollbar-thumb:hover {
102
+ @apply bg-muted-foreground/50;
103
+ }
104
+
105
+ .glass {
106
+ background: rgba(24, 24, 27, 0.6);
107
+ backdrop-filter: blur(12px);
108
+ border: 1px solid rgba(255, 255, 255, 0.08);
109
+ }
110
+ </style>
111
+ <!-- Phosphor Icons -->
112
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
113
+ </head>
114
+ <body>
115
+ <div id="app" class="flex flex-col h-screen overflow-hidden">
116
+ <!-- Top Navigation -->
117
+ <header class="h-14 border-b border-border glass flex items-center px-6 sticky top-0 z-50">
118
+ <div class="flex items-center gap-2 mr-8">
119
+ <div class="w-6 h-6 bg-primary rounded-md flex items-center justify-center text-primary-foreground font-bold text-xs">A</div>
120
+ <span class="font-bold text-lg tracking-tight">Aura UI</span>
121
+ </div>
122
+
123
+ <nav class="flex items-center gap-6 text-sm font-medium text-muted-foreground">
124
+ <a href="#" class="text-foreground transition-colors hover:text-foreground">文档</a>
125
+ <a href="#" class="transition-colors hover:text-foreground">组件</a>
126
+ <a href="#" class="transition-colors hover:text-foreground">博客</a>
127
+ <a href="#" class="transition-colors hover:text-foreground">展示</a>
128
+ </nav>
129
+
130
+ <div class="ml-auto flex items-center gap-4">
131
+ <div class="relative w-64 hidden md:block">
132
+ <i class="ph ph-magnifying-glass absolute left-2 top-2.5 text-muted-foreground"></i>
133
+ <input type="text" placeholder="搜索组件..." class="w-full h-9 pl-8 pr-4 rounded-md bg-secondary/50 border border-transparent focus:border-ring focus:outline-none text-sm text-foreground placeholder:text-muted-foreground transition-all">
134
+ <div class="absolute right-2 top-2 flex items-center gap-1">
135
+ <kbd class="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
136
+ <span class="text-xs">⌘</span>K
137
+ </kbd>
138
+ </div>
139
+ </div>
140
+ <a href="https://github.com" target="_blank" class="text-muted-foreground hover:text-foreground transition-colors">
141
+ <i class="ph ph-github-logo text-xl"></i>
142
+ </a>
143
+ </div>
144
+ </header>
145
+
146
+ <div class="flex flex-1 overflow-hidden">
147
+ <!-- Sidebar -->
148
+ <aside class="w-64 border-r border-border bg-background/50 overflow-y-auto hidden md:block">
149
+ <div class="p-6 space-y-8">
150
+ <div v-for="(group, groupName) in menuItems" :key="groupName">
151
+ <h4 class="mb-2 text-sm font-semibold tracking-tight text-foreground">${ groupName }</h4>
152
+ <div class="grid grid-flow-row auto-rows-max text-sm">
153
+ <a
154
+ v-for="item in group"
155
+ :key="item.id"
156
+ @click.prevent="selectComponent(item.id)"
157
+ href="#"
158
+ class="group flex w-full items-center rounded-md border border-transparent px-2 py-1.5 hover:underline text-muted-foreground hover:text-foreground"
159
+ :class="{ 'font-medium text-foreground bg-accent/50': currentComponent === item.id }"
160
+ >
161
+ ${ item.name }
162
+ <span v-if="item.new" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline font-bold">New</span>
163
+ </a>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </aside>
168
+
169
+ <!-- Main Content -->
170
+ <main class="flex-1 overflow-y-auto p-8 xl:px-12">
171
+ <div class="max-w-4xl mx-auto space-y-8 pb-12">
172
+ <!-- Breadcrumb -->
173
+ <div class="flex items-center space-x-2 text-sm text-muted-foreground">
174
+ <span>组件</span>
175
+ <i class="ph ph-caret-right text-xs"></i>
176
+ <span class="text-foreground font-medium">${ activeComponent.name }</span>
177
+ </div>
178
+
179
+ <!-- Header -->
180
+ <div class="space-y-2">
181
+ <h1 class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
182
+ ${ activeComponent.name }
183
+ </h1>
184
+ <p class="text-lg text-muted-foreground">
185
+ ${ activeComponent.description }
186
+ </p>
187
+ </div>
188
+
189
+ <!-- Tabs -->
190
+ <div class="w-full">
191
+ <div class="flex items-center w-full border-b border-border mb-4">
192
+ <button
193
+ @click="activeTab = 'preview'"
194
+ class="relative h-10 px-4 py-2 -mb-px text-sm font-medium transition-colors border-b-2"
195
+ :class="activeTab === 'preview' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground'"
196
+ >
197
+ 预览
198
+ </button>
199
+ <button
200
+ @click="activeTab = 'code'"
201
+ class="relative h-10 px-4 py-2 -mb-px text-sm font-medium transition-colors border-b-2"
202
+ :class="activeTab === 'code' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground'"
203
+ >
204
+ 代码
205
+ </button>
206
+ </div>
207
+
208
+ <!-- Preview Tab -->
209
+ <div v-if="activeTab === 'preview'" class="relative rounded-xl border border-border bg-background/50 shadow-sm min-h-[350px] flex items-center justify-center p-10 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
210
+ <!-- Dynamic Component Rendering -->
211
+ <div v-html="activeComponent.html" class="w-full max-w-md mx-auto flex flex-col items-center gap-4"></div>
212
+ </div>
213
+
214
+ <!-- Code Tab -->
215
+ <div v-if="activeTab === 'code'" class="relative rounded-xl border border-border bg-[#09090b] shadow-sm overflow-hidden">
216
+ <div class="flex items-center justify-between px-4 py-3 border-b border-border bg-muted/20">
217
+ <span class="text-xs text-muted-foreground">index.html</span>
218
+ <button class="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1">
219
+ <i class="ph ph-copy"></i> 复制
220
+ </button>
221
+ </div>
222
+ <div class="p-4 overflow-x-auto">
223
+ <pre class="text-sm font-mono text-muted-foreground leading-relaxed"><code v-text="activeComponent.code"></code></pre>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <!-- Installation -->
229
+ <div class="space-y-4 pt-8">
230
+ <h3 class="text-2xl font-semibold tracking-tight">安装</h3>
231
+ <div class="rounded-lg border border-border bg-[#09090b] px-4 py-3 font-mono text-sm text-foreground">
232
+ <span class="text-muted-foreground">$</span> npm install @aura-ui/${ activeComponent.id }
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </main>
237
+ </div>
238
+ </div>
239
+
240
+ <script>
241
+ const { createApp } = Vue
242
+
243
+ createApp({
244
+ delimiters: ['${', '}'],
245
+ data() {
246
+ return {
247
+ currentComponent: 'button',
248
+ activeTab: 'preview',
249
+ uploadStatus: '',
250
+ menuItems: {
251
+ '入门': [
252
+ { id: 'intro', name: '介绍' },
253
+ { id: 'install', name: '安装' }
254
+ ],
255
+ '基础组件': [
256
+ { id: 'button', name: 'Button 按钮' },
257
+ { id: 'badge', name: 'Badge 徽章' },
258
+ { id: 'card', name: 'Card 卡片', new: true },
259
+ { id: 'input', name: 'Input 输入框' },
260
+ { id: 'dialog', name: 'Dialog 对话框' },
261
+ { id: 'upload', name: 'Upload 上传', new: true },
262
+ { id: 'table', name: 'Table 表格' }
263
+ ]
264
+ },
265
+ components: {
266
+ button: {
267
+ id: 'button',
268
+ name: 'Button',
269
+ description: '触发业务逻辑或控制操作的交互式元素。',
270
+ html: `
271
+ <div class="flex flex-wrap gap-4 items-center justify-center">
272
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">
273
+ 主要按钮
274
+ </button>
275
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground hover:bg-secondary/80 h-10 px-4 py-2">
276
+ 次要按钮
277
+ </button>
278
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2">
279
+ 轮廓按钮
280
+ </button>
281
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2">
282
+ 幽灵按钮
283
+ </button>
284
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground hover:bg-destructive/90 h-10 px-4 py-2">
285
+ 破坏性按钮
286
+ </button>
287
+ </div>
288
+ `,
289
+ code: `<Button variant="default">主要按钮</Button>
290
+ <Button variant="secondary">次要按钮</Button>
291
+ <Button variant="outline">轮廓按钮</Button>
292
+ <Button variant="ghost">幽灵按钮</Button>
293
+ <Button variant="destructive">破坏性按钮</Button>`
294
+ },
295
+ badge: {
296
+ id: 'badge',
297
+ name: 'Badge',
298
+ description: '用于显示状态或属性的小型视觉元素。',
299
+ html: `
300
+ <div class="flex gap-4">
301
+ <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">默认</div>
302
+ <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">次要</div>
303
+ <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80">破坏性</div>
304
+ <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground">轮廓</div>
305
+ </div>
306
+ `,
307
+ code: `<Badge>默认</Badge>
308
+ <Badge variant="secondary">次要</Badge>
309
+ <Badge variant="destructive">破坏性</Badge>
310
+ <Badge variant="outline">轮廓</Badge>`
311
+ },
312
+ card: {
313
+ id: 'card',
314
+ name: 'Card',
315
+ description: '显示相关内容的容器。',
316
+ html: `
317
+ <div class="rounded-xl border border-border bg-card text-card-foreground shadow-sm w-[350px]">
318
+ <div class="flex flex-col space-y-1.5 p-6">
319
+ <h3 class="text-2xl font-semibold leading-none tracking-tight">创建项目</h3>
320
+ <p class="text-sm text-muted-foreground">一键部署你的新项目到 Aura 平台。</p>
321
+ </div>
322
+ <div class="p-6 pt-0 space-y-4">
323
+ <div class="space-y-2">
324
+ <label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">项目名称</label>
325
+ <input class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" placeholder="我的新项目">
326
+ </div>
327
+ <div class="space-y-2">
328
+ <label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">框架</label>
329
+ <select class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
330
+ <option>Vue.js</option>
331
+ <option>React</option>
332
+ <option>Svelte</option>
333
+ </select>
334
+ </div>
335
+ </div>
336
+ <div class="flex items-center p-6 pt-0 gap-4">
337
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2 flex-1">取消</button>
338
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 flex-1">部署</button>
339
+ </div>
340
+ </div>
341
+ `,
342
+ code: `<Card class="w-[350px]">
343
+ <CardHeader>
344
+ <CardTitle>创建项目</CardTitle>
345
+ <CardDescription>一键部署你的新项目到 Aura 平台。</CardDescription>
346
+ </CardHeader>
347
+ <CardContent>
348
+ <form>
349
+ <div class="grid w-full items-center gap-4">
350
+ <div class="flex flex-col space-y-1.5">
351
+ <Label htmlFor="name">项目名称</Label>
352
+ <Input id="name" placeholder="我的新项目" />
353
+ </div>
354
+ <div class="flex flex-col space-y-1.5">
355
+ <Label htmlFor="framework">框架</Label>
356
+ <Select>
357
+ <SelectTrigger id="framework">
358
+ <SelectValue placeholder="选择" />
359
+ </SelectTrigger>
360
+ <SelectContent position="popper">
361
+ <SelectItem value="vue">Vue.js</SelectItem>
362
+ <SelectItem value="react">React</SelectItem>
363
+ </SelectContent>
364
+ </Select>
365
+ </div>
366
+ </div>
367
+ </form>
368
+ </CardContent>
369
+ <CardFooter class="flex justify-between">
370
+ <Button variant="outline">取消</Button>
371
+ <Button>部署</Button>
372
+ </CardFooter>
373
+ </Card>`
374
+ },
375
+ input: {
376
+ id: 'input',
377
+ name: 'Input',
378
+ description: '允许用户输入文本的字段。',
379
+ html: `
380
+ <div class="grid w-full max-w-sm items-center gap-1.5">
381
+ <label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">邮箱</label>
382
+ <input type="email" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" placeholder="Email" value="user@example.com">
383
+ <p class="text-[0.8rem] text-muted-foreground">请输入您的常用邮箱地址。</p>
384
+ </div>
385
+ `,
386
+ code: `<div class="grid w-full max-w-sm items-center gap-1.5">
387
+ <Label htmlFor="email">邮箱</Label>
388
+ <Input type="email" id="email" placeholder="Email" />
389
+ </div>`
390
+ },
391
+ dialog: {
392
+ id: 'dialog',
393
+ name: 'Dialog',
394
+ description: '模态对话框,覆盖在主要内容之上。',
395
+ html: `
396
+ <div class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4">
397
+ <div class="fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg sm:rounded-lg md:w-full">
398
+ <div class="flex flex-col space-y-1.5 text-center sm:text-left">
399
+ <h2 class="text-lg font-semibold leading-none tracking-tight">编辑资料</h2>
400
+ <p class="text-sm text-muted-foreground">在这里修改您的个人信息。完成后点击保存。</p>
401
+ </div>
402
+ <div class="grid gap-4 py-4">
403
+ <div class="grid grid-cols-4 items-center gap-4">
404
+ <label class="text-right text-sm font-medium">姓名</label>
405
+ <input class="col-span-3 flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" value="Pedro Duarte">
406
+ </div>
407
+ <div class="grid grid-cols-4 items-center gap-4">
408
+ <label class="text-right text-sm font-medium">用户名</label>
409
+ <input class="col-span-3 flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" value="@peduarte">
410
+ </div>
411
+ </div>
412
+ <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
413
+ <button class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">保存修改</button>
414
+ </div>
415
+ <button class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
416
+ <i class="ph ph-x text-lg"></i>
417
+ </button>
418
+ </div>
419
+ </div>
420
+ `,
421
+ code: `<Dialog>
422
+ <DialogTrigger>Open</DialogTrigger>
423
+ <DialogContent>
424
+ <DialogHeader>
425
+ <DialogTitle>编辑资料</DialogTitle>
426
+ <DialogDescription>
427
+ 在这里修改您的个人信息。完成后点击保存。
428
+ </DialogDescription>
429
+ </DialogHeader>
430
+ <!-- Form Fields -->
431
+ <DialogFooter>
432
+ <Button type="submit">保存修改</Button>
433
+ </DialogFooter>
434
+ </DialogContent>
435
+ </Dialog>`
436
+ },
437
+ upload: {
438
+ id: 'upload',
439
+ name: 'Upload',
440
+ description: '文件上传组件,支持拖拽和点击上传。',
441
+ html: `
442
+ <div class="w-full max-w-sm items-center gap-1.5">
443
+ <div class="flex items-center justify-center w-full">
444
+ <label for="dropzone-file" class="flex flex-col items-center justify-center w-full h-64 border-2 border-dashed rounded-lg cursor-pointer bg-muted/50 hover:bg-muted border-border hover:border-primary transition-colors">
445
+ <div class="flex flex-col items-center justify-center pt-5 pb-6">
446
+ <i class="ph ph-cloud-arrow-up text-4xl mb-4 text-muted-foreground"></i>
447
+ <p class="mb-2 text-sm text-muted-foreground"><span class="font-semibold">点击上传</span> 或拖拽文件到这里</p>
448
+ <p class="text-xs text-muted-foreground">支持各种格式 (最大 100MB)</p>
449
+ </div>
450
+ <input id="dropzone-file" type="file" class="hidden" @change="handleFileUpload" />
451
+ </label>
452
+ </div>
453
+ <div v-if="uploadStatus" class="mt-4 p-4 rounded-md bg-muted/50 text-sm overflow-auto max-h-40">
454
+ <p class="font-medium mb-1">状态:</p>
455
+ <pre class="text-xs whitespace-pre-wrap font-mono">${ uploadStatus }</pre>
456
+ </div>
457
+ <div class="mt-4 flex justify-center">
458
+ <button @click="triggerUpload" class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">
459
+ 手动触发上传
460
+ </button>
461
+ </div>
462
+ </div>
463
+ `,
464
+ code: `<Upload url="/api/upload" />`
465
+ },
466
+ table: {
467
+ id: 'table',
468
+ name: 'Table',
469
+ description: '展示数据集合的表格组件。',
470
+ html: `
471
+ <div class="w-full overflow-auto rounded-lg border border-border">
472
+ <table class="w-full caption-bottom text-sm">
473
+ <thead class="[&_tr]:border-b">
474
+ <tr class="border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
475
+ <th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">发票号</th>
476
+ <th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">状态</th>
477
+ <th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">方式</th>
478
+ <th class="h-12 px-4 text-right align-middle font-medium text-muted-foreground">金额</th>
479
+ </tr>
480
+ </thead>
481
+ <tbody class="[&_tr:last-child]:border-0">
482
+ <tr class="border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
483
+ <td class="p-4 align-middle font-medium">INV001</td>
484
+ <td class="p-4 align-middle">已支付</td>
485
+ <td class="p-4 align-middle">信用卡</td>
486
+ <td class="p-4 align-middle text-right">¥250.00</td>
487
+ </tr>
488
+ <tr class="border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
489
+ <td class="p-4 align-middle font-medium">INV002</td>
490
+ <td class="p-4 align-middle">处理中</td>
491
+ <td class="p-4 align-middle">PayPal</td>
492
+ <td class="p-4 align-middle text-right">¥150.00</td>
493
+ </tr>
494
+ <tr class="border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
495
+ <td class="p-4 align-middle font-medium">INV003</td>
496
+ <td class="p-4 align-middle">未支付</td>
497
+ <td class="p-4 align-middle">银行转账</td>
498
+ <td class="p-4 align-middle text-right">¥350.00</td>
499
+ </tr>
500
+ </tbody>
501
+ </table>
502
+ </div>
503
+ `,
504
+ code: `<Table :data="invoices" />`
505
+ }
506
+ }
507
+ }
508
+ },
509
+ computed: {
510
+ activeComponent() {
511
+ return this.components[this.currentComponent] || {
512
+ name: 'Aura UI',
513
+ description: 'Modern, Accessible, Open Source.',
514
+ html: '<div class="text-center">请从左侧选择组件</div>',
515
+ code: ''
516
+ }
517
+ }
518
+ },
519
+ methods: {
520
+ selectComponent(id) {
521
+ if (this.components[id]) {
522
+ this.currentComponent = id;
523
+ this.activeTab = 'preview';
524
+ this.uploadStatus = '';
525
+ }
526
+ },
527
+ triggerUpload() {
528
+ const fileInput = document.getElementById('dropzone-file');
529
+ if (fileInput) {
530
+ fileInput.click();
531
+ }
532
+ },
533
+ async handleFileUpload(event) {
534
+ const file = event.target.files[0];
535
+ if (!file) return;
536
+
537
+ const formData = new FormData();
538
+ formData.append('file', file);
539
+
540
+ this.uploadStatus = '正在上传...';
541
+
542
+ try {
543
+ const response = await fetch('/upload', {
544
+ method: 'POST',
545
+ body: formData
546
+ });
547
+ const result = await response.json();
548
+ if (response.ok) {
549
+ this.uploadStatus = '成功: ' + JSON.stringify(result, null, 2);
550
+ } else {
551
+ this.uploadStatus = '错误: ' + JSON.stringify(result, null, 2);
552
+ }
553
+ } catch (error) {
554
+ this.uploadStatus = '网络错误: ' + error.message;
555
+ }
556
+ }
557
+ }
558
+ }).mount('#app')
559
+ </script>
560
+ </body>
561
+ </html>