fix: 新建article时 ,上传文件extraFile缺失entityId

This commit is contained in:
wkj 2025-12-15 18:18:04 +08:00
parent 21348fff43
commit ae1d1cf70a
12 changed files with 176 additions and 232 deletions

View File

@ -21,7 +21,6 @@ export default OakComponent({
data: { data: {
editor: null, editor: null,
html: '', html: '',
contentTip: false,
}, },
properties: { properties: {
articleMenuId: '', articleMenuId: '',
@ -40,15 +39,14 @@ export default OakComponent({
next.editor.setHtml(next.content); next.editor.setHtml(next.content);
} }
}, },
oakId(prev, next) { // oakId(prev, next) {
if (prev.oakId !== next.oakId) { // if (prev.oakId !== next.oakId) {
const { editor } = this.state; // const { editor } = this.state;
if (editor == null) // if (editor == null) return;
return; // editor.destroy();
editor.destroy(); // this.setEditor(null);
this.setEditor(null); // }
} // },
},
name(prev, next) { name(prev, next) {
if (prev.name !== next.name) { if (prev.name !== next.name) {
window.document.title = next.name ?? ''; window.document.title = next.name ?? '';
@ -58,7 +56,7 @@ export default OakComponent({
lifetimes: { lifetimes: {
async ready() { async ready() {
const { oakId, articleMenuId } = this.props; const { oakId, articleMenuId } = this.props;
if (!oakId) { if (this.isCreation()) {
if (articleMenuId) { if (articleMenuId) {
this.update({ this.update({
articleMenuId, articleMenuId,
@ -89,11 +87,6 @@ export default OakComponent({
editor, editor,
}); });
}, },
clearContentTip() {
this.setState({
contentTip: false,
});
},
async check() { async check() {
if (this.state.name && if (this.state.name &&
this.state.name.length > 0 && this.state.name.length > 0 &&

View File

@ -7,7 +7,7 @@
"name": "请输入文章标题", "name": "请输入文章标题",
"content": "请输入文章内容..." "content": "请输入文章内容..."
}, },
"chcek": { "check": {
"no name": "请填写文章标题!", "no name": "请填写文章标题!",
"no content": "请填写文章内容!" "no content": "请填写文章内容!"
} }

View File

@ -8,7 +8,6 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
editor: any; editor: any;
content?: string; content?: string;
origin?: null | EntityDict['extraFile']['Schema']['origin']; origin?: null | EntityDict['extraFile']['Schema']['origin'];
contentTip: boolean;
articleMenuId: string; articleMenuId: string;
oakId: string; oakId: string;
tocPosition: 'none' | 'left' | 'right'; tocPosition: 'none' | 'left' | 'right';
@ -26,6 +25,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
setEditor: (editor: any) => void; setEditor: (editor: any) => void;
check: () => void; check: () => void;
uploadFile: (extraFile: EntityDict['extraFile']['CreateOperationData'], file: File) => Promise<string>; uploadFile: (extraFile: EntityDict['extraFile']['CreateOperationData'], file: File) => Promise<string>;
clearContentTip: () => void;
gotoPreview: (content?: string, title?: string) => void; gotoPreview: (content?: string, title?: string) => void;
}>): React.JSX.Element; }>): React.JSX.Element;

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Alert, Button, Space, Input, } from "antd"; import { Button, Space, Input, } from "antd";
import "@wangeditor/editor/dist/css/style.css"; // 引入 css import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react"; import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import { SlateNode } from "@wangeditor/editor"; import { SlateNode } from "@wangeditor/editor";
@ -30,7 +30,7 @@ function customCheckImageFn(src, alt, url) {
} }
export default function Render(props) { export default function Render(props) {
const { methods, data } = props; const { methods, data } = props;
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods; const { t, setMessage, setEditor, check, uploadFile, update, setHtml, gotoPreview, } = methods;
const { oakId, oakFullpath, id, name, content, editor, origin, tocPosition = 'none', highlightBgColor, scrollId, tocWidth, tocHeight, height = 600, tocClosed = false, execuable, activeColor, html, oakLoading, oakExecuting, } = data; const { oakId, oakFullpath, id, name, content, editor, origin, tocPosition = 'none', highlightBgColor, scrollId, tocWidth, tocHeight, height = 600, tocClosed = false, execuable, activeColor, html, oakLoading, oakExecuting, } = data;
const [articleId, setArticleId] = useState(''); const [articleId, setArticleId] = useState('');
const [toc, setToc] = useState([]); const [toc, setToc] = useState([]);
@ -80,7 +80,6 @@ export default function Render(props) {
<div className={classNames(Style.editorContainer, { <div className={classNames(Style.editorContainer, {
[Style.editorExternalContainer]: !!scrollId, [Style.editorExternalContainer]: !!scrollId,
})}> })}>
{data.contentTip && (<Alert type="info" message={t("tips.content")} closable onClose={() => clearContentTip()}/>)}
<div className={Style.titleContainer}> <div className={Style.titleContainer}>
<Input onChange={(e) => { <Input onChange={(e) => {
if (e.target.value.trim() && e.target.value.trim() !== '') { if (e.target.value.trim() && e.target.value.trim() !== '') {
@ -89,102 +88,107 @@ export default function Render(props) {
else { else {
update({ name: null }); update({ name: null });
} }
}} value={data.name ?? ''} placeholder={t('placeholder.name')} size="large" maxLength={32} }} value={data.name ?? ''} placeholder={t('placeholder.name')} size="large" maxLength={32} showCount className={Style.titleInput}/>
// suffix={`${(data.name || "").length}/32`}
showCount className={Style.titleInput}/>
</div> </div>
<div> <div>
<Editor defaultConfig={{ {!!articleId && <Editor defaultConfig={{
autoFocus: true, autoFocus: true,
placeholder: t('placeholder.content'), placeholder: t('placeholder.content'),
MENU_CONF: { MENU_CONF: {
checkImage: customCheckImageFn, checkImage: customCheckImageFn,
uploadImage: { uploadImage: {
// 自定义上传 // 自定义上传
async customUpload(file, insertFn) { async customUpload(file, insertFn) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = file; const { name, size, type } = file;
const extension = name.substring(name.lastIndexOf(".") + 1); const extension = name.substring(name.lastIndexOf(".") + 1);
const filename = name.substring(0, name.lastIndexOf(".")); const filename = name.substring(0, name.lastIndexOf("."));
const extraFile = { const extraFile = {
entity: "article", entity: "article",
entityId: oakId || articleId, entityId: oakId || articleId,
origin: origin, origin: origin,
type: "image", type: "image",
tag1: "source", tag1: "source",
objectId: generateNewId(), objectId: generateNewId(),
filename, filename,
size, size,
extension, extension,
bucket: "", bucket: "",
id: generateNewId(), id: generateNewId(),
fileType: type, fileType: type,
}; };
try { try {
// 自己实现上传,并得到图片 url alt href // 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile, file); const url = await uploadFile(extraFile, file);
// 最后插入图片 // 最后插入图片
insertFn(url, extraFile.filename); insertFn(url, extraFile.filename);
} }
catch (err) { } catch (err) {
setMessage({
type: "error",
content: err.message,
});
}
},
},
uploadVideo: {
// 自定义上传
async customUpload(file, insertFn) {
// TS 语法
// file 即选中的文件
const { name, size, type } = file;
const extension = name.substring(name.lastIndexOf(".") + 1);
const filename = name.substring(0, name.lastIndexOf("."));
const extraFile = {
entity: "article",
entityId: oakId || articleId,
origin: origin,
type: "video",
tag1: "source",
objectId: generateNewId(),
filename,
size,
extension,
bucket: "",
id: generateNewId(),
fileType: type,
};
try {
// 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile, file);
// 最后插入图片
insertFn(url, url + "?vframe/jpg/offset/0");
}
catch (err) {
setMessage({
type: "error",
content: err.message,
});
}
},
}, },
}, },
uploadVideo: { }} onCreated={setEditor} onChange={(editor) => {
// 自定义上传 setHtml(editor.getHtml());
async customUpload(file, insertFn) { const headers = editor.getElemsByTypePrefix("header");
// TS 语法 const tocItems = headers.map((header) => {
// file 即选中的文件 const text = SlateNode.string(header);
const { name, size, type } = file; const { id, type } = header;
const extension = name.substring(name.lastIndexOf(".") + 1); return {
const filename = name.substring(0, name.lastIndexOf(".")); text,
const extraFile = { level: parseInt(type.substring(6)),
entity: "article", id,
entityId: oakId || articleId, };
origin: origin, });
type: "video", setToc([...tocItems]);
tag1: "source", }} style={{
objectId: generateNewId(), minHeight: '100%',
filename, }} mode="default"/>}
size,
extension,
bucket: "",
id: generateNewId(),
fileType: type,
};
try {
// 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile, file);
// 最后插入图片
insertFn(url, url +
"?vframe/jpg/offset/0");
}
catch (err) { }
},
},
},
}} onCreated={setEditor} onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map((header) => {
const text = SlateNode.string(header);
const { id, type } = header;
return {
text,
level: parseInt(type.substring(6)),
id,
};
});
setToc([...tocItems]);
}} style={{
minHeight: '100%',
}} mode="default"/>
</div> </div>
</div> </div>
</div> </div>
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} {tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
// highlightBgColor={highlightBgColor}
activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
</div> </div>
</div>); </div>);
} }

View File

@ -30,7 +30,7 @@ function customCheckImageFn(src, alt, url) {
} }
export default function Render(props) { export default function Render(props) {
const { methods, data } = props; const { methods, data } = props;
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods; const { t, setMessage, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
const { oakId, oakFullpath, id, content, editor, origin, tocPosition = 'none', highlightBgColor, activeColor, scrollId, tocWidth, tocHeight, height = 600, html, oakLoading, oakExecuting, } = data; const { oakId, oakFullpath, id, content, editor, origin, tocPosition = 'none', highlightBgColor, activeColor, scrollId, tocWidth, tocHeight, height = 600, html, oakLoading, oakExecuting, } = data;
const [articleId, setArticleId] = useState(''); const [articleId, setArticleId] = useState('');
const [toc, setToc] = useState([]); const [toc, setToc] = useState([]);
@ -98,7 +98,12 @@ export default function Render(props) {
// 最后插入图片 // 最后插入图片
insertFn(url, extraFile.filename); insertFn(url, extraFile.filename);
} }
catch (err) { } catch (err) {
setMessage({
type: "error",
content: err.message,
});
}
}, },
}, },
uploadVideo: { uploadVideo: {
@ -130,7 +135,12 @@ export default function Render(props) {
insertFn(url, url + insertFn(url, url +
"?vframe/jpg/offset/0"); "?vframe/jpg/offset/0");
} }
catch (err) { } catch (err) {
setMessage({
type: "error",
content: err.message,
});
}
}, },
}, },
}, },

View File

@ -68,7 +68,7 @@ const i18ns = [
"name": "请输入文章标题", "name": "请输入文章标题",
"content": "请输入文章内容..." "content": "请输入文章内容..."
}, },
"chcek": { "check": {
"no name": "请填写文章标题!", "no name": "请填写文章标题!",
"no content": "请填写文章内容!" "no content": "请填写文章内容!"
} }

View File

@ -70,7 +70,7 @@ const i18ns = [
"name": "请输入文章标题", "name": "请输入文章标题",
"content": "请输入文章内容..." "content": "请输入文章内容..."
}, },
"chcek": { "check": {
"no name": "请填写文章标题!", "no name": "请填写文章标题!",
"no content": "请填写文章内容!" "no content": "请填写文章内容!"
} }

View File

@ -1,6 +1,5 @@
import { IDomEditor } from '@wangeditor/editor'; import { IDomEditor } from '@wangeditor/editor';
import { EntityDict } from '../../../oak-app-domain'; import { EntityDict } from '../../../oak-app-domain';
import { OakRowInconsistencyException } from 'oak-domain/lib/types';
export default OakComponent({ export default OakComponent({
entity: 'article', entity: 'article',
@ -25,7 +24,6 @@ export default OakComponent({
data: { data: {
editor: null as IDomEditor | null, editor: null as IDomEditor | null,
html: '', html: '',
contentTip: false,
}, },
properties: { properties: {
articleMenuId: '', articleMenuId: '',
@ -44,14 +42,14 @@ export default OakComponent({
next.editor.setHtml(next.content); next.editor.setHtml(next.content);
} }
}, },
oakId(prev, next) { // oakId(prev, next) {
if (prev.oakId !== next.oakId) { // if (prev.oakId !== next.oakId) {
const { editor } = this.state; // const { editor } = this.state;
if (editor == null) return; // if (editor == null) return;
editor.destroy(); // editor.destroy();
this.setEditor(null); // this.setEditor(null);
} // }
}, // },
name(prev, next) { name(prev, next) {
if (prev.name !== next.name) { if (prev.name !== next.name) {
window.document.title = next.name ?? ''; window.document.title = next.name ?? '';
@ -61,7 +59,7 @@ export default OakComponent({
lifetimes: { lifetimes: {
async ready() { async ready() {
const { oakId, articleMenuId } = this.props; const { oakId, articleMenuId } = this.props;
if (!oakId) { if (this.isCreation()) {
if (articleMenuId) { if (articleMenuId) {
this.update({ this.update({
articleMenuId, articleMenuId,
@ -98,11 +96,6 @@ export default OakComponent({
editor, editor,
}); });
}, },
clearContentTip() {
this.setState({
contentTip: false,
});
},
async check() { async check() {
if ( if (
this.state.name && this.state.name &&

View File

@ -7,7 +7,7 @@
"name": "请输入文章标题", "name": "请输入文章标题",
"content": "请输入文章内容..." "content": "请输入文章内容..."
}, },
"chcek": { "check": {
"no name": "请填写文章标题!", "no name": "请填写文章标题!",
"no content": "请填写文章内容!" "no content": "请填写文章内容!"
} }

View File

@ -56,7 +56,6 @@ export default function Render(
editor: any; editor: any;
content?: string; content?: string;
origin?: null | EntityDict['extraFile']['Schema']['origin']; origin?: null | EntityDict['extraFile']['Schema']['origin'];
contentTip: boolean;
articleMenuId: string; articleMenuId: string;
oakId: string; oakId: string;
tocPosition: 'none' | 'left' | 'right'; tocPosition: 'none' | 'left' | 'right';
@ -78,7 +77,6 @@ export default function Render(
extraFile: EntityDict['extraFile']['CreateOperationData'], extraFile: EntityDict['extraFile']['CreateOperationData'],
file: File file: File
) => Promise<string>; ) => Promise<string>;
clearContentTip: () => void;
gotoPreview: (content?: string, title?: string) => void; gotoPreview: (content?: string, title?: string) => void;
} }
> >
@ -86,13 +84,13 @@ export default function Render(
const { methods, data } = props; const { methods, data } = props;
const { const {
t, t,
setMessage,
setEditor, setEditor,
check, check,
uploadFile, uploadFile,
update, update,
setHtml, setHtml,
gotoPreview, gotoPreview,
clearContentTip,
} = methods; } = methods;
const { const {
oakId, oakId,
@ -194,14 +192,6 @@ export default function Render(
[Style.editorExternalContainer]: !!scrollId, [Style.editorExternalContainer]: !!scrollId,
})} })}
> >
{data.contentTip && (
<Alert
type="info"
message={t("tips.content")}
closable
onClose={() => clearContentTip()}
/>
)}
<div className={Style.titleContainer}> <div className={Style.titleContainer}>
<Input <Input
onChange={(e) => { onChange={(e) => {
@ -215,13 +205,12 @@ export default function Render(
placeholder={t('placeholder.name')} placeholder={t('placeholder.name')}
size="large" size="large"
maxLength={32} maxLength={32}
// suffix={`${(data.name || "").length}/32`}
showCount showCount
className={Style.titleInput} className={Style.titleInput}
/> />
</div> </div>
<div> <div>
<Editor {!!articleId && <Editor
defaultConfig={{ defaultConfig={{
autoFocus: true, autoFocus: true,
placeholder: t('placeholder.content'), placeholder: t('placeholder.content'),
@ -235,21 +224,9 @@ export default function Render(
) { ) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = const { name, size, type } = file;
file; const extension = name.substring(name.lastIndexOf(".") + 1);
const extension = const filename = name.substring(0, name.lastIndexOf("."));
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = { const extraFile = {
entity: "article", entity: "article",
entityId: oakId || articleId, entityId: oakId || articleId,
@ -267,17 +244,15 @@ export default function Render(
try { try {
// 自己实现上传,并得到图片 url alt href // 自己实现上传,并得到图片 url alt href
const url = const url = await uploadFile(extraFile, file);
await uploadFile(
extraFile,
file
);
// 最后插入图片 // 最后插入图片
insertFn( insertFn(url, extraFile.filename);
url, } catch (err: any) {
extraFile.filename setMessage({
); type: "error",
} catch (err) { } content: err.message,
})
}
}, },
}, },
uploadVideo: { uploadVideo: {
@ -288,21 +263,9 @@ export default function Render(
) { ) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = const { name, size, type } = file;
file; const extension = name.substring(name.lastIndexOf(".") + 1);
const extension = const filename = name.substring(0, name.lastIndexOf("."));
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = { const extraFile = {
entity: "article", entity: "article",
entityId: oakId || articleId, entityId: oakId || articleId,
@ -320,18 +283,15 @@ export default function Render(
try { try {
// 自己实现上传,并得到图片 url alt href // 自己实现上传,并得到图片 url alt href
const url = const url = await uploadFile(extraFile, file);
await uploadFile(
extraFile,
file
);
// 最后插入图片 // 最后插入图片
insertFn( insertFn(url, url + "?vframe/jpg/offset/0");
url, } catch (err: any) {
url + setMessage({
"?vframe/jpg/offset/0" type: "error",
); content: err.message,
} catch (err) { } })
}
}, },
}, },
}, },
@ -359,7 +319,7 @@ export default function Render(
minHeight: '100%', minHeight: '100%',
}} }}
mode="default" mode="default"
/> />}
</div> </div>
</div> </div>
</div> </div>
@ -369,7 +329,6 @@ export default function Render(
showToc={showToc} showToc={showToc}
tocPosition="right" tocPosition="right"
setShowToc={setShowToc} setShowToc={setShowToc}
// highlightBgColor={highlightBgColor}
activeColor={activeColor} activeColor={activeColor}
scrollId={containerId} scrollId={containerId}
tocWidth={tocWidth} tocWidth={tocWidth}

View File

@ -84,6 +84,7 @@ export default function Render(
const { methods, data } = props; const { methods, data } = props;
const { const {
t, t,
setMessage,
setEditor, setEditor,
check, check,
uploadFile, uploadFile,
@ -198,21 +199,9 @@ export default function Render(
) { ) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = const { name, size, type } = file;
file; const extension = name.substring(name.lastIndexOf(".") + 1);
const extension = const filename = name.substring(0, name.lastIndexOf("."));
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = { const extraFile = {
entity: "article", entity: "article",
entityId: oakId || articleId, entityId: oakId || articleId,
@ -240,7 +229,12 @@ export default function Render(
url, url,
extraFile.filename extraFile.filename
); );
} catch (err) { } } catch (err: any) {
setMessage({
type: "error",
content: err.message,
})
}
}, },
}, },
uploadVideo: { uploadVideo: {
@ -251,21 +245,9 @@ export default function Render(
) { ) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = const { name, size, type } = file;
file; const extension = name.substring(name.lastIndexOf(".") + 1);
const extension = const filename = name.substring(0, name.lastIndexOf("."));
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = { const extraFile = {
entity: "article", entity: "article",
entityId: oakId || articleId, entityId: oakId || articleId,
@ -294,7 +276,12 @@ export default function Render(
url + url +
"?vframe/jpg/offset/0" "?vframe/jpg/offset/0"
); );
} catch (err) { } } catch (err: any) {
setMessage({
type: "error",
content: err.message,
})
}
}, },
}, },
}, },

View File

@ -70,7 +70,7 @@ const i18ns: I18n[] = [
"name": "请输入文章标题", "name": "请输入文章标题",
"content": "请输入文章内容..." "content": "请输入文章内容..."
}, },
"chcek": { "check": {
"no name": "请填写文章标题!", "no name": "请填写文章标题!",
"no content": "请填写文章内容!" "no content": "请填写文章内容!"
} }