index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <template>
  2. <div class="bm-main-box">
  3. <div class="flex items-center">
  4. <div class="bm-main-box-title">文档</div>
  5. </div>
  6. <template v-if="!state.stage.show">
  7. <CzrContent
  8. v-model:tableHead="state.query.head"
  9. @handleReset="onReset"
  10. @handleSearch="onSearch"
  11. >
  12. <template #tableTitle>
  13. <div class="flex gap-2.5">
  14. <CzrButton
  15. type="add"
  16. title="添加文件"
  17. @click="
  18. $router.push({
  19. name: '18e6009c-a72c-4359-864b-e7725fccca69',
  20. params: {
  21. id: ID,
  22. },
  23. })
  24. "
  25. />
  26. <CzrButton
  27. type="add"
  28. title="添加分段"
  29. @click="() => (state.detail.show = true)"
  30. />
  31. <CzrButton title="迁移" icon="move" @click="onKnowledge()" />
  32. <CzrButton title="归档" icon="cloud" />
  33. <CzrButton
  34. type="del"
  35. title="删除"
  36. icon="czr_del"
  37. @click="onDel()"
  38. />
  39. </div>
  40. </template>
  41. <template #buttons>
  42. <div class="flex items-center gap-2.5">
  43. <CzrForm
  44. class="bm-filter"
  45. label-width="0px"
  46. @handleEnter="onSearch"
  47. >
  48. <CzrFormColumn
  49. width="15.63rem"
  50. class="__czr-table-form-column"
  51. :span="24"
  52. label-width="0px"
  53. v-model:param="state.text"
  54. placeholder="按文档名称搜索"
  55. :prefix-icon="Search"
  56. />
  57. </CzrForm>
  58. </div>
  59. </template>
  60. <template #table>
  61. <CzrTable
  62. v-loading="state.query.loading"
  63. :data="state.query.result.data"
  64. :head="state.query.head"
  65. :total="state.query.result.total"
  66. :page="state.query.page.pageNum"
  67. :pageSize="state.query.page.pageSize"
  68. @handlePage="onPage"
  69. v-model:selected="state.query.selected"
  70. >
  71. <template #name-column-value="{ scope }">
  72. <div class="flex justify-center">
  73. <template v-if="scope.row.indexingStatus === 'completed'">
  74. <CzrButton
  75. type="table"
  76. :title="scope.row.name"
  77. @click="onStage(scope.row)"
  78. />
  79. </template>
  80. <template v-else>
  81. {{ scope.row.name }}
  82. </template>
  83. </div>
  84. </template>
  85. <template #caozuo-column-value="{ scope }">
  86. <div class="__czr-table-operations">
  87. <template v-if="scope.row.indexingStatus === 'archived'">
  88. <CzrButton
  89. type="table"
  90. title="撤销归档"
  91. @click="onUnArchive(scope.row)"
  92. />
  93. </template>
  94. <template v-if="scope.row.indexingStatus === 'completed'">
  95. <CzrButton
  96. type="table"
  97. title="归档"
  98. @click="onArchive(scope.row)"
  99. />
  100. </template>
  101. <CzrButton
  102. type="table"
  103. title="重命名"
  104. @click="onRename(scope.row)"
  105. />
  106. <CzrButton
  107. type="table"
  108. title="迁移"
  109. @click="onKnowledge(scope.row)"
  110. />
  111. <CzrButton type="table-del" @click="onDel(scope.row)" />
  112. </div>
  113. </template>
  114. </CzrTable>
  115. </template>
  116. </CzrContent>
  117. <detailCom
  118. v-model:show="state.detail.show"
  119. :transfer="state.detail.transfer"
  120. @refresh="onSearch"
  121. />
  122. <renameCom
  123. v-model:show="state.rename.show"
  124. :transfer="state.rename.transfer"
  125. @refresh="onSearch"
  126. />
  127. <moveSelectCom
  128. v-model:show="state.moveSelect.show"
  129. :transfer="state.moveSelect.transfer"
  130. @refresh="onSearch"
  131. />
  132. </template>
  133. <template v-else>
  134. <stageIndexCom
  135. v-model:show="state.stage.show"
  136. :document="state.stage.document"
  137. />
  138. </template>
  139. </div>
  140. </template>
  141. <script setup lang="ts">
  142. import {
  143. computed,
  144. getCurrentInstance,
  145. inject,
  146. onMounted,
  147. reactive,
  148. ref,
  149. watch,
  150. } from 'vue'
  151. import { Search } from '@element-plus/icons-vue'
  152. import { debounce } from 'lodash'
  153. import { useDialogStore, useDictionaryStore } from '@/stores'
  154. import { ElMessage } from 'element-plus'
  155. import detailCom from './detail.vue'
  156. import renameCom from './rename.vue'
  157. import moveSelectCom from './move-select.vue'
  158. import stageIndexCom from './stage-index.vue'
  159. import {
  160. documentArchive,
  161. documentDocsDelete,
  162. documentGetDocumentsByPage,
  163. documentNoArchive,
  164. } from '@/api/modules/knowledge/document'
  165. const DialogStore = useDialogStore()
  166. const DictionaryStore = useDictionaryStore()
  167. const emit = defineEmits([])
  168. const props = defineProps({
  169. knowledge: <any>{},
  170. })
  171. const { proxy }: any = getCurrentInstance()
  172. const ID = inject('ID')
  173. const state: any = reactive({
  174. text: '',
  175. query: {
  176. init: false,
  177. loading: false,
  178. head: [
  179. { value: 'name', label: '文件名称', show: true, width: 200 },
  180. { value: 'wordCount', label: '字符数', show: true, width: 100 },
  181. {
  182. value: 'indexingStatus',
  183. label: '状态',
  184. show: true,
  185. width: 100,
  186. dictList: computed(() => DictionaryStore.indexingStatusList),
  187. },
  188. { value: 'recallCount', label: '召回次数', show: true, width: 100 },
  189. {
  190. value: 'isUse',
  191. label: '启用/停用',
  192. show: true,
  193. width: 100,
  194. dictList: computed(() => DictionaryStore.documentUseList),
  195. },
  196. { value: 'p1', label: '关联应用(无)', show: true },
  197. {
  198. value: 'createTime',
  199. label: '创建时间',
  200. show: true,
  201. width: 180,
  202. datetime: true,
  203. },
  204. {
  205. value: 'updateTime',
  206. label: '更新时间',
  207. show: true,
  208. width: 180,
  209. datetime: true,
  210. },
  211. {
  212. value: 'caozuo',
  213. label: '操作',
  214. show: true,
  215. width: 300,
  216. fixed: 'right',
  217. popover: false,
  218. },
  219. ],
  220. page: {
  221. pageNum: 1,
  222. pageSize: 20,
  223. },
  224. form: {},
  225. formReal: {},
  226. result: {
  227. total: 0,
  228. data: [],
  229. },
  230. selected: [],
  231. },
  232. detail: {
  233. show: false,
  234. transfer: {
  235. ID: ID,
  236. },
  237. },
  238. rename: {
  239. show: false,
  240. transfer: {},
  241. },
  242. moveSelect: {
  243. show: false,
  244. transfer: {},
  245. },
  246. stage: {
  247. show: false,
  248. document: {},
  249. },
  250. })
  251. const setText = debounce((v) => {
  252. state.query.form.name = v
  253. }, 1000)
  254. watch(
  255. () => state.text,
  256. (n) => {
  257. setText(n)
  258. },
  259. )
  260. watch(
  261. () => state.query.form,
  262. (n) => {
  263. if (state.query.init) {
  264. onSearch()
  265. }
  266. },
  267. { deep: true },
  268. )
  269. const onPage = (pageNum, pageSize) => {
  270. setTimeout(() => {
  271. state.query.init = true
  272. }, 100)
  273. state.query.page = {
  274. pageNum: pageNum,
  275. pageSize: pageSize,
  276. }
  277. const params = {
  278. datasetId: ID,
  279. page: state.query.page.pageNum,
  280. size: state.query.page.pageSize,
  281. }
  282. // 添加表单参数
  283. for (const [k, v] of Object.entries(state.query.formReal)) {
  284. if (proxy.$czrUtil.isValue(v)) {
  285. params[k] = v
  286. }
  287. }
  288. state.query.loading = true
  289. documentGetDocumentsByPage(params)
  290. .then(({ data }: any) => {
  291. state.query.result.total = data.totalElements
  292. state.query.result.data = data.content
  293. })
  294. .catch(() => {})
  295. .finally(() => {
  296. state.query.loading = false
  297. })
  298. }
  299. const onSearch = () => {
  300. state.query.selected = []
  301. state.query.formReal = JSON.parse(JSON.stringify(state.query.form))
  302. onPage(1, state.query.page.pageSize)
  303. }
  304. const onReset = () => {
  305. state.query.page = {
  306. pageNum: 1,
  307. pageSize: 20,
  308. }
  309. state.query.form = {}
  310. onSearch()
  311. }
  312. const onDel = (row: any = null) => {
  313. if (row) {
  314. DialogStore.confirm({
  315. title: '删除确认',
  316. content: `是否删除文档:${row.name}?<br/>此文档下的${row.segmentCount}个分段都会被删除,请谨慎操作。`,
  317. onSubmit: () => {
  318. documentDocsDelete([row.id])
  319. .then(() => {
  320. ElMessage.success('删除成功!')
  321. })
  322. .catch(() => {})
  323. .finally(() => {
  324. onSearch()
  325. })
  326. },
  327. })
  328. } else {
  329. if (state.query.selected.length === 0) {
  330. ElMessage.warning('请至少选择一条记录!')
  331. return
  332. }
  333. DialogStore.confirm({
  334. title: '删除确认',
  335. content: `是否批量删除${state.query.selected.length}个文档?<br/>所选文档中的分段会跟随删除,请谨慎操作。`,
  336. onSubmit: () => {
  337. documentDocsDelete(state.query.selected.map((v) => v.id))
  338. .then(() => {
  339. ElMessage.success('删除成功!')
  340. })
  341. .catch(() => {})
  342. .finally(() => {
  343. onSearch()
  344. })
  345. },
  346. })
  347. }
  348. }
  349. const onArchive = (row: any = null) => {
  350. if (row) {
  351. DialogStore.confirm({
  352. title: '归档确认',
  353. content: `是否归档文档:${row.name}?<br/>归档后的数据就只能查看或删除,无法重新编辑,请谨慎操作。`,
  354. onSubmit: () => {
  355. documentArchive([row.id])
  356. .then(() => {
  357. ElMessage.success('归档成功!')
  358. })
  359. .catch(() => {})
  360. .finally(() => {
  361. onSearch()
  362. })
  363. },
  364. })
  365. } else {
  366. if (state.query.selected.length === 0) {
  367. ElMessage.warning('请至少选择一条记录!')
  368. return
  369. }
  370. DialogStore.confirm({
  371. title: '归档确认',
  372. content: `是否批量删除${state.query.selected.length}个文档?<br/>归档后的数据就只能查看或删除,无法重新编辑,请谨慎操作。`,
  373. onSubmit: () => {
  374. documentArchive(state.query.selected.map((v) => v.id))
  375. .then(() => {
  376. ElMessage.success('归档成功!')
  377. })
  378. .catch(() => {})
  379. .finally(() => {
  380. onSearch()
  381. })
  382. },
  383. })
  384. }
  385. }
  386. const onUnArchive = (row: any = null) => {
  387. DialogStore.confirm({
  388. title: '撤销归档确认',
  389. content: `是否撤销归档:${row.name}?<br/>撤销归档后的数据,可重新编辑,请谨慎操作。`,
  390. onSubmit: () => {
  391. documentNoArchive([row.id])
  392. .then(() => {
  393. ElMessage.success('撤销归档成功!')
  394. })
  395. .catch(() => {})
  396. .finally(() => {
  397. onSearch()
  398. })
  399. },
  400. })
  401. }
  402. const onRename = (row) => {
  403. state.rename.transfer = {
  404. id: row.id,
  405. name: JSON.parse(JSON.stringify(row.name)),
  406. }
  407. state.rename.show = true
  408. }
  409. const onKnowledge = (row: any = null) => {
  410. if (row) {
  411. state.moveSelect.transfer = {
  412. row: JSON.parse(JSON.stringify(row)),
  413. type: 'text',
  414. }
  415. state.moveSelect.show = true
  416. } else {
  417. if (state.query.selected.length === 0) {
  418. ElMessage.warning('请至少选择一条记录!')
  419. return
  420. }
  421. state.moveSelect.transfer = {
  422. list: [...state.query.selected],
  423. type: 'text',
  424. }
  425. state.moveSelect.show = true
  426. }
  427. }
  428. const onStage = (row) => {
  429. state.stage.document = JSON.parse(JSON.stringify(row))
  430. state.stage.show = true
  431. }
  432. onMounted(() => {
  433. initDictionary()
  434. onReset()
  435. })
  436. const initDictionary = () => {
  437. DictionaryStore.initDict('document_use')
  438. DictionaryStore.initDict('indexing_status')
  439. }
  440. </script>
  441. <style lang="scss" scoped>
  442. .knowledge {
  443. width: 100%;
  444. height: 11.81rem;
  445. background-image: url('@/assets/images/knowledge/knowledge-item-bg.png');
  446. background-repeat: no-repeat;
  447. background-size: 100% 100%;
  448. padding: 1rem 1.5rem;
  449. }
  450. </style>