feat: 优化思维导图功能

- 修复新增节点居中逻辑,从当前位置平滑移动到新节点
- 实现节点拖拽保存到数据库功能
- 移除所有'正在加载'弹窗提示
- 优化删除节点时的位置保持
- 添加节点拖拽成功/失败通知
This commit is contained in:
lixinran 2025-10-09 14:20:51 +08:00
parent 25abc09cb4
commit 3b39f86f83
12 changed files with 475 additions and 868 deletions

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

BIN
frontend/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,470 +0,0 @@
import "./chunk-FOSKEDPS.js";
// src/lib/mind-elixir/node_modules/@viselect/vanilla/dist/viselect.mjs
var X = class {
constructor() {
this._listeners = /* @__PURE__ */ new Map(), this.on = this.addEventListener, this.off = this.removeEventListener, this.emit = this.dispatchEvent;
}
addEventListener(e, t) {
const s = this._listeners.get(e) ?? /* @__PURE__ */ new Set();
return this._listeners.set(e, s), s.add(t), this;
}
removeEventListener(e, t) {
var s;
return (s = this._listeners.get(e)) == null || s.delete(t), this;
}
dispatchEvent(e, ...t) {
let s = true;
for (const i of this._listeners.get(e) ?? [])
s = i(...t) !== false && s;
return s;
}
unbindAllListeners() {
this._listeners.clear();
}
};
var L = (l, e = "px") => typeof l == "number" ? l + e : l;
var y = ({ style: l }, e, t) => {
if (typeof e == "object")
for (const [s, i] of Object.entries(e))
i !== void 0 && (l[s] = L(i));
else
t !== void 0 && (l[e] = L(t));
};
var M = (l = 0, e = 0, t = 0, s = 0) => {
const i = { x: l, y: e, width: t, height: s, top: e, left: l, right: l + t, bottom: e + s };
return { ...i, toJSON: () => JSON.stringify(i) };
};
var Y = (l) => {
let e, t = -1, s = false;
return {
next: (...i) => {
e = i, s || (s = true, t = requestAnimationFrame(() => {
l(...e), s = false;
}));
},
cancel: () => {
cancelAnimationFrame(t), s = false;
}
};
};
var k = (l, e, t = "touch") => {
switch (t) {
case "center": {
const s = e.left + e.width / 2, i = e.top + e.height / 2;
return s >= l.left && s <= l.right && i >= l.top && i <= l.bottom;
}
case "cover":
return e.left >= l.left && e.top >= l.top && e.right <= l.right && e.bottom <= l.bottom;
case "touch":
return l.right >= e.left && l.left <= e.right && l.bottom >= e.top && l.top <= e.bottom;
}
};
var H = () => matchMedia("(hover: none), (pointer: coarse)").matches;
var N = () => "safari" in window;
var A = (l) => Array.isArray(l) ? l : [l];
var O = (l) => (e, t, s, i = {}) => {
(e instanceof HTMLCollection || e instanceof NodeList) && (e = Array.from(e)), t = A(t), e = A(e);
for (const o of e)
if (o)
for (const n of t)
o[l](n, s, { capture: false, ...i });
};
var S = O("addEventListener");
var g = O("removeEventListener");
var x = (l) => {
var i;
const { clientX: e, clientY: t, target: s } = ((i = l.touches) == null ? void 0 : i[0]) ?? l;
return { x: e, y: t, target: s };
};
var E = (l, e = document) => A(l).map(
(t) => typeof t == "string" ? Array.from(e.querySelectorAll(t)) : t instanceof Element ? t : null
).flat().filter(Boolean);
var q = (l, e) => e.some((t) => typeof t == "number" ? l.button === t : typeof t == "object" ? t.button !== l.button ? false : t.modifiers.every((s) => {
switch (s) {
case "alt":
return l.altKey;
case "ctrl":
return l.ctrlKey || l.metaKey;
case "shift":
return l.shiftKey;
}
}) : false);
var { abs: b, max: C, min: B, ceil: R } = Math;
var D = (l = []) => ({
stored: l,
selected: [],
touched: [],
changed: { added: [], removed: [] }
});
var T = class T2 extends X {
constructor(e) {
var o, n, r, a, u;
super(), this._selection = D(), this._targetBoundaryScrolled = true, this._selectables = [], this._areaLocation = { y1: 0, x2: 0, y2: 0, x1: 0 }, this._areaRect = M(), this._singleClick = true, this._scrollAvailable = true, this._scrollingActive = false, this._scrollSpeed = { x: 0, y: 0 }, this._scrollDelta = { x: 0, y: 0 }, this._lastMousePosition = { x: 0, y: 0 }, this.enable = this._toggleStartEvents, this.disable = this._toggleStartEvents.bind(this, false), this._options = {
selectionAreaClass: "selection-area",
selectionContainerClass: void 0,
selectables: [],
document: window.document,
startAreas: ["html"],
boundaries: ["html"],
container: "body",
...e,
behaviour: {
overlap: "invert",
intersect: "touch",
triggers: [0],
...e.behaviour,
startThreshold: (o = e.behaviour) != null && o.startThreshold ? typeof e.behaviour.startThreshold == "number" ? e.behaviour.startThreshold : { x: 10, y: 10, ...e.behaviour.startThreshold } : { x: 10, y: 10 },
scrolling: {
speedDivider: 10,
manualSpeed: 750,
...(n = e.behaviour) == null ? void 0 : n.scrolling,
startScrollMargins: {
x: 0,
y: 0,
...(a = (r = e.behaviour) == null ? void 0 : r.scrolling) == null ? void 0 : a.startScrollMargins
}
}
},
features: {
range: true,
touch: true,
deselectOnBlur: false,
...e.features,
singleTap: {
allow: true,
intersect: "native",
...(u = e.features) == null ? void 0 : u.singleTap
}
}
};
for (const _ of Object.getOwnPropertyNames(Object.getPrototypeOf(this)))
typeof this[_] == "function" && (this[_] = this[_].bind(this));
const { document: t, selectionAreaClass: s, selectionContainerClass: i } = this._options;
this._area = t.createElement("div"), this._clippingElement = t.createElement("div"), this._clippingElement.appendChild(this._area), this._area.classList.add(s), i && this._clippingElement.classList.add(i), y(this._area, {
willChange: "top, left, bottom, right, width, height",
top: 0,
left: 0,
position: "fixed"
}), y(this._clippingElement, {
overflow: "hidden",
position: "fixed",
transform: "translate3d(0, 0, 0)",
// https://stackoverflow.com/a/38268846
pointerEvents: "none",
zIndex: "1"
}), this._frame = Y((_) => {
this._recalculateSelectionAreaRect(), this._updateElementSelection(), this._emitEvent("move", _), this._redrawSelectionArea();
}), this.enable();
}
_toggleStartEvents(e = true) {
const { document: t, features: s } = this._options, i = e ? S : g;
i(t, "mousedown", this._onTapStart), s.touch && i(t, "touchstart", this._onTapStart, { passive: false });
}
_onTapStart(e, t = false) {
const { x: s, y: i, target: o } = x(e), { document: n, startAreas: r, boundaries: a, features: u, behaviour: _ } = this._options, c = o.getBoundingClientRect();
if (e instanceof MouseEvent && !q(e, _.triggers))
return;
const p = E(r, n), m = E(a, n);
this._targetElement = m.find(
(v) => k(v.getBoundingClientRect(), c)
);
const f = e.composedPath(), d = p.find((v) => f.includes(v));
if (this._targetBoundary = m.find((v) => f.includes(v)), !this._targetElement || !d || !this._targetBoundary || !t && this._emitEvent("beforestart", e) === false)
return;
this._areaLocation = { x1: s, y1: i, x2: 0, y2: 0 };
const h = n.scrollingElement ?? n.body;
this._scrollDelta = { x: h.scrollLeft, y: h.scrollTop }, this._singleClick = true, this.clearSelection(false, true), S(n, ["touchmove", "mousemove"], this._delayedTapMove, { passive: false }), S(n, ["mouseup", "touchcancel", "touchend"], this._onTapStop), S(n, "scroll", this._onScroll), u.deselectOnBlur && (this._targetBoundaryScrolled = false, S(this._targetBoundary, "scroll", this._onStartAreaScroll));
}
_onSingleTap(e) {
const { singleTap: { intersect: t }, range: s } = this._options.features, i = x(e);
let o;
if (t === "native")
o = i.target;
else if (t === "touch") {
this.resolveSelectables();
const { x: r, y: a } = i;
o = this._selectables.find((u) => {
const { right: _, left: c, top: p, bottom: m } = u.getBoundingClientRect();
return r < _ && r > c && a < m && a > p;
});
}
if (!o)
return;
for (this.resolveSelectables(); !this._selectables.includes(o); )
if (o.parentElement)
o = o.parentElement;
else {
this._targetBoundaryScrolled || this.clearSelection();
return;
}
const { stored: n } = this._selection;
if (this._emitEvent("start", e), e.shiftKey && s && this._latestElement) {
const r = this._latestElement, [a, u] = r.compareDocumentPosition(o) & 4 ? [o, r] : [r, o], _ = [...this._selectables.filter(
(c) => c.compareDocumentPosition(a) & 4 && c.compareDocumentPosition(u) & 2
), a, u];
this.select(_), this._latestElement = r;
} else
n.includes(o) && (n.length === 1 || e.ctrlKey || n.every((r) => this._selection.stored.includes(r))) ? this.deselect(o) : (this.select(o), this._latestElement = o);
}
_delayedTapMove(e) {
const { container: t, document: s, behaviour: { startThreshold: i } } = this._options, { x1: o, y1: n } = this._areaLocation, { x: r, y: a } = x(e);
if (
// Single number for both coordinates
typeof i == "number" && b(r + a - (o + n)) >= i || // Different x and y threshold
typeof i == "object" && b(r - o) >= i.x || b(a - n) >= i.y
) {
if (g(s, ["mousemove", "touchmove"], this._delayedTapMove, { passive: false }), this._emitEvent("beforedrag", e) === false) {
g(s, ["mouseup", "touchcancel", "touchend"], this._onTapStop);
return;
}
S(s, ["mousemove", "touchmove"], this._onTapMove, { passive: false }), y(this._area, "display", "block"), E(t, s)[0].appendChild(this._clippingElement), this.resolveSelectables(), this._singleClick = false, this._targetRect = this._targetElement.getBoundingClientRect(), this._scrollAvailable = this._targetElement.scrollHeight !== this._targetElement.clientHeight || this._targetElement.scrollWidth !== this._targetElement.clientWidth, this._scrollAvailable && (S(this._targetElement, "wheel", this._wheelScroll, { passive: false }), S(this._options.document, "keydown", this._keyboardScroll, { passive: false }), this._selectables = this._selectables.filter((u) => this._targetElement.contains(u))), this._setupSelectionArea(), this._emitEvent("start", e), this._onTapMove(e);
}
this._handleMoveEvent(e);
}
_setupSelectionArea() {
const { _clippingElement: e, _targetElement: t, _area: s } = this, i = this._targetRect = t.getBoundingClientRect();
this._scrollAvailable ? (y(e, {
top: i.top,
left: i.left,
width: i.width,
height: i.height
}), y(s, {
marginTop: -i.top,
marginLeft: -i.left
})) : (y(e, {
top: 0,
left: 0,
width: "100%",
height: "100%"
}), y(s, {
marginTop: 0,
marginLeft: 0
}));
}
_onTapMove(e) {
const { _scrollSpeed: t, _areaLocation: s, _options: i, _frame: o } = this, { speedDivider: n } = i.behaviour.scrolling, r = this._targetElement, { x: a, y: u } = x(e);
if (s.x2 = a, s.y2 = u, this._lastMousePosition.x = a, this._lastMousePosition.y = u, this._scrollAvailable && !this._scrollingActive && (t.y || t.x)) {
this._scrollingActive = true;
const _ = () => {
if (!t.x && !t.y) {
this._scrollingActive = false;
return;
}
const { scrollTop: c, scrollLeft: p } = r;
t.y && (r.scrollTop += R(t.y / n), s.y1 -= r.scrollTop - c), t.x && (r.scrollLeft += R(t.x / n), s.x1 -= r.scrollLeft - p), o.next(e), requestAnimationFrame(_);
};
requestAnimationFrame(_);
} else
o.next(e);
this._handleMoveEvent(e);
}
_handleMoveEvent(e) {
const { features: t } = this._options;
(t.touch && H() || this._scrollAvailable && N()) && e.preventDefault();
}
_onScroll() {
const { _scrollDelta: e, _options: { document: t } } = this, { scrollTop: s, scrollLeft: i } = t.scrollingElement ?? t.body;
this._areaLocation.x1 += e.x - i, this._areaLocation.y1 += e.y - s, e.x = i, e.y = s, this._setupSelectionArea(), this._frame.next(null);
}
_onStartAreaScroll() {
this._targetBoundaryScrolled = true, g(this._targetElement, "scroll", this._onStartAreaScroll);
}
_wheelScroll(e) {
const { manualSpeed: t } = this._options.behaviour.scrolling, s = e.deltaY ? e.deltaY > 0 ? 1 : -1 : 0, i = e.deltaX ? e.deltaX > 0 ? 1 : -1 : 0;
this._scrollSpeed.y += s * t, this._scrollSpeed.x += i * t, this._onTapMove(e), e.preventDefault();
}
_keyboardScroll(e) {
const { manualSpeed: t } = this._options.behaviour.scrolling, s = e.key === "ArrowLeft" ? -1 : e.key === "ArrowRight" ? 1 : 0, i = e.key === "ArrowUp" ? -1 : e.key === "ArrowDown" ? 1 : 0;
this._scrollSpeed.x += Math.sign(s) * t, this._scrollSpeed.y += Math.sign(i) * t, e.preventDefault(), this._onTapMove({
clientX: this._lastMousePosition.x,
clientY: this._lastMousePosition.y,
preventDefault: () => {
}
});
}
_recalculateSelectionAreaRect() {
const { _scrollSpeed: e, _areaLocation: t, _targetElement: s, _options: i } = this, { scrollTop: o, scrollHeight: n, clientHeight: r, scrollLeft: a, scrollWidth: u, clientWidth: _ } = s, c = this._targetRect, { x1: p, y1: m } = t;
let { x2: f, y2: d } = t;
const { behaviour: { scrolling: { startScrollMargins: h } } } = i;
f < c.left + h.x ? (e.x = a ? -b(c.left - f + h.x) : 0, f = f < c.left ? c.left : f) : f > c.right - h.x ? (e.x = u - a - _ ? b(c.left + c.width - f - h.x) : 0, f = f > c.right ? c.right : f) : e.x = 0, d < c.top + h.y ? (e.y = o ? -b(c.top - d + h.y) : 0, d = d < c.top ? c.top : d) : d > c.bottom - h.y ? (e.y = n - o - r ? b(c.top + c.height - d - h.y) : 0, d = d > c.bottom ? c.bottom : d) : e.y = 0;
const v = B(p, f), w = B(m, d), j = C(p, f), K = C(m, d);
this._areaRect = M(v, w, j - v, K - w);
}
_redrawSelectionArea() {
const { x: e, y: t, width: s, height: i } = this._areaRect, { style: o } = this._area;
o.left = `${e}px`, o.top = `${t}px`, o.width = `${s}px`, o.height = `${i}px`;
}
_onTapStop(e, t) {
var n;
const { document: s, features: i } = this._options, { _singleClick: o } = this;
g(this._targetElement, "scroll", this._onStartAreaScroll), g(s, ["mousemove", "touchmove"], this._delayedTapMove), g(s, ["touchmove", "mousemove"], this._onTapMove), g(s, ["mouseup", "touchcancel", "touchend"], this._onTapStop), g(s, "scroll", this._onScroll), this._keepSelection(), e && o && i.singleTap.allow ? this._onSingleTap(e) : !o && !t && (this._updateElementSelection(), this._emitEvent("stop", e)), this._scrollSpeed.x = 0, this._scrollSpeed.y = 0, g(this._targetElement, "wheel", this._wheelScroll, { passive: true }), g(this._options.document, "keydown", this._keyboardScroll, { passive: true }), this._clippingElement.remove(), (n = this._frame) == null || n.cancel(), y(this._area, "display", "none");
}
_updateElementSelection() {
const { _selectables: e, _options: t, _selection: s, _areaRect: i } = this, { stored: o, selected: n, touched: r } = s, { intersect: a, overlap: u } = t.behaviour, _ = u === "invert", c = [], p = [], m = [];
for (let d = 0; d < e.length; d++) {
const h = e[d];
if (k(i, h.getBoundingClientRect(), a)) {
if (n.includes(h))
o.includes(h) && !r.includes(h) && r.push(h);
else if (_ && o.includes(h)) {
m.push(h);
continue;
} else
p.push(h);
c.push(h);
}
}
_ && p.push(...o.filter((d) => !n.includes(d)));
const f = u === "keep";
for (let d = 0; d < n.length; d++) {
const h = n[d];
!c.includes(h) && !// Check if the user wants to keep previously selected elements, e.g.,
// not make them part of the current selection as soon as they're touched.
(f && o.includes(h)) && m.push(h);
}
s.selected = c, s.changed = { added: p, removed: m }, this._latestElement = void 0;
}
_emitEvent(e, t) {
return this.emit(e, {
event: t,
store: this._selection,
selection: this
});
}
_keepSelection() {
const { _options: e, _selection: t } = this, { selected: s, changed: i, touched: o, stored: n } = t, r = s.filter((a) => !n.includes(a));
switch (e.behaviour.overlap) {
case "drop": {
t.stored = [
...r,
...n.filter((a) => !o.includes(a))
// Elements not touched
];
break;
}
case "invert": {
t.stored = [
...r,
...n.filter((a) => !i.removed.includes(a))
// Elements not removed from selection
];
break;
}
case "keep": {
t.stored = [
...n,
...s.filter((a) => !n.includes(a))
// Newly added
];
break;
}
}
}
/**
* Manually triggers the start of a selection
* @param evt A MouseEvent / TouchEvent-like object
* @param silent If beforestart should be fired
*/
trigger(e, t = true) {
this._onTapStart(e, t);
}
/**
* Can be used if during a selection elements have been added
* Will update everything that can be selected
*/
resolveSelectables() {
this._selectables = E(this._options.selectables, this._options.document);
}
/**
* Same as deselecting, but for all elements currently selected
* @param includeStored If the store should also get cleared
* @param quiet If move / stop events should be fired
*/
clearSelection(e = true, t = false) {
const { selected: s, stored: i, changed: o } = this._selection;
o.added = [], o.removed.push(
...s,
...e ? i : []
), t || (this._emitEvent("move", null), this._emitEvent("stop", null)), this._selection = D(e ? [] : i);
}
/**
* @returns {Array} Selected elements
*/
getSelection() {
return this._selection.stored;
}
/**
* @returns {HTMLElement} The selection area element
*/
getSelectionArea() {
return this._area;
}
/**
* @returns {Element[]} Available selectable elements for current selection
*/
getSelectables() {
return this._selectables;
}
/**
* Set the location of the selection area
* @param location A partial AreaLocation object
*/
setAreaLocation(e) {
Object.assign(this._areaLocation, e), this._redrawSelectionArea();
}
/**
* @returns {AreaLocation} The current location of the selection area
*/
getAreaLocation() {
return this._areaLocation;
}
/**
* Cancel the current selection process, pass true to fire a stop event after cancel
* @param keepEvent If a stop event should be fired
*/
cancel(e = false) {
this._onTapStop(null, !e);
}
/**
* Unbinds all events and removes the area-element.
*/
destroy() {
this.cancel(), this.disable(), this._clippingElement.remove(), super.unbindAllListeners();
}
/**
* Adds elements to the selection
* @param query CSS Query, can be an array of queries
* @param quiet If this should not trigger the move event
*/
select(e, t = false) {
const { changed: s, selected: i, stored: o } = this._selection, n = E(e, this._options.document).filter(
(r) => !i.includes(r) && !o.includes(r)
);
return o.push(...n), i.push(...n), s.added.push(...n), s.removed = [], this._latestElement = void 0, t || (this._emitEvent("move", null), this._emitEvent("stop", null)), n;
}
/**
* Removes a particular element from the selection
* @param query CSS Query, can be an array of queries
* @param quiet If this should not trigger the move event
*/
deselect(e, t = false) {
const { selected: s, stored: i, changed: o } = this._selection, n = E(e, this._options.document).filter(
(r) => s.includes(r) || i.includes(r)
);
this._selection.stored = i.filter((r) => !n.includes(r)), this._selection.selected = s.filter((r) => !n.includes(r)), this._selection.changed.added = [], this._selection.changed.removed.push(
...n.filter((r) => !o.removed.includes(r))
), this._latestElement = void 0, t || (this._emitEvent("move", null), this._emitEvent("stop", null));
}
};
T.version = "3.9.0";
var P = T;
export {
P as default
};
/*! Bundled license information:
@viselect/vanilla/dist/viselect.mjs:
(*! @viselect/vanilla v3.9.0 MIT | https://github.com/Simonwep/selection/tree/master/packages/vanilla *)
*/
//# sourceMappingURL=@viselect_vanilla.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,83 +1,77 @@
{ {
"hash": "76772e52", "hash": "94dd0fae",
"browserHash": "a4ef7769", "browserHash": "b8442f90",
"optimized": { "optimized": {
"axios": { "axios": {
"src": "../../axios/index.js", "src": "../../axios/index.js",
"file": "axios.js", "file": "axios.js",
"fileHash": "923e7809", "fileHash": "d8567397",
"needsInterop": false
},
"mammoth": {
"src": "../../mammoth/lib/index.js",
"file": "mammoth.js",
"fileHash": "2d23e669",
"needsInterop": true
},
"marked": {
"src": "../../marked/lib/marked.esm.js",
"file": "marked.js",
"fileHash": "e6802b70",
"needsInterop": false
},
"pdfjs-dist": {
"src": "../../pdfjs-dist/build/pdf.mjs",
"file": "pdfjs-dist.js",
"fileHash": "b0d6a144",
"needsInterop": false
},
"prismjs": {
"src": "../../prismjs/prism.js",
"file": "prismjs.js",
"fileHash": "0c7d8fc7",
"needsInterop": true
},
"prismjs/components/prism-css": {
"src": "../../prismjs/components/prism-css.js",
"file": "prismjs_components_prism-css.js",
"fileHash": "b5dc8638",
"needsInterop": true
},
"prismjs/components/prism-javascript": {
"src": "../../prismjs/components/prism-javascript.js",
"file": "prismjs_components_prism-javascript.js",
"fileHash": "a3fb501c",
"needsInterop": true
},
"prismjs/components/prism-json": {
"src": "../../prismjs/components/prism-json.js",
"file": "prismjs_components_prism-json.js",
"fileHash": "2d44f86b",
"needsInterop": true
},
"prismjs/components/prism-python": {
"src": "../../prismjs/components/prism-python.js",
"file": "prismjs_components_prism-python.js",
"fileHash": "ca5259af",
"needsInterop": true
},
"prismjs/components/prism-sql": {
"src": "../../prismjs/components/prism-sql.js",
"file": "prismjs_components_prism-sql.js",
"fileHash": "4a89443c",
"needsInterop": true
},
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "b4ce9a46",
"needsInterop": false "needsInterop": false
}, },
"katex": { "katex": {
"src": "../../katex/dist/katex.mjs", "src": "../../katex/dist/katex.mjs",
"file": "katex.js", "file": "katex.js",
"fileHash": "e7ed5c7a", "fileHash": "7dd124eb",
"needsInterop": false "needsInterop": false
}, },
"@viselect/vanilla": { "mammoth": {
"src": "../../../src/lib/mind-elixir/node_modules/@viselect/vanilla/dist/viselect.mjs", "src": "../../mammoth/lib/index.js",
"file": "@viselect_vanilla.js", "file": "mammoth.js",
"fileHash": "b39569df", "fileHash": "9aaed3ae",
"needsInterop": true
},
"marked": {
"src": "../../marked/lib/marked.esm.js",
"file": "marked.js",
"fileHash": "d5cd504b",
"needsInterop": false
},
"pdfjs-dist": {
"src": "../../pdfjs-dist/build/pdf.mjs",
"file": "pdfjs-dist.js",
"fileHash": "4804df17",
"needsInterop": false
},
"prismjs": {
"src": "../../prismjs/prism.js",
"file": "prismjs.js",
"fileHash": "13932d2e",
"needsInterop": true
},
"prismjs/components/prism-css": {
"src": "../../prismjs/components/prism-css.js",
"file": "prismjs_components_prism-css.js",
"fileHash": "f24b2e04",
"needsInterop": true
},
"prismjs/components/prism-javascript": {
"src": "../../prismjs/components/prism-javascript.js",
"file": "prismjs_components_prism-javascript.js",
"fileHash": "b44ed50e",
"needsInterop": true
},
"prismjs/components/prism-json": {
"src": "../../prismjs/components/prism-json.js",
"file": "prismjs_components_prism-json.js",
"fileHash": "0d3b4905",
"needsInterop": true
},
"prismjs/components/prism-python": {
"src": "../../prismjs/components/prism-python.js",
"file": "prismjs_components_prism-python.js",
"fileHash": "f1831121",
"needsInterop": true
},
"prismjs/components/prism-sql": {
"src": "../../prismjs/components/prism-sql.js",
"file": "prismjs_components_prism-sql.js",
"fileHash": "e04f8e66",
"needsInterop": true
},
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "cba89b1c",
"needsInterop": false "needsInterop": false
} }
}, },

View File

@ -15,13 +15,13 @@ export {};
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A; type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
type __VLS_SpreadMerge<A, B> = Omit<A, keyof B> & B; type __VLS_SpreadMerge<A, B> = Omit<A, keyof B> & B;
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string, N3 extends string> = type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string, N3 extends string> =
N1 extends keyof LocalComponents ? N1 extends N0 ? Pick<LocalComponents, N0 extends keyof LocalComponents ? N0 : never> : { [K in N0]: LocalComponents[N1] } : N1 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N1] } :
N2 extends keyof LocalComponents ? N2 extends N0 ? Pick<LocalComponents, N0 extends keyof LocalComponents ? N0 : never> : { [K in N0]: LocalComponents[N2] } : N2 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N2] } :
N3 extends keyof LocalComponents ? N3 extends N0 ? Pick<LocalComponents, N0 extends keyof LocalComponents ? N0 : never> : { [K in N0]: LocalComponents[N3] } : N3 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N3] } :
Self extends object ? { [K in N0]: Self } : Self extends object ? { [K in N0]: Self } :
N1 extends keyof __VLS_GlobalComponents ? N1 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N1] } : N1 extends keyof __VLS_GlobalComponents ? { [K in N0]: __VLS_GlobalComponents[N1] } :
N2 extends keyof __VLS_GlobalComponents ? N2 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N2] } : N2 extends keyof __VLS_GlobalComponents ? { [K in N0]: __VLS_GlobalComponents[N2] } :
N3 extends keyof __VLS_GlobalComponents ? N3 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N3] } : N3 extends keyof __VLS_GlobalComponents ? { [K in N0]: __VLS_GlobalComponents[N3] } :
{}; {};
type __VLS_FunctionalComponentCtx<T, K> = __VLS_PickNotAny<'__ctx' extends keyof __VLS_PickNotAny<K, {}> type __VLS_FunctionalComponentCtx<T, K> = __VLS_PickNotAny<'__ctx' extends keyof __VLS_PickNotAny<K, {}>
? K extends { __ctx?: infer Ctx } ? NonNullable<Ctx> : never : any ? K extends { __ctx?: infer Ctx } ? NonNullable<Ctx> : never : any
@ -83,6 +83,10 @@ export {};
} }
> >
>; >;
type __VLS_EmitsToProps<T> = __VLS_PrettifyGlobal<{
[K in string & keyof T as `on${Capitalize<K>}`]?:
(...args: T[K] extends (...args: infer P) => any ? P : T[K] extends null ? any[] : never) => any;
}>;
type __VLS_ResolveEmits< type __VLS_ResolveEmits<
Comp, Comp,
Emits, Emits,
@ -90,10 +94,16 @@ export {};
NormalizedEmits = __VLS_NormalizeEmits<Emits> extends infer E ? string extends keyof E ? {} : E : never, NormalizedEmits = __VLS_NormalizeEmits<Emits> extends infer E ? string extends keyof E ? {} : E : never,
> = __VLS_SpreadMerge<NormalizedEmits, TypeEmits>; > = __VLS_SpreadMerge<NormalizedEmits, TypeEmits>;
type __VLS_ResolveDirectives<T> = { type __VLS_ResolveDirectives<T> = {
[K in Exclude<keyof T, keyof __VLS_GlobalDirectives> & string as `v${Capitalize<K>}`]: T[K]; [K in keyof T & string as `v${Capitalize<K>}`]: T[K];
}; };
type __VLS_PrettifyGlobal<T> = { [K in keyof T as K]: T[K]; } & {}; type __VLS_PrettifyGlobal<T> = { [K in keyof T as K]: T[K]; } & {};
type __VLS_WithDefaultsGlobal<P, D> = {
[K in keyof P as K extends keyof D ? K : never]-?: P[K];
} & {
[K in keyof P as K extends keyof D ? never : K]: P[K];
};
type __VLS_UseTemplateRef<T> = Readonly<import('vue').ShallowRef<T | null>>; type __VLS_UseTemplateRef<T> = Readonly<import('vue').ShallowRef<T | null>>;
type __VLS_ProxyRefs<T> = import('vue').ShallowUnwrapRef<T>;
function __VLS_getVForSourceType<T extends number | string | any[] | Iterable<any>>(source: T): [ function __VLS_getVForSourceType<T extends number | string | any[] | Iterable<any>>(source: T): [
item: T extends number ? number item: T extends number ? number
@ -115,7 +125,6 @@ export {};
: T extends (...args: any) => any : T extends (...args: any) => any
? T ? T
: (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void; : (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void;
function __VLS_makeOptional<T>(t: T): { [K in keyof T]?: T[K] };
function __VLS_asFunctionalComponent<T, K = T extends new (...args: any) => any ? InstanceType<T> : unknown>(t: T, instance?: K): function __VLS_asFunctionalComponent<T, K = T extends new (...args: any) => any ? InstanceType<T> : unknown>(t: T, instance?: K):
T extends new (...args: any) => any ? __VLS_FunctionalComponent<K> T extends new (...args: any) => any ? __VLS_FunctionalComponent<K>
: T extends () => any ? (props: {}, ctx?: any) => ReturnType<T> : T extends () => any ? (props: {}, ctx?: any) => ReturnType<T>

BIN
frontend/src/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,6 +1,6 @@
<template> <template>
<div id="app">« <!-- 测试模式切换按钮 --> <div id="app">« <!-- 测试模式切换按钮 -->
<div class="test-mode-toggle"> <div class="test-mode-toggle" style="display: none;">
<button @click="toggleTestMode" class="test-btn"> <button @click="toggleTestMode" class="test-btn">
{{ isTestMode ? '切换到思维导图' : '测试Markdown渲染' }} {{ isTestMode ? '切换到思维导图' : '测试Markdown渲染' }}
</button> </button>

View File

@ -303,9 +303,9 @@ const generateMarkdownFromFile = async () => {
await convertToJSON(); await convertToJSON();
// //
addToHistory('AI生成: ' + uploadedFile.value.name, markdownContent.value); addToHistory(uploadedFile.value.name, markdownContent.value);
showNotification('AI生成Markdown成功正在自动保存...', 'success'); // showNotification('AIMarkdown...', 'success');
// //
setTimeout(async () => { setTimeout(async () => {
@ -437,14 +437,14 @@ const generateMarkdown = async () => {
await convertToJSON(); await convertToJSON();
// //
addToHistory('AI生成: ' + aiPrompt.value.substring(0, 30) + '...', markdownContent.value); addToHistory(aiPrompt.value.substring(0, 30) + '...', markdownContent.value);
// AI // AI
if (aiPrompt.value !== savedAiPrompt.value) { if (aiPrompt.value !== savedAiPrompt.value) {
aiPrompt.value = savedAiPrompt.value; aiPrompt.value = savedAiPrompt.value;
} }
showNotification('AI生成Markdown成功正在自动保存...', 'success'); // showNotification('AIMarkdown...', 'success');
// //
setTimeout(async () => { setTimeout(async () => {
@ -627,7 +627,7 @@ Level 4 标题用 #####
try { try {
// //
showNotification('AI正在分析文档请耐心等待最多2分钟...', 'info'); // showNotification('AI2...', 'info');
// - // -
const controller = new AbortController(); const controller = new AbortController();
@ -685,7 +685,7 @@ Level 4 标题用 #####
if (isTruncated && retryCount < 2) { if (isTruncated && retryCount < 2) {
console.warn(`⚠️ 检测到内容可能被截断,进行第${retryCount + 1}次重试`); console.warn(`⚠️ 检测到内容可能被截断,进行第${retryCount + 1}次重试`);
showNotification(`检测到内容可能被截断,正在重试...${retryCount + 1}/2`, 'warning'); // showNotification(`...${retryCount + 1}/2`, 'warning');
// 1 // 1
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
@ -1099,7 +1099,7 @@ const importToMindmap = () => {
window.dispatchEvent(event); window.dispatchEvent(event);
// alert // alert
showNotification('已触发导入事件,正在处理...', 'success'); // showNotification('...', 'success');
} catch (error) { } catch (error) {
console.error('JSON解析失败:', error); console.error('JSON解析失败:', error);
showNotification('JSON格式错误请检查数据', 'error'); showNotification('JSON格式错误请检查数据', 'error');
@ -1116,8 +1116,8 @@ const previewMindmap = async () => {
return; return;
} }
isProcessing.value = true; // isProcessing.value = true;
processingMessage.value = '正在保存思维导图...'; // processingMessage.value = '...';
try { try {
const mindmapData = JSON.parse(convertedJSON.value); const mindmapData = JSON.parse(convertedJSON.value);
@ -1141,8 +1141,8 @@ const previewMindmap = async () => {
// //
setTimeout(() => { setTimeout(() => {
isProcessing.value = false; // isProcessing.value = false;
processingMessage.value = ''; // processingMessage.value = '';
showNotification('思维导图已保存成功!', 'success'); showNotification('思维导图已保存成功!', 'success');
// //
@ -1159,8 +1159,8 @@ const previewMindmap = async () => {
}, 2000); }, 2000);
} catch (error) { } catch (error) {
isProcessing.value = false; // isProcessing.value = false;
processingMessage.value = ''; // processingMessage.value = '';
console.error('JSON解析失败:', error); console.error('JSON解析失败:', error);
showNotification('JSON格式错误请检查数据', 'error'); showNotification('JSON格式错误请检查数据', 'error');
} }
@ -1422,7 +1422,7 @@ const loadHistoryItem = async (item) => {
})); }));
// //
showNotification(`正在加载: ${item.title}`, 'info'); // showNotification(`: ${item.title}`, 'info');
} else { } else {
// ID使 // ID使
markdownContent.value = item.content; markdownContent.value = item.content;
@ -1440,7 +1440,7 @@ const loadHistoryItem = async (item) => {
})); }));
// //
showNotification(`正在加载: ${item.title}`, 'info'); // showNotification(`: ${item.title}`, 'info');
} }
}; };

View File

@ -1,33 +1,5 @@
<template> <template>
<div class="mindmap-container"> <div class="mindmap-container">
<!-- 缩放控制按钮 -->
<div class="zoom-controls" :class="{ 'welcome-mode': showWelcome }">
<button @click="zoomIn" class="zoom-btn" title="放大">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
<path d="M11 8v6"></path>
<path d="M8 11h6"></path>
</svg>
</button>
<button @click="zoomOut" class="zoom-btn" title="缩小">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
<path d="M8 11h6"></path>
</svg>
</button>
<button @click="resetZoom" class="zoom-btn" title="重置缩放">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
<path d="M21 3v5h-5"></path>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
<path d="M3 21v-5h5"></path>
</svg>
</button>
<span class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</span>
</div>
<!-- 欢迎页面 --> <!-- 欢迎页面 -->
<div v-if="showWelcome" class="welcome-page"> <div v-if="showWelcome" class="welcome-page">
<div class="welcome-content" :class="{ 'ai-sidebar-collapsed': isAISidebarCollapsed }"> <div class="welcome-content" :class="{ 'ai-sidebar-collapsed': isAISidebarCollapsed }">
@ -105,7 +77,7 @@
<!-- 保存和刷新按钮 --> <!-- 保存和刷新按钮 -->
<div v-if="!showWelcome" class="save-controls"> <div v-if="!showWelcome" class="save-controls">
<button @click="saveMindMap" class="save-btn" title="保存思维导图"> <button @click="saveMindMap" class="save-btn" title="保存思维导图" style="display: none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path> <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17,21 17,13 7,13 7,21"></polyline> <polyline points="17,21 17,13 7,13 7,21"></polyline>
@ -114,7 +86,7 @@
<span>保存</span> <span>保存</span>
</button> </button>
<button @click="refreshMindMap" class="refresh-btn" title="刷新思维导图"> <button @click="refreshMindMap" class="refresh-btn" title="刷新思维导图" style="display: none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 4v6h6"></path> <path d="M1 4v6h6"></path>
<path d="M23 20v-6h-6"></path> <path d="M23 20v-6h-6"></path>
@ -282,10 +254,13 @@ const saveCurrentPosition = () => {
if (!mindElixir.value || !mindmapEl.value) return null; if (!mindElixir.value || !mindmapEl.value) return null;
try { try {
const canvas = mindmapEl.value.querySelector('canvas'); // MindElixir 使 .map-canvas transform
if (canvas) { const mapCanvas = mindmapEl.value.querySelector('.map-canvas');
const transform = canvas.style.transform; if (mapCanvas) {
return { transform }; const transform = mapCanvas.style.transform;
const scaleVal = mindElixir.value.scaleVal || 1;
console.log('📍 保存位置:', { transform, scaleVal });
return { transform, scaleVal };
} }
} catch (error) { } catch (error) {
console.warn('保存位置失败:', error); console.warn('保存位置失败:', error);
@ -295,12 +270,24 @@ const saveCurrentPosition = () => {
// //
const restorePosition = (position) => { const restorePosition = (position) => {
if (!position || !mindmapEl.value) return; if (!position || !mindmapEl.value || !mindElixir.value) return;
try { try {
const canvas = mindmapEl.value.querySelector('canvas'); // MindElixir 使 .map-canvas transform
if (canvas && position.transform) { const mapCanvas = mindmapEl.value.querySelector('.map-canvas');
canvas.style.transform = position.transform; if (mapCanvas && position.transform) {
// transform使 !important
mapCanvas.style.setProperty('transform', position.transform, 'important');
//
if (position.scaleVal && mindElixir.value.scaleVal !== position.scaleVal) {
mindElixir.value.scaleVal = position.scaleVal;
}
// transform-origin
mapCanvas.style.setProperty('transform-origin', 'center center', 'important');
console.log('📍 恢复位置:', { transform: position.transform, scaleVal: position.scaleVal });
} }
} catch (error) { } catch (error) {
console.warn('恢复位置失败:', error); console.warn('恢复位置失败:', error);
@ -308,10 +295,16 @@ const restorePosition = (position) => {
}; };
// - // -
const loadMindmapData = async (data, keepPosition = false) => { const loadMindmapData = async (data, keepPosition = false, shouldCenterRoot = true) => {
try { try {
// //
// keepPosition=false, shouldCenterRoot=false
const currentPosition = keepPosition ? saveCurrentPosition() : null; const currentPosition = keepPosition ? saveCurrentPosition() : null;
if (currentPosition) {
console.log('📍 保存当前位置用于保持原位:', currentPosition);
} else if (!keepPosition && !shouldCenterRoot) {
console.log('📍 添加节点模式:不保存位置,等待居中到新节点');
}
// ID // ID
console.log("🔍 loadMindmapData 被调用,数据:", data); console.log("🔍 loadMindmapData 被调用,数据:", data);
@ -365,10 +358,10 @@ const loadMindmapData = async (data, keepPosition = false) => {
el: mindmapEl.value, el: mindmapEl.value,
direction: MindElixir.RIGHT, direction: MindElixir.RIGHT,
draggable: true, draggable: true,
contextMenu: true, contextMenu: false,
toolBar: true, toolBar: true,
nodeMenu: false, nodeMenu: false,
keypress: true, keypress: false, //
autoCenter: false, autoCenter: false,
infinite: true, infinite: true,
maxScale: 5, maxScale: 5,
@ -393,6 +386,22 @@ const loadMindmapData = async (data, keepPosition = false) => {
const result = mindElixir.value.init(data); const result = mindElixir.value.init(data);
console.log('✅ Mind Elixir实例创建成功初始化结果:', result); console.log('✅ Mind Elixir实例创建成功初始化结果:', result);
// init
if (currentPosition) {
//
restorePosition(currentPosition);
console.log('📍 初始化后立即恢复位置');
//
setTimeout(() => {
restorePosition(currentPosition);
console.log('📍 二次确认位置恢复');
}, 100);
} else if (!keepPosition && !shouldCenterRoot) {
//
console.log('📍 跳过根节点居中,等待居中新节点');
}
// Mind Elixir使markdown // Mind Elixir使markdown
} else { } else {
@ -404,10 +413,10 @@ const loadMindmapData = async (data, keepPosition = false) => {
el: mindmapEl.value, el: mindmapEl.value,
direction: MindElixir.RIGHT, direction: MindElixir.RIGHT,
draggable: true, draggable: true,
contextMenu: true, contextMenu: false,
toolBar: true, toolBar: true,
nodeMenu: false, nodeMenu: false,
keypress: true, keypress: false, //
autoCenter: false, autoCenter: false,
infinite: true, infinite: true,
maxScale: 5, maxScale: 5,
@ -424,13 +433,28 @@ const loadMindmapData = async (data, keepPosition = false) => {
const result = mindElixir.value.init(data); const result = mindElixir.value.init(data);
console.log('✅ Mind Elixir实例延迟创建成功'); console.log('✅ Mind Elixir实例延迟创建成功');
// init
if (currentPosition) {
restorePosition(currentPosition);
console.log('📍 延迟创建后立即恢复位置');
//
setTimeout(() => {
restorePosition(currentPosition);
console.log('📍 延迟创建后二次确认位置恢复');
}, 100);
} else if (!keepPosition && !shouldCenterRoot) {
//
console.log('📍 延迟创建后跳过根节点居中,等待居中新节点');
}
// //
setTimeout(() => { setTimeout(() => {
// Mind Elixir使markdown // Mind Elixir使markdown
if (keepPosition && currentPosition) { if (currentPosition) {
restorePosition(currentPosition); restorePosition(currentPosition);
} else { } else if (shouldCenterRoot) {
centerMindMap(); centerMindMap();
} }
bindEventListeners(); bindEventListeners();
@ -451,10 +475,10 @@ const loadMindmapData = async (data, keepPosition = false) => {
el: mindmapEl.value, el: mindmapEl.value,
direction: MindElixir.RIGHT, direction: MindElixir.RIGHT,
draggable: true, draggable: true,
contextMenu: true, contextMenu: false,
toolBar: true, toolBar: true,
nodeMenu: false, nodeMenu: false,
keypress: true, keypress: false, //
autoCenter: false, autoCenter: false,
infinite: true, infinite: true,
maxScale: 5, maxScale: 5,
@ -471,11 +495,26 @@ const loadMindmapData = async (data, keepPosition = false) => {
const result = mindElixir.value.init(data); const result = mindElixir.value.init(data);
// console.log(' Mind Elixir'); // console.log(' Mind Elixir');
// init
if (currentPosition) {
restorePosition(currentPosition);
console.log('📍 重试创建后立即恢复位置');
//
setTimeout(() => {
restorePosition(currentPosition);
console.log('📍 重试创建后二次确认位置恢复');
}, 100);
} else if (!keepPosition && !shouldCenterRoot) {
//
console.log('📍 重试创建后跳过根节点居中,等待居中新节点');
}
// //
setTimeout(() => { setTimeout(() => {
if (keepPosition && currentPosition) { if (currentPosition) {
restorePosition(currentPosition); restorePosition(currentPosition);
} else { } else if (shouldCenterRoot) {
centerMindMap(); centerMindMap();
} }
bindEventListeners(); bindEventListeners();
@ -494,20 +533,20 @@ const loadMindmapData = async (data, keepPosition = false) => {
setTimeout(() => { setTimeout(() => {
if (keepPosition && currentPosition) { if (keepPosition && currentPosition) {
restorePosition(currentPosition); restorePosition(currentPosition);
} else { } else if (shouldCenterRoot) {
centerMindMap(); centerMindMap();
} }
hideWelcomePage(); hideWelcomePage();
bindEventListeners(); bindEventListeners();
// //
if (keepPosition && currentPosition) { if (currentPosition) {
setTimeout(() => { setTimeout(() => {
restorePosition(currentPosition); restorePosition(currentPosition);
// //
addNodeDescriptions(); addNodeDescriptions();
}, 500); }, 500);
} else { } else if (shouldCenterRoot) {
// //
setTimeout(() => { setTimeout(() => {
centerMindMap(); centerMindMap();
@ -515,6 +554,11 @@ const loadMindmapData = async (data, keepPosition = false) => {
// //
addNodeDescriptions(); addNodeDescriptions();
}, 500); }, 500);
} else {
//
setTimeout(() => {
addNodeDescriptions();
}, 500);
} }
}, 100); }, 100);
@ -909,6 +953,14 @@ const applyCustomNodePositions = () => {
// //
const centerMindMap = () => { const centerMindMap = () => {
try { try {
// 使 MindElixir toCenter
if (mindElixir.value && mindElixir.value.toCenter) {
mindElixir.value.toCenter();
console.log('✅ 使用 MindElixir toCenter 方法实现根节点居中');
return;
}
// toCenter 使
const mindmapContainer = mindmapEl.value; const mindmapContainer = mindmapEl.value;
if (!mindmapContainer) return; if (!mindmapContainer) return;
@ -990,9 +1042,54 @@ const centerMindMap = () => {
//
const centerNodeAndEdit = async (nodeId) => {
if (!mindElixir.value || !nodeId) return;
try {
console.log('🎯 开始处理新节点:', nodeId);
//
let topicElement = null;
let attempts = 0;
const maxAttempts = 5;
while (!topicElement && attempts < maxAttempts) {
topicElement = mindElixir.value.findEle(nodeId);
if (!topicElement) {
attempts++;
await new Promise(resolve => setTimeout(resolve, 50)); //
}
}
if (topicElement) {
console.log('✅ 找到节点元素:', topicElement);
// 使
if (mindElixir.value.scrollIntoView) {
// scrollIntoView move(-offsetX, -offsetY, true)
mindElixir.value.scrollIntoView(topicElement);
console.log('✅ 节点已平滑居中显示');
}
//
setTimeout(() => {
if (mindElixir.value.beginEdit) {
mindElixir.value.beginEdit(topicElement);
console.log('✅ 节点已进入编辑状态');
}
}, 350); // 0.3s + 50ms
} else {
console.error('❌ 多次尝试后仍未找到节点元素:', nodeId);
}
} catch (error) {
console.error('❌ 居中显示节点失败:', error);
}
};
// //
const calculateMenuPosition = () => { const calculateMenuPosition = () => {
// console.log(":", selectedNode.value); // console.log("🎯 :", selectedNode.value); //
if (!selectedNode.value) return; if (!selectedNode.value) return;
// 使Mind ElixirAPI // 使Mind ElixirAPI
@ -1057,28 +1154,8 @@ const calculateMenuPosition = () => {
} }
} }
// // -
if (!nodeElement) { // 使
const allElements = document.querySelectorAll('*');
for (const element of allElements) {
if (element.textContent && element.textContent.trim() === selectedNode.value.topic) {
// me-tpc
if (element.tagName === 'ME-TPC') {
nodeElement = element;
break;
}
// me-tpc
if (element.closest('me-tpc')) {
nodeElement = element;
break;
}
if (element.classList.contains('topic') || element.closest('.topic')) {
nodeElement = element.closest('.topic') || element;
break;
}
}
}
}
// console.log(":", nodeElement); // console.log(":", nodeElement);
@ -1708,6 +1785,36 @@ const showCopyError = () => {
}, 3000); }, 3000);
}; };
//
const showNotification = (message, type = 'success') => {
const notification = document.createElement('div');
notification.textContent = message;
const bgColor = type === 'success' ? '#4CAF50' : type === 'error' ? '#f44336' : '#ff9800';
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${bgColor};
color: white;
padding: 12px 20px;
border-radius: 6px;
font-size: 14px;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
animation: slideIn 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 2000);
};
// //
const showEditSuccessNotification = () => { const showEditSuccessNotification = () => {
const notification = document.createElement('div'); const notification = document.createElement('div');
@ -1797,25 +1904,88 @@ const handleNodeMove = async (node, oldParent, newParent) => {
} }
}; };
// //
const handleNodeMoveOperation = async (operation) => { const handleNodeDragOperation = async (operation) => {
try { try {
// console.log("🎯 处理节点拖拽操作:", operation.name);
const movedNode = operation.obj; // console.log("📦 操作详情:", {
const targetNode = operation.toObj; // name: operation.name,
const originParentId = operation.originParentId; // ID objs: operation.objs,
toObj: operation.toObj
});
if (movedNode && targetNode) { // MindElixirDOM
// movedNode targetNode setTimeout(async () => {
const node = movedNode; try {
const newParentId = targetNode.id; const movedNodes = operation.objs || []; //
const targetNode = operation.toObj; //
// API
await updateNodeParent(node, newParentId); if (!movedNodes.length || !targetNode) {
} console.warn('⚠️ 拖拽操作缺少必要信息');
return;
}
console.log(`📦 准备保存 ${movedNodes.length} 个节点的父子关系`);
//
let newParentId = null;
if (operation.name === 'moveNodeIn') {
//
newParentId = targetNode.id;
console.log(`📌 拖入操作:新父节点为 ${newParentId}`);
} else if (operation.name === 'moveNodeBefore' || operation.name === 'moveNodeAfter') {
//
newParentId = targetNode.parent?.id || null;
console.log(`📌 拖到兄弟位置:新父节点为 ${newParentId || '根节点'}`);
}
//
const updatePromises = movedNodes.map(async (node) => {
try {
console.log(`🔄 更新节点 ${node.id} 的父节点为 ${newParentId || '根节点'}`);
const response = await mindmapAPI.updateNode(node.id, {
newParentId: newParentId
});
if (response.data && response.data.success) {
console.log(`✅ 节点 ${node.id} 父子关系更新成功`);
return { success: true, nodeId: node.id };
} else {
console.warn(`⚠️ 节点 ${node.id} 父子关系更新失败:`, response);
return { success: false, nodeId: node.id };
}
} catch (error) {
console.error(`❌ 节点 ${node.id} 父子关系更新失败:`, error);
return { success: false, nodeId: node.id, error };
}
});
//
const results = await Promise.all(updatePromises);
const successCount = results.filter(r => r.success).length;
const failCount = results.filter(r => !r.success).length;
console.log(`📊 拖拽保存结果: ${successCount} 成功, ${failCount} 失败`);
//
// if (successCount > 0) {
// showNotification(` (${successCount}/${movedNodes.length})`, 'success');
// }
// if (failCount > 0) {
// showNotification(` (${failCount}/${movedNodes.length})`, 'error');
// }
} catch (error) {
console.error('❌ 保存拖拽后的结构失败:', error);
showNotification('❌ 节点拖拽保存失败', 'error');
}
}, 500); // 500msDOM
} catch (error) { } catch (error) {
// console.error('❌ 处理节点拖拽操作失败:', error);
} }
}; };
@ -1967,47 +2137,23 @@ const addChildNodeToAPI = async (parentNode) => {
// console.log("🎯 使loadMindmapData..."); // console.log("🎯 使loadMindmapData...");
try { try {
//
const currentPosition = saveCurrentPosition();
// //
await new Promise(resolve => setTimeout(resolve, 800)); await new Promise(resolve => setTimeout(resolve, 800));
// //
const mindmapResponse = await mindmapAPI.getMindmap(mindmapId); const mindmapResponse = await mindmapAPI.getMindmap(mindmapId);
if (mindmapResponse.data && mindmapResponse.data.nodeData) { if (mindmapResponse.data && mindmapResponse.data.nodeData) {
// 使loadMindmapData // 使loadMindmapData
await loadMindmapData(mindmapResponse.data, true); await loadMindmapData(mindmapResponse.data, false, false);
// console.log(" "); // console.log(" ");
// //
setTimeout(async () => { try {
try { console.log('🎯 开始居中显示新子节点:', newNode.id);
// await centerNodeAndEdit(newNode.id);
const latestResponse = await mindmapAPI.getMindmap(mindmapId); } catch (error) {
if (latestResponse.data && latestResponse.data.nodeData) { console.error("居中显示新节点失败:", error);
// }
const findNewNode = (node) => {
if (node.id === newNode.id) return node;
if (node.children) {
for (const child of node.children) {
const found = findNewNode(child);
if (found) return found;
}
}
return null;
};
const foundNode = findNewNode(latestResponse.data.nodeData);
if (foundNode && mindElixir.value) {
// console.log(":", foundNode);
mindElixir.value.editText(foundNode);
}
}
} catch (error) {
console.error("查找新节点失败:", error);
}
}, 1000);
} else { } else {
throw new Error("无法获取思维导图数据"); throw new Error("无法获取思维导图数据");
@ -2071,46 +2217,20 @@ const addSiblingNodeToAPI = async (node) => {
console.log("🎯 使用MindElixir init方法重新初始化数据..."); console.log("🎯 使用MindElixir init方法重新初始化数据...");
try { try {
//
const currentPosition = saveCurrentPosition();
// //
const mindmapResponse = await mindmapAPI.getMindmap(mindmapId); const mindmapResponse = await mindmapAPI.getMindmap(mindmapId);
if (mindmapResponse.data && mindmapResponse.data.nodeData) { if (mindmapResponse.data && mindmapResponse.data.nodeData) {
// 使loadMindmapData // 使loadMindmapData
await loadMindmapData(mindmapResponse.data, true); await loadMindmapData(mindmapResponse.data, false, false);
console.log("✅ 思维导图刷新成功"); console.log("✅ 思维导图刷新成功");
//
try {
// console.log('🎯 开始居中显示新兄弟节点:', newNode.id);
setTimeout(async () => { await centerNodeAndEdit(newNode.id);
try { } catch (error) {
// console.error("居中显示新节点失败:", error);
const latestResponse = await mindmapAPI.getMindmap(mindmapId); }
if (latestResponse.data && latestResponse.data.nodeData) {
//
const findNewNode = (node) => {
if (node.id === newNode.id) return node;
if (node.children) {
for (const child of node.children) {
const found = findNewNode(child);
if (found) return found;
}
}
return null;
};
const foundNode = findNewNode(latestResponse.data.nodeData);
if (foundNode && mindElixir.value) {
console.log("找到新节点,进入编辑模式:", foundNode);
mindElixir.value.editText(foundNode);
}
}
} catch (error) {
console.error("查找新节点失败:", error);
}
}, 1000);
} else { } else {
throw new Error("无法获取思维导图数据"); throw new Error("无法获取思维导图数据");
} }
@ -2121,7 +2241,7 @@ const addSiblingNodeToAPI = async (node) => {
// 退 // 退
const mindmapResponse = await mindmapAPI.getMindmap(mindmapId); const mindmapResponse = await mindmapAPI.getMindmap(mindmapId);
if (mindmapResponse.data && mindmapResponse.data.nodeData) { if (mindmapResponse.data && mindmapResponse.data.nodeData) {
await loadMindmapData(mindmapResponse.data, true); await loadMindmapData(mindmapResponse.data, true, false);
} }
} }
} }
@ -2145,26 +2265,11 @@ const deleteNodeFromAPI = async (node) => {
// ID // ID
const mindmapId = currentMindmapId.value || node.mindmap_id || node.mindmapId; const mindmapId = currentMindmapId.value || node.mindmap_id || node.mindmapId;
if (mindmapId) { if (mindmapId) {
// //
const mindmapResponse = await mindmapAPI.getMindmap(mindmapId); const mindmapResponse = await mindmapAPI.getMindmap(mindmapId);
if (mindmapResponse.data && mindmapResponse.data.nodeData) { if (mindmapResponse.data && mindmapResponse.data.nodeData) {
await loadMindmapData(mindmapResponse.data, true); // await loadMindmapData(mindmapResponse.data, true, false); //
console.log("✅ 删除节点后思维导图已刷新,保持当前位置");
//
setTimeout(async () => {
try {
console.log("重新加载思维导图数据...");
const refreshResponse = await mindmapAPI.getMindmap(mindmapId);
if (refreshResponse.data && refreshResponse.data.nodeData) {
await loadMindmapData(refreshResponse.data, true); //
console.log("思维导图数据重新加载成功");
}
} catch (error) {
console.error("重新加载思维导图失败:", error);
}
}, 1500);
} }
} }
} }
@ -2273,13 +2378,25 @@ const createNodesRecursively = async (node, mindmapId, parentId) => {
}; };
// //
//
let wheelHandler = null;
let clickHandler = null;
const bindEventListeners = () => { const bindEventListeners = () => {
if (!mindElixir.value) return; if (!mindElixir.value) return;
console.log("绑定事件监听器..."); console.log("绑定事件监听器...");
//
if (wheelHandler) {
mindmapEl.value.removeEventListener('wheel', wheelHandler);
}
if (clickHandler) {
mindmapEl.value.removeEventListener('click', clickHandler);
}
// //
mindmapEl.value.addEventListener('wheel', (event) => { wheelHandler = (event) => {
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
event.preventDefault(); event.preventDefault();
@ -2296,27 +2413,27 @@ const bindEventListeners = () => {
} }
} }
} }
}); };
mindmapEl.value.addEventListener('wheel', wheelHandler);
// //
mindElixir.value.bus.addListener("select", (node) => { mindElixir.value.bus.addListener("select", (node) => {
console.log("select事件触发:", node); // console.log("select:", node); //
selectedNode.value = node; selectedNode.value = node;
// DOM
setTimeout(() => { setTimeout(() => {
if (!contextMenuStyle.value.left || contextMenuStyle.value.left === '50%') { calculateMenuPosition();
calculateMenuPosition(); }, 50);
}
}, 200);
}); });
mindElixir.value.bus.addListener("selectNode", (node) => { mindElixir.value.bus.addListener("selectNode", (node) => {
console.log("selectNode事件触发:", node); // console.log("selectNode:", node); //
selectedNode.value = node; selectedNode.value = node;
// DOM
setTimeout(() => { setTimeout(() => {
if (!contextMenuStyle.value.left || contextMenuStyle.value.left === '50%') { calculateMenuPosition();
calculateMenuPosition(); }, 50);
}
}, 200);
}); });
// //
@ -2329,19 +2446,24 @@ const bindEventListeners = () => {
} }
}); });
// //
//
if (window.zoomIntervalId) {
clearInterval(window.zoomIntervalId);
}
const zoomInterval = setInterval(() => { const zoomInterval = setInterval(() => {
if (zoomLevel.value !== 1 && mindmapEl.value) { if (zoomLevel.value !== 1 && mindmapEl.value) {
maintainZoomLevel(); maintainZoomLevel();
} }
}, 100); }, 500); // 500msCPU
// ID便 // ID便
window.zoomIntervalId = zoomInterval; window.zoomIntervalId = zoomInterval;
// //
let lastClickTime = 0; let lastClickTime = 0;
mindmapEl.value.addEventListener('click', (event) => { clickHandler = (event) => {
const currentTime = Date.now(); const currentTime = Date.now();
if (currentTime - lastClickTime < 100) { if (currentTime - lastClickTime < 100) {
return; return;
@ -2349,21 +2471,33 @@ const bindEventListeners = () => {
lastClickTime = currentTime; lastClickTime = currentTime;
const clickedElement = event.target; const clickedElement = event.target;
const isNodeClick = clickedElement.closest('me-tpc') || const topicElement = clickedElement.closest('me-tpc') ||
clickedElement.closest('.topic') || clickedElement.closest('.topic') ||
clickedElement.classList.contains('topic') || (clickedElement.classList.contains('topic') ? clickedElement : null) ||
clickedElement.tagName === 'ME-TPC'; (clickedElement.tagName === 'ME-TPC' ? clickedElement : null);
if (selectedNode.value && isNodeClick) { if (topicElement) {
const containerRect = mindmapEl.value.getBoundingClientRect(); //
const left = event.clientX - containerRect.left; const nodeObj = topicElement.nodeObj;
const top = event.clientY - containerRect.top + 50; if (nodeObj) {
selectedNode.value = nodeObj;
contextMenuStyle.value = {
left: `${left}px`, //
top: `${top}px` const containerRect = mindmapEl.value.getBoundingClientRect();
}; const topicRect = topicElement.getBoundingClientRect();
} else if (!isNodeClick) { // CSS transform: translateX(-50%)
const left = topicRect.left - containerRect.left + topicRect.width / 2;
const top = topicRect.bottom - containerRect.top + 8; // 8px
contextMenuStyle.value = {
left: `${left}px`,
top: `${top}px`
};
// console.log(' :', nodeObj.topic); //
}
} else {
//
selectedNode.value = null; selectedNode.value = null;
// AI // AI
if (showAIDialog.value) { if (showAIDialog.value) {
@ -2373,7 +2507,9 @@ const bindEventListeners = () => {
isAIProcessing.value = false; isAIProcessing.value = false;
} }
} }
}); };
mindmapEl.value.addEventListener('click', clickHandler);
mindElixir.value.bus.addListener("edit", (node) => { mindElixir.value.bus.addListener("edit", (node) => {
console.log("edit事件触发:", node); console.log("edit事件触发:", node);
@ -2385,28 +2521,30 @@ const bindEventListeners = () => {
handleEditFinish(operation); handleEditFinish(operation);
}); });
// - // -
mindElixir.value.bus.addListener("operation", (operation) => { mindElixir.value.bus.addListener("operation", (operation) => {
console.log("Mind Elixir操作事件:", operation); console.log("Mind Elixir操作事件:", operation);
if (operation.name === 'moveNode') { //
console.log("检测到节点移动操作:", operation); if (operation.name === 'moveNodeIn' || operation.name === 'moveNodeBefore' || operation.name === 'moveNodeAfter') {
handleNodeMoveOperation(operation); console.log("检测到节点拖拽操作:", operation.name, operation);
handleNodeDragOperation(operation);
} else if (operation.name === 'finishEdit') { } else if (operation.name === 'finishEdit') {
console.log("检测到编辑完成操作:", operation); console.log("检测到编辑完成操作:", operation);
handleEditFinish(operation); handleEditFinish(operation);
} }
}); });
mindElixir.value.bus.addListener("addChild", (node) => { // MindElixir 使
console.log("添加子节点:", node); // mindElixir.value.bus.addListener("addChild", (node) => {
addChildNodeToAPI(node); // console.log(":", node);
}); // addChildNodeToAPI(node);
// });
mindElixir.value.bus.addListener("addSibling", (node) => { // mindElixir.value.bus.addListener("addSibling", (node) => {
console.log("添加兄弟节点:", node); // console.log(":", node);
addSiblingNodeToAPI(node); // addSiblingNodeToAPI(node);
}); // });
mindElixir.value.bus.addListener("removeNode", (node) => { mindElixir.value.bus.addListener("removeNode", (node) => {
console.log("删除节点:", node); console.log("删除节点:", node);
@ -2738,8 +2876,8 @@ const refreshMindMap = async () => {
if (response.data && response.data.nodeData) { if (response.data && response.data.nodeData) {
console.log("✅ 获取到最新数据,开始刷新显示..."); console.log("✅ 获取到最新数据,开始刷新显示...");
// 使loadMindmapData // 使loadMindmapData
await loadMindmapData(response.data, true); await loadMindmapData(response.data, true, false);
console.log("🎉 思维导图刷新完成!"); console.log("🎉 思维导图刷新完成!");
} else { } else {
@ -3091,10 +3229,25 @@ onMounted(async () => {
// //
const cleanupIntervals = () => { const cleanupIntervals = () => {
//
if (window.zoomIntervalId) { if (window.zoomIntervalId) {
clearInterval(window.zoomIntervalId); clearInterval(window.zoomIntervalId);
window.zoomIntervalId = null; window.zoomIntervalId = null;
} }
//
if (mindmapEl.value) {
if (wheelHandler) {
mindmapEl.value.removeEventListener('wheel', wheelHandler);
wheelHandler = null;
}
if (clickHandler) {
mindmapEl.value.removeEventListener('click', clickHandler);
clickHandler = null;
}
}
console.log('✅ 已清理所有定时器和事件监听器');
}; };
// //
@ -3142,7 +3295,7 @@ const refreshMindmapAfterEdit = async (mindmapId, savedPosition) => {
try { try {
const mindmapResponse = await mindmapAPI.getMindmap(mindmapId); const mindmapResponse = await mindmapAPI.getMindmap(mindmapId);
if (mindmapResponse.data && mindmapResponse.data.nodeData) { if (mindmapResponse.data && mindmapResponse.data.nodeData) {
await loadMindmapData(mindmapResponse.data, true); await loadMindmapData(mindmapResponse.data, true, false);
console.log("✅ 使用回退方案刷新成功"); console.log("✅ 使用回退方案刷新成功");
} }
} catch (fallbackError) { } catch (fallbackError) {
@ -3332,10 +3485,10 @@ const updateMindMapRealtime = async (data, title) => {
el: mindmapEl.value, el: mindmapEl.value,
direction: MindElixir.RIGHT, direction: MindElixir.RIGHT,
draggable: true, draggable: true,
contextMenu: true, contextMenu: false,
toolBar: true, toolBar: true,
nodeMenu: false, nodeMenu: true,
keypress: true, keypress: false, //
autoCenter: false, autoCenter: false,
infinite: true, infinite: true,
maxScale: 5, maxScale: 5,
@ -3445,78 +3598,6 @@ const updateMindMapRealtime = async (data, title) => {
overflow: visible; overflow: visible;
} }
/* 缩放控制按钮样式 */
.zoom-controls {
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
gap: 8px;
background: white;
border-radius: 8px;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.zoom-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background: white;
color: #666;
cursor: pointer;
transition: all 0.2s ease;
}
.zoom-btn:hover {
background: #f5f5f5;
border-color: #ccc;
color: #333;
}
.zoom-btn:active {
background: #e0e0e0;
transform: scale(0.95);
}
.zoom-level {
font-size: 12px;
color: #666;
font-weight: 500;
min-width: 40px;
text-align: center;
}
/* 欢迎模式下的缩放控制按钮样式 */
.zoom-controls.welcome-mode {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 1px solid rgba(102, 8, 116, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.zoom-controls.welcome-mode .zoom-btn {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(102, 8, 116, 0.2);
color: #660874;
}
.zoom-controls.welcome-mode .zoom-btn:hover {
background: rgba(255, 255, 255, 0.95);
border-color: rgba(102, 8, 116, 0.3);
color: #5a0666;
}
.zoom-controls.welcome-mode .zoom-level {
color: #660874;
}
/* 保存控制按钮样式 */ /* 保存控制按钮样式 */
.save-controls { .save-controls {
position: absolute; position: absolute;

BIN
frontend/src/lib/.DS_Store vendored Normal file

Binary file not shown.