Merge branch 'xingyy' into dev
This commit is contained in:
commit
1ae317dbb3
@ -26,6 +26,7 @@
|
|||||||
"@vueuse/core": "^10.7.0",
|
"@vueuse/core": "^10.7.0",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"highlight.js": "^11.5.0",
|
"highlight.js": "^11.5.0",
|
||||||
"js-audio-recorder": "^1.0.7",
|
"js-audio-recorder": "^1.0.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
@ -44,6 +44,9 @@ importers:
|
|||||||
axios:
|
axios:
|
||||||
specifier: ^1.6.2
|
specifier: ^1.6.2
|
||||||
version: 1.9.0
|
version: 1.9.0
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.13
|
||||||
|
version: 1.11.13
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: ^11.5.0
|
specifier: ^11.5.0
|
||||||
version: 11.11.1
|
version: 11.11.1
|
||||||
|
@ -76,7 +76,9 @@ const navs = ref([
|
|||||||
|
|
||||||
const mentionList = ref([])
|
const mentionList = ref([])
|
||||||
const currentMentionQuery = ref('')
|
const currentMentionQuery = ref('')
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('props.members',props.members)
|
||||||
|
}, 1000)
|
||||||
// 编辑器内容
|
// 编辑器内容
|
||||||
const editorContent = ref('')
|
const editorContent = ref('')
|
||||||
const editorHtml = ref('')
|
const editorHtml = ref('')
|
||||||
@ -100,6 +102,7 @@ const handleInput = (event) => {
|
|||||||
const editorClone = target.cloneNode(true)
|
const editorClone = target.cloneNode(true)
|
||||||
const quoteElements = editorClone.querySelectorAll('.editor-quote')
|
const quoteElements = editorClone.querySelectorAll('.editor-quote')
|
||||||
quoteElements.forEach(quote => quote.remove())
|
quoteElements.forEach(quote => quote.remove())
|
||||||
|
quoteElements.forEach(quote => quote.remove())
|
||||||
|
|
||||||
// 处理表情图片,将其 alt 属性(表情文本)添加到文本内容中
|
// 处理表情图片,将其 alt 属性(表情文本)添加到文本内容中
|
||||||
const emojiImages = editorClone.querySelectorAll('img.editor-emoji')
|
const emojiImages = editorClone.querySelectorAll('img.editor-emoji')
|
||||||
@ -119,7 +122,8 @@ const handleInput = (event) => {
|
|||||||
editorContent.value = textContent
|
editorContent.value = textContent
|
||||||
|
|
||||||
// 检查是否需要清空编辑器以显示placeholder
|
// 检查是否需要清空编辑器以显示placeholder
|
||||||
const isEmpty = textContent.trim() === '' &&
|
// 只有当编辑器中没有任何内容(包括空格)且没有其他元素时才清空
|
||||||
|
const isEmpty = textContent === '' &&
|
||||||
!target.querySelector('img, .editor-file, .mention')
|
!target.querySelector('img, .editor-file, .mention')
|
||||||
|
|
||||||
if (isEmpty && target.innerHTML !== '') {
|
if (isEmpty && target.innerHTML !== '') {
|
||||||
@ -163,11 +167,30 @@ const showMentionList = () => {
|
|||||||
mentionList.value = props.members.filter(member => {
|
mentionList.value = props.members.filter(member => {
|
||||||
return member.value.toLowerCase().startsWith(query)
|
return member.value.toLowerCase().startsWith(query)
|
||||||
})
|
})
|
||||||
|
if(dialogueStore.groupInfo.is_manager){
|
||||||
|
mentionList.value.unshift({ id: 0, nickname: '全体成员', avatar: defAvatar, value: '全体成员' })
|
||||||
|
}
|
||||||
showMention.value = mentionList.value.length > 0
|
showMention.value = mentionList.value.length > 0
|
||||||
selectedMentionIndex.value = 0
|
selectedMentionIndex.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理鼠标点击选择mention
|
||||||
|
const handleMentionSelectByMouse = (member) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection && selection.rangeCount > 0) {
|
||||||
|
insertMention(member, selection.getRangeAt(0).cloneRange());
|
||||||
|
} else {
|
||||||
|
// 如果没有有效的选区,尝试聚焦编辑器并获取选区
|
||||||
|
editorRef.value?.focus();
|
||||||
|
nextTick(() => {
|
||||||
|
const newSelection = window.getSelection();
|
||||||
|
if (newSelection && newSelection.rangeCount > 0) {
|
||||||
|
insertMention(member, newSelection.getRangeAt(0).cloneRange());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 隐藏mention列表
|
// 隐藏mention列表
|
||||||
const hideMentionList = () => {
|
const hideMentionList = () => {
|
||||||
showMention.value = false
|
showMention.value = false
|
||||||
@ -187,65 +210,73 @@ const updateMentionPosition = (range) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 插入mention
|
// 插入mention
|
||||||
const insertMention = (member) => {
|
const insertMention = (member, clonedRange) => {
|
||||||
const selection = window.getSelection()
|
console.log('插入mention', member);
|
||||||
if (!selection.rangeCount) return
|
const selection = window.getSelection();
|
||||||
|
if (!clonedRange || !selection) return;
|
||||||
const range = selection.getRangeAt(0)
|
|
||||||
const textNode = range.startContainer
|
const range = clonedRange; // 使用传入的克隆 range
|
||||||
const offset = range.startOffset
|
|
||||||
|
const textNode = range.startContainer;
|
||||||
// 找到@符号的位置
|
const offset = range.startOffset;
|
||||||
const textContent = textNode.textContent || ''
|
const textContent = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent || '' : '';
|
||||||
const atIndex = textContent.lastIndexOf('@', offset - 1)
|
// @符号的查找逻辑仅当光标在文本节点内且不在开头时才有意义
|
||||||
|
const atIndex = (textNode.nodeType === Node.TEXT_NODE && offset > 0) ? textContent.lastIndexOf('@', offset - 1) : -1;
|
||||||
// 创建mention元素
|
|
||||||
const mentionSpan = document.createElement('span')
|
const mentionSpan = document.createElement('span');
|
||||||
mentionSpan.className = 'mention'
|
mentionSpan.className = 'mention';
|
||||||
mentionSpan.setAttribute('data-user-id', member.id || member.user_id)
|
mentionSpan.setAttribute('data-user-id', String(member.id));
|
||||||
mentionSpan.textContent = `@${member.value || member.nickname}`
|
mentionSpan.textContent = `@${member.value || member.nickname}`;
|
||||||
mentionSpan.contentEditable = 'false'
|
mentionSpan.contentEditable = 'false';
|
||||||
|
|
||||||
if (atIndex !== -1) {
|
if (atIndex !== -1 && textNode.nodeType === Node.TEXT_NODE) {
|
||||||
// 如果找到@符号,替换文本
|
const parent = textNode.parentNode;
|
||||||
const beforeText = textContent.substring(0, atIndex)
|
if (!parent) return; // Sanity check
|
||||||
const afterText = textContent.substring(offset)
|
|
||||||
|
// 从@符号开始删除,直到当前光标位置
|
||||||
// 创建新的文本节点
|
range.setStart(textNode, atIndex);
|
||||||
const beforeNode = document.createTextNode(beforeText)
|
range.setEnd(textNode, offset);
|
||||||
const afterNode = document.createTextNode(' ' + afterText)
|
range.deleteContents();
|
||||||
|
|
||||||
// 替换内容
|
// 插入 mention 元素
|
||||||
const parent = textNode.parentNode
|
range.insertNode(mentionSpan);
|
||||||
parent.insertBefore(beforeNode, textNode)
|
|
||||||
parent.insertBefore(mentionSpan, textNode)
|
|
||||||
parent.insertBefore(afterNode, textNode)
|
|
||||||
parent.removeChild(textNode)
|
|
||||||
} else {
|
} else {
|
||||||
// 如果没有找到@符号,直接在光标位置插入
|
// 如果没有找到@符号,或者光标不在合适的文本节点内,直接在当前光标位置插入
|
||||||
range.deleteContents()
|
if (!range.collapsed) {
|
||||||
|
range.deleteContents();
|
||||||
// 插入@提及元素
|
}
|
||||||
range.insertNode(mentionSpan)
|
range.insertNode(mentionSpan);
|
||||||
|
|
||||||
// 在@提及元素后添加空格
|
|
||||||
const spaceNode = document.createTextNode(' ')
|
|
||||||
range.setStartAfter(mentionSpan)
|
|
||||||
range.insertNode(spaceNode)
|
|
||||||
|
|
||||||
// 将光标移动到空格后
|
|
||||||
range.setStartAfter(spaceNode)
|
|
||||||
range.collapse(true)
|
|
||||||
selection.removeAllRanges()
|
|
||||||
selection.addRange(range)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 触发输入事件以更新编辑器内容
|
// 在 mention 之后插入一个空格,并将光标移到空格之后
|
||||||
handleInput({ target: editorRef.value })
|
const spaceNode = document.createTextNode('\u00A0'); // 使用不间断空格
|
||||||
|
const currentParent = mentionSpan.parentNode;
|
||||||
// 隐藏mention列表
|
if (currentParent) {
|
||||||
hideMentionList()
|
// 将空格节点插入到 mentionSpan 之后
|
||||||
}
|
if (mentionSpan.nextSibling) {
|
||||||
|
currentParent.insertBefore(spaceNode, mentionSpan.nextSibling);
|
||||||
|
} else {
|
||||||
|
currentParent.appendChild(spaceNode);
|
||||||
|
}
|
||||||
|
// 设置光标到空格之后
|
||||||
|
range.setStartAfter(spaceNode);
|
||||||
|
range.collapse(true);
|
||||||
|
} else {
|
||||||
|
// Fallback: 如果 mentionSpan 没有父节点(理论上不应该发生),则将光标设置在 mentionSpan 之后
|
||||||
|
range.setStartAfter(mentionSpan);
|
||||||
|
range.collapse(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
|
||||||
|
editorRef.value?.focus(); // 确保编辑器在操作后仍有焦点
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
handleInput({ target: editorRef.value });
|
||||||
|
hideMentionList();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 处理粘贴事件
|
// 处理粘贴事件
|
||||||
const handlePaste = (event) => {
|
const handlePaste = (event) => {
|
||||||
@ -343,12 +374,15 @@ const handleKeydown = (event) => {
|
|||||||
break
|
break
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
const selectedMember = mentionList.value[selectedMentionIndex.value]
|
const selectedMember = mentionList.value[selectedMentionIndex.value];
|
||||||
if (selectedMember) {
|
if (selectedMember) {
|
||||||
insertMention(selectedMember)
|
const selection = window.getSelection();
|
||||||
|
if (selection && selection.rangeCount > 0) {
|
||||||
|
insertMention(selectedMember, selection.getRangeAt(0).cloneRange());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
hideMentionList()
|
hideMentionList()
|
||||||
break
|
break
|
||||||
@ -391,6 +425,25 @@ const handleKeydown = (event) => {
|
|||||||
}
|
}
|
||||||
prevSibling = prevSibling.previousSibling
|
prevSibling = prevSibling.previousSibling
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 如果光标在文本节点中间或末尾,且当前文本节点只包含空格
|
||||||
|
// 检查前一个兄弟节点是否是mention
|
||||||
|
if (!container.textContent.trim()) {
|
||||||
|
let prevSibling = container.previousSibling
|
||||||
|
while (prevSibling) {
|
||||||
|
if (prevSibling.nodeType === Node.ELEMENT_NODE &&
|
||||||
|
prevSibling.classList &&
|
||||||
|
prevSibling.classList.contains('mention')) {
|
||||||
|
targetMention = prevSibling
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 如果是文本节点且不为空,停止查找
|
||||||
|
if (prevSibling.nodeType === Node.TEXT_NODE && prevSibling.textContent.trim()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevSibling = prevSibling.previousSibling
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (container.nodeType === Node.ELEMENT_NODE) {
|
} else if (container.nodeType === Node.ELEMENT_NODE) {
|
||||||
// 如果光标在元素节点中,检查前一个子节点
|
// 如果光标在元素节点中,检查前一个子节点
|
||||||
@ -525,24 +578,29 @@ const sendMessage = () => {
|
|||||||
if (messageData.items.length === 0 ||
|
if (messageData.items.length === 0 ||
|
||||||
(messageData.items.length === 1 &&
|
(messageData.items.length === 1 &&
|
||||||
messageData.items[0].type === 1 &&
|
messageData.items[0].type === 1 &&
|
||||||
!messageData.items[0].content.trim())) {
|
!messageData.items[0].content.trimEnd())) {
|
||||||
return // 没有内容,不发送
|
return // 没有内容,不发送
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理不同类型的消息
|
// 处理不同类型的消息
|
||||||
messageData.items.forEach(item => {
|
messageData.items.forEach(item => {
|
||||||
// 处理文本内容
|
// 处理文本内容
|
||||||
if (item.type === 1 && item.content.trim()) {
|
if (item.type === 1 && item.content.trimEnd()) {
|
||||||
const data = {
|
const data = {
|
||||||
items: [{
|
items: [{
|
||||||
content: item.content,
|
content: item.content,
|
||||||
type: 1
|
type: 1
|
||||||
}],
|
}],
|
||||||
mentionUids: messageData.mentionUids,
|
mentionUids: messageData.mentionUids,
|
||||||
mentions: [],
|
mentions: messageData.mentionUids.map(uid => {
|
||||||
|
return {
|
||||||
|
atid: uid,
|
||||||
|
name: mentionList.value.find(member => member.id === uid)?.nickname || ''
|
||||||
|
}
|
||||||
|
}),
|
||||||
quoteId: messageData.quoteId,
|
quoteId: messageData.quoteId,
|
||||||
}
|
}
|
||||||
|
console.log('data',data)
|
||||||
emit(
|
emit(
|
||||||
'editor-event',
|
'editor-event',
|
||||||
emitCall('text_event', data)
|
emitCall('text_event', data)
|
||||||
@ -602,7 +660,7 @@ const parseEditorContent = () => {
|
|||||||
// 处理@提及
|
// 处理@提及
|
||||||
const userId = node.getAttribute('data-user-id')
|
const userId = node.getAttribute('data-user-id')
|
||||||
if (userId) {
|
if (userId) {
|
||||||
mentionUids.push(parseInt(userId))
|
mentionUids.push(Number(userId))
|
||||||
}
|
}
|
||||||
textContent += node.textContent
|
textContent += node.textContent
|
||||||
} else if (node.tagName === 'IMG') {
|
} else if (node.tagName === 'IMG') {
|
||||||
@ -634,7 +692,7 @@ const parseEditorContent = () => {
|
|||||||
if (textContent.trim()) {
|
if (textContent.trim()) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 1,
|
type: 1,
|
||||||
content: textContent.trim()
|
content: textContent.trimEnd()
|
||||||
})
|
})
|
||||||
textContent = ''
|
textContent = ''
|
||||||
}
|
}
|
||||||
@ -674,7 +732,7 @@ const parseEditorContent = () => {
|
|||||||
if (textContent.trim()) {
|
if (textContent.trim()) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 1,
|
type: 1,
|
||||||
content: textContent.trim()
|
content: textContent.trimEnd()
|
||||||
})
|
})
|
||||||
textContent = ''
|
textContent = ''
|
||||||
}
|
}
|
||||||
@ -696,7 +754,7 @@ const parseEditorContent = () => {
|
|||||||
if (textContent.trim()) {
|
if (textContent.trim()) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 1,
|
type: 1,
|
||||||
content: textContent.trim()
|
content: textContent.trimEnd()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,27 +976,44 @@ const insertImageEmoji = (imgSrc, altText) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 事件监听
|
// 事件监听
|
||||||
const onSubscribeMention = (data) => {
|
const onSubscribeMention = async (data) => {
|
||||||
// 确保编辑器获得焦点
|
const editorNode = editorRef.value;
|
||||||
editorRef.value?.focus()
|
if (!editorNode) return;
|
||||||
|
|
||||||
// 如果编辑器为空或者光标不在编辑器内,将光标移动到编辑器末尾
|
editorNode.focus();
|
||||||
const selection = window.getSelection()
|
await nextTick(); // 确保焦点已设置
|
||||||
if (!selection.rangeCount || !editorRef.value.contains(selection.anchorNode)) {
|
|
||||||
const range = document.createRange()
|
let selection = window.getSelection();
|
||||||
if (editorRef.value.lastChild) {
|
if (!selection || selection.rangeCount === 0) {
|
||||||
range.setStartAfter(editorRef.value.lastChild)
|
const range = document.createRange();
|
||||||
|
if (editorNode.lastChild) {
|
||||||
|
range.setStartAfter(editorNode.lastChild);
|
||||||
} else {
|
} else {
|
||||||
range.setStart(editorRef.value, 0)
|
range.setStart(editorNode, 0);
|
||||||
}
|
}
|
||||||
range.collapse(true)
|
range.collapse(true);
|
||||||
selection.removeAllRanges()
|
if (selection) selection.removeAllRanges();
|
||||||
selection.addRange(range)
|
selection?.addRange(range);
|
||||||
|
await nextTick();
|
||||||
|
selection = window.getSelection();
|
||||||
|
} else if (!editorNode.contains(selection.anchorNode)) {
|
||||||
|
const range = document.createRange();
|
||||||
|
if (editorNode.lastChild) {
|
||||||
|
range.setStartAfter(editorNode.lastChild);
|
||||||
|
} else {
|
||||||
|
range.setStart(editorNode, 0);
|
||||||
|
}
|
||||||
|
range.collapse(true);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
await nextTick();
|
||||||
|
selection = window.getSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入@提及
|
if (selection && selection.rangeCount > 0) {
|
||||||
insertMention(data)
|
insertMention(data, selection.getRangeAt(0).cloneRange());
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSubscribeQuote = (data) => {
|
const onSubscribeQuote = (data) => {
|
||||||
// 保存引用数据,但不保存到草稿中
|
// 保存引用数据,但不保存到草稿中
|
||||||
@ -1431,7 +1506,7 @@ const handleEditorClick = (event) => {
|
|||||||
<n-icon size="18" class="icon" :component="nav.icon" />
|
<n-icon size="18" class="icon" :component="nav.icon" />
|
||||||
<p class="tip-title">{{ nav.title }}</p>
|
<p class="tip-title">{{ nav.title }}</p>
|
||||||
</div>
|
</div>
|
||||||
<n-button class="w-80px h-30px ml-auto" type="primary">
|
<n-button class="w-80px h-30px ml-auto" type="primary" @click="sendMessage">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon>
|
<n-icon>
|
||||||
<IosSend />
|
<IosSend />
|
||||||
@ -1483,7 +1558,7 @@ const handleEditorClick = (event) => {
|
|||||||
:key="member.user_id || member.id"
|
:key="member.user_id || member.id"
|
||||||
class="cursor-pointer px-14px h-42px"
|
class="cursor-pointer px-14px h-42px"
|
||||||
:class="{ 'bg-#EEE9F9': index === selectedMentionIndex }"
|
:class="{ 'bg-#EEE9F9': index === selectedMentionIndex }"
|
||||||
@mousedown.prevent="insertMention(member)"
|
@mousedown.prevent="handleMentionSelectByMouse(member)"
|
||||||
@mouseover="selectedMentionIndex = index"
|
@mouseover="selectedMentionIndex = index"
|
||||||
>
|
>
|
||||||
<div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 h-full">
|
<div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 h-full">
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import { useDialogueStore } from '@/store/modules/dialogue.js'
|
import { useDialogueStore } from '@/store/modules/dialogue.js'
|
||||||
|
|
||||||
interface IDropdown {
|
interface IDropdown {
|
||||||
@ -14,11 +15,10 @@ const isRevoke = (uid: any, item: any): boolean => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const datetime = item.created_at.replace(/-/g, '/')
|
const messageTime = dayjs(item.created_at)
|
||||||
|
const now = dayjs()
|
||||||
const time = new Date().getTime() - Date.parse(datetime)
|
const diffInMinutes = now.diff(messageTime, 'minute')
|
||||||
|
return diffInMinutes <= 5
|
||||||
return Math.floor(time / 1000 / 60) <= 2
|
|
||||||
}
|
}
|
||||||
const dialogueStore = useDialogueStore()
|
const dialogueStore = useDialogueStore()
|
||||||
export function useMenu() {
|
export function useMenu() {
|
||||||
@ -48,9 +48,20 @@ export function useMenu() {
|
|||||||
|
|
||||||
dropdown.options.push({ label: '多选', key: 'multiSelect' })
|
dropdown.options.push({ label: '多选', key: 'multiSelect' })
|
||||||
dropdown.options.push({ label: '引用', key: 'quote' })
|
dropdown.options.push({ label: '引用', key: 'quote' })
|
||||||
if (isRevoke(uid, item)|| (dialogueStore.groupInfo as any).is_manager) {
|
//如果是单聊
|
||||||
dropdown.options.push({ label: `撤回`, key: 'revoke' })
|
if(item.talk_type===1){
|
||||||
|
//撤回时间限制内,并且是自己发的
|
||||||
|
if(isRevoke(uid, item)&&item.float==='right'){
|
||||||
|
dropdown.options.push({ label: `撤回`, key: 'revoke' })
|
||||||
|
}
|
||||||
|
//群聊
|
||||||
|
}else if(item.talk_type===2){
|
||||||
|
//管理员可以强制撤回所有成员信息
|
||||||
|
if ((dialogueStore.groupInfo as any).is_manager) {
|
||||||
|
dropdown.options.push({ label: `撤回`, key: 'revoke' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dropdown.options.push({ label: '删除', key: 'delete' })
|
dropdown.options.push({ label: '删除', key: 'delete' })
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user