diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue index 666f5900..14e47109 100644 --- a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue @@ -408,7 +408,10 @@ async function handleFlipCard(record: CardFlipRecord) { await new Promise(resolve => requestAnimationFrame(resolve)); // 4. 移动克隆卡片到屏幕中心并放大(考虑边界限制) - const scale = Math.min(1.8, window.innerWidth / rect.width * 0.6); // 动态计算缩放比例 + // 移动端使用更小的缩放比例 + const isMobile = window.innerWidth <= 768; + const maxScale = isMobile ? 1.5 : 1.8; + const scale = Math.min(maxScale, window.innerWidth / rect.width * (isMobile ? 0.5 : 0.6)); const scaledWidth = rect.width * scale; const scaledHeight = rect.height * scale; @@ -416,8 +419,8 @@ async function handleFlipCard(record: CardFlipRecord) { let centerX = window.innerWidth / 2; let centerY = window.innerHeight / 2; - // 边界检查:确保卡片完全在视口内(留20px边距) - const margin = 20; + // 边界检查:确保卡片完全在视口内(移动端留更多边距) + const margin = isMobile ? 30 : 20; const minX = scaledWidth / 2 + margin; const maxX = window.innerWidth - scaledWidth / 2 - margin; const minY = scaledHeight / 2 + margin; @@ -1253,6 +1256,11 @@ function getCardClass(record: CardFlipRecord): string[] { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; + @media (max-width: 768px) { + padding: 12px; + border-radius: 12px; + } + /* 自定义滚动条 */ &::-webkit-scrollbar { width: 6px; @@ -1277,15 +1285,36 @@ function getCardClass(record: CardFlipRecord): string[] { .lucky-float-ball { position: fixed; left: 50%; - /* left: 20px; */ - /* top: 20px; */ + transform: translateX(-50%); z-index: 999; bottom: 20px; - transition: all 0.3s - cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; + &:hover { - transform: scale(1.1); + transform: translateX(-50%) scale(1.1); + } + + @media (max-width: 768px) { + bottom: 15px; + + .lucky-circle { + width: 70px; + height: 70px; + } + + .lucky-content .lucky-icon { + font-size: 20px; + } + + .lucky-content .lucky-text { + font-size: 12px; + } + + .lucky-label { + font-size: 11px; + margin-top: 4px; + } } &.lucky-full { @@ -1408,6 +1437,12 @@ function getCardClass(record: CardFlipRecord): string[] { animation: slideIn 0.5s ease; flex-wrap: wrap; + @media (max-width: 768px) { + padding: 8px 10px; + gap: 8px; + margin-bottom: 10px; + } + .compact-stats { display: flex; align-items: center; @@ -1497,6 +1532,11 @@ function getCardClass(record: CardFlipRecord): string[] { border-radius: 12px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + + @media (max-width: 768px) { + padding: 12px 20px; + border-radius: 8px; + } } .shuffle-text { @@ -1505,6 +1545,15 @@ function getCardClass(record: CardFlipRecord): string[] { color: #fff; text-shadow: 0 2px 8px rgba(255, 215, 0, 0.6); animation: textPulse 1.5s ease-in-out infinite; + white-space: nowrap; + + @media (max-width: 768px) { + font-size: 14px; + } + + @media (max-width: 480px) { + font-size: 12px; + } } } @@ -1515,8 +1564,13 @@ function getCardClass(record: CardFlipRecord): string[] { max-width: 100%; @media (max-width: 768px) { - grid-template-columns: repeat(5, 1fr); - gap: 6px; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + } + + @media (max-width: 480px) { + grid-template-columns: repeat(2, 1fr); + gap: 12px; } // 洗牌阶段样式 @@ -1683,6 +1737,11 @@ function getCardClass(record: CardFlipRecord): string[] { background: rgba(0, 0, 0, 0.2); padding: 2px 6px; border-radius: 4px; + + @media (max-width: 768px) { + font-size: 9px; + padding: 2px 5px; + } } .card-content { @@ -1691,6 +1750,10 @@ function getCardClass(record: CardFlipRecord): string[] { align-items: center; gap: 8px; z-index: 1; + + @media (max-width: 768px) { + gap: 6px; + } } // 系统logo样式(优化为居中圆形,更丰富的效果) @@ -1709,6 +1772,12 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 3; filter: brightness(1.1); + @media (max-width: 768px) { + width: 40px; + height: 40px; + border: 3px solid rgba(255, 255, 255, 1); + } + // 外层光晕效果 &::before { content: ''; @@ -1741,6 +1810,10 @@ function getCardClass(record: CardFlipRecord): string[] { font-weight: bold; color: #fff; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + + @media (max-width: 768px) { + font-size: 24px; + } } .card-type-badge { @@ -1753,6 +1826,12 @@ function getCardClass(record: CardFlipRecord): string[] { border-radius: 10px; backdrop-filter: blur(4px); z-index: 2; + + @media (max-width: 768px) { + font-size: 9px; + padding: 2px 6px; + bottom: 4px; + } } .card-shine { @@ -1827,6 +1906,10 @@ function getCardClass(record: CardFlipRecord): string[] { position: relative; z-index: 1; + @media (max-width: 768px) { + padding: 8px; + } + // logo水印样式 .result-watermark { position: absolute; @@ -1869,6 +1952,10 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; filter: drop-shadow(0 4px 8px rgba(255, 215, 0, 0.5)); margin-bottom: 4px; + + @media (max-width: 768px) { + font-size: 36px; + } } .result-text { @@ -1883,6 +1970,11 @@ function getCardClass(record: CardFlipRecord): string[] { letter-spacing: 2px; position: relative; + @media (max-width: 768px) { + font-size: 16px; + letter-spacing: 1px; + } + // 文字外发光 &::after { content: attr(data-text); @@ -1907,6 +1999,11 @@ function getCardClass(record: CardFlipRecord): string[] { position: relative; filter: drop-shadow(0 3px 10px rgba(255, 215, 0, 0.6)); + @media (max-width: 768px) { + font-size: 32px; + margin: 8px 0; + } + // 金色光效边框 &::before { content: attr(data-amount); @@ -1932,6 +2029,11 @@ function getCardClass(record: CardFlipRecord): string[] { letter-spacing: 3px; text-transform: uppercase; margin-top: 4px; + + @media (max-width: 768px) { + font-size: 14px; + letter-spacing: 2px; + } } } @@ -1953,6 +2055,10 @@ function getCardClass(record: CardFlipRecord): string[] { margin-bottom: 6px; filter: drop-shadow(0 2px 6px rgba(147, 112, 219, 0.3)); animation: gentleBounce 2s ease-in-out infinite; + + @media (max-width: 768px) { + font-size: 36px; + } } .result-text { @@ -1964,6 +2070,10 @@ function getCardClass(record: CardFlipRecord): string[] { margin: 8px 0; z-index: 1; letter-spacing: 1px; + + @media (max-width: 768px) { + font-size: 15px; + } } .result-tip { @@ -1972,6 +2082,10 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; margin-top: 6px; font-weight: 500; + + @media (max-width: 768px) { + font-size: 12px; + } } } @@ -1994,6 +2108,11 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; filter: drop-shadow(0 4px 12px rgba(255, 215, 0, 0.8)); margin: 10px 0; + + @media (max-width: 768px) { + font-size: 42px; + margin: 8px 0; + } } .mystery-text { @@ -2004,6 +2123,11 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; letter-spacing: 4px; margin: 8px 0; + + @media (max-width: 768px) { + font-size: 18px; + letter-spacing: 2px; + } } .mystery-hint { @@ -2012,6 +2136,11 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; letter-spacing: 2px; margin-top: 6px; + + @media (max-width: 768px) { + font-size: 12px; + letter-spacing: 1px; + } } .mystery-stars { @@ -2060,6 +2189,11 @@ function getCardClass(record: CardFlipRecord): string[] { margin-bottom: 16px; text-align: center; line-height: 1.6; + + @media (max-width: 768px) { + font-size: 13px; + margin-bottom: 12px; + } } .code-input { @@ -2078,10 +2212,19 @@ function getCardClass(record: CardFlipRecord): string[] { border: 1px solid #bae6fd; border-radius: 12px; + @media (max-width: 768px) { + padding: 20px 12px; + } + .filled-icon { font-size: 48px; display: block; margin-bottom: 12px; + + @media (max-width: 768px) { + font-size: 40px; + margin-bottom: 10px; + } } .filled-text { @@ -2090,6 +2233,10 @@ function getCardClass(record: CardFlipRecord): string[] { line-height: 1.6; margin: 0; font-weight: 500; + + @media (max-width: 768px) { + font-size: 14px; + } } } } @@ -2128,12 +2275,22 @@ function getCardClass(record: CardFlipRecord): string[] { border-radius: 12px; margin-bottom: 16px; + @media (max-width: 768px) { + padding: 14px; + margin-bottom: 12px; + } + .code-text { font-size: 28px; font-weight: bold; color: #fff; letter-spacing: 6px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + + @media (max-width: 768px) { + font-size: 24px; + letter-spacing: 4px; + } } } @@ -2142,8 +2299,17 @@ function getCardClass(record: CardFlipRecord): string[] { gap: 8px; margin-bottom: 12px; + @media (max-width: 768px) { + flex-direction: column; + gap: 10px; + } + .el-button { flex: 1; + + @media (max-width: 768px) { + width: 100%; + } } } @@ -2153,6 +2319,10 @@ function getCardClass(record: CardFlipRecord): string[] { line-height: 1.5; margin: 0 0 12px 0; + @media (max-width: 768px) { + font-size: 12px; + } + strong { color: #f56c6c; font-weight: 600; @@ -2199,12 +2369,20 @@ function getCardClass(record: CardFlipRecord): string[] { padding: 12px; text-align: left; + @media (max-width: 768px) { + padding: 10px; + } + .share-preview-title { font-size: 14px; font-weight: bold; color: #303133; margin-bottom: 8px; text-align: center; + + @media (max-width: 768px) { + font-size: 13px; + } } .share-preview-content { @@ -2218,6 +2396,12 @@ function getCardClass(record: CardFlipRecord): string[] { max-height: 200px; overflow-y: auto; + @media (max-width: 768px) { + font-size: 12px; + padding: 8px; + max-height: 150px; + } + /* 自定义滚动条 */ &::-webkit-scrollbar { width: 4px; @@ -2276,9 +2460,17 @@ function getCardClass(record: CardFlipRecord): string[] { display: inline-block; margin-bottom: 20px; + @media (max-width: 768px) { + margin-bottom: 16px; + } + .double-icon { font-size: 64px; animation: bounce 1s infinite; + + @media (max-width: 768px) { + font-size: 52px; + } } .double-sparkle { @@ -2287,6 +2479,10 @@ function getCardClass(record: CardFlipRecord): string[] { right: -10px; font-size: 32px; animation: spin 2s linear infinite; + + @media (max-width: 768px) { + font-size: 26px; + } } } @@ -2295,6 +2491,11 @@ function getCardClass(record: CardFlipRecord): string[] { font-weight: bold; color: #303133; margin-bottom: 16px; + + @media (max-width: 768px) { + font-size: 18px; + margin-bottom: 12px; + } } .double-text { @@ -2303,10 +2504,19 @@ function getCardClass(record: CardFlipRecord): string[] { color: #606266; margin-bottom: 24px; + @media (max-width: 768px) { + font-size: 14px; + margin-bottom: 20px; + } + .highlight { color: #f56c6c; font-weight: bold; font-size: 16px; + + @media (max-width: 768px) { + font-size: 15px; + } } } diff --git a/Yi.Ai.Vue3/src/config/index.ts b/Yi.Ai.Vue3/src/config/index.ts index 5f054135..7d60d8fc 100644 --- a/Yi.Ai.Vue3/src/config/index.ts +++ b/Yi.Ai.Vue3/src/config/index.ts @@ -12,4 +12,4 @@ export const COLLAPSE_THRESHOLD: number = 600; export const SIDE_BAR_WIDTH: number = 280; // 路由白名单地址[本地存在的路由 staticRouter.ts 中] -export const ROUTER_WHITE_LIST: string[] = ['/chat', '/chat/not_login', '/products', '/model-library', '/403', '/404']; +export const ROUTER_WHITE_LIST: string[] = ['/chat', '/chat/conversation', '/chat/image', '/chat/video', '/model-library', '/403', '/404']; diff --git a/Yi.Ai.Vue3/src/config/permission.ts b/Yi.Ai.Vue3/src/config/permission.ts new file mode 100644 index 00000000..c1c3c64a --- /dev/null +++ b/Yi.Ai.Vue3/src/config/permission.ts @@ -0,0 +1,110 @@ +/** + * 权限配置文件 + * 用于配置特定页面的访问权限 + */ + +/** + * 权限配置接口 + */ +export interface PermissionConfig { + /** 路由路径 */ + path: string; + /** 允许访问的用户名列表 */ + allowedUsers: string[]; + /** 权限描述 */ + description?: string; +} + +/** + * 页面权限配置列表 + * 在这里配置需要特殊权限控制的页面 + */ +export const PAGE_PERMISSIONS: PermissionConfig[] = [ + { + path: '/console/channel', + allowedUsers: ['cc', 'Guo'], + description: '渠道商管理页面 - 仅限cc和Guo用户访问', + }, + // 可以在这里继续添加其他需要权限控制的页面 + // { + // path: '/console/admin', + // allowedUsers: ['admin', 'superadmin'], + // description: '管理员页面', + // }, +]; + +/** + * 检查用户是否有权限访问指定路径 + * @param path 路由路径 + * @param userName 用户名 + * @returns 是否有权限 + */ +export function checkPagePermission(path: string, userName: string | undefined): boolean { + // 如果没有用户名,返回false + if (!userName) { + return false; + } + + // 查找该路径的权限配置 + const permissionConfig = PAGE_PERMISSIONS.find(config => config.path === path); + + // 如果没有配置权限,说明该页面不需要特殊权限,返回true + if (!permissionConfig) { + return true; + } + + // 检查用户名是否在允许列表中(不区分大小写) + return permissionConfig.allowedUsers.some( + allowedUser => allowedUser.toLowerCase() === userName.toLowerCase(), + ); +} + +/** + * 获取用户无权访问的路由列表 + * @param userName 用户名 + * @returns 无权访问的路由路径数组 + */ +export function getRestrictedRoutes(userName: string | undefined): string[] { + if (!userName) { + return PAGE_PERMISSIONS.map(config => config.path); + } + + return PAGE_PERMISSIONS.filter( + config => !config.allowedUsers.some( + allowedUser => allowedUser.toLowerCase() === userName.toLowerCase(), + ), + ).map(config => config.path); +} + +/** + * 检查路由是否需要权限控制 + * @param path 路由路径 + * @returns 是否需要权限控制 + */ +export function isRestrictedRoute(path: string): boolean { + return PAGE_PERMISSIONS.some(config => config.path === path); +} + +/** + * 过滤菜单路由,移除用户无权访问的菜单项 + * @param routes 路由配置数组 + * @param userName 用户名 + * @returns 过滤后的路由配置数组 + */ +export function filterMenuRoutes(routes: any[], userName: string | undefined): any[] { + return routes.filter((route) => { + // 检查当前路由是否有权限 + const hasPermission = checkPagePermission(route.path, userName); + + if (!hasPermission) { + return false; + } + + // 如果有子路由,递归过滤 + if (route.children && route.children.length > 0) { + route.children = filterMenuRoutes(route.children, userName); + } + + return true; + }); +} diff --git a/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue b/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue index 3f50855d..2145c693 100644 --- a/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue +++ b/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue @@ -12,10 +12,17 @@ import { ZoomIn, } from '@element-plus/icons-vue'; import { ElMessage } from 'element-plus'; -import { computed, onMounted, onUnmounted, ref } from 'vue'; +import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { getSelectableTokenInfo } from '@/api'; import { generateImage, getImageModels, getTaskStatus } from '@/api/aiImage'; +const props = defineProps({ + isActive: { + type: Boolean, + default: true, + }, +}); + const emit = defineEmits(['task-created']); // State @@ -41,6 +48,19 @@ const canGenerate = computed(() => { return selectedModelId.value && prompt.value && !generating.value; }); +// Watch isActive to manage polling +watch(() => props.isActive, (active) => { + if (active) { + // Resume polling if we have a processing task + if (currentTaskId.value && currentTask.value?.taskStatus === 'Processing') { + startPolling(currentTaskId.value); + } + } + else { + stopPolling(); + } +}); + // Methods async function fetchTokens() { tokenLoading.value = true; @@ -197,6 +217,12 @@ function startPolling(taskId: string) { } async function pollStatus(taskId: string) { + // Double check active status before polling (though timer should be cleared) + if (!props.isActive) { + stopPolling(); + return; + } + try { const res = await getTaskStatus(taskId); // Handle response structure if needed @@ -257,7 +283,8 @@ async function downloadImage() { link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); - } catch (e) { + } + catch (e) { console.error('Download failed', e); // Fallback window.open(currentTask.value.storeUrl, '_blank'); @@ -324,14 +351,15 @@ onUnmounted(() => { 配置 - -