feat: update baishun and push admin flows
This commit is contained in:
parent
6f71528fcb
commit
69fe442306
@ -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 },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user