index.tsx 42 KB

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