duqing2026 commited on
Commit
3ca9bbc
·
1 Parent(s): 6a675b0

Initial commit for Mock Data Master

Browse files
Files changed (6) hide show
  1. .gitignore +5 -0
  2. Dockerfile +17 -0
  3. README.md +83 -5
  4. app.py +75 -0
  5. requirements.txt +2 -0
  6. templates/index.html +303 -0
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .DS_Store
4
+ venv/
5
+ .env
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-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 Hugging Face Spaces
11
+ RUN useradd -m -u 1000 user
12
+ USER user
13
+
14
+ ENV HOME=/home/user \
15
+ PATH=/home/user/.local/bin:$PATH
16
+
17
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,11 +1,89 @@
1
  ---
2
  title: Mock Data Master
3
- emoji: 🐨
4
- colorFrom: purple
5
- colorTo: blue
6
  sdk: docker
7
  pinned: false
8
- short_description: '1'
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Mock Data Master
3
+ emoji: 🎲
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ short_description: 虚拟数据生成大师 - 开发者必备工具
9
  ---
10
 
11
+ # Mock Data Master (虚拟数据生成大师)
12
+
13
+ **Mock Data Master** 是一个强大的虚拟数据生成工具,专为开发者、测试人员和产品经理设计。它可以快速生成成千上万条逼真的测试数据,支持导出为 CSV、JSON 和 SQL 格式。
14
+
15
+ 不再需要手动编造测试数据,**Mock Data Master** 帮你一键搞定!
16
+
17
+ ## ✨ 核心功能
18
+
19
+ - **🚀 极速生成**:毫秒级响应,支持一次生成多达 1000 条数据预览。
20
+ - **🇨🇳 中文优化**:基于 `Faker` 库,完美支持中文姓名、地址、公司名、职位等。
21
+ - **🧩 丰富类型**:
22
+ - **基础信息**: 姓名, 邮箱, 手机号, 地址, 身份证号, 城市.
23
+ - **商业/办公**: 公司名, 职位, ISBN, 信用卡号, 颜色.
24
+ - **网络/技术**: UUID, IPv4, User Agent, URL, 图片链接, 文件名.
25
+ - **内容**: 短句, 段落, 布尔值, 整数 (支持范围).
26
+ - **⚡ 快速模板**: 内置 **用户(User)**、**电商(E-commerce)**、**订单(Order)**、**文章(Article)** 等常用模型,一键加载。
27
+ - **💾 多格式导出**:
28
+ - **CSV**: 适用于 Excel 分析。
29
+ - **JSON**: 适用于 API 模拟。
30
+ - **SQL**: 直接生成 `INSERT` 语句 (支持自定义表名),快速导入数据库。
31
+ - **🛠️ 实用工具**:
32
+ - **自动保存**: 本地缓存配置,刷新不丢失。
33
+ - **一键复制**: 方便快捷地获取 JSON/SQL 代码。
34
+ - **重置功能**: 快速恢复默认状态。
35
+ - **🔒 隐私安全**: 所有数据均为随机生成,无真实用户信息。
36
+
37
+ ## 🛠 技术栈
38
+
39
+ - **Backend**: Flask (Python)
40
+ - **Frontend**: Vue 3 (CDN), Tailwind CSS (CDN)
41
+ - **Core Library**: Faker
42
+ - **Icons**: Phosphor Icons
43
+ - **Deployment**: Docker (Hugging Face Spaces Compatible)
44
+
45
+ ## 🚀 快速开始
46
+
47
+ ### Docker 部署 (推荐)
48
+
49
+ ```bash
50
+ # 构建镜像
51
+ docker build -t mock-data-master .
52
+
53
+ # 运行容器
54
+ docker run -p 7860:7860 mock-data-master
55
+ ```
56
+
57
+ 访问 `http://localhost:7860` 即可使用。
58
+
59
+ ### 本地开发
60
+
61
+ 1. 安装依赖:
62
+ ```bash
63
+ pip install -r requirements.txt
64
+ ```
65
+
66
+ 2. 运行应用:
67
+ ```bash
68
+ python app.py
69
+ ```
70
+
71
+ ## 📝 使用指南
72
+
73
+ 1. **选择模板** (可选):点击“快速模板”下拉框,选择一个场景(如电商产品)快速填充字段。
74
+ 2. **自定义配置**:
75
+ - 添加/删除字段。
76
+ - 修改字段名和类型。
77
+ - 设置整数范围(如价格 10-5000)。
78
+ 3. **生成数据**:点击“立即生成数据”按钮。
79
+ 4. **导出/复制**:
80
+ - 点击 CSV/JSON/SQL 按钮下载文件。
81
+ - 在 SQL 弹窗中可以直接复制语句。
82
+
83
+ ## 🤝 贡献
84
+
85
+ 欢迎提交 Issue 和 PR!
86
+
87
+ ## 📄 许可证
88
+
89
+ MIT License
app.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, send_file
2
+ from faker import Faker
3
+ import csv
4
+ import json
5
+ import io
6
+ import random
7
+
8
+ app = Flask(__name__)
9
+
10
+ # Resolve conflict with Vue.js
11
+ app.jinja_env.variable_start_string = '[['
12
+ app.jinja_env.variable_end_string = ']]'
13
+
14
+ fake = Faker('zh_CN') # Default to Chinese
15
+
16
+ # Mapping of frontend types to Faker methods or custom logic
17
+ FIELD_TYPES = {
18
+ 'name': {'label': '姓名', 'method': lambda f: f.name()},
19
+ 'address': {'label': '地址', 'method': lambda f: f.address()},
20
+ 'email': {'label': '邮箱', 'method': lambda f: f.email()},
21
+ 'phone_number': {'label': '手机号', 'method': lambda f: f.phone_number()},
22
+ 'company': {'label': '公司', 'method': lambda f: f.company()},
23
+ 'job': {'label': '职位', 'method': lambda f: f.job()},
24
+ 'ssn': {'label': '身份证号', 'method': lambda f: f.ssn()},
25
+ 'date_this_year': {'label': '今年日期', 'method': lambda f: f.date_this_year().isoformat()},
26
+ 'date_of_birth': {'label': '出生日期', 'method': lambda f: f.date_of_birth().isoformat()},
27
+ 'city': {'label': '城市', 'method': lambda f: f.city()},
28
+ 'text': {'label': '随机文本', 'method': lambda f: f.sentence()},
29
+ 'integer': {'label': '随机整数', 'method': lambda f: random.randint(0, 100)}, # Basic default
30
+ 'boolean': {'label': '布尔值', 'method': lambda f: random.choice([True, False])},
31
+ 'uuid': {'label': 'UUID', 'method': lambda f: f.uuid4()},
32
+ 'ipv4': {'label': 'IPv4地址', 'method': lambda f: f.ipv4()},
33
+ 'url': {'label': 'URL链接', 'method': lambda f: f.url()},
34
+ }
35
+
36
+ @app.route('/')
37
+ def index():
38
+ return render_template('index.html')
39
+
40
+ @app.route('/api/types')
41
+ def get_types():
42
+ return jsonify([{
43
+ 'value': k,
44
+ 'label': v['label']
45
+ } for k, v in FIELD_TYPES.items()])
46
+
47
+ @app.route('/api/generate', methods=['POST'])
48
+ def generate_data():
49
+ data = request.json
50
+ count = min(data.get('count', 10), 1000) # Limit to 1000 for preview/safety
51
+ schema = data.get('schema', [])
52
+
53
+ results = []
54
+ for _ in range(count):
55
+ row = {}
56
+ for field in schema:
57
+ field_name = field.get('name')
58
+ field_type = field.get('type')
59
+
60
+ if field_type in FIELD_TYPES:
61
+ # Handle special cases if needed (e.g. ranges for integers)
62
+ if field_type == 'integer':
63
+ min_val = int(field.get('min', 0))
64
+ max_val = int(field.get('max', 100))
65
+ row[field_name] = random.randint(min_val, max_val)
66
+ else:
67
+ row[field_name] = FIELD_TYPES[field_type]['method'](fake)
68
+ else:
69
+ row[field_name] = None
70
+ results.append(row)
71
+
72
+ return jsonify(results)
73
+
74
+ if __name__ == '__main__':
75
+ app.run(host='0.0.0.0', port=7860, debug=True)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Flask==3.0.0
2
+ Faker==22.5.1
templates/index.html ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mock Data Master - 虚拟数据生成大师</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body { font-family: 'Inter', sans-serif; }
13
+ .scrollbar-hide::-webkit-scrollbar { display: none; }
14
+ .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
15
+ </style>
16
+ </head>
17
+ <body class="bg-gray-50 text-gray-800 h-screen flex flex-col overflow-hidden">
18
+ <div id="app" class="flex flex-col h-full">
19
+ <!-- Header -->
20
+ <header class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between shadow-sm z-10">
21
+ <div class="flex items-center gap-3">
22
+ <div class="bg-blue-600 text-white p-2 rounded-lg">
23
+ <i class="ph ph-database text-xl"></i>
24
+ </div>
25
+ <div>
26
+ <h1 class="font-bold text-xl text-gray-900">Mock Data Master</h1>
27
+ <p class="text-xs text-gray-500">虚拟数据生成大师</p>
28
+ </div>
29
+ </div>
30
+ <div class="flex items-center gap-4">
31
+ <a href="https://github.com/duqing2026" target="_blank" class="text-gray-500 hover:text-gray-900">
32
+ <i class="ph ph-github-logo text-2xl"></i>
33
+ </a>
34
+ </div>
35
+ </header>
36
+
37
+ <!-- Main Content -->
38
+ <main class="flex-1 flex overflow-hidden">
39
+ <!-- Left Panel: Configuration -->
40
+ <div class="w-1/3 min-w-[350px] max-w-[500px] bg-white border-r border-gray-200 flex flex-col z-0">
41
+ <div class="p-5 border-b border-gray-100 bg-gray-50/50">
42
+ <h2 class="font-semibold text-gray-700 mb-4 flex items-center gap-2">
43
+ <i class="ph ph-faders"></i> 数据模型配置
44
+ </h2>
45
+ <div class="flex items-center gap-4 mb-2">
46
+ <label class="text-sm font-medium text-gray-600">生成行数</label>
47
+ <input type="number" v-model.number="rowCount" min="1" max="1000" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition text-sm">
48
+ </div>
49
+ </div>
50
+
51
+ <div class="flex-1 overflow-y-auto p-5 space-y-3">
52
+ <div v-for="(field, index) in schema" :key="index" class="bg-white border border-gray-200 rounded-lg p-3 shadow-sm hover:shadow-md transition group relative">
53
+ <div class="flex items-start gap-3">
54
+ <div class="flex-1 space-y-2">
55
+ <div class="flex gap-2">
56
+ <input type="text" v-model="field.name" placeholder="字段名 (如: username)" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-blue-500 outline-none font-mono">
57
+ <select v-model="field.type" class="w-32 px-2 py-1.5 border border-gray-300 rounded text-sm focus:border-blue-500 outline-none bg-gray-50">
58
+ <option v-for="type in availableTypes" :value="type.value">{{ type.label }}</option>
59
+ </select>
60
+ </div>
61
+ <!-- Extra options for integer -->
62
+ <div v-if="field.type === 'integer'" class="flex gap-2 items-center text-xs text-gray-500 pl-1">
63
+ <span>Min:</span>
64
+ <input type="number" v-model.number="field.min" class="w-16 border rounded px-1 py-0.5">
65
+ <span>Max:</span>
66
+ <input type="number" v-model.number="field.max" class="w-16 border rounded px-1 py-0.5">
67
+ </div>
68
+ </div>
69
+ <button @click="removeField(index)" class="text-gray-400 hover:text-red-500 p-1 rounded hover:bg-red-50 transition">
70
+ <i class="ph ph-trash"></i>
71
+ </button>
72
+ </div>
73
+ <div class="absolute left-0 top-0 bottom-0 w-1 bg-blue-500 rounded-l-lg opacity-0 group-hover:opacity-100 transition"></div>
74
+ </div>
75
+
76
+ <button @click="addField" class="w-full py-2 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-blue-500 hover:text-blue-500 hover:bg-blue-50 transition flex items-center justify-center gap-2 font-medium">
77
+ <i class="ph ph-plus-circle"></i> 添加字段
78
+ </button>
79
+ </div>
80
+
81
+ <div class="p-5 border-t border-gray-200 bg-white">
82
+ <button @click="generateData" :disabled="loading" class="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold shadow-lg shadow-blue-200 transition flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed">
83
+ <span v-if="loading" class="animate-spin"><i class="ph ph-spinner"></i></span>
84
+ <span v-else><i class="ph ph-lightning"></i> 生成数据</span>
85
+ </button>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Right Panel: Preview & Export -->
90
+ <div class="flex-1 flex flex-col bg-gray-50 overflow-hidden">
91
+ <!-- Toolbar -->
92
+ <div class="px-6 py-3 border-b border-gray-200 bg-white flex items-center justify-between">
93
+ <div class="flex items-center gap-2 text-sm text-gray-500">
94
+ <i class="ph ph-table"></i>
95
+ <span>数据预览 ({{ generatedData.length }} 行)</span>
96
+ </div>
97
+ <div class="flex gap-2" v-if="generatedData.length > 0">
98
+ <button @click="exportCSV" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm hover:bg-gray-50 text-gray-700 flex items-center gap-2 transition">
99
+ <i class="ph ph-file-csv text-green-600"></i> CSV
100
+ </button>
101
+ <button @click="exportJSON" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm hover:bg-gray-50 text-gray-700 flex items-center gap-2 transition">
102
+ <i class="ph ph-brackets-curly text-yellow-600"></i> JSON
103
+ </button>
104
+ <button @click="exportSQL" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm hover:bg-gray-50 text-gray-700 flex items-center gap-2 transition">
105
+ <i class="ph ph-database text-blue-600"></i> SQL
106
+ </button>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- Table Content -->
111
+ <div class="flex-1 overflow-auto p-6 relative">
112
+ <div v-if="generatedData.length === 0" class="h-full flex flex-col items-center justify-center text-gray-400">
113
+ <i class="ph ph-table text-6xl mb-4 text-gray-300"></i>
114
+ <p>暂无数据,请配置模型并点击生成</p>
115
+ </div>
116
+
117
+ <div v-else class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
118
+ <div class="overflow-x-auto">
119
+ <table class="w-full text-sm text-left">
120
+ <thead class="bg-gray-50 border-b border-gray-200 text-gray-600 font-medium">
121
+ <tr>
122
+ <th class="px-4 py-3 w-16 text-center">#</th>
123
+ <th v-for="col in schema" :key="col.name" class="px-4 py-3 whitespace-nowrap">{{ col.name }}</th>
124
+ </tr>
125
+ </thead>
126
+ <tbody class="divide-y divide-gray-100">
127
+ <tr v-for="(row, idx) in generatedData" :key="idx" class="hover:bg-blue-50/30 transition">
128
+ <td class="px-4 py-3 text-center text-gray-400 font-mono text-xs">{{ idx + 1 }}</td>
129
+ <td v-for="col in schema" :key="col.name" class="px-4 py-3 whitespace-nowrap text-gray-700 max-w-xs truncate">
130
+ {{ formatValue(row[col.name]) }}
131
+ </td>
132
+ </tr>
133
+ </tbody>
134
+ </table>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </main>
140
+
141
+ <!-- SQL Modal (Simple Overlay) -->
142
+ <div v-if="showSQLModal" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
143
+ <div class="bg-white rounded-xl shadow-2xl w-full max-w-3xl flex flex-col max-h-[80vh]">
144
+ <div class="p-4 border-b border-gray-100 flex justify-between items-center">
145
+ <h3 class="font-bold text-lg">SQL 预览</h3>
146
+ <button @click="showSQLModal = false" class="text-gray-400 hover:text-gray-600"><i class="ph ph-x text-xl"></i></button>
147
+ </div>
148
+ <div class="flex-1 overflow-auto p-4 bg-gray-900 text-gray-100 font-mono text-sm rounded-b-xl whitespace-pre">
149
+ {{ sqlContent }}
150
+ </div>
151
+ <div class="p-4 border-t border-gray-100 flex justify-end gap-3 bg-white rounded-b-xl">
152
+ <input type="text" v-model="tableName" placeholder="表名" class="border rounded px-2 py-1 text-sm">
153
+ <button @click="downloadSQL" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">下载 .sql</button>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <script>
160
+ const { createApp, ref, onMounted } = Vue;
161
+
162
+ createApp({
163
+ setup() {
164
+ const schema = ref([
165
+ { name: 'id', type: 'integer', min: 1, max: 1000 },
166
+ { name: 'full_name', type: 'name' },
167
+ { name: 'email', type: 'email' },
168
+ { name: 'city', type: 'city' }
169
+ ]);
170
+ const availableTypes = ref([]);
171
+ const rowCount = ref(50);
172
+ const generatedData = ref([]);
173
+ const loading = ref(false);
174
+ const showSQLModal = ref(false);
175
+ const sqlContent = ref('');
176
+ const tableName = ref('users');
177
+
178
+ // Fetch types from backend
179
+ const fetchTypes = async () => {
180
+ try {
181
+ const res = await fetch('/api/types');
182
+ availableTypes.value = await res.json();
183
+ } catch (e) {
184
+ console.error(e);
185
+ }
186
+ };
187
+
188
+ const addField = () => {
189
+ schema.value.push({ name: 'new_field', type: 'text' });
190
+ };
191
+
192
+ const removeField = (index) => {
193
+ schema.value.splice(index, 1);
194
+ };
195
+
196
+ const generateData = async () => {
197
+ if (schema.value.length === 0) return;
198
+ loading.value = true;
199
+ try {
200
+ const res = await fetch('/api/generate', {
201
+ method: 'POST',
202
+ headers: { 'Content-Type': 'application/json' },
203
+ body: JSON.stringify({
204
+ count: rowCount.value,
205
+ schema: schema.value
206
+ })
207
+ });
208
+ generatedData.value = await res.json();
209
+ } catch (e) {
210
+ alert('生成失败');
211
+ } finally {
212
+ loading.value = false;
213
+ }
214
+ };
215
+
216
+ const formatValue = (val) => {
217
+ if (val === true) return 'TRUE';
218
+ if (val === false) return 'FALSE';
219
+ return val;
220
+ };
221
+
222
+ // Export functions
223
+ const exportCSV = () => {
224
+ if (!generatedData.value.length) return;
225
+ const headers = schema.value.map(f => f.name).join(',');
226
+ const rows = generatedData.value.map(row =>
227
+ schema.value.map(f => {
228
+ let val = row[f.name];
229
+ if (val === null) return '';
230
+ // Handle string escaping for CSV
231
+ if (typeof val === 'string') {
232
+ val = val.replace(/"/g, '""');
233
+ if (val.includes(',') || val.includes('"') || val.includes('\n')) {
234
+ val = `"${val}"`;
235
+ }
236
+ }
237
+ return val;
238
+ }).join(',')
239
+ ).join('\n');
240
+
241
+ const csvContent = `${headers}\n${rows}`;
242
+ downloadFile(csvContent, 'mock_data.csv', 'text/csv;charset=utf-8;');
243
+ };
244
+
245
+ const exportJSON = () => {
246
+ if (!generatedData.value.length) return;
247
+ downloadFile(JSON.stringify(generatedData.value, null, 2), 'mock_data.json', 'application/json');
248
+ };
249
+
250
+ const generateSQL = () => {
251
+ if (!generatedData.value.length) return '';
252
+ const table = tableName.value || 'table_name';
253
+ const cols = schema.value.map(f => f.name).join(', ');
254
+
255
+ const stmts = generatedData.value.map(row => {
256
+ const vals = schema.value.map(f => {
257
+ const val = row[f.name];
258
+ if (val === null) return 'NULL';
259
+ if (typeof val === 'number') return val;
260
+ if (typeof val === 'boolean') return val ? 1 : 0;
261
+ return `'${String(val).replace(/'/g, "''")}'`;
262
+ }).join(', ');
263
+ return `INSERT INTO ${table} (${cols}) VALUES (${vals});`;
264
+ }).join('\n');
265
+
266
+ return stmts;
267
+ };
268
+
269
+ const exportSQL = () => {
270
+ sqlContent.value = generateSQL();
271
+ showSQLModal.value = true;
272
+ };
273
+
274
+ const downloadSQL = () => {
275
+ const sql = generateSQL();
276
+ downloadFile(sql, 'mock_data.sql', 'text/sql');
277
+ };
278
+
279
+ const downloadFile = (content, fileName, mimeType) => {
280
+ const blob = new Blob([content], { type: mimeType });
281
+ const link = document.createElement('a');
282
+ link.href = URL.createObjectURL(blob);
283
+ link.download = fileName;
284
+ link.click();
285
+ URL.revokeObjectURL(link.href);
286
+ };
287
+
288
+ onMounted(() => {
289
+ fetchTypes();
290
+ generateData(); // Auto generate on load
291
+ });
292
+
293
+ return {
294
+ schema, availableTypes, rowCount, generatedData, loading,
295
+ addField, removeField, generateData, formatValue,
296
+ exportCSV, exportJSON, exportSQL,
297
+ showSQLModal, sqlContent, tableName, downloadSQL
298
+ };
299
+ }
300
+ }).mount('#app');
301
+ </script>
302
+ </body>
303
+ </html>