index.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. import * as React from 'react';
  2. import { observer } from 'mobx-react';
  3. import { List, Button, Divider, Flex, Layout, Empty, Image, Modal, Tag, message, Tooltip, Select, Form } from 'antd';
  4. import { PlusOutlined, FileOutlined, SettingOutlined, DeleteOutlined, StepForwardOutlined } from '@ant-design/icons';
  5. import { apis } from '@/apis';
  6. import './style.less';
  7. import { PaginationConfig } from 'antd/es/pagination';
  8. import router from '@/router';
  9. import LocalStorage from '@/LocalStorage';
  10. import { create } from 'domain';
  11. import audit from '../../audit';
  12. import { set } from 'mobx';
  13. const { Header, Footer, Sider, Content } = Layout;
  14. const { Option } = Select;
  15. const FormItem = Form.Item;
  16. const headerStyle: React.CSSProperties = {
  17. textAlign: 'center',
  18. height: 24,
  19. paddingInline: 48,
  20. lineHeight: '30px',
  21. backgroundColor: '#fff',
  22. };
  23. const contentStyle: React.CSSProperties = {
  24. textAlign: 'center',
  25. lineHeight: '40px',
  26. backgroundColor: '#fff',
  27. };
  28. const siderStyle: React.CSSProperties = {
  29. paddingLeft: 30,
  30. paddingTop: 30,
  31. height: 80,
  32. backgroundColor: '#fff',
  33. };
  34. const footerStyle: React.CSSProperties = {
  35. textAlign: 'center',
  36. color: '#fff',
  37. height: 24,
  38. backgroundColor: '#4096ff',
  39. };
  40. const layoutStyle = {
  41. borderRadius: 8,
  42. overflow: 'hidden',
  43. width: 'calc(10% - 8px)',
  44. maxWidth: 'calc(20% - 8px)',
  45. };
  46. const QuestionAnswerList: React.FC = () => {
  47. const [form] = Form.useForm();
  48. interface Item {
  49. name: string,
  50. desc: string,
  51. appId: number,
  52. createBy: string,
  53. typeId: string;
  54. status: string;
  55. comment: string;
  56. auditStatus: string;
  57. projectName: string;
  58. };
  59. interface PageInfo {
  60. pageNumber: number,
  61. pageSize: number,
  62. total: number,
  63. };
  64. type AppTypeList = {
  65. label: string,
  66. value: string,
  67. }[];
  68. type ProjectTypeList = {
  69. label: string,
  70. value: string,
  71. }[];
  72. const [listLoading, setListLoading] = React.useState(false);
  73. const [list, setList] = React.useState<Item[]>([]);
  74. const [page, setPage] = React.useState<PageInfo>({
  75. pageNumber: 1,
  76. pageSize: 10,
  77. total: 0,
  78. });
  79. const [appCount, setAppCount] = React.useState<string>();
  80. const [knowCount, setKnowCount] = React.useState<string>();
  81. const { Header, Footer, Sider, Content } = Layout;
  82. const [appTypeList, setAppTypeList] = React.useState<AppTypeList>([]);
  83. const [createFlag, setCreateFlag] = React.useState(false);
  84. const [deleteFlag, setDeleteFlag] = React.useState(false);
  85. const [updateFlag, setUpdateFlag] = React.useState(false);
  86. const [projectList, setProjectList] = React.useState<ProjectTypeList>([]);
  87. const [appProjectList, setAppProjectList] = React.useState<AppTypeList>([]);
  88. const [showSubPanel, setShowSubPanel] = React.useState(false);
  89. const [selectedType, setSelectedType] = React.useState<number | null>(null);
  90. const wrapperRef = React.useRef<HTMLDivElement>(null);
  91. const selectRef = React.useRef<any>(null);
  92. const [levelTypeList, setLevelTypeList] = React.useState<AppTypeList>([]);
  93. const appApi = {
  94. fetchList: async (typeId: any, projectId: any) => {
  95. setListLoading(true);
  96. try {
  97. const userInfo = LocalStorage.getUserInfo();
  98. const userId = (userInfo?.id ?? '').toString();
  99. const res = await apis.fetchTakaiAppList({
  100. pageSize: page.pageSize,
  101. pageNumber: page.pageNumber,
  102. userId: userId,
  103. typeId: typeId,
  104. projectId: projectId,
  105. })
  106. const list = res.rows.map((item: any) => {
  107. return {
  108. name: item.name,
  109. desc: item.desc,
  110. appId: item.appId,
  111. createBy: item.createBy,
  112. typeId: item.typeId,
  113. status: item.status,
  114. comment: item.comment,
  115. auditStatus: item.auditStatus,
  116. projectName: item.projectName
  117. }
  118. });
  119. const c = LocalStorage.getStatusFlag('deepseek:application:create');
  120. const u = LocalStorage.getStatusFlag('deepseek:application:delete');
  121. const filteredList = list.filter((item: any) => {
  122. // 如果有 createFlag 或 updateFlag 权限,显示所有数据
  123. if (c || u) {
  124. return true;
  125. }
  126. // 没有权限时排除 status='5' 的数据
  127. return item.status !== '5';
  128. });
  129. setList(filteredList);
  130. setPage({
  131. pageNumber: page.pageNumber,
  132. pageSize: page.pageSize,
  133. total: res.total,
  134. });
  135. } catch (error) {
  136. console.error(error);
  137. } finally {
  138. setListLoading(false);
  139. }
  140. },
  141. auditApplication: async (appId: string, userId: string) => {
  142. const res = await apis.auditTakaiApplicationLibApi(appId, userId);
  143. if (res.data === 9) {
  144. message.error('您没有添加审核人');
  145. }
  146. await appApi.fetchList(null, null);
  147. }
  148. };
  149. // 删除应用
  150. const delApplication = async (appId: string) => {
  151. try {
  152. await apis.deleteTakaiApplicationApi(appId);
  153. await appApi.fetchList(null, null);
  154. } catch (error) {
  155. console.error(error);
  156. }
  157. }
  158. const indexApi = {
  159. fetchIndex: async (typeId: any, projectId: any) => {
  160. try {
  161. const userInfo = LocalStorage.getUserInfo();
  162. const userId = (userInfo?.id ?? '').toString();
  163. const res = await apis.fetchTakaiIndexCount({
  164. pageSize: page.pageSize,
  165. pageNumber: page.pageNumber,
  166. userId: userId,
  167. typeId: typeId,
  168. projectId: projectId,
  169. })
  170. setAppCount(res.data.applicationCount);
  171. setKnowCount(res.data.knowledgeCount);
  172. } catch (error) {
  173. console.error(error);
  174. } finally {
  175. setListLoading(false);
  176. }
  177. }
  178. };
  179. // 获取应用类型
  180. const appTypeApi = {
  181. fetchAppType: async () => {
  182. try {
  183. const res = await apis.fetchTakaiAppTypeList('app_type');
  184. const list = res.data.map((item: any) => {
  185. return {
  186. label: item.dictLabel,
  187. value: item.dictCode,
  188. }
  189. });
  190. setAppTypeList(list);
  191. } catch (error: any) {
  192. console.error(error);
  193. }
  194. },
  195. };
  196. // 项目级应用下的类型
  197. const appProTypeApi = {
  198. fetchAppProType: async () => {
  199. try {
  200. const res = await apis.fetchTakaiAppTypeList('project_type');
  201. const list = res.data.map((item: any) => {
  202. return {
  203. label: item.dictLabel,
  204. value: item.dictCode,
  205. }
  206. });
  207. setAppProjectList(list);
  208. } catch (error: any) {
  209. console.error(error);
  210. }
  211. },
  212. };
  213. const projectApi = {
  214. fetchProject: async () => {
  215. try {
  216. const res = await apis.fetchTakaiProjectLibApi();
  217. const list = res.data.map((item: any) => {
  218. return {
  219. label: item.projectName,
  220. value: item.projectId,
  221. }
  222. });
  223. setProjectList(list);
  224. } catch (error: any) {
  225. console.error(error);
  226. }
  227. },
  228. };
  229. // 获取应用类型
  230. const levelTypeApi = {
  231. fetchLevelAppType: async () => {
  232. try {
  233. const res = await apis.fetchTakaiAppTypeList('project_type');
  234. const list = res.data.map((item: any) => {
  235. return {
  236. label: item.dictLabel,
  237. value: item.dictCode,
  238. }
  239. });
  240. setLevelTypeList(list);
  241. } catch (error: any) {
  242. console.error(error);
  243. }
  244. },
  245. };
  246. const init = async () => {
  247. await appApi.fetchList(null, null);
  248. await indexApi.fetchIndex(null, null);
  249. await appTypeApi.fetchAppType();
  250. await projectApi.fetchProject();
  251. await appProTypeApi.fetchAppProType();
  252. await levelTypeApi.fetchLevelAppType();
  253. }
  254. React.useEffect(() => {
  255. setCreateFlag(LocalStorage.getStatusFlag('deepseek:application:create'));
  256. setDeleteFlag(LocalStorage.getStatusFlag('deepseek:application:delete'));
  257. setUpdateFlag(LocalStorage.getStatusFlag('deepseek:application:update'));
  258. init();
  259. }, [page.pageSize, page.pageNumber])
  260. const paginationConfig: PaginationConfig = {
  261. // 显示数据总量
  262. showTotal: (total: number) => {
  263. return `共 ${total} 条`;
  264. },
  265. // 展示分页条数切换
  266. showSizeChanger: true,
  267. // 指定每页显示条数
  268. // pageSizeOptions: ['2', '20', '50', '100'],
  269. // 快速跳转至某页
  270. showQuickJumper: true,
  271. current: page.pageNumber,
  272. pageSize: page.pageSize,
  273. total: page.total,
  274. onChange: (pageNumber, pageSize) => {
  275. setPage({
  276. pageNumber: pageNumber,
  277. pageSize: pageSize,
  278. total: page.total,
  279. });
  280. },
  281. };
  282. // 点击查询
  283. const handleClickSearch = async () => {
  284. form.validateFields().then(async (values) => {
  285. if(values.proTypeId){
  286. values.typeId = values.proTypeId;
  287. }
  288. await indexApi.fetchIndex(values.typeId, values.projectId);
  289. await appApi.fetchList(values.typeId, values.projectId);
  290. }).catch((error) => {
  291. console.error(error);
  292. });
  293. };
  294. // 点击重置
  295. const handleClickReset = async () => {
  296. form.resetFields();
  297. setShowSubPanel(false);
  298. page.pageNumber = 1;
  299. page.pageSize = 10;
  300. await appApi.fetchList(null, null);
  301. await indexApi.fetchIndex(null, null);
  302. };
  303. /** 点击外部关闭面板 */
  304. React.useEffect(() => {
  305. const handleClickOutside = (event: MouseEvent) => {
  306. if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
  307. setShowSubPanel(false);
  308. }
  309. };
  310. document.addEventListener('mousedown', handleClickOutside, true);
  311. return () => {
  312. document.removeEventListener('mousedown', handleClickOutside, true);
  313. };
  314. }, []);
  315. const handleAppTypeChange = (value: number) => {
  316. console.log(value, 'sssss');
  317. if (value === 41) {
  318. // 如果是项目级应用,切换面板状态
  319. // setShowSubPanel(prev => !prev);
  320. setShowSubPanel(true);
  321. } else {
  322. // 其他选项,隐藏面板
  323. setShowSubPanel(false);
  324. }
  325. setSelectedType(value);
  326. form.setFieldsValue({ typeId: value });
  327. };
  328. const handleAppProTypeChange = (value: number) => {
  329. console.log(value, 'valuevalue');
  330. setSelectedType(value);
  331. form.setFieldsValue({ typeId: value });
  332. };
  333. return (
  334. <div >
  335. <div >
  336. <Form form={form} layout='inline' colon={false}>
  337. <div style={{ display: 'flex', alignItems: 'center', position: 'relative' }} >
  338. {/* 主选择器 */}
  339. <FormItem label="应用类型" name="typeId" style={{ marginBottom: 0 }}>
  340. <Select
  341. ref={selectRef}
  342. style={{ width: 200 }}
  343. placeholder="请选择应用类型"
  344. onChange={handleAppTypeChange}
  345. value={selectedType}
  346. allowClear
  347. >
  348. {appTypeList.map(item => (
  349. <Option key={item.value} value={item.value}>
  350. {item.label}
  351. </Option>
  352. ))}
  353. </Select>
  354. </FormItem>
  355. {/* 子选项面板 */}
  356. {showSubPanel && selectedType === 41 && (
  357. // <div
  358. // style={{
  359. // position: 'absolute',
  360. // left: '68%',
  361. // top: 35,
  362. // marginLeft: 10,
  363. // padding: 12,
  364. // background: '#fff',
  365. // border: '1px solid #d9d9d9',
  366. // borderRadius: 4,
  367. // boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
  368. // zIndex: 1000,
  369. // width: 200
  370. // }}
  371. // >
  372. <FormItem
  373. label='类型'
  374. name='proTypeId'
  375. rules={[{ required: true, message: '类型不能为空' }]}
  376. >
  377. <Select
  378. placeholder='请选择'
  379. allowClear
  380. style={{ width: 200 }}
  381. // onChange={handleAppProTypeChange}
  382. >
  383. {
  384. appProjectList.map((item, index) => {
  385. return <Option value={item.value} key={index}>
  386. {item.label}
  387. </Option>
  388. })
  389. }
  390. </Select>
  391. </FormItem>
  392. // </div>
  393. )}
  394. </div>
  395. {/* {
  396. appProjectList.map((subItem, index) => (
  397. <div key={index}>
  398. {subItem.label}
  399. </div>
  400. ))
  401. } */}
  402. <FormItem
  403. label='项目'
  404. name='projectId'
  405. >
  406. <Select
  407. style={{ width: '200px' }}
  408. placeholder='请选择项目'
  409. allowClear
  410. >
  411. {
  412. projectList.map((item, index) => {
  413. return <Option value={item.value} key={index}>
  414. {item.label}
  415. </Option>
  416. })
  417. }
  418. </Select>
  419. </FormItem>
  420. <FormItem>
  421. <Button
  422. style={{ marginRight: 16 }}
  423. type='primary'
  424. onClick={handleClickSearch}
  425. >
  426. 查询
  427. </Button>
  428. <Button onClick={handleClickReset}>
  429. 重置
  430. </Button>
  431. </FormItem>
  432. </Form>
  433. </div>
  434. {
  435. list.length
  436. ?
  437. <div className='questionAnswerList'>
  438. <div style={{ overflow: 'auto' }}>
  439. <Flex gap="middle" wrap>
  440. <Layout style={layoutStyle}>
  441. <Sider width="25%" style={siderStyle}>
  442. <FileOutlined />
  443. </Sider>
  444. <Layout>
  445. <Header style={headerStyle}>问答应用总数</Header>
  446. <Content style={contentStyle}>{appCount}个</Content>
  447. </Layout>
  448. </Layout>
  449. <Layout style={layoutStyle}>
  450. <Sider width="25%" style={siderStyle}>
  451. <FileOutlined />
  452. </Sider>
  453. <Layout>
  454. <Header style={headerStyle}>知识库总数</Header>
  455. <Content style={contentStyle}>{knowCount} 个</Content>
  456. </Layout>
  457. </Layout>
  458. </Flex>
  459. </div>
  460. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  461. <div>所有问答应用</div>
  462. {
  463. createFlag &&
  464. <Button type='primary'
  465. icon={<PlusOutlined />}
  466. onClick={() => {
  467. router.navigate({ pathname: '/deepseek/questionAnswer/create' });
  468. }}>创建问答应用</Button>
  469. }
  470. </div>
  471. <div className='applicationList'>
  472. <List style={{ height: 400 }}
  473. grid={{
  474. gutter: 16,
  475. xs: 1,
  476. sm: 2,
  477. md: 4,
  478. lg: 4,
  479. xl: 6,
  480. xxl: 2, // 展示的列数
  481. }}
  482. dataSource={list}
  483. renderItem={(item) => (
  484. <List.Item>
  485. <div className='card'>
  486. <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', overflow: 'auto' }}>
  487. <div style={{ display: 'flex', alignItems: 'center', overflow: 'auto' }}>
  488. <div style={{ marginRight: 10, overflow: 'auto' }}>
  489. <Image
  490. width={30}
  491. height={30}
  492. src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
  493. />
  494. </div>
  495. <div style={{ overflow: 'auto' }}>
  496. {item.name}
  497. </div>
  498. </div>
  499. <div >
  500. <>
  501. {item.projectName}
  502. {
  503. (item.status !== null && item.status !== '3') &&
  504. < Tag style={{ marginLeft: 16, width: 65, color: '#fff', height: 25, backgroundColor: item.status === '1' ? '#D26900' : item.status === '2' ? '#408080' : item.auditStatus === '4' ? '#CE0000' : item.status === '5' ? '#5151A2' : '' }}>
  505. {item.status === '1' ? '待审核' : item.status === '2' ? '审核中' : item.auditStatus === '4' ? '审核拒绝' : item.status === '5' ? '待提交' : '未知'}
  506. </Tag>
  507. }
  508. {
  509. (item.auditStatus === '4') &&
  510. <Tooltip title={item.comment}>
  511. {
  512. item.comment !== '' && item.comment !== null && item.comment.length > 10 ?
  513. item.comment.substring(0, 10) + '......' :
  514. item.comment
  515. }
  516. </Tooltip>
  517. }
  518. </>
  519. </div>
  520. </div>
  521. <Divider plain></Divider>
  522. <div className='desc'>
  523. {
  524. item.desc !== '' && item.desc !== null && item.desc.length > 35 ? item.desc.substring(0, 35) + '......' : item.desc
  525. }
  526. </div>
  527. <div style={{ display: 'flex', justifyContent: 'space-between', overflow: 'auto' }}>
  528. <div style={{ overflow: 'auto' }}>
  529. {
  530. (item.status === '5' || item.status === '4' || item.status === '3' || item.status === '' || item.status === null) &&
  531. <>
  532. {
  533. updateFlag &&
  534. <a style={{ marginRight: 16 }} onClick={() => {
  535. router.navigate({ pathname: '/deepseek/questionAnswer/modify' }, { state: { id: item.appId } });
  536. }}>
  537. <SettingOutlined /> 编辑
  538. </a>
  539. }
  540. {
  541. deleteFlag &&
  542. <a className='text-error' onClick={() => {
  543. Modal.confirm({
  544. title: '删除',
  545. content: `确定删除应用名称: ` + item.name + ` 吗?`,
  546. okType: 'danger',
  547. onOk: async () => {
  548. await delApplication(item.appId.toString());
  549. }
  550. });
  551. }}>
  552. <DeleteOutlined /> 删除
  553. </a>
  554. }
  555. </>
  556. }
  557. {
  558. createFlag && item.status === '5' &&
  559. <a style={{ marginLeft: 16 }} onClick={() => {
  560. Modal.confirm({
  561. title: '提交审核',
  562. content: `确认提交审核应用名称: ` + item.name + `吗?`,
  563. okType: 'danger',
  564. onOk: async () => {
  565. const userInfo = LocalStorage.getUserInfo();
  566. const userId = (userInfo?.id ?? '').toString();
  567. appApi.auditApplication(item.appId.toString(), userId);
  568. }
  569. });
  570. }}>
  571. <StepForwardOutlined /> 提交审核
  572. </a>
  573. }
  574. </div>
  575. <div>
  576. <Tag
  577. style={{
  578. padding: '4px 8px',
  579. fontSize: 14,
  580. fontWeight: 'bold',
  581. background: '#f0f0f0',
  582. border: '1px solid #d9d9d9'
  583. }}
  584. >
  585. {
  586. appTypeList
  587. .find(item1 => item1.value.toString() === item.typeId)?.label || levelTypeList.find(item2 => item2.value.toString() === item.typeId)?.label || '未分类'
  588. }
  589. </Tag>
  590. </div>
  591. </div>
  592. </div>
  593. </List.Item>
  594. )}
  595. pagination={paginationConfig} // 分页
  596. />
  597. </div>
  598. </div>
  599. :
  600. <div>
  601. {
  602. createFlag &&
  603. <Button type='primary'
  604. icon={<PlusOutlined />}
  605. onClick={() => {
  606. router.navigate({ pathname: '/deepseek/questionAnswer/create' });
  607. }}>创建问答应用</Button>
  608. }
  609. <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
  610. </div>
  611. }
  612. </div >
  613. )
  614. };
  615. export default observer(QuestionAnswerList);