smartComparison.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. <template>
  2. <div class="smart-comparison">
  3. <div class="header">
  4. <home-header sub-title="自然资源大模型" />
  5. </div>
  6. <div class="content-box">
  7. <div class="top" v-if="false">
  8. <div
  9. v-for="(item, index) in fileList"
  10. :key="item"
  11. class="list-item"
  12. @click="toggleFile(item)"
  13. @mouseenter="handleMouseEnter(index)"
  14. @mouseleave="handleMouseLeave(index)"
  15. >
  16. <div class="file-item">{{ item.f_title }}</div>
  17. <div class="close-icon" v-if="hoveredIndex === index">
  18. <CloseCircleOutlined class="close-icon-font" @click="remove(item)" />
  19. </div>
  20. <div class="vs-font" v-if="index != fileList.length - 1">VS</div>
  21. </div>
  22. </div>
  23. <div class="content">
  24. <div class="left">
  25. <div class="upload-box">
  26. <div class="upload-input">
  27. <a-upload
  28. v-if="fileList1.length === 0"
  29. class="upload"
  30. action="/aisKnowledge/infra/file/upload"
  31. @change="(info)=>handleChange(info, 1)"
  32. :showUploadList="false"
  33. :data="{ type: 'temp' }"
  34. accept=".pdf"
  35. >
  36. <p class="upload_title">点击上传或拖入政策文档</p>
  37. <p class="upload_text">文档单个最大50MB;当前仅支持PDF格式 </p>
  38. </a-upload>
  39. <div v-else class="preview">
  40. <span class="icon"><img src="../../assets/images/pdf_icon.png" /></span>
  41. <div @click="switchPdfSource(1)">
  42. <span class="title">{{ fileList1[0]['fileName'] }}</span>
  43. <span class="size">{{ (fileList1[0]['size'] / 1024).toFixed(2) }}KB</span>
  44. </div>
  45. <span class="close" @click="deleteFileHandle(1)">x</span>
  46. </div>
  47. </div>
  48. <div class="upload-icon">VS</div>
  49. <div class="upload-input">
  50. <a-upload
  51. v-if="fileList2.length === 0"
  52. class="upload"
  53. action="/aisKnowledge/infra/file/upload"
  54. @change="(info)=>handleChange(info, 2)"
  55. :showUploadList="false"
  56. :data="{ type: 'temp' }"
  57. accept=".pdf"
  58. >
  59. <p class="upload_title">点击上传或拖入政策文档</p>
  60. <p class="upload_text">文档单个最大50MB;当前仅支持PDF格式 </p>
  61. </a-upload>
  62. <div v-else class="preview">
  63. <span class="icon"><img src="../../assets/images/pdf_icon.png" /></span>
  64. <div @click="switchPdfSource(2)">
  65. <span class="title">{{ fileList2[0]['fileName'] }}</span>
  66. <span class="size">{{ (fileList2[0]['size'] / 1024).toFixed(2) }}KB</span>
  67. </div>
  68. <span class="close" @click="deleteFileHandle(2)">x</span>
  69. </div>
  70. </div>
  71. </div>
  72. <div class="pdf">
  73. <PDFViewer :src="current.url" ref="ifRef" v-if="current.url"> </PDFViewer>
  74. </div>
  75. </div>
  76. <div class="affix_box">
  77. <div class="right" :class="{ zm: zoomFlag }">
  78. <AiAssistant :data="fileList" />
  79. </div>
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. </template>
  85. <script setup>
  86. /**
  87. * @description 智能对比
  88. */
  89. import HomeHeader from '@/views/home/components/HomeHeader.vue';
  90. import { ref, onMounted, watch, toRefs, nextTick } from 'vue';
  91. import { message } from 'ant-design-vue';
  92. import { CloseCircleOutlined } from '@ant-design/icons-vue';
  93. import PDFViewer from '@/components/pdf/PDFViewerSearch.vue';
  94. import AiAssistant from './components/aiAssistant.vue';
  95. const fileList = ref([]);
  96. import api from '@/utils/policy-api';
  97. import { useUserStore } from '@/stores/user/user';
  98. import PubsubService from '@/utils/PubsubService';
  99. const userStore = useUserStore();
  100. const { user } = userStore;
  101. const current = ref({});
  102. const index = ref(-1);
  103. const ifRef = ref(null);
  104. const zoomFlag = ref(false);
  105. // 控制鼠标移入的索引
  106. const hoveredIndex = ref(null);
  107. // 处理鼠标移入
  108. const handleMouseEnter = (index) => {
  109. hoveredIndex.value = index;
  110. };
  111. onMounted(() => {
  112. // 接收开关事件
  113. PubsubService.subscribe('change-doc', (t) => {
  114. nextTick(() => {
  115. ifRef.value.goSourceLocation(t.value, 2);
  116. });
  117. });
  118. PubsubService.subscribe('change-view', (t) => {
  119. zoomFlag.value = t.value;
  120. });
  121. index.value=-1;
  122. });
  123. const fileList1 = ref([])
  124. const fileList2 = ref([])
  125. const handleChange = (info, type) => {
  126. var name = info.file.name.split('.')[0];
  127. var fileName = info.file.name;
  128. const status = info.file.status;
  129. if (status === 'done') {
  130. message.success(`${info.file.name} 文件上传成功.`);
  131. const id = 'title-' + new Date().getTime();
  132. const url = 'https://ai.zrzyt.zj.gov.cn/aisKnowledge' + info.file.response.data;
  133. var item = {
  134. id: id,
  135. f_title: name,
  136. f_type: 'temp',
  137. file: info.file.originFileObj,
  138. size: info.file.size,
  139. fileName: fileName,
  140. url: url
  141. };
  142. if (type === 1) {
  143. fileList1.value = [item]
  144. }
  145. if (type === 2) {
  146. fileList2.value = [item]
  147. }
  148. fileList.value.push(item)
  149. current.value = item;
  150. if (fileList.value.length > 1) {
  151. // current.value = fileList.value[0];
  152. queryFilesName();
  153. }
  154. } else if (status === 'error') {
  155. message.error(`${info.file.name} file upload failed.`);
  156. }
  157. };
  158. const switchPdfSource = (type) => {
  159. if (type === 1) {
  160. current.value = fileList1.value[0];
  161. }else if (type === 2) {
  162. current.value = fileList2.value[0];
  163. }
  164. }
  165. const deleteFileHandle = (type) => {
  166. let file = null;
  167. if (type === 1) {
  168. file = fileList1.value[0]
  169. fileList1.value = []
  170. }
  171. if (type === 2) {
  172. file = fileList2.value[0]
  173. fileList2.value = []
  174. }
  175. for (let i = 0; i < fileList.value.length; i++){
  176. if(fileList.value[i]['id'] === file['id']) {
  177. fileList.value.splice(i, 1);
  178. break;
  179. }
  180. }
  181. if (fileList.value.length > 0) {
  182. current.value = fileList.value[0]
  183. } else if (fileList.value.length === 0) {
  184. current.value = {}
  185. }
  186. }
  187. const queryFilesName = () => {
  188. if (index.value < fileList.value.length) {
  189. index.value++;
  190. getFileName();
  191. }
  192. };
  193. const getFileName = () => {
  194. var item = fileList.value[index.value];
  195. initFileByUrl(item);
  196. queryFilesName();
  197. };
  198. const initFileByUrl = async (item) => {
  199. var fileName = item.fileName;
  200. var file = await fetchPdfFileStream(item.url, fileName);
  201. item.file = file;
  202. };
  203. const remove = (item) => {
  204. if (item.id == current.value.id) {
  205. current.value = null;
  206. }
  207. fileList.value = fileList.value.filter((citem) => citem.id != item.id);
  208. if (fileList.value && fileList.value.length > 0) {
  209. current.value = JSON.parse(JSON.stringify(fileList.value[0]));
  210. }
  211. };
  212. import axios from 'axios';
  213. //在线地址转文件
  214. const fetchPdfFileStream = (pdfUrl, name) => {
  215. return new Promise((resolve, reject) => {
  216. // const xhr = new XMLHttpRequest();
  217. // xhr.open('GET', pdfUrl);
  218. // xhr.responseType = 'blob';
  219. // var type = name.indexOf('pdf') > -1 ? 'application/pdf' : 'application/docx';
  220. // xhr.onload = function () {
  221. // if (xhr.status === 200) {
  222. // const blob = xhr.response;
  223. // const file = new File([blob], name, { type: type });
  224. // resolve(file); // 成功时,‌解析并返回File对象
  225. // } else {
  226. // resolve(null);
  227. // // reject(new Error('请求失败,状态码:' + xhr.status)); // 如果HTTP状态不是200,‌则拒绝Promise
  228. // }
  229. // };
  230. // xhr.onerror = function () {
  231. // reject(new Error('网络请求失败,请检查网络连接')); // 处理网络错误
  232. // };
  233. // xhr.send();
  234. var type = 'application/pdf';
  235. if (name.indexOf('pdf') > -1) {
  236. type = 'application/pdf';
  237. } else {
  238. type = 'application/docx';
  239. }
  240. axios
  241. .get(pdfUrl, { responseType: 'blob' })
  242. .then((response) => {
  243. const blob = new Blob([response.data], { type: type });
  244. const file = new File([blob], name, { type: type });
  245. resolve(file);
  246. // 接下来可以使用URL.createObjectURL(blob)创建一个可以用于<iframe>的URL
  247. })
  248. .catch((error) => {
  249. console.error('Error loading PDF:', error)
  250. reject(new Error('网络请求失败,请检查网络连接')); // 处理网络错误
  251. });
  252. });
  253. };
  254. // 处理鼠标移出
  255. const handleMouseLeave = () => {
  256. hoveredIndex.value = null;
  257. };
  258. </script>
  259. <style scoped lang="scss">
  260. .smart-comparison {
  261. width: 100%;
  262. height: 100%;
  263. >.content-box {
  264. height: calc(100% - 60px);
  265. display: flex;
  266. justify-content: center;
  267. flex-wrap: wrap;
  268. font-family: Alibaba PuHuiTi 2;
  269. position: relative;
  270. z-index: 1;
  271. .top {
  272. width: 1280px;
  273. height: auto;
  274. margin-top: 20px;
  275. margin-bottom: 20px;
  276. display: flex;
  277. // justify-content: space-between;
  278. justify-content: center;
  279. .list-item {
  280. width: auto;
  281. cursor: pointer;
  282. display: flex;
  283. position: relative;
  284. }
  285. .file-item {
  286. width: 345px;
  287. height: 106px;
  288. background: #ffffff;
  289. box-shadow: 0px 1px 8px 0px #e4e4e4;
  290. border-radius: 10px;
  291. border: 1px solid #3c8afb;
  292. padding: 20px;
  293. line-height: 30px;
  294. }
  295. .vs-font {
  296. width: auto;
  297. height: 106px;
  298. font-weight: bold;
  299. font-size: 24px;
  300. color: #2185f2;
  301. line-height: 106px;
  302. margin: 0px 47px;
  303. }
  304. .close-icon {
  305. position: absolute;
  306. top: 42px;
  307. left: 335px;
  308. .close-icon-font {
  309. color: #fff;
  310. background-color: #2185f2;
  311. border-radius: 50%;
  312. font-size: 20px;
  313. cursor: pointer;
  314. }
  315. }
  316. }
  317. .content {
  318. width: calc(100% - 300px);
  319. height: auto;
  320. display: flex;
  321. margin-top: 20px;
  322. margin-bottom: 20px;
  323. .left {
  324. min-height: calc(100vh - 270px);
  325. background: #ffffff;
  326. box-shadow: 0px 1px 8px 0px #e4e4e4;
  327. border-radius: 10px;
  328. width: calc(100% - 500px);
  329. >.upload-box {
  330. display: flex;
  331. align-items: center;
  332. margin-bottom: 20px;
  333. >.upload-icon {
  334. background: #4F7FFF;
  335. font-size: 26px;
  336. font-weight: bold;
  337. text-align: center;
  338. color: #fff;
  339. width: 60px;
  340. height: 60px;
  341. line-height: 60px;
  342. margin: 0px 20px;
  343. border-radius: 50%;
  344. }
  345. >.upload-input {
  346. background: #F8FCFF;
  347. border-radius: 6px 6px 6px 6px;
  348. border: 1px dashed #4B95E6;
  349. flex: 1;
  350. text-align: center;
  351. height: 82px;
  352. width: calc(50% - 50px);
  353. >.upload {
  354. cursor: pointer;
  355. }
  356. >.preview {
  357. text-align: left;
  358. padding: 15px;
  359. position: relative;
  360. cursor: pointer;
  361. >.icon {
  362. width: 50px;
  363. height: 50px;
  364. display: inline-block;
  365. vertical-align: top;
  366. margin-right: 10px;
  367. >img {
  368. width: 100%;
  369. height: 100%;
  370. }
  371. }
  372. >div {
  373. display: inline-block;
  374. width: calc(100% - 80px);
  375. >span {
  376. display: block;
  377. margin-bottom: 5px;
  378. &.title {
  379. color: #34383C;
  380. font-size: 16px;
  381. overflow: hidden;
  382. text-overflow: ellipsis;
  383. white-space: nowrap;
  384. }
  385. &.size {
  386. color: #A5ADB9;
  387. font-size: 14px;
  388. }
  389. }
  390. }
  391. >.close {
  392. position: absolute;
  393. right: -8px;
  394. top: -8px;
  395. width: 20px;
  396. height: 20px;
  397. font-size: 14px;
  398. text-align: center;
  399. background: #A3BFDD;
  400. border-radius: 50%;
  401. color: #fff;
  402. cursor: pointer;
  403. }
  404. }
  405. .upload_title {
  406. color: #0F0F0F;
  407. text-align: center;
  408. font-size: 18px;
  409. margin-bottom: 0px;
  410. margin-top: 15px;
  411. }
  412. .upload_text {
  413. color: #727272;
  414. text-align: center;
  415. margin-bottom: 15px;
  416. }
  417. }
  418. }
  419. .pdf {
  420. width: 100%;
  421. height: 100%;
  422. }
  423. }
  424. .affix_box {
  425. position: fixed;
  426. right: 150px;
  427. top: 75px;
  428. bottom: 20px;
  429. }
  430. .right {
  431. width: 480px;
  432. margin-left: 20px;
  433. background: #ffffff;
  434. box-shadow: 0px 1px 8px 0px #e4e4e4;
  435. border-radius: 10px;
  436. position: relative;
  437. height: 100%;
  438. .title {
  439. font-weight: 600;
  440. font-size: 20px;
  441. color: #000;
  442. line-height: 50px;
  443. border-bottom: 1px solid #eaeaea;
  444. }
  445. .return-home {
  446. position: absolute;
  447. top: 620px;
  448. left: 0;
  449. height: 40px;
  450. width: 460px;
  451. background-color: #fff;
  452. box-shadow: 0px 1px 8px 0px #e4e4e4;
  453. border-radius: 10px;
  454. display: flex;
  455. justify-content: center;
  456. align-items: center;
  457. cursor: pointer;
  458. span {
  459. margin: 0 5px;
  460. font-size: 16px;
  461. }
  462. }
  463. .return-home:hover {
  464. color: #1677ff;
  465. }
  466. }
  467. .zm {
  468. width: 640px;
  469. }
  470. }
  471. }
  472. }
  473. </style>