Compare commits
No commits in common. "5045256d723a7a408c1d6661358a588e13f0cadc" and "1db5607cb8fa55178bcb346e1b1ac83c1507df32" have entirely different histories.
5045256d72
...
1db5607cb8
@ -7,11 +7,8 @@ const SPECIFIED_GIFT_WEEKLY_RANK_BASE =
|
|||||||
const SIGN_IN_REWARD_BASE = '/go/resident-activity/sign-in-reward';
|
const SIGN_IN_REWARD_BASE = '/go/resident-activity/sign-in-reward';
|
||||||
const ROOM_TURNOVER_REWARD_BASE =
|
const ROOM_TURNOVER_REWARD_BASE =
|
||||||
'/go/resident-activity/room-turnover-reward';
|
'/go/resident-activity/room-turnover-reward';
|
||||||
const CP_BASE = '/resident-activity/cp';
|
|
||||||
const VIP_BASE = '/go/resident-activity/vip';
|
const VIP_BASE = '/go/resident-activity/vip';
|
||||||
const RECHARGE_REWARD_BASE = '/go/resident-activity/recharge-reward';
|
const RECHARGE_REWARD_BASE = '/go/resident-activity/recharge-reward';
|
||||||
const FIRST_RECHARGE_REWARD_BASE =
|
|
||||||
'/go/resident-activity/first-recharge-reward';
|
|
||||||
const TASK_CENTER_BASE = '/go/resident-activity/task-center';
|
const TASK_CENTER_BASE = '/go/resident-activity/task-center';
|
||||||
const VOICE_ROOM_RED_PACKET_BASE = '/go/resident-activity/voice-room-red-packet';
|
const VOICE_ROOM_RED_PACKET_BASE = '/go/resident-activity/voice-room-red-packet';
|
||||||
const VOICE_ROOM_ROCKET_BASE = '/go/resident-activity/voice-room-rocket';
|
const VOICE_ROOM_ROCKET_BASE = '/go/resident-activity/voice-room-rocket';
|
||||||
@ -58,16 +55,6 @@ export async function resetResidentRegisterRewardConfig(sysOrigin: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getResidentCpConfig(sysOrigin: string) {
|
|
||||||
return requestClient.get<Record<string, any>>(`${CP_BASE}/config`, {
|
|
||||||
params: { sysOrigin },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveResidentCpConfig(data: Record<string, any>) {
|
|
||||||
return requestClient.post<Record<string, any>>(`${CP_BASE}/config/save`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function pageResidentRegisterRewardDailyRecords(
|
export async function pageResidentRegisterRewardDailyRecords(
|
||||||
params: Record<string, any>,
|
params: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
@ -188,31 +175,6 @@ export async function pageResidentRechargeRewardClaimRecords(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getResidentFirstRechargeRewardConfig(sysOrigin: string) {
|
|
||||||
return requestClient.get<Record<string, any>>(
|
|
||||||
`${FIRST_RECHARGE_REWARD_BASE}/config`,
|
|
||||||
{ params: { sysOrigin } },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveResidentFirstRechargeRewardConfig(
|
|
||||||
data: Record<string, any>,
|
|
||||||
) {
|
|
||||||
return requestClient.post<Record<string, any>>(
|
|
||||||
`${FIRST_RECHARGE_REWARD_BASE}/config/save`,
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function pageResidentFirstRechargeRewardGrantRecords(
|
|
||||||
params: Record<string, any>,
|
|
||||||
) {
|
|
||||||
return requestClient.get<Record<string, any>>(
|
|
||||||
`${FIRST_RECHARGE_REWARD_BASE}/grant-record/page`,
|
|
||||||
{ params },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getResidentDailyTaskConfig(
|
export async function getResidentDailyTaskConfig(
|
||||||
sysOrigin: string,
|
sysOrigin: string,
|
||||||
taskCategory = 'DAILY',
|
taskCategory = 'DAILY',
|
||||||
|
|||||||
@ -62,7 +62,6 @@ const ROUTE_MENU_OVERRIDES: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LOCAL_RESIDENT_ACTIVITY_FALLBACK_ROUTES = new Set([
|
const LOCAL_RESIDENT_ACTIVITY_FALLBACK_ROUTES = new Set([
|
||||||
'ResidentCpConfig',
|
|
||||||
'ResidentSmashGoldenEggConfig',
|
'ResidentSmashGoldenEggConfig',
|
||||||
'ResidentVoiceRoomRocket',
|
'ResidentVoiceRoomRocket',
|
||||||
'ResidentWheelConfig',
|
'ResidentWheelConfig',
|
||||||
|
|||||||
@ -23,12 +23,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('#/views/resident-activity/vip-config.vue'),
|
component: () => import('#/views/resident-activity/vip-config.vue'),
|
||||||
meta: { title: 'VIP' },
|
meta: { title: 'VIP' },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'ResidentCpConfig',
|
|
||||||
path: 'cp/config',
|
|
||||||
component: () => import('#/views/resident-activity/cp-config.vue'),
|
|
||||||
meta: { title: 'CP' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'ResidentWheelConfig',
|
name: 'ResidentWheelConfig',
|
||||||
path: 'wheel/config',
|
path: 'wheel/config',
|
||||||
@ -48,13 +42,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('#/views/resident-activity/recharge-reward-config.vue'),
|
component: () => import('#/views/resident-activity/recharge-reward-config.vue'),
|
||||||
meta: { title: '充值奖励配置' },
|
meta: { title: '充值奖励配置' },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'ResidentFirstRechargeRewardConfig',
|
|
||||||
path: 'first-recharge-reward/config',
|
|
||||||
component: () =>
|
|
||||||
import('#/views/resident-activity/first-recharge-reward-config.vue'),
|
|
||||||
meta: { title: '首冲奖励' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'ResidentDailyTaskConfig',
|
name: 'ResidentDailyTaskConfig',
|
||||||
path: 'daily-task/config',
|
path: 'daily-task/config',
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { useAccessStore } from '@vben/stores';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Image,
|
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
message,
|
message,
|
||||||
@ -27,11 +26,6 @@ import {
|
|||||||
pageAppHomePopups,
|
pageAppHomePopups,
|
||||||
saveAppHomePopup,
|
saveAppHomePopup,
|
||||||
} from '#/api/legacy/app-system';
|
} from '#/api/legacy/app-system';
|
||||||
import {
|
|
||||||
getAccessImgUrl,
|
|
||||||
OSS_FILE_BUCKETS,
|
|
||||||
simpleUploadFile,
|
|
||||||
} from '#/api/legacy/oss';
|
|
||||||
import InlineFilterField from '#/views/_shared/inline-filter-field.vue';
|
import InlineFilterField from '#/views/_shared/inline-filter-field.vue';
|
||||||
import InlineFilterToolbar from '#/views/_shared/inline-filter-toolbar.vue';
|
import InlineFilterToolbar from '#/views/_shared/inline-filter-toolbar.vue';
|
||||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
@ -67,10 +61,8 @@ const sysOriginOptions = computed(() => {
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const imageUploading = ref(false);
|
|
||||||
const formOpen = ref(false);
|
const formOpen = ref(false);
|
||||||
const formTitle = ref('新增弹窗');
|
const formTitle = ref('新增弹窗');
|
||||||
const imageInputRef = ref<HTMLInputElement | null>(null);
|
|
||||||
const list = ref<AppHomePopupItem[]>([]);
|
const list = ref<AppHomePopupItem[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
|
||||||
@ -156,31 +148,6 @@ function handleEdit(record: AppHomePopupItem) {
|
|||||||
formOpen.value = true;
|
formOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickImage() {
|
|
||||||
imageInputRef.value?.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleImageUpload(event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
const file = target.files?.[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
imageUploading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await simpleUploadFile(file, OSS_FILE_BUCKETS.other);
|
|
||||||
form.image = getAccessImgUrl(result.name);
|
|
||||||
message.success('上传成功');
|
|
||||||
} finally {
|
|
||||||
imageUploading.value = false;
|
|
||||||
target.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearImage() {
|
|
||||||
form.image = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
if (!String(form.popupKey || '').trim()) {
|
if (!String(form.popupKey || '').trim()) {
|
||||||
message.warning('请输入弹窗 key');
|
message.warning('请输入弹窗 key');
|
||||||
@ -373,28 +340,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>图片</label>
|
<label>图片</label>
|
||||||
<input
|
<Input v-model:value="form.image" placeholder="https://..." />
|
||||||
ref="imageInputRef"
|
|
||||||
accept="image/*"
|
|
||||||
class="hidden-input"
|
|
||||||
type="file"
|
|
||||||
@change="handleImageUpload"
|
|
||||||
/>
|
|
||||||
<div class="upload-row">
|
|
||||||
<Image
|
|
||||||
v-if="form.image"
|
|
||||||
:preview="false"
|
|
||||||
:src="form.image"
|
|
||||||
class="image-preview"
|
|
||||||
/>
|
|
||||||
<div v-else class="image-empty">未上传</div>
|
|
||||||
<Space>
|
|
||||||
<Button :loading="imageUploading" @click="pickImage">
|
|
||||||
{{ form.image ? '重新上传' : '上传图片' }}
|
|
||||||
</Button>
|
|
||||||
<Button v-if="form.image" @click="clearImage">清除</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>跳转类型</label>
|
<label>跳转类型</label>
|
||||||
@ -457,34 +403,5 @@ watch(
|
|||||||
color: hsl(var(--muted-foreground));
|
color: hsl(var(--muted-foreground));
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-row {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview,
|
|
||||||
.image-empty {
|
|
||||||
border-radius: 8px;
|
|
||||||
height: 96px;
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-empty {
|
|
||||||
align-items: center;
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
display: flex;
|
|
||||||
font-size: 13px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden-input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const emit = defineEmits<{
|
|||||||
success: [];
|
success: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
type ResourceKind = 'badge' | 'currency' | 'gift' | 'props' | 'vip';
|
type ResourceKind = 'badge' | 'gift' | 'props' | 'vip';
|
||||||
|
|
||||||
type TypeOption = {
|
type TypeOption = {
|
||||||
icon: string;
|
icon: string;
|
||||||
@ -93,7 +93,6 @@ const sourceLoading = ref(false);
|
|||||||
const resourceKeyword = ref('');
|
const resourceKeyword = ref('');
|
||||||
const currentResourcePage = ref(1);
|
const currentResourcePage = ref(1);
|
||||||
const selectedType = ref('');
|
const selectedType = ref('');
|
||||||
const goldAmount = ref<null | number>(null);
|
|
||||||
|
|
||||||
const form = reactive<{
|
const form = reactive<{
|
||||||
id: number | string;
|
id: number | string;
|
||||||
@ -122,12 +121,6 @@ const typeOptions = computed<TypeOption[]>(() => [
|
|||||||
label: item.name,
|
label: item.name,
|
||||||
value: String(item.value),
|
value: String(item.value),
|
||||||
})),
|
})),
|
||||||
{
|
|
||||||
icon: 'lucide:coins',
|
|
||||||
kind: 'currency',
|
|
||||||
label: '金币',
|
|
||||||
value: 'GOLD',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: 'lucide:award',
|
icon: 'lucide:award',
|
||||||
kind: 'badge',
|
kind: 'badge',
|
||||||
@ -309,9 +302,6 @@ function getResourceKey(kind: ResourceKind, detailType: string, id: number | str
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRewardKind(item: RewardGroupItem): ResourceKind {
|
function getRewardKind(item: RewardGroupItem): ResourceKind {
|
||||||
if (item.type === 'GOLD') {
|
|
||||||
return 'currency';
|
|
||||||
}
|
|
||||||
if (item.type === 'GIFT') {
|
if (item.type === 'GIFT') {
|
||||||
return 'gift';
|
return 'gift';
|
||||||
}
|
}
|
||||||
@ -348,9 +338,6 @@ function getItemName(item: RewardGroupItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getQuantityLabel(item: RewardGroupItem) {
|
function getQuantityLabel(item: RewardGroupItem) {
|
||||||
if (item.type === 'GOLD') {
|
|
||||||
return '金币';
|
|
||||||
}
|
|
||||||
if (item.type === 'GIFT') {
|
if (item.type === 'GIFT') {
|
||||||
return '数量';
|
return '数量';
|
||||||
}
|
}
|
||||||
@ -361,9 +348,6 @@ function getQuantityLabel(item: RewardGroupItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultQuantity(resource: DisplayResource) {
|
function getDefaultQuantity(resource: DisplayResource) {
|
||||||
if (resource.kind === 'currency') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (resource.kind === 'gift') {
|
if (resource.kind === 'gift') {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -377,9 +361,6 @@ function getDefaultQuantity(resource: DisplayResource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRewardType(resource: DisplayResource) {
|
function getRewardType(resource: DisplayResource) {
|
||||||
if (resource.kind === 'currency') {
|
|
||||||
return resource.type;
|
|
||||||
}
|
|
||||||
if (resource.kind === 'gift') {
|
if (resource.kind === 'gift') {
|
||||||
return 'GIFT';
|
return 'GIFT';
|
||||||
}
|
}
|
||||||
@ -415,7 +396,6 @@ function selectType(option: TypeOption) {
|
|||||||
selectedType.value = option.value;
|
selectedType.value = option.value;
|
||||||
resourceKeyword.value = '';
|
resourceKeyword.value = '';
|
||||||
currentResourcePage.value = 1;
|
currentResourcePage.value = 1;
|
||||||
goldAmount.value = null;
|
|
||||||
void loadOptions(option.value);
|
void loadOptions(option.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,9 +403,6 @@ async function loadOptions(type: string) {
|
|||||||
if (!type || !form.sysOrigin) {
|
if (!type || !form.sysOrigin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === 'GOLD') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const store = createTypeStore(type);
|
const store = createTypeStore(type);
|
||||||
if (store.loaded || store.loading) {
|
if (store.loaded || store.loading) {
|
||||||
return;
|
return;
|
||||||
@ -516,33 +493,6 @@ function toggleResource(resource: DisplayResource) {
|
|||||||
form.rewardConfigList.push(buildRewardItem(resource));
|
form.rewardConfigList.push(buildRewardItem(resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
function addGoldItem() {
|
|
||||||
const amount = Number(goldAmount.value);
|
|
||||||
if (!Number.isFinite(amount) || amount <= 0) {
|
|
||||||
message.warning('请输入金币数量');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const item: RewardGroupItem = {
|
|
||||||
amount,
|
|
||||||
content: amount,
|
|
||||||
detailType: 'GOLD',
|
|
||||||
name: '金币',
|
|
||||||
quantity: 0,
|
|
||||||
remark: '金币',
|
|
||||||
type: 'GOLD',
|
|
||||||
typeLabel: '金币',
|
|
||||||
};
|
|
||||||
const index = form.rewardConfigList.findIndex(
|
|
||||||
(reward) => reward.type === 'GOLD' && reward.detailType === 'GOLD',
|
|
||||||
);
|
|
||||||
if (index !== -1) {
|
|
||||||
form.rewardConfigList[index] = item;
|
|
||||||
} else {
|
|
||||||
form.rewardConfigList.push(item);
|
|
||||||
}
|
|
||||||
goldAmount.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeItem(index: number) {
|
function removeItem(index: number) {
|
||||||
form.rewardConfigList.splice(index, 1);
|
form.rewardConfigList.splice(index, 1);
|
||||||
}
|
}
|
||||||
@ -572,21 +522,9 @@ function updateItemQuantity(index: number, value: number | string | null) {
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item.type === 'GOLD') {
|
|
||||||
item.content = value ?? 0;
|
|
||||||
item.amount = value ?? 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
item.quantity = value ?? 0;
|
item.quantity = value ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemInputValue(item: RewardGroupItem) {
|
|
||||||
if (item.type === 'GOLD') {
|
|
||||||
return Number(item.content || 0);
|
|
||||||
}
|
|
||||||
return Number(item.quantity || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateItems() {
|
function validateItems() {
|
||||||
for (const item of form.rewardConfigList) {
|
for (const item of form.rewardConfigList) {
|
||||||
const quantity = Number(item.quantity);
|
const quantity = Number(item.quantity);
|
||||||
@ -594,14 +532,6 @@ function validateItems() {
|
|||||||
message.warning(`${getItemName(item)} 的${getQuantityLabel(item)}不能为空`);
|
message.warning(`${getItemName(item)} 的${getQuantityLabel(item)}不能为空`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (item.type === 'GOLD') {
|
|
||||||
const amount = Number(item.content);
|
|
||||||
if (!Number.isFinite(amount) || amount <= 0) {
|
|
||||||
message.warning('金币数量必须大于 0');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (item.type === 'GIFT' && quantity <= 0) {
|
if (item.type === 'GIFT' && quantity <= 0) {
|
||||||
message.warning(`${getItemName(item)} 的数量必须大于 0`);
|
message.warning(`${getItemName(item)} 的数量必须大于 0`);
|
||||||
return false;
|
return false;
|
||||||
@ -763,22 +693,7 @@ watch(filteredResources, () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="selectedType === 'GOLD'" class="gold-editor">
|
<div class="resource-section">
|
||||||
<div class="gold-editor__title">添加金币</div>
|
|
||||||
<div class="gold-editor__row">
|
|
||||||
<InputNumber
|
|
||||||
v-model:value="goldAmount"
|
|
||||||
:controls="false"
|
|
||||||
:min="1"
|
|
||||||
:precision="0"
|
|
||||||
class="gold-editor__input"
|
|
||||||
placeholder="输入金币数量"
|
|
||||||
/>
|
|
||||||
<Button type="primary" @click="addGoldItem">加入资源组</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="resource-section">
|
|
||||||
<div class="resource-head">
|
<div class="resource-head">
|
||||||
<div class="field-head">
|
<div class="field-head">
|
||||||
<span>资源列表</span>
|
<span>资源列表</span>
|
||||||
@ -889,7 +804,7 @@ watch(filteredResources, () => {
|
|||||||
<InputNumber
|
<InputNumber
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
:value="getItemInputValue(item)"
|
:value="Number(item.quantity || 0)"
|
||||||
class="quantity-input"
|
class="quantity-input"
|
||||||
@change="(value: number | string | null) => updateItemQuantity(Number(index), value)"
|
@change="(value: number | string | null) => updateItemQuantity(Number(index), value)"
|
||||||
/>
|
/>
|
||||||
@ -1050,35 +965,6 @@ watch(filteredResources, () => {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gold-editor {
|
|
||||||
align-content: start;
|
|
||||||
background: #fbfcfe;
|
|
||||||
border: 1px solid #edf0f3;
|
|
||||||
border-radius: 8px;
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
min-height: 420px;
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gold-editor__title {
|
|
||||||
color: #111827;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gold-editor__row {
|
|
||||||
align-items: center;
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: minmax(240px, 360px) auto;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gold-editor__input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-head,
|
.field-head,
|
||||||
.resource-head,
|
.resource-head,
|
||||||
.selected-panel__head {
|
.selected-panel__head {
|
||||||
|
|||||||
@ -1,317 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getResidentCpConfig,
|
|
||||||
saveResidentCpConfig,
|
|
||||||
} from '#/api/legacy/resident-activity';
|
|
||||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
Modal,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Spin,
|
|
||||||
Table,
|
|
||||||
Tag,
|
|
||||||
message,
|
|
||||||
} from 'antdv-next';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ResidentCpConfigPage' });
|
|
||||||
|
|
||||||
interface CpLevelConfig {
|
|
||||||
cabinConfigId?: null | string;
|
|
||||||
level: number;
|
|
||||||
levelName: string;
|
|
||||||
requiredIntimacyValue: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type RelationType = 'BROTHER' | 'CP' | 'SISTERS';
|
|
||||||
|
|
||||||
const relationTypeOptions: Array<{ label: string; value: RelationType }> = [
|
|
||||||
{ label: 'CP', value: 'CP' },
|
|
||||||
{ label: 'Brother', value: 'BROTHER' },
|
|
||||||
{ label: 'Sisters', value: 'SISTERS' },
|
|
||||||
];
|
|
||||||
|
|
||||||
function defaultLevel(level: number): CpLevelConfig {
|
|
||||||
return {
|
|
||||||
cabinConfigId: null,
|
|
||||||
level,
|
|
||||||
levelName: `CP ${level}级`,
|
|
||||||
requiredIntimacyValue: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultLevels() {
|
|
||||||
return Array.from({ length: 5 }, (_, index) => defaultLevel(index + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultDismissConsumeGolds(): Record<RelationType, number> {
|
|
||||||
return {
|
|
||||||
BROTHER: 0,
|
|
||||||
CP: 0,
|
|
||||||
SISTERS: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const sysOriginOptions = computed(() => {
|
|
||||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
|
||||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const saving = ref(false);
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{ dataIndex: 'level', key: 'level', title: '等级', width: 100 },
|
|
||||||
{ dataIndex: 'levelName', key: 'levelName', title: '等级文案', width: 260 },
|
|
||||||
{
|
|
||||||
dataIndex: 'requiredIntimacyValue',
|
|
||||||
key: 'requiredIntimacyValue',
|
|
||||||
title: '所需亲密值',
|
|
||||||
width: 220,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
configured: false,
|
|
||||||
dismissConsumeGolds: defaultDismissConsumeGolds(),
|
|
||||||
sysOrigin: '',
|
|
||||||
levels: defaultLevels(),
|
|
||||||
});
|
|
||||||
|
|
||||||
function normalizeLevels(levels: Array<Record<string, any>> | undefined) {
|
|
||||||
const levelMap = new Map<number, Record<string, any>>();
|
|
||||||
for (const item of levels || []) {
|
|
||||||
const level = Number(item.level || 0);
|
|
||||||
if (level >= 1 && level <= 5) {
|
|
||||||
levelMap.set(level, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultLevels().map((fallback) => {
|
|
||||||
const item = levelMap.get(fallback.level);
|
|
||||||
if (!item) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
cabinConfigId: item.cabinConfigId ? String(item.cabinConfigId) : null,
|
|
||||||
level: fallback.level,
|
|
||||||
levelName: String(item.levelName || fallback.levelName),
|
|
||||||
requiredIntimacyValue: Number(item.requiredIntimacyValue || 0),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyConfig(result: Record<string, any> | null | undefined) {
|
|
||||||
const value = result ?? {};
|
|
||||||
const dismissConsumeGolds = value.dismissConsumeGolds || {};
|
|
||||||
form.configured = Boolean(value.configured);
|
|
||||||
form.sysOrigin = String(
|
|
||||||
value.sysOrigin || form.sysOrigin || sysOriginOptions.value[0]?.value || '',
|
|
||||||
);
|
|
||||||
form.dismissConsumeGolds = {
|
|
||||||
BROTHER: Number(dismissConsumeGolds.BROTHER || 0),
|
|
||||||
CP: Number(dismissConsumeGolds.CP || 0),
|
|
||||||
SISTERS: Number(dismissConsumeGolds.SISTERS || 0),
|
|
||||||
};
|
|
||||||
form.levels = normalizeLevels(value.levels);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadConfig() {
|
|
||||||
if (!form.sysOrigin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await getResidentCpConfig(form.sysOrigin);
|
|
||||||
applyConfig(result);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateLevels() {
|
|
||||||
for (const item of relationTypeOptions) {
|
|
||||||
const gold = Number(form.dismissConsumeGolds[item.value] || 0);
|
|
||||||
if (gold < 0) {
|
|
||||||
message.error(`${item.label} 解绑金币不能小于 0`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let previous = -1;
|
|
||||||
for (const item of form.levels) {
|
|
||||||
const requiredValue = Number(item.requiredIntimacyValue || 0);
|
|
||||||
if (requiredValue < 0) {
|
|
||||||
message.error('所需亲密值不能小于 0');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (requiredValue < previous) {
|
|
||||||
message.error('所需亲密值需要按等级递增');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
previous = requiredValue;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDismissConsumeGolds() {
|
|
||||||
const result = defaultDismissConsumeGolds();
|
|
||||||
for (const item of relationTypeOptions) {
|
|
||||||
result[item.value] = Number(form.dismissConsumeGolds[item.value] || 0);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitForm() {
|
|
||||||
if (!validateLevels()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saving.value = true;
|
|
||||||
try {
|
|
||||||
await saveResidentCpConfig({
|
|
||||||
sysOrigin: form.sysOrigin,
|
|
||||||
dismissConsumeGolds: buildDismissConsumeGolds(),
|
|
||||||
levels: form.levels.map((item) => ({
|
|
||||||
cabinConfigId: item.cabinConfigId || null,
|
|
||||||
level: item.level,
|
|
||||||
levelName: String(item.levelName || `CP ${item.level}级`).trim(),
|
|
||||||
requiredIntimacyValue: Number(item.requiredIntimacyValue || 0),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
message.success('保存成功');
|
|
||||||
await loadConfig();
|
|
||||||
} finally {
|
|
||||||
saving.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSave() {
|
|
||||||
Modal.confirm({
|
|
||||||
async onOk() {
|
|
||||||
await submitForm();
|
|
||||||
},
|
|
||||||
title: '确认保存 CP 配置?',
|
|
||||||
content: '保存后 Flutter 端 CP 权益配置接口会读取新的等级亲密值和解绑金币。',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
sysOriginOptions,
|
|
||||||
(options) => {
|
|
||||||
if (!form.sysOrigin) {
|
|
||||||
form.sysOrigin = String(options[0]?.value || '');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => form.sysOrigin,
|
|
||||||
(value) => {
|
|
||||||
if (value) {
|
|
||||||
void loadConfig();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page auto-content-height>
|
|
||||||
<Spin :spinning="loading">
|
|
||||||
<Card :bordered="false" title="CP 配置">
|
|
||||||
<Space class="toolbar" wrap>
|
|
||||||
<Select
|
|
||||||
v-model:value="form.sysOrigin"
|
|
||||||
:options="sysOriginOptions"
|
|
||||||
option-filter-prop="label"
|
|
||||||
option-label-prop="label"
|
|
||||||
placeholder="请选择系统"
|
|
||||||
show-search
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
<Tag :color="form.configured ? 'success' : 'processing'">
|
|
||||||
{{ form.configured ? '已配置' : '未配置' }}
|
|
||||||
</Tag>
|
|
||||||
<Button :loading="saving" type="primary" @click="handleSave">
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
|
|
||||||
<div class="section-title">解绑金币配置</div>
|
|
||||||
<Space class="gold-fields" wrap>
|
|
||||||
<div
|
|
||||||
v-for="item in relationTypeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
class="gold-field"
|
|
||||||
>
|
|
||||||
<div class="field-label">{{ item.label }}</div>
|
|
||||||
<InputNumber
|
|
||||||
v-model:value="form.dismissConsumeGolds[item.value]"
|
|
||||||
:min="0"
|
|
||||||
:precision="0"
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
|
|
||||||
<Table
|
|
||||||
:columns="columns"
|
|
||||||
:data-source="form.levels"
|
|
||||||
:pagination="false"
|
|
||||||
row-key="level"
|
|
||||||
>
|
|
||||||
<template #bodyCell="{ column, record }">
|
|
||||||
<template v-if="column.key === 'level'">
|
|
||||||
第 {{ record.level }} 级
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'levelName'">
|
|
||||||
<Input
|
|
||||||
v-model:value="record.levelName"
|
|
||||||
:maxlength="30"
|
|
||||||
placeholder="请输入等级文案"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'requiredIntimacyValue'">
|
|
||||||
<InputNumber
|
|
||||||
v-model:value="record.requiredIntimacyValue"
|
|
||||||
:min="0"
|
|
||||||
:precision="0"
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
</Spin>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.toolbar {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
margin: 8px 0 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gold-fields {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
color: rgb(80 80 80);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,640 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
message,
|
|
||||||
Modal,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Spin,
|
|
||||||
Switch,
|
|
||||||
Table,
|
|
||||||
Tabs,
|
|
||||||
TabPane,
|
|
||||||
Tag,
|
|
||||||
} from 'antdv-next';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getResidentFirstRechargeRewardConfig,
|
|
||||||
pageResidentFirstRechargeRewardGrantRecords,
|
|
||||||
saveResidentFirstRechargeRewardConfig,
|
|
||||||
} from '#/api/legacy/resident-activity';
|
|
||||||
import ActivityResourceGroupSelectDrawer from '#/views/props/components/activity-resource-group-select-drawer.vue';
|
|
||||||
import RewardRow from '#/views/props/components/reward-row.vue';
|
|
||||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ResidentFirstRechargeRewardConfigPage' });
|
|
||||||
|
|
||||||
let localRowId = 0;
|
|
||||||
|
|
||||||
function createRowKey(seed?: number | string) {
|
|
||||||
localRowId += 1;
|
|
||||||
return `first-recharge-reward-${seed || localRowId}-${localRowId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLevel(level = 1, rechargeAmount = 10) {
|
|
||||||
return {
|
|
||||||
enabled: true,
|
|
||||||
id: null,
|
|
||||||
level,
|
|
||||||
rechargeAmount,
|
|
||||||
rechargeAmountCents: 0,
|
|
||||||
rewardGroupId: null,
|
|
||||||
rewardGroupName: '',
|
|
||||||
rewardItems: [] as Array<Record<string, any>>,
|
|
||||||
rowKey: createRowKey(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultLevels() {
|
|
||||||
return [createLevel(1, 10), createLevel(2, 50), createLevel(3, 100)];
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const sysOriginOptions = computed(() => {
|
|
||||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
|
||||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedSysOrigin = ref('');
|
|
||||||
const activeTab = ref('config');
|
|
||||||
const loading = ref(false);
|
|
||||||
const recordLoading = ref(false);
|
|
||||||
const saving = ref(false);
|
|
||||||
const groupPickerOpen = ref(false);
|
|
||||||
const pickerLevelIndex = ref(-1);
|
|
||||||
|
|
||||||
const form = reactive<Record<string, any>>({
|
|
||||||
configured: false,
|
|
||||||
enabled: false,
|
|
||||||
id: 0,
|
|
||||||
levelConfigs: defaultLevels(),
|
|
||||||
sysOrigin: '',
|
|
||||||
updateTime: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const levelColumns = [
|
|
||||||
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
|
||||||
{ dataIndex: 'level', key: 'level', title: '档位', width: 110 },
|
|
||||||
{ dataIndex: 'rechargeAmount', key: 'rechargeAmount', title: '首冲金额', width: 180 },
|
|
||||||
{ dataIndex: 'rewardGroup', key: 'rewardGroup', title: '奖励资源组', width: 420 },
|
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const enabledLevelCount = computed(() =>
|
|
||||||
(form.levelConfigs || []).filter((item: Record<string, any>) => item.enabled !== false).length,
|
|
||||||
);
|
|
||||||
|
|
||||||
const statusMeta = computed(() => {
|
|
||||||
if (!form.configured) {
|
|
||||||
return { color: 'processing', text: '未配置' };
|
|
||||||
}
|
|
||||||
if (form.enabled) {
|
|
||||||
return { color: 'success', text: '已启用' };
|
|
||||||
}
|
|
||||||
return { color: 'default', text: '已关闭' };
|
|
||||||
});
|
|
||||||
|
|
||||||
const recordQuery = reactive<Record<string, any>>({
|
|
||||||
cursor: 1,
|
|
||||||
limit: 20,
|
|
||||||
status: '',
|
|
||||||
userId: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const recordPage = reactive<Record<string, any>>({
|
|
||||||
current: 1,
|
|
||||||
records: [],
|
|
||||||
size: 20,
|
|
||||||
total: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const statusOptions = [
|
|
||||||
{ label: '全部状态', value: '' },
|
|
||||||
{ label: '发放中', value: 'PENDING' },
|
|
||||||
{ label: '已发放', value: 'SUCCESS' },
|
|
||||||
{ label: '发放失败', value: 'FAILED' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const recordColumns = [
|
|
||||||
{ dataIndex: 'user', key: 'user', title: '用户', width: 260 },
|
|
||||||
{ dataIndex: 'rechargeAmount', key: 'rechargeAmount', title: '充值金额', width: 120 },
|
|
||||||
{ dataIndex: 'rechargeThreshold', key: 'rechargeThreshold', title: '达标金额', width: 120 },
|
|
||||||
{ dataIndex: 'level', key: 'level', title: '档位', width: 90 },
|
|
||||||
{ dataIndex: 'payPlatform', key: 'payPlatform', title: '充值方式', width: 180 },
|
|
||||||
{ dataIndex: 'sourceOrderId', key: 'sourceOrderId', title: '订单', width: 180 },
|
|
||||||
{ dataIndex: 'rewardGroup', key: 'rewardGroup', title: '奖励资源组', width: 360 },
|
|
||||||
{ dataIndex: 'status', key: 'status', title: '发放状态', width: 120 },
|
|
||||||
{ dataIndex: 'sentAt', key: 'sentAt', title: '发放时间', width: 180 },
|
|
||||||
{ dataIndex: 'lastError', key: 'lastError', title: '失败原因', width: 220 },
|
|
||||||
];
|
|
||||||
|
|
||||||
function toAmountCents(value: number | string | undefined) {
|
|
||||||
const numeric = Number(value || 0);
|
|
||||||
if (!Number.isFinite(numeric)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.round(numeric * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatAmount(value: number | string | undefined) {
|
|
||||||
const numeric = Number(value || 0);
|
|
||||||
return Number.isFinite(numeric) ? numeric.toFixed(2) : '0.00';
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeConfig(result: null | Record<string, any> | undefined) {
|
|
||||||
const value = result || {};
|
|
||||||
form.configured = Boolean(value.configured);
|
|
||||||
form.enabled = Boolean(value.enabled);
|
|
||||||
form.id = value.id || 0;
|
|
||||||
form.sysOrigin = String(value.sysOrigin || selectedSysOrigin.value || '');
|
|
||||||
form.updateTime = String(value.updateTime || '');
|
|
||||||
const levels = Array.isArray(value.levelConfigs) && value.levelConfigs.length > 0
|
|
||||||
? value.levelConfigs
|
|
||||||
: defaultLevels();
|
|
||||||
form.levelConfigs = levels
|
|
||||||
.map((item: Record<string, any>, index: number) => {
|
|
||||||
const rechargeAmount = Number(
|
|
||||||
item.rechargeAmount ?? Number(item.rechargeAmountCents || 0) / 100,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
enabled: item.enabled !== false,
|
|
||||||
id: item.id ?? null,
|
|
||||||
level: Number(item.level || index + 1),
|
|
||||||
rechargeAmount: Number.isFinite(rechargeAmount) ? rechargeAmount : 0,
|
|
||||||
rechargeAmountCents: Number(item.rechargeAmountCents || 0),
|
|
||||||
rewardGroupId: item.rewardGroupId ?? null,
|
|
||||||
rewardGroupName: String(item.rewardGroupName || ''),
|
|
||||||
rewardItems: Array.isArray(item.rewardItems) ? item.rewardItems : [],
|
|
||||||
rowKey: createRowKey(item.id || index),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.toSorted((left: Record<string, any>, right: Record<string, any>) =>
|
|
||||||
toAmountCents(left.rechargeAmount) - toAmountCents(right.rechargeAmount),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadConfig() {
|
|
||||||
if (!selectedSysOrigin.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await getResidentFirstRechargeRewardConfig(selectedSysOrigin.value);
|
|
||||||
normalizeConfig(result);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeRecordPage(result: null | Record<string, any> | undefined) {
|
|
||||||
const value = result || {};
|
|
||||||
recordPage.records = Array.isArray(value.records) ? value.records : [];
|
|
||||||
recordPage.total = Number(value.total || 0);
|
|
||||||
recordPage.current = Number(value.current || 1);
|
|
||||||
recordPage.size = Number(value.size || 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadGrantRecords() {
|
|
||||||
if (!selectedSysOrigin.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
recordLoading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await pageResidentFirstRechargeRewardGrantRecords({
|
|
||||||
...recordQuery,
|
|
||||||
sysOrigin: selectedSysOrigin.value,
|
|
||||||
});
|
|
||||||
normalizeRecordPage(result);
|
|
||||||
} finally {
|
|
||||||
recordLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addLevel() {
|
|
||||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
|
||||||
const maxLevel = Math.max(0, ...levels.map((item) => Number(item.level || 0)));
|
|
||||||
const maxAmount = Math.max(0, ...levels.map((item) => Number(item.rechargeAmount || 0)));
|
|
||||||
levels.push(createLevel(maxLevel + 1, maxAmount + 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeLevel(index: number) {
|
|
||||||
(form.levelConfigs as Array<Record<string, any>>).splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmRemoveLevel(index: number) {
|
|
||||||
Modal.confirm({
|
|
||||||
onOk: () => removeLevel(index),
|
|
||||||
title: '确认删除该首冲奖励档位?',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openRewardPicker(index: number) {
|
|
||||||
pickerLevelIndex.value = index;
|
|
||||||
groupPickerOpen.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearRewardGroup(record: Record<string, any>) {
|
|
||||||
record.rewardGroupId = null;
|
|
||||||
record.rewardGroupName = '';
|
|
||||||
record.rewardItems = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRewardGroupSelect(row: Record<string, any>) {
|
|
||||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
|
||||||
const target = levels[pickerLevelIndex.value];
|
|
||||||
if (!target) {
|
|
||||||
groupPickerOpen.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target.rewardGroupId = row.id;
|
|
||||||
target.rewardGroupName = row.name;
|
|
||||||
target.rewardItems = Array.isArray(row.rewardConfigList) ? row.rewardConfigList : [];
|
|
||||||
groupPickerOpen.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateBeforeSave() {
|
|
||||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
|
||||||
if (levels.length === 0) {
|
|
||||||
message.warning('请至少配置一个首冲奖励档位');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const seenLevels = new Set<string>();
|
|
||||||
const seenAmounts = new Set<string>();
|
|
||||||
for (const item of levels) {
|
|
||||||
const level = Number(item.level || 0);
|
|
||||||
const amountCents = toAmountCents(item.rechargeAmount);
|
|
||||||
const rewardGroupId = Number(item.rewardGroupId || 0);
|
|
||||||
if (level <= 0) {
|
|
||||||
message.warning('档位必须大于 0');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (amountCents <= 0) {
|
|
||||||
message.warning('首冲金额必须大于 0');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rewardGroupId <= 0) {
|
|
||||||
message.warning('每个档位都需要配置奖励资源组');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (seenLevels.has(String(level))) {
|
|
||||||
message.warning('档位不能重复');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (seenAmounts.has(String(amountCents))) {
|
|
||||||
message.warning('首冲金额不能重复');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
seenLevels.add(String(level));
|
|
||||||
seenAmounts.add(String(amountCents));
|
|
||||||
}
|
|
||||||
if (form.enabled && enabledLevelCount.value === 0) {
|
|
||||||
message.warning('启用配置时至少要启用一个首冲奖励档位');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitForm() {
|
|
||||||
if (!validateBeforeSave()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saving.value = true;
|
|
||||||
try {
|
|
||||||
await saveResidentFirstRechargeRewardConfig({
|
|
||||||
enabled: Boolean(form.enabled),
|
|
||||||
id: form.id || 0,
|
|
||||||
levelConfigs: (form.levelConfigs || []).map((item: Record<string, any>) => ({
|
|
||||||
enabled: item.enabled !== false,
|
|
||||||
id: item.id || 0,
|
|
||||||
level: Number(item.level || 0),
|
|
||||||
rechargeAmount: formatAmount(item.rechargeAmount),
|
|
||||||
rechargeAmountCents: toAmountCents(item.rechargeAmount),
|
|
||||||
rewardGroupId: item.rewardGroupId ?? null,
|
|
||||||
rewardGroupName: String(item.rewardGroupName || ''),
|
|
||||||
})),
|
|
||||||
sysOrigin: selectedSysOrigin.value,
|
|
||||||
});
|
|
||||||
message.success('保存成功');
|
|
||||||
await loadConfig();
|
|
||||||
if (activeTab.value === 'records') {
|
|
||||||
await loadGrantRecords();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
saving.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchGrantRecords() {
|
|
||||||
recordQuery.cursor = 1;
|
|
||||||
void loadGrantRecords();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleGrantTableChange(pagination: Record<string, any>) {
|
|
||||||
recordQuery.cursor = Number(pagination.current || 1);
|
|
||||||
recordQuery.limit = Number(pagination.pageSize || 20);
|
|
||||||
void loadGrantRecords();
|
|
||||||
}
|
|
||||||
|
|
||||||
function avatarBackgroundStyle(url: string) {
|
|
||||||
return {
|
|
||||||
backgroundImage: `url("${String(url || '').replaceAll('"', '%22')}")`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function grantStatusMeta(status: string) {
|
|
||||||
if (status === 'SUCCESS') {
|
|
||||||
return { color: 'success', text: '已发放' };
|
|
||||||
}
|
|
||||||
if (status === 'FAILED') {
|
|
||||||
return { color: 'error', text: '发放失败' };
|
|
||||||
}
|
|
||||||
return { color: 'processing', text: '发放中' };
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
sysOriginOptions,
|
|
||||||
(options) => {
|
|
||||||
if (!selectedSysOrigin.value) {
|
|
||||||
selectedSysOrigin.value = String(options[0]?.value || '');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
selectedSysOrigin,
|
|
||||||
(value) => {
|
|
||||||
if (value) {
|
|
||||||
recordQuery.cursor = 1;
|
|
||||||
void loadConfig();
|
|
||||||
void loadGrantRecords();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(activeTab, (value) => {
|
|
||||||
if (value === 'records') {
|
|
||||||
void loadGrantRecords();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page auto-content-height>
|
|
||||||
<Spin :spinning="loading">
|
|
||||||
<Card :bordered="false" title="首冲奖励">
|
|
||||||
<div class="toolbar">
|
|
||||||
<Select
|
|
||||||
v-model:value="selectedSysOrigin"
|
|
||||||
:options="sysOriginOptions"
|
|
||||||
option-filter-prop="label"
|
|
||||||
option-label-prop="label"
|
|
||||||
placeholder="请选择系统"
|
|
||||||
show-search
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
<Tag :color="statusMeta.color">{{ statusMeta.text }}</Tag>
|
|
||||||
<Tag color="processing">仅 Google / 三方充值</Tag>
|
|
||||||
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabs v-model:activeKey="activeTab">
|
|
||||||
<TabPane key="config" tab="奖励配置">
|
|
||||||
<div class="config-row">
|
|
||||||
<Space align="center" wrap>
|
|
||||||
<span class="field-label">启用</span>
|
|
||||||
<Switch v-model:checked="form.enabled" />
|
|
||||||
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Table
|
|
||||||
:columns="levelColumns"
|
|
||||||
:data-source="form.levelConfigs"
|
|
||||||
:loading="loading"
|
|
||||||
:pagination="false"
|
|
||||||
row-key="rowKey"
|
|
||||||
:scroll="{ x: 900 }"
|
|
||||||
>
|
|
||||||
<template #bodyCell="{ column, index, record }">
|
|
||||||
<template v-if="column.key === 'enabled'">
|
|
||||||
<Switch v-model:checked="record.enabled" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'level'">
|
|
||||||
<InputNumber
|
|
||||||
v-model:value="record.level"
|
|
||||||
:min="1"
|
|
||||||
:precision="0"
|
|
||||||
style="width: 90px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'rechargeAmount'">
|
|
||||||
<InputNumber
|
|
||||||
v-model:value="record.rechargeAmount"
|
|
||||||
:min="0.01"
|
|
||||||
:precision="2"
|
|
||||||
style="width: 140px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'rewardGroup'">
|
|
||||||
<div class="reward-group-cell">
|
|
||||||
<Space wrap>
|
|
||||||
<Button size="small" @click="openRewardPicker(index)">
|
|
||||||
{{ record.rewardGroupId ? '更换奖励组' : '选择奖励组' }}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
v-if="record.rewardGroupId"
|
|
||||||
danger
|
|
||||||
size="small"
|
|
||||||
type="link"
|
|
||||||
@click="clearRewardGroup(record)"
|
|
||||||
>
|
|
||||||
清空
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
<div v-if="record.rewardGroupId" class="reward-group-name">
|
|
||||||
{{ record.rewardGroupName || '-' }} (ID: {{ record.rewardGroupId }})
|
|
||||||
</div>
|
|
||||||
<div v-else class="sub-text">未选择奖励组</div>
|
|
||||||
<RewardRow v-if="record.rewardItems?.length" :list="record.rewardItems" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'actions'">
|
|
||||||
<Button danger type="link" @click="confirmRemoveLevel(index)">删除</Button>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<Button @click="addLevel">新增档位</Button>
|
|
||||||
<Button :loading="saving" type="primary" @click="submitForm">保存配置</Button>
|
|
||||||
</div>
|
|
||||||
</TabPane>
|
|
||||||
|
|
||||||
<TabPane key="records" tab="发奖记录">
|
|
||||||
<div class="toolbar">
|
|
||||||
<Input
|
|
||||||
v-model:value="recordQuery.userId"
|
|
||||||
allow-clear
|
|
||||||
placeholder="用户长ID"
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
v-model:value="recordQuery.status"
|
|
||||||
:options="statusOptions"
|
|
||||||
style="width: 140px"
|
|
||||||
/>
|
|
||||||
<Button :loading="recordLoading" type="primary" @click="searchGrantRecords">
|
|
||||||
查询
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
:columns="recordColumns"
|
|
||||||
:data-source="recordPage.records"
|
|
||||||
:loading="recordLoading"
|
|
||||||
:pagination="{
|
|
||||||
current: recordPage.current,
|
|
||||||
pageSize: recordPage.size,
|
|
||||||
showSizeChanger: true,
|
|
||||||
total: recordPage.total,
|
|
||||||
}"
|
|
||||||
row-key="id"
|
|
||||||
:scroll="{ x: 1810 }"
|
|
||||||
@change="handleGrantTableChange"
|
|
||||||
>
|
|
||||||
<template #bodyCell="{ column, record }">
|
|
||||||
<template v-if="column.key === 'user'">
|
|
||||||
<div class="user-cell">
|
|
||||||
<div
|
|
||||||
v-if="record.userAvatar"
|
|
||||||
class="user-avatar"
|
|
||||||
:style="avatarBackgroundStyle(record.userAvatar)"
|
|
||||||
/>
|
|
||||||
<div class="user-main">
|
|
||||||
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
|
||||||
<div class="sub-text">
|
|
||||||
{{ record.account || record.userId || '-' }}
|
|
||||||
<span v-if="record.countryCode"> · {{ record.countryCode }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'payPlatform'">
|
|
||||||
<div>{{ record.payPlatform || '-' }}</div>
|
|
||||||
<div class="sub-text">{{ record.paymentMethod || '-' }}</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'rewardGroup'">
|
|
||||||
<div class="reward-group-name">
|
|
||||||
{{ record.rewardGroupName || '-' }} (ID: {{ record.rewardGroupId || '-' }})
|
|
||||||
</div>
|
|
||||||
<RewardRow v-if="record.rewardItems?.length" :list="record.rewardItems" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'status'">
|
|
||||||
<Tag :color="grantStatusMeta(record.status).color">
|
|
||||||
{{ grantStatusMeta(record.status).text }}
|
|
||||||
</Tag>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'sentAt'">
|
|
||||||
{{ record.sentAt || '-' }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'lastError'">
|
|
||||||
<span class="error-text">{{ record.lastError || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<ActivityResourceGroupSelectDrawer
|
|
||||||
:open="groupPickerOpen"
|
|
||||||
:sys-origin="selectedSysOrigin"
|
|
||||||
@close="groupPickerOpen = false"
|
|
||||||
@select="handleRewardGroupSelect"
|
|
||||||
/>
|
|
||||||
</Spin>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.toolbar {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
color: rgb(71 85 105);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-text {
|
|
||||||
color: rgb(100 116 139);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-group-cell {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-group-name {
|
|
||||||
color: rgb(15 23 42);
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-cell {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-avatar {
|
|
||||||
background-color: rgb(226 232 240);
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-main {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-name {
|
|
||||||
color: rgb(15 23 42);
|
|
||||||
font-size: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-text {
|
|
||||||
color: rgb(185 28 28);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -78,7 +78,7 @@ const arpuDashboards = ref<{
|
|||||||
});
|
});
|
||||||
const initialDate = dayjs().tz(DEFAULT_STAT_TIMEZONE);
|
const initialDate = dayjs().tz(DEFAULT_STAT_TIMEZONE);
|
||||||
const dateRange = ref<[string, string] | null>([
|
const dateRange = ref<[string, string] | null>([
|
||||||
initialDate.format('YYYY-MM-DD'),
|
initialDate.subtract(6, 'day').format('YYYY-MM-DD'),
|
||||||
initialDate.format('YYYY-MM-DD'),
|
initialDate.format('YYYY-MM-DD'),
|
||||||
]);
|
]);
|
||||||
const selectedCountryCodes = ref<string[]>([]);
|
const selectedCountryCodes = ref<string[]>([]);
|
||||||
@ -117,6 +117,8 @@ const gameQuery = reactive<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
|
periodType: 'DAY',
|
||||||
|
statTimezone: DEFAULT_STAT_TIMEZONE,
|
||||||
sysOrigin: '',
|
sysOrigin: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,6 +148,19 @@ const permissionsSysOriginCode = computed(() =>
|
|||||||
sysOriginOptions.value.map((item) => String(item.value || '')),
|
sysOriginOptions.value.map((item) => String(item.value || '')),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const periodTypeOptions = [
|
||||||
|
{ label: '日', value: 'DAY' },
|
||||||
|
{ label: '周', value: 'WEEK' },
|
||||||
|
{ label: '月', value: 'MONTH' },
|
||||||
|
{ label: '全部', value: 'ALL' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const statTimezoneOptions = [
|
||||||
|
{ label: '利雅得(服务器)', value: 'Asia/Riyadh' },
|
||||||
|
{ label: 'UTC', value: 'UTC' },
|
||||||
|
{ label: '北京', value: 'Asia/Shanghai' },
|
||||||
|
];
|
||||||
|
|
||||||
const gameProviderOptions = [
|
const gameProviderOptions = [
|
||||||
{ label: '全部游戏厂商', value: '' },
|
{ label: '全部游戏厂商', value: '' },
|
||||||
{ label: '站内游戏', value: 'INTERNAL' },
|
{ label: '站内游戏', value: 'INTERNAL' },
|
||||||
@ -463,7 +478,9 @@ function hasPermission(code: string) {
|
|||||||
|
|
||||||
const canQuery = computed(() => hasPermission('datav:query'));
|
const canQuery = computed(() => hasPermission('datav:query'));
|
||||||
const canPaidPreview = computed(() => hasPermission('datav:paid:preview'));
|
const canPaidPreview = computed(() => hasPermission('datav:paid:preview'));
|
||||||
const currentTimezoneLabel = '利雅得(服务器)';
|
const currentTimezoneLabel = computed(() => {
|
||||||
|
return statTimezoneOptions.find((item) => item.value === query.statTimezone)?.label || query.statTimezone;
|
||||||
|
});
|
||||||
|
|
||||||
function syncFullscreenState() {
|
function syncFullscreenState() {
|
||||||
fullscreen.value = document.fullscreenElement === datavFullscreen.value;
|
fullscreen.value = document.fullscreenElement === datavFullscreen.value;
|
||||||
@ -489,10 +506,35 @@ async function toggleVisualMode() {
|
|||||||
await renderVisualCharts();
|
await renderVisualCharts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultRange(periodType: string): [string, string] | null {
|
||||||
|
const now = dayjs().tz(query.statTimezone || DEFAULT_STAT_TIMEZONE);
|
||||||
|
if (periodType === 'ALL') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (periodType === 'MONTH') {
|
||||||
|
return [now.startOf('month').format('YYYY-MM-DD'), now.format('YYYY-MM-DD')];
|
||||||
|
}
|
||||||
|
if (periodType === 'WEEK') {
|
||||||
|
const daysSinceMonday = now.day() === 0 ? 6 : now.day() - 1;
|
||||||
|
return [now.subtract(daysSinceMonday, 'day').format('YYYY-MM-DD'), now.format('YYYY-MM-DD')];
|
||||||
|
}
|
||||||
|
return [now.subtract(6, 'day').format('YYYY-MM-DD'), now.format('YYYY-MM-DD')];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePeriodChange(value: string) {
|
||||||
|
dateRange.value = defaultRange(value);
|
||||||
|
loadDashboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTimezoneChange() {
|
||||||
|
dateRange.value = defaultRange(query.periodType);
|
||||||
|
loadDashboard();
|
||||||
|
}
|
||||||
|
|
||||||
function buildParams() {
|
function buildParams() {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
periodType: 'DAY',
|
periodType: query.periodType,
|
||||||
statTimezone: DEFAULT_STAT_TIMEZONE,
|
statTimezone: query.statTimezone || DEFAULT_STAT_TIMEZONE,
|
||||||
sysOrigin: query.sysOrigin || undefined,
|
sysOrigin: query.sysOrigin || undefined,
|
||||||
};
|
};
|
||||||
if (dateRange.value?.[0] && dateRange.value?.[1]) {
|
if (dateRange.value?.[0] && dateRange.value?.[1]) {
|
||||||
@ -503,13 +545,13 @@ function buildParams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildRecentDayParams(dayCount: number) {
|
function buildRecentDayParams(dayCount: number) {
|
||||||
const end = dayjs().tz(DEFAULT_STAT_TIMEZONE);
|
const end = dayjs().tz(query.statTimezone || DEFAULT_STAT_TIMEZONE);
|
||||||
const start = end.subtract(dayCount - 1, 'day');
|
const start = end.subtract(dayCount - 1, 'day');
|
||||||
return {
|
return {
|
||||||
periodType: 'DAY',
|
periodType: 'DAY',
|
||||||
startDate: start.format('YYYY-MM-DD'),
|
startDate: start.format('YYYY-MM-DD'),
|
||||||
endDate: end.format('YYYY-MM-DD'),
|
endDate: end.format('YYYY-MM-DD'),
|
||||||
statTimezone: DEFAULT_STAT_TIMEZONE,
|
statTimezone: query.statTimezone || DEFAULT_STAT_TIMEZONE,
|
||||||
sysOrigin: query.sysOrigin || undefined,
|
sysOrigin: query.sysOrigin || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1179,6 +1221,18 @@ onBeforeUnmount(() => {
|
|||||||
style="width: 150px"
|
style="width: 150px"
|
||||||
@change="loadDashboard"
|
@change="loadDashboard"
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="query.statTimezone"
|
||||||
|
:options="statTimezoneOptions"
|
||||||
|
style="width: 170px"
|
||||||
|
@change="handleTimezoneChange"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="query.periodType"
|
||||||
|
:options="periodTypeOptions"
|
||||||
|
style="width: 110px"
|
||||||
|
@change="handlePeriodChange"
|
||||||
|
/>
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
v-model:value="dateRange"
|
v-model:value="dateRange"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
SET NAMES utf8mb4;
|
|
||||||
|
|
||||||
INSERT INTO sys_menu (
|
|
||||||
id,
|
|
||||||
parent_id,
|
|
||||||
menu_name,
|
|
||||||
path,
|
|
||||||
menu_type,
|
|
||||||
icon,
|
|
||||||
create_user,
|
|
||||||
update_user,
|
|
||||||
sort,
|
|
||||||
status,
|
|
||||||
router,
|
|
||||||
alias
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
1700000264,
|
|
||||||
(SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1),
|
|
||||||
'首冲奖励',
|
|
||||||
'resident-activity/first-recharge-reward-config/index',
|
|
||||||
2,
|
|
||||||
'form',
|
|
||||||
'0',
|
|
||||||
'0',
|
|
||||||
8,
|
|
||||||
0,
|
|
||||||
'first-recharge-reward/config',
|
|
||||||
'ResidentFirstRechargeRewardConfig'
|
|
||||||
WHERE NOT EXISTS (
|
|
||||||
SELECT 1 FROM sys_menu WHERE alias = 'ResidentFirstRechargeRewardConfig'
|
|
||||||
);
|
|
||||||
|
|
||||||
UPDATE sys_menu
|
|
||||||
SET
|
|
||||||
parent_id = (
|
|
||||||
SELECT resident_menu.id
|
|
||||||
FROM (
|
|
||||||
SELECT id
|
|
||||||
FROM sys_menu
|
|
||||||
WHERE alias = 'ResidentActivityManager'
|
|
||||||
LIMIT 1
|
|
||||||
) AS resident_menu
|
|
||||||
),
|
|
||||||
menu_name = '首冲奖励',
|
|
||||||
path = 'resident-activity/first-recharge-reward-config/index',
|
|
||||||
menu_type = 2,
|
|
||||||
icon = 'form',
|
|
||||||
create_user = '0',
|
|
||||||
update_user = '0',
|
|
||||||
sort = 8,
|
|
||||||
status = 0,
|
|
||||||
router = 'first-recharge-reward/config'
|
|
||||||
WHERE alias = 'ResidentFirstRechargeRewardConfig';
|
|
||||||
|
|
||||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
|
||||||
SELECT DISTINCT
|
|
||||||
parent_role.role_id,
|
|
||||||
target_menu.id
|
|
||||||
FROM sys_role_menu AS parent_role
|
|
||||||
JOIN sys_menu AS parent_menu
|
|
||||||
ON parent_menu.id = parent_role.menu_id
|
|
||||||
AND parent_menu.alias = 'ResidentActivityManager'
|
|
||||||
JOIN sys_menu AS target_menu
|
|
||||||
ON target_menu.alias = 'ResidentFirstRechargeRewardConfig'
|
|
||||||
LEFT JOIN sys_role_menu AS existing_role_menu
|
|
||||||
ON existing_role_menu.role_id = parent_role.role_id
|
|
||||||
AND existing_role_menu.menu_id = target_menu.id
|
|
||||||
WHERE existing_role_menu.id IS NULL;
|
|
||||||
@ -416,59 +416,6 @@ SET
|
|||||||
router = 'recharge-reward/config'
|
router = 'recharge-reward/config'
|
||||||
WHERE alias = 'ResidentRechargeRewardConfig';
|
WHERE alias = 'ResidentRechargeRewardConfig';
|
||||||
|
|
||||||
INSERT INTO sys_menu (
|
|
||||||
id,
|
|
||||||
parent_id,
|
|
||||||
menu_name,
|
|
||||||
path,
|
|
||||||
menu_type,
|
|
||||||
icon,
|
|
||||||
create_user,
|
|
||||||
update_user,
|
|
||||||
sort,
|
|
||||||
status,
|
|
||||||
router,
|
|
||||||
alias
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
1700000264,
|
|
||||||
(SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1),
|
|
||||||
'首冲奖励',
|
|
||||||
'resident-activity/first-recharge-reward-config/index',
|
|
||||||
2,
|
|
||||||
'form',
|
|
||||||
'0',
|
|
||||||
'0',
|
|
||||||
8,
|
|
||||||
0,
|
|
||||||
'first-recharge-reward/config',
|
|
||||||
'ResidentFirstRechargeRewardConfig'
|
|
||||||
WHERE NOT EXISTS (
|
|
||||||
SELECT 1 FROM sys_menu WHERE alias = 'ResidentFirstRechargeRewardConfig'
|
|
||||||
);
|
|
||||||
|
|
||||||
UPDATE sys_menu
|
|
||||||
SET
|
|
||||||
parent_id = (
|
|
||||||
SELECT resident_menu.id
|
|
||||||
FROM (
|
|
||||||
SELECT id
|
|
||||||
FROM sys_menu
|
|
||||||
WHERE alias = 'ResidentActivityManager'
|
|
||||||
LIMIT 1
|
|
||||||
) AS resident_menu
|
|
||||||
),
|
|
||||||
menu_name = '首冲奖励',
|
|
||||||
path = 'resident-activity/first-recharge-reward-config/index',
|
|
||||||
menu_type = 2,
|
|
||||||
icon = 'form',
|
|
||||||
create_user = '0',
|
|
||||||
update_user = '0',
|
|
||||||
sort = 8,
|
|
||||||
status = 0,
|
|
||||||
router = 'first-recharge-reward/config'
|
|
||||||
WHERE alias = 'ResidentFirstRechargeRewardConfig';
|
|
||||||
|
|
||||||
INSERT INTO sys_menu (
|
INSERT INTO sys_menu (
|
||||||
id,
|
id,
|
||||||
parent_id,
|
parent_id,
|
||||||
@ -492,7 +439,7 @@ SELECT
|
|||||||
'form',
|
'form',
|
||||||
'0',
|
'0',
|
||||||
'0',
|
'0',
|
||||||
9,
|
8,
|
||||||
0,
|
0,
|
||||||
'daily-task/config',
|
'daily-task/config',
|
||||||
'ResidentDailyTaskConfig'
|
'ResidentDailyTaskConfig'
|
||||||
@ -517,7 +464,7 @@ SET
|
|||||||
icon = 'form',
|
icon = 'form',
|
||||||
create_user = '0',
|
create_user = '0',
|
||||||
update_user = '0',
|
update_user = '0',
|
||||||
sort = 9,
|
sort = 8,
|
||||||
status = 0,
|
status = 0,
|
||||||
router = 'daily-task/config'
|
router = 'daily-task/config'
|
||||||
WHERE alias = 'ResidentDailyTaskConfig';
|
WHERE alias = 'ResidentDailyTaskConfig';
|
||||||
@ -665,7 +612,6 @@ JOIN sys_menu AS target_menu
|
|||||||
'ResidentSpecifiedGiftWeeklyRank',
|
'ResidentSpecifiedGiftWeeklyRank',
|
||||||
'ResidentVipConfig',
|
'ResidentVipConfig',
|
||||||
'ResidentRechargeRewardConfig',
|
'ResidentRechargeRewardConfig',
|
||||||
'ResidentFirstRechargeRewardConfig',
|
|
||||||
'ResidentDailyTaskConfig',
|
'ResidentDailyTaskConfig',
|
||||||
'OperateGameManager',
|
'OperateGameManager',
|
||||||
'GameConfig',
|
'GameConfig',
|
||||||
@ -692,93 +638,6 @@ LEFT JOIN sys_role_menu AS existing_role_menu
|
|||||||
AND existing_role_menu.menu_id = target_menu.id
|
AND existing_role_menu.menu_id = target_menu.id
|
||||||
WHERE existing_role_menu.id IS NULL;
|
WHERE existing_role_menu.id IS NULL;
|
||||||
|
|
||||||
INSERT INTO sys_menu (
|
|
||||||
id,
|
|
||||||
parent_id,
|
|
||||||
menu_name,
|
|
||||||
path,
|
|
||||||
menu_type,
|
|
||||||
icon,
|
|
||||||
create_user,
|
|
||||||
update_user,
|
|
||||||
sort,
|
|
||||||
status,
|
|
||||||
router,
|
|
||||||
alias
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
next_id,
|
|
||||||
(SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1),
|
|
||||||
'CP',
|
|
||||||
'resident-activity/cp-config/index',
|
|
||||||
2,
|
|
||||||
'form',
|
|
||||||
'0',
|
|
||||||
'0',
|
|
||||||
6,
|
|
||||||
0,
|
|
||||||
'cp/config',
|
|
||||||
'ResidentCpConfig'
|
|
||||||
FROM (
|
|
||||||
SELECT COALESCE(MAX(id), 1700000000) + 1 AS next_id
|
|
||||||
FROM sys_menu
|
|
||||||
) AS menu_id
|
|
||||||
WHERE NOT EXISTS (
|
|
||||||
SELECT 1 FROM sys_menu WHERE alias = 'ResidentCpConfig'
|
|
||||||
);
|
|
||||||
|
|
||||||
UPDATE sys_menu
|
|
||||||
SET
|
|
||||||
parent_id = (
|
|
||||||
SELECT resident_menu.id
|
|
||||||
FROM (
|
|
||||||
SELECT id
|
|
||||||
FROM sys_menu
|
|
||||||
WHERE alias = 'ResidentActivityManager'
|
|
||||||
LIMIT 1
|
|
||||||
) AS resident_menu
|
|
||||||
),
|
|
||||||
menu_name = 'CP',
|
|
||||||
path = 'resident-activity/cp-config/index',
|
|
||||||
menu_type = 2,
|
|
||||||
icon = 'form',
|
|
||||||
create_user = '0',
|
|
||||||
update_user = '0',
|
|
||||||
sort = 6,
|
|
||||||
status = 0,
|
|
||||||
router = 'cp/config'
|
|
||||||
WHERE alias = 'ResidentCpConfig';
|
|
||||||
|
|
||||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
|
||||||
SELECT DISTINCT
|
|
||||||
parent_role.role_id,
|
|
||||||
target_menu.id
|
|
||||||
FROM sys_role_menu AS parent_role
|
|
||||||
JOIN sys_menu AS parent_menu
|
|
||||||
ON parent_menu.id = parent_role.menu_id
|
|
||||||
AND parent_menu.alias = 'ResidentActivityManager'
|
|
||||||
JOIN sys_menu AS target_menu
|
|
||||||
ON target_menu.alias = 'ResidentCpConfig'
|
|
||||||
LEFT JOIN sys_role_menu AS existing_role_menu
|
|
||||||
ON existing_role_menu.role_id = parent_role.role_id
|
|
||||||
AND existing_role_menu.menu_id = target_menu.id
|
|
||||||
WHERE existing_role_menu.id IS NULL;
|
|
||||||
|
|
||||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
|
||||||
SELECT DISTINCT
|
|
||||||
parent_role.role_id,
|
|
||||||
target_menu.id
|
|
||||||
FROM sys_role_menu AS parent_role
|
|
||||||
JOIN sys_menu AS parent_menu
|
|
||||||
ON parent_menu.id = parent_role.menu_id
|
|
||||||
AND parent_menu.alias = 'ResidentActivityManager'
|
|
||||||
JOIN sys_menu AS target_menu
|
|
||||||
ON target_menu.alias = 'ResidentFirstRechargeRewardConfig'
|
|
||||||
LEFT JOIN sys_role_menu AS existing_role_menu
|
|
||||||
ON existing_role_menu.role_id = parent_role.role_id
|
|
||||||
AND existing_role_menu.menu_id = target_menu.id
|
|
||||||
WHERE existing_role_menu.id IS NULL;
|
|
||||||
|
|
||||||
UPDATE sys_menu
|
UPDATE sys_menu
|
||||||
SET menu_name = CONVERT(0xE5B8B8E9A9BBE6B4BBE58AA8 USING utf8mb4)
|
SET menu_name = CONVERT(0xE5B8B8E9A9BBE6B4BBE58AA8 USING utf8mb4)
|
||||||
WHERE alias = 'ResidentActivityManager';
|
WHERE alias = 'ResidentActivityManager';
|
||||||
@ -811,10 +670,6 @@ UPDATE sys_menu
|
|||||||
SET menu_name = CONVERT(0xE58585E580BCE5A596E58AB1E9858DE7BDAE USING utf8mb4)
|
SET menu_name = CONVERT(0xE58585E580BCE5A596E58AB1E9858DE7BDAE USING utf8mb4)
|
||||||
WHERE alias = 'ResidentRechargeRewardConfig';
|
WHERE alias = 'ResidentRechargeRewardConfig';
|
||||||
|
|
||||||
UPDATE sys_menu
|
|
||||||
SET menu_name = CONVERT(0xE9A696E586B2E5A596E58AB1 USING utf8mb4)
|
|
||||||
WHERE alias = 'ResidentFirstRechargeRewardConfig';
|
|
||||||
|
|
||||||
UPDATE sys_menu
|
UPDATE sys_menu
|
||||||
SET menu_name = CONVERT(0xE6AF8FE697A5E4BBBBE58AA1 USING utf8mb4)
|
SET menu_name = CONVERT(0xE6AF8FE697A5E4BBBBE58AA1 USING utf8mb4)
|
||||||
WHERE alias = 'ResidentDailyTaskConfig';
|
WHERE alias = 'ResidentDailyTaskConfig';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user