diff --git a/apps/src/views/resident-activity/components/resource-select-drawer.vue b/apps/src/views/resident-activity/components/resource-select-drawer.vue new file mode 100644 index 0000000..e7c0025 --- /dev/null +++ b/apps/src/views/resident-activity/components/resource-select-drawer.vue @@ -0,0 +1,442 @@ + + + + + diff --git a/apps/src/views/resident-activity/voice-room-rocket.vue b/apps/src/views/resident-activity/voice-room-rocket.vue index a04f794..e254d95 100644 --- a/apps/src/views/resident-activity/voice-room-rocket.vue +++ b/apps/src/views/resident-activity/voice-room-rocket.vue @@ -29,14 +29,10 @@ import { saveVoiceRoomRocketLevelConfigs, saveVoiceRoomRocketRewardConfigs, } from '#/api/legacy/resident-activity'; -import { listBadgePictureBySysOrigin } from '#/api/legacy/badge'; -import { listGiftBySysOrigin } from '#/api/legacy/gift'; -import { - listNotFamilyBySysOriginType, - listSysOriginTypeList, -} from '#/api/legacy/props'; import { getAllowedSysOrigins } from '#/views/system/shared'; +import ResourceSelectDrawer from './components/resource-select-drawer.vue'; + defineOptions({ name: 'ResidentVoiceRoomRocketPage' }); const RIYADH_TIMEZONE = 'Asia/Riyadh'; @@ -95,12 +91,6 @@ const rewardLoading = ref(false); const configSaving = ref(false); const levelSaving = ref(false); const rewardSaving = ref(false); -const rocketResourceLoading = ref(false); -const rocketResourceRows = ref([]); -const rewardResourceLoading = reactive>({}); -const rewardResourceRows = reactive>({}); - -const ROCKET_RESOURCE_TYPE = 'CUSTOMIZE'; const configForm = reactive({ broadcastDurationSeconds: 7, @@ -120,6 +110,23 @@ const configForm = reactive({ const levelRows = ref([]); const rewardRows = ref([]); +const resourcePickerOpen = ref(false); +const resourcePickerTitle = ref('选择资源'); +const resourcePickerDefaultType = ref('ALL_PROPS'); +const resourcePickerFixedType = ref(false); +const resourcePickerTarget = ref< + | { + field: 'rocketAnimationUrl' | 'rocketIconUrl'; + kind: 'level'; + row: Record; + } + | { + kind: 'reward'; + row: Record; + } + | null +>(null); + const sceneOptions = [ { label: '贡献第一', value: 'TOP1' }, { label: '点火人', value: 'IGNITE' }, @@ -303,93 +310,6 @@ function normalizeRewardRows(rows: Array> | undefined) { })); } -function normalizePropsResource(item: Record, type: string): ResourceOption { - return { - amount: item.amount, - cover: String(item.cover || item.img || item.icon || ''), - id: item.id, - name: String(item.name || item.title || item.remark || item.id || ''), - remark: item.remark, - sourceUrl: String(item.sourceUrl || item.url || item.cover || ''), - type, - }; -} - -function normalizeGiftResource(item: Record): ResourceOption { - return { - amount: item.giftCandy, - cover: String(item.giftPhoto || ''), - id: item.id, - name: String(item.giftName || item.id || ''), - sourceUrl: String(item.giftSourceUrl || item.giftPhoto || ''), - type: 'GIFT', - }; -} - -function normalizeBadgeResource(item: Record): ResourceOption { - return { - cover: String(item.selectUrl || item.cover || ''), - id: item.badgeConfigId || item.id, - name: String(item.badgeName || item.name || item.id || ''), - sourceUrl: String(item.animationUrl || item.selectUrl || item.cover || ''), - type: 'BADGE', - }; -} - -function optionLabel(item: ResourceOption) { - return `${item.id} / ${item.name}`.trim(); -} - -function toResourceSelectOptions(rows: ResourceOption[]) { - return rows - .filter((item) => item.id !== undefined && item.id !== null && item.id !== '') - .map((item) => ({ - label: optionLabel(item), - value: String(item.id), - })); -} - -function toRocketResourceSelectOptions( - record: Record, - key: 'rocketAnimationUrl' | 'rocketIconUrl', -) { - const options = rocketResourceRows.value - .map((item) => { - const value = - key === 'rocketIconUrl' - ? String(item.cover || item.sourceUrl || '') - : String(item.sourceUrl || item.cover || ''); - return { - label: optionLabel(item), - value, - }; - }) - .filter((item) => item.value); - const current = String(record[key] || ''); - if (current && !options.some((item) => item.value === current)) { - options.unshift({ label: current, value: current }); - } - return options; -} - -async function loadRocketResources() { - if (!selectedSysOrigin.value || rocketResourceLoading.value) { - return; - } - rocketResourceLoading.value = true; - try { - const result = await listSysOriginTypeList( - selectedSysOrigin.value, - ROCKET_RESOURCE_TYPE, - ); - rocketResourceRows.value = (result || []).map((item) => - normalizePropsResource(item, ROCKET_RESOURCE_TYPE), - ); - } finally { - rocketResourceLoading.value = false; - } -} - function rewardResourceType(rewardType: string) { const value = String(rewardType || '').trim(); const mapping: Record = { @@ -403,57 +323,65 @@ function rewardResourceType(rewardType: string) { return mapping[value] || ''; } +function rewardTypeText(rewardType: string) { + return ( + rewardTypeOptions.find((item) => item.value === rewardType)?.label || + rewardType || + '资源' + ); +} + function isRewardResourceDisabled(row: Record) { return row.rewardType === 'GOLD' || row.rewardType === 'NONE'; } -async function loadRewardResources(rewardType: string) { - const type = String(rewardType || '').trim(); - const sourceType = rewardResourceType(type); - if ( - !selectedSysOrigin.value || - !sourceType || - (rewardResourceRows[type] || []).length > 0 - ) { - return; - } - rewardResourceLoading[type] = true; - try { - if (type === 'GIFT') { - const result = await listGiftBySysOrigin(selectedSysOrigin.value); - rewardResourceRows[type] = (result || []).map(normalizeGiftResource); - return; - } - if (type === 'BADGE') { - const result = await listBadgePictureBySysOrigin( - selectedSysOrigin.value, - 'ACTIVITY', - ); - rewardResourceRows[type] = (result || []).map(normalizeBadgeResource); - return; - } - const loader = - type === 'VIP_CARD' ? listNotFamilyBySysOriginType : listSysOriginTypeList; - const result = await loader(selectedSysOrigin.value, sourceType); - rewardResourceRows[type] = (result || []).map((item) => - normalizePropsResource(item, sourceType), - ); - } finally { - rewardResourceLoading[type] = false; - } +function openLevelResourcePicker( + row: Record, + field: 'rocketAnimationUrl' | 'rocketIconUrl', +) { + resourcePickerTarget.value = { + field, + kind: 'level', + row, + }; + resourcePickerTitle.value = + field === 'rocketIconUrl' ? '选择火箭图标资源' : '选择发射动画资源'; + resourcePickerDefaultType.value = 'ALL_PROPS'; + resourcePickerFixedType.value = false; + resourcePickerOpen.value = true; } -function rewardResourceOptions(row: Record) { - const rows = rewardResourceRows[row.rewardType] || []; - const options = toResourceSelectOptions(rows); - const current = String(row.rewardItemId || '').trim(); - if (current && !options.some((item) => item.value === current)) { - options.unshift({ - label: `${current} / ${row.rewardName || '已配置资源'}`, - value: current, - }); +function openRewardResourcePicker(row: Record) { + if (isRewardResourceDisabled(row)) { + return; + } + const sourceType = rewardResourceType(row.rewardType); + if (!sourceType) { + return; + } + resourcePickerTarget.value = { + kind: 'reward', + row, + }; + resourcePickerTitle.value = `选择${rewardTypeText(row.rewardType)}资源`; + resourcePickerDefaultType.value = sourceType; + resourcePickerFixedType.value = true; + resourcePickerOpen.value = true; +} + +function clearLevelResource( + row: Record, + field: 'rocketAnimationUrl' | 'rocketIconUrl', +) { + row[field] = ''; +} + +function clearRewardResource(row: Record) { + row.rewardItemId = null; + row.rewardCover = ''; + if (row.rewardName !== '金币' && row.rewardName !== '轮空') { + row.rewardName = ''; } - return options; } function handleRewardSceneChange(row: Record) { @@ -463,21 +391,32 @@ function handleRewardSceneChange(row: Record) { } } -function handleRewardResourceChange( - row: Record, - value?: null | number | string, -) { - const selected = - (rewardResourceRows[row.rewardType] || []).find( - (item) => String(item.id) === String(value), - ) || null; - if (!selected) { - row.rewardItemId = value || null; +function handleResourceSelect(item: ResourceOption) { + const target = resourcePickerTarget.value; + if (!target) { return; } - row.rewardItemId = selected.id; - row.rewardName = selected.name; - row.rewardCover = selected.cover || selected.sourceUrl || ''; + if (target.kind === 'level') { + const value = + target.field === 'rocketIconUrl' + ? String(item.cover || item.sourceUrl || '') + : String(item.sourceUrl || item.cover || ''); + target.row[target.field] = value; + } else { + target.row.rewardItemId = item.id; + target.row.rewardName = item.name || String(item.id || ''); + target.row.rewardCover = item.cover || item.sourceUrl || ''; + } + resourcePickerOpen.value = false; + resourcePickerTarget.value = null; +} + +function resourceUrlName(url: string) { + const text = String(url || '').trim(); + if (!text) { + return '未选择'; + } + return text; } async function loadConfig() { @@ -523,7 +462,7 @@ async function loadRewards() { } async function loadAll() { - await Promise.all([loadConfig(), loadLevels(), loadRewards(), loadRocketResources()]); + await Promise.all([loadConfig(), loadLevels(), loadRewards()]); } async function saveConfig() { @@ -630,7 +569,6 @@ function handleRewardTypeChange(row: Record) { if (row.rewardName === '金币' || row.rewardName === '轮空') { row.rewardName = ''; } - void loadRewardResources(row.rewardType); } function validateRewards() { @@ -714,10 +652,6 @@ watch( watch( selectedSysOrigin, () => { - rocketResourceRows.value = []; - Object.keys(rewardResourceRows).forEach((key) => { - delete rewardResourceRows[key]; - }); void loadAll(); }, { immediate: true }, @@ -865,30 +799,62 @@ watch(rewardLevel, () => { /> @@ -1121,14 +1111,43 @@ watch(rewardLevel, () => { width: 140px; } -.table-resource-select { - width: 220px; -} - .table-number { width: 110px; } +.resource-cell { + display: grid; + gap: 8px; + min-width: 220px; +} + +.resource-cell__preview { + align-items: center; + color: #6b7280; + display: flex; + font-size: 12px; + gap: 8px; + min-height: 36px; +} + +.resource-cell__preview img { + border-radius: 4px; + flex: 0 0 auto; + height: 32px; + object-fit: cover; + width: 32px; +} + +.resource-cell__preview span, +.resource-cell__summary { + color: #6b7280; + font-size: 12px; + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .cover-preview { display: flex; align-items: center;