index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <template>
  2. <div class="page-doc">
  3. <div class="header">
  4. <home-header @login="emits('login')" sub-title="知识库管理" />
  5. </div>
  6. <FileDetail
  7. v-if="fileViewerVisabled"
  8. :fileDetail="currentFile"
  9. :fileType="fileType"
  10. :changeFile="showFileDetail"
  11. @close="closeViewerHandle"
  12. />
  13. <div class="my-doc-container">
  14. <div class="left_box">
  15. <div class="type_item" v-if="isFolderEditor" @click="()=>{onMenuClickHandle({type: 'add', 'id': 0})}">
  16. <div class="btn">新建一级类型</div>
  17. </div>
  18. <div class="menu_box">
  19. <menus
  20. mode="inline"
  21. :items="folders"
  22. :editor="isFolderEditor"
  23. @menu-click="onMenuClickHandle" >
  24. </menus>
  25. </div>
  26. <div class="type_item">
  27. <div class="btn" @click="isFolderEditor=!isFolderEditor">{{ isFolderEditor ? '退出编辑': '业务类型管理' }}</div>
  28. </div>
  29. </div>
  30. <div class="content_box">
  31. <div class="page-content">
  32. <div class="content-top">
  33. <div class="left">
  34. <a-button
  35. type="primary"
  36. :icon="h(CloudUploadOutlined)"
  37. @click="uploadFiles(state.selectedRowKeys)"
  38. class="top_btn"
  39. >导入</a-button
  40. >
  41. <a-button
  42. :icon="h(DownloadOutlined)"
  43. @click="downloadFiles(state.selectedRowKeys)"
  44. class="top_btn m-l-15"
  45. >下载</a-button
  46. >
  47. <a-dropdown>
  48. <template #overlay>
  49. <a-menu @click="onChangeBusClass">
  50. <a-menu-item key="1">
  51. </a-menu-item>
  52. </a-menu>
  53. </template>
  54. <a-button class="top_btn m-l-15">
  55. 业务是否分类
  56. <DownOutlined />
  57. </a-button>
  58. </a-dropdown>
  59. <a-dropdown>
  60. <template #overlay>
  61. <a-menu @click="onChangeDocType">
  62. <a-menu-item key="0">pdf</a-menu-item>
  63. <a-menu-item key="1">doc</a-menu-item>
  64. </a-menu>
  65. </template>
  66. <a-button class="top_btn m-l-15">
  67. 文档格式
  68. <DownOutlined />
  69. </a-button>
  70. </a-dropdown>
  71. </div>
  72. <div class="right">
  73. <a-input
  74. v-model:value="searchValue"
  75. placeholder="搜索文档"
  76. style="width: 280px; height: 40px"
  77. @pressEnter="initTableList"
  78. >
  79. <template #suffix>
  80. <SearchOutlined
  81. @click="initTableList"
  82. style=""
  83. class="search-icon"
  84. />
  85. </template>
  86. </a-input>
  87. </div>
  88. </div>
  89. <div class="content-table">
  90. <a-table
  91. :row-selection="{
  92. selectedRowKeys: state.selectedRowKeys,
  93. onChange: onSelectChange,
  94. }"
  95. rowKey="id"
  96. :loading="loading"
  97. :columns="columns"
  98. :data-source="dataSource"
  99. :pagination="false"
  100. >
  101. <template #bodyCell="{ column, record }">
  102. <template v-if="column.dataIndex === 'name'">
  103. <div class="editable-cell">
  104. <div v-if="editableData[record.id] && !record.type">
  105. <a-input v-model:value="editableData[record.id].name" @pressEnter="updateHandle(record.id)" />
  106. </div>
  107. <div v-else class="editable-cell-text-wrapper">
  108. <div class="name-box" @click="showFileDetail(record)">
  109. <MyIcon icon="icon-wenjianleixing-suolvetu-PDFwendang" size="18" />
  110. <span>{{ record.name }}</span>
  111. </div>
  112. </div>
  113. </div>
  114. </template>
  115. <template v-if="column.key === 'createTime'">
  116. <div>
  117. {{ dayjs(record.createTime).format("YYYY-MM-DD HH:mm:ss") }}
  118. </div>
  119. </template>
  120. <template v-else-if="column.key === 'action'">
  121. <a-button
  122. type="text"
  123. :icon="h(DownloadOutlined)"
  124. style="margin-right: 15px"
  125. @click="downloadFile(record)"
  126. >下载</a-button
  127. >
  128. <a-dropdown>
  129. <a class="ant-dropdown-link" @click.prevent>
  130. <span style="font-size: 18px; font-wight: 600">···</span>
  131. </a>
  132. <template #overlay>
  133. <a-menu>
  134. <a-menu-item>
  135. <a-button
  136. type="link"
  137. :icon="h(DeleteOutlined)"
  138. style="color: #000"
  139. @click="onDeleteFileHandle(record.id)"
  140. >刪除</a-button
  141. >
  142. </a-menu-item>
  143. <a-menu-item>
  144. <a-button
  145. type="link"
  146. :icon="h(DeliveredProcedureOutlined)"
  147. style="color: #000"
  148. @click="moveFiles([record.id])"
  149. >移动</a-button
  150. >
  151. </a-menu-item>
  152. <a-menu-item>
  153. <a-button
  154. type="link"
  155. :icon="h(VerticalAlignTopOutlined)"
  156. style="color: #000"
  157. @click="topFileHandle(record)"
  158. >置顶</a-button
  159. >
  160. </a-menu-item>
  161. <a-menu-item>
  162. <a-button
  163. type="link"
  164. :icon="h(FormOutlined)"
  165. style="color: #000"
  166. @click="editFolderName(record.id)"
  167. >编辑</a-button
  168. >
  169. </a-menu-item>
  170. </a-menu>
  171. </template>
  172. </a-dropdown>
  173. </template>
  174. </template>
  175. </a-table>
  176. </div>
  177. <div class="pagination-box">
  178. <a-pagination v-model:current="current1" :showSizeChanger="false" show-quick-jumper :total="total" @change="onPageChange" />
  179. <span>共{{ total }}个文件</span>
  180. </div>
  181. </div>
  182. </div>
  183. </div>
  184. <a-modal v-model:open="openNewFolderModel" title="新建类型" @ok="createFolder">
  185. <a-input
  186. style="margin-top: 15px; margin-bottom: 10px"
  187. v-model:value="folderName"
  188. placeholder="请输入类型名称"
  189. />
  190. </a-modal>
  191. <a-modal v-model:open="editVisabled" title="编辑类型" @ok="updateHandle">
  192. <a-input
  193. style="margin-top: 15px; margin-bottom: 10px"
  194. v-model:value="editableData.name"
  195. placeholder="请输入类型名称"
  196. />
  197. </a-modal>
  198. <a-modal v-model:open="deleteVisiabled" title="提示" @ok="deleteFolders">
  199. <h2>确定删除该目录吗?</h2>
  200. </a-modal>
  201. <a-modal v-model:open="deleteFileVisiabled" title="提示" @ok="deleteFiles">
  202. <h2>确定删除该文件吗?</h2>
  203. </a-modal>
  204. <MoveFileModel
  205. :open="moveFileModel"
  206. :ids="moveFileIds"
  207. :closeModel="closeMoveFileModel"
  208. />
  209. <FileUpload ref="userUploadFileRef" :pid="currentId" @close="closeHandle"/>
  210. </div>
  211. </template>
  212. <script lang="ts" setup>
  213. /**
  214. * @description 我的文档
  215. */
  216. import { cloneDeep } from 'lodash-es';
  217. import { h, ref, reactive, onMounted } from "vue";
  218. import http from "@/utils/http";
  219. import { listToTree } from '@/utils/struct'
  220. import {
  221. DownOutlined,
  222. FormOutlined,
  223. DeleteOutlined,
  224. CloudUploadOutlined,
  225. SearchOutlined,
  226. DownloadOutlined,
  227. FolderAddOutlined,
  228. VerticalAlignTopOutlined,
  229. DeliveredProcedureOutlined,
  230. } from "@ant-design/icons-vue";
  231. import HomeHeader from '@/views/home/components/HomeHeader.vue';
  232. import { message } from "ant-design-vue";
  233. import MyIcon from "@/components/myIcon/index.vue";
  234. import Menus from "./Menus.vue";
  235. import MoveFileModel from "./MoveFiles.vue";
  236. import FileUpload from "./FileUpload.vue";
  237. import FileDetail from "./FileDetail.vue";
  238. import dayjs from "dayjs";
  239. const currentId = ref(-1);
  240. const onChangeBusClass = () => {
  241. }
  242. const onChangeDocType = () => {
  243. }
  244. // 表格列
  245. const columns = [
  246. {
  247. title: "文档名称",
  248. dataIndex: "name",
  249. key: "name",
  250. width: 360
  251. },
  252. {
  253. title: "文档类型",
  254. dataIndex: "type",
  255. key: "type",
  256. },
  257. {
  258. title: "业务类型",
  259. dataIndex: "typeName",
  260. key: "typeName",
  261. },
  262. {
  263. title: "文档大小",
  264. dataIndex: "size",
  265. key: "size",
  266. sorter: {
  267. compare: (a, b) => a.size - b.size,
  268. multiple: 2,
  269. },
  270. },
  271. {
  272. title: "创建时间",
  273. dataIndex: "createTime",
  274. key: "createTime",
  275. align: "center",
  276. },
  277. {
  278. title: "操作",
  279. key: "action",
  280. width: 250,
  281. align: "center",
  282. },
  283. ];
  284. const searchValue = ref("");
  285. const dataSource = ref<any[]>([]);
  286. const pageNo = ref(1)
  287. const total = ref(0)
  288. const loading = ref(false)
  289. const getTableList = () => {
  290. const urlStr = `/ai/knowledge/file/page`
  291. loading.value = true;
  292. const sendData = {
  293. pageNo: pageNo.value,
  294. pageSize: 10,
  295. name: searchValue.value,
  296. parentId: currentId.value === -1 ? '' : currentId.value
  297. }
  298. http.get(urlStr, sendData).then((result) => {
  299. loading.value = false;
  300. const resultData = result.data;
  301. if (resultData['list']) {
  302. dataSource.value = resultData['list'];
  303. total.value = resultData['total']
  304. }
  305. }).catch((err) => {
  306. loading.value = false;
  307. });
  308. };
  309. const initTableList = () => {
  310. pageNo.value = 1;
  311. getTableList();
  312. }
  313. getTableList();
  314. const onPageChange = (page) => {
  315. pageNo.value = page;
  316. getTableList();
  317. }
  318. const state = reactive<{
  319. loading: boolean;
  320. }>({
  321. selectedRowKeys: [], // Check here to configure the default column
  322. loading: false,
  323. });
  324. const onSelectChange = (selectedRowKeys1) => {
  325. state.selectedRowKeys = selectedRowKeys1;
  326. };
  327. const searchHandle = () => {
  328. initTableList()
  329. }
  330. const currentFileId = ref(null)
  331. const deleteFileVisiabled = ref(false)
  332. const deleteFiles = () => {
  333. const urlStr = `/ai/knowledge/file/delete?id=${currentFileId.value}`
  334. http.get(urlStr).then((result) => {
  335. if (result.data) {
  336. message.success("删除成功!");
  337. getTableList();
  338. } else {
  339. message.error("删除失败,请稍后重试!");
  340. }
  341. deleteFileVisiabled.value = false;
  342. });
  343. }
  344. const onDeleteFileHandle = (id) => {
  345. deleteFileVisiabled.value = true
  346. currentFileId.value = id
  347. }
  348. /***
  349. * 左侧文件夹目录功能
  350. * 查询、新增、重命名、删除
  351. */
  352. const isFolderEditor = ref(false)
  353. const folders = ref([])
  354. const getTypeList = () => {
  355. const urlStr = `/ai/knowledge/type/list`
  356. http.get(urlStr, {
  357. name:''
  358. }).then((result) => {
  359. loading.value = false;
  360. if (result.data && result.data.length > 0) {
  361. const lists = [{
  362. "id": -1,
  363. "name": "全部知识库",
  364. "parentId": 0,
  365. "sort": null
  366. }].concat(result.data)
  367. folders.value = listToTree(lists)
  368. console.log(folders.value)
  369. }
  370. })
  371. }
  372. getTypeList();
  373. // 新建文件夹
  374. const folderName = ref("");
  375. const openNewFolderModel = ref(false);
  376. const createFolder = async () => {
  377. const urlStr = '/ai/knowledge/type/create'
  378. const formData = new FormData();
  379. formData.append("name", folderName.value)
  380. formData.append("parentId", currentId.value);
  381. http.post(urlStr, formData).then((result) => {
  382. if (result.data) {
  383. message.success("文件夹新建成功!");
  384. folderName.value = "";
  385. getTypeList();
  386. } else {
  387. message.error("新建文件夹失败,请稍后重试!");
  388. }
  389. openNewFolderModel.value = false;
  390. });
  391. };
  392. //文件夹重命名
  393. const editableData = ref({});
  394. const editVisabled = ref(false)
  395. const updateHandle = (id: string) => {
  396. const urlStr = `/ai/knowledge/type/update`
  397. const sendData = {
  398. ...editableData.value
  399. }
  400. http.post(urlStr, sendData).then((result) => {
  401. if (result.data) {
  402. message.success("重命名成功!");
  403. getTypeList();
  404. }
  405. editVisabled.value = false;
  406. });
  407. };
  408. //文件夹刪除
  409. const deleteVisiabled = ref(false)
  410. const deleteFolders = () => {
  411. const urlStr = `/ai/knowledge/type/delete?id=${currentId.value}`
  412. http.get(urlStr).then((result) => {
  413. if (result.data) {
  414. message.success("删除成功!");
  415. getTypeList();
  416. } else {
  417. message.error("删除失败,请稍后重试!");
  418. }
  419. deleteVisiabled.value = false;
  420. });
  421. }
  422. //文件夹刪除
  423. const moveUpHandle = (id) => {
  424. const urlStr = `/ai/knowledge/type/move-up?id=${id}`
  425. http.get(urlStr).then((result) => {
  426. if (result.data) {
  427. message.success("上移成功!");
  428. getTypeList();
  429. } else {
  430. message.error("上移失败,请稍后重试!");
  431. }
  432. });
  433. }
  434. //文件夹刪除
  435. const moveDownHandle = (id) => {
  436. const urlStr = `/ai/knowledge/type/move-down?id=${id}`
  437. http.get(urlStr).then((result) => {
  438. if (result.data) {
  439. message.success("下移成功!");
  440. getTypeList();
  441. } else {
  442. message.error("下移失败,请稍后重试!");
  443. }
  444. });
  445. }
  446. const onMenuClickHandle = (data) => {
  447. const type = data['type']
  448. const payload = data['payload']
  449. currentId.value = payload['id'] == -1 ? 0 : payload['id']
  450. switch (type) {
  451. case 'query':
  452. searchValue.value = ''
  453. console.log("payload value:", payload)
  454. initTableList();
  455. break;
  456. case 'add':
  457. openNewFolderModel.value = true
  458. break;
  459. case 'edit':
  460. editVisabled.value = true
  461. editableData.value = cloneDeep(payload);
  462. break;
  463. case 'delete':
  464. deleteVisiabled.value = true
  465. break;
  466. case 'move_up':
  467. moveUpHandle(payload['id'])
  468. break;
  469. case 'move_down':
  470. moveDownHandle(payload['id'])
  471. break;
  472. }
  473. };
  474. /**
  475. * 文件
  476. */
  477. // 下载(单文件下载)
  478. const downloadFile = async (data: any) => {
  479. const downloadUrl = `${window.AppGlobalConfig.knowledgeDocUrlProxy}${data.name}`
  480. const response = await fetch(downloadUrl);
  481. const blob = await response.blob();
  482. const url = window.URL.createObjectURL(blob);
  483. const a = document.createElement("a");
  484. a.href = url;
  485. a.download = data.name ?? "download.pdf"; // 强制下载并指定文件名
  486. document.body.appendChild(a);
  487. a.click();
  488. document.body.removeChild(a);
  489. // 释放 Blob URL
  490. window.URL.revokeObjectURL(url);
  491. };
  492. //文件导入
  493. // 上传文档
  494. const userUploadFileRef = ref<any>(null);
  495. const uploadFiles = () => userUploadFileRef.value.showModal();
  496. const closeHandle = () => {
  497. getTableList();
  498. }
  499. // 多文件打包下载
  500. const downloadFiles = async (ids: any[] = []) => {
  501. message.success("正在为你下载文件,请稍等 ...");
  502. const selectedArrs = dataSource.value.filter((item) => {
  503. return ids.includes(item['id']);
  504. })
  505. selectedArrs.forEach((item) => {
  506. downloadFile(item['name'])
  507. })
  508. };
  509. // 置顶
  510. const topFileHandle = async (item) => {
  511. const urlStr = `/ai/knowledge/file/update`
  512. const sendData = {
  513. ...item
  514. }
  515. sendData['sort'] = 0
  516. http.post(urlStr, sendData).then((result) => {
  517. if (result.data) {
  518. message.success("置顶成功!");
  519. getTableList();
  520. } else {
  521. message.error("置顶失败,请稍后重试!");
  522. }
  523. });
  524. };
  525. // 移动:最外层文件禁止多选移动,文件夹类型禁止移动
  526. const moveFileModel = ref(false);
  527. const moveFileIds = ref<any[]>([]);
  528. const moveFiles = (ids: any[] = []) => {
  529. moveFileIds.value = ids;
  530. moveFileModel.value = true;
  531. };
  532. const closeMoveFileModel = () => {
  533. moveFileModel.value = false;
  534. moveFileIds.value = [];
  535. };
  536. // 跳到文件详情页面
  537. const fileViewerVisabled = ref(false)
  538. const fileType = ref('')
  539. const currentFile = ref()
  540. const showFileDetail = (data: any) => {
  541. console.log(data)
  542. fileViewerVisabled.value = true;
  543. data['viewerUrl'] = `${window.AppGlobalConfig.knowledgeDocUrlProxy}${data.name}`
  544. currentFile.value = data;
  545. const name = data.name;
  546. fileType.value = name.slice(name.indexOf(".") + 1)
  547. };
  548. const closeViewerHandle = () => {
  549. fileViewerVisabled.value = false;
  550. }
  551. </script>
  552. <style scoped lang="scss">
  553. @import "./index.scss";
  554. </style>