diff --git a/package.json b/package.json index 8e9b6a9..040a9a4 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "clsx": "^2.1.1", "dayjs": "^1.11.11", "file-saver": "^2.0.5", + "jszip": "^3.10.1", "qs": "^6.12.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/assets/index.scss b/src/assets/index.scss index c9f76ac..e0a4c3d 100644 --- a/src/assets/index.scss +++ b/src/assets/index.scss @@ -18,7 +18,6 @@ body { min-width: 1000px; } - .dashboard-layout { background-color: var(--main-bg-color); } @@ -29,7 +28,7 @@ body { box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); .nav-item { - padding: 0px 30px; + padding: 0px 20px; &.active{ @apply text-blue-500; } diff --git a/src/components/article/article.module.scss b/src/components/article/article.module.scss index 161d5ce..349a72a 100644 --- a/src/components/article/article.module.scss +++ b/src/components/article/article.module.scss @@ -24,6 +24,9 @@ .image { @apply border border-blue-200 p-2 flex-1 rounded focus:border-blue-200; min-height: 100px; + &:hover{ + @apply border-blue-500; + } :global{ .ant-upload-wrapper{ display: block; @@ -58,7 +61,13 @@ } .text { - @apply border border-blue-200 overflow-hidden flex-1 rounded focus:border-blue-200; + @apply border border-blue-200 overflow-hidden flex-1 rounded focus:border-blue-200 transition; + &:hover{ + @apply border-blue-500; + } + &:focus-within{ + @apply border-blue-500 shadow-md; + } } .textarea { diff --git a/src/components/article/block.tsx b/src/components/article/block.tsx index 6eedf46..65882e6 100644 --- a/src/components/article/block.tsx +++ b/src/components/article/block.tsx @@ -23,6 +23,7 @@ export default function ArticleBlock({className, blocks, editable, onRemove, onA // 删除当前项 onChange?.(blocks.filter((_, idx) => index !== idx)) } + const firstTextBlockIndex = blocks.findIndex(it => it.type === 'text') // 新增 const handleAddBlock = (type: 'text' | 'image', insertIndex: number = -1) => { const newBlock: BlockContent = type === 'text' ? {type: 'text', content: ''} : {type: 'image', content: ''}; @@ -44,41 +45,46 @@ export default function ArticleBlock({className, blocks, editable, onRemove, onA return
- {blocks.map((it, idx) => ( -
- { - it.type === 'text' - ? handleBlockChange(idx, block)} data={it} - editable={editable}/> - : - } - {editable &&
- - 请确认删除此{it.type === 'text' ? '文本' : '图片'}? -
} - onConfirm={() => handleBlockRemove(idx)} - okText="删除" - cancelText="取消" - > - - - - -
- handleAddBlock('text', idx + 1)} - className="article-action-icon" title="新增文本"> - handleAddBlock('image', idx + 1)} - className="article-action-icon mt-1" title="新增图片"> -
-
} -
- ))} + {blocks.map((it, idx) => { + const isFirstTextBlock = index == 0 && it.type ==='text' && firstTextBlockIndex == idx + return (
+
+ { + it.type === 'text' + ? handleBlockChange(idx, block)} data={it} + editable={editable}/> + : + } + {editable &&
+ {isFirstTextBlock?: + 请确认删除此{it.type === 'text' ? '文本' : '图片'}? +
} + onConfirm={() => handleBlockRemove(idx)} + okText="删除" + cancelText="取消" + > + + + + } +
+ handleAddBlock('text', idx + 1)} + className="article-action-icon" title="新增文本"> + handleAddBlock('image', idx + 1)} + className="article-action-icon mt-1" title="新增图片"> +
+
} +
+ {isFirstTextBlock &&
该编辑框内容由数字人播报
} +
) + } + )} {editable && blocks.length == 0 &&
diff --git a/src/components/article/edit-modal.tsx b/src/components/article/edit-modal.tsx index be2a3ba..6a731b6 100644 --- a/src/components/article/edit-modal.tsx +++ b/src/components/article/edit-modal.tsx @@ -2,7 +2,7 @@ import {Input, Modal} from "antd"; import ArticleGroup from "@/components/article/group.tsx"; import {useEffect, useState} from "react"; import {useSetState} from "ahooks"; -import {getArticleDetail} from "@/service/api/article.ts"; +import {getById} from "@/service/api/article.ts"; type Props = { id?: number; @@ -32,7 +32,7 @@ export default function ArticleEditModal(props: Props) { useEffect(() => { if(props.id){ if(props.id > 0){ - getArticleDetail(props.id).then(res => { + getById(props.id).then(res => { setGroups(res.content_group) setTitle(res.title) }) diff --git a/src/components/article/group.tsx b/src/components/article/group.tsx index 04f8e2f..20963f8 100644 --- a/src/components/article/group.tsx +++ b/src/components/article/group.tsx @@ -27,7 +27,9 @@ export default function ArticleGroup({groups, editable, onChange}: Props) { return
{groups.map((g, index) => ( { groups[index] = blocks onChange?.([...groups]) @@ -46,6 +48,6 @@ export default function ArticleGroup({groups, editable, onChange}: Props) { /> ))} {groups.length == 0 && editable && - onChange?.([blocks])} blocks={[]}/>} + onChange?.([blocks])} index={0} blocks={[{type:'text',content:''}]}/>}
} \ No newline at end of file diff --git a/src/components/article/item.tsx b/src/components/article/item.tsx index 2b62dd7..963cfbf 100644 --- a/src/components/article/item.tsx +++ b/src/components/article/item.tsx @@ -1,97 +1,98 @@ -import React, {useMemo, useRef, useState} from "react"; -import {Button, Input, Upload} from "antd"; -import {TextAreaRef} from "antd/es/input/TextArea"; +import React, {useState} from "react"; +import {Button, Input, Spin, Upload, UploadProps} from "antd"; import styles from './article.module.scss' +import {getOssPolicy} from "@/service/api/common.ts"; +import {showToast} from "@/components/message.ts"; +import {clsx} from "clsx"; type Props = { children?: React.ReactNode; className?: string; data: BlockContent; editable?: boolean; - groupIndex?: number; - blockIndex?: number; onChange?: (data: BlockContent) => void; + isFirstBlock?: boolean; } -export function BlockImage({data, editable}: Props) { +const MimeTypes = ['image/jpeg', 'image/png', 'image/jpg'] +const Data: { uploadConfig?: TOSSPolicy } = {} + +export function BlockImage({data, editable, onChange}: Props) { + + const [loading, setLoading] = useState(-1) + // oss上传文件所需的数据 + const getUploadData: UploadProps['data'] = (file) => ({ + key: file.url, + OSSAccessKeyId: Data.uploadConfig?.access_id, + policy: Data.uploadConfig?.policy, + Signature: Data.uploadConfig?.signature, + }); + const beforeUpload = async (file: any) => { + try { + // 因为有超时问题,所以每次上传都重新获取参数 + Data.uploadConfig = await getOssPolicy(); + const suffix = file.name.slice(file.name.lastIndexOf('.')); + const filename = Date.now().toString(16) + suffix; + file.url = Data.uploadConfig.dir + filename; + } catch (e) { + // 设置错误状态 + file.status = 'error' + throw e; + } + } + // 处理图片上传后的状态 + const onUploadChange = async (info) => { + if (info.fileList.length == 0) return; + const file = info.fileList[0]; + console.log('onChange', file); + if (file.status == 'done') { + setLoading(-1) + onChange?.({type: 'image', content: Data.uploadConfig?.host + file.url}) + } else if (file.status == 'error') { + setLoading(-1) + showToast('上传图片失败,请重试', 'warning') + } else if (file.status == 'uploading') { + setLoading(file.percent) + } + } + // return
{editable ?
- -
- {data.content ? <> - -
- 更换图片 -
- :
- -
} -
-
+ = 0} percent={loading == 0 ? 'auto' : loading}> + (Data.uploadConfig!.host)} + beforeUpload={beforeUpload} onChange={onUploadChange} + > +
+ {data.content ? <> + +
+ 更换图片 +
+ :
+ +
} +
+
+
:
}
} -export function BlockText({data, editable, onChange, groupIndex,blockIndex}: Props) { - const inputRef = useRef(null); - // 内容分割 - const contentSentence = useMemo(() => { - const textContent = data.content - if (!/[.|。]/.test(textContent)) { - return [textContent]; - } - const firstSentence = textContent.split(/[.|。]/)[0]! - // 获取第一个句子 - return [textContent.substring(0, firstSentence.length + 1), textContent.substring(firstSentence.length + 1)] - }, [data.content]) - - const [editorMode, setEditMode] = useState({ - preview: true - }) - const handleTextBlur = () => { - setEditMode({preview: true}) - } - return
- {editable ?
- {/*onChange?.({type:'text',content:e.target.value})}*/} - {/*>*/} - { - onChange?.({type: 'text', content: e.target.value}) - }} - placeholder={'请输入文本'} onBlur={handleTextBlur} value={data.content} autoSize={{minRows: 3}} - variant={"borderless"}/> - {groupIndex == 0 && blockIndex == 0 && -
{ - inputRef.current!.focus({cursor: 'end'}); - setEditMode({preview: false}) - }} style={editorMode.preview && data.content?.length > 0 ? { - padding: '4px 11px' - } : { - opacity: 0, - pointerEvents: 'none' - }}> - {contentSentence.map((sentence, index) => { - return {sentence}{index == 0 ? '(本句由数字人播报)' : ''} - })} -
} - {/*{firstSentence}*/} - {/**/} -
:

{data.content}

} +export function BlockText({data, editable, onChange, isFirstBlock}: Props) { + return
+
+ {editable ?
+ { + onChange?.({type: 'text', content: e.target.value}) + }} + placeholder={'请输入文本'} value={data.content} autoSize={{minRows: 3, maxRows: 8}} + variant={"borderless"}/> +
:

{data.content}

} +
} \ No newline at end of file diff --git a/src/components/message.ts b/src/components/message.ts new file mode 100644 index 0000000..00d304f --- /dev/null +++ b/src/components/message.ts @@ -0,0 +1,31 @@ +import {message} from "antd"; + +export function showToast(content: string, type?: 'success' | 'info' | 'warning' | 'error') { + + message.open({ + type, + content, + className: 'aui-toast' + }).then(); +} + +export function showLoading(content = 'Loading...') { + const key = 'globalLoading_' + (new Date().getTime()); + message.open({ + key, + type: 'loading', + content, + }).then(); + return { + update(content: string,type?: 'success' | 'info' | 'warning' | 'error'){ + message.open({ + key, + content, + type + }).then(); + }, + close(){ + message.destroy(key); + } + } +} \ No newline at end of file diff --git a/src/components/video/video-list-item.tsx b/src/components/video/video-list-item.tsx index 02f4573..3a4a9fa 100644 --- a/src/components/video/video-list-item.tsx +++ b/src/components/video/video-list-item.tsx @@ -25,7 +25,7 @@ export const VideoListItem = ( { index, id, video, onPlay, onRemove, checked, onCheckedChange, onEdit, active, editable, - sortable + }: Props) => { const { attributes, listeners, @@ -47,9 +47,9 @@ export const VideoListItem = (
}
-
{video.id} - {video.title}
+
{video.title}
- {video.title}/ + {video.title}/
{editable && diff --git a/src/hooks/useArticleTags.ts b/src/hooks/useArticleTags.ts index 7a19020..d759363 100644 --- a/src/hooks/useArticleTags.ts +++ b/src/hooks/useArticleTags.ts @@ -4,16 +4,14 @@ import {getAllCategory} from "@/service/api/article.ts"; const ArticleTags: OptionItem[] = []; export default function useArticleTags() { - const [tags, _setTags] = useState([]); + const [tags, _setTags] = useState([]); const setTags = useCallback(() => { _setTags([ - { - label: '全部', - value: -1, - }, + ...ArticleTags ]) }, []) + useEffect(() => { if (ArticleTags.length === 0) { getAllCategory().then(res => { @@ -31,11 +29,8 @@ export default function useArticleTags() { }) setTags() }) - } - return () => { - // 清除 - setTags([]) - ArticleTags.length = 0; + }else{ + setTags() } }, []) return tags diff --git a/src/main.tsx b/src/main.tsx index 6ce1cad..631273a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,7 +5,7 @@ import App from './App.tsx' import '@/assets/index.scss' ReactDOM.createRoot(document.getElementById('root')!).render( - + // - , + // , ) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index b4771ec..bb2f43b 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -1,5 +1,5 @@ import {Button, message, Modal} from "antd"; -import React, {useRef, useState} from "react"; +import React, {useEffect, useRef, useState} from "react"; import {ArticleGroupList, MockVideoDataList} from "@/_local/mock-data"; import {DndContext} from "@dnd-kit/core"; @@ -9,6 +9,7 @@ import ArticleEditModal from "@/components/article/edit-modal.tsx"; import {useSetState} from "ahooks"; import {CheckCircleFilled} from "@ant-design/icons"; import {clsx} from "clsx"; +import {getList} from "@/service/api/video.ts"; export default function CreateIndex() { @@ -17,10 +18,17 @@ export default function CreateIndex() { groups?: ArticleContentGroup[]; }>({}) - const [videoData, setVideoData] = useState(MockVideoDataList) + const [videoData, setVideoData] = useState([]) + + useEffect(() => { + getList({}).then((ret) => { + setVideoData(ret.list) + }) + }, []) + const [modal, contextHolder] = Modal.useModal() const videoRef = useRef(null) - const [state,setState] = useSetState({ + const [state, setState] = useSetState({ checkedAll: false }) const [checkedIdArray, setCheckedIdArray] = useState([]) @@ -42,14 +50,14 @@ export default function CreateIndex() { videoRef.current!.src = video.play_url } } - const handleAllCheckedChange = ()=>{ + const handleAllCheckedChange = () => { // setVideoData(list=>{ // list.map(s=>{ // s.checked = !state.checkedAll // }) // return list // }) - setCheckedIdArray(state.checkedAll?[]:videoData.map(v=>v.id)) + setCheckedIdArray(state.checkedAll ? [] : videoData.map(v => v.id)) setState({ checkedAll: !state.checkedAll }) @@ -65,7 +73,8 @@ export default function CreateIndex() {
批量删除 -
@@ -106,7 +115,7 @@ export default function CreateIndex() { }} onPlay={() => playVideo(v)} onEdit={() => { - setEditNews({title:v.title, groups: [...ArticleGroupList]}) + setEditNews({title: v.title, groups: [...ArticleGroupList]}) }} editable />))} diff --git a/src/pages/library/components/search-form.tsx b/src/pages/library/components/search-form.tsx index 2fbb21f..c3ddf40 100644 --- a/src/pages/library/components/search-form.tsx +++ b/src/pages/library/components/search-form.tsx @@ -1,7 +1,7 @@ import {Button, Form, Input, Select, Space} from "antd"; import {useSetState} from "ahooks"; import {PlayCircleOutlined} from "@ant-design/icons"; -import {ListTimes} from "@/pages/news/components/news-source.ts"; +import {SearchListTimes} from "@/pages/news/components/news-source.ts"; type SearchParams = { keywords?: string; @@ -40,7 +40,7 @@ export default function SearchForm({onSearch, onBtnStartClick}: Props) { { + setParams({title: e.target.value}) + }} + allowClear + type="text" className="rounded px-3 w-[250px]" + suffix={} + placeholder="请输入你先搜索的关键词" + /> + 来源 + + +
+ ) +} \ No newline at end of file diff --git a/src/pages/news/components/news-source.ts b/src/pages/news/components/news-source.ts index 020ec76..5a90119 100644 --- a/src/pages/news/components/news-source.ts +++ b/src/pages/news/components/news-source.ts @@ -1,4 +1,3 @@ - /* 人民日报客户端 环球时报客户端 @@ -31,150 +30,80 @@ 中国商务部 */ -export const NewsSources:OptionItem[] = [ +export const NewsSources: OptionItem[] = [ { label: '全部', - value: 'all', + value: -1, }, { label: '人民日报', - value: 'people', + value: 1, children: [ { label: '要闻', - value: 'important' + value: 101 }, { label: '国际', - value: 'international' + value: 102 }, { label: '国内', - value: 'domestic' + value: 103 }, { label: '社会', - value: 'society' - }, - { - label: '娱乐', - value: 'entertainment' - }, - { - label: '军事', - value: 'military' - }, + value: 104 + } ] }, { label: '环球时报', - value: 'global' - }, - { - label: '新华社', - value: 'xh-net' - }, - { - label: '央视新闻', - value: 'cctv' - }, - { - label: '解放军报', - value: '81' - }, - { - label: '澎湃新闻', - value: 'the-paper' - }, - { - label: '海客新闻', - value: 'haike' - }, - { - label: '中新经纬', - value: 'zxjw' - }, - { - label: '央视体育', - value: 'cctv-sports' - }, - { - label: '参考消息', - value: 'can-kao' - }, - { - label: '百姓关注', - value: 'baixin' - }, - { - label: '大象新闻', - value: 'dx-news' - }, - { - label: '四川观察', - value: 'sc-news' - }, - { - label: '新京报', - value: 'xjb' - }, - { - label: '北京日报', - value: 'bjrb' - }, - { - label: '中国纪检监察报', - value: 'jx-news' - }, - { - label: '腾讯网', - value: 'qq' - }, - { - label: '红网', - value: 'hong-news' - }, - { - label: '新湖南客户端', - value: 'xhn' - }, - { - label: '晨视频客户端', - value: 'chen-video' + value: 2, + children: [ + { + label: '要闻', + value: 201 + }, + { + label: '国际', + value: 202 + }, + { + label: '国内', + value: 203 + }, + { + label: '社会', + value: 204 + } + ] }, ] -export const ListTimes = [ +export const SearchListTimes = [ { - label: '半小时', - value: '30' + label: '半小时内', + value: 1 }, { - label: '一小时', - value: '60' + label: '一小时内', + value: 2 }, { - label: '两小时', - value: '120' + label: '四小时内', + value: 3 }, { - label: '四小时', - value: '240' - }, - { - label: '近一天', - value: '1440' + label: '一天内', + value: 4 }, { label: '近一周', - value: '10080' - }, - { - label: '近一月', - value: '43800' + value: 5 }, { label: '全部', - value: '-1' + value: 0 } ] \ No newline at end of file diff --git a/src/pages/news/components/search-panel.tsx b/src/pages/news/components/search-panel.tsx index f38582e..a6fcbe0 100644 --- a/src/pages/news/components/search-panel.tsx +++ b/src/pages/news/components/search-panel.tsx @@ -1,90 +1,111 @@ -import {Button, Form, Input, Select, Space} from "antd"; +import {Button, Input, Select} from "antd"; import {useSetState} from "ahooks"; -import {ListTimes, NewsSources} from "@/pages/news/components/news-source.ts"; import {useState} from "react"; +import useArticleTags from "@/hooks/useArticleTags.ts"; -type SearchParams = { - search: string; - date: string; - source: string; -} +import {SearchListTimes} from "@/pages/news/components/news-source.ts"; type SearchPanelProps = { - onSearch?: (params: SearchParams) => Promise; + onSearch?: (params: ApiArticleSearchParams) => void; +} +const pagination = { + limit: 10, page: 1 } export default function SearchPanel({onSearch}: SearchPanelProps) { + const tags = useArticleTags(); + const [params, setParams] = useSetState({ + pagination + }); + const [state, setState] = useSetState<{ - time: string; - source: string; - searching: boolean; - subOptions: string[] + source: string | number; + subOptions: (string | number)[] }>({ - time: '-1', - source: 'all', - searching: false, + source: -1, subOptions: [] }) + // 二级分类 const [subOptions, setSubOptions] = useState([]) - const onFinish = (values: any) => { - setState({searching: true}) + const onFinish = () => { + if(state.source != -1){ + params.tags = []; + state.subOptions.forEach(level2 => { + params.tags!.push({ + level1: state.source, + level2 + }) + }) + }else{ + params.tags = undefined; + } + onSearch?.({ - search: values.search, - date: values.date.join('-'), - source: state.source - }).finally(() => { - setState({searching: false}) + ...params }) } + // 重置 + const onReset = () => { + setParams({pagination, title: ''}) + setState({source: -1,subOptions: []}) + setSubOptions([]) + onSearch?.({pagination}) + } return (
-
-
- - - - - setParams({title: e.target.value})} + className="w-[240px]" + placeholder={'请输入新闻标题开始查找新闻'} + /> +
+ 更新时间 + { - setParams({search: e.target.value}) - }} - type="text" className="rounded px-3 w-[250px]" - suffix={} - placeholder="请输入你先搜索的关键词" - /> - 来源 - { - console.log('e.target.value',e) - }} - displayRender={label => label.join('-')} - expandTrigger="hover" - multiple - maxTagCount="responsive" - /> - {/* (*/} - {/*
*/} - {/* */} - {/* {option.label}*/} - {/*
*/} - {/* )}*/} - {/* labelRender={(props) => {*/} - {/* if (props.value == 'all') return 全部*/} - {/* return {props.label}*/} - {/* }}*/} - {/*/>*/} -
+
rowSelection={{type: 'checkbox', ...rowSelection}} columns={columns} - dataSource={data?.list||[]} + dataSource={data?.list || []} rowKey={'id'} bordered - pagination={{ - position: ['bottomLeft'], - simple: true, - defaultCurrent: params.page, - total: data?.pagination.total || 0, - pageSize: params.limit, - showSizeChanger: false, - rootClassName: 'simple-pagination', - onChange: (page) => setParams({page}) - }} + pagination={false} /> + {data?.pagination.total > 0 &&
+ setParams(prev=>({ + ...prev, + pagination: {page, limit: 10} + }))} + /> + +
}
- setEditId(-1)} /> + setEditId(-1)}/>
) } \ No newline at end of file diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx index 3dc07f8..8a8720c 100644 --- a/src/pages/news/index.tsx +++ b/src/pages/news/index.tsx @@ -1,115 +1,138 @@ import {useState} from "react"; -import {Button, Checkbox, Modal, Pagination, Space} from "antd"; +import {Checkbox, Empty, Modal, Pagination, Space} from "antd"; +import {useRequest, useSetState} from "ahooks"; import {Card} from "@/components/card"; +import {getList} from "@/service/api/article.ts"; + import SearchPanel from "@/pages/news/components/search-panel.tsx"; - import styles from './style.module.scss' - - -function onVideoCreateClick(){} -function onVideoDownloadClick(){} +import {getById} from "@/service/api/news.ts"; +import {showLoading} from "@/components/message.ts"; +import {formatTime} from "@/util/strings.ts"; +import ButtonPushNews2Article from "@/pages/news/components/button-push-news2article.tsx"; +import ButtonNewsDownload from "@/pages/news/components/button-news-download.tsx"; export default function NewsIndex() { - const [list,] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + + const [params, setParams] = useState({ + pagination: { + page: 1, + limit: 10 + } + }) const [checkedId, setCheckedId] = useState([]) const [activeNews, setActiveNews] = useState() + const [state, setState] = useState<{ checkAll?: boolean; }>({}) + const {data} = useRequest(() => getList(params), { + refreshDeps: [params], + onSuccess: () => { + setCheckedId([]) + setState({checkAll:false}) + } + }) + + const handleViewNewsDetail = (id: number) => { + const {update, close} = showLoading('获取新闻详情...') + getById(id).then(res => { + close() + setActiveNews(res) + }).catch(() => { + update('获取新闻详情失败', 'info') + }) + } + return (
- + setActiveNews(undefined)}>
{activeNews?.title}
- - 人民日报客户端 - - 2024-11-20 11:11:11 -
-
-

当地时间11月19日下午,国家主席习近平乘专机抵达巴西利亚,开始对巴西进行国事访问。

-

专机抵达巴西利亚空军基地时,巴西总统府首席部长科斯塔、巴西利亚空军基地司令米格尔、司法部长莱万多夫斯基、总统府机构关系部长帕迪利亚等高级官员在机场热情迎接,代表卢拉总统和巴西政府热烈欢迎习近平主席到访。

-

几十名巴塔拉艺术家演奏热情奔放的巴西特色鼓乐。

-

蔡奇、王毅等陪同人员同机抵达。

-

习近平乘车从机场赴下榻饭店途中,当地华侨华人、中资机构和留学生代表在道路旁挥舞中巴两国国旗,高举“欢迎习近平主席访问巴西”“中巴友谊万岁!”等红色横幅,热烈欢迎习近平到访。

-

习近平是在结束二十国集团领导人第十九次峰会活动后离开里约热内卢抵达巴西利亚的。

+ {activeNews?.media_name} + {formatTime(activeNews?.publish_time)}
+
-
+
- { + { setState({checkAll: e.target.checked}) if (e.target.checked) { - setCheckedId([...list]) + setCheckedId(data.list.map(item => item.id)) } else { setCheckedId([]) } }}>全选
- - + +
- {list.map(id => ( -
+ {data?.list?.map(item => ( +
- { - if (checkedId.includes(id)) { - setCheckedId(checkedId.filter(item => item != id)) + className={`checkbox mt-[2px] mr-2 ${checkedId.includes(item.id) ? '' : 'opacity-0'} group-hover:opacity-100`}> + { + if (checkedId.includes(item.id)) { + setCheckedId(checkedId.filter(id => id != item.id)) } else { - setCheckedId([...checkedId, id]) + setCheckedId([...checkedId, item.id]) } }}/>
{ - setActiveNews({ - id: 1, - title: '习近平抵达巴西利亚开始对巴西进行国事访问', - content: '', cover: "", source: "", time: "" - }) - }}>习近平抵达巴西利亚开始对巴西进行国事访问 -
- {id == 1 &&
已加入编辑界面
} + handleViewNewsDetail(item.id) + }}>{item.id}{item.title}
+ {item.internal_article_id > 0 && +
已加入编辑界面
}
-
- -
+ {item.cover &&
+ +
}
- 当地时间11月19日下午,国家主席习近平乘专机抵达巴西利亚,开始对巴西进行国事访问。
- 专机抵达巴西利亚空军基地时,巴西总统府首席部长科斯塔、巴西利亚空军基地司令米格尔、司法部长莱万多夫斯基、总统府机构关系部长帕迪利亚等高级官员在机场热情迎接,代表卢拉总统和巴西政府热烈欢迎习近平主席到访。 + {item.summary}
-
来源: 新华社
+
来源: {item.media_name}
{/**/} -
发布时间: 2024-11-18 10:10:12
+
发布时间: {item.publish_time}
))}
-
- + {data?.pagination.total > 0 ?
+ setParams(prev=>({...prev,pagination: {page, limit: 10}}))} + /> +
:
+
+ }
) } \ No newline at end of file diff --git a/src/routes/layout/dashboard-layout.tsx b/src/routes/layout/dashboard-layout.tsx index 7f50d6a..d305f01 100644 --- a/src/routes/layout/dashboard-layout.tsx +++ b/src/routes/layout/dashboard-layout.tsx @@ -30,7 +30,7 @@ const NavigationUserContainer = () => { }}>退出
, }, ]; - return (
+ return (
@@ -46,8 +46,10 @@ export const BaseLayout: React.FC = ({children}) => {
- - +
+ + +
diff --git a/src/routes/layout/dashboard-navigation.tsx b/src/routes/layout/dashboard-navigation.tsx index 99e7302..ab764a0 100644 --- a/src/routes/layout/dashboard-navigation.tsx +++ b/src/routes/layout/dashboard-navigation.tsx @@ -5,32 +5,32 @@ import {NavLink} from "react-router-dom"; const NavItems = [ { key: 'news', - name: '新闻素材库', - icon: '+', + name: '新闻素材', + icon: 'news', path:'/' }, { key: 'video', - name: '新闻素材编辑', - icon: '+', + name: '新闻编辑', + icon: 'e', path:'/edit' }, { key: 'create', - name: '数字人视频生成', - icon: '+', + name: 'AI视频', + icon: 'ai', path:'/create' }, { key: 'library', - name: '数字人视频库', + name: '视频库', icon: '+', path:'/library' }, { key: 'live', name: '数字人直播间', - icon: '+', + icon: 'v', path:'/live' } ] @@ -38,8 +38,8 @@ const NavItems = [ export function DashboardNavigation() { return (
{NavItems.map((it, idx) => ( - - {it.name} + + {it.name} ))}
diff --git a/src/service/api/article.ts b/src/service/api/article.ts index 85cd21a..7fb948c 100644 --- a/src/service/api/article.ts +++ b/src/service/api/article.ts @@ -4,7 +4,7 @@ export function getAllCategory() { return post<{ tags: ArticleCategory[] }>({url: '/spider/tags'}) } -export function getArticleList(data: ApiArticleSearchParams & ApiRequestPageParams) { +export function getList(data: ApiArticleSearchParams) { return post>({url: '/article/search', data}) } @@ -12,17 +12,19 @@ export function getArticleList(data: ApiArticleSearchParams & ApiRequestPagePara * 删除 【本期不做】 * @param id */ -export function deleteArticle(id: Id) { +export function deleteById(id: Id) { throw new Error('Not implement') return post<{ article: any }>({url: '/article/delete/' + id}) } -export function getArticleDetail(id: Id) { + +export function getById(id: Id) { return post({url: '/article/detail/' + id}) } -export function saveArticle(title:string,content_group: BlockContent[][],id: number) { + +export function save(title: string, content_group: BlockContent[][], id: number) { return post<{ content: string }>({ url: '/spider/article', - data:{ + data: { title, content_group, id diff --git a/src/service/api/common.ts b/src/service/api/common.ts index 78db1f5..8ec8b30 100644 --- a/src/service/api/common.ts +++ b/src/service/api/common.ts @@ -3,8 +3,8 @@ import {post} from "@/service/request.ts"; export function getOssPolicy(scene = 'workbench') { return post({ data: {scene}, - baseURL: '/api/v1/common/get_oss_policy', - url: `/api/v1/common/get_oss_policy` + baseURL: '/api/v1', + url: `/common/get_oss_policy` }) } diff --git a/src/service/api/news.ts b/src/service/api/news.ts index e69de29..03e3977 100644 --- a/src/service/api/news.ts +++ b/src/service/api/news.ts @@ -0,0 +1,13 @@ +import {post} from "@/service/request.ts"; + +export function getList(data: ApiArticleSearchParams & ApiRequestPageParams) { + return post>({url: '/article/search', data}) +} + +export function getById(id: Id) { + return post({url: '/spider/detail/' + id}) +} + +export function push2article(ids: Id[]) { + return post({url: '/spider/push2article', data: {spider_ids: ids}}) +} \ No newline at end of file diff --git a/src/service/api/video.ts b/src/service/api/video.ts new file mode 100644 index 0000000..f8273a7 --- /dev/null +++ b/src/service/api/video.ts @@ -0,0 +1,39 @@ +import {post} from "@/service/request.ts"; + +export function getList(data: { + title?: string, + time_flag?: number; +}) { + return post>({url: '/video/list', data}) +} + +/** + * 视频列表的文章编辑(需要重新生成视频) + * @param title + * @param content_group + * @param article_id + */ +export function regenerate(title: string, content_group: BlockContent[][], article_id: number) { + return post<{ content: string }>({ + url: '/video/regenerate', + data: { + title, + content_group, + article_id + } + }) +} + +export function getById(id: Id) { + return post({url: '/video/detail/' + id}) +} + +export function deleteById(id: Id) { + return post({url: '/video/detail/' + id}) +} +export function modifyOrder(ids: Id[]) { + return post({url: ' /video/modifyorder',data:{ids}}) +} +export function push2room(ids: Id[]) { + return post({url: ' /video/push2room',data:{ids}}) +} \ No newline at end of file diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 8f19b69..ce4ca4b 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -5,13 +5,18 @@ declare interface ApiRequestPageParams { } } -declare interface ApiArticleSearchParams { - // 1级标签id - tag_level_1_id?: number; - // 2级标签id 没有则为0 - tag_level_2_id?: number; +declare interface ApiArticleSearchParams extends ApiRequestPageParams{ + // // 1级标签id + // tag_level_1_id?: number; + // // 2级标签id 没有则为0 + // tag_level_2_id?: number; + tags?: { + level1: Id; + level2: Id; + }[]; // 标题 title?: string; + time_flag?: number; } declare interface DataList { @@ -35,7 +40,7 @@ interface ArticleCategory extends BaseArticleCategory { sons: BaseArticleCategory[]; } -declare interface VideoInfo { +declare interface VideoInfo1 { id: number; title: string; cover: string; @@ -74,4 +79,13 @@ declare interface ListCrawlerNewsItem extends BasicArticleInfo { data_source_name: string; // 内部文章关联id internal_article_id: number; -} \ No newline at end of file +} +declare interface VideoInfo { + id: number; + title: string; + cover?: string; + oss_video_url: string; + duration: number; + article_id: number; + status: number; +} diff --git a/src/types/core.d.ts b/src/types/core.d.ts index c1aabe8..67f07ca 100644 --- a/src/types/core.d.ts +++ b/src/types/core.d.ts @@ -11,9 +11,10 @@ declare interface RecordList { filter?: string; }; } + declare interface OptionItem { label: string; - value: string|number; + value: string | number; children?: OptionItem[]; } @@ -34,13 +35,12 @@ declare interface ArticleDetail { } declare interface NewsInfo { - id: number; title: string; - cover?: string; content: string; - source: string; - time: string|number; + media_name: string; + publish_time: string | number; } + declare interface TOSSPolicy { //Oss access id access_id: string; diff --git a/vite.config.ts b/vite.config.ts index 0cfb3e1..8b60631 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -32,9 +32,13 @@ export default defineConfig(({mode}) => { port: 10021, proxy: { '/mgmt': { - target: 'http://192.168.0.231:9999', //\ - // changeOrigin: true, - // ws: true, + target: 'http://192.168.0.231:9999', + changeOrigin: true, + // rewrite: (path) => path.replace(/^\/api/, '') + }, + '/api': { + target: 'http://192.168.0.231:9999', + changeOrigin: true, // rewrite: (path) => path.replace(/^\/api/, '') } } diff --git a/yarn.lock b/yarn.lock index 49e2dc4..7634d58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1291,6 +1291,11 @@ copy-to-clipboard@^3.3.3: dependencies: toggle-selection "^1.0.6" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.5" resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82" @@ -1786,6 +1791,11 @@ ignore@^5.2.0, ignore@^5.3.1: resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + immutable@^5.0.2: version "5.0.2" resolved "https://registry.npmmirror.com/immutable/-/immutable-5.0.2.tgz#bb8a987349a73efbe6b3b292a9cbaf1b530d296b" @@ -1812,7 +1822,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1863,6 +1873,11 @@ is-path-inside@^3.0.3: resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1931,6 +1946,16 @@ json5@^2.2.3: resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -1946,6 +1971,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" @@ -2148,6 +2180,11 @@ package-json-from-dist@^1.0.0: resolved "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -2266,6 +2303,11 @@ prelude-ls@^1.2.1: resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + prop-types@^15.7.2: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -2715,6 +2757,19 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@^4.0.1: version "4.0.2" resolved "https://registry.npmmirror.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" @@ -2797,6 +2852,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + sass@^1.81.0: version "1.81.0" resolved "https://registry.npmmirror.com/sass/-/sass-1.81.0.tgz#a9010c0599867909dfdbad057e4a6fbdd5eec941" @@ -2849,6 +2909,11 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2910,6 +2975,13 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -3075,7 +3147,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -util-deprecate@^1.0.2: +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==