mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-11-04 15:53:42 +08:00 
			
		
		
		
	Remove useless page
This commit is contained in:
		@@ -12,12 +12,12 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetAllFiles(c *gin.Context) {
 | 
			
		||||
func GetAllChannels(c *gin.Context) {
 | 
			
		||||
	p, _ := strconv.Atoi(c.Query("p"))
 | 
			
		||||
	if p < 0 {
 | 
			
		||||
		p = 0
 | 
			
		||||
	}
 | 
			
		||||
	files, err := model.GetAllFiles(p*common.ItemsPerPage, common.ItemsPerPage)
 | 
			
		||||
	files, err := model.GetAllChannels(p*common.ItemsPerPage, common.ItemsPerPage)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
@@ -33,9 +33,9 @@ func GetAllFiles(c *gin.Context) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SearchFiles(c *gin.Context) {
 | 
			
		||||
func SearchChannels(c *gin.Context) {
 | 
			
		||||
	keyword := c.Query("keyword")
 | 
			
		||||
	files, err := model.SearchFiles(keyword)
 | 
			
		||||
	files, err := model.SearchChannels(keyword)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
@@ -85,7 +85,7 @@ func UploadFile(c *gin.Context) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		// save to database
 | 
			
		||||
		fileObj := &model.File{
 | 
			
		||||
		fileObj := &model.Channel{
 | 
			
		||||
			Description: description,
 | 
			
		||||
			Uploader:    uploader,
 | 
			
		||||
			UploadTime:  currentTime,
 | 
			
		||||
@@ -116,7 +116,7 @@ func DeleteFile(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fileObj := &model.File{
 | 
			
		||||
	fileObj := &model.Channel{
 | 
			
		||||
		Id: fileId,
 | 
			
		||||
	}
 | 
			
		||||
	model.DB.Where("id = ?", fileId).First(&fileObj)
 | 
			
		||||
@@ -8,7 +8,7 @@ import (
 | 
			
		||||
	"path"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type File struct {
 | 
			
		||||
type Channel struct {
 | 
			
		||||
	Id              int    `json:"id"`
 | 
			
		||||
	Filename        string `json:"filename" gorm:"index"`
 | 
			
		||||
	Description     string `json:"description"`
 | 
			
		||||
@@ -19,27 +19,27 @@ type File struct {
 | 
			
		||||
	DownloadCounter int    `json:"download_counter"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAllFiles(startIdx int, num int) ([]*File, error) {
 | 
			
		||||
	var files []*File
 | 
			
		||||
func GetAllChannels(startIdx int, num int) ([]*Channel, error) {
 | 
			
		||||
	var files []*Channel
 | 
			
		||||
	var err error
 | 
			
		||||
	err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&files).Error
 | 
			
		||||
	return files, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SearchFiles(keyword string) (files []*File, err error) {
 | 
			
		||||
func SearchChannels(keyword string) (files []*Channel, err error) {
 | 
			
		||||
	err = DB.Select([]string{"id", "filename", "description", "uploader", "uploader_id", "link", "upload_time", "download_counter"}).Where(
 | 
			
		||||
		"filename LIKE ? or uploader LIKE ? or uploader_id = ?", keyword+"%", keyword+"%", keyword).Find(&files).Error
 | 
			
		||||
	return files, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (file *File) Insert() error {
 | 
			
		||||
func (file *Channel) Insert() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	err = DB.Create(file).Error
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete Make sure link is valid! Because we will use os.Remove to delete it!
 | 
			
		||||
func (file *File) Delete() error {
 | 
			
		||||
func (file *Channel) Delete() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	err = DB.Delete(file).Error
 | 
			
		||||
	err = os.Remove(path.Join(common.UploadPath, file.Link))
 | 
			
		||||
@@ -47,5 +47,5 @@ func (file *File) Delete() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateDownloadCounter(link string) {
 | 
			
		||||
	DB.Model(&File{}).Where("link = ?", link).UpdateColumn("download_counter", gorm.Expr("download_counter + 1"))
 | 
			
		||||
	DB.Model(&Channel{}).Where("link = ?", link).UpdateColumn("download_counter", gorm.Expr("download_counter + 1"))
 | 
			
		||||
}
 | 
			
		||||
@@ -52,7 +52,7 @@ func InitDB() (err error) {
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		DB = db
 | 
			
		||||
		err := db.AutoMigrate(&File{})
 | 
			
		||||
		err := db.AutoMigrate(&Channel{})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,8 +57,8 @@ func SetApiRouter(router *gin.Engine) {
 | 
			
		||||
		fileRoute := apiRouter.Group("/file")
 | 
			
		||||
		fileRoute.Use(middleware.AdminAuth())
 | 
			
		||||
		{
 | 
			
		||||
			fileRoute.GET("/", controller.GetAllFiles)
 | 
			
		||||
			fileRoute.GET("/search", controller.SearchFiles)
 | 
			
		||||
			fileRoute.GET("/", controller.GetAllChannels)
 | 
			
		||||
			fileRoute.GET("/search", controller.SearchChannels)
 | 
			
		||||
			fileRoute.POST("/", middleware.UploadRateLimit(), controller.UploadFile)
 | 
			
		||||
			fileRoute.DELETE("/:id", controller.DeleteFile)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import PasswordResetForm from './components/PasswordResetForm';
 | 
			
		||||
import GitHubOAuth from './components/GitHubOAuth';
 | 
			
		||||
import PasswordResetConfirm from './components/PasswordResetConfirm';
 | 
			
		||||
import { UserContext } from './context/User';
 | 
			
		||||
import File from './pages/File';
 | 
			
		||||
import Channel from './pages/Channel';
 | 
			
		||||
 | 
			
		||||
const Home = lazy(() => import('./pages/Home'));
 | 
			
		||||
const About = lazy(() => import('./pages/About'));
 | 
			
		||||
@@ -65,10 +65,10 @@ function App() {
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <Route
 | 
			
		||||
        path='/file'
 | 
			
		||||
        path='/channel'
 | 
			
		||||
        element={
 | 
			
		||||
          <PrivateRoute>
 | 
			
		||||
            <File />
 | 
			
		||||
            <Channel />
 | 
			
		||||
          </PrivateRoute>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										300
									
								
								web/src/components/ChannelsTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								web/src/components/ChannelsTable.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,300 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { Button, Form, Label, Pagination, Table } from 'semantic-ui-react';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { API, showError, showSuccess } from '../helpers';
 | 
			
		||||
 | 
			
		||||
import { ITEMS_PER_PAGE } from '../constants';
 | 
			
		||||
 | 
			
		||||
function renderRole(role) {
 | 
			
		||||
  switch (role) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      return <Label>普通用户</Label>;
 | 
			
		||||
    case 10:
 | 
			
		||||
      return <Label color='yellow'>管理员</Label>;
 | 
			
		||||
    case 100:
 | 
			
		||||
      return <Label color='orange'>超级管理员</Label>;
 | 
			
		||||
    default:
 | 
			
		||||
      return <Label color='red'>未知身份</Label>;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ChannelsTable = () => {
 | 
			
		||||
  const [users, setUsers] = useState([]);
 | 
			
		||||
  const [loading, setLoading] = useState(true);
 | 
			
		||||
  const [activePage, setActivePage] = useState(1);
 | 
			
		||||
  const [searchKeyword, setSearchKeyword] = useState('');
 | 
			
		||||
  const [searching, setSearching] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const loadUsers = async (startIdx) => {
 | 
			
		||||
    const res = await API.get(`/api/user/?p=${startIdx}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      if (startIdx === 0) {
 | 
			
		||||
        setUsers(data);
 | 
			
		||||
      } else {
 | 
			
		||||
        let newUsers = users;
 | 
			
		||||
        newUsers.push(...data);
 | 
			
		||||
        setUsers(newUsers);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const onPaginationChange = (e, { activePage }) => {
 | 
			
		||||
    (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);
 | 
			
		||||
      }
 | 
			
		||||
      setActivePage(activePage);
 | 
			
		||||
    })();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    loadUsers(0)
 | 
			
		||||
      .then()
 | 
			
		||||
      .catch((reason) => {
 | 
			
		||||
        showError(reason);
 | 
			
		||||
      });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const manageUser = (username, action, idx) => {
 | 
			
		||||
    (async () => {
 | 
			
		||||
      const res = await API.post('/api/user/manage', {
 | 
			
		||||
        username,
 | 
			
		||||
        action,
 | 
			
		||||
      });
 | 
			
		||||
      const { success, message } = res.data;
 | 
			
		||||
      if (success) {
 | 
			
		||||
        showSuccess('操作成功完成!');
 | 
			
		||||
        let user = res.data.data;
 | 
			
		||||
        let newUsers = [...users];
 | 
			
		||||
        let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
 | 
			
		||||
        if (action === 'delete') {
 | 
			
		||||
          newUsers[realIdx].deleted = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          newUsers[realIdx].status = user.status;
 | 
			
		||||
          newUsers[realIdx].role = user.role;
 | 
			
		||||
        }
 | 
			
		||||
        setUsers(newUsers);
 | 
			
		||||
      } else {
 | 
			
		||||
        showError(message);
 | 
			
		||||
      }
 | 
			
		||||
    })();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const renderStatus = (status) => {
 | 
			
		||||
    switch (status) {
 | 
			
		||||
      case 1:
 | 
			
		||||
        return <Label basic>已激活</Label>;
 | 
			
		||||
      case 2:
 | 
			
		||||
        return (
 | 
			
		||||
          <Label basic color='red'>
 | 
			
		||||
            已封禁
 | 
			
		||||
          </Label>
 | 
			
		||||
        );
 | 
			
		||||
      default:
 | 
			
		||||
        return (
 | 
			
		||||
          <Label basic color='grey'>
 | 
			
		||||
            未知状态
 | 
			
		||||
          </Label>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const searchUsers = async () => {
 | 
			
		||||
    if (searchKeyword === '') {
 | 
			
		||||
      // if keyword is blank, load files instead.
 | 
			
		||||
      await loadUsers(0);
 | 
			
		||||
      setActivePage(1);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setSearching(true);
 | 
			
		||||
    const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      setUsers(data);
 | 
			
		||||
      setActivePage(1);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
    setSearching(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleKeywordChange = async (e, { value }) => {
 | 
			
		||||
    setSearchKeyword(value.trim());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const sortUser = (key) => {
 | 
			
		||||
    if (users.length === 0) return;
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    let sortedUsers = [...users];
 | 
			
		||||
    sortedUsers.sort((a, b) => {
 | 
			
		||||
      return ('' + a[key]).localeCompare(b[key]);
 | 
			
		||||
    });
 | 
			
		||||
    if (sortedUsers[0].id === users[0].id) {
 | 
			
		||||
      sortedUsers.reverse();
 | 
			
		||||
    }
 | 
			
		||||
    setUsers(sortedUsers);
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Form onSubmit={searchUsers}>
 | 
			
		||||
        <Form.Input
 | 
			
		||||
          icon='search'
 | 
			
		||||
          fluid
 | 
			
		||||
          iconPosition='left'
 | 
			
		||||
          placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
 | 
			
		||||
          value={searchKeyword}
 | 
			
		||||
          loading={searching}
 | 
			
		||||
          onChange={handleKeywordChange}
 | 
			
		||||
        />
 | 
			
		||||
      </Form>
 | 
			
		||||
 | 
			
		||||
      <Table basic>
 | 
			
		||||
        <Table.Header>
 | 
			
		||||
          <Table.Row>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortUser('username');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              用户名
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortUser('display_name');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              显示名称
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortUser('email');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              邮箱地址
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortUser('role');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              用户角色
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortUser('status');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              状态
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell>操作</Table.HeaderCell>
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Header>
 | 
			
		||||
 | 
			
		||||
        <Table.Body>
 | 
			
		||||
          {users
 | 
			
		||||
            .slice(
 | 
			
		||||
              (activePage - 1) * ITEMS_PER_PAGE,
 | 
			
		||||
              activePage * ITEMS_PER_PAGE
 | 
			
		||||
            )
 | 
			
		||||
            .map((user, idx) => {
 | 
			
		||||
              if (user.deleted) return <></>;
 | 
			
		||||
              return (
 | 
			
		||||
                <Table.Row key={user.id}>
 | 
			
		||||
                  <Table.Cell>{user.username}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>{user.display_name}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>{user.email ? user.email : '无'}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>{renderRole(user.role)}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>{renderStatus(user.status)}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        positive
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          manageUser(user.username, 'promote', idx);
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        提升
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        color={'yellow'}
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          manageUser(user.username, 'demote', idx);
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        降级
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        negative
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          manageUser(user.username, 'delete', idx);
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        删除
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          manageUser(
 | 
			
		||||
                            user.username,
 | 
			
		||||
                            user.status === 1 ? 'disable' : 'enable',
 | 
			
		||||
                            idx
 | 
			
		||||
                          );
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        {user.status === 1 ? '禁用' : '启用'}
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        as={Link}
 | 
			
		||||
                        to={'/user/edit/' + user.id}
 | 
			
		||||
                      >
 | 
			
		||||
                        编辑
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Table.Cell>
 | 
			
		||||
                </Table.Row>
 | 
			
		||||
              );
 | 
			
		||||
            })}
 | 
			
		||||
        </Table.Body>
 | 
			
		||||
 | 
			
		||||
        <Table.Footer>
 | 
			
		||||
          <Table.Row>
 | 
			
		||||
            <Table.HeaderCell colSpan='6'>
 | 
			
		||||
              <Button size='small' as={Link} to='/user/add' loading={loading}>
 | 
			
		||||
                添加新的用户
 | 
			
		||||
              </Button>
 | 
			
		||||
              <Pagination
 | 
			
		||||
                floated='right'
 | 
			
		||||
                activePage={activePage}
 | 
			
		||||
                onPageChange={onPaginationChange}
 | 
			
		||||
                size='small'
 | 
			
		||||
                siblingRange={1}
 | 
			
		||||
                totalPages={
 | 
			
		||||
                  Math.ceil(users.length / ITEMS_PER_PAGE) +
 | 
			
		||||
                  (users.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Footer>
 | 
			
		||||
      </Table>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ChannelsTable;
 | 
			
		||||
@@ -1,303 +0,0 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
  Form,
 | 
			
		||||
  Header,
 | 
			
		||||
  Icon,
 | 
			
		||||
  Pagination,
 | 
			
		||||
  Popup,
 | 
			
		||||
  Progress,
 | 
			
		||||
  Segment,
 | 
			
		||||
  Table,
 | 
			
		||||
} from 'semantic-ui-react';
 | 
			
		||||
import { API, copy, showError, showSuccess } from '../helpers';
 | 
			
		||||
import { useDropzone } from 'react-dropzone';
 | 
			
		||||
import { ITEMS_PER_PAGE } from '../constants';
 | 
			
		||||
 | 
			
		||||
const FilesTable = () => {
 | 
			
		||||
  const [files, setFiles] = useState([]);
 | 
			
		||||
  const [loading, setLoading] = useState(true);
 | 
			
		||||
  const [activePage, setActivePage] = useState(1);
 | 
			
		||||
  const [searchKeyword, setSearchKeyword] = useState('');
 | 
			
		||||
  const [searching, setSearching] = useState(false);
 | 
			
		||||
  const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
 | 
			
		||||
  const [uploading, setUploading] = useState(false);
 | 
			
		||||
  const [uploadProgress, setUploadProgress] = useState('0');
 | 
			
		||||
 | 
			
		||||
  const loadFiles = async (startIdx) => {
 | 
			
		||||
    const res = await API.get(`/api/file/?p=${startIdx}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      if (startIdx === 0) {
 | 
			
		||||
        setFiles(data);
 | 
			
		||||
      } else {
 | 
			
		||||
        let newFiles = files;
 | 
			
		||||
        newFiles.push(...data);
 | 
			
		||||
        setFiles(newFiles);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const onPaginationChange = (e, { activePage }) => {
 | 
			
		||||
    (async () => {
 | 
			
		||||
      if (activePage === Math.ceil(files.length / ITEMS_PER_PAGE) + 1) {
 | 
			
		||||
        // In this case we have to load more data and then append them.
 | 
			
		||||
        await loadFiles(activePage - 1);
 | 
			
		||||
      }
 | 
			
		||||
      setActivePage(activePage);
 | 
			
		||||
    })();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    loadFiles(0)
 | 
			
		||||
      .then()
 | 
			
		||||
      .catch((reason) => {
 | 
			
		||||
        showError(reason);
 | 
			
		||||
      });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const downloadFile = (link, filename) => {
 | 
			
		||||
    let linkElement = document.createElement('a');
 | 
			
		||||
    linkElement.download = filename;
 | 
			
		||||
    linkElement.href = '/upload/' + link;
 | 
			
		||||
    linkElement.click();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const copyLink = (link) => {
 | 
			
		||||
    let url = window.location.origin + '/upload/' + link;
 | 
			
		||||
    copy(url).then();
 | 
			
		||||
    showSuccess('链接已复制到剪贴板');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const deleteFile = async (id, idx) => {
 | 
			
		||||
    const res = await API.delete(`/api/file/${id}`);
 | 
			
		||||
    const { success, message } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      let newFiles = [...files];
 | 
			
		||||
      let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
 | 
			
		||||
      newFiles[realIdx].deleted = true;
 | 
			
		||||
      // newFiles.splice(idx, 1);
 | 
			
		||||
      setFiles(newFiles);
 | 
			
		||||
      showSuccess('文件已删除!');
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const searchFiles = async () => {
 | 
			
		||||
    if (searchKeyword === '') {
 | 
			
		||||
      // if keyword is blank, load files instead.
 | 
			
		||||
      await loadFiles(0);
 | 
			
		||||
      setActivePage(1);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setSearching(true);
 | 
			
		||||
    const res = await API.get(`/api/file/search?keyword=${searchKeyword}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      setFiles(data);
 | 
			
		||||
      setActivePage(1);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
    setSearching(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleKeywordChange = async (e, { value }) => {
 | 
			
		||||
    setSearchKeyword(value.trim());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const sortFile = (key) => {
 | 
			
		||||
    if (files.length === 0) return;
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    let sortedUsers = [...files];
 | 
			
		||||
    sortedUsers.sort((a, b) => {
 | 
			
		||||
      return ('' + a[key]).localeCompare(b[key]);
 | 
			
		||||
    });
 | 
			
		||||
    if (sortedUsers[0].id === files[0].id) {
 | 
			
		||||
      sortedUsers.reverse();
 | 
			
		||||
    }
 | 
			
		||||
    setFiles(sortedUsers);
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const uploadFiles = async () => {
 | 
			
		||||
    if (acceptedFiles.length === 0) return;
 | 
			
		||||
    setUploading(true);
 | 
			
		||||
    let formData = new FormData();
 | 
			
		||||
    for (let i = 0; i < acceptedFiles.length; i++) {
 | 
			
		||||
      formData.append('file', acceptedFiles[i]);
 | 
			
		||||
    }
 | 
			
		||||
    const res = await API.post(`/api/file`, formData, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Content-Type': 'multipart/form-data',
 | 
			
		||||
      },
 | 
			
		||||
      onUploadProgress: (e) => {
 | 
			
		||||
        let uploadProgress = ((e.loaded / e.total) * 100).toFixed(2);
 | 
			
		||||
        setUploadProgress(uploadProgress);
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    const { success, message } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      showSuccess(`${acceptedFiles.length} 个文件上传成功!`);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
    setUploading(false);
 | 
			
		||||
    setUploadProgress('0');
 | 
			
		||||
    setSearchKeyword('');
 | 
			
		||||
    loadFiles(0).then();
 | 
			
		||||
    setActivePage(1);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    uploadFiles().then();
 | 
			
		||||
  }, [acceptedFiles]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Segment
 | 
			
		||||
        placeholder
 | 
			
		||||
        {...getRootProps({ className: 'dropzone' })}
 | 
			
		||||
        loading={uploading || loading}
 | 
			
		||||
        style={{ cursor: 'pointer' }}
 | 
			
		||||
      >
 | 
			
		||||
        <Header icon>
 | 
			
		||||
          <Icon name='file outline' />
 | 
			
		||||
          拖拽上传或点击上传
 | 
			
		||||
          <input {...getInputProps()} />
 | 
			
		||||
        </Header>
 | 
			
		||||
      </Segment>
 | 
			
		||||
      {uploading ? (
 | 
			
		||||
        <Progress
 | 
			
		||||
          percent={uploadProgress}
 | 
			
		||||
          success
 | 
			
		||||
          progress='percent'
 | 
			
		||||
        ></Progress>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
      <Form onSubmit={searchFiles}>
 | 
			
		||||
        <Form.Input
 | 
			
		||||
          icon='search'
 | 
			
		||||
          fluid
 | 
			
		||||
          iconPosition='left'
 | 
			
		||||
          placeholder='搜索文件的名称,上传者以及描述信息 ...'
 | 
			
		||||
          value={searchKeyword}
 | 
			
		||||
          loading={searching}
 | 
			
		||||
          onChange={handleKeywordChange}
 | 
			
		||||
        />
 | 
			
		||||
      </Form>
 | 
			
		||||
 | 
			
		||||
      <Table basic>
 | 
			
		||||
        <Table.Header>
 | 
			
		||||
          <Table.Row>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortFile('filename');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              文件名
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortFile('uploader_id');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              上传者
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortFile('email');
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              上传时间
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell>操作</Table.HeaderCell>
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Header>
 | 
			
		||||
 | 
			
		||||
        <Table.Body>
 | 
			
		||||
          {files
 | 
			
		||||
            .slice(
 | 
			
		||||
              (activePage - 1) * ITEMS_PER_PAGE,
 | 
			
		||||
              activePage * ITEMS_PER_PAGE
 | 
			
		||||
            )
 | 
			
		||||
            .map((file, idx) => {
 | 
			
		||||
              if (file.deleted) return <></>;
 | 
			
		||||
              return (
 | 
			
		||||
                <Table.Row key={file.id}>
 | 
			
		||||
                  <Table.Cell>
 | 
			
		||||
                    <a href={'/upload/' + file.link} target='_blank'>
 | 
			
		||||
                      {file.filename}
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </Table.Cell>
 | 
			
		||||
                  <Popup
 | 
			
		||||
                    content={'上传者 ID:' + file.uploader_id}
 | 
			
		||||
                    trigger={<Table.Cell>{file.uploader}</Table.Cell>}
 | 
			
		||||
                  />
 | 
			
		||||
                  <Table.Cell>{file.upload_time}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        positive
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          downloadFile(file.link, file.filename);
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        下载
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        negative
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          deleteFile(file.id, idx).then();
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        删除
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          copyLink(file.link);
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        复制链接
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Table.Cell>
 | 
			
		||||
                </Table.Row>
 | 
			
		||||
              );
 | 
			
		||||
            })}
 | 
			
		||||
        </Table.Body>
 | 
			
		||||
 | 
			
		||||
        <Table.Footer>
 | 
			
		||||
          <Table.Row>
 | 
			
		||||
            <Table.HeaderCell colSpan='6'>
 | 
			
		||||
              <Pagination
 | 
			
		||||
                floated='right'
 | 
			
		||||
                activePage={activePage}
 | 
			
		||||
                onPageChange={onPaginationChange}
 | 
			
		||||
                size='small'
 | 
			
		||||
                siblingRange={1}
 | 
			
		||||
                totalPages={
 | 
			
		||||
                  Math.ceil(files.length / ITEMS_PER_PAGE) +
 | 
			
		||||
                  (files.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Footer>
 | 
			
		||||
      </Table>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default FilesTable;
 | 
			
		||||
@@ -14,9 +14,9 @@ const headerButtons = [
 | 
			
		||||
    icon: 'home',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '文件',
 | 
			
		||||
    to: '/file',
 | 
			
		||||
    icon: 'file',
 | 
			
		||||
    name: '渠道',
 | 
			
		||||
    to: '/channel',
 | 
			
		||||
    icon: 'sitemap',
 | 
			
		||||
    admin: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Header, Segment } from 'semantic-ui-react';
 | 
			
		||||
import FilesTable from '../../components/FilesTable';
 | 
			
		||||
import ChannelsTable from '../../components/ChannelsTable';
 | 
			
		||||
 | 
			
		||||
const File = () => (
 | 
			
		||||
  <>
 | 
			
		||||
    <Segment>
 | 
			
		||||
      <Header as='h3'>管理文件</Header>
 | 
			
		||||
      <FilesTable />
 | 
			
		||||
      <Header as='h3'>管理渠道</Header>
 | 
			
		||||
      <ChannelsTable />
 | 
			
		||||
    </Segment>
 | 
			
		||||
  </>
 | 
			
		||||
);
 | 
			
		||||
		Reference in New Issue
	
	Block a user