This commit is contained in:
ggchinazhangwei 2025-04-13 21:47:45 +08:00
parent 3720aaa471
commit 2a66dff0e0

View File

@ -1,6 +1,5 @@
import React, { useMemo } from "react"; import React, { useMemo, useState, useCallback } from "react";
import { Controlled } from "react-codemirror2"; import { Controlled } from "react-codemirror2";
import { useState } from "react";
import { Button, message } from "antd"; import { Button, message } from "antd";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import Logo from "@/assets/logo.png"; import Logo from "@/assets/logo.png";
@ -13,7 +12,7 @@ require("codemirror/mode/javascript/javascript");
const serverUrl = isDev ? "http://localhost:3000" : "http://localhost:3000"; const serverUrl = isDev ? "http://localhost:3000" : "http://localhost:3000";
let html = `<!DOCTYPE html> const defaultHtml = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -48,97 +47,97 @@ let html = `<!DOCTYPE html>
</html> </html>
`; `;
export default function() { export default function CodeEditor() {
const [isUpdate, setUpdate] = useState(false); const [htmlContent, setHtmlContent] = useState(defaultHtml);
const [cursor, setCursor] = useState<CodeMirror.Position>({ line: 1, ch: 1 }); const [cursorPosition, setCursorPosition] = useState({ line: 1, ch: 1 });
const [data, setData] = useState<{ data: string }>({ data: html }); const [isUpdated, setIsUpdated] = useState(false);
const handleChange = (
const rect = useGetRect();
// 计算高度
const contentHeight = useMemo(() => {
const baseHeight = rect.height - 42 - 1; // 减去顶部高度和滚动条防溢出
return Math.max(baseHeight, 694); // 最小高度为 694
}, [rect.height]);
const phoneHeight = 694; // 固定高度,避免大屏幕问题
const iframeHeight = phoneHeight - 30 - 24; // 上边距 30上下 padding 各 12
// 保存页面
const savePage = useCallback(
(content?: string) => {
const contentToSave = content ?? htmlContent;
fetch(`${serverUrl}/dooring/render`, {
method: "POST",
body: contentToSave,
}).then(() => {
message.success("已保存");
setIsUpdated((prev) => !prev); // 触发重新渲染
});
},
[htmlContent]
);
// 下载 HTML 文件
const downloadHtml = useCallback(() => {
const file = new File([htmlContent], `${Date.now()}.html`, {
type: "text/html;charset=utf-8",
});
saveAs(file);
}, [htmlContent]);
// 快捷键保存
useHotkeys(
"ctrl+s",
(event) => {
savePage();
event.preventDefault();
},
[savePage]
);
// CodeMirror 编辑器事件处理
const handleCodeChange = (
_editor: CodeMirror.Editor, _editor: CodeMirror.Editor,
_data: CodeMirror.EditorChange, _data: CodeMirror.EditorChange,
value: string value: string
) => { ) => {
setData({ data: value }); setHtmlContent(value);
};
const fetchPage = useMemo(() => {
return (v?: string) => {
let res = v ?? data.data;
fetch(`${serverUrl}/dooring/render`, { method: "POST", body: res }).then(
() => {
html = res;
message.success("已保存");
setUpdate(prev => !prev);
}
);
};
}, [data]);
const downLoadHtml = () => {
var file = new File([data.data], `${Date.now()}.html`, {
type: "text/html;charset=utf-8"
});
saveAs(file);
}; };
const onCursorChange = ( const handleCursorChange = (
_editor: CodeMirror.Editor, _editor: CodeMirror.Editor,
data1: CodeMirror.Position position: CodeMirror.Position
) => { ) => {
const { line, ch } = data1; setCursorPosition(position);
setCursor({ line, ch });
}; };
useHotkeys<HTMLDivElement>( const handleEditorKeyDown = (editor: CodeMirror.Editor, event: KeyboardEvent) => {
"ctrl+s",
event => {
fetchPage();
event.preventDefault();
},
[data]
);
const editHotKey = useMemo(() => {
return (editor: CodeMirror.Editor, event: KeyboardEvent) => {
if (event.ctrlKey && event.key === "s") { if (event.ctrlKey && event.key === "s") {
fetchPage(editor.getValue()); savePage(editor.getValue());
event.preventDefault(); event.preventDefault();
} }
}; };
}, [fetchPage]);
const CodeMirrorRender = useMemo(() => { // CodeMirror 渲染
return ( const codeMirrorRender = useMemo(() => (
<Controlled <Controlled
className={styles.codeWrap} className={styles.codeWrap}
value={data.data} value={htmlContent}
options={{ options={{
mode: "xml", mode: "xml",
theme: "material", theme: "material",
lineNumbers: true lineNumbers: true,
}} }}
onBeforeChange={handleChange} onBeforeChange={handleCodeChange}
cursor={cursor} onCursor={handleCursorChange}
onCursor={onCursorChange} onKeyDown={handleEditorKeyDown}
onKeyDown={editHotKey}
/> />
); ), [htmlContent]);
}, [cursor, data.data, editHotKey]);
const rect = useGetRect();
const height = useMemo(() => {
let res = rect.height - 42 - 1; //-1防止差值产生滚动条
return res < 694 ? 694 : res;
}, [rect.height]);
const phoneHeight = useMemo(() => {
//let res = rect.height - 42 - 30 - 1; //30是其上边距
//return res < 694 ? 694 : res;
return 694; //大屏幕过长,维持高度,需要变高另外处理
}, []);
const iframeHeight = useMemo(() => {
return phoneHeight - 30 - 12 - 12; //上边距30 上下padding 12
}, [phoneHeight]);
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
{/* Header */}
<div className={styles.header}> <div className={styles.header}>
<div className={styles.logoArea}> <div className={styles.logoArea}>
<div className={styles.logo} title="Dooring"> <div className={styles.logo} title="Dooring">
@ -149,50 +148,36 @@ export default function() {
<div className={styles.logoText}>| 线</div> <div className={styles.logoText}>| 线</div>
</div> </div>
<div className={styles.operationBar}> <div className={styles.operationBar}>
<Button <Button type="primary" title="保存ctrl+s" onClick={savePage} style={{ marginRight: "10px" }}>
type="primary"
title="保存ctrl+s"
onClick={() => fetchPage()}
style={{ marginRight: "10px" }}
>
<SaveOutlined /> <SaveOutlined />
</Button> </Button>
<Button <Button type="primary" onClick={downloadHtml} style={{ marginRight: "10px" }}>
type="primary"
onClick={downLoadHtml}
style={{ marginRight: "10px" }}
>
</Button> </Button>
<Button danger onClick={downLoadHtml}> <Button danger onClick={downloadHtml}>
</Button> </Button>
</div> </div>
</div> </div>
<div
className={styles.contentWrap} {/* 内容区域 */}
style={{ height: `${height}px`, position: "relative" }} <div className={styles.contentWrap} style={{ height: `${contentHeight}px`, position: "relative" }}>
> {/* 代码编辑器 */}
<div <div className={styles.codeWrap} style={{ height: `${contentHeight}px`, position: "relative" }}>
className={styles.codeWrap} {codeMirrorRender}
style={{ height: `${height}px`, position: "relative" }}
>
{CodeMirrorRender}
</div> </div>
<div {/* 预览区域 */}
className={styles.previewWrap} <div className={styles.previewWrap} style={{ height: `${phoneHeight}px` }}>
style={{ height: `${phoneHeight}px` }}
>
<iframe <iframe
title="preview" title="preview"
src={`${serverUrl}/html?flag=${isUpdate}`} src={`${serverUrl}/html?flag=${isUpdated}`}
style={{ style={{
width: "100%", width: "100%",
height: `${iframeHeight}px`, height: `${iframeHeight}px`,
margin: 0, margin: 0,
padding: 0, padding: 0,
border: "none" border: "none",
}} }}
></iframe> ></iframe>
</div> </div>