feat: 路由动态权限控制、图片广场优化

This commit is contained in:
Gsh
2026-01-03 17:03:42 +08:00
parent 3892ff1937
commit 42edd4c230
11 changed files with 660 additions and 145 deletions

View File

@@ -408,7 +408,10 @@ async function handleFlipCard(record: CardFlipRecord) {
await new Promise(resolve => requestAnimationFrame(resolve)); await new Promise(resolve => requestAnimationFrame(resolve));
// 4. 移动克隆卡片到屏幕中心并放大(考虑边界限制) // 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 scaledWidth = rect.width * scale;
const scaledHeight = rect.height * scale; const scaledHeight = rect.height * scale;
@@ -416,8 +419,8 @@ async function handleFlipCard(record: CardFlipRecord) {
let centerX = window.innerWidth / 2; let centerX = window.innerWidth / 2;
let centerY = window.innerHeight / 2; let centerY = window.innerHeight / 2;
// 边界检查:确保卡片完全在视口内(留20px边距) // 边界检查:确保卡片完全在视口内(移动端留更多边距)
const margin = 20; const margin = isMobile ? 30 : 20;
const minX = scaledWidth / 2 + margin; const minX = scaledWidth / 2 + margin;
const maxX = window.innerWidth - scaledWidth / 2 - margin; const maxX = window.innerWidth - scaledWidth / 2 - margin;
const minY = scaledHeight / 2 + margin; const minY = scaledHeight / 2 + margin;
@@ -1253,6 +1256,11 @@ function getCardClass(record: CardFlipRecord): string[] {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px; border-radius: 16px;
@media (max-width: 768px) {
padding: 12px;
border-radius: 12px;
}
/* 自定义滚动条 */ /* 自定义滚动条 */
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 6px; width: 6px;
@@ -1277,15 +1285,36 @@ function getCardClass(record: CardFlipRecord): string[] {
.lucky-float-ball { .lucky-float-ball {
position: fixed; position: fixed;
left: 50%; left: 50%;
/* left: 20px; */ transform: translateX(-50%);
/* top: 20px; */
z-index: 999; z-index: 999;
bottom: 20px; bottom: 20px;
transition: all 0.3s transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer; cursor: pointer;
&:hover { &: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 { &.lucky-full {
@@ -1408,6 +1437,12 @@ function getCardClass(record: CardFlipRecord): string[] {
animation: slideIn 0.5s ease; animation: slideIn 0.5s ease;
flex-wrap: wrap; flex-wrap: wrap;
@media (max-width: 768px) {
padding: 8px 10px;
gap: 8px;
margin-bottom: 10px;
}
.compact-stats { .compact-stats {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1497,6 +1532,11 @@ function getCardClass(record: CardFlipRecord): string[] {
border-radius: 12px; border-radius: 12px;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
@media (max-width: 768px) {
padding: 12px 20px;
border-radius: 8px;
}
} }
.shuffle-text { .shuffle-text {
@@ -1505,6 +1545,15 @@ function getCardClass(record: CardFlipRecord): string[] {
color: #fff; color: #fff;
text-shadow: 0 2px 8px rgba(255, 215, 0, 0.6); text-shadow: 0 2px 8px rgba(255, 215, 0, 0.6);
animation: textPulse 1.5s ease-in-out infinite; 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%; max-width: 100%;
@media (max-width: 768px) { @media (max-width: 768px) {
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 6px; 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); background: rgba(0, 0, 0, 0.2);
padding: 2px 6px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
@media (max-width: 768px) {
font-size: 9px;
padding: 2px 5px;
}
} }
.card-content { .card-content {
@@ -1691,6 +1750,10 @@ function getCardClass(record: CardFlipRecord): string[] {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
z-index: 1; z-index: 1;
@media (max-width: 768px) {
gap: 6px;
}
} }
// 系统logo样式优化为居中圆形更丰富的效果 // 系统logo样式优化为居中圆形更丰富的效果
@@ -1709,6 +1772,12 @@ function getCardClass(record: CardFlipRecord): string[] {
z-index: 3; z-index: 3;
filter: brightness(1.1); filter: brightness(1.1);
@media (max-width: 768px) {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 1);
}
// 外层光晕效果 // 外层光晕效果
&::before { &::before {
content: ''; content: '';
@@ -1741,6 +1810,10 @@ function getCardClass(record: CardFlipRecord): string[] {
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
@media (max-width: 768px) {
font-size: 24px;
}
} }
.card-type-badge { .card-type-badge {
@@ -1753,6 +1826,12 @@ function getCardClass(record: CardFlipRecord): string[] {
border-radius: 10px; border-radius: 10px;
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
z-index: 2; z-index: 2;
@media (max-width: 768px) {
font-size: 9px;
padding: 2px 6px;
bottom: 4px;
}
} }
.card-shine { .card-shine {
@@ -1827,6 +1906,10 @@ function getCardClass(record: CardFlipRecord): string[] {
position: relative; position: relative;
z-index: 1; z-index: 1;
@media (max-width: 768px) {
padding: 8px;
}
// logo水印样式 // logo水印样式
.result-watermark { .result-watermark {
position: absolute; position: absolute;
@@ -1869,6 +1952,10 @@ function getCardClass(record: CardFlipRecord): string[] {
z-index: 1; z-index: 1;
filter: drop-shadow(0 4px 8px rgba(255, 215, 0, 0.5)); filter: drop-shadow(0 4px 8px rgba(255, 215, 0, 0.5));
margin-bottom: 4px; margin-bottom: 4px;
@media (max-width: 768px) {
font-size: 36px;
}
} }
.result-text { .result-text {
@@ -1883,6 +1970,11 @@ function getCardClass(record: CardFlipRecord): string[] {
letter-spacing: 2px; letter-spacing: 2px;
position: relative; position: relative;
@media (max-width: 768px) {
font-size: 16px;
letter-spacing: 1px;
}
// 文字外发光 // 文字外发光
&::after { &::after {
content: attr(data-text); content: attr(data-text);
@@ -1907,6 +1999,11 @@ function getCardClass(record: CardFlipRecord): string[] {
position: relative; position: relative;
filter: drop-shadow(0 3px 10px rgba(255, 215, 0, 0.6)); filter: drop-shadow(0 3px 10px rgba(255, 215, 0, 0.6));
@media (max-width: 768px) {
font-size: 32px;
margin: 8px 0;
}
// 金色光效边框 // 金色光效边框
&::before { &::before {
content: attr(data-amount); content: attr(data-amount);
@@ -1932,6 +2029,11 @@ function getCardClass(record: CardFlipRecord): string[] {
letter-spacing: 3px; letter-spacing: 3px;
text-transform: uppercase; text-transform: uppercase;
margin-top: 4px; 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; margin-bottom: 6px;
filter: drop-shadow(0 2px 6px rgba(147, 112, 219, 0.3)); filter: drop-shadow(0 2px 6px rgba(147, 112, 219, 0.3));
animation: gentleBounce 2s ease-in-out infinite; animation: gentleBounce 2s ease-in-out infinite;
@media (max-width: 768px) {
font-size: 36px;
}
} }
.result-text { .result-text {
@@ -1964,6 +2070,10 @@ function getCardClass(record: CardFlipRecord): string[] {
margin: 8px 0; margin: 8px 0;
z-index: 1; z-index: 1;
letter-spacing: 1px; letter-spacing: 1px;
@media (max-width: 768px) {
font-size: 15px;
}
} }
.result-tip { .result-tip {
@@ -1972,6 +2082,10 @@ function getCardClass(record: CardFlipRecord): string[] {
z-index: 1; z-index: 1;
margin-top: 6px; margin-top: 6px;
font-weight: 500; font-weight: 500;
@media (max-width: 768px) {
font-size: 12px;
}
} }
} }
@@ -1994,6 +2108,11 @@ function getCardClass(record: CardFlipRecord): string[] {
z-index: 1; z-index: 1;
filter: drop-shadow(0 4px 12px rgba(255, 215, 0, 0.8)); filter: drop-shadow(0 4px 12px rgba(255, 215, 0, 0.8));
margin: 10px 0; margin: 10px 0;
@media (max-width: 768px) {
font-size: 42px;
margin: 8px 0;
}
} }
.mystery-text { .mystery-text {
@@ -2004,6 +2123,11 @@ function getCardClass(record: CardFlipRecord): string[] {
z-index: 1; z-index: 1;
letter-spacing: 4px; letter-spacing: 4px;
margin: 8px 0; margin: 8px 0;
@media (max-width: 768px) {
font-size: 18px;
letter-spacing: 2px;
}
} }
.mystery-hint { .mystery-hint {
@@ -2012,6 +2136,11 @@ function getCardClass(record: CardFlipRecord): string[] {
z-index: 1; z-index: 1;
letter-spacing: 2px; letter-spacing: 2px;
margin-top: 6px; margin-top: 6px;
@media (max-width: 768px) {
font-size: 12px;
letter-spacing: 1px;
}
} }
.mystery-stars { .mystery-stars {
@@ -2060,6 +2189,11 @@ function getCardClass(record: CardFlipRecord): string[] {
margin-bottom: 16px; margin-bottom: 16px;
text-align: center; text-align: center;
line-height: 1.6; line-height: 1.6;
@media (max-width: 768px) {
font-size: 13px;
margin-bottom: 12px;
}
} }
.code-input { .code-input {
@@ -2078,10 +2212,19 @@ function getCardClass(record: CardFlipRecord): string[] {
border: 1px solid #bae6fd; border: 1px solid #bae6fd;
border-radius: 12px; border-radius: 12px;
@media (max-width: 768px) {
padding: 20px 12px;
}
.filled-icon { .filled-icon {
font-size: 48px; font-size: 48px;
display: block; display: block;
margin-bottom: 12px; margin-bottom: 12px;
@media (max-width: 768px) {
font-size: 40px;
margin-bottom: 10px;
}
} }
.filled-text { .filled-text {
@@ -2090,6 +2233,10 @@ function getCardClass(record: CardFlipRecord): string[] {
line-height: 1.6; line-height: 1.6;
margin: 0; margin: 0;
font-weight: 500; font-weight: 500;
@media (max-width: 768px) {
font-size: 14px;
}
} }
} }
} }
@@ -2128,12 +2275,22 @@ function getCardClass(record: CardFlipRecord): string[] {
border-radius: 12px; border-radius: 12px;
margin-bottom: 16px; margin-bottom: 16px;
@media (max-width: 768px) {
padding: 14px;
margin-bottom: 12px;
}
.code-text { .code-text {
font-size: 28px; font-size: 28px;
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
letter-spacing: 6px; letter-spacing: 6px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 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; gap: 8px;
margin-bottom: 12px; margin-bottom: 12px;
@media (max-width: 768px) {
flex-direction: column;
gap: 10px;
}
.el-button { .el-button {
flex: 1; flex: 1;
@media (max-width: 768px) {
width: 100%;
}
} }
} }
@@ -2153,6 +2319,10 @@ function getCardClass(record: CardFlipRecord): string[] {
line-height: 1.5; line-height: 1.5;
margin: 0 0 12px 0; margin: 0 0 12px 0;
@media (max-width: 768px) {
font-size: 12px;
}
strong { strong {
color: #f56c6c; color: #f56c6c;
font-weight: 600; font-weight: 600;
@@ -2199,12 +2369,20 @@ function getCardClass(record: CardFlipRecord): string[] {
padding: 12px; padding: 12px;
text-align: left; text-align: left;
@media (max-width: 768px) {
padding: 10px;
}
.share-preview-title { .share-preview-title {
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
color: #303133; color: #303133;
margin-bottom: 8px; margin-bottom: 8px;
text-align: center; text-align: center;
@media (max-width: 768px) {
font-size: 13px;
}
} }
.share-preview-content { .share-preview-content {
@@ -2218,6 +2396,12 @@ function getCardClass(record: CardFlipRecord): string[] {
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
@media (max-width: 768px) {
font-size: 12px;
padding: 8px;
max-height: 150px;
}
/* 自定义滚动条 */ /* 自定义滚动条 */
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 4px; width: 4px;
@@ -2276,9 +2460,17 @@ function getCardClass(record: CardFlipRecord): string[] {
display: inline-block; display: inline-block;
margin-bottom: 20px; margin-bottom: 20px;
@media (max-width: 768px) {
margin-bottom: 16px;
}
.double-icon { .double-icon {
font-size: 64px; font-size: 64px;
animation: bounce 1s infinite; animation: bounce 1s infinite;
@media (max-width: 768px) {
font-size: 52px;
}
} }
.double-sparkle { .double-sparkle {
@@ -2287,6 +2479,10 @@ function getCardClass(record: CardFlipRecord): string[] {
right: -10px; right: -10px;
font-size: 32px; font-size: 32px;
animation: spin 2s linear infinite; animation: spin 2s linear infinite;
@media (max-width: 768px) {
font-size: 26px;
}
} }
} }
@@ -2295,6 +2491,11 @@ function getCardClass(record: CardFlipRecord): string[] {
font-weight: bold; font-weight: bold;
color: #303133; color: #303133;
margin-bottom: 16px; margin-bottom: 16px;
@media (max-width: 768px) {
font-size: 18px;
margin-bottom: 12px;
}
} }
.double-text { .double-text {
@@ -2303,10 +2504,19 @@ function getCardClass(record: CardFlipRecord): string[] {
color: #606266; color: #606266;
margin-bottom: 24px; margin-bottom: 24px;
@media (max-width: 768px) {
font-size: 14px;
margin-bottom: 20px;
}
.highlight { .highlight {
color: #f56c6c; color: #f56c6c;
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
@media (max-width: 768px) {
font-size: 15px;
}
} }
} }

View File

@@ -12,4 +12,4 @@ export const COLLAPSE_THRESHOLD: number = 600;
export const SIDE_BAR_WIDTH: number = 280; export const SIDE_BAR_WIDTH: number = 280;
// 路由白名单地址[本地存在的路由 staticRouter.ts 中] // 路由白名单地址[本地存在的路由 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'];

View File

@@ -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;
});
}

View File

@@ -12,10 +12,17 @@ import {
ZoomIn, ZoomIn,
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus'; 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 { getSelectableTokenInfo } from '@/api';
import { generateImage, getImageModels, getTaskStatus } from '@/api/aiImage'; import { generateImage, getImageModels, getTaskStatus } from '@/api/aiImage';
const props = defineProps({
isActive: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['task-created']); const emit = defineEmits(['task-created']);
// State // State
@@ -41,6 +48,19 @@ const canGenerate = computed(() => {
return selectedModelId.value && prompt.value && !generating.value; 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 // Methods
async function fetchTokens() { async function fetchTokens() {
tokenLoading.value = true; tokenLoading.value = true;
@@ -197,6 +217,12 @@ function startPolling(taskId: string) {
} }
async function pollStatus(taskId: string) { async function pollStatus(taskId: string) {
// Double check active status before polling (though timer should be cleared)
if (!props.isActive) {
stopPolling();
return;
}
try { try {
const res = await getTaskStatus(taskId); const res = await getTaskStatus(taskId);
// Handle response structure if needed // Handle response structure if needed
@@ -257,7 +283,8 @@ async function downloadImage() {
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
} catch (e) { }
catch (e) {
console.error('Download failed', e); console.error('Download failed', e);
// Fallback // Fallback
window.open(currentTask.value.storeUrl, '_blank'); window.open(currentTask.value.storeUrl, '_blank');
@@ -324,14 +351,15 @@ onUnmounted(() => {
配置 配置
</h2> </h2>
<!-- Token & Model --> <el-form label-position="top" class="space-y-2" label-width="auto">
<div class="bg-gray-50 p-4 rounded-lg space-y-4"> <!-- Token -->
<el-form-item label="API密钥" class="mb-0"> <el-form-item label="API密钥 (可选)">
<el-select <el-select
v-model="selectedTokenId" v-model="selectedTokenId"
placeholder="请选择API密钥" placeholder="请选择API密钥"
class="w-full" class="w-full"
:loading="tokenLoading" :loading="tokenLoading"
clearable
> >
<el-option <el-option
v-for="token in tokenOptions" v-for="token in tokenOptions"
@@ -343,7 +371,8 @@ onUnmounted(() => {
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="模型" class="mb-0"> <!-- Model -->
<el-form-item label="模型" required>
<el-select <el-select
v-model="selectedModelId" v-model="selectedModelId"
placeholder="请选择模型" placeholder="请选择模型"
@@ -363,57 +392,59 @@ onUnmounted(() => {
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</div>
<!-- Prompt --> <!-- Prompt -->
<div class="space-y-2"> <el-form-item label="提示词" required class="prompt-form-item ">
<div class="flex justify-between items-center"> <template #label>
<label class="text-sm font-medium text-gray-700">提示词</label> <div class="flex justify-between items-center w-full ">
<el-button link type="primary" size="small" @click="clearPrompt"> <span>提示词</span>
<el-icon class="mr-1"> <el-button link type="primary" size="small" @click="clearPrompt">
<Delete /> <el-icon class="mr-1">
</el-icon>清空 <Delete />
</el-button> </el-icon>清空
</div> </el-button>
<el-input </div>
v-model="prompt" </template>
type="textarea" <el-input
:autosize="{ minRows: 8, maxRows: 15 }" v-model="prompt"
placeholder="描述你想要生成的画面,例如:一只在太空中飞行的赛博朋克风格的猫..." type="textarea"
maxlength="2000" :autosize="{ minRows: 8, maxRows: 8 }"
show-word-limit placeholder="描述你想要生成的画面,例如:一只在太空中飞行的赛博朋克风格的猫..."
class="custom-textarea" maxlength="2000"
/> show-word-limit
</div> class="custom-textarea"
resize="vertical"
<!-- Reference Image --> />
<div class="space-y-2"> </el-form-item>
<label class="text-sm font-medium text-gray-700">参考图 (可选)</label>
<div class="bg-gray-50 p-4 rounded-lg border border-dashed border-gray-300 hover:border-blue-400 transition-colors"> <!-- Reference Image -->
<el-upload <el-form-item label="参考图 (可选)">
v-model:file-list="fileList" <div class="w-full bg-gray-50 p-4 rounded-lg border border-dashed border-gray-300 hover:border-blue-400 transition-colors">
action="#" <el-upload
list-type="picture-card" v-model:file-list="fileList"
:auto-upload="false" action="#"
:limit="2" list-type="picture-card"
:on-change="handleFileChange" :auto-upload="false"
:on-remove="handleRemove" :limit="2"
accept=".jpg,.jpeg,.png,.bmp,.webp" :on-change="handleFileChange"
:class="{ 'hide-upload-btn': fileList.length >= 2 }" :on-remove="handleRemove"
> accept=".jpg,.jpeg,.png,.bmp,.webp"
<div class="flex flex-col items-center justify-center text-gray-400"> :class="{ 'hide-upload-btn': fileList.length >= 2 }"
<el-icon class="text-2xl mb-2"> >
<Plus /> <div class="flex flex-col items-center justify-center text-gray-400">
</el-icon> <el-icon class="text-2xl mb-2">
<span class="text-xs">点击上传</span> <Plus />
</el-icon>
<span class="text-xs">点击上传</span>
</div>
</el-upload>
<div class="text-xs text-gray-400 mt-2 flex justify-between items-center flex-wrap gap-2">
<span>最多2张< 5MB (支持 JPG/PNG/WEBP)</span>
<el-checkbox v-model="compressImage" label="压缩图片" size="small" />
</div> </div>
</el-upload>
<div class="text-xs text-gray-400 mt-2 flex justify-between items-center flex-wrap gap-2">
<span>最多2张< 5MB (支持 JPG/PNG/WEBP)</span>
<el-checkbox v-model="compressImage" label="压缩图片" size="small" />
</div> </div>
</div> </el-form-item>
</div> </el-form>
</div> </div>
<div class="mt-auto pt-4"> <div class="mt-auto pt-4">
@@ -450,7 +481,7 @@ onUnmounted(() => {
<div class="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity flex gap-2 z-10"> <div class="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity flex gap-2 z-10">
<el-button circle type="primary" :icon="ZoomIn" @click="showViewer = true" /> <el-button circle type="primary" :icon="ZoomIn" @click="showViewer = true" />
<el-button circle type="primary" :icon="Download" @click="downloadImage" /> <el-button circle type="primary" :icon="Download" @click="downloadImage" />
<el-button circle type="info" :icon="Refresh" title="新任务" @click="currentTask = null" /> <el-button circle type="success" :icon="Refresh" title="重新生成" @click="handleGenerate" />
</div> </div>
<el-image-viewer <el-image-viewer
@@ -563,4 +594,8 @@ onUnmounted(() => {
:deep(.hide-upload-btn .el-upload--picture-card) { :deep(.hide-upload-btn .el-upload--picture-card) {
display: none; display: none;
} }
/* 隐藏默认的标签 */
:deep(.prompt-form-item .el-form-item__label){
display: flex;
}
</style> </style>

View File

@@ -55,18 +55,22 @@
class="w-full h-full" class="w-full h-full"
:preview-src-list="[currentTask.storeUrl]" :preview-src-list="[currentTask.storeUrl]"
/> />
<!-- Download Button Overlay -->
<div class="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity z-10">
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
</div>
</div> </div>
<!-- Right Info --> <!-- Right Info -->
<div class="w-full md:w-[300px] flex flex-col gap-4 overflow-y-auto"> <div class="w-full md:w-[300px] flex flex-col gap-4 overflow-hidden">
<div> <div class="flex-1 flex flex-col min-h-0">
<h3 class="font-bold text-gray-800 mb-2 flex items-center gap-2"> <h3 class="font-bold text-gray-800 mb-2 flex items-center gap-2 shrink-0">
<el-icon><MagicStick /></el-icon> 提示词 <el-icon><MagicStick /></el-icon> 提示词
</h3> </h3>
<div class="bg-gray-50 p-4 rounded-lg border border-gray-100 text-sm text-gray-600 leading-relaxed relative group/prompt"> <div class="bg-gray-50 p-4 rounded-lg border border-gray-100 text-sm text-gray-600 leading-relaxed relative group/prompt overflow-y-auto custom-scrollbar flex-1">
{{ currentTask.prompt }} {{ currentTask.prompt }}
<el-button <el-button
class="absolute top-2 right-2 opacity-0 group-hover/prompt:opacity-100 transition-opacity shadow-sm" class="absolute top-2 right-2 opacity-0 group-hover/prompt:opacity-100 transition-opacity shadow-sm z-10"
size="small" size="small"
circle circle
:icon="CopyDocument" :icon="CopyDocument"
@@ -76,7 +80,7 @@
</div> </div>
</div> </div>
<div class="mt-auto space-y-3 pt-4 border-t border-gray-100"> <div class="mt-auto space-y-3 pt-4 border-t border-gray-100 shrink-0">
<div class="flex justify-between text-sm"> <div class="flex justify-between text-sm">
<span class="text-gray-500">创建时间</span> <span class="text-gray-500">创建时间</span>
<span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span> <span class="text-gray-800">{{ formatTime(currentTask.creationTime) }}</span>
@@ -118,7 +122,7 @@ import TaskCard from './TaskCard.vue';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
import { Picture, Loading, MagicStick, CopyDocument } from '@element-plus/icons-vue'; import { Picture, Loading, MagicStick, CopyDocument, Download } from '@element-plus/icons-vue';
const emit = defineEmits(['use-prompt', 'use-reference']); const emit = defineEmits(['use-prompt', 'use-reference']);
@@ -197,6 +201,24 @@ const copyPrompt = async (text: string) => {
await copy(text); await copy(text);
ElMessage.success('提示词已复制'); ElMessage.success('提示词已复制');
}; };
const downloadImage = async (url: string) => {
try {
const response = await fetch(url);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = `image-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
} catch (e) {
console.error('Download failed', e);
window.open(url, '_blank');
}
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -15,7 +15,7 @@
@click="handleCardClick(task)" @click="handleCardClick(task)"
@use-prompt="$emit('use-prompt', $event)" @use-prompt="$emit('use-prompt', $event)"
@use-reference="$emit('use-reference', $event)" @use-reference="$emit('use-reference', $event)"
@publish="handlePublish($event)" @publish="openPublishDialog(task)"
/> />
</div> </div>
@@ -50,7 +50,7 @@
> >
<div v-if="currentTask" class="flex flex-col md:flex-row gap-6 h-[600px]"> <div v-if="currentTask" class="flex flex-col md:flex-row gap-6 h-[600px]">
<!-- Left Image --> <!-- Left Image -->
<div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative"> <div class="flex-1 bg-black/5 rounded-lg flex items-center justify-center overflow-hidden relative group">
<el-image <el-image
v-if="currentTask.storeUrl" v-if="currentTask.storeUrl"
:src="currentTask.storeUrl" :src="currentTask.storeUrl"
@@ -66,6 +66,11 @@
<span class="text-sm opacity-80">{{ currentTask.errorInfo || '未知错误' }}</span> <span class="text-sm opacity-80">{{ currentTask.errorInfo || '未知错误' }}</span>
</div> </div>
</div> </div>
<!-- Download Button Overlay -->
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity z-10">
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
</div>
</div> </div>
<!-- Right Info --> <!-- Right Info -->
@@ -127,7 +132,7 @@
type="success" type="success"
class="w-full" class="w-full"
:icon="Share" :icon="Share"
@click="handlePublish(currentTask)" @click="openPublishDialog(currentTask)"
> >
发布到广场 发布到广场
</el-button> </el-button>
@@ -139,18 +144,64 @@
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
<!-- Publish Dialog -->
<el-dialog
v-model="publishDialogVisible"
title="发布到广场"
width="500px"
append-to-body
align-center
>
<el-form label-position="top">
<el-form-item label="标签 (回车添加)">
<div class="flex gap-2 flex-wrap w-full p-2 border border-gray-200 rounded-md min-h-[40px]">
<el-tag
v-for="tag in publishTags"
:key="tag"
closable
:disable-transitions="false"
@close="handleCloseTag(tag)"
>
{{ tag }}
</el-tag>
<el-input
v-if="inputVisible"
ref="InputRef"
v-model="inputValue"
class="w-24"
size="small"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<el-button v-else class="button-new-tag" size="small" @click="showInput">
+ New Tag
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="publishDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmPublish" :loading="publishing">
发布
</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed, nextTick } from 'vue';
import { getMyTasks, publishImage } from '@/api/aiImage'; import { getMyTasks, publishImage } from '@/api/aiImage';
import type { TaskItem } from '@/api/aiImage/types'; import type { TaskItem } from '@/api/aiImage/types';
import TaskCard from './TaskCard.vue'; import TaskCard from './TaskCard.vue';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import type { InputInstance } from 'element-plus';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
import { Picture, Loading, MagicStick, CopyDocument, Share } from '@element-plus/icons-vue'; import { Picture, Loading, MagicStick, CopyDocument, Share, CircleCloseFilled, Download } from '@element-plus/icons-vue';
const emit = defineEmits(['use-prompt', 'use-reference']); const emit = defineEmits(['use-prompt', 'use-reference']);
@@ -162,6 +213,15 @@ const noMore = ref(false);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const currentTask = ref<TaskItem | null>(null); const currentTask = ref<TaskItem | null>(null);
// Publish Dialog State
const publishDialogVisible = ref(false);
const publishing = ref(false);
const publishTags = ref<string[]>([]);
const inputValue = ref('');
const inputVisible = ref(false);
const InputRef = ref<InputInstance>();
const taskToPublish = ref<TaskItem | null>(null);
const { copy } = useClipboard(); const { copy } = useClipboard();
const disabled = computed(() => loading.value || noMore.value); const disabled = computed(() => loading.value || noMore.value);
@@ -230,23 +290,71 @@ const copyPrompt = async (text: string) => {
ElMessage.success('提示词已复制'); ElMessage.success('提示词已复制');
}; };
const handlePublish = async (task: TaskItem) => { const openPublishDialog = (task: TaskItem) => {
try { taskToPublish.value = task;
await ElMessageBox.confirm('确定要发布这张图片到广场吗?', '提示', { publishTags.value = [];
confirmButtonText: '确定', inputValue.value = '';
cancelButtonText: '取消', inputVisible.value = false;
type: 'info', publishDialogVisible.value = true;
}); };
const handleCloseTag = (tag: string) => {
publishTags.value.splice(publishTags.value.indexOf(tag), 1);
};
const showInput = () => {
inputVisible.value = true;
nextTick(() => {
InputRef.value!.input!.focus();
});
};
const handleInputConfirm = () => {
if (inputValue.value) {
if (!publishTags.value.includes(inputValue.value)) {
publishTags.value.push(inputValue.value);
}
}
inputVisible.value = false;
inputValue.value = '';
};
const confirmPublish = async () => {
if (!taskToPublish.value) return;
publishing.value = true;
try {
await publishImage({ await publishImage({
taskId: task.id, taskId: taskToPublish.value.id,
categories: [] categories: publishTags.value
}); });
ElMessage.success('发布成功'); ElMessage.success('发布成功');
task.publishStatus = 'Published'; taskToPublish.value.publishStatus = 'Published';
publishDialogVisible.value = false;
} catch (e) { } catch (e) {
// Cancelled or error console.error(e);
ElMessage.error('发布失败');
} finally {
publishing.value = false;
}
};
const downloadImage = async (url: string) => {
try {
const response = await fetch(url);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = `image-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
} catch (e) {
console.error('Download failed', e);
window.open(url, '_blank');
} }
}; };

View File

@@ -1,63 +1,35 @@
<template>
<div class="image-page-container h-full flex flex-col">
<el-tabs v-model="activeTab" class="flex-1 flex flex-col" type="border-card">
<el-tab-pane label="提示词广场" name="plaza" class="h-full">
<ImagePlaza
v-if="activeTab === 'plaza'"
@use-prompt="handleUsePrompt"
@use-reference="handleUseReference"
/>
</el-tab-pane>
<el-tab-pane label="图片生成" name="generate" class="h-full">
<ImageGenerator
ref="imageGeneratorRef"
@task-created="handleTaskCreated"
/>
</el-tab-pane>
<el-tab-pane label="我的图库" name="my-images" class="h-full">
<MyImages
ref="myImagesRef"
v-if="activeTab === 'my-images'"
@use-prompt="handleUsePrompt"
@use-reference="handleUseReference"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue'; import { nextTick, ref, watch } from 'vue';
import ImagePlaza from './components/ImagePlaza.vue';
import ImageGenerator from './components/ImageGenerator.vue'; import ImageGenerator from './components/ImageGenerator.vue';
import ImagePlaza from './components/ImagePlaza.vue';
import MyImages from './components/MyImages.vue'; import MyImages from './components/MyImages.vue';
const activeTab = ref('plaza'); const activeTab = ref('plaza');
const myImagesRef = ref(); const myImagesRef = ref();
const imageGeneratorRef = ref(); const imageGeneratorRef = ref();
const handleTaskCreated = () => { function handleTaskCreated() {
// Optional: Switch to My Images or just notify // Optional: Switch to My Images or just notify
// For now, we stay on Generator page to see the result. // For now, we stay on Generator page to see the result.
}; }
const handleUsePrompt = (prompt: string) => { function handleUsePrompt(prompt: string) {
activeTab.value = 'generate'; activeTab.value = 'generate';
nextTick(() => { nextTick(() => {
if (imageGeneratorRef.value) { if (imageGeneratorRef.value) {
imageGeneratorRef.value.setPrompt(prompt); imageGeneratorRef.value.setPrompt(prompt);
} }
}); });
}; }
const handleUseReference = (url: string) => { function handleUseReference(url: string) {
activeTab.value = 'generate'; activeTab.value = 'generate';
nextTick(() => { nextTick(() => {
if (imageGeneratorRef.value && url) { if (imageGeneratorRef.value && url) {
imageGeneratorRef.value.addReferenceImage(url); imageGeneratorRef.value.addReferenceImage(url);
} }
}); });
}; }
// Refresh My Images when tab is activated // Refresh My Images when tab is activated
watch(activeTab, (val) => { watch(activeTab, (val) => {
@@ -67,17 +39,47 @@ watch(activeTab, (val) => {
}); });
</script> </script>
<template>
<div class="image-page-container h-full flex flex-col">
<el-tabs v-model="activeTab" class="flex-1 flex flex-col" type="border-card">
<el-tab-pane label="图片广场" name="plaza" class="h-full">
<ImagePlaza
v-if="activeTab === 'plaza'"
@use-prompt="handleUsePrompt"
@use-reference="handleUseReference"
/>
</el-tab-pane>
<el-tab-pane label="图片生成" name="generate" class="h-full">
<ImageGenerator
ref="imageGeneratorRef"
:is-active="activeTab === 'generate'"
@task-created="handleTaskCreated"
/>
</el-tab-pane>
<el-tab-pane label="我的图库" name="my-images" class="h-full">
<MyImages
v-if="activeTab === 'my-images'"
ref="myImagesRef"
@use-prompt="handleUsePrompt"
@use-reference="handleUseReference"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<style scoped> <style scoped>
.image-page-container { .image-page-container {
height: 100%; height: 100%;
padding: 10px;
box-sizing: border-box; box-sizing: border-box;
} }
:deep(.el-tabs__content) { :deep(.el-tabs__content) {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;
height: calc(100% - 40px); height: calc(100% - 40px);
} }
:deep(.el-tab-pane) { :deep(.el-tab-pane) {
height: 100%; height: 100%;

View File

@@ -2,6 +2,8 @@
import { Expand, Fold } from '@element-plus/icons-vue'; import { Expand, Fold } from '@element-plus/icons-vue';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { checkPagePermission } from '@/config/permission.ts';
import { useUserStore } from '@/stores';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -13,9 +15,16 @@ const inviteCodeFromUrl = computed(() => {
// 控制侧边栏折叠状态 // 控制侧边栏折叠状态
const isCollapsed = ref(false); const isCollapsed = ref(false);
const userStore = useUserStore();
const userName = userStore.userInfo?.user?.userName;
const hasPermission = checkPagePermission('/console/channel', userName);
// 菜单项配置 // 菜单项配置
const navItems = [
// 基础菜单项
const baseNavItems = [
{ name: 'user', label: '用户信息', icon: 'User', path: '/console/user' }, { name: 'user', label: '用户信息', icon: 'User', path: '/console/user' },
{ name: 'apikey', label: 'API密钥', icon: 'Key', path: '/console/apikey' }, { name: 'apikey', label: 'API密钥', icon: 'Key', path: '/console/apikey' },
{ name: 'recharge-log', label: '充值记录', icon: 'Document', path: '/console/recharge-log' }, { name: 'recharge-log', label: '充值记录', icon: 'Document', path: '/console/recharge-log' },
@@ -24,9 +33,13 @@ const navItems = [
{ name: 'daily-task', label: '每日任务(限时)', icon: 'Trophy', path: '/console/daily-task' }, { name: 'daily-task', label: '每日任务(限时)', icon: 'Trophy', path: '/console/daily-task' },
{ name: 'invite', label: '每周邀请(限时)', icon: 'Present', path: '/console/invite' }, { name: 'invite', label: '每周邀请(限时)', icon: 'Present', path: '/console/invite' },
{ name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' }, { name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' },
{ name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' },
]; ];
// 根据权限动态添加渠道商管理
const navItems = hasPermission
? [...baseNavItems, { name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' }]
: baseNavItems;
// 当前激活的菜单 // 当前激活的菜单
const activeNav = computed(() => { const activeNav = computed(() => {
const path = route.path; const path = route.path;

View File

@@ -1,4 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
// 页面效果查看
// https://ai.ccnetcore.com/pay-result?charset=UTF-8&out_trade_no=YI_20251015232040_3316&method=alipay.trade.page.pay.return&total_amount=215.90&sign=htfny1D%2B8wcLZzK7StevZG%2BD441RLXvksoAR%2BzOq%2B1WHMwfJdVkzyZF2bBmvbU%2FsHBB2HMl8TT3KoaHaf8UfWZgtDGbMoQC%2F1O%2BRcEw8jljlpW3XLMdKGx6dytqZkhq9lRD6tR3ofBiuviv2PmxVd1l%2Bcqs7nwNWwKJWonWI0c5UOE%2BYWgg3hjEJnMYVQjUb6FvrVLfANEU0YyTO%2Bi6vL55Gwug6GIXvGqUPZc3GbwXc%2FUHnu1qv4Yi6tc1dtUoLUNHVfTKrC2N55T84AALZteIK0m7suzrkvBPcKdpn4NGVDtv5cCBCHPjtD3COrNISrNUf3sQXpTvqJGw6dWag6g%3D%3D&trade_no=2025101522001438971445003566&auth_app_id=2021005182687851&version=1.0&app_id=2021005182687851&sign_type=RSA2&seller_id=2088870286599802&timestamp=2025-10-15+23%3A21%3A01
import { ElButton, ElDivider, ElMessage } from 'element-plus'; import { ElButton, ElDivider, ElMessage } from 'element-plus';
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';

View File

@@ -1,7 +1,9 @@
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'; import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
import { ElMessage } from 'element-plus';
import { useNProgress } from '@vueuse/integrations/useNProgress'; import { useNProgress } from '@vueuse/integrations/useNProgress';
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
import { ROUTER_WHITE_LIST } from '@/config'; import { ROUTER_WHITE_LIST } from '@/config';
import { checkPagePermission } from '@/config/permission';
import { errorRouter, layoutRouter, staticRouter } from '@/routers/modules/staticRouter'; import { errorRouter, layoutRouter, staticRouter } from '@/routers/modules/staticRouter';
import { useDesignStore, useUserStore } from '@/stores'; import { useDesignStore, useUserStore } from '@/stores';
@@ -63,6 +65,16 @@ router.beforeEach(
if (!userStore.token) if (!userStore.token)
userStore.logout(); userStore.logout();
// 7. 页面权限检查
const userName = userStore.userInfo?.user?.userName;
const hasPermission = checkPagePermission(to.path, userName);
if (!hasPermission) {
// 用户无权访问该页面跳转到403页面
ElMessage.warning('您没有权限访问该页面');
return next('/403');
}
// 其余逻辑 预留... // 其余逻辑 预留...
// 8. 放行路由 // 8. 放行路由

View File

@@ -187,15 +187,15 @@
--button-padding: 12px 24px; --button-padding: 12px 24px;
/* ========== 覆盖 element-plus 样式 ========== */ /* ========== 覆盖 element-plus 样式 ========== */
--el-border-radius-base: 12px !important; //--el-border-radius-base: 12px !important;
--el-messagebox-border-radius: 16px !important; //--el-messagebox-border-radius: 16px !important;
--el-color-primary: var(--color-primary) !important; //--el-color-primary: var(--color-primary) !important;
--el-color-success: var(--color-success) !important; //--el-color-success: var(--color-success) !important;
--el-color-warning: var(--color-warning) !important; //--el-color-warning: var(--color-warning) !important;
--el-color-danger: var(--color-danger) !important; //--el-color-danger: var(--color-danger) !important;
--el-color-info: var(--color-info) !important; //--el-color-info: var(--color-info) !important;
--el-font-size-base: var(--font-size-base) !important; //--el-font-size-base: var(--font-size-base) !important;
--el-font-family: var(--font-family-sans) !important; //--el-font-family: var(--font-family-sans) !important;
/* Element Plus 组件特定变量 */ /* Element Plus 组件特定变量 */
--el-menu-item-height: 48px; --el-menu-item-height: 48px;
@@ -205,16 +205,16 @@
//--el-menu-hover-bg-color: var(--color-gray-100); //--el-menu-hover-bg-color: var(--color-gray-100);
/* 表单相关 */ /* 表单相关 */
--el-form-label-font-size: var(--font-size-sm); //--el-form-label-font-size: var(--font-size-sm);
--el-input-height: var(--input-height); //--el-input-height: var(--input-height);
--el-input-border-color: var(--border-color-light); //--el-input-border-color: var(--border-color-light);
--el-input-hover-border-color: var(--color-primary-light); //--el-input-hover-border-color: var(--color-primary-light);
--el-input-focus-border-color: var(--color-primary); //--el-input-focus-border-color: var(--color-primary);
/* 按钮相关 */ /* 按钮相关 */
--el-button-border-radius-base: var(--border-radius-md); //--el-button-border-radius-base: var(--border-radius-md);
--el-button-hover-bg-color: var(--color-primary-dark); //--el-button-hover-bg-color: var(--color-primary-dark);
--el-button-active-bg-color: var(--color-primary-darker); //--el-button-active-bg-color: var(--color-primary-darker);
} }
/* ========== 暗色模式变量 ========== */ /* ========== 暗色模式变量 ========== */