✨ feat(im): 修一批 L 危:window.open 加 noopener、@ 浮层箭头方向跟随定位、置顶展开切群重置、加黑名单取消静默、菜单负坐标兜底、群选择回显跨页补查
parent
3949e0c89f
commit
1015423431
|
|
@ -76,7 +76,8 @@ const adjustedPosition = computed(() => {
|
||||||
x = window.innerWidth - menuWidth
|
x = window.innerWidth - menuWidth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { x, y }
|
// 视口很小 / 菜单项很多时上面减法会算出负值,把菜单顶 / 左边推到 0 兜底
|
||||||
|
return { x: Math.max(0, x), y: Math.max(0, y) }
|
||||||
})
|
})
|
||||||
|
|
||||||
type MenuItem = (typeof contextMenu.value.items)[number]
|
type MenuItem = (typeof contextMenu.value.items)[number]
|
||||||
|
|
|
||||||
|
|
@ -386,7 +386,11 @@ async function handleBlock() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const target = props.user
|
const target = props.user
|
||||||
await message.confirm(`确定将「${target.nickname || ''}」加入黑名单吗?`, '加入黑名单')
|
try {
|
||||||
|
await message.confirm(`确定将「${target.nickname || ''}」加入黑名单吗?`, '加入黑名单')
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
await friendStore.blockFriend(target.id)
|
await friendStore.blockFriend(target.id)
|
||||||
message.success('已加入黑名单')
|
message.success('已加入黑名单')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,11 @@
|
||||||
/>
|
/>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
|
||||||
<!-- 底部三角指针:旋转 45° 的方块半露出底边,指向输入区里的 @ 字符 -->
|
<!-- 三角指针:picker 在 @ 下方(position.top 锚定)→ 箭头朝上贴顶;picker 在 @ 上方(position.bottom 锚定)→ 箭头朝下贴底 -->
|
||||||
<div class="absolute left-4 -bottom-1.5 w-3 h-3 rotate-45 bg-[var(--el-bg-color)]"></div>
|
<div
|
||||||
|
class="absolute left-4 w-3 h-3 rotate-45 bg-[var(--el-bg-color)]"
|
||||||
|
:class="position.top != null ? '-top-1.5' : '-bottom-1.5'"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import Icon from '@/components/Icon/src/Icon.vue'
|
import Icon from '@/components/Icon/src/Icon.vue'
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { ImConversationType, ImGroupMemberRole } from '@/views/im/utils/constants'
|
import { ImConversationType, ImGroupMemberRole } from '@/views/im/utils/constants'
|
||||||
|
|
@ -93,6 +93,15 @@ const group = computed(() => groupStore.getGroup(props.groupId))
|
||||||
const expanded = ref(false)
|
const expanded = ref(false)
|
||||||
const removingId = ref<number | null>(null)
|
const removingId = ref<number | null>(null)
|
||||||
|
|
||||||
|
// 切群时重置展开 / 移除中状态:本地 ref 不跟随 groupId,否则上一群"展开"或"移除中"会带到新群
|
||||||
|
watch(
|
||||||
|
() => props.groupId,
|
||||||
|
() => {
|
||||||
|
expanded.value = false
|
||||||
|
removingId.value = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/** 当前群置顶消息列表(直接走 group.value,跟随响应式) */
|
/** 当前群置顶消息列表(直接走 group.value,跟随响应式) */
|
||||||
const pinnedMessages = computed<Message[]>(() => group.value?.pinnedMessages ?? [])
|
const pinnedMessages = computed<Message[]>(() => group.value?.pinnedMessages ?? [])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,8 @@ function handleFileClick() {
|
||||||
if (isUploading.value || !filePayload.value?.url) {
|
if (isUploading.value || !filePayload.value?.url) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.open(filePayload.value.url, '_blank')
|
// noopener,noreferrer 切断新窗口对原页面的 window.opener 引用,防 Tabnabbing
|
||||||
|
window.open(filePayload.value.url, '_blank', 'noopener,noreferrer')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 语音点击:托管给 useVoicePlayer 全局互斥播放,新点的语音会停掉旧的 */
|
/** 语音点击:托管给 useVoicePlayer 全局互斥播放,新点的语音会停掉旧的 */
|
||||||
|
|
|
||||||
|
|
@ -198,14 +198,14 @@ const getList = async () => {
|
||||||
list.value = data.list
|
list.value = data.list
|
||||||
total.value = data.total
|
total.value = data.total
|
||||||
await nextTick()
|
await nextTick()
|
||||||
applyPreSelection()
|
await applyPreSelection()
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 恢复预选状态(当前页可见范围内) */
|
/** 恢复预选状态:当前页命中直接复用 row,否则单选时调单条详情接口补齐(防已选群不在第一页时确认报"请选择") */
|
||||||
const applyPreSelection = () => {
|
const applyPreSelection = async () => {
|
||||||
if (preSelectedIds.value.length === 0) {
|
if (preSelectedIds.value.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -219,12 +219,24 @@ const applyPreSelection = () => {
|
||||||
table.toggleRowSelection(row, true)
|
table.toggleRowSelection(row, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
return
|
||||||
const match = list.value.find((row) => preSelectedIds.value.includes(row.id))
|
}
|
||||||
if (match) {
|
const targetId = preSelectedIds.value[0]
|
||||||
selectedRadioId.value = match.id
|
const match = list.value.find((row) => row.id === targetId)
|
||||||
currentRadioRow.value = match
|
if (match) {
|
||||||
|
selectedRadioId.value = match.id
|
||||||
|
currentRadioRow.value = match
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 已选群不在当前页可见范围 → 走详情接口补齐,让 currentRadioRow 至少有 id + 名字给 confirmSelect 用
|
||||||
|
try {
|
||||||
|
const detail = await ManagerGroupApi.getManagerGroup(targetId)
|
||||||
|
if (detail) {
|
||||||
|
selectedRadioId.value = detail.id
|
||||||
|
currentRadioRow.value = detail
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[GroupSelectDialog] 预选群详情拉取失败', { targetId }, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,8 @@ const mergePreviewLines = computed(() => {
|
||||||
function openVideo() {
|
function openVideo() {
|
||||||
const url = videoPayload.value?.url
|
const url = videoPayload.value?.url
|
||||||
if (url) {
|
if (url) {
|
||||||
window.open(url, '_blank')
|
// noopener,noreferrer 切断新窗口对原页面的 window.opener 引用,防 Tabnabbing
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue