From bc04695ac8a24eac7f493b42f5e25567de00432b Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 21 May 2026 20:06:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A6=96=E5=86=B2=E5=A5=96?= =?UTF-8?q?=E5=8A=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/src/api/legacy/resident-activity.ts | 27 + .../routes/modules/resident-activity.ts | 7 + .../views/app-system/home-popup-manager.vue | 87 ++- .../props/components/source-group-drawer.vue | 120 +++- .../first-recharge-reward-config.vue | 640 ++++++++++++++++++ .../sync_admin_first_recharge_reward_menu.sql | 54 ++ ...min_resident_activity_and_baishun_menu.sql | 57 +- 7 files changed, 985 insertions(+), 7 deletions(-) create mode 100644 apps/src/views/resident-activity/first-recharge-reward-config.vue create mode 100644 scripts/sql/sync_admin_first_recharge_reward_menu.sql diff --git a/apps/src/api/legacy/resident-activity.ts b/apps/src/api/legacy/resident-activity.ts index 9c7d301..85aff77 100644 --- a/apps/src/api/legacy/resident-activity.ts +++ b/apps/src/api/legacy/resident-activity.ts @@ -9,6 +9,8 @@ const ROOM_TURNOVER_REWARD_BASE = '/go/resident-activity/room-turnover-reward'; const VIP_BASE = '/go/resident-activity/vip'; const RECHARGE_REWARD_BASE = '/go/resident-activity/recharge-reward'; +const FIRST_RECHARGE_REWARD_BASE = + '/go/resident-activity/first-recharge-reward'; const TASK_CENTER_BASE = '/go/resident-activity/task-center'; const VOICE_ROOM_RED_PACKET_BASE = '/go/resident-activity/voice-room-red-packet'; const VOICE_ROOM_ROCKET_BASE = '/go/resident-activity/voice-room-rocket'; @@ -175,6 +177,31 @@ export async function pageResidentRechargeRewardClaimRecords( ); } +export async function getResidentFirstRechargeRewardConfig(sysOrigin: string) { + return requestClient.get>( + `${FIRST_RECHARGE_REWARD_BASE}/config`, + { params: { sysOrigin } }, + ); +} + +export async function saveResidentFirstRechargeRewardConfig( + data: Record, +) { + return requestClient.post>( + `${FIRST_RECHARGE_REWARD_BASE}/config/save`, + data, + ); +} + +export async function pageResidentFirstRechargeRewardGrantRecords( + params: Record, +) { + return requestClient.get>( + `${FIRST_RECHARGE_REWARD_BASE}/grant-record/page`, + { params }, + ); +} + export async function getResidentDailyTaskConfig( sysOrigin: string, taskCategory = 'DAILY', diff --git a/apps/src/router/routes/modules/resident-activity.ts b/apps/src/router/routes/modules/resident-activity.ts index 4b52a81..680d122 100644 --- a/apps/src/router/routes/modules/resident-activity.ts +++ b/apps/src/router/routes/modules/resident-activity.ts @@ -42,6 +42,13 @@ const routes: RouteRecordRaw[] = [ component: () => import('#/views/resident-activity/recharge-reward-config.vue'), meta: { title: '充值奖励配置' }, }, + { + name: 'ResidentFirstRechargeRewardConfig', + path: 'first-recharge-reward/config', + component: () => + import('#/views/resident-activity/first-recharge-reward-config.vue'), + meta: { title: '首冲奖励' }, + }, { name: 'ResidentDailyTaskConfig', path: 'daily-task/config', diff --git a/apps/src/views/app-system/home-popup-manager.vue b/apps/src/views/app-system/home-popup-manager.vue index 01dfbd4..241d4af 100644 --- a/apps/src/views/app-system/home-popup-manager.vue +++ b/apps/src/views/app-system/home-popup-manager.vue @@ -9,6 +9,7 @@ import { useAccessStore } from '@vben/stores'; import { Button, Card, + Image, Input, InputNumber, message, @@ -26,6 +27,11 @@ import { pageAppHomePopups, saveAppHomePopup, } from '#/api/legacy/app-system'; +import { + getAccessImgUrl, + OSS_FILE_BUCKETS, + simpleUploadFile, +} from '#/api/legacy/oss'; import InlineFilterField from '#/views/_shared/inline-filter-field.vue'; import InlineFilterToolbar from '#/views/_shared/inline-filter-toolbar.vue'; import { getAllowedSysOrigins } from '#/views/system/shared'; @@ -61,8 +67,10 @@ const sysOriginOptions = computed(() => { const loading = ref(false); const saving = ref(false); +const imageUploading = ref(false); const formOpen = ref(false); const formTitle = ref('新增弹窗'); +const imageInputRef = ref(null); const list = ref([]); const total = ref(0); @@ -148,6 +156,31 @@ function handleEdit(record: AppHomePopupItem) { formOpen.value = true; } +function pickImage() { + imageInputRef.value?.click(); +} + +async function handleImageUpload(event: Event) { + const target = event.target as HTMLInputElement; + const file = target.files?.[0]; + if (!file) { + return; + } + imageUploading.value = true; + try { + const result = await simpleUploadFile(file, OSS_FILE_BUCKETS.other); + form.image = getAccessImgUrl(result.name); + message.success('上传成功'); + } finally { + imageUploading.value = false; + target.value = ''; + } +} + +function clearImage() { + form.image = ''; +} + function validateForm() { if (!String(form.popupKey || '').trim()) { message.warning('请输入弹窗 key'); @@ -340,7 +373,28 @@ watch(
- + +
+ +
未上传
+ + + + +
@@ -403,5 +457,34 @@ watch( color: hsl(var(--muted-foreground)); font-size: 13px; } - +.upload-row { + align-items: center; + display: flex; + gap: 12px; +} + +.image-preview, +.image-empty { + border-radius: 8px; + height: 96px; + width: 160px; +} + +.image-preview { + object-fit: cover; +} + +.image-empty { + align-items: center; + background: hsl(var(--muted)); + color: hsl(var(--muted-foreground)); + display: flex; + font-size: 13px; + justify-content: center; +} + +.hidden-input { + display: none; +} + diff --git a/apps/src/views/props/components/source-group-drawer.vue b/apps/src/views/props/components/source-group-drawer.vue index 7654bc7..7d818e0 100644 --- a/apps/src/views/props/components/source-group-drawer.vue +++ b/apps/src/views/props/components/source-group-drawer.vue @@ -49,7 +49,7 @@ const emit = defineEmits<{ success: []; }>(); -type ResourceKind = 'badge' | 'gift' | 'props' | 'vip'; +type ResourceKind = 'badge' | 'currency' | 'gift' | 'props' | 'vip'; type TypeOption = { icon: string; @@ -93,6 +93,7 @@ const sourceLoading = ref(false); const resourceKeyword = ref(''); const currentResourcePage = ref(1); const selectedType = ref(''); +const goldAmount = ref(null); const form = reactive<{ id: number | string; @@ -121,6 +122,12 @@ const typeOptions = computed(() => [ label: item.name, value: String(item.value), })), + { + icon: 'lucide:coins', + kind: 'currency', + label: '金币', + value: 'GOLD', + }, { icon: 'lucide:award', kind: 'badge', @@ -302,6 +309,9 @@ function getResourceKey(kind: ResourceKind, detailType: string, id: number | str } function getRewardKind(item: RewardGroupItem): ResourceKind { + if (item.type === 'GOLD') { + return 'currency'; + } if (item.type === 'GIFT') { return 'gift'; } @@ -338,6 +348,9 @@ function getItemName(item: RewardGroupItem) { } function getQuantityLabel(item: RewardGroupItem) { + if (item.type === 'GOLD') { + return '金币'; + } if (item.type === 'GIFT') { return '数量'; } @@ -348,6 +361,9 @@ function getQuantityLabel(item: RewardGroupItem) { } function getDefaultQuantity(resource: DisplayResource) { + if (resource.kind === 'currency') { + return 0; + } if (resource.kind === 'gift') { return 1; } @@ -361,6 +377,9 @@ function getDefaultQuantity(resource: DisplayResource) { } function getRewardType(resource: DisplayResource) { + if (resource.kind === 'currency') { + return resource.type; + } if (resource.kind === 'gift') { return 'GIFT'; } @@ -396,6 +415,7 @@ function selectType(option: TypeOption) { selectedType.value = option.value; resourceKeyword.value = ''; currentResourcePage.value = 1; + goldAmount.value = null; void loadOptions(option.value); } @@ -403,6 +423,9 @@ async function loadOptions(type: string) { if (!type || !form.sysOrigin) { return; } + if (type === 'GOLD') { + return; + } const store = createTypeStore(type); if (store.loaded || store.loading) { return; @@ -493,6 +516,33 @@ function toggleResource(resource: DisplayResource) { form.rewardConfigList.push(buildRewardItem(resource)); } +function addGoldItem() { + const amount = Number(goldAmount.value); + if (!Number.isFinite(amount) || amount <= 0) { + message.warning('请输入金币数量'); + return; + } + const item: RewardGroupItem = { + amount, + content: amount, + detailType: 'GOLD', + name: '金币', + quantity: 0, + remark: '金币', + type: 'GOLD', + typeLabel: '金币', + }; + const index = form.rewardConfigList.findIndex( + (reward) => reward.type === 'GOLD' && reward.detailType === 'GOLD', + ); + if (index !== -1) { + form.rewardConfigList[index] = item; + } else { + form.rewardConfigList.push(item); + } + goldAmount.value = null; +} + function removeItem(index: number) { form.rewardConfigList.splice(index, 1); } @@ -522,9 +572,21 @@ function updateItemQuantity(index: number, value: number | string | null) { if (!item) { return; } + if (item.type === 'GOLD') { + item.content = value ?? 0; + item.amount = value ?? 0; + return; + } item.quantity = value ?? 0; } +function getItemInputValue(item: RewardGroupItem) { + if (item.type === 'GOLD') { + return Number(item.content || 0); + } + return Number(item.quantity || 0); +} + function validateItems() { for (const item of form.rewardConfigList) { const quantity = Number(item.quantity); @@ -532,6 +594,14 @@ function validateItems() { message.warning(`${getItemName(item)} 的${getQuantityLabel(item)}不能为空`); return false; } + if (item.type === 'GOLD') { + const amount = Number(item.content); + if (!Number.isFinite(amount) || amount <= 0) { + message.warning('金币数量必须大于 0'); + return false; + } + continue; + } if (item.type === 'GIFT' && quantity <= 0) { message.warning(`${getItemName(item)} 的数量必须大于 0`); return false; @@ -693,7 +763,22 @@ watch(filteredResources, () => {
-
+
+
添加金币
+
+ + +
+
+ +
资源列表 @@ -804,7 +889,7 @@ watch(filteredResources, () => { @@ -965,6 +1050,35 @@ watch(filteredResources, () => { gap: 8px; } +.gold-editor { + align-content: start; + background: #fbfcfe; + border: 1px solid #edf0f3; + border-radius: 8px; + display: grid; + gap: 10px; + min-height: 420px; + padding: 14px; +} + +.gold-editor__title { + color: #111827; + font-size: 15px; + font-weight: 700; +} + +.gold-editor__row { + align-items: center; + display: grid; + gap: 8px; + grid-template-columns: minmax(240px, 360px) auto; + justify-content: start; +} + +.gold-editor__input { + width: 100%; +} + .field-head, .resource-head, .selected-panel__head { diff --git a/apps/src/views/resident-activity/first-recharge-reward-config.vue b/apps/src/views/resident-activity/first-recharge-reward-config.vue new file mode 100644 index 0000000..6a5e83e --- /dev/null +++ b/apps/src/views/resident-activity/first-recharge-reward-config.vue @@ -0,0 +1,640 @@ + + + + + diff --git a/scripts/sql/sync_admin_first_recharge_reward_menu.sql b/scripts/sql/sync_admin_first_recharge_reward_menu.sql new file mode 100644 index 0000000..e588160 --- /dev/null +++ b/scripts/sql/sync_admin_first_recharge_reward_menu.sql @@ -0,0 +1,54 @@ +SET NAMES utf8mb4; + +INSERT INTO sys_menu ( + id, + parent_id, + menu_name, + path, + menu_type, + icon, + create_user, + update_user, + sort, + status, + router, + alias +) +SELECT + 1700000264, + (SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1), + '首冲奖励', + 'resident-activity/first-recharge-reward-config/index', + 2, + 'form', + '0', + '0', + 8, + 0, + 'first-recharge-reward/config', + 'ResidentFirstRechargeRewardConfig' +WHERE NOT EXISTS ( + SELECT 1 FROM sys_menu WHERE alias = 'ResidentFirstRechargeRewardConfig' +); + +UPDATE sys_menu +SET + parent_id = ( + SELECT resident_menu.id + FROM ( + SELECT id + FROM sys_menu + WHERE alias = 'ResidentActivityManager' + LIMIT 1 + ) AS resident_menu + ), + menu_name = '首冲奖励', + path = 'resident-activity/first-recharge-reward-config/index', + menu_type = 2, + icon = 'form', + create_user = '0', + update_user = '0', + sort = 8, + status = 0, + router = 'first-recharge-reward/config' +WHERE alias = 'ResidentFirstRechargeRewardConfig'; diff --git a/scripts/sql/sync_admin_resident_activity_and_baishun_menu.sql b/scripts/sql/sync_admin_resident_activity_and_baishun_menu.sql index 8564806..71614c6 100644 --- a/scripts/sql/sync_admin_resident_activity_and_baishun_menu.sql +++ b/scripts/sql/sync_admin_resident_activity_and_baishun_menu.sql @@ -416,6 +416,59 @@ SET router = 'recharge-reward/config' WHERE alias = 'ResidentRechargeRewardConfig'; +INSERT INTO sys_menu ( + id, + parent_id, + menu_name, + path, + menu_type, + icon, + create_user, + update_user, + sort, + status, + router, + alias +) +SELECT + 1700000264, + (SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1), + '首冲奖励', + 'resident-activity/first-recharge-reward-config/index', + 2, + 'form', + '0', + '0', + 8, + 0, + 'first-recharge-reward/config', + 'ResidentFirstRechargeRewardConfig' +WHERE NOT EXISTS ( + SELECT 1 FROM sys_menu WHERE alias = 'ResidentFirstRechargeRewardConfig' +); + +UPDATE sys_menu +SET + parent_id = ( + SELECT resident_menu.id + FROM ( + SELECT id + FROM sys_menu + WHERE alias = 'ResidentActivityManager' + LIMIT 1 + ) AS resident_menu + ), + menu_name = '首冲奖励', + path = 'resident-activity/first-recharge-reward-config/index', + menu_type = 2, + icon = 'form', + create_user = '0', + update_user = '0', + sort = 8, + status = 0, + router = 'first-recharge-reward/config' +WHERE alias = 'ResidentFirstRechargeRewardConfig'; + INSERT INTO sys_menu ( id, parent_id, @@ -439,7 +492,7 @@ SELECT 'form', '0', '0', - 8, + 9, 0, 'daily-task/config', 'ResidentDailyTaskConfig' @@ -464,7 +517,7 @@ SET icon = 'form', create_user = '0', update_user = '0', - sort = 8, + sort = 9, status = 0, router = 'daily-task/config' WHERE alias = 'ResidentDailyTaskConfig';