2026-05-22 19:18:11 +08:00

318 lines
7.8 KiB
Vue

<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>