index.tsx 45 KB

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