Просмотр исходного кода

♻️Refactor: Redemptions Page

Apple\Apple 9 месяцев назад
Родитель
Сommit
9a6c540013

+ 237 - 150
web/src/components/RedemptionsTable.js

@@ -11,17 +11,33 @@ import { ITEMS_PER_PAGE } from '../constants';
 import { renderQuota } from '../helpers/render';
 import {
   Button,
+  Card,
   Divider,
-  Form,
+  Dropdown,
+  Input,
   Modal,
-  Popconfirm,
   Popover,
+  Space,
   Table,
   Tag,
+  Typography,
 } from '@douyinfe/semi-ui';
+import {
+  IconPlus,
+  IconCopy,
+  IconSearch,
+  IconEyeOpened,
+  IconEdit,
+  IconDelete,
+  IconStop,
+  IconPlay,
+  IconMore,
+} from '@douyinfe/semi-icons';
 import EditRedemption from '../pages/Redemption/EditRedemption';
 import { useTranslation } from 'react-i18next';
 
+const { Text } = Typography;
+
 function renderTimestamp(timestamp) {
   return <>{timestamp2string(timestamp)}</>;
 }
@@ -33,25 +49,25 @@ const RedemptionsTable = () => {
     switch (status) {
       case 1:
         return (
-          <Tag color='green' size='large'>
+          <Tag color='green' size='large' shape='circle'>
             {t('未使用')}
           </Tag>
         );
       case 2:
         return (
-          <Tag color='red' size='large'>
+          <Tag color='red' size='large' shape='circle'>
             {t('已禁用')}
           </Tag>
         );
       case 3:
         return (
-          <Tag color='grey' size='large'>
+          <Tag color='grey' size='large' shape='circle'>
             {t('已使用')}
           </Tag>
         );
       default:
         return (
-          <Tag color='black' size='large'>
+          <Tag color='black' size='large' shape='circle'>
             {t('未知状态')}
           </Tag>
         );
@@ -99,76 +115,107 @@ const RedemptionsTable = () => {
     {
       title: '',
       dataIndex: 'operate',
-      render: (text, record, index) => (
-        <div>
-          <Popover content={record.key} style={{ padding: 20 }} position='top'>
-            <Button theme='light' type='tertiary' style={{ marginRight: 1 }}>
-              {t('查看')}
-            </Button>
-          </Popover>
-          <Button
-            theme='light'
-            type='secondary'
-            style={{ marginRight: 1 }}
-            onClick={async (text) => {
-              await copyText(record.key);
-            }}
-          >
-            {t('复制')}
-          </Button>
-          <Popconfirm
-            title={t('确定是否要删除此兑换码?')}
-            content={t('此修改将不可逆')}
-            okType={'danger'}
-            position={'left'}
-            onConfirm={() => {
-              manageRedemption(record.id, 'delete', record).then(() => {
-                removeRecord(record.key);
+      render: (text, record, index) => {
+        // 创建更多操作的下拉菜单项
+        const moreMenuItems = [
+          {
+            node: 'item',
+            name: t('删除'),
+            icon: <IconDelete />,
+            type: 'danger',
+            onClick: () => {
+              Modal.confirm({
+                title: t('确定是否要删除此兑换码?'),
+                content: t('此修改将不可逆'),
+                onOk: () => {
+                  manageRedemption(record.id, 'delete', record).then(() => {
+                    removeRecord(record.key);
+                  });
+                },
               });
-            }}
-          >
-            <Button theme='light' type='danger' style={{ marginRight: 1 }}>
-              {t('删除')}
-            </Button>
-          </Popconfirm>
-          {record.status === 1 ? (
+            },
+          }
+        ];
+
+        // 动态添加启用/禁用按钮
+        if (record.status === 1) {
+          moreMenuItems.push({
+            node: 'item',
+            name: t('禁用'),
+            icon: <IconStop />,
+            type: 'warning',
+            onClick: () => {
+              manageRedemption(record.id, 'disable', record);
+            },
+          });
+        } else {
+          moreMenuItems.push({
+            node: 'item',
+            name: t('启用'),
+            icon: <IconPlay />,
+            type: 'secondary',
+            onClick: () => {
+              manageRedemption(record.id, 'enable', record);
+            },
+            disabled: record.status === 3,
+          });
+        }
+
+        return (
+          <Space>
+            <Popover content={record.key} style={{ padding: 20 }} position='top'>
+              <Button
+                icon={<IconEyeOpened />}
+                theme='light'
+                type='tertiary'
+                size="small"
+                className="!rounded-full"
+              >
+                {t('查看')}
+              </Button>
+            </Popover>
             <Button
+              icon={<IconCopy />}
               theme='light'
-              type='warning'
-              style={{ marginRight: 1 }}
+              type='secondary'
+              size="small"
+              className="!rounded-full"
               onClick={async () => {
-                manageRedemption(record.id, 'disable', record);
+                await copyText(record.key);
               }}
             >
-              {t('禁用')}
+              {t('复制')}
             </Button>
-          ) : (
             <Button
+              icon={<IconEdit />}
               theme='light'
-              type='secondary'
-              style={{ marginRight: 1 }}
-              onClick={async () => {
-                manageRedemption(record.id, 'enable', record);
+              type='tertiary'
+              size="small"
+              className="!rounded-full"
+              onClick={() => {
+                setEditingRedemption(record);
+                setShowEdit(true);
               }}
-              disabled={record.status === 3}
+              disabled={record.status !== 1}
             >
-              {t('启用')}
+              {t('编辑')}
             </Button>
-          )}
-          <Button
-            theme='light'
-            type='tertiary'
-            style={{ marginRight: 1 }}
-            onClick={() => {
-              setEditingRedemption(record);
-              setShowEdit(true);
-            }}
-            disabled={record.status !== 1}
-          >
-            {t('编辑')}
-          </Button>
-        </div>
-      ),
+            <Dropdown
+              trigger='click'
+              position='bottomRight'
+              menu={moreMenuItems}
+            >
+              <Button
+                icon={<IconMore />}
+                theme='light'
+                type='tertiary'
+                size="small"
+                className="!rounded-full"
+              />
+            </Dropdown>
+          </Space>
+        );
+      },
     },
   ];
 
@@ -187,6 +234,11 @@ const RedemptionsTable = () => {
 
   const closeEdit = () => {
     setShowEdit(false);
+    setTimeout(() => {
+      setEditingRedemption({
+        id: undefined,
+      });
+    }, 500);
   };
 
   const setRedemptionFormat = (redeptions) => {
@@ -225,8 +277,11 @@ const RedemptionsTable = () => {
     if (await copy(text)) {
       showSuccess(t('已复制到剪贴板!'));
     } else {
-      // setSearchKeyword(text);
-      Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
+      Modal.error({
+        title: t('无法复制到剪贴板,请手动复制'),
+        content: text,
+        size: 'large'
+      });
     }
   };
 
@@ -245,13 +300,14 @@ const RedemptionsTable = () => {
       .catch((reason) => {
         showError(reason);
       });
-  }, []);
+  }, [pageSize]);
 
   const refresh = async () => {
     await loadRedemptions(activePage - 1, pageSize);
   };
 
   const manageRedemption = async (id, action, record) => {
+    setLoading(true);
     let data = { id };
     let res;
     switch (action) {
@@ -272,7 +328,6 @@ const RedemptionsTable = () => {
       showSuccess(t('操作成功完成!'));
       let redemption = res.data.data;
       let newRedemptions = [...redemptions];
-      // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
       if (action === 'delete') {
       } else {
         record.status = redemption.status;
@@ -281,6 +336,7 @@ const RedemptionsTable = () => {
     } else {
       showError(message);
     }
+    setLoading(false);
   };
 
   const searchRedemptions = async (keyword, page, pageSize) => {
@@ -333,8 +389,8 @@ const RedemptionsTable = () => {
 
   let pageData = redemptions;
   const rowSelection = {
-    onSelect: (record, selected) => {},
-    onSelectAll: (selected, selectedRows) => {},
+    onSelect: (record, selected) => { },
+    onSelectAll: (selected, selectedRows) => { },
     onChange: (selectedRowKeys, selectedRows) => {
       setSelectedKeys(selectedRows);
     },
@@ -352,6 +408,80 @@ const RedemptionsTable = () => {
     }
   };
 
+  const renderHeader = () => (
+    <div className="flex flex-col w-full">
+      <div className="mb-2">
+        <div className="flex items-center text-orange-500">
+          <IconEyeOpened className="mr-2" />
+          <Text>{t('兑换码可以批量生成和分发,适合用于推广活动或批量充值。')}</Text>
+        </div>
+      </div>
+
+      <Divider margin="12px" />
+
+      <div className="flex flex-col md:flex-row justify-between items-center gap-4 w-full">
+        <div className="flex gap-2 w-full md:w-auto order-2 md:order-1">
+          <Button
+            theme='light'
+            type='primary'
+            icon={<IconPlus />}
+            className="!rounded-full w-full md:w-auto"
+            onClick={() => {
+              setEditingRedemption({
+                id: undefined,
+              });
+              setShowEdit(true);
+            }}
+          >
+            {t('添加兑换码')}
+          </Button>
+          <Button
+            type='warning'
+            icon={<IconCopy />}
+            className="!rounded-full w-full md:w-auto"
+            onClick={async () => {
+              if (selectedKeys.length === 0) {
+                showError(t('请至少选择一个兑换码!'));
+                return;
+              }
+              let keys = '';
+              for (let i = 0; i < selectedKeys.length; i++) {
+                keys +=
+                  selectedKeys[i].name + '    ' + selectedKeys[i].key + '\n';
+              }
+              await copyText(keys);
+            }}
+          >
+            {t('复制所选兑换码到剪贴板')}
+          </Button>
+        </div>
+
+        <div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2">
+          <div className="relative w-full md:w-64">
+            <Input
+              prefix={<IconSearch />}
+              placeholder={t('关键字(id或者名称)')}
+              value={searchKeyword}
+              onChange={handleKeywordChange}
+              className="!rounded-full"
+              showClear
+            />
+          </div>
+          <Button
+            type="primary"
+            onClick={() => {
+              searchRedemptions(searchKeyword, 1, pageSize).then();
+            }}
+            loading={searching}
+            className="!rounded-full w-full md:w-auto"
+          >
+            {t('查询')}
+          </Button>
+        </div>
+      </div>
+    </div>
+  );
+
   return (
     <>
       <EditRedemption
@@ -360,88 +490,45 @@ const RedemptionsTable = () => {
         visiable={showEdit}
         handleClose={closeEdit}
       ></EditRedemption>
-      <Form
-        onSubmit={() => {
-          searchRedemptions(searchKeyword, activePage, pageSize).then();
-        }}
+
+      <Card
+        className="!rounded-2xl overflow-hidden"
+        title={renderHeader()}
+        shadows='hover'
       >
-        <Form.Input
-          label={t('搜索关键字')}
-          field='keyword'
-          icon='search'
-          iconPosition='left'
-          placeholder={t('关键字(id或者名称)')}
-          value={searchKeyword}
-          loading={searching}
-          onChange={handleKeywordChange}
-        />
-      </Form>
-      <Divider style={{ margin: '5px 0 15px 0' }} />
-      <div>
-        <Button
-          theme='light'
-          type='primary'
-          style={{ marginRight: 8 }}
-          onClick={() => {
-            setEditingRedemption({
-              id: undefined,
-            });
-            setShowEdit(true);
-          }}
-        >
-          {t('添加兑换码')}
-        </Button>
-        <Button
-          label={t('复制所选兑换码')}
-          type='warning'
-          onClick={async () => {
-            if (selectedKeys.length === 0) {
-              showError(t('请至少选择一个兑换码!'));
-              return;
-            }
-            let keys = '';
-            for (let i = 0; i < selectedKeys.length; i++) {
-              keys +=
-                selectedKeys[i].name + '    ' + selectedKeys[i].key + '\n';
-            }
-            await copyText(keys);
+        <Table
+          columns={columns}
+          dataSource={pageData}
+          pagination={{
+            currentPage: activePage,
+            pageSize: pageSize,
+            total: tokenCount,
+            showSizeChanger: true,
+            pageSizeOptions: [10, 20, 50, 100],
+            formatPageText: (page) =>
+              t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
+                start: page.currentStart,
+                end: page.currentEnd,
+                total: tokenCount,
+              }),
+            onPageSizeChange: (size) => {
+              setPageSize(size);
+              setActivePage(1);
+              if (searchKeyword === '') {
+                loadRedemptions(1, size).then();
+              } else {
+                searchRedemptions(searchKeyword, 1, size).then();
+              }
+            },
+            onPageChange: handlePageChange,
           }}
-        >
-          {t('复制所选兑换码到剪贴板')}
-        </Button>
-      </div>
-
-      <Table
-        style={{ marginTop: 20 }}
-        columns={columns}
-        dataSource={pageData}
-        pagination={{
-          currentPage: activePage,
-          pageSize: pageSize,
-          total: tokenCount,
-          showSizeChanger: true,
-          pageSizeOpts: [10, 20, 50, 100],
-          formatPageText: (page) =>
-            t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
-              start: page.currentStart,
-              end: page.currentEnd,
-              total: tokenCount,
-            }),
-          onPageSizeChange: (size) => {
-            setPageSize(size);
-            setActivePage(1);
-            if (searchKeyword === '') {
-              loadRedemptions(1, size).then();
-            } else {
-              searchRedemptions(searchKeyword, 1, size).then();
-            }
-          },
-          onPageChange: handlePageChange,
-        }}
-        loading={loading}
-        rowSelection={rowSelection}
-        onRow={handleRow}
-      ></Table>
+          loading={loading}
+          rowSelection={rowSelection}
+          onRow={handleRow}
+          className="rounded-xl overflow-hidden"
+          size="middle"
+        ></Table>
+      </Card>
     </>
   );
 };

+ 2 - 1
web/src/i18n/locales/en.json

@@ -1432,5 +1432,6 @@
   "30个": "30 items",
   "100个": "100 items",
   "Midjourney 任务记录": "Midjourney Task Records",
-  "任务记录": "Task Records"
+  "任务记录": "Task Records",
+  "兑换码可以批量生成和分发,适合用于推广活动或批量充值。": "Redemption codes can be batch generated and distributed, suitable for promotion activities or bulk recharge."
 }

+ 1 - 11
web/src/pages/Redemption/index.js

@@ -1,20 +1,10 @@
 import React from 'react';
 import RedemptionsTable from '../../components/RedemptionsTable';
-import { Layout } from '@douyinfe/semi-ui';
-import { useTranslation } from 'react-i18next';
 
 const Redemption = () => {
-  const { t } = useTranslation();
   return (
     <>
-      <Layout>
-        <Layout.Header>
-          <h3>{t('管理兑换码')}</h3>
-        </Layout.Header>
-        <Layout.Content>
-          <RedemptionsTable />
-        </Layout.Content>
-      </Layout>
+      <RedemptionsTable />
     </>
   );
 };

+ 1 - 2
web/src/pages/Token/index.js

@@ -1,8 +1,7 @@
 import React from 'react';
 import TokensTable from '../../components/TokensTable';
-import { useTranslation } from 'react-i18next';
+
 const Token = () => {
-  const { t } = useTranslation();
   return (
     <>
       <TokensTable />