web-模块、权限、成就

This commit is contained in:
2025-10-25 17:45:47 +08:00
parent f7057a0cc9
commit 5d211faee1
32 changed files with 4024 additions and 876 deletions

View File

@@ -0,0 +1,233 @@
/**
* @description 成就相关API
* @author yslg
* @since 2025-10-24
*/
import { api } from '@/apis/index';
import type { Achievement, UserAchievement, UserAchievementProgress, AchievementVO, AchievementEvent, ResultDomain, PageParam } from '@/types';
/**
* 成就API服务
*/
export const achievementApi = {
// ==================== 成就定义管理(管理员)====================
/**
* 创建成就
* @param achievement 成就信息
* @returns Promise<ResultDomain<Achievement>>
*/
async createAchievement(achievement: Achievement): Promise<ResultDomain<Achievement>> {
const response = await api.post<Achievement>('/achievements/achievement', achievement);
return response.data;
},
/**
* 更新成就
* @param achievement 成就信息
* @returns Promise<ResultDomain<Achievement>>
*/
async updateAchievement(achievement: Achievement): Promise<ResultDomain<Achievement>> {
const response = await api.put<Achievement>('/achievements/achievement', achievement);
return response.data;
},
/**
* 删除成就
* @param achievement 成就信息包含achievementID
* @returns Promise<ResultDomain<void>>
*/
async deleteAchievement(achievement: Achievement): Promise<ResultDomain<void>> {
const response = await api.delete<void>('/achievements/achievement', achievement);
return response.data;
},
/**
* 获取所有成就列表
* @param filter 过滤条件type: 成就类型, level: 成就等级)
* @returns Promise<ResultDomain<Achievement>>
*/
async getAllAchievements(filter?: Partial<Achievement>): Promise<ResultDomain<Achievement>> {
const response = await api.post<Achievement>('/achievements/list', filter || {});
return response.data;
},
/**
* 分页查询成就
* @param filter 过滤条件
* @param pageParam 分页参数
* @returns Promise<ResultDomain<Achievement>>
*/
async getAchievementPage(filter?: Partial<Achievement>, pageParam?: PageParam): Promise<ResultDomain<Achievement>> {
const response = await api.post<Achievement>('/achievements/page', { filter, pageParam });
return response.data;
},
/**
* 获取成就详情
* @param achievementID 成就ID
* @returns Promise<ResultDomain<Achievement>>
*/
async getAchievementDetail(achievementID: string): Promise<ResultDomain<Achievement>> {
const response = await api.get<Achievement>(`/achievements/detail/${achievementID}`);
return response.data;
},
// ==================== 用户成就查询 ====================
/**
* 获取用户已获得的成就
* @param userID 用户ID
* @param type 成就类型(可选)
* @returns Promise<ResultDomain<UserAchievement>>
*/
async getUserAchievements(userID: string, type?: number): Promise<ResultDomain<UserAchievement>> {
const params: Record<string, any> = {};
if (type !== undefined) {
params.type = type;
}
const response = await api.get<UserAchievement>(`/achievements/user/${userID}`, params);
return response.data;
},
/**
* 获取当前用户的成就列表(含进度信息)
* 返回所有成就,包括:
* - 已获得的成就:显示获得时间
* - 未获得的成就:显示当前进度
* @param type 成就类型(可选)
* @returns Promise<ResultDomain<AchievementVO>>
*/
async getMyAchievements(type?: number): Promise<ResultDomain<AchievementVO>> {
const params: Record<string, any> = {};
if (type !== undefined) {
params.type = type;
}
const response = await api.get<AchievementVO>('/achievements/my', params);
return response.data;
},
/**
* 检查用户是否已获得成就
* @param userID 用户ID
* @param achievementID 成就ID
* @returns Promise<ResultDomain<boolean>>
*/
async hasAchievement(userID: string, achievementID: string): Promise<ResultDomain<boolean>> {
const response = await api.get<boolean>(`/achievements/check/${userID}/${achievementID}`);
return response.data;
},
// ==================== 成就授予(管理员)====================
/**
* 手动授予用户成就
* @param userID 用户ID
* @param achievementID 成就ID
* @returns Promise<ResultDomain<UserAchievement>>
*/
async grantAchievement(userID: string, achievementID: string): Promise<ResultDomain<UserAchievement>> {
const response = await api.post<UserAchievement>('/achievements/grant', null, {
params: { userID, achievementID }
});
return response.data;
},
/**
* 撤销用户成就
* @param userID 用户ID
* @param achievementID 成就ID
* @returns Promise<ResultDomain<void>>
*/
async revokeAchievement(userID: string, achievementID: string): Promise<ResultDomain<void>> {
const response = await api.delete<void>('/achievements/revoke', null, {
params: { userID, achievementID }
});
return response.data;
},
// ==================== 成就进度查询 ====================
/**
* 获取用户成就进度
* @param userID 用户ID
* @param achievementID 成就ID可选为空则获取所有进度
* @returns Promise<ResultDomain<UserAchievementProgress>>
*/
async getUserAchievementProgress(userID: string, achievementID?: string): Promise<ResultDomain<UserAchievementProgress>> {
const params: Record<string, any> = {};
if (achievementID) {
params.achievementID = achievementID;
}
const response = await api.get<UserAchievementProgress>(`/achievements/progress/${userID}`, params);
return response.data;
},
// ==================== 成就检测 ====================
/**
* 处理成就事件(内部接口,由其他服务调用)
* @param event 成就事件
* @returns Promise<ResultDomain<UserAchievement>>
*/
async processAchievementEvent(event: AchievementEvent): Promise<ResultDomain<UserAchievement>> {
const response = await api.post<UserAchievement>('/achievements/event/process', event);
return response.data;
},
/**
* 检查用户是否满足成就条件
* @param userID 用户ID
* @param achievementID 成就ID
* @returns Promise<ResultDomain<boolean>>
*/
async checkAchievementCondition(userID: string, achievementID: string): Promise<ResultDomain<boolean>> {
const response = await api.get<boolean>(`/achievements/condition/check/${userID}/${achievementID}`);
return response.data;
},
/**
* 批量检查用户可获得的成就
* @param userID 用户ID
* @returns Promise<ResultDomain<Achievement>>
*/
async checkAvailableAchievements(userID: string): Promise<ResultDomain<Achievement>> {
const response = await api.get<Achievement>(`/achievements/available/${userID}`);
return response.data;
},
// ==================== 成就统计 ====================
/**
* 获取用户成就统计
* @param userID 用户ID
* @returns Promise<ResultDomain<Record<string, any>>>
*/
async getUserAchievementStatistics(userID: string): Promise<ResultDomain<Record<string, any>>> {
const response = await api.get<Record<string, any>>(`/achievements/statistics/${userID}`);
return response.data;
},
/**
* 获取成就排行榜
* @param limit 排行榜条数默认10条
* @returns Promise<ResultDomain<Record<string, any>>>
*/
async getAchievementRanking(limit = 10): Promise<ResultDomain<Record<string, any>>> {
const response = await api.get<Record<string, any>>('/achievements/ranking', { limit });
return response.data;
},
/**
* 获取最近获得成就的用户
* @param pageParam 分页参数
* @param filter 过滤条件
* @param limit 查询条数默认10条
* @returns Promise<ResultDomain<UserAchievement>>
*/
async getRecentAchievers(pageParam: PageParam, filter?:Achievement ): Promise<ResultDomain<UserAchievement>> {
const response = await api.post<UserAchievement>(`/achievements/recent`, {pageParam, filter });
return response.data;
}
};

View File

@@ -0,0 +1 @@
export * from './achievement';

View File

@@ -178,9 +178,6 @@ request.interceptors.response.use(
}
);
/**
* API封装
*/
/**
* API封装
*/

View File

@@ -12,4 +12,5 @@ export { menuApi } from './menu';
export { permissionApi } from './permission';
export { authApi } from './auth';
export { fileApi } from './file';
export { moduleApi } from './module';

View File

@@ -0,0 +1,197 @@
/**
* @description 系统模块相关API
* @author yslg
* @since 2025-10-25
*/
import { api } from '@/apis/index';
import type { SysModule, ResultDomain, PageParam, SysPermission } from '@/types';
/**
* 系统模块API服务
*/
export const moduleApi = {
baseUrl: '/system/modules',
/**
* 查询模块列表
* @param filter 过滤条件
* @returns Promise<ResultDomain<SysModule>>
*/
async getModuleList(filter?: Partial<SysModule>): Promise<ResultDomain<SysModule>> {
const response = await api.post<SysModule>(`${this.baseUrl}/list`, filter);
return response.data;
},
/**
* 根据模块ID查询模块信息
* @param moduleID 模块ID
* @returns Promise<ResultDomain<SysModule>>
*/
async getModuleById(moduleID: string): Promise<ResultDomain<SysModule>> {
const response = await api.get<SysModule>(`${this.baseUrl}/${moduleID}`);
return response.data;
},
/**
* 根据模块代码查询模块信息
* @param code 模块代码
* @returns Promise<ResultDomain<SysModule>>
*/
async getModuleByCode(code: string): Promise<ResultDomain<SysModule>> {
const response = await api.get<SysModule>(`${this.baseUrl}/code/${code}`);
return response.data;
},
/**
* 查询启用的模块列表
* @returns Promise<ResultDomain<SysModule>>
*/
async getActiveModules(): Promise<ResultDomain<SysModule>> {
const response = await api.get<SysModule>(`${this.baseUrl}/active`);
return response.data;
},
/**
* 创建模块
* @param module 模块信息
* @returns Promise<ResultDomain<SysModule>>
*/
async createModule(module: SysModule): Promise<ResultDomain<SysModule>> {
const response = await api.post<SysModule>(`${this.baseUrl}/module`, module);
return response.data;
},
/**
* 更新模块
* @param module 模块信息
* @returns Promise<ResultDomain<SysModule>>
*/
async updateModule(module: SysModule): Promise<ResultDomain<SysModule>> {
const response = await api.put<SysModule>(`${this.baseUrl}/module`, module);
return response.data;
},
/**
* 删除模块
* @param moduleID 模块ID
* @returns Promise<ResultDomain<boolean>>
*/
async deleteModule(moduleID: string): Promise<ResultDomain<boolean>> {
const response = await api.delete<boolean>(`${this.baseUrl}/module`, { moduleID });
return response.data;
},
/**
* 批量删除模块
* @param moduleIDs 模块ID列表
* @returns Promise<ResultDomain<boolean>>
*/
async batchDeleteModules(moduleIDs: string[]): Promise<ResultDomain<boolean>> {
const response = await api.delete<boolean>(`${this.baseUrl}/batch`, { moduleIDs });
return response.data;
},
/**
* 更新模块状态
* @param moduleID 模块ID
* @param status 状态0禁用 1启用
* @returns Promise<ResultDomain<boolean>>
*/
async updateModuleStatus(moduleID: string, status: number): Promise<ResultDomain<boolean>> {
const response = await api.put<boolean>(`${this.baseUrl}/${moduleID}/status/${status}`);
return response.data;
},
/**
* 更新模块排序
* @param moduleID 模块ID
* @param orderNum 排序号
* @returns Promise<ResultDomain<boolean>>
*/
async updateModuleOrder(moduleID: string, orderNum: number): Promise<ResultDomain<boolean>> {
const response = await api.put<boolean>(`${this.baseUrl}/${moduleID}/order/${orderNum}`);
return response.data;
},
/**
* 分页查询模块列表
* @param filter 过滤条件
* @param pageParam 分页参数
* @returns Promise<ResultDomain<SysModule>>
*/
async getModuleListPage(filter?: Partial<SysModule>, pageParam?: PageParam): Promise<ResultDomain<SysModule>> {
const response = await api.post<SysModule>(`${this.baseUrl}/page`, {
filter,
pageParam: {pageNumber: pageParam?.page || 1, pageSize: pageParam?.size || 10}
});
return response.data;
},
/**
* 统计模块数量
* @param filter 过滤条件
* @returns Promise<ResultDomain<number>>
*/
async countModules(filter?: Partial<SysModule>): Promise<ResultDomain<number>> {
const response = await api.post<number>(`${this.baseUrl}/count`, filter);
return response.data;
},
/**
* 检查模块代码是否存在
* @param code 模块代码
* @param excludeID 排除的模块ID
* @returns Promise<ResultDomain<boolean>>
*/
async checkModuleCodeExists(code: string, excludeID?: string): Promise<ResultDomain<boolean>> {
const response = await api.get<boolean>(`${this.baseUrl}/check-code`, {
code,
excludeID
});
return response.data;
},
/**
* 在模块中创建权限
* @param moduleID 模块ID
* @param permission 权限信息
* @returns Promise<ResultDomain<SysPermission>>
*/
async createPermissionInModule(moduleID: string, permission: SysPermission): Promise<ResultDomain<SysPermission>> {
const response = await api.post<SysPermission>(`${this.baseUrl}/${moduleID}/permissions`, permission);
return response.data;
},
/**
* 更新模块中的权限
* @param moduleID 模块ID
* @param permission 权限信息
* @returns Promise<ResultDomain<SysPermission>>
*/
async updatePermissionInModule(moduleID: string, permission: SysPermission): Promise<ResultDomain<SysPermission>> {
const response = await api.put<SysPermission>(`${this.baseUrl}/${moduleID}/permissions`, permission);
return response.data;
},
/**
* 删除模块中的权限
* @param moduleID 模块ID
* @param permissionID 权限ID
* @returns Promise<ResultDomain<boolean>>
*/
async deletePermissionInModule(moduleID: string, permissionID: string): Promise<ResultDomain<boolean>> {
const response = await api.delete<boolean>(`${this.baseUrl}/${moduleID}/permissions/${permissionID}`);
return response.data;
},
/**
* 获取模块的权限列表
* @param moduleID 模块ID
* @returns Promise<ResultDomain<SysPermission>>
*/
async getModulePermissions(moduleID: string): Promise<ResultDomain<SysPermission>> {
const response = await api.get<SysPermission>(`${this.baseUrl}/${moduleID}/permissions`);
return response.data;
}
};

View File

@@ -1,46 +0,0 @@
/**
* @description 用户成就相关API
* @author yslg
* @since 2025-10-15
*/
import { api } from '@/apis/index';
import type { UserAchievement, Achievement, ResultDomain } from '@/types';
/**
* 用户成就API服务
*/
export const userAchievementApi = {
/**
* 获取用户成就列表
* @param userID 用户ID
* @returns Promise<ResultDomain<UserAchievement>>
*/
async getUserAchievements(userID: string): Promise<ResultDomain<UserAchievement>> {
const response = await api.get<UserAchievement>('/usercenter/achievement/user-list', { userID });
return response.data;
},
/**
* 获取所有成就列表
* @returns Promise<ResultDomain<Achievement>>
*/
async getAllAchievements(): Promise<ResultDomain<Achievement>> {
const response = await api.get<Achievement>('/usercenter/achievement/list');
return response.data;
},
/**
* 检查用户成就进度
* @param userID 用户ID
* @param achievementID 成就ID
* @returns Promise<ResultDomain<{ progress: number; isCompleted: boolean }>>
*/
async checkAchievementProgress(userID: string, achievementID: string): Promise<ResultDomain<{ progress: number; isCompleted: boolean }>> {
const response = await api.get<{ progress: number; isCompleted: boolean }>('/usercenter/achievement/progress', {
userID,
achievementID
});
return response.data;
}
};

View File

@@ -8,5 +8,4 @@
export { userCollectionApi } from './collection';
export { userBrowseRecordApi } from './browse-record';
export { userPointsApi } from './points';
export { userAchievementApi } from './achievement';
export { userProfileApi } from './profile';

View File

@@ -1,120 +0,0 @@
<svg width="103" height="113" viewBox="0 0 103 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_526_16855)">
<path d="M21.9736 94.2714V65.5754H45.0503V87.4085L24.1968 95.7749C23.1325 96.2019 21.9736 95.4182 21.9736 94.2714Z" fill="#8C7A79"/>
<path d="M80.7031 94.2714V65.5754H57.6264V87.4085L78.4799 95.7749C79.5443 96.2019 80.7031 95.4182 80.7031 94.2714Z" fill="#8C7A79"/>
<g filter="url(#filter1_d_526_16855)">
<path d="M47.1592 17.3863L21.7933 31.1852C18.839 32.7922 17 35.8861 17 39.2492L17 71.037C17 74.4001 18.839 77.494 21.7932 79.101L47.1592 92.8999C49.8945 94.3878 53.1975 94.3878 55.9327 92.8999L81.2987 79.101C84.2529 77.494 86.0919 74.4001 86.0919 71.037L86.0919 39.2492C86.0919 35.8861 84.2529 32.7923 81.2987 31.1852L55.9327 17.3863C53.1975 15.8984 49.8945 15.8984 47.1592 17.3863Z" fill="url(#paint0_linear_526_16855)"/>
<path d="M47.1343 23.4572L27.0262 34.3958C24.0831 35.9968 22.251 39.0791 22.251 42.4295L22.251 67.7166C22.251 71.0671 24.083 74.1493 27.0262 75.7503L47.1343 86.6889C49.8592 88.1713 53.1498 88.1713 55.8748 86.6889L75.9829 75.7503C78.926 74.1493 80.7581 71.0671 80.7581 67.7166L80.7581 42.4295C80.7581 39.0791 78.926 35.9968 75.9829 34.3958L55.8748 23.4572C53.1498 21.9749 49.8592 21.9749 47.1343 23.4572Z" fill="url(#paint1_linear_526_16855)"/>
<g filter="url(#filter2_f_526_16855)">
<path d="M47.1343 23.4572L27.0262 34.3958C24.0831 35.9968 22.251 39.0791 22.251 42.4295L22.251 67.7166C22.251 71.0671 24.083 74.1493 27.0262 75.7503L47.1343 86.6889C49.8592 88.1713 53.1498 88.1713 55.8748 86.6889L75.9829 75.7503C78.926 74.1493 80.7581 71.0671 80.7581 67.7166L80.7581 42.4295C80.7581 39.0791 78.926 35.9968 75.9829 34.3958L55.8748 23.4572C53.1498 21.9749 49.8592 21.9749 47.1343 23.4572Z" stroke="url(#paint2_linear_526_16855)" stroke-width="0.54"/>
</g>
<g opacity="0.5">
<mask id="mask0_526_16855" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="24" y="25" width="55" height="61">
<path d="M47.529 26.1025L29.2825 36.0195C26.4813 37.542 24.7373 40.4747 24.7373 43.6629L24.7373 66.5935C24.7373 69.7817 26.4813 72.7144 29.2825 74.2369L47.529 84.1539C50.1195 85.5618 53.2468 85.5618 55.8373 84.1539L74.0839 74.2369C76.885 72.7144 78.629 69.7817 78.629 66.5935L78.629 43.6629C78.629 40.4747 76.885 37.542 74.0839 36.0195L55.8373 26.1025C53.2468 24.6946 50.1195 24.6946 47.529 26.1025Z" fill="#A6D3DE"/>
</mask>
<g mask="url(#mask0_526_16855)">
<path d="M47.529 26.1025L29.2825 36.0195C26.4813 37.542 24.7373 40.4747 24.7373 43.6629L24.7373 66.5935C24.7373 69.7817 26.4813 72.7144 29.2825 74.2369L47.529 84.1539C50.1195 85.5618 53.2468 85.5618 55.8373 84.1539L74.0839 74.2369C76.885 72.7144 78.629 69.7817 78.629 66.5935L78.629 43.6629C78.629 40.4747 76.885 37.542 74.0839 36.0195L55.8373 26.1025C53.2468 24.6946 50.1195 24.6946 47.529 26.1025Z" fill="url(#paint3_radial_526_16855)"/>
<path opacity="0.5" d="M47.1788 87.6469L46.1084 22.6052H57.2584L56.188 87.6469H47.1788Z" fill="url(#paint4_linear_526_16855)"/>
<path opacity="0.5" d="M47.0348 87.0697L13.5869 31.2771L23.2431 25.7021L54.837 82.5651L47.0348 87.0697Z" fill="url(#paint5_linear_526_16855)"/>
<path opacity="0.5" d="M46.1063 82.5656L77.7002 25.7026L87.3564 31.2776L53.9085 87.0702L46.1063 82.5656Z" fill="url(#paint6_linear_526_16855)"/>
<rect x="24.7373" y="51.4089" width="53.8917" height="34.9986" fill="url(#paint7_linear_526_16855)"/>
<g filter="url(#filter3_i_526_16855)">
<path d="M47.529 26.1025L29.2825 36.0195C26.4813 37.542 24.7373 40.4747 24.7373 43.6629L24.7373 66.5935C24.7373 69.7817 26.4813 72.7144 29.2825 74.2369L47.529 84.1539C50.1195 85.5618 53.2468 85.5618 55.8373 84.1539L74.0839 74.2369C76.885 72.7144 78.629 69.7817 78.629 66.5935L78.629 43.6629C78.629 40.4747 76.885 37.542 74.0839 36.0195L55.8373 26.1025C53.2468 24.6946 50.1195 24.6946 47.529 26.1025Z" fill="#BCB0AE" fill-opacity="0.03"/>
</g>
</g>
</g>
</g>
</g>
<g clip-path="url(#clip0_526_16855)">
<g filter="url(#filter4_d_526_16855)">
<path d="M51.9134 42.1565L36.3965 51.208L51.9134 60.2596L64.8441 52.7167V62.1992H67.4303V51.208L51.9134 42.1565ZM41.5675 57.0146V62.8458C43.9266 65.9866 47.6826 68.0181 51.9131 68.0181C56.1436 68.0181 59.8996 65.9866 62.2586 62.8458L62.2581 57.0156L51.9138 63.0499L41.5675 57.0146Z" fill="url(#paint8_linear_526_16855)"/>
</g>
</g>
<defs>
<filter id="filter0_d_526_16855" x="0.799999" y="0.070507" width="101.492" height="112.023" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="8.1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.92549 0 0 0 0 0.909804 0 0 0 0 0.913725 0 0 0 0.48 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16855"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16855" result="shape"/>
</filter>
<filter id="filter1_d_526_16855" x="13.76" y="16.2705" width="75.5718" height="85.3054" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4.32"/>
<feGaussianBlur stdDeviation="1.62"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.475 0 0 0 0 0.425521 0 0 0 0 0.425521 0 0 0 0.35 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16855"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16855" result="shape"/>
</filter>
<filter id="filter2_f_526_16855" x="21.8194" y="21.9134" width="59.3709" height="66.3194" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.081" result="effect1_foregroundBlur_526_16855"/>
</filter>
<filter id="filter3_i_526_16855" x="24.7373" y="25.0466" width="53.8916" height="60.1633" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3.63103"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.508333 0 0 0 0 0.437506 0 0 0 0 0.415139 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_526_16855"/>
</filter>
<filter id="filter4_d_526_16855" x="31.8615" y="39.7696" width="40.5816" height="35.409" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.238685" dy="2.38685"/>
<feGaussianBlur stdDeviation="2.38685"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.729167 0 0 0 0 0.653971 0 0 0 0 0.647135 0 0 0 0.38 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16855"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16855" result="shape"/>
</filter>
<linearGradient id="paint0_linear_526_16855" x1="37.0367" y1="24.6729" x2="68.128" y2="86.4411" gradientUnits="userSpaceOnUse">
<stop stop-color="#EDE9EA"/>
<stop offset="1" stop-color="#BEB5B6"/>
</linearGradient>
<linearGradient id="paint1_linear_526_16855" x1="36.7603" y1="29.7854" x2="63.568" y2="79.2552" gradientUnits="userSpaceOnUse">
<stop stop-color="#C3BBBB"/>
<stop offset="1" stop-color="#ECE8E9"/>
</linearGradient>
<linearGradient id="paint2_linear_526_16855" x1="39.6621" y1="27.989" x2="68.5426" y2="79.9462" gradientUnits="userSpaceOnUse">
<stop stop-color="#C9C2C3"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<radialGradient id="paint3_radial_526_16855" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(51.6832 55.1282) rotate(90) scale(31.2835 26.9459)">
<stop stop-color="#DFD6D5"/>
<stop offset="1" stop-color="#918888"/>
</radialGradient>
<linearGradient id="paint4_linear_526_16855" x1="51.6834" y1="22.6052" x2="51.6834" y2="87.6469" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint5_linear_526_16855" x1="18.415" y1="28.4896" x2="50.9359" y2="84.8174" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear_526_16855" x1="82.5283" y1="28.4901" x2="50.0074" y2="84.8179" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint7_linear_526_16855" x1="51.6832" y1="66.5853" x2="51.5206" y2="84.2391" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEDADA" stop-opacity="0"/>
<stop offset="1" stop-color="#DECFCF"/>
</linearGradient>
<linearGradient id="paint8_linear_526_16855" x1="49.4555" y1="35.6738" x2="69.962" y2="43.1525" gradientUnits="userSpaceOnUse">
<stop offset="0.168737" stop-color="#FFFCFA"/>
<stop offset="0.360439" stop-color="white"/>
<stop offset="0.451944" stop-color="white"/>
<stop offset="0.627859" stop-color="#EEEBE6"/>
<stop offset="0.80404" stop-color="#F3F3F3"/>
</linearGradient>
<clipPath id="clip0_526_16855">
<rect width="31.0338" height="31.0338" fill="white" transform="translate(36.3965 39.5703)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -1,4 +0,0 @@
<svg width="46" height="31" viewBox="0 0 46 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M35.1456 11.2989C37.2897 10.3436 39.2997 9.21307 41.1758 7.90948H46V30.9839H40.7738V12.8632C39.0757 14.0814 37.1996 15.0794 35.1456 15.8617V11.2989ZM23.7379 0L10.6008 14.9453V0H2.65247V0.0144333H0V2.58624H2.6524V31H10.6009L36.9964 0.0144333L23.7379 0Z" fill="#334155"/>
</svg>

Before

Width:  |  Height:  |  Size: 386 B

View File

@@ -1,128 +0,0 @@
<svg width="102" height="120" viewBox="0 0 102 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_dd_526_16878)">
<path d="M24.6221 90.3889V60.2524H77.6894V90.1256C77.6894 90.4065 77.5392 90.666 77.2955 90.8058L51.2101 105.775C50.9675 105.914 50.6691 105.914 50.427 105.774L25.0135 91.0677C24.7713 90.9275 24.6221 90.6688 24.6221 90.3889Z" fill="#84B7C4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.0456 60.2524V99.7663L25.0135 91.0677C24.7713 90.9275 24.6221 90.6688 24.6221 90.3889V60.2524H40.0456Z" fill="#6B9BAA"/>
<path d="M40.0439 99.7659L39.5205 99.4631V60.2532H40.0439V99.7659Z" fill="#B0D2DD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.264 59.9917V99.5056L77.296 90.8069C77.5383 90.6667 77.6875 90.4081 77.6875 90.1281V59.9917H62.264Z" fill="#6B9BAA"/>
<path d="M62.2646 99.5054L62.7871 99.2026V59.9917H62.2646V99.5054Z" fill="#B0D2DD"/>
<g filter="url(#filter1_d_526_16878)">
<path d="M48.8068 15.2026L19.4156 31.1911C17.9268 32.001 17 33.5602 17 35.2551L17 71.803C17 73.4979 17.9268 75.0571 19.4156 75.867L48.8068 91.8555C50.1852 92.6054 51.8498 92.6054 53.2283 91.8555L82.6194 75.867C84.1083 75.0571 85.0351 73.4979 85.0351 71.803L85.0351 35.2551C85.0351 33.5602 84.1083 32.001 82.6195 31.1911L53.2283 15.2026C51.8498 14.4528 50.1852 14.4528 48.8068 15.2026Z" fill="url(#paint0_linear_526_16878)"/>
<path d="M48.7745 21.1854L24.5774 34.3484C23.0942 35.1553 22.1709 36.7086 22.1709 38.3971L22.1709 68.524C22.1709 70.2125 23.0942 71.7659 24.5774 72.5727L48.7745 85.7357C50.1478 86.4828 51.8062 86.4828 53.1794 85.7357L77.3765 72.5727C78.8598 71.7659 79.7831 70.2125 79.7831 68.524L79.7831 38.3971C79.7831 36.7086 78.8598 35.1553 77.3766 34.3484L53.1794 21.1854C51.8062 20.4384 50.1478 20.4384 48.7745 21.1854Z" fill="url(#paint1_linear_526_16878)"/>
<g filter="url(#filter2_f_526_16878)">
<path d="M48.7745 21.1854L24.5774 34.3484C23.0942 35.1553 22.1709 36.7086 22.1709 38.3971L22.1709 68.524C22.1709 70.2125 23.0942 71.7659 24.5774 72.5727L48.7745 85.7357C50.1478 86.4828 51.8062 86.4828 53.1794 85.7357L77.3765 72.5727C78.8598 71.7659 79.7831 70.2125 79.7831 68.524L79.7831 38.3971C79.7831 36.7086 78.8598 35.1553 77.3766 34.3484L53.1794 21.1854C51.8062 20.4384 50.1478 20.4384 48.7745 21.1854Z" stroke="url(#paint2_linear_526_16878)" stroke-width="0.27214"/>
</g>
<g opacity="0.5">
<mask id="mask0_526_16878" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="24" y="23" width="54" height="61">
<path d="M49.0622 23.8468L26.9127 35.8851C25.501 36.6524 24.6221 38.1304 24.6221 39.7371L24.6221 67.2908C24.6221 68.8975 25.501 70.3755 26.9127 71.1428L49.0622 83.181C50.3677 83.8906 51.9438 83.8906 53.2493 83.181L75.3988 71.1428C76.8105 70.3755 77.6894 68.8975 77.6894 67.2908L77.6894 39.7371C77.6894 38.1304 76.8105 36.6524 75.3988 35.8851L53.2493 23.8468C51.9438 23.1373 50.3677 23.1373 49.0622 23.8468Z" fill="#A6D3DE"/>
</mask>
<g mask="url(#mask0_526_16878)">
<path d="M49.0622 23.8468L26.9127 35.8851C25.501 36.6524 24.6221 38.1304 24.6221 39.7371L24.6221 67.2908C24.6221 68.8975 25.501 70.3755 26.9127 71.1428L49.0622 83.181C50.3677 83.8906 51.9438 83.8906 53.2493 83.181L75.3988 71.1428C76.8105 70.3755 77.6894 68.8975 77.6894 67.2908L77.6894 39.7371C77.6894 38.1304 76.8105 36.6524 75.3988 35.8851L53.2493 23.8468C51.9438 23.1373 50.3677 23.1373 49.0622 23.8468Z" fill="url(#paint3_radial_526_16878)"/>
<path opacity="0.5" d="M46.7171 85.5356L45.6631 21.4888H56.6425L55.5885 85.5356H46.7171Z" fill="url(#paint4_linear_526_16878)"/>
<path opacity="0.5" d="M46.5759 84.9675L13.6396 30.0283L23.1481 24.5386L54.2587 80.5318L46.5759 84.9675Z" fill="url(#paint5_linear_526_16878)"/>
<path opacity="0.5" d="M45.6609 80.5313L76.7715 24.5381L86.28 30.0278L53.3438 84.967L45.6609 80.5313Z" fill="url(#paint6_linear_526_16878)"/>
<rect x="24.6221" y="49.8516" width="53.0674" height="34.4633" fill="url(#paint7_linear_526_16878)"/>
<g filter="url(#filter3_i_526_16878)">
<path d="M49.0622 23.8468L26.9127 35.8851C25.501 36.6524 24.6221 38.1304 24.6221 39.7371L24.6221 67.2908C24.6221 68.8975 25.501 70.3755 26.9127 71.1428L49.0622 83.181C50.3677 83.8906 51.9438 83.8906 53.2493 83.181L75.3988 71.1428C76.8105 70.3755 77.6894 68.8975 77.6894 67.2908L77.6894 39.7371C77.6894 38.1304 76.8105 36.6524 75.3988 35.8851L53.2493 23.8468C51.9438 23.1373 50.3677 23.1373 49.0622 23.8468Z" fill="#AFF5F1" fill-opacity="0.03"/>
</g>
</g>
</g>
</g>
</g>
<g clip-path="url(#clip0_526_16878)">
<g filter="url(#filter4_d_526_16878)">
<path d="M50.679 40.7412L35.3994 49.6543L50.679 58.5674L63.4119 51.1398V60.4773H65.9585V49.6543L50.679 40.7412ZM40.4914 55.372V61.1141C42.8144 64.2068 46.5129 66.2073 50.6787 66.2073C54.8444 66.2073 58.543 64.2068 60.866 61.1141L60.8655 55.373L50.6794 61.315L40.4914 55.372Z" fill="url(#paint8_linear_526_16878)"/>
</g>
</g>
<defs>
<filter id="filter0_dd_526_16878" x="0.345984" y="-0.903612" width="101.343" height="124.547" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.77567"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.558594 0 0 0 0 0.869297 0 0 0 0 0.9375 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16878"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.11027"/>
<feGaussianBlur stdDeviation="8.32701"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.670833 0 0 0 0 1 0 0 0 0 0.94075 0 0 0 0.33 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_526_16878" result="effect2_dropShadow_526_16878"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_526_16878" result="shape"/>
</filter>
<filter id="filter1_d_526_16878" x="15.3672" y="14.6401" width="71.3008" height="81.5878" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.17712"/>
<feGaussianBlur stdDeviation="0.816421"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.208958 0 0 0 0 0.389892 0 0 0 0 0.491667 0 0 0 0.54 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16878"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16878" result="shape"/>
</filter>
<filter id="filter2_f_526_16878" x="21.9535" y="20.4074" width="58.0471" height="66.1064" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.040821" result="effect1_foregroundBlur_526_16878"/>
</filter>
<filter id="filter3_i_526_16878" x="24.6221" y="23.3147" width="53.0674" height="60.3984" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.82991"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.37849 0 0 0 0 0.586923 0 0 0 0 0.704167 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_526_16878"/>
</filter>
<filter id="filter4_d_526_16878" x="33.5295" y="39.757" width="34.4963" height="29.4028" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.0984178" dy="0.984179"/>
<feGaussianBlur stdDeviation="0.984179"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.564618 0 0 0 0 0.775175 0 0 0 0 0.841667 0 0 0 0.65 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16878"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16878" result="shape"/>
</filter>
<linearGradient id="paint0_linear_526_16878" x1="36.7302" y1="23.5249" x2="67.346" y2="84.3483" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8ECF1"/>
<stop offset="1" stop-color="#82B3BF"/>
</linearGradient>
<linearGradient id="paint1_linear_526_16878" x1="36.4583" y1="28.5597" x2="62.8559" y2="77.2728" gradientUnits="userSpaceOnUse">
<stop stop-color="#A1C9D3"/>
<stop offset="1" stop-color="#D0ECF2"/>
</linearGradient>
<linearGradient id="paint2_linear_526_16878" x1="39.3157" y1="26.7908" x2="67.7544" y2="77.9532" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0ECF2"/>
<stop offset="1" stop-color="#E6F8FC"/>
</linearGradient>
<radialGradient id="paint3_radial_526_16878" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(51.1557 53.5139) rotate(90) scale(30.805 26.5337)">
<stop stop-color="#D0EBF2"/>
<stop offset="1" stop-color="#8DD3E2"/>
</radialGradient>
<linearGradient id="paint4_linear_526_16878" x1="51.1528" y1="21.4888" x2="51.1528" y2="85.5356" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint5_linear_526_16878" x1="18.3939" y1="27.2835" x2="50.4173" y2="82.7496" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear_526_16878" x1="81.5257" y1="27.2829" x2="49.5023" y2="82.7491" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint7_linear_526_16878" x1="51.1557" y1="64.7958" x2="50.9957" y2="82.1796" gradientUnits="userSpaceOnUse">
<stop stop-color="#EEFFFF" stop-opacity="0"/>
<stop offset="1" stop-color="#B5EBFB"/>
</linearGradient>
<linearGradient id="paint8_linear_526_16878" x1="48.2587" y1="34.3577" x2="68.4515" y2="41.722" gradientUnits="userSpaceOnUse">
<stop offset="0.168737" stop-color="#E3FAFF"/>
<stop offset="0.360439" stop-color="#DDF7FF"/>
<stop offset="0.451944" stop-color="#CBEDF8"/>
<stop offset="0.627859" stop-color="#C8F1FA"/>
<stop offset="0.80404" stop-color="#D8F5FC"/>
</linearGradient>
<clipPath id="clip0_526_16878">
<rect width="30.5591" height="30.5591" fill="white" transform="translate(35.3994 38.1943)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,5 +0,0 @@
<svg width="52" height="32" viewBox="0 0 52 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.7379 0L10.6008 14.9453V0H2.65247V0.0144333H0V2.58624H2.6524V31H10.6009L36.9964 0.0144333L23.7379 0Z" fill="#334155"/>
<path d="M42.8826 12.9266C42.0518 12.9266 41.337 13.1778 40.738 13.6801C40.1391 14.1631 39.6271 14.9069 39.2021 15.9115L35 13.0425C35.6376 11.6128 36.6229 10.4247 37.956 9.47798C39.289 8.51198 40.9892 8.01932 43.0564 8C44.66 8 46.0414 8.29946 47.2006 8.89838C48.3791 9.47798 49.2775 10.3087 49.8957 11.3907C50.514 12.4533 50.8231 13.6801 50.8231 15.0711C50.8231 16.3076 50.5236 17.4958 49.9247 18.6357C49.3451 19.7755 48.5047 20.9154 47.4034 22.0553L40.767 28.9525L39.6078 26.2574H51.2868V31.2999H35.029V27.7354L43.2013 19.3602C43.9935 18.5294 44.5441 17.8146 44.8532 17.2156C45.1623 16.6167 45.3169 15.9985 45.3169 15.3609C45.3169 14.6268 45.1044 14.0375 44.6793 13.5931C44.2736 13.1488 43.6747 12.9266 42.8826 12.9266Z" fill="#334155"/>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

View File

@@ -1,219 +0,0 @@
<svg width="118" height="120" viewBox="0 0 118 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M52.5607 39.6881L50.543 -6H66.6851L64.6674 39.6881H52.5607Z" fill="url(#paint0_linear_526_16911)"/>
<path d="M71.4407 52.7496L112.017 73.8462L103.946 87.8257L65.3874 63.2342L71.4407 52.7496Z" fill="url(#paint1_linear_526_16911)"/>
<path d="M64.3787 64.0453L66.3965 109.733H50.2543L52.2721 64.0453H64.3787Z" fill="url(#paint2_linear_526_16911)"/>
<path d="M51.6294 63.3787L13.0713 87.9702L5.0002 73.9907L45.5761 52.8941L51.6294 63.3787Z" fill="url(#paint3_linear_526_16911)"/>
<path d="M71.4407 51.0558L112.017 29.9592L103.946 15.9797L65.3874 40.5712L71.4407 51.0558Z" fill="url(#paint4_linear_526_16911)"/>
<path d="M51.6294 40.4269L13.0713 15.8354L5.00021 29.815L45.5761 50.9116L51.6294 40.4269Z" fill="url(#paint5_linear_526_16911)"/>
<g filter="url(#filter0_dd_526_16911)">
<path d="M17.2334 40.75H27.1333V64.1249C20.9459 64.1249 17.2334 59.7506 17.2334 56.1965V40.75Z" fill="url(#paint6_linear_526_16911)"/>
<path d="M17.2334 40.75H27.1333V64.1249C20.9459 64.1249 17.2334 59.7506 17.2334 56.1965V40.75Z" stroke="url(#paint7_linear_526_16911)" stroke-width="0.549997"/>
<path d="M100.282 40.7498H90.3823V64.1246C96.5697 64.1246 100.282 59.7504 100.282 56.1963V40.7498Z" fill="url(#paint8_linear_526_16911)"/>
<path d="M100.282 40.7498H90.3823V64.1246C96.5697 64.1246 100.282 59.7504 100.282 56.1963V40.7498Z" stroke="url(#paint9_linear_526_16911)" stroke-width="0.549997"/>
<path d="M45.5576 61.9253H72.3707V100.363L59.1897 107.927C58.9445 108.068 58.643 108.068 58.3983 107.926L45.5576 100.495V61.9253Z" fill="#BB9986" stroke="url(#paint10_linear_526_16911)" stroke-width="0.549997"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.6323 61.9255V99.6122L32.4735 93.066C32.231 92.9237 32.082 92.6636 32.082 92.3824V61.9255H43.6323Z" fill="#8C685C" stroke="url(#paint11_linear_526_16911)" stroke-width="0.549997"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M74.1578 61.9253V99.6119L85.3165 93.0657C85.559 92.9235 85.708 92.6633 85.708 92.3822V61.9253H74.1578Z" fill="#8C685C" stroke="url(#paint12_linear_526_16911)" stroke-width="0.549997"/>
<path d="M84.7451 78.2869V91.3493C84.1493 91.3951 82.9301 91.8993 82.8201 93.5493L75.2577 97.9492V82.9618" stroke="url(#paint13_linear_526_16911)" stroke-width="0.274998"/>
<path d="M46.7959 98.9338C47.1754 99.0051 47.5435 99.1218 47.8672 99.4114C48.2141 99.7218 48.4939 100.217 48.7021 101.022L58.7949 106.749H58.9961L69.0889 101.021C69.297 100.216 69.5771 99.7218 69.9238 99.4114C70.2474 99.1218 70.6157 99.0051 70.9951 98.9338V85.5745H71.2705V99.1663L71.1553 99.1848C70.7504 99.2523 70.4041 99.3501 70.1074 99.6155C69.8095 99.882 69.5446 100.334 69.3418 101.145L69.3271 101.202L59.1104 107.001L59.0332 107.025H58.7217L48.4639 101.202L48.4502 101.146C48.2474 100.335 47.9815 99.8831 47.6836 99.6165C47.3868 99.3509 47.0408 99.2523 46.6357 99.1848L46.5205 99.1663V85.5745H46.7959V98.9338Z" fill="url(#paint14_linear_526_16911)"/>
<path d="M33.1836 78.2869V91.3493C33.7794 91.3951 34.9986 91.8993 35.1086 93.5493L42.671 97.9492V82.9618" stroke="url(#paint15_linear_526_16911)" stroke-width="0.274998"/>
<g filter="url(#filter1_d_526_16911)">
<path d="M56.5236 13.9164L26.8238 30.0728C25.3193 30.8912 24.3828 32.4668 24.3828 34.1795L24.3828 71.1112C24.3828 72.8239 25.3193 74.3995 26.8238 75.2179L56.5236 91.3743C57.9166 92.132 59.5986 92.132 60.9916 91.3743L90.6914 75.2179C92.1959 74.3995 93.1324 72.8239 93.1324 71.1112L93.1324 34.1795C93.1324 32.4668 92.1959 30.8912 90.6914 30.0728L60.9916 13.9164C59.5986 13.1587 57.9166 13.1587 56.5236 13.9164Z" fill="url(#paint16_linear_526_16911)"/>
<path d="M56.4914 19.9614L32.0402 33.2626C30.5414 34.078 29.6084 35.6476 29.6084 37.3538L29.6084 67.7972C29.6084 69.5034 30.5414 71.0731 32.0402 71.8884L56.4914 85.1896C57.8791 85.9445 59.5549 85.9445 60.9426 85.1896L85.3938 71.8884C86.8926 71.0731 87.8256 69.5034 87.8256 67.7972L87.8256 37.3539C87.8256 35.6476 86.8926 34.078 85.3938 33.2626L60.9426 19.9614C59.5549 19.2065 57.8791 19.2065 56.4914 19.9614Z" fill="url(#paint17_linear_526_16911)"/>
<g filter="url(#filter2_f_526_16911)">
<path d="M56.4914 19.9614L32.0402 33.2626C30.5414 34.078 29.6084 35.6476 29.6084 37.3538L29.6084 67.7972C29.6084 69.5034 30.5414 71.0731 32.0402 71.8884L56.4914 85.1896C57.8791 85.9445 59.5549 85.9445 60.9426 85.1896L85.3938 71.8884C86.8926 71.0731 87.8256 69.5034 87.8256 67.7972L87.8256 37.3539C87.8256 35.6476 86.8926 34.078 85.3938 33.2626L60.9426 19.9614C59.5549 19.2065 57.8791 19.2065 56.4914 19.9614Z" stroke="url(#paint18_linear_526_16911)" stroke-width="0.274998"/>
</g>
<g opacity="0.5">
<mask id="mask0_526_16911" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="32" y="22" width="54" height="62">
<path d="M56.7788 22.651L34.3967 34.8157C32.9702 35.591 32.082 37.0846 32.082 38.7082L32.082 66.5512C32.082 68.1748 32.9702 69.6683 34.3967 70.4437L56.7788 82.6084C58.0981 83.3254 59.6907 83.3254 61.0099 82.6084L83.3921 70.4437C84.8186 69.6683 85.7067 68.1748 85.7067 66.5512L85.7067 38.7082C85.7067 37.0846 84.8186 35.591 83.3921 34.8157L61.0099 22.651C59.6907 21.934 58.0981 21.934 56.7788 22.651Z" fill="#A6D3DE"/>
</mask>
<g mask="url(#mask0_526_16911)">
<path d="M56.7788 22.651L34.3967 34.8157C32.9702 35.591 32.082 37.0846 32.082 38.7082L32.082 66.5512C32.082 68.1748 32.9702 69.6683 34.3967 70.4437L56.7788 82.6084C58.0981 83.3254 59.6907 83.3254 61.0099 82.6084L83.3921 70.4437C84.8186 69.6683 85.7067 68.1748 85.7067 66.5512L85.7067 38.7082C85.7067 37.0846 84.8186 35.591 83.3921 34.8157L61.0099 22.651C59.6907 21.934 58.0981 21.934 56.7788 22.651Z" fill="url(#paint19_radial_526_16911)"/>
<path opacity="0.5" d="M54.4128 84.9877L53.3477 20.2683H64.4424L63.3773 84.9877H54.4128Z" fill="url(#paint20_linear_526_16911)"/>
<path opacity="0.5" d="M54.2714 84.4143L20.9893 28.8982L30.5976 23.3508L62.0349 79.932L54.2714 84.4143Z" fill="url(#paint21_linear_526_16911)"/>
<path opacity="0.5" d="M53.3459 79.9306L84.7832 23.3494L94.3915 28.8967L61.1094 84.4129L53.3459 79.9306Z" fill="url(#paint22_linear_526_16911)"/>
<rect x="32.082" y="48.9294" width="53.6247" height="34.8252" fill="url(#paint23_linear_526_16911)"/>
<g filter="url(#filter3_i_526_16911)">
<path d="M56.7788 22.651L34.3967 34.8157C32.9702 35.591 32.082 37.0846 32.082 38.7082L32.082 66.5512C32.082 68.1748 32.9702 69.6683 34.3967 70.4437L56.7788 82.6084C58.0981 83.3254 59.6907 83.3254 61.0099 82.6084L83.3921 70.4437C84.8186 69.6683 85.7067 68.1748 85.7067 66.5512L85.7067 38.7082C85.7067 37.0846 84.8186 35.591 83.3921 34.8157L61.0099 22.651C59.6907 21.934 58.0981 21.934 56.7788 22.651Z" fill="#F5C4AF" fill-opacity="0.03"/>
</g>
</g>
</g>
</g>
</g>
<g clip-path="url(#clip0_526_16911)">
<g filter="url(#filter4_d_526_16911)">
<path d="M58.0025 39.7212L42.5625 48.7279L58.0025 57.7346L70.8692 50.229V59.6646H73.4425V48.7279L58.0025 39.7212ZM47.7079 54.5057V60.308C50.0553 63.4332 53.7927 65.4547 58.0023 65.4547C62.2117 65.4547 65.9491 63.4332 68.2965 60.308L68.296 54.5067L58.0029 60.5111L47.7079 54.5057Z" fill="url(#paint24_linear_526_16911)"/>
</g>
</g>
<defs>
<filter id="filter0_dd_526_16911" x="0.129086" y="-2.35885" width="117.257" height="128.617" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.80482"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.9375 0 0 0 0 0.695 0 0 0 0 0.558594 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16911"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.12193"/>
<feGaussianBlur stdDeviation="8.41446"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.749833 0 0 0 0 0.670833 0 0 0 0.6 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_526_16911" result="effect2_dropShadow_526_16911"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_526_16911" result="shape"/>
</filter>
<filter id="filter1_d_526_16911" x="22.7328" y="13.3481" width="72.05" height="82.4445" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.19999"/>
<feGaussianBlur stdDeviation="0.824995"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.491667 0 0 0 0 0.276808 0 0 0 0 0.208958 0 0 0 0.54 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16911"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16911" result="shape"/>
</filter>
<filter id="filter2_f_526_16911" x="29.3057" y="19.0928" width="58.8222" height="66.9655" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.0824995" result="effect1_foregroundBlur_526_16911"/>
</filter>
<filter id="filter3_i_526_16911" x="32.082" y="22.1133" width="53.625" height="61.033" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.84913"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.283333 0 0 0 0 0.158667 0 0 0 0 0.0566667 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_526_16911"/>
</filter>
<filter id="filter4_d_526_16911" x="40.6729" y="38.7267" width="34.8579" height="29.7115" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.0994515" dy="0.994515"/>
<feGaussianBlur stdDeviation="0.994515"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.620833 0 0 0 0 0.428789 0 0 0 0 0.320764 0 0 0 0.44 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16911"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16911" result="shape"/>
</filter>
<linearGradient id="paint0_linear_526_16911" x1="58.6141" y1="-6" x2="58.6141" y2="39.6881" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFBEAA" stop-opacity="0"/>
<stop offset="1" stop-color="#FFC9AA" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint1_linear_526_16911" x1="107.981" y1="80.836" x2="68.414" y2="57.9919" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFBEAA" stop-opacity="0"/>
<stop offset="1" stop-color="#FFC9AA" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint2_linear_526_16911" x1="58.3254" y1="109.733" x2="58.3254" y2="64.0453" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFBEAA" stop-opacity="0"/>
<stop offset="1" stop-color="#FFC9AA" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint3_linear_526_16911" x1="9.03575" y1="80.9805" x2="48.6028" y2="58.1364" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFBEAA" stop-opacity="0"/>
<stop offset="1" stop-color="#FFC9AA" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint4_linear_526_16911" x1="107.981" y1="22.9695" x2="68.414" y2="45.8135" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFBEAA" stop-opacity="0"/>
<stop offset="1" stop-color="#FFC9AA" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint5_linear_526_16911" x1="9.03575" y1="22.8252" x2="48.6028" y2="45.6693" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFBEAA" stop-opacity="0"/>
<stop offset="1" stop-color="#FFC9AA" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint6_linear_526_16911" x1="22.1834" y1="40.75" x2="22.1834" y2="64.1249" gradientUnits="userSpaceOnUse">
<stop stop-color="#EFD9BD"/>
<stop offset="1" stop-color="#C39470"/>
</linearGradient>
<linearGradient id="paint7_linear_526_16911" x1="17.2334" y1="40.75" x2="22.1834" y2="64.1249" gradientUnits="userSpaceOnUse">
<stop stop-color="#F3DDC4"/>
<stop offset="0.378992" stop-color="#F3DDC4"/>
<stop offset="0.46253" stop-color="#F3DDC4"/>
<stop offset="1" stop-color="#74523F"/>
</linearGradient>
<linearGradient id="paint8_linear_526_16911" x1="95.3323" y1="40.7498" x2="95.3323" y2="64.1246" gradientUnits="userSpaceOnUse">
<stop stop-color="#EFD9BD"/>
<stop offset="1" stop-color="#C39470"/>
</linearGradient>
<linearGradient id="paint9_linear_526_16911" x1="100.282" y1="40.7498" x2="95.3323" y2="64.1246" gradientUnits="userSpaceOnUse">
<stop stop-color="#F3DDC4"/>
<stop offset="0.378992" stop-color="#F3DDC4"/>
<stop offset="0.46253" stop-color="#F3DDC4"/>
<stop offset="1" stop-color="#74523F"/>
</linearGradient>
<linearGradient id="paint10_linear_526_16911" x1="45.5572" y1="97.6751" x2="72.5071" y2="100.013" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8B697"/>
<stop offset="0.374385" stop-color="#F0D6BD"/>
<stop offset="0.71988" stop-color="#C89E78"/>
<stop offset="0.9381" stop-color="#DFCBB9"/>
</linearGradient>
<linearGradient id="paint11_linear_526_16911" x1="32.0819" y1="91.0691" x2="43.7538" y2="91.6041" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8B697"/>
<stop offset="0.374385" stop-color="#F0D6BD"/>
<stop offset="0.71988" stop-color="#C89E78"/>
<stop offset="0.9381" stop-color="#DFCBB9"/>
</linearGradient>
<linearGradient id="paint12_linear_526_16911" x1="85.7082" y1="91.0689" x2="74.0362" y2="91.6038" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8B697"/>
<stop offset="0.374385" stop-color="#F0D6BD"/>
<stop offset="0.71988" stop-color="#C89E78"/>
<stop offset="0.9381" stop-color="#DFCBB9"/>
</linearGradient>
<linearGradient id="paint13_linear_526_16911" x1="84.7453" y1="93.492" x2="75.1874" y2="94.1817" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8B697"/>
<stop offset="0.374385" stop-color="#F0D6BD"/>
<stop offset="0.71988" stop-color="#C89E78"/>
<stop offset="0.9381" stop-color="#DFCBB9"/>
</linearGradient>
<linearGradient id="paint14_linear_526_16911" x1="47.0705" y1="91.8995" x2="69.2081" y2="100.974" gradientUnits="userSpaceOnUse">
<stop offset="0.192454" stop-color="#DFCBB9"/>
<stop offset="0.475371" stop-color="#D8B290"/>
<stop offset="0.678187" stop-color="#DEBFA4"/>
<stop offset="0.866423" stop-color="#DFCBB9"/>
</linearGradient>
<linearGradient id="paint15_linear_526_16911" x1="33.1835" y1="93.492" x2="42.7413" y2="94.1817" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8B697"/>
<stop offset="0.374385" stop-color="#F0D6BD"/>
<stop offset="0.71988" stop-color="#C89E78"/>
<stop offset="0.9381" stop-color="#DFCBB9"/>
</linearGradient>
<linearGradient id="paint16_linear_526_16911" x1="44.3202" y1="22.3261" x2="75.2575" y2="83.7882" gradientUnits="userSpaceOnUse">
<stop stop-color="#F1DCC1"/>
<stop offset="1" stop-color="#BD8A65"/>
</linearGradient>
<linearGradient id="paint17_linear_526_16911" x1="44.0458" y1="27.4132" x2="70.7207" y2="76.6379" gradientUnits="userSpaceOnUse">
<stop stop-color="#BF8E69"/>
<stop offset="1" stop-color="#E4C196"/>
</linearGradient>
<linearGradient id="paint18_linear_526_16911" x1="46.9333" y1="25.6257" x2="75.6706" y2="77.3254" gradientUnits="userSpaceOnUse">
<stop stop-color="#EBD4B7"/>
<stop offset="1" stop-color="#FFE3CE"/>
</linearGradient>
<radialGradient id="paint19_radial_526_16911" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(58.8944 52.6297) rotate(90) scale(31.1285 26.8123)">
<stop stop-color="#E3C29C"/>
<stop offset="1" stop-color="#D1A479"/>
</radialGradient>
<linearGradient id="paint20_linear_526_16911" x1="58.895" y1="20.2683" x2="58.895" y2="84.9877" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint21_linear_526_16911" x1="25.7934" y1="26.1245" x2="58.1531" y2="82.1732" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint22_linear_526_16911" x1="89.5874" y1="26.1231" x2="57.2277" y2="82.1717" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint23_linear_526_16911" x1="58.8944" y1="64.0306" x2="58.7326" y2="81.597" gradientUnits="userSpaceOnUse">
<stop offset="0.291667" stop-color="#FFFAF7" stop-opacity="0"/>
<stop offset="1" stop-color="#E4CBAD"/>
</linearGradient>
<linearGradient id="paint24_linear_526_16911" x1="55.5568" y1="33.2706" x2="75.9617" y2="40.7123" gradientUnits="userSpaceOnUse">
<stop offset="0.168737" stop-color="#FFEFDC"/>
<stop offset="0.360439" stop-color="#FFF3E3"/>
<stop offset="0.47754" stop-color="#FFEAD1"/>
<stop offset="0.655872" stop-color="#F0DBC0"/>
<stop offset="0.80404" stop-color="#F0DBC2"/>
</linearGradient>
<clipPath id="clip0_526_16911">
<rect width="30.88" height="30.88" fill="white" transform="translate(42.5625 37.1477)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,5 +0,0 @@
<svg width="51" height="32" viewBox="0 0 51 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.7379 0L10.6008 14.9453V0H2.65247V0.0144333H0V2.58624H2.6524V31H10.6009L36.9964 0.0144333L23.7379 0Z" fill="#334155"/>
<path d="M42.0275 31.2999C39.9989 31.2999 38.3084 30.8266 36.956 29.8799C35.6036 28.9332 34.6182 27.7257 34 26.2574L38.2021 23.4174C38.6078 24.422 39.1101 25.1755 39.7091 25.6778C40.308 26.1801 41.0615 26.4313 41.9695 26.4313C42.5491 26.4313 43.0611 26.325 43.5054 26.1125C43.9691 25.8807 44.3169 25.5522 44.5487 25.1272C44.7999 24.7021 44.9255 24.1998 44.9255 23.6202C44.9255 23.0406 44.7999 22.5383 44.5487 22.1133C44.3169 21.6689 43.9788 21.3308 43.5344 21.099C43.1094 20.8478 42.6071 20.7126 42.0275 20.6932C41.6411 20.6739 41.2933 20.7222 40.9842 20.8381C40.6944 20.9347 40.3273 21.1183 39.8829 21.3888L36.927 17.7083L44.8385 10.4053L46.1136 13.0425H34.8694V8H49.2145V12.376L44.0271 16.6071L44.085 16.752C45.2056 16.752 46.2392 17.0514 47.1859 17.6503C48.1326 18.2299 48.8764 19.051 49.4174 20.1136C49.9776 21.1569 50.2578 22.3258 50.2578 23.6202C50.2578 25.1079 49.9004 26.4313 49.1855 27.5905C48.49 28.7497 47.5143 29.6577 46.2585 30.3146C45.0221 30.9715 43.6117 31.2999 42.0275 31.2999Z" fill="#334155"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,400 +0,0 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5" filter="url(#filter0_f_526_16957)">
<circle cx="60.494" cy="7.28428" r="3.41099" fill="url(#paint0_radial_526_16957)"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.9255 106.961C87.2412 106.961 109.385 84.8176 109.385 57.5019C109.385 30.1862 87.2412 8.04252 59.9255 8.04252C32.6098 8.04252 10.4661 30.1862 10.4661 57.5019C10.4661 84.8176 32.6098 106.961 59.9255 106.961ZM59.9255 108.098C87.8691 108.098 110.522 85.4455 110.522 57.5019C110.522 29.5583 87.8691 6.90552 59.9255 6.90552C31.9819 6.90552 9.3291 29.5583 9.3291 57.5019C9.3291 85.4455 31.9819 108.098 59.9255 108.098Z" fill="url(#paint1_linear_526_16957)"/>
<g filter="url(#filter1_f_526_16957)">
<path d="M60.4954 1.60059L60.9831 6.76312L65.8014 7.28557L60.9831 7.80802L60.4954 12.9706L60.0078 7.80802L55.1895 7.28557L60.0078 6.76312L60.4954 1.60059Z" fill="white"/>
</g>
<path d="M54.2333 41.9522L52.248 -3H68.1302L66.1449 41.9522H54.2333Z" fill="url(#paint2_linear_526_16957)"/>
<path d="M72.8091 54.8035L112.731 75.5603L104.79 89.3147L66.8533 65.1193L72.8091 54.8035Z" fill="url(#paint3_linear_526_16957)"/>
<path d="M65.8604 65.917L67.8457 110.869H51.9635L53.9488 65.917H65.8604Z" fill="url(#paint4_linear_526_16957)"/>
<path d="M53.3141 65.2621L15.377 89.4575L7.43587 75.7032L47.3582 54.9464L53.3141 65.2621Z" fill="url(#paint5_linear_526_16957)"/>
<path d="M72.8091 53.1374L112.731 32.3806L104.79 18.6263L66.8533 42.8216L72.8091 53.1374Z" fill="url(#paint6_linear_526_16957)"/>
<path d="M53.314 42.6788L15.377 18.4834L7.43587 32.2378L47.3582 52.9945L53.314 42.6788Z" fill="url(#paint7_linear_526_16957)"/>
<g filter="url(#filter2_dd_526_16957)">
<path d="M31.3799 48.4875L31.3799 37.3146L4.99941 37.3146C4.99941 44.2977 9.93611 48.4875 13.9472 48.4875L31.3799 48.4875Z" fill="url(#paint8_linear_526_16957)"/>
<path d="M31.3799 48.4875L31.3799 37.3146L4.99941 37.3146C4.99941 44.2977 9.93611 48.4875 13.9472 48.4875L31.3799 48.4875Z" stroke="url(#paint9_linear_526_16957)" stroke-width="0.620717"/>
<path d="M40.6924 58.418L40.6924 47.2451L14.3119 47.2451C14.3119 54.2281 19.2486 58.418 23.2597 58.418L40.6924 58.418Z" fill="url(#paint10_linear_526_16957)"/>
<path d="M40.6924 58.418L40.6924 47.2451L14.3119 47.2451C14.3119 54.2281 19.2486 58.418 23.2597 58.418L40.6924 58.418Z" stroke="url(#paint11_linear_526_16957)" stroke-width="0.620717"/>
<path d="M89.5078 48.4868L89.5078 37.3139L115.888 37.3139C115.888 44.297 110.952 48.4868 106.941 48.4868L89.5078 48.4868Z" fill="url(#paint12_linear_526_16957)"/>
<path d="M89.5078 48.4868L89.5078 37.3139L115.888 37.3139C115.888 44.297 110.952 48.4868 106.941 48.4868L89.5078 48.4868Z" stroke="url(#paint13_linear_526_16957)" stroke-width="0.620717"/>
<path d="M80.1943 58.418L80.1943 47.2451L106.575 47.2451C106.575 54.2281 101.638 58.418 97.627 58.418L80.1943 58.418Z" fill="url(#paint14_linear_526_16957)"/>
<path d="M80.1943 58.418L80.1943 47.2451L106.575 47.2451C106.575 54.2281 101.638 58.418 97.627 58.418L80.1943 58.418Z" stroke="url(#paint15_linear_526_16957)" stroke-width="0.620717"/>
<g filter="url(#filter3_d_526_16957)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.293 71.948V95.9858L53.2819 100.279C53.4622 100.376 53.6806 100.246 53.6806 100.041C53.6798 91.0843 53.6885 80.8618 53.6885 71.948H45.293Z" fill="#9CB7ED"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.293 71.948V95.9858L53.2819 100.279C53.4622 100.376 53.6806 100.246 53.6806 100.041C53.6798 91.0843 53.6885 80.8618 53.6885 71.948H45.293Z" stroke="url(#paint16_linear_526_16957)" stroke-width="0.270569"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.9875 94.9101V85.4771H45.8341V95.0379L45.8982 95.0486C46.1102 95.0839 46.3461 95.2094 46.5387 95.3928C46.7311 95.576 46.8734 95.8105 46.9088 96.0576L46.9146 96.0987L53.1211 99.2761V88.6244H52.9677V99.0348L47.0538 95.9945C47.0034 95.721 46.8447 95.4724 46.6445 95.2817C46.4524 95.0988 46.2162 94.9635 45.9875 94.9101Z" fill="url(#paint17_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.9609 98.4732C53.1263 98.1935 53.1358 97.7968 53.123 97.5405L52.9698 97.5482C52.9825 97.8033 52.969 98.1583 52.8289 98.3951C52.761 98.5099 52.6648 98.5944 52.5265 98.6292C52.3857 98.6645 52.1912 98.6508 51.9236 98.5467L51.868 98.6897C52.1527 98.8004 52.3813 98.8238 52.5639 98.778C52.7488 98.7315 52.8761 98.6166 52.9609 98.4732Z" fill="#E3EFFB"/>
<g filter="url(#filter4_d_526_16957)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.9404 95.4449V71.9482H34.1961V90.2022C34.1961 90.513 34.3735 90.7965 34.653 90.9323L43.9404 95.4449Z" fill="#C6D9FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.9404 95.4449V71.9482H34.1961V90.2022C34.1961 90.513 34.3735 90.7965 34.653 90.9323L43.9404 95.4449Z" stroke="url(#paint18_linear_526_16957)" stroke-width="0.270569"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.9212 89.5724V78.4414H34.7402V89.7232L34.8158 89.7358C35.0661 89.7775 35.3443 89.9256 35.5716 90.142C35.7986 90.3582 35.9666 90.6348 36.0083 90.9265L36.0152 90.9749L43.3389 94.405V82.1553H43.1578V94.1203L36.1795 90.852C36.1199 90.5293 35.9327 90.2359 35.6964 90.0109C35.4698 89.795 35.1911 89.6355 34.9212 89.5724Z" fill="url(#paint19_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.1459 93.4582C43.3411 93.1282 43.3522 92.6601 43.3371 92.3577L43.1563 92.3667C43.1714 92.6677 43.1554 93.0866 42.9901 93.366C42.9099 93.5015 42.7965 93.6013 42.6333 93.6423C42.4672 93.684 42.2377 93.6678 41.9219 93.545L41.8563 93.7137C42.1922 93.8443 42.462 93.8719 42.6773 93.8178C42.8956 93.763 43.0457 93.6275 43.1459 93.4582Z" fill="#F2F9FF"/>
<g filter="url(#filter5_d_526_16957)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.8749 71.948V95.9858L67.886 100.279C67.7058 100.376 67.4874 100.246 67.4874 100.041C67.4882 91.0843 67.4795 80.8618 67.4795 71.948H75.8749Z" fill="#9CB7ED"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.8749 71.948V95.9858L67.886 100.279C67.7058 100.376 67.4874 100.246 67.4874 100.041C67.4882 91.0843 67.4795 80.8618 67.4795 71.948H75.8749Z" stroke="url(#paint20_linear_526_16957)" stroke-width="0.270569"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.1785 94.9101V85.4771H75.3319V95.0379L75.2678 95.0486C75.0558 95.0839 74.8199 95.2094 74.6273 95.3928C74.435 95.576 74.2926 95.8105 74.2573 96.0576L74.2514 96.0987L68.0449 99.2761V88.6244H68.1983V99.0348L74.1122 95.9945C74.1627 95.721 74.3213 95.4724 74.5215 95.2817C74.7137 95.0988 74.9498 94.9635 75.1785 94.9101Z" fill="url(#paint21_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M68.2071 98.4732C68.0416 98.1935 68.0322 97.7968 68.045 97.5405L68.1982 97.5482C68.1855 97.8033 68.199 98.1583 68.3391 98.3951C68.407 98.5099 68.5031 98.5944 68.6415 98.6292C68.7822 98.6645 68.9767 98.6508 69.2443 98.5467L69.2999 98.6897C69.0153 98.8004 68.7866 98.8238 68.6041 98.778C68.4192 98.7315 68.2919 98.6166 68.2071 98.4732Z" fill="#E3EFFB"/>
<g filter="url(#filter6_d_526_16957)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M77.2236 95.4449V71.9482H86.968V90.2022C86.968 90.513 86.7905 90.7965 86.511 90.9323L77.2236 95.4449Z" fill="#C6D9FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M77.2236 95.4449V71.9482H86.968V90.2022C86.968 90.513 86.7905 90.7965 86.511 90.9323L77.2236 95.4449Z" stroke="url(#paint22_linear_526_16957)" stroke-width="0.270569"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M86.2487 89.5724V78.4414H86.4297V89.7232L86.3541 89.7358C86.1039 89.7775 85.8256 89.9256 85.5983 90.142C85.3713 90.3582 85.2033 90.6348 85.1616 90.9265L85.1547 90.9749L77.8311 94.405V82.1553H78.0121V94.1203L84.9904 90.852C85.05 90.5293 85.2372 90.2359 85.4735 90.0109C85.7002 89.795 85.9788 89.6355 86.2487 89.5724Z" fill="url(#paint23_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.0211 93.4582C77.8259 93.1282 77.8148 92.6601 77.8299 92.3577L78.0107 92.3667C77.9956 92.6677 78.0116 93.0866 78.1769 93.366C78.2571 93.5015 78.3705 93.6013 78.5337 93.6423C78.6998 93.684 78.9293 93.6678 79.2451 93.545L79.3107 93.7137C78.9748 93.8443 78.705 93.8719 78.4896 93.8178C78.2714 93.763 78.1212 93.6275 78.0211 93.4582Z" fill="#F2F9FF"/>
<g filter="url(#filter7_d_526_16957)">
<path d="M51.2432 64.9126H69.9124V102.707L60.8657 108.226C60.616 108.378 60.3022 108.378 60.0527 108.226L51.2432 102.837V64.9126Z" fill="#6A88C4"/>
<path d="M51.2432 64.9126H69.9124V102.707L60.8657 108.226C60.616 108.378 60.3022 108.378 60.0527 108.226L51.2432 102.837V64.9126Z" stroke="url(#paint24_linear_526_16957)" stroke-width="0.541138"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.1904 88.4529H52.461V102.041L60.4434 106.964L68.5599 102.04V88.4529H68.8304V102.193L60.4422 107.281L52.1904 102.192V88.4529Z" fill="url(#paint25_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.5614 101.44V106.349H60.3262V101.44H60.5614Z" fill="url(#paint26_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.7378 104.606C59.2697 105.146 58.6993 105.266 58.3066 105.2L58.3453 104.968C58.6582 105.02 59.1462 104.929 59.5601 104.452C59.9756 103.972 60.3251 103.093 60.3251 101.556H60.5603C60.5603 103.124 60.2042 104.068 59.7378 104.606Z" fill="url(#paint27_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M61.148 104.606C61.6161 105.146 62.1864 105.266 62.5791 105.201L62.5404 104.969C62.2276 105.021 61.7395 104.93 61.3257 104.452C60.9102 103.973 60.5606 103.094 60.5606 101.557H60.3255C60.3255 103.125 60.6815 104.068 61.148 104.606Z" fill="url(#paint28_linear_526_16957)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.3939 106.457L57.7383 104.969L58.3263 104.969L60.4431 106.221L62.5597 104.958H63.0787L60.4916 106.457C60.4606 106.471 60.4249 106.471 60.3939 106.457Z" fill="url(#paint29_linear_526_16957)"/>
<g filter="url(#filter8_d_526_16957)">
<path d="M58.2462 15.7826L29.0247 31.6788C27.5445 32.484 26.6231 34.0342 26.6231 35.7193L26.623 72.0562C26.623 73.7413 27.5445 75.2915 29.0247 76.0967L58.2462 91.9928C59.6167 92.7384 61.2717 92.7384 62.6422 91.9928L91.8636 76.0967C93.3438 75.2915 94.2653 73.7413 94.2653 72.0562L94.2653 35.7193C94.2653 34.0342 93.3438 32.484 91.8636 31.6788L62.6422 15.7826C61.2717 15.0371 59.6167 15.0371 58.2462 15.7826Z" fill="url(#paint30_linear_526_16957)"/>
<path d="M58.2147 21.7315L34.1573 34.8184C32.6826 35.6206 31.7647 37.165 31.7647 38.8438L31.7646 68.7968C31.7646 70.4755 32.6826 72.0199 34.1573 72.8221L58.2147 85.909C59.58 86.6518 61.2288 86.6518 62.5941 85.909L86.6515 72.8221C88.1262 72.0199 89.0442 70.4755 89.0442 68.7968L89.0442 38.8438C89.0442 37.165 88.1262 35.6207 86.6516 34.8184L62.5941 21.7315C61.2288 20.9887 59.58 20.9887 58.2147 21.7315Z" fill="url(#paint31_linear_526_16957)"/>
<g filter="url(#filter9_f_526_16957)">
<path d="M58.2147 21.7315L34.1573 34.8184C32.6826 35.6206 31.7647 37.165 31.7647 38.8438L31.7646 68.7968C31.7646 70.4755 32.6826 72.0199 34.1573 72.8221L58.2147 85.909C59.58 86.6518 61.2288 86.6518 62.5941 85.909L86.6515 72.8221C88.1262 72.0199 89.0442 70.4755 89.0442 68.7968L89.0442 38.8438C89.0442 37.165 88.1262 35.6207 86.6516 34.8184L62.5941 21.7315C61.2288 20.9887 59.58 20.9887 58.2147 21.7315Z" stroke="url(#paint32_linear_526_16957)" stroke-width="0.270569"/>
</g>
<g opacity="0.5">
<mask id="mask0_526_16957" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="34" y="23" width="53" height="61">
<path d="M58.4972 24.3774L36.4756 36.3461C35.0721 37.109 34.1982 38.5784 34.1982 40.1759L34.1982 67.5705C34.1982 69.1679 35.0721 70.6374 36.4756 71.4002L58.4972 83.369C59.7952 84.0744 61.3622 84.0744 62.6602 83.369L84.6818 71.4002C86.0854 70.6374 86.9592 69.1679 86.9592 67.5705L86.9592 40.1759C86.9592 38.5784 86.0854 37.109 84.6818 36.3461L62.6602 24.3774C61.3622 23.6719 59.7952 23.6719 58.4972 24.3774Z" fill="#A6D3DE"/>
</mask>
<g mask="url(#mask0_526_16957)">
<path d="M58.4972 24.3774L36.4756 36.3461C35.0721 37.109 34.1982 38.5784 34.1982 40.1759L34.1982 67.5705C34.1982 69.1679 35.0721 70.6374 36.4756 71.4002L58.4972 83.369C59.7952 84.0744 61.3622 84.0744 62.6602 83.369L84.6818 71.4002C86.0854 70.6374 86.9592 69.1679 86.9592 67.5705L86.9592 40.1759C86.9592 38.5784 86.0854 37.109 84.6818 36.3461L62.6602 24.3774C61.3622 23.6719 59.7952 23.6719 58.4972 24.3774Z" fill="url(#paint33_radial_526_16957)"/>
<path opacity="0.5" d="M56.1681 85.7104L55.1201 22.0334H66.0362L64.9882 85.7104H56.1681Z" fill="url(#paint34_linear_526_16957)"/>
<path opacity="0.5" d="M56.0283 85.1441L23.2822 30.5222L32.7358 25.0642L63.6668 80.7341L56.0283 85.1441Z" fill="url(#paint35_linear_526_16957)"/>
<path opacity="0.5" d="M55.1189 80.7343L86.0498 25.0645L95.5034 30.5225L62.7573 85.1444L55.1189 80.7343Z" fill="url(#paint36_linear_526_16957)"/>
<rect x="34.1982" y="50.2324" width="52.7609" height="34.2643" fill="url(#paint37_linear_526_16957)"/>
<g filter="url(#filter10_i_526_16957)">
<path d="M58.4972 24.3774L36.4756 36.3461C35.0721 37.109 34.1982 38.5784 34.1982 40.1759L34.1982 67.5705C34.1982 69.1679 35.0721 70.6374 36.4756 71.4002L58.4972 83.369C59.7952 84.0744 61.3622 84.0744 62.6602 83.369L84.6818 71.4002C86.0854 70.6374 86.9592 69.1679 86.9592 67.5705L86.9592 40.1759C86.9592 38.5784 86.0854 37.109 84.6818 36.3461L62.6602 24.3774C61.3622 23.6719 59.7952 23.6719 58.4972 24.3774Z" fill="#6052B4" fill-opacity="0.01"/>
</g>
</g>
</g>
</g>
</g>
<g clip-path="url(#clip0_526_16957)">
<g filter="url(#filter11_d_526_16957)">
<path d="M60.5409 40.8918L45.3496 49.7535L60.5409 58.6151L73.2004 51.2304V60.514H75.7323V49.7535L60.5409 40.8918ZM50.4121 55.4382V61.1471C52.7218 64.2219 56.3989 66.2109 60.5407 66.2109C64.6823 66.2109 68.3595 64.2219 70.6691 61.1471L70.6686 55.4392L60.5413 61.3468L50.4121 55.4382Z" fill="url(#paint38_linear_526_16957)"/>
</g>
</g>
<defs>
<filter id="filter0_f_526_16957" x="56.325" y="3.11529" width="8.33826" height="8.33802" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.378999" result="effect1_foregroundBlur_526_16957"/>
</filter>
<filter id="filter1_f_526_16957" x="54.9621" y="1.37319" width="11.0671" height="11.8247" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.1137" result="effect1_foregroundBlur_526_16957"/>
</filter>
<filter id="filter2_dd_526_16957" x="-11.8684" y="-0.230612" width="144.625" height="126.503" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.75964"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.558594 0 0 0 0 0.710156 0 0 0 0 0.9375 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.10386"/>
<feGaussianBlur stdDeviation="8.27893"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.670833 0 0 0 0 0.8025 0 0 0 0 1 0 0 0 0.6 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_526_16957" result="effect2_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter3_d_526_16957" x="42.9937" y="69.6482" width="12.9951" height="32.9634" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.08228"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.288889 0 0 0 0 0.357333 0 0 0 0 0.533333 0 0 0 0.74 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter4_d_526_16957" x="31.896" y="69.6484" width="14.3447" height="28.1772" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.08228"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.288889 0 0 0 0 0.357333 0 0 0 0 0.533333 0 0 0 0.74 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter5_d_526_16957" x="65.1792" y="69.6482" width="12.9951" height="32.9634" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.08228"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.288889 0 0 0 0 0.357333 0 0 0 0 0.533333 0 0 0 0.74 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter6_d_526_16957" x="74.9233" y="69.6484" width="14.3447" height="28.1772" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.08228"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.288889 0 0 0 0 0.357333 0 0 0 0 0.533333 0 0 0 0.74 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter7_d_526_16957" x="48.8081" y="62.4775" width="23.5391" height="48.2979" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.08228"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.288889 0 0 0 0 0.357333 0 0 0 0 0.533333 0 0 0 0.8 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter8_d_526_16957" x="24.9996" y="15.2234" width="70.8894" height="81.1166" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.16455"/>
<feGaussianBlur stdDeviation="0.811707"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.141181 0 0 0 0 0.190317 0 0 0 0 0.316667 0 0 0 0.51 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<filter id="filter9_f_526_16957" x="31.5477" y="20.9579" width="57.7131" height="65.7246" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.0405853" result="effect1_foregroundBlur_526_16957"/>
</filter>
<filter id="filter10_i_526_16957" x="34.1982" y="23.8484" width="52.7607" height="60.0496" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.81934"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.297222 0 0 0 0 0.329917 0 0 0 0 0.445833 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_526_16957"/>
</filter>
<filter id="filter11_d_526_16957" x="43.8074" y="40.0801" width="33.6296" height="28.5659" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.0811707" dy="0.811707"/>
<feGaussianBlur stdDeviation="0.811707"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.625387 0 0 0 0 0.624375 0 0 0 0 0.675 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_526_16957"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_526_16957" result="shape"/>
</filter>
<radialGradient id="paint0_radial_526_16957" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60.494 7.28428) rotate(90) scale(5.45759)">
<stop stop-color="#E7ECFF"/>
<stop offset="0.979167" stop-color="#ABC3FF" stop-opacity="0.8825"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.88"/>
</radialGradient>
<linearGradient id="paint1_linear_526_16957" x1="60.115" y1="7.47402" x2="60.115" y2="88.5799" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEE7FF"/>
<stop offset="0.0722622" stop-color="#CCDDFF" stop-opacity="0.33"/>
<stop offset="1" stop-color="#98C6F0" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_526_16957" x1="60.1891" y1="-3" x2="60.1891" y2="41.9522" gradientUnits="userSpaceOnUse">
<stop stop-color="#AACCFF" stop-opacity="0"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint3_linear_526_16957" x1="108.761" y1="82.4375" x2="69.8312" y2="59.9614" gradientUnits="userSpaceOnUse">
<stop stop-color="#AACCFF" stop-opacity="0"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint4_linear_526_16957" x1="59.9046" y1="110.869" x2="59.9046" y2="65.917" gradientUnits="userSpaceOnUse">
<stop stop-color="#AACCFF" stop-opacity="0"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint5_linear_526_16957" x1="11.4064" y1="82.5803" x2="50.3361" y2="60.1042" gradientUnits="userSpaceOnUse">
<stop stop-color="#AACCFF" stop-opacity="0"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint6_linear_526_16957" x1="108.761" y1="25.5034" x2="69.8312" y2="47.9795" gradientUnits="userSpaceOnUse">
<stop stop-color="#AACCFF" stop-opacity="0"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint7_linear_526_16957" x1="11.4064" y1="25.3606" x2="50.3361" y2="47.8367" gradientUnits="userSpaceOnUse">
<stop stop-color="#AACCFF" stop-opacity="0"/>
<stop offset="1" stop-color="#AACCFF" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint8_linear_526_16957" x1="21.1381" y1="48.4875" x2="11.6721" y2="39.0216" gradientUnits="userSpaceOnUse">
<stop stop-color="#C3C5D1"/>
<stop offset="0.875" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="paint9_linear_526_16957" x1="6.5512" y1="37.3146" x2="15.5516" y2="48.4876" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9F9F9"/>
<stop offset="0.378992" stop-color="#F9F9F9"/>
<stop offset="0.480868" stop-color="#DFDFE6"/>
<stop offset="0.729174" stop-color="#B8BAC6"/>
</linearGradient>
<linearGradient id="paint10_linear_526_16957" x1="27.6573" y1="57.0214" x2="18.1914" y2="47.2451" gradientUnits="userSpaceOnUse">
<stop stop-color="#C3C5D1"/>
<stop offset="0.875" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="paint11_linear_526_16957" x1="15.7085" y1="47.2451" x2="20.5191" y2="57.0214" gradientUnits="userSpaceOnUse">
<stop stop-color="#F4F4F5"/>
<stop offset="0.306027" stop-color="#F4F4F5"/>
<stop offset="0.462087" stop-color="#E8E9EC"/>
<stop offset="0.682292" stop-color="#DFDFE6"/>
<stop offset="1" stop-color="#B8BAC6"/>
</linearGradient>
<linearGradient id="paint12_linear_526_16957" x1="99.7496" y1="48.4868" x2="109.216" y2="39.0209" gradientUnits="userSpaceOnUse">
<stop stop-color="#C3C5D1"/>
<stop offset="0.875" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="paint13_linear_526_16957" x1="114.336" y1="37.3139" x2="105.336" y2="48.4868" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9F9F9"/>
<stop offset="0.378992" stop-color="#F9F9F9"/>
<stop offset="0.480868" stop-color="#DFDFE6"/>
<stop offset="0.729174" stop-color="#B8BAC6"/>
</linearGradient>
<linearGradient id="paint14_linear_526_16957" x1="93.2294" y1="57.0214" x2="102.695" y2="47.2451" gradientUnits="userSpaceOnUse">
<stop stop-color="#C3C5D1"/>
<stop offset="0.875" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="paint15_linear_526_16957" x1="105.178" y1="47.2451" x2="100.368" y2="57.0214" gradientUnits="userSpaceOnUse">
<stop stop-color="#F4F4F5"/>
<stop offset="0.306027" stop-color="#F4F4F5"/>
<stop offset="0.462087" stop-color="#E8E9EC"/>
<stop offset="0.682292" stop-color="#DFDFE6"/>
<stop offset="1" stop-color="#B8BAC6"/>
</linearGradient>
<linearGradient id="paint16_linear_526_16957" x1="53.6885" y1="91.1672" x2="44.8411" y2="92.5366" gradientUnits="userSpaceOnUse">
<stop stop-color="#91B1E3"/>
<stop offset="0.208702" stop-color="#F0F6FF"/>
<stop offset="0.514766" stop-color="#A4BFE9"/>
<stop offset="0.728274" stop-color="#8BADE0"/>
<stop offset="0.971703" stop-color="#D0DEF3"/>
</linearGradient>
<linearGradient id="paint17_linear_526_16957" x1="46.178" y1="93.2731" x2="50.5634" y2="91.9657" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFCFF"/>
<stop offset="0.309893" stop-color="#D7E6FC"/>
<stop offset="0.523799" stop-color="#C1D8F4"/>
<stop offset="0.77229" stop-color="#BBD6FF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint18_linear_526_16957" x1="33.9254" y1="85.4767" x2="44.3719" y2="97.1809" gradientUnits="userSpaceOnUse">
<stop stop-color="#91B1E3"/>
<stop offset="0.208702" stop-color="#F0F6FF"/>
<stop offset="0.648432" stop-color="#A4BFE9"/>
<stop offset="0.780206" stop-color="#8BADE0"/>
<stop offset="0.971703" stop-color="#93B3E4"/>
</linearGradient>
<linearGradient id="paint19_linear_526_16957" x1="35.146" y1="87.6408" x2="40.3208" y2="86.098" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFCFF"/>
<stop offset="0.309893" stop-color="#D7E6FC"/>
<stop offset="0.523799" stop-color="#C1D8F4"/>
<stop offset="0.77229" stop-color="#BBD6FF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint20_linear_526_16957" x1="67.4795" y1="91.1672" x2="76.3269" y2="92.5366" gradientUnits="userSpaceOnUse">
<stop stop-color="#91B1E3"/>
<stop offset="0.208702" stop-color="#F0F6FF"/>
<stop offset="0.514766" stop-color="#A4BFE9"/>
<stop offset="0.728274" stop-color="#8BADE0"/>
<stop offset="0.971703" stop-color="#D0DEF3"/>
</linearGradient>
<linearGradient id="paint21_linear_526_16957" x1="74.9881" y1="93.2731" x2="70.6026" y2="91.9657" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFCFF"/>
<stop offset="0.309893" stop-color="#D7E6FC"/>
<stop offset="0.523799" stop-color="#C1D8F4"/>
<stop offset="0.77229" stop-color="#BBD6FF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint22_linear_526_16957" x1="87.2386" y1="85.4767" x2="76.7922" y2="97.1809" gradientUnits="userSpaceOnUse">
<stop stop-color="#91B1E3"/>
<stop offset="0.208702" stop-color="#F0F6FF"/>
<stop offset="0.648432" stop-color="#A4BFE9"/>
<stop offset="0.780206" stop-color="#8BADE0"/>
<stop offset="0.971703" stop-color="#93B3E4"/>
</linearGradient>
<linearGradient id="paint23_linear_526_16957" x1="86.024" y1="87.6408" x2="80.8491" y2="86.098" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFCFF"/>
<stop offset="0.309893" stop-color="#D7E6FC"/>
<stop offset="0.523799" stop-color="#C1D8F4"/>
<stop offset="0.77229" stop-color="#BBD6FF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint24_linear_526_16957" x1="51.2432" y1="95.8927" x2="70.5104" y2="100.007" gradientUnits="userSpaceOnUse">
<stop stop-color="#91B1E3"/>
<stop offset="0.208702" stop-color="#F0F6FF"/>
<stop offset="0.648432" stop-color="#A4BFE9"/>
<stop offset="0.780206" stop-color="#8BADE0"/>
<stop offset="0.971703" stop-color="#93B3E4"/>
</linearGradient>
<linearGradient id="paint25_linear_526_16957" x1="68.0452" y1="99.3028" x2="59.2462" y2="94.9989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFCFF"/>
<stop offset="0.309893" stop-color="#D7E6FC"/>
<stop offset="0.523799" stop-color="#C1D8F4"/>
<stop offset="0.77229" stop-color="#BBD6FF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint26_linear_526_16957" x1="60.4438" y1="101.44" x2="60.4438" y2="106.026" gradientUnits="userSpaceOnUse">
<stop stop-color="#6A88C3"/>
<stop offset="0.479167" stop-color="#E4EFFD"/>
<stop offset="1" stop-color="#E1ECFD"/>
</linearGradient>
<linearGradient id="paint27_linear_526_16957" x1="59.4335" y1="101.556" x2="59.4335" y2="105.216" gradientUnits="userSpaceOnUse">
<stop stop-color="#E2F5FF" stop-opacity="0"/>
<stop offset="0.479167" stop-color="#B6CAFE"/>
<stop offset="1" stop-color="#CCD7FF"/>
</linearGradient>
<linearGradient id="paint28_linear_526_16957" x1="61.4523" y1="101.557" x2="61.4523" y2="105.217" gradientUnits="userSpaceOnUse">
<stop stop-color="#E2F5FF" stop-opacity="0"/>
<stop offset="0.479167" stop-color="#B6CAFE"/>
<stop offset="1" stop-color="#CCD7FF"/>
</linearGradient>
<linearGradient id="paint29_linear_526_16957" x1="60.1583" y1="105.474" x2="57.1075" y2="105.835" gradientUnits="userSpaceOnUse">
<stop stop-color="#C9DEFC"/>
<stop offset="0.309893" stop-color="#ECF4FF"/>
<stop offset="0.523799" stop-color="#EAF6FF"/>
<stop offset="0.613641" stop-color="#CCE7FF"/>
<stop offset="1" stop-color="#CEE4F9"/>
</linearGradient>
<linearGradient id="paint30_linear_526_16957" x1="46.2393" y1="24.0568" x2="76.6783" y2="84.529" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAFAFA"/>
<stop offset="1" stop-color="#C2C4CD"/>
</linearGradient>
<linearGradient id="paint31_linear_526_16957" x1="45.9695" y1="29.0632" x2="72.2147" y2="77.495" gradientUnits="userSpaceOnUse">
<stop stop-color="#CDCFD6"/>
<stop offset="1" stop-color="#EFEFF1"/>
</linearGradient>
<linearGradient id="paint32_linear_526_16957" x1="48.8105" y1="27.3045" x2="77.0849" y2="78.1715" gradientUnits="userSpaceOnUse">
<stop stop-color="#DDDFE2"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<radialGradient id="paint33_radial_526_16957" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60.5787 53.8732) rotate(90) scale(30.6271 26.3805)">
<stop stop-color="#F5F7FF"/>
<stop offset="1" stop-color="#B3B5C5"/>
</radialGradient>
<linearGradient id="paint34_linear_526_16957" x1="60.5781" y1="22.0334" x2="60.5781" y2="85.7104" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint35_linear_526_16957" x1="28.009" y1="27.7932" x2="59.8475" y2="82.9391" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint36_linear_526_16957" x1="90.7766" y1="27.7935" x2="58.9381" y2="82.9394" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F2EDED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint37_linear_526_16957" x1="60.5787" y1="65.0904" x2="60.4195" y2="82.3738" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="#D0DBFF"/>
</linearGradient>
<linearGradient id="paint38_linear_526_16957" x1="58.1346" y1="34.5452" x2="78.2109" y2="41.8669" gradientUnits="userSpaceOnUse">
<stop offset="0.168737" stop-color="#F4F3F8"/>
<stop offset="0.360439" stop-color="white"/>
<stop offset="0.451944" stop-color="white"/>
<stop offset="0.627859" stop-color="#E7E6EE"/>
<stop offset="0.80404" stop-color="#F3F3F3"/>
</linearGradient>
<clipPath id="clip0_526_16957">
<rect width="30.3827" height="30.3827" fill="white" transform="translate(45.3496 38.3594)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,5 +0,0 @@
<svg width="51" height="31" viewBox="0 0 51 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.7379 0L10.6008 14.9453V0H2.65247V0.0144333H0V2.58624H2.6524V31H10.6009L36.9964 0.0144333L23.7379 0Z" fill="#334155"/>
<path d="M47.3594 30.8942H41.9691V10.2315H43.9687L38.2597 21.8524H50.1705V26.895H32V23.5912L39.9115 8H47.3594V30.8942Z" fill="#334155"/>
</svg>

Before

Width:  |  Height:  |  Size: 377 B

View File

@@ -51,6 +51,6 @@ export const APP_CONFIG = {
refreshThreshold: 5 * 60 * 1000 // 提前5分钟刷新
}
};
export const PUBLIC_IMG_PATH = '/schoolNewsWeb/img';
export default APP_CONFIG;

View File

@@ -0,0 +1,114 @@
import { BaseDTO } from '../base';
import { AchievementEventType } from '../enums';
/**
* 成就实体
*/
export interface Achievement extends BaseDTO {
/** 成就唯一标识 */
achievementID?: string;
/** 成就名称 */
name?: string;
/** 成就描述 */
description?: string;
/** 成就图标 */
icon?: string;
/** 成就类型1勋章 2等级 */
type?: number;
/** 成就等级 */
level?: number;
/** 获取条件类型1学习时长 2资源数量 3课程数量 4连续学习天数 */
conditionType?: number;
/** 条件值 */
conditionValue?: number;
/** 获得积分 */
points?: number;
/** 排序号 */
orderNum?: number;
/** 创建者 */
creator?: string;
/** 更新者 */
updater?: string;
}
/**
* 用户成就实体
*/
export interface UserAchievement extends BaseDTO {
/** 用户ID */
userID?: string;
/** 成就ID */
achievementID?: string;
/** 获得时间 */
obtainTime?: string;
}
/**
* 用户成就进度实体
*/
export interface UserAchievementProgress extends BaseDTO {
/** 用户ID */
userID?: string;
/** 成就ID */
achievementID?: string;
/** 当前进度值 */
currentValue?: number;
/** 目标进度值 */
targetValue?: number;
/** 进度百分比 (0-100) */
progressPercentage?: number;
/** 是否已完成 */
completed?: boolean;
/** 最后更新时间 */
lastUpdateTime?: string;
}
/**
* 成就视图对象 - 包含成就信息、获得状态和进度信息
*/
export interface AchievementVO extends Achievement {
// ==================== 用户成就表的字段 ====================
/** 用户成就记录ID */
userAchievementID?: string;
/** 用户ID */
userID?: string;
/** 获得时间 */
obtainTime?: string;
// ==================== 成就进度表的字段 ====================
/** 进度记录ID */
progressID?: string;
/** 当前进度值 */
currentValue?: number;
/** 目标进度值 */
targetValue?: number;
/** 进度百分比 (0-100) */
progressPercentage?: number;
/** 是否已完成 */
completed?: boolean;
/** 最后更新时间 */
lastUpdateTime?: string;
// ==================== 扩展字段 ====================
/** 是否已获得该成就 */
obtained?: boolean;
}
/**
* 成就事件实体
* 用于触发成就检测和授予
* @since 2025-10-24
*/
export interface AchievementEvent {
/** 用户ID */
userID: string;
/** 事件类型 */
eventType: AchievementEventType;
/** 事件值(如学习时长、资源数量等) */
eventValue?: number;
/** 事件时间 */
eventTime?: string;
/** 额外数据 */
extraData?: Record<string, any>;
}

View File

@@ -0,0 +1 @@
export * from './achievement';

View File

@@ -0,0 +1,186 @@
/**
* @description 成就相关枚举辅助工具
* @author yslg
* @since 2025-10-25
*/
import { AchievementType, AchievementConditionType, AchievementEventType } from './index';
/**
* 成就类型描述映射
*/
export const AchievementTypeDescriptions: Record<AchievementType, string> = {
[AchievementType.BADGE]: '勋章',
[AchievementType.LEVEL]: '等级'
};
/**
* 成就条件类型描述映射
*/
export const AchievementConditionTypeDescriptions: Record<AchievementConditionType, string> = {
[AchievementConditionType.LEARNING_TIME]: '学习时长',
[AchievementConditionType.RESOURCE_VIEW_COUNT]: '浏览资源数量',
[AchievementConditionType.COURSE_COMPLETE_COUNT]: '完成课程数量',
[AchievementConditionType.CONTINUOUS_LOGIN_DAYS]: '连续登录天数',
[AchievementConditionType.RESOURCE_COLLECT_COUNT]: '收藏资源数量',
[AchievementConditionType.TASK_COMPLETE_COUNT]: '完成任务数量',
[AchievementConditionType.POINTS_EARNED]: '获得积分数量',
[AchievementConditionType.COMMENT_COUNT]: '发表评论数量',
[AchievementConditionType.CHAPTER_COMPLETE_COUNT]: '完成章节数量',
[AchievementConditionType.TOTAL_LOGIN_DAYS]: '累计登录天数'
};
/**
* 成就事件类型描述映射
*/
export const AchievementEventTypeDescriptions: Record<AchievementEventType, string> = {
// 学习相关事件
[AchievementEventType.LEARNING_TIME_UPDATED]: '学习时长更新',
[AchievementEventType.COURSE_COMPLETED]: '课程完成',
[AchievementEventType.COURSE_STARTED]: '开始学习课程',
[AchievementEventType.CHAPTER_COMPLETED]: '章节完成',
// 资源相关事件
[AchievementEventType.RESOURCE_VIEWED]: '浏览资源',
[AchievementEventType.RESOURCE_COLLECTED]: '收藏资源',
[AchievementEventType.RESOURCE_SHARED]: '分享资源',
// 任务相关事件
[AchievementEventType.TASK_COMPLETED]: '任务完成',
[AchievementEventType.TASK_ITEM_COMPLETED]: '任务项完成',
// 互动相关事件
[AchievementEventType.COMMENT_POSTED]: '发表评论',
[AchievementEventType.LIKE_GIVEN]: '点赞',
// 登录相关事件
[AchievementEventType.USER_LOGIN]: '用户登录',
[AchievementEventType.CONTINUOUS_LOGIN]: '连续登录',
// 积分相关事件
[AchievementEventType.POINTS_EARNED_EVENT]: '获得积分',
// 测试相关事件
[AchievementEventType.TEST_PASSED]: '测试通过',
[AchievementEventType.TEST_PERFECT_SCORE]: '测试满分'
};
/**
* 成就枚举辅助类
*/
export class AchievementEnumHelper {
/**
* 获取成就类型描述
* @param type 成就类型
* @returns 描述文本
*/
static getAchievementTypeDescription(type: AchievementType): string {
return AchievementTypeDescriptions[type] || '未知类型';
}
/**
* 获取成就条件类型描述
* @param type 条件类型
* @returns 描述文本
*/
static getConditionTypeDescription(type: AchievementConditionType): string {
return AchievementConditionTypeDescriptions[type] || '未知条件';
}
/**
* 获取成就事件类型描述
* @param type 事件类型
* @returns 描述文本
*/
static getEventTypeDescription(type: AchievementEventType): string {
return AchievementEventTypeDescriptions[type] || '未知事件';
}
/**
* 根据code获取成就条件类型
* @param code 条件类型代码
* @returns 条件类型枚举值或null
*/
static getConditionTypeFromCode(code: number): AchievementConditionType | null {
const types = Object.values(AchievementConditionType).filter(
(value): value is AchievementConditionType => typeof value === 'number'
);
return types.find(type => type === code) || null;
}
/**
* 根据code获取成就事件类型
* @param code 事件类型代码
* @returns 事件类型枚举值或null
*/
static getEventTypeFromCode(code: string): AchievementEventType | null {
const types = Object.values(AchievementEventType).filter(
(value): value is AchievementEventType => typeof value === 'string'
);
return types.find(type => type === code) || null;
}
/**
* 获取所有成就条件类型选项(用于下拉框等)
* @returns 选项数组
*/
static getAllConditionTypeOptions(): Array<{ value: AchievementConditionType; label: string }> {
return Object.entries(AchievementConditionTypeDescriptions).map(([value, label]) => ({
value: Number(value) as AchievementConditionType,
label
}));
}
/**
* 获取所有成就类型选项(用于下拉框等)
* @returns 选项数组
*/
static getAllAchievementTypeOptions(): Array<{ value: AchievementType; label: string }> {
return Object.entries(AchievementTypeDescriptions).map(([value, label]) => ({
value: Number(value) as AchievementType,
label
}));
}
/**
* 获取所有成就事件类型选项(用于下拉框等)
* @returns 选项数组
*/
static getAllEventTypeOptions(): Array<{ value: AchievementEventType; label: string }> {
return Object.entries(AchievementEventTypeDescriptions).map(([value, label]) => ({
value: value as AchievementEventType,
label
}));
}
/**
* 格式化条件值显示
* @param conditionType 条件类型
* @param conditionValue 条件值
* @returns 格式化后的显示文本
*/
static formatConditionValue(conditionType: AchievementConditionType, conditionValue: number): string {
const typeDesc = this.getConditionTypeDescription(conditionType);
switch (conditionType) {
case AchievementConditionType.LEARNING_TIME:
return `${typeDesc}达到${conditionValue}分钟`;
case AchievementConditionType.RESOURCE_VIEW_COUNT:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.COURSE_COMPLETE_COUNT:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.CONTINUOUS_LOGIN_DAYS:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.RESOURCE_COLLECT_COUNT:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.TASK_COMPLETE_COUNT:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.POINTS_EARNED:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.COMMENT_COUNT:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.CHAPTER_COMPLETE_COUNT:
return `${typeDesc}达到${conditionValue}`;
case AchievementConditionType.TOTAL_LOGIN_DAYS:
return `${typeDesc}达到${conditionValue}`;
default:
return `${typeDesc}达到${conditionValue}`;
}
}
}

View File

@@ -210,3 +210,90 @@ export enum TaskItemType {
/** 课程类型 */
COURSE = 2
}
/**
* 成就类型枚举
*/
export enum AchievementType {
/** 勋章 */
BADGE = 1,
/** 等级 */
LEVEL = 2
}
/**
* 成就条件类型枚举
*/
export enum AchievementConditionType {
/** 学习时长(分钟) */
LEARNING_TIME = 1,
/** 浏览资源数量 */
RESOURCE_VIEW_COUNT = 2,
/** 完成课程数量 */
COURSE_COMPLETE_COUNT = 3,
/** 连续登录天数 */
CONTINUOUS_LOGIN_DAYS = 4,
/** 收藏资源数量 */
RESOURCE_COLLECT_COUNT = 5,
/** 完成任务数量 */
TASK_COMPLETE_COUNT = 6,
/** 获得积分数量 */
POINTS_EARNED = 7,
/** 发表评论数量 */
COMMENT_COUNT = 8,
/** 完成章节数量 */
CHAPTER_COMPLETE_COUNT = 9,
/** 累计登录天数 */
TOTAL_LOGIN_DAYS = 10
}
/**
* 成就事件类型枚举
*/
export enum AchievementEventType {
// ==================== 学习相关事件 ====================
/** 学习时长更新 */
LEARNING_TIME_UPDATED = 'learning_time_updated',
/** 课程完成 */
COURSE_COMPLETED = 'course_completed',
/** 开始学习课程 */
COURSE_STARTED = 'course_started',
/** 章节完成 */
CHAPTER_COMPLETED = 'chapter_completed',
// ==================== 资源相关事件 ====================
/** 浏览资源 */
RESOURCE_VIEWED = 'resource_viewed',
/** 收藏资源 */
RESOURCE_COLLECTED = 'resource_collected',
/** 分享资源 */
RESOURCE_SHARED = 'resource_shared',
// ==================== 任务相关事件 ====================
/** 任务完成 */
TASK_COMPLETED = 'task_completed',
/** 任务项完成 */
TASK_ITEM_COMPLETED = 'task_item_completed',
// ==================== 互动相关事件 ====================
/** 发表评论 */
COMMENT_POSTED = 'comment_posted',
/** 点赞 */
LIKE_GIVEN = 'like_given',
// ==================== 登录相关事件 ====================
/** 用户登录 */
USER_LOGIN = 'user_login',
/** 连续登录 */
CONTINUOUS_LOGIN = 'continuous_login',
// ==================== 积分相关事件 ====================
/** 获得积分 */
POINTS_EARNED_EVENT = 'points_earned',
// ==================== 测试相关事件 ====================
/** 测试通过 */
TEST_PASSED = 'test_passed',
/** 测试满分 */
TEST_PERFECT_SCORE = 'test_perfect_score'
}

View File

@@ -22,6 +22,11 @@ export * from './menu';
// 权限相关
export * from './permission';
// 系统相关
export * from './module';
export * from './achievement';
// 认证相关
export * from './auth';
@@ -42,6 +47,7 @@ export * from './usercenter';
// 枚举类型
export * from './enums';
export * from './enums/achievement-enums';
// 常量
export * from './constants';

View File

@@ -0,0 +1,162 @@
/**
* @description 系统相关类型定义
* @author yslg
* @since 2025-10-25
*/
import { BaseDTO } from '../base';
/**
* 系统模块
*/
export interface SysModule extends BaseDTO {
/** 模块ID */
moduleID?: string;
/** 模块名称 */
name?: string;
/** 模块代码 */
code?: string;
/** 模块描述 */
description?: string;
/** 模块图标 */
icon?: string;
/** 模块排序号 */
orderNum?: number;
/** 模块状态0禁用 1启用 */
status?: number;
/** 创建者 */
creator?: string;
/** 更新者 */
updater?: string;
}
/**
* 系统配置
*/
export interface SysConfig extends BaseDTO {
/** 配置键 */
configKey?: string;
/** 配置值 */
configValue?: string;
/** 配置名称 */
configName?: string;
/** 配置描述 */
description?: string;
/** 配置类型 */
configType?: string;
/** 是否系统内置 */
isSystem?: boolean;
/** 创建者 */
creator?: string;
/** 更新者 */
updater?: string;
}
/**
* 系统字典类型
*/
export interface SysDictType extends BaseDTO {
/** 字典类型 */
dictType?: string;
/** 字典名称 */
dictName?: string;
/** 状态0禁用 1启用 */
status?: number;
/** 备注 */
remark?: string;
/** 创建者 */
creator?: string;
/** 更新者 */
updater?: string;
}
/**
* 系统字典数据
*/
export interface SysDictData extends BaseDTO {
/** 字典排序 */
dictSort?: number;
/** 字典标签 */
dictLabel?: string;
/** 字典键值 */
dictValue?: string;
/** 字典类型 */
dictType?: string;
/** 状态0禁用 1启用 */
status?: number;
/** 是否默认 */
isDefault?: boolean;
/** 备注 */
remark?: string;
/** 创建者 */
creator?: string;
/** 更新者 */
updater?: string;
}
/**
* 系统通知
*/
export interface SysNotification extends BaseDTO {
/** 通知ID */
notificationID?: string;
/** 通知标题 */
title?: string;
/** 通知内容 */
content?: string;
/** 通知类型 */
type?: number;
/** 接收用户ID */
receiverID?: string;
/** 是否已读 */
isRead?: boolean;
/** 已读时间 */
readTime?: string;
/** 创建者 */
creator?: string;
}
/**
* 系统操作日志
*/
export interface SysOperationLog extends BaseDTO {
/** 操作用户 */
operator?: string;
/** 操作模块 */
module?: string;
/** 操作类型 */
operationType?: string;
/** 操作方法 */
method?: string;
/** 请求参数 */
requestParams?: string;
/** 返回结果 */
responseResult?: string;
/** 操作状态0失败 1成功 */
status?: number;
/** 错误消息 */
errorMsg?: string;
/** 操作IP */
operationIP?: string;
/** 操作时间 */
operationTime?: string;
/** 执行时长(毫秒) */
duration?: number;
}
/**
* 系统访问统计
*/
export interface SysVisitStatistics extends BaseDTO {
/** 统计日期 */
statisticsDate?: string;
/** 访问量 */
visitCount?: number;
/** 独立访客数 */
uniqueVisitors?: number;
/** 页面浏览量 */
pageViews?: number;
/** 平均访问时长(秒) */
avgDuration?: number;
}

View File

@@ -7,6 +7,7 @@
import { BaseDTO } from '../base';
import { SysMenu } from '../menu';
import { SysRole } from '../role';
import { SysModule } from '../module';
/**
* 系统权限
@@ -16,6 +17,7 @@ export interface SysPermission extends BaseDTO {
id?:string;
/** 权限ID */
permissionID?: string;
moduleID?: string;
/** 权限名称 */
name?: string;
/** 权限描述 */
@@ -34,5 +36,5 @@ export interface SysPermission extends BaseDTO {
menus?: SysMenu[];
roles?: SysRole[];
permissions?: SysPermission[];
}
module?: SysModule;
}

View File

@@ -68,38 +68,6 @@ export interface PointsRecord extends BaseDTO {
relatedType?: number;
}
/**
* 用户成就实体
*/
export interface UserAchievement extends BaseDTO {
/** 用户ID */
userID?: string;
/** 成就ID */
achievementID?: string;
/** 获得时间 */
achieveTime?: string;
}
/**
* 成就实体
*/
export interface Achievement extends BaseDTO {
/** 成就名称 */
name?: string;
/** 成就描述 */
description?: string;
/** 成就图标 */
icon?: string;
/** 成就类型 */
type?: number;
/** 获得条件 */
condition?: string;
/** 奖励积分 */
rewardPoints?: number;
/** 状态0禁用 1启用 */
status?: number;
}
/**
* 个人中心统计信息
*/

View File

@@ -0,0 +1,618 @@
<template>
<div class="achievement-management">
<div class="header">
<h2>成就管理</h2>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增成就
</el-button>
</div>
<!-- 筛选条件 -->
<div class="filter-bar">
<div class="filter-item">
<span class="filter-label">成就类型</span>
<el-select v-model="filter.type" placeholder="全部" clearable style="width: 150px">
<el-option
v-for="option in achievementTypeOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">成就等级</span>
<el-input-number
v-model="filter.level"
:min="0"
:max="10"
placeholder="全部"
clearable
style="width: 120px"
/>
</div>
<div class="filter-item">
<span class="filter-label">条件类型</span>
<el-select v-model="filter.conditionType" placeholder="全部" clearable style="width: 180px">
<el-option
v-for="option in conditionTypeOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
<div class="filter-actions">
<el-button type="primary" @click="loadAchievementList">查询</el-button>
<el-button @click="resetFilter">重置</el-button>
</div>
</div>
<!-- 成就列表 -->
<el-table
:data="achievementList"
style="width: 100%"
v-loading="loading"
border
stripe
row-key="achievementID"
>
<el-table-column prop="icon" label="图标" width="80" align="center">
<template #default="{ row }">
<el-image
:src="getIconUrl(row.icon)"
style="width: 48px; height: 48px"
fit="contain"
:preview-src-list="[getIconUrl(row.icon)]"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column prop="name" label="成就名称" min-width="150" />
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
<el-table-column prop="type" label="类型" width="100">
<template #default="{ row }">
<el-tag :type="row.type === 1 ? 'success' : 'primary'">
{{ getAchievementTypeLabel(row.type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="level" label="等级" width="80" align="center" />
<el-table-column prop="conditionType" label="条件类型" width="150">
<template #default="{ row }">
{{ getConditionTypeLabel(row.conditionType) }}
</template>
</el-table-column>
<el-table-column prop="conditionValue" label="条件值" width="100" align="center" />
<el-table-column prop="points" label="积分奖励" width="100" align="center" />
<el-table-column prop="orderNum" label="排序号" width="80" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
@click="handleEdit(row)"
link
>
编辑
</el-button>
<el-button
type="info"
size="small"
@click="handleViewUsers(row)"
link
>
查看获得者
</el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
link
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增/编辑成就对话框 -->
<el-dialog
v-model="achievementDialogVisible"
:title="isEdit ? '编辑成就' : '新增成就'"
width="700px"
@close="resetForm"
>
<el-form
ref="achievementFormRef"
:model="currentAchievement"
:rules="achievementFormRules"
label-width="120px"
>
<el-form-item label="成就名称" prop="name">
<el-input v-model="currentAchievement.name" placeholder="请输入成就名称" />
</el-form-item>
<el-form-item label="成就描述" prop="description">
<el-input
v-model="currentAchievement.description"
type="textarea"
:rows="3"
placeholder="请输入成就描述"
/>
</el-form-item>
<el-form-item label="成就图标" prop="icon">
<el-input
v-model="currentAchievement.icon"
placeholder="请输入图标路径,如:/img/achievement/v1-icon.svg 或完整URL"
>
<template #append>
<el-button @click="showIconUpload = true">上传</el-button>
</template>
</el-input>
<div v-if="currentAchievement.icon" style="margin-top: 10px">
<div style="display: flex; align-items: center; gap: 10px;">
<el-image
:src="getIconUrl(currentAchievement.icon)"
style="width: 80px; height: 80px"
fit="contain"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<div style="font-size: 12px; color: #909399;">
<div>路径: {{ currentAchievement.icon }}</div>
<div>完整URL: {{ getIconUrl(currentAchievement.icon) }}</div>
</div>
</div>
</div>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="成就类型" prop="type">
<el-select v-model="currentAchievement.type" placeholder="请选择成就类型" style="width: 100%">
<el-option
v-for="option in achievementTypeOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成就等级" prop="level">
<el-input-number
v-model="currentAchievement.level"
:min="1"
:max="10"
placeholder="请输入等级"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="条件类型" prop="conditionType">
<el-select
v-model="currentAchievement.conditionType"
placeholder="请选择条件类型"
style="width: 100%"
>
<el-option
v-for="option in conditionTypeOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="条件值" prop="conditionValue">
<el-input-number
v-model="currentAchievement.conditionValue"
:min="1"
placeholder="请输入条件值"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="条件预览" v-if="currentAchievement.conditionType && currentAchievement.conditionValue">
<el-alert
:title="formatConditionValue(currentAchievement.conditionType, currentAchievement.conditionValue)"
type="info"
:closable="false"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="积分奖励" prop="points">
<el-input-number
v-model="currentAchievement.points"
:min="0"
placeholder="请输入积分奖励"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="排序号" prop="orderNum">
<el-input-number
v-model="currentAchievement.orderNum"
:min="0"
placeholder="请输入排序号"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="achievementDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveAchievement" :loading="submitting">
{{ isEdit ? '更新' : '创建' }}
</el-button>
</template>
</el-dialog>
<!-- 查看获得者对话框 -->
<el-dialog
v-model="usersDialogVisible"
title="成就获得者列表"
width="900px"
>
<div class="achievement-info-bar">
<el-alert
:title="`成就:${currentAchievement.name} - ${currentAchievement.description}`"
type="info"
:closable="false"
style="margin-bottom: 20px"
/>
</div>
<el-table
:data="achieverList"
v-loading="achieversLoading"
border
stripe
>
<el-table-column prop="userID" label="用户ID" width="200" />
<el-table-column prop="username" label="用户名" min-width="120" />
<el-table-column prop="realName" label="真实姓名" min-width="100" />
<el-table-column prop="obtainTime" label="获得时间" width="180" />
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button
type="danger"
size="small"
@click="handleRevokeAchievement(row)"
>
撤销成就
</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Plus, Picture } from '@element-plus/icons-vue';
import { achievementApi } from '@/apis/achievement';
import type { Achievement, UserAchievement } from '@/types';
import { AchievementEnumHelper } from '@/types/enums/achievement-enums';
import { PUBLIC_IMG_PATH } from '@/config';
// 响应式数据
const loading = ref(false);
const submitting = ref(false);
const achieversLoading = ref(false);
const achievementList = ref<Achievement[]>([]);
const achieverList = ref<UserAchievement[]>([]);
// 对话框控制
const achievementDialogVisible = ref(false);
const usersDialogVisible = ref(false);
const showIconUpload = ref(false);
const isEdit = ref(false);
// 筛选条件
const filter = ref<Partial<Achievement>>({});
// 当前操作的成就
const currentAchievement = ref<Achievement>({});
// 枚举选项
const achievementTypeOptions = AchievementEnumHelper.getAllAchievementTypeOptions();
const conditionTypeOptions = AchievementEnumHelper.getAllConditionTypeOptions();
// 表单引用
const achievementFormRef = ref();
// 表单验证规则
const achievementFormRules = {
name: [
{ required: true, message: '请输入成就名称', trigger: 'blur' }
],
description: [
{ required: true, message: '请输入成就描述', trigger: 'blur' }
],
icon: [
{ required: true, message: '请输入图标URL', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择成就类型', trigger: 'change' }
],
level: [
{ required: true, message: '请输入成就等级', trigger: 'blur' }
],
conditionType: [
{ required: true, message: '请选择条件类型', trigger: 'change' }
],
conditionValue: [
{ required: true, message: '请输入条件值', trigger: 'blur' }
],
points: [
{ required: true, message: '请输入积分奖励', trigger: 'blur' }
],
orderNum: [
{ required: true, message: '请输入排序号', trigger: 'blur' }
]
};
// 获取成就类型标签
function getAchievementTypeLabel(type?: number): string {
if (type === undefined) return '未知';
return AchievementEnumHelper.getAchievementTypeDescription(type);
}
// 获取条件类型标签
function getConditionTypeLabel(type?: number): string {
if (type === undefined) return '未知';
return AchievementEnumHelper.getConditionTypeDescription(type);
}
// 格式化条件值显示
function formatConditionValue(conditionType?: number, conditionValue?: number): string {
if (conditionType === undefined || conditionValue === undefined) return '';
return AchievementEnumHelper.formatConditionValue(conditionType, conditionValue);
}
// 获取图标完整路径
function getIconUrl(icon?: string): string {
if (!icon) return '';
// 如果是http或https开头直接返回
if (icon.startsWith('http://') || icon.startsWith('https://')) {
return icon;
}
// 否则拼接默认成就图标路径
const path = `${PUBLIC_IMG_PATH}/achievement`;
return icon.startsWith('/') ? `${path}${icon}` : `${path}/${icon}`;
}
// 加载成就列表
async function loadAchievementList() {
try {
loading.value = true;
const result = await achievementApi.getAllAchievements(filter.value);
achievementList.value = result.dataList || [];
} catch (error) {
console.error('加载成就列表失败:', error);
ElMessage.error('加载成就列表失败');
} finally {
loading.value = false;
}
}
// 重置筛选条件
function resetFilter() {
filter.value = {};
loadAchievementList();
}
// 新增成就
function handleAdd() {
isEdit.value = false;
currentAchievement.value = {
type: 1,
level: 1,
points: 0,
orderNum: 0
};
achievementDialogVisible.value = true;
}
// 编辑成就
function handleEdit(row: Achievement) {
isEdit.value = true;
currentAchievement.value = { ...row };
achievementDialogVisible.value = true;
}
// 删除成就
async function handleDelete(row: Achievement) {
try {
await ElMessageBox.confirm(
`确定要删除成就 "${row.name}" 吗?删除后无法恢复。`,
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
await achievementApi.deleteAchievement(row);
ElMessage.success('删除成功');
await loadAchievementList();
} catch (error) {
if (error !== 'cancel') {
console.error('删除成就失败:', error);
ElMessage.error('删除成就失败');
}
}
}
// 保存成就
async function saveAchievement() {
try {
await achievementFormRef.value?.validate();
submitting.value = true;
if (isEdit.value) {
await achievementApi.updateAchievement(currentAchievement.value);
ElMessage.success('更新成功');
} else {
await achievementApi.createAchievement(currentAchievement.value);
ElMessage.success('创建成功');
}
achievementDialogVisible.value = false;
await loadAchievementList();
} catch (error) {
console.error('保存成就失败:', error);
if (error !== false) {
ElMessage.error('保存成就失败');
}
} finally {
submitting.value = false;
}
}
// 查看获得者
async function handleViewUsers(row: Achievement) {
currentAchievement.value = { ...row };
usersDialogVisible.value = true;
try {
achieversLoading.value = true;
const result = await achievementApi.getRecentAchievers(
{ page: 1, size: 100 },
{ achievementID: row.achievementID }
);
achieverList.value = result.dataList || [];
} catch (error) {
console.error('加载获得者列表失败:', error);
ElMessage.error('加载获得者列表失败');
} finally {
achieversLoading.value = false;
}
}
// 撤销成就
async function handleRevokeAchievement(row: UserAchievement) {
try {
await ElMessageBox.confirm(
'确定要撤销该用户的成就吗?',
'确认撤销',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
await achievementApi.revokeAchievement(row.userID || '', row.achievementID || '');
ElMessage.success('撤销成功');
// 刷新获得者列表
await handleViewUsers(currentAchievement.value);
} catch (error) {
if (error !== 'cancel') {
console.error('撤销成就失败:', error);
ElMessage.error('撤销成就失败');
}
}
}
// 重置表单
function resetForm() {
currentAchievement.value = {};
achievementFormRef.value?.clearValidate();
}
// 组件挂载时加载数据
onMounted(() => {
loadAchievementList();
});
</script>
<style scoped lang="scss">
.achievement-management {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #141F38;
}
}
.filter-bar {
display: flex;
align-items: center;
gap: 16px;
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
.filter-item {
display: flex;
align-items: center;
gap: 8px;
.filter-label {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
}
.filter-actions {
display: flex;
gap: 8px;
margin-left: auto;
}
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f5f5;
color: #ccc;
font-size: 24px;
}
.achievement-info-bar {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,2 @@
export { default as AchievementManagementView } from './AchievementManagementView.vue';

File diff suppressed because it is too large Load Diff

View File

@@ -1,721 +0,0 @@
<template>
<div class="permission-manage">
<div class="header">
<h2>权限管理</h2>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增权限
</el-button>
</div>
<el-table
:data="permissionList"
style="width: 100%"
v-loading="loading"
border
stripe
>
<el-table-column prop="name" label="权限名称" min-width="150" />
<el-table-column prop="code" label="权限编码" min-width="200" />
<el-table-column prop="description" label="权限描述" min-width="200" show-overflow-tooltip />
<el-table-column prop="creatorName" label="创建人" width="120" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
@click="handleEdit(row)"
link
>
编辑
</el-button>
<el-button
type="primary"
size="small"
@click="handleBindMenu(row)"
link
>
绑定菜单
</el-button>
<el-button
type="primary"
size="small"
@click="handleBindRole(row)"
link
>
绑定角色
</el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
link
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑权限' : '新增权限'"
width="500px"
@close="resetForm"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="权限名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入权限名称"
clearable
/>
</el-form-item>
<el-form-item label="权限编码" prop="code">
<el-input
v-model="formData.code"
placeholder="请输入权限编码"
clearable
/>
</el-form-item>
<el-form-item label="权限描述" prop="description">
<el-input
v-model="formData.description"
type="textarea"
:rows="3"
placeholder="请输入权限描述"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
type="primary"
@click="handleSubmit"
:loading="submitting"
>
确定
</el-button>
</template>
</el-dialog>
<!-- 绑定菜单对话框 -->
<el-dialog v-model="bindMenuDialogVisible" title="绑定菜单" width="800px" @close="resetBindList">
<div class="menu-binding-container">
<!-- 权限信息显示 -->
<div class="permission-info" v-if="currentPermission">
<h4>权限信息{{ currentPermission.name }}</h4>
<p>权限编码{{ currentPermission.code }}</p>
</div>
<!-- 菜单绑定状态表格 -->
<el-table :data="menuList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isMenuSelected(row.menuID) ? 'success' : 'info'"
size="small"
>
{{ isMenuSelected(row.menuID) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="菜单名称" min-width="150" />
<el-table-column prop="menuID" label="菜单ID" min-width="120" />
<el-table-column prop="url" label="菜单路径" min-width="200" show-overflow-tooltip />
<el-table-column prop="component" label="菜单组件" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isMenuSelected(row.menuID) ? 'danger' : 'primary'"
size="small"
@click="toggleMenuSelection(row)"
>
{{ isMenuSelected(row.menuID) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 统计信息 -->
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedMenus.length} 个菜单,未绑定 ${menuList.length - selectedMenus.length} 个菜单`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindMenuDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveMenuBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
<!-- 绑定角色对话框 -->
<el-dialog v-model="bindRoleDialogVisible" title="绑定角色" width="800px" @close="resetBindList">
<div class="role-binding-container">
<!-- 权限信息显示 -->
<div class="permission-info" v-if="currentPermission">
<h4>权限信息{{ currentPermission.name }}</h4>
<p>权限编码{{ currentPermission.code }}</p>
</div>
<!-- 角色绑定状态表格 -->
<el-table :data="roleList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isRoleSelected(row.id) ? 'success' : 'info'"
size="small"
>
{{ isRoleSelected(row.id) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="角色名称" min-width="150" />
<el-table-column prop="id" label="角色ID" min-width="120" />
<el-table-column prop="description" label="角色描述" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isRoleSelected(row.id) ? 'danger' : 'primary'"
size="small"
@click="toggleRoleSelection(row)"
>
{{ isRoleSelected(row.id) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 统计信息 -->
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedRoles.length} 个角色,未绑定 ${roleList.length - selectedRoles.length} 个角色`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindRoleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveRoleBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { permissionApi } from '@/apis/system/permission';
import { roleApi } from '@/apis/system/role';
import { menuApi } from '@/apis/system/menu';
import { SysPermission, SysRole, SysMenu } from '@/types';
import { ref, onMounted, reactive } from 'vue';
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
// 数据状态
const permissionList = ref<SysPermission[]>([]);
const loading = ref(false);
const submitting = ref(false);
const bindList = ref<SysPermission>({
menus: [],
roles: []
});
const roleList = ref<SysRole[]>([]);
const menuList = ref<SysMenu[]>([]);
const selectedMenus = ref<string[]>([]);
const selectedRoles = ref<string[]>([]);
const currentPermission = ref<SysPermission | null>(null);
// 对话框状态
const dialogVisible = ref(false);
const isEdit = ref(false);
const formRef = ref<FormInstance>();
const bindMenuDialogVisible = ref(false);
const bindRoleDialogVisible = ref(false);
const queryFilter = ref<SysPermission>({
name: '',
code: '',
description: ''
});
// 表单数据
const formData = reactive<SysPermission>({
name: '',
code: '',
description: ''
});
// 表单验证规则
const formRules: FormRules = {
name: [
{ required: true, message: '请输入权限名称', trigger: 'blur' },
{ min: 2, max: 50, message: '权限名称长度在 2 到 50 个字符', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入权限编码', trigger: 'blur' },
{
pattern: /^[a-zA-Z][a-zA-Z0-9:_-]*$/,
message: '权限编码只能包含字母、数字、冒号、下划线和横线,且必须以字母开头',
trigger: 'blur'
},
{ min: 2, max: 100, message: '权限编码长度在 2 到 100 个字符', trigger: 'blur' }
],
description: [
{ max: 200, message: '权限描述不能超过 200 个字符', trigger: 'blur' }
]
};
// 加载权限列表
async function loadPermissionList() {
try {
loading.value = true;
const result = await permissionApi.getPermissionList(queryFilter.value);
permissionList.value = result.dataList || [];
} catch (error) {
console.error('加载权限列表失败:', error);
ElMessage.error('加载权限列表失败');
} finally {
loading.value = false;
}
}
// 新增权限
function handleAdd() {
isEdit.value = false;
dialogVisible.value = true;
}
// 编辑权限
function handleEdit(row: SysPermission) {
isEdit.value = true;
Object.assign(formData, row);
dialogVisible.value = true;
}
// 查看绑定菜单
async function handleBindMenu(row: SysPermission) {
currentPermission.value = row;
row.bindType = "menu";
try {
// 获取已绑定的菜单
const bindingResult = await permissionApi.getPermissionBindingList(row);
bindList.value.menus = bindingResult.data?.menus || [];
// 获取所有菜单
const menuResult = await menuApi.getAllMenuList();
menuList.value = menuResult.dataList || [];
// 设置已选中的菜单
selectedMenus.value = bindList.value.menus.map(menu => menu.menuID).filter((id): id is string => !!id);
console.log('已绑定的菜单:', bindList.value.menus);
console.log('所有菜单:', menuList.value);
bindMenuDialogVisible.value = true;
} catch (error) {
console.error('获取菜单绑定信息失败:', error);
ElMessage.error('获取菜单绑定信息失败');
}
}
// 查看绑定角色
async function handleBindRole(row: SysPermission) {
currentPermission.value = row;
row.bindType = "role";
try {
// 获取已绑定的角色
const bindingResult = await permissionApi.getPermissionBindingList(row);
bindList.value.roles = bindingResult.data?.roles || [];
// 获取所有角色
const roleResult = await roleApi.getAllRoles();
roleList.value = roleResult.dataList || [];
// 设置已选中的角色
selectedRoles.value = bindList.value.roles.map(role => role.id).filter((id): id is string => !!id);
console.log('已绑定的角色:', bindList.value.roles);
console.log('所有角色:', roleList.value);
bindRoleDialogVisible.value = true;
} catch (error) {
console.error('获取角色绑定信息失败:', error);
ElMessage.error('获取角色绑定信息失败');
}
}
// 删除权限
async function handleDelete(row: SysPermission) {
try {
await ElMessageBox.confirm(
`确定要删除权限 "${row.name}" 吗?`,
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
await permissionApi.deletePermission(row);
ElMessage.success('删除成功');
await loadPermissionList();
} catch (error) {
if (error !== 'cancel') {
console.error('删除权限失败:', error);
ElMessage.error('删除权限失败');
}
}
}
// 重置绑定列表
function resetBindList() {
bindList.value = {
menus: [],
roles: []
};
selectedMenus.value = [];
selectedRoles.value = [];
currentPermission.value = null;
}
// 检查菜单是否已选中
function isMenuSelected(menuID: string | undefined): boolean {
return menuID ? selectedMenus.value.includes(menuID) : false;
}
// 切换菜单选择状态
function toggleMenuSelection(menu: SysMenu) {
if (!menu.menuID) return;
const index = selectedMenus.value.indexOf(menu.menuID);
if (index > -1) {
selectedMenus.value.splice(index, 1);
} else {
selectedMenus.value.push(menu.menuID);
}
}
// 保存菜单绑定
async function saveMenuBinding() {
if (!currentPermission.value || !currentPermission.value.permissionID) {
ElMessage.error('权限信息不完整');
return;
}
try {
submitting.value = true;
// 获取当前已绑定的菜单ID
const currentBoundMenus = (bindList.value.menus || []).map(menu => menu.menuID).filter((id): id is string => !!id);
// 找出需要绑定的菜单(新增的)
const menusToBind = selectedMenus.value.filter(menuID => !currentBoundMenus.includes(menuID));
// 找出需要解绑的菜单(移除的)
const menusToUnbind = currentBoundMenus.filter(menuID => !selectedMenus.value.includes(menuID));
// 构建需要绑定的菜单对象数组
if (menusToBind.length > 0) {
const menusToBindObjects = menusToBind.map(menuID => {
const menu = menuList.value.find(m => m.menuID === menuID);
return menu || { menuID: menuID };
});
const bindPermission = {
...currentPermission.value,
menus: menusToBindObjects,
permissions: [{permissionID: currentPermission.value.permissionID}]
};
await permissionApi.bindMenu(bindPermission);
}
// 构建需要解绑的菜单对象数组
if (menusToUnbind.length > 0) {
const menusToUnbindObjects = menusToUnbind.map(menuID => {
const menu = menuList.value.find(m => m.menuID === menuID);
return menu || { menuID: menuID };
});
const unbindPermission = {
...currentPermission.value,
menus: menusToUnbindObjects,
permissions: [{permissionID: currentPermission.value.permissionID}]
};
await permissionApi.unbindMenu(unbindPermission);
}
ElMessage.success('菜单绑定保存成功');
bindMenuDialogVisible.value = false;
// 刷新权限列表
await loadPermissionList();
} catch (error) {
console.error('保存菜单绑定失败:', error);
ElMessage.error('保存菜单绑定失败');
} finally {
submitting.value = false;
}
}
// 检查角色是否已选中
function isRoleSelected(roleID: string | undefined): boolean {
return roleID ? selectedRoles.value.includes(roleID) : false;
}
// 切换角色选择状态
function toggleRoleSelection(role: SysRole) {
if (!role.id) return;
const index = selectedRoles.value.indexOf(role.id);
if (index > -1) {
selectedRoles.value.splice(index, 1);
} else {
selectedRoles.value.push(role.id);
}
}
// 保存角色绑定
async function saveRoleBinding() {
if (!currentPermission.value || !currentPermission.value.permissionID) {
ElMessage.error('权限信息不完整');
return;
}
try {
submitting.value = true;
// 获取当前已绑定的角色ID
const currentBoundRoles = (bindList.value.roles || []).map(role => role.id).filter((id): id is string => !!id);
// 找出需要绑定的角色(新增的)
const rolesToBind = selectedRoles.value.filter(roleID => !currentBoundRoles.includes(roleID));
// 找出需要解绑的角色(移除的)
const rolesToUnbind = currentBoundRoles.filter(roleID => !selectedRoles.value.includes(roleID));
// 构建需要绑定的角色对象数组
if (rolesToBind.length > 0) {
const rolesToBindObjects = rolesToBind.map(roleID => {
const role = roleList.value.find(r => r.id === roleID);
return role || { id: roleID };
});
const bindPermission = {
...currentPermission.value,
roles: rolesToBindObjects,
permissions: [{permissionID: currentPermission.value.permissionID}]
};
await permissionApi.bindRole(bindPermission);
}
// 构建需要解绑的角色对象数组
if (rolesToUnbind.length > 0) {
const rolesToUnbindObjects = rolesToUnbind.map(roleID => {
const role = roleList.value.find(r => r.id === roleID);
return role || { id: roleID };
});
const unbindPermission = {
...currentPermission.value,
roles: rolesToUnbindObjects,
permissions: [{permissionID: currentPermission.value.permissionID}]
};
await permissionApi.unbindRole(unbindPermission);
}
ElMessage.success('角色绑定保存成功');
bindRoleDialogVisible.value = false;
// 刷新权限列表
await loadPermissionList();
} catch (error) {
console.error('保存角色绑定失败:', error);
ElMessage.error('保存角色绑定失败');
} finally {
submitting.value = false;
}
}
// 提交表单
async function handleSubmit() {
if (!formRef.value) return;
try {
await formRef.value.validate();
submitting.value = true;
if (isEdit.value) {
await permissionApi.updatePermission(formData);
ElMessage.success('更新成功');
} else {
await permissionApi.addPermission(formData);
ElMessage.success('新增成功');
}
dialogVisible.value = false;
await loadPermissionList();
} catch (error) {
if (error !== false) { // 表单验证失败时error为false
console.error('提交失败:', error);
ElMessage.error(isEdit.value ? '更新失败' : '新增失败');
}
} finally {
submitting.value = false;
}
}
// 重置表单
function resetForm() {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, {
name: '',
code: '',
description: ''
});
}
// 页面加载时获取权限列表
onMounted(() => {
loadPermissionList();
});
</script>
<style scoped lang="scss">
.permission-manage {
padding: 20px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
margin: 0;
color: #303133;
font-size: 20px;
font-weight: 600;
}
}
.el-table {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
}
// 对话框样式优化
:deep(.el-dialog) {
.el-dialog__header {
padding: 20px 20px 10px;
border-bottom: 1px solid #ebeef5;
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.el-dialog__body {
padding: 20px;
}
.el-dialog__footer {
padding: 10px 20px 20px;
border-top: 1px solid #ebeef5;
}
}
// 表单样式优化
:deep(.el-form) {
.el-form-item__label {
font-weight: 500;
color: #606266;
}
.el-input__wrapper {
box-shadow: 0 0 0 1px #dcdfe6 inset;
&:hover {
box-shadow: 0 0 0 1px #c0c4cc inset;
}
&.is-focus {
box-shadow: 0 0 0 1px #409eff inset;
}
}
.el-textarea__inner {
box-shadow: 0 0 0 1px #dcdfe6 inset;
&:hover {
box-shadow: 0 0 0 1px #c0c4cc inset;
}
&:focus {
box-shadow: 0 0 0 1px #409eff inset;
}
}
}
// 按钮样式优化
:deep(.el-button) {
&.is-link {
padding: 0;
margin-right: 12px;
&:last-child {
margin-right: 0;
}
}
}
.menu-binding-container,
.role-binding-container {
.permission-info {
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
h4 {
margin: 0 0 8px 0;
color: #303133;
font-size: 16px;
}
p {
margin: 0;
color: #606266;
font-size: 14px;
}
}
.binding-stats {
margin-top: 20px;
}
}
</style>

View File

@@ -3,59 +3,257 @@
<div class="achievements-header">
<h2>我的成就</h2>
<div class="achievement-stats">
<span>已获得 <strong>{{ earnedCount }}</strong> / {{ totalCount }} 个成就</span>
<div class="stat-item">
<span class="stat-label">已获得</span>
<span class="stat-value">{{ earnedCount }}</span>
<span class="stat-total"> / {{ totalCount }}</span>
</div>
<div class="stat-item">
<span class="stat-label">完成率</span>
<span class="stat-value">{{ completionRate }}%</span>
</div>
</div>
</div>
<div class="achievements-grid">
<!-- 成就类型筛选 -->
<div class="filter-tabs">
<el-radio-group v-model="selectedType" @change="filterAchievements">
<el-radio-button :label="undefined">全部</el-radio-button>
<el-radio-button
v-for="option in achievementTypeOptions"
:key="option.value"
:label="option.value"
>
{{ option.label }}
</el-radio-button>
</el-radio-group>
<el-checkbox v-model="showOnlyEarned" @change="filterAchievements">
仅显示已获得
</el-checkbox>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<el-skeleton :rows="3" animated />
</div>
<!-- 成就网格 -->
<div v-else class="achievements-grid">
<div
class="achievement-item"
v-for="achievement in achievements"
:key="achievement.id"
:class="{ earned: achievement.earned, locked: !achievement.earned }"
v-for="achievement in filteredAchievements"
:key="achievement.achievementID"
:class="{ earned: achievement.obtained, locked: !achievement.obtained }"
>
<div class="achievement-icon">
<img :src="achievement.icon" :alt="achievement.name" />
<div class="achievement-badge" v-if="achievement.earned"></div>
<el-image
:src="getIconUrl(achievement.icon)"
:alt="achievement.name"
fit="contain"
>
<template #error>
<div class="image-placeholder">
<el-icon><Trophy /></el-icon>
</div>
</template>
</el-image>
<div class="achievement-badge" v-if="achievement.obtained">
<el-icon><Check /></el-icon>
</div>
<div class="achievement-level" v-if="achievement.level">
Lv.{{ achievement.level }}
</div>
</div>
<div class="achievement-info">
<h3>{{ achievement.name }}</h3>
<p class="achievement-description">{{ achievement.description }}</p>
<div class="achievement-progress" v-if="!achievement.earned && achievement.progress">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: achievement.progress + '%' }"></div>
</div>
<span class="progress-text">{{ achievement.progress }}%</span>
<div class="achievement-header">
<h3>{{ achievement.name }}</h3>
<el-tag
:type="achievement.type === 1 ? 'success' : 'primary'"
size="small"
>
{{ getAchievementTypeLabel(achievement.type) }}
</el-tag>
</div>
<div class="achievement-date" v-if="achievement.earned">
获得时间{{ achievement.earnedDate }}
<p class="achievement-description">{{ achievement.description }}</p>
<!-- 条件说明 -->
<div class="achievement-condition">
<el-icon><InfoFilled /></el-icon>
<span>{{ formatConditionValue(achievement.conditionType, achievement.conditionValue) }}</span>
</div>
<!-- 进度条 -->
<div class="achievement-progress" v-if="!achievement.obtained">
<div class="progress-info">
<span class="progress-label">进度</span>
<span class="progress-text">
{{ achievement.currentValue || 0 }} / {{ achievement.targetValue || achievement.conditionValue }}
</span>
</div>
<el-progress
:percentage="achievement.progressPercentage || 0"
:color="progressColor"
:show-text="false"
/>
</div>
<!-- 获得信息 -->
<div class="achievement-footer" v-if="achievement.obtained">
<div class="achievement-date">
<el-icon><Calendar /></el-icon>
<span>{{ formatDate(achievement.obtainTime) }}</span>
</div>
<div class="achievement-points" v-if="achievement.points">
<el-icon><Star /></el-icon>
<span>+{{ achievement.points }} 积分</span>
</div>
</div>
<!-- 未获得时显示积分奖励 -->
<div class="achievement-reward" v-else-if="achievement.points">
<el-icon><Present /></el-icon>
<span>奖励 {{ achievement.points }} 积分</span>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<el-empty
v-if="!loading && filteredAchievements.length === 0"
description="暂无成就数据"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { Trophy, Check, InfoFilled, Calendar, Star, Present } from '@element-plus/icons-vue';
import { achievementApi } from '@/apis/achievement';
import type { AchievementVO } from '@/types';
import { AchievementEnumHelper } from '@/types/enums/achievement-enums';
import { PUBLIC_IMG_PATH } from '@/config';
const achievements = ref<any[]>([]);
// 响应式数据
const loading = ref(false);
const achievements = ref<AchievementVO[]>([]);
const selectedType = ref<number | undefined>(undefined);
const showOnlyEarned = ref(false);
// 枚举选项
const achievementTypeOptions = AchievementEnumHelper.getAllAchievementTypeOptions();
// 进度条颜色
const progressColor = [
{ color: '#f56c6c', percentage: 30 },
{ color: '#e6a23c', percentage: 60 },
{ color: '#5cb87a', percentage: 100 }
];
// 已获得数量
const earnedCount = computed(() => {
return achievements.value.filter(a => a.earned).length;
return achievements.value.filter(a => a.obtained).length;
});
// 总数量
const totalCount = computed(() => {
return achievements.value.length;
});
// 完成率
const completionRate = computed(() => {
if (totalCount.value === 0) return 0;
return Math.round((earnedCount.value / totalCount.value) * 100);
});
// 筛选后的成就列表
const filteredAchievements = computed(() => {
let result = achievements.value;
// 按类型筛选
if (selectedType.value !== undefined) {
result = result.filter(a => a.type === selectedType.value);
}
// 仅显示已获得
if (showOnlyEarned.value) {
result = result.filter(a => a.obtained);
}
// 排序:已获得的在前,按等级排序
return result.sort((a, b) => {
if (a.obtained !== b.obtained) {
return a.obtained ? -1 : 1;
}
return (a.level || 0) - (b.level || 0);
});
});
// 获取成就类型标签
function getAchievementTypeLabel(type?: number): string {
if (type === undefined) return '未知';
return AchievementEnumHelper.getAchievementTypeDescription(type);
}
// 格式化条件值显示
function formatConditionValue(conditionType?: number, conditionValue?: number): string {
if (conditionType === undefined || conditionValue === undefined) return '';
return AchievementEnumHelper.formatConditionValue(conditionType, conditionValue);
}
// 格式化日期
function formatDate(dateStr?: string): string {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
// 获取图标完整路径
function getIconUrl(icon?: string): string {
if (!icon) return '';
// 如果是http或https开头直接返回
if (icon.startsWith('http://') || icon.startsWith('https://')) {
return icon;
}
// 否则拼接默认成就图标路径
const path = `${PUBLIC_IMG_PATH}/achievement`;
return icon.startsWith('/') ? `${path}${icon}` : `${path}/${icon}`;
}
// 筛选成就
function filterAchievements() {
// 触发计算属性重新计算
}
// 加载成就数据
async function loadAchievements() {
try {
loading.value = true;
const result = await achievementApi.getMyAchievements();
achievements.value = result.dataList || [];
} catch (error) {
console.error('加载成就数据失败:', error);
ElMessage.error('加载成就数据失败');
} finally {
loading.value = false;
}
}
onMounted(() => {
// TODO: 加载成就数据
loadAchievements();
});
</script>
<style lang="scss" scoped>
.my-achievements {
padding: 20px 0;
.achievements-header {
display: flex;
justify-content: space-between;
@@ -63,27 +261,60 @@ onMounted(() => {
margin-bottom: 32px;
h2 {
font-size: 24px;
font-size: 28px;
font-weight: 600;
color: #141F38;
margin: 0;
}
}
}
.achievement-stats {
font-size: 16px;
color: #666;
display: flex;
gap: 32px;
strong {
color: #C62828;
font-size: 20px;
.stat-item {
display: flex;
align-items: baseline;
gap: 8px;
.stat-label {
font-size: 14px;
color: #666;
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: #C62828;
}
.stat-total {
font-size: 16px;
color: #999;
}
}
}
.filter-tabs {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
}
.loading-container {
padding: 40px 0;
}
.achievements-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.achievement-item {
@@ -91,110 +322,233 @@ onMounted(() => {
gap: 16px;
padding: 20px;
border: 2px solid #e0e0e0;
border-radius: 8px;
border-radius: 12px;
transition: all 0.3s;
background: white;
&.earned {
border-color: #C62828;
background: linear-gradient(135deg, #fff5f5, #ffffff);
border-color: #52c41a;
background: linear-gradient(135deg, #f6ffed, #ffffff);
.achievement-icon {
img {
:deep(.el-image__inner) {
filter: none;
}
}
}
&.locked {
opacity: 0.6;
opacity: 0.75;
.achievement-icon {
img {
:deep(.el-image__inner) {
filter: grayscale(100%);
}
}
}
&:hover.earned {
box-shadow: 0 4px 12px rgba(198, 40, 40, 0.2);
box-shadow: 0 4px 16px rgba(82, 196, 26, 0.3);
transform: translateY(-2px);
}
&:hover.locked {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
.achievement-icon {
position: relative;
width: 64px;
height: 64px;
width: 80px;
height: 80px;
flex-shrink: 0;
img {
:deep(.el-image) {
width: 100%;
height: 100%;
object-fit: contain;
}
.image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 8px;
color: #ccc;
font-size: 32px;
}
}
.achievement-badge {
position: absolute;
bottom: -4px;
top: -4px;
right: -4px;
width: 24px;
height: 24px;
background: #4caf50;
width: 28px;
height: 28px;
background: #52c41a;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-size: 16px;
font-weight: 600;
border: 3px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.achievement-level {
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
padding: 2px 8px;
background: #1890ff;
color: white;
font-size: 12px;
font-weight: 600;
border-radius: 10px;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.achievement-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
h3 {
font-size: 16px;
font-weight: 600;
color: #141F38;
margin-bottom: 8px;
.achievement-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
h3 {
font-size: 18px;
font-weight: 600;
color: #141F38;
margin: 0;
flex: 1;
}
}
}
.achievement-description {
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 12px;
line-height: 1.6;
margin: 0;
}
.achievement-condition {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #999;
padding: 6px 12px;
background: #fafafa;
border-radius: 6px;
:deep(.el-icon) {
font-size: 14px;
}
}
.achievement-progress {
margin-bottom: 8px;
.progress-bar {
width: 100%;
height: 6px;
background: #f5f5f5;
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
.progress-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.progress-label {
font-size: 13px;
color: #666;
font-weight: 500;
}
.progress-text {
font-size: 13px;
color: #1890ff;
font-weight: 600;
}
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #C62828, #E53935);
transition: width 0.3s;
}
.progress-text {
font-size: 12px;
color: #999;
:deep(.el-progress) {
.el-progress-bar__outer {
background-color: #f0f0f0;
}
}
}
.achievement-date {
font-size: 12px;
color: #999;
.achievement-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
.achievement-date,
.achievement-points {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #666;
:deep(.el-icon) {
font-size: 14px;
}
}
.achievement-points {
color: #faad14;
font-weight: 600;
}
}
.achievement-reward {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #faad14;
font-weight: 600;
padding: 6px 12px;
background: #fffbe6;
border-radius: 6px;
:deep(.el-icon) {
font-size: 14px;
}
}
// 响应式设计
@media (max-width: 1200px) {
.achievements-grid {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
}
@media (max-width: 768px) {
.achievements-grid {
grid-template-columns: 1fr;
}
.achievement-stats {
flex-direction: column;
gap: 16px;
}
.filter-tabs {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
}
</style>