EditToken.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import React, {useEffect, useRef, useState} from 'react';
  2. import {useParams, useNavigate} from 'react-router-dom';
  3. import {API, isMobile, showError, showSuccess, timestamp2string} from '../../helpers';
  4. import {renderQuota, renderQuotaWithPrompt} from '../../helpers/render';
  5. import {Layout, SideSheet, Button, Space, Spin, Banner, Input, DatePicker, AutoComplete, Typography} from "@douyinfe/semi-ui";
  6. import Title from "@douyinfe/semi-ui/lib/es/typography/title";
  7. import {Divider} from "semantic-ui-react";
  8. const EditToken = (props) => {
  9. const isEdit = props.editingToken.id !== undefined;
  10. const [loading, setLoading] = useState(isEdit);
  11. const originInputs = {
  12. name: '',
  13. remain_quota: isEdit ? 0 : 500000,
  14. expired_time: -1,
  15. unlimited_quota: false
  16. };
  17. const [inputs, setInputs] = useState(originInputs);
  18. const {name, remain_quota, expired_time, unlimited_quota} = inputs;
  19. // const [visible, setVisible] = useState(false);
  20. const navigate = useNavigate();
  21. const handleInputChange = (name, value) => {
  22. setInputs((inputs) => ({...inputs, [name]: value}));
  23. };
  24. const handleCancel = () => {
  25. props.handleClose();
  26. }
  27. const setExpiredTime = (month, day, hour, minute) => {
  28. let now = new Date();
  29. let timestamp = now.getTime() / 1000;
  30. let seconds = month * 30 * 24 * 60 * 60;
  31. seconds += day * 24 * 60 * 60;
  32. seconds += hour * 60 * 60;
  33. seconds += minute * 60;
  34. if (seconds !== 0) {
  35. timestamp += seconds;
  36. setInputs({...inputs, expired_time: timestamp2string(timestamp)});
  37. } else {
  38. setInputs({...inputs, expired_time: -1});
  39. }
  40. };
  41. const setUnlimitedQuota = () => {
  42. setInputs({...inputs, unlimited_quota: !unlimited_quota});
  43. };
  44. const loadToken = async () => {
  45. setLoading(true);
  46. let res = await API.get(`/api/token/${props.editingToken.id}`);
  47. const {success, message, data} = res.data;
  48. if (success) {
  49. if (data.expired_time !== -1) {
  50. data.expired_time = timestamp2string(data.expired_time);
  51. }
  52. setInputs(data);
  53. } else {
  54. showError(message);
  55. }
  56. setLoading(false);
  57. };
  58. useEffect(() => {
  59. if (isEdit) {
  60. loadToken().then(
  61. () => {
  62. // console.log(inputs);
  63. }
  64. );
  65. } else {
  66. setInputs(originInputs);
  67. }
  68. }, [props.editingToken.id]);
  69. // 新增 state 变量 tokenCount 来记录用户想要创建的令牌数量,默认为 1
  70. const [tokenCount, setTokenCount] = useState(1);
  71. // 新增处理 tokenCount 变化的函数
  72. const handleTokenCountChange = (value) => {
  73. // 确保用户输入的是正整数
  74. const count = parseInt(value, 10);
  75. if (!isNaN(count) && count > 0) {
  76. setTokenCount(count);
  77. }
  78. };
  79. // 生成一个随机的四位字母数字字符串
  80. const generateRandomSuffix = () => {
  81. const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  82. let result = '';
  83. for (let i = 0; i < 6; i++) {
  84. result += characters.charAt(Math.floor(Math.random() * characters.length));
  85. }
  86. return result;
  87. };
  88. const submit = async () => {
  89. setLoading(true);
  90. if (isEdit) {
  91. // 编辑令牌的逻辑保持不变
  92. let localInputs = {...inputs};
  93. localInputs.remain_quota = parseInt(localInputs.remain_quota);
  94. if (localInputs.expired_time !== -1) {
  95. let time = Date.parse(localInputs.expired_time);
  96. if (isNaN(time)) {
  97. showError('过期时间格式错误!');
  98. setLoading(false);
  99. return;
  100. }
  101. localInputs.expired_time = Math.ceil(time / 1000);
  102. }
  103. let res = await API.put(`/api/token/`, {...localInputs, id: parseInt(props.editingToken.id)});
  104. const {success, message} = res.data;
  105. if (success) {
  106. showSuccess('令牌更新成功!');
  107. props.refresh();
  108. props.handleClose();
  109. } else {
  110. showError(message);
  111. }
  112. } else {
  113. // 处理新增多个令牌的情况
  114. let successCount = 0; // 记录成功创建的令牌数量
  115. for (let i = 0; i < tokenCount; i++) {
  116. let localInputs = {...inputs};
  117. if (i !== 0) {
  118. // 如果用户想要创建多个令牌,则给每个令牌一个序号后缀
  119. localInputs.name = `${inputs.name}-${generateRandomSuffix()}`;
  120. }
  121. localInputs.remain_quota = parseInt(localInputs.remain_quota);
  122. if (localInputs.expired_time !== -1) {
  123. let time = Date.parse(localInputs.expired_time);
  124. if (isNaN(time)) {
  125. showError('过期时间格式错误!');
  126. setLoading(false);
  127. break;
  128. }
  129. localInputs.expired_time = Math.ceil(time / 1000);
  130. }
  131. let res = await API.post(`/api/token/`, localInputs);
  132. const {success, message} = res.data;
  133. if (success) {
  134. successCount++;
  135. } else {
  136. showError(message);
  137. break; // 如果创建失败,终止循环
  138. }
  139. }
  140. if (successCount > 0) {
  141. showSuccess(`${successCount}个令牌创建成功,请在列表页面点击复制获取令牌!`);
  142. }
  143. }
  144. setLoading(false);
  145. setInputs(originInputs); // 重置表单
  146. setTokenCount(1); // 重置数量为默认值
  147. props.refresh();
  148. props.handleClose();
  149. };
  150. return (
  151. <>
  152. <SideSheet
  153. placement={isEdit ? 'right' : 'left'}
  154. title={<Title level={3}>{isEdit ? '更新令牌信息' : '创建新的令牌'}</Title>}
  155. headerStyle={{borderBottom: '1px solid var(--semi-color-border)'}}
  156. bodyStyle={{borderBottom: '1px solid var(--semi-color-border)'}}
  157. visible={props.visiable}
  158. footer={
  159. <div style={{display: 'flex', justifyContent: 'flex-end'}}>
  160. <Space>
  161. <Button theme='solid' size={'large'} onClick={submit}>提交</Button>
  162. <Button theme='solid' size={'large'} type={'tertiary'} onClick={handleCancel}>取消</Button>
  163. </Space>
  164. </div>
  165. }
  166. closeIcon={null}
  167. onCancel={() => handleCancel()}
  168. width={isMobile() ? '100%' : 600}
  169. >
  170. <Spin spinning={loading}>
  171. <Input
  172. style={{marginTop: 20}}
  173. label='名称'
  174. name='name'
  175. placeholder={'请输入名称'}
  176. onChange={(value) => handleInputChange('name', value)}
  177. value={name}
  178. autoComplete='new-password'
  179. required={!isEdit}
  180. />
  181. <Divider/>
  182. <DatePicker
  183. label='过期时间'
  184. name='expired_time'
  185. placeholder={'请选择过期时间'}
  186. onChange={(value) => handleInputChange('expired_time', value)}
  187. value={expired_time}
  188. autoComplete='new-password'
  189. type='dateTime'
  190. />
  191. <div style={{marginTop: 20}}>
  192. <Space>
  193. <Button type={'tertiary'} onClick={() => {
  194. setExpiredTime(0, 0, 0, 0);
  195. }}>永不过期</Button>
  196. <Button type={'tertiary'} onClick={() => {
  197. setExpiredTime(0, 0, 1, 0);
  198. }}>一小时</Button>
  199. <Button type={'tertiary'} onClick={() => {
  200. setExpiredTime(1, 0, 0, 0);
  201. }}>一个月</Button>
  202. <Button type={'tertiary'} onClick={() => {
  203. setExpiredTime(0, 1, 0, 0);
  204. }}>一天</Button>
  205. </Space>
  206. </div>
  207. <Divider/>
  208. <Banner type={'warning'}
  209. description={'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'}></Banner>
  210. <div style={{marginTop: 20}}>
  211. <Typography.Text>{`额度${renderQuotaWithPrompt(remain_quota)}`}</Typography.Text>
  212. </div>
  213. <AutoComplete
  214. style={{marginTop: 8}}
  215. name='remain_quota'
  216. placeholder={'请输入额度'}
  217. onChange={(value) => handleInputChange('remain_quota', value)}
  218. value={remain_quota}
  219. autoComplete='new-password'
  220. type='number'
  221. position={'top'}
  222. data={[
  223. {value: 500000, label: '1$'},
  224. {value: 5000000, label: '10$'},
  225. {value: 25000000, label: '50$'},
  226. {value: 50000000, label: '100$'},
  227. {value: 250000000, label: '500$'},
  228. {value: 500000000, label: '1000$'},
  229. ]}
  230. disabled={unlimited_quota}
  231. />
  232. <div style={{marginTop: 20}}>
  233. <Typography.Text>新建数量</Typography.Text>
  234. </div>
  235. {!isEdit && (
  236. <AutoComplete
  237. style={{ marginTop: 8 }}
  238. label='数量'
  239. placeholder={'请选择或输入创建令牌的数量'}
  240. onChange={(value) => handleTokenCountChange(value)}
  241. onSelect={(value) => handleTokenCountChange(value)}
  242. value={tokenCount.toString()}
  243. autoComplete='off'
  244. type='number'
  245. data={[
  246. { value: 10, label: '10个' },
  247. { value: 20, label: '20个' },
  248. { value: 30, label: '30个' },
  249. { value: 100, label: '100个' },
  250. ]}
  251. disabled={unlimited_quota}
  252. />
  253. )}
  254. <div>
  255. <Button style={{marginTop: 8}} type={'warning'} onClick={() => {
  256. setUnlimitedQuota();
  257. }}>{unlimited_quota ? '取消无限额度' : '设为无限额度'}</Button>
  258. </div>
  259. </Spin>
  260. </SideSheet>
  261. </>
  262. );
  263. };
  264. export default EditToken;