shenwu 6 months ago
parent
commit
635f799ff2

File diff suppressed because it is too large
+ 1 - 1
src/components/TextEditor/Expression.tsx


+ 0 - 1
src/components/TextEditor/index.less

@@ -4,7 +4,6 @@
   display: flex;
   position: relative;
   padding-bottom: 10px;
-
   .box {
     width: 420px;
     display: flex;

+ 58 - 24
src/components/TextEditor/index.tsx

@@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react"
 import Expression, { emoList } from "./Expression";
 const { Text } = Typography
 import style from './index.less'
+import AddLink from "@/pages/MiniApp/EntWeChat/Welcome/components/link";
 
 type Props = {
     /**是否展示底部*/
@@ -46,10 +47,12 @@ export function TextEditor(props: Props) {
                         }
                     })
                 }
-                setText(newValue?.replaceAll('\n', '<br/>'))
+                setText(newValue)//?.replaceAll('\n', '<br/>')
             } else {
                 setText(value?.innerHTML)
             }
+        } else {//不存在内容设置焦点
+            ref?.current?.focus()
         }
     }, [value])
 
@@ -60,20 +63,20 @@ export function TextEditor(props: Props) {
             let range = selection?.getRangeAt(0)
             setRange(range)
             //点击表情不更新
-            if (!e.relatedTarget.className.includes('myEmo')) {
-                onChange?.(ref.current)
+            if (!e?.relatedTarget?.className?.match(/(myEmo)|(textEditor_btn)/g)) {
+                onChange?.(ref.current?.innerHTML)
             }
         } catch (err) {
         }
     }
-    // 插入按钮
-    const btnClick = useCallback((btnName: string) => {
+    // 自定义文本插入按钮
+    const btnClick = useCallback((btnName: any) => {
         if (range) {
-            let str = "" //` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">${btnName}<strong data-name="${btnName}" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=${btnName}]').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=${btnName}]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `
+            let str = btnName //` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">${btnName}<strong data-name="${btnName}" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=${btnName}]').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=${btnName}]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `
             let selection = window.getSelection()
             selection?.empty()//清空选择range
             selection?.addRange(range as Range)//插入新的range
-            document.execCommand('insertHTML', false, str);//插入内容
+            document.execCommand('insertHTML', false, btnName);//插入内容
             range.collapse()
             selection?.collapseToEnd()//光标插入到末尾
         } else {
@@ -91,7 +94,14 @@ export function TextEditor(props: Props) {
             document.execCommand('insertHTML', false, str);
         }
     }, [])
-
+    const addLink = useCallback((str: string, range: Range) => {
+        if (range) {
+            let selection = window.getSelection()
+            selection?.empty()//清空选择range
+            selection?.addRange(range)//插入新的range
+            document.execCommand('insertHTML', false, str);
+        }
+    }, [])
     //初始化设置
     useEffect(() => {
         if (initBtnNames && initBtnNames?.length > 0) {
@@ -119,13 +129,41 @@ export function TextEditor(props: Props) {
             document.execCommand('insertHTML', false, text);
         }
     }
+    const insertHtmlAtCursor = (html: string) => {
+        const selection = window.getSelection();
+        if (!selection || selection.rangeCount === 0) return;
+
+        const range = selection.getRangeAt(0);
+        range.deleteContents(); // 删除光标处的内容
+
+        const tempDiv = document.createElement('div');
+        tempDiv.innerHTML = html; // 创建包含 HTML 的临时元素
+        const frag = document.createDocumentFragment();
+        
+        // 将临时元素的子元素添加到文档片段中
+        while (tempDiv.firstChild) {
+            frag.appendChild(tempDiv.firstChild);
+        }
+        range.insertNode(frag); // 将内容插入光标位置
+        // 重新设置光标位置
+        range.collapse(false);
+        selection.removeAllRanges();
+        selection.addRange(range);
+    };
     return <List
+        size="small"
         header={<div style={{ display: 'flex', justifyContent: 'space-between' }}>
-            <Space wrap>
-                {emo && <Expression addEmo={addEmo} range={range as Range} />}
+            <Space wrap size={[20, 0]}>
+                {/* {emo && <Expression addEmo={addEmo} range={range as Range} />} */}
                 {btnNames?.map(name => {
-                    return <Button onClick={() => { btnClick(name) }} key={name}>{name}</Button>
+                    return <Button className="textEditor_btn" size="small" onClick={() => { btnClick(name) }} key={name}>{name}</Button>
                 })}
+                {/* <AddLink onChange={(value) => {
+                    if (value) {
+                        addEmo("1", range as any)
+                    }
+                }} /> */}
+                <button onClick={() => insertHtmlAtCursor('<a href="https://example.com" target="_blank">https://example.com</a>')}>插入链接</button>
             </Space>
             {maxStr && <Typography.Text type='secondary'><Typography.Text type={strLength > maxStr ? 'danger' : 'secondary'} >{strLength}</Typography.Text>/{maxStr}</Typography.Text>}
             {/* <Button type="link">复制</Button> */}
@@ -134,7 +172,7 @@ export function TextEditor(props: Props) {
         bordered
         dataSource={['']}
         renderItem={(item) => (
-            <List.Item>
+            <List.Item style={{ padding: 5 }}>
                 <pre
                     className={style.myPre}
                     style={{
@@ -148,26 +186,22 @@ export function TextEditor(props: Props) {
                         textAlign: 'left',
                         wordBreak: 'break-all'
                     }}
-                    onInput={(e) => {
-                        if (isOnInput) {
-                            onChange?.(e.target)
-                            // setText((e.target as any).innerHTML)
-                        }
-                    }}
                     contentEditable="true"
                     dangerouslySetInnerHTML={{
                         __html: text
                     }}
                     ref={ref}
-                    onKeyDown={(e: React.KeyboardEvent<HTMLPreElement>) => {
-                        if (e.key === 'Enter') {
-                            document.execCommand('insertHTML', false, '<br/>\n')
-                            e.preventDefault()
-                        }
+                    // onKeyDown={(e: React.KeyboardEvent<HTMLPreElement>) => {
+                    //     if (e.key === 'Enter') {
+                    //         e.preventDefault()
+                    //         document.execCommand('insertHTML', false, '\n')
+                    //     }
+                    // }}
+                    onInput={(e: any) => {
+
                     }}
                     onKeyUp={(e: React.KeyboardEvent<HTMLPreElement>) => {
                         if (e.key === 'Enter') {
-                            e.preventDefault()
                         } else {
                             if (maxStr) {
                                 let childrens = (e.target as any).childNodes

+ 117 - 0
src/components/TextEditor/index1.tsx

@@ -0,0 +1,117 @@
+import { Button } from 'antd';
+import React, { useState, useRef, useEffect } from 'react';
+import Expression from './Expression';
+type Props = {
+    value?: any,
+    onChange?: (v: any) => void
+}
+const EditablePre: React.FC<Props> = (props) => {
+    const { value, onChange } = props
+    const editableDivRef = useRef<HTMLDivElement>(null);
+    const [savedRange, setSavedRange] = useState<Range | null>(null); // 用于存储光标位置
+    //内容变更
+    const handleInput = () => {
+        console.log(11111)
+        if (editableDivRef.current) {
+            const newStr = editableDivRef.current.innerHTML
+                .replace(/<div[^>]*>(\s|&nbsp;)*<\/div>/g, '') // 移除空的 <div> 标签
+                .replace(/<div[^>]*>/g, '') // 将 <div> 替换为 ""
+                .replace(/<\/div>/g, '\n') // 将 </div> 替换为 "\n"
+                .replace(/<br\s*\/?>/g, '\n') // 替换 <br> 为换行符
+            console.log(newStr)
+            onChange?.(newStr);
+        }
+    };
+    // 记录光标位置
+    const handleFocusLoss = () => {
+        const selection: any = window.getSelection();
+        if (selection?.rangeCount > 0) {
+            setSavedRange(selection.getRangeAt(0)); // 记录当前光标位置
+        }
+    };
+    const insertAtSavedPosition = (html: string) => {
+        if (!savedRange) return; // 如果没有记录的光标位置,则不执行
+    
+        const tempDiv = document.createElement('div');
+        tempDiv.innerHTML = html; // 创建包含 HTML 的临时元素
+        const frag = document.createDocumentFragment();
+    
+        // 将临时元素的子元素添加到文档片段中
+        while (tempDiv.firstChild) {
+            frag.appendChild(tempDiv.firstChild);
+        }
+    
+        // 插入内容到记录的光标位置
+        savedRange.deleteContents(); // 删除光标处的内容
+        savedRange.insertNode(frag); // 将内容插入光标位置
+    
+        // 重新设置光标位置在插入内容之后
+        if (frag.lastChild) {
+            // 设置光标在插入节点后
+            const selection:any = window.getSelection();
+            selection.removeAllRanges();
+            savedRange.setStartAfter(frag.lastChild); // 设置光标在插入节点后
+            savedRange.collapse(true); // 折叠到新的光标位置
+            selection.addRange(savedRange); // 将新的范围添加到选区
+        }
+    };
+    const insertHtmlAtCursor = (html: string) => {
+        const selection = window.getSelection();
+        if (!selection || selection.rangeCount === 0) return;
+
+        const range = selection.getRangeAt(0);
+        range.deleteContents(); // 删除光标处的内容
+
+        const tempDiv = document.createElement('div');
+        tempDiv.innerHTML = html; // 创建包含 HTML 的临时元素
+        const frag = document.createDocumentFragment();
+
+        // 将临时元素的子元素添加到文档片段中
+        while (tempDiv.firstChild) {
+            frag.appendChild(tempDiv.firstChild);
+        }
+        range.insertNode(frag); // 将内容插入光标位置
+
+        // 重新设置光标位置
+        range.collapse(false);
+        selection.removeAllRanges();
+        selection.addRange(range);
+        handleInput()
+    };
+
+    useEffect(() => {
+        if (value) {
+            let newHtml = value?.split('\n')
+                .map((line: any, index: any) => `<div>${line}</div>`)
+                .join('');
+            if (editableDivRef.current) {
+                editableDivRef.current.innerHTML = newHtml.replace(/<div[^>]*>(\s|&nbsp;)*<\/div>/g, '')
+            }
+        }
+    }, [])
+    return (
+        <div>
+            <div
+                ref={editableDivRef}
+                contentEditable
+                onInput={handleInput}
+                onBlur={handleFocusLoss}
+                style={{
+                    border: '1px solid #ccc',
+                    padding: '10px',
+                    minHeight: '100px',
+                    overflowY: 'auto',
+                    whiteSpace: 'pre-wrap',
+                    outline: 'none',
+                }}
+            >
+                {/* 初始内容可以在此处放置 */}
+            </div>
+            <Expression addEmo={insertAtSavedPosition} range={savedRange as Range} />
+            <Button onClick={() => insertHtmlAtCursor('<a href="https://example.com" target="_blank">https://example.com</a>')}>插入链接</Button>
+            <Button onClick={() => insertHtmlAtCursor('@用户昵称')}>@用户昵称</Button>
+        </div>
+    );
+};
+
+export default EditablePre;

+ 19 - 3
src/pages/MiniApp/EntWeChat/Welcome/content.tsx

@@ -4,6 +4,8 @@ import { ProForm, ProFormSelect, ProFormText, ProFormTextArea } from "@ant-desig
 import { useModel } from "@umijs/max"
 import UploadImg from "@/components/uploadImg"
 import AddLink from "./components/link"
+import { TextEditor } from "@/components/TextEditor"
+import EditablePre from "@/components/TextEditor/index1"
 
 
 const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v: any) => void }, ref) => {
@@ -74,7 +76,21 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
                 let mediaType = getFieldValue("mediaType")
                 switch (mediaType) {
                     case "text":
-                        return <>文本</>
+                        return <div>
+                            <Form.Item label="文本内容" name="textContent" rules={[
+                                {
+                                    required: true,
+                                    message: '此项为必填项',
+                                }
+                            ]}>
+                                {/* <TextEditor
+                                    footer={null}
+                                    btnNames={["@用户昵称"]}
+                                    initBtnNames={[]}
+                                /> */}
+                                <EditablePre />
+                            </Form.Item>
+                        </div>
                     case "link":
                         return <div>
                             <ProFormText
@@ -113,7 +129,7 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
                                     type="image"
                                 />
                             </Form.Item>
-                            <Form.Item label="链接" name="linkUrl" rules={[
+                            <Form.Item label="链接" name="advertisingLink" rules={[
                                 {
                                     required: true,
                                     message: '此项为必填项',
@@ -162,7 +178,7 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
                                     isCropper
                                 />
                             </Form.Item>
-                            <Form.Item label="链接" name="miniprogramPage" rules={[
+                            <Form.Item label="链接" name="advertisingLink" rules={[
                                 {
                                     required: true,
                                     message: '此项为必填项',

+ 4 - 1
src/pages/MiniApp/EntWeChat/Welcome/index.tsx

@@ -25,8 +25,11 @@ const Page: React.FC = () => {
     // 切换tab获取对应数据
     useEffect(() => {
         if (key) {
-            getConfig()
+            setTimeout(()=>{
+                getConfig()
+            },100)
         }
+    
     }, [key])
     // 获取配置
     let getConfig = () => {

Some files were not shown because too many files changed in this diff