index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. <template>
  2. <div class="interpretation">
  3. <progress-bar :num="processNum" />
  4. <div class="header">
  5. <home-header sub-title="自然资源大模型" />
  6. </div>
  7. <div class="content-box">
  8. <div class="left-panel">
  9. <div class="content-top">
  10. <div class="left">
  11. <a-input :value="fileDetail?.name" style="width: 400px; margin-right: 10px" />
  12. <a-upload
  13. class="upload"
  14. action="/aisKnowledge/infra/file/upload"
  15. @change="handleChange"
  16. :showUploadList="false"
  17. :data="{ type: 'temp' }"
  18. accept=".pdf"
  19. >
  20. <img src="/images/zcjd/upload-icon.png" />上传本地文件
  21. </a-upload>
  22. </div>
  23. <div class="right" v-if="false">
  24. <span @click="downloadFile"><img src="/images/zcjd/download-icon.png" />下载</span>
  25. <span><img src="/images/zcjd/star-icon.png" />收藏</span>
  26. </div>
  27. </div>
  28. <div class="content-detail">
  29. <div class="file-preview">
  30. <!-- <img :src="fileDetail?.content" alt="" v-if="fileDetail?.content" /> -->
  31. <!-- <div class="spin" v-if="loading"><a-spin></a-spin></div> -->
  32. <PDFViewer :src="fileUrl" ref="ifRef"></PDFViewer>
  33. </div>
  34. </div>
  35. </div>
  36. <div class="affix_box" :offsetTop="20">
  37. <div class="right-panel">
  38. <div class="ai-interpretation">
  39. <div class="check-type">
  40. <div class="check">
  41. <a-radio-group v-model:value="checkType" button-style="solid">
  42. <a-radio-button value="question">AI解读</a-radio-button>
  43. </a-radio-group>
  44. </div>
  45. </div>
  46. <div class="result">
  47. <div class="content" id="scrollArea" v-if="checkType == 'interpretation'">
  48. <a-spin class="spin" v-if="aiLoading"></a-spin>
  49. <template v-else>
  50. <div class="desc">
  51. <div class="chain">
  52. <vue-markdown-it
  53. :source="isChain === '0' ? content : content.substring(0, content.indexOf('###'))"
  54. v-if="content"
  55. :options="{
  56. html: true,
  57. linkify: true
  58. }"
  59. />
  60. </div>
  61. <vue-markdown-it
  62. :source="isChain === '1' ? content.substring(content.indexOf('###')) : ''"
  63. v-if="content"
  64. :options="{
  65. html: true,
  66. linkify: true
  67. }"
  68. />
  69. </div>
  70. </template>
  71. </div>
  72. <div class="question" v-else>
  73. <div class="chat_box" id="chat_box">
  74. <div class="item">
  75. <div class="value-panel">
  76. <div class="name">
  77. 文件内容太多,读起来太累了?可以试着让我来帮您解读。<span
  78. >你可以试着这样问我:</span
  79. >
  80. </div>
  81. <div
  82. class="citem"
  83. v-for="(item, index) in question"
  84. :key="index"
  85. @click="toggleQuestion(index)"
  86. >
  87. <div class="value">{{ item }}</div>
  88. </div>
  89. </div>
  90. </div>
  91. <div class="chat">
  92. <div class="item" v-for="(item, index) in chatDesc" :key="index">
  93. <div class="user-panel">
  94. <div class="name">{{ item.name }}</div>
  95. <img src="/images/zczk/user.png" />
  96. </div>
  97. <div class="ai-panel">
  98. <img src="/images/zczk/icon-ai-title.png" />
  99. <div class="desc" v-if="item.content" id="scrollArea-1">
  100. <div class="chain">
  101. <vue-markdown-it
  102. :source="item.isChain === '0' ? item.content : item.content.substring(0, item.content.indexOf('###'))"
  103. :options="{
  104. html: true,
  105. linkify: true
  106. }"
  107. />
  108. </div>
  109. <vue-markdown-it
  110. :source="item.isChain === '1' ? item.content.substring(item.content.indexOf('###')) : ''"
  111. :options="{
  112. html: true,
  113. linkify: true
  114. }"
  115. />
  116. </div>
  117. <div class="desc animation" v-else>{{ status === 2 ? '网络错误': '正在思考中' }}<span class="dots" v-if="status !== 2"></span></div>
  118. </div>
  119. </div>
  120. </div>
  121. </div>
  122. <div class="input-panel">
  123. <a-textarea
  124. v-model:value="keyword"
  125. placeholder="请输入"
  126. @keydown.enter="keydownEnter"
  127. >
  128. </a-textarea>
  129. <div class="send" @click="send">
  130. 发送<img src="/images/zczk/icon-white-send.png" />
  131. </div>
  132. </div>
  133. </div>
  134. </div>
  135. </div>
  136. </div>
  137. </div>
  138. </div>
  139. </div>
  140. </template>
  141. <script setup>
  142. /**
  143. * @description 解读详情
  144. */
  145. import { ref, onMounted } from 'vue';
  146. import { useRoute } from 'vue-router';
  147. import HomeHeader from '@/views/home/components/HomeHeader.vue';
  148. import PDFViewer from '@/components/pdf/PDFViewerSearch.vue';
  149. import ProgressBar from '@/components/ProgressBar.vue';
  150. import api from '@/utils/policy-api';
  151. import { getNumAll } from '@/utils/common';
  152. import { message } from 'ant-design-vue';
  153. import { fetchEventSource } from '@microsoft/fetch-event-source';
  154. import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
  155. const route = useRoute();
  156. const chatDesc = ref([]);
  157. const keyword = ref('');
  158. const ctr = new AbortController();
  159. const fileDetail = ref(null);
  160. const fileUrl = ref('');
  161. const aiLoading = ref(false);
  162. const queryText = ref('分别给出该文章的全文综述和要点总结');
  163. const content = ref('');
  164. const loading = ref(false);
  165. const ifRef = ref(null);
  166. const docs = ref([]);
  167. const question = [
  168. '帮我总结一下核心内容?',
  169. '有没有类似的政策文件?',
  170. '文件中提到了哪些信息化内容?'
  171. ];
  172. const checkType = ref('question');
  173. onMounted(async () => {
  174. const id = route.query.id;
  175. const url = route.query.url;
  176. //政策跳转 请求文件
  177. if (id) {
  178. getFileByID(id);
  179. }
  180. //政策解读 请求文件
  181. if (url) {
  182. fileUrl.value = url;
  183. var name = url.substring(url.lastIndexOf('/') + 1);
  184. var file = await fetchPdfFileStream(fileUrl.value, name);
  185. if (file) {
  186. setTimeout(() => {
  187. fileDetail.value = file;
  188. query();
  189. }, 100);
  190. }
  191. }
  192. });
  193. const getFileByID = (id) => {
  194. loading.value = true;
  195. var url = '/policy/query/' + id;
  196. api.get(url, {}, this, false).then(async (res) => {
  197. if (res && res.code == 200) {
  198. if (res.data.item) {
  199. var fileName = res.data.file_name;
  200. fileName = res.data.file_fullname;
  201. fileUrl.value = '/knowledge/file/' + fileName;
  202. loading.value = false;
  203. var file = await fetchPdfFileStream(fileUrl.value, fileName);
  204. if (file) {
  205. setTimeout(() => {
  206. fileDetail.value = file;
  207. query();
  208. }, 100);
  209. }
  210. }
  211. }
  212. });
  213. };
  214. import axios from 'axios';
  215. //在线地址转文件
  216. const fetchPdfFileStream = (pdfUrl, name) => {
  217. return new Promise((resolve, reject) => {
  218. var type = 'application/pdf';
  219. if (name.indexOf('pdf') > -1) {
  220. type = 'application/pdf';
  221. } else {
  222. type = 'application/docx';
  223. }
  224. axios
  225. .get(pdfUrl, { responseType: 'blob' })
  226. .then((response) => {
  227. const blob = new Blob([response.data], { type: type });
  228. const file = new File([blob], name, { type: type });
  229. resolve(file);
  230. // 接下来可以使用URL.createObjectURL(blob)创建一个可以用于<iframe>的URL
  231. })
  232. .catch((error) => {
  233. console.error('Error loading PDF:', error)
  234. reject(new Error('网络请求失败,请检查网络连接')); // 处理网络错误
  235. });
  236. });
  237. };
  238. const send = () => {
  239. if (!keyword.value) {
  240. message.info('请输入!');
  241. return;
  242. }
  243. queryText.value = keyword.value;
  244. keyword.value = '';
  245. var item = { name: queryText.value, content: '', isChain: '0' };
  246. chatDesc.value.push(item);
  247. query();
  248. scrollToBottom('chat_box')
  249. };
  250. const toggleQuestion = (index) => {
  251. keyword.value = question[index];
  252. };
  253. const keydownEnter = () => {
  254. send();
  255. };
  256. const processNum = ref(0)
  257. //文件上传
  258. const handleChange = (info) => {
  259. loading.value = true;
  260. fileDetail.value = null;
  261. const status = info.file.status;
  262. processNum.value = 1
  263. if (status === 'done') {
  264. processNum.value = 100;
  265. fileDetail.value = info.file.originFileObj;
  266. fileUrl.value = 'https://ai.zrzyt.zj.gov.cn/aisKnowledge' + info.file.response.data;
  267. chatDesc.value = [];
  268. loading.value = false;
  269. content.value = '';
  270. checkType.value = 'question';
  271. // uploadFile();
  272. } else if (status === 'error') {
  273. message.error(`${info.file.name} file upload failed.`);
  274. }
  275. };
  276. const downloadFile = () => {
  277. let a = document.createElement('a');
  278. var name = fileDetail.value.name;
  279. var type = fileUrl.value.indexOf('temp') > -1 ? 'temp' : '1';
  280. a.href = '/knowledge/policy/download/' + name + '/' + type;
  281. a.click();
  282. };
  283. const uploadFile = () => {
  284. queryText.value = '分别给出该文章的全文综述和要点总结';
  285. query();
  286. };
  287. const scrollToBottom = async (idDom) => {
  288. // 滚动到底部
  289. if (!idDom) return;
  290. await nextTick(() => {
  291. const scrollEle = document.getElementById(idDom);
  292. if (scrollEle != null) {
  293. scrollEle.scrollTo({
  294. top: scrollEle.scrollHeight - scrollEle.clientHeight + 50,
  295. behavior: 'smooth'
  296. });
  297. }
  298. });
  299. };
  300. const goLocation = (content) => {
  301. nextTick(() => {
  302. ifRef.value.goSourceLocation(content, 2);
  303. });
  304. };
  305. const openDocByIndex = (ind) => {
  306. var flag = false;
  307. docs.value.forEach((t) => {
  308. if (t.index == ind) {
  309. flag = true;
  310. goLocation(t.content);
  311. }
  312. });
  313. if (!flag) {
  314. message.info('未找到出处!');
  315. }
  316. };
  317. window.openDocByIndex = openDocByIndex;
  318. const handleDocs = (data) => {
  319. if (data) {
  320. docs.value = data.map((v, i) => {
  321. if (v.indexOf('.pdf') > 0) {
  322. return {
  323. index: v.substring(v.indexOf('[[') + 2, v.indexOf(']]')),
  324. doc: v.substring(v.indexOf('] [') + 3, v.indexOf('.pdf]') + 4),
  325. link: v.substring(v.indexOf('.pdf]') + 6, v.indexOf('.pdf)') + 4),
  326. content: v.substring(v.indexOf('.pdf]') + 6),
  327. showContent: false,
  328. type: 'pdf'
  329. };
  330. } else if (v.indexOf('.txt') > 0) {
  331. return {
  332. index: i++,
  333. doc: v.substring(v.indexOf('] [') + 3, v.indexOf('.txt]') + 4),
  334. link: v.substring(v.indexOf('.txt]') + 6, v.indexOf('.txt)') + 4),
  335. content: v.substring(v.indexOf('.txt)') + 6),
  336. showContent: false,
  337. type: 'txt'
  338. };
  339. }
  340. });
  341. }
  342. };
  343. const timers = ref([]) //缓存定时器
  344. const timerIndex = ref(0)
  345. const status = ref('0'); //0 未请求 1 请求成功 2 请求错误
  346. const isChain = ref(0); //0 未开始思维链 1 已经开始思维链
  347. const query = async () => {
  348. var params = new FormData();
  349. aiLoading.value = true;
  350. params.append('files', fileDetail.value);
  351. params.append('stream', true);
  352. params.append('query', queryText.value);
  353. timers.value = []
  354. timerIndex.value = 0
  355. status.value = 0
  356. isChain.value = '0'
  357. await fetchEventSource(window.AppGlobalConfig.aiServer + '/chat/complete_file_chat', {
  358. method: 'POST',
  359. openWhenHidden: true,
  360. body: params,
  361. signal: ctr.signal,
  362. async onmessage(msg) {
  363. if (msg) {
  364. try {
  365. aiLoading.value = false;
  366. const rData = JSON.parse(msg.data);
  367. if (rData) {
  368. var con = rData.choices[0].delta.content;
  369. if (rData.status == 3) {
  370. timers.value.forEach((timer) => {
  371. clearTimeout(timer);
  372. })
  373. let nums = getNumAll(con);
  374. nums.forEach(num => {
  375. con = con.replace(
  376. `[[${num}]]`,
  377. ''
  378. // `<span onclick="window.openDocByIndex(${num})" class="poi" style=" cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 14px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 4px;background: #fff;color:#435C80;border: 1px solid #BACAE3">${num}</span>`
  379. )
  380. })
  381. if (checkType.value == 'interpretation') {
  382. content.value = con;
  383. scrollToBottom('scrollArea');
  384. } else {
  385. chatDesc.value[chatDesc.value.length - 1].isChain = '1'
  386. chatDesc.value[chatDesc.value.length - 1].content = con + '\n\n #### 以上是我思考的内容!\n\n';
  387. scrollToBottom('chat_box');
  388. }
  389. if (rData.docs) {
  390. handleDocs(rData.docs);
  391. }
  392. } else if (rData.status == 2) {
  393. let timer = null;
  394. if (checkType.value == 'interpretation') {
  395. timer = setTimeout(() => {
  396. content.value += con;
  397. if (content.value.indexOf('###') !== -1) {
  398. isChain.value = '1'
  399. }
  400. scrollToBottom('scrollArea');
  401. clearTimeout(timer)
  402. }, timerIndex.value * 15)
  403. } else {
  404. timer = setTimeout(() => {
  405. chatDesc.value[chatDesc.value.length - 1].content += con;
  406. if (chatDesc.value[chatDesc.value.length - 1].content.indexOf('###') !== -1) {
  407. chatDesc.value[chatDesc.value.length - 1].isChain = '1'
  408. }
  409. scrollToBottom('chat_box');
  410. clearTimeout(timer)
  411. }, timerIndex.value * 15)
  412. }
  413. timers.value.push(timer)
  414. timerIndex.value++;
  415. }
  416. }
  417. } catch (e) {
  418. console.log('===' + e.message);
  419. }
  420. }
  421. },
  422. onclose() {
  423. aiLoading.value = false;
  424. status.value = 1
  425. },
  426. onerror(err) {
  427. aiLoading.value = false;
  428. status.value = 2
  429. throw err;
  430. },
  431. onopen() {}
  432. });
  433. };
  434. </script>
  435. <style scoped lang="scss">
  436. .interpretation {
  437. width: 100%;
  438. height: 100%;
  439. position: relative;
  440. z-index: 1;
  441. background: #f0f4f7;
  442. .content-box {
  443. width: calc(100% - 300px);
  444. height: calc(100% - 75px);
  445. margin: auto;
  446. margin-top: 15px;
  447. overflow-y: auto;
  448. box-shadow: 0px 1px 8px 0px #e4e4e4;
  449. display: flex;
  450. .left-panel {
  451. border-radius: 6px;
  452. padding: 10px 0px 0px 0px;
  453. background-color: #fff;
  454. width: calc(100% - 600px);
  455. }
  456. .affix_box {
  457. position: fixed;
  458. right: 150px;
  459. top: 75px;
  460. }
  461. .right-panel {
  462. width: 580px;
  463. height: calc(100vh - 86px) !important;
  464. border-radius: 6px;
  465. background-color: #fff;
  466. margin-left: 20px;
  467. .ai-interpretation {
  468. width: 100%;
  469. height: 100%;
  470. .check-type {
  471. width: 100%;
  472. display: flex;
  473. align-items: center;
  474. height: 50px;
  475. .check {
  476. height: 50px;
  477. line-height: 50px;
  478. padding-left: 10px;
  479. }
  480. justify-content: space-between;
  481. }
  482. .icon {
  483. width: 30px;
  484. cursor: pointer;
  485. height: 30px;
  486. margin-right: 12px;
  487. background: #e0efff;
  488. display: flex;
  489. justify-content: center;
  490. align-items: center;
  491. border-radius: 5px 5px 5px 5px;
  492. img {
  493. width: 17px;
  494. height: 17px;
  495. }
  496. }
  497. .result {
  498. width: 100%;
  499. height: calc(100% - 50px);
  500. padding: 0px 0px 20px 0px;
  501. .content {
  502. padding: 0px 27px 0px 23px;
  503. height: 100%;
  504. background: transparent;
  505. box-shadow: none;
  506. overflow-y: scroll;
  507. .title {
  508. font-family: PingFang SC;
  509. font-weight: bold;
  510. font-size: 16px;
  511. margin-bottom: 23px;
  512. color: #000;
  513. }
  514. .desc {
  515. font-family: PingFang SC;
  516. font-weight: normal;
  517. font-size: 14px;
  518. //color: #85909e;
  519. line-height: 28px;
  520. margin-bottom: 36px;
  521. >.chain {
  522. color: #666;
  523. border-left: 1px solid #ececec;
  524. padding-left: 15px;
  525. }
  526. }
  527. .spin {
  528. height: 100%;
  529. display: flex;
  530. align-items: center;
  531. justify-content: center;
  532. }
  533. }
  534. .question {
  535. height: 100%;
  536. padding: 0px 0px 6px 0px;
  537. position: relative;
  538. display: flex;
  539. flex-direction: column;
  540. .item {
  541. display: flex;
  542. width: 100%;
  543. background: #fff;
  544. padding: 4px 20px 15px 20px;
  545. img {
  546. width: 40px;
  547. height: 29px;
  548. margin-right: 13px;
  549. }
  550. .value-panel {
  551. flex: 1;
  552. .name {
  553. font-family: PingFang SC, PingFang SC;
  554. font-weight: 400;
  555. font-size: 14px;
  556. color: #2c2c2c;
  557. line-height: 21px;
  558. span {
  559. color: #5a5b5e;
  560. }
  561. }
  562. .citem {
  563. margin-top: 0px;
  564. display: flex;
  565. cursor: pointer;
  566. align-items: center;
  567. padding: 16px 0px 0px 0px;
  568. img {
  569. width: 18px;
  570. height: 18px;
  571. margin-right: 9px;
  572. }
  573. .value {
  574. flex: 1;
  575. font-family: PingFang SC, PingFang SC;
  576. font-weight: 400;
  577. font-size: 14px;
  578. color: #ef7217;
  579. line-height: 21px;
  580. }
  581. }
  582. }
  583. }
  584. .scale-panel {
  585. display: flex;
  586. justify-content: flex-end;
  587. font-family: PingFang SC, PingFang SC;
  588. font-weight: 400;
  589. margin-top: 7px;
  590. margin-bottom: 27px;
  591. cursor: pointer;
  592. width: calc(100% - 30px);
  593. font-size: 13px;
  594. color: #778490;
  595. span {
  596. font-family: PingFang SC, PingFang SC;
  597. font-weight: 500;
  598. font-size: 13px;
  599. color: #3c7bff;
  600. }
  601. }
  602. .chat_box {
  603. flex: 1;
  604. height: 0px;
  605. overflow-y: auto;
  606. }
  607. .chat {
  608. font-family: PingFang SC;
  609. font-weight: 500;
  610. margin: 10px 0px;
  611. line-height: 28px;
  612. font-size: 16px;
  613. color: #333333;
  614. .item {
  615. display: flex;
  616. flex-direction: column;
  617. padding: 0px;
  618. background: transparent;
  619. width: unset;
  620. .user-panel {
  621. display: flex;
  622. margin: 20px 0px;
  623. justify-content: flex-end;
  624. img {
  625. width: 36px;
  626. margin-left: 7px;
  627. height: 36px;
  628. }
  629. .name {
  630. max-width: calc(100% - 43px);
  631. background: #3c7bff;
  632. border-radius: 5px 5px 5px 5px;
  633. padding: 7px 24px 7px 11px;
  634. font-family: PingFang SC, PingFang SC;
  635. font-weight: 500;
  636. font-size: 14px;
  637. color: #ffffff;
  638. line-height: 21px;
  639. }
  640. }
  641. .ai-panel {
  642. display: flex;
  643. img {
  644. width: 32px;
  645. margin-left: 13px;
  646. height: 32px;
  647. }
  648. .desc {
  649. max-width: calc(100% - 95px);
  650. background: #f5f8fc;
  651. border-radius: 6px 6px 6px 6px;
  652. padding: 10px 18px 10px 13px;
  653. font-weight: 500;
  654. overflow-y: auto;
  655. font-size: 14px;
  656. color: #333333;
  657. line-height: 28px;
  658. >.chain {
  659. color: #666;
  660. border-left: 1px solid #ccc;
  661. padding-left: 15px;
  662. }
  663. }
  664. .animation {
  665. @keyframes dot {
  666. 0%,
  667. 20% {
  668. content: '';
  669. }
  670. 40% {
  671. content: '.';
  672. }
  673. 60% {
  674. content: '..';
  675. }
  676. 80%,
  677. 100% {
  678. content: '...';
  679. }
  680. }
  681. .dots::after {
  682. display: inline-block;
  683. animation: dot 1.5s steps(1, end) infinite;
  684. content: '';
  685. }
  686. }
  687. }
  688. }
  689. }
  690. .input-panel {
  691. position: relative;
  692. bottom: 0px;
  693. background: #f5f8fc;
  694. border-radius: 6px;
  695. height: 111px;
  696. padding: 2px;
  697. width: calc(100% - 54px);
  698. margin-left: 25px;
  699. textarea {
  700. border: none;
  701. padding: 16px 22px;
  702. height: 100%;
  703. background: #f5f8fc;
  704. font-weight: 400;
  705. font-size: 16px;
  706. color: #333333;
  707. &:focus-visible {
  708. border: none !important;
  709. outline: none;
  710. box-shadow: none;
  711. }
  712. &:focus {
  713. border: none !important;
  714. outline: none;
  715. box-shadow: none;
  716. }
  717. }
  718. .send {
  719. right: 17px;
  720. bottom: 14px;
  721. cursor: pointer;
  722. width: 79px;
  723. height: 32px;
  724. background: linear-gradient(134deg, #1ba3fb 0%, #0745f6 100%);
  725. border-radius: 16px 16px 16px 16px;
  726. z-index: 2;
  727. position: absolute;
  728. display: flex;
  729. align-items: center;
  730. justify-content: center;
  731. img {
  732. width: 13px;
  733. height: 13px;
  734. margin-left: 6px;
  735. }
  736. font-family: PingFang SC, PingFang SC;
  737. font-weight: 400;
  738. font-size: 14px;
  739. color: #ffffff;
  740. }
  741. }
  742. }
  743. }
  744. }
  745. }
  746. .zm {
  747. width: 640px;
  748. }
  749. }
  750. .content-top {
  751. width: 100%;
  752. height: 80px;
  753. padding: 0 30px;
  754. display: flex;
  755. justify-content: space-between;
  756. align-items: center;
  757. border-bottom: 1px solid #e9e9e9;
  758. .left {
  759. .upload {
  760. font-size: 16px;
  761. color: #2185f2;
  762. cursor: pointer;
  763. img {
  764. margin-right: 5px;
  765. }
  766. }
  767. .ai-btn {
  768. width: 120px;
  769. height: 40px;
  770. background: url('/images/zcjd/ai-btn-icon.png');
  771. background-repeat: no-repeat;
  772. background-size: 100% 100%;
  773. border-radius: 12px;
  774. padding-left: 35px;
  775. margin-left: 20px;
  776. font-size: 18px;
  777. font-weight: 600;
  778. color: #ffffff;
  779. }
  780. }
  781. .right {
  782. height: 80px;
  783. line-height: 80px;
  784. color: #666666;
  785. span {
  786. margin-left: 20px;
  787. cursor: pointer;
  788. }
  789. span:hover {
  790. box-shadow: 1px 1px 1px 1px rgba(140, 140, 140, 0.2);
  791. }
  792. img {
  793. margin-right: 4px;
  794. width: 20px;
  795. height: 20px;
  796. }
  797. }
  798. }
  799. .content-detail {
  800. width: 100%;
  801. min-height: calc(100% - 80px);
  802. display: flex;
  803. .file-preview {
  804. flex: 1;
  805. text-align: center;
  806. .spin {
  807. display: flex;
  808. align-items: center;
  809. height: 100%;
  810. justify-content: center;
  811. }
  812. }
  813. }
  814. ::-webkit-scrollbar {
  815. width: 3px !important;
  816. }
  817. }
  818. </style>