对article upset页面样式优化

This commit is contained in:
Wang Kejun 2024-11-30 20:55:05 +08:00
parent 0007146658
commit 37d87844f7
73 changed files with 896 additions and 952 deletions

View File

@ -104,7 +104,7 @@ export async function createSession(params, context) {
origin: 'wechat',
type: 'image',
tag1: 'image',
objectId: await generateNewIdAsync(),
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@ -129,7 +129,7 @@ export async function createSession(params, context) {
origin: 'wechat',
type: 'video',
tag1: 'video',
objectId: await generateNewIdAsync(),
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@ -151,7 +151,7 @@ export async function createSession(params, context) {
origin: 'wechat',
type: 'audio',
tag1: 'audio',
objectId: await generateNewIdAsync(),
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,

View File

@ -2325,8 +2325,8 @@ export async function refreshToken(params, context) {
// 只有server模式去刷新token
// 'development' | 'production' | 'staging'
const intervals = {
development: 7200 * 1000,
staging: 600 * 1000,
development: 7200 * 1000, // 2小时
staging: 600 * 1000, // 十分钟
production: 600 * 1000, // 十分钟
};
const application = context.getApplication();

View File

@ -161,7 +161,7 @@ export async function createWechatQrCode(options, context) {
permanent,
url,
expired: false,
expiresAt: Date.now() + 2592000 * 1000,
expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大由上层关联对象来主动过期by Xc, 20230131)
props,
};
// 直接创建

View File

@ -1,2 +1,2 @@
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
export default checkers;

View File

@ -1,11 +1,12 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", false, {
tocWidth: number;
tocClosed: boolean;
tocFixed: boolean;
tocPosition: "left" | "right" | "none";
tocPosition: "none" | "left" | "right";
highlightBgColor: string;
headerTop: number;
className: string;
scrollId: string;
tocWidth: number | "auto" | undefined;
tocHeight: number | "auto" | undefined;
}>) => React.ReactElement;
export default _default;

View File

@ -19,14 +19,15 @@ export default OakComponent({
};
},
properties: {
tocWidth: 228,
tocClosed: false,
tocFixed: true,
tocPosition: 'none',
highlightBgColor: 'none',
headerTop: 0,
headerTop: 0, //页面中吸顶部分高度
className: '',
scrollId: '', // 滚动条所在容器id不传默认body
tocWidth: undefined,
tocHeight: undefined,
},
lifetimes: {},
methods: {}

View File

@ -2,7 +2,7 @@ import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'article', false, {
title?: string;
name?: string;
content?: string;
tocPosition: 'none' | 'left' | 'right';
highlightBgColor: string;
@ -10,6 +10,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
className?: string;
tocFixed: boolean;
tocClosed: boolean;
tocWidth: 228;
scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
}, {}>): React.JSX.Element;

View File

@ -1,12 +1,11 @@
import React, { useState, useEffect } from 'react';
import { Col, Row } from 'antd';
import { Editor } from "@wangeditor/editor-for-react";
import { SlateNode } from "@wangeditor/editor";
import classNames from 'classnames';
import { TocView } from '../toc/tocView';
import Style from './web.module.less';
import Styles from './web.module.less';
export default function Render(props) {
const { className, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed = false, tocWidth = 228, scrollId } = props.data;
const { className, name, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight } = props.data;
const editorConfig = {
readOnly: true,
autoFocus: true,
@ -34,21 +33,23 @@ export default function Render(props) {
setShowToc(true);
}
}, [tocPosition]);
return (<div className={classNames(Style.container, className)}>
<Row gutter={[16, 0]}>
{tocPosition === 'left' ? (<Col flex={`${tocWidth}px`}>
<TocView toc={toc} showToc={showToc} tocPosition='left' setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId}/>
</Col>) : null}
useEffect(() => {
if (name) {
window.document.title = name;
}
}, [name]);
return (<div className={classNames(Styles.container, className)}>
<div className={Styles.contentContainer}>
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
<Col flex="auto">
<div className={Style.content}>
<div className={Style.editorContainer}>
<div className={Styles.content}>
<div className={Styles.editorContainer}>
<div style={{ width: "100%" }}>
<Editor defaultConfig={editorConfig} value={html} mode="default" style={{
width: '100%'
}} onCreated={setEditor} onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header');
const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map((header) => {
const text = SlateNode.string(header);
const { id, type } = header;
@ -63,10 +64,7 @@ export default function Render(props) {
</div>
</div>
</div>
</Col>
{tocPosition === 'right' ? (<Col flex={`${tocWidth}px`}>
<TocView toc={toc} showToc={showToc} tocPosition='right' setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId}/>
</Col>) : null}
</Row>
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
</div>
</div>);
}

View File

@ -2,6 +2,12 @@
padding: 16px;
}
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content {
display: flex;
@ -14,7 +20,7 @@
max-width: 794px;
display: flex;
flex-direction: column;
background: var(--oak-bg-color-container);
background: #fff;
padding: 20px 50px 50px 50px;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
}

View File

@ -1,10 +1,12 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, {
tocClosed: boolean;
tocFixed: boolean;
tocPosition: "left" | "right" | "none";
tocPosition: "none" | "left" | "right";
highlightBgColor: string;
headerTop: number;
className: string;
scrollId: string;
tocWidth: number | "auto" | undefined;
tocHeight: number | "auto" | undefined;
}>) => React.ReactElement;
export default _default;

View File

@ -6,7 +6,6 @@ export default OakComponent({
data: {
content: '',
title: '',
author: '',
},
lifetimes: {
async attached() {
@ -18,7 +17,6 @@ export default OakComponent({
this.setState({
content: article?.content,
title: article?.title,
author: article?.author,
});
},
detached() {
@ -30,9 +28,11 @@ export default OakComponent({
tocFixed: true,
tocPosition: 'none',
highlightBgColor: 'none',
headerTop: 0,
headerTop: 0, //页面中吸顶部分高度
className: '',
scrollId: '', // 滚动条所在容器id不传默认body
tocWidth: undefined,
tocHeight: undefined,
},
methods: {},
});

View File

@ -10,4 +10,6 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
tocFixed: boolean;
tocClosed: boolean;
scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
}, {}>): React.JSX.Element;

View File

@ -1,12 +1,11 @@
import React, { useState, useEffect } from 'react';
import { Editor } from '@wangeditor/editor-for-react';
import { SlateNode } from '@wangeditor/editor';
import { Col, Row } from 'antd';
import classNames from 'classnames';
import { TocView } from '../toc/tocView';
import Styles from './web.module.less';
export default function Render(props) {
const { className, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId } = props.data;
const { className, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight } = props.data;
const editorConfig = {
readOnly: true,
autoFocus: true,
@ -35,20 +34,17 @@ export default function Render(props) {
}
}, [tocPosition]);
return (<div className={classNames(Styles.container, className)}>
<Row gutter={[16, 0]}>
{tocPosition === 'left' && (<Col flex="228px">
<TocView toc={toc} showToc={showToc} tocPosition='left' setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId}/>
</Col>)}
<div className={Styles.contentContainer}>
{tocPosition === "left" && (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight}/>)}
<Col flex="auto">
<div className={Styles.content}>
<div className={Styles.editorContainer}>
<div style={{ width: "100%" }}>
<Editor defaultConfig={editorConfig} value={html} mode="default" style={{
width: '100%'
width: "100%",
}} onCreated={setEditor} onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header');
const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map((header) => {
const text = SlateNode.string(header);
const { id, type } = header;
@ -63,10 +59,7 @@ export default function Render(props) {
</div>
</div>
</div>
</Col>
{tocPosition === 'right' && (<Col flex="228px">
<TocView toc={toc} showToc={showToc} tocPosition='right' setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId}/>
</Col>)}
</Row>
{tocPosition === "right" && (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} fixed={tocFixed} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight}/>)}
</div>
</div>);
}

View File

@ -1,8 +1,13 @@
.container {
// min-height: 100vh;
padding: 16px;
}
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content {
display: flex;

View File

@ -14,5 +14,6 @@ export declare function TocView(props: {
fixed?: boolean;
scrollId?: string;
closed?: boolean;
tocWidth?: number;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
}): import("react").JSX.Element;

View File

@ -4,7 +4,7 @@ import { CaretDownOutlined, CloseOutlined, MenuOutlined } from "@ant-design/icon
import classNames from "classnames";
import Style from './tocView.module.less';
export function TocView(props) {
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, fixed = false, closed = false, tocWidth = 228 } = props;
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, fixed = false, closed = false, tocWidth, tocHeight } = props;
useEffect(() => {
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
}, [highlightBgColor]);
@ -84,7 +84,7 @@ export function TocView(props) {
};
return (<div className={classNames(Style.tocContainer, {
[Style.fixed]: fixed
})}>
})} style={Object.assign({}, tocWidth ? { width: tocWidth } : {}, tocHeight ? { height: tocHeight } : {})}>
{showToc ? (<>
<div className={Style.catalogTitle}>
<div style={{ color: '#A5A5A5' }}>大纲</div>

View File

@ -88,9 +88,10 @@ export default function Render(props) {
}}/>
</div> : <>
<Button type="text" icon={<EditOutlined />} size="small" onClick={(e) => {
e.stopPropagation();
setName(ele.name);
setNameEditing(ele.id);
}} style={{ marginRight: 4 }}/>
e.stopPropagation();
}}/>
<div className={Styles.name}>
<div style={{ marginLeft: 4, overflow: 'hidden', width: '150px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ele?.name}</div>
</div>

View File

@ -3,7 +3,7 @@
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
height: 50px;
justify-content: space-between;
cursor: pointer;

View File

@ -2,10 +2,11 @@ import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "article", false, {
articleMenuId: string;
changeIsEdit: () => void;
tocPosition: "left" | "right" | "none";
tocPosition: "none" | "left" | "right";
highlightBgColor: string;
onArticlePreview: (content?: string, title?: string) => void;
origin: string;
scrollId: string;
height: number | "auto";
}>) => React.ReactElement;
export default _default;

View File

@ -25,11 +25,12 @@ export default OakComponent({
properties: {
articleMenuId: '',
changeIsEdit: () => undefined,
tocPosition: 'none',
highlightBgColor: 'none',
onArticlePreview: (content, title) => undefined,
origin: 'qiniu',
tocPosition: 'none', //目录显示位置none为不显示目录
highlightBgColor: 'none', //点击目录时标题高亮背景色none为不显示高亮背景色
onArticlePreview: (content, title) => undefined, //预览文章
origin: 'qiniu', // 默认为七牛云
scrollId: '', // 滚动条所在容器id不传默认页面编辑器容器id
height: 600,
},
listeners: {
'editor,content'(prev, next) {

View File

@ -7,7 +7,6 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
name: string;
editor: any;
content?: string;
html?: string;
origin?: string;
contentTip: boolean;
articleMenuId: string;
@ -15,6 +14,9 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
tocPosition: 'none' | 'left' | 'right';
highlightBgColor: string;
scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
height?: number | 'auto';
}, {
setHtml: (content: string) => void;
setEditor: (editor: any) => void;

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { Alert, Button, Row, Col, Space, Input, Tooltip, } from "antd";
import { Alert, Button, Space, Input, } from "antd";
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import { SlateNode } from "@wangeditor/editor";
@ -7,7 +7,8 @@ import { generateNewId } from "oak-domain/lib/utils/uuid";
import classNames from "classnames";
import Prompt from "../../../components/common/prompt";
import Style from "./web.module.less";
import { CloseOutlined, EyeOutlined, MenuOutlined, CaretDownOutlined } from "@ant-design/icons";
import { TocView } from '../toc/tocView';
import { EyeOutlined, } from "@ant-design/icons";
// 工具栏配置
const toolbarConfig = {
excludeKeys: ["fullScreen"],
@ -27,105 +28,14 @@ function customCheckImageFn(src, alt, url) {
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
// 3. 返回 undefined即没有任何返回说明检查未通过编辑器会阻止插入。但不会提示任何信息
}
function TocView(props) {
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, scrollId } = props;
useEffect(() => {
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
}, [highlightBgColor]);
const generateTocList = (items, currentLevel = 1, parentId = null) => {
//递归生成嵌套列表
const result = [];
let lastId = parentId;
while (items.length > 0) {
const item = items[0];
//有无子级标题,有则显示展开图标
const hasChildren = items.length > 1 && items[1].level > item.level;
if (item.level > currentLevel) {
// 递归生成子列表
const sublist = generateTocList(items, item.level, item.id);
result.push(<li key={item.id} id={`ul-${lastId}`}>
<ul style={{ listStyleType: 'none', paddingInlineStart: '6px' }}>
{sublist}
</ul>
</li>);
}
else if (item.level < currentLevel) {
// 结束当前层级
break;
}
else {
// 添加当前层级的 <li>
result.push(<li className={Style.listItem} key={item.id} id={`li-${item.id}`} style={{ paddingLeft: `${(item.level - 1) * 6}px`, }}>
<CaretDownOutlined id={`icon-${item.id}`} className={Style.icon} style={{ visibility: hasChildren && item.level < 5 ? 'visible' : 'hidden', color: '#A5A5A5' }} onClick={() => {
const iconElem = document.getElementById(`icon-${item.id}`);
const ulElem = document.getElementById(`ul-${item.id}`);
const isFolded = iconElem?.className.includes('iconFold') || ulElem?.className.includes('fold');
if (isFolded) {
iconElem?.classList.remove(Style.iconFold);
ulElem?.classList.remove(Style.fold);
}
else {
iconElem?.classList.add(Style.iconFold);
ulElem?.classList.add(Style.fold);
}
}}/>
<div style={{ fontSize: '1em', fontWeight: item.level === 1 ? 'bold' : 'normal' }} className={Style.tocItem} onClick={(event) => {
// editor.scrollToElem(item.id);
//编辑器滚动到对应元素
const elem = document.getElementById(item.id);
const elemTop = elem?.getBoundingClientRect().top;
const scrollContainer = document.getElementById(scrollId || 'article-upsert-editorContainer');
const containerTop = scrollContainer?.getBoundingClientRect().top;
scrollContainer?.scrollBy({
top: elemTop - containerTop,
behavior: 'smooth',
});
//添加背景色
elem?.classList.add(Style.highlight);
//移除背景色类名
setTimeout(function () {
elem?.classList.remove(Style.highlight);
}, 1000);
event.preventDefault();
event.stopPropagation();
}}>
{item.text}
</div>
</li>);
items.shift(); // 移除已处理的项
lastId = item.id;
}
}
return result;
};
return (<div className={classNames(Style.tocContainer, {
// [Style.fixed]: fixed
})}>
{showToc ? (<>
<div className={Style.catalogTitle}>
<div style={{ color: '#A5A5A5' }}>大纲</div>
<CloseOutlined style={{ color: '#A5A5A5' }} onClick={() => setShowToc(false)}/>
</div>
{(toc && toc.length > 0) ? (<ul style={{ listStyleType: 'none', paddingInlineStart: '0px' }}>{generateTocList([...toc])}</ul>) : (<div style={{ display: 'flex', alignItems: 'center', color: '#B1B1B1', height: '200px' }}>
<div>
对文档内容应用标题样式即可生成大纲
</div>
</div>)}
</>) : (<div className={classNames(Style.tocButton, { [Style.tocButtonRight]: tocPosition === 'right' })}>
<Tooltip title="显示大纲" placement={tocPosition === 'right' ? 'left' : 'right'}>
<Button size="small" icon={<MenuOutlined />} onClick={() => setShowToc(true)}/>
</Tooltip>
</div>)}
</div>);
}
export default function Render(props) {
const { methods, data } = props;
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
const { id, content, editor, origin = 'qiniu', oakFullpath, html, tocPosition = 'none', highlightBgColor = 'none', scrollId } = data;
const { oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600 } = data;
const [articleId, setArticleId] = useState('');
const [toc, setToc] = useState([]);
const [showToc, setShowToc] = useState(false);
const containerId = scrollId || 'article-upsert-editorContainer';
useEffect(() => {
if (id) {
setArticleId(id);
@ -137,28 +47,25 @@ export default function Render(props) {
}
}, [tocPosition]);
return (<div className={Style.container}>
<Prompt when={!id || data.oakDirty} message={'您确认离开页面吗?'}/>
<div style={{ width: 'calc(100% - 16px)', }}>
<Prompt when={!id || data.oakDirty} message={"您确认离开页面吗?"}/>
<div style={{ width: "calc(100% - 16px)" }}>
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default"/>
</div>
<Row gutter={[16, 0]}>
{tocPosition === 'left' ? (<Col flex="228px">
<TocView toc={toc} showToc={showToc} tocPosition='left' setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={scrollId}/>
</Col>) : null}
<div className={Style.contentContainer}>
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight || height}/>) : null}
<Col flex="auto">
<div className={Style.content}>
<div id="article-upsert-editorContainer" className={classNames(Style.editorContainer, {
[Style.editorExternalContainer]: !!scrollId
<div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
<div id={containerId} className={classNames(Style.editorContainer, {
[Style.editorExternalContainer]: !!scrollId,
})}>
{data.contentTip && (<Alert type="info" message={t('tips.content')} closable onClose={() => clearContentTip()}/>)}
{data.contentTip && (<Alert type="info" message={t("tips.content")} closable onClose={() => clearContentTip()}/>)}
<div className={Style.titleContainer}>
<Input onChange={(e) => update({ name: e.target.value })} value={data.name} placeholder={'请输入文章标题'} size="large" maxLength={32} suffix={`${[...(data.name || '')].length}/32`} className={Style.titleInput}/>
<Input onChange={(e) => update({ name: e.target.value })} value={data.name} placeholder={"请输入文章标题"} size="large" maxLength={32} suffix={`${(data.name || "").length}/32`} className={Style.titleInput}/>
</div>
<div className={Style.editorContent}>
<Editor defaultConfig={{
autoFocus: true,
placeholder: '请输入文章内容...',
placeholder: "请输入文章内容...",
MENU_CONF: {
checkImage: customCheckImageFn,
uploadImage: {
@ -167,21 +74,21 @@ export default function Render(props) {
// TS 语法
// file 即选中的文件
const { name, size, type } = file;
const extension = name.substring(name.lastIndexOf('.') + 1);
const filename = name.substring(0, name.lastIndexOf('.'));
const extension = name.substring(name.lastIndexOf(".") + 1);
const filename = name.substring(0, name.lastIndexOf("."));
const extraFile = {
entity: 'article',
entity: "article",
entityId: articleId,
origin: origin,
type: 'image',
tag1: 'source',
type: "image",
tag1: "source",
objectId: generateNewId(),
filename,
size,
extension,
bucket: '',
bucket: "",
id: generateNewId(),
fileType: type
fileType: type,
};
try {
// 自己实现上传,并得到图片 url alt href
@ -198,27 +105,28 @@ export default function Render(props) {
// TS 语法
// file 即选中的文件
const { name, size, type } = file;
const extension = name.substring(name.lastIndexOf('.') + 1);
const filename = name.substring(0, name.lastIndexOf('.'));
const extension = name.substring(name.lastIndexOf(".") + 1);
const filename = name.substring(0, name.lastIndexOf("."));
const extraFile = {
entity: 'article',
entity: "article",
entityId: articleId,
origin: origin,
type: 'video',
tag1: 'source',
type: "video",
tag1: "source",
objectId: generateNewId(),
filename,
size,
extension,
bucket: '',
bucket: "",
id: generateNewId(),
fileType: type
fileType: type,
};
try {
// 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile, file);
// 最后插入图片
insertFn(url, url + '?vframe/jpg/offset/0');
insertFn(url, url +
"?vframe/jpg/offset/0");
}
catch (err) { }
},
@ -226,7 +134,7 @@ export default function Render(props) {
},
}} onCreated={setEditor} onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header');
const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map((header) => {
const text = SlateNode.string(header);
const { id, type } = header;
@ -241,11 +149,8 @@ export default function Render(props) {
minHeight: 440,
}} mode="default"/>
</div>
</div>
<div className={Style.footer}>
<Row align="middle">
<Col flex="none">
<Space>
<Button disabled={!data.oakDirty ||
data.oakExecuting} type="primary" onClick={() => {
@ -255,19 +160,13 @@ export default function Render(props) {
</Button>
<Button onClick={() => {
gotoPreview(content, data.name);
}}>
<EyeOutlined />
}} icon={<EyeOutlined />}>
预览
</Button>
</Space>
</Col>
</Row>
</div>
</div>
</Col>
{tocPosition === 'right' ? (<Col flex="228px">
<TocView toc={toc} showToc={showToc} tocPosition='right' setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={scrollId}/>
</Col>) : null}
</Row>
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
</div>
</div>);
}

View File

@ -4,10 +4,21 @@
box-sizing: border-box;
}
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
}
.editorContainer {
width: 100%;
margin: 30px auto 30px auto;
@ -45,10 +56,8 @@
}
.footer {
// position: absolute;
bottom: 0;
height: 50px;
margin-top: 10px;
}
.contentNumber {
@ -60,7 +69,7 @@
}
.tocContainer {
padding-top: 28px;
// padding-top: 28px;
position: relative;
box-sizing: border-box;
overflow-y: auto;
@ -101,7 +110,7 @@
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #DDD;
padding: 6px 6px 10px 6px;
padding: 34px 6px 10px 6px;
box-sizing: border-box;
position: sticky;
top: 0;

View File

@ -15,7 +15,6 @@ export default function Render(props) {
}
}, [editArticleId]);
const [modal, contextHolder] = Modal.useModal();
const [name, setName] = useState('');
const [nameEditing, setNameEditing] = useState(false);
const [showSub, setShowSub] = useState(false);
const [newBreadcrumbItems, setNewBreadcrumbItems] = useState([]);
@ -51,9 +50,7 @@ export default function Render(props) {
oakPath={`$articleMenu-parent-${row.id}`} onGrandChildEditArticleChange={onChildEditArticleChange} show={show} getBreadcrumbItems={getBreadcrumbItemsByParent} breadcrumbItems={newBreadcrumbItems} drawerOpen={drawerOpen} changeDrawerOpen={changeDrawerOpen} selectedArticleId={selectedArticleId} openArray={openArray ? openArray : undefined} getTopInfo={getTopInfo} articleId={articleId} currentArticle={currentArticle} setCurrentArticle={setCurrentArticle}/>);
if (!row.parentId && articleMenuId) {
return (<>
<div>
{Sub}
</div>
{contextHolder}
</>);
}
@ -145,39 +142,23 @@ export default function Render(props) {
return (<>
<div className={Styles.container}>
<div className={Styles.ne}>
{
// nameEditing ? <div className={Styles.name}>
// <Input
// autoFocus
// value={ name || row?.name}
// onChange={(evt) => setName(evt.target.value)}
// onPressEnter={async () => {
// if (name && name !== row?.name) {
// await onUpdateName(name);
// }
// setNameEditing(false);
// }}
// onBlur={async () => {
// if (name && name !== row?.name) {
// await onUpdateName(name);
// }
// setNameEditing(false);
// }}
// />
// </div> :
<>
<Button type="text" icon={<EditOutlined />} size="small" onClick={() => {
setNameEditing(true);
const modalInstance = modal.confirm({
title: '编辑分类',
cancelText: '取消',
okText: '提交',
title: "编辑分类",
cancelText: "取消",
okText: "提交",
content: (<div>
<Form.Item label="分类名称">
<Input ref={menuNameRef} defaultValue={row.name} onChange={(val) => update({ name: val.target.value })}/>
<Input ref={menuNameRef} defaultValue={row.name} onChange={(val) => update({
name: val.target
.value,
})}/>
</Form.Item>
<Form.Item label="LOGO" help={<div>
<span>请上传LOGO高清图片</span>
<span>
请上传LOGO高清图片
</span>
<span>
108*108像素仅支持PNGJPG格式大小不超过300KB
</span>
@ -197,11 +178,12 @@ export default function Render(props) {
// });
// }
// }
footer: <Space>
footer: (<Space>
<ExtraFileCommit entity={oakEntity} oakPath={oakFullpath} afterCommit={() => {
modalInstance.destroy();
}} beforeCommit={() => {
if (menuNameRef.current.input.value) {
if (menuNameRef.current
.input.value) {
return true;
}
else {
@ -211,22 +193,31 @@ export default function Render(props) {
<Button onClick={() => modalInstance.destroy()}>
取消
</Button>
</Space>
</Space>),
});
}} style={{ marginRight: 4 }}/>
}}/>
<div className={Styles.name}>
{logo ? (<Image height={26} width={26} src={logo} preview={false}/>) : null}
<div style={{ marginLeft: 4, overflow: 'hidden', width: '100px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{row?.name}</div>
<div style={{
marginLeft: 4,
overflow: "hidden",
width: "100px",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}>
{row?.name}
</div>
</>}
</div>
<Divider type="vertical" style={{ height: '100%', marginTop: 4, marginBottom: 4 }}/>
</div>
<Divider type="vertical" style={{
height: "100%",
marginTop: 4,
marginBottom: 4,
}}/>
<div className={Styles.control}>
{!row.parentId && <Button type="text" onClick={() => {
{!row.parentId && (<Button type="text" size="small" onClick={() => {
gotoDoc(row?.id);
}} icon={<EyeOutlined />}>
</Button>}
}} icon={<EyeOutlined />}></Button>)}
<Dropdown menu={{ items }} placement="bottomRight" arrow>
<Button type="text" icon={<PlusOutlined />} size="small"/>
@ -234,23 +225,21 @@ export default function Render(props) {
<Button type="text" icon={<MinusOutlined />} size="small" onClick={() => {
if (!allowRemove) {
modal.error({
title: '无法删除',
content: hasSubArticles ? '请先删除目录下的文章' : '请先删除目录下的子目录',
okText: '确认'
title: "无法删除",
content: hasSubArticles
? "请先删除目录下的文章"
: "请先删除目录下的子目录",
okText: "确认",
});
}
else {
onRemove();
}
}}/>
{(hasSubArticles || hasSubMenus) ? (showSub ?
<Button type="text" icon={<UpOutlined />} size="small" onClick={() => setShowSub(false)}/> :
<Button type="text" icon={<DownOutlined />} size="small" onClick={() => setShowSub(true)}/>) : <div className={Styles.ph}/>}
{hasSubArticles || hasSubMenus ? (showSub ? (<Button type="text" icon={<UpOutlined />} size="small" onClick={() => setShowSub(false)}/>) : (<Button type="text" icon={<DownOutlined />} size="small" onClick={() => setShowSub(true)}/>)) : (<div className={Styles.ph}/>)}
</div>
</div>
{showSub && (<div className={Styles.sub}>
{Sub}
</div>)}
{showSub ? Sub : null}
{contextHolder}
</>);
}

View File

@ -3,7 +3,7 @@
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
height: 50px;
justify-content: space-between;
.ne {
@ -11,7 +11,7 @@
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
margin-left: 10px;
align-items: center;
justify-content: space-between;
@ -43,6 +43,8 @@
}
}
.sub {
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.02);
padding-left: 18px;
}
@ -78,7 +80,6 @@
flex-direction: row;
align-items: center;
justify-content: space-evenly;
// padding: 10px;
.ph {
width: 24px;

View File

@ -4,14 +4,14 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
show: "edit" | "doc" | "preview";
articleMenuId: string;
articleId: string;
tocPosition: "left" | "right" | "none";
tocPosition: "none" | "left" | "right";
highlightBgColor: string;
onMenuView: () => void;
onMenuViewById: (articleMenuId: string) => void;
onArticleView: (oakId: string) => void;
onArticleView: (articleId: string) => void;
onArticlePreview: (content?: string, title?: string) => void;
onArticleEdit: (oakId: string) => void;
setCopyArticleUrl: (id: string) => string;
onArticleEdit: (articleId: string) => void;
setCopyArticleUrl: (articleId: string) => string;
origin: string;
scrollId: string;
}>) => React.ReactElement;

View File

@ -14,18 +14,18 @@ export default OakComponent({
properties: {
entity: '',
entityId: '',
show: 'edit',
articleMenuId: '',
articleId: '',
tocPosition: 'none',
highlightBgColor: 'none',
onMenuView: () => undefined,
onMenuViewById: (articleMenuId) => undefined,
onArticleView: (oakId) => undefined,
onArticlePreview: (content, title) => undefined,
onArticleEdit: (oakId) => undefined,
setCopyArticleUrl: (id) => '',
origin: 'qiniu',
show: 'edit', // edit为编辑doc为查看preview为预览
articleMenuId: '', // 菜单id
articleId: '', //文章id
tocPosition: 'none', //文章目录显示位置none为不显示目录
highlightBgColor: 'none', //点击文章目录时标题高亮背景色none为不显示高亮背景色
onMenuView: () => undefined, //查看全部菜单
onMenuViewById: (articleMenuId) => undefined, //查看指定id菜单
onArticleView: (articleId) => undefined, //查看文章
onArticlePreview: (content, title) => undefined, //预览文章
onArticleEdit: (articleId) => undefined, //编辑文章
setCopyArticleUrl: (articleId) => '',
origin: 'qiniu', // cos origin默认七牛云
scrollId: '', // 滚动条所在容器id不传默认页面编辑器容器id
},
});

View File

@ -7,7 +7,7 @@
overflow-x: hidden;
.menu {
min-width: 320px;
width: 320px;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
@ -17,13 +17,14 @@
flex-direction: row;
justify-content: space-between;
align-items: center;
min-width: 320px;
padding: 8px 10px;
width: 320px;
padding: 0px 10px;
position: sticky;
z-index: 99;
background: #ffffff;
top: 0;
border-bottom: 1px solid #f0f0f0;
height: 50px;
.menuTitle {
font-size: 16px;
@ -129,7 +130,6 @@
padding: 20px 15px;
min-width: 320px;
height: 100%;
// max-height: 800px;
overflow-y: scroll;
overflow-x: hidden;
}

View File

@ -4,8 +4,8 @@ import { ReactComponentProps } from 'oak-frontend-base';
import { ECode } from '../../../types/ErrorPage';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
code: ECode;
title?: string | undefined;
desc?: string | undefined;
title?: string;
desc?: string;
children?: React.ReactNode;
icon?: React.ReactNode;
}>) => React.ReactElement;

View File

@ -1,4 +1,3 @@
/// <reference types="react" />
import { EntityDict } from '../../../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
@ -9,31 +8,14 @@ type AfterCommit = (() => void) | undefined;
type BeforeCommit = (() => boolean | undefined | Promise<boolean | undefined>) | undefined;
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
action?: string | undefined;
action?: string;
size?: ButtonProps['size'] | AmButtonProps['size'];
block?: boolean | undefined;
block?: boolean;
type?: ButtonProps['type'] | AmButtonProps['type'];
executeText?: string | undefined;
buttonProps?: (ButtonProps & {
color?: "default" | "success" | "warning" | "primary" | "danger" | undefined;
fill?: "none" | "solid" | "outline" | undefined;
size?: "small" | "middle" | "large" | "mini" | undefined;
block?: boolean | undefined;
loading?: boolean | "auto" | undefined;
loadingText?: string | undefined;
loadingIcon?: import("react").ReactNode;
disabled?: boolean | undefined;
onClick?: ((event: import("react").MouseEvent<HTMLButtonElement, MouseEvent>) => unknown) | undefined;
type?: "button" | "submit" | "reset" | undefined;
shape?: "default" | "rounded" | "rectangular" | undefined;
children?: import("react").ReactNode;
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseDown" | "onMouseUp" | "onTouchEnd" | "onTouchStart"> & {
className?: string | undefined;
style?: (import("react").CSSProperties & Partial<Record<"--text-color" | "--background-color" | "--border-radius" | "--border-width" | "--border-style" | "--border-color", string>>) | undefined;
tabIndex?: number | undefined;
} & import("react").AriaAttributes) | undefined;
executeText?: string;
buttonProps?: ButtonProps & AmButtonProps;
afterCommit?: AfterCommit;
beforeCommit?: BeforeCommit;
messageProps?: boolean | MessageProps | undefined;
messageProps?: MessageProps | boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -55,9 +55,9 @@ export default OakComponent({
data: {
isModalOpen: false,
isModalOpen1: false,
renderImgs: [],
renderImgs: [], // 读取的原文图片在modal使用
methodsType: '',
bridgeUrl: '',
bridgeUrl: '', // 通过桥接方式获得的url
selectedId: -1,
},
properties: {

View File

@ -11,6 +11,6 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
tag2: string;
entity: keyof ED2;
entityId: string;
style?: string | undefined;
style?: string;
}>) => React.ReactElement;
export default _default;

View File

@ -39,6 +39,6 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
entityId: string;
theme: Theme;
children?: React.ReactNode;
style?: string | undefined;
style?: string;
}>) => React.ReactElement;
export default _default;

View File

@ -21,8 +21,8 @@ export default OakComponent({
},
properties: {
disabled: '',
url: '',
callback: undefined,
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
setLoginMode: (value) => undefined,
digit: 4, //验证码位数
},

View File

@ -17,15 +17,15 @@ export default OakComponent({
allowPassword: false,
allowWechatMp: false,
setLoginModeMp(value) { this.setLoginMode(value); },
smsDigit: 4,
smsDigit: 4, //短信验证码位数
emailDigit: 4, //邮箱验证码位数
},
properties: {
onlyCaptcha: false,
onlyPassword: false,
disabled: '',
redirectUri: '',
url: '',
redirectUri: '', // 微信登录后的redirectUri要指向wechatUser/login去处理
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
},
formData({ features, props }) {

View File

@ -18,12 +18,12 @@ export default OakComponent({
},
properties: {
disabled: '',
redirectUri: '',
url: '',
callback: undefined,
redirectUri: '', // 微信登录后的redirectUri要指向wechatUser/login去处理
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
allowSms: false,
allowEmail: false,
allowWechatMp: false,
allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
},
lifetimes: {},

View File

@ -21,10 +21,10 @@ export default OakComponent({
},
properties: {
disabled: '',
url: '',
callback: undefined,
allowPassword: false,
allowWechatMp: false,
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
allowPassword: false, //小程序切换密码登录
allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
digit: 4 //验证码位数,
},

View File

@ -1,7 +1,7 @@
import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "userEntityGrant", false, {
picker: ((props: {
disabled?: boolean | undefined;
disabled?: boolean;
entity: keyof EntityDict;
entityFilter: object;
relationIds: string[];
@ -9,8 +9,8 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
ruleOnRow: EntityDict['userEntityGrant']['OpSchema']['ruleOnRow'];
onPickRelations: (ids: string[]) => void;
onPickRows: (ids: string[]) => void;
pickedRowIds?: string[] | undefined;
pickedRelationIds?: string[] | undefined;
pickedRowIds?: string[];
pickedRelationIds?: string[];
oakPath: string;
}) => React.ReactElement) | undefined;
hideInfo: boolean;

View File

@ -4,7 +4,7 @@ import { ReactComponentProps } from 'oak-frontend-base';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"];
redirectToAfterConfirm: ED2['userEntityGrant']['Schema']['redirectTo'];
qrCodeType: string;
showTitle: true;
showBack: false;

View File

@ -5,8 +5,8 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
entity: keyof ED2;
entityId: string;
relations: EntityDict['relation']['OpSchema'][];
passwordRequire?: boolean | undefined;
allowUpdateName?: boolean | undefined;
allowUpdateNickname?: boolean | undefined;
passwordRequire?: boolean;
allowUpdateName?: boolean;
allowUpdateNickname?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -4,7 +4,7 @@ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
entityId: string;
allowUpdateName?: boolean | undefined;
allowUpdateNickname?: boolean | undefined;
allowUpdateName?: boolean;
allowUpdateNickname?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -5,7 +5,7 @@ import { ReactComponentProps } from 'oak-frontend-base';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"];
redirectToAfterConfirm: ED2['userEntityGrant']['Schema']['redirectTo'];
qrCodeType: QrCodeType;
type: EntityDict['userEntityGrant']['Schema']['type'];
relations: EntityDict['relation']['OpSchema'][];

View File

@ -4,11 +4,11 @@ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"];
redirectToAfterConfirm: ED2['userEntityGrant']['Schema']['redirectTo'];
claimUrl: string;
qrCodeType: string;
passwordRequire?: boolean | undefined;
allowUpdateName?: boolean | undefined;
allowUpdateNickname?: boolean | undefined;
passwordRequire?: boolean;
allowUpdateName?: boolean;
allowUpdateNickname?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -4,7 +4,7 @@ export const desc = {
origin: {
notNull: true,
type: "enum",
enumeration: ["qiniu", "wechat", "ctyun", "aliyun", "tencent", "unknown"]
enumeration: ["qiniu", "wechat", "ctyun", "aliyun", "tencent", "local", "unknown"]
},
type: {
notNull: true,

View File

@ -6,6 +6,7 @@ export const style = {
ctyun: '#ff0000',
aliyun: '#1677ff',
tencent: '#0052d9',
local: '#A9A9A9',
unknown: '#A9A9A9',
},
type: {

View File

@ -1 +1,47 @@
{ "name": "文件", "attr": { "origin": "源", "type": "类型", "bucket": "桶", "objectId": "对象编号", "tag1": "标签一", "tag2": "标签二", "filename": "文件名", "md5": "md5", "entity": "关联对象", "entityId": "关联对象id", "extra1": "额外信息", "extra2": "非结构化额外信息", "extension": "后缀名", "size": "文件大小", "sort": "排序", "fileType": "文件类型", "isBridge": "是否桥接访问", "uploadState": "上传状态", "uploadMeta": "上传需要的metadata", "application": "来源应用" }, "v": { "origin": { "qiniu": "七牛云", "ctyun": "天翼云", "wechat": "微信", "aliyun": "阿里云", "tencent": "腾讯云", "unknown": "未知" }, "type": { "image": "图像", "video": "视频", "audio": "音频", "file": "文件" }, "uploadState": { "success": "上传成功", "failed": "上传失败", "uploading": "上传中" } } }
{
"name": "文件",
"attr": {
"origin": "源",
"type": "类型",
"bucket": "桶",
"objectId": "对象编号",
"tag1": "标签一",
"tag2": "标签二",
"filename": "文件名",
"md5": "md5",
"entity": "关联对象",
"entityId": "关联对象id",
"extra1": "额外信息",
"extra2": "非结构化额外信息",
"extension": "后缀名",
"size": "文件大小",
"sort": "排序",
"fileType": "文件类型",
"isBridge": "是否桥接访问",
"uploadState": "上传状态",
"uploadMeta": "上传需要的metadata",
"application": "来源应用"
},
"v": {
"origin": {
"qiniu": "七牛云",
"ctyun": "天翼云",
"wechat": "微信",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地",
"unknown": "未知"
},
"type": {
"image": "图像",
"video": "视频",
"audio": "音频",
"file": "文件"
},
"uploadState": {
"success": "上传成功",
"failed": "上传失败",
"uploading": "上传中"
}
}
}

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -61,7 +61,7 @@ async function sendNotification(notification, context) {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
openId: data1.openId,
openId: data1.openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV],
});

View File

@ -107,7 +107,7 @@ async function createSession(params, context) {
origin: 'wechat',
type: 'image',
tag1: 'image',
objectId: await (0, uuid_1.generateNewIdAsync)(),
objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@ -132,7 +132,7 @@ async function createSession(params, context) {
origin: 'wechat',
type: 'video',
tag1: 'video',
objectId: await (0, uuid_1.generateNewIdAsync)(),
objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@ -154,7 +154,7 @@ async function createSession(params, context) {
origin: 'wechat',
type: 'audio',
tag1: 'audio',
objectId: await (0, uuid_1.generateNewIdAsync)(),
objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,

View File

@ -2346,8 +2346,8 @@ async function refreshToken(params, context) {
// 只有server模式去刷新token
// 'development' | 'production' | 'staging'
const intervals = {
development: 7200 * 1000,
staging: 600 * 1000,
development: 7200 * 1000, // 2小时
staging: 600 * 1000, // 十分钟
production: 600 * 1000, // 十分钟
};
const application = context.getApplication();

View File

@ -165,7 +165,7 @@ async function createWechatQrCode(options, context) {
permanent,
url,
expired: false,
expiresAt: Date.now() + 2592000 * 1000,
expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大由上层关联对象来主动过期by Xc, 20230131)
props,
};
// 直接创建

View File

@ -7,7 +7,7 @@ exports.desc = {
origin: {
notNull: true,
type: "enum",
enumeration: ["qiniu", "wechat", "ctyun", "aliyun", "tencent", "unknown"]
enumeration: ["qiniu", "wechat", "ctyun", "aliyun", "tencent", "local", "unknown"]
},
type: {
notNull: true,

View File

@ -9,6 +9,7 @@ exports.style = {
ctyun: '#ff0000',
aliyun: '#1677ff',
tencent: '#0052d9',
local: '#A9A9A9',
unknown: '#A9A9A9',
},
type: {

View File

@ -1 +1,47 @@
{ "name": "文件", "attr": { "origin": "源", "type": "类型", "bucket": "桶", "objectId": "对象编号", "tag1": "标签一", "tag2": "标签二", "filename": "文件名", "md5": "md5", "entity": "关联对象", "entityId": "关联对象id", "extra1": "额外信息", "extra2": "非结构化额外信息", "extension": "后缀名", "size": "文件大小", "sort": "排序", "fileType": "文件类型", "isBridge": "是否桥接访问", "uploadState": "上传状态", "uploadMeta": "上传需要的metadata", "application": "来源应用" }, "v": { "origin": { "qiniu": "七牛云", "ctyun": "天翼云", "wechat": "微信", "aliyun": "阿里云", "tencent": "腾讯云", "unknown": "未知" }, "type": { "image": "图像", "video": "视频", "audio": "音频", "file": "文件" }, "uploadState": { "success": "上传成功", "failed": "上传失败", "uploading": "上传中" } } }
{
"name": "文件",
"attr": {
"origin": "源",
"type": "类型",
"bucket": "桶",
"objectId": "对象编号",
"tag1": "标签一",
"tag2": "标签二",
"filename": "文件名",
"md5": "md5",
"entity": "关联对象",
"entityId": "关联对象id",
"extra1": "额外信息",
"extra2": "非结构化额外信息",
"extension": "后缀名",
"size": "文件大小",
"sort": "排序",
"fileType": "文件类型",
"isBridge": "是否桥接访问",
"uploadState": "上传状态",
"uploadMeta": "上传需要的metadata",
"application": "来源应用"
},
"v": {
"origin": {
"qiniu": "七牛云",
"ctyun": "天翼云",
"wechat": "微信",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地",
"unknown": "未知"
},
"type": {
"image": "图像",
"video": "视频",
"audio": "音频",
"file": "文件"
},
"uploadState": {
"success": "上传成功",
"failed": "上传失败",
"uploading": "上传中"
}
}
}

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -64,7 +64,7 @@ async function sendNotification(notification, context) {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
openId: data1.openId,
openId: data1.openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV],
});

View File

@ -20,7 +20,6 @@ export default OakComponent({
};
},
properties: {
tocWidth: 228,
tocClosed: false,
tocFixed: true,
tocPosition: 'none' as 'none' | 'left' | 'right',
@ -28,6 +27,8 @@ export default OakComponent({
headerTop: 0, //页面中吸顶部分高度
className: '',
scrollId: '', // 滚动条所在容器id不传默认body
tocWidth: undefined as number | 'auto' | undefined,
tocHeight: undefined as number | 'auto' | undefined,
},
lifetimes: {
},

View File

@ -2,6 +2,12 @@
padding: 16px;
}
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content {
display: flex;
@ -14,7 +20,7 @@
max-width: 794px;
display: flex;
flex-direction: column;
background: var(--oak-bg-color-container);
background: #fff;
padding: 20px 50px 50px 50px;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
}

View File

@ -8,7 +8,7 @@ import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { TocItem, TocView } from '../toc/tocView';
import Style from './web.module.less';
import Styles from './web.module.less';
export default function Render(
props: WebComponentProps<
@ -16,7 +16,7 @@ export default function Render(
'article',
false,
{
title?: string;
name?: string;
content?: string;
tocPosition: 'none' | 'left' | 'right';
highlightBgColor: string;
@ -24,13 +24,14 @@ export default function Render(
className?: string;
tocFixed: boolean;
tocClosed: boolean;
tocWidth: 228,
scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
},
{}
>
) {
const { className, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed= false, tocWidth = 228, scrollId } = props.data;
const { className, name, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed= false, scrollId, tocWidth, tocHeight } = props.data;
const editorConfig: Partial<IEditorConfig> = {
readOnly: true,
autoFocus: true,
@ -63,28 +64,33 @@ export default function Render(
}
}, [tocPosition])
useEffect(() => {
if (name) {
window.document.title = name;
}
}, [name]);
return (
<div className={classNames(Style.container, className)}>
<Row gutter={[16, 0]}>
{tocPosition === 'left' ? (
<Col flex={`${tocWidth}px`}>
<div className={classNames(Styles.container, className)}>
<div className={Styles.contentContainer}>
{tocPosition === "left" ? (
<TocView
toc={toc}
showToc={showToc}
tocPosition='left'
tocPosition="left"
setShowToc={setShowToc}
highlightBgColor={highlightBgColor}
headerTop={headerTop}
fixed={tocFixed}
closed={tocClosed}
scrollId={scrollId}
tocWidth={tocWidth}
tocHeight={tocHeight}
/>
</Col>
) : null}
<Col flex="auto">
<div className={Style.content}>
<div className={Style.editorContainer}>
<div className={Styles.content}>
<div className={Styles.editorContainer}>
<div style={{ width: "100%" }}>
<Editor
defaultConfig={editorConfig}
@ -96,39 +102,44 @@ export default function Render(
onCreated={setEditor}
onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header');
const tocItems = headers.map((header: any) => {
const text = SlateNode.string(header)
const { id, type } = header
const headers =
editor.getElemsByTypePrefix("header");
const tocItems = headers.map(
(header: any) => {
const text =
SlateNode.string(header);
const { id, type } = header;
return {
text,
level: parseInt(type.substring(6)),
level: parseInt(
type.substring(6)
),
id,
};
});
}
);
setToc([...tocItems]);
}}
/>
</div>
</div>
</div>
</Col>
{tocPosition === 'right' ? (
<Col flex={`${tocWidth}px`}>
{tocPosition === "right" ? (
<TocView
toc={toc}
showToc={showToc}
tocPosition='right'
tocPosition="right"
setShowToc={setShowToc}
highlightBgColor={highlightBgColor}
headerTop={headerTop}
fixed={tocFixed}
closed={tocClosed}
scrollId={scrollId}
tocWidth={tocWidth}
tocHeight={tocHeight}
/>
</Col>
) : null}
</Row>
</div>
</div>
);
}

View File

@ -8,7 +8,6 @@ export default OakComponent({
data: {
content: '',
title: '',
author: '',
},
lifetimes: {
async attached() {
@ -20,7 +19,6 @@ export default OakComponent({
this.setState({
content: article?.content,
title: article?.title,
author: article?.author,
});
},
detached() {
@ -35,6 +33,8 @@ export default OakComponent({
headerTop: 0, //页面中吸顶部分高度
className: '',
scrollId: '', // 滚动条所在容器id不传默认body
tocWidth: undefined as number | 'auto' | undefined,
tocHeight: undefined as number | 'auto' | undefined,
},
methods: {},
});

View File

@ -1,8 +1,13 @@
.container {
// min-height: 100vh;
padding: 16px;
}
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content {
display: flex;

View File

@ -22,11 +22,13 @@ export default function Render(
tocFixed: boolean;
tocClosed: boolean;
scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
},
{}
>
) {
const { className, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed= false, scrollId } = props.data;
const { className, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed= false, scrollId, tocWidth, tocHeight } = props.data;
const editorConfig: Partial<IEditorConfig> = {
readOnly: true,
autoFocus: true,
@ -60,24 +62,23 @@ export default function Render(
return (
<div className={classNames(Styles.container, className)}>
<Row gutter={[16, 0]}>
{tocPosition === 'left' && (
<Col flex="228px">
<div className={Styles.contentContainer}>
{tocPosition === "left" && (
<TocView
toc={toc}
showToc={showToc}
tocPosition='left'
tocPosition="left"
setShowToc={setShowToc}
highlightBgColor={highlightBgColor}
headerTop={headerTop}
fixed={tocFixed}
closed={tocClosed}
scrollId={scrollId}
tocWidth={tocWidth}
tocHeight={tocHeight}
/>
</Col>
)}
<Col flex="auto">
<div className={Styles.content}>
<div className={Styles.editorContainer}>
<div style={{ width: "100%" }}>
@ -86,44 +87,49 @@ export default function Render(
value={html}
mode="default"
style={{
width: '100%'
width: "100%",
}}
onCreated={setEditor}
onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header');
const tocItems = headers.map((header: any) => {
const text = SlateNode.string(header)
const { id, type } = header
const headers =
editor.getElemsByTypePrefix("header");
const tocItems = headers.map(
(header: any) => {
const text =
SlateNode.string(header);
const { id, type } = header;
return {
text,
level: parseInt(type.substring(6)),
level: parseInt(
type.substring(6)
),
id,
};
});
}
);
setToc([...tocItems]);
}}
/>
</div>
</div>
</div>
</Col>
{tocPosition === 'right' && (
<Col flex="228px">
{tocPosition === "right" && (
<TocView
toc={toc}
showToc={showToc}
tocPosition='right'
tocPosition="right"
setShowToc={setShowToc}
highlightBgColor={highlightBgColor}
headerTop={headerTop}
fixed={tocFixed}
closed={tocClosed}
scrollId={scrollId}
tocWidth={tocWidth}
tocHeight={tocHeight}
/>
</Col>
)}
</Row>
</div>
</div>
);
}

View File

@ -21,10 +21,11 @@ export function TocView(
fixed?: boolean;
scrollId?: string;
closed?: boolean,
tocWidth?: number,
tocWidth?: number | 'auto',
tocHeight?: number | 'auto'
}
) {
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, fixed = false, closed = false, tocWidth = 228 } = props;
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, fixed = false, closed = false, tocWidth, tocHeight } = props;
useEffect(() => {
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
@ -118,9 +119,12 @@ export function TocView(
};
return (
<div className={classNames(Style.tocContainer, {
<div
className={classNames(Style.tocContainer, {
[Style.fixed]: fixed
})}>
})}
style={Object.assign({}, tocWidth ? { width: tocWidth } : {}, tocHeight ? { height: tocHeight } : {})}
>
{showToc ? (
<>
<div className={Style.catalogTitle}>

View File

@ -3,7 +3,7 @@
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
height: 50px;
justify-content: space-between;
cursor: pointer;

View File

@ -157,10 +157,10 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
icon={<EditOutlined />}
size="small"
onClick={(e) => {
e.stopPropagation();
setName(ele.name);
setNameEditing(ele.id);
e.stopPropagation();
}}
style={{ marginRight: 4 }}
/>
<div className={Styles.name}>
<div style={{ marginLeft: 4, overflow: 'hidden', width: '150px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ele?.name}</div>

View File

@ -33,6 +33,7 @@ export default OakComponent({
onArticlePreview: (content?: string, title?: string) => undefined as void, //预览文章
origin: 'qiniu', // 默认为七牛云
scrollId: '', // 滚动条所在容器id不传默认页面编辑器容器id
height: 600 as number | 'auto',
},
listeners: {
'editor,content'(prev, next) {

View File

@ -4,10 +4,21 @@
box-sizing: border-box;
}
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
}
.editorContainer {
width: 100%;
margin: 30px auto 30px auto;
@ -45,10 +56,8 @@
}
.footer {
// position: absolute;
bottom: 0;
height: 50px;
margin-top: 10px;
}
.contentNumber {
@ -60,7 +69,7 @@
}
.tocContainer {
padding-top: 28px;
// padding-top: 28px;
position: relative;
box-sizing: border-box;
overflow-y: auto;
@ -101,7 +110,7 @@
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #DDD;
padding: 6px 6px 10px 6px;
padding: 34px 6px 10px 6px;
box-sizing: border-box;
position: sticky;
top: 0;

View File

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import { Alert, Card, Button, Row, Col, Space, Input, Modal, Tooltip, } from "antd";
import { Alert, Button, Row, Col, Space, Input, Modal, Tooltip, } from "antd";
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import { IToolbarConfig, SlateNode } from "@wangeditor/editor";
@ -10,11 +10,10 @@ import classNames from "classnames";
import Prompt from "../../../components/common/prompt";
import { EntityDict } from "./../../../oak-app-domain";
import Style from "./web.module.less";
import { TocItem, TocView } from '../toc/tocView';
import {
CloseOutlined,
EyeOutlined,
MenuOutlined,
CaretDownOutlined
} from "@ant-design/icons";
type InsertFnType = (url: string, alt?: string, href?: string) => void;
@ -46,141 +45,6 @@ function customCheckImageFn(
// 3. 返回 undefined即没有任何返回说明检查未通过编辑器会阻止插入。但不会提示任何信息
}
type TocItem = {
text: string;
level: number;
id: string;
}
function TocView(
props: {
toc: TocItem[];
showToc: boolean;
tocPosition: 'left' | 'right';
setShowToc: (showToc: boolean) => void;
highlightBgColor: string;
scrollId?: string
}
) {
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, scrollId } = props;
useEffect(() => {
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
}, [highlightBgColor]);
const generateTocList = (items: TocItem[], currentLevel: number = 1, parentId: string | null = null): React.ReactNode => {
//递归生成嵌套列表
const result: React.ReactNode[] = [];
let lastId: string | null = parentId;
while (items.length > 0) {
const item = items[0];
//有无子级标题,有则显示展开图标
const hasChildren = items.length > 1 && items[1].level > item.level;
if (item.level > currentLevel) {
// 递归生成子列表
const sublist = generateTocList(items, item.level, item.id);
result.push(
<li key={item.id} id={`ul-${lastId}`} >
<ul style={{ listStyleType: 'none', paddingInlineStart: '6px' }}>
{sublist}
</ul>
</li>
);
} else if (item.level < currentLevel) {
// 结束当前层级
break;
} else {
// 添加当前层级的 <li>
result.push(
<li className={Style.listItem} key={item.id} id={`li-${item.id}`} style={{ paddingLeft: `${(item.level - 1) * 6}px`, }}>
<CaretDownOutlined id={`icon-${item.id}`} className={Style.icon} style={{ visibility: hasChildren && item.level < 5 ? 'visible' : 'hidden', color: '#A5A5A5' }}
onClick={() => {
const iconElem = document.getElementById(`icon-${item.id}`);
const ulElem = document.getElementById(`ul-${item.id}`);
const isFolded = iconElem?.className.includes('iconFold') || ulElem?.className.includes('fold');
if (isFolded) {
iconElem?.classList.remove(Style.iconFold);
ulElem?.classList.remove(Style.fold);
} else {
iconElem?.classList.add(Style.iconFold);
ulElem?.classList.add(Style.fold);
}
}} />
<div
style={{ fontSize: '1em', fontWeight: item.level === 1 ? 'bold' : 'normal' }}
className={Style.tocItem}
onClick={(event: any) => {
// editor.scrollToElem(item.id);
//编辑器滚动到对应元素
const elem = document.getElementById(item.id);
const elemTop = elem?.getBoundingClientRect().top;
const scrollContainer = document.getElementById(scrollId || 'article-upsert-editorContainer');
const containerTop = scrollContainer?.getBoundingClientRect().top;
scrollContainer?.scrollBy({
top: elemTop! - containerTop!,
behavior: 'smooth',
});
//添加背景色
elem?.classList.add(Style.highlight);
//移除背景色类名
setTimeout(function () {
elem?.classList.remove(Style.highlight);
}, 1000);
event.preventDefault();
event.stopPropagation();
}}
>
{item.text}
</div>
</li>
);
items.shift(); // 移除已处理的项
lastId = item.id;
}
}
return result;
};
return (
<div className={classNames(Style.tocContainer, {
// [Style.fixed]: fixed
})}>
{showToc ? (
<>
<div className={Style.catalogTitle}>
<div style={{ color: '#A5A5A5' }}></div>
<CloseOutlined style={{ color: '#A5A5A5' }} onClick={() => setShowToc(false)} />
</div>
{(toc && toc.length > 0) ? (
<ul style={{ listStyleType: 'none', paddingInlineStart: '0px' }}>{generateTocList([...toc])}</ul>
) : (
<div style={{ display: 'flex', alignItems: 'center', color: '#B1B1B1', height: '200px' }}>
<div>
</div>
</div>
)}
</>
) : (
<div className={classNames(Style.tocButton, { [Style.tocButtonRight]: tocPosition === 'right' })}>
<Tooltip title="显示大纲" placement={tocPosition === 'right' ? 'left' : 'right'}>
<Button
size="small"
icon={<MenuOutlined />}
onClick={() => setShowToc(true)}
/>
</Tooltip>
</div>
)}
</div>
)
}
export default function Render(
props: WebComponentProps<
EntityDict,
@ -191,14 +55,16 @@ export default function Render(
name: string;
editor: any;
content?: string;
html?: string;
origin?: string;
contentTip: boolean;
articleMenuId: string;
oakId: string;
tocPosition: 'none' | 'left' | 'right';
highlightBgColor: string;
scrollId?: string
scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
height?: number | 'auto';
},
{
setHtml: (content: string) => void;
@ -225,19 +91,22 @@ export default function Render(
clearContentTip,
} = methods;
const {
oakFullpath,
id,
content,
editor,
origin = 'qiniu',
oakFullpath,
html,
tocPosition = 'none',
highlightBgColor = 'none',
scrollId
scrollId,
tocWidth,
tocHeight,
height = 600
} = data;
const [articleId, setArticleId] = useState('');
const [toc, setToc] = useState<TocItem[]>([]);
const [showToc, setShowToc] = useState(false);
const containerId = scrollId || 'article-upsert-editorContainer';
useEffect(() => {
if (id) {
@ -253,37 +122,42 @@ export default function Render(
return (
<div className={Style.container}>
<Prompt when={!id || data.oakDirty} message={'您确认离开页面吗?'} />
<div style={{ width: 'calc(100% - 16px)', }}>
<Prompt
when={!id || data.oakDirty}
message={"您确认离开页面吗?"}
/>
<div style={{ width: "calc(100% - 16px)" }}>
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
/>
</div>
<Row gutter={[16, 0]}>
{tocPosition === 'left' ? (
<Col flex="228px">
<div className={Style.contentContainer}>
{tocPosition === "left" ? (
<TocView
toc={toc}
showToc={showToc}
tocPosition='left'
tocPosition="left"
setShowToc={setShowToc}
highlightBgColor={highlightBgColor}
scrollId={scrollId}
scrollId={containerId}
tocWidth={tocWidth}
tocHeight={tocHeight || height}
/>
</Col>
) : null}
<Col flex="auto">
<div className={Style.content}>
<div id="article-upsert-editorContainer" className={classNames(Style.editorContainer, {
[Style.editorExternalContainer]: !!scrollId
})}>
<div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
<div
id={containerId}
className={classNames(Style.editorContainer, {
[Style.editorExternalContainer]: !!scrollId,
})}
>
{data.contentTip && (
<Alert
type="info"
message={t('tips.content')}
message={t("tips.content")}
closable
onClose={() => clearContentTip()}
/>
@ -294,10 +168,10 @@ export default function Render(
update({ name: e.target.value })
}
value={data.name}
placeholder={'请输入文章标题'}
placeholder={"请输入文章标题"}
size="large"
maxLength={32}
suffix={`${[...(data.name || '')].length}/32`}
suffix={`${(data.name || "").length}/32`}
className={Style.titleInput}
/>
</div>
@ -305,7 +179,7 @@ export default function Render(
<Editor
defaultConfig={{
autoFocus: true,
placeholder: '请输入文章内容...',
placeholder: "请输入文章内容...",
MENU_CONF: {
checkImage: customCheckImageFn,
uploadImage: {
@ -316,30 +190,50 @@ export default function Render(
) {
// TS 语法
// file 即选中的文件
const { name, size, type } = file;
const extension = name.substring(name.lastIndexOf('.') + 1);
const filename = name.substring(0,name.lastIndexOf('.'));
const { name, size, type } =
file;
const extension =
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = {
entity: 'article',
entity: "article",
entityId: articleId,
origin: origin,
type: 'image',
tag1: 'source',
objectId: generateNewId(),
type: "image",
tag1: "source",
objectId:
generateNewId(),
filename,
size,
extension,
bucket: '',
bucket: "",
id: generateNewId(),
fileType: type
} as EntityDict['extraFile']['CreateSingle']['data'];
fileType: type,
} as EntityDict["extraFile"]["CreateSingle"]["data"];
try {
// 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile, file);
const url =
await uploadFile(
extraFile,
file
);
// 最后插入图片
insertFn( url, extraFile.filename);
} catch (err) { }
insertFn(
url,
extraFile.filename
);
} catch (err) {}
},
},
uploadVideo: {
@ -350,30 +244,51 @@ export default function Render(
) {
// TS 语法
// file 即选中的文件
const { name, size, type } = file;
const extension = name.substring(name.lastIndexOf('.') + 1);
const filename = name.substring(0,name.lastIndexOf('.'));
const { name, size, type } =
file;
const extension =
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = {
entity: 'article',
entity: "article",
entityId: articleId,
origin: origin,
type: 'video',
tag1: 'source',
objectId: generateNewId(),
type: "video",
tag1: "source",
objectId:
generateNewId(),
filename,
size,
extension,
bucket: '',
bucket: "",
id: generateNewId(),
fileType: type
} as EntityDict['extraFile']['CreateSingle']['data'];
fileType: type,
} as EntityDict["extraFile"]["CreateSingle"]["data"];
try {
// 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile,file);
const url =
await uploadFile(
extraFile,
file
);
// 最后插入图片
insertFn(url, url + '?vframe/jpg/offset/0');
} catch (err) { }
insertFn(
url,
url +
"?vframe/jpg/offset/0"
);
} catch (err) {}
},
},
},
@ -381,16 +296,20 @@ export default function Render(
onCreated={setEditor}
onChange={(editor) => {
setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header');
const tocItems = headers.map((header: any) => {
const text = SlateNode.string(header)
const { id, type } = header
const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map(
(header: any) => {
const text = SlateNode.string(header);
const { id, type } = header;
return {
text,
level: parseInt(type.substring(6)),
level: parseInt(
type.substring(6)
),
id,
};
});
}
);
setToc([...tocItems]);
}}
style={{
@ -399,11 +318,8 @@ export default function Render(
mode="default"
/>
</div>
</div>
<div className={Style.footer}>
<Row align="middle">
<Col flex="none">
<Space>
<Button
disabled={
@ -419,34 +335,28 @@ export default function Render(
</Button>
<Button
onClick={() => {
gotoPreview(
content,
data.name
);
gotoPreview(content, data.name);
}}
icon={<EyeOutlined />}
>
<EyeOutlined />
</Button>
</Space>
</Col>
</Row>
</div>
</div>
</Col>
{tocPosition === 'right' ? (
<Col flex="228px">
{tocPosition === "right" ? (
<TocView
toc={toc}
showToc={showToc}
tocPosition='right'
tocPosition="right"
setShowToc={setShowToc}
highlightBgColor={highlightBgColor}
scrollId={scrollId}
scrollId={containerId}
tocWidth={tocWidth}
tocHeight={tocHeight}
/>
</Col>
) : null}
</Row>
</div >
</div>
</div>
);
}

View File

@ -3,7 +3,7 @@
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
height: 50px;
justify-content: space-between;
.ne {
@ -11,7 +11,7 @@
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
margin-left: 10px;
align-items: center;
justify-content: space-between;
@ -43,6 +43,8 @@
}
}
.sub {
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.02);
padding-left: 18px;
}
@ -78,7 +80,6 @@
flex-direction: row;
align-items: center;
justify-content: space-evenly;
// padding: 10px;
.ph {
width: 24px;

View File

@ -74,7 +74,6 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
}
}, [editArticleId]);
const [modal, contextHolder] = Modal.useModal();
const [name, setName] = useState('');
const [nameEditing, setNameEditing] = useState(false);
const [showSub, setShowSub] = useState(false);
const [newBreadcrumbItems, setNewBreadcrumbItems] = useState([] as string[]);
@ -145,9 +144,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
if (!row.parentId && articleMenuId) {
return (
<>
<div>
{Sub}
</div>
{contextHolder}
</>
);
@ -294,30 +291,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
return (
<>
<div className={Styles.container}>
<div
className={Styles.ne}
>
{
// nameEditing ? <div className={Styles.name}>
// <Input
// autoFocus
// value={ name || row?.name}
// onChange={(evt) => setName(evt.target.value)}
// onPressEnter={async () => {
// if (name && name !== row?.name) {
// await onUpdateName(name);
// }
// setNameEditing(false);
// }}
// onBlur={async () => {
// if (name && name !== row?.name) {
// await onUpdateName(name);
// }
// setNameEditing(false);
// }}
// />
// </div> :
<>
<div className={Styles.ne}>
<Button
type="text"
icon={<EditOutlined />}
@ -325,25 +299,30 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
onClick={() => {
setNameEditing(true);
const modalInstance = modal.confirm({
title: '编辑分类',
cancelText: '取消',
okText: '提交',
title: "编辑分类",
cancelText: "取消",
okText: "提交",
content: (
<div>
<Form.Item
label="分类名称"
>
<Form.Item label="分类名称">
<Input
ref={menuNameRef}
defaultValue={row.name}
onChange={(val) => update({ name: val.target.value })}
onChange={(val) =>
update({
name: val.target
.value,
})
}
/>
</Form.Item>
<Form.Item
label="LOGO"
help={
<div>
<span>LOGO高清图片</span>
<span>
LOGO高清图片
</span>
<span>
108*108PNGJPG格式300KB
</span>
@ -374,28 +353,36 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
// });
// }
// }
footer: <Space>
footer: (
<Space>
<ExtraFileCommit
entity={oakEntity}
oakPath={oakFullpath}
afterCommit={() => {
modalInstance!.destroy()
modalInstance!.destroy();
}}
beforeCommit={() => {
if (menuNameRef.current!.input!.value) {
return true
if (
menuNameRef.current!
.input!.value
) {
return true;
} else {
return false
return false;
}
}}
/>
<Button onClick={() => modalInstance!.destroy()}>
<Button
onClick={() =>
modalInstance!.destroy()
}
>
</Button>
</Space>
),
});
}}
style={{ marginRight: 4 }}
/>
<div className={Styles.name}>
{logo ? (
@ -406,26 +393,44 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
preview={false}
/>
) : null}
<div style={{ marginLeft: 4, overflow: 'hidden', width: '100px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{row?.name}</div>
<div
style={{
marginLeft: 4,
overflow: "hidden",
width: "100px",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{row?.name}
</div>
</>
}
</div>
<Divider type="vertical" style={{ height: '100%', marginTop: 4, marginBottom: 4 }} />
</div>
<Divider
type="vertical"
style={{
height: "100%",
marginTop: 4,
marginBottom: 4,
}}
/>
<div className={Styles.control}>
{
!row.parentId && <Button
{!row.parentId && (
<Button
type="text"
size="small"
onClick={() => {
gotoDoc(row?.id);
}}
icon={<EyeOutlined />}
></Button>
)}
<Dropdown
menu={{ items }}
placement="bottomRight"
arrow
>
</Button>
}
<Dropdown menu={{ items }} placement="bottomRight" arrow>
<Button
type="text"
icon={<PlusOutlined />}
@ -439,42 +444,39 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
onClick={() => {
if (!allowRemove) {
modal.error({
title: '无法删除',
content: hasSubArticles ? '请先删除目录下的文章' : '请先删除目录下的子目录',
okText: '确认'
title: "无法删除",
content: hasSubArticles
? "请先删除目录下的文章"
: "请先删除目录下的子目录",
okText: "确认",
});
}
else {
} else {
onRemove();
}
}}
/>
{
(hasSubArticles || hasSubMenus) ? (
showSub ?
{hasSubArticles || hasSubMenus ? (
showSub ? (
<Button
type="text"
icon={<UpOutlined />}
size="small"
onClick={() => setShowSub(false)}
/> :
/>
) : (
<Button
type="text"
icon={<DownOutlined />}
size="small"
onClick={() => setShowSub(true)}
/>
) : <div className={Styles.ph} />
}
</div>
</div>
{
showSub && (
<div className={Styles.sub}>
{Sub}
</div>
)
}
) : (
<div className={Styles.ph} />
)}
</div>
</div>
{showSub ? Sub : null}
{contextHolder}
</>
);

View File

@ -22,10 +22,10 @@ export default OakComponent({
highlightBgColor: 'none', //点击文章目录时标题高亮背景色none为不显示高亮背景色
onMenuView: () => undefined as void, //查看全部菜单
onMenuViewById: (articleMenuId: string) => undefined as void, //查看指定id菜单
onArticleView: (oakId: string) => undefined as void, //查看文章
onArticleView: (articleId: string) => undefined as void, //查看文章
onArticlePreview: (content?: string, title?: string) => undefined as void, //预览文章
onArticleEdit: (oakId: string) => undefined as void, //编辑文章
setCopyArticleUrl: (id: string) => '' as string,
onArticleEdit: (articleId: string) => undefined as void, //编辑文章
setCopyArticleUrl: (articleId: string) => '' as string,
origin: 'qiniu', // cos origin默认七牛云
scrollId: '', // 滚动条所在容器id不传默认页面编辑器容器id
},

View File

@ -7,7 +7,7 @@
overflow-x: hidden;
.menu {
min-width: 320px;
width: 320px;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
@ -17,13 +17,14 @@
flex-direction: row;
justify-content: space-between;
align-items: center;
min-width: 320px;
padding: 8px 10px;
width: 320px;
padding: 0px 10px;
position: sticky;
z-index: 99;
background: #ffffff;
top: 0;
border-bottom: 1px solid #f0f0f0;
height: 50px;
.menuTitle {
font-size: 16px;
@ -129,7 +130,6 @@
padding: 20px 15px;
min-width: 320px;
height: 100%;
// max-height: 800px;
overflow-y: scroll;
overflow-x: hidden;
}

View File

@ -1,6 +1,5 @@
import React, { useEffect, useState, useRef } from 'react';
import { Button, Divider, Tooltip, Space, Drawer, Tag } from 'antd';
import type { MenuProps } from 'antd';
import { Button, Divider, Tooltip, Space, Drawer } from 'antd';
import { EyeOutlined, CopyOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined, EditOutlined, FileOutlined } from '@ant-design/icons';
import copy from 'copy-to-clipboard';
import { WebComponentProps } from 'oak-frontend-base';