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

View File

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

View File

@ -161,7 +161,7 @@ export async function createWechatQrCode(options, context) {
permanent, permanent,
url, url,
expired: false, expired: false,
expiresAt: Date.now() + 2592000 * 1000, expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大由上层关联对象来主动过期by Xc, 20230131)
props, 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; 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, { declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", false, {
tocWidth: number;
tocClosed: boolean; tocClosed: boolean;
tocFixed: boolean; tocFixed: boolean;
tocPosition: "left" | "right" | "none"; tocPosition: "none" | "left" | "right";
highlightBgColor: string; highlightBgColor: string;
headerTop: number; headerTop: number;
className: string; className: string;
scrollId: string; scrollId: string;
tocWidth: number | "auto" | undefined;
tocHeight: number | "auto" | undefined;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

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

View File

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

View File

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

View File

@ -2,6 +2,12 @@
padding: 16px; padding: 16px;
} }
.contentContainer {
display: flex;
flex-direction: row;
flex: 1;
gap: 8px;
}
.content { .content {
display: flex; display: flex;
@ -14,7 +20,7 @@
max-width: 794px; max-width: 794px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--oak-bg-color-container); background: #fff;
padding: 20px 50px 50px 50px; padding: 20px 50px 50px 50px;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%); 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, { declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, {
tocClosed: boolean; tocClosed: boolean;
tocFixed: boolean; tocFixed: boolean;
tocPosition: "left" | "right" | "none"; tocPosition: "none" | "left" | "right";
highlightBgColor: string; highlightBgColor: string;
headerTop: number; headerTop: number;
className: string; className: string;
scrollId: string; scrollId: string;
tocWidth: number | "auto" | undefined;
tocHeight: number | "auto" | undefined;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
height: 60px; height: 50px;
justify-content: space-between; justify-content: space-between;
cursor: pointer; 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, { declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "article", false, {
articleMenuId: string; articleMenuId: string;
changeIsEdit: () => void; changeIsEdit: () => void;
tocPosition: "left" | "right" | "none"; tocPosition: "none" | "left" | "right";
highlightBgColor: string; highlightBgColor: string;
onArticlePreview: (content?: string, title?: string) => void; onArticlePreview: (content?: string, title?: string) => void;
origin: string; origin: string;
scrollId: string; scrollId: string;
height: number | "auto";
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react"; 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 "@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";
@ -7,7 +7,8 @@ import { generateNewId } from "oak-domain/lib/utils/uuid";
import classNames from "classnames"; import classNames from "classnames";
import Prompt from "../../../components/common/prompt"; import Prompt from "../../../components/common/prompt";
import Style from "./web.module.less"; 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 = { const toolbarConfig = {
excludeKeys: ["fullScreen"], excludeKeys: ["fullScreen"],
@ -27,105 +28,14 @@ function customCheckImageFn(src, alt, url) {
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串) // 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
// 3. 返回 undefined即没有任何返回说明检查未通过编辑器会阻止插入。但不会提示任何信息 // 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) { 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, 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 [articleId, setArticleId] = useState('');
const [toc, setToc] = useState([]); const [toc, setToc] = useState([]);
const [showToc, setShowToc] = useState(false); const [showToc, setShowToc] = useState(false);
const containerId = scrollId || 'article-upsert-editorContainer';
useEffect(() => { useEffect(() => {
if (id) { if (id) {
setArticleId(id); setArticleId(id);
@ -137,28 +47,25 @@ export default function Render(props) {
} }
}, [tocPosition]); }, [tocPosition]);
return (<div className={Style.container}> return (<div className={Style.container}>
<Prompt when={!id || data.oakDirty} message={'您确认离开页面吗?'}/> <Prompt when={!id || data.oakDirty} message={"您确认离开页面吗?"}/>
<div style={{ width: 'calc(100% - 16px)', }}> <div style={{ width: "calc(100% - 16px)" }}>
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default"/> <Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default"/>
</div> </div>
<Row gutter={[16, 0]}> <div className={Style.contentContainer}>
{tocPosition === 'left' ? (<Col flex="228px"> {tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight || height}/>) : null}
<TocView toc={toc} showToc={showToc} tocPosition='left' setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={scrollId}/>
</Col>) : null}
<Col flex="auto"> <div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
<div className={Style.content}> <div id={containerId} className={classNames(Style.editorContainer, {
<div id="article-upsert-editorContainer" className={classNames(Style.editorContainer, { [Style.editorExternalContainer]: !!scrollId,
[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}> <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>
<div className={Style.editorContent}> <div className={Style.editorContent}>
<Editor defaultConfig={{ <Editor defaultConfig={{
autoFocus: true, autoFocus: true,
placeholder: '请输入文章内容...', placeholder: "请输入文章内容...",
MENU_CONF: { MENU_CONF: {
checkImage: customCheckImageFn, checkImage: customCheckImageFn,
uploadImage: { uploadImage: {
@ -167,21 +74,21 @@ export default function Render(props) {
// 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: articleId, entityId: 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
@ -198,27 +105,28 @@ export default function Render(props) {
// 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: articleId, entityId: articleId,
origin: origin, origin: origin,
type: 'video', type: "video",
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, url + '?vframe/jpg/offset/0'); insertFn(url, url +
"?vframe/jpg/offset/0");
} }
catch (err) { } catch (err) { }
}, },
@ -226,7 +134,7 @@ export default function Render(props) {
}, },
}} onCreated={setEditor} onChange={(editor) => { }} onCreated={setEditor} onChange={(editor) => {
setHtml(editor.getHtml()); setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header'); const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map((header) => { const tocItems = headers.map((header) => {
const text = SlateNode.string(header); const text = SlateNode.string(header);
const { id, type } = header; const { id, type } = header;
@ -241,11 +149,8 @@ export default function Render(props) {
minHeight: 440, minHeight: 440,
}} mode="default"/> }} mode="default"/>
</div> </div>
</div> </div>
<div className={Style.footer}> <div className={Style.footer}>
<Row align="middle">
<Col flex="none">
<Space> <Space>
<Button disabled={!data.oakDirty || <Button disabled={!data.oakDirty ||
data.oakExecuting} type="primary" onClick={() => { data.oakExecuting} type="primary" onClick={() => {
@ -255,19 +160,13 @@ export default function Render(props) {
</Button> </Button>
<Button onClick={() => { <Button onClick={() => {
gotoPreview(content, data.name); gotoPreview(content, data.name);
}}> }} icon={<EyeOutlined />}>
<EyeOutlined />
预览 预览
</Button> </Button>
</Space> </Space>
</Col>
</Row>
</div> </div>
</div> </div>
</Col> {tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
{tocPosition === 'right' ? (<Col flex="228px"> </div>
<TocView toc={toc} showToc={showToc} tocPosition='right' setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={scrollId}/>
</Col>) : null}
</Row>
</div>); </div>);
} }

View File

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

View File

@ -15,7 +15,6 @@ export default function Render(props) {
} }
}, [editArticleId]); }, [editArticleId]);
const [modal, contextHolder] = Modal.useModal(); const [modal, contextHolder] = Modal.useModal();
const [name, setName] = useState('');
const [nameEditing, setNameEditing] = useState(false); const [nameEditing, setNameEditing] = useState(false);
const [showSub, setShowSub] = useState(false); const [showSub, setShowSub] = useState(false);
const [newBreadcrumbItems, setNewBreadcrumbItems] = useState([]); 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}/>); 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) { if (!row.parentId && articleMenuId) {
return (<> return (<>
<div>
{Sub} {Sub}
</div>
{contextHolder} {contextHolder}
</>); </>);
} }
@ -145,39 +142,23 @@ export default function Render(props) {
return (<> return (<>
<div className={Styles.container}> <div className={Styles.container}>
<div className={Styles.ne}> <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={() => { <Button type="text" icon={<EditOutlined />} size="small" onClick={() => {
setNameEditing(true); setNameEditing(true);
const modalInstance = modal.confirm({ const modalInstance = modal.confirm({
title: '编辑分类', title: "编辑分类",
cancelText: '取消', cancelText: "取消",
okText: '提交', okText: "提交",
content: (<div> content: (<div>
<Form.Item label="分类名称"> <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>
<Form.Item label="LOGO" help={<div> <Form.Item label="LOGO" help={<div>
<span>请上传LOGO高清图片</span> <span>
请上传LOGO高清图片
</span>
<span> <span>
108*108像素仅支持PNGJPG格式大小不超过300KB 108*108像素仅支持PNGJPG格式大小不超过300KB
</span> </span>
@ -197,11 +178,12 @@ export default function Render(props) {
// }); // });
// } // }
// } // }
footer: <Space> footer: (<Space>
<ExtraFileCommit entity={oakEntity} oakPath={oakFullpath} afterCommit={() => { <ExtraFileCommit entity={oakEntity} oakPath={oakFullpath} afterCommit={() => {
modalInstance.destroy(); modalInstance.destroy();
}} beforeCommit={() => { }} beforeCommit={() => {
if (menuNameRef.current.input.value) { if (menuNameRef.current
.input.value) {
return true; return true;
} }
else { else {
@ -211,22 +193,31 @@ export default function Render(props) {
<Button onClick={() => modalInstance.destroy()}> <Button onClick={() => modalInstance.destroy()}>
取消 取消
</Button> </Button>
</Space> </Space>),
}); });
}} style={{ marginRight: 4 }}/> }}/>
<div className={Styles.name}> <div className={Styles.name}>
{logo ? (<Image height={26} width={26} src={logo} preview={false}/>) : null} {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>
</>}
</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}> <div className={Styles.control}>
{!row.parentId && <Button type="text" onClick={() => { {!row.parentId && (<Button type="text" size="small" onClick={() => {
gotoDoc(row?.id); gotoDoc(row?.id);
}} icon={<EyeOutlined />}> }} icon={<EyeOutlined />}></Button>)}
</Button>}
<Dropdown menu={{ items }} placement="bottomRight" arrow> <Dropdown menu={{ items }} placement="bottomRight" arrow>
<Button type="text" icon={<PlusOutlined />} size="small"/> <Button type="text" icon={<PlusOutlined />} size="small"/>
@ -234,23 +225,21 @@ export default function Render(props) {
<Button type="text" icon={<MinusOutlined />} size="small" onClick={() => { <Button type="text" icon={<MinusOutlined />} size="small" onClick={() => {
if (!allowRemove) { if (!allowRemove) {
modal.error({ modal.error({
title: '无法删除', title: "无法删除",
content: hasSubArticles ? '请先删除目录下的文章' : '请先删除目录下的子目录', content: hasSubArticles
okText: '确认' ? "请先删除目录下的文章"
: "请先删除目录下的子目录",
okText: "确认",
}); });
} }
else { else {
onRemove(); 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}/>)}
<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>
</div> </div>
{showSub && (<div className={Styles.sub}> {showSub ? Sub : null}
{Sub}
</div>)}
{contextHolder} {contextHolder}
</>); </>);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
/// <reference types="react" />
import { EntityDict } from '../../../oak-app-domain'; import { EntityDict } from '../../../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page'; 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; type BeforeCommit = (() => boolean | undefined | Promise<boolean | undefined>) | undefined;
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, { declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2; entity: keyof ED2;
action?: string | undefined; action?: string;
size?: ButtonProps['size'] | AmButtonProps['size']; size?: ButtonProps['size'] | AmButtonProps['size'];
block?: boolean | undefined; block?: boolean;
type?: ButtonProps['type'] | AmButtonProps['type']; type?: ButtonProps['type'] | AmButtonProps['type'];
executeText?: string | undefined; executeText?: string;
buttonProps?: (ButtonProps & { buttonProps?: ButtonProps & AmButtonProps;
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;
afterCommit?: AfterCommit; afterCommit?: AfterCommit;
beforeCommit?: BeforeCommit; beforeCommit?: BeforeCommit;
messageProps?: boolean | MessageProps | undefined; messageProps?: MessageProps | boolean;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { EntityDict } from '../../../oak-app-domain'; import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "userEntityGrant", false, { declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "userEntityGrant", false, {
picker: ((props: { picker: ((props: {
disabled?: boolean | undefined; disabled?: boolean;
entity: keyof EntityDict; entity: keyof EntityDict;
entityFilter: object; entityFilter: object;
relationIds: string[]; relationIds: string[];
@ -9,8 +9,8 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
ruleOnRow: EntityDict['userEntityGrant']['OpSchema']['ruleOnRow']; ruleOnRow: EntityDict['userEntityGrant']['OpSchema']['ruleOnRow'];
onPickRelations: (ids: string[]) => void; onPickRelations: (ids: string[]) => void;
onPickRows: (ids: string[]) => void; onPickRows: (ids: string[]) => void;
pickedRowIds?: string[] | undefined; pickedRowIds?: string[];
pickedRelationIds?: string[] | undefined; pickedRelationIds?: string[];
oakPath: string; oakPath: string;
}) => React.ReactElement) | undefined; }) => React.ReactElement) | undefined;
hideInfo: boolean; 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, { declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2; entity: keyof ED2;
entityId: string; entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"]; redirectToAfterConfirm: ED2['userEntityGrant']['Schema']['redirectTo'];
qrCodeType: string; qrCodeType: string;
showTitle: true; showTitle: true;
showBack: false; showBack: false;

View File

@ -5,8 +5,8 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
entity: keyof ED2; entity: keyof ED2;
entityId: string; entityId: string;
relations: EntityDict['relation']['OpSchema'][]; relations: EntityDict['relation']['OpSchema'][];
passwordRequire?: boolean | undefined; passwordRequire?: boolean;
allowUpdateName?: boolean | undefined; allowUpdateName?: boolean;
allowUpdateNickname?: boolean | undefined; allowUpdateNickname?: boolean;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; 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, { declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2; entity: keyof ED2;
entityId: string; entityId: string;
allowUpdateName?: boolean | undefined; allowUpdateName?: boolean;
allowUpdateNickname?: boolean | undefined; allowUpdateNickname?: boolean;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; 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, { declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2; entity: keyof ED2;
entityId: string; entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"]; redirectToAfterConfirm: ED2['userEntityGrant']['Schema']['redirectTo'];
qrCodeType: QrCodeType; qrCodeType: QrCodeType;
type: EntityDict['userEntityGrant']['Schema']['type']; type: EntityDict['userEntityGrant']['Schema']['type'];
relations: EntityDict['relation']['OpSchema'][]; 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, { declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2; entity: keyof ED2;
entityId: string; entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"]; redirectToAfterConfirm: ED2['userEntityGrant']['Schema']['redirectTo'];
claimUrl: string; claimUrl: string;
qrCodeType: string; qrCodeType: string;
passwordRequire?: boolean | undefined; passwordRequire?: boolean;
allowUpdateName?: boolean | undefined; allowUpdateName?: boolean;
allowUpdateNickname?: boolean | undefined; allowUpdateNickname?: boolean;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

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

View File

@ -6,6 +6,7 @@ export const style = {
ctyun: '#ff0000', ctyun: '#ff0000',
aliyun: '#1677ff', aliyun: '#1677ff',
tencent: '#0052d9', tencent: '#0052d9',
local: '#A9A9A9',
unknown: '#A9A9A9', unknown: '#A9A9A9',
}, },
type: { 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; export default _default;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ exports.style = {
ctyun: '#ff0000', ctyun: '#ff0000',
aliyun: '#1677ff', aliyun: '#1677ff',
tencent: '#0052d9', tencent: '#0052d9',
local: '#A9A9A9',
unknown: '#A9A9A9', unknown: '#A9A9A9',
}, },
type: { 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; export default _default;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,10 +21,11 @@ export function TocView(
fixed?: boolean; fixed?: boolean;
scrollId?: string; scrollId?: string;
closed?: boolean, 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(() => { useEffect(() => {
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor); document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
@ -118,9 +119,12 @@ export function TocView(
}; };
return ( return (
<div className={classNames(Style.tocContainer, { <div
className={classNames(Style.tocContainer, {
[Style.fixed]: fixed [Style.fixed]: fixed
})}> })}
style={Object.assign({}, tocWidth ? { width: tocWidth } : {}, tocHeight ? { height: tocHeight } : {})}
>
{showToc ? ( {showToc ? (
<> <>
<div className={Style.catalogTitle}> <div className={Style.catalogTitle}>

View File

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

View File

@ -157,10 +157,10 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
icon={<EditOutlined />} icon={<EditOutlined />}
size="small" size="small"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); setName(ele.name);
setNameEditing(ele.id); setNameEditing(ele.id);
e.stopPropagation();
}} }}
style={{ marginRight: 4 }}
/> />
<div className={Styles.name}> <div className={Styles.name}>
<div style={{ marginLeft: 4, overflow: 'hidden', width: '150px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ele?.name}</div> <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, //预览文章 onArticlePreview: (content?: string, title?: string) => undefined as void, //预览文章
origin: 'qiniu', // 默认为七牛云 origin: 'qiniu', // 默认为七牛云
scrollId: '', // 滚动条所在容器id不传默认页面编辑器容器id scrollId: '', // 滚动条所在容器id不传默认页面编辑器容器id
height: 600 as number | 'auto',
}, },
listeners: { listeners: {
'editor,content'(prev, next) { 'editor,content'(prev, next) {

View File

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

View File

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useMemo } from "react"; 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 "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react"; import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import { IToolbarConfig, SlateNode } from "@wangeditor/editor"; import { IToolbarConfig, SlateNode } from "@wangeditor/editor";
@ -10,11 +10,10 @@ import classNames from "classnames";
import Prompt from "../../../components/common/prompt"; import Prompt from "../../../components/common/prompt";
import { EntityDict } from "./../../../oak-app-domain"; import { EntityDict } from "./../../../oak-app-domain";
import Style from "./web.module.less"; import Style from "./web.module.less";
import { TocItem, TocView } from '../toc/tocView';
import { import {
CloseOutlined,
EyeOutlined, EyeOutlined,
MenuOutlined,
CaretDownOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
type InsertFnType = (url: string, alt?: string, href?: string) => void; type InsertFnType = (url: string, alt?: string, href?: string) => void;
@ -46,141 +45,6 @@ function customCheckImageFn(
// 3. 返回 undefined即没有任何返回说明检查未通过编辑器会阻止插入。但不会提示任何信息 // 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( export default function Render(
props: WebComponentProps< props: WebComponentProps<
EntityDict, EntityDict,
@ -191,14 +55,16 @@ export default function Render(
name: string; name: string;
editor: any; editor: any;
content?: string; content?: string;
html?: string;
origin?: string; origin?: string;
contentTip: boolean; contentTip: boolean;
articleMenuId: string; articleMenuId: string;
oakId: string; oakId: string;
tocPosition: 'none' | 'left' | 'right'; tocPosition: 'none' | 'left' | 'right';
highlightBgColor: string; highlightBgColor: string;
scrollId?: string scrollId?: string;
tocWidth?: number | 'auto';
tocHeight?: number | 'auto';
height?: number | 'auto';
}, },
{ {
setHtml: (content: string) => void; setHtml: (content: string) => void;
@ -225,19 +91,22 @@ export default function Render(
clearContentTip, clearContentTip,
} = methods; } = methods;
const { const {
oakFullpath,
id, id,
content, content,
editor, editor,
origin = 'qiniu', origin = 'qiniu',
oakFullpath,
html,
tocPosition = 'none', tocPosition = 'none',
highlightBgColor = 'none', highlightBgColor = 'none',
scrollId scrollId,
tocWidth,
tocHeight,
height = 600
} = data; } = data;
const [articleId, setArticleId] = useState(''); const [articleId, setArticleId] = useState('');
const [toc, setToc] = useState<TocItem[]>([]); const [toc, setToc] = useState<TocItem[]>([]);
const [showToc, setShowToc] = useState(false); const [showToc, setShowToc] = useState(false);
const containerId = scrollId || 'article-upsert-editorContainer';
useEffect(() => { useEffect(() => {
if (id) { if (id) {
@ -253,37 +122,42 @@ export default function Render(
return ( return (
<div className={Style.container}> <div className={Style.container}>
<Prompt when={!id || data.oakDirty} message={'您确认离开页面吗?'} /> <Prompt
<div style={{ width: 'calc(100% - 16px)', }}> when={!id || data.oakDirty}
message={"您确认离开页面吗?"}
/>
<div style={{ width: "calc(100% - 16px)" }}>
<Toolbar <Toolbar
editor={editor} editor={editor}
defaultConfig={toolbarConfig} defaultConfig={toolbarConfig}
mode="default" mode="default"
/> />
</div> </div>
<Row gutter={[16, 0]}> <div className={Style.contentContainer}>
{tocPosition === 'left' ? ( {tocPosition === "left" ? (
<Col flex="228px">
<TocView <TocView
toc={toc} toc={toc}
showToc={showToc} showToc={showToc}
tocPosition='left' tocPosition="left"
setShowToc={setShowToc} setShowToc={setShowToc}
highlightBgColor={highlightBgColor} highlightBgColor={highlightBgColor}
scrollId={scrollId} scrollId={containerId}
tocWidth={tocWidth}
tocHeight={tocHeight || height}
/> />
</Col>
) : null} ) : null}
<Col flex="auto"> <div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
<div className={Style.content}> <div
<div id="article-upsert-editorContainer" className={classNames(Style.editorContainer, { id={containerId}
[Style.editorExternalContainer]: !!scrollId className={classNames(Style.editorContainer, {
})}> [Style.editorExternalContainer]: !!scrollId,
})}
>
{data.contentTip && ( {data.contentTip && (
<Alert <Alert
type="info" type="info"
message={t('tips.content')} message={t("tips.content")}
closable closable
onClose={() => clearContentTip()} onClose={() => clearContentTip()}
/> />
@ -294,10 +168,10 @@ export default function Render(
update({ name: e.target.value }) update({ name: e.target.value })
} }
value={data.name} value={data.name}
placeholder={'请输入文章标题'} placeholder={"请输入文章标题"}
size="large" size="large"
maxLength={32} maxLength={32}
suffix={`${[...(data.name || '')].length}/32`} suffix={`${(data.name || "").length}/32`}
className={Style.titleInput} className={Style.titleInput}
/> />
</div> </div>
@ -305,7 +179,7 @@ export default function Render(
<Editor <Editor
defaultConfig={{ defaultConfig={{
autoFocus: true, autoFocus: true,
placeholder: '请输入文章内容...', placeholder: "请输入文章内容...",
MENU_CONF: { MENU_CONF: {
checkImage: customCheckImageFn, checkImage: customCheckImageFn,
uploadImage: { uploadImage: {
@ -316,29 +190,49 @@ export default function Render(
) { ) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = file; const { name, size, type } =
const extension = name.substring(name.lastIndexOf('.') + 1); file;
const filename = name.substring(0,name.lastIndexOf('.')); const extension =
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = { const extraFile = {
entity: 'article', entity: "article",
entityId: articleId, entityId: 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,
} as EntityDict['extraFile']['CreateSingle']['data']; } as EntityDict["extraFile"]["CreateSingle"]["data"];
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) {}
}, },
}, },
@ -350,29 +244,50 @@ export default function Render(
) { ) {
// TS 语法 // TS 语法
// file 即选中的文件 // file 即选中的文件
const { name, size, type } = file; const { name, size, type } =
const extension = name.substring(name.lastIndexOf('.') + 1); file;
const filename = name.substring(0,name.lastIndexOf('.')); const extension =
name.substring(
name.lastIndexOf(
"."
) + 1
);
const filename =
name.substring(
0,
name.lastIndexOf(
"."
)
);
const extraFile = { const extraFile = {
entity: 'article', entity: "article",
entityId: articleId, entityId: articleId,
origin: origin, origin: origin,
type: 'video', type: "video",
tag1: 'source', tag1: "source",
objectId: generateNewId(), objectId:
generateNewId(),
filename, filename,
size, size,
extension, extension,
bucket: '', bucket: "",
id: generateNewId(), id: generateNewId(),
fileType: type fileType: type,
} as EntityDict['extraFile']['CreateSingle']['data']; } as EntityDict["extraFile"]["CreateSingle"]["data"];
try { try {
// 自己实现上传,并得到图片 url alt href // 自己实现上传,并得到图片 url alt href
const url = await uploadFile(extraFile,file); const url =
await uploadFile(
extraFile,
file
);
// 最后插入图片 // 最后插入图片
insertFn(url, url + '?vframe/jpg/offset/0'); insertFn(
url,
url +
"?vframe/jpg/offset/0"
);
} catch (err) {} } catch (err) {}
}, },
}, },
@ -381,16 +296,20 @@ export default function Render(
onCreated={setEditor} onCreated={setEditor}
onChange={(editor) => { onChange={(editor) => {
setHtml(editor.getHtml()); setHtml(editor.getHtml());
const headers = editor.getElemsByTypePrefix('header'); const headers = editor.getElemsByTypePrefix("header");
const tocItems = headers.map((header: any) => { const tocItems = headers.map(
const text = SlateNode.string(header) (header: any) => {
const { id, type } = header const text = SlateNode.string(header);
const { id, type } = header;
return { return {
text, text,
level: parseInt(type.substring(6)), level: parseInt(
type.substring(6)
),
id, id,
}; };
}); }
);
setToc([...tocItems]); setToc([...tocItems]);
}} }}
style={{ style={{
@ -399,11 +318,8 @@ export default function Render(
mode="default" mode="default"
/> />
</div> </div>
</div> </div>
<div className={Style.footer}> <div className={Style.footer}>
<Row align="middle">
<Col flex="none">
<Space> <Space>
<Button <Button
disabled={ disabled={
@ -419,34 +335,28 @@ export default function Render(
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
gotoPreview( gotoPreview(content, data.name);
content,
data.name
);
}} }}
icon={<EyeOutlined />}
> >
<EyeOutlined />
</Button> </Button>
</Space> </Space>
</Col>
</Row>
</div> </div>
</div> </div>
</Col> {tocPosition === "right" ? (
{tocPosition === 'right' ? (
<Col flex="228px">
<TocView <TocView
toc={toc} toc={toc}
showToc={showToc} showToc={showToc}
tocPosition='right' tocPosition="right"
setShowToc={setShowToc} setShowToc={setShowToc}
highlightBgColor={highlightBgColor} highlightBgColor={highlightBgColor}
scrollId={scrollId} scrollId={containerId}
tocWidth={tocWidth}
tocHeight={tocHeight}
/> />
</Col>
) : null} ) : null}
</Row> </div>
</div> </div>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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