feat(player/admin/api): 站内邮箱、在线状态、员工菜单权限与内容管理增强
API: - 新增 player-messages 域:充值审核通过/拒绝、Banner/公告推广消息,支持多语言模板 - 新增 presence 域:Redis 心跳在线状态,管理端可查询在线玩家数 - User 表增加 visible_menus 字段;新增 player_messages 表及迁移 - 充值审核通过/拒绝时按系统配置自动写入玩家站内消息 - 管理端新增 GET /deposit-orders/pending-count、GET /presence/online-count - 玩家端新增消息 CRUD、presence/ping、home 返回 inbox 开关配置 - 员工管理支持 visibleMenus 配置与删除保护(不能删自己/最后超管) - SystemConfig 增加 inbox 功能开关及各类通知开关 Admin: - 员工管理:按角色默认菜单 + 可勾选可见菜单项 - ManageLayout:按 visibleMenus 过滤侧栏;充值待审数量角标轮询 - Contents:富文本编辑器、图片字段组件重构 - DashboardPlayers:展示在线玩家数;AdminPlayerStatusCell 在线状态列 - 多页面 i18n 与权限细节调整 Player: - 站内邮箱中心(InboxHub):消息列表/详情、未读角标、一键已读/删除 - 公告列表与详情页;走马灯可跳转详情 - 客服 Modal 改为 Panel,与邮箱 Hub 整合 - 充值状态轮询通知;presence 心跳;BetSlip 清空二次确认 - HomeView 今日赛事板块;FootballView 等体验优化 Shared: 新增 CANNOT_DELETE_SELF、STAFF_NOT_FOUND、MESSAGE_NOT_FOUND 等错误码 Docs: 玩家端缺失功能分析文档 Chore: 移除 .agents/skills 设计类 skill 文件 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,7 @@ const zh: Record<string, string> = {
|
||||
'staff.col.last_login': '最近登录',
|
||||
'staff.dialog.create': '创建后台账号',
|
||||
'staff.dialog.edit': '编辑后台账号',
|
||||
'staff.field.visible_menus': '菜单权限',
|
||||
'login.captcha_ph': '验证码',
|
||||
'login.captcha_refresh': '点击刷新',
|
||||
|
||||
@@ -69,6 +70,7 @@ const zh: Record<string, string> = {
|
||||
'deposit.status_approved': '已通过',
|
||||
'deposit.status_rejected': '已拒绝',
|
||||
'deposit.search_player_ph': '搜索玩家...',
|
||||
'deposit.pending_badge': '待审核 {n} 笔',
|
||||
'deposit.order_no': '订单号',
|
||||
'deposit.player': '玩家',
|
||||
'deposit.amount': '金额',
|
||||
@@ -114,7 +116,7 @@ const zh: Record<string, string> = {
|
||||
'deposit.add_method': '+ 添加',
|
||||
'deposit.display_name': '展示名称',
|
||||
'deposit.details': '详情',
|
||||
'deposit.sort': '排序',
|
||||
'deposit.sort': '排序值',
|
||||
'deposit.active': '启用',
|
||||
'deposit.show_player': '前台展示',
|
||||
'deposit.edit_method': '编辑收款方式',
|
||||
@@ -124,7 +126,7 @@ const zh: Record<string, string> = {
|
||||
'deposit.account_number': '银行账号',
|
||||
'deposit.usdt_address': 'USDT 地址',
|
||||
'deposit.qr_code': '二维码',
|
||||
'deposit.sort_order': '排序序号',
|
||||
'deposit.sort_order': '排序值',
|
||||
'deposit.show_on_player': '展示给玩家',
|
||||
'deposit.save': '保存',
|
||||
'deposit.confirm_deactivate': '确认停用此收款方式?',
|
||||
@@ -150,11 +152,12 @@ const zh: Record<string, string> = {
|
||||
'role.tier2_agent': '二级代理',
|
||||
'logout': '退出',
|
||||
'lang': '语言',
|
||||
'portal.admin': '平台后台',
|
||||
'portal.agent': '代理后台',
|
||||
'portal.admin': '平台后台管理',
|
||||
'portal.agent': '代理后台管理',
|
||||
|
||||
'common.all': '全部',
|
||||
'common.search': '查询',
|
||||
'common.refresh': '刷新',
|
||||
'common.reset': '重置',
|
||||
'common.edit': '编辑',
|
||||
'common.yes': '是',
|
||||
@@ -164,9 +167,13 @@ const zh: Record<string, string> = {
|
||||
'common.confirm': '确定',
|
||||
'common.retry': '重试',
|
||||
'common.status': '状态',
|
||||
'user.status.ACTIVE': '正常',
|
||||
'user.status.SUSPENDED': '已冻结',
|
||||
'user.status.DISABLED': '已停用',
|
||||
'common.type': '类型',
|
||||
'common.keyword': '关键词',
|
||||
'common.actions': '操作',
|
||||
'common.seq': '序号',
|
||||
'common.more': '更多',
|
||||
'common.loading': '加载中…',
|
||||
'common.no_data': '暂无数据',
|
||||
@@ -218,6 +225,9 @@ const zh: Record<string, string> = {
|
||||
'dash.match_pending_settle': '待结算',
|
||||
'dash.match_settled': '已结算',
|
||||
'dash.user_active': '正常玩家',
|
||||
'dash.user_online': '当前在线',
|
||||
'dash.players_online': '当前在线',
|
||||
'dash.players_online_hint': '约 2 分钟内活跃',
|
||||
'dash.user_suspended': '停用',
|
||||
'dash.user_direct': '直属',
|
||||
'dash.user_agents': '代理',
|
||||
@@ -334,6 +344,7 @@ const en: Record<string, string> = {
|
||||
'staff.col.last_login': 'Last login',
|
||||
'staff.dialog.create': 'Create staff account',
|
||||
'staff.dialog.edit': 'Edit staff account',
|
||||
'staff.field.visible_menus': 'Menu Permissions',
|
||||
'login.captcha_ph': 'Captcha',
|
||||
'login.captcha_refresh': 'Click to refresh',
|
||||
|
||||
@@ -370,6 +381,7 @@ const en: Record<string, string> = {
|
||||
'deposit.status_approved': 'Approved',
|
||||
'deposit.status_rejected': 'Rejected',
|
||||
'deposit.search_player_ph': 'Search player...',
|
||||
'deposit.pending_badge': '{n} pending',
|
||||
'deposit.order_no': 'Order No',
|
||||
'deposit.player': 'Player',
|
||||
'deposit.amount': 'Amount',
|
||||
@@ -449,11 +461,12 @@ const en: Record<string, string> = {
|
||||
'role.tier2_agent': 'Tier-2 Agent',
|
||||
'logout': 'Logout',
|
||||
'lang': 'Language',
|
||||
'portal.admin': 'Platform Admin',
|
||||
'portal.agent': 'Agent Portal',
|
||||
'portal.admin': 'Admin Console',
|
||||
'portal.agent': 'Agent Console',
|
||||
|
||||
'common.all': 'All',
|
||||
'common.search': 'Search',
|
||||
'common.refresh': 'Refresh',
|
||||
'common.reset': 'Reset',
|
||||
'common.edit': 'Edit',
|
||||
'common.yes': 'Yes',
|
||||
@@ -463,9 +476,13 @@ const en: Record<string, string> = {
|
||||
'common.confirm': 'OK',
|
||||
'common.retry': 'Retry',
|
||||
'common.status': 'Status',
|
||||
'user.status.ACTIVE': 'Active',
|
||||
'user.status.SUSPENDED': 'Suspended',
|
||||
'user.status.DISABLED': 'Disabled',
|
||||
'common.type': 'Type',
|
||||
'common.keyword': 'Keyword',
|
||||
'common.actions': 'Actions',
|
||||
'common.seq': 'No.',
|
||||
'common.more': 'More',
|
||||
'common.loading': 'Loading…',
|
||||
'common.no_data': 'No data',
|
||||
@@ -517,6 +534,9 @@ const en: Record<string, string> = {
|
||||
'dash.match_pending_settle': 'Pending settlement',
|
||||
'dash.match_settled': 'Settled',
|
||||
'dash.user_active': 'Active players',
|
||||
'dash.user_online': 'Online now',
|
||||
'dash.players_online': 'Online now',
|
||||
'dash.players_online_hint': 'Active within ~2 min',
|
||||
'dash.user_suspended': 'Suspended',
|
||||
'dash.user_direct': 'Direct',
|
||||
'dash.user_agents': 'Agents',
|
||||
@@ -633,6 +653,7 @@ const ms: Record<string, string> = {
|
||||
'staff.col.last_login': 'Log masuk terakhir',
|
||||
'staff.dialog.create': 'Cipta akaun kakitangan',
|
||||
'staff.dialog.edit': 'Edit akaun kakitangan',
|
||||
'staff.field.visible_menus': 'Kebenaran Menu',
|
||||
'login.captcha_ph': 'Captcha',
|
||||
'login.captcha_refresh': 'Klik untuk muat semula',
|
||||
|
||||
@@ -669,6 +690,7 @@ const ms: Record<string, string> = {
|
||||
'deposit.status_approved': 'Diluluskan',
|
||||
'deposit.status_rejected': 'Ditolak',
|
||||
'deposit.search_player_ph': 'Cari pemain...',
|
||||
'deposit.pending_badge': '{n} menunggu',
|
||||
'deposit.order_no': 'No. Pesanan',
|
||||
'deposit.player': 'Pemain',
|
||||
'deposit.amount': 'Jumlah',
|
||||
@@ -748,11 +770,12 @@ const ms: Record<string, string> = {
|
||||
'role.tier2_agent': 'Ejen Peringkat 2',
|
||||
'logout': 'Log keluar',
|
||||
'lang': 'Bahasa',
|
||||
'portal.admin': 'Admin Platform',
|
||||
'portal.agent': 'Portal Ejen',
|
||||
'portal.admin': 'Pengurusan Admin',
|
||||
'portal.agent': 'Pengurusan Ejen',
|
||||
|
||||
'common.all': 'Semua',
|
||||
'common.search': 'Cari',
|
||||
'common.refresh': 'Muat semula',
|
||||
'common.reset': 'Set semula',
|
||||
'common.edit': 'Edit',
|
||||
'common.yes': 'Ya',
|
||||
@@ -762,9 +785,13 @@ const ms: Record<string, string> = {
|
||||
'common.confirm': 'OK',
|
||||
'common.retry': 'Cuba lagi',
|
||||
'common.status': 'Status',
|
||||
'user.status.ACTIVE': 'Aktif',
|
||||
'user.status.SUSPENDED': 'Digantung',
|
||||
'user.status.DISABLED': 'Dinyahaktifkan',
|
||||
'common.type': 'Jenis',
|
||||
'common.keyword': 'Kata kunci',
|
||||
'common.actions': 'Tindakan',
|
||||
'common.seq': 'No.',
|
||||
'common.more': 'Lagi',
|
||||
'common.loading': 'Memuatkan…',
|
||||
'common.no_data': 'Tiada data',
|
||||
@@ -816,6 +843,9 @@ const ms: Record<string, string> = {
|
||||
'dash.match_pending_settle': 'Menunggu penyelesaian',
|
||||
'dash.match_settled': 'Diselesaikan',
|
||||
'dash.user_active': 'Pemain aktif',
|
||||
'dash.user_online': 'Dalam talian',
|
||||
'dash.players_online': 'Dalam talian',
|
||||
'dash.players_online_hint': 'Aktif dalam ~2 min',
|
||||
'dash.user_suspended': 'Digantung',
|
||||
'dash.user_direct': 'Terus',
|
||||
'dash.user_agents': 'Ejen',
|
||||
|
||||
@@ -28,6 +28,10 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'user.filter.agent': 'Ejen',
|
||||
'user.filter.agent_ph': 'Semua',
|
||||
'user.col.username': 'Nama pengguna',
|
||||
'user.col.online': 'Dalam talian',
|
||||
'user.online_yes': 'Sedang dalam talian',
|
||||
'user.presence_online': 'Dalam talian',
|
||||
'user.presence_offline': 'Luar talian',
|
||||
'user.col.agent': 'Ejen',
|
||||
'user.col.invite_code': 'Kod jemputan',
|
||||
'user.col.balance': 'Tersedia / Dibekukan',
|
||||
@@ -765,11 +769,39 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'content.btn.enable': 'Aktifkan',
|
||||
'content.btn.disable': 'Nyahaktif',
|
||||
'content.dialog.create': 'Kandungan awam baharu',
|
||||
'content.dialog.edit': 'Edit kandungan awam',
|
||||
'content.dialog.create_banner': 'Promosi laman utama baharu',
|
||||
'content.dialog.create_notice': 'Terbitkan notifikasi',
|
||||
'content.dialog.edit': 'Edit kandungan',
|
||||
'content.dialog.edit_banner': 'Edit promosi laman utama',
|
||||
'content.dialog.edit_notice': 'Edit notifikasi',
|
||||
'content.confirm_delete': 'Padam "{title}"?',
|
||||
'content.type.BANNER': 'Banner laman utama',
|
||||
'content.type.ANNOUNCEMENT': 'Pengumuman',
|
||||
'content.hint.announcement': 'Dipaparkan di ticker atas pemain; isi tajuk atau kandungan',
|
||||
'content.type.BANNER': 'Promosi laman utama',
|
||||
'content.type.ANNOUNCEMENT': 'Notifikasi',
|
||||
'content.type.INBOX_NOTIFY': 'Notifikasi peti mesej',
|
||||
'content.inbox_notify.inbox_enabled': 'Peti mesej pemain',
|
||||
'content.inbox_notify.inbox_enabled_hint': 'Jika dimatikan, pemain hanya dibawa ke sokongan (tiada tab peti mesej)',
|
||||
'content.inbox_notify.deposit': 'Keputusan deposit',
|
||||
'content.inbox_notify.deposit_hint': 'Hantar mesej apabila diluluskan atau ditolak',
|
||||
'content.inbox_notify.manual_title': 'Tanda "Notifikasi peti mesej" semasa mencipta:',
|
||||
'content.inbox_notify.banner_note': 'Promosi laman utama',
|
||||
'content.inbox_notify.announcement_note': 'Notifikasi / ticker',
|
||||
'content.hint.banner': 'Dipaparkan dalam karusel laman utama; ketik untuk halaman butiran. Muat naik imej muka depan dan kandungan kaya seperti pengumuman laman rasmi.',
|
||||
'content.hint.announcement': 'Dipaparkan sebagai teks bergulir di bahagian atas aplikasi pemain. Isi tajuk dan teks bergulir setiap bahasa — teks biasa sahaja.',
|
||||
'content.section.publish': 'Tetapan terbitan',
|
||||
'content.section.content': 'Kandungan notifikasi',
|
||||
'content.field.publish_kind': 'Jenis terbitan',
|
||||
'content.publish_kind.notice': 'Notifikasi penuh (halaman butiran)',
|
||||
'content.publish_kind.ticker': 'Teks ticker sahaja',
|
||||
'content.publish_kind.hint': 'Notifikasi penuh muncul dalam senarai dan butiran; ticker hanya papar satu baris bergulir.',
|
||||
'content.field.cover_image': 'Imej muka depan',
|
||||
'content.field.ticker_title_ph': 'Tajuk ticker (pilihan)',
|
||||
'content.field.ticker_body_ph': 'Teks dipaparkan dalam ticker',
|
||||
'content.field.ticker_hint': 'Ticker hanya papar teks biasa — tiada imej atau format kaya.',
|
||||
'content.editor.placeholder': 'Tulis kandungan notifikasi; boleh sisip imej dan senarai…',
|
||||
'content.editor.insert_image': 'Sisip imej',
|
||||
'content.upload.cover_size_hint': 'Lebar disyorkan 860px+. Imej muka depan dan dalam kandungan akan menyesuaikan lebar pada pemain.',
|
||||
'content.upload.pick_media_title': 'Pilih imej',
|
||||
'content.upload.no_media': 'Tiada imej dalam pustaka — muat naik dahulu',
|
||||
'content.status.DRAFT': 'Draf',
|
||||
'content.status.ACTIVE': 'Aktif',
|
||||
'content.status.INACTIVE': 'Tidak aktif',
|
||||
@@ -783,6 +815,9 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'content.field.link_target': 'Sasaran pautan',
|
||||
'content.field.start_time': 'Masa mula',
|
||||
'content.field.end_time': 'Masa tamat',
|
||||
'content.field.notify_inbox': 'Notifikasi peti mesej',
|
||||
'content.field.notify_inbox_hint': 'Hantar mesej peti kepada semua pemain tentang promosi ini selepas simpan',
|
||||
'content.msg.notify_sent': 'Notifikasi dihantar kepada {count} pemain',
|
||||
'content.field.title': 'Tajuk',
|
||||
'content.field.title_ph': 'Pilihan',
|
||||
'content.field.body': 'Kandungan',
|
||||
@@ -795,8 +830,6 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'content.upload.size_error': 'Imej mestilah di bawah 5 MB',
|
||||
'content.upload.remove': 'Buang imej',
|
||||
'content.upload.pick_media': 'Pilih dari pustaka',
|
||||
'content.upload.pick_media_title': 'Pilih Imej Banner',
|
||||
'content.upload.no_media': 'Tiada imej banner dalam pustaka — muat naik dahulu',
|
||||
'content.upload.url_placeholder': 'Atau tampal URL imej',
|
||||
'content.upload.recommended_size': 'Saiz disyorkan: 860 x 360 px, atau imej nisbah 43:18. Karusel pemain memaparkan imej penuh dan mengisi ruang tambahan.',
|
||||
'content.link.none': 'Tiada pautan',
|
||||
|
||||
@@ -838,15 +838,33 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'content.btn.enable': '启用',
|
||||
'content.btn.disable': '停用',
|
||||
'content.dialog.create': '新建公共内容',
|
||||
'content.dialog.edit': '编辑公共内容',
|
||||
'content.dialog.create_banner': '新建首页推广',
|
||||
'content.dialog.create_notice': '发布通知公告',
|
||||
'content.dialog.edit': '编辑内容',
|
||||
'content.dialog.edit_banner': '编辑首页推广',
|
||||
'content.dialog.edit_notice': '编辑通知公告',
|
||||
'content.confirm_delete': '确定删除「{title}」?',
|
||||
'content.type.BANNER': '首页轮播',
|
||||
'content.type.ANNOUNCEMENT': '公告滚动',
|
||||
'content.hint.announcement': '显示在玩家端顶部跑马灯;标题与正文填一项即可,建议正文为主',
|
||||
'content.type.BANNER': '首页推广',
|
||||
'content.type.ANNOUNCEMENT': '通知公告',
|
||||
'content.hint.banner': '用于首页轮播展示,点击后进入通知详情页;请填写封面图与正文,像官网发布活动通知一样编辑。',
|
||||
'content.hint.announcement': '在玩家端顶部显示滚动跑马灯文字;填写各语言标题与滚动文案即可,纯文本无富文本。',
|
||||
'content.section.publish': '发布设置',
|
||||
'content.section.content': '通知内容',
|
||||
'content.field.publish_kind': '发布类型',
|
||||
'content.publish_kind.notice': '完整通知(详情页)',
|
||||
'content.publish_kind.ticker': '仅跑马灯文字',
|
||||
'content.publish_kind.hint': '完整通知会出现在公告列表与详情页;跑马灯仅显示顶部滚动一行文字。',
|
||||
'content.field.cover_image': '封面图',
|
||||
'content.field.ticker_title_ph': '跑马灯标题(选填)',
|
||||
'content.field.ticker_body_ph': '跑马灯显示的文字',
|
||||
'content.field.ticker_hint': '跑马灯只显示纯文字,不支持图片与富文本格式。',
|
||||
'content.editor.placeholder': '输入通知正文,可插入图片、列表等…',
|
||||
'content.editor.insert_image': '插入图片',
|
||||
'content.upload.cover_size_hint': '建议宽度 860px 以上;玩家端详情页会完整显示封面,正文内图片也会自适应宽度。',
|
||||
'content.status.DRAFT': '草稿',
|
||||
'content.status.ACTIVE': '已启用',
|
||||
'content.status.INACTIVE': '已停用',
|
||||
'content.col.sort': '排序',
|
||||
'content.col.sort': '排序值',
|
||||
'content.col.preview': '预览',
|
||||
'content.col.title': '标题/摘要',
|
||||
'content.col.player_visible': '玩家可见',
|
||||
@@ -857,7 +875,7 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'content.field.start_time': '开始时间',
|
||||
'content.field.end_time': '结束时间',
|
||||
'content.field.title': '标题',
|
||||
'content.field.title_ph': '选填,可与正文相同',
|
||||
'content.field.title_ph': '通知标题,玩家端详情页展示',
|
||||
'content.field.body': '正文',
|
||||
'content.field.announce_text': '滚动文案',
|
||||
'content.field.image_url': '图片地址',
|
||||
@@ -868,8 +886,8 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'content.upload.size_error': '图片大小不能超过 5MB',
|
||||
'content.upload.remove': '移除图片',
|
||||
'content.upload.pick_media': '从媒体库选择',
|
||||
'content.upload.pick_media_title': '选择 Banner 图片',
|
||||
'content.upload.no_media': '媒体库中暂无 Banner 图片,请先上传',
|
||||
'content.upload.pick_media_title': '选择图片',
|
||||
'content.upload.no_media': '媒体库中暂无图片,请先上传',
|
||||
'content.upload.url_placeholder': '或手动粘贴图片 URL',
|
||||
'content.upload.recommended_size': '建议尺寸:860 x 360 px,或 43:18 同比例图片;前台会完整显示并自动填充不合比例区域。',
|
||||
'content.link.none': '无跳转',
|
||||
@@ -1911,11 +1929,29 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'content.btn.enable': 'Enable',
|
||||
'content.btn.disable': 'Disable',
|
||||
'content.dialog.create': 'New public content',
|
||||
'content.dialog.edit': 'Edit public content',
|
||||
'content.dialog.create_banner': 'New home promotion',
|
||||
'content.dialog.create_notice': 'Publish notification',
|
||||
'content.dialog.edit': 'Edit content',
|
||||
'content.dialog.edit_banner': 'Edit home promotion',
|
||||
'content.dialog.edit_notice': 'Edit notification',
|
||||
'content.confirm_delete': 'Delete "{title}"?',
|
||||
'content.type.BANNER': 'Home banners',
|
||||
'content.type.ANNOUNCEMENT': 'Announcements',
|
||||
'content.hint.announcement': 'Shown in the player top marquee; fill title or body (body recommended)',
|
||||
'content.type.BANNER': 'Home promotions',
|
||||
'content.type.ANNOUNCEMENT': 'Notifications',
|
||||
'content.hint.banner': 'Shown in the home carousel; tapping opens the detail page. Add a cover image and rich body like an official site announcement.',
|
||||
'content.hint.announcement': 'Shows as a scrolling marquee at the top of the player app. Enter title and marquee text per language — plain text only.',
|
||||
'content.section.publish': 'Publish settings',
|
||||
'content.section.content': 'Notification content',
|
||||
'content.field.publish_kind': 'Publish type',
|
||||
'content.publish_kind.notice': 'Full notification (detail page)',
|
||||
'content.publish_kind.ticker': 'Ticker text only',
|
||||
'content.publish_kind.hint': 'Full notifications appear in the list and detail page; ticker-only shows one scrolling line at the top.',
|
||||
'content.field.cover_image': 'Cover image',
|
||||
'content.field.ticker_title_ph': 'Ticker title (optional)',
|
||||
'content.field.ticker_body_ph': 'Text shown in the marquee',
|
||||
'content.field.ticker_hint': 'Ticker shows plain text only — no images or rich formatting.',
|
||||
'content.editor.placeholder': 'Write the notification body; you can insert images and lists…',
|
||||
'content.editor.insert_image': 'Insert image',
|
||||
'content.upload.cover_size_hint': 'Recommended width 860px+. Cover and inline images scale to fit on the player detail page.',
|
||||
'content.status.DRAFT': 'Draft',
|
||||
'content.status.ACTIVE': 'Active',
|
||||
'content.status.INACTIVE': 'Inactive',
|
||||
@@ -1930,7 +1966,7 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'content.field.start_time': 'Start time',
|
||||
'content.field.end_time': 'End time',
|
||||
'content.field.title': 'Title',
|
||||
'content.field.title_ph': 'Optional; can match body',
|
||||
'content.field.title_ph': 'Title shown on the player detail page',
|
||||
'content.field.body': 'Body',
|
||||
'content.field.announce_text': 'Marquee text',
|
||||
'content.field.image_url': 'Image URL',
|
||||
@@ -1941,8 +1977,8 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'content.upload.size_error': 'Image must be under 5 MB',
|
||||
'content.upload.remove': 'Remove image',
|
||||
'content.upload.pick_media': 'Pick from library',
|
||||
'content.upload.pick_media_title': 'Select Banner Image',
|
||||
'content.upload.no_media': 'No banner images in library — upload one first',
|
||||
'content.upload.pick_media_title': 'Select image',
|
||||
'content.upload.no_media': 'No images in library — upload one first',
|
||||
'content.upload.url_placeholder': 'Or paste image URL',
|
||||
'content.upload.recommended_size': 'Recommended size: 860 x 360 px, or any 43:18 image. The player carousel keeps the full image visible and fills extra space.',
|
||||
'content.link.none': 'No link',
|
||||
|
||||
@@ -8,15 +8,17 @@ const loaders: Record<AdminLocale, () => Promise<{ default: Record<string, strin
|
||||
};
|
||||
|
||||
const inflight = new Map<AdminLocale, Promise<void>>();
|
||||
const loaded = new Set<AdminLocale>();
|
||||
|
||||
/** 按语言动态加载文案包(仅当前 locale 进入主路径,其余为独立 chunk)。 */
|
||||
export async function ensureAdminLocaleLoaded(locale: AdminLocale): Promise<void> {
|
||||
if (Object.keys(adminMessages[locale]).length > 0) return;
|
||||
if (loaded.has(locale)) return;
|
||||
const pending = inflight.get(locale);
|
||||
if (pending) return pending;
|
||||
|
||||
const task = loaders[locale]().then((mod) => {
|
||||
adminMessages[locale] = mod.default;
|
||||
Object.assign(adminMessages[locale], mod.default);
|
||||
loaded.add(locale);
|
||||
});
|
||||
inflight.set(locale, task);
|
||||
try {
|
||||
|
||||
@@ -27,6 +27,10 @@ const adminPages: Record<string, string> = {
|
||||
'user.filter.agent': 'Agent',
|
||||
'user.filter.agent_ph': 'All',
|
||||
'user.col.username': 'Username',
|
||||
'user.col.online': 'Online',
|
||||
'user.online_yes': 'Online now',
|
||||
'user.presence_online': 'Online',
|
||||
'user.presence_offline': 'Offline',
|
||||
'user.col.agent': 'Agent',
|
||||
'user.col.agent_cashback': 'Agent cashback',
|
||||
'user.col.player_cashback': 'Player cashback',
|
||||
@@ -786,15 +790,43 @@ const adminPages: Record<string, string> = {
|
||||
'msg.outright_odds_saved': 'Outright odds saved',
|
||||
'msg.load_failed': 'Load failed',
|
||||
|
||||
'content.upload.pick_media_title': 'Select image',
|
||||
'content.upload.no_media': 'No images in library — upload one first',
|
||||
'content.btn.create': 'New content',
|
||||
'content.btn.enable': 'Enable',
|
||||
'content.btn.disable': 'Disable',
|
||||
'content.dialog.create': 'New public content',
|
||||
'content.dialog.edit': 'Edit public content',
|
||||
'content.dialog.create_banner': 'New home promotion',
|
||||
'content.dialog.create_notice': 'Publish notification',
|
||||
'content.dialog.edit': 'Edit content',
|
||||
'content.dialog.edit_banner': 'Edit home promotion',
|
||||
'content.dialog.edit_notice': 'Edit notification',
|
||||
'content.confirm_delete': 'Delete "{title}"?',
|
||||
'content.type.BANNER': 'Home banners',
|
||||
'content.type.ANNOUNCEMENT': 'Announcements',
|
||||
'content.hint.announcement': 'Shown in the player top marquee; fill title or body (body recommended)',
|
||||
'content.type.BANNER': 'Home promotions',
|
||||
'content.type.ANNOUNCEMENT': 'Notifications',
|
||||
'content.type.INBOX_NOTIFY': 'Inbox notify',
|
||||
'content.inbox_notify.inbox_enabled': 'Player inbox',
|
||||
'content.inbox_notify.inbox_enabled_hint': 'When off, the player hub opens support only (no mailbox tab)',
|
||||
'content.inbox_notify.deposit': 'Deposit results',
|
||||
'content.inbox_notify.deposit_hint': 'Auto-send inbox messages on approve or reject',
|
||||
'content.inbox_notify.manual_title': 'Check "Inbox notify" when creating:',
|
||||
'content.inbox_notify.banner_note': 'Homepage promo',
|
||||
'content.inbox_notify.announcement_note': 'Announcements / ticker',
|
||||
'content.hint.banner': 'Shown in the home carousel; tapping opens the detail page. Add a cover image and rich body like an official site announcement.',
|
||||
'content.hint.announcement': 'Shows as a scrolling marquee at the top of the player app. Enter title and marquee text per language — plain text only.',
|
||||
'content.section.publish': 'Publish settings',
|
||||
'content.section.content': 'Notification content',
|
||||
'content.field.publish_kind': 'Publish type',
|
||||
'content.publish_kind.notice': 'Full notification (detail page)',
|
||||
'content.publish_kind.ticker': 'Ticker text only',
|
||||
'content.publish_kind.hint': 'Full notifications appear in the list and detail page; ticker-only shows one scrolling line at the top.',
|
||||
'content.field.cover_image': 'Cover image',
|
||||
'content.field.ticker_title_ph': 'Ticker title (optional)',
|
||||
'content.field.ticker_body_ph': 'Text shown in the marquee',
|
||||
'content.field.ticker_hint': 'Ticker shows plain text only — no images or rich formatting.',
|
||||
'content.editor.placeholder': 'Write the notification body; you can insert images and lists…',
|
||||
'content.editor.insert_image': 'Insert image',
|
||||
'content.upload.cover_size_hint': 'Recommended width 860px+. Cover and inline images scale to fit on the player detail page.',
|
||||
'content.status.DRAFT': 'Draft',
|
||||
'content.status.ACTIVE': 'Active',
|
||||
'content.status.INACTIVE': 'Inactive',
|
||||
@@ -808,8 +840,11 @@ const adminPages: Record<string, string> = {
|
||||
'content.field.link_target': 'Link target',
|
||||
'content.field.start_time': 'Start time',
|
||||
'content.field.end_time': 'End time',
|
||||
'content.field.notify_inbox': 'Inbox notification',
|
||||
'content.field.notify_inbox_hint': 'Send an inbox message to all players about this promotion after saving',
|
||||
'content.msg.notify_sent': 'Inbox notification sent to {count} player(s)',
|
||||
'content.field.title': 'Title',
|
||||
'content.field.title_ph': 'Optional; can match body',
|
||||
'content.field.title_ph': 'Title shown on the player detail page',
|
||||
'content.field.body': 'Body',
|
||||
'content.field.announce_text': 'Marquee text',
|
||||
'content.field.image_url': 'Image URL',
|
||||
@@ -820,8 +855,6 @@ const adminPages: Record<string, string> = {
|
||||
'content.upload.size_error': 'Image must be under 5 MB',
|
||||
'content.upload.remove': 'Remove image',
|
||||
'content.upload.pick_media': 'Pick from library',
|
||||
'content.upload.pick_media_title': 'Select Banner Image',
|
||||
'content.upload.no_media': 'No banner images in library — upload one first',
|
||||
'content.upload.url_placeholder': 'Or paste image URL',
|
||||
'content.upload.recommended_size': 'Recommended size: 860 x 360 px, or any 43:18 image. The player carousel keeps the full image visible and fills extra space.',
|
||||
'content.link.none': 'No link',
|
||||
|
||||
@@ -28,6 +28,10 @@ const adminPages: Record<string, string> = {
|
||||
'user.filter.agent': '所属代理',
|
||||
'user.filter.agent_ph': '全部',
|
||||
'user.col.username': '用户名',
|
||||
'user.col.online': '在线',
|
||||
'user.online_yes': '当前在线',
|
||||
'user.presence_online': '在线',
|
||||
'user.presence_offline': '离线',
|
||||
'user.col.agent': '所属代理',
|
||||
'user.col.agent_cashback': '代理返水率',
|
||||
'user.col.player_cashback': '玩家返水率',
|
||||
@@ -790,15 +794,43 @@ const adminPages: Record<string, string> = {
|
||||
'content.btn.enable': '启用',
|
||||
'content.btn.disable': '停用',
|
||||
'content.dialog.create': '新建公共内容',
|
||||
'content.dialog.edit': '编辑公共内容',
|
||||
'content.dialog.create_banner': '新建首页推广',
|
||||
'content.dialog.create_notice': '发布通知公告',
|
||||
'content.dialog.edit': '编辑内容',
|
||||
'content.dialog.edit_banner': '编辑首页推广',
|
||||
'content.dialog.edit_notice': '编辑通知公告',
|
||||
'content.confirm_delete': '确定删除「{title}」?',
|
||||
'content.type.BANNER': '首页轮播',
|
||||
'content.type.ANNOUNCEMENT': '公告滚动',
|
||||
'content.hint.announcement': '显示在玩家端顶部跑马灯;标题与正文填一项即可,建议正文为主',
|
||||
'content.type.BANNER': '首页推广',
|
||||
'content.type.ANNOUNCEMENT': '通知公告',
|
||||
'content.type.INBOX_NOTIFY': '邮箱通知',
|
||||
'content.inbox_notify.inbox_enabled': '站内邮箱',
|
||||
'content.inbox_notify.inbox_enabled_hint': '关闭后玩家端入口直达客服,不展示邮箱标签页',
|
||||
'content.inbox_notify.deposit': '充值结果',
|
||||
'content.inbox_notify.deposit_hint': '审核通过或拒绝时自动发送站内信',
|
||||
'content.inbox_notify.manual_title': '以下类型在新建时勾选「邮箱通知」',
|
||||
'content.inbox_notify.banner_note': '首页推广',
|
||||
'content.inbox_notify.announcement_note': '通知公告 / 跑马灯',
|
||||
'content.hint.banner': '用于首页轮播展示,点击后进入通知详情页;请填写封面图与正文,像官网发布活动通知一样编辑。',
|
||||
'content.hint.announcement': '在玩家端顶部显示滚动跑马灯文字;填写各语言标题与滚动文案即可,纯文本无富文本。',
|
||||
'content.section.publish': '发布设置',
|
||||
'content.section.content': '通知内容',
|
||||
'content.field.publish_kind': '发布类型',
|
||||
'content.publish_kind.notice': '完整通知(详情页)',
|
||||
'content.publish_kind.ticker': '仅跑马灯文字',
|
||||
'content.publish_kind.hint': '完整通知会出现在公告列表与详情页;跑马灯仅显示顶部滚动一行文字。',
|
||||
'content.field.cover_image': '封面图',
|
||||
'content.field.ticker_title_ph': '跑马灯标题(选填)',
|
||||
'content.field.ticker_body_ph': '跑马灯显示的文字',
|
||||
'content.field.ticker_hint': '跑马灯只显示纯文字,不支持图片与富文本格式。',
|
||||
'content.editor.placeholder': '输入通知正文,可插入图片、列表等…',
|
||||
'content.editor.insert_image': '插入图片',
|
||||
'content.upload.cover_size_hint': '建议宽度 860px 以上;玩家端详情页会完整显示封面,正文内图片也会自适应宽度。',
|
||||
'content.upload.pick_media_title': '选择图片',
|
||||
'content.upload.no_media': '媒体库中暂无图片,请先上传',
|
||||
'content.status.DRAFT': '草稿',
|
||||
'content.status.ACTIVE': '已启用',
|
||||
'content.status.INACTIVE': '已停用',
|
||||
'content.col.sort': '排序',
|
||||
'content.col.sort': '排序值',
|
||||
'content.col.preview': '预览',
|
||||
'content.col.title': '标题/摘要',
|
||||
'content.col.player_visible': '玩家可见',
|
||||
@@ -808,8 +840,11 @@ const adminPages: Record<string, string> = {
|
||||
'content.field.link_target': '链接目标',
|
||||
'content.field.start_time': '开始时间',
|
||||
'content.field.end_time': '结束时间',
|
||||
'content.field.notify_inbox': '邮箱通知',
|
||||
'content.field.notify_inbox_hint': '保存后向全部玩家发送站内信,通知查看此推广',
|
||||
'content.msg.notify_sent': '已向 {count} 位玩家发送邮箱通知',
|
||||
'content.field.title': '标题',
|
||||
'content.field.title_ph': '选填,可与正文相同',
|
||||
'content.field.title_ph': '通知标题,玩家端详情页展示',
|
||||
'content.field.body': '正文',
|
||||
'content.field.announce_text': '滚动文案',
|
||||
'content.field.image_url': '图片地址',
|
||||
@@ -820,8 +855,6 @@ const adminPages: Record<string, string> = {
|
||||
'content.upload.size_error': '图片大小不能超过 5MB',
|
||||
'content.upload.remove': '移除图片',
|
||||
'content.upload.pick_media': '从媒体库选择',
|
||||
'content.upload.pick_media_title': '选择 Banner 图片',
|
||||
'content.upload.no_media': '媒体库中暂无 Banner 图片,请先上传',
|
||||
'content.upload.url_placeholder': '或手动粘贴图片 URL',
|
||||
'content.upload.recommended_size': '建议尺寸:860 x 360 px,或 43:18 同比例图片;前台会完整显示并自动填充不合比例区域。',
|
||||
'content.link.none': '无跳转',
|
||||
|
||||
Reference in New Issue
Block a user