yumi-admin/apps/src/views/operate/badge-config.vue
2026-04-13 19:31:36 +08:00

524 lines
14 KiB
Vue

<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
import { Page } from '@vben/common-ui';
import {
addBadge,
badgeTable,
deleteBadge,
giveBadge,
giveBadgeTable,
listUserNotAchieveBadge,
retrieveBadge,
updateBadge,
} from '#/api/legacy/badge';
import AccountInput from '#/components/account-input.vue';
import { formatDate } from '#/views/system/shared';
import {
Button,
Card,
Image,
Input,
Modal,
Pagination,
Select,
Space,
Table,
Tabs,
TabPane,
message,
} from 'antdv-next';
import { BADGE_GROUP_OPTIONS } from './constants';
import BadgeSourceEditModal from './components/badge-source-edit-modal.vue';
defineOptions({ name: 'OperateBadgeConfig' });
function createBadgeForm() {
return {
badgeKey: '',
badgeLevel: '',
badgeName: '',
id: '',
milestone: '',
type: '',
};
}
const badgeTypeOptions = BADGE_GROUP_OPTIONS.flatMap((group) =>
group.options.map((item) => ({
label: `${group.label} - ${item.label}`,
value: item.value,
})),
);
const activeTab = ref('rule');
const ruleLoading = ref(false);
const ruleList = ref<Array<Record<string, any>>>([]);
const ruleTotal = ref(0);
const ruleQuery = reactive({
badgeKey: '',
badgeName: '',
cursor: 1,
id: '',
limit: 20,
type: '',
});
const badgeFormOpen = ref(false);
const badgeFormSaving = ref(false);
const badgeForm = reactive(createBadgeForm());
const sourceEditOpen = ref(false);
const activeRuleRow = ref<Record<string, any> | null>(null);
const giveLoading = ref(false);
const giveList = ref<Array<Record<string, any>>>([]);
const giveTotal = ref(0);
const giveQuery = reactive({
cursor: 1,
limit: 20,
userId: '',
});
const revokeLoading = ref(false);
const revokeList = ref<Array<Record<string, any>>>([]);
const revokeQuery = reactive({
userId: '',
});
const ruleColumns = [
{ dataIndex: 'id', key: 'id', title: 'ID', width: 120 },
{ dataIndex: 'badgeName', key: 'badgeName', title: '徽章名称', width: 160 },
{ dataIndex: 'typeName', key: 'typeName', title: '类型', width: 160 },
{ dataIndex: 'badgeKey', key: 'badgeKey', title: '徽章Key', width: 160 },
{ dataIndex: 'badgeLevel', key: 'badgeLevel', title: '等级', width: 100 },
{ dataIndex: 'milestone', key: 'milestone', title: '里程碑', width: 100 },
{ dataIndex: 'badgePictures', key: 'badgePictures', title: '资源信息', width: 260 },
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 220 },
];
const giveColumns = [
{ dataIndex: 'id', key: 'id', title: '徽章规则ID', width: 120 },
{ dataIndex: 'selectUrl', key: 'selectUrl', title: '徽章图标', width: 100 },
{ dataIndex: 'badgeName', key: 'badgeName', title: '徽章名称', width: 160 },
{ dataIndex: 'typeName', key: 'typeName', title: '徽章类型', width: 160 },
{ dataIndex: 'badgeKey', key: 'badgeKey', title: '徽章Key', width: 160 },
{ dataIndex: 'badgeLevel', key: 'badgeLevel', title: '等级', width: 100 },
{ dataIndex: 'milestone', key: 'milestone', title: '可领取里程碑', width: 120 },
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
];
const revokeColumns = [
{ dataIndex: 'badgeConfigId', key: 'badgeConfigId', title: '徽章ID', width: 120 },
{ dataIndex: 'selectUrl', key: 'selectUrl', title: '徽章图标', width: 100 },
{ dataIndex: 'badgeName', key: 'badgeName', title: '徽章名称', width: 160 },
{ dataIndex: 'typeName', key: 'typeName', title: '徽章类型', width: 160 },
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
];
async function loadRules(reset = false) {
if (reset) {
ruleQuery.cursor = 1;
}
ruleLoading.value = true;
try {
const result = await badgeTable({ ...ruleQuery });
ruleList.value = result.records || [];
ruleTotal.value = result.total || 0;
} finally {
ruleLoading.value = false;
}
}
async function loadGive(reset = false) {
if (!giveQuery.userId) {
message.warning('请输入用户ID');
return;
}
if (reset) {
giveQuery.cursor = 1;
}
giveLoading.value = true;
try {
const result = await giveBadgeTable({ ...giveQuery });
giveList.value = result.records || [];
giveTotal.value = result.total || 0;
} finally {
giveLoading.value = false;
}
}
async function loadRevoke() {
if (!revokeQuery.userId) {
message.warning('请输入用户ID');
return;
}
revokeLoading.value = true;
try {
revokeList.value = (await listUserNotAchieveBadge(revokeQuery.userId)) || [];
} finally {
revokeLoading.value = false;
}
}
function handleRulePageChange(page: number, pageSize: number) {
ruleQuery.cursor = page;
ruleQuery.limit = pageSize;
void loadRules();
}
function handleGivePageChange(page: number, pageSize: number) {
giveQuery.cursor = page;
giveQuery.limit = pageSize;
void loadGive();
}
function openBadgeCreate() {
Object.assign(badgeForm, createBadgeForm());
badgeFormOpen.value = true;
}
function openBadgeEdit(row: Record<string, any>) {
Object.assign(badgeForm, {
badgeKey: row.badgeKey || '',
badgeLevel: row.badgeLevel || '',
badgeName: row.badgeName || '',
id: row.id || '',
milestone: row.milestone || '',
type: row.type || '',
});
badgeFormOpen.value = true;
}
async function handleBadgeSubmit() {
if (!badgeForm.type) {
message.warning('请选择类型');
return;
}
if (!badgeForm.badgeName.trim()) {
message.warning('请输入徽章名称');
return;
}
if (!badgeForm.badgeKey.trim()) {
message.warning('请输入徽章Key');
return;
}
if (!badgeForm.badgeLevel) {
message.warning('请输入等级');
return;
}
if (!badgeForm.milestone) {
message.warning('请输入里程碑');
return;
}
badgeFormSaving.value = true;
try {
if (badgeForm.id) {
await updateBadge({ ...badgeForm });
} else {
await addBadge({ ...badgeForm });
}
message.success('保存成功');
badgeFormOpen.value = false;
await loadRules(true);
} finally {
badgeFormSaving.value = false;
}
}
async function handleRuleDelete(row: Record<string, any>) {
await deleteBadge(row.id);
message.success('删除成功');
await loadRules(true);
}
async function handleGive(row: Record<string, any>) {
if (!giveQuery.userId) {
return;
}
await giveBadge(row.id, giveQuery.userId);
message.success('赠送成功');
await loadGive(false);
}
async function handleRetrieve(badgeId: number | string, userId: number | string) {
await retrieveBadge(badgeId, userId);
message.success('收回成功');
}
watch(
() => activeTab.value,
(value) => {
if (value === 'rule' && ruleList.value.length === 0) {
void loadRules(true);
}
},
{ immediate: true },
);
</script>
<template>
<Page title="徽章配置管理">
<Card>
<Tabs v-model:activeKey="activeTab">
<TabPane key="rule" tab="徽章配置" />
<TabPane key="give" tab="赠送 or 收回" />
<TabPane key="revoke" tab="收回" />
</Tabs>
<div v-if="activeTab === 'rule'">
<Space class="toolbar" wrap>
<Select option-label-prop="label"
v-model:value="ruleQuery.type"
allow-clear
placeholder="类型"
style="width: 220px"
@change="loadRules(true)"
:options="badgeTypeOptions.map((item) => ({ label: `${item.label}`, value: item.value as any }))"
/>
<Input
v-model:value="ruleQuery.id"
allow-clear
placeholder="ID"
style="width: 180px"
/>
<Input
v-model:value="ruleQuery.badgeName"
allow-clear
placeholder="徽章名称"
style="width: 180px"
/>
<Input
v-model:value="ruleQuery.badgeKey"
allow-clear
placeholder="徽章Key"
style="width: 180px"
/>
<Button :loading="ruleLoading" type="primary" @click="loadRules(true)">
搜索
</Button>
<Button type="primary" @click="openBadgeCreate">
新增
</Button>
</Space>
<Table
:columns="ruleColumns"
:data-source="ruleList"
:loading="ruleLoading"
:pagination="false"
row-key="id"
:scroll="{ x: 1560 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'badgePictures'">
<div class="picture-row">
<div
v-for="item in record.badgePictures || []"
:key="`${item.sysOrigin}-${item.selectUrl}`"
class="picture-item"
>
<Image :src="item.selectUrl" class="badge-image" />
<div>{{ item.sysOrigin }}</div>
</div>
</div>
</template>
<template v-else-if="column.key === 'createTime'">
{{ formatDate(record.createTime) }}
</template>
<template v-else-if="column.key === 'actions'">
<Space wrap>
<Button size="small" type="link" @click="openBadgeEdit(record)">
编辑规则
</Button>
<Button
size="small"
type="link"
@click="activeRuleRow = record; sourceEditOpen = true"
>
编辑资源
</Button>
<Button danger size="small" type="link" @click="handleRuleDelete(record)">
删除
</Button>
</Space>
</template>
</template>
</Table>
<div class="pager">
<Pagination
:current="ruleQuery.cursor"
:page-size="ruleQuery.limit"
:total="ruleTotal"
show-size-changer
@change="handleRulePageChange"
@showSizeChange="handleRulePageChange"
/>
</div>
</div>
<div v-else-if="activeTab === 'give'">
<Space class="toolbar" wrap>
<AccountInput
v-model:value="giveQuery.userId"
placeholder="用户ID"
style="width: 360px"
/>
<Button :loading="giveLoading" type="primary" @click="loadGive(true)">
搜索
</Button>
</Space>
<Table
:columns="giveColumns"
:data-source="giveList"
:loading="giveLoading"
:pagination="false"
row-key="id"
:scroll="{ x: 1320 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'selectUrl'">
<Image :src="record.selectUrl" class="badge-image" />
</template>
<template v-else-if="column.key === 'createTime'">
{{ formatDate(record.createTime) }}
</template>
<template v-else-if="column.key === 'actions'">
<Button
size="small"
type="link"
@click="record.presented
? handleRetrieve(record.id, giveQuery.userId)
: handleGive(record)"
>
{{ record.presented ? '收回' : '赠送' }}
</Button>
</template>
</template>
</Table>
<div class="pager">
<Pagination
:current="giveQuery.cursor"
:page-size="giveQuery.limit"
:total="giveTotal"
show-size-changer
@change="handleGivePageChange"
@showSizeChange="handleGivePageChange"
/>
</div>
</div>
<div v-else>
<Space class="toolbar" wrap>
<AccountInput
v-model:value="revokeQuery.userId"
placeholder="用户ID"
style="width: 360px"
/>
<Button :loading="revokeLoading" type="primary" @click="loadRevoke">
搜索
</Button>
</Space>
<Table
:columns="revokeColumns"
:data-source="revokeList"
:loading="revokeLoading"
:pagination="false"
row-key="badgeConfigId"
:scroll="{ x: 920 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'selectUrl'">
<Image :src="record.selectUrl" class="badge-image" />
</template>
<template v-else-if="column.key === 'createTime'">
{{ formatDate(record.createTime) }}
</template>
<template v-else-if="column.key === 'actions'">
<Button
size="small"
type="link"
@click="handleRetrieve(record.badgeConfigId, revokeQuery.userId)"
>
收回
</Button>
</template>
</template>
</Table>
</div>
</Card>
<Modal
:confirm-loading="badgeFormSaving"
:open="badgeFormOpen"
destroy-on-close
:title="badgeForm.id ? '编辑徽章规则' : '新增徽章规则'"
@cancel="badgeFormOpen = false"
@ok="handleBadgeSubmit"
>
<Space direction="vertical" style="width: 100%">
<Select
v-model:value="badgeForm.type"
:options="badgeTypeOptions"
option-label-prop="label"
placeholder="类型"
/>
<Input v-model:value="badgeForm.badgeName" placeholder="徽章名称" />
<Input v-model:value="badgeForm.badgeKey" placeholder="徽章Key" />
<Input v-model:value="badgeForm.badgeLevel" placeholder="等级" />
<Input v-model:value="badgeForm.milestone" placeholder="里程碑进度" />
</Space>
</Modal>
<BadgeSourceEditModal
:open="sourceEditOpen"
:row="activeRuleRow"
@close="sourceEditOpen = false"
@success="loadRules(false)"
/>
</Page>
</template>
<style scoped>
.toolbar {
margin-bottom: 16px;
}
.pager {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
.badge-image {
border-radius: 12px;
height: 48px;
object-fit: cover;
width: 48px;
}
.picture-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.picture-item {
align-items: center;
display: grid;
gap: 6px;
justify-items: center;
}
</style>