From 44d9eb9d05e52c4fbebe0122546d20b248a169b1 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Fri, 10 Nov 2023 00:10:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=85=91=E6=8D=A2=E7=A0=81?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/token.go | 3 +- model/token.go | 8 +- web/src/components/RedemptionsTable.js | 659 +++++++++++---------- web/src/components/TokensTable.js | 16 +- web/src/pages/Redemption/EditRedemption.js | 145 +++-- web/src/pages/Redemption/index.js | 14 +- web/src/pages/Token/EditToken.js | 4 +- web/src/pages/Token/index.js | 9 +- 8 files changed, 490 insertions(+), 368 deletions(-) diff --git a/controller/token.go b/controller/token.go index fa42c00..0f37fd0 100644 --- a/controller/token.go +++ b/controller/token.go @@ -39,7 +39,8 @@ func GetAllTokens(c *gin.Context) { func SearchTokens(c *gin.Context) { userId := c.GetInt("id") keyword := c.Query("keyword") - tokens, err := model.SearchUserTokens(userId, keyword) + token := c.Query("token") + tokens, err := model.SearchUserTokens(userId, keyword, token) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/model/token.go b/model/token.go index 0fa984d..9121ca4 100644 --- a/model/token.go +++ b/model/token.go @@ -5,6 +5,7 @@ import ( "fmt" "gorm.io/gorm" "one-api/common" + "strings" ) type Token struct { @@ -28,8 +29,11 @@ func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) { return tokens, err } -func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) { - err = DB.Where("user_id = ?", userId).Where("name LIKE ?", keyword+"%").Find(&tokens).Error +func SearchUserTokens(userId int, keyword string, token string) (tokens []*Token, err error) { + if token != "" { + token = strings.Trim(token, "sk-") + } + err = DB.Where("user_id = ?", userId).Where("name LIKE ? or key LIKE ?", keyword+"%", token+"%").Find(&tokens).Error return tokens, err } diff --git a/web/src/components/RedemptionsTable.js b/web/src/components/RedemptionsTable.js index 7505bb7..c7205bf 100644 --- a/web/src/components/RedemptionsTable.js +++ b/web/src/components/RedemptionsTable.js @@ -1,326 +1,375 @@ -import React, { useEffect, useState } from 'react'; -import { Form, Label, Popup, Pagination } from 'semantic-ui-react'; -import { Link } from 'react-router-dom'; -import { API, copy, showError, showInfo, showSuccess, showWarning, timestamp2string } from '../helpers'; +import React, {useEffect, useState} from 'react'; +import {Link} from 'react-router-dom'; +import {API, copy, showError, showInfo, showSuccess, showWarning, timestamp2string} from '../helpers'; -import { ITEMS_PER_PAGE } from '../constants'; -import { renderQuota } from '../helpers/render'; -import {Button, Modal, Popconfirm, Popover, Table, Tag} from "@douyinfe/semi-ui"; +import {ITEMS_PER_PAGE} from '../constants'; +import {renderQuota} from '../helpers/render'; +import {Button, Modal, Popconfirm, Popover, Table, Tag, Form} from "@douyinfe/semi-ui"; +import EditRedemption from "../pages/Redemption/EditRedemption"; +import editRedemption from "../pages/Redemption/EditRedemption"; function renderTimestamp(timestamp) { - return ( - <> - {timestamp2string(timestamp)} - - ); + return ( + <> + {timestamp2string(timestamp)} + + ); } function renderStatus(status) { - switch (status) { - case 1: - return 未使用; - case 2: - return 已禁用 ; - case 3: - return 已使用 ; - default: - return 未知状态 ; - } + switch (status) { + case 1: + return 未使用; + case 2: + return 已禁用 ; + case 3: + return 已使用 ; + default: + return 未知状态 ; + } } const RedemptionsTable = () => { - const columns = [ - { - title: 'ID', - dataIndex: 'id', - }, - { - title: '名称', - dataIndex: 'name', - }, - { - title: '状态', - dataIndex: 'status', - key: 'status', - render: (text, record, index) => { - return ( -
- {renderStatus(text)} -
- ); - }, - }, - { - title: '额度', - dataIndex: 'quota', - render: (text, record, index) => { - return ( -
- {renderQuota(parseInt(text))} -
- ); - }, - }, - { - title: '创建时间', - dataIndex: 'created_time', - render: (text, record, index) => { - return ( -
- {renderTimestamp(text)} -
- ); - }, - }, - { - title: '', - dataIndex: 'operate', - render: (text, record, index) => ( -
- - - - - { - manageRedemption(record.id, 'delete', record).then( - () => { - removeRecord(record.key); - } - ) - }} - > - - - { - record.status === 1 ? - + + + { + manageRedemption(record.id, 'delete', record).then( + () => { + removeRecord(record.key); + } + ) + }} + > + + + { + record.status === 1 ? + : + } - }>禁用 : - - } - {/**/} -
- ), - }, - ]; + + + ), + }, + ]; - const [redemptions, setRedemptions] = useState([]); - const [loading, setLoading] = useState(true); - const [activePage, setActivePage] = useState(1); - const [searchKeyword, setSearchKeyword] = useState(''); - const [searching, setSearching] = useState(false); - const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE); - const [selectedKeys, setSelectedKeys] = useState([]); - - const loadRedemptions = async (startIdx) => { - const res = await API.get(`/api/redemption/?p=${startIdx}`); - const { success, message, data } = res.data; - if (success) { - if (startIdx === 0) { - setRedemptions(data); - } else { - let newRedemptions = redemptions; - newRedemptions.push(...data); - setRedemptions(newRedemptions); - } - } else { - showError(message); - } - setLoading(false); - }; - - const removeRecord = key => { - let newDataSource = [...redemptions]; - if (key != null) { - let idx = newDataSource.findIndex(data => data.key === key); - - if (idx > -1) { - newDataSource.splice(idx, 1); - setRedemptions(newDataSource); - } - } - }; - - const copyText = async (text) => { - if (await copy(text)) { - showSuccess('已复制到剪贴板!'); - } else { - // setSearchKeyword(text); - Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); - } - } - - const onPaginationChange = (e, { activePage }) => { - (async () => { - if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { - // In this case we have to load more data and then append them. - await loadRedemptions(activePage - 1); - } - setActivePage(activePage); - })(); - }; - - useEffect(() => { - loadRedemptions(0) - .then() - .catch((reason) => { - showError(reason); - }); - }, []); - - const manageRedemption = async (id, action, record) => { - let data = { id }; - let res; - switch (action) { - case 'delete': - res = await API.delete(`/api/redemption/${id}/`); - break; - case 'enable': - data.status = 1; - res = await API.put('/api/redemption/?status_only=true', data); - break; - case 'disable': - data.status = 2; - res = await API.put('/api/redemption/?status_only=true', data); - break; - } - const { success, message } = res.data; - if (success) { - showSuccess('操作成功完成!'); - let redemption = res.data.data; - let newRedemptions = [...redemptions]; - // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; - if (action === 'delete') { - - } else { - record.status = redemption.status; - } - setRedemptions(newRedemptions); - } else { - showError(message); - } - }; - - const searchRedemptions = async () => { - if (searchKeyword === '') { - // if keyword is blank, load files instead. - await loadRedemptions(0); - setActivePage(1); - return; - } - setSearching(true); - const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`); - const { success, message, data } = res.data; - if (success) { - setRedemptions(data); - setActivePage(1); - } else { - showError(message); - } - setSearching(false); - }; - - const handleKeywordChange = async (e, { value }) => { - setSearchKeyword(value.trim()); - }; - - const sortRedemption = (key) => { - if (redemptions.length === 0) return; - setLoading(true); - let sortedRedemptions = [...redemptions]; - sortedRedemptions.sort((a, b) => { - return ('' + a[key]).localeCompare(b[key]); + const [redemptions, setRedemptions] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searching, setSearching] = useState(false); + const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE); + const [selectedKeys, setSelectedKeys] = useState([]); + const [editingRedemption, setEditingRedemption] = useState({ + id: undefined, }); - if (sortedRedemptions[0].id === redemptions[0].id) { - sortedRedemptions.reverse(); + const [showEdit, setShowEdit] = useState(false); + + const closeEdit = () => { + setShowEdit(false); } - setRedemptions(sortedRedemptions); - setLoading(false); - }; - const handlePageChange = page => { - setActivePage(page); - if (page === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { - // In this case we have to load more data and then append them. - loadRedemptions(page - 1).then(r => {}); + const loadRedemptions = async (startIdx) => { + const res = await API.get(`/api/redemption/?p=${startIdx}`); + const {success, message, data} = res.data; + if (success) { + if (startIdx === 0) { + setRedemptions(data); + } else { + let newRedemptions = redemptions; + newRedemptions.push(...data); + setRedemptions(newRedemptions); + } + } else { + showError(message); + } + setLoading(false); + }; + + const removeRecord = key => { + let newDataSource = [...redemptions]; + if (key != null) { + let idx = newDataSource.findIndex(data => data.key === key); + + if (idx > -1) { + newDataSource.splice(idx, 1); + setRedemptions(newDataSource); + } + } + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess('已复制到剪贴板!'); + } else { + // setSearchKeyword(text); + Modal.error({title: '无法复制到剪贴板,请手动复制', content: text}); + } } - }; - let pageData = redemptions.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE); - const rowSelection = { - onSelect: (record, selected) => { - }, - onSelectAll: (selected, selectedRows) => { - }, - onChange: (selectedRowKeys, selectedRows) => { - setSelectedKeys(selectedRows); - }, - }; + const onPaginationChange = (e, {activePage}) => { + (async () => { + if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { + // In this case we have to load more data and then append them. + await loadRedemptions(activePage - 1); + } + setActivePage(activePage); + })(); + }; - return ( - <> -
- - + useEffect(() => { + loadRedemptions(0) + .then() + .catch((reason) => { + showError(reason); + }); + }, []); - `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${redemptions.length} 条`, - // onPageSizeChange: (size) => { - // setPageSize(size); - // setActivePage(1); - // }, - onPageChange: handlePageChange, - }} loading={loading} rowSelection={rowSelection}> -
- - ); + const refresh = async () => { + await loadRedemptions(activePage - 1); + }; + + const manageRedemption = async (id, action, record) => { + let data = {id}; + let res; + switch (action) { + case 'delete': + res = await API.delete(`/api/redemption/${id}/`); + break; + case 'enable': + data.status = 1; + res = await API.put('/api/redemption/?status_only=true', data); + break; + case 'disable': + data.status = 2; + res = await API.put('/api/redemption/?status_only=true', data); + break; + } + const {success, message} = res.data; + if (success) { + showSuccess('操作成功完成!'); + let redemption = res.data.data; + let newRedemptions = [...redemptions]; + // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; + if (action === 'delete') { + + } else { + record.status = redemption.status; + } + setRedemptions(newRedemptions); + } else { + showError(message); + } + }; + + const searchRedemptions = async () => { + if (searchKeyword === '') { + // if keyword is blank, load files instead. + await loadRedemptions(0); + setActivePage(1); + return; + } + setSearching(true); + const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`); + const {success, message, data} = res.data; + if (success) { + setRedemptions(data); + setActivePage(1); + } else { + showError(message); + } + setSearching(false); + }; + + const handleKeywordChange = async (value) => { + setSearchKeyword(value.trim()); + }; + + const sortRedemption = (key) => { + if (redemptions.length === 0) return; + setLoading(true); + let sortedRedemptions = [...redemptions]; + sortedRedemptions.sort((a, b) => { + return ('' + a[key]).localeCompare(b[key]); + }); + if (sortedRedemptions[0].id === redemptions[0].id) { + sortedRedemptions.reverse(); + } + setRedemptions(sortedRedemptions); + setLoading(false); + }; + + const handlePageChange = page => { + setActivePage(page); + if (page === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { + // In this case we have to load more data and then append them. + loadRedemptions(page - 1).then(r => { + }); + } + }; + + let pageData = redemptions.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE); + const rowSelection = { + onSelect: (record, selected) => { + }, + onSelectAll: (selected, selectedRows) => { + }, + onChange: (selectedRowKeys, selectedRows) => { + setSelectedKeys(selectedRows); + }, + }; + + return ( + <> + +
+ + + + `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${redemptions.length} 条`, + // onPageSizeChange: (size) => { + // setPageSize(size); + // setActivePage(1); + // }, + onPageChange: handlePageChange, + }} loading={loading} rowSelection={rowSelection}> +
+ + + + ); }; export default RedemptionsTable; diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index d267660..b04fb84 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -179,6 +179,7 @@ const TokensTable = () => { const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(''); + const [searchToken, setSearchToken] = useState(''); const [searching, setSearching] = useState(false); const [showTopUpModal, setShowTopUpModal] = useState(false); const [targetTokenIdx, setTargetTokenIdx] = useState(0); @@ -188,9 +189,6 @@ const TokensTable = () => { const closeEdit = () => { setShowEdit(false); - // setEditingToken({ - // id: undefined, - // }); } const setTokensFormat = (tokens) => { @@ -401,6 +399,10 @@ const TokensTable = () => { setSearchKeyword(value.trim()); }; + const handleSearchTokenChange = async (value) => { + setSearchToken(value.trim()); + }; + const sortToken = (key) => { if (tokens.length === 0) return; setLoading(true); @@ -447,6 +449,14 @@ const TokensTable = () => { loading={searching} onChange={handleKeywordChange} /> + diff --git a/web/src/pages/Redemption/EditRedemption.js b/web/src/pages/Redemption/EditRedemption.js index 7a33f77..54aa47d 100644 --- a/web/src/pages/Redemption/EditRedemption.js +++ b/web/src/pages/Redemption/EditRedemption.js @@ -1,15 +1,17 @@ import React, { useEffect, useState } from 'react'; -import { Button, Form, Header, Segment } from 'semantic-ui-react'; import { useParams, useNavigate } from 'react-router-dom'; -import { API, downloadTextAsFile, showError, showSuccess } from '../../helpers'; +import {API, downloadTextAsFile, isMobile, showError, showSuccess} from '../../helpers'; import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; +import {SideSheet, Space, Spin, Button, Input, Typography, AutoComplete, Modal} from "@douyinfe/semi-ui"; +import Title from "@douyinfe/semi-ui/lib/es/typography/title"; +import {Divider} from "semantic-ui-react"; -const EditRedemption = () => { - const params = useParams(); - const navigate = useNavigate(); - const redemptionId = params.id; - const isEdit = redemptionId !== undefined; +const EditRedemption = (props) => { + const isEdit = props.editingRedemption.id !== undefined; const [loading, setLoading] = useState(isEdit); + + const params = useParams(); + const navigate = useNavigate() const originInputs = { name: '', quota: 100000, @@ -19,15 +21,16 @@ const EditRedemption = () => { const { name, quota, count } = inputs; const handleCancel = () => { - navigate('/redemption'); - }; + props.handleClose(); + } - const handleInputChange = (e, { name, value }) => { + const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); }; const loadRedemption = async () => { - let res = await API.get(`/api/redemption/${redemptionId}`); + setLoading(true); + let res = await API.get(`/api/redemption/${props.editingRedemption.id}`); const { success, message, data } = res.data; if (success) { setInputs(data); @@ -36,20 +39,28 @@ const EditRedemption = () => { } setLoading(false); }; + useEffect(() => { if (isEdit) { - loadRedemption().then(); + loadRedemption().then( + () => { + // console.log(inputs); + } + ); + } else { + setInputs(originInputs); } - }, []); + }, [props.editingRedemption.id]); const submit = async () => { if (!isEdit && inputs.name === '') return; + setLoading(true); let localInputs = inputs; localInputs.count = parseInt(localInputs.count); localInputs.quota = parseInt(localInputs.quota); let res; if (isEdit) { - res = await API.put(`/api/redemption/`, { ...localInputs, id: parseInt(redemptionId) }); + res = await API.put(`/api/redemption/`, { ...localInputs, id: parseInt(props.editingRedemption.id) }); } else { res = await API.post(`/api/redemption/`, { ...localInputs @@ -59,9 +70,13 @@ const EditRedemption = () => { if (success) { if (isEdit) { showSuccess('兑换码更新成功!'); + props.refresh(); + props.handleClose(); } else { showSuccess('兑换码创建成功!'); setInputs(originInputs); + props.refresh(); + props.handleClose(); } } else { showError(message); @@ -71,56 +86,94 @@ const EditRedemption = () => { for (let i = 0; i < data.length; i++) { text += data[i] + "\n"; } - downloadTextAsFile(text, `${inputs.name}.txt`); + // downloadTextAsFile(text, `${inputs.name}.txt`); + Modal.confirm({ + title: '兑换码创建成功', + content: ( +
+

兑换码创建成功,是否下载兑换码?

+

兑换码将以文本文件的形式下载,文件名为兑换码的名称。

+
+ ), + onOk: () => { + downloadTextAsFile(text, `${inputs.name}.txt`); + } + }); } + setLoading(false); }; return ( <> - -
{isEdit ? '更新兑换码信息' : '创建新的兑换码'}
-
- - {isEdit ? '更新兑换码信息' : '创建新的兑换码'}} + headerStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + bodyStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + visible={props.visiable} + footer={ +
+ + + + +
+ } + closeIcon={null} + onCancel={() => handleCancel()} + width={isMobile() ? '100%' : 600} + > + + handleInputChange('name', value)} value={name} autoComplete='new-password' required={!isEdit} - /> -
- - + +
+ {`额度${renderQuotaWithPrompt(quota)}`} +
+ handleInputChange('quota', value)} value={quota} autoComplete='new-password' type='number' - /> -
+ position={'bottom'} + data={[ + {value: 500000, label: '1$'}, + {value: 5000000, label: '10$'}, + {value: 25000000, label: '50$'}, + {value: 50000000, label: '100$'}, + {value: 250000000, label: '500$'}, + {value: 500000000, label: '1000$'}, + ]} + /> { - !isEdit && <> - - + + 生成数量 + handleInputChange('count', value)} + value={count} + autoComplete='new-password' + type='number' /> - - + } - - -
-
+ + ); }; diff --git a/web/src/pages/Redemption/index.js b/web/src/pages/Redemption/index.js index c064941..0b3c963 100644 --- a/web/src/pages/Redemption/index.js +++ b/web/src/pages/Redemption/index.js @@ -1,13 +1,19 @@ import React from 'react'; import { Segment, Header } from 'semantic-ui-react'; import RedemptionsTable from '../../components/RedemptionsTable'; +import TokensTable from "../../components/TokensTable"; +import {Layout} from "@douyinfe/semi-ui"; const Redemption = () => ( <> - -
管理兑换码
- -
+ + +

管理兑换码

+
+ + + +
); diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js index e21e9cb..9512a02 100644 --- a/web/src/pages/Token/EditToken.js +++ b/web/src/pages/Token/EditToken.js @@ -63,7 +63,7 @@ const EditToken = (props) => { if (isEdit) { loadToken().then( () => { - console.log(inputs); + // console.log(inputs); } ); } else { @@ -72,8 +72,8 @@ const EditToken = (props) => { }, [props.editingToken.id]); const submit = async () => { - setLoading(true); if (!isEdit && inputs.name === '') return; + setLoading(true); let localInputs = inputs; localInputs.remain_quota = parseInt(localInputs.remain_quota); if (localInputs.expired_time !== -1) { diff --git a/web/src/pages/Token/index.js b/web/src/pages/Token/index.js index 5583bfb..eaa1f4b 100644 --- a/web/src/pages/Token/index.js +++ b/web/src/pages/Token/index.js @@ -1,16 +1,15 @@ import React from 'react'; import TokensTable from '../../components/TokensTable'; import {Layout} from "@douyinfe/semi-ui"; -const {Content, Header} = Layout; const Token = () => ( <> -
+

我的令牌

-
- + + - +
);