feat(admin+api+player): 站内信手动发送、媒体库选择与发送记录详情
新增管理员手动发送站内信(三语富文本),支持发送记录查看与删除;修复全站「从媒体库选择」因分类过滤导致列表为空的问题;玩家端支持渲染管理员自定义消息;并优化后台列表页双层卡片布局。
This commit is contained in:
@@ -11,7 +11,7 @@ import api from '../api';
|
||||
import AdminTableEmpty from '../components/AdminTableEmpty.vue';
|
||||
import ContentImageField from '../components/ContentImageField.vue';
|
||||
import ContentRichEditor from '../components/ContentRichEditor.vue';
|
||||
import { stripHtml } from '../utils/html';
|
||||
import { stripHtml, sanitizeAnnouncementHtml, isHtmlEmpty } from '../utils/html';
|
||||
import {
|
||||
normalizeStartTimeForApi,
|
||||
normalizeStartTimeForPicker,
|
||||
@@ -77,12 +77,65 @@ interface InboxNotifySettings {
|
||||
deposit: boolean;
|
||||
}
|
||||
|
||||
interface MessageBroadcastItem {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
translations?: Record<string, { title: string; body: string }>;
|
||||
targetType: 'ALL' | 'USER';
|
||||
targetUserId: string | null;
|
||||
targetUsername: string | null;
|
||||
recipientCount: number;
|
||||
createdById: string | null;
|
||||
createdByUsername: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface BroadcastTranslationForm {
|
||||
locale: string;
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
const inboxNotifySettings = ref<InboxNotifySettings>({
|
||||
inboxEnabled: true,
|
||||
deposit: true,
|
||||
});
|
||||
const inboxNotifySaving = ref(false);
|
||||
|
||||
const broadcastLoading = ref(false);
|
||||
const broadcastSending = ref(false);
|
||||
const broadcastDialogVisible = ref(false);
|
||||
const broadcastActiveLocale = ref<string>('zh-CN');
|
||||
const broadcastEditorRef = ref<InstanceType<typeof ContentRichEditor> | null>(null);
|
||||
const broadcastItems = ref<MessageBroadcastItem[]>([]);
|
||||
const broadcastTotal = ref(0);
|
||||
const broadcastPage = ref(1);
|
||||
const broadcastPageSize = ref(10);
|
||||
const broadcastDetailVisible = ref(false);
|
||||
const broadcastDetailRow = ref<MessageBroadcastItem | null>(null);
|
||||
const broadcastDetailLocale = ref<string>('zh-CN');
|
||||
|
||||
function emptyBroadcastTranslations(): BroadcastTranslationForm[] {
|
||||
return LOCALES.map((locale) => ({
|
||||
locale,
|
||||
title: '',
|
||||
body: '',
|
||||
}));
|
||||
}
|
||||
|
||||
const broadcastForm = ref({
|
||||
targetType: 'ALL' as 'ALL' | 'USER',
|
||||
targetUsername: '',
|
||||
translations: emptyBroadcastTranslations(),
|
||||
});
|
||||
|
||||
const broadcastActiveTranslation = computed(
|
||||
() =>
|
||||
broadcastForm.value.translations.find((tr) => tr.locale === broadcastActiveLocale.value) ??
|
||||
broadcastForm.value.translations[0],
|
||||
);
|
||||
|
||||
const form = ref({
|
||||
sortOrder: 0,
|
||||
status: 'DRAFT' as ContentStatus,
|
||||
@@ -194,6 +247,163 @@ async function saveInboxNotifySettings() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBroadcasts() {
|
||||
broadcastLoading.value = true;
|
||||
try {
|
||||
const { data } = await api.get('/admin/player-message-broadcasts', {
|
||||
params: { page: broadcastPage.value, pageSize: broadcastPageSize.value },
|
||||
});
|
||||
broadcastItems.value = data.data?.items ?? [];
|
||||
broadcastTotal.value = data.data?.total ?? 0;
|
||||
} catch (e: unknown) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
ElMessage.error(err.response?.data?.error ?? t('msg.load_failed'));
|
||||
} finally {
|
||||
broadcastLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetBroadcastForm() {
|
||||
broadcastForm.value = {
|
||||
targetType: 'ALL',
|
||||
targetUsername: '',
|
||||
translations: emptyBroadcastTranslations(),
|
||||
};
|
||||
broadcastActiveLocale.value = 'zh-CN';
|
||||
}
|
||||
|
||||
function hasBroadcastContent() {
|
||||
return broadcastForm.value.translations.some(
|
||||
(tr) => tr.title.trim() || tr.body.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
function openBroadcastDialog() {
|
||||
resetBroadcastForm();
|
||||
broadcastDialogVisible.value = true;
|
||||
}
|
||||
|
||||
function closeBroadcastDialog() {
|
||||
if (broadcastSending.value) return;
|
||||
broadcastDialogVisible.value = false;
|
||||
}
|
||||
|
||||
async function sendBroadcast() {
|
||||
if (!canManageContent.value) return;
|
||||
|
||||
const editor = broadcastEditorRef.value;
|
||||
if (editor) {
|
||||
broadcastActiveTranslation.value.body = editor.getHtml();
|
||||
for (const tr of broadcastForm.value.translations) {
|
||||
tr.body = await editor.uploadPendingImages(tr.body);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBroadcastContent()) {
|
||||
ElMessage.warning(t('content.inbox_broadcast.form_invalid'));
|
||||
return;
|
||||
}
|
||||
if (broadcastForm.value.targetType === 'USER' && !broadcastForm.value.targetUsername.trim()) {
|
||||
ElMessage.warning(t('content.inbox_broadcast.target_user_required'));
|
||||
return;
|
||||
}
|
||||
broadcastSending.value = true;
|
||||
try {
|
||||
const { data } = await api.post('/admin/player-message-broadcasts', {
|
||||
translations: broadcastForm.value.translations.map((tr) => ({
|
||||
locale: tr.locale,
|
||||
title: tr.title.trim() || undefined,
|
||||
body: tr.body.trim() || undefined,
|
||||
})),
|
||||
targetType: broadcastForm.value.targetType,
|
||||
targetUsername:
|
||||
broadcastForm.value.targetType === 'USER'
|
||||
? broadcastForm.value.targetUsername.trim()
|
||||
: undefined,
|
||||
});
|
||||
ElMessage.success(
|
||||
t('content.inbox_broadcast.send_success', {
|
||||
n: data.data?.recipientCount ?? 0,
|
||||
}),
|
||||
);
|
||||
resetBroadcastForm();
|
||||
broadcastPage.value = 1;
|
||||
broadcastDialogVisible.value = false;
|
||||
await loadBroadcasts();
|
||||
} catch (e: unknown) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
|
||||
} finally {
|
||||
broadcastSending.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastTargetLabel(row: MessageBroadcastItem) {
|
||||
if (row.targetType === 'ALL') return t('content.inbox_broadcast.target_all');
|
||||
return row.targetUsername || `#${row.targetUserId ?? ''}`;
|
||||
}
|
||||
|
||||
function formatBroadcastTime(value: string) {
|
||||
if (!value) return '—';
|
||||
return new Date(value).toLocaleString(localeTag.value);
|
||||
}
|
||||
|
||||
function openBroadcastDetail(row: MessageBroadcastItem) {
|
||||
broadcastDetailRow.value = row;
|
||||
const tr = row.translations ?? {};
|
||||
const firstWithContent =
|
||||
LOCALES.find((locale) => {
|
||||
const item = tr[locale];
|
||||
return item && (item.title?.trim() || !isHtmlEmpty(item.body));
|
||||
}) ?? 'zh-CN';
|
||||
broadcastDetailLocale.value = firstWithContent;
|
||||
broadcastDetailVisible.value = true;
|
||||
}
|
||||
|
||||
function broadcastDetailTranslation(locale: string) {
|
||||
const row = broadcastDetailRow.value;
|
||||
if (!row) return { title: '', body: '' };
|
||||
return row.translations?.[locale] ?? { title: '', body: '' };
|
||||
}
|
||||
|
||||
async function deleteBroadcast(row: MessageBroadcastItem) {
|
||||
if (!canManageContent.value) return;
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
t('content.inbox_broadcast.delete_confirm', { title: row.title }),
|
||||
t('common.confirm'),
|
||||
{ type: 'warning' },
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
broadcastLoading.value = true;
|
||||
try {
|
||||
await api.delete(`/admin/player-message-broadcasts/${row.id}`);
|
||||
ElMessage.success(t('msg.saved'));
|
||||
if (broadcastItems.value.length === 1 && broadcastPage.value > 1) {
|
||||
broadcastPage.value -= 1;
|
||||
}
|
||||
await loadBroadcasts();
|
||||
} catch (e: unknown) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
|
||||
} finally {
|
||||
broadcastLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onBroadcastPageChange(page: number) {
|
||||
broadcastPage.value = page;
|
||||
void loadBroadcasts();
|
||||
}
|
||||
|
||||
function onBroadcastSizeChange(size: number) {
|
||||
broadcastPageSize.value = size;
|
||||
broadcastPage.value = 1;
|
||||
void loadBroadcasts();
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -234,13 +444,18 @@ watch([activeType, filterStatus], () => {
|
||||
tableRef.value?.clearSelection();
|
||||
if (activeType.value === 'INBOX_NOTIFY') {
|
||||
void loadInboxNotifySettings();
|
||||
void loadBroadcasts();
|
||||
return;
|
||||
}
|
||||
void load();
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (activeType.value === 'INBOX_NOTIFY') return;
|
||||
if (activeType.value === 'INBOX_NOTIFY') {
|
||||
void loadInboxNotifySettings();
|
||||
if (broadcastItems.value.length > 0) void loadBroadcasts();
|
||||
return;
|
||||
}
|
||||
if (items.value.length > 0) void load();
|
||||
});
|
||||
|
||||
@@ -575,6 +790,18 @@ void load();
|
||||
<li>{{ t('content.inbox_notify.banner_note') }}</li>
|
||||
<li>{{ t('content.inbox_notify.announcement_note') }}</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="inboxNotifySettings.inboxEnabled" class="inbox-toolbar">
|
||||
<el-button
|
||||
v-if="canManageContent"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openBroadcastDialog"
|
||||
>
|
||||
{{ t('content.inbox_broadcast.title') }}
|
||||
</el-button>
|
||||
<span class="inbox-toolbar-hint">{{ t('content.inbox_broadcast.hint') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -613,6 +840,79 @@ void load();
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card
|
||||
v-if="isInboxNotifyTab && inboxNotifySettings.inboxEnabled"
|
||||
v-loading="broadcastLoading"
|
||||
class="data-card"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="broadcastItems" row-key="id" stripe size="small">
|
||||
<template #empty>
|
||||
<AdminTableEmpty />
|
||||
</template>
|
||||
<el-table-column
|
||||
type="index"
|
||||
:index="(i: number) => (broadcastPage - 1) * broadcastPageSize + i + 1"
|
||||
:label="t('common.seq')"
|
||||
width="70"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column :label="t('content.inbox_broadcast.col_title')" min-width="160" prop="title" />
|
||||
<el-table-column :label="t('content.inbox_broadcast.col_target')" width="140">
|
||||
<template #default="{ row }">
|
||||
{{ broadcastTargetLabel(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="t('content.inbox_broadcast.col_recipients')"
|
||||
width="90"
|
||||
align="center"
|
||||
prop="recipientCount"
|
||||
/>
|
||||
<el-table-column :label="t('content.inbox_broadcast.col_sender')" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.createdByUsername || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('content.inbox_broadcast.col_time')" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ formatBroadcastTime(row.createdAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" width="130" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="openBroadcastDetail(row)">
|
||||
{{ t('common.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageContent"
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
@click="deleteBroadcast(row)"
|
||||
>
|
||||
{{ t('common.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-if="broadcastTotal > 0" class="pager-row">
|
||||
<el-pagination
|
||||
v-model:current-page="broadcastPage"
|
||||
v-model:page-size="broadcastPageSize"
|
||||
:total="broadcastTotal"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
background
|
||||
small
|
||||
@current-change="onBroadcastPageChange"
|
||||
@size-change="onBroadcastSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="!isInboxNotifyTab" v-loading="loading" class="data-card" shadow="never">
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
@@ -966,6 +1266,140 @@ void load();
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="broadcastDetailVisible"
|
||||
:title="t('content.inbox_broadcast.view_title')"
|
||||
width="min(760px, 96vw)"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
class="inbox-broadcast-detail-dialog"
|
||||
>
|
||||
<template v-if="broadcastDetailRow">
|
||||
<dl class="broadcast-detail-meta">
|
||||
<div class="meta-row">
|
||||
<dt>{{ t('content.inbox_broadcast.col_target') }}</dt>
|
||||
<dd>{{ broadcastTargetLabel(broadcastDetailRow) }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>{{ t('content.inbox_broadcast.col_recipients') }}</dt>
|
||||
<dd>{{ broadcastDetailRow.recipientCount }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>{{ t('content.inbox_broadcast.col_sender') }}</dt>
|
||||
<dd>{{ broadcastDetailRow.createdByUsername || '—' }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>{{ t('content.inbox_broadcast.col_time') }}</dt>
|
||||
<dd>{{ formatBroadcastTime(broadcastDetailRow.createdAt) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<el-tabs v-model="broadcastDetailLocale" class="locale-tabs">
|
||||
<el-tab-pane
|
||||
v-for="locale in LOCALES"
|
||||
:key="locale"
|
||||
:label="localeLabel(locale)"
|
||||
:name="locale"
|
||||
>
|
||||
<div class="broadcast-detail-block">
|
||||
<div class="broadcast-detail-label">{{ t('content.inbox_broadcast.field_title') }}</div>
|
||||
<div class="broadcast-detail-title">
|
||||
{{ broadcastDetailTranslation(locale).title || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="broadcast-detail-block">
|
||||
<div class="broadcast-detail-label">{{ t('content.inbox_broadcast.field_body') }}</div>
|
||||
<div
|
||||
v-if="!isHtmlEmpty(broadcastDetailTranslation(locale).body)"
|
||||
class="broadcast-detail-body rich-html"
|
||||
v-html="sanitizeAnnouncementHtml(broadcastDetailTranslation(locale).body)"
|
||||
/>
|
||||
<div v-else class="broadcast-detail-empty">—</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="broadcastDetailVisible = false">{{ t('common.close') }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="broadcastDialogVisible"
|
||||
:title="t('content.inbox_broadcast.title')"
|
||||
width="min(920px, 96vw)"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="!broadcastSending"
|
||||
class="inbox-broadcast-dialog content-publish-dialog"
|
||||
@close="closeBroadcastDialog"
|
||||
>
|
||||
<el-form label-position="top" class="inbox-broadcast-form" @submit.prevent>
|
||||
<el-form-item :label="t('content.inbox_broadcast.field_target')">
|
||||
<el-radio-group v-model="broadcastForm.targetType" :disabled="broadcastSending">
|
||||
<el-radio value="ALL">{{ t('content.inbox_broadcast.target_all') }}</el-radio>
|
||||
<el-radio value="USER">{{ t('content.inbox_broadcast.target_user') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="broadcastForm.targetType === 'USER'"
|
||||
:label="t('content.inbox_broadcast.field_username')"
|
||||
>
|
||||
<el-input
|
||||
v-model="broadcastForm.targetUsername"
|
||||
:placeholder="t('content.inbox_broadcast.username_placeholder')"
|
||||
:disabled="broadcastSending"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<section class="broadcast-content-section">
|
||||
<div class="broadcast-content-head">
|
||||
<h3 class="section-title">{{ t('content.section.content') }}</h3>
|
||||
<p class="field-hint">{{ t('content.inbox_broadcast.locale_fallback_hint') }}</p>
|
||||
</div>
|
||||
<el-tabs v-model="broadcastActiveLocale" class="locale-tabs">
|
||||
<el-tab-pane
|
||||
v-for="tr in broadcastForm.translations"
|
||||
:key="tr.locale"
|
||||
:label="localeLabel(tr.locale)"
|
||||
:name="tr.locale"
|
||||
>
|
||||
<el-form-item :label="t('content.inbox_broadcast.field_title')">
|
||||
<el-input
|
||||
v-model="tr.title"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
:placeholder="t('content.field.title_ph')"
|
||||
:disabled="broadcastSending"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="broadcast-editor-head">
|
||||
<span class="publish-body-label">{{ t('content.inbox_broadcast.field_body') }}</span>
|
||||
<span class="locale-badge">{{ localeLabel(broadcastActiveLocale) }}</span>
|
||||
</div>
|
||||
<ContentRichEditor
|
||||
ref="broadcastEditorRef"
|
||||
v-model="broadcastActiveTranslation.body"
|
||||
fill
|
||||
upload-category="contents"
|
||||
:placeholder="t('content.editor.placeholder')"
|
||||
:disabled="broadcastSending"
|
||||
class="broadcast-rich-editor"
|
||||
/>
|
||||
</section>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="broadcastSending" @click="closeBroadcastDialog">
|
||||
{{ t('common.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="broadcastSending" @click="sendBroadcast">
|
||||
{{ t('content.inbox_broadcast.send') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1323,4 +1757,127 @@ void load();
|
||||
.inbox-notify-notes li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.inbox-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.inbox-toolbar-hint {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.inbox-broadcast-form :deep(.el-form-item) {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.broadcast-content-section {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.broadcast-content-head {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.broadcast-content-head .section-title {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.broadcast-editor-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 12px 0 8px;
|
||||
}
|
||||
|
||||
.broadcast-rich-editor {
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.inbox-broadcast-dialog :deep(.el-dialog__body) {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.broadcast-detail-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 16px;
|
||||
margin: 0 0 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #fbfaf7;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.meta-row dt {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.meta-row dd {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.broadcast-detail-block + .broadcast-detail-block {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.broadcast-detail-label {
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.broadcast-detail-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.broadcast-detail-body,
|
||||
.broadcast-detail-empty {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.broadcast-detail-empty {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.rich-html :deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.rich-html :deep(p) {
|
||||
margin: 0 0 0.75em;
|
||||
}
|
||||
|
||||
.rich-html :deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user