You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/ui/page/test.html

266 lines
7.3 KiB
HTML

7 months ago
<!doctype html>
<html>
<head>
<title>可编辑 Div 示例</title>
</head>
<style>
.editable-div {
border: 1px solid #ccc;
padding: 10px;
min-height: 100px;
outline: none;
white-space: pre-wrap;
}
.suggestions {
border: 1px solid #ddd;
background: white;
max-height: 150px;
overflow-y: auto;
position: absolute;
z-index: 1000;
}
.suggestion-item {
padding: 8px;
cursor: pointer;
}
.suggestion-item:hover {
background-color: #f0f0f0;
}
.selected-tag {
color: green;
font-weight: bold;
}
</style>
<style unscoped>
.test {
position: relative;
color: red;
display: inline-block;
min-width: 10px;
margin-left: 50px;
margin-right: 50px;
}
/* .test::before { */
/* position: absolute; */
/* left: -20px; */
/* content: '@'; */
/* display: inline-block; */
/* background: #0f0; */
/* width: 10px; */
/* } */
/* .test::after { */
/* content: '@'; */
/* width: 10px; */
/* display: inline-block; */
/* background: #0ff; */
/* /* content: attr(data-content) */
*/
/* } */
</style>
<body>
<div class="editable-div" contenteditable="true" @input="handleInput" @keydown="handleKeydown" ref="editableDiv">
</div>
<div class="suggestions" v-if="showSuggestions">
<div class="suggestion-item" v-for="(item, index) in filteredSuggestions" :key="index"
@mousedown="selectSuggestion(item)">
{{ item }}
</div>
</div>
</body>
<script setup>
// 初始化数据
textContent = "";
suggestions = ["Alice", "Bob", "Charlie", "David", "Eve"];
filteredSuggestions = [];
showSuggestions = false;
currentSelectionIndex = -1;
// 处理输入事件
handleInput = (e) => {
// const div = $node.querySelector(".editable-div");
// textContent = div.innerText;
// // 检测是否输入了 @ 符号
// const caretPosition = getCaretPosition(div);
// const textBeforeCaret = textContent.substring(0, caretPosition);
// const lastAtPos = textBeforeCaret.lastIndexOf("@");
//
// if (lastAtPos !== -1) {
// const query = textBeforeCaret.substring(lastAtPos + 1);
// filteredSuggestions = suggestions.filter((item) =>
// item.toLowerCase().includes(query.toLowerCase())
// );
// } else {
// showSuggestions = false;
// }
};
// 处理键盘事件(上下箭头、回车)
handleKeydown = (e) => {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
let dom = selection.anchorNode;
if (dom.nodeType === 3) {
dom = dom.parentNode;
}
if (dom && dom.hasAttribute && dom.hasAttribute('test')) {
showSuggestions = true
let sub = dom.innerText.slice(1)
console.log(sub)
filteredSuggestions = suggestions.filter((item) =>
item.toLowerCase().includes(sub)
);
} else {
showSuggestions = false
}
if (!showSuggestions) {
if (e.key === '@') {
e.preventDefault();
const div = document.createElement('div');
div.setAttribute('test', '')
div.innerText = '@'
div.classList.add('test')
// 插入换行符到当前光标位置
range.deleteContents(); // 清除选中的内容(如果有)
// range.insertNode(document.createElement('div'))
range.insertNode(div);
// 确保光标移动到换行后的位置
// const nextNode = br.nextSibling || br.parentNode.appendChild(document.createTextNode(''));
const newRange = document.createRange();
newRange.setStartAfter(div.childNodes[0]);
newRange.collapse(true);
// 更新选区
selection.removeAllRanges();
selection.addRange(newRange);
}
return
}
if (e.key === "ArrowDown") {
e.preventDefault();
currentSelectionIndex =
(currentSelectionIndex + 1) % filteredSuggestions.length;
if (isNaN(currentSelectionIndex)) {
currentSelectionIndex = 0
}
highlightSuggestion();
} else if (e.key === "ArrowUp") {
e.preventDefault();
currentSelectionIndex =
(currentSelectionIndex - 1 + filteredSuggestions.length) %
filteredSuggestions.length;
if (isNaN(currentSelectionIndex)) {
currentSelectionIndex = 0
}
highlightSuggestion();
} else if (e.key === "Enter") {
e.preventDefault();
if (currentSelectionIndex !== -1) {
let item = filteredSuggestions[currentSelectionIndex]
console.log(dom, item)
dom.innerText = '@' + item
const newRange = document.createRange();
if (!dom.nextSibling) {
dom.parentNode.appendChild(document.createTextNode(''))
}
if (dom.nextSibling.nodeType === 3) {
let span = document.createElement('span')
span.innerText = dom.nextSibling.textContent || ' '
span.style.minWidth = '10px'
dom.nextSibling.replaceWith(span)
}
let next = dom.nextSibling
if (next.nodeType === 3) {
next = next.nextSibling
}
console.log([next])
newRange.setStartAfter(next);
newRange.collapse(true);
selection.removeAllRanges(); // 清除当前的选择
selection.addRange(newRange);
dom.setAttribute('contenteditable', 'false')
dom.style.contenteditable = false
// selectSuggestion(filteredSuggestions[currentSelectionIndex]);
}
}
};
// 选中建议项
selectSuggestion = (item) => {
const div = $node.querySelector(".editable-div");
const caretPosition = getCaretPosition(div);
const textBeforeCaret = textContent.substring(0, caretPosition);
const lastAtPos = textBeforeCaret.lastIndexOf("@");
// 替换 @ 后的内容为绿色标记
let before = textBeforeCaret.substring(0, lastAtPos);
let after = textContent.substring(caretPosition)
const newText = before +
`<span class="selected-tag">@${item}</span>` +
after
console.log(newText)
div.innerHTML = newText;
setCaretPosition(div, lastAtPos + 2); // 重新设置光标位置
// 隐藏建议框
showSuggestions = false;
currentSelectionIndex = -1;
// 更新文本内容
textContent = div.innerText;
};
// 获取光标位置
getCaretPosition = (element) => {
let position = 0;
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
position = preCaretRange.toString().length;
}
return position;
};
// 设置光标位置
setCaretPosition = (element, position) => {
const range = document.createRange();
const selection = window.getSelection();
console.log(element.childNodes[0], element, position);
range.setStart(element, position);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
};
// 高亮当前选中的建议项
highlightSuggestion = () => {
const suggestionItems = $node.querySelectorAll(".suggestion-item");
suggestionItems.forEach((item, index) => {
item.style.backgroundColor =
index === currentSelectionIndex ? "#f0f0f0" : "";
});
};
</script>
</html>