diff --git a/controller/token.go b/controller/token.go index de0e65eb..7f6b4505 100644 --- a/controller/token.go +++ b/controller/token.go @@ -16,7 +16,10 @@ func GetAllTokens(c *gin.Context) { if p < 0 { p = 0 } - tokens, err := model.GetAllUserTokens(userId, p*config.ItemsPerPage, config.ItemsPerPage) + + order := c.Query("order") + tokens, err := model.GetAllUserTokens(userId, p*config.ItemsPerPage, config.ItemsPerPage, order) + if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/controller/user.go b/controller/user.go index c11b940e..8b614e5d 100644 --- a/controller/user.go +++ b/controller/user.go @@ -180,24 +180,27 @@ func Register(c *gin.Context) { } func GetAllUsers(c *gin.Context) { - p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 - } - users, err := model.GetAllUsers(p*config.ItemsPerPage, config.ItemsPerPage) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": users, - }) - return + p, _ := strconv.Atoi(c.Query("p")) + if p < 0 { + p = 0 + } + + order := c.DefaultQuery("order", "") + users, err := model.GetAllUsers(p*config.ItemsPerPage, config.ItemsPerPage, order) + + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": users, + }) } func SearchUsers(c *gin.Context) { diff --git a/model/token.go b/model/token.go index 98214cf9..493e27c9 100644 --- a/model/token.go +++ b/model/token.go @@ -25,10 +25,21 @@ type Token struct { UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota } -func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) { +func GetAllUserTokens(userId int, startIdx int, num int, order string) ([]*Token, error) { var tokens []*Token var err error - err = DB.Where("user_id = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Find(&tokens).Error + query := DB.Where("user_id = ?", userId) + + switch order { + case "remain_quota": + query = query.Order("unlimited_quota desc, remain_quota desc") + case "used_quota": + query = query.Order("used_quota desc") + default: + query = query.Order("id desc") + } + + err = query.Limit(num).Offset(startIdx).Find(&tokens).Error return tokens, err } diff --git a/model/user.go b/model/user.go index 1c5833d9..5e729b5e 100644 --- a/model/user.go +++ b/model/user.go @@ -40,9 +40,22 @@ func GetMaxUserId() int { return user.Id } -func GetAllUsers(startIdx int, num int) (users []*User, err error) { - err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("password").Where("status != ?", common.UserStatusDeleted).Find(&users).Error - return users, err +func GetAllUsers(startIdx int, num int, order string) (users []*User, err error) { + query := DB.Limit(num).Offset(startIdx).Omit("password").Where("status != ?", common.UserStatusDeleted) + + switch order { + case "quota": + query = query.Order("quota desc") + case "used_quota": + query = query.Order("used_quota desc") + case "request_count": + query = query.Order("request_count desc") + default: + query = query.Order("id desc") + } + + err = query.Find(&users).Error + return users, err } func SearchUsers(keyword string) (users []*User, err error) { diff --git a/web/air/src/components/TokensTable.js b/web/air/src/components/TokensTable.js index 9c4deb6e..c106b388 100644 --- a/web/air/src/components/TokensTable.js +++ b/web/air/src/components/TokensTable.js @@ -247,6 +247,8 @@ const TokensTable = () => { const [editingToken, setEditingToken] = useState({ id: undefined }); + const [orderBy, setOrderBy] = useState(''); + const [dropdownVisible, setDropdownVisible] = useState(false); const closeEdit = () => { setShowEdit(false); @@ -269,7 +271,7 @@ const TokensTable = () => { let pageData = tokens.slice((activePage - 1) * pageSize, activePage * pageSize); const loadTokens = async (startIdx) => { setLoading(true); - const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`); + const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}&order=${orderBy}`); const { success, message, data } = res.data; if (success) { if (startIdx === 0) { @@ -289,7 +291,7 @@ const TokensTable = () => { (async () => { if (activePage === Math.ceil(tokens.length / pageSize) + 1) { // In this case we have to load more data and then append them. - await loadTokens(activePage - 1); + await loadTokens(activePage - 1, orderBy); } setActivePage(activePage); })(); @@ -392,12 +394,12 @@ const TokensTable = () => { }; useEffect(() => { - loadTokens(0) + loadTokens(0, orderBy) .then() .catch((reason) => { showError(reason); }); - }, [pageSize]); + }, [pageSize, orderBy]); const removeRecord = key => { let newDataSource = [...tokens]; @@ -452,6 +454,7 @@ const TokensTable = () => { // if keyword is blank, load files instead. await loadTokens(0); setActivePage(1); + setOrderBy(''); return; } setSearching(true); @@ -520,6 +523,23 @@ const TokensTable = () => { } }; + const handleOrderByChange = (e, { value }) => { + setOrderBy(value); + setActivePage(1); + setDropdownVisible(false); + }; + + const renderSelectedOption = (orderBy) => { + switch (orderBy) { + case 'remain_quota': + return '按剩余额度排序'; + case 'used_quota': + return '按已用额度排序'; + default: + return '默认排序'; + } + }; + return ( <> @@ -579,6 +599,21 @@ const TokensTable = () => { await copyText(keys); } }>复制所选令牌到剪贴板 + setDropdownVisible(visible)} + render={ + + handleOrderByChange('', { value: '' })}>默认排序 + handleOrderByChange('', { value: 'remain_quota' })}>按剩余额度排序 + handleOrderByChange('', { value: 'used_quota' })}>按已用额度排序 + + } + > + + ); }; diff --git a/web/air/src/components/UsersTable.js b/web/air/src/components/UsersTable.js index f3de46d6..4fc16ba5 100644 --- a/web/air/src/components/UsersTable.js +++ b/web/air/src/components/UsersTable.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { API, showError, showSuccess } from '../helpers'; -import { Button, Form, Popconfirm, Space, Table, Tag, Tooltip } from '@douyinfe/semi-ui'; +import { Button, Form, Popconfirm, Space, Table, Tag, Tooltip, Dropdown } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../constants'; import { renderGroup, renderNumber, renderQuota } from '../helpers/render'; import AddUser from '../pages/User/AddUser'; @@ -139,6 +139,8 @@ const UsersTable = () => { const [editingUser, setEditingUser] = useState({ id: undefined }); + const [orderBy, setOrderBy] = useState(''); + const [dropdownVisible, setDropdownVisible] = useState(false); const setCount = (data) => { if (data.length >= (activePage) * ITEMS_PER_PAGE) { @@ -162,7 +164,7 @@ const UsersTable = () => { }; const loadUsers = async (startIdx) => { - const res = await API.get(`/api/user/?p=${startIdx}`); + const res = await API.get(`/api/user/?p=${startIdx}&order=${orderBy}`); const { success, message, data } = res.data; if (success) { if (startIdx === 0) { @@ -184,19 +186,19 @@ const UsersTable = () => { (async () => { if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) { // In this case we have to load more data and then append them. - await loadUsers(activePage - 1); + await loadUsers(activePage - 1, orderBy); } setActivePage(activePage); })(); }; useEffect(() => { - loadUsers(0) + loadUsers(0, orderBy) .then() .catch((reason) => { showError(reason); }); - }, []); + }, [orderBy]); const manageUser = async (username, action, record) => { const res = await API.post('/api/user/manage', { @@ -239,6 +241,7 @@ const UsersTable = () => { // if keyword is blank, load files instead. await loadUsers(0); setActivePage(1); + setOrderBy(''); return; } setSearching(true); @@ -301,6 +304,25 @@ const UsersTable = () => { } }; + const handleOrderByChange = (e, { value }) => { + setOrderBy(value); + setActivePage(1); + setDropdownVisible(false); + }; + + const renderSelectedOption = (orderBy) => { + switch (orderBy) { + case 'quota': + return '按剩余额度排序'; + case 'used_quota': + return '按已用额度排序'; + case 'request_count': + return '按请求次数排序'; + default: + return '默认排序'; + } + }; + return ( <> @@ -331,6 +353,22 @@ const UsersTable = () => { setShowAddUser(true); } }>添加用户 + setDropdownVisible(visible)} + render={ + + handleOrderByChange('', { value: '' })}>默认排序 + handleOrderByChange('', { value: 'quota' })}>按剩余额度排序 + handleOrderByChange('', { value: 'used_quota' })}>按已用额度排序 + handleOrderByChange('', { value: 'request_count' })}>按请求次数排序 + + } + > + + ); }; diff --git a/web/default/src/components/TokensTable.js b/web/default/src/components/TokensTable.js index d6ad2a21..19a688bb 100644 --- a/web/default/src/components/TokensTable.js +++ b/web/default/src/components/TokensTable.js @@ -48,9 +48,10 @@ const TokensTable = () => { const [searching, setSearching] = useState(false); const [showTopUpModal, setShowTopUpModal] = useState(false); const [targetTokenIdx, setTargetTokenIdx] = useState(0); + const [orderBy, setOrderBy] = useState(''); const loadTokens = async (startIdx) => { - const res = await API.get(`/api/token/?p=${startIdx}`); + const res = await API.get(`/api/token/?p=${startIdx}&order=${orderBy}`); const { success, message, data } = res.data; if (success) { if (startIdx === 0) { @@ -70,7 +71,7 @@ const TokensTable = () => { (async () => { if (activePage === Math.ceil(tokens.length / ITEMS_PER_PAGE) + 1) { // In this case we have to load more data and then append them. - await loadTokens(activePage - 1); + await loadTokens(activePage - 1, orderBy); } setActivePage(activePage); })(); @@ -160,12 +161,12 @@ const TokensTable = () => { } useEffect(() => { - loadTokens(0) + loadTokens(0, orderBy) .then() .catch((reason) => { showError(reason); }); - }, []); + }, [orderBy]); const manageToken = async (id, action, idx) => { let data = { id }; @@ -205,6 +206,7 @@ const TokensTable = () => { // if keyword is blank, load files instead. await loadTokens(0); setActivePage(1); + setOrderBy(''); return; } setSearching(true); @@ -243,6 +245,11 @@ const TokensTable = () => { setLoading(false); }; + const handleOrderByChange = (e, { value }) => { + setOrderBy(value); + setActivePage(1); + }; + return ( <>
@@ -427,6 +434,18 @@ const TokensTable = () => { 添加新的令牌 + { const [activePage, setActivePage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(''); const [searching, setSearching] = useState(false); + const [orderBy, setOrderBy] = useState(''); const loadUsers = async (startIdx) => { - const res = await API.get(`/api/user/?p=${startIdx}`); + const res = await API.get(`/api/user/?p=${startIdx}&order=${orderBy}`); const { success, message, data } = res.data; if (success) { if (startIdx === 0) { @@ -47,19 +48,19 @@ const UsersTable = () => { (async () => { if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) { // In this case we have to load more data and then append them. - await loadUsers(activePage - 1); + await loadUsers(activePage - 1, orderBy); } setActivePage(activePage); })(); }; useEffect(() => { - loadUsers(0) + loadUsers(0, orderBy) .then() .catch((reason) => { showError(reason); }); - }, []); + }, [orderBy]); const manageUser = (username, action, idx) => { (async () => { @@ -110,6 +111,7 @@ const UsersTable = () => { // if keyword is blank, load files instead. await loadUsers(0); setActivePage(1); + setOrderBy(''); return; } setSearching(true); @@ -148,6 +150,11 @@ const UsersTable = () => { setLoading(false); }; + const handleOrderByChange = (e, { value }) => { + setOrderBy(value); + setActivePage(1); + }; + return ( <> @@ -322,6 +329,19 @@ const UsersTable = () => { +