feat: update baishun and push admin flows

This commit is contained in:
hy 2026-04-24 12:57:19 +08:00
parent 6f71528fcb
commit 69fe442306
6 changed files with 180 additions and 17 deletions

View File

@ -10,6 +10,13 @@ export async function saveBaishunProviderConfig(data: Record<string, any>) {
return requestClient.post<Record<string, any>>('/go/operate/baishun-game/config', data);
}
export async function activateBaishunProviderProfile(data: Record<string, any>) {
return requestClient.post<Record<string, any>>(
'/go/operate/baishun-game/config/activate',
data,
);
}
export async function pageBaishunGames(params: Record<string, any>) {
return requestClient.get<Record<string, any>>('/go/operate/baishun-game/page', {
params,
@ -26,9 +33,13 @@ export async function saveBaishunGame(data: Record<string, any>) {
return requestClient.post<Record<string, any>>('/go/operate/baishun-game/save', data);
}
export async function deleteBaishunGame(id: number | string, sysOrigin: string) {
export async function deleteBaishunGame(
id: number | string,
sysOrigin: string,
profile?: string,
) {
return requestClient.delete<Record<string, any>>('/go/operate/baishun-game', {
params: { id, sysOrigin },
params: { id, profile, sysOrigin },
});
}

View File

@ -197,6 +197,13 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/gift-history.vue'),
meta: { title: '礼物赠送记录' },
},
{
name: 'OperatePush',
path: 'push',
alias: '/operate/manager/push',
component: () => import('#/views/operate/push.vue'),
meta: { title: '消息推送' },
},
{
name: 'RunningWaterUserProps',
path: 'running/water/user/props',

View File

@ -161,12 +161,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/teen-patti.vue'),
meta: { title: '炸金花' },
},
{
name: 'OperatePush',
path: 'push',
component: () => import('#/views/operate/push.vue'),
meta: { title: '消息推送' },
},
{
name: 'OperateSysCountry',
path: 'sys/country',

View File

@ -5,6 +5,7 @@ import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
import {
activateBaishunProviderProfile,
fetchBaishunCatalog,
getBaishunProviderConfig,
importBaishunCatalog,
@ -22,6 +23,7 @@ import {
Input,
InputNumber,
Pagination,
Select,
Table,
Tag,
message,
@ -32,6 +34,7 @@ defineOptions({ name: 'OperateBaishunConfig' });
function createProviderForm(sysOrigin = '') {
return {
sysOrigin,
profile: 'PROD',
platformBaseUrl: '',
appId: undefined as any,
appName: '',
@ -49,7 +52,12 @@ const sysOriginOptions = computed(() => {
return options.length > 0 ? options : getAllowedSysOrigins([]);
});
const contextQuery = reactive({ sysOrigin: '' });
const profileOptions = [
{ label: '正式环境', value: 'PROD' },
{ label: '测试环境', value: 'TEST' },
];
const contextQuery = reactive({ profile: 'PROD', sysOrigin: '' });
const providerForm = reactive(createProviderForm());
const catalogQuery = reactive<Record<string, any>>({
cursor: 1,
@ -62,9 +70,20 @@ const providerSaving = ref(false);
const catalogLoading = ref(false);
const catalogFetching = ref(false);
const catalogImporting = ref(false);
const profileActivating = ref(false);
const totalCatalog = ref(0);
const catalogList = ref<Array<Record<string, any>>>([]);
const selectedCatalogKeys = ref<Array<number>>([]);
const activeProfile = ref('PROD');
const isCurrentProfileActive = computed(
() => activeProfile.value === contextQuery.profile,
);
const activeProfileLabel = computed(
() =>
profileOptions.find((item) => item.value === activeProfile.value)?.label ||
activeProfile.value,
);
const catalogColumns = [
{ dataIndex: 'cover', key: 'cover', title: '封面', width: 90 },
@ -100,12 +119,15 @@ watch(
);
watch(
() => contextQuery.sysOrigin,
(value) => {
if (!value) {
() => [contextQuery.sysOrigin, contextQuery.profile],
([sysOrigin, profile]) => {
if (!sysOrigin || !profile) {
return;
}
Object.assign(providerForm, createProviderForm(value));
Object.assign(providerForm, createProviderForm(sysOrigin), {
profile,
sysOrigin,
});
selectedCatalogKeys.value = [];
void Promise.all([loadProviderConfig(), loadCatalog(true)]);
},
@ -113,8 +135,10 @@ watch(
);
function applyProviderForm(record: Record<string, any>) {
activeProfile.value = String(record.activeProfile || activeProfile.value || 'PROD');
Object.assign(providerForm, createProviderForm(contextQuery.sysOrigin), {
...record,
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
appId: Number(record.appId || 0) || undefined,
gsp: Number(record.gsp || 101) || 101,
@ -130,6 +154,7 @@ async function loadProviderConfig() {
providerLoading.value = true;
try {
const result = await getBaishunProviderConfig({
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
});
applyProviderForm(result || {});
@ -153,6 +178,7 @@ async function persistProviderConfig(showSuccess = true) {
try {
const result = await saveBaishunProviderConfig({
...providerForm,
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
appId: Number(providerForm.appId || 0),
gsp: Number(providerForm.gsp || 101),
@ -182,6 +208,7 @@ async function loadCatalog(reset = false) {
try {
const result = await pageBaishunCatalog({
...catalogQuery,
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
});
catalogList.value = Array.isArray(result.records) ? result.records : [];
@ -205,6 +232,24 @@ async function handleSaveProviderConfig() {
await persistProviderConfig(true);
}
async function handleActivateProfile() {
if (!(await persistProviderConfig(false))) {
return;
}
profileActivating.value = true;
try {
const result = await activateBaishunProviderProfile({
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
});
applyProviderForm(result || providerForm);
message.success(`已切换到${activeProfileLabel.value}`);
await loadCatalog(true);
} finally {
profileActivating.value = false;
}
}
async function handleFetchCatalog() {
if (!(await persistProviderConfig(false))) {
return;
@ -212,6 +257,7 @@ async function handleFetchCatalog() {
catalogFetching.value = true;
try {
const result = await fetchBaishunCatalog({
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
});
message.success(
@ -234,6 +280,7 @@ async function handleImportSelected() {
catalogImporting.value = true;
try {
const result = await importBaishunCatalog({
profile: contextQuery.profile,
sysOrigin: contextQuery.sysOrigin,
vendorGameIds: selectedCatalogKeys.value,
});
@ -259,6 +306,13 @@ async function handleImportSelected() {
:options="sysOriginOptions"
/>
</FormItem>
<FormItem label="配置环境" extra="app 侧只读取当前启用环境的配置和游戏列表。">
<Select
v-model:value="contextQuery.profile"
:options="profileOptions"
option-label-prop="label"
/>
</FormItem>
<FormItem
label="平台地址"
extra="填写百顺平台接口地址,例如 https://game-cn-test.jieyou.shop不要填 Lucky Gift 域名。"
@ -321,11 +375,21 @@ async function handleImportSelected() {
>
保存配置
</Button>
<Button
:disabled="isCurrentProfileActive"
:loading="profileActivating"
@click="handleActivateProfile"
>
启用此环境
</Button>
<Button :loading="catalogFetching" @click="handleFetchCatalog">
获取百顺游戏列表
</Button>
<Tag :color="isCurrentProfileActive ? 'success' : 'warning'">
当前启用{{ activeProfileLabel }}
</Tag>
<span class="hint">
最终入库统一落到游戏管理 / 游戏列表这里仅负责百顺接入配置和目录导入
正式和测试环境各自保存配置目录和已添加游戏切换启用环境后Go app 游戏列表同步切换
</span>
</div>
</Form>

View File

@ -10,6 +10,7 @@ import {
regionConfigTable,
saveOrUpdateGameConfig,
} from '#/api/legacy/system';
import { deleteBaishunGame } from '#/api/legacy/baishun-game';
import {
OSS_FILE_BUCKETS,
getAccessImgUrl,
@ -79,7 +80,10 @@ const regions = ref<Array<Record<string, any>>>([]);
const modalOpen = ref(false);
const saving = ref(false);
const uploadLoading = ref(false);
const batchDeleting = ref(false);
const fileInput = ref<HTMLInputElement | null>(null);
const selectedRowKeys = ref<Array<number | string>>([]);
const selectedRows = ref<Array<Record<string, any>>>([]);
const query = reactive<Record<string, any>>({
cursor: 1,
@ -141,6 +145,16 @@ const columns = [
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 140, fixed: 'right' as const },
];
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys: Array<number | string>, rows: Array<Record<string, any>>) => {
selectedRowKeys.value = keys;
selectedRows.value = rows;
},
}));
const selectedCount = computed(() => selectedRowKeys.value.length);
watch(
sysOriginOptions,
(options) => {
@ -185,6 +199,22 @@ function normalizeMultiValue(value: any) {
.filter(Boolean);
}
function clearSelection() {
selectedRowKeys.value = [];
selectedRows.value = [];
}
function isBaishunGame(record: Record<string, any>) {
return String(record.gameOrigin || query.gameOrigin || '').toUpperCase() === 'BAISHUN';
}
function deleteGameRecord(record: Record<string, any>) {
if (isBaishunGame(record)) {
return deleteBaishunGame(record.id, record.sysOrigin || query.sysOrigin);
}
return deleteGameConfig(record.id, record.sysOrigin || query.sysOrigin);
}
async function loadData(reset = false) {
if (reset) {
query.cursor = 1;
@ -194,6 +224,7 @@ async function loadData(reset = false) {
const result = await pageGameConfig({ ...query });
list.value = result.records || [];
total.value = result.total || 0;
clearSelection();
} finally {
loading.value = false;
}
@ -271,13 +302,42 @@ function handleDelete(record: Record<string, any>) {
Modal.confirm({
title: '确认删除吗?',
async onOk() {
await deleteGameConfig(record.id, record.sysOrigin);
await deleteGameRecord(record);
message.success('删除成功');
await loadData(true);
},
});
}
function handleBatchDelete() {
if (selectedRows.value.length === 0) {
message.warning('请选择要删除的游戏');
return;
}
const rows = [...selectedRows.value];
Modal.confirm({
title: `确认删除已选 ${rows.length} 个游戏吗?`,
async onOk() {
batchDeleting.value = true;
try {
const results = await Promise.allSettled(
rows.map((row) => deleteGameRecord(row)),
);
const failed = results.filter((item) => item.status === 'rejected').length;
const success = results.length - failed;
if (failed > 0) {
message.warning(`批量删除完成:成功 ${success} 个,失败 ${failed}`);
} else {
message.success(`已删除 ${success} 个游戏`);
}
await loadData(true);
} finally {
batchDeleting.value = false;
}
},
});
}
function openUpload() {
fileInput.value?.click();
}
@ -316,6 +376,17 @@ void loadData(true);
<Button :loading="loading" type="primary" @click="handleSearch">
搜索
</Button>
<Button
danger
:disabled="selectedCount === 0"
:loading="batchDeleting"
@click="handleBatchDelete"
>
批量删除
</Button>
<span v-if="selectedCount > 0" class="selected-tip">
已选 {{ selectedCount }}
</span>
</div>
<Table
@ -323,6 +394,7 @@ void loadData(true);
:data-source="list"
:loading="loading"
:pagination="false"
:row-selection="rowSelection"
row-key="id"
:scroll="{ x: 2160 }"
>
@ -538,4 +610,9 @@ void loadData(true);
justify-content: flex-end;
margin-top: 16px;
}
.selected-tip {
color: rgb(100 116 139);
line-height: 32px;
}
</style>

View File

@ -84,6 +84,7 @@ const languageSelectOptions = LANGUAGE_OPTIONS.map((item) => ({
const businessSceneOptions = [
{ label: '官方通知', value: 'OFFICIAL_MESSAGE_NOTICE' as any },
];
const PUSH_SUCCESS_VALUES = new Set(['0', 'SUCCESS', '成功']);
const accessStore = useAccessStore();
const sysOriginOptions = computed(() => {
@ -219,6 +220,15 @@ function updateFixedCount(value: string) {
fixedUidCount.value = normalizeFixedIds(value).length;
}
function isPushSuccess(value: unknown) {
const normalized = String(value ?? '').trim().toUpperCase();
return PUSH_SUCCESS_VALUES.has(normalized);
}
function getPushStatusLabel(value: unknown) {
return isPushSuccess(value) ? '成功' : '失败';
}
async function handleSendPush() {
if (!newPushForm.title.trim() || !newPushForm.content.trim()) {
message.warning('请填写标题和内容');
@ -479,8 +489,8 @@ watch(
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'pushStatus'">
<Tag :color="record.pushStatus === 0 ? 'success' : 'error'">
{{ record.pushStatus === 0 ? '成功' : '失败' }}
<Tag :color="isPushSuccess(record.pushStatus) ? 'success' : 'error'">
{{ getPushStatusLabel(record.pushStatus) }}
</Tag>
</template>
</template>