EditRedemption.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import React, { useEffect, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import {
  4. API,
  5. downloadTextAsFile,
  6. isMobile,
  7. showError,
  8. showSuccess,
  9. renderQuota,
  10. renderQuotaWithPrompt
  11. } from '../../helpers';
  12. import {
  13. AutoComplete,
  14. Button,
  15. Input,
  16. Modal,
  17. SideSheet,
  18. Space,
  19. Spin,
  20. Typography,
  21. Card,
  22. Tag,
  23. Form,
  24. DatePicker,
  25. Avatar,
  26. } from '@douyinfe/semi-ui';
  27. import {
  28. IconCreditCard,
  29. IconSave,
  30. IconClose,
  31. IconGift,
  32. } from '@douyinfe/semi-icons';
  33. const { Text, Title } = Typography;
  34. const EditRedemption = (props) => {
  35. const { t } = useTranslation();
  36. const isEdit = props.editingRedemption.id !== undefined;
  37. const [loading, setLoading] = useState(isEdit);
  38. const originInputs = {
  39. name: '',
  40. quota: 100000,
  41. count: 1,
  42. expired_time: 0,
  43. };
  44. const [inputs, setInputs] = useState(originInputs);
  45. const { name, quota, count, expired_time } = inputs;
  46. const handleCancel = () => {
  47. props.handleClose();
  48. };
  49. const handleInputChange = (name, value) => {
  50. setInputs((inputs) => ({ ...inputs, [name]: value }));
  51. };
  52. const loadRedemption = async () => {
  53. setLoading(true);
  54. let res = await API.get(`/api/redemption/${props.editingRedemption.id}`);
  55. const { success, message, data } = res.data;
  56. if (success) {
  57. setInputs(data);
  58. } else {
  59. showError(message);
  60. }
  61. setLoading(false);
  62. };
  63. useEffect(() => {
  64. if (isEdit) {
  65. loadRedemption().then(() => {
  66. // console.log(inputs);
  67. });
  68. } else {
  69. setInputs(originInputs);
  70. }
  71. }, [props.editingRedemption.id]);
  72. const submit = async () => {
  73. let name = inputs.name;
  74. if (!isEdit && inputs.name === '') {
  75. // set default name
  76. name = renderQuota(quota);
  77. }
  78. setLoading(true);
  79. let localInputs = inputs;
  80. localInputs.count = parseInt(localInputs.count);
  81. localInputs.quota = parseInt(localInputs.quota);
  82. localInputs.name = name;
  83. if (localInputs.expired_time === null || localInputs.expired_time === undefined) {
  84. localInputs.expired_time = 0;
  85. }
  86. let res;
  87. if (isEdit) {
  88. res = await API.put(`/api/redemption/`, {
  89. ...localInputs,
  90. id: parseInt(props.editingRedemption.id),
  91. });
  92. } else {
  93. res = await API.post(`/api/redemption/`, {
  94. ...localInputs,
  95. });
  96. }
  97. const { success, message, data } = res.data;
  98. if (success) {
  99. if (isEdit) {
  100. showSuccess(t('兑换码更新成功!'));
  101. props.refresh();
  102. props.handleClose();
  103. } else {
  104. showSuccess(t('兑换码创建成功!'));
  105. setInputs(originInputs);
  106. props.refresh();
  107. props.handleClose();
  108. }
  109. } else {
  110. showError(message);
  111. }
  112. if (!isEdit && data) {
  113. let text = '';
  114. for (let i = 0; i < data.length; i++) {
  115. text += data[i] + '\n';
  116. }
  117. Modal.confirm({
  118. title: t('兑换码创建成功'),
  119. content: (
  120. <div>
  121. <p>{t('兑换码创建成功,是否下载兑换码?')}</p>
  122. <p>{t('兑换码将以文本文件的形式下载,文件名为兑换码的名称。')}</p>
  123. </div>
  124. ),
  125. onOk: () => {
  126. downloadTextAsFile(text, `${inputs.name}.txt`);
  127. },
  128. });
  129. }
  130. setLoading(false);
  131. };
  132. return (
  133. <>
  134. <SideSheet
  135. placement={isEdit ? 'right' : 'left'}
  136. title={
  137. <Space>
  138. {isEdit ?
  139. <Tag color="blue" shape="circle">{t('更新')}</Tag> :
  140. <Tag color="green" shape="circle">{t('新建')}</Tag>
  141. }
  142. <Title heading={4} className="m-0">
  143. {isEdit ? t('更新兑换码信息') : t('创建新的兑换码')}
  144. </Title>
  145. </Space>
  146. }
  147. headerStyle={{
  148. borderBottom: '1px solid var(--semi-color-border)',
  149. padding: '24px'
  150. }}
  151. bodyStyle={{ padding: '0' }}
  152. visible={props.visiable}
  153. width={isMobile() ? '100%' : 600}
  154. footer={
  155. <div className="flex justify-end bg-white">
  156. <Space>
  157. <Button
  158. theme="solid"
  159. className="!rounded-full"
  160. onClick={submit}
  161. icon={<IconSave />}
  162. loading={loading}
  163. >
  164. {t('提交')}
  165. </Button>
  166. <Button
  167. theme="light"
  168. className="!rounded-full"
  169. type="primary"
  170. onClick={handleCancel}
  171. icon={<IconClose />}
  172. >
  173. {t('取消')}
  174. </Button>
  175. </Space>
  176. </div>
  177. }
  178. closeIcon={null}
  179. onCancel={() => handleCancel()}
  180. >
  181. <Spin spinning={loading}>
  182. <div className="p-6">
  183. <Card className="!rounded-2xl shadow-sm border-0 mb-6">
  184. {/* Header: Basic Info */}
  185. <div className="flex items-center mb-2">
  186. <Avatar size="small" color="blue" className="mr-2 shadow-md">
  187. <IconGift size={16} />
  188. </Avatar>
  189. <div>
  190. <Text className="text-lg font-medium">{t('基本信息')}</Text>
  191. <div className="text-xs text-gray-600">{t('设置兑换码的基本信息')}</div>
  192. </div>
  193. </div>
  194. <div className="space-y-4">
  195. <div>
  196. <Text strong className="block mb-2">{t('名称')}</Text>
  197. <Input
  198. placeholder={t('请输入名称')}
  199. onChange={(value) => handleInputChange('name', value)}
  200. value={name}
  201. autoComplete="new-password"
  202. className="!rounded-lg"
  203. showClear
  204. required={!isEdit}
  205. />
  206. </div>
  207. <div>
  208. <Text strong className="block mb-2">{t('过期时间')}</Text>
  209. <DatePicker
  210. type="dateTime"
  211. placeholder={t('选择过期时间(可选,留空为永久)')}
  212. showClear
  213. value={expired_time ? new Date(expired_time * 1000) : null}
  214. onChange={(value) => {
  215. if (value === null || value === undefined) {
  216. handleInputChange('expired_time', 0);
  217. } else {
  218. const timestamp = Math.floor(value.getTime() / 1000);
  219. handleInputChange('expired_time', timestamp);
  220. }
  221. }}
  222. className="!rounded-lg w-full"
  223. />
  224. </div>
  225. </div>
  226. </Card>
  227. <Card className="!rounded-2xl shadow-sm border-0">
  228. {/* Header: Quota Settings */}
  229. <div className="flex items-center mb-2">
  230. <Avatar size="small" color="green" className="mr-2 shadow-md">
  231. <IconCreditCard size={16} />
  232. </Avatar>
  233. <div>
  234. <Text className="text-lg font-medium">{t('额度设置')}</Text>
  235. <div className="text-xs text-gray-600">{t('设置兑换码的额度和数量')}</div>
  236. </div>
  237. </div>
  238. <div className="space-y-4">
  239. <div>
  240. <div className="flex justify-between mb-2">
  241. <Text strong>{t('额度')}</Text>
  242. <Text type="tertiary">{renderQuotaWithPrompt(quota)}</Text>
  243. </div>
  244. <AutoComplete
  245. placeholder={t('请输入额度')}
  246. onChange={(value) => handleInputChange('quota', value)}
  247. value={quota}
  248. autoComplete="new-password"
  249. type="number"
  250. className="w-full !rounded-lg"
  251. data={[
  252. { value: 500000, label: '1$' },
  253. { value: 5000000, label: '10$' },
  254. { value: 25000000, label: '50$' },
  255. { value: 50000000, label: '100$' },
  256. { value: 250000000, label: '500$' },
  257. { value: 500000000, label: '1000$' },
  258. ]}
  259. />
  260. </div>
  261. {!isEdit && (
  262. <div>
  263. <Text strong className="block mb-2">{t('生成数量')}</Text>
  264. <Input
  265. placeholder={t('请输入生成数量')}
  266. onChange={(value) => handleInputChange('count', value)}
  267. value={count}
  268. autoComplete="new-password"
  269. type="number"
  270. className="!rounded-lg"
  271. />
  272. </div>
  273. )}
  274. </div>
  275. </Card>
  276. </div>
  277. </Spin>
  278. </SideSheet>
  279. </>
  280. );
  281. };
  282. export default EditRedemption;