test-aftercare.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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>售后归档数据连接测试</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  15. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  16. min-height: 100vh;
  17. padding: 20px;
  18. }
  19. .container {
  20. max-width: 1200px;
  21. margin: 0 auto;
  22. }
  23. .header {
  24. background: white;
  25. padding: 30px;
  26. border-radius: 12px;
  27. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  28. margin-bottom: 20px;
  29. }
  30. .header h1 {
  31. color: #667eea;
  32. margin-bottom: 10px;
  33. }
  34. .test-section {
  35. background: white;
  36. padding: 25px;
  37. border-radius: 12px;
  38. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  39. margin-bottom: 20px;
  40. }
  41. .test-section h2 {
  42. color: #333;
  43. margin-bottom: 15px;
  44. padding-bottom: 10px;
  45. border-bottom: 2px solid #667eea;
  46. }
  47. .button-group {
  48. display: flex;
  49. gap: 10px;
  50. margin-bottom: 20px;
  51. flex-wrap: wrap;
  52. }
  53. button {
  54. padding: 12px 24px;
  55. border: none;
  56. border-radius: 8px;
  57. background: #667eea;
  58. color: white;
  59. font-size: 14px;
  60. font-weight: 600;
  61. cursor: pointer;
  62. transition: all 0.3s;
  63. }
  64. button:hover {
  65. background: #5568d3;
  66. transform: translateY(-2px);
  67. box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
  68. }
  69. button:disabled {
  70. background: #ccc;
  71. cursor: not-allowed;
  72. transform: none;
  73. }
  74. .log {
  75. background: #f8f9fa;
  76. border: 1px solid #dee2e6;
  77. border-radius: 8px;
  78. padding: 15px;
  79. font-family: 'Courier New', monospace;
  80. font-size: 13px;
  81. line-height: 1.6;
  82. max-height: 400px;
  83. overflow-y: auto;
  84. white-space: pre-wrap;
  85. word-wrap: break-word;
  86. }
  87. .log-entry {
  88. margin-bottom: 5px;
  89. }
  90. .log-success {
  91. color: #28a745;
  92. }
  93. .log-error {
  94. color: #dc3545;
  95. }
  96. .log-info {
  97. color: #17a2b8;
  98. }
  99. .log-warning {
  100. color: #ffc107;
  101. }
  102. .stats {
  103. display: grid;
  104. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  105. gap: 15px;
  106. margin: 20px 0;
  107. }
  108. .stat-card {
  109. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  110. color: white;
  111. padding: 20px;
  112. border-radius: 8px;
  113. text-align: center;
  114. }
  115. .stat-card h3 {
  116. font-size: 14px;
  117. margin-bottom: 10px;
  118. opacity: 0.9;
  119. }
  120. .stat-card .value {
  121. font-size: 32px;
  122. font-weight: bold;
  123. }
  124. .file-upload {
  125. border: 2px dashed #667eea;
  126. border-radius: 8px;
  127. padding: 30px;
  128. text-align: center;
  129. cursor: pointer;
  130. transition: all 0.3s;
  131. margin: 20px 0;
  132. }
  133. .file-upload:hover {
  134. background: #f8f9fa;
  135. border-color: #5568d3;
  136. }
  137. .file-upload input {
  138. display: none;
  139. }
  140. </style>
  141. </head>
  142. <body>
  143. <div class="container">
  144. <div class="header">
  145. <h1>🔧 售后归档数据连接测试</h1>
  146. <p>测试项目ID: <strong>yjVLy8KxyG</strong></p>
  147. <p>测试公司ID: <strong>cDL6R1hgSi</strong></p>
  148. </div>
  149. <!-- 1. ProjectPayment测试 -->
  150. <div class="test-section">
  151. <h2>💰 ProjectPayment表测试</h2>
  152. <div class="button-group">
  153. <button onclick="testProjectPayment()">查询付款记录</button>
  154. <button onclick="createTestPayment()">创建测试数据</button>
  155. <button onclick="clearPayments()">清空测试数据</button>
  156. </div>
  157. <div class="stats" id="payment-stats"></div>
  158. <div class="log" id="payment-log"></div>
  159. </div>
  160. <!-- 2. 文件上传测试 -->
  161. <div class="test-section">
  162. <h2>📤 文件上传测试(NovaStorage)</h2>
  163. <div class="file-upload" onclick="document.getElementById('fileInput').click()">
  164. <input type="file" id="fileInput" accept="image/*" onchange="uploadFile(event)">
  165. <p>📁 点击选择图片文件</p>
  166. <p style="font-size: 12px; color: #666; margin-top: 10px;">支持 JPG, PNG, GIF等格式</p>
  167. </div>
  168. <div class="log" id="upload-log"></div>
  169. </div>
  170. <!-- 3. AI识别测试 -->
  171. <div class="test-section">
  172. <h2>🤖 AI凭证识别测试</h2>
  173. <div class="button-group">
  174. <button onclick="testAIRecognition()">测试AI识别</button>
  175. </div>
  176. <div class="log" id="ai-log"></div>
  177. </div>
  178. <!-- 4. 完整流程测试 -->
  179. <div class="test-section">
  180. <h2>🔄 完整流程测试</h2>
  181. <div class="button-group">
  182. <button onclick="testFullFlow()">测试完整流程</button>
  183. <button onclick="clearAllLogs()">清空日志</button>
  184. </div>
  185. <div class="log" id="flow-log"></div>
  186. </div>
  187. </div>
  188. <script type="module">
  189. import { FmodeParse } from 'https://unpkg.com/fmode-ng@latest/dist/parse/index.mjs';
  190. import { NovaStorage } from 'https://unpkg.com/fmode-ng@latest/dist/core/index.mjs';
  191. import { completionJSON } from 'https://unpkg.com/fmode-ng@latest/dist/core/agent/chat/completion.mjs';
  192. const Parse = FmodeParse.with('nova');
  193. const PROJECT_ID = 'yjVLy8KxyG';
  194. const COMPANY_ID = 'cDL6R1hgSi';
  195. // 日志函数
  196. window.log = function(elementId, message, type = 'info') {
  197. const logElement = document.getElementById(elementId);
  198. const timestamp = new Date().toLocaleTimeString();
  199. const className = `log-${type}`;
  200. const icon = {
  201. success: '✅',
  202. error: '❌',
  203. info: 'ℹ️',
  204. warning: '⚠️'
  205. }[type] || '';
  206. const entry = document.createElement('div');
  207. entry.className = `log-entry ${className}`;
  208. entry.textContent = `[${timestamp}] ${icon} ${message}`;
  209. logElement.appendChild(entry);
  210. logElement.scrollTop = logElement.scrollHeight;
  211. };
  212. // 清空日志
  213. window.clearAllLogs = function() {
  214. const logs = document.querySelectorAll('.log');
  215. logs.forEach(log => log.innerHTML = '');
  216. };
  217. // 更新统计卡片
  218. function updateStats(stats) {
  219. const statsElement = document.getElementById('payment-stats');
  220. statsElement.innerHTML = `
  221. <div class="stat-card">
  222. <h3>总金额</h3>
  223. <div class="value">¥${stats.totalAmount || 0}</div>
  224. </div>
  225. <div class="stat-card">
  226. <h3>已支付</h3>
  227. <div class="value">¥${stats.paidAmount || 0}</div>
  228. </div>
  229. <div class="stat-card">
  230. <h3>待支付</h3>
  231. <div class="value">¥${stats.remainingAmount || 0}</div>
  232. </div>
  233. <div class="stat-card">
  234. <h3>尾款</h3>
  235. <div class="value">¥${stats.finalAmount || 0}</div>
  236. </div>
  237. `;
  238. }
  239. // 1. 测试ProjectPayment查询
  240. window.testProjectPayment = async function() {
  241. log('payment-log', '开始查询ProjectPayment表...', 'info');
  242. try {
  243. const query = new Parse.Query('ProjectPayment');
  244. query.equalTo('project', {
  245. __type: 'Pointer',
  246. className: 'Project',
  247. objectId: PROJECT_ID
  248. });
  249. query.include('voucherFile', 'product', 'paidBy');
  250. query.descending('createdAt');
  251. const payments = await query.find();
  252. log('payment-log', `找到 ${payments.length} 条付款记录`, 'success');
  253. let totalAmount = 0;
  254. let paidAmount = 0;
  255. let finalAmount = 0;
  256. for (const payment of payments) {
  257. const amount = payment.get('amount') || 0;
  258. const type = payment.get('type');
  259. const status = payment.get('status');
  260. log('payment-log', ` ${type}: ¥${amount} (${status})`, 'info');
  261. totalAmount += amount;
  262. if (status === 'paid') {
  263. paidAmount += amount;
  264. }
  265. if (type === 'final') {
  266. finalAmount += amount;
  267. }
  268. }
  269. const stats = {
  270. totalAmount,
  271. paidAmount,
  272. remainingAmount: totalAmount - paidAmount,
  273. finalAmount
  274. };
  275. updateStats(stats);
  276. log('payment-log', `统计完成: 总${totalAmount}, 已付${paidAmount}, 待付${totalAmount - paidAmount}`, 'success');
  277. if (payments.length === 0) {
  278. log('payment-log', '⚠️ 没有找到付款记录,请创建测试数据', 'warning');
  279. }
  280. } catch (error) {
  281. log('payment-log', `查询失败: ${error.message}`, 'error');
  282. console.error(error);
  283. }
  284. };
  285. // 2. 创建测试付款数据
  286. window.createTestPayment = async function() {
  287. log('payment-log', '开始创建测试数据...', 'info');
  288. try {
  289. // 创建预付款
  290. const advance = new Parse.Object('ProjectPayment');
  291. advance.set('project', { __type: 'Pointer', className: 'Project', objectId: PROJECT_ID });
  292. advance.set('company', { __type: 'Pointer', className: 'Company', objectId: COMPANY_ID });
  293. advance.set('type', 'advance');
  294. advance.set('stage', 'order');
  295. advance.set('amount', 40000);
  296. advance.set('method', 'bank_transfer');
  297. advance.set('status', 'paid');
  298. advance.set('paymentDate', new Date());
  299. advance.set('currency', 'CNY');
  300. advance.set('description', '项目预付款');
  301. await advance.save();
  302. log('payment-log', '✅ 创建预付款: ¥40000', 'success');
  303. // 创建尾款
  304. const final = new Parse.Object('ProjectPayment');
  305. final.set('project', { __type: 'Pointer', className: 'Project', objectId: PROJECT_ID });
  306. final.set('company', { __type: 'Pointer', className: 'Company', objectId: COMPANY_ID });
  307. final.set('type', 'final');
  308. final.set('stage', 'aftercare');
  309. final.set('amount', 80000);
  310. final.set('method', 'bank_transfer');
  311. final.set('status', 'pending');
  312. final.set('currency', 'CNY');
  313. final.set('description', '项目尾款');
  314. const dueDate = new Date();
  315. dueDate.setDate(dueDate.getDate() + 30);
  316. final.set('dueDate', dueDate);
  317. await final.save();
  318. log('payment-log', '✅ 创建尾款: ¥80000 (待支付)', 'success');
  319. log('payment-log', '✅ 测试数据创建成功!', 'success');
  320. // 重新查询
  321. await testProjectPayment();
  322. } catch (error) {
  323. log('payment-log', `创建失败: ${error.message}`, 'error');
  324. console.error(error);
  325. }
  326. };
  327. // 3. 清空测试数据
  328. window.clearPayments = async function() {
  329. log('payment-log', '开始清空测试数据...', 'info');
  330. try {
  331. const query = new Parse.Query('ProjectPayment');
  332. query.equalTo('project', {
  333. __type: 'Pointer',
  334. className: 'Project',
  335. objectId: PROJECT_ID
  336. });
  337. const payments = await query.find();
  338. log('payment-log', `找到 ${payments.length} 条记录,正在删除...`, 'info');
  339. for (const payment of payments) {
  340. await payment.destroy();
  341. }
  342. log('payment-log', '✅ 清空完成', 'success');
  343. updateStats({ totalAmount: 0, paidAmount: 0, remainingAmount: 0, finalAmount: 0 });
  344. } catch (error) {
  345. log('payment-log', `清空失败: ${error.message}`, 'error');
  346. }
  347. };
  348. // 4. 测试文件上传
  349. window.uploadFile = async function(event) {
  350. const file = event.target.files[0];
  351. if (!file) return;
  352. log('upload-log', `准备上传文件: ${file.name}`, 'info');
  353. try {
  354. // 初始化存储
  355. const storage = await NovaStorage.withCid(COMPANY_ID);
  356. log('upload-log', '✅ NovaStorage初始化成功', 'success');
  357. // 上传文件
  358. log('upload-log', '正在上传...', 'info');
  359. const result = await storage.upload(file, {
  360. prefixKey: `project/${PROJECT_ID}/test/`,
  361. onProgress: (progress) => {
  362. const percent = progress?.total?.percent || 0;
  363. log('upload-log', `上传进度: ${percent.toFixed(1)}%`, 'info');
  364. }
  365. });
  366. log('upload-log', `✅ 上传成功!`, 'success');
  367. log('upload-log', `文件名: ${result.name}`, 'info');
  368. log('upload-log', `大小: ${result.size} bytes`, 'info');
  369. log('upload-log', `URL: ${result.url}`, 'info');
  370. return result;
  371. } catch (error) {
  372. log('upload-log', `❌ 上传失败: ${error.message}`, 'error');
  373. console.error(error);
  374. }
  375. };
  376. // 5. 测试AI识别
  377. window.testAIRecognition = async function() {
  378. log('ai-log', '开始测试AI识别...', 'info');
  379. log('ai-log', '⚠️ 需要先上传图片才能测试AI识别', 'warning');
  380. log('ai-log', '请使用上方的文件上传功能', 'info');
  381. };
  382. // 6. 测试完整流程
  383. window.testFullFlow = async function() {
  384. log('flow-log', '========== 开始完整流程测试 ==========', 'info');
  385. try {
  386. // Step 1: 查询项目
  387. log('flow-log', 'Step 1: 查询项目信息...', 'info');
  388. const projectQuery = new Parse.Query('Project');
  389. const project = await projectQuery.get(PROJECT_ID);
  390. log('flow-log', `✅ 找到项目: ${project.get('title')}`, 'success');
  391. // Step 2: 查询产品
  392. log('flow-log', 'Step 2: 查询产品列表...', 'info');
  393. const productQuery = new Parse.Query('Product');
  394. productQuery.equalTo('project', project.toPointer());
  395. const products = await productQuery.find();
  396. log('flow-log', `✅ 找到 ${products.length} 个产品`, 'success');
  397. // Step 3: 查询付款记录
  398. log('flow-log', 'Step 3: 查询付款记录...', 'info');
  399. const paymentQuery = new Parse.Query('ProjectPayment');
  400. paymentQuery.equalTo('project', project.toPointer());
  401. const payments = await paymentQuery.find();
  402. log('flow-log', `✅ 找到 ${payments.length} 条付款记录`, 'success');
  403. // Step 4: 查询评价记录
  404. log('flow-log', 'Step 4: 查询评价记录...', 'info');
  405. const feedbackQuery = new Parse.Query('ProjectFeedback');
  406. feedbackQuery.equalTo('project', project.toPointer());
  407. const feedbacks = await feedbackQuery.find();
  408. log('flow-log', `✅ 找到 ${feedbacks.length} 条评价记录`, 'success');
  409. // Step 5: 检查归档状态
  410. log('flow-log', 'Step 5: 检查归档状态...', 'info');
  411. const data = project.get('data') || {};
  412. log('flow-log', `归档状态: ${data.archiveStatus ? '已归档' : '未归档'}`, 'info');
  413. log('flow-log', `复盘数据: ${data.retrospective ? '已生成' : '未生成'}`, 'info');
  414. log('flow-log', '========== 完整流程测试完成 ==========', 'success');
  415. } catch (error) {
  416. log('flow-log', `流程测试失败: ${error.message}`, 'error');
  417. console.error(error);
  418. }
  419. };
  420. // 页面加载时自动测试
  421. window.addEventListener('load', () => {
  422. log('payment-log', '页面加载完成,准备测试...', 'info');
  423. log('upload-log', 'NovaStorage文件上传准备就绪', 'info');
  424. log('ai-log', 'AI识别服务准备就绪', 'info');
  425. log('flow-log', '完整流程测试准备就绪', 'info');
  426. });
  427. </script>
  428. </body>
  429. </html>