inspection.ejs 14 KB

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