inspection.ejs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>质量巡检单生成助手</title>
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <link rel="stylesheet" href="/public/css/bootstrap/bootstrap.min.css">
  8. <link rel="stylesheet" href="/public/css/wap/main.css">
  9. <link rel="stylesheet" href="/public/css/toast.css">
  10. <link rel="stylesheet" href="/public/css/font-awesome/font-awesome.min.css">
  11. <link rel="stylesheet" href="/public/css/toastr.css">
  12. <link rel="shortcut icon" href="/public/images/favicon.ico">
  13. <style>
  14. body,
  15. html {
  16. height: 100%;
  17. overflow: hidden;
  18. margin: 0; /* 去掉默认 margin */
  19. padding: 0;
  20. -webkit-overflow-scrolling: touch;
  21. }
  22. body {
  23. background: #f1f3f5;
  24. font-size: 16px;
  25. padding-bottom: 100px;
  26. }
  27. .chat-box {
  28. height: calc(100vh - 100px); /* 减去头部和底部高度,150px 是示例,需根据实际调整 */
  29. overflow-y: auto;
  30. /*padding: 10px;*/
  31. box-sizing: border-box;
  32. }
  33. .message {
  34. display: flex;
  35. margin-bottom: 12px;
  36. padding: 0 15px;
  37. }
  38. .message.user {
  39. justify-content: flex-end;
  40. }
  41. .message.ai {
  42. justify-content: flex-start;
  43. }
  44. .bubble {
  45. max-width: 95%;
  46. padding: 10px 14px;
  47. border-radius: 18px;
  48. line-height: 1.4;
  49. word-wrap: break-word;
  50. }
  51. .message.user .bubble {
  52. background: #007bff;
  53. color: white;
  54. border-bottom-right-radius: 0;
  55. }
  56. .message.ai .bubble {
  57. background: #dee2e6;
  58. color: #333;
  59. border-bottom-left-radius: 0;
  60. }
  61. .chat-footer {
  62. position: fixed;
  63. bottom: 0;
  64. left: 0;
  65. right: 0;
  66. width: 100%;
  67. background: #fff;
  68. padding: 22px env(safe-area-inset-right) calc(10px + env(safe-area-inset-bottom)) env(safe-area-inset-left);
  69. box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.1);
  70. z-index: 999;
  71. }
  72. .chat-footer .input-group {
  73. bottom: 22px;
  74. }
  75. .chat-footer input.form-control {
  76. border: none;
  77. padding: 10px 12px;
  78. height: 44px;
  79. }
  80. .chat-footer .btn {
  81. border-radius: 0;
  82. }
  83. .form-preview {
  84. background: #fff;
  85. border: 1px solid #dee2e6;
  86. border-radius: 12px;
  87. padding: 15px 20px;
  88. margin: 20px 15px 0 15px;
  89. box-shadow: 0 2px 6px rgba(0,0,0,0.05);
  90. }
  91. .form-preview h6 {
  92. font-weight: bold;
  93. margin-bottom: 16px;
  94. border-bottom: 1px solid #dee2e6;
  95. padding-bottom: 8px;
  96. font-size: 17px;
  97. }
  98. .form-item {
  99. margin-bottom: 12px;
  100. font-size: 15px;
  101. line-height: 1.5;
  102. }
  103. .form-item label {
  104. font-weight: 500;
  105. color: #555;
  106. }
  107. .form-item span {
  108. display: inline-block;
  109. margin-left: 5px;
  110. color: #333;
  111. }
  112. .form-actions {
  113. border-top: 1px solid #dee2e6;
  114. padding-top: 12px;
  115. margin-top: 16px;
  116. display: flex;
  117. justify-content: space-between;
  118. }
  119. /* 1. WebKit 浏览器(Chrome, Safari, Android) */
  120. #chat-box::-webkit-scrollbar {
  121. width: 4px;
  122. }
  123. #chat-box::-webkit-scrollbar-track {
  124. background: transparent;
  125. }
  126. #chat-box::-webkit-scrollbar-thumb {
  127. background-color: rgba(0, 0, 0, 0.3);
  128. border-radius: 4px;
  129. }
  130. /* 2. Firefox */
  131. #chat-box {
  132. scrollbar-width: thin;
  133. scrollbar-color: rgba(0, 0, 0, 0.3) transparent;
  134. }
  135. /* 3. 选项:在小屏上彻底隐藏滚动条(可滑动但看不见) */
  136. @media (max-width: 768px) {
  137. #chat-box::-webkit-scrollbar {
  138. display: none;
  139. }
  140. #chat-box {
  141. -ms-overflow-style: none; /* IE 10+ */
  142. scrollbar-width: none; /* Firefox */
  143. }
  144. }
  145. .input-group {
  146. position: relative;
  147. }
  148. .input-icon {
  149. position: absolute;
  150. right: 75px; /* 根据按钮宽度调整 */
  151. top: 50%;
  152. transform: translateY(-50%);
  153. cursor: pointer;
  154. color: #888;
  155. z-index: 10;
  156. }
  157. #voice-icon.active {
  158. color: #007bff;
  159. }
  160. .xj-title {
  161. position: sticky;
  162. top: 0;
  163. background: #fff;
  164. }
  165. #recording-toast {
  166. position: fixed;
  167. bottom: 80px;
  168. left: 50%;
  169. transform: translateX(-50%);
  170. background-color: rgba(0, 0, 0, 0.85);
  171. color: #fff;
  172. padding: 12px 20px;
  173. border-radius: 24px;
  174. font-size: 14px;
  175. display: flex;
  176. align-items: center;
  177. z-index: 9999;
  178. animation: fadeInUp 0.3s ease-out;
  179. }
  180. #recording-toast .fa {
  181. margin-right: 8px;
  182. }
  183. @keyframes fadeInUp {
  184. from {
  185. opacity: 0;
  186. transform: translate(-50%, 100%);
  187. }
  188. to {
  189. opacity: 1;
  190. transform: translate(-50%, 0);
  191. }
  192. }
  193. </style>
  194. </head>
  195. <body>
  196. <div class="container mb-3 px-0">
  197. <h5 class="text-center py-2 xj-title mb-0">质量巡检单生成助手</h5>
  198. <div id="chat-box" class="chat-box mb-3">
  199. <div id="messages"></div>
  200. <!-- 表单预览卡片 -->
  201. <div id="form-preview" class="form-preview d-none">
  202. <h6><i class="fa fa-clipboard-check mr-2"></i>巡检单信息确认</h6>
  203. <div class="form-item">
  204. <label>检查项目:</label>
  205. <span id="field-check_item">—</span>
  206. </div>
  207. <div class="form-item">
  208. <label>现场检查情况:</label>
  209. <span id="field-check_situation">—</span>
  210. </div>
  211. <div class="form-item">
  212. <label>处理要求及措施:</label>
  213. <span id="field-action">—</span>
  214. </div>
  215. <div class="form-item">
  216. <label>检查日期:</label>
  217. <span id="field-date">—</span>
  218. </div>
  219. <div class="form-item">
  220. <label>质检员:</label>
  221. <span id="field-inspector">—</span>
  222. </div>
  223. <div class="form-actions">
  224. <button id="modify-btn" class="btn btn-outline-secondary btn-sm">
  225. <i class="fa fa-edit"></i> 修改
  226. </button>
  227. <button id="confirm-btn" class="btn btn-success btn-sm">
  228. <i class="fa fa-check-circle"></i> 确认生成
  229. </button>
  230. </div>
  231. </div>
  232. </div>
  233. <div class="chat-footer">
  234. <div class="input-group">
  235. <input type="text" id="user-input" class="form-control" placeholder="请输入...">
  236. <div class="input-icon">
  237. <i id="voice-icon" class="fa fa-microphone"></i>
  238. </div>
  239. <div class="input-group-append">
  240. <button class="btn btn-primary btn-send" id="send-btn">发送</button>
  241. </div>
  242. </div>
  243. </div>
  244. </div>
  245. <div id="recording-toast" style="display: none;">
  246. <div class="toast-inner">
  247. <i class="fa fa-microphone"></i>
  248. <span>正在录音,请讲话...</span>
  249. </div>
  250. </div>
  251. <script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
  252. <script src="/public/js/popper/popper.min.js"></script>
  253. <script src="/public/js/bootstrap/bootstrap.min.js"></script>
  254. <script src="/public/js/toastr.min.js"></script>
  255. <script src="/public/js/cookies.js"></script>
  256. <script src="/public/js/wap/global.js"></script>
  257. <script>
  258. const csrf = '<%= ctx.csrf %>';
  259. </script>
  260. <script>
  261. const $chatBox = $('#chat-box');
  262. const $formPreview = $('#form-preview');
  263. let conversationId = '';
  264. $(function () {
  265. const appendMessage = (text, sender = 'user', cssClass = '') => {
  266. const formatted = text.replace(/\n/g, '<br>');
  267. const $message = $('<div class="message"></div>').addClass(sender);
  268. if (cssClass) {
  269. $message.addClass(cssClass);
  270. }
  271. const $bubble = $('<div class="bubble"></div>').html(formatted);
  272. $message.append($bubble);
  273. $('#messages').append($message);
  274. $chatBox.scrollTop($chatBox[0].scrollHeight);
  275. };
  276. appendMessage("👋 嗨,我是质量巡检单生成助手,告诉我项目名称可以帮你快速生成巡检单", "ai", 'mt-3');
  277. const updatePreviewForm = (data) => {
  278. $('#field-check_item').text(data.check_item || '—');
  279. $('#field-check_situation').text(data.check_situation || '—');
  280. $('#field-action').text(data.action || '—');
  281. $('#field-date').text(data.date || '—');
  282. $('#field-inspector').text(data.inspector || '—');
  283. $formPreview.removeClass('d-none');
  284. };
  285. $('#send-btn').on('click', () => {
  286. const input = $('#user-input').val().trim();
  287. if (!input) return;
  288. appendMessage(input, 'user');
  289. $('#user-input').val('');
  290. $formPreview.addClass('d-none');
  291. postData('/wap/inspection/ask', { user_input: input, conversationId }, function (result) {
  292. if (result) {
  293. conversationId = result.conversation_id;
  294. if (result.answer_text) {
  295. appendMessage(result.answer_text, 'ai');
  296. }
  297. if (result.answer_json) {
  298. updatePreviewForm(result.answer_json);
  299. $formPreview.removeClass('d-none');
  300. } else {
  301. $formPreview.addClass('d-none');
  302. }
  303. } else {
  304. appendMessage('请求出错,请稍后重试。', 'ai');
  305. }
  306. }, function () {
  307. appendMessage('请求出错,请再输入一次。', 'ai');
  308. });
  309. });
  310. $('#user-input').on('keypress', function(e) {
  311. if (e.which === 13) $('#send-btn').click();
  312. });
  313. $('#confirm-btn').on('click', () => {
  314. alert('✅ 表单已提交!你可以跳转页面或保存到数据库');
  315. // 可跳转页面如 window.location.href = '/form/preview';
  316. });
  317. $('#modify-btn').on('click', () => {
  318. $formPreview.addClass('d-none');
  319. appendMessage('请继续修改你想调整的信息。', 'ai');
  320. });
  321. const originalHeight = window.innerHeight;
  322. $('#user-input').on('focus', function () {
  323. const $input = $(this);
  324. const checkKeyboardOpen = function () {
  325. const newHeight = window.innerHeight;
  326. if (newHeight < originalHeight) {
  327. // 键盘已弹出
  328. setTimeout(function () {
  329. $input[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
  330. }, 100);
  331. $(window).off('resize', checkKeyboardOpen);
  332. }
  333. };
  334. $(window).on('resize', checkKeyboardOpen);
  335. });
  336. const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
  337. recognition.continuous = false;
  338. recognition.lang = 'zh-CN';
  339. recognition.interimResults = false;
  340. let isRecording = false;
  341. let pressTimer = null;
  342. $('#voice-icon').on('touchstart', function (e) {
  343. e.preventDefault();
  344. $(this).addClass('active');
  345. // if (!isRecording) {
  346. // recognition.start();
  347. // $(this).addClass('active');
  348. // $('#recording-toast').fadeIn(); // 👉 显示录音提示
  349. // isRecording = true;
  350. // }
  351. pressTimer = setTimeout(() => {
  352. recognition.start();
  353. isRecording = true;
  354. $('#recording-toast').fadeIn(); // 👉 显示录音提示
  355. }, 300); // 按住 300ms 开始录音,可根据体验调整
  356. });
  357. $('#voice-icon').on('touchend touchcancel', function (e) {
  358. e.preventDefault();
  359. clearTimeout(pressTimer);
  360. if (isRecording) {
  361. recognition.stop();
  362. isRecording = false;
  363. $('#recording-toast').fadeOut(); // 👉 隐藏录音提示
  364. }
  365. $(this).removeClass('active');
  366. });
  367. recognition.onresult = function (event) {
  368. const result = event.results[0][0].transcript.trim();
  369. $('#user-input').val(result);
  370. };
  371. recognition.onerror = function (err) {
  372. console.error('语音识别出错:', err);
  373. $('#voice-icon').removeClass('active');
  374. $('#recording-toast').fadeOut(); // 👉 出错时也隐藏
  375. isRecording = false;
  376. };
  377. recognition.onend = function () {
  378. $('#voice-icon').removeClass('active');
  379. $('#recording-toast').fadeOut(); // 👉 识别结束时隐藏
  380. isRecording = false;
  381. };
  382. if (isWeChat()) {
  383. $('#voice-icon').hide(); // 在微信内隐藏语音按钮
  384. }
  385. });
  386. function isWeChat() {
  387. const ua = navigator.userAgent.toLowerCase();
  388. return /micromessenger/.test(ua);
  389. }
  390. </script>
  391. </body>
  392. </html>