feat(Talk): 群头像改为成员九宫格默认并支持自定义,移除成员面板管理员计数
This commit is contained in:
1 parent
3fa2d8043c
commit
c9635a99db
3 files changed
+308
-34
No files matched your search
@@ -93,6 +93,8 @@ export interface TalkConversation {
|
||||
Announcement?: string
|
||||
/** 群公告更新时间 */
|
||||
AnnouncementTime?: string
|
||||
/** 群头像资源 SHA1(空串=使用成员九宫格默认头像) */
|
||||
AvatarSHA1?: string
|
||||
}
|
||||
|
||||
/** 会话列表返回 */
|
||||
@@ -240,6 +242,17 @@ export function setTalkAnnouncement(data: {
|
||||
)
|
||||
}
|
||||
|
||||
/** 设置群头像(空串即恢复默认九宫格;群主/群管理员) */
|
||||
export function setTalkConversationAvatar(data: {
|
||||
ConversationUUID: string
|
||||
AvatarSHA1: string
|
||||
}): Promise<{ ConversationUUID: string; AvatarSHA1: string }> {
|
||||
return post<{ ConversationUUID: string; AvatarSHA1: string }>(
|
||||
`${TALK_BASE}/Conversation/Avatar`,
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
/** 消息分页(BeforeTime 取更早一页) */
|
||||
export function getTalkMessages(params: {
|
||||
ConversationUUID: string
|
||||
@@ -313,3 +326,15 @@ export function buildTalkAvatarUrl(UserUUID: string): string {
|
||||
if (scope.admin_verify) params.set('admin_verify', scope.admin_verify)
|
||||
return `/Query/GetELTUserAvatar?${params.toString()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 群头像(自定义)资源 URL —— 与附件同一套规则
|
||||
*
|
||||
* SHA1 由 /API/Talk/Upload 上传图片得到,后端存 FileAsset;
|
||||
* 生产环境绝对化到统一 API 域名,开发环境保留相对路径走 Vite 代理。
|
||||
*/
|
||||
export function buildTalkAssetUrl(SHA1: string): string {
|
||||
if (!SHA1) return ''
|
||||
if (import.meta.env.PROD) return `${ASSET_DOWNLOAD_BASE}/${SHA1}`
|
||||
return `/API/Asset/Download/${SHA1}`
|
||||
}
|
||||
+172
-10
@@ -31,9 +31,11 @@ import {
|
||||
removeTalkMembers,
|
||||
transferTalkOwner,
|
||||
setTalkAnnouncement,
|
||||
setTalkConversationAvatar,
|
||||
searchTalkMembers,
|
||||
resolveTalkAttachmentUrl,
|
||||
resolveTalkAttachmentDownloadUrl,
|
||||
buildTalkAssetUrl,
|
||||
TalkConversationType,
|
||||
TalkMsgType,
|
||||
TalkMemberRole,
|
||||
@@ -60,6 +62,8 @@ const TALK_PATH = '/talk'
|
||||
const MESSAGE_PAGE_SIZE = 30
|
||||
/** 上传大小上限:10MB */
|
||||
const UPLOAD_MAX_SIZE = 10 * 1024 * 1024
|
||||
/** 群头像大小上限:2MB */
|
||||
const AVATAR_MAX_SIZE = 2 * 1024 * 1024
|
||||
/** 输入中节流:2s 内只发一次 talk:typing */
|
||||
const TYPING_THROTTLE = 2000
|
||||
/** 相邻消息间隔小于该值不重复显示时间(5 分钟) */
|
||||
@@ -101,6 +105,13 @@ const RenameSubmitting = ref(false)
|
||||
const AnnouncementVisible = ref(false)
|
||||
const AnnouncementText = ref('')
|
||||
const AnnouncementSubmitting = ref(false)
|
||||
const AvatarVisible = ref(false)
|
||||
/** 待提交的群头像 SHA1(空串=恢复默认九宫格) */
|
||||
const AvatarSHA1 = ref('')
|
||||
/** 群头像预览地址 */
|
||||
const AvatarPreview = ref('')
|
||||
const AvatarUploading = ref(false)
|
||||
const AvatarSubmitting = ref(false)
|
||||
|
||||
// === 实时状态 ===
|
||||
const SelfUUID = ref('')
|
||||
@@ -111,6 +122,7 @@ const TypingUsers = ref<Record<string, string>>({})
|
||||
const ScrollbarRef = ref()
|
||||
const ImageInput = ref<HTMLInputElement | null>(null)
|
||||
const FileInput = ref<HTMLInputElement | null>(null)
|
||||
const AvatarInput = ref<HTMLInputElement | null>(null)
|
||||
let TypingSentAt = 0
|
||||
const TypingTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const ReadMap = new Map<string, string>()
|
||||
@@ -155,6 +167,11 @@ const CanManageAnnouncement = computed(
|
||||
() => Entry.value?.IsAdmin === true || ActiveConversation.value?.CanManage === true,
|
||||
)
|
||||
|
||||
/** 群头像预览用九宫格成员(当前会话前 9 名成员) */
|
||||
const AvatarPreviewGrid = computed(() =>
|
||||
(ActiveConversation.value?.Members || []).map((m) => m.UserUUID).slice(0, 9),
|
||||
)
|
||||
|
||||
/** 是否有权转让群主:仅当前群主或 Talk 管理员 */
|
||||
const CanTransferOwner = computed(
|
||||
() => Entry.value?.IsAdmin === true || ActiveConversation.value?.IsOwner === true,
|
||||
@@ -219,13 +236,26 @@ function ConversationTitle(conversation: TalkConversation): string {
|
||||
return other ? MemberName(other) : '会话'
|
||||
}
|
||||
|
||||
/** 会话头像对应的用户:单聊取对方(用其 GERP 系统头像),群聊返回空(用群名首字色块) */
|
||||
/** 会话头像对应的用户:单聊取对方(用其 GERP 系统头像),群聊返回空 */
|
||||
function ConversationAvatarUUID(conversation: TalkConversation): string {
|
||||
if (conversation.ConversationType === TalkConversationType.Group) return ''
|
||||
const other = conversation.Members?.find((m) => m.UserUUID !== MyUUID.value)
|
||||
return other?.UserUUID || ''
|
||||
}
|
||||
|
||||
/** 会话自定义群头像图片地址(群聊且已设置自定义头像时) */
|
||||
function ConversationAvatarSrc(conversation: TalkConversation): string {
|
||||
if (conversation.ConversationType !== TalkConversationType.Group) return ''
|
||||
return buildTalkAssetUrl(conversation.AvatarSHA1 || '')
|
||||
}
|
||||
|
||||
/** 会话默认群头像九宫格成员(群聊且无自定义头像时取前 9 名成员) */
|
||||
function ConversationAvatarGrid(conversation: TalkConversation): string[] {
|
||||
if (conversation.ConversationType !== TalkConversationType.Group) return []
|
||||
if (conversation.AvatarSHA1) return []
|
||||
return (conversation.Members || []).map((m) => m.UserUUID).slice(0, 9)
|
||||
}
|
||||
|
||||
/** 时间字符串 → 毫秒(无效返回 0) */
|
||||
function TimeValue(time: string): number {
|
||||
if (!time) return 0
|
||||
@@ -580,6 +610,7 @@ async function UploadAndSend(file: File): Promise<void> {
|
||||
/** 下拉命令分发 */
|
||||
function HandleGroupCommand(command: string): void {
|
||||
if (command === 'rename') OpenRename()
|
||||
else if (command === 'avatar') OpenAvatar()
|
||||
else if (command === 'invite') OpenInvite()
|
||||
else if (command === 'announcement') OpenAnnouncement()
|
||||
else if (command === 'leave') void LeaveGroup()
|
||||
@@ -701,6 +732,75 @@ async function SubmitAnnouncement(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开群头像编辑 */
|
||||
function OpenAvatar(): void {
|
||||
if (!ActiveConversation.value) return
|
||||
if (!CanManageGroup.value) {
|
||||
ElMessage.warning('仅群主或管理员可修改群头像')
|
||||
return
|
||||
}
|
||||
AvatarSHA1.value = ActiveConversation.value.AvatarSHA1 || ''
|
||||
AvatarPreview.value = AvatarSHA1.value ? buildTalkAssetUrl(AvatarSHA1.value) : ''
|
||||
AvatarVisible.value = true
|
||||
}
|
||||
|
||||
/** 选择群头像图片 */
|
||||
function PickAvatar(): void {
|
||||
AvatarInput.value?.click()
|
||||
}
|
||||
|
||||
/** 群头像图片选中:校验后上传取 SHA1 */
|
||||
async function OnAvatarPicked(event: Event): Promise<void> {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
input.value = ''
|
||||
if (!file) return
|
||||
if (!file.type.startsWith('image/')) {
|
||||
ElMessage.warning('请选择图片文件')
|
||||
return
|
||||
}
|
||||
if (file.size > AVATAR_MAX_SIZE) {
|
||||
ElMessage.warning('群头像图片不能超过 2MB')
|
||||
return
|
||||
}
|
||||
AvatarUploading.value = true
|
||||
try {
|
||||
const result = await uploadTalkAttachment(file)
|
||||
AvatarSHA1.value = result.SHA1
|
||||
AvatarPreview.value = buildTalkAssetUrl(result.SHA1)
|
||||
} catch {
|
||||
// 业务错误已由响应拦截器统一提示
|
||||
} finally {
|
||||
AvatarUploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 群头像预览切回默认九宫格 */
|
||||
function ResetAvatar(): void {
|
||||
AvatarSHA1.value = ''
|
||||
AvatarPreview.value = ''
|
||||
}
|
||||
|
||||
/** 提交群头像(空串即恢复默认九宫格) */
|
||||
async function SubmitAvatar(): Promise<void> {
|
||||
const conversation = ActiveConversation.value
|
||||
if (!conversation) return
|
||||
AvatarSubmitting.value = true
|
||||
try {
|
||||
const result = await setTalkConversationAvatar({
|
||||
ConversationUUID: conversation.ConversationUUID,
|
||||
AvatarSHA1: AvatarSHA1.value,
|
||||
})
|
||||
conversation.AvatarSHA1 = result?.AvatarSHA1 ?? ''
|
||||
ElMessage.success(conversation.AvatarSHA1 ? '群头像已更新' : '已恢复默认群头像')
|
||||
AvatarVisible.value = false
|
||||
} catch {
|
||||
// 业务错误已由响应拦截器统一提示
|
||||
} finally {
|
||||
AvatarSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开改群名 */
|
||||
function OpenRename(): void {
|
||||
if (!ActiveConversation.value) return
|
||||
@@ -1007,6 +1107,8 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<TalkAvatar
|
||||
:userUUID="ConversationAvatarUUID(conversation)"
|
||||
:src="ConversationAvatarSrc(conversation)"
|
||||
:gridUUIDs="ConversationAvatarGrid(conversation)"
|
||||
:name="ConversationTitle(conversation)"
|
||||
:seed="conversation.ConversationUUID"
|
||||
/>
|
||||
@@ -1049,6 +1151,9 @@ onBeforeUnmount(() => {
|
||||
<el-dropdown-item v-if="CanManageGroup" command="rename">
|
||||
<el-icon><EditPen /></el-icon>修改群名
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="CanManageGroup" command="avatar">
|
||||
<el-icon><Picture /></el-icon>修改群头像
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="CanManageAnnouncement" command="announcement">
|
||||
<el-icon><Bell /></el-icon>群公告
|
||||
</el-dropdown-item>
|
||||
@@ -1189,7 +1294,6 @@ onBeforeUnmount(() => {
|
||||
<aside v-if="IsGroupChat && !MembersPanelCollapsed" class="talk-members">
|
||||
<div class="talk-members-header">
|
||||
<span>群成员({{ ActiveConversation?.MemberCount || 0 }})</span>
|
||||
<span class="talk-members-admin-count">管理员 {{ AdminCount }}/{{ AdminLimit }}</span>
|
||||
<el-button text size="small" @click="MembersPanelCollapsed = true">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
@@ -1292,6 +1396,36 @@ onBeforeUnmount(() => {
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 修改群头像 -->
|
||||
<el-drawer v-model="AvatarVisible" title="修改群头像" direction="rtl" :size="420">
|
||||
<div class="talk-avatar-editor">
|
||||
<TalkAvatar
|
||||
:src="AvatarPreview"
|
||||
:gridUUIDs="AvatarPreviewGrid"
|
||||
:name="ActiveConversation ? ConversationTitle(ActiveConversation) : ''"
|
||||
:seed="ActiveConversation?.ConversationUUID || ''"
|
||||
/>
|
||||
</div>
|
||||
<p class="talk-avatar-hint">
|
||||
未设置自定义头像时,群头像默认由前 9 名成员的九宫格组成。
|
||||
</p>
|
||||
<input
|
||||
ref="AvatarInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="talk-avatar-file-input"
|
||||
@change="OnAvatarPicked"
|
||||
/>
|
||||
<div class="talk-avatar-actions">
|
||||
<el-button :loading="AvatarUploading" @click="PickAvatar">选择图片</el-button>
|
||||
<el-button :disabled="!AvatarPreview" @click="ResetAvatar">恢复默认</el-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="AvatarVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="AvatarSubmitting" @click="SubmitAvatar">保存</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1758,16 +1892,44 @@ onBeforeUnmount(() => {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.talk-members-admin-count {
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.talk-member-more {
|
||||
flex-shrink: 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
/* ---------- 群头像编辑 ---------- */
|
||||
.talk-avatar-editor {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px 0 12px;
|
||||
|
||||
:deep(.talk-avatar) {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
:deep(.talk-avatar-grid) {
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.talk-avatar-hint {
|
||||
margin: 0 0 16px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
/* 隐藏的原生文件选择器(点击「选择图片」代触发) */
|
||||
.talk-avatar-file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.talk-avatar-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
+111
-24
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Talk 头像 —— 统一使用 GERP 系统头像
|
||||
* Talk 头像 —— 用户头像用 GERP 系统头像;群头像支持「成员九宫格」与「自定义图片」
|
||||
*
|
||||
* 传入 userUUID 时以系统头像(/Query/GetELTUserAvatar)呈现;
|
||||
* 群聊等无对应用户的场景(userUUID 为空)或头像加载失败时,回退为首字母色块。
|
||||
* 展示优先级:
|
||||
* ① src(自定义群头像图片)
|
||||
* ② gridUUIDs(群默认头像:取前 9 名成员拼九宫格)
|
||||
* ③ userUUID(单聊/成员/消息头像:/Query/GetELTUserAvatar)
|
||||
* ④ 首字母色块(以上均无或加载失败时回退)
|
||||
*
|
||||
* 注意:prop 名与模板绑定必须同形(与 UserDetailPanel 一致,调用方写作 :userUUID)。
|
||||
* Vue 只按 camelize(属性名) 匹配声明键——`user-uuid` 会被 camelize 成 `userUuid`,
|
||||
@@ -14,7 +17,7 @@ import { buildTalkAvatarUrl } from '@/api/talk'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 对应用户 UUID;为空时直接使用首字母回退(如群聊头像) */
|
||||
/** 对应用户 UUID;为空时走九宫格/首字母回退 */
|
||||
userUUID?: string
|
||||
/** 显示名(首字母回退与 img alt 用) */
|
||||
name?: string
|
||||
@@ -22,14 +25,56 @@ const props = withDefaults(
|
||||
size?: 'normal' | 'small'
|
||||
/** 首字母回退的配色种子(缺省用 userUUID / name) */
|
||||
seed?: string
|
||||
/** 自定义图片地址(群头像),优先级最高 */
|
||||
src?: string
|
||||
/** 群默认头像:成员 UUID 列表(最多取前 9 个拼九宫格) */
|
||||
gridUUIDs?: string[]
|
||||
}>(),
|
||||
{ userUUID: '', name: '', size: 'normal', seed: '' },
|
||||
{ userUUID: '', name: '', size: 'normal', seed: '', src: '', gridUUIDs: () => [] },
|
||||
)
|
||||
|
||||
/** 系统头像是否加载失败(失败即回退首字母,避免破图) */
|
||||
const LoadFailed = ref(false)
|
||||
/** 克制色板(首字母回退底色与九宫格空格底色共用) */
|
||||
const PALETTE = ['#2563eb', '#0891b2', '#16a34a', '#ca8a04', '#dc2626', '#7c3aed', '#db2777', '#475569']
|
||||
|
||||
const Src = computed(() => (LoadFailed.value ? '' : buildTalkAvatarUrl(props.userUUID || '')))
|
||||
/** 由种子稳定散列到色板 */
|
||||
function HashColor(seed: string): string {
|
||||
let hash = 0
|
||||
for (let i = 0; i < seed.length; i++) hash = (hash * 31 + seed.charCodeAt(i)) >>> 0
|
||||
return PALETTE[hash % PALETTE.length]
|
||||
}
|
||||
|
||||
/** 单张系统头像是否加载失败(失败即回退,避免破图) */
|
||||
const LoadFailed = ref(false)
|
||||
/** 九宫格中各成员头像的加载失败标记(失败即隐藏,露出底色) */
|
||||
const GridFailed = ref<Record<string, boolean>>({})
|
||||
|
||||
/** 自定义群头像 */
|
||||
const CustomSrc = computed(() => props.src || '')
|
||||
|
||||
/** 九宫格成员(自定义图片优先,最多 9 人) */
|
||||
const GridList = computed(() => {
|
||||
if (CustomSrc.value) return []
|
||||
return (props.gridUUIDs || []).filter(Boolean).slice(0, 9)
|
||||
})
|
||||
|
||||
/** 九宫格列数:≤4 人用 2 列,否则 3 列 */
|
||||
const GridCols = computed(() => (GridList.value.length <= 4 ? 2 : 3))
|
||||
|
||||
/** 单张系统头像地址(仅在无自定义图片、无九宫格时使用) */
|
||||
const SingleSrc = computed(() => {
|
||||
if (CustomSrc.value || GridList.value.length) return ''
|
||||
return LoadFailed.value ? '' : buildTalkAvatarUrl(props.userUUID || '')
|
||||
})
|
||||
|
||||
/** 是否走首字母回退 */
|
||||
const ShowFallback = computed(
|
||||
() => !CustomSrc.value && !GridList.value.length && !SingleSrc.value,
|
||||
)
|
||||
|
||||
/** 根节点内联样式:仅首字母回退时铺底色 */
|
||||
const RootStyle = computed(() =>
|
||||
ShowFallback.value ? { background: HashColor(props.seed || props.userUUID || props.name || '') } : undefined,
|
||||
)
|
||||
|
||||
/** 头像首字母 */
|
||||
const FallbackText = computed(() => {
|
||||
@@ -37,31 +82,51 @@ const FallbackText = computed(() => {
|
||||
return text ? text.slice(0, 1).toUpperCase() : '?'
|
||||
})
|
||||
|
||||
/** 回退底色:由种子稳定散列到克制色板 */
|
||||
const FallbackColor = computed(() => {
|
||||
const palette = ['#2563eb', '#0891b2', '#16a34a', '#ca8a04', '#dc2626', '#7c3aed', '#db2777', '#475569']
|
||||
const seed = props.seed || props.userUUID || props.name || ''
|
||||
let hash = 0
|
||||
for (let i = 0; i < seed.length; i++) hash = (hash * 31 + seed.charCodeAt(i)) >>> 0
|
||||
return palette[hash % palette.length]
|
||||
})
|
||||
/** 单张系统头像地址(九宫格单元格用) */
|
||||
function CellSrc(UserUUID: string): string {
|
||||
return buildTalkAvatarUrl(UserUUID)
|
||||
}
|
||||
|
||||
// 复用同一组件实例渲染不同用户(如切换会话)时重置失败标记
|
||||
// 复用同一组件实例渲染不同会话/用户时重置失败标记
|
||||
watch(
|
||||
() => props.userUUID,
|
||||
() => [props.userUUID, props.src, (props.gridUUIDs || []).join(',')],
|
||||
() => {
|
||||
LoadFailed.value = false
|
||||
GridFailed.value = {}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="talk-avatar"
|
||||
:class="{ small: size === 'small' }"
|
||||
:style="Src ? undefined : { background: FallbackColor }"
|
||||
>
|
||||
<img v-if="Src" :src="Src" :alt="name" @error="LoadFailed = true" />
|
||||
<div class="talk-avatar" :class="{ small: size === 'small' }" :style="RootStyle">
|
||||
<!-- ① 自定义群头像 -->
|
||||
<img v-if="CustomSrc" :src="CustomSrc" :alt="name" />
|
||||
|
||||
<!-- ② 成员九宫格(群默认头像) -->
|
||||
<div
|
||||
v-else-if="GridList.length"
|
||||
class="talk-avatar-grid"
|
||||
:style="{ gridTemplateColumns: `repeat(${GridCols}, 1fr)` }"
|
||||
>
|
||||
<span
|
||||
v-for="uuid in GridList"
|
||||
:key="uuid"
|
||||
class="talk-avatar-cell"
|
||||
:style="{ background: HashColor(uuid) }"
|
||||
>
|
||||
<img
|
||||
v-if="!GridFailed[uuid]"
|
||||
:src="CellSrc(uuid)"
|
||||
alt=""
|
||||
@error="GridFailed = { ...GridFailed, [uuid]: true }"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- ③ 单张系统头像 -->
|
||||
<img v-else-if="SingleSrc" :src="SingleSrc" :alt="name" @error="LoadFailed = true" />
|
||||
|
||||
<!-- ④ 首字母回退 -->
|
||||
<template v-else>{{ FallbackText }}</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -95,4 +160,26 @@ watch(
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* 成员九宫格:填满头像框,格间极细白缝 */
|
||||
.talk-avatar-grid {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.talk-avatar-cell {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user