EditUser.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import React, { useEffect, useState } from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import { API, isMobile, showError, showSuccess } from '../../helpers';
  4. import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
  5. import Title from '@douyinfe/semi-ui/lib/es/typography/title';
  6. import {
  7. Button,
  8. Divider,
  9. Input,
  10. Modal,
  11. Select,
  12. SideSheet,
  13. Space,
  14. Spin,
  15. Typography,
  16. } from '@douyinfe/semi-ui';
  17. import { useTranslation } from 'react-i18next';
  18. const EditUser = (props) => {
  19. const userId = props.editingUser.id;
  20. const [loading, setLoading] = useState(true);
  21. const [addQuotaModalOpen, setIsModalOpen] = useState(false);
  22. const [addQuotaLocal, setAddQuotaLocal] = useState('');
  23. const [inputs, setInputs] = useState({
  24. username: '',
  25. display_name: '',
  26. password: '',
  27. github_id: '',
  28. wechat_id: '',
  29. email: '',
  30. quota: 0,
  31. group: 'default',
  32. });
  33. const [groupOptions, setGroupOptions] = useState([]);
  34. const {
  35. username,
  36. display_name,
  37. password,
  38. github_id,
  39. wechat_id,
  40. telegram_id,
  41. email,
  42. quota,
  43. group,
  44. } = inputs;
  45. const handleInputChange = (name, value) => {
  46. setInputs((inputs) => ({ ...inputs, [name]: value }));
  47. };
  48. const fetchGroups = async () => {
  49. try {
  50. let res = await API.get(`/api/group/`);
  51. setGroupOptions(
  52. res.data.data.map((group) => ({
  53. label: group,
  54. value: group,
  55. })),
  56. );
  57. } catch (error) {
  58. showError(error.message);
  59. }
  60. };
  61. const navigate = useNavigate();
  62. const handleCancel = () => {
  63. props.handleClose();
  64. };
  65. const loadUser = async () => {
  66. setLoading(true);
  67. let res = undefined;
  68. if (userId) {
  69. res = await API.get(`/api/user/${userId}`);
  70. } else {
  71. res = await API.get(`/api/user/self`);
  72. }
  73. const { success, message, data } = res.data;
  74. if (success) {
  75. data.password = '';
  76. setInputs(data);
  77. } else {
  78. showError(message);
  79. }
  80. setLoading(false);
  81. };
  82. useEffect(() => {
  83. loadUser().then();
  84. if (userId) {
  85. fetchGroups().then();
  86. }
  87. }, [props.editingUser.id]);
  88. const submit = async () => {
  89. setLoading(true);
  90. let res = undefined;
  91. if (userId) {
  92. let data = { ...inputs, id: parseInt(userId) };
  93. if (typeof data.quota === 'string') {
  94. data.quota = parseInt(data.quota);
  95. }
  96. res = await API.put(`/api/user/`, data);
  97. } else {
  98. res = await API.put(`/api/user/self`, inputs);
  99. }
  100. const { success, message } = res.data;
  101. if (success) {
  102. showSuccess('用户信息更新成功!');
  103. props.refresh();
  104. props.handleClose();
  105. } else {
  106. showError(message);
  107. }
  108. setLoading(false);
  109. };
  110. const addLocalQuota = () => {
  111. let newQuota = parseInt(quota) + parseInt(addQuotaLocal);
  112. setInputs((inputs) => ({ ...inputs, quota: newQuota }));
  113. };
  114. const openAddQuotaModal = () => {
  115. setAddQuotaLocal('0');
  116. setIsModalOpen(true);
  117. };
  118. const { t } = useTranslation();
  119. return (
  120. <>
  121. <SideSheet
  122. placement={'right'}
  123. title={<Title level={3}>{t('编辑用户')}</Title>}
  124. headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
  125. bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
  126. visible={props.visible}
  127. footer={
  128. <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
  129. <Space>
  130. <Button theme='solid' size={'large'} onClick={submit}>
  131. {t('提交')}
  132. </Button>
  133. <Button
  134. theme='solid'
  135. size={'large'}
  136. type={'tertiary'}
  137. onClick={handleCancel}
  138. >
  139. {t('取消')}
  140. </Button>
  141. </Space>
  142. </div>
  143. }
  144. closeIcon={null}
  145. onCancel={() => handleCancel()}
  146. width={isMobile() ? '100%' : 600}
  147. >
  148. <Spin spinning={loading}>
  149. <div style={{ marginTop: 20 }}>
  150. <Typography.Text>{t('用户名')}</Typography.Text>
  151. </div>
  152. <Input
  153. label={t('用户名')}
  154. name='username'
  155. placeholder={t('请输入新的用户名')}
  156. onChange={(value) => handleInputChange('username', value)}
  157. value={username}
  158. autoComplete='new-password'
  159. />
  160. <div style={{ marginTop: 20 }}>
  161. <Typography.Text>{t('密码')}</Typography.Text>
  162. </div>
  163. <Input
  164. label={t('密码')}
  165. name='password'
  166. type={'password'}
  167. placeholder={t('请输入新的密码,最短 8 位')}
  168. onChange={(value) => handleInputChange('password', value)}
  169. value={password}
  170. autoComplete='new-password'
  171. />
  172. <div style={{ marginTop: 20 }}>
  173. <Typography.Text>{t('显示名称')}</Typography.Text>
  174. </div>
  175. <Input
  176. label={t('显示名称')}
  177. name='display_name'
  178. placeholder={t('请输入新的显示名称')}
  179. onChange={(value) => handleInputChange('display_name', value)}
  180. value={display_name}
  181. autoComplete='new-password'
  182. />
  183. {userId && (
  184. <>
  185. <div style={{ marginTop: 20 }}>
  186. <Typography.Text>{t('分组')}</Typography.Text>
  187. </div>
  188. <Select
  189. placeholder={t('请选择分组')}
  190. name='group'
  191. fluid
  192. search
  193. selection
  194. allowAdditions
  195. additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
  196. onChange={(value) => handleInputChange('group', value)}
  197. value={inputs.group}
  198. autoComplete='new-password'
  199. optionList={groupOptions}
  200. />
  201. <div style={{ marginTop: 20 }}>
  202. <Typography.Text>{`${t('剩余额度')}${renderQuotaWithPrompt(quota)}`}</Typography.Text>
  203. </div>
  204. <Space>
  205. <Input
  206. name='quota'
  207. placeholder={t('请输入新的剩余额度')}
  208. onChange={(value) => handleInputChange('quota', value)}
  209. value={quota}
  210. type={'number'}
  211. autoComplete='new-password'
  212. />
  213. <Button onClick={openAddQuotaModal}>{t('添加额度')}</Button>
  214. </Space>
  215. </>
  216. )}
  217. <Divider style={{ marginTop: 20 }}>{t('以下信息不可修改')}</Divider>
  218. <div style={{ marginTop: 20 }}>
  219. <Typography.Text>{t('已绑定的 GitHub 账户')}</Typography.Text>
  220. </div>
  221. <Input
  222. name='github_id'
  223. value={github_id}
  224. autoComplete='new-password'
  225. placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
  226. readonly
  227. />
  228. <div style={{ marginTop: 20 }}>
  229. <Typography.Text>{t('已绑定的微信账户')}</Typography.Text>
  230. </div>
  231. <Input
  232. name='wechat_id'
  233. value={wechat_id}
  234. autoComplete='new-password'
  235. placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
  236. readonly
  237. />
  238. <div style={{ marginTop: 20 }}>
  239. <Typography.Text>{t('已绑定的邮箱账户')}</Typography.Text>
  240. </div>
  241. <Input
  242. name='email'
  243. value={email}
  244. autoComplete='new-password'
  245. placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
  246. readonly
  247. />
  248. <div style={{ marginTop: 20 }}>
  249. <Typography.Text>{t('已绑定的Telegram账户')}</Typography.Text>
  250. </div>
  251. <Input
  252. name='telegram_id'
  253. value={telegram_id}
  254. autoComplete='new-password'
  255. placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
  256. readonly
  257. />
  258. </Spin>
  259. </SideSheet>
  260. <Modal
  261. centered={true}
  262. visible={addQuotaModalOpen}
  263. onOk={() => {
  264. addLocalQuota();
  265. setIsModalOpen(false);
  266. }}
  267. onCancel={() => setIsModalOpen(false)}
  268. closable={null}
  269. >
  270. <div style={{ marginTop: 20 }}>
  271. <Typography.Text>{`${t('新额度')}${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}</Typography.Text>
  272. </div>
  273. <Input
  274. name='addQuotaLocal'
  275. placeholder={t('需要添加的额度(支持负数)')}
  276. onChange={(value) => {
  277. setAddQuotaLocal(value);
  278. }}
  279. value={addQuotaLocal}
  280. type={'number'}
  281. autoComplete='new-password'
  282. />
  283. </Modal>
  284. </>
  285. );
  286. };
  287. export default EditUser;