index.tsx 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. import * as React from 'react';
  2. import { observer } from 'mobx-react';
  3. import {
  4. List,
  5. Button,
  6. Divider,
  7. Flex,
  8. Layout,
  9. Empty,
  10. Image,
  11. Modal,
  12. Tag,
  13. message,
  14. Tooltip,
  15. Select,
  16. Form,
  17. Space,
  18. Row,
  19. Col,
  20. Input, Cascader, Card
  21. } from 'antd';
  22. import {
  23. PlusOutlined,
  24. FileOutlined,
  25. SettingOutlined,
  26. DeleteOutlined,
  27. StepForwardOutlined,
  28. SearchOutlined,
  29. ReloadOutlined,
  30. BookOutlined,
  31. TeamOutlined,
  32. AppstoreOutlined,
  33. EditOutlined, CloseOutlined, BulbOutlined, UpOutlined, DownOutlined
  34. } from '@ant-design/icons';
  35. import { apis } from '@/apis';
  36. import './style.less';
  37. import { PaginationConfig } from 'antd/es/pagination';
  38. import router from '@/router';
  39. import LocalStorage from '@/LocalStorage';
  40. import { create } from 'domain';
  41. import audit from '../../audit';
  42. import { set } from 'mobx';
  43. import IconSvg from "@/assets/public/icon.svg";
  44. import dayjs from 'dayjs';
  45. import UpdateNotification from '@/help/components/UpdateNotification';
  46. import { CURRENT_VERSION } from '@/help/components/UpdateNotification/version';
  47. const { Header, Footer, Sider, Content } = Layout;
  48. const { Option } = Select;
  49. const FormItem = Form.Item;
  50. const headerStyle: React.CSSProperties = {
  51. textAlign: 'center',
  52. height: 24,
  53. paddingInline: 48,
  54. lineHeight: '30px',
  55. backgroundColor: '#fff',
  56. };
  57. const contentStyle: React.CSSProperties = {
  58. textAlign: 'center',
  59. lineHeight: '40px',
  60. backgroundColor: '#fff',
  61. };
  62. const siderStyle: React.CSSProperties = {
  63. paddingLeft: 30,
  64. paddingTop: 30,
  65. height: 80,
  66. backgroundColor: '#fff',
  67. };
  68. const footerStyle: React.CSSProperties = {
  69. textAlign: 'center',
  70. color: '#fff',
  71. height: 24,
  72. backgroundColor: '#4096ff',
  73. };
  74. const layoutStyle = {
  75. borderRadius: 8,
  76. overflow: 'hidden',
  77. width: 'calc(10% - 8px)',
  78. maxWidth: 'calc(20% - 8px)',
  79. };
  80. const QuestionAnswerList: React.FC = () => {
  81. const [form] = Form.useForm();
  82. interface Item {
  83. name: string,
  84. desc: string,
  85. appId: number,
  86. createBy: string,
  87. typeId: string;
  88. status: string;
  89. comment: string;
  90. auditStatus: string;
  91. projectName: string;
  92. updateTime: string;
  93. };
  94. interface PageInfo {
  95. pageNumber: number,
  96. pageSize: number,
  97. total: number,
  98. };
  99. type AppTypeList = {
  100. label: string,
  101. value: string,
  102. }[];
  103. type ProjectTypeList = {
  104. label: string,
  105. value: string,
  106. }[];
  107. const [listLoading, setListLoading] = React.useState(false);
  108. const [list, setList] = React.useState<Item[]>([]);
  109. const [originalList, setOriginalList] = React.useState<Item[]>([]); // 保存原始列表数据,用于前端过滤
  110. const [page, setPage] = React.useState<PageInfo>({
  111. pageNumber: 1,
  112. pageSize: 10,
  113. total: 0,
  114. });
  115. const [appCount, setAppCount] = React.useState<string>();
  116. const [knowCount, setKnowCount] = React.useState<string>();
  117. const { Header, Footer, Sider, Content } = Layout;
  118. const [appTypeList, setAppTypeList] = React.useState<AppTypeList>([]);
  119. const [createFlag, setCreateFlag] = React.useState(false);
  120. const [deleteFlag, setDeleteFlag] = React.useState(false);
  121. const [updateFlag, setUpdateFlag] = React.useState(false);
  122. const [userInfoAll, setUserInfoAll] = React.useState<any>({});
  123. const [projectList, setProjectList] = React.useState<ProjectTypeList>([]);
  124. const [appProjectList, setAppProjectList] = React.useState<AppTypeList>([]);
  125. const [showSubPanel, setShowSubPanel] = React.useState(false);
  126. const [selectedType, setSelectedType] = React.useState<string | null>('全部');
  127. const wrapperRef = React.useRef<HTMLDivElement>(null);
  128. const selectRef = React.useRef<any>(null);
  129. const [levelTypeList, setLevelTypeList] = React.useState<AppTypeList>([]);
  130. // 新手引导整体可见性(持久化到 localStorage)
  131. const [showGuide, setShowGuide] = React.useState<boolean>(() => localStorage.getItem('appGuideHidden') !== 'true');
  132. // 新手引导展开/折叠状态(持久化到 localStorage),默认展开
  133. const [isGuideExpanded, setIsGuideExpanded] = React.useState<boolean>(() => {
  134. const saved = localStorage.getItem('appGuideExpanded');
  135. // 如果 localStorage 中没有值,默认展开(返回 true)
  136. return saved === null ? true : saved === 'true';
  137. });
  138. const toggleGuide = () => {
  139. setIsGuideExpanded(prev => {
  140. const newValue = !prev;
  141. // 保存到 localStorage
  142. localStorage.setItem('appGuideExpanded', String(newValue));
  143. return newValue;
  144. });
  145. };
  146. // 搜索输入框展开状态
  147. const [isSearchExpanded, setIsSearchExpanded] = React.useState(false);
  148. const searchWrapperRef = React.useRef<HTMLDivElement>(null);
  149. const searchInputRef = React.useRef<any>(null);
  150. // 标记是否正在重置,避免触发 useEffect 循环
  151. const isResettingRef = React.useRef(false);
  152. const appApi = {
  153. fetchList: async (typeId: any, projectId: any, forceRefresh: boolean = false, pageNumber?: number) => {
  154. const keyword = form.getFieldValue('keyword');
  155. const currentPageNumber = pageNumber !== undefined ? pageNumber : page.pageNumber;
  156. // 如果有关键字且不是强制刷新,直接从现有数据筛选,不请求接口
  157. if (keyword && keyword.trim() !== '' && !forceRefresh) {
  158. applyFrontendFilter(originalList, keyword, currentPageNumber);
  159. return;
  160. }
  161. setListLoading(true);
  162. try {
  163. const userInfo = LocalStorage.getUserInfo();
  164. const userId = (userInfo?.id ?? '').toString();
  165. const res = await apis.fetchTakaiAppList({
  166. pageSize: page.pageSize,
  167. pageNumber: currentPageNumber,
  168. userId: userId,
  169. typeId: typeId,
  170. projectId: projectId?.toString(),
  171. keyword: '', // 不传关键字给后端
  172. name: '', // 不传name给后端
  173. })
  174. const list = res.rows.map((item: any) => {
  175. return {
  176. name: item.name,
  177. desc: item.desc,
  178. appId: item.appId,
  179. createBy: item.createBy,
  180. typeId: item.typeId,
  181. status: item.status,
  182. comment: item.comment,
  183. auditStatus: item.auditStatus,
  184. projectName: item.projectName,
  185. updateTime: item.updateTime
  186. }
  187. });
  188. const c = LocalStorage.getStatusFlag('deepseek:application:create');
  189. const u = LocalStorage.getStatusFlag('deepseek:application:delete');
  190. const filteredList = list.filter((item: any) => {
  191. // 如果有 createFlag 或 updateFlag 权限,显示所有数据
  192. if (c || u) {
  193. return true;
  194. }
  195. // 没有权限时排除 status='5' 的数据
  196. return item.status !== '5';
  197. });
  198. // 保存原始数据
  199. setOriginalList(filteredList);
  200. // 没有关键字时,显示当前页的数据
  201. setList(filteredList);
  202. setPage({
  203. pageNumber: currentPageNumber,
  204. pageSize: page.pageSize,
  205. total: res.total,
  206. });
  207. } catch (error) {
  208. console.error(error);
  209. } finally {
  210. setListLoading(false);
  211. }
  212. },
  213. auditApplication: async (appId: string, userId: string) => {
  214. const res = await apis.auditTakaiApplicationLibApi(appId, userId);
  215. if (res.data === 9) {
  216. message.error('您没有添加审核人');
  217. }
  218. await appApi.fetchList(null, null);
  219. }
  220. };
  221. // 立即使用应用
  222. const useNowAppLication = (appId: number) => {
  223. const userInfo = LocalStorage.getUserInfo();
  224. const getToken = LocalStorage.getToken();
  225. // const baseUrl = import.meta.env.VITE_JUMP_URL;
  226. const baseUrl = 'https://llm.jkec.info:11432/#/knowledgeChat?showMenu=false&chatMode=LOCAL'
  227. window.open(`${baseUrl}&appId=${appId}&userId=${userInfo?.id}&nickName=${userInfo?.name}&token=${getToken}`, '_blank');
  228. }
  229. // 删除应用
  230. const delApplication = async (appId: string) => {
  231. try {
  232. await apis.deleteTakaiApplicationApi(appId);
  233. await appApi.fetchList(null, null);
  234. } catch (error) {
  235. console.error(error);
  236. }
  237. }
  238. const indexApi = {
  239. fetchIndex: async (typeId: any, projectId: any, pageNumber?: number) => {
  240. try {
  241. const userInfo = LocalStorage.getUserInfo();
  242. const userId = (userInfo?.id ?? '').toString();
  243. const keyword = form.getFieldValue('keyword');
  244. const currentPageNumber = pageNumber !== undefined ? pageNumber : page.pageNumber;
  245. const res = await apis.fetchTakaiIndexCount({
  246. pageSize: page.pageSize,
  247. pageNumber: currentPageNumber,
  248. userId: userId,
  249. typeId: typeId,
  250. projectId: projectId?.toString(),
  251. keyword: keyword,
  252. })
  253. setAppCount(res.data.applicationCount);
  254. setKnowCount(res.data.knowledgeCount);
  255. } catch (error) {
  256. console.error(error);
  257. } finally {
  258. setListLoading(false);
  259. }
  260. }
  261. };
  262. // 获取应用类型
  263. const appTypeApi = {
  264. fetchAppType: async () => {
  265. try {
  266. const res = await apis.fetchTakaiAppTypeList('app_type');
  267. const list = res.data.map((item: any) => {
  268. return {
  269. label: item.dictLabel,
  270. value: item.dictCode,
  271. }
  272. });
  273. setAppTypeList(list);
  274. } catch (error: any) {
  275. console.error(error);
  276. }
  277. },
  278. };
  279. // 项目级应用下的类型
  280. const appProTypeApi = {
  281. fetchAppProType: async () => {
  282. try {
  283. const res = await apis.fetchTakaiAppTypeList('projectTree');
  284. // const list: AppTypeList = res.data;
  285. const list: AppTypeList = res.data?.reduce((acc: any, item: any) => {
  286. if (item.children.length > 0) {
  287. item.children.forEach((val: any) => {
  288. acc.push({
  289. label: val.label,
  290. value: `${val.value}`,
  291. })
  292. })
  293. }
  294. return acc;
  295. }, []);
  296. setAppProjectList(list);
  297. // const res = await apis.fetchTakaiAppTypeList('projectTree');
  298. // const list = res.data.map((item: any) => {
  299. // return {
  300. // label: item.dictLabel,
  301. // value: item.dictCode,
  302. // }
  303. // });
  304. // setAppProjectList(list);
  305. } catch (error: any) {
  306. console.error(error);
  307. }
  308. },
  309. };
  310. const projectApi = {
  311. fetchProject: async () => {
  312. try {
  313. const res = await apis.fetchTakaiProjectLibApi();
  314. const list = res.data.map((item: any) => {
  315. return {
  316. label: item.projectName,
  317. value: item.projectId,
  318. }
  319. });
  320. setProjectList(list);
  321. } catch (error: any) {
  322. console.error(error);
  323. }
  324. },
  325. };
  326. // 获取应用类型
  327. const levelTypeApi = {
  328. fetchLevelAppType: async () => {
  329. try {
  330. const res = await apis.fetchTakaiAppTypeList('project_type');
  331. const list = res.data.map((item: any) => {
  332. return {
  333. label: item.dictLabel,
  334. value: item.dictCode,
  335. }
  336. });
  337. setLevelTypeList(list);
  338. } catch (error: any) {
  339. console.error(error);
  340. }
  341. },
  342. };
  343. const init = async () => {
  344. await appApi.fetchList(null, null, true); // 强制刷新
  345. await indexApi.fetchIndex(null, null);
  346. await appTypeApi.fetchAppType();
  347. await projectApi.fetchProject();
  348. await appProTypeApi.fetchAppProType();
  349. await levelTypeApi.fetchLevelAppType();
  350. // 设置默认选择"全部"
  351. form.setFieldsValue({ typeId: '全部' });
  352. }
  353. React.useEffect(() => {
  354. // 如果正在重置,跳过执行,避免循环
  355. if (isResettingRef.current) {
  356. isResettingRef.current = false;
  357. return;
  358. }
  359. setCreateFlag(LocalStorage.getStatusFlag('deepseek:application:create'));
  360. setDeleteFlag(LocalStorage.getStatusFlag('deepseek:application:delete'));
  361. setUpdateFlag(LocalStorage.getStatusFlag('deepseek:application:update'));
  362. setUserInfoAll(LocalStorage.getUserInfo());
  363. init();
  364. }, [page.pageSize, page.pageNumber])
  365. const paginationConfig: PaginationConfig = {
  366. // 显示数据总量
  367. showTotal: (total: number) => {
  368. return `共 ${total} 条`;
  369. },
  370. // 展示分页条数切换
  371. showSizeChanger: true,
  372. // 指定每页显示条数
  373. // pageSizeOptions: ['2', '20', '50', '100'],
  374. // 快速跳转至某页
  375. showQuickJumper: true,
  376. current: page.pageNumber,
  377. pageSize: page.pageSize,
  378. total: page.total,
  379. onChange: (pageNumber, pageSize) => {
  380. const keyword = form.getFieldValue('keyword');
  381. // 根据是否有搜索关键字,决定使用哪个数据源
  382. const dataSource = keyword && keyword.trim() !== ''
  383. ? originalList.filter((item: Item) =>
  384. item.name && item.name.toLowerCase().includes(keyword.toLowerCase().trim())
  385. )
  386. : originalList;
  387. // 获取新页的数据
  388. const startIndex = (pageNumber - 1) * pageSize;
  389. const endIndex = startIndex + pageSize;
  390. const paginatedData = dataSource.slice(startIndex, endIndex);
  391. setList(paginatedData);
  392. setPage({
  393. pageNumber: pageNumber,
  394. pageSize: pageSize,
  395. total: dataSource.length,
  396. });
  397. },
  398. };
  399. // 前端模糊查询过滤函数
  400. const applyFrontendFilter = (dataList: Item[], keyword: string | undefined, currentPageNumber: number = 1) => {
  401. if (!keyword || keyword.trim() === '') {
  402. // 如果没有关键字,显示所有数据
  403. const paginatedData = getPaginatedData(dataList, currentPageNumber, page.pageSize);
  404. setList(paginatedData);
  405. setPage(prev => ({
  406. ...prev,
  407. total: dataList.length,
  408. pageNumber: currentPageNumber,
  409. }));
  410. return;
  411. }
  412. // 对标题进行模糊匹配(不区分大小写)
  413. const filtered = dataList.filter((item: Item) => {
  414. return item.name && item.name.toLowerCase().includes(keyword.toLowerCase().trim());
  415. });
  416. // 计算过滤后的总页数
  417. const totalPages = Math.ceil(filtered.length / page.pageSize);
  418. // 如果当前页码超出了总页数,调整到最后一页(至少为1)
  419. const adjustedPageNumber = Math.min(currentPageNumber, Math.max(1, totalPages));
  420. // 获取调整后页码的数据
  421. const paginatedData = getPaginatedData(filtered, adjustedPageNumber, page.pageSize);
  422. setList(paginatedData);
  423. setPage(prev => ({
  424. ...prev,
  425. total: filtered.length,
  426. pageNumber: adjustedPageNumber,
  427. }));
  428. };
  429. // 获取分页数据
  430. const getPaginatedData = (dataList: Item[], pageNumber: number, pageSize: number) => {
  431. const startIndex = (pageNumber - 1) * pageSize;
  432. const endIndex = startIndex + pageSize;
  433. return dataList.slice(startIndex, endIndex);
  434. };
  435. // 点击查询
  436. const handleClickSearch = async () => {
  437. form.validateFields().then(async (values) => {
  438. const keyword = values.keyword;
  439. // 如果有关键字,直接从现有数据中筛选,不请求接口
  440. if (keyword && keyword.trim() !== '') {
  441. // 模糊查询:从现有数据中筛选,保持当前页码
  442. applyFrontendFilter(originalList, keyword, page.pageNumber);
  443. return;
  444. }
  445. // 如果没有关键字,正常查询接口
  446. if (values.proTypeId) {
  447. values.typeId = values.proTypeId;
  448. }
  449. if (values.typeId === '全部') {
  450. values.typeId = null;
  451. }
  452. if (values.projectId instanceof Array && values.projectId.length == 2) {
  453. values.projectId = values.projectId[1];
  454. }
  455. // 重置到第一页
  456. setPage(prev => ({ ...prev, pageNumber: 1 }));
  457. await indexApi.fetchIndex(values.typeId, values.projectId);
  458. await appApi.fetchList(values.typeId, values.projectId, true); // 强制刷新
  459. }).catch((error) => {
  460. console.error(error);
  461. });
  462. };
  463. // 点击重置
  464. const handleClickReset = async () => {
  465. form.resetFields();
  466. setShowSubPanel(false);
  467. setSelectedType('全部'); // 重置为"全部"
  468. // 标记正在重置,避免触发 useEffect 循环
  469. isResettingRef.current = true;
  470. // 先更新 page 到第一页
  471. setPage({
  472. pageNumber: 1,
  473. pageSize: 10,
  474. total: 0,
  475. });
  476. // 调用接口获取数据,传递 pageNumber: 1 确保返回第一页
  477. await appApi.fetchList(null, null, true, 1); // 强制刷新,第一页
  478. await indexApi.fetchIndex(null, null, 1); // 第一页
  479. };
  480. /** 点击外部关闭面板 */
  481. React.useEffect(() => {
  482. const handleClickOutside = (event: MouseEvent) => {
  483. if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
  484. setShowSubPanel(false);
  485. }
  486. };
  487. document.addEventListener('mousedown', handleClickOutside, true);
  488. return () => {
  489. document.removeEventListener('mousedown', handleClickOutside, true);
  490. };
  491. }, []);
  492. /** 点击外部关闭搜索框 */
  493. React.useEffect(() => {
  494. const handleClickOutside = (event: MouseEvent) => {
  495. if (searchWrapperRef.current && !searchWrapperRef.current.contains(event.target as Node)) {
  496. setIsSearchExpanded(false);
  497. }
  498. };
  499. if (isSearchExpanded) {
  500. document.addEventListener('mousedown', handleClickOutside, true);
  501. return () => {
  502. document.removeEventListener('mousedown', handleClickOutside, true);
  503. };
  504. }
  505. }, [isSearchExpanded]);
  506. // 展开搜索框并聚焦输入框
  507. const handleExpandSearch = () => {
  508. setIsSearchExpanded(true);
  509. // 延迟聚焦,确保输入框已渲染
  510. setTimeout(() => {
  511. const input = searchInputRef.current?.input || searchInputRef.current;
  512. if (input) {
  513. input.focus();
  514. input.select(); // 选中输入框中的文本
  515. }
  516. }, 150);
  517. };
  518. const handleAppTypeChange = (value: string) => {
  519. if (value === '41') {
  520. // 如果是项目级应用,切换面板状态
  521. // setShowSubPanel(prev => !prev);
  522. setShowSubPanel(true);
  523. } else {
  524. // 其他选项,隐藏面板
  525. setShowSubPanel(false);
  526. }
  527. setSelectedType(value);
  528. form.setFieldsValue({ typeId: value });
  529. // 自动提交逻辑
  530. if (value === '全部') {
  531. // 全部选项,传递null给后端
  532. appApi.fetchList(null, null, true); // 强制刷新
  533. indexApi.fetchIndex(null, null);
  534. } else {
  535. // 其他选项,传递对应的typeId
  536. appApi.fetchList(value, null, true); // 强制刷新
  537. indexApi.fetchIndex(value, null);
  538. }
  539. };
  540. const handleAppProTypeChange = (value: string) => {
  541. console.log(value, 'valuevalue');
  542. setSelectedType(value);
  543. form.setFieldsValue({ typeId: value });
  544. };
  545. return (
  546. <div>
  547. {/* 更新通知弹窗 - 只在此页面显示 */}
  548. <UpdateNotification version={CURRENT_VERSION} />
  549. <div style={{ padding: '16px 20px', display: 'flex' }}>
  550. <Form
  551. form={form}
  552. layout='inline'
  553. colon={false}
  554. style={{ flex: 1 }}
  555. >
  556. <div>
  557. {/* 主选择器 - 修改为按钮组形式 */}
  558. <FormItem
  559. name="typeId"
  560. style={{ marginBottom: 0 }}
  561. >
  562. <div className="filter-button-group">
  563. {/* 全部按钮 */}
  564. <Button
  565. key="全部"
  566. type={selectedType === '全部' ? 'primary' : 'default'}
  567. size="small"
  568. onClick={() => handleAppTypeChange('全部')}
  569. >
  570. 全部
  571. </Button>
  572. {/* 动态应用类型按钮 */}
  573. {appTypeList.map(item => {
  574. // 根据label匹配对应的图标
  575. let icon = null;
  576. const isSelected = selectedType === item.value;
  577. if (item.label === '专业知识') {
  578. icon = <BookOutlined style={{ fontSize: '14px', marginRight: '0' }} />;
  579. } else if (item.label === '职能管理') {
  580. icon = <TeamOutlined style={{ fontSize: '14px', marginRight: '0' }} />;
  581. } else if (item.label === '项目级应用') {
  582. icon = <AppstoreOutlined style={{ fontSize: '14px', marginRight: '0' }} />;
  583. }
  584. return (
  585. <Button
  586. key={item.value}
  587. type={isSelected ? 'primary' : 'default'}
  588. size="small"
  589. onClick={() => handleAppTypeChange(item.value)}
  590. >
  591. {icon}
  592. {item.label}
  593. </Button>
  594. );
  595. })}
  596. </div>
  597. </FormItem>
  598. {/* 子选项面板 */}
  599. {showSubPanel && selectedType === '41' && (
  600. <FormItem
  601. label='类型'
  602. name='proTypeId'
  603. rules={[{ required: true, message: '类型不能为空' }]}
  604. >
  605. <Select
  606. placeholder='请选择'
  607. allowClear
  608. // style={ { width: 200 } }
  609. onChange={(value) => {
  610. // 项目类型选择器自动提交逻辑
  611. const currentProjectId = form.getFieldValue('projectId');
  612. appApi.fetchList(value, currentProjectId, true); // 强制刷新
  613. indexApi.fetchIndex(value, currentProjectId);
  614. }}
  615. >
  616. {
  617. appProjectList.map((item, index) => {
  618. return <Option value={item.value} key={index}>
  619. {item.label}
  620. </Option>
  621. })
  622. }
  623. </Select>
  624. </FormItem>
  625. )}
  626. </div>
  627. {/* {
  628. appProjectList.map((subItem, index) => (
  629. <div key={index}>
  630. {subItem.label}
  631. </div>
  632. ))
  633. } */ }
  634. {appTypeList.find(item => item.value === selectedType)?.label === '项目级应用' && (
  635. <FormItem name='projectId'>
  636. <Cascader
  637. options={appProjectList}
  638. placeholder="请选择项目"
  639. showSearch
  640. onChange={(value) => {
  641. // 项目选择器自动提交逻辑
  642. const currentProTypeId = form.getFieldValue('proTypeId');
  643. let projectId: any = value;
  644. // 如果是数组且长度为2,取第二个元素(项目ID)
  645. if (projectId instanceof Array && projectId.length === 2) {
  646. projectId = projectId[1];
  647. }
  648. appApi.fetchList(currentProTypeId, projectId, true); // 强制刷新
  649. indexApi.fetchIndex(currentProTypeId, projectId);
  650. }}
  651. />
  652. </FormItem>
  653. )}
  654. {/*<Select*/}
  655. {/* placeholder='请选择项目'*/}
  656. {/* allowClear*/}
  657. {/* onChange={(value) => {*/}
  658. {/* // 项目选择器自动提交逻辑*/}
  659. {/* const currentTypeId = form.getFieldValue('typeId');*/}
  660. {/* const typeId = currentTypeId === '全部' ? null : currentTypeId;*/}
  661. {/* appApi.fetchList(typeId, value);*/}
  662. {/* indexApi.fetchIndex(typeId, value);*/}
  663. {/* }}*/}
  664. {/*>*/}
  665. {/* {*/}
  666. {/* projectList.map((item, index) => {*/}
  667. {/* return <Option value={item.value} key={index}>*/}
  668. {/* {item.label}*/}
  669. {/* </Option>*/}
  670. {/* })*/}
  671. {/* }*/}
  672. {/*</Select>*/}
  673. {/* </FormItem> */}
  674. <FormItem>
  675. <Space size={12}>
  676. {/* <div
  677. className={`search-expand-wrapper ${isSearchExpanded ? 'expanded' : ''}`}
  678. ref={searchWrapperRef}
  679. >
  680. <div className={`search-input-container ${isSearchExpanded ? 'expanded' : ''}`}>
  681. <FormItem name='keyword' style={{ marginBottom: 0 }}>
  682. <Input
  683. ref={searchInputRef}
  684. className="search-input"
  685. placeholder="请输入关键字"
  686. allowClear
  687. onPressEnter={handleClickSearch}
  688. onFocus={() => setIsSearchExpanded(true)}
  689. />
  690. </FormItem>
  691. </div>
  692. <Button
  693. type='primary'
  694. className="search-button-blue"
  695. onClick={() => {
  696. if (isSearchExpanded) {
  697. handleClickSearch();
  698. } else {
  699. handleExpandSearch();
  700. }
  701. }}
  702. icon={<SearchOutlined />}
  703. />
  704. </div> */}
  705. <Tooltip title="重置">
  706. <Button
  707. shape="circle"
  708. icon={<ReloadOutlined />}
  709. onClick={handleClickReset}
  710. />
  711. </Tooltip>
  712. </Space>
  713. </FormItem>
  714. {/* {
  715. createFlag && (
  716. <div style={ { marginLeft: 'auto' } }>
  717. <Button type='primary'
  718. icon={ <PlusOutlined /> }
  719. onClick={ () => {
  720. router.navigate( { pathname: '/deepseek/questionAnswer/create' } );
  721. } }>创建问答应用</Button>
  722. </div>
  723. )
  724. }
  725. */}
  726. {/* 创建按钮已移至面包屑组件 */}
  727. </Form>
  728. </div>
  729. {/* 新手使用提示区块 */}
  730. {showGuide && (
  731. <div style={{ padding: '12px 20px 12px 20px' }}>
  732. <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: isGuideExpanded ? 8 : 0 }}>
  733. <div style={{ fontSize: 13, color: '#333', display: 'flex', alignItems: 'center', gap: 6 }}>
  734. <BulbOutlined style={{ color: '#faad14' }} />
  735. 提示:如何创建并发布自己的RAG(增强检索生成)应用?
  736. </div>
  737. <div onClick={toggleGuide} style={{ color: '#999', cursor: 'pointer', display: 'flex', alignItems: 'center' }}>
  738. {isGuideExpanded ? <UpOutlined /> : <DownOutlined />}
  739. </div>
  740. </div>
  741. {isGuideExpanded && (
  742. <Row gutter={12}>
  743. <Col xs={24} sm={24} md={12} lg={8}>
  744. <Card
  745. size="small"
  746. bordered={false}
  747. style={{
  748. background: 'linear-gradient(90deg, #e6f4ff 0%, #f0f7ff 100%)',
  749. boxShadow: '0 1px 2px rgba(0,0,0,0.04)',
  750. borderRadius: 8,
  751. height: '100%'
  752. }}
  753. bodyStyle={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', minHeight: 80 }}
  754. title={<span style={{ fontWeight: 600 }}>创建或选用公共知识库</span>}
  755. >
  756. <div style={{ color: '#666', fontSize: 12 }}>选择公共知识库,或点击知识库菜单栏,创建自己的知识库。</div>
  757. <div style={{ textAlign: 'right', color: '#1677ff', fontWeight: 600 }}>step 1</div>
  758. </Card>
  759. </Col>
  760. <Col xs={24} sm={24} md={12} lg={8}>
  761. <Card
  762. size="small"
  763. bordered={false}
  764. style={{
  765. background: 'linear-gradient(90deg, #e6fffb 0%, #f0fffe 100%)',
  766. boxShadow: '0 1px 2px rgba(0,0,0,0.04)',
  767. borderRadius: 8,
  768. height: '100%'
  769. }}
  770. bodyStyle={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', minHeight: 80 }}
  771. title={<span style={{ fontWeight: 600 }}>参数配置</span>}
  772. >
  773. <div style={{ color: '#666', fontSize: 12 }}>点击右上角,创建应用,选择模型与知识范围,配置Prompt、检索策略并调试。</div>
  774. <div style={{ textAlign: 'right', color: '#1677ff', fontWeight: 600 }}>step 2</div>
  775. </Card>
  776. </Col>
  777. <Col xs={24} sm={24} md={12} lg={8}>
  778. <Card
  779. size="small"
  780. bordered={false}
  781. style={{
  782. background: 'linear-gradient(90deg, #fff7e6 0%, #fffaf0 100%)',
  783. boxShadow: '0 1px 2px rgba(0,0,0,0.04)',
  784. borderRadius: 8,
  785. height: '100%'
  786. }}
  787. bodyStyle={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', minHeight: 80 }}
  788. title={<span style={{ fontWeight: 600 }}>发布应用</span>}
  789. >
  790. <div style={{ color: '#666', fontSize: 12 }}>发布后可在相应的项目内使用。</div>
  791. <div style={{ textAlign: 'right', color: '#1677ff', fontWeight: 600 }}>step 3</div>
  792. </Card>
  793. </Col>
  794. </Row>
  795. )}
  796. </div>
  797. )}
  798. {
  799. list.length
  800. ?
  801. <div className='questionAnswerList'>
  802. {/*<div style={ { overflow: 'auto' } }>*/}
  803. {/* <Flex gap="middle" wrap>*/}
  804. {/* <Layout style={ layoutStyle }>*/}
  805. {/* <Sider width="25%" style={ siderStyle }>*/}
  806. {/* <FileOutlined />*/}
  807. {/* </Sider>*/}
  808. {/* <Layout>*/}
  809. {/* <Header style={ headerStyle }>问答应用总数</Header>*/}
  810. {/* <Content style={ contentStyle }>{ appCount }个</Content>*/}
  811. {/* </Layout>*/}
  812. {/* </Layout>*/}
  813. {/* <Layout style={ layoutStyle }>*/}
  814. {/* <Sider width="25%" style={ siderStyle }>*/}
  815. {/* <FileOutlined />*/}
  816. {/* </Sider>*/}
  817. {/* <Layout>*/}
  818. {/* <Header style={ headerStyle }>知识库总数</Header>*/}
  819. {/* <Content style={ contentStyle }>{ knowCount } 个</Content>*/}
  820. {/* </Layout>*/}
  821. {/* </Layout>*/}
  822. {/* </Flex>*/}
  823. {/*</div>*/}
  824. {/*<div style={ { display: 'flex', justifyContent: 'space-between', padding: '16px 20px' } }>*/}
  825. {/* <div>所有问答应用</div>*/}
  826. {/* /!*{*!/*/}
  827. {/* /!* createFlag &&*!/*/}
  828. {/* /!* <Button type='primary'*!/*/}
  829. {/* /!* icon={ <PlusOutlined /> }*!/*/}
  830. {/* /!* onClick={ () => {*!/*/}
  831. {/* /!* router.navigate( { pathname: '/deepseek/questionAnswer/create' } );*!/*/}
  832. {/* /!* } }>创建问答应用</Button>*!/*/}
  833. {/* /!*}*!/*/}
  834. {/*</div>*/}
  835. <div className='applicationList'>
  836. <List grid={{
  837. gutter: 16,
  838. xs: 1,
  839. sm: 1,
  840. md: 2,
  841. lg: 2,
  842. xl: 3,
  843. xxl: 4, // 展示的列数
  844. }}
  845. dataSource={list}
  846. renderItem={(item) => (
  847. <List.Item>
  848. <div className='card'>
  849. <div style={{
  850. display: 'flex',
  851. justifyContent: 'space-between',
  852. alignItems: 'center',
  853. overflow: 'hidden',
  854. }}>
  855. <div style={{ display: 'flex', alignItems: 'center', overflow: 'auto' }}>
  856. <div style={{ marginRight: 10, overflow: 'auto' }}>
  857. <Image
  858. width={32}
  859. height={32}
  860. src={IconSvg}
  861. preview={false}
  862. />
  863. </div>
  864. {/*<div style={ { overflow: 'auto' } }>*/}
  865. {/* { item.name }*/}
  866. {/*</div>*/}
  867. <div style={{
  868. display: 'flex',
  869. flexDirection: 'column',
  870. justifyContent: 'center',
  871. overflow: 'hidden',
  872. maxWidth: '85%',
  873. height: '100%'
  874. }}>
  875. <Tooltip title={item.name} placement="top">
  876. <div style={{
  877. lineHeight: '18px',
  878. fontSize: 14,
  879. fontWeight: 500,
  880. overflow: 'hidden',
  881. textOverflow: 'ellipsis',
  882. whiteSpace: 'nowrap',
  883. // maxWidth: '200px', // 可以根据需要调整宽度
  884. cursor: 'pointer'
  885. }}>
  886. {item.name.length > 20 ? `${item.name.substring(0, 30)}...` : item.name}
  887. </div>
  888. </Tooltip>
  889. <Space size={4} style={{ lineHeight: '18px' }}>
  890. <span style={{
  891. color: '#999',
  892. fontSize: 12,
  893. margin: 0
  894. }}>ID:{item.appId}</span>
  895. <Divider type="vertical" style={{ color: '999', margin: 0, height: 12 }} />
  896. <span
  897. style={ {
  898. color: '#999',
  899. fontSize: 12,
  900. display: 'inline-block',
  901. maxWidth: '100%', // 限制最大宽度
  902. flex: 1, // 使span占据剩余空间
  903. whiteSpace: 'nowrap', // 禁止换行
  904. overflow: 'hidden', // 隐藏溢出内容
  905. textOverflow: 'ellipsis', // 显示省略号
  906. verticalAlign: 'middle' // 垂直居中
  907. } }
  908. title={ item.projectName } // 鼠标悬停时显示完整名称
  909. >
  910. {item.projectName}
  911. </span>
  912. {/*<span style={{ color: '#999', fontSize: 12 }}>*/}
  913. {/* {*/}
  914. {/* item.projectName && item.projectName.length > 8 ?*/}
  915. {/* `${item.projectName.substring(0, 8)}...` :*/}
  916. {/* item.projectName*/}
  917. {/* }*/}
  918. {/*</span>*/}
  919. </Space>
  920. </div>
  921. </div>
  922. <div>
  923. <>
  924. {/*{ item.projectName } 移动599 line*/}
  925. {
  926. (item.status !== null && item.status !== '3') &&
  927. < Tag style={{
  928. // marginLeft: 16,
  929. width: 65,
  930. color: '#fff',
  931. height: 24,
  932. backgroundColor: item.status === '1' ? '#D26900' : item.status === '2' ? '#408080' : item.auditStatus === '4' ? '#CE0000' : item.status === '5' ? '#5151A2' : ''
  933. }}>
  934. {item.status === '1' ? '待审核' : item.status === '2' ? '审核中' : item.auditStatus === '4' ? '审核拒绝' : item.status === '5' ? '待提交' : '未知'}
  935. </Tag>
  936. }
  937. {
  938. (item.auditStatus === '4') &&
  939. <Tooltip title={item.comment}>
  940. {
  941. item.comment !== '' && item.comment !== null && item.comment.length > 10 ?
  942. item.comment.substring(0, 10) + '......' :
  943. item.comment
  944. }
  945. </Tooltip>
  946. }
  947. </>
  948. </div>
  949. </div>
  950. <Divider plain style={{ margin: '16px 0' }}></Divider>
  951. <div className='desc'>
  952. {
  953. item.desc !== '' && item.desc !== null && item.desc.length > 40 ? item.desc.substring(0, 40) + '......' : item.desc
  954. }
  955. </div>
  956. <div style={{
  957. display: 'flex',
  958. justifyContent: 'space-between',
  959. alignItems: 'center'
  960. }}>
  961. <span style={{
  962. color: '#999',
  963. fontSize: 12,
  964. margin: 0
  965. }}>更新时间: {dayjs(item.updateTime).format("YYYY-MM-DD HH:mm:ss")}</span>
  966. </div>
  967. <div style={{
  968. display: 'flex',
  969. justifyContent: 'space-between',
  970. alignItems: 'flex-end',
  971. paddingTop: 3,
  972. // overflow: 'auto',
  973. // paddingTop: 16,
  974. // height: '100%'
  975. }}>
  976. <div style={{
  977. overflow: 'auto'
  978. }}>
  979. {
  980. (item.status === '5' || item.status === '4' || item.status === '3' || item.status === '' || item.status === null) &&
  981. <>
  982. {
  983. (updateFlag || userInfoAll.id === item.createBy) &&
  984. <a
  985. className="action-button"
  986. style={{ marginRight: 16 }}
  987. onClick={() => {
  988. router.navigate({ pathname: '/deepseek/questionAnswer/modify' }, { state: { id: item.appId } });
  989. }}>
  990. <EditOutlined /> 编辑
  991. </a>
  992. }
  993. {
  994. (deleteFlag || userInfoAll.id === item.createBy) &&
  995. <a className='delete-button' onClick={() => {
  996. Modal.confirm({
  997. title: '删除',
  998. content: `确定删除应用: ` + item.name + ` 吗?`,
  999. okType: 'danger',
  1000. onOk: async () => {
  1001. await delApplication(item.appId.toString());
  1002. }
  1003. });
  1004. }}>
  1005. <DeleteOutlined /> 删除
  1006. </a>
  1007. }
  1008. </>
  1009. }
  1010. {
  1011. createFlag && item.status === '5' &&
  1012. <a className="action-button" style={{ marginLeft: 16 }} onClick={() => {
  1013. Modal.confirm({
  1014. title: '提交审核',
  1015. content: `确认提交审核应用: ` + item.name + `吗?`,
  1016. okType: 'danger',
  1017. onOk: async () => {
  1018. const userInfo = LocalStorage.getUserInfo();
  1019. const userId = (userInfo?.id ?? '').toString();
  1020. appApi.auditApplication(item.appId.toString(), userId);
  1021. }
  1022. });
  1023. }}>
  1024. <StepForwardOutlined /> 提交审核
  1025. </a>
  1026. }
  1027. <Button size='small' style={{
  1028. background: 'transparent',
  1029. border: '1px solid #1677ff',
  1030. color: '#1677ff',
  1031. marginTop: '2px',
  1032. marginLeft: createFlag && item.status === '5' || (item.status === '5' || item.status === '4' || item.status === '3' || item.status === '' || item.status === null) && updateFlag||userInfoAll.id === item.createBy ? '10px' : 0,
  1033. fontSize: 12,
  1034. }} type="primary" variant="outlined" onClick={() => { useNowAppLication(item.appId) }}>立即使用</Button>
  1035. </div>
  1036. <div>
  1037. <Tag
  1038. style={{
  1039. // padding: '4px 8px',
  1040. marginRight: 0,
  1041. fontSize: 12,
  1042. fontWeight: 600,
  1043. background: '#f8f8f8',
  1044. // border: '1px solid #d9d9d9'
  1045. }}
  1046. >
  1047. {
  1048. appTypeList
  1049. .find(item1 => item1.value.toString() === item.typeId)?.label || levelTypeList.find(item2 => item2.value.toString() === item.typeId)?.label || '未分类'
  1050. }
  1051. </Tag>
  1052. </div>
  1053. </div>
  1054. </div>
  1055. </List.Item>
  1056. )}
  1057. pagination={paginationConfig} // 分页
  1058. />
  1059. </div>
  1060. </div>
  1061. :
  1062. <div>
  1063. {/* {
  1064. createFlag &&
  1065. <Button type='primary'
  1066. icon={ <PlusOutlined /> }
  1067. onClick={ () => {
  1068. router.navigate( { pathname: '/deepseek/questionAnswer/create' } );
  1069. } }>创建问答应用</Button>
  1070. } */}
  1071. <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
  1072. </div>
  1073. }
  1074. </div>
  1075. )
  1076. };
  1077. export default observer(QuestionAnswerList);