| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- import React, { useEffect, useState } from 'react';
- import {
- Button,
- Space,
- Table,
- Form,
- Typography,
- Empty,
- Divider,
- Avatar,
- Modal,
- Tag,
- Switch
- } from '@douyinfe/semi-ui';
- import {
- IllustrationNoResult,
- IllustrationNoResultDark
- } from '@douyinfe/semi-illustrations';
- import {
- Plus,
- Edit,
- Trash2,
- Save,
- Settings
- } from 'lucide-react';
- import { API, showError, showSuccess } from '../../../helpers';
- import { useTranslation } from 'react-i18next';
- const { Text } = Typography;
- const SettingsAPIInfo = ({ options, refresh }) => {
- const { t } = useTranslation();
- const [apiInfoList, setApiInfoList] = useState([]);
- const [showApiModal, setShowApiModal] = useState(false);
- const [showDeleteModal, setShowDeleteModal] = useState(false);
- const [deletingApi, setDeletingApi] = useState(null);
- const [editingApi, setEditingApi] = useState(null);
- const [modalLoading, setModalLoading] = useState(false);
- const [loading, setLoading] = useState(false);
- const [hasChanges, setHasChanges] = useState(false);
- const [apiForm, setApiForm] = useState({
- url: '',
- description: '',
- route: '',
- color: 'blue'
- });
- const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(10);
- const [selectedRowKeys, setSelectedRowKeys] = useState([]);
- // 面板启用状态 state
- const [panelEnabled, setPanelEnabled] = useState(true);
- const colorOptions = [
- { value: 'blue', label: 'blue' },
- { value: 'green', label: 'green' },
- { value: 'cyan', label: 'cyan' },
- { value: 'purple', label: 'purple' },
- { value: 'pink', label: 'pink' },
- { value: 'red', label: 'red' },
- { value: 'orange', label: 'orange' },
- { value: 'amber', label: 'amber' },
- { value: 'yellow', label: 'yellow' },
- { value: 'lime', label: 'lime' },
- { value: 'light-green', label: 'light-green' },
- { value: 'teal', label: 'teal' },
- { value: 'light-blue', label: 'light-blue' },
- { value: 'indigo', label: 'indigo' },
- { value: 'violet', label: 'violet' },
- { value: 'grey', label: 'grey' }
- ];
- const updateOption = async (key, value) => {
- const res = await API.put('/api/option/', {
- key,
- value,
- });
- const { success, message } = res.data;
- if (success) {
- showSuccess('API信息已更新');
- if (refresh) refresh();
- } else {
- showError(message);
- }
- };
- const submitApiInfo = async () => {
- try {
- setLoading(true);
- const apiInfoJson = JSON.stringify(apiInfoList);
- await updateOption('console_setting.api_info', apiInfoJson);
- setHasChanges(false);
- } catch (error) {
- console.error('API信息更新失败', error);
- showError('API信息更新失败');
- } finally {
- setLoading(false);
- }
- };
- const handleAddApi = () => {
- setEditingApi(null);
- setApiForm({
- url: '',
- description: '',
- route: '',
- color: 'blue'
- });
- setShowApiModal(true);
- };
- const handleEditApi = (api) => {
- setEditingApi(api);
- setApiForm({
- url: api.url,
- description: api.description,
- route: api.route,
- color: api.color
- });
- setShowApiModal(true);
- };
- const handleDeleteApi = (api) => {
- setDeletingApi(api);
- setShowDeleteModal(true);
- };
- const confirmDeleteApi = () => {
- if (deletingApi) {
- const newList = apiInfoList.filter(api => api.id !== deletingApi.id);
- setApiInfoList(newList);
- setHasChanges(true);
- showSuccess('API信息已删除,请及时点击“保存设置”进行保存');
- }
- setShowDeleteModal(false);
- setDeletingApi(null);
- };
- const handleSaveApi = async () => {
- if (!apiForm.url || !apiForm.route || !apiForm.description) {
- showError('请填写完整的API信息');
- return;
- }
- try {
- setModalLoading(true);
- let newList;
- if (editingApi) {
- newList = apiInfoList.map(api =>
- api.id === editingApi.id
- ? { ...api, ...apiForm }
- : api
- );
- } else {
- const newId = Math.max(...apiInfoList.map(api => api.id), 0) + 1;
- const newApi = {
- id: newId,
- ...apiForm
- };
- newList = [...apiInfoList, newApi];
- }
- setApiInfoList(newList);
- setHasChanges(true);
- setShowApiModal(false);
- showSuccess(editingApi ? 'API信息已更新,请及时点击“保存设置”进行保存' : 'API信息已添加,请及时点击“保存设置”进行保存');
- } catch (error) {
- showError('操作失败: ' + error.message);
- } finally {
- setModalLoading(false);
- }
- };
- const parseApiInfo = (apiInfoStr) => {
- if (!apiInfoStr) {
- setApiInfoList([]);
- return;
- }
- try {
- const parsed = JSON.parse(apiInfoStr);
- setApiInfoList(Array.isArray(parsed) ? parsed : []);
- } catch (error) {
- console.error('解析API信息失败:', error);
- setApiInfoList([]);
- }
- };
- useEffect(() => {
- const apiInfoStr = options['console_setting.api_info'] ?? options.ApiInfo;
- if (apiInfoStr !== undefined) {
- parseApiInfo(apiInfoStr);
- }
- }, [options['console_setting.api_info'], options.ApiInfo]);
- useEffect(() => {
- const enabledStr = options['console_setting.api_info_enabled'];
- setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true);
- }, [options['console_setting.api_info_enabled']]);
- const handleToggleEnabled = async (checked) => {
- const newValue = checked ? 'true' : 'false';
- try {
- const res = await API.put('/api/option/', {
- key: 'console_setting.api_info_enabled',
- value: newValue,
- });
- if (res.data.success) {
- setPanelEnabled(checked);
- showSuccess(t('设置已保存'));
- refresh?.();
- } else {
- showError(res.data.message);
- }
- } catch (err) {
- showError(err.message);
- }
- };
- const columns = [
- {
- title: 'ID',
- dataIndex: 'id',
- },
- {
- title: t('API地址'),
- dataIndex: 'url',
- render: (text, record) => (
- <Tag
- color={record.color}
- shape='circle'
- style={{ maxWidth: '280px' }}
- >
- {text}
- </Tag>
- ),
- },
- {
- title: t('线路描述'),
- dataIndex: 'route',
- render: (text, record) => (
- <Tag shape='circle'>
- {text}
- </Tag>
- ),
- },
- {
- title: t('说明'),
- dataIndex: 'description',
- ellipsis: true,
- render: (text, record) => (
- <Tag shape='circle'>
- {text || '-'}
- </Tag>
- ),
- },
- {
- title: t('颜色'),
- dataIndex: 'color',
- render: (color) => (
- <Avatar
- size="extra-extra-small"
- color={color}
- />
- ),
- },
- {
- title: t('操作'),
- fixed: 'right',
- width: 150,
- render: (_, record) => (
- <Space>
- <Button
- icon={<Edit size={14} />}
- theme='light'
- type='tertiary'
- size='small'
- onClick={() => handleEditApi(record)}
- >
- {t('编辑')}
- </Button>
- <Button
- icon={<Trash2 size={14} />}
- type='danger'
- theme='light'
- size='small'
- onClick={() => handleDeleteApi(record)}
- >
- {t('删除')}
- </Button>
- </Space>
- ),
- },
- ];
- const handleBatchDelete = () => {
- if (selectedRowKeys.length === 0) {
- showError('请先选择要删除的API信息');
- return;
- }
- const newList = apiInfoList.filter(api => !selectedRowKeys.includes(api.id));
- setApiInfoList(newList);
- setSelectedRowKeys([]);
- setHasChanges(true);
- showSuccess(`已删除 ${selectedRowKeys.length} 个API信息,请及时点击“保存设置”进行保存`);
- };
- const renderHeader = () => (
- <div className="flex flex-col w-full">
- <div className="mb-2">
- <div className="flex items-center text-blue-500">
- <Settings size={16} className="mr-2" />
- <Text>{t('API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)')}</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={<Plus size={14} />}
- className="w-full md:w-auto"
- onClick={handleAddApi}
- >
- {t('添加API')}
- </Button>
- <Button
- icon={<Trash2 size={14} />}
- type='danger'
- theme='light'
- onClick={handleBatchDelete}
- disabled={selectedRowKeys.length === 0}
- className="w-full md:w-auto"
- >
- {t('批量删除')} {selectedRowKeys.length > 0 && `(${selectedRowKeys.length})`}
- </Button>
- <Button
- icon={<Save size={14} />}
- onClick={submitApiInfo}
- loading={loading}
- disabled={!hasChanges}
- type='secondary'
- className="w-full md:w-auto"
- >
- {t('保存设置')}
- </Button>
- </div>
- {/* 启用开关 */}
- <div className="order-1 md:order-2 flex items-center gap-2">
- <Switch
- checked={panelEnabled}
- onChange={handleToggleEnabled}
- />
- <Text>{panelEnabled ? t('已启用') : t('已禁用')}</Text>
- </div>
- </div>
- </div>
- );
- // 计算当前页显示的数据
- const getCurrentPageData = () => {
- const startIndex = (currentPage - 1) * pageSize;
- const endIndex = startIndex + pageSize;
- return apiInfoList.slice(startIndex, endIndex);
- };
- const rowSelection = {
- selectedRowKeys,
- onChange: (selectedRowKeys, selectedRows) => {
- setSelectedRowKeys(selectedRowKeys);
- },
- onSelect: (record, selected, selectedRows) => {
- console.log(`选择行: ${selected}`, record);
- },
- onSelectAll: (selected, selectedRows) => {
- console.log(`全选: ${selected}`, selectedRows);
- },
- getCheckboxProps: (record) => ({
- disabled: false,
- name: record.id,
- }),
- };
- return (
- <>
- <Form.Section text={renderHeader()}>
- <Table
- columns={columns}
- dataSource={getCurrentPageData()}
- rowSelection={rowSelection}
- rowKey="id"
- scroll={{ x: 'max-content' }}
- pagination={{
- currentPage: currentPage,
- pageSize: pageSize,
- total: apiInfoList.length,
- showSizeChanger: true,
- showQuickJumper: true,
- formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
- start: page.currentStart,
- end: page.currentEnd,
- total: apiInfoList.length,
- }),
- pageSizeOptions: ['5', '10', '20', '50'],
- onChange: (page, size) => {
- setCurrentPage(page);
- setPageSize(size);
- },
- onShowSizeChange: (current, size) => {
- setCurrentPage(1);
- setPageSize(size);
- }
- }}
- size='middle'
- loading={loading}
- empty={
- <Empty
- image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
- darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
- description={t('暂无API信息')}
- style={{ padding: 30 }}
- />
- }
- className="overflow-hidden"
- />
- </Form.Section>
- <Modal
- title={editingApi ? t('编辑API') : t('添加API')}
- visible={showApiModal}
- onOk={handleSaveApi}
- onCancel={() => setShowApiModal(false)}
- okText={t('保存')}
- cancelText={t('取消')}
- confirmLoading={modalLoading}
- >
- <Form layout='vertical' initValues={apiForm} key={editingApi ? editingApi.id : 'new'}>
- <Form.Input
- field='url'
- label={t('API地址')}
- placeholder='https://api.example.com'
- rules={[{ required: true, message: t('请输入API地址') }]}
- onChange={(value) => setApiForm({ ...apiForm, url: value })}
- />
- <Form.Input
- field='route'
- label={t('线路描述')}
- placeholder={t('如:香港线路')}
- rules={[{ required: true, message: t('请输入线路描述') }]}
- onChange={(value) => setApiForm({ ...apiForm, route: value })}
- />
- <Form.Input
- field='description'
- label={t('说明')}
- placeholder={t('如:大带宽批量分析图片推荐')}
- rules={[{ required: true, message: t('请输入说明') }]}
- onChange={(value) => setApiForm({ ...apiForm, description: value })}
- />
- <Form.Select
- field='color'
- label={t('标识颜色')}
- optionList={colorOptions}
- onChange={(value) => setApiForm({ ...apiForm, color: value })}
- render={(option) => (
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
- <Avatar
- size="extra-extra-small"
- color={option.value}
- />
- {option.label}
- </div>
- )}
- />
- </Form>
- </Modal>
- <Modal
- title={t('确认删除')}
- visible={showDeleteModal}
- onOk={confirmDeleteApi}
- onCancel={() => {
- setShowDeleteModal(false);
- setDeletingApi(null);
- }}
- okText={t('确认删除')}
- cancelText={t('取消')}
- type="warning"
- okButtonProps={{
- type: 'danger',
- theme: 'solid'
- }}
- >
- <Text>{t('确定要删除此API信息吗?')}</Text>
- </Modal>
- </>
- );
- };
- export default SettingsAPIInfo;
|