index.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>图片搜索测试界面</title>
  7. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
  8. <style>
  9. .preview-image {
  10. max-width: 200px;
  11. max-height: 200px;
  12. margin: 10px;
  13. border: 1px solid #ddd;
  14. border-radius: 4px;
  15. padding: 5px;
  16. }
  17. .preview-container {
  18. margin: 10px 0;
  19. min-height: 100px;
  20. display: flex;
  21. align-items: center;
  22. justify-content: center;
  23. }
  24. .loading {
  25. position: relative;
  26. min-height: 200px;
  27. display: flex;
  28. align-items: center;
  29. justify-content: center;
  30. background-color: rgba(255, 255, 255, 0.8);
  31. }
  32. .loading::after {
  33. content: "搜索中...";
  34. font-size: 16px;
  35. color: #666;
  36. }
  37. .result-card {
  38. border: 1px solid #ddd;
  39. border-radius: 8px;
  40. padding: 10px;
  41. margin: 10px;
  42. width: 250px;
  43. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  44. }
  45. .result-image {
  46. max-width: 230px;
  47. max-height: 230px;
  48. object-fit: contain;
  49. display: block;
  50. margin: 0 auto;
  51. }
  52. .results-container {
  53. display: flex;
  54. flex-wrap: wrap;
  55. gap: 15px;
  56. margin-top: 20px;
  57. justify-content: flex-start;
  58. }
  59. .result-info {
  60. margin-top: 10px;
  61. font-size: 14px;
  62. }
  63. .score-badge {
  64. background-color: #007bff;
  65. color: white;
  66. padding: 2px 6px;
  67. border-radius: 4px;
  68. font-size: 12px;
  69. }
  70. </style>
  71. </head>
  72. <body>
  73. <div class="container mt-5">
  74. <h2 class="mb-4">图片搜索测试界面</h2>
  75. <!-- Token Input -->
  76. <div class="mb-4">
  77. <label for="token" class="form-label">Authorization Token:</label>
  78. <input type="text" class="form-control" id="token" placeholder="输入token">
  79. </div>
  80. <!-- Upload Section -->
  81. <div class="card mb-4">
  82. <div class="card-header">
  83. 上传图片
  84. </div>
  85. <div class="card-body">
  86. <div class="mb-3">
  87. <label for="imageUrl" class="form-label">图片URL</label>
  88. <input type="text" class="form-control" id="imageUrl" placeholder="输入图片URL">
  89. </div>
  90. <div class="mb-3">
  91. <label for="nameCn" class="form-label">中文名称</label>
  92. <input type="text" class="form-control" id="nameCn">
  93. </div>
  94. <div id="uploadPreview"></div>
  95. <button class="btn btn-primary" onclick="uploadImage()">上传</button>
  96. </div>
  97. </div>
  98. <!-- Search Section -->
  99. <div class="card mb-4">
  100. <div class="card-header">
  101. 搜索图片
  102. </div>
  103. <div class="card-body">
  104. <div class="mb-3">
  105. <label for="searchImageUrl" class="form-label">搜索图片URL</label>
  106. <input type="text" class="form-control" id="searchImageUrl" placeholder="输入搜索图片URL">
  107. </div>
  108. <div id="searchPreview" class="preview-container"></div>
  109. <button class="btn btn-primary" onclick="searchImage()" id="searchBtn">搜索</button>
  110. </div>
  111. </div>
  112. <!-- Results Section -->
  113. <div class="card">
  114. <div class="card-header">
  115. 搜索结果
  116. </div>
  117. <div class="card-body">
  118. <div id="searchResults" class="results-container"></div>
  119. </div>
  120. </div>
  121. </div>
  122. <script>
  123. const baseUrl = 'http://47.96.90.34:18081/website';
  124. // 预览URL图片
  125. document.getElementById('imageUrl').addEventListener('change', function(e) {
  126. previewImageUrl(this.value, 'uploadPreview');
  127. });
  128. document.getElementById('searchImageUrl').addEventListener('input', function(e) {
  129. previewImageUrl(this.value, 'searchPreview');
  130. });
  131. // 清理URL,移除可能的重复拼接
  132. function cleanImageUrl(url) {
  133. try {
  134. // 如果URL包含我们的API endpoint,说明可能是重复拼接的
  135. const apiEndpoint = `${baseUrl}/image/fassi/list?url=`;
  136. if (url.includes(apiEndpoint)) {
  137. // 提取实际的图片URL
  138. const startIndex = url.lastIndexOf(apiEndpoint) + apiEndpoint.length;
  139. url = decodeURIComponent(url.substring(startIndex));
  140. }
  141. return url;
  142. } catch (error) {
  143. console.error('URL清理错误:', error);
  144. return url;
  145. }
  146. }
  147. function previewImageUrl(url, previewId) {
  148. if (!url) {
  149. document.getElementById(previewId).innerHTML = '';
  150. return;
  151. }
  152. const cleanedUrl = cleanImageUrl(url);
  153. const previewDiv = document.getElementById(previewId);
  154. previewDiv.innerHTML = `
  155. <img src="${cleanedUrl}"
  156. class="preview-image"
  157. onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiNhYWEiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lm77niYfliqDovb3lpLHotKU8L3RleHQ+PC9zdmc+'"
  158. alt="预览图片">
  159. `;
  160. }
  161. async function uploadImage() {
  162. const imageUrl = document.getElementById('imageUrl').value;
  163. if (!imageUrl) {
  164. alert('请输入图片URL');
  165. return;
  166. }
  167. const token = document.getElementById('token').value;
  168. if (!token) {
  169. alert('请输入token');
  170. return;
  171. }
  172. try {
  173. const response = await fetch(`${baseUrl}/image/create`, {
  174. method: 'POST',
  175. headers: {
  176. 'Authorization': token,
  177. 'Content-Type': 'application/json'
  178. },
  179. body: JSON.stringify({
  180. rawImage: { url: imageUrl },
  181. nameCn: document.getElementById('nameCn').value || 'test'
  182. })
  183. });
  184. const data = await response.json();
  185. if (response.ok) {
  186. alert('上传成功');
  187. } else {
  188. alert('上传失败: ' + JSON.stringify(data));
  189. }
  190. } catch (error) {
  191. alert('上传出错: ' + error.message);
  192. }
  193. }
  194. async function searchImage() {
  195. const imageUrl = document.getElementById('searchImageUrl').value;
  196. if (!imageUrl) {
  197. alert('请输入搜索图片URL');
  198. return;
  199. }
  200. const token = document.getElementById('token').value;
  201. if (!token) {
  202. alert('请输入token');
  203. return;
  204. }
  205. // 禁用搜索按钮并显示加载状态
  206. const searchBtn = document.getElementById('searchBtn');
  207. const resultsDiv = document.getElementById('searchResults');
  208. searchBtn.disabled = true;
  209. resultsDiv.innerHTML = '<div class="loading"></div>';
  210. try {
  211. const cleanedUrl = cleanImageUrl(imageUrl);
  212. const encodedUrl = encodeURIComponent(cleanedUrl);
  213. const response = await fetch(`${baseUrl}/image/fassi/list?url=${encodedUrl}`, {
  214. method: 'GET',
  215. headers: {
  216. 'Authorization': token,
  217. 'Content-Type': 'application/json'
  218. }
  219. });
  220. const data = await response.json();
  221. if (response.ok && data.errorNo === 200) {
  222. displayResults(data.result);
  223. } else {
  224. resultsDiv.innerHTML = `<div class="alert alert-danger" role="alert">
  225. 搜索失败: ${data.errorDesc || JSON.stringify(data)}
  226. </div>`;
  227. }
  228. } catch (error) {
  229. resultsDiv.innerHTML = `<div class="alert alert-danger" role="alert">
  230. 搜索出错: ${error.message}
  231. </div>`;
  232. } finally {
  233. // 恢复搜索按钮状态
  234. searchBtn.disabled = false;
  235. }
  236. }
  237. function displayResults(results) {
  238. const resultsDiv = document.getElementById('searchResults');
  239. resultsDiv.innerHTML = '';
  240. if (!results || results.length === 0) {
  241. resultsDiv.innerHTML = '<p>没有找到匹配的图片</p>';
  242. return;
  243. }
  244. results.forEach(result => {
  245. const imgUrl = result.rawImage?.url;
  246. if (imgUrl) {
  247. const resultCard = document.createElement('div');
  248. resultCard.className = 'result-card';
  249. // 格式化时间
  250. const createTime = new Date(result.createTime);
  251. const formattedTime = createTime.toLocaleString('zh-CN', {
  252. year: 'numeric',
  253. month: '2-digit',
  254. day: '2-digit',
  255. hour: '2-digit',
  256. minute: '2-digit'
  257. });
  258. // 格式化相似度分数
  259. const score = (result.score * 100).toFixed(2);
  260. resultCard.innerHTML = `
  261. <img src="${imgUrl}" class="result-image" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiNhYWEiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lm77niYfliqDovb3lpLHotKU8L3RleHQ+PC9zdmc+'" alt="${result.nameCn || '未命名'}">
  262. <div class="result-info">
  263. <div><strong>${result.nameCn || '未命名'}</strong></div>
  264. <div>相似度: <span class="score-badge">${score}%</span></div>
  265. <div>创建时间: ${formattedTime}</div>
  266. </div>
  267. `;
  268. resultsDiv.appendChild(resultCard);
  269. }
  270. });
  271. }
  272. </script>
  273. </body>
  274. </html>