Knowledge.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <template>
  2. <div class="knowledge-container">
  3. <el-tabs v-model="state.queryParams.Attribution" @tab-change="handleQuery">
  4. <el-tab-pane label="推荐知识库" name=""> </el-tab-pane>
  5. <el-tab-pane label="中心知识库" name="中心知识库"> </el-tab-pane>
  6. <el-tab-pane label="部门知识库" name="部门知识库"> </el-tab-pane>
  7. <el-tab-pane label="办事指南" name="guide" v-if="['LuZhou'].includes(themeConfig.appScope)"> </el-tab-pane>
  8. <el-tab-pane label="知识推荐" name="recommend" v-if="['LuZhou'].includes(themeConfig.appScope)"> </el-tab-pane>
  9. </el-tabs>
  10. <div class="knowledge-input" v-if="['', '中心知识库', '部门知识库'].includes(state.queryParams.Attribution)">
  11. <el-input v-model="state.queryParams.Keyword" placeholder="关键词" clearable class="mr10 w100" @keyup.enter="knowledgeRetrievalPaged">
  12. <template #prefix>
  13. <SvgIcon name="ele-Search" size="16px" />
  14. </template>
  15. <template #append>
  16. <el-button type="primary" @click="knowledgeRetrievalPaged" class="search-button" size="small" round
  17. ><SvgIcon name="ele-Search" class="mr4" /> 查询
  18. </el-button>
  19. </template>
  20. </el-input>
  21. </div>
  22. <div class="mt5" style="display: flex; flex-wrap: wrap; justify-content: space-between" v-if="state.queryParams.Attribution === ''">
  23. <div style="display: flex">
  24. <div style="height: 32px; line-height: 32px">推荐类型:</div>
  25. <el-radio-group v-model="state.queryParams.recommendType" @change="changeRecommendType">
  26. <el-radio value="1">关键词推荐</el-radio>
  27. <el-radio value="2">热点推荐</el-radio>
  28. </el-radio-group>
  29. </div>
  30. <div style="display: flex">
  31. <div style="height: 32px; line-height: 32px">排序:</div>
  32. <el-radio-group v-model="state.queryParams.Sort" @change="handleQuery">
  33. <el-radio value="1">浏览量</el-radio>
  34. <el-radio value="2">收藏量</el-radio>
  35. <el-radio value="3">创建时间</el-radio>
  36. </el-radio-group>
  37. </div>
  38. </div>
  39. <div class="mt10 retrieval-content" v-loading="state.loading" v-if="['', '中心知识库', '部门知识库'].includes(state.queryParams.Attribution)">
  40. <el-empty description="暂无数据" v-if="!state.knowledgeList.length" class="mb20">
  41. <template #image>
  42. <span></span>
  43. </template>
  44. </el-empty>
  45. <el-scrollbar>
  46. <div v-for="(v, i) in state.knowledgeList" :key="i" class="retrieval-content-item" @click="onPreview(v)">
  47. <div class="mb10" style="display: flex">
  48. <p class="text-no-wrap" style="flex: 1">{{ v.title }}</p>
  49. <el-button type="primary" size="small" @click.stop="changeYYType(v)" v-if="['ZiGong', 'YiBin'].includes(themeConfig.appScope)">{{
  50. v.isChoose ? '取消引用' : '引用'
  51. }}</el-button>
  52. </div>
  53. <!-- <div class="text-ellipsis2">{{ v.summary }}</div>-->
  54. <div class="flex-center-between mt10 color-info">
  55. <div>
  56. <span class="mr10">创建部门:{{ v.creatorOrgName }}</span>
  57. <span>创建时间:{{ formatDate(v.creationTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
  58. </div>
  59. <div class="flex-center-align">
  60. <span class="flex-center-align"><SvgIcon name="ele-StarFilled" size="18px" class="mr3" />{{ v.score }}</span>
  61. <!-- <span class="flex-center-align ml10"><SvgIcon name="ele-ChatDotSquare" size="16px" class="mr3" />{{ v.commentNum }}</span>-->
  62. <span class="flex-center-align ml10"><SvgIcon name="ele-View" size="16px" class="mr3" />{{ v.pageView }}</span>
  63. </div>
  64. </div>
  65. </div>
  66. </el-scrollbar>
  67. </div>
  68. </div>
  69. <pagination
  70. @pagination="knowledgeRetrievalPaged"
  71. :total="state.knowledgeTotal"
  72. v-model:current-page="state.queryParams.PageIndex"
  73. v-model:page-size="state.queryParams.PageSize"
  74. layout="prev, pager, next"
  75. class="pt10"
  76. v-if="['', '中心知识库', '部门知识库'].includes(state.queryParams.Attribution)"
  77. />
  78. <!-- 办事指南 -->
  79. <div v-if="['guide'].includes(state.queryParams.Attribution)" class="guide">
  80. <div class="flex flex-center-between mt10">
  81. <el-radio-group v-model="selectType" @change="getGuideType">
  82. <el-radio-button label="个人" value="1" />
  83. <el-radio-button label="企业" value="2" />
  84. </el-radio-group>
  85. <el-select v-model="guideType" placeholder="请选择办事分类" style="width: 240px" @change="handleQueryGuide">
  86. <el-option v-for="item in guideTypeList" :key="item.code" :label="item.describe" :value="item.code" />
  87. </el-select>
  88. </div>
  89. <el-input v-model="guideKeyword" placeholder="请输入文档名称" clearable class="mr10 mt10 w100" @keyup.enter="sendGuide">
  90. <template #append>
  91. <el-button type="primary" @click="sendGuide" size="small" round>搜索</el-button>
  92. </template>
  93. </el-input>
  94. <div v-loading="state.loading" class="guide-content">
  95. <el-empty description="暂无数据" v-if="!guideList.length" class="mb20"> </el-empty>
  96. <el-scrollbar max-height="350px">
  97. <div v-for="(item, index) in guideList" :key="index" class="guide-content-item">
  98. <el-button link type="primary" @click="guideDetail(item)">{{ item.name }}</el-button>
  99. </div>
  100. </el-scrollbar>
  101. <pagination
  102. @pagination="getGuideDataList"
  103. :total="guideListTotal"
  104. v-model:current-page="guidePageIndex"
  105. v-model:page-size="guidePageSize"
  106. class="pt10"
  107. />
  108. </div>
  109. </div>
  110. <!-- 推荐知识 -->
  111. <div v-if="['recommend'].includes(state.queryParams.Attribution)" class="recommend">
  112. <el-input v-model="recommendKeyword" placeholder="请输入问题内容" clearable class="mr10 w100" @keyup.enter="sendRecommend">
  113. <template #append>
  114. <el-button type="primary" @click="sendRecommend" size="small" round>搜索</el-button>
  115. </template>
  116. </el-input>
  117. <div v-loading="state.loading" class="recommend-content">
  118. <el-empty description="暂无数据" v-if="!recommendList.length" class="mb20"> </el-empty>
  119. <el-scrollbar max-height="400px">
  120. <div v-for="(item, index) in recommendList" :key="index" class="recommend-content-item">
  121. <div class="recommend-content-item-question">问:{{ item.question }}</div>
  122. <div class="recommend-content-item-answer" v-html="item.answer" style="text-indent: 1em"></div>
  123. </div>
  124. </el-scrollbar>
  125. </div>
  126. </div>
  127. <el-dialog :title="dialogTitle" v-model="dialogVisible" draggable destroy-on-close append-to-body>
  128. <div v-loading="loading" class="formatted-text">
  129. {{documentContent}}
  130. </div>
  131. <template #footer>
  132. <span class="dialog-footer">
  133. <el-button @click="dialogVisible = false" class="default-button">关闭</el-button>
  134. </span>
  135. </template>
  136. </el-dialog>
  137. </template>
  138. <script setup lang="ts" name="orderAcceptKnowledge">
  139. // 定义变量内容
  140. import { onMounted, reactive, defineAsyncComponent, ref, watch } from 'vue';
  141. import { useRouter } from 'vue-router';
  142. import { formatDate } from '@/utils/formatTime';
  143. import { knowledgeRetrieval, knowledgeRetrievalAccept } from '@/api/knowledge/retrieval';
  144. import { throttle } from '@/utils/tools';
  145. import { useThemeConfig } from '@/stores/themeConfig';
  146. import { storeToRefs } from 'pinia';
  147. import { removeDuplicate } from '@/utils/arrayOperation';
  148. import { lzRXFZAuth, lzRXFZDocumentDetail, lzRXFZDocumentList, lzRXFZDocumentType, lzRXFZKnowledge } from '@/api/business/lzRXFZ';
  149. import { useUserInfo } from '@/stores/userInfo';
  150. import { Cookie } from '@/utils/storage';
  151. const pagination = defineAsyncComponent(() => import('@/components/ProTable/components/Pagination.vue')); // 分页
  152. const props = defineProps({
  153. type: {
  154. // 知识库类型 (中心|部门)
  155. type: String,
  156. default: '',
  157. },
  158. formData: {
  159. // 表单填写的数据
  160. type: Object,
  161. default: () => {
  162. return {};
  163. },
  164. },
  165. });
  166. const storesThemeConfig = useThemeConfig();
  167. const { themeConfig } = storeToRefs(storesThemeConfig);
  168. const state = reactive<any>({
  169. loading: false, // 知识检索加载状态
  170. knowledgeList: [],
  171. knowledgeTotal: 0,
  172. queryParams: {
  173. PageIndex: 1,
  174. PageSize: 5,
  175. Keyword: null,
  176. RetrievalType: 0, // 检索类型
  177. Attribution: '',
  178. Sort: '1',
  179. recommendType: '1',
  180. },
  181. });
  182. // 手动查询,将页码设置为1
  183. const handleQuery = () => {
  184. if (['recommend'].includes(state.queryParams.Attribution)) return;
  185. if (state.queryParams.Attribution === 'guide') {
  186. getGuideType();
  187. return;
  188. }
  189. state.queryParams.PageIndex = 1;
  190. knowledgeRetrievalPaged();
  191. };
  192. // 知识查询
  193. const knowledgeRetrievalPaged = throttle(async () => {
  194. if (['guide', 'recommend'].includes(state.queryParams.Attribution)) return;
  195. try {
  196. state.loading = true;
  197. let request: EmptyObjectType = {};
  198. if (state.queryParams.recommendType === '1') {
  199. //关键词
  200. request = {
  201. ...state.queryParams,
  202. Content: props.formData.content,
  203. };
  204. Reflect.deleteProperty(request, 'HotspotId');
  205. }
  206. if (state.queryParams.recommendType === '2') {
  207. //关键词
  208. request = {
  209. ...state.queryParams,
  210. HotspotId: props.formData.hotspotId,
  211. };
  212. Reflect.deleteProperty(request, 'Content');
  213. }
  214. const { result } = await knowledgeRetrievalAccept(request);
  215. state.knowledgeList = result?.items ?? [];
  216. state.knowledgeTotal = result?.total ?? 0;
  217. if (props.formData.knowledgeQuote) {
  218. for (let i of state.knowledgeList) {
  219. for (let j of props.formData.knowledgeQuote) {
  220. if (i.id === j.key) {
  221. i.isChoose = true;
  222. }
  223. }
  224. }
  225. }
  226. state.loading = false;
  227. } catch (error) {
  228. console.log(error);
  229. state.loading = false;
  230. }
  231. }, 300);
  232. const router = useRouter(); //
  233. // 预览知识
  234. const onPreview = (row: any) => {
  235. router.push({
  236. name: 'knowledgePreview',
  237. params: {
  238. id: row.id,
  239. tagsViewName: '知识查看',
  240. },
  241. });
  242. };
  243. // 提供外部查询
  244. const HotspotName = ref('');
  245. const querySearch = async (name: string) => {
  246. try {
  247. // state.queryParams.Keyword = name;
  248. HotspotName.value = name;
  249. state.loading = true;
  250. const request = {
  251. ...state.queryParams,
  252. HotspotName: name,
  253. };
  254. const res: any = await knowledgeRetrieval(request);
  255. state.knowledgeList = res.result?.items ?? [];
  256. state.knowledgeTotal = res.result?.total ?? 0;
  257. if (props.formData.knowledgeQuote) {
  258. for (let i of state.knowledgeList) {
  259. for (let j of props.formData.knowledgeQuote) {
  260. if (i.id === j.key) {
  261. i.isChoose = true;
  262. }
  263. }
  264. }
  265. }
  266. state.loading = false;
  267. } catch (error) {
  268. console.log(error);
  269. state.loading = false;
  270. }
  271. };
  272. // 切换推荐类型查询
  273. const changeRecommendType = () => {
  274. knowledgeRetrievalPaged();
  275. };
  276. const emit = defineEmits(['changeYYType']);
  277. const chooseArray = ref<EmptyArrayType>([]);
  278. const changeYYType = (row: any) => {
  279. // 为了实现分页也能选中
  280. row.isChoose = !row.isChoose;
  281. if (row.isChoose) {
  282. chooseArray.value.push(row);
  283. removeDuplicate(chooseArray.value, row.id);
  284. }
  285. // 获取选中的数据
  286. const data = chooseArray.value.filter((v: any) => v.isChoose);
  287. const reData = data.map((v: any) => {
  288. return {
  289. key: v.id,
  290. value: v.title,
  291. };
  292. });
  293. console.log(reData);
  294. emit('changeYYType', reData);
  295. };
  296. watch(
  297. () => props.formData.hotspotId,
  298. () => {
  299. knowledgeRetrievalPaged();
  300. },
  301. { immediate: true, deep: true }
  302. );
  303. // 指南关键词
  304. const guideKeyword = ref('');
  305. const selectType = ref('1'); // 选择类型 1 个人 2 企业
  306. const guideType = ref(null); // 指南类型
  307. const guideTypeList = ref<EmptyArrayType>([]); // 指南类型列表
  308. // 获取指南类型列表
  309. const getGuideType = () => {
  310. lzRXFZDocumentType({ type: selectType.value })
  311. .then((res: any) => {
  312. guideTypeList.value = res.data;
  313. guideType.value = res.data[0].code;
  314. handleQueryGuide();
  315. })
  316. .catch((err: any) => {
  317. console.log(err);
  318. });
  319. };
  320. // 根据分类获取指南数据列表
  321. const guideListTotal = ref(0);
  322. const guidePageIndex = ref(1);
  323. const guidePageSize = ref(10);
  324. const guideList = ref<EmptyArrayType>([]); // 指南列表
  325. const handleQueryGuide = () => {
  326. guidePageIndex.value = 1;
  327. getGuideDataList();
  328. };
  329. const getGuideDataList = () => {
  330. state.loading = true;
  331. lzRXFZDocumentList({
  332. type: selectType.value,
  333. contentType: guideType.value,
  334. pageNum: guidePageIndex.value,
  335. pageSize: guidePageSize.value,
  336. name: guideKeyword.value,
  337. })
  338. .then((res: any) => {
  339. guideListTotal.value = parseInt(res.data?.total);
  340. guideList.value = res.data.rows;
  341. state.loading = false;
  342. })
  343. .catch((err: any) => {
  344. console.log(err);
  345. state.loading = false;
  346. });
  347. };
  348. // 发送问题
  349. const sendGuide = () => {
  350. handleQueryGuide();
  351. };
  352. // 点击查看文档详情
  353. const dialogVisible = ref(false);
  354. const dialogTitle = ref('文档详情');
  355. const loading = ref(false); // 加载
  356. const documentContent = ref(null);
  357. const guideDetail = (item: any) => {
  358. dialogVisible.value = true;
  359. loading.value = true;
  360. dialogTitle.value = `文档详情(${item.name})`;
  361. lzRXFZDocumentDetail({ documentId: item.id })
  362. .then((res: any) => {
  363. documentContent.value = res.data.content;
  364. loading.value = false;
  365. })
  366. .catch((err: any) => {
  367. console.log(err);
  368. loading.value = false;
  369. });
  370. };
  371. // 推荐知识关键词
  372. const recommendKeyword = ref('');
  373. // 搜索
  374. const recommendList = ref<EmptyArrayType>([]); // 推荐列表
  375. const sendRecommend = () => {
  376. if (!recommendKeyword.value) {
  377. recommendList.value = [];
  378. return;
  379. }
  380. state.loading = true;
  381. lzRXFZKnowledge({ content: recommendKeyword.value })
  382. .then((res: any) => {
  383. recommendList.value = res.data;
  384. state.loading = false;
  385. })
  386. .catch((err: any) => {
  387. console.log(err);
  388. state.loading = false;
  389. });
  390. };
  391. // 热线赋值授权
  392. const stores = useUserInfo(); // 用户信息
  393. const { userInfos } = storeToRefs(stores); // 用户信息
  394. const getRXFZAuth = () => {
  395. lzRXFZAuth({ uuid: userInfos.value.id })
  396. .then((res: any) => {
  397. Cookie.set('lzRXFZToken', res.data.accessToken);
  398. })
  399. .catch((err: any) => {
  400. console.log(err);
  401. });
  402. };
  403. onMounted(() => {
  404. knowledgeRetrievalPaged();
  405. if (['LuZhou'].includes(themeConfig.value.appScope)) {
  406. getRXFZAuth();
  407. }
  408. });
  409. defineExpose({
  410. knowledgeRetrievalPaged,
  411. querySearch,
  412. });
  413. </script>
  414. <style scoped lang="scss">
  415. .knowledge-container {
  416. position: relative;
  417. .el-radio {
  418. margin-right: 10px;
  419. line-height: 32px;
  420. }
  421. .knowledge-search-button {
  422. height: calc(100% - 6px);
  423. }
  424. .retrieval-content {
  425. &-item {
  426. border-bottom: var(--el-border);
  427. padding: 5px 15px;
  428. margin-bottom: 10px;
  429. cursor: pointer;
  430. &:last-child {
  431. margin-bottom: 0;
  432. border: none;
  433. }
  434. &:hover {
  435. color: var(--el-color-primary);
  436. }
  437. }
  438. }
  439. }
  440. .recommend-content {
  441. margin-top: 10px;
  442. .recommend-content-item {
  443. padding: 5px;
  444. border-bottom: var(--el-border);
  445. &:last-child {
  446. border: none;
  447. }
  448. .recommend-content-item-question {
  449. font-weight: bold;
  450. margin-bottom: 5px;
  451. }
  452. }
  453. }
  454. .guide-content {
  455. margin-top: 10px;
  456. .guide-content-item {
  457. padding: 5px;
  458. border-bottom: var(--el-border);
  459. &:last-child {
  460. border: none;
  461. }
  462. }
  463. }
  464. </style>