|
@@ -6,6 +6,7 @@ import React, {
|
|
|
forwardRef,
|
|
forwardRef,
|
|
|
useImperativeHandle,
|
|
useImperativeHandle,
|
|
|
} from "react";
|
|
} from "react";
|
|
|
|
|
+import { Spin } from 'antd';
|
|
|
import HintBox from "./HintBox";
|
|
import HintBox from "./HintBox";
|
|
|
import styles from "./fomular.module.less";
|
|
import styles from "./fomular.module.less";
|
|
|
import { moveInTextNode, isFirefox } from "./utils";
|
|
import { moveInTextNode, isFirefox } from "./utils";
|
|
@@ -19,6 +20,7 @@ const Formular = (props, ref) => {
|
|
|
const [cursorHtmlRange, setCursorHtmlRange] = useState();
|
|
const [cursorHtmlRange, setCursorHtmlRange] = useState();
|
|
|
const [formularText, setFormularText] = useState();
|
|
const [formularText, setFormularText] = useState();
|
|
|
const [formularHtml, setFormularHtml] = useState();
|
|
const [formularHtml, setFormularHtml] = useState();
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
// const [globalRange, setGlobalRange] = useState();
|
|
// const [globalRange, setGlobalRange] = useState();
|
|
|
const rangeCurrent = useRef(); //历史光标range位置
|
|
const rangeCurrent = useRef(); //历史光标range位置
|
|
|
const globalRangeRef = useRef(); //全局range的记录
|
|
const globalRangeRef = useRef(); //全局range的记录
|
|
@@ -103,18 +105,23 @@ const Formular = (props, ref) => {
|
|
|
var match = inputStr.match(regx);
|
|
var match = inputStr.match(regx);
|
|
|
match = match.map((item) => item.replace("[", "").replace("]", ""));
|
|
match = match.map((item) => item.replace("[", "").replace("]", ""));
|
|
|
// const res = await API.energySearchByPointsIds(match)
|
|
// const res = await API.energySearchByPointsIds(match)
|
|
|
- const res = await API2.searchPoint({
|
|
|
|
|
|
|
+ setLoading(true)
|
|
|
|
|
+ API2.searchPoint({
|
|
|
point_ids: match,
|
|
point_ids: match,
|
|
|
count: 20,
|
|
count: 20,
|
|
|
type: 1,
|
|
type: 1,
|
|
|
- });
|
|
|
|
|
- if (res.state === 0) {
|
|
|
|
|
- res.data?.forEach((item) => {
|
|
|
|
|
- vars[item.point_id] = item.name;
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
- vars = {};
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ }).then(res => {
|
|
|
|
|
+ if (res.state === 0) {
|
|
|
|
|
+ res.data?.forEach((item) => {
|
|
|
|
|
+ vars[item.point_id] = item.name;
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ vars = {};
|
|
|
|
|
+ }
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ setLoading(false)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
console.log(vars);
|
|
console.log(vars);
|
|
|
initDisplay({ vars: vars, formula: inputStr });
|
|
initDisplay({ vars: vars, formula: inputStr });
|
|
|
outStrRef.current = inputStr;
|
|
outStrRef.current = inputStr;
|
|
@@ -302,7 +309,7 @@ const Formular = (props, ref) => {
|
|
|
const setValue = () => {
|
|
const setValue = () => {
|
|
|
let formula = "";
|
|
let formula = "";
|
|
|
const vars = {};
|
|
const vars = {};
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const processNode = (node) => {
|
|
const processNode = (node) => {
|
|
|
if (node.dataset?.point_id) {
|
|
if (node.dataset?.point_id) {
|
|
|
// 处理点位节点
|
|
// 处理点位节点
|
|
@@ -324,9 +331,9 @@ const Formular = (props, ref) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
formulaRef.current.childNodes.forEach(processNode);
|
|
formulaRef.current.childNodes.forEach(processNode);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const res = {
|
|
const res = {
|
|
|
formula,
|
|
formula,
|
|
|
vars,
|
|
vars,
|
|
@@ -335,14 +342,138 @@ const Formular = (props, ref) => {
|
|
|
console.log(outStrRef.current);
|
|
console.log(outStrRef.current);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const handlePaste = useCallback((e) => {
|
|
|
|
|
- // 允许粘贴,但在粘贴后更新内容
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- setValue();
|
|
|
|
|
- updateCursorLocation();
|
|
|
|
|
- }, 0);
|
|
|
|
|
|
|
+ const cleanPastedText = useCallback((text) => {
|
|
|
|
|
+ if (!text || typeof text !== 'string') return ''
|
|
|
|
|
+
|
|
|
|
|
+ /** 检测是否包含HTML标签和换行符(用于调试日志) */
|
|
|
|
|
+ const hasHtmlTags = /<[^>]*>/g.test(text)
|
|
|
|
|
+ const hasLineBreaks = /[\r\n\t]+/g.test(text)
|
|
|
|
|
+
|
|
|
|
|
+ if (hasHtmlTags) {
|
|
|
|
|
+ console.warn('检测到粘贴内容包含HTML标签,已自动清理')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hasLineBreaks) {
|
|
|
|
|
+ console.warn('检测到粘贴内容包含换行符,已转换为空格')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return text
|
|
|
|
|
+ /** 移除零宽字符 */
|
|
|
|
|
+ .replace(/[\u200B-\u200D\uFEFF]/g, '')
|
|
|
|
|
+ /** 移除控制字符 */
|
|
|
|
|
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '')
|
|
|
|
|
+ /** 移除HTML标签 */
|
|
|
|
|
+ .replace(/<[^>]*>/g, '')
|
|
|
|
|
+ /** 统一处理所有空白字符:换行符、制表符、各种空格 → 单个普通空格 */
|
|
|
|
|
+ .replace(/[\r\n\t\s\u00A0\u2000-\u200A\u202F\u205F\u3000]+/g, ' ')
|
|
|
|
|
+ /** 移除首尾空格 */
|
|
|
|
|
+ .trim()
|
|
|
|
|
+ }, [])
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 异步获取点位信息并转换文本为HTML,[point_id]转换为img节点,其他保持为文本
|
|
|
|
|
+ * @param {string} text - 输入文本,如 "[123]+[456]"
|
|
|
|
|
+ * @returns {Promise<string>} - 转换后的HTML字符串
|
|
|
|
|
+ */
|
|
|
|
|
+ const convertTextToHtmlWithApi = useCallback(async (text) => {
|
|
|
|
|
+ // 匹配 [point_id] 模式
|
|
|
|
|
+ const pointPattern = /\[([^\[\]]+)\]/g;
|
|
|
|
|
+
|
|
|
|
|
+ let match;
|
|
|
|
|
+ const matches = [];
|
|
|
|
|
+ const pointIds = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 收集所有匹配项
|
|
|
|
|
+ while ((match = pointPattern.exec(text)) !== null) {
|
|
|
|
|
+ matches.push({
|
|
|
|
|
+ fullMatch: match[0], // [123]
|
|
|
|
|
+ pointId: match[1], // 123
|
|
|
|
|
+ index: match.index
|
|
|
|
|
+ });
|
|
|
|
|
+ pointIds.push(match[1]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (pointIds.length === 0) {
|
|
|
|
|
+ // 没有点位变量,直接返回原文本
|
|
|
|
|
+ return text;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 调用API获取点位信息
|
|
|
|
|
+ let pointVars = {};
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await API2.searchPoint({
|
|
|
|
|
+ point_ids: pointIds,
|
|
|
|
|
+ count: 20,
|
|
|
|
|
+ type: 1,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (res.state === 0) {
|
|
|
|
|
+ res.data?.forEach((item) => {
|
|
|
|
|
+ pointVars[item.point_id] = item.name;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取点位信息失败:', error);
|
|
|
|
|
+ // 如果API调用失败,使用point_id作为name
|
|
|
|
|
+ pointIds.forEach(id => {
|
|
|
|
|
+ pointVars[id] = id;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let result = text;
|
|
|
|
|
+
|
|
|
|
|
+ // 从后往前替换,避免索引位置变化
|
|
|
|
|
+ for (let i = matches.length - 1; i >= 0; i--) {
|
|
|
|
|
+ const matchItem = matches[i];
|
|
|
|
|
+ const pointName = pointVars[matchItem.pointId] || matchItem.pointId;
|
|
|
|
|
+ const imgHtml = oneNode(matchItem.pointId, pointName);
|
|
|
|
|
+ result = result.substring(0, matchItem.index) +
|
|
|
|
|
+ imgHtml +
|
|
|
|
|
+ result.substring(matchItem.index + matchItem.fullMatch.length);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
+ const handlePaste = useCallback(async (e) => {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ /** 获取剪贴板数据 */
|
|
|
|
|
+ const clipboardData = e.clipboardData
|
|
|
|
|
+ if (!clipboardData) return false
|
|
|
|
|
+
|
|
|
|
|
+ /** 获取并清理粘贴的文本内容 */
|
|
|
|
|
+ const rawText = clipboardData.getData('text/plain')
|
|
|
|
|
+ const cleanedText = cleanPastedText(rawText)
|
|
|
|
|
+ /** 如果清理后没有有效文本内容,使用默认行为或阻止粘贴 */
|
|
|
|
|
+ if (!cleanedText) {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log('原始文本:', cleanedText);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ setLoading(true);
|
|
|
|
|
+
|
|
|
|
|
+ /** 异步获取点位信息并转换为HTML格式 */
|
|
|
|
|
+ const htmlContent = await convertTextToHtmlWithApi(cleanedText);
|
|
|
|
|
+ console.log('转换后的HTML:', htmlContent);
|
|
|
|
|
+
|
|
|
|
|
+ /** 将转换后的HTML插入到编辑器中 */
|
|
|
|
|
+ dtInsertFormular(htmlContent);
|
|
|
|
|
+
|
|
|
|
|
+ /** 更新输出值 */
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ setValue();
|
|
|
|
|
+ }, 0);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('粘贴处理失败:', error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }, [convertTextToHtmlWithApi, cleanPastedText]);
|
|
|
|
|
+
|
|
|
const handleCopy = useCallback((e) => {
|
|
const handleCopy = useCallback((e) => {
|
|
|
// 允许复制操作
|
|
// 允许复制操作
|
|
|
updateCursorLocation();
|
|
updateCursorLocation();
|
|
@@ -358,30 +489,32 @@ const Formular = (props, ref) => {
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div className={styles.wrapper}>
|
|
<div className={styles.wrapper}>
|
|
|
- <div
|
|
|
|
|
- id="formulaId"
|
|
|
|
|
- ref={formulaRef}
|
|
|
|
|
- className="editor"
|
|
|
|
|
- contentEditable
|
|
|
|
|
- onClick={handleClick}
|
|
|
|
|
- onKeyUp={handleKeyUp}
|
|
|
|
|
- onKeyDown={handleKeyDown}
|
|
|
|
|
- onCut={handleCut}
|
|
|
|
|
- onBlur={divBlur}
|
|
|
|
|
- onPaste={handlePaste}
|
|
|
|
|
- onCopy={handleCopy}
|
|
|
|
|
- ></div>
|
|
|
|
|
- <div>
|
|
|
|
|
- <HintBox
|
|
|
|
|
- visible={hintVisible}
|
|
|
|
|
- setVis={() => setHintVisible(false)}
|
|
|
|
|
- // hintData={pointData}
|
|
|
|
|
- queryString={queryString}
|
|
|
|
|
- position={position}
|
|
|
|
|
- onPickPoint={handlePickPoint}
|
|
|
|
|
- onClickPoint={handlePickPoint}
|
|
|
|
|
- ></HintBox>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <Spin spinning={loading}>
|
|
|
|
|
+ <div
|
|
|
|
|
+ id="formulaId"
|
|
|
|
|
+ ref={formulaRef}
|
|
|
|
|
+ className="editor"
|
|
|
|
|
+ contentEditable
|
|
|
|
|
+ onClick={handleClick}
|
|
|
|
|
+ onKeyUp={handleKeyUp}
|
|
|
|
|
+ onKeyDown={handleKeyDown}
|
|
|
|
|
+ onCut={handleCut}
|
|
|
|
|
+ onBlur={divBlur}
|
|
|
|
|
+ onPaste={handlePaste}
|
|
|
|
|
+ onCopy={handleCopy}
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <HintBox
|
|
|
|
|
+ visible={hintVisible}
|
|
|
|
|
+ setVis={() => setHintVisible(false)}
|
|
|
|
|
+ // hintData={pointData}
|
|
|
|
|
+ queryString={queryString}
|
|
|
|
|
+ position={position}
|
|
|
|
|
+ onPickPoint={handlePickPoint}
|
|
|
|
|
+ onClickPoint={handlePickPoint}
|
|
|
|
|
+ ></HintBox>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Spin>
|
|
|
{/* <button onClick={backEndInput}>后端输入</button>
|
|
{/* <button onClick={backEndInput}>后端输入</button>
|
|
|
<button onClick={()=>dtInsertFormular('()')}>输入字符</button> */}
|
|
<button onClick={()=>dtInsertFormular('()')}>输入字符</button> */}
|
|
|
</div>
|
|
</div>
|