Bladeren bron

新增图片显示

sunsheng 2 maanden geleden
bovenliggende
commit
77bec64598

+ 316 - 0
package-lock.json

@@ -19,6 +19,7 @@
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
         "react-markdown": "^10.1.0",
+        "react-quill": "^2.0.0",
         "react-router-dom": "^7.1.0"
       },
       "devDependencies": {
@@ -1866,6 +1867,15 @@
       "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.14.tgz",
       "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
     },
+    "node_modules/@types/quill": {
+      "version": "1.3.10",
+      "resolved": "https://registry.npmmirror.com/@types/quill/-/quill-1.3.10.tgz",
+      "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
+      "license": "MIT",
+      "dependencies": {
+        "parchment": "^1.1.2"
+      }
+    },
     "node_modules/@types/react": {
       "version": "18.3.18",
       "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.18.tgz",
@@ -2266,6 +2276,24 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/call-bind": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz",
+      "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.0",
+        "es-define-property": "^1.0.0",
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/call-bind-apply-helpers": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -2279,6 +2307,22 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
@@ -2363,6 +2407,15 @@
       "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
       "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
     },
+    "node_modules/clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -2505,6 +2558,60 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/deep-equal": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz",
+      "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-arguments": "^1.1.1",
+        "is-date-object": "^1.0.5",
+        "is-regex": "^1.1.4",
+        "object-is": "^1.1.5",
+        "object-keys": "^1.1.1",
+        "regexp.prototype.flags": "^1.5.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/define-properties": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
+      "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.0.1",
+        "has-property-descriptors": "^1.0.0",
+        "object-keys": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -2746,6 +2853,12 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "license": "MIT"
     },
+    "node_modules/fast-diff": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz",
+      "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
+      "license": "Apache-2.0"
+    },
     "node_modules/fdir": {
       "version": "6.4.6",
       "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
@@ -2840,6 +2953,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/functions-have-names": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz",
+      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/gensync": {
       "version": "1.0.0-beta.2",
       "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2920,6 +3042,18 @@
         "node": ">=8"
       }
     },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/has-symbols": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -3114,6 +3248,22 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/is-arguments": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz",
+      "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "has-tostringtag": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -3135,6 +3285,22 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/is-date-object": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz",
+      "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "has-tostringtag": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-decimal": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz",
@@ -3167,6 +3333,24 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-regex": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz",
+      "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "gopd": "^1.2.0",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-what": {
       "version": "3.14.1",
       "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz",
@@ -4153,6 +4337,37 @@
       "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
       "dev": true
     },
+    "node_modules/object-is": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz",
+      "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/parchment": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz",
+      "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -4358,6 +4573,40 @@
       ],
       "license": "MIT"
     },
+    "node_modules/quill": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz",
+      "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "clone": "^2.1.1",
+        "deep-equal": "^1.0.1",
+        "eventemitter3": "^2.0.3",
+        "extend": "^3.0.2",
+        "parchment": "^1.1.4",
+        "quill-delta": "^3.6.2"
+      }
+    },
+    "node_modules/quill-delta": {
+      "version": "3.6.3",
+      "resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-3.6.3.tgz",
+      "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
+      "license": "MIT",
+      "dependencies": {
+        "deep-equal": "^1.0.1",
+        "extend": "^3.0.2",
+        "fast-diff": "1.1.2"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/quill/node_modules/eventemitter3": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz",
+      "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
+      "license": "MIT"
+    },
     "node_modules/rc-cascader": {
       "version": "3.34.0",
       "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.34.0.tgz",
@@ -5012,6 +5261,21 @@
         "react": ">=18"
       }
     },
+    "node_modules/react-quill": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/react-quill/-/react-quill-2.0.0.tgz",
+      "integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/quill": "^1.3.10",
+        "lodash": "^4.17.4",
+        "quill": "^1.3.7"
+      },
+      "peerDependencies": {
+        "react": "^16 || ^17 || ^18",
+        "react-dom": "^16 || ^17 || ^18"
+      }
+    },
     "node_modules/react-refresh": {
       "version": "0.14.2",
       "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -5064,6 +5328,26 @@
       "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
       "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
     },
+    "node_modules/regexp.prototype.flags": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+      "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.8",
+        "define-properties": "^1.2.1",
+        "es-errors": "^1.3.0",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "set-function-name": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/remark-parse": {
       "version": "11.0.0",
       "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz",
@@ -5224,6 +5508,38 @@
       "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
       "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
     },
+    "node_modules/set-function-length": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
+      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "gopd": "^1.0.1",
+        "has-property-descriptors": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/set-function-name": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz",
+      "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
+        "functions-have-names": "^1.2.3",
+        "has-property-descriptors": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",

+ 2 - 1
package.json

@@ -22,6 +22,7 @@
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-markdown": "^10.1.0",
+    "react-quill": "^2.0.0",
     "react-router-dom": "^7.1.0"
   },
   "devDependencies": {
@@ -33,4 +34,4 @@
     "typescript": "^5.7.0",
     "vite": "6.3.4"
   }
-}
+}

+ 220 - 0
src/pages/deepseek/knowledgeLib/slice/detail/imgPre.tsx

@@ -0,0 +1,220 @@
+import React, { useState, useRef, useEffect } from 'react';
+import ReactQuill from 'react-quill';
+import 'react-quill/dist/quill.snow.css';
+import { Form } from 'antd';
+import { EyeOutlined, CloseOutlined } from '@ant-design/icons';
+
+// 提取纯文本工具函数
+const extractPlainText = (html: string): string => {
+  if (!html) return '';
+  const tempDiv = document.createElement('div');
+  tempDiv.innerHTML = html;
+  return tempDiv.textContent || '';
+};
+
+interface LinkData {
+  id: string;
+  originalText: string;
+  url: string;
+  isDeleted: boolean;
+}
+
+const PersistentImagePreviewEditor = () => {
+  const [form] = Form.useForm();
+  const [editorHtml, setEditorHtml] = useState<string>(`
+    <p>系统工作人员联系方式</p>
+    <p>系统工作人员联系方式表:
+      <a href="http://xia0miduo.gicp.net:9000/papbtest//pdf/a2965738189226381312/a2966033634305507328/【示意图序号_a2966033634305507328_3】.jpg" target="_blank">【示意图序号_a2966033634305507328_2】</a>,
+      第二个标签 <a href="https://jkeckms.ryuiso.com/chat-bg.jpg" target="_blank">【示意图序号_a2966033634305507328_2】</a>,
+      第三个标签 <a href="https://jkeckms.ryuiso.com/chat-bg.jpg" target="_blank">【示意图序号_a2966033634305507328_2】</a>
+    </p>
+  `);
+  const [plainText, setPlainText] = useState<string>(extractPlainText(editorHtml));
+  const [linkDataList, setLinkDataList] = useState<LinkData[]>([]);
+  const [activeImageUrl, setActiveImageUrl] = useState<string | null>(null);
+  
+  // 跟踪鼠标是否在链接或预览区上
+  const [isHoveringLink, setIsHoveringLink] = useState(false);
+  const [isHoveringPreview, setIsHoveringPreview] = useState(false);
+  
+  const editorContainerRef = useRef<HTMLDivElement>(null);
+  const previewRef = useRef<HTMLDivElement>(null);
+
+  // 初始化链接数据
+  useEffect(() => {
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(editorHtml, 'text/html');
+    const links = doc.getElementsByTagName('a');
+    const linksData: LinkData[] = [];
+
+    Array.from(links).forEach((link, index) => {
+      linksData.push({
+        id: `link-${index}-${Date.now()}`,
+        originalText: link.textContent || '',
+        url: link.href,
+        isDeleted: false
+      });
+    });
+
+    setLinkDataList(linksData);
+  }, []);
+
+  // 处理内容变化
+  const handleEditorChange = (html: string) => {
+    setEditorHtml(html);
+    const text = extractPlainText(html);
+    setPlainText(text);
+    form.setFieldsValue({ slice_text: text });
+
+    if (linkDataList.length > 0) {
+      const updatedLinks = [...linkDataList];
+      updatedLinks.forEach(link => {
+        const feature = link.originalText.slice(0, 5);
+        link.isDeleted = !text.includes(feature);
+      });
+      setLinkDataList(updatedLinks);
+    }
+  };
+
+  // 处理鼠标悬停链接事件
+  useEffect(() => {
+    if (!editorContainerRef.current) return;
+
+    const handleMouseOverLink = (e: MouseEvent) => {
+      const target = e.target as HTMLAnchorElement;
+      if (target.tagName === 'A') {
+        const url = target.href;
+        const isKnownLink = linkDataList.some(link => 
+          !link.isDeleted && url.includes(link.url)
+        );
+
+        if (isKnownLink) {
+          console.log('图片地址:', url);
+          setActiveImageUrl(url);
+          setIsHoveringLink(true);
+        }
+      }
+    };
+
+    const handleMouseOutLink = () => {
+      setIsHoveringLink(false);
+    };
+
+    const container = editorContainerRef.current;
+    container.addEventListener('mouseover', handleMouseOverLink);
+    container.addEventListener('mouseout', handleMouseOutLink);
+
+    return () => {
+      container.removeEventListener('mouseover', handleMouseOverLink);
+      container.removeEventListener('mouseout', handleMouseOutLink);
+    };
+  }, [linkDataList]);
+
+  // 控制预览区显示/隐藏的核心逻辑
+  useEffect(() => {
+    // 当鼠标既不在链接上也不在预览区上时,才隐藏预览
+    if (!isHoveringLink && !isHoveringPreview) {
+      setActiveImageUrl(null);
+    }
+  }, [isHoveringLink, isHoveringPreview]);
+
+  // 配置编辑器(无工具栏)
+  const modules = {
+    toolbar: false
+  };
+
+  return (
+    <Form form={form} initialValues={{ slice_text: plainText }}>
+      <Form.Item
+        name="slice_text"
+        rules={[{ required: true, message: '内容不能为空' }]}
+      >
+        {/* 容器:编辑器 + 右侧图片预览区 */}
+        <div style={{ 
+          display: 'flex', 
+          gap: '16px', 
+          alignItems: 'flex-start'
+        }}>
+          {/* 500x500的编辑器 */}
+          <div 
+            ref={editorContainerRef}
+            style={{ 
+              width: '500px', 
+              height: '500px',
+              border: '1px solid #d9d9d9', 
+              borderRadius: '4px',
+              overflow: 'hidden'
+            }}
+          >
+            <ReactQuill
+              value={editorHtml}
+              onChange={handleEditorChange}
+              modules={modules}
+              style={{ height: '100%' }}
+              placeholder="请输入内容..."
+            />
+          </div>
+
+          {/* 右侧图片预览区(固定位置) */}
+          {activeImageUrl && (
+            <div
+              ref={previewRef}
+              style={{
+                width: '300px',
+                height: '500px',
+                backgroundColor: 'white',
+                borderRadius: '8px',
+                boxShadow: '0 4px 16px rgba(0, 0, 0, 0.15)',
+                padding: '12px',
+                border: '1px solid #eee',
+                display: 'flex',
+                flexDirection: 'column'
+              }}
+              onMouseOver={() => setIsHoveringPreview(true)}
+              onMouseOut={() => setIsHoveringPreview(false)}
+            >
+              <div style={{ 
+                display: 'flex', 
+                justifyContent: 'space-between', 
+                alignItems: 'center', 
+                marginBottom: '12px',
+                paddingBottom: '8px',
+                borderBottom: '1px solid #f0f0f0'
+              }}>
+                <div style={{ display: 'flex', alignItems: 'center' }}>
+                  <EyeOutlined style={{ color: '#1890ff', marginRight: '8px' }} />
+                  <span style={{ fontSize: '14px', color: '#333' }}>图片预览</span>
+                </div>
+                <CloseOutlined 
+                  style={{ color: '#999', cursor: 'pointer', fontSize: '16px' }}
+                  onClick={() => setActiveImageUrl(null)}
+                />
+              </div>
+              
+              <div style={{ 
+                flex: 1, 
+                display: 'flex', 
+                alignItems: 'center', 
+                justifyContent: 'center',
+                overflow: 'hidden'
+              }}>
+                <img
+                  src={activeImageUrl}
+                  alt="示意图预览"
+                  style={{ 
+                    maxWidth: '100%', 
+                    maxHeight: '100%', 
+                    objectFit: 'contain' 
+                  }}
+                  onError={() => setActiveImageUrl(null)}
+                />
+              </div>
+            </div>
+          )}
+        </div>
+      </Form.Item>
+    </Form>
+  );
+};
+
+export default PersistentImagePreviewEditor;

+ 226 - 5
src/pages/deepseek/knowledgeLib/slice/detail/index.tsx

@@ -6,8 +6,32 @@ import { apis } from '@/apis';
 import router from '@/router';
 import { ArrowLeftOutlined } from '@ant-design/icons';
 import config, { getHeaders } from '@/apis/config';
-
+import ImgPre from './imgPre';
 const { TextArea } = Input;
+import { useState, useRef, useEffect } from 'react';
+import ReactQuill from 'react-quill';
+import 'react-quill/dist/quill.snow.css';
+import { EyeOutlined, CloseOutlined } from '@ant-design/icons';
+import './style.less';
+
+const extractPlainText = (html: string): string => {
+    if (!html) return '';
+    const tempDiv = document.createElement('div');
+    tempDiv.innerHTML = html;
+    return tempDiv.textContent || '';
+};
+
+interface LinkData {
+    id: string;
+    originalText: string;
+    url: string;
+    isDeleted: boolean;
+}
+
+
+
+
+
 
 const FormItem = Form.Item;
 
@@ -26,6 +50,193 @@ const SliceDetail: React.FC = () => {
     const { text, page } = location.state;
     const [listLoading, setListLoading] = React.useState(false);
 
+    const [editorHtml, setEditorHtml] = useState<string>(``);
+    const RichTextImageRight = () => {
+        const [plainText, setPlainText] = useState<string>(extractPlainText(editorHtml));
+        const [linkDataList, setLinkDataList] = useState<LinkData[]>([]);
+        const [activeImageUrl, setActiveImageUrl] = useState<string | null>(null);
+        const editorContainerRef = useRef<HTMLDivElement>(null);
+        // 跟踪鼠标是否在链接或预览区上
+        const [isHoveringLink, setIsHoveringLink] = useState(false);
+        const [isHoveringPreview, setIsHoveringPreview] = useState(false);
+        // 初始化链接数据
+        useEffect(() => {
+            const parser = new DOMParser();
+            const doc = parser.parseFromString(editorHtml, 'text/html');
+            const links = doc.getElementsByTagName('a');
+            const linksData: LinkData[] = [];
+
+            Array.from(links).forEach((link, index) => {
+                linksData.push({
+                    id: `link-${index}-${Date.now()}`,
+                    originalText: link.textContent || '',
+                    url: link.href,
+                    isDeleted: false
+                });
+            });
+
+            setLinkDataList(linksData);
+        }, []);
+
+        // 处理内容变化
+        const handleEditorChange = (html: string) => {
+            setEditorHtml(html);
+            const text = extractPlainText(html);
+            // console.log('text---', text);
+            setPlainText(text);
+            form.setFieldsValue({ slice_text: text });
+
+            if (linkDataList.length > 0) {
+                const updatedLinks = [...linkDataList];
+                updatedLinks.forEach(link => {
+                    const feature = link.originalText.slice(0, 5);
+                    link.isDeleted = !text.includes(feature);
+                });
+                setLinkDataList(updatedLinks);
+            }
+        };
+
+        // 处理鼠标悬停链接事件
+        useEffect(() => {
+            if (!editorContainerRef.current) return;
+
+            const handleMouseOver = (e: MouseEvent) => {
+                const target = e.target as HTMLAnchorElement;
+                if (target.tagName === 'A') {
+                    const url = target.href;
+                    const isKnownLink = linkDataList.some(link =>
+                        !link.isDeleted && url.includes(link.url)
+                    );
+
+                    if (isKnownLink) {
+                        console.log('图片地址:', url);
+                        setActiveImageUrl(url); // 只设置图片地址,不计算位置
+                        setIsHoveringLink(true);
+                    }
+                }
+            };
+
+            const handleMouseOut = (e: MouseEvent) => {
+                if ((e.target as HTMLAnchorElement).tagName === 'A') {
+                    // 延迟关闭,避免快速移动鼠标时的闪烁
+                    setTimeout(() => {
+                        const activeElement = document.activeElement as HTMLElement;
+                        if (!activeElement || activeElement.tagName !== 'A') {
+                            setActiveImageUrl(null);
+                        }
+                    }, 300);
+                }
+            };
+
+            const container = editorContainerRef.current;
+            container.addEventListener('mouseover', handleMouseOver);
+            container.addEventListener('mouseout', handleMouseOut);
+
+            return () => {
+                container.removeEventListener('mouseover', handleMouseOver);
+                container.removeEventListener('mouseout', handleMouseOut);
+            };
+        }, [linkDataList]);
+        // 控制预览区显示/隐藏的核心逻辑
+        useEffect(() => {
+            // 当鼠标既不在链接上也不在预览区上时,才隐藏预览
+            if (!isHoveringLink && !isHoveringPreview) {
+                setActiveImageUrl(null);
+            }
+        }, [isHoveringLink, isHoveringPreview]);
+        // 配置编辑器(无工具栏)
+        const modules = {
+            toolbar: false
+        };
+
+        return (
+            <div>
+                {/* 容器:编辑器 + 右侧图片预览区 */}
+                <div style={{
+                    display: 'flex',
+                    gap: '16px',
+                    alignItems: 'flex-start',
+                }}>
+                    {/* 500x500的编辑器 */}
+                    <div
+                        ref={editorContainerRef}
+                        style={{
+                            width: '300px',
+                            height: '400px',
+                            marginTop: '20px',
+                            marginBottom: '0px',
+                            borderRadius: '4px',
+                            overflow: 'hidden'
+                        }}
+                    >
+                        <ReactQuill
+                            value={editorHtml}
+                            onChange={handleEditorChange}
+                            modules={modules}
+                            style={{ height: '100%' }}
+                            placeholder="请输入内容..."
+                        />
+                    </div>
+
+                    {/* 右侧图片预览区(固定位置) */}
+                    {activeImageUrl && (
+                        <div
+                            style={{
+                                width: '300px',
+                                height: '500px',
+                                backgroundColor: 'white',
+                                borderRadius: '8px',
+                                boxShadow: '0 4px 16px rgba(0, 0, 0, 0.15)',
+                                padding: '12px',
+                                border: '1px solid #eee',
+                                display: 'flex',
+                                flexDirection: 'column'
+                            }}
+                        >
+                            <div style={{
+                                display: 'flex',
+                                justifyContent: 'space-between',
+                                alignItems: 'center',
+                                marginBottom: '12px',
+                                paddingBottom: '8px',
+                                borderBottom: '1px solid #f0f0f0'
+                            }}>
+                                <div style={{ display: 'flex', alignItems: 'center' }}>
+                                    <EyeOutlined style={{ color: '#1890ff', marginRight: '8px' }} />
+                                    <span style={{ fontSize: '14px', color: '#333' }}>图片预览</span>
+                                </div>
+                                {/* <CloseOutlined 
+                  style={{ color: '#999', cursor: 'pointer', fontSize: '16px' }}
+                  onClick={() => setActiveImageUrl(null)}
+                /> */}
+                            </div>
+
+                            <div style={{
+                                flex: 1,
+                                display: 'flex',
+                                alignItems: 'center',
+                                justifyContent: 'center',
+                                overflow: 'hidden'
+                            }}>
+                                <img
+                                    src={activeImageUrl}
+                                    alt="示意图预览"
+                                    style={{
+                                        maxWidth: '100%',
+                                        maxHeight: '100%',
+                                        objectFit: 'contain'
+                                    }}
+                                    onError={() => setActiveImageUrl(null)}
+                                />
+                            </div>
+                        </div>
+                    )}
+                </div>
+            </div>
+        );
+    };
+
+
     const appApi = {
         fetchList: async () => {
             setListLoading(true);
@@ -35,6 +246,7 @@ const SliceDetail: React.FC = () => {
                 }
                 const res = await apis.fetchTakaiSliceDetail(params.sliceId, params.knowledgeId);
                 const info = res.data.data;
+                setEditorHtml(info.slice_text || '');
                 form.setFieldsValue({
                     slice_id: info.slice_id,
                     slice_text: info.slice_text,
@@ -75,14 +287,22 @@ const SliceDetail: React.FC = () => {
 
     return (
         <div>
+            {/* <ImgPre></ImgPre> */}
             <div className='questionAnswerList'>
-                <div style={{ overflow: 'hidden' }}>
+                <div style={{ height: '100%', marginLeft: '10px' }}>
                     <Form
                         style={{ paddingTop: '20px', height: '100%' }}
                         form={form}
                         layout='vertical'
                     >
-                        <FormItem
+                        <Form.Item
+                            name="slice_text"
+                            rules={[{ required: true, message: '内容不能为空' }]}
+                        >
+                            {RichTextImageRight()}
+
+                        </Form.Item>
+                        {false && <FormItem
                             name="slice_text"
                             rules={[{ required: true, message: '切片内容不能为空' }]}>
                             <TextArea
@@ -102,7 +322,7 @@ const SliceDetail: React.FC = () => {
                                     setCursorEndPosition(target.selectionEnd);
                                 }}
                             />
-                        </FormItem>
+                        </FormItem>}
                         <Upload
                             {...uploadImageConfig}
                             onChange={(info) => {
@@ -119,7 +339,7 @@ const SliceDetail: React.FC = () => {
                                     } else {
                                         newValue = slice_text.slice(0, position) + text + slice_text.slice(position);
                                     }
-
+                                    setEditorHtml(newValue || '');
                                     form.setFieldsValue({ slice_text: newValue });
                                 }
                                 const file = info.file;
@@ -160,6 +380,7 @@ const SliceDetail: React.FC = () => {
                             type='primary'
                             onClick={() => {
                                 form.validateFields().then(async (values) => {
+                                    console.log('values', values)
                                     // 验证参数是否存在
                                     if (!params.knowledgeId || !params.sliceId) {
                                         message.error('知识库ID或切片ID无效');

+ 3 - 0
src/pages/deepseek/knowledgeLib/slice/detail/style.less

@@ -0,0 +1,3 @@
+.ql-container.ql-snow{
+    border: 1px solid #d9d9d9;
+}