yumi-admin/apps/src/views/props/components/source-group-drawer.vue

652 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue';
import {
Button,
Drawer,
Form,
FormItem,
Input,
message,
Select,
Space,
Switch,
} from 'antdv-next';
import { listEmojiGroupsBySysOrigin } from '#/api/legacy/app-system';
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
import { listGiftBySysOrigin } from '#/api/legacy/gift';
import {
listNotFamilyBySysOriginType,
listSysOriginTypeList,
saveOrUpdatePropsActivityRewardGroup,
} from '#/api/legacy/props';
import { getDisplaySysOriginText } from '#/views/system/shared';
import {
formatRewardText,
getPropsTypeName,
getRewardBadgeType,
isBadgeRewardType,
isCurrencyRewardType,
isGameCouponType,
isPropsCouponType,
PROPS_SOURCE_GROUP_ADD_TYPES,
PROPS_SOURCE_GROUP_TYPE_MAP,
} from '../shared';
import RewardIcon from './reward-icon.vue';
const props = defineProps<{
open: boolean;
record: null | Record<string, any>;
sysOrigin: string;
}>();
const emit = defineEmits<{
close: [];
success: [];
}>();
const saving = ref(false);
const draftLoading = ref(false);
type DraftSourceOption = {
badgeName?: string;
cover?: string;
id: number | string;
name: string;
remark?: string;
sourceUrl?: string;
};
type RewardGroupItem = {
badgeName?: string;
content: number | string;
cover?: string;
detailType: string;
quantity: number | string;
remark: string;
sourceUrl?: string;
type: string;
};
const form = reactive<{
id: number | string;
name: string;
rewardConfigList: RewardGroupItem[];
shelfStatus: boolean;
sysOrigin: string;
}>({
id: '',
name: '',
rewardConfigList: [],
shelfStatus: true,
sysOrigin: '',
});
const draft = reactive<{
content: number | string;
quantity: number | string;
remark: string;
type: string;
}>({
content: '',
quantity: '',
remark: '',
type: '',
});
const sourceOptionsMap = reactive<
Record<string, { list: DraftSourceOption[]; loaded: boolean; loading: boolean }>
>({});
const selectedDraftResource = ref<DraftSourceOption | null>(null);
const title = computed(() => {
const sysOrigin = getDisplaySysOriginText(props.sysOrigin || form.sysOrigin);
const action = form.id ? '修改' : '添加';
return sysOrigin ? `${action}${sysOrigin}` : action;
});
const rewardItems = computed(() => form.rewardConfigList);
const sourceSelectOptions = computed<DraftSourceOption[]>(
() => sourceOptionsMap[draft.type]?.list || [],
);
const addTypeOptions = computed(() =>
PROPS_SOURCE_GROUP_ADD_TYPES.map((type) => ({
label: getAddTypeName(type),
value: type as any,
})),
);
const draftResourceOptions = computed(() =>
sourceSelectOptions.value.map((item) => ({
label: `${item.id} ${item.name}`,
value: item.id as any,
})),
);
function resetSourceOptionsMap() {
Object.keys(sourceOptionsMap).forEach((key) => {
delete sourceOptionsMap[key];
});
}
function createTypeStore(type: string) {
if (!sourceOptionsMap[type]) {
sourceOptionsMap[type] = { list: [], loaded: false, loading: false };
}
return sourceOptionsMap[type];
}
function resetForm() {
form.id = '';
form.name = '';
form.rewardConfigList = [];
form.shelfStatus = true;
form.sysOrigin = props.sysOrigin || '';
}
function resetDraft() {
draft.content = '';
draft.quantity = '';
draft.remark = '';
draft.type = '';
selectedDraftResource.value = null;
}
watch(
() => props.open,
(open) => {
if (!open) {
return;
}
resetSourceOptionsMap();
const record = props.record;
if (!record) {
resetForm();
resetDraft();
return;
}
form.id = record.id ?? '';
form.name = record.name ?? '';
form.rewardConfigList = (record.rewardConfigList || []).map((item: RewardGroupItem) => ({
...item,
}));
form.shelfStatus = Boolean(record.shelfStatus ?? true);
form.sysOrigin = props.sysOrigin || record.sysOrigin || '';
resetDraft();
},
{ immediate: true },
);
watch(
() => props.sysOrigin,
(value) => {
if (value) {
if (form.sysOrigin && form.sysOrigin !== value) {
resetSourceOptionsMap();
resetDraft();
}
form.sysOrigin = value;
}
},
{ immediate: true },
);
function needsRemoteOptions(type: string) {
return !(
['DIAMOND', 'GAME_COUPON', 'GOLD', 'PROP_COUPON', 'SPECIAL_ID'].includes(type)
);
}
async function loadOptions(type: string) {
if (!type || !form.sysOrigin || !needsRemoteOptions(type)) {
return;
}
const store = createTypeStore(type);
if (store.loaded || store.loading) {
return;
}
store.loading = true;
draftLoading.value = true;
try {
switch (type) {
case 'BADGE': {
const list = await listSysOriginTypeList(form.sysOrigin, 'BADGE');
store.list = list.map((item) => ({
badgeName: item.name || '',
cover: item.cover || '',
id: item.id ?? '',
name: item.name || '',
sourceUrl: item.sourceUrl || '',
}));
break;
}
case 'EMOJI': {
const list = await listEmojiGroupsBySysOrigin(form.sysOrigin);
store.list = list.map((item) => ({
cover: item.cover || '',
id: item.id ?? '',
name: item.groupName || '',
sourceUrl: '',
}));
break;
}
case 'GIFT': {
const list = await listGiftBySysOrigin(form.sysOrigin);
store.list = list.map((item) => ({
cover: item.giftPhoto || '',
id: item.id ?? '',
name: item.giftName || '',
sourceUrl: item.giftSourceUrl || '',
}));
break;
}
case 'NOBLE_VIP': {
const list = await listNotFamilyBySysOriginType(form.sysOrigin, type);
store.list = list.map((item) => ({
cover: item.cover || '',
id: item.id ?? '',
name: item.name || '',
sourceUrl: item.sourceUrl || '',
}));
break;
}
default: {
if (isBadgeRewardType(type)) {
const badgeType = getRewardBadgeType(type);
const list = await listBadgePictureBySysOrigin(form.sysOrigin, badgeType);
store.list = list.map((item) => ({
badgeName: item.badgeName || '',
cover: item.selectUrl || '',
id: item.badgeConfigId ?? '',
name: item.badgeName || '',
sourceUrl: item.animationUrl || '',
}));
break;
}
const list = await listSysOriginTypeList(form.sysOrigin, type);
store.list = list.map((item) => ({
cover: item.cover || '',
id: item.id ?? '',
name: item.name || '',
remark: item.remark || '',
sourceUrl: item.sourceUrl || '',
}));
}
}
store.loaded = true;
} finally {
store.loading = false;
draftLoading.value = false;
}
}
async function handleDraftTypeChange(value: string) {
resetDraft();
draft.type = value;
await loadOptions(value);
}
function handleDraftContentChange(value: number | string) {
if (!draft.type || !needsRemoteOptions(draft.type)) {
return;
}
const option = sourceSelectOptions.value.find(
(item) => String(item.id) === String(value),
);
selectedDraftResource.value = option || null;
}
function buildRewardItem(): null | RewardGroupItem {
if (!draft.type) {
return null;
}
const mapping = PROPS_SOURCE_GROUP_TYPE_MAP[draft.type];
if (!mapping) {
return null;
}
const base: RewardGroupItem = {
content: draft.content,
cover: selectedDraftResource.value?.cover || '',
detailType: draft.type,
quantity: draft.quantity,
remark: draft.remark,
sourceUrl: selectedDraftResource.value?.sourceUrl || '',
type: mapping.value,
};
if (isBadgeRewardType(draft.type)) {
base.badgeName = selectedDraftResource.value?.name || '';
}
return base;
}
function getAddTypeName(type: string) {
return PROPS_SOURCE_GROUP_TYPE_MAP[type]?.name || getPropsTypeName(type);
}
function validateDraft() {
if (!draft.type) {
message.warning('请选择奖励类型');
return false;
}
if (draft.type === 'SPECIAL_ID') {
if (!String(draft.content || '').trim()) {
message.warning('请输入靓号类型');
return false;
}
if (draft.quantity === '' || Number.isNaN(Number(draft.quantity))) {
message.warning('请输入数量');
return false;
}
return true;
}
if (isCurrencyRewardType(draft.type) || isGameCouponType(draft.type)) {
if (draft.content === '' || Number.isNaN(Number(draft.content))) {
message.warning('请输入数量');
return false;
}
return true;
}
if (isPropsCouponType(draft.type)) {
if (draft.content === '' || Number.isNaN(Number(draft.content))) {
message.warning('请输入券数');
return false;
}
if (draft.quantity === '' || Number.isNaN(Number(draft.quantity))) {
message.warning('请输入数量');
return false;
}
return true;
}
if (!draft.content) {
message.warning('请选择资源');
return false;
}
if (draft.type === 'CUSTOMIZE') {
if (!String(draft.remark || '').trim()) {
message.warning('请输入描述');
return false;
}
return true;
}
if (draft.quantity === '' || Number.isNaN(Number(draft.quantity))) {
message.warning('请输入数量/天数');
return false;
}
return true;
}
function addDraftItem() {
if (!validateDraft()) {
return;
}
const item = buildRewardItem();
if (!item) {
return;
}
form.rewardConfigList.push(item);
resetDraft();
}
function moveItem(index: number, direction: -1 | 1) {
const target = index + direction;
if (target < 0 || target >= form.rewardConfigList.length) {
return;
}
const list = [...form.rewardConfigList];
const current = list[index];
const next = list[target];
if (!current || !next) {
return;
}
list[index] = next;
list[target] = current;
form.rewardConfigList = list;
}
function removeItem(index: number) {
form.rewardConfigList.splice(index, 1);
}
async function handleSubmit() {
if (!String(form.name || '').trim()) {
message.warning('请输入类型名称');
return;
}
if (form.rewardConfigList.length === 0) {
message.warning('请先配置资源组内容');
return;
}
saving.value = true;
try {
await saveOrUpdatePropsActivityRewardGroup({
id: form.id || undefined,
name: form.name.trim(),
rewardConfigList: form.rewardConfigList.map((item: RewardGroupItem, index: number) => ({
content: item.content,
detailType: item.detailType,
quantity: item.quantity,
remark: item.remark,
sort: index + 1,
type: item.type,
})),
shelfStatus: form.shelfStatus,
sysOrigin: form.sysOrigin,
});
message.success('保存成功');
emit('success');
emit('close');
} finally {
saving.value = false;
}
}
</script>
<template>
<Drawer
:open="open"
:title="title"
width="980"
@close="emit('close')"
>
<Form layout="vertical">
<FormItem label="类型名称">
<Input v-model:value="form.name" placeholder="类别名称" />
</FormItem>
<FormItem label="上架状态">
<Switch
v-model:checked="form.shelfStatus"
checked-children="上架"
un-checked-children="下架"
/>
</FormItem>
<div class="reward-list">
<div
v-for="(item, index) in rewardItems"
:key="`${item.detailType}-${item.content}-${index}`"
class="reward-list__item"
>
<div class="reward-list__order">{{ Number(index) + 1 }}</div>
<RewardIcon :item="item" />
<div class="reward-list__info">
<div class="reward-list__title">
{{ getPropsTypeName(item.detailType || item.type) }}
</div>
<div class="reward-list__desc">
{{ formatRewardText(item) }}
</div>
</div>
<Space>
<Button size="small" @click="moveItem(Number(index), -1)">上移</Button>
<Button size="small" @click="moveItem(Number(index), 1)">下移</Button>
<Button danger size="small" @click="removeItem(Number(index))">删除</Button>
</Space>
</div>
</div>
<div class="draft-box">
<div class="draft-box__title">添加配置</div>
<FormItem label="奖励类型">
<Select
v-model:value="draft.type"
:options="addTypeOptions"
option-label-prop="label"
placeholder="请选择奖励类型"
@change="handleDraftTypeChange"
/>
</FormItem>
<template v-if="draft.type === 'SPECIAL_ID'">
<FormItem label="靓号类型">
<Input v-model:value="draft.content" placeholder="请输入靓号类型" />
</FormItem>
<FormItem label="数量">
<Input v-model:value="draft.quantity" placeholder="请输入数量" />
</FormItem>
</template>
<template v-else-if="isCurrencyRewardType(draft.type) || isGameCouponType(draft.type)">
<FormItem label="数量">
<Input v-model:value="draft.content" placeholder="请输入数量" />
</FormItem>
</template>
<template v-else-if="isPropsCouponType(draft.type)">
<FormItem label="券数">
<Input v-model:value="draft.content" placeholder="请输入券数" />
</FormItem>
<FormItem label="数量">
<Input v-model:value="draft.quantity" placeholder="请输入数量" />
</FormItem>
</template>
<template v-else>
<FormItem label="资源">
<Select
v-model:value="draft.content"
:options="draftResourceOptions"
:loading="draftLoading"
option-filter-prop="label"
option-label-prop="label"
placeholder="请选择资源"
show-search
@change="handleDraftContentChange"
/>
</FormItem>
<div v-if="selectedDraftResource" class="draft-preview">
<RewardIcon :item="selectedDraftResource" :size="56" />
<div class="draft-preview__meta">
<div>{{ selectedDraftResource.name }}</div>
<div class="draft-preview__id">
ID: {{ selectedDraftResource.id }}
</div>
</div>
</div>
<FormItem v-if="draft.type === 'CUSTOMIZE'" label="描述">
<Input v-model:value="draft.remark" placeholder="请输入描述" />
</FormItem>
<FormItem v-else label="数量/天数">
<Input
v-model:value="draft.quantity"
:placeholder="
isBadgeRewardType(draft.type)
? '请输入天数0 表示永久'
: '请输入数量/天数'
"
/>
</FormItem>
</template>
<Button type="primary" @click="addDraftItem">加入配置</Button>
</div>
</Form>
<template #footer>
<Space>
<Button @click="emit('close')">取消</Button>
<Button :loading="saving" type="primary" @click="handleSubmit">
保存
</Button>
</Space>
</template>
</Drawer>
</template>
<style scoped>
.draft-box {
background: rgb(248 250 252);
border: 1px solid rgb(226 232 240);
border-radius: 16px;
margin-top: 16px;
padding: 16px;
}
.draft-box__title {
color: rgb(15 23 42);
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.draft-preview {
align-items: center;
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.draft-preview__id {
color: rgb(100 116 139);
font-size: 12px;
}
.reward-list {
display: grid;
gap: 12px;
}
.reward-list__item {
align-items: center;
background: #fff;
border: 1px solid rgb(226 232 240);
border-radius: 16px;
display: grid;
gap: 12px;
grid-template-columns: 40px 56px 1fr auto;
padding: 12px;
}
.reward-list__info {
min-width: 0;
}
.reward-list__order {
align-items: center;
background: rgb(241 245 249);
border-radius: 999px;
color: rgb(15 23 42);
display: inline-flex;
font-size: 12px;
font-weight: 600;
height: 32px;
justify-content: center;
width: 32px;
}
.reward-list__title {
color: rgb(15 23 42);
font-weight: 600;
}
.reward-list__desc {
color: rgb(100 116 139);
font-size: 13px;
margin-top: 4px;
}
</style>