feat: 用户中心新增每日任务组件并在头像菜单中集成
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 领取任务奖励输入
|
||||||
|
/// </summary>
|
||||||
|
public class ClaimTaskRewardInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务等级(1=1000w任务,2=3000w任务)
|
||||||
|
/// </summary>
|
||||||
|
public int TaskLevel { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日任务状态输出
|
||||||
|
/// </summary>
|
||||||
|
public class DailyTaskStatusOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 今日消耗的尊享包Token数
|
||||||
|
/// </summary>
|
||||||
|
public long TodayConsumedTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务列表
|
||||||
|
/// </summary>
|
||||||
|
public List<DailyTaskItem> Tasks { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日任务项
|
||||||
|
/// </summary>
|
||||||
|
public class DailyTaskItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务等级(1=1000w任务,2=3000w任务)
|
||||||
|
/// </summary>
|
||||||
|
public int Level { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务描述
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务要求的Token消耗量
|
||||||
|
/// </summary>
|
||||||
|
public long RequiredTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 奖励的Token数量
|
||||||
|
/// </summary>
|
||||||
|
public long RewardTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务状态:0=未完成,1=可领取,2=已领取
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务进度百分比(0-100)
|
||||||
|
/// </summary>
|
||||||
|
public decimal Progress { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
using Volo.Abp.Users;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask;
|
||||||
|
using Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||||
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
|
using Yi.Framework.AiHub.Domain.Managers;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日任务服务
|
||||||
|
/// </summary>
|
||||||
|
[Authorize]
|
||||||
|
public class DailyTaskService : ApplicationService
|
||||||
|
{
|
||||||
|
private readonly ISqlSugarRepository<DailyTaskRewardRecordAggregateRoot> _dailyTaskRepository;
|
||||||
|
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
||||||
|
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||||||
|
private readonly ILogger<DailyTaskService> _logger;
|
||||||
|
|
||||||
|
// 任务配置
|
||||||
|
private readonly Dictionary<int, (long RequiredTokens, long RewardTokens, string Name, string Description)>
|
||||||
|
_taskConfigs = new()
|
||||||
|
{
|
||||||
|
{ 1, (10000000, 2000000, "尊享包1000w token任务", "累积使用尊享包 1000w token") }, // 1000w消耗 -> 200w奖励
|
||||||
|
{ 2, (30000000, 4000000, "尊享包3000w token任务", "累积使用尊享包 3000w token") } // 3000w消耗 -> 600w奖励
|
||||||
|
};
|
||||||
|
|
||||||
|
public DailyTaskService(
|
||||||
|
ISqlSugarRepository<DailyTaskRewardRecordAggregateRoot> dailyTaskRepository,
|
||||||
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||||
|
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
||||||
|
ILogger<DailyTaskService> logger)
|
||||||
|
{
|
||||||
|
_dailyTaskRepository = dailyTaskRepository;
|
||||||
|
_messageRepository = messageRepository;
|
||||||
|
_premiumPackageRepository = premiumPackageRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取今日任务状态
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<DailyTaskStatusOutput> GetTodayTaskStatusAsync()
|
||||||
|
{
|
||||||
|
var userId = CurrentUser.GetId();
|
||||||
|
var today = DateTime.Today;
|
||||||
|
|
||||||
|
// 1. 统计今日尊享包Token消耗量
|
||||||
|
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
||||||
|
|
||||||
|
// 2. 查询今日已领取的任务
|
||||||
|
var claimedTasks = await _dailyTaskRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId && x.TaskDate == today)
|
||||||
|
.Select(x => new { x.TaskLevel, x.IsRewarded })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// 3. 构建任务列表
|
||||||
|
var tasks = new List<DailyTaskItem>();
|
||||||
|
foreach (var (level, config) in _taskConfigs)
|
||||||
|
{
|
||||||
|
var claimed = claimedTasks.FirstOrDefault(x => x.TaskLevel == level);
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (claimed != null && claimed.IsRewarded)
|
||||||
|
{
|
||||||
|
status = 2; // 已领取
|
||||||
|
}
|
||||||
|
else if (todayConsumed >= config.RequiredTokens)
|
||||||
|
{
|
||||||
|
status = 1; // 可领取
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status = 0; // 未完成
|
||||||
|
}
|
||||||
|
|
||||||
|
var progress = todayConsumed >= config.RequiredTokens
|
||||||
|
? 100
|
||||||
|
: Math.Round((decimal)todayConsumed / config.RequiredTokens * 100, 2);
|
||||||
|
|
||||||
|
tasks.Add(new DailyTaskItem
|
||||||
|
{
|
||||||
|
Level = level,
|
||||||
|
Name = config.Name,
|
||||||
|
Description = config.Description,
|
||||||
|
RequiredTokens = config.RequiredTokens,
|
||||||
|
RewardTokens = config.RewardTokens,
|
||||||
|
Status = status,
|
||||||
|
Progress = progress
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DailyTaskStatusOutput
|
||||||
|
{
|
||||||
|
TodayConsumedTokens = todayConsumed,
|
||||||
|
Tasks = tasks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 领取任务奖励
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task ClaimTaskRewardAsync(ClaimTaskRewardInput input)
|
||||||
|
{
|
||||||
|
var userId = CurrentUser.GetId();
|
||||||
|
var today = DateTime.Today;
|
||||||
|
|
||||||
|
// 1. 验证任务等级
|
||||||
|
if (!_taskConfigs.TryGetValue(input.TaskLevel, out var taskConfig))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException($"无效的任务等级: {input.TaskLevel}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否已领取
|
||||||
|
var existingRecord = await _dailyTaskRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId && x.TaskDate == today && x.TaskLevel == input.TaskLevel)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
if (existingRecord != null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("今日该任务奖励已领取,请明天再来!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证今日Token消耗是否达标
|
||||||
|
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
||||||
|
if (todayConsumed < taskConfig.RequiredTokens)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(
|
||||||
|
$"Token消耗未达标!需要 {taskConfig.RequiredTokens / 10000}w,当前 {todayConsumed / 10000}w");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 创建奖励包(使用 PremiumPackageManager)
|
||||||
|
|
||||||
|
var premiumPackage =
|
||||||
|
new PremiumPackageAggregateRoot(userId, taskConfig.RewardTokens, $"每日任务:{taskConfig.Name}")
|
||||||
|
{
|
||||||
|
PurchaseAmount = 0, // 奖励不需要付费
|
||||||
|
Remark = $"{today:yyyy-MM-dd} 每日任务奖励"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _premiumPackageRepository.InsertAsync(premiumPackage);
|
||||||
|
|
||||||
|
// 5. 记录领取记录
|
||||||
|
var record = new DailyTaskRewardRecordAggregateRoot(userId, input.TaskLevel, today, taskConfig.RewardTokens)
|
||||||
|
{
|
||||||
|
Remark = $"完成任务{input.TaskLevel},名称:{taskConfig.Name},消耗 {todayConsumed / 10000}w token"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _dailyTaskRepository.InsertAsync(record);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
$"用户 {userId} 领取每日任务 {input.TaskLevel} 奖励成功,获得 {taskConfig.RewardTokens / 10000}w tokens");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取今日尊享包Token消耗量
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户ID</param>
|
||||||
|
/// <param name="today">今日日期</param>
|
||||||
|
/// <returns>消耗的Token总数</returns>
|
||||||
|
private async Task<long> GetTodayPremiumTokenConsumptionAsync(Guid userId, DateTime today)
|
||||||
|
{
|
||||||
|
var tomorrow = today.AddDays(1);
|
||||||
|
|
||||||
|
// 查询今日所有使用尊享包模型的消息(role=system 表示消耗)
|
||||||
|
var totalTokens = await _messageRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Where(x => x.Role == "system") // system角色表示实际消耗
|
||||||
|
.Where(x => PremiumPackageConst.ModeIds.Contains(x.ModelId)) // 尊享包模型
|
||||||
|
.Where(x => x.CreationTime >= today && x.CreationTime < tomorrow)
|
||||||
|
.SumAsync(x => x.TokenUsage.TotalTokenCount);
|
||||||
|
|
||||||
|
return totalTokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日任务奖励领取记录
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_DailyTaskRewardRecord")]
|
||||||
|
[SugarIndex($"index_{nameof(UserId)}_{nameof(TaskDate)}",
|
||||||
|
nameof(UserId), OrderByType.Asc,
|
||||||
|
nameof(TaskDate), OrderByType.Desc)]
|
||||||
|
public class DailyTaskRewardRecordAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||||
|
{
|
||||||
|
public DailyTaskRewardRecordAggregateRoot()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DailyTaskRewardRecordAggregateRoot(Guid userId, int taskLevel, DateTime taskDate, long rewardTokens)
|
||||||
|
{
|
||||||
|
UserId = userId;
|
||||||
|
TaskLevel = taskLevel;
|
||||||
|
TaskDate = taskDate.Date; // 确保只存储日期部分
|
||||||
|
RewardTokens = rewardTokens;
|
||||||
|
IsRewarded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务等级(1=1000w任务,2=3000w任务)
|
||||||
|
/// </summary>
|
||||||
|
public int TaskLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务日期(只包含日期,不包含时间)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime TaskDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 奖励的Token数量
|
||||||
|
/// </summary>
|
||||||
|
public long RewardTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已发放奖励
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRewarded { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注信息
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
}
|
||||||
12
Yi.Ai.Vue3/src/api/dailyTask/index.ts
Normal file
12
Yi.Ai.Vue3/src/api/dailyTask/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { get, post } from '@/utils/request';
|
||||||
|
import type { DailyTaskStatusOutput, ClaimTaskRewardInput } from './types';
|
||||||
|
|
||||||
|
// 获取今日任务状态
|
||||||
|
export function getTodayTaskStatus() {
|
||||||
|
return get<DailyTaskStatusOutput>('/daily-task/today-task-status').json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 领取任务奖励
|
||||||
|
export function claimTaskReward(data: ClaimTaskRewardInput) {
|
||||||
|
return post<void>('/daily-task/claim-task-reward', data).json();
|
||||||
|
}
|
||||||
21
Yi.Ai.Vue3/src/api/dailyTask/types.ts
Normal file
21
Yi.Ai.Vue3/src/api/dailyTask/types.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// 每日任务状态
|
||||||
|
export interface DailyTaskStatusOutput {
|
||||||
|
todayConsumedTokens: number; // 今日消耗的尊享包Token数
|
||||||
|
tasks: DailyTaskItem[]; // 任务列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每日任务项
|
||||||
|
export interface DailyTaskItem {
|
||||||
|
level: number; // 任务等级(1=1000w任务,2=3000w任务)
|
||||||
|
name: string; // 任务名称
|
||||||
|
description: string; // 任务描述
|
||||||
|
requiredTokens: number; // 任务要求的Token消耗量
|
||||||
|
rewardTokens: number; // 奖励的Token数量
|
||||||
|
status: number; // 任务状态:0=未完成,1=可领取,2=已领取
|
||||||
|
progress: number; // 任务进度百分比(0-100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 领取任务奖励输入
|
||||||
|
export interface ClaimTaskRewardInput {
|
||||||
|
taskLevel: number; // 任务等级(1=1000w任务,2=3000w任务)
|
||||||
|
}
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { getTodayTaskStatus, claimTaskReward } from '@/api/dailyTask';
|
||||||
|
import type { DailyTaskStatusOutput, DailyTaskItem } from '@/api/dailyTask/types';
|
||||||
|
|
||||||
|
const taskData = ref<DailyTaskStatusOutput | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const claiming = ref<{ [key: number]: boolean }>({});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchTaskStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchTaskStatus() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getTodayTaskStatus();
|
||||||
|
taskData.value = res.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.message || '获取任务状态失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleClaim(task: DailyTaskItem) {
|
||||||
|
if (task.status !== 1) return;
|
||||||
|
|
||||||
|
claiming.value[task.level] = true;
|
||||||
|
try {
|
||||||
|
await claimTaskReward({ taskLevel: task.level });
|
||||||
|
ElMessage.success(`恭喜!获得 ${formatTokenDisplay(task.rewardTokens)} token`);
|
||||||
|
|
||||||
|
// 刷新任务状态
|
||||||
|
await fetchTaskStatus();
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.message || '领取奖励失败');
|
||||||
|
} finally {
|
||||||
|
claiming.value[task.level] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化 Token 显示(单位:万)
|
||||||
|
function formatTokenDisplay(tokens: number): string {
|
||||||
|
return `${(tokens / 10000).toFixed(0)}w`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务状态文本
|
||||||
|
function getStatusText(task: DailyTaskItem): string {
|
||||||
|
switch (task.status) {
|
||||||
|
case 0:
|
||||||
|
return '未达成';
|
||||||
|
case 1:
|
||||||
|
return `领取 ${formatTokenDisplay(task.rewardTokens)}`;
|
||||||
|
case 2:
|
||||||
|
return '✓ 已领取';
|
||||||
|
default:
|
||||||
|
return '未知';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取按钮样式类
|
||||||
|
function getButtonClass(task: DailyTaskItem): string {
|
||||||
|
switch (task.status) {
|
||||||
|
case 0:
|
||||||
|
return 'btn-disabled';
|
||||||
|
case 1:
|
||||||
|
return 'btn-claimable';
|
||||||
|
case 2:
|
||||||
|
return 'btn-claimed';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取进度条颜色
|
||||||
|
function getProgressColor(task: DailyTaskItem): string {
|
||||||
|
if (task.status === 2) return '#FFD700'; // 已完成:金色
|
||||||
|
if (task.status === 1) return '#67C23A'; // 可领取:绿色
|
||||||
|
return '#409EFF'; // 进行中:蓝色
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-loading="loading" class="daily-task-container">
|
||||||
|
<div class="task-header">
|
||||||
|
<h2>每日任务</h2>
|
||||||
|
<p class="task-desc">完成每日任务,领取额外尊享包 Token 奖励,可累加重复</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="taskData" class="task-content">
|
||||||
|
<!-- 今日消耗统计 -->
|
||||||
|
<div class="consumption-card">
|
||||||
|
<div class="consumption-icon">🔥</div>
|
||||||
|
<div class="consumption-info">
|
||||||
|
<div class="consumption-label">今日尊享包消耗</div>
|
||||||
|
<div class="consumption-value">
|
||||||
|
{{ formatTokenDisplay(taskData.todayConsumedTokens) }} Tokens
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 任务列表 -->
|
||||||
|
<div class="task-list">
|
||||||
|
<div
|
||||||
|
v-for="task in taskData.tasks"
|
||||||
|
:key="task.level"
|
||||||
|
class="task-item"
|
||||||
|
:class="{
|
||||||
|
'task-completed': task.status === 2,
|
||||||
|
'task-claimable': task.status === 1
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="task-icon">
|
||||||
|
<span v-if="task.status === 2">🎁</span>
|
||||||
|
<span v-else-if="task.status === 1">✨</span>
|
||||||
|
<span v-else>📦</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-main">
|
||||||
|
<div class="task-title">
|
||||||
|
<span class="task-name">{{ task.name }}</span>
|
||||||
|
<span class="task-badge" :class="`badge-status-${task.status}`">
|
||||||
|
{{ task.status === 0 ? '未完成' : task.status === 1 ? '可领取' : '已完成' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-description">
|
||||||
|
{{ task.description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-progress-section">
|
||||||
|
<div class="progress-info">
|
||||||
|
<span class="progress-text">
|
||||||
|
{{ formatTokenDisplay(taskData.todayConsumedTokens) }} / {{ formatTokenDisplay(task.requiredTokens) }}
|
||||||
|
</span>
|
||||||
|
<span class="progress-percent">{{ task.progress.toFixed(0) }}%</span>
|
||||||
|
</div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="Math.min(task.progress, 100)"
|
||||||
|
:color="getProgressColor(task)"
|
||||||
|
:show-text="false"
|
||||||
|
:stroke-width="8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-reward">
|
||||||
|
<span class="reward-label">奖励:</span>
|
||||||
|
<span class="reward-value">{{ formatTokenDisplay(task.rewardTokens) }} Tokens</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-action">
|
||||||
|
<el-button
|
||||||
|
:class="getButtonClass(task)"
|
||||||
|
:disabled="task.status !== 1 || claiming[task.level]"
|
||||||
|
:loading="claiming[task.level]"
|
||||||
|
size="large"
|
||||||
|
@click="handleClaim(task)"
|
||||||
|
>
|
||||||
|
{{ getStatusText(task) }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<div class="task-tips">
|
||||||
|
<el-alert
|
||||||
|
title="温馨提示"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<ul>
|
||||||
|
<li>任务每日 0 点自动重置</li>
|
||||||
|
<li>使用尊享包模型消耗的 Token 计入任务进度</li>
|
||||||
|
<li>完成任务后立即领取奖励,奖励直接发放到您的尊享包账户</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.daily-task-container {
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-header h2 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-desc {
|
||||||
|
margin: 0;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消耗统计卡片 */
|
||||||
|
.consumption-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.consumption-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consumption-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consumption-label {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consumption-value {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 任务列表 */
|
||||||
|
.task-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 20px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2px solid #e4e7ed;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.task-claimable {
|
||||||
|
border-color: #67C23A;
|
||||||
|
background: linear-gradient(to right, rgba(103, 194, 58, 0.05) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.task-completed {
|
||||||
|
border-color: #FFD700;
|
||||||
|
background: linear-gradient(to right, rgba(255, 215, 0, 0.05) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-badge {
|
||||||
|
padding: 2px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status-0 {
|
||||||
|
background: #f4f4f5;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status-1 {
|
||||||
|
background: #f0f9ff;
|
||||||
|
color: #67C23A;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status-2 {
|
||||||
|
background: #fffbf0;
|
||||||
|
color: #FFD700;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-description {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-progress-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-percent {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-reward {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reward-label {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reward-value {
|
||||||
|
color: #F56C6C;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.btn-disabled {
|
||||||
|
background: #f4f4f5;
|
||||||
|
border-color: #e4e7ed;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-claimable {
|
||||||
|
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-claimable:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 4px 16px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 20px rgba(255, 215, 0, 0.6);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 40px rgba(255, 215, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-claimed {
|
||||||
|
background: #f4f4f5;
|
||||||
|
border-color: #e4e7ed;
|
||||||
|
color: #67C23A;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示信息 */
|
||||||
|
.task-tips {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tips ul {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tips li {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -65,9 +65,11 @@ const navItems = [
|
|||||||
// { name: 'permission', label: '权限管理', icon: 'Key' },
|
// { name: 'permission', label: '权限管理', icon: 'Key' },
|
||||||
// { name: 'userInfo', label: '用户信息', icon: 'User' },
|
// { name: 'userInfo', label: '用户信息', icon: 'User' },
|
||||||
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||||
|
|
||||||
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
||||||
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
||||||
{ name: 'premiumService', label: '尊享服务', icon: 'ColdDrink' },
|
{ name: 'premiumService', label: '尊享服务', icon: 'ColdDrink' },
|
||||||
|
{ name: 'dailyTask', label: '每日任务', icon: 'Trophy' }
|
||||||
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
||||||
];
|
];
|
||||||
function openDialog() {
|
function openDialog() {
|
||||||
@@ -344,6 +346,9 @@ function onProductPackage() {
|
|||||||
<template #apiKey>
|
<template #apiKey>
|
||||||
<APIKeyManagement />
|
<APIKeyManagement />
|
||||||
</template>
|
</template>
|
||||||
|
<template #dailyTask>
|
||||||
|
<daily-task />
|
||||||
|
</template>
|
||||||
<template #rechargeLog>
|
<template #rechargeLog>
|
||||||
<recharge-log />
|
<recharge-log />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
1
Yi.Ai.Vue3/types/components.d.ts
vendored
1
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -10,6 +10,7 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
|
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
|
||||||
APIKeyManagement: typeof import('./../src/components/userPersonalCenter/components/APIKeyManagement.vue')['default']
|
APIKeyManagement: typeof import('./../src/components/userPersonalCenter/components/APIKeyManagement.vue')['default']
|
||||||
|
DailyTask: typeof import('./../src/components/userPersonalCenter/components/DailyTask.vue')['default']
|
||||||
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
|
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
|
|||||||
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
@@ -6,7 +6,6 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_WEB_ENV: string;
|
readonly VITE_WEB_ENV: string;
|
||||||
readonly VITE_WEB_BASE_API: string;
|
readonly VITE_WEB_BASE_API: string;
|
||||||
readonly VITE_API_URL: string;
|
readonly VITE_API_URL: string;
|
||||||
readonly VITE_BUILD_COMPRESS: string;
|
|
||||||
readonly VITE_SSO_SEVER_URL: string;
|
readonly VITE_SSO_SEVER_URL: string;
|
||||||
readonly VITE_APP_VERSION: string;
|
readonly VITE_APP_VERSION: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user