活动优化
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled

This commit is contained in:
hahwu 2026-03-23 17:12:06 +08:00
parent e0d7b5c509
commit b1383a7091
29 changed files with 10043 additions and 75 deletions

View File

@ -0,0 +1,41 @@
import { requestClient } from '#/api/request';
export interface ActivityData {
id: number;
type: number;
title?: string;
mail_title?: string;
mail_content?: string;
start_time: number;
end_time: number;
level?: number;
cfg?: string;
extra?: string;
}
export interface ActivityListParam {
AppId : number;
ServerId: number;
PageSize: number;
CurrentPage: number;
}
export interface EditActivityParam {
AppId : number;
Cfg : ActivityData;
}
export async function getActivityListApi(p:ActivityListParam) {
return requestClient.post<ActivityData[]>('/activity/list', p);
}
export async function editActivityApi(p: EditActivityParam) {
return requestClient.post('/activity/edit', p);
}
export async function addActivityApi(p: EditActivityParam) {
return requestClient.post('/activity/add', p);
}

View File

@ -58,6 +58,7 @@ export interface UserLogInfo {
ActLog?:actlog[];
MaxCharge?: number;
FriendList?: friendRecord[];
AdWatch?: number;
}
export interface actlog {

View File

@ -4,5 +4,5 @@ import type { copyUserParam } from '#/model/type';
export async function copyUser(data:copyUserParam) {
return requestClient.post('/operation/copyUser', data);
return requestClient.post('/operation/copyUser', data, {timeout: 120000});
}

View File

@ -16,6 +16,8 @@ export interface UserListParam {
StartTime?: number;
EndTime?: number;
Nickname?: string;
Username?: string;
DeviceId?: string;
}

View File

@ -56,7 +56,8 @@
"mail": "邮件管理",
"order": "订单管理",
"language": "翻译管理",
"copyUser": "用户数据复制"
"copyUser": "用户数据复制",
"activity": "活动管理"
},
"server":{
"merge_pet_test":"测试服",

View File

@ -43,7 +43,7 @@ const routes: RouteRecordRaw[] = [
affixTab: false,
icon: 'lucide:app-window',
title: $t('page.dashboard.app-list'),
authority: ['super', 'admin'],
authority: ['super'],
},
},
{
@ -54,7 +54,7 @@ const routes: RouteRecordRaw[] = [
affixTab: false,
icon: 'lucide:server',
title: $t('page.dashboard.node-list'),
authority: ['super', 'admin'],
authority: ['super'],
},
},
{
@ -65,7 +65,7 @@ const routes: RouteRecordRaw[] = [
affixTab: false,
icon: 'lucide:database',
title: $t('page.dashboard.mysql-list'),
authority: ['super', 'admin'],
authority: ['super'],
},
},
],

View File

@ -55,6 +55,16 @@ const routes: RouteRecordRaw[] = [
icon: 'lets-icons:order',
title: $t('page.operation.order'),
},
},
{
name: 'Activity',
path: '/activity',
component: () => import('#/views/operation/activity/index.vue'),
meta: {
affixTab: true,
icon: 'lets-icons:order',
title: $t('page.operation.activity'),
},
}
],
},

View File

@ -33,6 +33,7 @@ const routes: RouteRecordRaw[] = [
affixTab: true,
icon: 'solar:stars-bold',
title: $t('page.userlog.assetlog'),
authority: ['super'],
},
},
{
@ -43,6 +44,7 @@ const routes: RouteRecordRaw[] = [
affixTab: true,
icon: 'lucide:apple',
title: $t('page.userlog.eventlog'),
authority: ['super'],
},
},
{
@ -53,9 +55,9 @@ const routes: RouteRecordRaw[] = [
affixTab: true,
icon: 'solar:chat-round-money-bold',
title: $t('page.userlog.orderlog'),
authority: ['super'],
},
},
],
},
];

File diff suppressed because it is too large Load Diff

View File

@ -27,3 +27,13 @@ export const triggerTypeData: Record<string, string> = {
'Order': '订单触发器',
'Lv': '等级触发器',
};
export const activityTypeData: Record<number, string> = {
1: '挖矿活动',
2: '猜颜色',
3: '赛跑活动',
4: '限时特惠礼包',
5: '买一赠一礼包',
6: '超值加购礼包',
7: '好友合作活动',
};

View File

@ -80,9 +80,26 @@ export const getItemUrl = (itemId: number): string => {
switch (itemId) {
case 100001:
return './Assets/Art_SubModule/Art_Resource/Art_UISprites/Shop/Big/shop_energy_LV1.png';
case 100002:
return './Assets/GameMain/UI/UISprites/MergeObj/Packed/Other/Production_star_LV1.png';
case 100003:
return './Assets/Art_SubModule/Art_Resource/Art_UISprites/Shop/Big/shop_diamond_LV2.png';
default:
return '';
}
};
export const formatItems = (items: string) => {
try {
const itemList = JSON.parse(items);
const r = itemList.map((item: { Id: number; Num: number }) => ({
...item,
url: getItemUrl(item.Id),
}));
return r;
} catch (e) {
//console.error('Failed to parse items:', e);
return [];
}
}

View File

@ -5,6 +5,8 @@ import { restartServer, getServerListApi, reloadServer } from '#/api/core/server
import AddServerModal from './addServer.vue'
import EditServer from './editServer.vue';
import { useVbenModal } from '@vben/common-ui'
import { useAccess } from '@vben/access';
const { hasAccessByRoles } = useAccess();
defineOptions({
name: 'AppList',
@ -20,6 +22,9 @@ const [editServerM, editServerApi] = useVbenModal({
connectedComponent: EditServer,
});
async function editServer(Server: ServerData) {
if (!hasAccessByRoles(['super'])) {
return;
}
editServerApi.setData(Server);
editServerApi.open();
}

View File

@ -8,6 +8,7 @@ import type { AppData, ServerData } from '#/api/core/server';
import { useVbenModal } from '@vben/common-ui'
import { ref, onMounted } from 'vue';
import addServerModal from './addServer.vue';
import { AccessControl } from '@vben/access';
import dayjs from 'dayjs';
import { $t } from '#/locales'
const appId = ref<number>(1);
@ -124,10 +125,10 @@ onMounted(async () => {
]);
const serverResponse = await getServerListApi({ AppId: app.AppId });
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
setInterval(async () => {
const serverResponse = await getServerListApi({ AppId: appId.value });
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
}, 60000);
// setInterval(async () => {
// const serverResponse = await getServerListApi({ AppId: appId.value });
// ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
// }, 60000);
} catch (e) {
appList.value = serverList;
//console.log(e);
@ -227,10 +228,14 @@ async function updateReview() {
<Card class="mb-5" title="服务器操作">
<BaseForm />
<Space>
<AccessControl :codes="['super']" type="role">
<Button type="primary" @click="addServer">新增</Button>
<Button type="primary" @click="confirmUpdate" :loading="reload"> 更新正式环境 </Button>
</AccessControl>
<Button type="primary" @click="confirmUpdate" :loading="reload"> 更新游戏文件 </Button>
<AccessControl :codes="['super']" type="role">
<Button type="primary" @click="confirmUpdateReview" :loading="reloadReview"> 更新review环境 </Button>
<Button type="primary"> 更新配置 </Button>
</AccessControl>
<!-- <Button type="primary"> 更新配置 </Button> -->
</Space>
</Card>
<div class="p-5">

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { message } from "ant-design-vue";
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import type { languageType } from '#/model/type';
import type { VxeGridProps } from '#/adapter/vxe-table';
@ -21,6 +22,7 @@ const gridOptions: VxeGridProps<languageType> = {
title: 'zh_CN',
},
{ editRender: { name: 'input' }, field: 'pt_BR', title: 'pt_BR' },
{ editRender: { name: 'input' }, field: 'es_LATAM', title: 'es_LATAM' },
],
data: ld,
pagerConfig: {
@ -44,7 +46,17 @@ const [Form] = useVbenForm({
handleSubmit: async (formValues) => {
//
try {
await addLanguageList(ld);
const response = await addLanguageList(ld);
if (response) {
//
message.success('Languages added successfully');
console.log('Languages added successfully:', response);
modalApi.close();
} else {
//
message.error('Failed to add languages');
console.error('Failed to add languages:', response);
}
} catch (error) {
console.error('Error adding languages:', error);
}
@ -57,7 +69,7 @@ const [Form] = useVbenForm({
label: $t('page.language.newLineContent'),
componentProps: {
disabled: false,
placeholder: 'key | en_US | zh_CN| pt_BR record per line',
placeholder: 'key | en_US | zh_CN| pt_BR| es_LATAM record per line',
type: 'textarea',
rows: 8,
onChange: (e: Event) => {
@ -73,6 +85,7 @@ const [Form] = useVbenForm({
en_US: parts[1] || '',
zh_CN: parts[2] || '',
pt_BR: parts[3] || '',
es_LATAM: parts[4] || '',
};
});
ld = ld.concat(newData);

View File

@ -265,14 +265,14 @@ const gridOptions: VxeGridProps<languageType> = {
title: 'Image',
width: 60,
},
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US', filters: [{ data: "" }], filterRender: { name: "input" }, visible: en_USVisible },
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US', filters: [{ data: "" }], filterRender: { name: "input" }, visible: en_USVisible, className:'en-us-column' },
{
editRender: { name: 'input' },
field: 'zh_CN',
title: 'zh_CN', filters: [{ data: "" }], filterRender: { name: "input" }, visible: chineseVisible
title: 'zh_CN', filters: [{ data: "" }], filterRender: { name: "input" }, visible: chineseVisible, className: 'zh-cn-column'
},
{ editRender: { name: 'input' }, field: 'pt_BR', title: 'pt_BR', filters: [{ data: "" }], filterRender: { name: "input" }, visible: pt_BRVisible },
{ editRender: { name: 'input' }, field: 'es_LATAM', title: 'es_LATAM', filters: [{ data: "" }], filterRender: { name: "input" }, visible: es_LATAMVisible },
{ editRender: { name: 'input' }, field: 'pt_BR', title: 'pt_BR', filters: [{ data: "" }], filterRender: { name: "input" }, visible: pt_BRVisible, className: 'en-us-column' },
{ editRender: { name: 'input' }, field: 'es_LATAM', title: 'es_LATAM', filters: [{ data: "" }], filterRender: { name: "input" }, visible: es_LATAMVisible, className: 'en-us-column' },
{ slots: { default: 'glossary' }, title: 'Glossary', width: 150 },
{ slots: { default: 'action' }, title: $t('page.common.action'), width: 150, visible: actionVisible },
],
@ -452,7 +452,12 @@ function getGlossary(row: languageType) {
.row-green {
background-color: #0ea800;
}
.zh-cn-column {
font-family: "猫啃什锦黑", sans-serif;
}
.en-us-column {
font-family: "Poetsen One","Arial", "Helvetica", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", sans-serif;
}
.row-yellow {
background-color: #c9bc04;
}

View File

@ -0,0 +1,176 @@
<script lang="ts" setup>
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { Image } from "ant-design-vue";
import { Tag } from 'ant-design-vue';
import { ref } from "vue";
import { addActivityApi } from '#/api/core/activity';
import type { EditActivityParam } from '#/api/core/activity';
import { getUnixTime } from '#/store/util';
interface MailItem {
Id: string | number;
Num: number;
url?: string;
}
let items = ref<MailItem[]>([]);
defineOptions({
name: 'DetailMailModal',
});
const [Form, FormApi] = useVbenForm({
//
commonConfig: {
//
componentProps: {
class: 'w-full h-full',
},
},
// 使 tailwindcss grid
//
// labelinputvertical
layout: 'horizontal',
showDefaultActions: false,
// labelinput
schema: [
{
component: 'Select',
fieldName: 'type',
label: '活动类型',
componentProps: {
options: [
{
label: '挖矿活动',
value: 1,
},
{
label: '猜颜色',
value: 2,
},
{
label: '赛跑活动',
value: 3,
},
{
label: '限时特惠礼包',
value: 4,
},
{
label: '买一赠一礼包',
value: 5,
},
{
label: '超值加购礼包',
value: 6,
},
{
label: '好友合作活动',
value: 7,
},
],
},
},
{
component: 'Input',
fieldName: 'level',
label: '解锁等级',
componentProps: {
},
},
{
component: 'DatePicker',
fieldName: 'start_time',
label: '开始时间',
componentProps: {
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
component: 'DatePicker',
fieldName: 'end_time',
label: '结束时间',
componentProps: {
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
component: 'Input',
fieldName: 'title',
label: '活动标题',
rules: 'required',
},
{
component: 'Input',
fieldName: 'mail_title',
label: '回收邮件标题',
rules: 'required',
},
{
component: 'Input',
fieldName: 'mail_content',
label: '回收邮件内容',
},
{
component: 'Textarea',
fieldName: 'cfg',
label: '配置',
defaultValue: '{}',
componentProps: {
type: 'textarea',
rows: 8,
},
},
{
component: 'Textarea',
defaultValue: '{}',
fieldName: 'extra',
label: '额外配置',
},
],
});
const [Modal, modalApi] = useVbenModal({
confirmText: '提交',
onConfirm: async () => {
const values = await FormApi.getValues();
const mMdata = modalApi.getData();
const cfgjson = JSON.parse(values.cfg);
const params: EditActivityParam = {
AppId: mMdata.AppId,
Cfg: {
id: 0,
type: values.type,
start_time: values.start_time ? getUnixTime(values.start_time) : 0,
end_time: values.end_time ? getUnixTime(values.end_time) : 0,
title: values.title,
level: values.level,
mail_title: values.mail_title,
mail_content: values.mail_content,
cfg: JSON.stringify(cfgjson),
extra: values.extra,
},
};
await addActivityApi(params);
modalApi.close();
},
});
</script>
<template>
<Modal :width="800" title="邮件详情">
<Form>
<template #items>
<div class="flex flex-wrap items-center justify-center">
<div v-for="item in items" :key="item.Id" class="flex items-center gap-1">
<template v-if="item.url">
<Image :src="item.url" width="30px" />
</template>
<template v-else>
<Tag class="w-[45px] h-[30px] flex items-center justify-center text-xs">
{{ item.Id }}
</Tag>
</template>
<span>x{{ item.Num }}</span>
</div>
</div>
</template>
</Form>
</Modal>
</template>

View File

@ -0,0 +1,184 @@
<script lang="ts" setup>
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { Image, message } from "ant-design-vue";
import { formatItems } from '#/store/util';
import { activityTypeData } from '#/store/order';
import { Tag } from 'ant-design-vue';
import { ref } from "vue";
import dayjs from 'dayjs';
import { editActivityApi } from '#/api/core/activity';
import type { EditActivityParam } from '#/api/core/activity';
import { getUnixTime } from '#/store/util';
interface MailItem {
Id: string | number;
Num: number;
url?: string;
}
let items = ref<MailItem[]>([]);
defineOptions({
name: 'DetailMailModal',
});
const [Form, FormApi] = useVbenForm({
//
commonConfig: {
//
componentProps: {
class: 'w-full h-full',
},
},
// 使 tailwindcss grid
//
// labelinputvertical
layout: 'horizontal',
showDefaultActions: false,
// labelinput
schema: [
{
component: 'Input',
fieldName: 'id',
label: 'Id',
componentProps: {
disabled: true,
},
},
{
component: 'Input',
fieldName: 'type',
label: '活动类型',
componentProps: {
disabled: true,
},
},
{
component: 'Input',
fieldName: 'level',
label: '解锁等级',
componentProps: {
},
},
{
component: 'DatePicker',
fieldName: 'start_time',
label: '开始时间(北京时间)',
componentProps: {
ariaLabel: 'start_time',
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
component: 'DatePicker',
fieldName: 'end_time',
label: '结束时间(北京时间)',
componentProps: {
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
component: 'Input',
fieldName: 'title',
label: '活动标题',
rules: 'required',
},
{
component: 'Input',
fieldName: 'mail_title',
label: '回收邮件标题',
rules: 'required',
},
{
component: 'Input',
fieldName: 'mail_content',
label: '回收邮件内容',
},
{
component: 'Textarea',
fieldName: 'cfg',
label: '配置',
componentProps: {
type: 'textarea',
rows: 8,
},
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'extra',
label: '额外配置',
rules: 'required',
},
],
});
const [Modal, modalApi] = useVbenModal({
confirmText: '提交',
onOpened: async () => {
const mMdata = modalApi.getData();
const modalData = mMdata.cfg || {};
const st = modalData.start_time ? dayjs(modalData.start_time*1000) : null;
const et = modalData.end_time ? dayjs(modalData.end_time*1000) : null;
FormApi.setValues({
id: modalData.id,
type: activityTypeData[modalData.type],
start_time: st,
end_time: et,
title: modalData.title,
level: modalData.level,
mail_title: modalData.mail_title,
mail_content: modalData.mail_content,
cfg: modalData.cfg,
extra: modalData.extra,
});
items.value = formatItems(modalData.items);
},
onConfirm: async () => {
const values = await FormApi.getValues();
const mMdata = modalApi.getData();
const modalData = mMdata.cfg || {};
const cfgjson = JSON.parse(values.cfg);
const level: number = Number(values.level) || 0;
const params: EditActivityParam = {
AppId: mMdata.AppId,
Cfg: {
id: modalData.id,
type: modalData.type,
start_time: values.start_time ? getUnixTime(values.start_time) : 0,
end_time: values.end_time ? getUnixTime(values.end_time) : 0,
title: values.title,
level: level,
mail_title: values.mail_title,
mail_content: values.mail_content,
cfg: JSON.stringify(cfgjson),
extra: values.extra,
},
};
const response = await editActivityApi(params);
if (response) {
//
message.success('活动更新成功');
}
modalApi.close();
},
});
</script>
<template>
<Modal :width="800" title="邮件详情">
<Form>
<template #items>
<div class="flex flex-wrap items-center justify-center">
<div v-for="item in items" :key="item.Id" class="flex items-center gap-1">
<template v-if="item.url">
<Image :src="item.url" width="30px" />
</template>
<template v-else>
<Tag class="w-[45px] h-[30px] flex items-center justify-center text-xs">
{{ item.Id }}
</Tag>
</template>
<span>x{{ item.Num }}</span>
</div>
</div>
</template>
</Form>
</Modal>
</template>

View File

@ -0,0 +1,187 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import { Button, Card, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';
import type { VbenFormProps } from '#/adapter/form';
import type { ActivityData } from '#/api/core/activity';
import { getServerListApi, getAppListApi } from '#/api/core/server';
import { getActivityListApi } from '#/api/core/activity';
import type { AppData, ServerData } from '#/api/core/server';
import { useVbenModal } from '@vben/common-ui';
import { onMounted, ref } from 'vue';
import AddActivityModal from './activity-add.vue';
import DetailActivityModal from './activity-detail.vue';
import { activityTypeData } from '#/store/order';
import { $t } from '#/locales'
const appList = ref<AppData[]>([]);
const ServerList = ref<ServerData[]>([]);
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Select',
defaultValue: 1,
componentProps: {
onChange: async (value: number) => {
const serverResponse = await getServerListApi({
AppId: value,
Type: 1,
});
ServerList.value = Array.isArray(serverResponse)
? serverResponse
: [];
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: ServerList.value.map((item) => ({
label: item.ServerName,
value: item.ServerId,
})),
},
fieldName: 'ServerId',
},
]);
},
filterOption: true,
options: [],
placeholder: '请选择',
showSearch: true,
},
fieldName: 'AppId',
label: 'APP',
},
],
//
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
//
submitOnChange: false,
//
submitOnEnter: false,
};
const gridOptions: VxeGridProps<ActivityData> = {
columns: [
{ field: 'id', title: 'id' },
{ field: 'type', title: '活动类型', formatter: ({ cellValue }) => activityTypeData[cellValue] || cellValue },
{ field: 'level',title: '开启等级',},
{ field: 'start_time', title: '开启时间', formatter: ({ cellValue }) => new Date(cellValue * 1000).toLocaleString(), },
{ field: 'end_time', title: '结束时间', formatter: ({ cellValue }) => new Date(cellValue * 1000).toLocaleString(), },
],
height: 'auto',
pagerConfig: {},
proxyConfig: {
response: {
total: 'total',
result: 'data',
},
ajax: {
query: async ({ page }, formValues) => {
let AppId = parseInt(formValues.AppId, 10);
const response = await getActivityListApi({
AppId: AppId,
ServerId: formValues.ServerId,
PageSize: page.pageSize,
CurrentPage: page.currentPage,
});
return response;
},
},
},
showOverflow: false,
rowConfig: {
isHover: true,
},
};
const gridEvents: VxeGridListeners<ActivityData> = {
cellClick: async ({ row }) => {
const Value = await GridApi.formApi.getValues();
DetailActivityApi.setData({AppId:Value.AppId, cfg:row});
DetailActivityApi.open();
// message.info(`cell-click: ${row.title}`);
},
};
const [Grid, GridApi] = useVbenVxeGrid({
gridEvents,
formOptions,
gridOptions,
});
const [AddActivityM, AddActivityApi] = useVbenModal({
connectedComponent: AddActivityModal,
onClosed: async () => {
AddActivityApi.close();
GridApi.query();
//console.log("close")
},
});
const [DetailActivityM, DetailActivityApi] = useVbenModal({
connectedComponent: DetailActivityModal,
});
onMounted(async () => {
try {
const response = await getAppListApi();
appList.value = Array.isArray(response) ? response : [];
const app = appList.value[0];
if (!app) return;
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: appList.value.map((item) => ({
label: $t('page.server.' + item.AppName),
value: item.AppId,
})),
},
fieldName: 'AppId',
},
]);
const serverResponse = await getServerListApi({
AppId: app.AppId,
Type: 1,
});
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: ServerList.value.map((item) => ({
label: item.ServerName,
value: item.ServerId,
})),
},
fieldName: 'ServerId',
},
]);
} catch (e) {
appList.value = [];
//console.log(e);
}
});
async function addActivity() {
//console.log('addActivity');
const Value = await GridApi.formApi.getValues();
AddActivityApi.setData({ AppId: Value.AppId, ServerId: Value.ServerId });
AddActivityApi.open();
}
</script>
<template>
<Page auto-content-height class="h-[800px]">
<AddActivityM class="w-[50%]" />
<DetailActivityM class="w-[50%]" />
<Card class="mb-5" title="活动操作">
<Space>
<Button @click="addActivity">新增活动</Button>
</Space>
</Card>
<Grid />
</Page>
</template>

View File

@ -0,0 +1,12 @@
<script lang="ts" setup>
import AnalyticsVisitsTable from './activity-table.vue';
</script>
<template #table>
<AnalyticsVisitsTable />
</template>

View File

@ -0,0 +1,258 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import { Button, Card, Space, Image, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';
import type { VbenFormProps } from '#/adapter/form';
import type { MailData } from '#/api/core/mail';
import { getServerListApi, getAppListApi } from '#/api/core/server';
import { getMailListApi, deleteMailApi } from '#/api/core/mail';
import type { AppData, ServerData } from '#/api/core/server';
import { useVbenModal } from '@vben/common-ui';
import { onMounted, ref } from 'vue';
import AddMailModal from './activity-add.vue';
import DetailMailModal from './activity-detail.vue';
import { getItemUrl } from '#/store/util';
const appList = ref<AppData[]>([]);
const ServerList = ref<ServerData[]>([]);
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Select',
defaultValue: 1,
componentProps: {
onChange: async (value: number) => {
const serverResponse = await getServerListApi({
AppId: value,
Type: 1,
});
ServerList.value = Array.isArray(serverResponse)
? serverResponse
: [];
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: ServerList.value.map((item) => ({
label: item.ServerName,
value: item.ServerId,
})),
},
fieldName: 'ServerId',
},
]);
},
filterOption: true,
options: [],
placeholder: '请选择',
showSearch: true,
},
fieldName: 'AppId',
label: 'APP',
},
{
component: 'Select',
defaultValue: 1,
componentProps: {
options: [],
},
fieldName: 'ServerId',
label: '区服',
},
],
//
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
//
submitOnChange: false,
//
submitOnEnter: false,
};
const gridOptions: VxeGridProps<MailData> = {
columns: [
{ field: 'mail_id', title: 'id' },
{ field: 'title', title: '邮件标题' },
// { field: 'title_en', title: '' },
{ field: 'subtitle', title: '邮件副标题' },
// { field: 'subtitleEN', title: '' },
{ field: 'content', title: '邮件内容' },
// { field: 'content_en', title: '' },
{ field: 'items', title: '道具', slots: { default: 'items' } },
{
field: 'start_time',
title: '开始时间',
formatter: ({ cellValue }) =>
cellValue ? new Date(cellValue * 1000).toLocaleString() : '',
},
{
field: 'mail_type',
title: '邮件类型',
formatter: ({ cellValue }) => (cellValue == 1 ? '普通邮件' : '节日邮件'),
},
{
field: 'send_type',
title: '邮件发送类型',
formatter: ({ cellValue }) => (cellValue == 1 ? '全服邮件' : '个人邮件'),
},
{ field: 'to_uids', title: '接收者' },
{ field: 'create_time', title: '创建时间' },
{ slots: { default: 'action' }, title: '操作', width: 100 },
],
height: 'auto',
pagerConfig: {},
proxyConfig: {
response: {
total: 'total',
result: 'data',
},
ajax: {
query: async ({ page }, formValues) => {
let AppId = parseInt(formValues.AppId, 10);
return await getMailListApi({
AppId: AppId,
ServerId: formValues.ServerId,
PageSize: page.pageSize,
CurrentPage: page.currentPage,
});
},
},
},
showOverflow: false,
rowConfig: {
isHover: true,
},
};
const gridEvents: VxeGridListeners<MailData> = {
cellClick: ({ row }) => {
AddMailApi2.setData(row);
AddMailApi2.open();
// message.info(`cell-click: ${row.title}`);
},
};
const [Grid, GridApi] = useVbenVxeGrid({
gridEvents,
formOptions,
gridOptions,
});
const [AddMailM, AddMailApi] = useVbenModal({
connectedComponent: AddMailModal,
onClosed: async () => {
AddMailApi.close();
GridApi.query();
//console.log("close")
},
});
const [AddMailM2, AddMailApi2] = useVbenModal({
connectedComponent: DetailMailModal,
});
onMounted(async () => {
try {
const response = await getAppListApi();
appList.value = Array.isArray(response) ? response : [];
const app = appList.value[0];
if (!app) return;
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: appList.value.map((item) => ({
label: item.AppName,
value: item.AppId,
})),
},
fieldName: 'AppId',
},
]);
const serverResponse = await getServerListApi({
AppId: app.AppId,
Type: 1,
});
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: ServerList.value.map((item) => ({
label: item.ServerName,
value: item.ServerId,
})),
},
fieldName: 'ServerId',
},
]);
} catch (e) {
appList.value = [];
//console.log(e);
}
});
async function addMail() {
//console.log('addMail');
const Value = await GridApi.formApi.getValues();
AddMailApi.setData({ AppId: Value.AppId, ServerId: Value.ServerId });
AddMailApi.open();
}
async function deleteRow(row: MailData) {
//console.log(row);
GridApi.setLoading(true);
if (typeof row.mail_id == 'number') {
await deleteMailApi(row.AppId, row.ServerId, row.mail_id);
} else {
//console.error('Invalid mail_id:', row.mail_id);
}
GridApi.setLoading(false);
GridApi.query();
}
function fromatItems(items: string) {
try {
const itemList = JSON.parse(items);
const r = itemList.map((item: { Id: number; Num: number }) => ({
...item,
url: getItemUrl(item.Id),
}));
return r;
} catch (e) {
//console.error('Failed to parse items:', e);
return [];
}
}
</script>
<template>
<Page auto-content-height class="h-[800px]">
<AddMailM class="w-[50%]" />
<AddMailM2 class="w-[50%]" />
<Card class="mb-5" title="邮件操作">
<Space>
<Button @click="addMail">新增邮件</Button>
</Space>
</Card>
<Grid>
<template #items="{row}">
<div class="flex flex-wrap items-center justify-center">
<div v-for="item in fromatItems(row.items)" :key="item.Id" class="flex items-center gap-1">
<template v-if="item.url">
<Image :src="item.url" width="30px" />
</template>
<template v-else>
<Tag class="w-[45px] h-[30px] flex items-center justify-center text-xs">
{{item.Id}}
</Tag>
</template>
<span>x{{ item.Num }}</span>
</div>
</div>
</template>
<template #action="{ row }">
<Button type="link" @click="deleteRow(row)" style="color: #cc0000">删除</Button>
</template>
</Grid>
</Page>
</template>

View File

@ -1,8 +1,15 @@
<script lang="ts" setup>
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import {Image} from "ant-design-vue";
import Icon from '../../../../../../packages/@core/ui-kit/shadcn-ui/src/components/icon/icon.vue';
import { formatItems } from '#/store/util';
import { Tag } from 'ant-design-vue';
import {ref} from "vue";
interface MailItem {
Id: string | number;
Num: number;
url?: string;
}
let items = ref<MailItem[]>([]);
defineOptions({
name: 'DetailMailModal',
});
@ -63,10 +70,44 @@ const [Form, FormApi] = useVbenForm({
},
rules: 'required',
},
{
component: 'Input',
fieldName: 'TitlePTBR',
label: '葡萄牙(巴西)邮件标题',
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'ContentPTBR',
label: '葡萄牙(巴西)邮件内容',
componentProps: {
type: 'textarea',
rows: 8,
},
rules: 'required',
},
{
component: 'Input',
fieldName: 'TitleESLatam',
label: '西班牙(拉美)邮件标题',
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'ContentESLatam',
label: '西班牙(拉美)邮件内容',
componentProps: {
type: 'textarea',
rows: 8,
},
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'items',
label: '邮件道具11',
label: '邮件道具',
componentProps: {
placeholder: '{}',
type: 'textarea',
@ -158,6 +199,10 @@ const [Modal, modalApi] = useVbenModal({
TitleEN: modalData.title_en,
SubtitleEN: modalData.subtitle_en,
ContentEN: modalData.content_en,
TitlePTBR: modalData.title_ptbr,
ContentPTBR: modalData.content_ptbr,
TitleESLatam: modalData.title_es_latam,
ContentESLatam: modalData.content_es_latam,
items: modalData.items,
start_time: 0,
register_time: modalData.register_time,
@ -165,6 +210,7 @@ const [Modal, modalApi] = useVbenModal({
send_type: modalData.send_type === 1 ? '全服邮件' : '个人邮件',
ToUids: modalData.to_uids,
});
items = formatItems(modalData.items);
FormApi.updateSchema([
{
component: 'Input',
@ -196,6 +242,26 @@ const [Modal, modalApi] = useVbenModal({
disabled: true,
fieldName: 'ContentEN',
},
{
component: 'Input',
disabled: true,
fieldName: 'TitlePTBR',
},
{
component: 'Textarea',
disabled: true,
fieldName: 'ContentPTBR',
},
{
component: 'Input',
disabled: true,
fieldName: 'TitleESLatam',
},
{
component: 'Textarea',
disabled: true,
fieldName: 'ContentESLatam',
},
{
component: 'Input',
disabled: true,
@ -234,10 +300,19 @@ const [Modal, modalApi] = useVbenModal({
<Modal :width="800" title="邮件详情">
<Form>
<template #items>
<div class="border-2 border-solid border-x-sky-50 rounded-xl p-1">
<Image src="../../../../public/item/mini-gamesWK_icon_yanzhao.png" width="30px" />x20
<div class="flex flex-wrap items-center justify-center">
<div v-for="item in items" :key="item.Id" class="flex items-center gap-1">
<template v-if="item.url">
<Image :src="item.url" width="30px" />
</template>
<template v-else>
<Tag class="w-[45px] h-[30px] flex items-center justify-center text-xs">
{{item.Id}}
</Tag>
</template>
<span>x{{ item.Num }}</span>
</div>
</div>
</template>
</Form>
</Modal>

View File

@ -1,9 +1,15 @@
<script lang="ts" setup>
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { Select, Input, SelectOption, Image, SelectOptGroup } from 'ant-design-vue';
import dayjs from 'dayjs';
import { ref } from 'vue';
import { addMailApi } from '#/api/core/mail';
import type { MailData } from '#/api/core/mail';
import { VbenIcon } from '../../../../../../packages/@core/ui-kit/shadcn-ui/src/components';
import { getItemUrl } from '#/store/util';
const value1 = ref<string>();
const value2 = ref<number>();
const items = ref<{ Id: string | number; Num: number; url?: string }[]>([]);
const [Form, FormApi] = useVbenForm({
//
commonConfig: {
@ -113,6 +119,16 @@ const [Form, FormApi] = useVbenForm({
rows: 3,
},
},
{
component: 'Textarea',
fieldName: 'ItemList',
label: '道具',
componentProps: {
placeholder: '[{"Id":1,"Num":1},{"Id":2,"Num":2}]',
type: 'textarea',
rows: 3,
},
},
{
component: 'RangePicker',
fieldName: 'start_time',
@ -189,7 +205,9 @@ const [Form, FormApi] = useVbenForm({
});
const [Modal, modalApi] = useVbenModal({
confirmText: '提交',
onOpened: () => {
items.value = [];
},
onConfirm: async () => {
//
const values = await FormApi.getValues();
@ -212,8 +230,7 @@ const [Modal, modalApi] = useVbenModal({
const TitleEN = values.TitleEN;
const SubtitleEN = values.SubtitleEN;
const ContentEN = values.ContentEN;
const Items = values.Items;
const ToUids = values.ToUids;
const ToUids = values.ToUids ? String(values.ToUids) : '';
const modalData = modalApi.getData();
const param: MailData = {
AppId: modalData.AppId,
@ -230,7 +247,7 @@ const [Modal, modalApi] = useVbenModal({
title_es_latam: values.TitleESLatam,
subTitle_es_latam: values.SubtitleESLatam,
content_es_latam: values.ContentESLatam,
items: Items,
items: values.ItemList,
to_uids: ToUids,
start_time: start_time,
end_time: end_time,
@ -242,13 +259,54 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
});
function addItem() {
if (!value1.value || !value2.value) {
return;
}
const itemId: number = parseInt(value1.value);
const num: number = Number(value2.value);
items.value.push({
Id: itemId,
Num: num,
});
value1.value = '';
value2.value = undefined;
FormApi.setFieldValue('ItemList', JSON.stringify(items.value));
}
function removeItem(index: number) {
items.value.splice(index, 1);
}
defineOptions({
name: 'AddMailModal',
});
</script>
<template>
<Modal :width="800" title="添加邮件">
<Form />
<Form>
<template #Items>
<span>Id:</span>
<Select class="ml-2 w-[200px]" v-model:value="value1">
<SelectOptGroup>
<template #label>
<span>内置道具</span>
</template>
<SelectOption value="100001">能量</SelectOption>
<SelectOption value="100002">宠物币</SelectOption>
<SelectOption value="100003">钻石</SelectOption>
<SelectOption value="100021">小猪存钱罐</SelectOption>
</SelectOptGroup>
</Select>
<span class="ml-2">Num:</span>
<Input type="number" class="ml-2 border w-[200px]" v-model:value="value2" />
<div @click="addItem">
<VbenIcon icon="material-symbols:add-2" class="ml-2 border-2" />
</div>
</template>
</Form>
</Modal>
</template>

View File

@ -13,9 +13,10 @@ import { onMounted, ref } from 'vue';
import AddMailModal from './mail-info.vue';
import DetailMailModal from './mail-detail.vue';
import { getItemUrl } from '#/store/util';
import { $t } from '#/locales'
const appList = ref<AppData[]>([]);
const ServerList = ref<ServerData[]>([]);
const formOptions: VbenFormProps = {
//
collapsed: false,
@ -53,15 +54,6 @@ const formOptions: VbenFormProps = {
fieldName: 'AppId',
label: 'APP',
},
{
component: 'Select',
defaultValue: 1,
componentProps: {
options: [],
},
fieldName: 'ServerId',
label: '区服',
},
],
//
showCollapseButton: true,
@ -115,7 +107,7 @@ const gridOptions: VxeGridProps<MailData> = {
let AppId = parseInt(formValues.AppId, 10);
return await getMailListApi({
AppId: AppId,
ServerId: formValues.ServerId,
ServerId: 1,
PageSize: page.pageSize,
CurrentPage: page.currentPage,
});
@ -162,30 +154,14 @@ onMounted(async () => {
component: 'Select',
componentProps: {
options: appList.value.map((item) => ({
label: item.AppName,
label: $t('page.server.' + item.AppName),
value: item.AppId,
})),
},
fieldName: 'AppId',
},
]);
const serverResponse = await getServerListApi({
AppId: app.AppId,
Type: 1,
});
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
GridApi.formApi.updateSchema([
{
component: 'Select',
componentProps: {
options: ServerList.value.map((item) => ({
label: item.ServerName,
value: item.ServerId,
})),
},
fieldName: 'ServerId',
},
]);
} catch (e) {
appList.value = [];
//console.log(e);
@ -236,7 +212,7 @@ function fromatItems(items: string) {
</Space>
</Card>
<Grid>
<template #items="{ row }">
<template #items="{row}">
<div class="flex flex-wrap items-center justify-center">
<div v-for="item in fromatItems(row.items)" :key="item.Id" class="flex items-center gap-1">
<template v-if="item.url">

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { Button, Card, Space, notification, Tag } from 'ant-design-vue';
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
import { Button, notification, Tag } from 'ant-design-vue';
import { useVbenDrawer } from '@vben/common-ui';
import ExtraDrawer from './drawer.vue';
const [Drawer, drawerApi] = useVbenDrawer({
connectedComponent: ExtraDrawer,

View File

@ -9,6 +9,7 @@ import type { VxeGridProps } from '#/adapter/vxe-table';
import type { VbenFormProps } from '#/adapter/form';
import { ItemData } from '#/store/item';
import { eventModal } from '#/component';
import { getUnixTime } from "#/store/util";
import dayjs from 'dayjs';
// props
@ -138,7 +139,7 @@ const gridEvents: VxeGridListeners<RowType> = {
cellDblclick: async ({ row }) => {
console.log(row);
modalApi.setData({
StartTime: row.timestamp,
StartTime: getUnixTime(row.timestamp),
EndTime: row.timestamp,
});
modalApi.open();
@ -184,14 +185,14 @@ const gridOptions: VxeGridProps<RowType> = {
state.uid = uid;
const ItemId = parseInt(formValues.ItemId, 10);
console.log(formValues.StartTime.unix());
const StartTime = getUnixTime(formValues.StartTime);
const EndTime = getUnixTime(formValues.EndTime);
const r = await getUserLogAssetApi({
Id: uid,
AppId: props.app,
Event: formValues.Event,
StartTime: formValues.StartTime.unix(),
EndTime: formValues.EndTime.unix(),
StartTime: StartTime,
EndTime: EndTime,
ItemId: ItemId,
CurrentPage: page.currentPage,
PageSize: page.pageSize,

View File

@ -233,6 +233,7 @@ const info = ref<{
Chess: Chess[];
Friend: friendRecord[];
MaxCharge?: number;
AdWatch?: number;
}>({
Level: 0,
Star: 0,
@ -252,6 +253,7 @@ const info = ref<{
Chess: [],
Friend: [],
MaxCharge: 0,
AdWatch: 0,
});
let trendItems: WorkbenchTrendItem[] = [
@ -296,6 +298,7 @@ const [Modal, modalApi] = useVbenModal({
info.value.AreaId = r.AreaId;
info.value.MaxCharge = r.MaxCharge;
info.value.Friend = r.FriendList || [];
info.value.AdWatch = r.AdWatch || 0;
// Ban0-1
if (r.Ban && r.Ban > 0) {
const now = Math.floor(Date.now() / 1000);
@ -512,6 +515,7 @@ watch(
<template #AreaId>{{ info.AreaId }}</template>
<template #Code>{{ info.Code || 0 }}</template>
<template #TodayCumulative>{{ info.TodayCumulative }}</template>
<template #ad_watch>{{ info.AdWatch ? '是' : '否' }}</template>
</WorkbenchDetail>
<chessComponent :items="info.Chess" title="棋盘" class="mt-6" />
<friendComponent :Items="info.Friend" title="好友" class="mt-6" />

View File

@ -95,6 +95,19 @@ const formOptions: VbenFormProps = {
placeholder: '请输入',
showSearch: true,
},
fieldName: 'Username',
label: '登录名:',
},
{
component: 'Input',
componentProps: {
filterOption: true,
options: [
],
placeholder: '请输入',
showSearch: true,
},
fieldName: 'Nickname',
label: '昵称:',
},
@ -120,6 +133,19 @@ const formOptions: VbenFormProps = {
label: '结束时间',
},
{
component: 'Input',
componentProps: {
filterOption: true,
options: [
],
placeholder: '请输入',
showSearch: true,
},
fieldName: 'DeviceId',
label: '设备ID',
},
],
//
showCollapseButton: true,
@ -144,7 +170,7 @@ const gridEvents: VxeGridListeners<RowType> = {
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ field: 'Uid', title: 'id' , sortable: true, sortBy: 'Uid'},
{ field: 'Uid', title: 'id', sortable: true, sortBy: 'Uid' },
{ field: 'UserName', title: '登录名' },
{ field: 'Level', title: '等级', formatter: ({ cellValue }: { cellValue: string | number }) => `${cellValue}`, sortable: true, sortBy: 'Level' },
{ field: 'Node', title: '节点', },
@ -152,8 +178,8 @@ const gridOptions: VxeGridProps<RowType> = {
{ field: 'Diamond', title: '钻石', formatter: ({ cellValue }: { cellValue: string | number }) => `${cellValue} 💎`, sortable: true, sortBy: 'Diamond' },
{ field: 'Star', title: '星星', formatter: ({ cellValue }: { cellValue: string | number }) => `${cellValue}`, sortable: true, sortBy: 'Star' },
{ field: 'Energy', title: '能量', formatter: ({ cellValue }: { cellValue: string | number }) => `${cellValue}`, sortable: true, sortBy: 'Energy' },
{ field: 'LoginTime', sortable: true, title: '登录时间', formatter: ({ cellValue }: { cellValue: number }) => dayjs(cellValue * 1000).format('YYYY-MM-DD HH:mm:ss'),sortBy: 'LoginTime' },
{ field: 'Online', title: '在线状态', slots: { default: 'online' } , sortable: true, sortBy: 'Online' },
{ field: 'LoginTime', sortable: true, title: '登录时间', formatter: ({ cellValue }: { cellValue: number }) => dayjs(cellValue * 1000).format('YYYY-MM-DD HH:mm:ss'), sortBy: 'LoginTime' },
{ field: 'Online', title: '在线状态', slots: { default: 'online' }, sortable: true, sortBy: 'Online' },
],
height: 'auto',
pagerConfig: {},
@ -180,7 +206,9 @@ const gridOptions: VxeGridProps<RowType> = {
let ServerId = 1;
let Uid = parseInt(formValues.Uid, 10);
let Nickname = formValues.Nickname ? String(formValues.Nickname) : '';
let r = await getUserListApi({ Id: Id, ServerId: ServerId, pageSize: page.pageSize, currentPage: page.currentPage, Uid: Uid, Nickname:Nickname, StartTime: formValues.StartTime ? Math.floor(new Date(formValues.StartTime).getTime() / 1000) : undefined, EndTime: formValues.EndTime ? Math.floor(new Date(formValues.EndTime).getTime() / 1000) : undefined });
let Username = formValues.Username ? String(formValues.Username) : '';
let DeviceId = formValues.DeviceId ? String(formValues.DeviceId) : '';
let r = await getUserListApi({ Id: Id, ServerId: ServerId, pageSize: page.pageSize, currentPage: page.currentPage, Uid: Uid, Nickname: Nickname, Username: Username, DeviceId: DeviceId, StartTime: formValues.StartTime ? Math.floor(new Date(formValues.StartTime).getTime() / 1000) : undefined, EndTime: formValues.EndTime ? Math.floor(new Date(formValues.EndTime).getTime() / 1000) : undefined });
return r;
},
},

291
apps/web-antd/template.json Normal file
View File

@ -0,0 +1,291 @@
{
"Name": "挖矿活动",
"PassNum": 20,
"itemCost": [
{
"Id": 100026,
"Num": 1
}
],
"ItemId": 100026,
"startItemnum": 2,
"Gem": {
"1": {
"Area": "1*1"
},
"2": {
"Area": "1*2"
},
"3": {
"Area": "1*3"
},
"4": {
"Area": "1*4"
},
"5": {
"Area": "2*2"
},
"6": {
"Area": "2*3"
},
"7": {
"Area": "3*3"
}
},
"Jackpot": {
"1": {
"Prob": 15,
"Items": [
{
"Id": 100001,
"Num": 5
}
]
},
"2": {
"Prob": 10,
"Items": [
{
"Id": 100026,
"Num": 1
}
]
},
"3": {
"Prob": 10,
"Items": [
{
"Id": 100001,
"Num": 2
}
]
},
"4": {
"Prob": 65,
"Items": null
}
},
"Pass": {
"1": {
"Items": [
{
"Id": 100001,
"Num": 8
}
],
"Area": "4*4",
"Gem": 2,
"StarReward": null
},
"2": {
"Items": [
{
"Id": 100001,
"Num": 15
}
],
"Area": "6*4",
"Gem": "3|4",
"StarReward": null
},
"3": {
"Items": [
{
"Id": 100026,
"Num": 5
},
{
"Id": 100005,
"Num": 1
}
],
"Area": "6*6",
"Gem": "1|2|5",
"StarReward": 40
},
"4": {
"Items": [
{
"Id": 100001,
"Num": 30
}
],
"Area": "7*5",
"Gem": "3|3|4",
"StarReward": null
},
"5": {
"Items": [
{
"Id": 100001,
"Num": 15
}
],
"Area": "5*5",
"Gem": "2|3|3",
"StarReward": null
},
"6": {
"Items": [
{
"Id": 100026,
"Num": 5
}
],
"Area": "5*5",
"Gem": "2|4|4",
"StarReward": 55
},
"7": {
"Items": [
{
"Id": 100001,
"Num": 15
},
{
"Id": 100006,
"Num": 1
}
],
"Area": "6*6",
"Gem": "3|5|5",
"StarReward": null
},
"8": {
"Items": [
{
"Id": 100001,
"Num": 60
}
],
"Area": "6*6",
"Gem": "1|2|3|6",
"StarReward": null
},
"9": {
"Items": [
{
"Id": 100001,
"Num": 25
}
],
"Area": "5*5",
"Gem": "1|5",
"StarReward": null
},
"10": {
"Items": [
{
"Id": 100026,
"Num": 8
}
],
"Area": "7*4",
"Gem": "2|2|5|5",
"StarReward": 80
},
"11": {
"Items": [
{
"Id": 100001,
"Num": 40
}
],
"Area": "6*5",
"Gem": "1|4|4",
"StarReward": null
},
"12": {
"Items": [
{
"Id": 100001,
"Num": 90
},
{
"Id": 100007,
"Num": 1
}
],
"Area": "7*7",
"Gem": "4|6|6",
"StarReward": null
},
"13": {
"Items": null,
"Area": "6*7",
"Gem": "1|6|6",
"StarReward": 160
},
"14": {
"Items": [
{
"Id": 100001,
"Num": 45
}
],
"Area": "7*7",
"Gem": "2|2|5|6",
"StarReward": null
},
"15": {
"Items": [
{
"Id": 100001,
"Num": 75
}
],
"Area": "5*5",
"Gem": "2|2|6",
"StarReward": null
},
"16": {
"Items": [
{
"Id": 100026,
"Num": 8
}
],
"Area": "7*7",
"Gem": "2|7|7",
"StarReward": 275
},
"17": {
"Items": null,
"Area": "7*7",
"Gem": "1|1|5|6",
"StarReward": 345
},
"18": {
"Items": [
{
"Id": 101508,
"Num": 1
}
],
"Area": "7*7",
"Gem": "2|2|6|6",
"StarReward": null
},
"19": {
"Items": null,
"Area": "6*7",
"Gem": "7|3|5",
"StarReward": 500
},
"20": {
"Items": [
{
"Id": 100001,
"Num": 300
},
{
"Id": 100023,
"Num": 1
}
],
"Area": "7*7",
"Gem": "7|7",
"StarReward": 550
}
}
}

View File

@ -114,6 +114,18 @@ defineEmits(['click']);
</div>
</div>
</div>
<div class="flex w-full">
<div class="text-foreground/80 leading-2 mt-3 flex h-4 p-1 md:w-1/2">
<span class="font-bold">是否终身看广告:</span>
</div>
<div class="text-foreground/80 leading-2 mt-3 flex h-4 justify-start p-1 md:w-1/2">
<div class="Value">
<span v-if="$slots.ad_watch">
<slot name="ad_watch"></slot>
</span>
</div>
</div>
</div>
<div class="flex w-full">
<div class="text-foreground/80 leading-2 mt-3 flex h-4 p-1 md:w-1/2">