版本更新
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 2025-12-12 11:44:27 +08:00
parent b58800738c
commit 86285a1fee
48 changed files with 509 additions and 116 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -4,6 +4,12 @@ import type { UserInfo,AdminLog } from '#/model/admin.user';
export interface UserParam {
uid: string;
}
export interface AdminLogParam {
username?: string;
page?: number;
pageSize?: number;
opration?: string;
}
export async function getAdminInfoApi(param: UserParam) {
return requestClient.post<UserInfo>('/admin/info', param);
}
@ -12,8 +18,8 @@ export async function getAdminListApi() {
return requestClient.post<UserInfo[]>('/admin/list');
}
export async function getAdminLogListApi() {
return requestClient.post<AdminLog[]>('/admin/log/list');
export async function getAdminLogListApi(param:AdminLogParam) {
return requestClient.post<AdminLog[]>('/admin/log/list', param);
}

View File

@ -12,6 +12,9 @@ export interface MailData {
title_en: string;
subtitle_en?: string;
content_en: string;
title_ptbr?: string;
subTitle_ptbr?: string;
content_ptbr?: string;
items : string;
start_time: number;
end_time: number;

View File

@ -0,0 +1,8 @@
import { requestClient } from '#/api/request';
import type { copyUserParam } from '#/model/type';
export async function copyUser(data:copyUserParam) {
return requestClient.post('/operation/copyUser', data);
}

View File

@ -47,4 +47,8 @@ export async function addLanguageList(data: languageType[]) {
export async function exportLanguageFile() {
return requestClient.post('/language/export', {});
}
export async function deleteLanguageItem(data: {key: string}) {
return requestClient.post('/language/delete', data);
}

View File

@ -39,7 +39,7 @@ const orderTypeMeta: Record<number, { name: string; desc: string }> = {
1: { name: '普通订单', desc: '自动生成的基础订单。' },
2: { name: '额外订单(弃用)', desc: '历史类型,当前已弃用。' },
3: { name: '超级订单', desc: '奖励更高,难度更大的订单类型。' },
4: { name: '预热订单', desc: '用于活动或阶段开始前的预热内容。' },
4: { name: '预热订单', desc: '新获得发射器生成的特殊订单,订单内容和奖励固定' },
5: { name: '触发订单', desc: '由特定事件或条件触发生成。' },
6: { name: '退役发射器清理订单', desc: '用于清理退役发射器相关棋子。' },
7: { name: '清理无法生成订单的棋子', desc: '处理异常或失效的棋子。' },
@ -93,6 +93,8 @@ defineEmits(['click']);
<li><b>简单</b>订单总消耗体力在15-150之间</li>
<li><b>中等</b>订单总消耗体力在100-600之间</li>
<li><b>困难</b>订单总消耗体力在500-1200之间</li>
<li><b>零件订单</b>订单总消耗体力在40-400之间</li>
<li><b>消耗品订单</b>订单总消耗体力在40-200之间</li>
</ul>
</div>
</VbenPopover>

View File

@ -10,7 +10,8 @@
"searchField": "Search Field",
"value": "Value",
"startTime": "Start Time",
"endTime": "End Time"
"endTime": "End Time",
"action": "Action"
},
"auth": {
"login": "Login",
@ -45,7 +46,9 @@
"languageList": "Language List",
"translationList": "Translation List",
"lastUpdate": "Last update",
"newLineContent": "New Translation"
"newLineContent": "New Translation",
"noChangesToSave": "No changes to save",
"deleteSuccess": "Delete Success"
},
"operation": {
"title": "Operation",

View File

@ -10,7 +10,8 @@
"searchField": "搜索列",
"value": "值",
"startTime": "开始时间",
"endTime": "结束时间"
"endTime": "结束时间",
"action": "操作"
},
"auth": {
"login": "登录",
@ -45,14 +46,17 @@
"languageList": "语言列表",
"translationList": "翻译列表",
"lastUpdate": "最后修改日期",
"newLineContent": "新增翻译"
"newLineContent": "新增翻译",
"noChangesToSave": "没有需要保存的更改",
"deleteSuccess": "删除成功"
},
"operation": {
"title": "运营管理",
"level": "等级分布",
"mail": "邮件管理",
"order": "订单管理",
"language": "翻译管理"
"language": "翻译管理",
"copyUser": "用户数据复制"
},
"log": {
"event": {
@ -88,7 +92,17 @@
"emoji_income": "获得表情包",
"avatarIcon_income": "获得头像框",
"gm": "GM操作",
"nickname_set": "设置昵称"
"nickname_set": "设置昵称",
"logout": "登出",
"pet_item_get": "获得宠物物品",
"ReqGetChessFromBuff": "从暂存区取棋子",
"room_deco_get": "获得房间装饰",
"playroom_interact": "playroom互动",
"pet_item_use": "使用宠物物品",
"room_daily_task":"playroom日常任务",
"finish_mini_game":"完成小游戏",
"playerdeco_set":"设置玩家装饰",
"ReqGetMonthLoginReward":"获取月度登录奖励"
}
}
}

View File

@ -7,6 +7,7 @@ export interface UserInfo {
uid?: string;
group: string;
role: number;
remark?: string;
}
export interface AdminLog{

View File

@ -49,9 +49,9 @@ export interface Chess{
export interface languageType {
Id: number;
key: string;
English: string;
ChineseSimplified: string;
Portuguese: string;
en_US: string;
zh_CN: string;
pt_BR: string;
}
@ -64,3 +64,10 @@ export interface languageRecord{
Field: string;
Update?: number;
}
export interface copyUserParam{
src_app: number;
src_uid: number;
dst_app: number;
dst_uid: number;
}

View File

@ -7,7 +7,7 @@ const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'lucide:file-clock',
icon: 'solar:book-2-bold',
order: 1001,
title: $t('page.language.title'),
},

View File

@ -36,6 +36,16 @@ const routes: RouteRecordRaw[] = [
title: $t('page.operation.mail'),
},
},
{
name: 'CopyUser',
path: '/copyUser',
component: () => import('#/views/operation/copyUser/index.vue'),
meta: {
affixTab: true,
icon: 'lucide:mail',
title: $t('page.operation.copyUser'),
},
},
{
name: 'Order',
path: '/order',

View File

@ -4,25 +4,61 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { AdminLog } from '#/model/admin.user';
import { getAdminLogListApi } from '#/api/core/admin.user';
import type { AdminLogParam } from '#/api/core/admin.user';
import type { VbenFormProps } from '#/adapter/form';
const formOptions: VbenFormProps = {
//
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
schema: [
{
component: 'Select',
componentProps: {
filterOption: true,
options: [
{ label: 'key', value: 'key' },
{ label: 'English', value: 'English' },
{ label: 'ChineseSimplified', value: 'ChineseSimplified' },
{ label: 'Portuguese', value: 'Portuguese' },
],
placeholder: 'key',
showSearch: true,
},
fieldName: 'SearchField',
label: '操作:',
},
],
};
const gridOptions: VxeGridProps<AdminLog> = {
columns: [
{ field: 'admin', title: '用户名', },
{ field: 'action', title: '操作' },
{ field: 'params', title: '参数' },
{ field: 'ip', title: 'IP' },
{ field: 'createTime', title: '时间' },
{
field: 'createTime', title: '时间', formatter: ({ cellValue }) => new Date(cellValue * 1000).toLocaleString()
},
],
height: 'auto',
pagerConfig: {},
pagerConfig: {
pageSize: 100,
},
proxyConfig: {
response: {
total: 'total',
result: 'data',
},
ajax: {
query: async () => {
return await getAdminLogListApi();
query: async ({ page }) => {
const req: AdminLogParam = {
page: page.currentPage,
pageSize: page.pageSize,
};
return await getAdminLogListApi(req);
},
},
},
@ -32,7 +68,7 @@ const gridOptions: VxeGridProps<AdminLog> = {
},
};
const [Grid] = useVbenVxeGrid({ gridOptions });
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
</script>

View File

@ -73,6 +73,14 @@ const [Form, FormApi] = useVbenForm({
label: '用户组',
formItemClass: 'col-span-2',
},
{
component: 'Textarea',
componentProps: {},
rules: 'required',
fieldName: 'remark',
label: '备注',
formItemClass: 'col-span-2',
},
],
showDefaultActions: false,
// 321
@ -90,6 +98,7 @@ const [Modal, modalApi] = useVbenModal({
phone: values.phone,
role: values.role,
group: values.group,
remark: values.remark,
};
await addAdminApi(Parms);
modalApi.close();

View File

@ -11,9 +11,10 @@ import addUserModal from './addUser.vue';
const gridOptions: VxeGridProps<UserInfo> = {
columns: [
{ field: 'username', title: '用户名', },
{ field: 'phone', title: '手机号' },
// { field: 'phone', title: '' },
{ field: 'role', title: '角色' },
{ field: 'group', title: '用户组' },
{ field: 'remark', title: '备注' },
],
height: 'auto',
pagerConfig: {},
@ -48,7 +49,7 @@ const addAdmin = () => {
</script>
<template>
<Page auto-content-height>
<Page auto-content-height class="h-[1200px]">
<addUserM class="w-[50%]" />
<Card class="mb-5" title="用户操作">
<Space>

View File

@ -14,13 +14,13 @@ const gridOptions: VxeGridProps<languageType> = {
columns: [
{ title: 'Id', field: 'Id', width: 50 },
{ editRender: { name: 'input' }, field: 'key', title: 'key' },
{ editRender: { name: 'input' }, field: 'English', title: 'English' },
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US' },
{
editRender: { name: 'input' },
field: 'ChineseSimplified',
title: 'ChineseSimplified',
field: 'zh_CN',
title: 'zh_CN',
},
{ editRender: { name: 'input' }, field: 'Portuguese', title: 'Portuguese' },
{ editRender: { name: 'input' }, field: 'pt_BR', title: 'pt_BR' },
],
data: ld,
pagerConfig: {
@ -42,12 +42,9 @@ const [Form] = useVbenForm({
show: false,
},
handleSubmit: async (formValues) => {
console.log('Submitting form with values:', formValues);
console.log('Language data to add:', ld);
//
try {
await addLanguageList(ld);
modalApi.close();
} catch (error) {
console.error('Error adding languages:', error);
}
@ -60,7 +57,7 @@ const [Form] = useVbenForm({
label: $t('page.language.newLineContent'),
componentProps: {
disabled: false,
placeholder: 'key | English | ChineseSimplified| PortugueseOne record per line',
placeholder: 'key | en_US | zh_CN| pt_BR record per line',
type: 'textarea',
rows: 8,
onChange: (e: Event) => {
@ -73,9 +70,9 @@ const [Form] = useVbenForm({
return {
Id: (ld.length + index + 1),
key: parts[0] || '',
English: parts[1] || '',
ChineseSimplified: parts[2] || '',
Portuguese: parts[3] || '',
en_US: parts[1] || '',
zh_CN: parts[2] || '',
pt_BR: parts[3] || '',
};
});
ld = ld.concat(newData);
@ -91,6 +88,14 @@ const [Modal, modalApi] = useVbenModal({
confirmText: '提交',
showConfirmButton: false,
fullscreen: true,
onOpenChange: (open) => {
if (!open) {
ld = [] as languageType[];
GridApi.setGridOptions({
data: [],
});
}
},
});
</script>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import type { VxeGridProps, VxeGridListeners } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { Button, notification } from 'ant-design-vue';
import { Page } from '@vben/common-ui';
import { getLanguageList, exportLanguageFile, saveLanguageList } from '#/api/core/statistics';
import { getLanguageList, exportLanguageFile, saveLanguageList, deleteLanguageItem } from '#/api/core/statistics';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import addLanguage from './addLanguage.vue';
import type { VbenFormProps } from '#/adapter/form';
@ -11,6 +11,9 @@ import type { languageParam } from '#/api/core/statistics';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '#/locales'
import dayjs from 'dayjs';
import { AccessControl } from '@vben/access';
import { useAccess } from '@vben/access';
const { hasAccessByRoles } = useAccess();
const [AddLanguageModal, AddLanguageModalApi] = useVbenModal({
connectedComponent: addLanguage,
});
@ -19,12 +22,18 @@ let newData: languageType[] = [];
let op: languageRecord[] = [];
let lastOp: languageRecord[] = [];
let lastUpdate: string = '';
let columnStr: string = "";
let keyVisible: boolean = true;
let en_USVisible: boolean = true;
let chineseVisible: boolean = true;
let pt_BRVisible: boolean = true;
let actionVisible: boolean = true;
let total: languageType = {
Id: 1,
key: '',
English: '',
ChineseSimplified: '',
Portuguese: '',
en_US: '',
zh_CN: '',
pt_BR: '',
};
const startDate = dayjs().subtract(7, 'day').startOf('day');
const endDate = dayjs().endOf('day');
@ -38,9 +47,9 @@ const formOptions: VbenFormProps = {
filterOption: true,
options: [
{ label: 'key', value: 'key' },
{ label: 'English', value: 'English' },
{ label: 'ChineseSimplified', value: 'ChineseSimplified' },
{ label: 'Portuguese', value: 'Portuguese' },
{ label: 'en_US', value: 'en_US' },
{ label: 'zh_CN', value: 'zh_CN' },
{ label: 'pt_BR', value: 'pt_BR' },
],
placeholder: 'key',
showSearch: true,
@ -96,14 +105,15 @@ const gridOptions: VxeGridProps<languageType> = {
border: true,
columns: [
{ title: 'Id', field: 'Id', width: 100 },
{ editRender: { name: 'input' }, field: 'key', title: 'key', filters: [{ data: "" }], filterRender: { name: "input" } },
{ editRender: { name: 'input' }, field: 'English', title: 'English', filters: [{ data: "" }], filterRender: { name: "input" } },
{ editRender: { name: 'input' }, field: 'key', title: 'key', filters: [{ data: "" }], filterRender: { name: "input" }, visible: keyVisible },
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US', filters: [{ data: "" }], filterRender: { name: "input" }, visible: en_USVisible },
{
editRender: { name: 'input' },
field: 'ChineseSimplified',
title: 'ChineseSimplified', filters: [{ data: "" }], filterRender: { name: "input" }
field: 'zh_CN',
title: 'zh_CN', filters: [{ data: "" }], filterRender: { name: "input" }, visible: chineseVisible
},
{ editRender: { name: 'input' }, field: 'Portuguese', title: 'Portuguese', filters: [{ data: "" }], filterRender: { name: "input" } }
{ editRender: { name: 'input' }, field: 'pt_BR', title: 'pt_BR', filters: [{ data: "" }], filterRender: { name: "input" }, visible: pt_BRVisible },
{ slots: { default: 'action' }, title: $t('page.common.action'), width: 150 },
],
scrollY: {
enabled: true,
@ -155,6 +165,15 @@ const gridOptions: VxeGridProps<languageType> = {
SearchValue: formValues.SearchValue,
} as languageParam);
newData = response.data || [];
columnStr = response.column || "";
if (columnStr && columnStr.length > 0) {
const cols = columnStr.split(',');
keyVisible = cols.includes('key');
en_USVisible = cols.includes('en_US');
chineseVisible = cols.includes('zh_CN');
pt_BRVisible = cols.includes('pt_BR');
}
oldData = newData.map(item => ({ ...item }));
console.log('API response:', response);
lastOp = response.op || [];
@ -175,18 +194,18 @@ const gridOptions: VxeGridProps<languageType> = {
} else {
lastUpdate = '';
}
// response.data English don'tmother-in-law
const englishCount = (response.data || []).reduce((sum: number, item: languageType) => {
const s = item?.English || '';
// response.data en_US don'tmother-in-law
const en_USCount = (response.data || []).reduce((sum: number, item: languageType) => {
const s = item?.en_US || '';
const matches = s.match(/[A-Za-z]+(?:['-][A-Za-z]+)*/g);
return sum + (matches ? matches.length : 0);
}, 0);
// total footerData
total.English = String(englishCount);
total.en_US = String(en_USCount);
if (gridOptions && Array.isArray(gridOptions.footerData) && gridOptions.footerData.length > 0) {
(gridOptions.footerData[0] as any).English = String(englishCount);
(gridOptions.footerData[0] as any).en_US = String(en_USCount);
}
// ChineseSimplified
// zh_CN
const countHan = (str: string) => {
try {
const m = str.match(/\p{Script=Han}/gu);
@ -197,15 +216,41 @@ const gridOptions: VxeGridProps<languageType> = {
}
};
// pt_BR
const emptyPtBRCount = (response.data || []).reduce((sum: number, item: languageType) => {
const s = item?.pt_BR || '';
return sum + (s.trim() === '' ? 1 : 0);
}, 0);
const emptyZhCnCount = (response.data || []).reduce((sum: number, item: languageType) => {
const s = item?.zh_CN || '';
return sum + (s.trim() === '' ? 1 : 0);
}, 0);
const chineseCount = (response.data || []).reduce((sum: number, item: languageType) => {
return sum + countHan(item?.ChineseSimplified || '');
return sum + countHan(item?.zh_CN || '');
}, 0);
total.ChineseSimplified = String(chineseCount);
total.zh_CN = String(chineseCount);
if (gridOptions && Array.isArray(gridOptions.footerData) && gridOptions.footerData.length > 0) {
(gridOptions.footerData[0] as any).ChineseSimplified = String(chineseCount);
(gridOptions.footerData[0] as any).zh_CN = String(chineseCount);
}
if (hasAccessByRoles(['super', 'admin'])) {
actionVisible = true;
} else {
actionVisible = false;
}
let length = response.data ? response.data.length : 0;
GridApi.setGridOptions({
columns: [
{ editRender: { name: 'input' }, field: 'key', title: 'key', filters: [{ data: "" }], filterRender: { name: "input" }, visible: keyVisible },
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US', filters: [{ data: "" }], filterRender: { name: "input" }, visible: en_USVisible },
{
editRender: { name: 'input' },
field: 'zh_CN',
title: 'zh_CN', filters: [{ data: "" }], filterRender: { name: "input" }, visible: chineseVisible
},
{ editRender: { name: 'input' }, field: 'pt_BR', title: 'pt_BR', filters: [{ data: "" }], filterRender: { name: "input" }, visible: pt_BRVisible },
{ slots: { default: 'action' }, title: $t('page.common.action'), width: 150, visible: actionVisible },
],
pagerConfig: {
enabled: true,
pageSize: page.pageSize,
@ -214,9 +259,9 @@ const gridOptions: VxeGridProps<languageType> = {
footerData: [{
Id: 'Word',
key: '-',
English: total.English,
ChineseSimplified: total.ChineseSimplified,
Portuguese: '',
en_US: total.en_US,
zh_CN: emptyZhCnCount + '/' + length + ' empty cells',
pt_BR: emptyPtBRCount + '/' + length + ' empty cells',
}],
});
return {
@ -235,9 +280,15 @@ const gridOptions: VxeGridProps<languageType> = {
footerData: [{
Id: '字符数统计',
key: '-',
English: total.English,
ChineseSimplified: '',
Portuguese: '',
en_US: total.en_US,
zh_CN: '',
pt_BR: '',
}, {
Id: '空格数统计',
key: 'empty',
en_US: '',
zh_CN: '',
pt_BR: '',
}],
showFooter: true,
editConfig: {
@ -292,6 +343,13 @@ function addRow() {
});
}
function saveAll() {
if (op.length === 0) {
notification.info({
duration: 10,
message: $t('page.language.noChangesToSave'),
});
return;
}
saveLanguageList(op).then((response) => {
console.log('Save response:', response);
op = [];
@ -299,33 +357,40 @@ function saveAll() {
});
}
function downloadCSV(data: languageType[]) {
const headers = ['Id', 'key', 'English', 'ChineseSimplified'];
const csvRows = [];
csvRows.push(headers.join(','));
for (const row of data) {
const values = headers.map(header => {
const escaped = (row as any)[header]?.toString().replace(/"/g, '""') || '';
return `"${escaped}"`;
});
csvRows.push(values.join(','));
}
const csvContent = csvRows.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'language_export.csv');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function exportLang() {
exportLanguageFile().then((response) => {
// downloadCSV(response.data);
const code = response.code || 0;
const msg = response.msg || '';
if (code !== 0) {
notification.error({
duration: 10,
message: msg,
});
return;
}
notification.success({
duration: 10,
message: $t('page.language.exportSuccess'),
});
});
}
function deleteRow(row: languageType) {
deleteLanguageItem({ key: row.key }).then((response) => {
const code = response.code || 0;
const msg = response.msg || '';
if (code !== 0) {
notification.error({
duration: 10,
message: msg,
});
return;
}
notification.success({
duration: 10,
message: $t('page.language.deleteSuccess'),
});
GridApi.reload();
});
}
</script>
@ -340,8 +405,8 @@ function exportLang() {
}
</style>
<template>
<div class="h-full flex flex-col">
<Page auto-content-height class="h-[800px]">
<div class="min-h-screen flex flex-col">
<Page auto-content-height>
<AddLanguageModal width="1200px" height="1200px"></AddLanguageModal />
<Grid>
<template #toolbar-tools>
@ -352,6 +417,12 @@ function exportLang() {
<Button type="primary" @click="saveAll" class="mr-2"> {{ $t('page.common.save') }} </Button>
<Button type="primary" @click="exportLang"> {{ $t('page.common.gitCommit') }} </Button>
</template>
<template #action="{ row }">
<AccessControl :codes="['super', 'admin']" type="role">
<Button type="primary" @click="deleteRow(row)">删除</Button>
</AccessControl>
</template>
</Grid>
</Page>
</div>

View File

@ -0,0 +1,108 @@
<script setup lang="ts">
import { useVbenForm } from '#/adapter/form';
import { message } from 'ant-design-vue'
import { copyUser } from '#/api/core/operation';
import type { copyUserParam } from '#/model/type';
const [Form] = useVbenForm({
//
commonConfig: {
//
componentProps: {
class: 'w-full h-full',
},
},
// 使 tailwindcss grid
//
// labelinputvertical
layout: 'horizontal',
// labelinput
schema: [
{
component: 'Select',
fieldName: 'src_app',
label: '源应用ID',
rules: 'required',
defaultValue: 1,
componentProps: {
options: [
{
label: 'QA环境',
value: 2,
},
{
label: '测试环境',
value: 1,
},
{
label: 'US环境',
value: 0,
},
],
},
},
{
component: 'Input',
fieldName: 'src_uid',
label: '源用户ID',
rules: 'required',
defaultValue: 100100013,
componentProps: {
placeholder: '请输入源用户ID',
},
},
{
component: 'Select',
fieldName: 'dst_app',
label: '目标应用ID',
defaultValue: 1,
rules: 'required',
componentProps: {
options: [
{
label: 'QA环境',
value: 2,
},
{
label: '测试环境',
value: 1,
}
],
},
},
{
component: 'Input',
fieldName: 'dst_uid',
label: '目标用户ID',
defaultValue: 100100014,
rules: 'required',
componentProps: {
placeholder: '请输入目标用户ID',
},
},
],
handleSubmit: onSubmit,
});
async function onSubmit(values: Record<string, any>) {
const Param: copyUserParam = {
src_app: Number(values.src_app),
src_uid: Number(values.src_uid),
dst_app: Number(values.dst_app),
dst_uid: Number(values.dst_uid),
};
await copyUser(Param).then((respone) => {
if (respone.code === 0) {
message.success('用户复制成功');
} else {
message.error(`用户复制失败: ${respone.message}`);
}
});
}
</script>
<template>
<Form class="mt-20" />
</template>

View File

@ -0,0 +1,11 @@
<script lang="ts" setup>
import AnalyticsVisitsTable from './copy.vue';
</script>
<template>
<AnalyticsVisitsTable />
</template>

View File

@ -61,6 +61,27 @@ const [Form, FormApi] = useVbenForm({
},
rules: 'required',
},
{
component: 'Input',
fieldName: 'TitlePTBR',
label: '葡萄牙(巴西)邮件标题',
rules: 'required',
},
{
component: 'Input',
fieldName: 'SubtitlePTBR',
label: '葡萄牙(巴西)邮件副标题',
},
{
component: 'Textarea',
fieldName: 'ContentPTBR',
label: '葡萄牙(巴西)邮件内容',
componentProps: {
type: 'textarea',
rows: 8,
},
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'Items',
@ -182,6 +203,9 @@ const [Modal, modalApi] = useVbenModal({
title_en: TitleEN,
subtitle_en: SubtitleEN,
content_en: ContentEN,
title_ptbr: values.TitlePTBR,
subTitle_ptbr: values.SubtitlePTBR,
content_ptbr: values.ContentPTBR,
items: Items,
to_uids: ToUids,
start_time: start_time,

View File

@ -76,11 +76,11 @@ const gridOptions: VxeGridProps<MailData> = {
columns: [
{ field: 'mail_id', title: 'id' },
{ field: 'title', title: '邮件标题' },
{ field: 'title_en', title: '英文邮件标题' },
// { field: 'title_en', title: '' },
{ field: 'subtitle', title: '邮件副标题' },
{ field: 'subtitleEN', title: '英文邮件副标题' },
// { field: 'subtitleEN', title: '' },
{ field: 'content', title: '邮件内容' },
{ field: 'content_en', title: '英文邮件内容' },
// { field: 'content_en', title: '' },
{ field: 'items', title: '道具' },
{
field: 'mail_type',

View File

@ -114,6 +114,41 @@ const gridOptions: VxeGridProps<RowType> = {
}
state.uid = Uid;
state.Event = formValues.Event;
let startTimeUnix = 0;
let endTimeUnix = 0;
// formValues.StartTime/EndTime string DatePicker valueFormat
// dayjs valueFormat
if (formValues.StartTime) {
if (typeof formValues.StartTime === 'string') {
startTimeUnix = dayjs(formValues.StartTime).unix();
} else if (dayjs.isDayjs(formValues.StartTime)) {
startTimeUnix = formValues.StartTime.unix();
} else if (typeof (formValues.StartTime as any).unix === 'function') {
startTimeUnix = (formValues.StartTime as any).unix();
} else {
startTimeUnix = dayjs(formValues.StartTime).unix();
}
}
if (formValues.EndTime) {
if (typeof formValues.EndTime === 'string') {
endTimeUnix = dayjs(formValues.EndTime).unix();
} else if (dayjs.isDayjs(formValues.EndTime)) {
endTimeUnix = formValues.EndTime.unix();
} else if (typeof (formValues.EndTime as any).unix === 'function') {
endTimeUnix = (formValues.EndTime as any).unix();
} else {
endTimeUnix = dayjs(formValues.EndTime).unix();
}
}
// 使 unix
return await getUserlogEventApi({
Id: Uid,
Event: formValues.Event,
StartTime: startTimeUnix,
EndTime: endTimeUnix,
CurrentPage: page.currentPage,
PageSize: page.pageSize,
});
return await getUserlogEventApi({
Id: Uid,
Event: formValues.Event,

View File

@ -57,10 +57,24 @@ const [BaseForm] = useVbenForm({
{ label: 'addexp 100 增加100经验', value: 'addexp 100' },
{ label: 'setlv 1 设置为1级', value: 'setlv 1' },
{ label: 'AllFace 获取所有头像和头像框', value: 'AllFace' },
{ label: 'resetFace 重置用户头像和头像框', value: 'resetFace' },
{ label: 'addEmoji 获取所有表情', value: 'addEmoji' },
{ label: 'initEmoji 重置表情', value: 'initEmoji' },
//
{ label: 'reset_order 重置订单', value: 'reset_order' },
{ label: 'orderMerge 获取订单所需棋子', value: 'orderMerge' },
//
{ label: 'pay 10 模拟充值10挡位', value: 'pay 10' },
//
{ label: 'AddPart 增加所有类型零件10000个', value: 'AddPart' },
{ label: 'resetChess 重置棋盘', value: 'resetChess' },
{ label: 'addChessStar 5 增加5点棋子碎片', value: 'addChessStar 5' },
{ label: 'resetRetire 重置棋子退役', value: 'resetRetire' },
{ label: 'cleanBuff 清空缓存区', value: 'cleanBuff' },
{ label: 'addAllChess 获取所有棋子', value: 'addAllChess' },
{ label: 'addSeasonChess 获取当前轮次全部棋子', value: 'addSeasonChess' },
//
{ label: 'add_card_star 5 增加5点卡包碎片', value: 'add_card_star 5' },
@ -68,9 +82,11 @@ const [BaseForm] = useVbenForm({
{ label: 'addAddCard 获取所有卡牌', value: 'addAddCard' },
{ label: 'addAddCard 获取当前轮次全部卡牌', value: 'addAddCard' },
{ label: 'resetCardSeasonFirst 重置卡牌赛季初始奖励', value: 'resetCardSeasonFirst' },
{ label: 'reset_card_reward 重置卡牌大奖', value: 'reset_card_reward' },
//
{ label: 'setSevenLoginActive 20 设置签到进度为20', value: 'setSevenLoginActive 20' },
{ label: 'addDailyActive 增加每日任务活跃度', value: 'addDailyActive 10' },
//
{ label: 'setProgress 4 设置转盘进度为4', value: 'setProgress 4' },
@ -91,16 +107,28 @@ const [BaseForm] = useVbenForm({
{ label: 'resetTriggerTime 重置playroom触发订单cd', value: 'resetTriggerTime' },
{ label: 'playroomDress 获取playroom所有服装', value: 'playroomDress' },
{ label: 'playroomAir 获取playroom所有飞行背包', value: 'playroomAir' },
{ label: 'resetCollect 重置playroom家具', value: 'resetCollect' },
{ label: 'playroomDress 解锁所有playroom服装', value: 'playroomDress' },
{ label: 'resetAir 重置playroom飞行背包', value: 'resetAir' },
//
{ label: 'addFriend 对方uid 仅用于调试,对方好友列表不会增加', value: 'addFriend 对方uid' },
//
{ label: 'setDecorateArea 1 设置装饰区域id', value: 'setDecorateArea 1' },
{ label: 'setDecorateProgresse 1 设置装饰进度', value: 'setDecorateProgresse 1' },
{ label: 'setDecorateProgress 1 设置装饰进度', value: 'setDecorateProgress 1' },
//
{ label: 'handbook 解锁所有图鉴', value: 'handbook' },
//
{ label: 'guessColorReload 重置猜颜色游戏', value: 'guessColorReload' },
//
{ label: 'miningReload 重置挖矿游戏', value: 'miningReload' },
//
{ label: 'raceReload 重置赛跑游戏', value: 'raceReload' },
//
{ label: 'catnipReload 重置猫草大作战游戏', value: 'catnipReload' },
],
},
label: 'Gm:',

View File

@ -36,7 +36,7 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
<template>
<div
:class="[isDark]"
class="flex min-h-full flex-1 select-none overflow-x-hidden"
class="flex min-h-screen flex-1 select-none overflow-x-hidden"
>
<template v-if="toolbar">
<slot name="toolbar">
@ -46,7 +46,7 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
<!-- 左侧认证面板 -->
<AuthenticationFormView
v-if="authPanelLeft"
class="min-h-full w-2/5 flex-1"
class="min-h-screen w-2/5 flex-1"
transition-name="slide-left"
>
<template v-if="copyright" #copyright>
@ -133,38 +133,22 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
<style scoped>
.login-background {
background: linear-gradient(
-45deg,
#ff0000,
#ff4500,
#ff7f00,
#ffa500,
#ffff00,
#ffd700,
#ff6347,
#ff1493,
#ff0000
154deg,
#07070915 30%,
hsl(var(--primary) / 30%) 48%,
#07070915 64%
);
background-size: 400% 400%;
animation: rainbow-flow 8s ease-in-out infinite;
filter: blur(100px);
}
.dark {
.login-background {
background: linear-gradient(
-45deg,
#ff000080,
#ff450080,
#ff7f0080,
#ffa50080,
#ffff0080,
#ffd70080,
#ff634780,
#ff149380,
#ff000080
154deg,
#07070915 30%,
hsl(var(--primary) / 20%) 48%,
#07070915 64%
);
background-size: 400% 400%;
animation: rainbow-flow 8s ease-in-out infinite;
filter: blur(100px);
}
}

View File

@ -0,0 +1,23 @@
首次登入游戏 加入了拯救小猫的行列!
完成休息室 为小猫建造了一个温暖的家!
完成餐厅 为小猫准备了丰盛的食物!
完成浴室 把小猫整理得香喷喷的!
完成衣帽间 把小猫打扮得漂漂亮亮的!
获得新头像 收藏了一个新的头像!
获得新头像框 收藏了一个新的头像框!
获得新表情 收藏了一个新的表情!
获得新装饰品 获得了新的房间装饰!
获得新服装 获得了漂漂亮亮的新衣服!
完成卡册收集 收集了XXX的所有卡牌
完成全卡牌收集 收集了XXX的所有卡牌
获得锦标赛名次 在锦标赛中获得了第X名
获得锦标赛大奖 完成了锦标赛!
获得限时活动大奖 在XXX活动中获得了YYY
参加好友合作类活动 参加了合作伙伴活动!
获得拜访小游戏大奖 把邻居家的小猫关了起来!
获得拜访小游戏大奖 薅走了邻居的宠物币!
打开宠物宝藏 与好友们一起找到了宝藏!
拜访时点赞 给邻居点了个大大的赞!
完成图鉴收集成就 收集了XXX的所有物品
完成第X章所有场景 将章节X的变得焕然一新
流失用户回归 回来看小猫了!