feat: 编辑样式调整为新的样式
This commit is contained in:
parent
f946e9d4f7
commit
97d9200217
@ -1,5 +1,5 @@
|
||||
.blockContainer {
|
||||
@apply flex mb-5;
|
||||
@apply flex mb-5;
|
||||
}
|
||||
|
||||
.block {
|
||||
@ -14,6 +14,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.blockFist {
|
||||
@apply p-0 border-0 !important;
|
||||
}
|
||||
|
||||
.blockItem {
|
||||
|
||||
}
|
||||
@ -21,40 +25,61 @@
|
||||
.group {
|
||||
}
|
||||
|
||||
.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{
|
||||
.imageList {
|
||||
@apply grid grid-cols-4 gap-4 p-3 border border-blue-200;
|
||||
:global {
|
||||
.ant-upload-wrapper {
|
||||
display: block;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
.ant-upload{
|
||||
|
||||
.ant-upload {
|
||||
display: block;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply block m-0;
|
||||
max-width: 100%;
|
||||
height: 100px;
|
||||
object-fit: contain;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
@apply rounded bg-gray-100;
|
||||
height: 100px;
|
||||
|
||||
&:hover {
|
||||
@apply border-blue-500;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
.uploadImage {
|
||||
@apply flex justify-center items-center relative;
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
@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;
|
||||
}
|
||||
.uploadTips{
|
||||
@apply absolute inset-0 cursor-pointer opacity-0 rounded flex items-center justify-center bg-black/50 text-white;
|
||||
}
|
||||
.imagePlaceholder{
|
||||
|
||||
.imagePlaceholder {
|
||||
@apply flex items-center justify-center;
|
||||
height: 100px;
|
||||
}
|
||||
&:hover{
|
||||
.uploadTips{
|
||||
|
||||
&:hover {
|
||||
@apply bg-gray-100 cursor-pointer rounded text-blue-500;
|
||||
.uploadTips {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
@ -62,10 +87,11 @@
|
||||
|
||||
.text {
|
||||
@apply border border-blue-200 overflow-hidden flex-1 rounded focus:border-blue-200 transition;
|
||||
&:hover{
|
||||
&:hover {
|
||||
@apply border-blue-500;
|
||||
}
|
||||
&:focus-within{
|
||||
|
||||
&:focus-within {
|
||||
@apply border-blue-500 shadow-md;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,56 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import {Popconfirm, Space} from "antd";
|
||||
|
||||
import {IconAdd, IconAddImage, IconAddText, IconDelete} from "@/components/icons";
|
||||
import {BlockImage, BlockText} from "./item.tsx";
|
||||
import {IconAdd, IconDelete} from "@/components/icons";
|
||||
import ImageList from "@/components/article/list.tsx";
|
||||
|
||||
import { BlockText} from "./item.tsx";
|
||||
import styles from './article.module.scss'
|
||||
import {Button, Popconfirm} from "antd";
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
index?:number;
|
||||
index?: number;
|
||||
className?: string;
|
||||
blocks: BlockContent[];
|
||||
editable?: boolean;
|
||||
onChange?: (blocks: BlockContent[]) => void;
|
||||
onRemove?: () => void;
|
||||
onAdd?: () => void;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export default function ArticleBlock({className, blocks, editable, onRemove, onAdd, onChange,index}: Props) {
|
||||
function rebuildBlockArray(blocks: BlockContent[]) {
|
||||
const textBlock: BlockContent = {
|
||||
type: 'text',
|
||||
content: ''
|
||||
}
|
||||
const _blocks: BlockContent[] = [textBlock];
|
||||
const textArray: string[] = []
|
||||
blocks.forEach(it => {
|
||||
if (it.type == 'text') {
|
||||
textArray.push(it.content)
|
||||
} else {
|
||||
_blocks.push(it)
|
||||
}
|
||||
})
|
||||
textBlock.content = textArray.join('\n')
|
||||
return _blocks
|
||||
}
|
||||
|
||||
|
||||
export default function ArticleBlock(
|
||||
{
|
||||
className,
|
||||
blocks: defaultBlocks,
|
||||
editable,
|
||||
onRemove,
|
||||
onAdd,
|
||||
onChange,
|
||||
index,
|
||||
errorMessage
|
||||
}: Props) {
|
||||
const blocks = rebuildBlockArray(defaultBlocks)
|
||||
const handleBlockRemove = (index: number) => {
|
||||
// 删除当前项
|
||||
onChange?.(blocks.filter((_, idx) => index !== idx))
|
||||
@ -43,72 +75,39 @@ export default function ArticleBlock({className, blocks, editable, onRemove, onA
|
||||
}
|
||||
|
||||
return <div className={styles.blockContainer}>
|
||||
<div className={clsx(className || '', styles.block,' hover:bg-blue-10')}>
|
||||
<div className={clsx(className || '', styles.block, index == 0 ? styles.blockFist : '', ' hover:bg-blue-10')}>
|
||||
<div className={styles.blockBody}>
|
||||
{blocks.map((it, idx) => {
|
||||
const isFirstTextBlock = index == 0 && it.type ==='text' && firstTextBlockIndex == idx
|
||||
return (<div key={idx}>
|
||||
<div className={clsx(isFirstTextBlock?'':styles.blockItem, 'flex')}>
|
||||
{
|
||||
it.type === 'text'
|
||||
? <BlockText isFirstBlock={isFirstTextBlock} onChange={(block) => handleBlockChange(idx, block)} data={it}
|
||||
editable={editable}/>
|
||||
: <BlockImage data={it} editable={editable}/>
|
||||
}
|
||||
{editable && <div className="create-container ml-2 flex flex-col justify-between">
|
||||
{isFirstTextBlock?<span></span>:<Popconfirm
|
||||
title="提示"
|
||||
description={<div style={{minWidth: 150}}>
|
||||
<span>请确认删除此{it.type === 'text' ? '文本' : '图片'}?</span>
|
||||
</div>}
|
||||
onConfirm={() => handleBlockRemove(idx)}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<span
|
||||
className="article-action-icon"
|
||||
title={`删除此${it.type === 'text' ? '文本' : '图片'}`}>
|
||||
<IconDelete style={{fontSize: 18}}/>
|
||||
</span>
|
||||
</Popconfirm>}
|
||||
<div>
|
||||
<span onClick={() => handleAddBlock('text', idx + 1)}
|
||||
className="article-action-icon" title="新增文本"><IconAddText
|
||||
style={{fontSize: 18}}/></span>
|
||||
<span onClick={() => handleAddBlock('image', idx + 1)}
|
||||
className="article-action-icon mt-1" title="新增图片"><IconAddImage
|
||||
style={{fontSize: 16}}/></span>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
{isFirstTextBlock && <div className={'text-red-500 text-right pr-6 mt-1 text-sm'}>该编辑框内容由数字人播报</div>}
|
||||
</div>)
|
||||
}
|
||||
)}
|
||||
{editable && blocks.length == 0 &&
|
||||
<div style={{minHeight: 80}} className="flex items-center justify-center">
|
||||
<div className="flex gap-5">
|
||||
<Button onClick={() => handleAddBlock('text')}>添加文本</Button>
|
||||
<Button onClick={() => handleAddBlock('image')}>添加图片</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}>
|
||||
<BlockText
|
||||
isFirstBlock={true}
|
||||
onChange={(block) => handleBlockChange(0, block)}
|
||||
data={blocks[0]}
|
||||
isFirstBlock={index == 0}
|
||||
editable={editable}/>
|
||||
</div>
|
||||
{index == 0 && <div className="flex items-center text-red-500 justify-between text-sm mt-1">
|
||||
<div>{errorMessage}</div>
|
||||
<div>该编辑框内容由数字人播报</div>
|
||||
</div>}
|
||||
</div>
|
||||
{index > 0 && <ImageList blocks={blocks} editable={editable} onChange={onChange}/>}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{editable && <div className="ml-2 flex flex-col justify-between ">
|
||||
<Popconfirm
|
||||
title="提示"
|
||||
description={<div style={{minWidth: 150}}>
|
||||
<span>请确认删除此删除此分组?</span>
|
||||
</div>}
|
||||
onConfirm={onRemove}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<span className="article-action-icon" title="删除此分组"><IconDelete
|
||||
style={{fontSize: 24}}/></span>
|
||||
</Popconfirm>
|
||||
{
|
||||
index > 0 ? <Popconfirm
|
||||
title={<div style={{minWidth: 150}}><span>请确认删除此删除此分组?</span></div>}
|
||||
onConfirm={onRemove}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<span className="article-action-icon" title="删除此分组">
|
||||
<IconDelete style={{fontSize: 24}}/>
|
||||
</span>
|
||||
</Popconfirm> : <span></span>
|
||||
}
|
||||
<span onClick={onAdd} className="article-action-icon" title="新增分组"><IconAdd
|
||||
style={{fontSize: 24}}/></span>
|
||||
</div>}
|
||||
|
@ -2,11 +2,13 @@ import {Input, Modal} from "antd";
|
||||
import ArticleGroup from "@/components/article/group.tsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useSetState} from "ahooks";
|
||||
import {getById} from "@/service/api/article.ts";
|
||||
import * as article from "@/service/api/article.ts";
|
||||
import {regenerate} from "@/service/api/video.ts";
|
||||
|
||||
type Props = {
|
||||
id?: number;
|
||||
onClose?: () => void;
|
||||
type: 'news' | 'video';
|
||||
onClose?: (saved?: boolean) => void;
|
||||
}
|
||||
|
||||
export default function ArticleEditModal(props: Props) {
|
||||
@ -16,29 +18,59 @@ export default function ArticleEditModal(props: Props) {
|
||||
|
||||
const [state, setState] = useSetState({
|
||||
loading: false,
|
||||
open: false
|
||||
open: false,
|
||||
msgTitle: '',
|
||||
msgGroup: '',
|
||||
})
|
||||
// 保存数据
|
||||
const handleSave = () => {
|
||||
console.log(groups, title)
|
||||
if (!title) {
|
||||
// setState({msgTitle: '请输入标题内容'});
|
||||
return;
|
||||
}
|
||||
if (groups.length == 0 || groups[0].length == 0 || !groups[0][0].content) {
|
||||
// setState({msgGroup: '请输入正文文本内容'});
|
||||
return;
|
||||
}
|
||||
const save = props.type == 'news' ? article.save : regenerate
|
||||
setState({loading: true})
|
||||
save(title, groups, props.id > 0 ? props.id : undefined).then(() => {
|
||||
props.onClose?.(true)
|
||||
}).finally(() => {
|
||||
setState({loading: false})
|
||||
});
|
||||
props.onClose?.()
|
||||
// if (props.onSave) {
|
||||
// setState({loading: true})
|
||||
// props.onSave?.().then(() => {
|
||||
// setState({loading: false, open: false})
|
||||
// })
|
||||
// } else {
|
||||
// console.log(groups)
|
||||
// }
|
||||
}
|
||||
useEffect(() => {
|
||||
if(props.id){
|
||||
if(props.id > 0){
|
||||
getById(props.id).then(res => {
|
||||
setGroups(res.content_group)
|
||||
setTitle(res.title)
|
||||
})
|
||||
}else{
|
||||
setGroups([])
|
||||
setTitle('')
|
||||
if (props.id) {
|
||||
if (props.id > 0) {
|
||||
if (props.type == 'news') {
|
||||
article.getById(props.id).then(res => {
|
||||
setGroups(res.content_group)
|
||||
setTitle(res.title)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 新增
|
||||
setGroups([
|
||||
[{
|
||||
type: 'text',
|
||||
content: '韩国国会当地时间14日16时举行全体会议,就在野党阵营第二次提出的尹锡悦总统弹劾案进行表决。根据投票结果,有204票赞成,85票反对,3票弃权,8票无效,弹劾案最终获得通过,尹锡悦的总统职务立即停止。'
|
||||
}],
|
||||
[
|
||||
{
|
||||
type: 'text',
|
||||
content: '韩国宪法法院将在180天内完成弹劾案审判程序。如果宪法法院作出弹劾案不成立的裁决,尹锡悦将立即恢复总统职务;如果宪法法院认可弹劾案成立,尹锡悦将立即被罢免,预计韩国将在明年4月至6月间举行大选。'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
content: 'https://zverse-on.oss-cn-shanghai.aliyuncs.com/metahuman/workbench/20241214/193c442df75.jpeg'
|
||||
},
|
||||
|
||||
],
|
||||
])
|
||||
setTitle('韩国国会通过总统弹劾案 尹锡悦职务立即停止')
|
||||
}
|
||||
}
|
||||
}, [props.id])
|
||||
@ -49,7 +81,7 @@ export default function ArticleEditModal(props: Props) {
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
width={800}
|
||||
onCancel={props.onClose}
|
||||
onCancel={()=>props.onClose?.()}
|
||||
okButtonProps={{loading: state.loading}}
|
||||
onOk={handleSave}
|
||||
>
|
||||
@ -59,18 +91,26 @@ export default function ArticleEditModal(props: Props) {
|
||||
<span className="require ml-1 font-bold text-red-500">*</span>
|
||||
</div>
|
||||
<div className="box mt-1">
|
||||
<Input value={title} onChange={e => {
|
||||
<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>
|
||||
<div className="aricle-body mt-2">
|
||||
<div className="aricle-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">
|
||||
<ArticleGroup editable groups={groups} onChange={list => setGroups(() => list)}/>
|
||||
<ArticleGroup
|
||||
errorMessage={state.msgGroup} editable groups={groups}
|
||||
onChange={list => {
|
||||
setGroups(() => list)
|
||||
setState({msgGroup: (list.length == 0 || list[0].length == 0 || !list[0][0].content) ? '请输入正文文本内容' : ''});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>);
|
||||
|
@ -1,22 +1,46 @@
|
||||
|
||||
import {message} from "antd"
|
||||
import ArticleBlock from "@/components/article/block.tsx";
|
||||
|
||||
import styles from './article.module.scss'
|
||||
import {showToast} from "@/components/message.ts";
|
||||
|
||||
type Props = {
|
||||
groups: BlockContent[][];
|
||||
editable?: boolean;
|
||||
onChange?: (groups: BlockContent[][]) => void;
|
||||
errorMessage?: string;
|
||||
}
|
||||
export default function ArticleGroup({groups, editable, onChange}: Props) {
|
||||
|
||||
function rebuildGroups(groups: BlockContent[][]) {
|
||||
if (groups.length < 2) {
|
||||
Array(2 - groups.length).fill([{type: 'text', content: ''}]).forEach((it) => {
|
||||
groups.push(it)
|
||||
})
|
||||
}
|
||||
|
||||
return groups;
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default function ArticleGroup({groups: _groups, editable, onChange,errorMessage}: Props) {
|
||||
const groups = rebuildGroups(_groups)
|
||||
/**
|
||||
* 添加一个组
|
||||
* @param insertIndex 插入的位置,-1表示插入到末尾
|
||||
*/
|
||||
const handleAddGroup = ( insertIndex: number = -1) => {
|
||||
const newGroup: BlockContent[] = []
|
||||
const _groups = [...groups]
|
||||
const handleAddGroup = (insertIndex: number = -1) => {
|
||||
if (insertIndex !== -1 && insertIndex !== 1) {
|
||||
const triggerGroup = insertIndex == -1 || insertIndex >= groups.length ? groups[groups.length - 1] : groups[insertIndex - 1];
|
||||
// 判断
|
||||
if (triggerGroup.length == 0 || triggerGroup.some(s => !s.content)) {
|
||||
showToast('请先添加内容')
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const newGroup: BlockContent[] = [{type: 'text', content: ''}]
|
||||
const _groups = [...groups];
|
||||
if (insertIndex == -1 || insertIndex >= groups.length) { // -1或者越界表示新增
|
||||
_groups.push(newGroup)
|
||||
} else {
|
||||
@ -34,6 +58,7 @@ export default function ArticleGroup({groups, editable, onChange}: Props) {
|
||||
groups[index] = blocks
|
||||
onChange?.([...groups])
|
||||
}}
|
||||
errorMessage={errorMessage}
|
||||
index={index}
|
||||
onAdd={() => {
|
||||
handleAddGroup?.(index + 1)
|
||||
@ -48,6 +73,7 @@ export default function ArticleGroup({groups, editable, onChange}: Props) {
|
||||
/>
|
||||
))}
|
||||
{groups.length == 0 && editable &&
|
||||
<ArticleBlock editable onChange={blocks => onChange?.([blocks])} index={0} blocks={[{type:'text',content:''}]}/>}
|
||||
<ArticleBlock editable onChange={blocks => onChange?.([blocks])} index={0}
|
||||
blocks={[{type: 'text', content: ''}]}/>}
|
||||
</div>
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import React, {useState} from "react";
|
||||
import {Button, Input, Spin, Upload, UploadProps} from "antd";
|
||||
import {Input, Popconfirm, Spin, Upload, UploadProps} from "antd";
|
||||
import {CloseOutlined,CloudUploadOutlined} from "@ant-design/icons";
|
||||
import {clsx} from "clsx";
|
||||
|
||||
import styles from './article.module.scss'
|
||||
import {getOssPolicy} from "@/service/api/common.ts";
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import {clsx} from "clsx";
|
||||
import {IconAddImage} from "@/components/icons";
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
@ -14,11 +16,15 @@ type Props = {
|
||||
onChange?: (data: BlockContent) => void;
|
||||
isFirstBlock?: boolean;
|
||||
}
|
||||
type ImageProps = {
|
||||
onRemove?: () => void;
|
||||
onlyUpload?: boolean;
|
||||
} & Props;
|
||||
|
||||
const MimeTypes = ['image/jpeg', 'image/png', 'image/jpg']
|
||||
const Data: { uploadConfig?: TOSSPolicy } = {}
|
||||
|
||||
export function BlockImage({data, editable, onChange}: Props) {
|
||||
export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: ImageProps) {
|
||||
|
||||
const [loading, setLoading] = useState<number>(-1)
|
||||
// oss上传文件所需的数据
|
||||
@ -48,7 +54,7 @@ export function BlockImage({data, editable, onChange}: Props) {
|
||||
console.log('onChange', file);
|
||||
if (file.status == 'done') {
|
||||
setLoading(-1)
|
||||
onChange?.({type: 'image', content: Data.uploadConfig?.host + file.url})
|
||||
onChange?.({type: 'image', content: Data.uploadConfig?.host + '/' + file.url})
|
||||
} else if (file.status == 'error') {
|
||||
setLoading(-1)
|
||||
showToast('上传图片失败,请重试', 'warning')
|
||||
@ -58,7 +64,15 @@ export function BlockImage({data, editable, onChange}: Props) {
|
||||
}
|
||||
//
|
||||
return <div className={styles.image}>
|
||||
{editable ? <div>
|
||||
{editable ? <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}>
|
||||
<Upload
|
||||
multiple={false} maxCount={1} data={getUploadData}
|
||||
@ -73,7 +87,10 @@ export function BlockImage({data, editable, onChange}: Props) {
|
||||
<span>更换图片</span>
|
||||
</div>
|
||||
</> : <div className={styles.imagePlaceholder}>
|
||||
<Button>选择图片</Button>
|
||||
<div className={'text-center'}>
|
||||
<IconAddImage className={"text-4xl inline-block"} />
|
||||
<div className={'text-sm'}>上传图片</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</Upload>
|
||||
@ -84,13 +101,14 @@ export function BlockImage({data, editable, onChange}: Props) {
|
||||
|
||||
export function BlockText({data, editable, onChange, isFirstBlock}: Props) {
|
||||
return <div className='flex-1'>
|
||||
<div className={clsx(styles.text, isFirstBlock?'border-red-400 hover:border-red-500 focus-within:border-red-500':'')}>
|
||||
<div
|
||||
className={clsx(styles.text, isFirstBlock ? 'border-red-400 hover:border-red-500 focus-within:border-red-500' : '')}>
|
||||
{editable ? <div className="relative">
|
||||
<Input.TextArea
|
||||
onChange={e => {
|
||||
onChange?.({type: 'text', content: e.target.value})
|
||||
}}
|
||||
placeholder={'请输入文本'} value={data.content} autoSize={{minRows: 3, maxRows: 8}}
|
||||
placeholder={'请输入文本内容'} value={data.content} autoSize={{minRows: 3, maxRows: 8}}
|
||||
variant={"borderless"}/>
|
||||
</div> : <p className="p-2">{data.content}</p>}
|
||||
</div>
|
||||
|
42
src/components/article/list.tsx
Normal file
42
src/components/article/list.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
|
||||
import {BlockImage} from "@/components/article/item.tsx";
|
||||
|
||||
import styles from './article.module.scss'
|
||||
|
||||
export default function ImageList(props: {
|
||||
blocks: BlockContent[];
|
||||
editable?: boolean;
|
||||
onChange?: (blocks: BlockContent[]) => void;
|
||||
}) {
|
||||
|
||||
// 处理删除
|
||||
const handleRemove = (index: number) => {
|
||||
props.onChange?.(props.blocks.filter((_, idx) => index !== idx))
|
||||
const newBlocks = [...props.blocks]
|
||||
newBlocks.splice(index, 1)
|
||||
props.onChange?.(newBlocks)
|
||||
}
|
||||
// 处理新增
|
||||
const handleAdd = (data: BlockContent) => {
|
||||
props.onChange?.([...props.blocks, data])
|
||||
}
|
||||
// 处理更新
|
||||
const handleUpdate = (index: number, data: BlockContent) => {
|
||||
props.onChange?.(props.blocks.map((it, idx) => idx === index ? data : it))
|
||||
}
|
||||
|
||||
|
||||
return (<div className={styles.imageList}>
|
||||
{props.blocks.map((it, index) => (
|
||||
it.type === 'image' ? <BlockImage
|
||||
key={index} data={it} editable={props.editable}
|
||||
onChange={(data) => handleUpdate(index, data)}
|
||||
onRemove={() => handleRemove(index)}
|
||||
/> : null
|
||||
))}
|
||||
{props.editable &&
|
||||
<BlockImage onlyUpload onChange={handleAdd} data={{type: 'image', content: ''}} editable={true}/>}
|
||||
</div>)
|
||||
}
|
||||
|
@ -14,12 +14,9 @@ export default function NewEdit() {
|
||||
const [editId, setEditId] = useState(-1)
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<Id[]>([])
|
||||
const [params, setParams] = useState<ApiArticleSearchParams>({
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
pagination: {page: 1, limit: 10}
|
||||
})
|
||||
const {data} = useRequest(() => getList(params), {refreshDeps: [params]})
|
||||
const {data, refresh} = useRequest(() => getList(params), {refreshDeps: [params]})
|
||||
|
||||
const columns: TableColumnsType<ListArticleItem> = [
|
||||
{
|
||||
@ -86,15 +83,20 @@ export default function NewEdit() {
|
||||
showSizeChanger={false}
|
||||
simple={true}
|
||||
rootClassName={'simple-pagination'}
|
||||
onChange={(page) => setParams(prev=>({
|
||||
onChange={(page) => setParams(prev => ({
|
||||
...prev,
|
||||
pagination: {page, limit: 10}
|
||||
}))}
|
||||
/>
|
||||
<ButtonPush2Video ids={selectedRowKeys} />
|
||||
<ButtonPush2Video ids={selectedRowKeys}/>
|
||||
</div>}
|
||||
</div>
|
||||
<ArticleEditModal id={editId} onClose={() => setEditId(-1)}/>
|
||||
<ArticleEditModal
|
||||
type="news" id={editId}
|
||||
onClose={(saved) => {
|
||||
setEditId(-1)
|
||||
if (saved) refresh()
|
||||
}}/>
|
||||
</Card>
|
||||
</div>)
|
||||
}
|
@ -21,9 +21,9 @@ export function getById(id: Id) {
|
||||
return post<ArticleDetail>({url: '/article/detail/' + id})
|
||||
}
|
||||
|
||||
export function save(title: string, content_group: BlockContent[][], id: number) {
|
||||
export function save(title: string, content_group: BlockContent[][], id?: number) {
|
||||
return post<{ content: string }>({
|
||||
url: '/spider/article',
|
||||
url: '/article/save',
|
||||
data: {
|
||||
title,
|
||||
content_group,
|
||||
|
@ -13,7 +13,7 @@ export function getList(data: {
|
||||
* @param content_group
|
||||
* @param article_id
|
||||
*/
|
||||
export function regenerate(title: string, content_group: BlockContent[][], article_id: number) {
|
||||
export function regenerate(title: string, content_group: BlockContent[][], article_id?: Id) {
|
||||
return post<{ content: string }>({
|
||||
url: '/video/regenerate',
|
||||
data: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user