基于公司(KPI)需要,测试需要有一些 AI 相关产出和落地情况,然后测试自己就开始做工具(造轮子),经过 7 个工作日的努力,写出了一个基于 pdf、图片的测试用例生成(按概率随机)工具。
import cv2
import easyocr
import json
import os
import zipfile
import uuid
import numpy as np
from openai import OpenAI
from pathlib import Path
def ocr_image(image_path):
"""使用EasyOCR识别图片中的文字,支持中文路径"""
# 规范化路径
image_path = os.path.normpath(image_path)
# 检查文件是否存在
if not os.path.exists(image_path):
raise FileNotFoundError(f"文件不存在: {image_path}")
# 使用numpy先读取文件,然后再用OpenCV解码,解决中文路径问题
try:
img_array = np.fromfile(image_path, dtype=np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
# 检查图像是否成功加载
if img is None:
raise ValueError(f"无法解码图像: {image_path}")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 初始化OCR读取器
reader = easyocr.Reader(['ch_sim', 'en'])
results = reader.readtext(gray)
# 提取文本
text = ""
for detection in results:
text += detection[1] + "\n"
return text
except Exception as e:
raise Exception(f"处理图像时出错: {e}, 路径: {image_path}")
def text_to_test_points(text):
"""将OCR文本转换为测试点JSON结构"""
client = OpenAI(
# 避免api_key硬编码,从本地读取api_key文件
api_key=Path('D:/deepseek_api_key.txt').read_text(encoding='utf-8'),
base_url="https://api.deepseek.com",
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{
"role": "system",
"content": "请将OCR识别的文字内容转换为测试点JSON。格式要求:\n"
"{\n"
" \"topic\": \"主标题\",\n"
" \"children\": [\n"
" {\n"
" \"topic\": \"子标题1\",\n"
" \"children\": [{\"topic\": \"内容1\"}, {\"topic\": \"内容2\"}]\n"
" },\n"
" {\n"
" \"topic\": \"子标题2\",\n"
" \"children\": [{\"topic\": \"内容3\"}, {\"topic\": \"内容4\"}]\n"
" }\n"
" ]\n"
"}"
},
{"role": "user", "content": text},
],
response_format={"type": "json_object"},
)
return response.choices[0].message.content
def json_to_xmind(json_data, output_file):
"""将JSON测试点转换为XMind文件"""
# 解析JSON数据
data = json.loads(json_data)
# 创建内容数据结构
content = {
"id": f"root:{uuid.uuid4().hex}",
"class": "sheet",
"title": "测试点列表",
"rootTopic": {
"id": f"root-topic:{uuid.uuid4().hex}",
"class": "topic",
"title": data["topic"],
"children": {"attached": []}
},
"theme": {
"id": "theme:1",
"centralTopic": {}
}
}
# 递归构建主题树
def build_topics(parent, children_data):
children = []
for child in children_data:
topic = {
"id": f"topic:{uuid.uuid4().hex}",
"class": "topic",
"title": child["topic"]
}
if "children" in child and child["children"]:
topic["children"] = {"attached": []}
build_topics(topic["children"]["attached"], child["children"])
children.append(topic)
parent.extend(children)
# 构建主题树
build_topics(content["rootTopic"]["children"]["attached"], data["children"])
# 创建元数据
manifest = {
"file-entries": {
"content.json": {},
"metadata.json": {}
}
}
metadata = {
"creator": {
"name": "OCR-TestPoints-Generator",
"version": "1.0"
},
"created-time": "",
}
# 创建输出目录
os.makedirs(os.path.dirname(os.path.abspath(output_file)), exist_ok=True)
# 创建ZIP文件(XMind文件本质上是ZIP文件)
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
# 写入内容JSON
zipf.writestr('content.json', json.dumps([content], indent=2))
# 写入元数据
zipf.writestr('metadata.json', json.dumps(metadata, indent=2))
# 写入清单文件
zipf.writestr('manifest.json', json.dumps(manifest, indent=2))
print(f"XMind文件已生成: {os.path.abspath(output_file)}")
def image_to_xmind(image_path, output_path=None):
"""完整处理流程:图片 -> OCR识别 -> 测试点JSON -> XMind文件"""
# 规范化输入路径
image_path = os.path.normpath(image_path)
# 如果未指定输出路径,则基于输入文件名生成
if output_path is None:
base_name = os.path.splitext(os.path.basename(image_path))[0]
output_dir = "output"
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, f"{base_name}_测试点.xmind")
# 规范化输出路径
output_path = os.path.normpath(output_path)
# 1. OCR识别图片
print(f"正在进行OCR识别...路径: {image_path}")
text = ocr_image(image_path)
print(f"识别文字:\n{text[:200]}...")
# 2. 转换为测试点JSON
print("正在生成测试点JSON...")
json_data = text_to_test_points(text)
print("JSON生成完成")
# 3. 生成XMind文件
print(f"正在创建XMind文件...路径: {output_path}")
json_to_xmind(json_data, output_path)
return output_path
# 使用示例
if __name__ == "__main__":
# 示例图片路径 - 支持中文路径
image_path = r"D:/XXXX.png"
try:
# 转换为XMind
xmind_file = image_to_xmind(image_path)
print(f"处理完成,XMind文件保存在: {xmind_file}")
except Exception as e:
print(f"处理失败: {e}")
### 1.0 单机 demo 跑通了, 需求更新(采购了 1 台服务器,需要前端访问页面)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具页面</title>
<link rel="stylesheet" href="css/styles.css">
<script src="js/main.js"></script>
</head>
<body>
<div class="container-fluid">
<header>
<nav>
<ul>
<li><a href="#mysql">MySQL查询工具</a></li>
<li><a href="#xmind">XMind转换工具</a></li>
<li><a href="#testcase">测试点生成工具</a></li>
</ul>
</nav>
</header>
<section id="mysql">
<h2>MySQL查询工具</h2>
<div class="mysql-tool card">
<div class="db-row" style="display: flex; gap: 24px; align-items: flex-start;">
<div class="db-select card-body" style="flex: 1; display: flex; flex-direction: column; height: 260px;">
<h3 style="flex-shrink: 0;">选择数据库</h3>
<div class="scrollable" style="flex: 1; overflow-y: auto; min-height: 0;">
<ul id="dataSourceList"></ul>
</div>
<button type="button" class="btn" style="flex-shrink: 0; margin-bottom: 8px;">加载表</button>
</div>
<div class="table-list card-body" style="flex: 2;">
<h3>数据库表列表</h3>
<div class="scrollable">
<ul id="tableList"></ul>
</div>
</div>
</div>
<div class="table-structures" id="tableStructures" style="display: flex; gap: 16px; margin-top: 20px;"></div>
<div class="generate-section">
<h3>生成SQL</h3>
<label for="promptInput">输入提示:</label>
<textarea id="promptInput" rows="3" placeholder="输入生成SQL的描述..."></textarea>
<div class="selected-tables">
<p>已选表:</p>
<ul id="selectedTablesList"></ul>
</div>
<button id="generateSqlBtn">生成SQL</button>
</div>
<div class="sql-editor">
<textarea id="sqlTextarea" class="CodeMirror" placeholder="生成或输入SQL语句..."></textarea>
<button type="button" id="executeSql">执行SQL</button>
<button type="button" id="clearSql">清空</button>
</div>
<div class="query-result">
<h3>查询结果</h3>
<div class="result-table" id="queryResultContainer">
<!-- 动态结果将在这里显示 -->
</div>
</div>
</div>
</section>
<section id="xmind" style="display: none;">
<h2>XMind转换工具</h2>
<div class="xmind-converter">
<h3>XMind 文件转换</h3>
<form id="xmindForm">
<input type="file" id="xmindFile" accept=".xmind">
<select id="templateType">
<option value="ones">ones</option>
<option value="MeterSphere">MeterSphere</option>
</select>
<button type="button" onclick="handleUpload()">开始转换</button>
<div class="xmind-date card-body">
<h3>转换信息</h3>
<textarea class="xmind-response" id="xmindResponse" readonly></textarea>
</div>
<button type="button" onclick="handleDownload()" id="downloadBtn" disabled>下载Excel</button>
</form>
<div id="statusMessage"></div>
</div>
</section>
<section id="testcase" style="display: none;">
<h2>测试点生成工具</h2>
<div class="testcase-generator">
<!--<h3>测试点生成工具</h3>-->
<form id="testPointForm">
<input type="file" id="testPointFile" accept=".doc,.docx,.jpg,.png">
<button type="button" id="generateTestPointBtn">生成测试点</button>
</form>
<div class="testcase-result" id="testPointResult" style="display:none;">
<p id="testPointMsg"></p>
<div style="margin-top:16px;">
<div id="xmindPreviewContainer" style="width:100%;height:500px;border:1px solid #ccc;"></div>
</div>
<div style="margin-top:16px;">
<input type="text" id="regenerateInput" placeholder="请输入新的生成要求" style="width:60%;padding:6px;">
<button type="button" id="regenerateBtn">重新生成</button>
<button type="button" id="downloadTestPointBtn" disabled style="margin-left:12px;">下载文件</button>
</div>
</div>
</div>
</section>
</div>
<!-- xmind-embed-viewer CDN -->
<script src="https://unpkg.com/xmind-embed-viewer/dist/umd/xmind-embed-viewer.js"></script>
</body>
</html>
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
const navLinks = document.querySelectorAll('nav ul li a');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').slice(1);
document.querySelectorAll('section').forEach(section => {
section.style.display = 'none';
});
document.getElementById(targetId).style.display = 'block';
});
});
// ========== SQL生成工具相关js ==========
const sqlButton = document.querySelector('.sql-editor button');
const queryResult = document.querySelector('.query-result table tbody');
// 1. 加载数据库列表
function loadDataSources() {
fetch('/api/db/dataTables')
.then(res => {
if (!res.ok) throw new Error('获取数据库列表失败');
return res.json();
})
.then(dbs => {
const dataSourceList = document.getElementById('dataSourceList');
dataSourceList.innerHTML = '';
dbs.forEach(db => {
const li = document.createElement('li');
li.textContent = db;
li.addEventListener('click', () => {
// 在数据库点击事件中添加
document.querySelectorAll('#dataSourceList li').forEach(l => l.classList.remove('active'));
li.classList.add('active');
dbConnection.currentDb = dbName;
});
dataSourceList.appendChild(li);
});
// 默认选中第一个数据库
if (dbs.length > 0) {
dataSourceList.firstChild.click();
}
})
.catch(err => {
console.error(err);
document.getElementById('dataSourceList').innerHTML = '<li style="color:red;">加载数据库失败</li>';
});
}
// 添加载入表按钮事件
document.querySelector('.db-select button').addEventListener('click', function() {
const selectedDb = document.querySelector('#dataSourceList li.active');
if (selectedDb) {
loadTables(selectedDb.textContent);
} else {
alert('请先选择数据库');
}
});
// 获取数据库列表
// 2. 加载指定数据库的表列表(使用 form-data 请求方式)
// 修复后的loadTables函数
// 存储表结构信息
const tableStructures = {};
const selectedTables = new Set();
// 修改 loadTables 函数,添加表多选逻辑
function loadTables(dbName) {
const formData = new FormData();
formData.append('dbName', dbName);
fetch('/api/db/tables', { method: 'POST', body: formData })
.then(res => {
if (!res.ok) throw new Error('获取表列表失败');
return res.json();
})
.then(tables => {
const tableList = document.getElementById('tableList');
tableList.innerHTML = '';
tables.forEach(table => {
const li = document.createElement('li');
li.textContent = table;
if (selectedTables.has(table)) li.classList.add('active');
li.addEventListener('click', () => {
if (selectedTables.has(table)) {
selectedTables.delete(table);
li.classList.remove('active');
delete tableStructures[table];
} else {
selectedTables.add(table);
li.classList.add('active');
loadTableStructure(table);
}
renderTableStructures();
updateSelectedTablesList();
});
tableList.appendChild(li);
});
})
.catch(err => {
console.error(err);
document.getElementById('tableList').innerHTML = '<li style="color:red;">加载表列表失败</li>';
});
}
// 渲染已选表结构,多列横向展示
function renderTableStructures() {
const container = document.getElementById('tableStructures');
container.innerHTML = '';
selectedTables.forEach(table => {
const struct = tableStructures[table] || [];
let html = `<div class="table-struct-col"><h4>${table}</h4><table><thead><tr><th>名称</th><th>类型</th><th>主键</th></tr></thead><tbody>`;
struct.forEach(col => {
html += `<tr><td>${col.Field || ''}</td><td>${col.Type || ''}</td><td>${col.Key || ''}</td></tr>`;
});
html += '</tbody></table></div>';
container.innerHTML += html;
});
}
// 修改 loadTableStructure 函数,存储表结构并渲染
function loadTableStructure(tableName) {
fetch(`/api/db/table/${tableName}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP错误! 状态码: ${response.status}`);
}
return response.json();
})
.then(data => {
if (!data || !Array.isArray(data)) {
throw new Error('返回的数据格式不正确');
}
tableStructures[tableName] = data;
renderTableStructures();
})
.catch(error => {
console.error('加载表结构失败:', error);
tableStructures[tableName] = [{Field: '加载失败', Type: '', Key: ''}];
renderTableStructures();
});
}
// 更新已选表列表显示
function updateSelectedTablesList() {
const selectedTablesList = document.getElementById('selectedTablesList');
selectedTablesList.innerHTML = '';
selectedTables.forEach(table => {
const li = document.createElement('li');
li.textContent = table;
selectedTablesList.appendChild(li);
});
}
// 清空SQL输入框
const clearBtn = document.getElementById('clearSql');
if (clearBtn) {
clearBtn.addEventListener('click', function() {
document.getElementById('sqlTextarea').value = '';
});
}
//生成sql
// 初始化时绑定生成SQL按钮事件
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('generateSqlBtn').addEventListener('click', generateSql);
});
// 生成SQL逻辑
function generateSql() {
const prompt = document.getElementById('promptInput').value.trim();
if (!prompt) {
alert('请输入生成SQL的提示');
return;
}
if (selectedTables.size === 0) {
alert('请选择至少一个表');
return;
}
// 收集选中表的结构信息
const selectedTablesInfo = {};
selectedTables.forEach(table => {
selectedTablesInfo[table] = tableStructures[table] || [];
});
fetch('/api/db/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: prompt,
selectedTablesInfo: selectedTablesInfo
})
})
.then(res => {
if (!res.ok) throw new Error('生成SQL失败');
return res.json();
})
.then(data => {
document.getElementById('sqlTextarea').value = data.sql || '';
})
.catch(err => {
console.error('生成SQL失败:', err);
document.getElementById('sqlTextarea').value = '生成SQL失败,请检查输入或网络';
});
}
// 执行SQL查询
document.querySelector('#executeSql').addEventListener('click', function() {
const sql = document.querySelector('.sql-editor textarea').value;
fetch('/api/db/execute', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({sql: sql})
})
.then(response => response.json())
.then(result => {
console.log('渲染数据', result);
const resultContainer = document.getElementById('queryResultContainer');
resultContainer.innerHTML = '';
if (!result.success) {
resultContainer.innerHTML = `<div class="error">查询失败:${result.error || '未知错误'}</div>`;
return;
}
const data = result.data || [];
const columns = result.columns || [];
if (data.length === 0) {
resultContainer.innerHTML = '<div>无数据</div>';
return;
}
let html = '<table class="result-table"><thead><tr>';
columns.forEach(col => {
html += `<th>${col}</th>`;
});
html += '</tr></thead><tbody>';
data.forEach(row => {
html += '<tr>';
columns.forEach(col => {
html += `<td>${row[col]}</td>`;
});
html += '</tr>';
});
html += '</tbody></table>';
resultContainer.innerHTML = html;
})
.catch(err => {
document.getElementById('queryResultContainer').innerHTML = `<div class="error">请求异常:${err}</div>`;
});
});
// 页面加载完成后初始化数据库列表
loadDataSources();
const generateBtn = document.getElementById('generateSqlBtn');
if (generateBtn) {
generateBtn.addEventListener('click', generateSql);
}
});
// ==========xmind格式转换相关js ==========
function handleUpload() {
const fileInput = document.getElementById('xmindFile');
const templateType = document.getElementById('templateType').value;
if (!fileInput.files[0]) {
alert('请选择XMind文件');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('templateType', templateType);
fetch('/xmindToExcel/importFile', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
return response.json();
})
.then(data => {
// 将成功信息写入到 <textarea> 中
const responseTextarea = document.getElementById('xmindResponse');
responseTextarea.value = '转换成功: ' + JSON.stringify(data);
// 直接提取 excelUrl
if (data && data.excelUrl) {
sessionStorage.setItem('excelUrl', data.excelUrl);
document.getElementById('downloadBtn').disabled = false;
} else if (data && data.message && data.message.excelUrl) {
sessionStorage.setItem('excelUrl', data.message.excelUrl);
document.getElementById('downloadBtn').disabled = false;
} else {
document.getElementById('downloadBtn').disabled = true;
}
})
.catch(error => {
console.error('Error:', error);
alert('上传失败: ' + error.message);
});
}
function handleDownload() {
const excelUrl = sessionStorage.getItem('excelUrl');
if (!excelUrl) {
alert('没有可下载的Excel文件');
return;
}
// 直接用COS url下载
const a = document.createElement('a');
a.href = excelUrl;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// ========== 测试点生成工具相关 ===========
// 预览xmind文件
function previewXmindFile(url) {
// 加时间戳防缓存
const previewUrl = url + (url.includes('?') ? '&' : '?') + '_t=' + Date.now();
const container = document.getElementById('xmindPreviewContainer');
// 让容器宽高100%,高度自适应父容器
container.style.width = '100%';
// 取父容器高度
const parent = container.parentElement;
if (parent) {
const parentHeight = parent.clientHeight;
if (parentHeight > 0) {
container.style.height = parentHeight + 'px';
} else {
container.style.height = '500px'; // 兜底
}
} else {
container.style.height = '500px';
}
fetch(previewUrl)
.then(res => res.arrayBuffer())
.then(file => {
container.innerHTML = '';
const viewer = new window.XMindEmbedViewer({
el: '#xmindPreviewContainer',
style: { width: '100%', height: '100%' }
});
setTimeout(() => {
const inner = container.querySelector('div');
if (inner) {
inner.style.width = '100%';
inner.style.height = '100%';
}
}, 100);
viewer.load(file);
})
.catch(err => {
container.innerHTML = '预览失败:' + err + '<br>请确认文件已生成且为有效的xmind文件。';
});
}
// 判断文件类型
function getFileType(fileName) {
const extension = fileName.toLowerCase().split('.').pop();
if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(extension)) {
return 'image';
} else if (['doc', 'docx'].includes(extension)) {
return 'document';
} else {
return 'unknown';
}
}
// 文件上传
function handleTestPointUpload(requirement) {
const fileInput = document.getElementById('testPointFile');
if (!fileInput.files[0]) {
alert('请选择需求文档文件');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
if (requirement) {
formData.append('requirement', requirement);
}
fetch('/testpoint/generate', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
const resultDiv = document.getElementById('testPointResult');
const msgP = document.getElementById('testPointMsg');
const downloadBtn = document.getElementById('downloadTestPointBtn');
if (data.success) {
msgP.textContent = '测试点生成成功!';
downloadBtn.disabled = false;
downloadBtn.dataset.url = data.downloadUrl;
// 自动预览xmind
previewXmindFile(data.downloadUrl);
} else {
msgP.textContent = '生成失败:' + (data.error || '未知错误');
downloadBtn.disabled = true;
downloadBtn.dataset.url = '';
document.getElementById('xmindPreviewContainer').innerHTML = '';
}
resultDiv.style.display = 'block';
})
.catch(err => {
alert('请求失败: ' + err);
});
}
//文件下载
function handleTestPointDownload() {
const downloadBtn = document.getElementById('downloadTestPointBtn');
const url = downloadBtn.dataset.url;
if (!url) {
alert('没有可下载的文件');
return;
}
// 提取文件名参数
const fileParam = url.split('?file=')[1];
if (!fileParam) {
alert('下载链接无效');
return;
}
// GET下载,直接跳转/api/download接口
const downloadUrl = '/fileconfig/download?file=' + encodeURIComponent(fileParam);
fetch(downloadUrl)
.then(response => {
if (!response.ok) throw new Error('下载失败');
return response.blob();
})
.then(blob => {
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = fileParam;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
})
.catch(err => {
alert('下载失败,请确认文件已生成且为有效的xmind文件。');
});
}
// 上传文件,返回文件名
function uploadTestFile(callback) {
const fileInput = document.getElementById('testPointFile');
if (!fileInput.files[0]) {
alert('请选择需求文档文件');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
fetch('/fileconfig/fileupload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success && data.fileName) {
callback(data.fileName);
} else {
alert('文件上传失败: ' + (data.error || '未知错误'));
}
})
.catch(err => {
alert('文件上传失败: ' + err);
});
}
// 生成测试点,根据文件类型调用不同的API
function generateTestPoints(fileName, promot, callback) {
window.lastUploadedFileName = fileName; // 记录最近一次上传的文件名
const fileType = getFileType(fileName);
const done = function() { if (typeof callback === 'function') callback(); };
if (fileType === 'image') {
// 图片文件调用imageUpload接口
const fileInput = document.getElementById('testPointFile');
const formData = new FormData();
formData.append('file', fileInput.files[0]);
if (promot) {
formData.append('promot', promot);
}
fetch('/api/imageUpload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
handleTestPointResponse(data);
done();
})
.catch(err => {
alert('请求失败: ' + err);
done();
});
} else if (fileType === 'document') {
// 文档文件调用wordUpload接口
const formData = new FormData();
formData.append('fileName', fileName);
if (promot) {
formData.append('promot', promot);
}
fetch('/api/wordUpload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
handleTestPointResponse(data);
done();
})
.catch(err => {
alert('请求失败: ' + err);
done();
});
} else {
alert('不支持的文件类型,请上传图片(.jpg, .png等)或文档(.doc, .docx)文件');
done();
}
}
// 处理测试点生成响应
function handleTestPointResponse(data) {
const resultDiv = document.getElementById('testPointResult');
const msgP = document.getElementById('testPointMsg');
const downloadBtn = document.getElementById('downloadTestPointBtn');
if (data.success) {
msgP.textContent = '测试点生成成功!';
downloadBtn.disabled = false;
downloadBtn.dataset.url = data.downloadUrl;
// 只在xmindPreviewContainer预览区显示xmind,不切换tab
if (data.downloadUrl) {
previewXmindFile(data.downloadUrl);
}
} else {
msgP.textContent = '生成失败:' + (data.error || '未知错误');
downloadBtn.disabled = true;
downloadBtn.dataset.url = '';
document.getElementById('xmindPreviewContainer').innerHTML = '';
}
resultDiv.style.display = 'block';
}
function setGenerateBtnLoading(loading) {
const genBtn = document.getElementById('generateTestPointBtn');
if (genBtn) {
if (loading) {
genBtn.disabled = true;
genBtn.textContent = '生成中...';
} else {
genBtn.disabled = false;
genBtn.textContent = '生成测试点';
}
}
}
// 生成测试点按钮事件,默认promot
function handleGenerateBtn() {
const defaultPromot = '请根据上传的需求文档自动提取所有功能点并生成详细的测试点,要求结构化、分层级、覆盖全面。';
setGenerateBtnLoading(true);
uploadTestFile(function(fileName) {
generateTestPoints(fileName, defaultPromot, function() {
setGenerateBtnLoading(false);
});
});
}
function setRegenerateBtnLoading(loading) {
const regenBtn = document.getElementById('regenerateBtn');
if (regenBtn) {
if (loading) {
regenBtn.disabled = true;
regenBtn.textContent = '生成中...';
} else {
regenBtn.disabled = false;
regenBtn.textContent = '重新生成';
}
}
}
// 重新生成按钮事件,使用输入框内容
function handleRegenerateBtn() {
const promot = document.getElementById('regenerateInput').value;
if (!promot) {
alert('请输入新的生成要求');
return;
}
// 直接用上次上传的文件名
const lastFileName = window.lastUploadedFileName;
if (!lastFileName) {
alert('请先上传文件并生成一次测试点');
return;
}
setRegenerateBtnLoading(true);
generateTestPoints(lastFileName, promot, function() {
setRegenerateBtnLoading(false);
});
}
document.addEventListener('DOMContentLoaded', function() {
const genBtn = document.getElementById('generateTestPointBtn');
if (genBtn) {
genBtn.addEventListener('click', handleGenerateBtn);
}
const dlBtn = document.getElementById('downloadTestPointBtn');
if (dlBtn) {
dlBtn.addEventListener('click', handleTestPointDownload);
}
// 重新生成按钮
const regenBtn = document.getElementById('regenerateBtn');
if (regenBtn) {
regenBtn.addEventListener('click', handleRegenerateBtn);
}
});
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
width: 80%;
margin: 0 auto;
background-color: #fff;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
header {
background-color: #007bff;
color: #fff;
padding: 10px 0;
text-align: center;
}
nav ul {
list-style-type: none;
padding: 0;
display: flex;
justify-content: center;
}
nav ul li {
margin: 0 15px;
}
nav ul li a {
color: #fff;
text-decoration: none;
}
section {
margin: 20px 0;
}
h2 {
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
margin-bottom: 20px;
}
#dataSourceList li, #tableList li {
cursor: pointer;
padding: 5px;
border-bottom: 1px solid #eee;
}
#dataSourceList li:hover, #tableList li:hover {
background-color: #f5f5f5;
}
#dataSourceList li.active {
background-color: #007bff;
color: white;
}
.mysql-tool, .xmind-converter, .testcase-generator {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
.db-connection, .sql-generator, .sql-editor, .query-result {
background-color: #e9ecef;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
}
.db-tables, .table-structure {
background-color: #e9ecef;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
height: 200px;
overflow-y: auto;
}
textarea {
width: 100%;
height: 150px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: vertical;
}
button {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: #fff;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: 5px;
}
/* 数据库选择和表列表横向布局 */
.db-row {
display: flex;
gap: 24px;
align-items: flex-start;
}
.db-select, .table-list {
background: #e9ecef;
border-radius: 5px;
padding: 15px;
min-width: 180px;
height: 260px;
overflow-y: auto;
}
/* 多表结构横向分列 */
.table-structures {
display: flex;
gap: 16px;
margin-top: 20px;
flex-wrap: wrap;
}
.table-struct-col {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 10px;
min-width: 220px;
max-width: 260px;
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
}
.table-struct-col h4 {
margin: 0 0 8px 0;
font-size: 16px;
color: #007bff;
text-align: center;
}
.table-struct-col table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.table-struct-col th, .table-struct-col td {
border: 1px solid #ddd;
padding: 4px 6px;
text-align: left;
}
.table-struct-col th {
background: #f2f2f2;
}
/* 表多选高亮 */
#tableList li.active {
background-color: #28a745;
color: #fff;
}
#tableList li {
cursor: pointer;
padding: 5px;
border-bottom: 1px solid #eee;
border-radius: 3px;
transition: background 0.2s;
}
#tableList li:hover {
background: #e0f7fa;
}
/* SQL输入区按钮间距 */
.sql-editor button {
margin-right: 10px;
}
后端接口实现见下一篇《记一次测试工具从 V1.0 到 V1.1 的回溯和成果(二)》