feat: 路由动态权限控制、图片广场优化
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
110
Yi.Ai.Vue3/src/config/permission.ts
Normal file
110
Yi.Ai.Vue3/src/config/permission.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
taskToPublish.value = task;
|
||||||
|
publishTags.value = [];
|
||||||
|
inputValue.value = '';
|
||||||
|
inputVisible.value = false;
|
||||||
|
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 {
|
try {
|
||||||
await ElMessageBox.confirm('确定要发布这张图片到广场吗?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'info',
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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×tamp=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';
|
||||||
|
|
||||||
|
|||||||
@@ -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. 放行路由
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 暗色模式变量 ========== */
|
/* ========== 暗色模式变量 ========== */
|
||||||
|
|||||||
Reference in New Issue
Block a user