一家只做卫生巾的网站/手机怎么搭建网站
【AI4CODE】目录
【AI4CODE】1 Trae CN 锥安装配置与迁移
【AI4CODE】2 Trae 锤一个 To-Do-List
【AI4CODE】3 Trae 锤一个贪吃蛇的小游戏
应用场景就是从一个 REST API GET 到数据后,经过数据转换 POST 到另一个 REST API。用 Trae CN 折腾了 3 天终于搞成功了,记录一下,好脑瓜不如烂笔头。
1 构建提示词
1.1 构建整个框架提示词
# 0 数据搬运工 提示词
## 1 应用名称:数据搬运工
## 2 开发代号:DataHauler
## 3 功能包括
- 源端API数据定义:可请求方式,定义URL,请求头headers,请求数据负载 payload,还有测试按钮 请求成功获取 json格式定义的数据- 目标端API数据定义:可请求方式,定义URL,请求头headers,请求数据负载 payload,还有测试按钮- 数据对照关系定义:目标端API负载的json数据和源端API数据获取Json数据对照关系定义- 数据搬运按钮:点击执行数据搬运操作,也可以设置时间周期自动执行搬运## 4 技术栈: 采用 html+css+javascript的技术栈
## 5 托管部署: 可托管部署在csdn的inscode上
## 6 UI要求: 简洁美观
1.2 开始搬运按钮提示词
7 开始搬运按钮的代码逻辑
- 执行【源端测试连接】按钮上代码,获取源端API的响应数据后;
- 源端响应数据如下:
json { "data": { "rows": [ { "DATE_T": "2025-03-27T13:41:43+08:00", "N_AGE": 55, "P_ID": -300, "S_DATE": "2025-03-27", "S_NAME": "5217" }, { "DATE_T": "2024-04-09T00:00:00+08:00", "N_AGE": 17, "P_ID": 1, "S_DATE": "2024-04-08", "S_NAME": "blma" }, { "DATE_T": "2024-04-09T17:01:36+08:00", "N_AGE": 52, "P_ID": 2, "S_DATE": "2024-04-09", "S_NAME": "白龙马" } ] }, "msg": "", "status": 0, "total": 3 }
- 按照映射配置定义的json数据结构 ```json { “P_ID2”: “data.rows[1].P_ID”, “S_NAME2”: “data.rows[1].S_NAME”, “N_AGE2”: “data.rows[1].N_AGE” }
4. 以及目标端请求数据负载模版 ```json{ "P_ID2": 222, "S_NAME2": "blmA", "N_AGE2": 56 } ``` 逐行构造目标端API的负载数据 5. 执行【目标端测试连接】按钮上代码 6. 重复执行3-5步
1.3 定时搬运提示词
## 9 V7版本修改为可以定时搬运数据
scheduleTransfer()倒计时结束 直接调用 scheduleTransfer()实现定时搬运数据
在【定时搬运】按钮上显示倒计时
1.4 补充说明
-
首先我的前端三件套(HTML+CSS+JavaScript)是很菜的,很多都是现学现问的。
-
后端用的 Goland 开发的通用后端 REST2SQL 简单配置一下数据库连接字符串就可以提供 所有数据库表和视图的 REST 服务,甚至是直接执行SQL。
-
前端服务用在 Trae 下安装的 live-server 插件
-
整个都是用的 Builder 模式,构建过程不是很顺利,经常翻车,有时候是直接拷贝代码为 Trae 来修改,修改多次都不能满意就亲自上手,用的多了就知道如何用自然语言和 Trae 交互了。
2 数据搬运工的全部代码
2.1 index.html
页面布局
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>数据搬运工 - DataHauler</title><link rel="stylesheet" href="style.css">
</head><body><div class="container"><h1>数据搬运工</h1><div class="config-container"><!-- 源端API配置 --><div class="api-config source-config"><h2>源端API配置</h2><form id="sourceApiForm"><div class="form-group"><label for="sourceUrl">请求方法和API地址:</label><div class="form-group row"><select id="sourceMethod" required><option value="GET" selected>GET</option><option value="POST">POST</option><option value="PUT">PUT</option><option value="DELETE">DELETE</option></select><input type="text" id="sourceUrl" placeholder="http://127.0.0.1:5217/rest/dataTable" required></div></div><div class="form-group"><label for="sourceHeaders">请求头:</label><textarea id="sourceHeaders" rows="3" placeholder="{}" Authorization\": \"Bearer token\"}"></textarea></div><div class="form-group"><label for="sourcePayload">请求负载:</label><textarea id="sourcePayload" rows="3" placeholder="{}" param1\": \"value1\"}"></textarea></div><button type="button" id="testSourceApi">源端测试连接</button><div class="form-group"><label for="sourceResponse">响应数据:</label><textarea id="sourceResponse" rows="5" placeholder="源端API响应数据将显示在这里" readonly></textarea></div></form></div><!-- 目标端API配置 --><div class="api-config target-config"><h2>目标端API配置</h2><form id="targetApiForm"><div class="form-group"><label for="targetUrl">请求方法和API地址:</label><div class="form-group row"><select id="targetMethod" required><option value="GET">GET</option><option value="POST" selected>POST</option><option value="PUT">PUT</option><option value="DELETE">DELETE</option></select><input type="text" id="targetUrl" placeholder="https://api.example.com/insert" required></div></div><div class="form-group"><label for="targetHeaders">请求头:</label><textarea id="targetHeaders" rows="3" placeholder="{}" Content-Type\": \"application/json\"}"></textarea></div><div class="form-group"><label for="targetPayload">请求负载模板:</label><textarea id="targetPayload" rows="3" placeholder="{}" data\": \"{{sourceData}}\"}"></textarea></div><button type="button" id="testTargetApi">目标端测试连接</button><div class="form-group"><label for="targetResponse">响应数据:</label><textarea id="targetResponse" rows="5" placeholder="目标端API响应数据将显示在这里" readonly></textarea></div></form></div></div><!-- 数据映射配置 --><div id="mappingEditor" class="mapping-config"><h2>数据映射关系</h2><div class="form-group"><textarea id="mappingContent" rows="10"placeholder='[{"P_ID2": "data.rows[0].P_ID","S_NAME2": "data.rows[0].S_NAME","N_AGE2": "data.rows[0].N_AGE"}]'class="json-editor"></textarea></div></div><div style="text-align:center; margin: 10px 40px;"><button type="button" id="loadFromLocal">本地加载配置</button><button type="button" id="saveAllConfig">保存当前配置</button></div></div><!-- 操作按钮 --><div class="actions"><button id="startTransfer">开始搬运</button><button id="scheduleTransfer">定时搬运</button></div></div><script src="app.js"></script>
</body></html>
2.2 style.css
CSS样式
/* 基础样式重置 */
* {box-sizing: border-box;margin: 0;padding: 0;
}body {font-family: 'Segoe UI', system-ui, sans-serif;line-height: 1;padding: 5px;background-color: #f8f9fa;color: #333;
}h1 {text-align: center;margin: 0rem 0;padding: 1rem 0;
}.container {max-width: 1200px;margin: 0 auto;padding: 15px;background: white;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}.config-container h2 {text-align: center;margin: 1.5rem 0;padding: 0.5rem;font-size: 1.5rem;
}/* 三栏布局样式 */
.config-container {display: flex;gap: 15px;margin-bottom: 1rem;
}.api-config {flex: 1;
}.api-config {flex: 1;padding: 5px;background: white;border: 1px solid #e0e0e0;border-radius: 8px;margin-bottom: 5px;display: flex;flex-direction: column;gap: 10px;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}.mapping-config {margin-top: 1rem;
}.actions {display: flex;gap: 1rem;justify-content: center;margin-top: 2rem;
}.source-config {order: 1;flex: 1;
}.target-config {order: 2;flex: 1;
}/* 表单样式 */
.form-group {margin-bottom: 0.1rem;display: flex;flex-direction: column;gap: 0.05rem;
}.form-group.row {flex-direction: row;align-items: center;
}.form-group.row select {flex: 0.5;margin-right: 10px;
}.form-group.row input {flex: 5;
}.form-group label {font-weight: 600;color: #555;
}input,
textarea {padding: 0.8rem;border: 1px solid #ddd;border-radius: 4px;font-size: 1rem;font-family: Consolas, monospace;
}textarea {min-height: 100px;resize: vertical;
}#sourceResponse,
#targetResponse {min-height: 120px;
}/* 按钮样式 */
button {padding: 0.5rem 3rem;border: none;border-radius: 5px;cursor: pointer;transition: all 0.2s;font-weight: 600;
}#testSourceApi,
#testTargetApi {background: #576b95;color: white;
}#testSourceApi:hover,
#testTargetApi:hover {opacity: 0.9;
}/* 数据映射区域 */
.mapping-config {margin-bottom: 1rem;padding: 1rem;border: 1px solid #eee;border-radius: 8px;
}/* 操作按钮区域 */
.actions {display: flex;gap: 30rem;justify-content: center;
}#startTransfer {background: #07c160;color: white;padding: 10px 80px;border-radius: 25px;box-shadow: 0 2px 6px rgba(7, 193, 96, 0.3);
}#testSourceApi {display: block;margin: 0.5rem auto;text-align: center;
}#testTargetApi {display: block;margin: 0.5rem auto;text-align: center;
}#scheduleTransfer {background: #6467f2;color: white;padding: 10px 50px;
}#startTransfer:hover,
#scheduleTransfer:hover {opacity: 0.9;
}
2.3 app.js
代码逻辑
// 数据搬运工核心功能
class DataHauler {constructor() {this.sourceApi = {method:'',url: '',headers: {},payload: {}};this.targetApi = {method:'',url: '',headers: {},payloadTemplate: ''};// 初始化表单默认值document.getElementById('sourceHeaders').value = '{}';document.getElementById('sourcePayload').value = '{}';document.getElementById('targetHeaders').value = '{}';document.getElementById('targetPayload').value = '{}';document.getElementById('mappingContent').value = '{}';this.mappings = {};// 初始化事件监听this.initEventListeners();// 添加API配置文件输入元素const configFileInput = document.createElement('input');configFileInput.type = 'file';configFileInput.accept = '.json';configFileInput.style.display = 'none';configFileInput.addEventListener('change', (e) => this.loadConfigFromFile(e));document.body.appendChild(configFileInput);// 移除保存API配置按钮const saveApiBtn = document.querySelector('.button-group button:first-child');if (saveApiBtn && saveApiBtn.textContent === '保存API配置') {saveApiBtn.remove();}// 绑定从本地加载按钮事件document.getElementById('loadFromLocal').addEventListener('click', () => {const choice = confirm('确定要加载全部配置吗?\n\n确定 - 加载全部配置\n取消 - 不加载');if (choice) {//apiConfigFileInput.click();configFileInput.click();}});}// 初始化事件监听initEventListeners() {// 测试源端API连接document.getElementById('testSourceApi').addEventListener('click', () => {this.testSourceApi();});// 测试目标端API连接document.getElementById('testTargetApi').addEventListener('click', () => {this.testTargetApi();});// 开始搬运数据document.getElementById('startTransfer').addEventListener('click', () => {this.startTransfer();});// 定时搬运数据document.getElementById('scheduleTransfer').addEventListener('click', () => {this.scheduleTransfer();});}// 测试源端API连接async testSourceApi() {try {// 获取表单数据const sourceUrl = document.getElementById('sourceUrl').value.trim();if (!sourceUrl) {throw new Error('请填写源端API地址');}this.sourceApi.url = sourceUrl;// 解析请求头JSONconst headersValue = document.getElementById('sourceHeaders').value.trim();this.sourceApi.headers = headersValue ? JSON.parse(headersValue) : {};// 解析请求负载JSONconst payloadValue = document.getElementById('sourcePayload').value.trim();this.sourceApi.payload = payloadValue ? JSON.parse(payloadValue) : {};// 获取请求方法this.sourceApi.method = document.getElementById('sourceMethod').value;// 调用APIconst requestOptions = {method: this.sourceApi.method,headers: this.sourceApi.headers};// 只有非GET请求才需要bodyif (this.sourceApi.method !== 'GET' && Object.keys(this.sourceApi.payload).length > 0) {requestOptions.body = JSON.stringify(this.sourceApi.payload);}const response = await this.callApi(this.sourceApi.url, requestOptions);// 检查返回数据是否有效if (!response) {throw new Error('返回数据不能为空');}let formattedResponse;// 处理不同类型的响应数据if (typeof response === 'string') {try {// 尝试解析为JSONconst parsed = JSON.parse(response);formattedResponse = JSON.stringify(parsed, null, 2);} catch (e) {// 如果不是JSON,直接显示原始字符串formattedResponse = response;}} else if (typeof response === 'object') {// 如果是对象,格式化为JSON字符串formattedResponse = JSON.stringify(response, null, 2);} else {// 其他类型转换为字符串formattedResponse = String(response);}document.getElementById('sourceResponse').value = typeof formattedResponse === 'string' ? formattedResponse.replace(/\\"/g, '"') : JSON.stringify(formattedResponse, null, 2).replace(/\\"/g, '"');return response; // 返回数据用于后续处理} catch (error) {document.getElementById('sourceResponse').value = `错误: ${error.message}`;throw error; // 重新抛出错误以便外部捕获}}// 测试目标端API连接async testTargetApi() {try {// 获取表单数据this.targetApi.url = document.getElementById('targetUrl').value;this.targetApi.headers = JSON.parse(document.getElementById('targetHeaders').value || '{}');this.targetApi.payloadTemplate = document.getElementById('targetPayload').value;this.targetApi.method = document.getElementById('targetMethod').value;// 调用APIconst response = await this.callApi(this.targetApi.url, {method: this.targetApi.method,headers: this.targetApi.headers,body: this.targetApi.payloadTemplate});// 将响应数据显示在目标端响应框中document.getElementById('targetResponse').value = typeof response === 'string' ? response : JSON.stringify(response, null, 2).replace(/\\"/g, '"');} catch (error) {document.getElementById('targetResponse').value = `错误: ${error.message}`;}}// 开始搬运数据async startTransfer() { try {// 1. 测试源端APIconst sourceData = await this.testSourceApi(); // 2. 遍历源数据行并应用映射const parsed = JSON.parse(sourceData);// 从映射配置中动态获取rows路径const firstMapping = Object.values(this.mappings)[0];const rowsPathMatch = firstMapping.match(/data\.(rows\[\d+\]|rows)/);const rowsPath = rowsPathMatch ? rowsPathMatch[0] : 'data.rows'; // 使用匹配到的路径或默认路径// 动态获取rows数组const rows = rowsPath.split('.').reduce((obj, key) => {// 去除数组索引部分,只保留属性名const cleanKey = key.replace(/\[\d+\]/g, '');return obj?.[cleanKey];}, parsed) || [];for (const row of rows) {// 3. 应用数据映射const transformedData = {};for (const [targetKey, sourcePath] of Object.entries(this.mappings)) {const pathParts = sourcePath.split('.');let value = row;for (const part of pathParts.slice(2)) { // 跳过data.rows部分if (part.includes('[')) {const arrayPart = part.replace(/\[\d+\]/g, '');value = value[arrayPart];} else {value = value[part];}}transformedData[targetKey] = value;}// alert(`成功应用数据映射: ${JSON.stringify(transformedData)}`);// 5. 发送到目标端this.targetApi.method = document.getElementById('targetMethod').value;this.targetApi.url = document.getElementById('targetUrl').value;this.targetApi.headers = JSON.parse(document.getElementById('targetHeaders').value || '{}');//this.targetApi.payloadTemplate = targetPayload;document.getElementById('targetPayload').value = JSON.stringify(transformedData, null, 2);const response = await this.callApi(this.targetApi.url, {method: this.targetApi.method,headers: this.targetApi.headers,body: JSON.stringify(transformedData)});console.log(`成功搬运数据行: ${JSON.stringify(row)}`);}console.log(`${new Date()}数据搬运完成!共处理${rows.length}条记录`);} catch (error) {alert(`数据搬运失败: ${error.message}`);}}// 定时搬运数据scheduleTransfer() {// 清除现有的定时器和倒计时if (this.timers) {clearInterval(this.timers.transfer);clearInterval(this.timers.countdown);}const interval = prompt('请输入定时间隔(分钟):', '5217');if (interval && !isNaN(interval)) {const minutes = parseInt(interval);const intervalMs = minutes * 60 * 1000;const button = document.getElementById('scheduleTransfer');// 立即执行一次this.startTransfer();// 设置定时器const timerId = setInterval(() => this.startTransfer(), intervalMs);// 更新按钮文本显示倒计时let remaining = minutes * 60;const updateButtonText = () => {const hours = Math.floor(remaining / 3600);const mins = Math.floor((remaining % 3600) / 60);const secs = remaining % 60;button.textContent = `定时搬运 (${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')})`;};updateButtonText();// 每秒更新倒计时const countdown = setInterval(() => {remaining--;updateButtonText();if (remaining <= 0) {remaining = minutes * 60;}}, 1000);// 存储定时器ID以便后续清除this.timers = {transfer: timerId,countdown: countdown};}}// 加载API配置和映射配置async loadConfigFromFile(event) {try {const file = event.target.files[0];if (!file) {return;}const reader = new FileReader();reader.onload = (e) => {try {const config = JSON.parse(e.target.result);// 设置API配置if (config.sourceApi) {document.getElementById('sourceMethod').value = config.sourceApi.method;document.getElementById('sourceUrl').value = config.sourceApi.url;document.getElementById('sourceHeaders').value = JSON.stringify(config.sourceApi.headers, null, 2);document.getElementById('sourcePayload').value = JSON.stringify(config.sourceApi.payload, null, 2);}if (config.targetApi) {document.getElementById('targetMethod').value = config.targetApi.method;document.getElementById('targetUrl').value = config.targetApi.url;document.getElementById('targetHeaders').value = JSON.stringify(config.targetApi.headers, null, 2);document.getElementById('targetPayload').value = JSON.stringify(config.targetApi.payloadTemplate, null, 2);}// 设置映射配置if (config.mappings) {this.mappings = config.mappings;const mappingElement = document.getElementById('mappingContent') || document.getElementById('mappingsTextarea') || document.querySelector('textarea[id^="mapping"]');if (mappingElement) {mappingElement.value = JSON.stringify(this.mappings, null, 2);} else {console.warn('未找到映射配置输入框元素');// 创建新的映射配置输入框const newTextarea = document.createElement('textarea');newTextarea.id = 'mappingContent';newTextarea.rows = 10;newTextarea.cols = 80;newTextarea.value = JSON.stringify(this.mappings, null, 2);document.body.appendChild(newTextarea);}}alert(`成功从文件 ${file.name} 加载所有配置`);} catch (error) {alert(`解析配置文件失败: ${error.message}`);}};reader.onerror = () => {alert(`读取文件 ${file.name} 时发生错误`);};reader.readAsText(file);} catch (error) {alert(`加载配置文件失败: ${error.message}`);}}// 保存全部配置async saveAllConfig() {try {// 验证JSON格式const validateJSON = (jsonString) => {try {JSON.parse(jsonString);return true;} catch (e) {return false;}};// 获取并验证所有表单数据const sourceHeaders = document.getElementById('sourceHeaders').value;const sourcePayload = document.getElementById('sourcePayload').value;const targetHeaders = document.getElementById('targetHeaders').value;const targetPayload = document.getElementById('targetPayload').value;const mappingContent = document.getElementById('mappingContent').value;if (!validateJSON(sourceHeaders) || !validateJSON(sourcePayload) ||!validateJSON(targetHeaders) || !validateJSON(targetPayload) ||!validateJSON(mappingContent)) {throw new Error('请检查JSON格式是否正确');}// 合并所有配置const config = {sourceApi: {method: document.getElementById('sourceMethod').value,url: document.getElementById('sourceUrl').value,headers: JSON.parse(sourceHeaders),payload: JSON.parse(sourcePayload)},targetApi: {method: document.getElementById('targetMethod').value,url: document.getElementById('targetUrl').value,headers: JSON.parse(targetHeaders),payloadTemplate: JSON.parse(targetPayload)},mappings: JSON.parse(mappingContent)};// 保存配置const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = 'dhconfig.json';document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);alert('所有配置已保存到本地文件dhconfig.json');} catch (error) {console.error('保存配置时出错:', error);alert(`保存配置时出错: ${error.message}`);}}// 初始化事件监听initEventListeners() {// 测试源端API连接document.getElementById('testSourceApi').addEventListener('click', () => {this.testSourceApi();});// 测试目标端API连接document.getElementById('testTargetApi').addEventListener('click', () => {this.testTargetApi();});// 开始搬运数据document.getElementById('startTransfer').addEventListener('click', () => {this.startTransfer();});// 定时搬运数据document.getElementById('scheduleTransfer').addEventListener('click', () => {this.scheduleTransfer();});// 保存配置document.getElementById('saveAllConfig').addEventListener('click', () => {this.saveAllConfig();});}// 通用API调用方法async callApi(url, options) {try {const response = await fetch(url, options);if (!response.ok) {throw new Error(`HTTP错误! 状态码: ${response.status}`);}// 检查响应内容类型是否为JSONconst contentType = response.headers.get('content-type');if (contentType && contentType.includes('application/json')) {const data = await response.json();// 验证返回数据是否为对象if (typeof data !== 'object' || data === null) {throw new Error('返回数据必须是JSON对象');}return data;} else {// 如果不是JSON格式,返回原始文本return await response.text();}} catch (error) {console.error('API调用失败:', error);throw error; // 重新抛出错误以便外部捕获}}
}// 初始化应用
window.addEventListener('DOMContentLoaded', () => {new DataHauler();
});
2.4 dhconfig.json
配置文件
{"sourceApi": {"method": "GET","url": "http://127.0.0.1:5217/rest/data_type","headers": {},"payload": {}},"targetApi": {"method": "POST","url": "http://127.0.0.1:5217/rest/data_type2 ","headers": {},"payloadTemplate": {}},"mappings": {"P_ID2": "data.rows[1].P_ID","S_NAME2": "data.rows[1].S_NAME","N_AGE2": "data.rows[1].N_AGE"}
}
3 实操演练
3.1 源端 API GET 数据
首先配置源端 API保证能 GET 到数据,配置好API的URL地址,点【源端测试连接】,源端响应数据应该能看到数据才行。比如如下截图:
数据库表的原始数据如下截图:
3.2 目标端 API POST 数据
配置目标端 API 地址,请求负载,点【目标端测试连接】,目标端有响应数据显示。比如如下截图:
数据库验证时候 POST 成功:
3.3 配置数据映射关系 JSON
- 源数据
{"data": {"rows": [{"DATE_T": "2025-04-01T09:01:40+08:00","N_AGE": 55,"P_ID": -300,"S_DATE": "2025-04-01","S_NAME": "5217"},{"DATE_T": "2025-04-01T09:01:40+08:00","N_AGE": 17,"P_ID": 1,"S_DATE": "2025-04-01","S_NAME": "blma"},{"DATE_T": "2025-04-01T09:01:40+08:00","N_AGE": 52,"P_ID": 2,"S_DATE": "2025-04-01","S_NAME": "白龙马"}]},"msg": "","status": 0,"total": 3
}
- 目标数据负载
{"P_ID2": 222,"S_NAME2": "blmA","N_AGE2": 56
}
- 数据映射关系定义
{"P_ID2": "data.rows[0].P_ID","S_NAME2": "data.rows[0].S_NAME","N_AGE2": "data.rows[0].N_AGE"
}
源数据有5个字段,只需要3个字段,只映射3字段即可。
3.4 开始搬运
万事俱备,见证奇迹:点【开始搬运】按钮执行数据搬运。
控制台会有搬运日子输出。检查数据库是否搬运成功:
OK 搬运成功了。
3.5 定时搬运
设置2分钟定时搬运,倒计时开始,倒计时结束成功搬运。
定时设置成功会先执行一次搬运。然后按设定时间定时搬运。良辰吉时已到,检查一下数据库记录。
时间间隔 2:01,
OK!
本文完