diff --git a/admin-frontend/src/component/subscribeCard.tsx b/admin-frontend/src/component/subscribeCard.tsx new file mode 100644 index 0000000..6f73084 --- /dev/null +++ b/admin-frontend/src/component/subscribeCard.tsx @@ -0,0 +1,85 @@ +import {CopyOutlined, DeleteOutlined} from '@ant-design/icons'; +import {Card, Col, Form, message, Popconfirm, Select, Tag, Tooltip} from 'antd'; +import React, {ReactNode, useContext, useState} from "react"; +import {addSubscribe, delSubscribe} from 'src/api/config'; +import {GlobalConfContext} from "src/utils/context"; +import {PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type'; + +interface TargetGroupSelectionProp { + config: SubscribeConfig, + groups: SubscribeResp + children: ReactNode +} + +function TargetGroupSelection({ config, groups, children }: TargetGroupSelectionProp) { + let [ selectedGroups, setSelectGroups ] = useState<Array<string>>([]); + const submitCopy = () => { + let promise = null + for (let selectGroup of selectedGroups) { + if (! promise) { + promise = addSubscribe(selectGroup, config) + } else { + promise = promise.then(() => addSubscribe(selectGroup, config)) + } + } + if (promise) { + promise.then(() => message.success("复制订阅成功")) + } + return promise; + } + return <> + <Popconfirm title={ + <Select mode="multiple" onChange={(value: Array<string>) => setSelectGroups(value)}> + { + Object.keys(groups).map((groupNumber) => + <Select.Option value={groupNumber} key={groupNumber}> + {`${groupNumber} - ${groups[groupNumber].name}`} + </Select.Option>) + } + </Select> + } onConfirm={submitCopy} > + { children } + </Popconfirm> + </> +} +interface SubscribeCardProp { + groupNumber: string + config: SubscribeConfig + groupSubscribes: SubscribeResp + reload: () => null +} +export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: SubscribeCardProp) { + const globalConf = useContext(GlobalConfContext); + const platformConf = globalConf.platformConf[config.platformName] as PlatformConfig; + const handleDelete = (groupNumber: string, platformName: string, target: string) => () => { + delSubscribe(groupNumber, platformName, target).then(() => { + reload() + }) + } + return ( + <Col span={6} key={`${config.platformName}-${config.target}`}> + <Card title={`${platformConf.name} - ${config.targetName}`} + actions={[ + <Popconfirm title={`确定要删除 ${platformConf.name} - ${config.targetName}`} + onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}> + <Tooltip title="删除" ><DeleteOutlined /></Tooltip> + </Popconfirm>, + <TargetGroupSelection config={config} groups={groupSubscribes}> + <Tooltip title="添加到其他群"><CopyOutlined /></Tooltip> + </TargetGroupSelection> + ]}> + <Form labelCol={{ span: 6 }}> + <Form.Item label="订阅类型"> + {Object.keys(platformConf.categories).length > 0 ? + config.cats.map((catKey: number) => (<Tag color="green" key={catKey}>{platformConf.categories[catKey]}</Tag>)) : + <Tag color="red">不支持类型</Tag>} + </Form.Item> + <Form.Item label="订阅Tag"> + {platformConf.enabledTag ? config.tags.length > 0 ? config.tags.map(tag => (<Tag color="green" key={tag}>{tag}</Tag>)) : (<Tag color="blue">全部标签</Tag>) : + <Tag color="red">不支持Tag</Tag>} + </Form.Item> + </Form> + </Card> + </Col> + ) +} diff --git a/admin-frontend/src/pages/admin/configPage/index.tsx b/admin-frontend/src/pages/admin/configPage/index.tsx new file mode 100644 index 0000000..2e84dc4 --- /dev/null +++ b/admin-frontend/src/pages/admin/configPage/index.tsx @@ -0,0 +1,201 @@ +import {CopyOutlined, DeleteOutlined} from '@ant-design/icons'; +import {Button, Card, Col, Collapse, Empty, Form, Input, message, Modal, Popconfirm, Row, Select, Tag, Tooltip} from 'antd'; +import React, {ReactElement, ReactNode, useContext, useEffect, useState} from "react"; +import {addSubscribe, delSubscribe, getSubscribe, getTargetName} from 'src/api/config'; +import {InputTag} from 'src/component/inputTag'; +import {GlobalConfContext} from "src/utils/context"; +import {CategoryConfig, PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type'; + +interface ConfigPageProp { + tab: string +} +export function ConfigPage(prop: ConfigPageProp) { + const [ configData, setConfigData ] = useState<SubscribeResp>({}); + const [ showModal, setShowModal ] = useState<boolean>(false); + const [ currentAddingGroupNumber, setCurrentAddingGroupNumber ] = useState(''); + const globalConf = useContext(GlobalConfContext); + const loadData = () => { + getSubscribe() + .then(res => { + setConfigData(_ => res); + }); + } + useEffect(() => { + loadData() + }, [prop.tab]); + const clickNew = (groupNumber: string) => (e: React.MouseEvent<HTMLButtonElement>) => { + setShowModal(_ => true); + setCurrentAddingGroupNumber(groupNumber); + e.stopPropagation(); + } + + if (Object.keys(configData).length === 0) { + return <Empty /> + } else { + let groups: Array<ReactElement> = []; + for (let key of Object.keys(configData)) { + let value = configData[key]; + groups.push( + <Collapse.Panel header={ + <span>{`${key} - ${value.name}`}<Button style={{float: "right"}} onClick={clickNew(key)}>添加</Button></span> + } key={key}> + <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }} align="middle"> + {value.subscribes.map(genCard(key))} + </Row> + </Collapse.Panel> + ) + } + return ( + <div> + <Collapse> + {groups} + </Collapse> + <AddModal groupNumber={currentAddingGroupNumber} showModal={showModal} + refresh={loadData} setShowModal={(s: boolean) => setShowModal(_ => s)} /> + </div> + ) + } +} + + +interface InputTagCustomProp { + value?: Array<string>, + onChange?: (value: Array<string>) => void, + disabled?: boolean +} +function InputTagCustom(prop: InputTagCustomProp) { + const [value, setValue] = useState(prop.value || []); + const handleSetValue = (newVal: Array<string>) => { + setValue(() => newVal); + if (prop.onChange) { + prop.onChange(newVal); + } + } + return ( + <> + { + prop.disabled ? <Tag color="red">不支持标签</Tag>: + <> + {value.length === 0 && + <Tag color="green">全部标签</Tag> + } + <InputTag color="blue" addText="添加标签" value={value} onChange={handleSetValue} /> + </> + } + </> + ) +} + +interface AddModalProp { + showModal: boolean, + groupNumber: string, + setShowModal: (s: boolean) => void, + refresh: () => void +} +function AddModal(prop: AddModalProp) { + const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false); + const { platformConf } = useContext(GlobalConfContext); + const [ hasTarget, setHasTarget ] = useState(false); + const [ categories, setCategories ] = useState({} as CategoryConfig); + const [ enabledTag, setEnableTag ] = useState(false); + const [ form ] = Form.useForm(); + const changePlatformSelect = (platform: string) => { + setHasTarget(_ => platformConf[platform].hasTarget); + setCategories(_ => platformConf[platform].categories); + setEnableTag(platformConf[platform].enabledTag) + if (! platformConf[platform].hasTarget) { + getTargetName(platform, 'default') + .then(res => { + console.log(res) + form.setFieldsValue({ + targetName: res.targetName, + target: '' + }) + }) + } else { + form.setFieldsValue({ + targetName: '', + target: '' + }) + } + } + const handleSubmit = (value: any) => { + let newVal = Object.assign({}, value) + if (typeof newVal.tags != 'object') { + newVal.tags = [] + } + if (newVal.target === '') { + newVal.target = 'default' + } + addSubscribe(prop.groupNumber, newVal) + .then(() => { + setConfirmLoading(false); + prop.setShowModal(false); + prop.refresh(); + }); + } + const handleModleFinish = () => { + form.submit(); + setConfirmLoading(() => true); + } + + return <Modal title="添加订阅" visible={prop.showModal} + confirmLoading={confirmLoading} onCancel={() => prop.setShowModal(false)} + onOk={handleModleFinish}> + <Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit} + initialValues={{tags: [], categories: []}}> + <Form.Item label="平台" name="platformName" rules={[]}> + <Select style={{ width: '80%' }} onChange={changePlatformSelect}> + {Object.keys(platformConf).map(platformName => + <Select.Option key={platformName} value={platformName}>{platformConf[platformName].name}</Select.Option> + )} + </Select> + </Form.Item> + <Form.Item label="账号" name="target" rules={[ + {required: hasTarget, message: "请输入账号"}, + {validator: async (_, value) => { + try { + const res = await getTargetName(form.getFieldValue('platformName'), value); + if (res.targetName) { + form.setFieldsValue({ + targetName: res.targetName + }) + return Promise.resolve() + } else { + form.setFieldsValue({ + targetName: '' + }) + return Promise.reject("账号不正确,请重新检查账号") + } + } catch { + return Promise.reject('服务器错误,请稍后再试') + } + } + } + ]}> + <Input placeholder={hasTarget ? "获取方式见文档" : "此平台不需要账号"} + disabled={! hasTarget} style={{ width: "80%" }}/> + </Form.Item> + <Form.Item label="账号名称" name="targetName"> + <Input style={{ width: "80%" }} disabled /> + </Form.Item> + <Form.Item label="订阅分类" name="categories"> + <Select style={{ width: '80%' }} mode="multiple" + disabled={Object.keys(categories).length === 0} + placeholder={Object.keys(categories).length > 0 ? + "请选择要订阅的分类" : "本平台不支持分类"}> + {Object.keys(categories).length > 0 && + Object.keys(categories).map((indexStr) => + <Select.Option key={indexStr} value={parseInt(indexStr)}> + {categories[parseInt(indexStr)]} + </Select.Option> + ) + } + </Select> + </Form.Item> + <Form.Item label="订阅Tag" name="tags"> + <InputTagCustom disabled={!enabledTag}/> + </Form.Item> + </Form> + </Modal> +} diff --git a/admin-frontend/src/pages/admin/index.tsx b/admin-frontend/src/pages/admin/index.tsx index fb66804..38e998e 100644 --- a/admin-frontend/src/pages/admin/index.tsx +++ b/admin-frontend/src/pages/admin/index.tsx @@ -1,12 +1,9 @@ -import React, { FC, ReactElement, ReactNode, useContext, useEffect, useState } from "react"; -import { LoginContext, GlobalConfContext } from "../utils/context"; -import { Layout, Menu, Empty, Collapse, Card, Tag, Row, Col, Form, Tooltip, Button, Modal, Select, - Input, Popconfirm, message } from 'antd'; -import { SubscribeConfig, SubscribeResp, PlatformConfig, CategoryConfig } from '../utils/type'; -import { SettingOutlined, BugOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons'; -import { getSubscribe, getTargetName, addSubscribe, delSubscribe } from '../api/config'; -import { InputTag } from '../component/inputTag'; +import {BugOutlined, SettingOutlined} from '@ant-design/icons'; +import {Layout, Menu} from 'antd'; +import React, {useContext, useState} from "react"; +import {LoginContext} from "src/utils/context"; import './admin.css'; +import {ConfigPage} from './configPage'; export function Admin() { const { login } = useContext(LoginContext); @@ -37,265 +34,3 @@ export function Admin() { ) } -interface ConfigPageProp { - tab: string -} -function ConfigPage(prop: ConfigPageProp) { - const [ configData, setConfigData ] = useState<SubscribeResp>({}); - const [ showModal, setShowModal ] = useState<boolean>(false); - const [ currentAddingGroupNumber, setCurrentAddingGroupNumber ] = useState(''); - const globalConf = useContext(GlobalConfContext); - const loadData = () => { - getSubscribe() - .then(res => { - setConfigData(_ => res); - }); - } - useEffect(() => { - loadData() - }, [prop.tab]); - const clickNew = (groupNumber: string) => (e: React.MouseEvent<HTMLButtonElement>) => { - setShowModal(_ => true); - setCurrentAddingGroupNumber(groupNumber); - e.stopPropagation(); - } - const handleDelete = (groupNumber: string, platformName: string, target: string) => () => { - delSubscribe(groupNumber, platformName, target).then(() => { - loadData() - }) - } - const genCard = (groupNumber: string) => (config: SubscribeConfig) => { - const platformConf = globalConf.platformConf[config.platformName] as PlatformConfig; - return ( - <Col span={6} key={`${config.platformName}-${config.target}`}> - <Card title={`${platformConf.name} - ${config.targetName}`} - actions={[ - <Popconfirm title={`确定要删除 ${platformConf.name} - ${config.targetName}`} - onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}> - <Tooltip title="删除" ><DeleteOutlined /></Tooltip> - </Popconfirm>, - <TargetGroupSelection config={config} groups={configData}> - <Tooltip title="添加到其他群"><CopyOutlined /></Tooltip> - </TargetGroupSelection> - ]}> - <Form labelCol={{ span: 6 }}> - <Form.Item label="订阅类型"> - {Object.keys(platformConf.categories).length > 0 ? - config.cats.map((catKey: number) => (<Tag color="green" key={catKey}>{platformConf.categories[catKey]}</Tag>)) : - <Tag color="red">不支持类型</Tag>} - </Form.Item> - <Form.Item label="订阅Tag"> - {platformConf.enabledTag ? config.tags.length > 0 ? config.tags.map(tag => (<Tag color="green" key={tag}>{tag}</Tag>)) : (<Tag color="blue">全部标签</Tag>) : - <Tag color="red">不支持Tag</Tag>} - </Form.Item> - </Form> - </Card> - </Col> - ) - } - if (Object.keys(configData).length === 0) { - return <Empty /> - } else { - let groups: Array<ReactElement> = []; - for (let key of Object.keys(configData)) { - let value = configData[key]; - groups.push( - <Collapse.Panel header={ - <span>{`${key} - ${value.name}`}<Button style={{float: "right"}} onClick={clickNew(key)}>添加</Button></span> - } key={key}> - <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }} align="middle"> - {value.subscribes.map(genCard(key))} - </Row> - </Collapse.Panel> - ) - } - return ( - <div> - <Collapse> - {groups} - </Collapse> - <AddModal groupNumber={currentAddingGroupNumber} showModal={showModal} - refresh={loadData} setShowModal={(s: boolean) => setShowModal(_ => s)} /> - </div> - ) - } -} - -interface TargetGroupSelectionProp { - config: SubscribeConfig, - groups: SubscribeResp - children: ReactNode -} -function TargetGroupSelection({ config, groups, children }: TargetGroupSelectionProp) { - let [ selectedGroups, setSelectGroups ] = useState<Array<string>>([]); - const submitCopy = () => { - let promise = null - for (let selectGroup of selectedGroups) { - if (! promise) { - promise = addSubscribe(selectGroup, config) - } else { - promise = promise.then(() => addSubscribe(selectGroup, config)) - } - } - if (promise) { - promise.then(() => message.success("复制订阅成功")) - } - return promise; - } - return <> - <Popconfirm title={ - <Select mode="multiple" onChange={(value: Array<string>) => setSelectGroups(value)}> - { - Object.keys(groups).map((groupNumber) => - <Select.Option value={groupNumber} key={groupNumber}> - {`${groupNumber} - ${groups[groupNumber].name}`} - </Select.Option>) - } - </Select> - } onConfirm={submitCopy} > - { children } - </Popconfirm> - </> -} - -interface InputTagCustomProp { - value?: Array<string>, - onChange?: (value: Array<string>) => void, - disabled?: boolean -} -function InputTagCustom(prop: InputTagCustomProp) { - const [value, setValue] = useState(prop.value || []); - const handleSetValue = (newVal: Array<string>) => { - setValue(() => newVal); - if (prop.onChange) { - prop.onChange(newVal); - } - } - return ( - <> - { - prop.disabled ? <Tag color="red">不支持标签</Tag>: - <> - {value.length === 0 && - <Tag color="green">全部标签</Tag> - } - <InputTag color="blue" addText="添加标签" value={value} onChange={handleSetValue} /> - </> - } - </> - ) -} - -interface AddModalProp { - showModal: boolean, - groupNumber: string, - setShowModal: (s: boolean) => void, - refresh: () => void -} -function AddModal(prop: AddModalProp) { - const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false); - const { platformConf } = useContext(GlobalConfContext); - const [ hasTarget, setHasTarget ] = useState(false); - const [ categories, setCategories ] = useState({} as CategoryConfig); - const [ enabledTag, setEnableTag ] = useState(false); - const [ form ] = Form.useForm(); - const changePlatformSelect = (platform: string) => { - setHasTarget(_ => platformConf[platform].hasTarget); - setCategories(_ => platformConf[platform].categories); - setEnableTag(platformConf[platform].enabledTag) - if (! platformConf[platform].hasTarget) { - getTargetName(platform, 'default') - .then(res => { - console.log(res) - form.setFieldsValue({ - targetName: res.targetName, - target: '' - }) - }) - } else { - form.setFieldsValue({ - targetName: '', - target: '' - }) - } - } - const handleSubmit = (value: any) => { - let newVal = Object.assign({}, value) - if (typeof newVal.tags != 'object') { - newVal.tags = [] - } - if (newVal.target === '') { - newVal.target = 'default' - } - addSubscribe(prop.groupNumber, newVal) - .then(() => { - setConfirmLoading(false); - prop.setShowModal(false); - prop.refresh(); - }); - } - const handleModleFinish = () => { - form.submit(); - setConfirmLoading(() => true); - } - - return <Modal title="添加订阅" visible={prop.showModal} - confirmLoading={confirmLoading} onCancel={() => prop.setShowModal(false)} - onOk={handleModleFinish}> - <Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit} - initialValues={{tags: [], categories: []}}> - <Form.Item label="平台" name="platformName" rules={[]}> - <Select style={{ width: '80%' }} onChange={changePlatformSelect}> - {Object.keys(platformConf).map(platformName => - <Select.Option key={platformName} value={platformName}>{platformConf[platformName].name}</Select.Option> - )} - </Select> - </Form.Item> - <Form.Item label="账号" name="target" rules={[ - {required: hasTarget, message: "请输入账号"}, - {validator: async (_, value) => { - try { - const res = await getTargetName(form.getFieldValue('platformName'), value); - if (res.targetName) { - form.setFieldsValue({ - targetName: res.targetName - }) - return Promise.resolve() - } else { - form.setFieldsValue({ - targetName: '' - }) - return Promise.reject("账号不正确,请重新检查账号") - } - } catch { - return Promise.reject('服务器错误,请稍后再试') - } - } - } - ]}> - <Input placeholder={hasTarget ? "获取方式见文档" : "此平台不需要账号"} - disabled={! hasTarget} style={{ width: "80%" }}/> - </Form.Item> - <Form.Item label="账号名称" name="targetName"> - <Input style={{ width: "80%" }} disabled /> - </Form.Item> - <Form.Item label="订阅分类" name="categories"> - <Select style={{ width: '80%' }} mode="multiple" - disabled={Object.keys(categories).length === 0} - placeholder={Object.keys(categories).length > 0 ? - "请选择要订阅的分类" : "本平台不支持分类"}> - {Object.keys(categories).length > 0 && - Object.keys(categories).map((indexStr) => - <Select.Option key={indexStr} value={parseInt(indexStr)}> - {categories[parseInt(indexStr)]} - </Select.Option> - ) - } - </Select> - </Form.Item> - <Form.Item label="订阅Tag" name="tags"> - <InputTagCustom disabled={!enabledTag}/> - </Form.Item> - </Form> - </Modal> -} diff --git a/admin-frontend/tsconfig.json b/admin-frontend/tsconfig.json index a273b0c..c03361d 100644 --- a/admin-frontend/tsconfig.json +++ b/admin-frontend/tsconfig.json @@ -18,7 +18,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "baseUrl": "./" }, "include": [ "src"