feat: 添加分页组件和地址选择组件,优化样式和类型定义
refactor: 重构路由配置,移除订单页面路由 style: 统一使用CSS变量替换硬编码颜色值 docs: 添加前端数据需求分析文档 fix: 修复登录页面记住我功能,更新用户信息页面链接 perf: 优化搜索组件动画效果和响应式设计 chore: 更新TypeScript类型定义,添加订单和地址相关类型
This commit is contained in:
@@ -580,17 +580,17 @@ const removeItem = (id: number) => {
|
||||
}
|
||||
|
||||
.cart-item-model {
|
||||
font-size: 16px;
|
||||
padding: 18px 32px;
|
||||
color: #666;
|
||||
border-radius: 18px;
|
||||
font-size: var(--font-size-base);
|
||||
padding: var(--spacing-lg) var(--spacing-xl);
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--border-radius-lg);
|
||||
cursor: pointer;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: all var(--transition-transform);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
||||
border: 2px solid #f0f0f0;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||
border: 2px solid var(--border-color);
|
||||
box-shadow: var(--shadow-md);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -601,8 +601,8 @@ const removeItem = (id: number) => {
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 80, 0, 0.15), transparent);
|
||||
transition: left 0.6s ease;
|
||||
background: linear-gradient(90deg, transparent, rgba(var(--primary-light-rgb), 0.15), transparent);
|
||||
transition: left var(--transition-slow);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@@ -613,18 +613,18 @@ const removeItem = (id: number) => {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, rgba(255, 80, 0, 0.08) 0%, rgba(255, 80, 0, 0.04) 100%);
|
||||
background: linear-gradient(135deg, rgba(var(--primary-light-rgb), 0.08) 0%, rgba(var(--primary-light-rgb), 0.04) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
transition: opacity var(--transition-transform);
|
||||
z-index: -1;
|
||||
border-radius: 18px;
|
||||
border-radius: var(--border-radius-lg);
|
||||
}
|
||||
|
||||
.cart-item-model:hover {
|
||||
border: 2px dashed #ff5000;
|
||||
color: #ff5000;
|
||||
border: 2px dashed var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
transform: translateY(-5px) scale(1.05);
|
||||
box-shadow: 0 10px 24px rgba(255, 80, 0, 0.2);
|
||||
box-shadow: 0 10px 24px rgba(var(--primary-light-rgb), 0.2);
|
||||
}
|
||||
|
||||
.cart-item-model:hover::before {
|
||||
@@ -659,13 +659,13 @@ const removeItem = (id: number) => {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 15px;
|
||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
||||
border-radius: 20px;
|
||||
border: 2px solid #f0f0f0;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-md);
|
||||
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||
border-radius: var(--border-radius-xl);
|
||||
border: 2px solid var(--border-color);
|
||||
transition: all var(--transition-transform);
|
||||
box-shadow: var(--shadow-md);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -676,16 +676,16 @@ const removeItem = (id: number) => {
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, #ff5000, #ff8500, #ff5000);
|
||||
border-radius: var(--border-radius-xl);
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover), var(--primary-color));
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: opacity var(--transition-transform);
|
||||
}
|
||||
|
||||
.cart-item:hover .cart-item-price {
|
||||
border-color: #ff5000;
|
||||
box-shadow: 0 10px 30px rgba(255, 80, 0, 0.2);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 10px 30px rgba(var(--primary-light-rgb), 0.2);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
@@ -718,25 +718,25 @@ const removeItem = (id: number) => {
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #fff0f0 100%);
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, var(--bg-primary-light) 0%, var(--bg-primary-lighter) 100%);
|
||||
padding: var(--spacing-xs) var(--spacing-lg);
|
||||
border-radius: var(--border-radius-xl);
|
||||
align-self: flex-start;
|
||||
border: 1px solid #ffece5;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 3px 12px rgba(255, 80, 0, 0.1);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all var(--transition-normal);
|
||||
box-shadow: 0 3px 12px rgba(var(--primary-light-rgb), 0.1);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cart-item:hover .price-label {
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||
color: #ffffff;
|
||||
border-color: #ff5000;
|
||||
box-shadow: 0 6px 20px rgba(255, 80, 0, 0.35);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 6px 20px rgba(var(--primary-light-rgb), 0.35);
|
||||
transform: scale(1.08);
|
||||
animation: label-pulse 0.6s ease;
|
||||
}
|
||||
@@ -751,10 +751,10 @@ const removeItem = (id: number) => {
|
||||
}
|
||||
|
||||
.price-symbol {
|
||||
font-size: 16px;
|
||||
color: #ff5000;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.cart-item:hover .price-symbol {
|
||||
@@ -762,42 +762,42 @@ const removeItem = (id: number) => {
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 26px;
|
||||
color: #ff5000;
|
||||
font-size: var(--font-size-2xl);
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
transition: all 0.3s ease;
|
||||
transition: all var(--transition-normal);
|
||||
position: relative;
|
||||
text-shadow: 0 2px 4px rgba(255, 80, 0, 0.2);
|
||||
text-shadow: 0 2px 4px rgba(var(--primary-light-rgb), 0.2);
|
||||
}
|
||||
|
||||
.cart-item:hover .price-value {
|
||||
transform: scale(1.05);
|
||||
text-shadow: 0 4px 8px rgba(255, 80, 0, 0.35);
|
||||
text-shadow: 0 4px 8px rgba(var(--primary-light-rgb), 0.35);
|
||||
}
|
||||
|
||||
.original-price .price-symbol {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.original-price .price-value {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--text-tertiary);
|
||||
text-decoration: line-through;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.discount-badge {
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||
color: #ffffff;
|
||||
font-size: 11px;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
margin-left: 6px;
|
||||
box-shadow: 0 3px 6px rgba(255, 80, 0, 0.25);
|
||||
transition: all 0.3s ease;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--border-radius-sm);
|
||||
margin-left: var(--spacing-xs);
|
||||
box-shadow: 0 3px 6px rgba(var(--primary-light-rgb), 0.25);
|
||||
transition: all var(--transition-normal);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1006,12 +1006,12 @@ const removeItem = (id: number) => {
|
||||
.cart-item-quantity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
||||
padding: 10px 20px;
|
||||
border-radius: 32px;
|
||||
border: 2px solid #f0f0f0;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
gap: var(--spacing-sm);
|
||||
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
border-radius: var(--border-radius-2xl);
|
||||
border: 2px solid var(--border-color);
|
||||
transition: all var(--transition-transform);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1023,13 +1023,13 @@ const removeItem = (id: number) => {
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 80, 0, 0.08), transparent);
|
||||
transition: left 0.6s ease;
|
||||
background: linear-gradient(90deg, transparent, rgba(var(--primary-light-rgb), 0.08), transparent);
|
||||
transition: left var(--transition-slow);
|
||||
}
|
||||
|
||||
.cart-item:hover .cart-item-quantity {
|
||||
border-color: #ff5000;
|
||||
box-shadow: 0 6px 16px rgba(255, 80, 0, 0.15);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 6px 16px rgba(var(--primary-light-rgb), 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@@ -1040,14 +1040,14 @@ const removeItem = (id: number) => {
|
||||
.quantity-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid #f0f0f0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f8f8 100%);
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--border-color);
|
||||
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||
border-radius: var(--border-radius-full);
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
color: var(--text-secondary);
|
||||
transition: all var(--transition-transform);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1063,9 +1063,9 @@ const removeItem = (id: number) => {
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff7300 100%);
|
||||
border-radius: 50%;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||
border-radius: var(--border-radius-full);
|
||||
transition: all var(--transition-transform);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -1076,10 +1076,10 @@ const removeItem = (id: number) => {
|
||||
}
|
||||
|
||||
.quantity-btn:hover {
|
||||
border-color: #ff5000;
|
||||
border-color: var(--primary-color);
|
||||
color: #ffffff;
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 6px 16px rgba(255, 80, 0, 0.35);
|
||||
box-shadow: 0 6px 16px rgba(var(--primary-light-rgb), 0.35);
|
||||
}
|
||||
|
||||
.quantity-btn:active {
|
||||
@@ -1124,22 +1124,22 @@ const removeItem = (id: number) => {
|
||||
width: 80px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
transition: all var(--transition-transform);
|
||||
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.quantity-input:focus {
|
||||
outline: none;
|
||||
border-color: #ff5000;
|
||||
box-shadow: 0 0 0 4px rgba(255, 80, 0, 0.2), inset 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 4px rgba(var(--primary-light-rgb), 0.2), inset 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
transform: scale(1.1);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fff5f5 100%);
|
||||
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-primary-light) 100%);
|
||||
}
|
||||
|
||||
.quantity-input::-webkit-inner-spin-button,
|
||||
@@ -1174,28 +1174,28 @@ const removeItem = (id: number) => {
|
||||
width: 160px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: var(--spacing-md);
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
padding: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.cart-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 30px;
|
||||
border-radius: 32px;
|
||||
font-size: 15px;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-xl);
|
||||
border-radius: var(--border-radius-2xl);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: all var(--transition-transform);
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
min-width: 120px;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.cart-action-btn::before {
|
||||
@@ -1205,8 +1205,8 @@ const removeItem = (id: number) => {
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
border-radius: var(--border-radius-full);
|
||||
transition: all var(--transition-slow);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -1218,7 +1218,7 @@ const removeItem = (id: number) => {
|
||||
|
||||
.cart-action-btn:hover {
|
||||
transform: translateY(-3px) scale(1.05);
|
||||
box-shadow: 0 8px 24px rgba(255, 80, 0, 0.25);
|
||||
box-shadow: 0 8px 24px rgba(var(--primary-light-rgb), 0.25);
|
||||
}
|
||||
|
||||
.cart-action-btn:active {
|
||||
@@ -1242,43 +1242,43 @@ const removeItem = (id: number) => {
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
border-color: #ff4d4f;
|
||||
color: #ff4d4f;
|
||||
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||
border-color: var(--error-color);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.delete-btn::before {
|
||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
|
||||
background: linear-gradient(135deg, var(--error-color) 0%, #ff7875 100%);
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
color: #ffffff;
|
||||
border-color: #ff4d4f;
|
||||
border-color: var(--error-color);
|
||||
}
|
||||
|
||||
.favorite-btn {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
border-color: #ff5000;
|
||||
color: #ff5000;
|
||||
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.favorite-btn::before {
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff7300 100%);
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||
}
|
||||
|
||||
.favorite-btn:hover {
|
||||
color: #ffffff;
|
||||
border-color: #ff5000;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.favorite-btn.favorited {
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff7300 100%);
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||
color: #ffffff;
|
||||
border-color: #ff5000;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.favorite-btn.favorited:hover {
|
||||
background: linear-gradient(135deg, #ff7300 0%, #ff5000 100%);
|
||||
background: linear-gradient(135deg, var(--primary-hover) 0%, var(--primary-color) 100%);
|
||||
}
|
||||
|
||||
.favorite-btn.favorited i::before {
|
||||
@@ -1290,9 +1290,9 @@ const removeItem = (id: number) => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25px 40px;
|
||||
border-top: 2px solid #f0f0f0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
padding: var(--spacing-lg) var(--spacing-xxl);
|
||||
border-top: 2px solid var(--border-color);
|
||||
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
@@ -1301,77 +1301,77 @@ const removeItem = (id: number) => {
|
||||
.footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: var(--spacing-xs);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.select-all:hover {
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.selected-count {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 18px;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.total-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5px;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.total-symbol {
|
||||
font-size: 20px;
|
||||
color: #ff5000;
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 32px;
|
||||
color: #ff5000;
|
||||
font-size: var(--font-size-3xl);
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
padding: 12px 40px;
|
||||
font-size: 16px;
|
||||
padding: var(--spacing-sm) var(--spacing-xxl);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
border-radius: 24px;
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
||||
border-radius: var(--border-radius-xl);
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||
border: none;
|
||||
box-shadow: 0 4px 12px rgba(255, 80, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(var(--primary-light-rgb), 0.3);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.checkout-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(255, 80, 0, 0.4);
|
||||
box-shadow: 0 6px 20px rgba(var(--primary-light-rgb), 0.4);
|
||||
}
|
||||
</style>
|
||||
@@ -467,52 +467,52 @@ const sendMessage = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding: var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.chat-user-info h3 {
|
||||
margin: 0 0 2px;
|
||||
font-size: 16px;
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.user-status {
|
||||
font-size: 12px;
|
||||
color: #4caf50;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--success-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chat-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: var(--border-radius-full);
|
||||
background-color: var(--bg-light);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-size: var(--font-size-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* 聊天历史 */
|
||||
.chat-history {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
padding: var(--spacing-xl);
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--bg-color);
|
||||
background-image: url('?prompt=chat%20background%20pattern%20subtle%20light&image_size=landscape_16_9');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
@@ -520,7 +520,7 @@ const sendMessage = () => {
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: var(--spacing-md);
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
@@ -530,7 +530,7 @@ const sendMessage = () => {
|
||||
|
||||
.chat-message.sent .user-avatar {
|
||||
margin-right: 0;
|
||||
margin-left: 10px;
|
||||
margin-left: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.message-content {
|
||||
@@ -544,55 +544,55 @@ const sendMessage = () => {
|
||||
}
|
||||
|
||||
.message-text {
|
||||
padding: 10px 15px;
|
||||
border-radius: 18px;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--message-radius);
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.chat-message.received .message-text {
|
||||
background-color: #ffffff;
|
||||
background-color: var(--card-bg);
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-message.sent .message-text {
|
||||
background-color: #ff5000;
|
||||
background-color: var(--primary-color);
|
||||
color: #ffffff;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 11px;
|
||||
color: #999999;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 消息输入区 */
|
||||
.chat-input-area {
|
||||
background-color: #f5f5f5;
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-radius: 0 0 10px 0;
|
||||
background-color: var(--bg-color);
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-radius: 0 0 var(--border-radius-lg) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.input-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 24px;
|
||||
padding: 8px 15px;
|
||||
gap: 10px;
|
||||
background-color: var(--bg-light);
|
||||
border-radius: var(--border-radius-xl);
|
||||
padding: var(--spacing-xs) var(--spacing-md);
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.input-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
font-size: var(--font-size-base);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.message-input {
|
||||
@@ -600,29 +600,29 @@ const sendMessage = () => {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-primary);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.message-input::placeholder {
|
||||
color: #999999;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
padding: 8px 20px;
|
||||
background-color: #ff5000;
|
||||
padding: var(--spacing-xs) var(--spacing-xl);
|
||||
background-color: var(--primary-color);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
border-radius: var(--border-radius-xl);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.send-btn:hover {
|
||||
background-color: #ff6a00;
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
@@ -632,29 +632,29 @@ const sendMessage = () => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
padding: 40px;
|
||||
background-color: var(--bg-color);
|
||||
padding: var(--spacing-xxl);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 18px;
|
||||
margin: 0 0 var(--spacing-md);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
|
||||
@@ -24,12 +24,20 @@
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<!-- 记住我 如果用户登录成功,记住用户信息,5天内无需重新登录 -->
|
||||
<div class="form-group remember-me">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="loginform.rememberMe" class="checkbox-input">
|
||||
<span class="checkbox-text">记住我</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button type="submit" class="login-button" :disabled="isLoading">
|
||||
<span v-if="isLoading" class="loading-spinner"></span>
|
||||
{{ isLoading ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
|
||||
<!-- 注册链接 -->
|
||||
<div class="login-footer">
|
||||
<p class="register-link">
|
||||
还没有账号? <a href="#" @click.prevent="router.push({ name: 'register' })">立即注册</a>
|
||||
@@ -51,7 +59,8 @@ const router = useRouter()
|
||||
|
||||
const loginform = ref<Login>({
|
||||
username: '',
|
||||
password: ''
|
||||
password: '',
|
||||
rememberMe: false
|
||||
})
|
||||
|
||||
const errorMessage = ref('')
|
||||
@@ -140,6 +149,37 @@ const login = async () => {
|
||||
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 记住我复选框样式 */
|
||||
.remember-me {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox-input {
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-text {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-text:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 登录头部 */
|
||||
.login-header {
|
||||
text-align: center;
|
||||
|
||||
446
src/Views/User/UpdataAddress.vue
Normal file
446
src/Views/User/UpdataAddress.vue
Normal file
@@ -0,0 +1,446 @@
|
||||
<!-- 更新地址弹窗 上下结构 上方有一个输入框可以识别地址更新到下方 下方有可以更新地址信息(省份、城市、区县、详细地址) -->
|
||||
<!-- 父组件会传递一个地址对象 子组件需要根据这个地址对象更新弹窗的地址信息 -->
|
||||
<template>
|
||||
<a-modal :open="visible" title="更新地址" :footer="null" width="500px">
|
||||
<div class="modal-content">
|
||||
<!-- 地址识别输入框 -->
|
||||
<div class="form-group">
|
||||
<label for="address-input">地址识别</label>
|
||||
<a-input v-model:value="addressInput" placeholder="请输入完整地址,系统会自动识别省份、城市、区县"
|
||||
@change="handleAddressRecognition" />
|
||||
<div v-if="recognitionError" class="error-message">{{ recognitionError }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 地址信息表单 -->
|
||||
<div class="form-group">
|
||||
<label for="province">省份 <span class="required">*</span></label>
|
||||
<a-select v-model:value="form.province" :options="provinceOptions" placeholder="请选择省份"
|
||||
@change="handleProvinceChange" />
|
||||
<div v-if="errors.province" class="error-message">{{ errors.province }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="city">城市 <span class="required">*</span></label>
|
||||
<a-select v-model:value="form.city" :options="cityOptions" placeholder="请选择城市"
|
||||
@change="handleCityChange" />
|
||||
<div v-if="errors.city" class="error-message">{{ errors.city }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="district">区县 <span class="required">*</span></label>
|
||||
<a-select v-model:value="form.district" :options="districtOptions" placeholder="请选择区县" />
|
||||
<div v-if="errors.district" class="error-message">{{ errors.district }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="detailAddress">详细地址 <span class="required">*</span></label>
|
||||
<a-input v-model:value="form.detail" placeholder="请输入详细地址(街道、楼栋、门牌号等)" :rows="3" type="textarea" />
|
||||
<div v-if="errors.detail" class="error-message">{{ errors.detail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" @click="handleOk">确认更新</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import type { Address } from '@/Util/Type'
|
||||
const props = defineProps({
|
||||
// 设置弹窗显示状态控制
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 传递的地址对象
|
||||
address: {
|
||||
type: Object as () => Address,
|
||||
default: () => ({
|
||||
name: '',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detail: '',
|
||||
isDefault: false
|
||||
}) as Address,
|
||||
}
|
||||
})
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:visible', 'update:updateaddress'])
|
||||
// 弹窗显示状态控制
|
||||
const visible = ref(props.visible)
|
||||
// 表单数据
|
||||
const form = ref<Address>({
|
||||
id:props.address.id ,
|
||||
name: props.address.name,
|
||||
phone: props.address.phone,
|
||||
province: props.address.province,
|
||||
city: props.address.city,
|
||||
district: props.address.district,
|
||||
detail: props.address.detail,
|
||||
isDefault: props.address.isDefault
|
||||
})
|
||||
|
||||
// 地址识别输入框
|
||||
const addressInput = ref('')
|
||||
const recognitionError = ref('')
|
||||
|
||||
// 表单验证错误
|
||||
const errors = ref({
|
||||
name: '',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detail: '',
|
||||
isDefault: ''
|
||||
})
|
||||
|
||||
// 定义类型
|
||||
interface Data {
|
||||
[key: string]: { label: string; value: string }[] | undefined;
|
||||
}
|
||||
// 城市选项(根据省份动态生成)
|
||||
const cityOptions = ref<any[]>([])
|
||||
|
||||
// 区县选项(根据城市动态生成)
|
||||
const districtOptions = ref<any[]>([])
|
||||
|
||||
// 省份选项
|
||||
const provinceOptions = ref([
|
||||
{ label: '北京市', value: '北京市' },
|
||||
{ label: '上海市', value: '上海市' },
|
||||
{ label: '广东省', value: '广东省' },
|
||||
{ label: '浙江省', value: '浙江省' },
|
||||
{ label: '江苏省', value: '江苏省' },
|
||||
{ label: '四川省', value: '四川省' },
|
||||
{ label: '湖北省', value: '湖北省' },
|
||||
{ label: '湖南省', value: '湖南省' },
|
||||
{ label: '山东省', value: '山东省' },
|
||||
{ label: '河南省', value: '河南省' }
|
||||
])
|
||||
|
||||
// 城市数据
|
||||
const cityData: Data = {
|
||||
'北京市': [{ label: '北京市', value: '北京市' }],
|
||||
'上海市': [{ label: '上海市', value: '上海市' }],
|
||||
'广东省': [
|
||||
{ label: '广州市', value: '广州市' },
|
||||
{ label: '深圳市', value: '深圳市' },
|
||||
{ label: '东莞市', value: '东莞市' },
|
||||
{ label: '佛山市', value: '佛山市' }
|
||||
],
|
||||
'浙江省': [
|
||||
{ label: '杭州市', value: '杭州市' },
|
||||
{ label: '宁波市', value: '宁波市' },
|
||||
{ label: '温州市', value: '温州市' },
|
||||
{ label: '嘉兴市', value: '嘉兴市' }
|
||||
],
|
||||
'江苏省': [
|
||||
{ label: '南京市', value: '南京市' },
|
||||
{ label: '苏州市', value: '苏州市' },
|
||||
{ label: '无锡市', value: '无锡市' },
|
||||
{ label: '常州市', value: '常州市' }
|
||||
]
|
||||
}
|
||||
|
||||
// 区县数据
|
||||
const districtData: Data = {
|
||||
'北京市-北京市': [
|
||||
{ label: '东城区', value: '东城区' },
|
||||
{ label: '西城区', value: '西城区' },
|
||||
{ label: '朝阳区', value: '朝阳区' },
|
||||
{ label: '海淀区', value: '海淀区' },
|
||||
{ label: '丰台区', value: '丰台区' },
|
||||
{ label: '石景山区', value: '石景山区' },
|
||||
{ label: '门头沟区', value: '门头沟区' },
|
||||
{ label: '房山区', value: '房山区' },
|
||||
{ label: '通州区', value: '通州区' },
|
||||
{ label: '顺义区', value: '顺义区' }
|
||||
],
|
||||
'上海市-上海市': [
|
||||
{ label: '黄浦区', value: '黄浦区' },
|
||||
{ label: '徐汇区', value: '徐汇区' },
|
||||
{ label: '长宁区', value: '长宁区' },
|
||||
{ label: '静安区', value: '静安区' },
|
||||
{ label: '普陀区', value: '普陀区' },
|
||||
{ label: '虹口区', value: '虹口区' },
|
||||
{ label: '杨浦区', value: '杨浦区' },
|
||||
{ label: '浦东新区', value: '浦东新区' }
|
||||
],
|
||||
'广东省-广州市': [
|
||||
{ label: '越秀区', value: '越秀区' },
|
||||
{ label: '海珠区', value: '海珠区' },
|
||||
{ label: '荔湾区', value: '荔湾区' },
|
||||
{ label: '天河区', value: '天河区' },
|
||||
{ label: '白云区', value: '白云区' },
|
||||
{ label: '黄埔区', value: '黄埔区' },
|
||||
{ label: '番禺区', value: '番禺区' },
|
||||
{ label: '花都区', value: '花都区' }
|
||||
],
|
||||
'浙江省-杭州市': [
|
||||
{ label: '上城区', value: '上城区' },
|
||||
{ label: '下城区', value: '下城区' },
|
||||
{ label: '江干区', value: '江干区' },
|
||||
{ label: '拱墅区', value: '拱墅区' },
|
||||
{ label: '西湖区', value: '西湖区' },
|
||||
{ label: '滨江区', value: '滨江区' },
|
||||
{ label: '萧山区', value: '萧山区' },
|
||||
{ label: '余杭区', value: '余杭区' }
|
||||
]
|
||||
}
|
||||
|
||||
// 监听 address 变化 用于更新弹窗的地址信息
|
||||
watch(() => props.address, (newVal) => {
|
||||
if (newVal) {
|
||||
form.value = { ...newVal }
|
||||
// 更新城市和区县选项
|
||||
handleProvinceChange(newVal.province)
|
||||
if (newVal.city) {
|
||||
handleCityChange(newVal.city)
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
// 监听 visible 变化 用于更新弹窗的显示状态
|
||||
watch(() => props.visible, (newVal) => {
|
||||
visible.value = newVal
|
||||
})
|
||||
// 监听弹窗关闭事件
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (!newVal) {
|
||||
// 重置表单
|
||||
form.value = {
|
||||
id:0,
|
||||
name: '',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detail: '',
|
||||
isDefault: false
|
||||
}
|
||||
addressInput.value = ''
|
||||
recognitionError.value = ''
|
||||
errors.value = {
|
||||
name: '',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detail: '',
|
||||
isDefault: ''
|
||||
}
|
||||
cityOptions.value = []
|
||||
districtOptions.value = []
|
||||
}
|
||||
})
|
||||
|
||||
// 地址识别处理
|
||||
const handleAddressRecognition = () => {
|
||||
if (!addressInput.value) {
|
||||
recognitionError.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 简单的地址识别逻辑(实际项目中可以使用更复杂的算法或API)
|
||||
const address = addressInput.value
|
||||
let province = ''
|
||||
let city = ''
|
||||
let district = ''
|
||||
let detail = ''
|
||||
|
||||
// 识别省份
|
||||
const provinces = provinceOptions.value.map(p => p.value)
|
||||
for (const p of provinces) {
|
||||
if (address.includes(p)) {
|
||||
province = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 识别城市
|
||||
// if (province && (cityData as Record<string, { label: string; value: string }[]>)[province]) {
|
||||
// const cities = cityData[province].map((c: { value: any }) => c.value)
|
||||
// for (const c of cities) {
|
||||
// if (address.includes(c)) {
|
||||
// city = c
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 识别区县
|
||||
// if (province && city && districtData[`${province}-${city}`]) {
|
||||
// const districts = districtData[`${province}-${city}`].map((d: { value: any }) => d.value)
|
||||
// for (const d of districts) {
|
||||
// if (address.includes(d)) {
|
||||
// district = d
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 提取详细地址
|
||||
if (province && city && district) {
|
||||
detail = address.replace(province, '').replace(city, '').replace(district, '').trim()
|
||||
} else if (province && city) {
|
||||
detail = address.replace(province, '').replace(city, '').trim()
|
||||
} else if (province) {
|
||||
detail = address.replace(province, '').trim()
|
||||
} else {
|
||||
detail = address
|
||||
}
|
||||
|
||||
// 更新表单
|
||||
if (province) {
|
||||
form.value.province = province
|
||||
handleProvinceChange(province)
|
||||
if (city) {
|
||||
form.value.city = city
|
||||
handleCityChange(city)
|
||||
if (district) {
|
||||
form.value.district = district
|
||||
}
|
||||
}
|
||||
form.value.detail = detail
|
||||
recognitionError.value = ''
|
||||
} else {
|
||||
recognitionError.value = '未能识别出省份,请手动选择'
|
||||
}
|
||||
}
|
||||
|
||||
// 省份变化处理
|
||||
const handleProvinceChange = (province: string) => {
|
||||
form.value.province = province
|
||||
form.value.city = ''
|
||||
form.value.district = ''
|
||||
|
||||
// 更新城市选项
|
||||
if (province && cityData[province]) {
|
||||
cityOptions.value = cityData[province]
|
||||
} else {
|
||||
cityOptions.value = []
|
||||
}
|
||||
districtOptions.value = []
|
||||
|
||||
// 清除验证错误
|
||||
errors.value.province = ''
|
||||
errors.value.city = ''
|
||||
errors.value.district = ''
|
||||
}
|
||||
|
||||
// 城市变化处理
|
||||
const handleCityChange = (city: string) => {
|
||||
form.value.city = city
|
||||
form.value.district = ''
|
||||
|
||||
// 更新区县选项
|
||||
if (form.value.province && city && districtData[`${form.value.province}-${city}`]) {
|
||||
districtOptions.value = districtData[`${form.value.province}-${city}`] ?? []
|
||||
} else {
|
||||
districtOptions.value = []
|
||||
}
|
||||
|
||||
// 清除验证错误
|
||||
errors.value.city = ''
|
||||
errors.value.district = ''
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const validateForm = (): boolean => {
|
||||
let isValid = true
|
||||
|
||||
// 重置错误信息
|
||||
errors.value = {
|
||||
name: '',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detail: '',
|
||||
isDefault: ''
|
||||
}
|
||||
|
||||
// 验证省份
|
||||
if (!form.value.province) {
|
||||
errors.value.province = '请选择省份'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// 验证城市
|
||||
if (!form.value.city) {
|
||||
errors.value.city = '请选择城市'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// 验证区县
|
||||
if (!form.value.district) {
|
||||
errors.value.district = '请选择区县'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// 验证详细地址
|
||||
if (!form.value.detail) {
|
||||
errors.value.detail = '请输入详细地址'
|
||||
isValid = false
|
||||
} else if (form.value.detail.length < 5) {
|
||||
errors.value.detail = '详细地址至少需要5个字符'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// 点击确认按钮 触发事件 用于更新父组件的 address
|
||||
const handleOk = () => {
|
||||
// 表单验证
|
||||
if (!validateForm()) {
|
||||
return
|
||||
}
|
||||
emit('update:updateaddress', form.value)
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
// 点击取消按钮
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.modal-content {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: var(--error-color, #ff4d4f);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--error-color, #ff4d4f);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-md);
|
||||
border-top: 1px solid var(--border-light, #e8e8e8);
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* 为textarea添加最小高度 */
|
||||
:deep(.ant-input-textarea) {
|
||||
min-height: 80px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-actions">
|
||||
<a href="/user/address" class="action-link">收货地址</a>
|
||||
<a href="/user/follow" class="action-link">关注店铺</a>
|
||||
<a href="/user?nav=address" class="action-link">收货地址</a>
|
||||
<a href="/user?nav=follow" class="action-link">关注店铺</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,7 +265,7 @@ const navigateToOrder = (status: string) => {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #4ecdc4 50%, #45b7d1 100%);
|
||||
border-radius: 16px;
|
||||
padding: 10px;
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 10px;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
position: relative;
|
||||
@@ -429,7 +429,6 @@ const navigateToOrder = (status: string) => {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
.order-status-card:hover {
|
||||
|
||||
@@ -74,7 +74,7 @@ const productLists = ref([
|
||||
padding: 0 var(--spacing-md);
|
||||
}
|
||||
h1 {
|
||||
color: #42b983;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
#footer-contact {
|
||||
@@ -116,31 +116,31 @@ h1 {
|
||||
}
|
||||
|
||||
.copyright-links a:hover {
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.copyright-separator {
|
||||
color: #999;
|
||||
margin: 0 4px;
|
||||
color: var(--text-tertiary);
|
||||
margin: 0 var(--spacing-xs);
|
||||
}
|
||||
|
||||
.copyright-icp {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.copyright-content {
|
||||
padding: 0 20px;
|
||||
padding: 0 var(--spacing-xl);
|
||||
}
|
||||
|
||||
.copyright-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.copyright-links a {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="header">
|
||||
<div id="header" v-if="booleanHeader">
|
||||
<div id="header-profile">
|
||||
<div id="header-profile-left">
|
||||
<!-- 用户信息悬浮菜单 -->
|
||||
@@ -15,6 +15,19 @@
|
||||
</div>
|
||||
<span class="user-arrow">▼</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<div class="user-info-menu">
|
||||
<div class="user-info-menu-item">
|
||||
<a href="javascript:;">1st menu item</a>
|
||||
</div>
|
||||
<div class="user-info-menu-item">
|
||||
<a href="javascript:;">2nd menu item</a>
|
||||
</div>
|
||||
<div class="user-info-menu-item">
|
||||
<a href="javascript:;">3rd menu item</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div id="header-profile-right">
|
||||
@@ -93,9 +106,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Button, Col, Row, Space, Dropdown, Avatar, Menu, Divider, Tag } from 'ant-design-vue';
|
||||
import Input from 'ant-design-vue/es/input';
|
||||
const router = useRouter()
|
||||
// 定义搜索框绑定的变量
|
||||
const searchValue = ref('')
|
||||
@@ -105,19 +119,23 @@ const showHistory = ref(false)
|
||||
const isClearingHistory = ref(false)
|
||||
// 控制搜索框是否显示
|
||||
const booleanSearch = ref(true)
|
||||
// 路由事件
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.name === 'search' && to.query.keyword) {
|
||||
searchValue.value = to.query.keyword as string
|
||||
// 控制Header是否显示
|
||||
const booleanHeader = ref(true)
|
||||
// 监听路由变化 当路由变化时 更新搜索框的显示状态
|
||||
watch(() => router.currentRoute.value.name, (newName) => {
|
||||
// 如果是搜索页面 则更新查询参数
|
||||
if (newName === 'search') {
|
||||
searchValue.value = router.currentRoute.value.query.keyword as string
|
||||
}
|
||||
console.log(to.name)
|
||||
// 如果在商品页面 隐藏搜索框
|
||||
if (to.name === 'productDetail' || to.name === 'chat') {
|
||||
booleanSearch.value = false
|
||||
} else {
|
||||
// 如果是商品页面 则显示搜索框
|
||||
if (newName === 'productDetail' || newName === 'chat') {
|
||||
booleanSearch.value = true
|
||||
}
|
||||
next()
|
||||
// 如果在登录页面 则隐藏搜索框
|
||||
if (newName === 'login') {
|
||||
booleanHeader.value = false
|
||||
}
|
||||
console.log(newName)
|
||||
})
|
||||
// 定义搜索框的搜索事件
|
||||
const onSearch = (value: string) => {
|
||||
@@ -186,7 +204,7 @@ const setSearchType = (type: string, event: MouseEvent) => {
|
||||
const currentActiveItem = document.querySelector('.search-type-item.active')
|
||||
// 保存点击的元素引用
|
||||
const clickedTarget = event.currentTarget as HTMLElement
|
||||
|
||||
|
||||
// 为当前活跃的元素添加消失动画
|
||||
if (currentActiveItem && currentActiveItem !== clickedTarget) {
|
||||
if (currentSearchType.value === '宝贝' && type === '店铺') {
|
||||
@@ -270,9 +288,7 @@ const rightMenu = [
|
||||
<style scoped>
|
||||
#header {
|
||||
width: 100%;
|
||||
background-color: var(--card-bg);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
@@ -310,8 +326,6 @@ const rightMenu = [
|
||||
border-radius: var(--border-radius-3xl);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-transform);
|
||||
background-color: var(--card-bg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.user-info-container:hover {
|
||||
@@ -399,6 +413,35 @@ const rightMenu = [
|
||||
transform: rotate(180deg) scale(1.1);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
/* 用户信息菜单样式 */
|
||||
.user-info-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--spacing-md);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
transition: all var(--transition-transform);
|
||||
background-color: var(--card-bg);
|
||||
}
|
||||
|
||||
.user-info-menu-item {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--border-radius-2xl);
|
||||
transition: all var(--transition-transform);
|
||||
}
|
||||
|
||||
.user-info-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.user-info-menu-item:hover {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 右侧功能菜单样式 */
|
||||
.header-more-btn {
|
||||
@@ -715,6 +758,7 @@ const rightMenu = [
|
||||
.search-type-item.animate-left-out::after {
|
||||
animation: slideOutLeft 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 搜索类型选择器从左往右动画 */
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
@@ -727,6 +771,7 @@ const rightMenu = [
|
||||
left: -10%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索类型选择器从右往左动画 */
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
@@ -750,20 +795,20 @@ const rightMenu = [
|
||||
|
||||
to {
|
||||
width: 0;
|
||||
left: 100%;
|
||||
left: 200%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 从右往左消失动画 */
|
||||
@keyframes slideOutLeft {
|
||||
from {
|
||||
width: 10%;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
right: -10%;
|
||||
}
|
||||
|
||||
to {
|
||||
width: 0%;
|
||||
right: 100%;
|
||||
right: -200%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ const adList = [
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #42b983;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
#main-header {
|
||||
@@ -104,25 +104,25 @@ h1 {
|
||||
#main-header .ant-btn {
|
||||
width: 120px;
|
||||
height: 45px;
|
||||
font-size: 14px;
|
||||
font-size: var(--font-size-sm);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--border-radius-md);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#main-header .ant-btn-primary {
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
transition: all 0.3s ease;
|
||||
background-color: var(--secondary-color);
|
||||
border-color: var(--secondary-color);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
#main-header .ant-btn-primary:hover {
|
||||
background-color: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
background-color: var(--secondary-hover);
|
||||
border-color: var(--secondary-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
@@ -228,16 +228,16 @@ h1 {
|
||||
.category-detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) 0;
|
||||
border-bottom: 1px solid var(--bg-light);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.category-detail-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
padding-left: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--bg-light);
|
||||
padding-left: var(--spacing-xs);
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.category-detail-item:last-child {
|
||||
@@ -245,16 +245,16 @@ h1 {
|
||||
}
|
||||
|
||||
.category-detail-item a {
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: all var(--transition-transform);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-detail-item a:hover {
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
@@ -265,8 +265,8 @@ h1 {
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background-color: #ff5000;
|
||||
transition: width 0.3s ease;
|
||||
background-color: var(--primary-color);
|
||||
transition: width var(--transition-normal);
|
||||
}
|
||||
|
||||
.category-detail-item a:hover::after {
|
||||
@@ -274,98 +274,98 @@ h1 {
|
||||
}
|
||||
|
||||
.category-detail-item span {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
transition: color 0.3s ease;
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-size-sm);
|
||||
transition: color var(--transition-normal);
|
||||
}
|
||||
|
||||
.main-content-col-top .iconfont {
|
||||
font-size: 22px;
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
flex-shrink: 0;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: all var(--transition-transform);
|
||||
}
|
||||
|
||||
.main-content-col-top li:hover .iconfont {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.main-content-col-top a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: all var(--transition-transform);
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.main-content-col-top a:hover {
|
||||
color: #ff5000;
|
||||
background-color: rgba(255, 80, 0, 0.1);
|
||||
color: var(--primary-color);
|
||||
background-color: var(--primary-light);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.main-content-col-top span {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
transition: color 0.3s ease;
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-size-sm);
|
||||
transition: color var(--transition-normal);
|
||||
}
|
||||
|
||||
.main-content-col-top li:hover span {
|
||||
color: #ff5000;
|
||||
color: var(--primary-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 主内容区域样式 */
|
||||
#main-content {
|
||||
margin-top: 20px;
|
||||
gap: 32px;
|
||||
margin-top: var(--spacing-xl);
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.main-content-col-center{
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: var(--card-bg);
|
||||
border-radius: var(--border-radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.main-content-col-center h2{
|
||||
font-size: 18px;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 6px;
|
||||
padding: var(--spacing-md);
|
||||
background-color: var(--bg-light);
|
||||
border-radius: var(--border-radius-sm);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main-content-ad,
|
||||
#main-content-hot-goods {
|
||||
padding: 0 20px;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
#main-content-ad .ant-col,
|
||||
#main-content-hot-goods .ant-col {
|
||||
background-color: #fafafa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
background-color: var(--bg-light);
|
||||
border-radius: var(--border-radius-md);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
#main-content-ad .ant-col:hover,
|
||||
#main-content-hot-goods .ant-col:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
/* 轮播广告样式 */
|
||||
#carousel-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--border-radius-md);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.carousel-item {
|
||||
@@ -379,7 +379,7 @@ h1 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s ease;
|
||||
transition: transform var(--transition-slow);
|
||||
}
|
||||
|
||||
.carousel-item:hover .carousel-image {
|
||||
@@ -391,22 +391,22 @@ h1 {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
padding: var(--spacing-xl);
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||
color: #ffffff;
|
||||
font-size: 24px;
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 600;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.ant-carousel .slick-dots {
|
||||
bottom: 20px;
|
||||
bottom: var(--spacing-xl);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.ant-carousel .slick-dots li button {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
border-radius: var(--border-radius-full);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="tab-container">
|
||||
<div class="tab-header">
|
||||
<a-anchor direction="horizontal" :items="tabs" class="tab-item" />
|
||||
<a-anchor direction="horizontal" :bounds="150" :getCurrentAnchor="activeTab" :setAnchor="setActiveTab" :items="tabs" class="tab-item" />
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<!-- 评论 -->
|
||||
@@ -52,7 +52,74 @@
|
||||
<div class="reviews-content">
|
||||
<!-- 评论内容 -->
|
||||
<h3>用户评论</h3>
|
||||
<!-- 更多评论... -->
|
||||
<!-- 评论统计 -->
|
||||
<div class="reviews-stats">
|
||||
<div class="reviews-rating">
|
||||
<span class="rating-label">综合评分:</span>
|
||||
<span class="rating-value">4.8</span>
|
||||
<div class="rating-stars">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star-half"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reviews-count">共 128 条评论</div>
|
||||
</div>
|
||||
<!-- 评论列表 -->
|
||||
<div class="reviews-list">
|
||||
<div class="review-item">
|
||||
<div class="review-header">
|
||||
<span class="reviewer-name">用户123</span>
|
||||
<div class="review-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
</div>
|
||||
<span class="review-time">2026-01-15</span>
|
||||
</div>
|
||||
<div class="review-content">投影仪质量很好,画面清晰,操作简单,物流也很快,非常满意的一次购物!</div>
|
||||
<div class="review-images">
|
||||
<!-- <img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=projector%20in%20office%20room&size=800x600" alt="评论图片" width="100">
|
||||
<img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=projector%20screen%20display&size=800x600" alt="评论图片" width="100"> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="review-item">
|
||||
<div class="review-header">
|
||||
<span class="reviewer-name">科技达人</span>
|
||||
<div class="review-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
</div>
|
||||
<span class="review-time">2026-01-10</span>
|
||||
</div>
|
||||
<div class="review-content">亮度很高,白天也能看清楚,音响效果也不错,接口丰富,值得购买。</div>
|
||||
</div>
|
||||
<div class="review-item">
|
||||
<div class="review-header">
|
||||
<span class="reviewer-name">办公室采购</span>
|
||||
<div class="review-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star-o"></i>
|
||||
</div>
|
||||
<span class="review-time">2026-01-05</span>
|
||||
</div>
|
||||
<div class="review-content">性价比很高,适合办公室使用,客服态度也很好,有问必答。</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 查看更多评论 -->
|
||||
<div class="reviews-more">
|
||||
<a href="#" class="btn btn-secondary">查看更多评论</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文图详情 -->
|
||||
@@ -61,6 +128,30 @@
|
||||
文图详情
|
||||
<a href="#description"></a>
|
||||
</h2>
|
||||
<div class="description-content">
|
||||
<div class="product-intro">
|
||||
<h3>产品介绍</h3>
|
||||
<p>BenQ 明基投影仪采用先进的DLP投影技术,提供清晰锐利的画面效果。无论是商务会议、教育培训还是家庭娱乐,都能满足您的需求。</p>
|
||||
</div>
|
||||
<div class="product-features">
|
||||
<h3>产品特点</h3>
|
||||
<ul>
|
||||
<li>1080P全高清分辨率,画质清晰细腻</li>
|
||||
<li>3500ANSI流明高亮度,白天也能清晰观看</li>
|
||||
<li>智能系统,支持无线投屏</li>
|
||||
<li>多种接口,满足不同设备连接需求</li>
|
||||
<li>长寿命灯泡,节能环保</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="product-images">
|
||||
<h3>产品展示</h3>
|
||||
<div class="image-gallery">
|
||||
<!-- <img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=modern%20projector%20front%20view&size=1024x768" alt="投影仪正面" class="gallery-image">
|
||||
<img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=projector%20rear%20view%20with%20ports&size=1024x768" alt="投影仪背面" class="gallery-image">
|
||||
<img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=projector%20in%20meeting%20room&size=1024x768" alt="会议室使用场景" class="gallery-image"> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 参数信息 -->
|
||||
<div id="specs">
|
||||
@@ -68,6 +159,62 @@
|
||||
参数信息
|
||||
<a href="#specs"></a>
|
||||
</h2>
|
||||
<div class="specs-content">
|
||||
<table class="specs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数类别</th>
|
||||
<th>参数值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>投影技术</td>
|
||||
<td>DLP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>分辨率</td>
|
||||
<td>1920 x 1080 (1080P)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>亮度</td>
|
||||
<td>3500 ANSI流明</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>对比度</td>
|
||||
<td>15000:1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>投影尺寸</td>
|
||||
<td>30-300英寸</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>投影距离</td>
|
||||
<td>1.0-10.0米</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>灯泡寿命</td>
|
||||
<td>正常模式:4000小时,经济模式:6000小时</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>接口</td>
|
||||
<td>HDMI x 2, VGA x 1, USB x 2, Audio x 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>功耗</td>
|
||||
<td>正常模式:280W,经济模式:200W</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>尺寸</td>
|
||||
<td>310 x 220 x 100 mm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>重量</td>
|
||||
<td>2.8 kg</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 本店推荐 -->
|
||||
<div id="recommendations">
|
||||
@@ -75,12 +222,84 @@
|
||||
本店推荐
|
||||
<a href="#recommendations"></a>
|
||||
</h2>
|
||||
<div class="recommendations-content">
|
||||
<div class="recommended-products">
|
||||
<div class="recommended-product">
|
||||
<div class="product-image">
|
||||
<!-- <img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=mini%20portable%20projector&size=800x800" alt="迷你投影仪"> -->
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<h4 class="product-name">BenQ 明基 迷你便携投影仪</h4>
|
||||
<div class="product-price">¥2999</div>
|
||||
<div class="product-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star-half"></i>
|
||||
<span>(45)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommended-product">
|
||||
<div class="product-image">
|
||||
<!-- <img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=4k%20ultra%20hd%20projector&size=800x800" alt="4K投影仪"> -->
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<h4 class="product-name">BenQ 明基 4K超高清投影仪</h4>
|
||||
<div class="product-price">¥8999</div>
|
||||
<div class="product-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<span>(28)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommended-product">
|
||||
<div class="product-image">
|
||||
<!-- <img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=projector%20screen%20white&size=800x800" alt="投影幕布"> -->
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<h4 class="product-name">100英寸投影幕布</h4>
|
||||
<div class="product-price">¥599</div>
|
||||
<div class="product-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star-o"></i>
|
||||
<span>(67)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommended-product">
|
||||
<div class="product-image">
|
||||
<!-- <img src="https://neeko-copilot.bytedance.net/api/text2image?prompt=projector%20mount%20ceiling&size=800x800" alt="投影仪吊架"> -->
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<h4 class="product-name">投影仪吊架</h4>
|
||||
<div class="product-price">¥199</div>
|
||||
<div class="product-rating">
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star"></i>
|
||||
<i class="iconfont icon-star-half"></i>
|
||||
<span>(32)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<!-- 右侧内容 -->
|
||||
<div class="right-panel" >
|
||||
<div class="product-info">
|
||||
<h1 class="product-title">BenQ 明基投影仪 商务办公会议培训用 1080P高清智能投影机</h1>
|
||||
<div class="price-section">
|
||||
@@ -99,7 +318,7 @@
|
||||
<div class="model-selection">
|
||||
<div class="selection-container">
|
||||
<h3 class="selection-title">选择型号</h3>
|
||||
<div class="view-switch" @click="toggleView">
|
||||
<div class="view-switch" @click="toggleView">
|
||||
<p>{{ isSmallView ? '切换为大视图' : '切换为小视图' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,14 +326,14 @@
|
||||
<div v-if="isSmallView" class="model-options-small">
|
||||
<div v-for="model in models" :key="model.id" class="model-option"
|
||||
:class="{ active: selectedModel === model.id }" @click="selectModel(model.id)">
|
||||
<div class="model-small-item">
|
||||
<div class="model-small-item">
|
||||
<img :src="model.img" alt="{{ model.name }}">
|
||||
<span> {{ model.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图(大)文 图上文下-->
|
||||
<div v-if="!isSmallView" class="model-options-big">
|
||||
<div v-if="!isSmallView" class="model-options-big">
|
||||
<div v-for="model in models" :key="model.id" class="model-option"
|
||||
:class="{ active: selectedModel === model.id }" @click="selectModel(model.id)">
|
||||
<!-- 图片 -->
|
||||
@@ -124,6 +343,13 @@
|
||||
<div class="model-name">{{ model.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 数量 -->
|
||||
<div class="quantity-selector">
|
||||
<div class="quantity-label">数量</div>
|
||||
<div class="quantity-input">
|
||||
<a-input-number v-model:value="formOrderProduct.quantity" :min="1" :max="10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,13 +365,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettlementLite ref="settlementLite" v-model:visible="visible" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import SettlementLite from '../../Component/business/SettlementLite.vue'
|
||||
import type { OrderProduct } from '@/Util/Type';
|
||||
const router = useRouter()
|
||||
const productId = ref(router.currentRoute.value.params.productId)
|
||||
const visible = ref(false)
|
||||
|
||||
const currentIndex = ref(0)
|
||||
const images = ref([
|
||||
@@ -161,7 +390,11 @@ const currentImage = computed(() => images.value[currentIndex.value])
|
||||
const selectImage = (index: number) => {
|
||||
currentIndex.value = index
|
||||
}
|
||||
|
||||
// 提交表单 商品基础信息 型号 数量
|
||||
const formOrderProduct = ref<OrderProduct>({
|
||||
productId: 0,
|
||||
quantity: 1,
|
||||
})
|
||||
const selectedModel = ref('x500')
|
||||
const models = ref([
|
||||
{ id: 'x500', name: 'X500', price: '999', originalPrice: '1999', img: '/0.png' },
|
||||
@@ -175,6 +408,9 @@ const selectModel = (id: string) => {
|
||||
const selectedModelInfo = computed(() => models.value.find(model => model.id === selectedModel.value))
|
||||
|
||||
const activeTab = ref('reviews')
|
||||
const setActiveTab = (tab: string) => {
|
||||
activeTab.value = tab
|
||||
}
|
||||
const tabs = ref([
|
||||
{
|
||||
key: 'reviews',
|
||||
@@ -209,24 +445,37 @@ const collectProduct = () => {
|
||||
}
|
||||
|
||||
const addToCart = () => {
|
||||
// visible.value = true
|
||||
console.log('加入购物车')
|
||||
}
|
||||
|
||||
const buyProduct = () => {
|
||||
visible.value = true
|
||||
console.log('领券购买')
|
||||
}
|
||||
// 屏幕滚动 超过300px 右侧内容根据屏幕固定位置
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollTop = window.scrollY;
|
||||
const rightPanel = document.querySelector('.right-panel') as HTMLElement;
|
||||
console.log("scrollTop:", scrollTop)
|
||||
console.log("rightPanel:", rightPanel)
|
||||
|
||||
if (scrollTop > 150) {
|
||||
rightPanel.classList.add('fixed');
|
||||
} else {
|
||||
rightPanel.classList.remove('fixed');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
#product-detail {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.product-detail-container {
|
||||
display: flex;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
@@ -235,7 +484,7 @@ const buyProduct = () => {
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 50%;
|
||||
width: 761px;
|
||||
padding: 25px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
overflow-y: auto;
|
||||
@@ -270,7 +519,7 @@ const buyProduct = () => {
|
||||
|
||||
.shop-info-detail {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
border: 1px solid #f0f0f0;
|
||||
@@ -297,6 +546,7 @@ const buyProduct = () => {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.shop-btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -346,6 +596,7 @@ const buyProduct = () => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -354,7 +605,7 @@ const buyProduct = () => {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
max-height: 500px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -388,12 +639,15 @@ const buyProduct = () => {
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
padding: 35px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
}
|
||||
|
||||
.right-panel.fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 24px;
|
||||
}
|
||||
.product-info {
|
||||
width: 765px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
@@ -486,6 +740,7 @@ const buyProduct = () => {
|
||||
.selection-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 切换视图 */
|
||||
.view-switch {
|
||||
/* 靠右对齐 */
|
||||
@@ -500,7 +755,9 @@ const buyProduct = () => {
|
||||
.view-switch:hover {
|
||||
color: #ff5000;
|
||||
}
|
||||
.model-options-small, .model-options-big {
|
||||
|
||||
.model-options-small,
|
||||
.model-options-big {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
@@ -530,21 +787,25 @@ const buyProduct = () => {
|
||||
box-shadow: 0 4px 16px rgba(255, 80, 0, 0.25);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.model-image {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.model-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.model-small-item {
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
@@ -552,6 +813,7 @@ const buyProduct = () => {
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
||||
}
|
||||
|
||||
.model-small-item img {
|
||||
margin: 5px;
|
||||
height: 30px;
|
||||
@@ -578,48 +840,52 @@ const buyProduct = () => {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.action-buttons {
|
||||
width: 400px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-cart,
|
||||
.btn-buy {
|
||||
text-align: center;
|
||||
width: 200px;
|
||||
line-height: 48px;
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn-cart {
|
||||
width: 100px;
|
||||
border-radius: 24px 0 0 24px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fff5f5 100%);
|
||||
color: #ff5000;
|
||||
border: 2px solid #ff5000;
|
||||
}
|
||||
|
||||
.btn-cart:hover {
|
||||
.btn-buy {
|
||||
width: 300px;
|
||||
border-radius: 0 24px 24px 0;
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
||||
color: #ffffff;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 80, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-buy {
|
||||
.btn-cart:hover {
|
||||
width: 300px;
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4px 12px rgba(255, 80, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-cart:hover .btn-buy {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.btn-buy:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(255, 80, 0, 0.4);
|
||||
}
|
||||
|
||||
@@ -671,38 +937,54 @@ const buyProduct = () => {
|
||||
.tab-header {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.tab-header .ant-anchor-link-title a {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 18px 35px;
|
||||
font-size: 16px;
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
color: #ff5000;
|
||||
background-color: #fff5f5;
|
||||
border-bottom: 3px solid transparent;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #ff5000;
|
||||
font-weight: 600;
|
||||
background-color: #fff5f5;
|
||||
box-shadow: 0 2px 12px rgba(255, 80, 0, 0.15);
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #ff5000 0%, #ff6b00 100%);
|
||||
box-shadow: 0 2px 8px rgba(255, 80, 0, 0.3);
|
||||
box-shadow: 0 2px 12px rgba(255, 80, 0, 0.4);
|
||||
border-radius: 3px 3px 0 0;
|
||||
animation: tabSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes tabSlideIn {
|
||||
from {
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
@@ -748,6 +1030,313 @@ const buyProduct = () => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 评论部分样式 */
|
||||
.reviews-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.reviews-rating {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.rating-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.rating-value {
|
||||
font-size: 20px;
|
||||
color: #ff5000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rating-stars {
|
||||
color: #ffc107;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.reviews-count {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.reviews-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.review-item {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.review-item:hover {
|
||||
background-color: #fafafa;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.review-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.reviewer-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.review-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.review-content {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.review-images {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.review-images img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.review-images img:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.reviews-more {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
border: 1px solid #ff5000;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
color: #ff5000;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
||||
color: #ffffff;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 80, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 文图详情部分样式 */
|
||||
.description-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.product-intro h3,
|
||||
.product-features h3,
|
||||
.product-images h3 {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-weight: 600;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.product-intro p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.product-features ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.product-features li {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.product-features li::before {
|
||||
content: '•';
|
||||
color: #ff5000;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.gallery-image:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 参数信息部分样式 */
|
||||
.specs-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.specs-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.specs-table th,
|
||||
.specs-table td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.specs-table th {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.specs-table td {
|
||||
color: #666;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.specs-table tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 本店推荐部分样式 */
|
||||
.recommendations-content {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.recommended-products {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.recommended-product {
|
||||
background-color: #fafafa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recommended-product:hover {
|
||||
background-color: #ffffff;
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.recommended-product .product-image {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.recommended-product .product-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.recommended-product:hover .product-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.recommended-product .product-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.recommended-product .product-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.recommended-product .product-price {
|
||||
font-size: 16px;
|
||||
color: #ff5000;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.recommended-product .product-rating {
|
||||
font-size: 12px;
|
||||
color: #ffc107;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.recommended-product .product-rating span {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.params-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="product-modal-container">
|
||||
<div class="product-modal-right">
|
||||
<div class="product-modal-img">
|
||||
<img :src="currentProduct.img" alt="产品图片" class="product-modal-image">
|
||||
<img :src="currentProduct.image" alt="产品图片" class="product-modal-image">
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-modal-left">
|
||||
@@ -21,7 +21,7 @@
|
||||
<div v-for="item in currentItems" :key="item.id" class="model-item"
|
||||
:class="{ active: currentProduct.id === item.id }" @click="selectProduct(item)">
|
||||
<div class="model-img">
|
||||
<img :src="item.img" alt="型号图片" class="model-image">
|
||||
<img :src="item.image" alt="型号图片" class="model-image">
|
||||
</div>
|
||||
<div class="model-name">{{ item.model }}</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user