LogsTable.js 23 KB

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