From f6f5e08abac1a1390bac2647b0c2b23bd31550f0 Mon Sep 17 00:00:00 2001 From: callmeyan Date: Sun, 22 Dec 2024 17:47:34 +0800 Subject: [PATCH] =?UTF-8?q?:lipstick:=20update=20=E6=96=B0=E9=97=BB?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/core.scss | 56 ++++++++---- src/components/article/article.module.scss | 68 ++++++++++---- src/components/article/block.tsx | 70 ++++++++------- src/components/article/edit-modal.tsx | 68 +++++++------- src/components/article/group.tsx | 89 +++++++++++++------ src/components/article/item.tsx | 35 +++++--- src/components/icons/index.tsx | 13 ++- .../news/components/button-delete-batch.tsx | 47 ++++++++++ .../news/components/button-push2video.tsx | 16 +++- src/pages/news/edit.tsx | 6 +- 10 files changed, 325 insertions(+), 143 deletions(-) create mode 100644 src/pages/news/components/button-delete-batch.tsx diff --git a/src/assets/core.scss b/src/assets/core.scss index 72e2ebf..d3c84ff 100644 --- a/src/assets/core.scss +++ b/src/assets/core.scss @@ -185,13 +185,17 @@ font-size: 24px; } } - -.data-list-container { - height: calc(100vh - var(--app-header-header) - 200px); +.list-scroller-container{ overflow: auto; margin-right: -20px; padding-right: 16px; scrollbar-gutter: stable; +} + +.data-list-container { + @apply list-scroller-container; + height: calc(100vh - var(--app-header-header) - 200px); + .data-list-container-inner { @@ -229,8 +233,39 @@ } .ant-modal-body { - padding: 20px; + padding: 10px 0; } + .ant-modal-confirm-content{ + color: #999; + } + .ant-modal-confirm-btns{ + margin-top: 40px; + } + } +} +.article-edit-modal{ + + .ant-modal { + .ant-modal-content { + @apply bg-white p-0; + .ant-modal-body{ + @apply p-0; + } + } + } + .article-title{ + @apply px-6 pt-10 pb-6; + } + .article-body{ + @apply p-6 + } + .modal-control-footer{ + @apply p-6 + } + .input-box{ + // focus-within:shadow + @apply bg-[#f8f8f8] border border-transparent w-full px-4 py-2 focus-within:bg-[#f0f0f0] focus-within:border-gray-300; + border-radius: 8px; } } @@ -238,28 +273,17 @@ .page-action { @apply fixed right-10 bottom-10 flex flex-col gap-4; button { - @apply border-0 min-w-[120px] h-[40px] rounded-3xl text-white bg-blue-500 pl-4; + @apply border-0 min-w-[120px] h-[40px] rounded-3xl text-white pr-4 flex items-center justify-between; .text { flex: 1; } - &:hover { - @apply bg-blue-600; - } - - &:active { - @apply bg-blue-700; - } - &:disabled { @apply bg-gray-400; } &.btn-info { @apply bg-info text-gray-800; - .svg-icon { - @apply text-gray-800; - } } } } diff --git a/src/components/article/article.module.scss b/src/components/article/article.module.scss index 1a06d04..e0f801f 100644 --- a/src/components/article/article.module.scss +++ b/src/components/article/article.module.scss @@ -1,10 +1,30 @@ .blockContainer { - @apply flex mb-5; + @apply relative; + :global{ + .divider-container{ + @apply absolute inset-x-2 z-10; + &.before{ + top:-12px; + } + &.after{ + bottom: -10px; + } + .ant-divider-horizontal{ + margin: 0; + border-block-start: 1px rgba(5, 5, 5,0.1); + } + } + .article-action-add{ + @apply text-gray-400 text-sm inline-block bg-[#cce2ff] w-[80px] justify-center flex rounded-xl cursor-pointer hover:bg-blue-300 hover:text-white; + } + } } - +.blockInner{ + @apply flex px-4 py-10 hover:bg-[#e6ebf1] ; +} +.blockFooter{} .block { - @apply border border-gray-300 border-dashed p-3 rounded flex-1; - + @apply flex-1; &:last-child { @apply mb-0; } @@ -23,10 +43,28 @@ } .group { + @apply flex gap-4; + :global{ + .area-title{ + @apply text-gray-400 text-sm text-gray-800; + } + .digital-person{ + width: 450px; + } + .panel{ + @apply flex flex-col; + } + .panel-body{ + @apply bg-[#f0f0f0] flex-1 rounded-xl mt-2; + } + } } +.imagerOrText{ + @apply bg-[#f8f8f8] rounded-xl py-2; +} .imageList { - @apply grid grid-cols-4 gap-4 p-3 border border-blue-200; + @apply grid grid-cols-4 imagerOrText px-2 gap-2; :global { .ant-upload-wrapper { display: block; @@ -57,19 +95,14 @@ } } .imageDelete{ - @apply absolute flex items-center justify-center p-0.5 w-[22px] h-[22px] rounded-full border border-red-500 text-red-500 cursor-pointer z-10; - right:-10px; - top:-10px; - font-size: 14px; - &:hover{ - @apply text-white bg-red-500; - } + @apply absolute flex items-center justify-center right-0 top-0 w-[22px] h-[22px] rounded-full cursor-pointer z-10 ; + font-size: 16px; } .uploadImage { @apply flex justify-center items-center relative h-[100px] text-gray-400; .uploadTips { - @apply absolute inset-0 cursor-pointer opacity-0 transition rounded flex items-center justify-center bg-black/20 text-white; + @apply absolute inset-0 cursor-pointer opacity-0 transition rounded flex items-center justify-center bg-black/50 text-white; } .imagePlaceholder { @@ -86,13 +119,14 @@ } .text { - @apply border border-blue-200 overflow-hidden flex-1 rounded focus:border-blue-200 transition; + @apply imagerOrText overflow-hidden flex-1 rounded focus:border-blue-200 transition; &:hover { @apply border-blue-500; } - - &:focus-within { - @apply border-blue-500 shadow-md; + :global{ + .ant-input{ + @apply px-4; + } } } diff --git a/src/components/article/block.tsx b/src/components/article/block.tsx index 06ec74f..99d2766 100644 --- a/src/components/article/block.tsx +++ b/src/components/article/block.tsx @@ -1,8 +1,8 @@ import React from "react"; import clsx from "clsx"; -import {Popconfirm} from "antd"; +import {Divider, Popconfirm} from "antd"; -import {IconAdd, IconDelete} from "@/components/icons"; +import {IconAdd, IconAddCircle, IconDelete} from "@/components/icons"; import ImageList from "@/components/article/list.tsx"; import { BlockText} from "./item.tsx"; @@ -48,7 +48,7 @@ export default function ArticleBlock( onAdd, onChange, index, - errorMessage + errorMessage, }: Props) { const blocks = rebuildBlockArray(defaultBlocks) @@ -58,41 +58,43 @@ export default function ArticleBlock( onChange?.(_blocks) } - return
-
-
-
-
- handleBlockChange(0, block)} - data={blocks[0]} - isFirstBlock={index == 0} - editable={editable}/> + return
+ {editable && index == 1 &&
+ +
} +
+
+
+
+
+ handleBlockChange(0, block)} + data={blocks[0]} + isFirstBlock={index == 0} + editable={editable}/> +
- {index == 0 &&
-
{errorMessage}
-
该编辑框内容由数字人播报
-
} +
- {index > 0 && }
- -
- {editable &&
- { - index > 0 ? 请确认删除此分组?
} - onConfirm={onRemove} - okText="删除" - cancelText="取消" - > - + {editable &&
+ { + index > 0 ? 请确认删除此分组?
} + onConfirm={onRemove} + okText="删除" + cancelText="取消" + > + - : - } - -
} + : + } +
} +
+ + {editable &&
+ +
}
} \ No newline at end of file diff --git a/src/components/article/edit-modal.tsx b/src/components/article/edit-modal.tsx index 5a589ff..834baa3 100644 --- a/src/components/article/edit-modal.tsx +++ b/src/components/article/edit-modal.tsx @@ -1,4 +1,4 @@ -import {Input, Modal} from "antd"; +import {Input, Modal, Space} from "antd"; import ArticleGroup from "@/components/article/group.tsx"; import {useEffect, useState} from "react"; import {useSetState} from "ahooks"; @@ -16,12 +16,12 @@ const DEFAULT_STATE = { open: false, msgTitle: '', msgGroup: '', - error:'' + error: '' } -function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){ +function pushBlocksToGroup(blocks: BlockContent[], groups: BlockContent[][]) { const lastGroup = groups[groups.length - 1] - if (lastGroup && lastGroup.filter(s=>s.type == 'text').length == 0) { + if (lastGroup && lastGroup.filter(s => s.type == 'text').length == 0) { // 如果上一个group中没有文本则直接合并 lastGroup.push(...blocks) } else { @@ -32,21 +32,21 @@ function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){ function rebuildGroups(groups: BlockContent[][]) { const _groups: BlockContent[][] = []; if (!groups || groups.length == 0) return _groups; - groups.forEach((blocks,index) => { - if(!blocks) return; - blocks = blocks.filter(s=>!!s).sort((a,b) => { - if(a.type == 'text' && b.type == 'text') return 1; + groups.forEach((blocks, index) => { + if (!blocks) return; + blocks = blocks.filter(s => !!s).sort((a, b) => { + if (a.type == 'text' && b.type == 'text') return 1; return a.type == 'text' ? -1 : 1 }) if (blocks.length == 1) { - if(index == 0) _groups.push(blocks) - else pushBlocksToGroup(blocks,_groups) + if (index == 0) _groups.push(blocks) + else pushBlocksToGroup(blocks, _groups) } else { - if(index == 0){ + if (index == 0) { _groups.push([blocks[0]]) _groups.push(blocks.slice(1)) - }else{ - pushBlocksToGroup(blocks,_groups) + } else { + pushBlocksToGroup(blocks, _groups) } } }); @@ -60,6 +60,7 @@ function rebuildGroups(groups: BlockContent[][]) { } + export default function ArticleEditModal(props: Props) { const [groups, setGroups] = useState([]); @@ -83,7 +84,7 @@ export default function ArticleEditModal(props: Props) { setState({loading: true}) save(title, groups, props.id && props.id > 0 ? props.id : undefined).then(() => { props.onClose?.(true) - }).catch(e=>{ + }).catch(e => { setState({error: e.data || '保存失败,请重试!'}) }).finally(() => { setState({loading: false}) @@ -91,7 +92,7 @@ export default function ArticleEditModal(props: Props) { } useEffect(() => { setState({...DEFAULT_STATE}) - if (typeof(props.id) != 'undefined') { + if (typeof (props.id) != 'undefined') { if (props.id > 0) { article.getById(props.id).then(res => { setGroups(rebuildGroups(res.content_group)) @@ -106,34 +107,28 @@ export default function ArticleEditModal(props: Props) { }, [props.id]) return (= 0} maskClosable={false} keyboard={false} - width={800} - onCancel={()=>props.onClose?.()} + width={'1200px'} + footer={null} + closeIcon={null} + onCancel={() => props.onClose?.()} okButtonProps={{loading: state.loading}} onOk={handleSave} okText={props.type == 'news' ? '确定' : '重新生成'} >
-
- 标题 - * -
-
- { - setTitle(e.target.value) - setState({msgTitle: e.target.value ? '' : '请输入标题内容'}) - }} placeholder={'请输入文章标题'}/> -
+ { + setTitle(e.target.value) + setState({msgTitle: e.target.value ? '' : '请输入标题内容'}) + }} placeholder={'请输入文章标题'}/>
{state.msgTitle}
-
-
- 正文 - * -
+
{state.error &&
{state.error}
}
+
+ + {props.type == 'news' ? : null} + + + +
); } \ No newline at end of file diff --git a/src/components/article/group.tsx b/src/components/article/group.tsx index 08fe8ec..5125af6 100644 --- a/src/components/article/group.tsx +++ b/src/components/article/group.tsx @@ -1,8 +1,9 @@ -import {message} from "antd" +import {Input, message} from "antd" import ArticleBlock from "@/components/article/block.tsx"; import styles from './article.module.scss' import {showToast} from "@/components/message.ts"; +import React from "react"; type Props = { groups: BlockContent[][]; @@ -12,7 +13,6 @@ type Props = { } - export default function ArticleGroup({groups, editable, onChange, errorMessage}: Props) { // const groups = rebuildGroups(_groups) /** @@ -38,30 +38,69 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}: } onChange?.(_groups) } + + const handleDigitalPersonContentChange = (content:string) => { + groups[0] = [{type: 'text', content}] + onChange?.([...groups]) + } + return
- {groups.map((g, index) => ( - { - groups[index] = blocks - onChange?.([...groups]) - }} - errorMessage={errorMessage} - index={index} - onAdd={() => { - handleAddGroup?.(index + 1) - }} - onRemove={async () => { - if (groups.length == 1) { - message.warning('至少保留一个内容块') - return; - } - onChange?.(groups.filter((_, idx) => index !== idx)) - }} - /> - ))} +
+
+ 数字人主播台编辑区 + (出现数字人形象) +
+
+ {/* value={groups || groups[0][0].content}*/} +
+ {editable ?
+ 0 ? groups[0][0].content : ''} + autoSize={{minRows: 20, maxRows: 21}} + variant={"borderless"} + onChange={e => { + handleDigitalPersonContentChange(e.target.value) + }} + /> +
:

12123

} +
+
+
+
+
+ 数字人主播台编辑区 + (出现数字人形象) +
+ +
+
+ {groups.map((g, index) => ( + index == 0 ? null : { + groups[index] = blocks + onChange?.([...groups]) + }} + errorMessage={errorMessage} + index={index} + onAdd={() => { + handleAddGroup?.(index + 1) + }} + onRemove={async () => { + if (groups.length == 1) { + message.warning('至少保留一个内容块') + return; + } + onChange?.(groups.filter((_, idx) => index !== idx)) + }} + /> + ))} +
+
+
{groups.length == 0 && editable && onChange?.([blocks])} index={0} blocks={[{type: 'text', content: ''}]}/>} diff --git a/src/components/article/item.tsx b/src/components/article/item.tsx index c2a302c..055da0b 100644 --- a/src/components/article/item.tsx +++ b/src/components/article/item.tsx @@ -64,15 +64,7 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima } // return
- {editable ?
- {!onlyUpload && 请确认删除此删除此图片?
} - onConfirm={onRemove} - okText="删除" - cancelText="取消" - > - - } + {editable && onlyUpload ?
= 0} percent={loading == 0 ? 'auto' : loading}>
- 更换图片 + {!onlyUpload && 请确认删除此删除此图片?
} + onConfirm={onRemove} + okText="删除" + cancelText="取消" + > + + }
:
@@ -95,7 +94,19 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
-
:
} +
:
+ +
+ {!onlyUpload && 请确认删除此删除此图片?
} + onConfirm={onRemove} + okText="删除" + cancelText="取消" + > + + } +
+
}
} @@ -108,7 +119,7 @@ export function BlockText({data, editable, onChange, isFirstBlock}: Props) { onChange={e => { onChange?.({type: 'text', content: e.target.value}) }} - placeholder={'请输入文本内容'} value={data.content} autoSize={{minRows: 3, maxRows: 8}} + placeholder={'请输入文本内容'} value={data.content} autoSize={{minRows: 4, maxRows: 5}} variant={"borderless"}/>
:

{data.content}

}
diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index 575a6f8..1c03959 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -97,7 +97,7 @@ export const IconAddImage = ({style, className}: IconProps) => ( ) -export const IconAdd = ({style, className}: IconProps) => ( +export const IconAddCircle = ({style, className}: IconProps) => ( ( fill="currentColor"/> ) +export const IconAdd = ({style, className}: IconProps) => ( + + + + +) + + export const IconPlay = ({style, className}: IconProps) => ( { + setLoading(true) + push2article(props.ids).then(() => { + showToast('删除成功', 'success') + navigate('/edit') + }).catch(() => { + showToast('删除失败', 'error') + }).finally(() => { + setLoading(false) + }) + } + const onPushClick = () => { + if (props.ids.length === 0) { + showToast('请选择要删除的新闻', 'warning') + return + } + modal.confirm({ + title: '操作提示', + content: '是否确定删除选中的新闻?', + onOk: handlePush, + centered: true + }) + } + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/src/pages/news/components/button-push2video.tsx b/src/pages/news/components/button-push2video.tsx index b6b34b7..801ff1b 100644 --- a/src/pages/news/components/button-push2video.tsx +++ b/src/pages/news/components/button-push2video.tsx @@ -2,9 +2,10 @@ import {Button, Modal} from "antd"; import React, {useState} from "react"; import {showErrorToast, showToast} from "@/components/message.ts"; import {push2video} from "@/service/api/article.ts"; +import {IconArrowRight, IconDelete} from "@/components/icons"; -export default function ButtonPush2Video(props: { ids: Id[];onSuccess?:()=>void; }) { +export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => void; }) { const [loading, setLoading] = useState(false) const handlePush = () => { setLoading(true) @@ -27,6 +28,17 @@ export default function ButtonPush2Video(props: { ids: Id[];onSuccess?:()=>void; }) } return ( - +
+ +
) } \ No newline at end of file diff --git a/src/pages/news/edit.tsx b/src/pages/news/edit.tsx index 2eaeced..2a9f380 100644 --- a/src/pages/news/edit.tsx +++ b/src/pages/news/edit.tsx @@ -13,6 +13,7 @@ import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infini import {IconDelete, IconEdit} from "@/components/icons"; import {clsx} from "clsx"; import ButtonToTop from "@/components/scoller/button-to-top.tsx"; +import ButtonDeleteBatch from "@/pages/news/components/button-delete-batch.tsx"; export default function NewEdit() { @@ -127,9 +128,8 @@ export default function NewEdit() {
scrollerRef.current?.scrollToPosition(0)} /> -
- -
+ +