ZGRetrieval.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. <template>
  2. <div class="knowledge-retrieval-container layout-padding">
  3. <el-card shadow="never" class="h100">
  4. <el-tabs v-model="state.queryParams.Attribution" @tab-change="handleClick" v-if="userInfos.isCenter">
  5. <el-tab-pane label="全部" name=" " :disabled="centerLoading"></el-tab-pane>
  6. <el-tab-pane label="中心知识库" name="中心知识库" :disabled="centerLoading"></el-tab-pane>
  7. <el-tab-pane label="部门知识库" name="部门知识库" :disabled="centerLoading"></el-tab-pane>
  8. </el-tabs>
  9. <splitpanes class="h100" Vertical>
  10. <pane min-size="16" max-size="25" size="16" class="left-container">
  11. <template v-if="userInfos.isCenter">
  12. <el-tabs v-model="state.activeName" stretch @tab-change="resetNode">
  13. <el-tab-pane label="知识分类" name="1" :disabled="centerLoading">
  14. <el-input v-model="filterType" placeholder="请填写知识分类名称" class="input-with-select mb10" clearable @input="onQueryChangedType" :disabled="state.typeLoading">
  15. </el-input>
  16. </el-tab-pane>
  17. <el-tab-pane label="部门" name="0" :disabled="centerLoading">
  18. <el-input v-model="filterOrg" placeholder="请填写部门名称" class="input-with-select mb10" clearable @input="onQueryChanged" :disabled="state.orgLoading">
  19. </el-input>
  20. </el-tab-pane>
  21. <el-tab-pane label="热点" name="2" :disabled="centerLoading">
  22. <el-input v-model="filterHot" placeholder="请填写热点名称" class="input-with-select mb10" clearable @input="inputHotspot" :disabled="state.hotspotLoading"> </el-input>
  23. </el-tab-pane>
  24. </el-tabs>
  25. <el-scrollbar style="height: calc(100% - 160px);'" ref="scrollBarRef">
  26. <el-skeleton :loading="state.orgLoading" animated :rows="10" v-if="state.activeName === '0'">
  27. <template #default>
  28. <el-auto-resizer>
  29. <template #default="{ height, width }">
  30. <el-tree-v2
  31. :data="state.orgData"
  32. highlight-current
  33. :expand-on-click-node="false"
  34. :props="{ children: 'children', label: 'name' }"
  35. @node-click="handleNodeClick"
  36. ref="orgRef"
  37. :filter-method="filterNode"
  38. :item-size="32"
  39. empty-text="暂无组织数据"
  40. :height="height"
  41. >
  42. <template #default="{ node }">
  43. <text-tooltip :content="node.label + '(' + node.data.knowledgeNum + ')'" effect="dark" placement="top"></text-tooltip>
  44. </template>
  45. </el-tree-v2>
  46. </template>
  47. </el-auto-resizer>
  48. </template>
  49. </el-skeleton>
  50. <el-skeleton :loading="state.typeLoading" animated :rows="10" v-if="state.activeName === '1'">
  51. <template #default>
  52. <el-auto-resizer>
  53. <template #default="{ height, width }">
  54. <el-tree-v2
  55. :data="state.knowledgeOptions"
  56. highlight-current
  57. :expand-on-click-node="false"
  58. :props="{ children: 'children', label: 'name' }"
  59. @node-click="handleNodeClick"
  60. ref="typeRef"
  61. :filter-method="filterNodeType"
  62. :item-size="32"
  63. empty-text="暂无知识分类"
  64. :height="height"
  65. >
  66. <template #default="{ node }">
  67. <text-tooltip :content="node.label+'('+node.data.knowledgeNum+')'" effect="dark" placement="top"></text-tooltip>
  68. </template>
  69. </el-tree-v2>
  70. </template>
  71. </el-auto-resizer>
  72. </template>
  73. </el-skeleton>
  74. <el-skeleton :loading="state.hotspotLoading" animated :rows="10" v-if="state.activeName === '2'">
  75. <template #default>
  76. <el-tree
  77. node-key="id"
  78. :load="loadNode"
  79. lazy
  80. v-if="lazyShow"
  81. :props="{
  82. label: 'hotSpotFullName',
  83. children: 'children',
  84. isLeaf: 'hasChild',
  85. }"
  86. :filter-node-method="filterNodeHot"
  87. @node-click="handleNodeClick"
  88. highlight-current
  89. check-strictly
  90. :expand-on-click-node="false"
  91. ref="hotRef"
  92. > <template #default="{ node }">
  93. <text-tooltip :content="node.label + '(' + node.data.knowledgeNum + ')'" effect="dark" placement="top"></text-tooltip>
  94. </template>
  95. </el-tree>
  96. <el-tree
  97. ref="hotRef"
  98. :data="state.hotSpotData"
  99. node-key="id"
  100. v-else
  101. default-expand-all
  102. highlight-current
  103. :props="{
  104. label: 'hotSpotFullName',
  105. children: 'children',
  106. }"
  107. :filter-node-method="filterNodeHot"
  108. @node-click="handleNodeClick"
  109. :expand-on-click-node="false"
  110. check-strictly
  111. > <template #default="{ node }">
  112. <text-tooltip :content="node.label + '(' + node.data.knowledgeNum + ')'" effect="dark" placement="top"></text-tooltip>
  113. </template>
  114. </el-tree>
  115. </template>
  116. </el-skeleton>
  117. </el-scrollbar>
  118. </template>
  119. <template v-else>
  120. <el-input v-model="filterType" placeholder="请填写知识分类名称" class="input-with-select mb10" clearable> </el-input>
  121. <el-scrollbar ref="scrollBarRef" :style="userInfos.isCenter ? 'height: calc(100% - 90px);' : 'height: calc(100% - 50px)'">
  122. <el-skeleton :loading="state.typeLoading" animated :rows="10">
  123. <template #default>
  124. <el-auto-resizer>
  125. <template #default="{ height, width }">
  126. <el-tree-v2
  127. :data="state.knowledgeOptions"
  128. highlight-current
  129. :expand-on-click-node="false"
  130. :props="{ children: 'children', label: 'name' }"
  131. @node-click="handleNodeClick"
  132. ref="typeRef"
  133. :filter-method="filterNodeType"
  134. :item-size="32"
  135. empty-text="暂无知识分类"
  136. :height="height"
  137. >
  138. <template #default="{ node }">
  139. <text-tooltip :content="node.label+'('+node.data.knowledgeNum+')'" effect="dark" placement="top"></text-tooltip>
  140. </template>
  141. </el-tree-v2>
  142. </template>
  143. </el-auto-resizer>
  144. </template>
  145. </el-skeleton>
  146. </el-scrollbar>
  147. </template>
  148. </pane>
  149. <pane class="center-container">
  150. <div class="input-box">
  151. <el-select v-model="state.queryParams.RetrievalType" placeholder="请选择" class="width120" @change="handleQuery" :disabled="centerLoading">
  152. <el-option v-for="item in knowledgeRetrievalType" :key="item.key" :label="item.value" :value="item.key" />
  153. </el-select>
  154. <div class="input-with-button w100">
  155. <div class="flex">
  156. <el-input
  157. v-model="state.queryParams.Keyword"
  158. placeholder="多个关键词请用逗号或空格分割"
  159. clearable
  160. class="mr10 w100"
  161. @keyup.enter="handleQuery"
  162. :disabled="centerLoading"
  163. >
  164. </el-input>
  165. <el-button type="primary" class="btn" :loading="centerLoading" @click="handleQuery"
  166. ><SvgIcon name="ele-Search" class="mr5" />搜索</el-button
  167. >
  168. <el-button @click="resetQuery" class="default-button" :loading="centerLoading"> <SvgIcon name="ele-Refresh" class="mr5" />重置</el-button>
  169. </div>
  170. <div class="mt10" style="display: flex">
  171. <div style="display: flex; align-items: flex-start; flex-wrap: wrap">
  172. <div style="height: 18px; line-height: 18px">猜你想搜:</div>
  173. <div class="keyword-box">
  174. <span v-for="(v, i) in state.hotWordsList" :key="i" class="keyword-item" @click="keyWordSearch(v.keyWord)">{{ v.keyWord }}</span>
  175. </div>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. <div style="display: flex; margin: 10px 15px 0">
  181. <div style="height: 32px; line-height: 32px">排序:</div>
  182. <el-radio-group v-model="state.queryParams.Sort" @change="handleQuery" style="align-items: normal">
  183. <el-radio value="1">浏览量</el-radio>
  184. <el-radio value="2">评分</el-radio>
  185. <el-radio value="3">创建时间</el-radio>
  186. </el-radio-group>
  187. </div>
  188. <div
  189. v-loading="centerLoading"
  190. class="center-container-box"
  191. :style="userInfos.isCenter ? 'height: calc(100% - 200px)' : 'height: calc(100% - 160px)'"
  192. >
  193. <template v-if="state.retrievalList.length">
  194. <el-scrollbar>
  195. <div v-for="(v, i) in state.retrievalList" :key="i" class="retrieval-content-item" @click="onPreview(v)" title="查看详情">
  196. <h4 class="mb10 text-no-wrap retrieval-content-item-title">{{ v.title }}</h4>
  197. <!-- <div class="text-ellipsis2">{{ v.summary }}</div>-->
  198. <div class="flex-center-between mt10 color-info">
  199. <div>
  200. <span class="mr10">创建部门:{{ v.creatorOrgName }}</span>
  201. <span>创建时间:{{ formatDate(v.creationTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
  202. </div>
  203. <div class="flex-center-align">
  204. <span class="flex-center-align"><SvgIcon name="ele-StarFilled" size="18px" class="mr3" />{{ v.score }}</span>
  205. <!-- <span class="flex-center-align ml10"><SvgIcon name="ele-ChatDotSquare" size="16px" class="mr3" />{{ v.commentNum }}</span>-->
  206. <span class="flex-center-align ml10"><SvgIcon name="ele-View" size="16px" class="mr3" />{{ v.pageView }}</span>
  207. </div>
  208. </div>
  209. </div>
  210. </el-scrollbar>
  211. </template>
  212. <el-empty v-else description="暂无结果" />
  213. <pagination
  214. @pagination="queryList"
  215. :total="state.total"
  216. v-model:current-page="state.queryParams.PageIndex"
  217. v-model:page-size="state.queryParams.PageSize"
  218. :disabled="centerLoading"
  219. />
  220. </div>
  221. </pane>
  222. <pane min-size="20" max-size="30" size="20" class="right-container">
  223. <p class="flex-center-between pt10">
  224. <span class="font16">常用知识前10</span>
  225. <el-button link type="primary" @click="querySearchNum"><SvgIcon name="ele-Refresh" class="mr4" /> 刷新</el-button>
  226. </p>
  227. <el-divider />
  228. <p class="flex-center-between">
  229. <span>排名</span>
  230. <span>阅读量</span>
  231. </p>
  232. <el-skeleton :loading="rightLoading" animated :rows="10">
  233. <template #default>
  234. <div class="top10 mt10" :style="userInfos.isCenter ? 'height: calc(100% - 160px);' : 'height: calc(100% - 100px)'">
  235. <template v-if="topList.length">
  236. <el-scrollbar>
  237. <div class="flex-center-between top10-items" v-for="(item, index) in topList" :key="item.id">
  238. <p class="flex-center-align top10-items-title" @click="onPreview(item)">
  239. {{ index + 1 }}.
  240. <el-text class="text-no-wrap color-primary cursor-pointer" @click="onPreview(item)">
  241. <TextTooltip :content="item.title" placement="top" effect="dark" />
  242. </el-text>
  243. </p>
  244. <span class="top10-items-num">{{ item.pageView }}</span>
  245. </div>
  246. </el-scrollbar>
  247. </template>
  248. <el-empty v-else />
  249. </div>
  250. </template>
  251. </el-skeleton>
  252. </pane>
  253. </splitpanes>
  254. </el-card>
  255. </div>
  256. </template>
  257. <script setup lang="ts" name="knowledgeRetrieval">
  258. import { onMounted, reactive, ref, watch, defineAsyncComponent } from 'vue';
  259. import { useRouter } from 'vue-router';
  260. import { knowledgeRetrieval, searchNumList, knowledgeRetrievalBaseData } from '@/api/knowledge/retrieval';
  261. import { knowledgeDepartmentList, knowledgeHotSpotList, knowledgeHotSpotSearch, treeList } from '@/api/knowledge/type';
  262. import { formatDate } from '@/utils/formatTime';
  263. import { debounce, throttle } from '@/utils/tools';
  264. import { Splitpanes, Pane } from 'splitpanes';
  265. import 'splitpanes/dist/splitpanes.css';
  266. import { useUserInfo } from '@/stores/userInfo';
  267. import { storeToRefs } from 'pinia';
  268. import { getKnowledgeHotWordsList } from '@/api/knowledge/hotWords';
  269. const pagination = defineAsyncComponent(() => import('@/components/ProTable/components/Pagination.vue')); // 分页
  270. const TextTooltip = defineAsyncComponent(() => import('@/components/TextTooltip/index.vue'));
  271. const router = useRouter(); // 路由
  272. const storesUserInfo = useUserInfo();
  273. const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
  274. const state = reactive<any>({
  275. queryParams: {
  276. // 查询条件
  277. PageIndex: 1, // 当前页
  278. PageSize: 10, // 每页条数
  279. Attribution: userInfos.value.isCenter ? ' ' : '部门知识库',
  280. Keyword: null, // 关键词
  281. RetrievalType: 0, // 检索类型
  282. Sort: '1',
  283. },
  284. activeName: '1', //tab切换 默认知识分类
  285. orgData: [], // 部门
  286. knowledgeOptions: [], // 知识类型
  287. total: 0, // 总条数
  288. loading: false, // 加载状态
  289. retrievalList: [], // 检索列表
  290. hotSpotData: [], // 热点数据
  291. typeLoading: false, // 知识类型loading
  292. hotspotLoading:false,
  293. orgLoading:false,
  294. hotWordsList: [], // 获取热词
  295. });
  296. const topList = ref<EmptyArrayType>([]); // 常用知识前10
  297. const handleClick = () => {
  298. handleQuery();
  299. };
  300. // 部门查询
  301. const filterOrg = ref('');
  302. const orgRef = ref<RefType>();
  303. const onQueryChanged = (query: string) => {
  304. if (query) {
  305. orgRef.value!.filter(query);
  306. } else {
  307. orgRef.value!.filter(query);
  308. orgRef.value?.setExpandedKeys([]);
  309. }
  310. };
  311. const filterNode = (value: string, data: any) => {
  312. if (!value) return true;
  313. return data.name.includes(value);
  314. };
  315. // 知识分类查询
  316. const filterType = ref('');
  317. const typeRef = ref<RefType>();
  318. const filterNodeType = (value: string, data: any) => {
  319. if (!value) return true;
  320. return data.name.includes(value);
  321. };
  322. const onQueryChangedType = (query: string) => {
  323. if (query) {
  324. typeRef.value!.filter(query);
  325. } else {
  326. typeRef.value!.filter(query);
  327. typeRef.value?.setExpandedKeys([]);
  328. }
  329. };
  330. // 热点查询
  331. const filterHot = ref('');
  332. const hotRef = ref<RefType>();
  333. watch(filterHot, (val) => {
  334. if (val) {
  335. lazyShow.value = false;
  336. } else if (val == '' || val == ' ' || val == null) {
  337. lazyShow.value = true; // 懒加载树显示
  338. }
  339. });
  340. const filterNodeHot = (value: string, data: any) => {
  341. if (!value) return true;
  342. return data.hotSpotName.includes(value);
  343. };
  344. const inputHotspot = debounce((val:string)=>{
  345. state.hotspotLoading = true;
  346. if (val) {
  347. knowledgeHotSpotSearch({name:val,Attribution:state.queryParams.Attribution})
  348. .then((res) => {
  349. //获取后端搜索的数据
  350. state.hotSpotData.length = 0;
  351. state.hotSpotData = res.result ?? [];
  352. state.hotspotLoading = false;
  353. })
  354. .catch((e) => {
  355. console.log(e);
  356. state.hotspotLoading = false;
  357. });
  358. } else if (val == '' || val == ' ' || val == null) {
  359. state.hotspotLoading = false;
  360. }
  361. },500)
  362. // 热点分类懒加载
  363. const lazyShow = ref(true);
  364. const loadNode = async (node: any, resolve: any) => {
  365. try {
  366. if (node.isLeaf) return resolve([]);
  367. const {result} = await knowledgeHotSpotList({ id: node.data.id ? node.data.id : null,Attribution:state.queryParams.Attribution });
  368. resolve(result);
  369. } catch (error) {
  370. resolve([]);
  371. }
  372. };
  373. // 获取所有组织结构 和基础数据
  374. const getOrgListApi = async () => {
  375. state.orgLoading = true;
  376. try {
  377. const { result } = await knowledgeDepartmentList({ Attribution: state.queryParams.Attribution });
  378. state.orgData = result ?? []; //部门
  379. state.orgLoading = false;
  380. } catch (error) {
  381. state.orgLoading = false;
  382. }
  383. };
  384. // 获取知识分类
  385. const getKnowledgeType = async () => {
  386. state.typeLoading = true;
  387. try {
  388. const { result } = await treeList({ IsEnable: true,Attribution: state.queryParams.Attribution,status:3 });
  389. state.knowledgeOptions = result ?? [];
  390. state.typeLoading = false;
  391. } catch (error) {
  392. state.typeLoading = false;
  393. }
  394. };
  395. // 点击节点
  396. const handleNodeClick = (data: any) => {
  397. if (userInfos.value.isCenter) {
  398. switch (state.activeName) {
  399. case '0':
  400. state.queryParams.CreateOrgId = data.id;
  401. break;
  402. case '1':
  403. state.queryParams.KnowledgeTypeId = data.id;
  404. break;
  405. case '2':
  406. state.queryParams.HotspotId = data.id;
  407. break;
  408. default:
  409. break;
  410. }
  411. } else {
  412. state.queryParams.KnowledgeTypeId = data.id;
  413. }
  414. handleQuery();
  415. };
  416. // 预览
  417. const onPreview = (row: any) => {
  418. router.push({
  419. name: 'knowledgePreview',
  420. params: {
  421. id: row.id,
  422. isAddPv: 'isAddPv',
  423. tagsViewName: row.title,
  424. },
  425. });
  426. };
  427. // 切换tab 查询列表
  428. const rightLoading = ref(false); // 右侧加载状态
  429. // 常用知识前10
  430. const querySearchNum = () => {
  431. rightLoading.value = true;
  432. searchNumList({ Keyword: state.queryParams.Keyword })
  433. .then((res: any) => {
  434. topList.value = res.result?.items ?? [];
  435. rightLoading.value = false;
  436. })
  437. .catch(() => {
  438. rightLoading.value = false;
  439. });
  440. };
  441. const centerLoading = ref(false); // 中间加载状态
  442. /** 搜索按钮操作 */
  443. const handleQuery = () => {
  444. state.queryParams.PageIndex = 1;
  445. queryList();
  446. };
  447. const queryList = () => {
  448. centerLoading.value = true;
  449. knowledgeRetrieval(state.queryParams)
  450. .then((res: any) => {
  451. state.retrievalList = res.result?.items ?? [];
  452. /* state.retrievalList = [
  453. ...res.result?.items,
  454. ...res.result?.items,
  455. ...res.result?.items,
  456. ...res.result?.items,
  457. ...res.result?.items,
  458. ...res.result?.items,
  459. ...res.result?.items,
  460. ];*/
  461. state.total = res.result?.total ?? 0;
  462. centerLoading.value = false;
  463. })
  464. .catch(() => {
  465. centerLoading.value = false;
  466. state.retrievalList = [];
  467. state.total = 0;
  468. });
  469. };
  470. /** 重置按钮操作 */
  471. const resetQuery = throttle(() => {
  472. state.queryParams.PageIndex = 1;
  473. state.queryParams.PageSize = 10;
  474. state.queryParams.Keyword = null;
  475. state.queryParams.RetrievalType = 0;
  476. state.queryParams.Sort = '1';
  477. state.queryParams.Attribution = ' ';
  478. state.activeName = '1';
  479. state.queryParams.CreateOrgId = null;
  480. state.queryParams.KnowledgeTypeId = null;
  481. state.queryParams.HotspotId = null;
  482. filterOrg.value = '';
  483. filterType.value = '';
  484. filterHot.value = '';
  485. typeRef.value?.setCurrentKey(null);
  486. orgRef.value?.setCurrentKey(null);
  487. hotRef.value?.setCurrentKey(null);
  488. orgRef.value?.filter();
  489. orgRef.value?.setExpandedKeys([]);
  490. typeRef.value?.filter();
  491. typeRef.value?.setExpandedKeys([]);
  492. hotRef.value?.filter();
  493. queryList();
  494. }, 500);
  495. // 重置选中的节点
  496. const resetNode = () => {
  497. state.queryParams.CreateOrgId = null;
  498. state.queryParams.KnowledgeTypeId = null;
  499. state.queryParams.HotspotId = null;
  500. filterOrg.value = '';
  501. filterType.value = '';
  502. filterHot.value = '';
  503. typeRef.value?.setCurrentKey(null);
  504. orgRef.value?.setCurrentKey(null);
  505. hotRef.value?.setCurrentKey(null);
  506. queryList();
  507. };
  508. // 获取热词
  509. const getHotWords = async () => {
  510. try {
  511. const {result} = await getKnowledgeHotWordsList({ PageIndex: 1, PageSize: 10, IsEnable: true });
  512. state.hotWordsList = result?.items ?? [];
  513. } catch (error) {
  514. console.log(error);
  515. }
  516. };
  517. // 点击关键词检索
  518. const keyWordSearch = (word: string) => {
  519. state.queryParams.Keyword = word;
  520. handleQuery();
  521. };
  522. // 获取基础信息
  523. const knowledgeRetrievalType = ref<EmptyArrayType>([]);
  524. const getBaseData = async () => {
  525. try {
  526. const { result } = await knowledgeRetrievalBaseData();
  527. knowledgeRetrievalType.value = result.knowledgeRetrievalType;
  528. } catch (error) {
  529. console.log(error);
  530. }
  531. };
  532. onMounted(() => {
  533. getBaseData();
  534. getKnowledgeType();
  535. getOrgListApi();
  536. queryList();
  537. querySearchNum();
  538. getHotWords();
  539. });
  540. </script>
  541. <style scoped lang="scss">
  542. .knowledge-retrieval-container {
  543. .left-container {
  544. height: 100%;
  545. }
  546. .center-container {
  547. height: 100%;
  548. display: flex;
  549. flex-direction: column;
  550. .input-box {
  551. display: flex;
  552. }
  553. .retrieval-content {
  554. &-item {
  555. border-bottom: var(--el-border);
  556. padding: 10px 15px;
  557. margin-bottom: 10px;
  558. cursor: pointer;
  559. &:last-child {
  560. margin-bottom: 0;
  561. border: none;
  562. }
  563. &:hover {
  564. color: var(--el-color-primary);
  565. }
  566. }
  567. }
  568. }
  569. .right-container {
  570. height: 100%;
  571. .top10 {
  572. &-items {
  573. margin-bottom: 20px;
  574. &:last-child {
  575. margin-bottom: 0;
  576. }
  577. &-title {
  578. flex: 1;
  579. overflow: hidden;
  580. text-overflow: ellipsis;
  581. white-space: nowrap;
  582. }
  583. &-num {
  584. display: inline-block;
  585. width: 50px;
  586. text-align: center;
  587. }
  588. }
  589. }
  590. }
  591. :deep(.el-tree-node__content) {
  592. height: 32px;
  593. }
  594. :deep(.el-card__body) {
  595. height: 100%;
  596. }
  597. .keyword-box {
  598. display: flex;
  599. flex: 1;
  600. flex-wrap: wrap;
  601. line-height: 18px;
  602. .keyword-item {
  603. margin-right: 5px;
  604. cursor: pointer;
  605. color: var(--el-color-info);
  606. font-size: var(--el-font-size-extra-small);
  607. &:last-child {
  608. margin-right: 0;
  609. }
  610. &:hover {
  611. color: var(--el-color-primary);
  612. }
  613. }
  614. }
  615. }
  616. </style>