统一连线样式并优化连接点位置

- 修改sub函数采用与main函数相同的二次贝塞尔曲线样式
- 统一所有连线的视觉风格,消除曲线和横线的混合
- 优化连接点位置,向内偏移31像素确保连线连接到节点边框内部
- 消除连线与节点之间的视觉间隔,达到完美贴合效果
This commit is contained in:
lixinran 2025-10-11 12:51:05 +08:00
parent d69def44ca
commit 4bb72ba6a4
13 changed files with 144 additions and 109 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -23,8 +23,8 @@
flex-direction: column;
}
</style>
<script type="module" crossorigin src="/assets/index-a814442b.js"></script>
<link rel="stylesheet" href="/assets/index-1f5435d2.css">
<script type="module" crossorigin src="/assets/index-a5a78393.js"></script>
<link rel="stylesheet" href="/assets/index-2e253ee6.css">
</head>
<body>
<div id="app"></div>

View File

@ -164,6 +164,7 @@
</div>
</div>
</div>
</template>
@ -172,7 +173,7 @@ import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import MindElixir from '../lib/mind-elixir/dist/MindElixir.js';
import '../lib/mind-elixir/dist/style.css';
//
//
const customTheme = {
name: 'Light Purple',
type: 'light',
@ -209,6 +210,13 @@ const customTheme = {
'--panel-bgcolor': '#ffffff',
'--panel-border-color': '#eaeaea',
'--map-padding': '50px',
// -
'--enable-node-border': 'true',
'--node-border-padding': '8',
'--node-border-radius': '8',
'--node-border-color': '#660874',
'--node-border-fill': '#ffffff',
'--node-border-width': '1',
},
};
import { mindmapAPI } from '../api/mindmap.js';
@ -250,6 +258,7 @@ const imagePreviewError = ref('');
//
const showWelcomePage = () => {
showWelcome.value = true;
@ -351,6 +360,7 @@ const retryLoadImage = () => {
startImageLoadTimeout();
};
//
let imageLoadTimeout = null;
const startImageLoadTimeout = () => {
@ -543,6 +553,7 @@ const loadMindmapData = async (data, keepPosition = false, shouldCenterRoot = tr
maxScale: 5,
minScale: 0.1,
theme: customTheme, //
enableNodeBorder: true, //
markdown: (text, nodeObj) => {
// markdown
if (text.includes('|') || text.includes('**') || text.includes('`') || text.includes('#') || text.includes('![')) {
@ -645,6 +656,7 @@ const loadMindmapData = async (data, keepPosition = false, shouldCenterRoot = tr
maxScale: 5,
minScale: 0.1,
theme: customTheme, //
enableNodeBorder: true, //
markdown: (text, nodeObj) => {
// markdown
if (text.includes('|') || text.includes('**') || text.includes('`') || text.includes('#') || text.includes('![')) {
@ -748,6 +760,7 @@ const loadMindmapData = async (data, keepPosition = false, shouldCenterRoot = tr
maxScale: 5,
minScale: 0.1,
theme: customTheme, //
enableNodeBorder: true, //
markdown: (text, nodeObj) => {
// markdown
if (text.includes('|') || text.includes('**') || text.includes('`') || text.includes('#') || text.includes('![')) {
@ -4867,5 +4880,6 @@ const updateMindMapRealtime = async (data, title) => {
.retry-button:hover {
background: #7d0a8e;
}
</style>

View File

@ -77,11 +77,11 @@
margin: 10px;
padding: 0;
& > me-tpc {
border-radius: var(--main-radius);
background-color: var(--main-bgcolor);
border: 2px solid var(--main-color);
color: var(--main-color);
padding: 8px 25px;
border-radius: var(--main-radius) !important;
background-color: var(--main-bgcolor) !important;
border: 2px solid var(--main-color) !important;
color: var(--main-color) !important;
padding: 8px 25px !important;
}
}
}
@ -118,9 +118,12 @@
z-index: 10;
me-tpc {
position: relative;
border-radius: 3px;
border-radius: var(--main-radius, 20px); // 使用椭圆圆角
color: var(--color);
padding: var(--topic-padding);
// 所有节点都使用紫色椭圆边框
background-color: var(--node-border-fill, #ffffff);
border: 2px solid var(--node-border-color, #660874);
// drag preview
.insert-preview {

View File

@ -42,6 +42,7 @@ function MindElixir(
handleWheel,
markdown,
imageProxy,
enableNodeBorder,
}: Options
): void {
let ele: HTMLElement | null = null
@ -93,6 +94,7 @@ function MindElixir(
this.handleWheel = handleWheel ?? true
this.markdown = markdown || undefined // Custom markdown parser function
this.imageProxy = imageProxy || undefined // Image proxy function
this.enableNodeBorder = enableNodeBorder ?? false // Node border feature
// this.parentMap = {} // deal with large amount of nodes
this.currentNodes = [] // selected <tpc/> elements
this.currentArrow = null // the selected link svg element

View File

@ -41,6 +41,11 @@ export interface Topic extends HTMLElement {
image?: HTMLImageElement
icons?: HTMLSpanElement
tags?: HTMLDivElement
// 节点边框相关属性
borderRect?: SVGRectElement
borderObserver?: MutationObserver
borderResizeHandler?: () => void
}
export interface Expander extends HTMLElement {

View File

@ -56,6 +56,13 @@ export type Theme = {
'--panel-bgcolor': string
'--panel-border-color': string
'--map-padding': string
// 节点边框相关配置
'--enable-node-border'?: string
'--node-border-padding'?: string
'--node-border-radius'?: string
'--node-border-color'?: string
'--node-border-fill'?: string
'--node-border-width'?: string
}
}
@ -162,6 +169,11 @@ export interface Options {
* @default undefined
*/
imageProxy?: (url: string) => string
/**
* Enable node border feature
* @default false
*/
enableNodeBorder?: boolean
}
export type Uid = string

View File

@ -1,10 +1,11 @@
import { LEFT } from '../const'
import type { Topic, Wrapper, Parent, Children, Expander } from '../types/dom'
import type { MindElixirInstance, NodeObj } from '../types/index'
import { encodeHTML } from '../utils/index'
import { encodeHTML, getOffsetLT } from '../utils/index'
import { layoutChildren } from './layout'
// 移除imageProcessor引用使用MindElixir原生image属性
// DOM manipulation
const $d = document
export const findEle = function (this: MindElixirInstance, id: string, el?: HTMLElement) {
@ -175,9 +176,11 @@ export const createParent = function (this: MindElixirInstance, nodeObj: NodeObj
const tpc = this.createTopic(nodeObj)
shapeTpc.call(this, tpc, nodeObj)
p.appendChild(tpc)
return { p, tpc }
}
export const createChildren = function (this: MindElixirInstance, wrappers: Wrapper[]) {
const children = $d.createElement('me-children') as Children
children.append(...wrappers)

View File

@ -50,31 +50,27 @@ export function main({ pT, pL, pW, pH, cT, cL, cW, cH, direction, containerHeigh
}
export function sub(this: MindElixirInstance, { pT, pL, pW, pH, cT, cL, cW, cH, direction, isFirst }: SubLineParams) {
const GAP = parseInt(this.container.style.getPropertyValue('--node-gap-x')) // cache?
// const GAP = 30
let y1 = 0
let end = 0
if (isFirst) {
y1 = pT + pH / 2
} else {
y1 = pT + pH
}
const y2 = cT + cH
let x1 = 0
let x2 = 0
let xMid = 0
const offset = (Math.abs(y1 - y2) / 300) * GAP
// 完全复制main函数的逻辑
let x1 = pL + pW / 2
const y1 = pT + pH / 2
let x2
if (direction === DirectionClass.LHS) {
xMid = pL
x1 = xMid + GAP
x2 = xMid - GAP
end = cL + GAP
return `M ${x1} ${y1} C ${xMid} ${y1} ${xMid + offset} ${y2} ${x2} ${y2} H ${end}`
x2 = cL + cW - 31 // 向内31像素确保连接到边框内部
} else {
xMid = pL + pW
x1 = xMid - GAP
x2 = xMid + GAP
end = cL + cW - GAP
return `M ${x1} ${y1} C ${xMid} ${y1} ${xMid - offset} ${y2} ${x2} ${y2} H ${end}`
x2 = cL + 31 // 向内31像素确保连接到边框内部
}
const y2 = cT + cH / 2
// 使用与main函数相同的偏移计算方式
const containerHeight = this.container.offsetHeight || 1000 // 提供一个默认值
const pct = Math.abs(y2 - y1) / containerHeight
const offset = (1 - pct) * 0.25 * (pW / 2)
if (direction === DirectionClass.LHS) {
x1 = x1 - pW / 10 - offset
} else {
x1 = x1 + pW / 10 + offset
}
return `M ${x1} ${y1} Q ${x1} ${y2} ${x2} ${y2}`
}