LogsTable.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. import React, { useContext, useEffect, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import {
  4. API,
  5. copy,
  6. getTodayStartTimestamp,
  7. isAdmin,
  8. showError,
  9. showSuccess,
  10. timestamp2string,
  11. } from '../helpers';
  12. import {
  13. Avatar,
  14. Button, Descriptions,
  15. Form,
  16. Layout,
  17. Modal,
  18. Select,
  19. Space,
  20. Spin,
  21. Table,
  22. Tag,
  23. Tooltip
  24. } from '@douyinfe/semi-ui';
  25. import { ITEMS_PER_PAGE } from '../constants';
  26. import {
  27. renderAudioModelPrice, renderGroup,
  28. renderModelPrice, renderModelPriceSimple,
  29. renderNumber,
  30. renderQuota,
  31. stringToColor
  32. } from '../helpers/render';
  33. import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
  34. import { getLogOther } from '../helpers/other.js';
  35. import { StyleContext } from '../context/Style/index.js';
  36. const { Header } = Layout;
  37. function renderTimestamp(timestamp) {
  38. return <>{timestamp2string(timestamp)}</>;
  39. }
  40. const MODE_OPTIONS = [
  41. { key: 'all', text: 'all', value: 'all' },
  42. { key: 'self', text: 'current user', value: 'self' },
  43. ];
  44. const colors = [
  45. 'amber',
  46. 'blue',
  47. 'cyan',
  48. 'green',
  49. 'grey',
  50. 'indigo',
  51. 'light-blue',
  52. 'lime',
  53. 'orange',
  54. 'pink',
  55. 'purple',
  56. 'red',
  57. 'teal',
  58. 'violet',
  59. 'yellow',
  60. ];
  61. const LogsTable = () => {
  62. const { t } = useTranslation();
  63. function renderType(type) {
  64. switch (type) {
  65. case 1:
  66. return <Tag color='cyan' size='large'>{t('充值')}</Tag>;
  67. case 2:
  68. return <Tag color='lime' size='large'>{t('消费')}</Tag>;
  69. case 3:
  70. return <Tag color='orange' size='large'>{t('管理')}</Tag>;
  71. case 4:
  72. return <Tag color='purple' size='large'>{t('系统')}</Tag>;
  73. default:
  74. return <Tag color='black' size='large'>{t('未知')}</Tag>;
  75. }
  76. }
  77. function renderIsStream(bool) {
  78. if (bool) {
  79. return <Tag color='blue' size='large'>{t('流')}</Tag>;
  80. } else {
  81. return <Tag color='purple' size='large'>{t('非流')}</Tag>;
  82. }
  83. }
  84. function renderUseTime(type) {
  85. const time = parseInt(type);
  86. if (time < 101) {
  87. return (
  88. <Tag color='green' size='large'>
  89. {' '}
  90. {time} s{' '}
  91. </Tag>
  92. );
  93. } else if (time < 300) {
  94. return (
  95. <Tag color='orange' size='large'>
  96. {' '}
  97. {time} s{' '}
  98. </Tag>
  99. );
  100. } else {
  101. return (
  102. <Tag color='red' size='large'>
  103. {' '}
  104. {time} s{' '}
  105. </Tag>
  106. );
  107. }
  108. }
  109. function renderFirstUseTime(type) {
  110. let time = parseFloat(type) / 1000.0;
  111. time = time.toFixed(1);
  112. if (time < 3) {
  113. return (
  114. <Tag color='green' size='large'>
  115. {' '}
  116. {time} s{' '}
  117. </Tag>
  118. );
  119. } else if (time < 10) {
  120. return (
  121. <Tag color='orange' size='large'>
  122. {' '}
  123. {time} s{' '}
  124. </Tag>
  125. );
  126. } else {
  127. return (
  128. <Tag color='red' size='large'>
  129. {' '}
  130. {time} s{' '}
  131. </Tag>
  132. );
  133. }
  134. }
  135. const columns = [
  136. {
  137. title: t('时间'),
  138. dataIndex: 'timestamp2string',
  139. },
  140. {
  141. title: t('渠道'),
  142. dataIndex: 'channel',
  143. className: isAdmin() ? 'tableShow' : 'tableHiddle',
  144. render: (text, record, index) => {
  145. return isAdminUser ? (
  146. record.type === 0 || record.type === 2 ? (
  147. <div>
  148. {
  149. <Tag
  150. color={colors[parseInt(text) % colors.length]}
  151. size='large'
  152. >
  153. {' '}
  154. {text}{' '}
  155. </Tag>
  156. }
  157. </div>
  158. ) : (
  159. <></>
  160. )
  161. ) : (
  162. <></>
  163. );
  164. },
  165. },
  166. {
  167. title: t('用户'),
  168. dataIndex: 'username',
  169. className: isAdmin() ? 'tableShow' : 'tableHiddle',
  170. render: (text, record, index) => {
  171. return isAdminUser ? (
  172. <div>
  173. <Avatar
  174. size='small'
  175. color={stringToColor(text)}
  176. style={{ marginRight: 4 }}
  177. onClick={() => showUserInfo(record.user_id)}
  178. >
  179. {typeof text === 'string' && text.slice(0, 1)}
  180. </Avatar>
  181. {text}
  182. </div>
  183. ) : (
  184. <></>
  185. );
  186. },
  187. },
  188. {
  189. title: t('令牌'),
  190. dataIndex: 'token_name',
  191. render: (text, record, index) => {
  192. return record.type === 0 || record.type === 2 ? (
  193. <div>
  194. <Tag
  195. color='grey'
  196. size='large'
  197. onClick={() => {
  198. copyText(text);
  199. }}
  200. >
  201. {' '}
  202. {t(text)}{' '}
  203. </Tag>
  204. </div>
  205. ) : (
  206. <></>
  207. );
  208. },
  209. },
  210. {
  211. title: t('分组'),
  212. dataIndex: 'group',
  213. render: (text, record, index) => {
  214. if (record.type === 0 || record.type === 2) {
  215. if (record.group) {
  216. return (
  217. <>
  218. {renderGroup(record.group)}
  219. </>
  220. );
  221. } else {
  222. let other = JSON.parse(record.other);
  223. if (other === null) {
  224. return <></>;
  225. }
  226. if (other.group !== undefined) {
  227. return (
  228. <>
  229. {renderGroup(other.group)}
  230. </>
  231. );
  232. } else {
  233. return <></>;
  234. }
  235. }
  236. } else {
  237. return <></>;
  238. }
  239. },
  240. },
  241. {
  242. title: t('类型'),
  243. dataIndex: 'type',
  244. render: (text, record, index) => {
  245. return <>{renderType(text)}</>;
  246. },
  247. },
  248. {
  249. title: t('模型'),
  250. dataIndex: 'model_name',
  251. render: (text, record, index) => {
  252. return record.type === 0 || record.type === 2 ? (
  253. <>
  254. <Tag
  255. color={stringToColor(text)}
  256. size='large'
  257. onClick={() => {
  258. copyText(text);
  259. }}
  260. >
  261. {' '}
  262. {text}{' '}
  263. </Tag>
  264. </>
  265. ) : (
  266. <></>
  267. );
  268. },
  269. },
  270. {
  271. title: t('用时/首字'),
  272. dataIndex: 'use_time',
  273. render: (text, record, index) => {
  274. if (record.is_stream) {
  275. let other = getLogOther(record.other);
  276. return (
  277. <>
  278. <Space>
  279. {renderUseTime(text)}
  280. {renderFirstUseTime(other.frt)}
  281. {renderIsStream(record.is_stream)}
  282. </Space>
  283. </>
  284. );
  285. } else {
  286. return (
  287. <>
  288. <Space>
  289. {renderUseTime(text)}
  290. {renderIsStream(record.is_stream)}
  291. </Space>
  292. </>
  293. );
  294. }
  295. },
  296. },
  297. {
  298. title: t('提示'),
  299. dataIndex: 'prompt_tokens',
  300. render: (text, record, index) => {
  301. return record.type === 0 || record.type === 2 ? (
  302. <>{<span> {text} </span>}</>
  303. ) : (
  304. <></>
  305. );
  306. },
  307. },
  308. {
  309. title: t('补全'),
  310. dataIndex: 'completion_tokens',
  311. render: (text, record, index) => {
  312. return parseInt(text) > 0 &&
  313. (record.type === 0 || record.type === 2) ? (
  314. <>{<span> {text} </span>}</>
  315. ) : (
  316. <></>
  317. );
  318. },
  319. },
  320. {
  321. title: t('花费'),
  322. dataIndex: 'quota',
  323. render: (text, record, index) => {
  324. return record.type === 0 || record.type === 2 ? (
  325. <>{renderQuota(text, 6)}</>
  326. ) : (
  327. <></>
  328. );
  329. },
  330. },
  331. {
  332. title: t('重试'),
  333. dataIndex: 'retry',
  334. className: isAdmin() ? 'tableShow' : 'tableHiddle',
  335. render: (text, record, index) => {
  336. let content = t('渠道') + `:${record.channel}`;
  337. if (record.other !== '') {
  338. let other = JSON.parse(record.other);
  339. if (other === null) {
  340. return <></>;
  341. }
  342. if (other.admin_info !== undefined) {
  343. if (
  344. other.admin_info.use_channel !== null &&
  345. other.admin_info.use_channel !== undefined &&
  346. other.admin_info.use_channel !== ''
  347. ) {
  348. // channel id array
  349. let useChannel = other.admin_info.use_channel;
  350. let useChannelStr = useChannel.join('->');
  351. content = t('渠道') + `:${useChannelStr}`;
  352. }
  353. }
  354. }
  355. return isAdminUser ? <div>{content}</div> : <></>;
  356. },
  357. },
  358. {
  359. title: t('详情'),
  360. dataIndex: 'content',
  361. render: (text, record, index) => {
  362. let other = getLogOther(record.other);
  363. if (other == null || record.type !== 2) {
  364. return (
  365. <Paragraph
  366. ellipsis={{
  367. rows: 2,
  368. showTooltip: {
  369. type: 'popover',
  370. opts: { style: { width: 240 } },
  371. },
  372. }}
  373. style={{ maxWidth: 240 }}
  374. >
  375. {text}
  376. </Paragraph>
  377. );
  378. }
  379. let content = renderModelPriceSimple(
  380. other.model_ratio,
  381. other.model_price,
  382. other.group_ratio,
  383. );
  384. return (
  385. <Paragraph
  386. ellipsis={{
  387. rows: 2,
  388. }}
  389. style={{ maxWidth: 240 }}
  390. >
  391. {content}
  392. </Paragraph>
  393. );
  394. },
  395. },
  396. ];
  397. const [styleState, styleDispatch] = useContext(StyleContext);
  398. const [logs, setLogs] = useState([]);
  399. const [expandData, setExpandData] = useState({});
  400. const [showStat, setShowStat] = useState(false);
  401. const [loading, setLoading] = useState(false);
  402. const [loadingStat, setLoadingStat] = useState(false);
  403. const [activePage, setActivePage] = useState(1);
  404. const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
  405. const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
  406. const [logType, setLogType] = useState(0);
  407. const isAdminUser = isAdmin();
  408. let now = new Date();
  409. // 初始化start_timestamp为今天0点
  410. const [inputs, setInputs] = useState({
  411. username: '',
  412. token_name: '',
  413. model_name: '',
  414. start_timestamp: timestamp2string(getTodayStartTimestamp()),
  415. end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
  416. channel: '',
  417. group: '',
  418. });
  419. const {
  420. username,
  421. token_name,
  422. model_name,
  423. start_timestamp,
  424. end_timestamp,
  425. channel,
  426. group,
  427. } = inputs;
  428. const [stat, setStat] = useState({
  429. quota: 0,
  430. token: 0,
  431. });
  432. const handleInputChange = (value, name) => {
  433. setInputs(inputs => ({ ...inputs, [name]: value }));
  434. };
  435. const getLogSelfStat = async () => {
  436. let localStartTimestamp = Date.parse(start_timestamp) / 1000;
  437. let localEndTimestamp = Date.parse(end_timestamp) / 1000;
  438. let url = `/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`;
  439. url = encodeURI(url);
  440. let res = await API.get(url);
  441. const { success, message, data } = res.data;
  442. if (success) {
  443. setStat(data);
  444. } else {
  445. showError(message);
  446. }
  447. };
  448. const getLogStat = async () => {
  449. let localStartTimestamp = Date.parse(start_timestamp) / 1000;
  450. let localEndTimestamp = Date.parse(end_timestamp) / 1000;
  451. let url = `/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`;
  452. url = encodeURI(url);
  453. let res = await API.get(url);
  454. const { success, message, data } = res.data;
  455. if (success) {
  456. setStat(data);
  457. } else {
  458. showError(message);
  459. }
  460. };
  461. const handleEyeClick = async () => {
  462. if (loadingStat) {
  463. return;
  464. }
  465. setLoadingStat(true);
  466. if (isAdminUser) {
  467. await getLogStat();
  468. } else {
  469. await getLogSelfStat();
  470. }
  471. setShowStat(true);
  472. setLoadingStat(false);
  473. };
  474. const showUserInfo = async (userId) => {
  475. if (!isAdminUser) {
  476. return;
  477. }
  478. const res = await API.get(`/api/user/${userId}`);
  479. const { success, message, data } = res.data;
  480. if (success) {
  481. Modal.info({
  482. title: t('用户信息'),
  483. content: (
  484. <div style={{ padding: 12 }}>
  485. <p>{t('用户名')}: {data.username}</p>
  486. <p>{t('余额')}: {renderQuota(data.quota)}</p>
  487. <p>{t('已用额度')}:{renderQuota(data.used_quota)}</p>
  488. <p>{t('请求次数')}:{renderNumber(data.request_count)}</p>
  489. </div>
  490. ),
  491. centered: true,
  492. });
  493. } else {
  494. showError(message);
  495. }
  496. };
  497. const setLogsFormat = (logs) => {
  498. let expandDatesLocal = {};
  499. for (let i = 0; i < logs.length; i++) {
  500. logs[i].timestamp2string = timestamp2string(logs[i].created_at);
  501. logs[i].key = i;
  502. let other = getLogOther(logs[i].other);
  503. let expandDataLocal = [];
  504. if (isAdmin()) {
  505. // let content = '渠道:' + logs[i].channel;
  506. // if (other.admin_info !== undefined) {
  507. // if (
  508. // other.admin_info.use_channel !== null &&
  509. // other.admin_info.use_channel !== undefined &&
  510. // other.admin_info.use_channel !== ''
  511. // ) {
  512. // // channel id array
  513. // let useChannel = other.admin_info.use_channel;
  514. // let useChannelStr = useChannel.join('->');
  515. // content = `渠道:${useChannelStr}`;
  516. // }
  517. // }
  518. // expandDataLocal.push({
  519. // key: '渠道重试',
  520. // value: content,
  521. // })
  522. }
  523. if (other?.ws || other?.audio) {
  524. expandDataLocal.push({
  525. key: t('语音输入'),
  526. value: other.audio_input,
  527. });
  528. expandDataLocal.push({
  529. key: t('语音输出'),
  530. value: other.audio_output,
  531. });
  532. expandDataLocal.push({
  533. key: t('文字输入'),
  534. value: other.text_input,
  535. });
  536. expandDataLocal.push({
  537. key: t('文字输出'),
  538. value: other.text_output,
  539. });
  540. }
  541. expandDataLocal.push({
  542. key: t('日志详情'),
  543. value: logs[i].content,
  544. });
  545. if (logs[i].type === 2) {
  546. let content = '';
  547. if (other?.ws || other?.audio) {
  548. content = renderAudioModelPrice(
  549. other.text_input,
  550. other.text_output,
  551. other.model_ratio,
  552. other.model_price,
  553. other.completion_ratio,
  554. other.audio_input,
  555. other.audio_output,
  556. other?.audio_ratio,
  557. other?.audio_completion_ratio,
  558. other.group_ratio,
  559. );
  560. } else {
  561. content = renderModelPrice(
  562. logs[i].prompt_tokens,
  563. logs[i].completion_tokens,
  564. other.model_ratio,
  565. other.model_price,
  566. other.completion_ratio,
  567. other.group_ratio,
  568. );
  569. }
  570. expandDataLocal.push({
  571. key: t('计费过程'),
  572. value: content,
  573. });
  574. }
  575. expandDatesLocal[logs[i].key] = expandDataLocal;
  576. }
  577. setExpandData(expandDatesLocal);
  578. setLogs(logs);
  579. };
  580. const loadLogs = async (startIdx, pageSize, logType = 0) => {
  581. setLoading(true);
  582. let url = '';
  583. let localStartTimestamp = Date.parse(start_timestamp) / 1000;
  584. let localEndTimestamp = Date.parse(end_timestamp) / 1000;
  585. if (isAdminUser) {
  586. url = `/api/log/?p=${startIdx}&page_size=${pageSize}&type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`;
  587. } else {
  588. url = `/api/log/self/?p=${startIdx}&page_size=${pageSize}&type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`;
  589. }
  590. url = encodeURI(url);
  591. const res = await API.get(url);
  592. const { success, message, data } = res.data;
  593. if (success) {
  594. const newPageData = data.items;
  595. setActivePage(data.page);
  596. setPageSize(data.page_size);
  597. setLogCount(data.total);
  598. setLogsFormat(newPageData);
  599. } else {
  600. showError(message);
  601. }
  602. setLoading(false);
  603. };
  604. const handlePageChange = (page) => {
  605. setActivePage(page);
  606. loadLogs(page, pageSize, logType).then((r) => {});
  607. };
  608. const handlePageSizeChange = async (size) => {
  609. localStorage.setItem('page-size', size + '');
  610. setPageSize(size);
  611. setActivePage(1);
  612. loadLogs(activePage, size)
  613. .then()
  614. .catch((reason) => {
  615. showError(reason);
  616. });
  617. };
  618. const refresh = async () => {
  619. setActivePage(1);
  620. handleEyeClick();
  621. await loadLogs(activePage, pageSize, logType);
  622. };
  623. const copyText = async (text) => {
  624. if (await copy(text)) {
  625. showSuccess('已复制:' + text);
  626. } else {
  627. Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
  628. }
  629. };
  630. useEffect(() => {
  631. const localPageSize =
  632. parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE;
  633. setPageSize(localPageSize);
  634. loadLogs(activePage, localPageSize)
  635. .then()
  636. .catch((reason) => {
  637. showError(reason);
  638. });
  639. handleEyeClick();
  640. }, []);
  641. const expandRowRender = (record, index) => {
  642. return <Descriptions data={expandData[record.key]} />;
  643. };
  644. return (
  645. <>
  646. <Layout>
  647. <Header>
  648. <Spin spinning={loadingStat}>
  649. <Space>
  650. <Tag color='green' size='large' style={{ padding: 15 }}>
  651. {t('总消耗额度')}: {renderQuota(stat.quota)}
  652. </Tag>
  653. <Tag color='blue' size='large' style={{ padding: 15 }}>
  654. RPM: {stat.rpm}
  655. </Tag>
  656. <Tag color='purple' size='large' style={{ padding: 15 }}>
  657. TPM: {stat.tpm}
  658. </Tag>
  659. </Space>
  660. </Spin>
  661. </Header>
  662. <Form layout='horizontal' style={{ marginTop: 10 }}>
  663. <>
  664. <Form.Section>
  665. <div style={{ marginBottom: 10 }}>
  666. {
  667. styleState.isMobile ? (
  668. <div>
  669. <Form.DatePicker
  670. field='start_timestamp'
  671. label={t('起始时间')}
  672. style={{ width: 272 }}
  673. initValue={start_timestamp}
  674. type='dateTime'
  675. onChange={(value) => {
  676. console.log(value);
  677. handleInputChange(value, 'start_timestamp')
  678. }}
  679. />
  680. <Form.DatePicker
  681. field='end_timestamp'
  682. fluid
  683. label={t('结束时间')}
  684. style={{ width: 272 }}
  685. initValue={end_timestamp}
  686. type='dateTime'
  687. onChange={(value) => handleInputChange(value, 'end_timestamp')}
  688. />
  689. </div>
  690. ) : (
  691. <Form.DatePicker
  692. field="range_timestamp"
  693. label={t('时间范围')}
  694. initValue={[start_timestamp, end_timestamp]}
  695. type="dateTimeRange"
  696. name="range_timestamp"
  697. onChange={(value) => {
  698. if (Array.isArray(value) && value.length === 2) {
  699. handleInputChange(value[0], 'start_timestamp');
  700. handleInputChange(value[1], 'end_timestamp');
  701. }
  702. }}
  703. />
  704. )
  705. }
  706. </div>
  707. </Form.Section>
  708. <Form.Input
  709. field='token_name'
  710. label={t('令牌名称')}
  711. value={token_name}
  712. placeholder={t('可选值')}
  713. name='token_name'
  714. onChange={(value) => handleInputChange(value, 'token_name')}
  715. />
  716. <Form.Input
  717. field='model_name'
  718. label={t('模型名称')}
  719. value={model_name}
  720. placeholder={t('可选值')}
  721. name='model_name'
  722. onChange={(value) => handleInputChange(value, 'model_name')}
  723. />
  724. <Form.Input
  725. field='group'
  726. label={t('分组')}
  727. value={group}
  728. placeholder={t('可选值')}
  729. name='group'
  730. onChange={(value) => handleInputChange(value, 'group')}
  731. />
  732. {isAdminUser && (
  733. <>
  734. <Form.Input
  735. field='channel'
  736. label={t('渠道 ID')}
  737. value={channel}
  738. placeholder={t('可选值')}
  739. name='channel'
  740. onChange={(value) => handleInputChange(value, 'channel')}
  741. />
  742. <Form.Input
  743. field='username'
  744. label={t('用户名称')}
  745. value={username}
  746. placeholder={t('可选值')}
  747. name='username'
  748. onChange={(value) => handleInputChange(value, 'username')}
  749. />
  750. </>
  751. )}
  752. <Button
  753. label={t('查询')}
  754. type='primary'
  755. htmlType='submit'
  756. className='btn-margin-right'
  757. onClick={refresh}
  758. loading={loading}
  759. style={{ marginTop: 24 }}
  760. >
  761. {t('查询')}
  762. </Button>
  763. <Form.Section></Form.Section>
  764. </>
  765. </Form>
  766. <div style={{marginTop:10}}>
  767. <Select
  768. defaultValue='0'
  769. style={{ width: 120 }}
  770. onChange={(value) => {
  771. setLogType(parseInt(value));
  772. loadLogs(0, pageSize, parseInt(value));
  773. }}
  774. >
  775. <Select.Option value='0'>{t('全部')}</Select.Option>
  776. <Select.Option value='1'>{t('充值')}</Select.Option>
  777. <Select.Option value='2'>{t('消费')}</Select.Option>
  778. <Select.Option value='3'>{t('管理')}</Select.Option>
  779. <Select.Option value='4'>{t('系统')}</Select.Option>
  780. </Select>
  781. </div>
  782. <Table
  783. style={{ marginTop: 5 }}
  784. columns={columns}
  785. expandedRowRender={expandRowRender}
  786. expandRowByClick={true}
  787. dataSource={logs}
  788. rowKey="key"
  789. pagination={{
  790. currentPage: activePage,
  791. pageSize: pageSize,
  792. total: logCount,
  793. pageSizeOpts: [10, 20, 50, 100],
  794. showSizeChanger: true,
  795. onPageSizeChange: (size) => {
  796. handlePageSizeChange(size);
  797. },
  798. onPageChange: handlePageChange,
  799. }}
  800. />
  801. </Layout>
  802. </>
  803. );
  804. };
  805. export default LogsTable;