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); 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>) { export async function pageBaishunGames(params: Record<string, any>) {
return requestClient.get<Record<string, any>>('/go/operate/baishun-game/page', { return requestClient.get<Record<string, any>>('/go/operate/baishun-game/page', {
params, 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); 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', { 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'), component: () => import('#/views/operate/gift-history.vue'),
meta: { title: '礼物赠送记录' }, meta: { title: '礼物赠送记录' },
}, },
{
name: 'OperatePush',
path: 'push',
alias: '/operate/manager/push',
component: () => import('#/views/operate/push.vue'),
meta: { title: '消息推送' },
},
{ {
name: 'RunningWaterUserProps', name: 'RunningWaterUserProps',
path: 'running/water/user/props', path: 'running/water/user/props',

View File

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

View File

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

View File

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

View File

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