Add skill for gameplay entry type workflows
This commit is contained in:
@@ -48,6 +48,8 @@ import type {
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileReferralInviteCenterResponse,
|
||||
ProfileSaveArchiveSummary,
|
||||
ProfileTaskCenterResponse,
|
||||
ProfileTaskItem,
|
||||
ProfileWalletLedgerResponse,
|
||||
RedeemProfileRewardCodeResponse,
|
||||
} from '../../../packages/shared/src/contracts/runtime';
|
||||
@@ -61,7 +63,9 @@ import {
|
||||
import { copyTextToClipboard } from '../../services/clipboard';
|
||||
import {
|
||||
getRpgProfileReferralInviteCenter,
|
||||
getRpgProfileTasks,
|
||||
getRpgProfileWalletLedger,
|
||||
claimRpgProfileTaskReward,
|
||||
redeemRpgProfileReferralInviteCode,
|
||||
redeemRpgProfileRewardCode,
|
||||
} from '../../services/rpg-entry/rpgProfileClient';
|
||||
@@ -2004,6 +2008,7 @@ const WALLET_LEDGER_SOURCE_LABELS: Record<string, string> = {
|
||||
asset_operation_consume: '资产操作消耗',
|
||||
asset_operation_refund: '资产操作退回',
|
||||
redeem_code_reward: '兑换码奖励',
|
||||
daily_task_reward: '每日任务奖励',
|
||||
};
|
||||
|
||||
function formatWalletLedgerAmount(amountDelta: number) {
|
||||
@@ -2119,6 +2124,142 @@ function WalletLedgerModal({
|
||||
);
|
||||
}
|
||||
|
||||
const PROFILE_TASK_STATUS_LABELS: Record<ProfileTaskItem['status'], string> = {
|
||||
incomplete: '未完成',
|
||||
claimable: '可领取',
|
||||
claimed: '已领取',
|
||||
disabled: '已停用',
|
||||
};
|
||||
|
||||
function ProfileTaskCenterModal({
|
||||
center,
|
||||
isLoading,
|
||||
error,
|
||||
success,
|
||||
claimingTaskId,
|
||||
fallbackBalance,
|
||||
onClose,
|
||||
onRetry,
|
||||
onClaim,
|
||||
}: {
|
||||
center: ProfileTaskCenterResponse | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
success: string | null;
|
||||
claimingTaskId: string | null;
|
||||
fallbackBalance: number;
|
||||
onClose: () => void;
|
||||
onRetry: () => void;
|
||||
onClaim: (taskId: string) => void;
|
||||
}) {
|
||||
const tasks = center?.tasks ?? [];
|
||||
const walletBalance = center?.walletBalance ?? fallbackBalance;
|
||||
|
||||
return (
|
||||
<div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
|
||||
<div className="platform-recharge-modal w-full max-w-md overflow-hidden rounded-[1.4rem]">
|
||||
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
|
||||
<div>
|
||||
<div className="text-base font-black">每日任务</div>
|
||||
<div className="mt-1 text-xs font-semibold text-[var(--platform-text-soft)]">
|
||||
{walletBalance}光点
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭每日任务"
|
||||
onClick={onClose}
|
||||
className="platform-modal-close flex h-9 w-9 items-center justify-center rounded-full"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-3 px-5 py-5">
|
||||
{error ? (
|
||||
<div className="platform-profile-error rounded-2xl px-3 py-2 text-xs font-semibold">
|
||||
<div>{error}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRetry}
|
||||
className="platform-primary-button mt-3 rounded-2xl px-4 py-2 text-xs font-black"
|
||||
>
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{success ? (
|
||||
<div className="platform-profile-success rounded-2xl px-3 py-2 text-xs font-semibold">
|
||||
{success}
|
||||
</div>
|
||||
) : null}
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="h-20 animate-pulse rounded-2xl bg-white/10"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : tasks.length === 0 ? (
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-8 text-center text-sm font-semibold text-[var(--platform-text-soft)]">
|
||||
暂无任务
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{tasks.map((task) => {
|
||||
const isClaimable = task.status === 'claimable';
|
||||
const isClaiming = claimingTaskId === task.taskId;
|
||||
const progressLabel = `${Math.min(task.progressCount, task.threshold)}/${task.threshold}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={task.taskId}
|
||||
className="platform-subpanel rounded-2xl px-4 py-4"
|
||||
>
|
||||
<div className="flex min-w-0 items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-base font-black text-[var(--platform-text-strong)]">
|
||||
{task.title}
|
||||
</div>
|
||||
<div className="mt-1 text-xs font-semibold text-[var(--platform-text-soft)]">
|
||||
{progressLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 text-right">
|
||||
<div className="text-sm font-black text-[var(--platform-text-strong)]">
|
||||
+{task.rewardPoints}
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] font-semibold text-[var(--platform-text-soft)]">
|
||||
{PROFILE_TASK_STATUS_LABELS[task.status]}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!isClaimable || Boolean(claimingTaskId)}
|
||||
onClick={() => onClaim(task.taskId)}
|
||||
className="platform-primary-button mt-3 w-full rounded-2xl px-4 py-2.5 text-sm font-black disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{isClaiming
|
||||
? '领取中'
|
||||
: task.status === 'claimed'
|
||||
? '已领取'
|
||||
: isClaimable
|
||||
? '领取'
|
||||
: '未完成'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RewardCodeRedeemModal({
|
||||
value,
|
||||
isSubmitting,
|
||||
@@ -2528,6 +2669,14 @@ export function RpgEntryHomeView({
|
||||
null,
|
||||
);
|
||||
const [isLoadingWalletLedger, setIsLoadingWalletLedger] = useState(false);
|
||||
const [isTaskCenterOpen, setIsTaskCenterOpen] = useState(false);
|
||||
const [taskCenter, setTaskCenter] = useState<ProfileTaskCenterResponse | null>(
|
||||
null,
|
||||
);
|
||||
const [taskCenterError, setTaskCenterError] = useState<string | null>(null);
|
||||
const [isLoadingTaskCenter, setIsLoadingTaskCenter] = useState(false);
|
||||
const [claimingTaskId, setClaimingTaskId] = useState<string | null>(null);
|
||||
const [taskClaimSuccess, setTaskClaimSuccess] = useState<string | null>(null);
|
||||
const [profilePopupPanel, setProfilePopupPanel] =
|
||||
useState<ProfilePopupPanel | null>(null);
|
||||
const [referralCenter, setReferralCenter] =
|
||||
@@ -2961,6 +3110,24 @@ export function RpgEntryHomeView({
|
||||
setIsWalletLedgerOpen(true);
|
||||
loadWalletLedger();
|
||||
};
|
||||
const loadTaskCenter = () => {
|
||||
setTaskCenterError(null);
|
||||
setIsLoadingTaskCenter(true);
|
||||
void getRpgProfileTasks()
|
||||
.then(setTaskCenter)
|
||||
.catch((error: unknown) => {
|
||||
setTaskCenter(null);
|
||||
setTaskCenterError(
|
||||
error instanceof Error ? error.message : '读取每日任务失败',
|
||||
);
|
||||
})
|
||||
.finally(() => setIsLoadingTaskCenter(false));
|
||||
};
|
||||
const openTaskCenterPanel = () => {
|
||||
setIsTaskCenterOpen(true);
|
||||
setTaskClaimSuccess(null);
|
||||
loadTaskCenter();
|
||||
};
|
||||
const loadReferralCenter = useCallback(() => {
|
||||
setIsLoadingReferral(true);
|
||||
setIsReferralCenterInitialized(false);
|
||||
@@ -3070,6 +3237,27 @@ export function RpgEntryHomeView({
|
||||
})
|
||||
.finally(() => setIsSubmittingRewardCode(false));
|
||||
};
|
||||
const claimTaskReward = (taskId: string) => {
|
||||
if (claimingTaskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setClaimingTaskId(taskId);
|
||||
setTaskCenterError(null);
|
||||
setTaskClaimSuccess(null);
|
||||
void claimRpgProfileTaskReward(taskId)
|
||||
.then((response) => {
|
||||
setTaskCenter(response.center);
|
||||
setTaskClaimSuccess(`已领取 ${response.rewardPoints} 光点`);
|
||||
void onRechargeSuccess?.();
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
setTaskCenterError(
|
||||
error instanceof Error ? error.message : '领取任务奖励失败',
|
||||
);
|
||||
})
|
||||
.finally(() => setClaimingTaskId(null));
|
||||
};
|
||||
const clearWorkSearch = () => {
|
||||
setActiveWorkSearchKeyword('');
|
||||
setDesktopSearchKeyword('');
|
||||
@@ -3714,6 +3902,17 @@ export function RpgEntryHomeView({
|
||||
aria-label="常用功能"
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<ProfileShortcutButton
|
||||
label="每日任务"
|
||||
subLabel={
|
||||
<>
|
||||
<span>领10</span>
|
||||
<Coins className="h-3 w-3" />
|
||||
</>
|
||||
}
|
||||
icon={Star}
|
||||
onClick={openTaskCenterPanel}
|
||||
/>
|
||||
<ProfileShortcutButton
|
||||
label="邀请好友"
|
||||
subLabel={
|
||||
@@ -4197,6 +4396,19 @@ export function RpgEntryHomeView({
|
||||
/>
|
||||
) : null}
|
||||
{rewardCodeModal}
|
||||
{isTaskCenterOpen ? (
|
||||
<ProfileTaskCenterModal
|
||||
center={taskCenter}
|
||||
isLoading={isLoadingTaskCenter}
|
||||
error={taskCenterError}
|
||||
success={taskClaimSuccess}
|
||||
claimingTaskId={claimingTaskId}
|
||||
fallbackBalance={remainingNarrativeCoins}
|
||||
onClose={() => setIsTaskCenterOpen(false)}
|
||||
onRetry={loadTaskCenter}
|
||||
onClaim={claimTaskReward}
|
||||
/>
|
||||
) : null}
|
||||
{isProfilePlayStatsOpen ? (
|
||||
<ProfilePlayedWorksModal
|
||||
stats={profilePlayStats}
|
||||
@@ -4303,6 +4515,19 @@ export function RpgEntryHomeView({
|
||||
</div>
|
||||
</div>
|
||||
{rewardCodeModal}
|
||||
{isTaskCenterOpen ? (
|
||||
<ProfileTaskCenterModal
|
||||
center={taskCenter}
|
||||
isLoading={isLoadingTaskCenter}
|
||||
error={taskCenterError}
|
||||
success={taskClaimSuccess}
|
||||
claimingTaskId={claimingTaskId}
|
||||
fallbackBalance={remainingNarrativeCoins}
|
||||
onClose={() => setIsTaskCenterOpen(false)}
|
||||
onRetry={loadTaskCenter}
|
||||
onClaim={claimTaskReward}
|
||||
/>
|
||||
) : null}
|
||||
{profilePopupPanel ? (
|
||||
<ProfileReferralModal
|
||||
panel={profilePopupPanel}
|
||||
|
||||
Reference in New Issue
Block a user