sync(theme-3): 同步 main 最新 API/Admin 与玩家端逻辑
从 main 同步站内信手动发送、媒体库选择、列表子路由重构等 API/Admin 改动;玩家端仅合并 ADMIN_CUSTOM 富文本、消息预览与充值截图压缩逻辑,保留 theme-3 样式。
This commit is contained in:
@@ -6,6 +6,7 @@ import { formatLocalMatchDateTime } from '@thebet365/shared';
|
||||
import GoldSpinner from './GoldSpinner.vue';
|
||||
import ConfirmDialog from './ConfirmDialog.vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { stripHtml } from '../utils/html';
|
||||
import { usePlayerMessages, type DepositMessagePayload, type PlayerMessage } from '../composables/usePlayerMessages';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -44,7 +45,8 @@ function messagePreview(item: PlayerMessage) {
|
||||
const deposit = item.payload as DepositMessagePayload | null;
|
||||
if (deposit?.orderNo) return deposit.orderNo;
|
||||
}
|
||||
return item.body.length > 80 ? `${item.body.slice(0, 80)}…` : item.body;
|
||||
const plain = stripHtml(item.body);
|
||||
return plain.length > 80 ? `${plain.slice(0, 80)}…` : plain;
|
||||
}
|
||||
|
||||
function openDetail(id: string) {
|
||||
|
||||
@@ -5,7 +5,8 @@ export type PlayerMessageType =
|
||||
| 'DEPOSIT_APPROVED'
|
||||
| 'DEPOSIT_REJECTED'
|
||||
| 'BANNER_PROMO'
|
||||
| 'ANNOUNCEMENT_PROMO';
|
||||
| 'ANNOUNCEMENT_PROMO'
|
||||
| 'ADMIN_CUSTOM';
|
||||
|
||||
export type DepositMessagePayload = {
|
||||
depositOrderId?: string;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { formatLocalMatchDateTime } from '@thebet365/shared';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { sanitizeAnnouncementHtml } from '../utils/html';
|
||||
import { formatMoney } from '../utils/localeDisplay';
|
||||
import {
|
||||
usePlayerMessages,
|
||||
@@ -174,7 +175,12 @@ watch(messageId, () => {
|
||||
</span>
|
||||
<p v-if="message.createdAt" class="detail-date">{{ formatDate(message.createdAt) }}</p>
|
||||
<h2 class="detail-title">{{ messageTitle(message) }}</h2>
|
||||
<p class="detail-body">{{ messageBody(message) }}</p>
|
||||
<div
|
||||
v-if="message.type === 'ADMIN_CUSTOM'"
|
||||
class="detail-body rich-body"
|
||||
v-html="sanitizeAnnouncementHtml(message.body)"
|
||||
/>
|
||||
<p v-else class="detail-body">{{ messageBody(message) }}</p>
|
||||
|
||||
<div
|
||||
v-if="message.type === 'DEPOSIT_REJECTED' && depositPayload?.rejectReason?.trim()"
|
||||
@@ -323,6 +329,32 @@ watch(messageId, () => {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.rich-body :deep(p) {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.rich-body :deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rich-body :deep(img) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 12px 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.rich-body :deep(ul),
|
||||
.rich-body :deep(ol) {
|
||||
margin: 0 0 12px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.rich-body :deep(a) {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.reason-box {
|
||||
margin-top: 18px;
|
||||
padding: 14px;
|
||||
|
||||
@@ -5,10 +5,12 @@ import { useI18n } from 'vue-i18n';
|
||||
import imageCompression from 'browser-image-compression';
|
||||
import api from '../api';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
import { useDepositNotifications } from '../composables/useDepositNotifications';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { trackPendingOrder } = useDepositNotifications();
|
||||
|
||||
const reapplyOrderId = computed(() => {
|
||||
const id = route.query.orderId;
|
||||
@@ -93,30 +95,43 @@ function selectMethod(m: PaymentMethod) {
|
||||
}
|
||||
|
||||
const MAX_ORIGINAL_BYTES = 10 * 1024 * 1024;
|
||||
const MAX_SCREENSHOT_BYTES = 1024 * 1024;
|
||||
const MAX_SCREENSHOT_BYTES = 300 * 1024;
|
||||
|
||||
async function compressScreenshot(file: File): Promise<File> {
|
||||
const baseOptions = {
|
||||
maxSizeMB: 1,
|
||||
maxWidthOrHeight: 1920,
|
||||
maxSizeMB: 0.3,
|
||||
maxWidthOrHeight: 1200,
|
||||
useWebWorker: true,
|
||||
maxIteration: 15,
|
||||
maxIteration: 10,
|
||||
fileType: 'image/webp',
|
||||
} as const;
|
||||
|
||||
const attempts = [
|
||||
{ ...baseOptions, initialQuality: 0.85 },
|
||||
{ ...baseOptions, initialQuality: 0.65, maxWidthOrHeight: 1600 },
|
||||
{ ...baseOptions, initialQuality: 0.5, maxWidthOrHeight: 1280 },
|
||||
{ ...baseOptions, initialQuality: 0.8 },
|
||||
{ ...baseOptions, initialQuality: 0.6, maxWidthOrHeight: 1000 },
|
||||
];
|
||||
|
||||
let compressed: File | null = null;
|
||||
for (const options of attempts) {
|
||||
const compressed = (await imageCompression(file, options)) as File;
|
||||
if (compressed.size <= MAX_SCREENSHOT_BYTES) {
|
||||
return compressed;
|
||||
try {
|
||||
compressed = (await imageCompression(file, options)) as File;
|
||||
if (compressed.size <= MAX_SCREENSHOT_BYTES) {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Compression attempt failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('COMPRESS_TOO_LARGE');
|
||||
if (!compressed) {
|
||||
throw new Error('COMPRESS_FAILED');
|
||||
}
|
||||
|
||||
const name = file.name.replace(/\.[^/.]+$/, '') + '.webp';
|
||||
return new File([compressed], name, {
|
||||
type: 'image/webp',
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleFileChange(event: Event) {
|
||||
@@ -186,6 +201,7 @@ async function handleSubmit() {
|
||||
const { data } = await api.post(`/player/deposit-orders/${reapplyOrderId.value}/reapply`, fd);
|
||||
const result = data.data;
|
||||
orderNo.value = result?.orderNo ?? '';
|
||||
if (result?.id) trackPendingOrder(String(result.id));
|
||||
success.value = true;
|
||||
return;
|
||||
}
|
||||
@@ -194,6 +210,7 @@ async function handleSubmit() {
|
||||
const { data } = await api.post('/player/deposit-orders', fd);
|
||||
const result = data.data;
|
||||
orderNo.value = result?.orderNo ?? '';
|
||||
if (result?.id) trackPendingOrder(String(result.id));
|
||||
success.value = true;
|
||||
} catch (e: any) {
|
||||
alert(e.response?.data?.message || t('recharge.submit_failed'));
|
||||
|
||||
Reference in New Issue
Block a user