💄 update 新闻内容编辑器

This commit is contained in:
LittleBoy 2024-12-22 17:47:34 +08:00
parent c95fc4e79d
commit f6f5e08aba
10 changed files with 325 additions and 143 deletions

View File

@ -185,13 +185,17 @@
font-size: 24px; font-size: 24px;
} }
} }
.list-scroller-container{
.data-list-container {
height: calc(100vh - var(--app-header-header) - 200px);
overflow: auto; overflow: auto;
margin-right: -20px; margin-right: -20px;
padding-right: 16px; padding-right: 16px;
scrollbar-gutter: stable; scrollbar-gutter: stable;
}
.data-list-container {
@apply list-scroller-container;
height: calc(100vh - var(--app-header-header) - 200px);
.data-list-container-inner { .data-list-container-inner {
@ -229,8 +233,39 @@
} }
.ant-modal-body { .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 { .page-action {
@apply fixed right-10 bottom-10 flex flex-col gap-4; @apply fixed right-10 bottom-10 flex flex-col gap-4;
button { 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 { .text {
flex: 1; flex: 1;
} }
&:hover {
@apply bg-blue-600;
}
&:active {
@apply bg-blue-700;
}
&:disabled { &:disabled {
@apply bg-gray-400; @apply bg-gray-400;
} }
&.btn-info { &.btn-info {
@apply bg-info text-gray-800; @apply bg-info text-gray-800;
.svg-icon {
@apply text-gray-800;
}
} }
} }
} }

View File

@ -1,10 +1,30 @@
.blockContainer { .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 { .block {
@apply border border-gray-300 border-dashed p-3 rounded flex-1; @apply flex-1;
&:last-child { &:last-child {
@apply mb-0; @apply mb-0;
} }
@ -23,10 +43,28 @@
} }
.group { .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 { .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 { :global {
.ant-upload-wrapper { .ant-upload-wrapper {
display: block; display: block;
@ -57,19 +95,14 @@
} }
} }
.imageDelete{ .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; @apply absolute flex items-center justify-center right-0 top-0 w-[22px] h-[22px] rounded-full cursor-pointer z-10 ;
right:-10px; font-size: 16px;
top:-10px;
font-size: 14px;
&:hover{
@apply text-white bg-red-500;
}
} }
.uploadImage { .uploadImage {
@apply flex justify-center items-center relative h-[100px] text-gray-400; @apply flex justify-center items-center relative h-[100px] text-gray-400;
.uploadTips { .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 { .imagePlaceholder {
@ -86,13 +119,14 @@
} }
.text { .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 { &:hover {
@apply border-blue-500; @apply border-blue-500;
} }
:global{
&:focus-within { .ant-input{
@apply border-blue-500 shadow-md; @apply px-4;
}
} }
} }

View File

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import clsx from "clsx"; 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 ImageList from "@/components/article/list.tsx";
import { BlockText} from "./item.tsx"; import { BlockText} from "./item.tsx";
@ -48,7 +48,7 @@ export default function ArticleBlock(
onAdd, onAdd,
onChange, onChange,
index, index,
errorMessage errorMessage,
}: Props) { }: Props) {
const blocks = rebuildBlockArray(defaultBlocks) const blocks = rebuildBlockArray(defaultBlocks)
@ -58,41 +58,43 @@ export default function ArticleBlock(
onChange?.(_blocks) onChange?.(_blocks)
} }
return <div className={styles.blockContainer}> return <div className={`${styles.blockContainer} group`}>
<div className={clsx(className || '', styles.block, index == 0 ? styles.blockFist : '', ' hover:bg-blue-10')}> {editable && index == 1 && <div className={'divider-container before'}><Divider>
<div className={styles.blockBody}> <span onClick={onAdd} className="article-action-add" title="新增分组"><IconAdd style={{fontSize: 24}}/></span>
<div> </Divider></div> }
<div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}> <div className={styles.blockInner}>
<BlockText <div className={clsx(className || '', styles.block, index == 0 ? styles.blockFist : '', ' hover:bg-blue-10')}>
onChange={(block) => handleBlockChange(0, block)} <div className={styles.blockBody}>
data={blocks[0]} <div>
isFirstBlock={index == 0} <div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}>
editable={editable}/> <BlockText
onChange={(block) => handleBlockChange(0, block)}
data={blocks[0]}
isFirstBlock={index == 0}
editable={editable}/>
</div>
</div> </div>
{index == 0 && <div className="flex items-center text-red-500 justify-between text-sm mt-1"> <ImageList blocks={blocks} editable={editable} onChange={onChange}/>
<div>{errorMessage}</div>
<div></div>
</div>}
</div> </div>
{index > 0 && <ImageList blocks={blocks} editable={editable} onChange={onChange}/>}
</div> </div>
{editable && <div className="ml-2 flex items-center">
</div> {
{editable && <div className="ml-2 flex flex-col justify-between "> index > 0 ? <Popconfirm
{ title={<div style={{minWidth: 150}}><span>?</span></div>}
index > 0 ? <Popconfirm onConfirm={onRemove}
title={<div style={{minWidth: 150}}><span>?</span></div>} okText="删除"
onConfirm={onRemove} cancelText="取消"
okText="删除" >
cancelText="取消" <span className="article-action-icon hidden group-hover:block" title="删除此分组">
>
<span className="article-action-icon" title="删除此分组">
<IconDelete style={{fontSize: 24}}/> <IconDelete style={{fontSize: 24}}/>
</span> </span>
</Popconfirm> : <span></span> </Popconfirm> : <span></span>
} }
<span onClick={onAdd} className="article-action-icon" title="新增分组"><IconAdd </div>}
style={{fontSize: 24}}/></span> </div>
</div>}
{editable && <div className={'divider-container after'}><Divider>
<span onClick={onAdd} className="article-action-add" title="新增分组"><IconAdd style={{fontSize: 24}}/></span>
</Divider></div> }
</div> </div>
} }

View File

@ -1,4 +1,4 @@
import {Input, Modal} from "antd"; import {Input, Modal, Space} from "antd";
import ArticleGroup from "@/components/article/group.tsx"; import ArticleGroup from "@/components/article/group.tsx";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {useSetState} from "ahooks"; import {useSetState} from "ahooks";
@ -16,12 +16,12 @@ const DEFAULT_STATE = {
open: false, open: false,
msgTitle: '', msgTitle: '',
msgGroup: '', msgGroup: '',
error:'' error: ''
} }
function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){ function pushBlocksToGroup(blocks: BlockContent[], groups: BlockContent[][]) {
const lastGroup = groups[groups.length - 1] 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中没有文本则直接合并 // 如果上一个group中没有文本则直接合并
lastGroup.push(...blocks) lastGroup.push(...blocks)
} else { } else {
@ -32,21 +32,21 @@ function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
function rebuildGroups(groups: BlockContent[][]) { function rebuildGroups(groups: BlockContent[][]) {
const _groups: BlockContent[][] = []; const _groups: BlockContent[][] = [];
if (!groups || groups.length == 0) return _groups; if (!groups || groups.length == 0) return _groups;
groups.forEach((blocks,index) => { groups.forEach((blocks, index) => {
if(!blocks) return; if (!blocks) return;
blocks = blocks.filter(s=>!!s).sort((a,b) => { blocks = blocks.filter(s => !!s).sort((a, b) => {
if(a.type == 'text' && b.type == 'text') return 1; if (a.type == 'text' && b.type == 'text') return 1;
return a.type == 'text' ? -1 : 1 return a.type == 'text' ? -1 : 1
}) })
if (blocks.length == 1) { if (blocks.length == 1) {
if(index == 0) _groups.push(blocks) if (index == 0) _groups.push(blocks)
else pushBlocksToGroup(blocks,_groups) else pushBlocksToGroup(blocks, _groups)
} else { } else {
if(index == 0){ if (index == 0) {
_groups.push([blocks[0]]) _groups.push([blocks[0]])
_groups.push(blocks.slice(1)) _groups.push(blocks.slice(1))
}else{ } else {
pushBlocksToGroup(blocks,_groups) pushBlocksToGroup(blocks, _groups)
} }
} }
}); });
@ -60,6 +60,7 @@ function rebuildGroups(groups: BlockContent[][]) {
} }
export default function ArticleEditModal(props: Props) { export default function ArticleEditModal(props: Props) {
const [groups, setGroups] = useState<BlockContent[][]>([]); const [groups, setGroups] = useState<BlockContent[][]>([]);
@ -83,7 +84,7 @@ export default function ArticleEditModal(props: Props) {
setState({loading: true}) setState({loading: true})
save(title, groups, props.id && props.id > 0 ? props.id : undefined).then(() => { save(title, groups, props.id && props.id > 0 ? props.id : undefined).then(() => {
props.onClose?.(true) props.onClose?.(true)
}).catch(e=>{ }).catch(e => {
setState({error: e.data || '保存失败,请重试!'}) setState({error: e.data || '保存失败,请重试!'})
}).finally(() => { }).finally(() => {
setState({loading: false}) setState({loading: false})
@ -91,7 +92,7 @@ export default function ArticleEditModal(props: Props) {
} }
useEffect(() => { useEffect(() => {
setState({...DEFAULT_STATE}) setState({...DEFAULT_STATE})
if (typeof(props.id) != 'undefined') { if (typeof (props.id) != 'undefined') {
if (props.id > 0) { if (props.id > 0) {
article.getById(props.id).then(res => { article.getById(props.id).then(res => {
setGroups(rebuildGroups(res.content_group)) setGroups(rebuildGroups(res.content_group))
@ -106,34 +107,28 @@ export default function ArticleEditModal(props: Props) {
}, [props.id]) }, [props.id])
return (<Modal return (<Modal
title={'编辑文章'} title={null}
centered={true}
rootClassName={"article-edit-modal"}
open={props.id != undefined && props.id >= 0} open={props.id != undefined && props.id >= 0}
maskClosable={false} maskClosable={false}
keyboard={false} keyboard={false}
width={800} width={'1200px'}
onCancel={()=>props.onClose?.()} footer={null}
closeIcon={null}
onCancel={() => props.onClose?.()}
okButtonProps={{loading: state.loading}} okButtonProps={{loading: state.loading}}
onOk={handleSave} onOk={handleSave}
okText={props.type == 'news' ? '确定' : '重新生成'} okText={props.type == 'news' ? '确定' : '重新生成'}
> >
<div className="article-title mt-5"> <div className="article-title mt-5">
<div className="title"> <input className={'input-box text-lg'} value={title} onChange={e => {
<span className="text text-base"></span> setTitle(e.target.value)
<span className="require ml-1 font-bold text-red-500">*</span> setState({msgTitle: e.target.value ? '' : '请输入标题内容'})
</div> }} placeholder={'请输入文章标题'}/>
<div className="box mt-1">
<Input rootClassName={state.msgTitle ? 'border-red-500' : ''} value={title} onChange={e => {
setTitle(e.target.value)
setState({msgTitle: e.target.value ? '' : '请输入标题内容'})
}} placeholder={'请输入文章标题'}/>
</div>
<div className="text-red-500">{state.msgTitle}</div> <div className="text-red-500">{state.msgTitle}</div>
</div> </div>
<div className="aricle-body mt-3"> <div className="article-body mt-3">
<div className="title">
<span className="text text-base"></span>
<span className="require ml-1 font-bold text-red-500">*</span>
</div>
<div className="box mt-1"> <div className="box mt-1">
<ArticleGroup <ArticleGroup
errorMessage={state.msgGroup} editable groups={groups} errorMessage={state.msgGroup} editable groups={groups}
@ -145,5 +140,12 @@ export default function ArticleEditModal(props: Props) {
</div> </div>
{state.error && <div className="text-red-500">{state.error}</div>} {state.error && <div className="text-red-500">{state.error}</div>}
</div> </div>
<div className="modal-control-footer flex justify-end">
<Space>
{props.type == 'news' ? <button></button> : null}
<button onClick={() => props.onClose?.()}></button>
<button>{props.type == 'news' ? '确定' : '重新生成'}</button>
</Space>
</div>
</Modal>); </Modal>);
} }

View File

@ -1,8 +1,9 @@
import {message} from "antd" import {Input, message} from "antd"
import ArticleBlock from "@/components/article/block.tsx"; import ArticleBlock from "@/components/article/block.tsx";
import styles from './article.module.scss' import styles from './article.module.scss'
import {showToast} from "@/components/message.ts"; import {showToast} from "@/components/message.ts";
import React from "react";
type Props = { type Props = {
groups: BlockContent[][]; groups: BlockContent[][];
@ -12,7 +13,6 @@ type Props = {
} }
export default function ArticleGroup({groups, editable, onChange, errorMessage}: Props) { export default function ArticleGroup({groups, editable, onChange, errorMessage}: Props) {
// const groups = rebuildGroups(_groups) // const groups = rebuildGroups(_groups)
/** /**
@ -38,30 +38,69 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}:
} }
onChange?.(_groups) onChange?.(_groups)
} }
const handleDigitalPersonContentChange = (content:string) => {
groups[0] = [{type: 'text', content}]
onChange?.([...groups])
}
return <div className={styles.group}> return <div className={styles.group}>
{groups.map((g, index) => ( <div className={'panel digital-person'}>
<ArticleBlock <div className="area-title">
editable={editable} <span className=""></span>
key={index} <span className="text-gray-400"></span>
blocks={g} </div>
onChange={(blocks) => { <div className="panel-body p-3">
groups[index] = blocks {/* value={groups || groups[0][0].content}*/}
onChange?.([...groups]) <div className="h-[486px] pt-2 rounded-xl overflow-hidden bg-gray-50">
}} {editable ? <div className="relative">
errorMessage={errorMessage} <Input.TextArea
index={index} placeholder={'请输入文本内容'}
onAdd={() => { value={groups && groups.length > 0 ? groups[0][0].content : ''}
handleAddGroup?.(index + 1) autoSize={{minRows: 20, maxRows: 21}}
}} variant={"borderless"}
onRemove={async () => { onChange={e => {
if (groups.length == 1) { handleDigitalPersonContentChange(e.target.value)
message.warning('至少保留一个内容块') }}
return; />
} </div> : <p className="p-2">12123</p>}
onChange?.(groups.filter((_, idx) => index !== idx)) </div>
}} </div>
/> </div>
))} <div className={"panel groups-list flex-1"}>
<div className={"area-title"}>
<span className=""></span>
<span className="text-gray-400"></span>
</div>
<div className="panel-body py-3">
<div className="max-h-[485px] overflow-auto py-4">
{groups.map((g, index) => (
index == 0 ? null : <ArticleBlock
editable={editable}
key={index}
blocks={g}
onChange={(blocks) => {
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))
}}
/>
))}
</div>
</div>
</div>
{groups.length == 0 && editable && {groups.length == 0 && editable &&
<ArticleBlock editable onChange={blocks => onChange?.([blocks])} index={0} <ArticleBlock editable onChange={blocks => onChange?.([blocks])} index={0}
blocks={[{type: 'text', content: ''}]}/>} blocks={[{type: 'text', content: ''}]}/>}

View File

@ -64,15 +64,7 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
} }
// //
return <div className={styles.image}> return <div className={styles.image}>
{editable ? <div className={'relative'}> {editable && onlyUpload ? <div className={'relative'}>
{!onlyUpload && <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className={styles.imageDelete}><CloseOutlined/></span>
</Popconfirm>}
<Spin spinning={loading >= 0} percent={loading == 0 ? 'auto' : loading}> <Spin spinning={loading >= 0} percent={loading == 0 ? 'auto' : loading}>
<Upload <Upload
multiple={false} maxCount={1} data={getUploadData} multiple={false} maxCount={1} data={getUploadData}
@ -84,7 +76,14 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
{data.content ? <> {data.content ? <>
<img src={data.content}/> <img src={data.content}/>
<div className={styles.uploadTips}> <div className={styles.uploadTips}>
<span></span> {!onlyUpload && <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className={styles.imageDelete}><CloseOutlined/></span>
</Popconfirm>}
</div> </div>
</> : <div className={styles.imagePlaceholder}> </> : <div className={styles.imagePlaceholder}>
<div className={'text-center'}> <div className={'text-center'}>
@ -95,7 +94,19 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
</div> </div>
</Upload> </Upload>
</Spin> </Spin>
</div> : <div className={styles.uploadImage}><img src={data.content}/></div>} </div> : <div className={styles.uploadImage}>
<img src={data.content}/>
<div className={styles.uploadTips}>
{!onlyUpload && <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className={styles.imageDelete}><CloseOutlined/></span>
</Popconfirm>}
</div>
</div>}
</div> </div>
} }
@ -108,7 +119,7 @@ export function BlockText({data, editable, onChange, isFirstBlock}: Props) {
onChange={e => { onChange={e => {
onChange?.({type: 'text', content: e.target.value}) 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"}/> variant={"borderless"}/>
</div> : <p className="p-2">{data.content}</p>} </div> : <p className="p-2">{data.content}</p>}
</div> </div>

View File

@ -97,7 +97,7 @@ export const IconAddImage = ({style, className}: IconProps) => (
</svg> </svg>
) )
export const IconAdd = ({style, className}: IconProps) => ( export const IconAddCircle = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg" <svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1"> width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path <path
@ -109,6 +109,17 @@ export const IconAdd = ({style, className}: IconProps) => (
fill="currentColor"/> fill="currentColor"/>
</svg> </svg>
) )
export const IconAdd = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path d="M544 288v448c0 17.6-14.4 32-32 32s-32-14.4-32-32V288c0-17.6 14.4-32 32-32s32 14.4 32 32z"
fill="currentColor"/>
<path d="M736 544H288c-17.6 0-32-14.4-32-32s14.4-32 32-32h448c17.6 0 32 14.4 32 32s-14.4 32-32 32z"
fill="currentColor"/>
</svg>
)
export const IconPlay = ({style, className}: IconProps) => ( export const IconPlay = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg" <svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"

View File

@ -0,0 +1,47 @@
import {App, Button} from "antd";
import {showToast} from "@/components/message.ts";
import {useState} from "react";
import {push2article} from "@/service/api/news.ts";
import {IconArrowRight, IconDelete} from "@/components/icons";
import {useNavigate} from "react-router-dom";
export default function ButtonDeleteBatch(props: { ids: Id[]; }) {
const {modal} = App.useApp();
const [loading,setLoading] = useState(false)
const navigate = useNavigate();
const handlePush = () => {
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 (
<div>
<button
loading={loading}
onClick={onPushClick}
className='bg-gray-400 hover:bg-gray-500'
>
<span className={'text'}></span>
<IconDelete className={'text-white'} />
</button>
</div>
)
}

View File

@ -2,9 +2,10 @@ import {Button, Modal} from "antd";
import React, {useState} from "react"; import React, {useState} from "react";
import {showErrorToast, showToast} from "@/components/message.ts"; import {showErrorToast, showToast} from "@/components/message.ts";
import {push2video} from "@/service/api/article.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 [loading, setLoading] = useState(false)
const handlePush = () => { const handlePush = () => {
setLoading(true) setLoading(true)
@ -27,6 +28,17 @@ export default function ButtonPush2Video(props: { ids: Id[];onSuccess?:()=>void;
}) })
} }
return ( return (
<Button type="primary" loading={loading} onClick={onPushClick}></Button> <div>
<Button
type="primary"
loading={loading}
className='btn-action btn-gray-300'
icon={<IconArrowRight className={'text-white'}/>}
onClick={onPushClick}
iconPosition={'end'}
>
<span className={'text'}></span>
</Button>
</div>
) )
} }

View File

@ -13,6 +13,7 @@ import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infini
import {IconDelete, IconEdit} from "@/components/icons"; import {IconDelete, IconEdit} from "@/components/icons";
import {clsx} from "clsx"; import {clsx} from "clsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx"; import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import ButtonDeleteBatch from "@/pages/news/components/button-delete-batch.tsx";
export default function NewEdit() { export default function NewEdit() {
@ -127,9 +128,8 @@ export default function NewEdit() {
<div className="page-action"> <div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)} /> <ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)} />
<div> <ButtonDeleteBatch ids={selectedRowKeys} onSuccess={refresh}/>
<ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/> <ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/>
</div>
</div> </div>
</div> </div>
<ArticleEditModal <ArticleEditModal