feat(路由): 添加用户和聊天页面路由

style(页脚): 更新版权信息和样式,添加链接和响应式设计

style(全局样式): 引入CSS变量,统一设计系统

refactor(主页面): 使用CSS变量优化样式,提升可维护性

style(购物车): 应用CSS变量,优化交互效果和动画

feat(头部导航): 增强用户信息展示和搜索类型切换动画

feat(聊天页面): 新增聊天功能界面,支持用户列表和消息交互

feat(用户信息): 重构用户信息页面,添加订单状态和功能菜单
This commit is contained in:
qingfeng1121
2026-01-12 13:56:17 +08:00
parent 0c07d33bf9
commit c287650fbb
9 changed files with 3685 additions and 220 deletions

View File

@@ -5,8 +5,14 @@
<!-- 用户信息悬浮菜单 -->
<Dropdown :menu="{ items: userMenu }" trigger="hover">
<div class="user-info-container">
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=user123" />
<span class="user-name">用户名</span>
<div class="user-avatar-wrapper">
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=user123" class="user-avatar" />
<span class="user-online-indicator"></span>
</div>
<div class="user-info-text">
<span class="user-name">用户名</span>
<span class="user-level">Lv.3</span>
</div>
<span class="user-arrow"></span>
</div>
</Dropdown>
@@ -14,11 +20,15 @@
<div id="header-profile-right">
<!-- 右侧功能菜单 -->
<Dropdown :menu="{ items: rightMenu }" trigger="hover">
<Button type="text">更多</Button>
<Button type="text" class="header-more-btn">更多 <i class="iconfont icon-zhankaishouqi"></i></Button>
</Dropdown>
<a href="/">首页</a>
<Button type="primary" @click="router.push('/cart')">购物车</Button>
<Button type="primary" @click="router.push('/order')">订单</Button>
<a href="/" class="header-nav-link">首页</a>
<Button type="primary" @click="router.push('/cart')" class="header-cart-btn">
<i class="iconfont icon-gouwuche"></i> 购物车
</Button>
<Button type="primary" @click="router.push('/order')" class="header-order-btn">
<i class="iconfont icon-dingdan"></i> 订单
</Button>
</div>
</div>
<Row id="header-nav-row" v-if="booleanSearch">
@@ -31,30 +41,23 @@
<!-- 搜索类型选择 -->
<div class="search-type-selector">
<ul>
<li class="search-type-item active" @click="setSearchType('宝贝')">宝贝</li>
<li class="search-type-item" @click="setSearchType('店铺')">店铺</li>
<li class="search-type-item active" @click="setSearchType('宝贝', $event)">宝贝</li>
<li class="search-type-item" @click="setSearchType('店铺', $event)">店铺</li>
</ul>
</div>
<!-- 搜索输入区域 -->
<div class="search-input-wrapper">
<div class="search-input-prefix">
<i class="iconfont icon-sousuo prefix-icon"></i>
</div>
<Input
v-model:value="searchValue"
placeholder="请输入搜索关键词"
style="width: 100%"
@search="onSearch"
@focus="onInputFocus"
@blur="onInputBlur"
class="custom-search-input"
/>
<Input v-model:value="searchValue" placeholder="请输入搜索关键词" style="width: 100%" @search="onSearch"
@focus="onInputFocus" @blur="onInputBlur" class="custom-search-input" />
<div class="search-input-suffix" v-if="searchValue">
<i class="iconfont icon-guanbi" @click="clearSearch"></i>
</div>
</div>
<!-- 搜索按钮 -->
<div class="search-button-container" @onclick="onSearch">
<span class="search-button-text">搜索</span>
@@ -100,7 +103,7 @@ const searchValue = ref('')
const showHistory = ref(false)
// 标志:是否正在清空历史记录
const isClearingHistory = ref(false)
// 标志:是否在搜索界面
// 控制搜索框是否显示
const booleanSearch = ref(true)
// 路由事件
router.beforeEach((to, from, next) => {
@@ -109,7 +112,7 @@ router.beforeEach((to, from, next) => {
}
console.log(to.name)
// 如果在商品页面 隐藏搜索框
if (to.name === 'productDetail') {
if (to.name === 'productDetail' || to.name === 'chat') {
booleanSearch.value = false
} else {
booleanSearch.value = true
@@ -173,15 +176,56 @@ const onInputBlur = () => {
}, 200)
}
// 当前搜索类型
const currentSearchType = ref('宝贝')
// 设置搜索类型
const setSearchType = (type: string) => {
const setSearchType = (type: string, event: MouseEvent) => {
const typeItems = document.querySelectorAll('.search-type-item')
typeItems.forEach(item => {
item.classList.remove('active')
})
// 添加active类到点击的元素
// event?.currentTarget?.classList.add('active')
console.log('搜索类型:', type)
// 找到当前活跃的元素
const currentActiveItem = document.querySelector('.search-type-item.active')
// 保存点击的元素引用
const clickedTarget = event.currentTarget as HTMLElement
// 为当前活跃的元素添加消失动画
if (currentActiveItem && currentActiveItem !== clickedTarget) {
if (currentSearchType.value === '宝贝' && type === '店铺') {
// 宝贝 -> 店铺:宝贝元素从左往右消失
currentActiveItem.classList.add('animate-right-out')
} else if (currentSearchType.value === '店铺' && type === '宝贝') {
// 店铺 -> 宝贝:店铺元素从右往左消失
currentActiveItem.classList.add('animate-left-out')
}
// 等待消失动画完成后再继续
setTimeout(() => {
// 移除所有元素的类
typeItems.forEach(item => {
item.classList.remove('active', 'animate-left', 'animate-right', 'animate-left-out', 'animate-right-out')
})
// 添加active类到点击的元素
if (clickedTarget) {
// 判断切换方向
if (currentSearchType.value === '宝贝' && type === '店铺') {
// 宝贝 -> 店铺:从左往右出现
clickedTarget.classList.add('active', 'animate-right')
} else if (currentSearchType.value === '店铺' && type === '宝贝') {
// 店铺 -> 宝贝:从右往左出现
clickedTarget.classList.add('active', 'animate-left')
} else {
// 默认动画
clickedTarget.classList.add('active')
}
currentSearchType.value = type
}
console.log('搜索类型:', type)
}, 300)
} else {
// 如果点击的是当前活跃的元素,直接返回
return
}
}
// 清除搜索输入
@@ -226,8 +270,8 @@ const rightMenu = [
<style scoped>
#header {
width: 100%;
background-color: #ffffff;
padding: 10px 20px;
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;
@@ -242,7 +286,6 @@ const rightMenu = [
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
}
#header-profile-left {
@@ -255,38 +298,290 @@ const rightMenu = [
display: flex;
justify-content: flex-end;
align-items: center;
gap: 10px;
gap: var(--spacing-md);
}
/* 用户信息容器样式 */
.user-info-container {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 20px;
gap: var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius-3xl);
cursor: pointer;
transition: all 0.3s ease;
transition: all var(--transition-transform);
background-color: var(--card-bg);
box-shadow: var(--shadow-sm);
}
.user-info-container:hover {
background-color: #f0f2f5;
background-color: var(--bg-light);
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
/* 用户头像区域 */
.user-avatar-wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.user-avatar {
border: 2px solid #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.user-info-container:hover .user-avatar {
transform: scale(1.05);
}
.user-online-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
background-color: var(--success-color);
border: 2px solid var(--card-bg);
border-radius: var(--border-radius-full);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
}
}
/* 用户信息文本区域 */
.user-info-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.user-name {
font-size: 14px;
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-primary);
line-height: 1.2;
}
.user-level {
font-size: var(--font-size-xs);
font-weight: 500;
color: #333;
color: var(--warning-color);
background-color: #fff7e6;
padding: 1px 6px;
border-radius: var(--border-radius-xl);
align-self: flex-start;
}
.user-arrow {
font-size: 12px;
color: #999;
transition: transform 0.3s ease;
font-size: var(--font-size-xs);
color: var(--text-tertiary);
transition: transform var(--transition-transform);
}
.user-info-container:hover .user-arrow {
transform: rotate(180deg);
transform: rotate(180deg) scale(1.1);
color: var(--text-secondary);
}
/* 右侧功能菜单样式 */
.header-more-btn {
display: flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--border-radius-2xl);
transition: all var(--transition-transform);
font-size: var(--font-size-sm);
}
.header-more-btn:hover {
background-color: var(--bg-color);
color: var(--text-primary);
}
.header-nav-link {
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--text-secondary);
text-decoration: none;
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--border-radius-2xl);
transition: all var(--transition-transform);
position: relative;
}
.header-nav-link:hover {
color: var(--primary-color);
background-color: var(--bg-primary-lightest);
}
.header-nav-link::after {
content: '';
position: absolute;
bottom: 2px;
left: 50%;
width: 0;
height: 2px;
background-color: var(--primary-color);
border-radius: 1px;
transition: all var(--transition-transform);
}
.header-nav-link:hover::after {
width: 80%;
left: 10%;
}
/* 购物车和订单按钮样式 */
.header-cart-btn,
.header-order-btn {
display: flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius-3xl);
font-size: var(--font-size-sm);
font-weight: 500;
transition: all var(--transition-transform);
position: relative;
overflow: hidden;
}
.header-cart-btn {
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
border: none;
}
.header-order-btn {
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-hover));
border: none;
}
.header-cart-btn:hover,
.header-order-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.header-cart-btn:active,
.header-order-btn:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header-cart-btn::before,
.header-order-btn::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transform: rotate(45deg);
animation: shine 3s ease-in-out infinite;
}
@keyframes shine {
0% {
transform: rotate(45deg) translateX(-100%) translateY(-100%);
opacity: 0;
}
50% {
opacity: 0.8;
}
100% {
transform: rotate(45deg) translateX(100%) translateY(100%);
opacity: 0;
}
}
/* 响应式设计 */
@media (max-width: 768px) {
#header-profile {
padding: 8px 0;
}
.user-info-container {
gap: 8px;
padding: 8px 12px;
}
.user-name {
font-size: 13px;
}
.user-level {
font-size: 11px;
padding: 1px 4px;
}
#header-profile-right {
gap: 8px;
}
.header-nav-link {
font-size: 13px;
padding: 4px 8px;
}
.header-cart-btn,
.header-order-btn {
padding: 6px 12px;
font-size: 13px;
}
.header-cart-btn i,
.header-order-btn i,
.header-more-btn i {
font-size: 14px;
}
}
@media (max-width: 576px) {
.user-info-text {
display: none;
}
.user-info-container {
padding: 6px;
border-radius: 50%;
}
.header-nav-link {
font-size: 12px;
}
.header-cart-btn span,
.header-order-btn span {
display: none;
}
.header-cart-btn,
.header-order-btn {
padding: 8px;
border-radius: 50%;
min-width: unset;
}
}
/* Logo样式 */
@@ -311,7 +606,7 @@ const rightMenu = [
align-items: flex-start;
position: relative;
width: 100%;
max-width: 700px;
max-width: 800px;
margin: 0 auto;
}
@@ -394,22 +689,81 @@ const rightMenu = [
content: '';
position: absolute;
bottom: 8px;
left: 0;
width: 100%;
left: -10%;
width: 120%;
height: 2px;
background-color: #ff5000;
border-radius: 1px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
/* 从左往右动画 */
.search-type-item.animate-right::after {
animation: slideInRight 0.3s ease-out;
}
/* 从右往左动画 */
.search-type-item.animate-left::after {
animation: slideInLeft 0.3s ease-out;
}
/* 从左往右消失动画 */
.search-type-item.animate-right-out::after {
animation: slideOutRight 0.3s ease-out;
}
/* 从右往左消失动画 */
.search-type-item.animate-left-out::after {
animation: slideOutLeft 0.3s ease-out;
}
/* 搜索类型选择器从左往右动画 */
@keyframes slideInRight {
from {
width: 0;
left: 50%;
left: -10%;
}
to {
width: 100%;
left: 0;
width: 120%;
left: -10%;
}
}
/* 搜索类型选择器从右往左动画 */
@keyframes slideInLeft {
from {
width: 0;
left: 120%;
}
to {
width: 120%;
left: -10%;
}
}
/* 从左往右消失动画 */
@keyframes slideOutRight {
from {
width: 120%;
left: -10%;
}
to {
width: 0;
left: 100%;
}
}
/* 从右往左消失动画 */
@keyframes slideOutLeft {
from {
width: 10%;
right: 0;
}
to {
width: 0%;
right: 100%;
}
}
@@ -442,15 +796,17 @@ const rightMenu = [
font-size: 16px;
min-height: 52px;
height: 52px;
padding: 0 50px 0 48px;
padding: 0 25px 0 15px;
border: none;
box-shadow: none;
background-color: transparent;
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
font-weight: 400;
letter-spacing: 0.5px;
box-sizing: border-box; /* 关键! */
outline: none; /* 避免默认焦点轮廓 */
box-sizing: border-box;
/* 关键! */
outline: none;
/* 避免默认焦点轮廓 */
}
/* 可选:焦点状态 */
@@ -499,6 +855,7 @@ const rightMenu = [
opacity: 0;
transform: translateY(-50%) scale(0.8);
}
to {
opacity: 1;
transform: translateY(-50%) scale(1);
@@ -517,8 +874,7 @@ const rightMenu = [
/* 搜索按钮 */
.search-button-container {
margin-right: 1px;
padding: 15.5px;
padding: 16.5px 30px;
height: 100%;
background: linear-gradient(135deg, #ff5000, #ff8c00);
color: #ffffff;
@@ -597,42 +953,42 @@ const rightMenu = [
.search-type-selector {
padding: 0 12px;
}
.search-type-selector ul {
gap: 12px;
}
.search-type-item {
padding: 12px 0;
font-size: 13px;
}
.search-input-wrapper {
padding: 0 12px;
}
.custom-search-input {
font-size: 14px !important;
height: 48px !important;
padding: 0 40px 0 40px !important;
}
.search-input-prefix {
font-size: 16px;
}
.search-button-container {
padding: 0 24px;
}
.search-button-text {
font-size: 14px;
}
.search-button-icon {
font-size: 14px;
}
.search-button-container:hover .search-button-icon {
transform: scale(1.05) rotate(10deg);
}
@@ -642,36 +998,36 @@ const rightMenu = [
.search-type-selector {
padding: 0 10px;
}
.search-type-selector ul {
gap: 8px;
}
.search-type-item {
font-size: 12px;
}
.search-input-wrapper {
padding: 0 10px;
}
.custom-search-input {
font-size: 13px !important;
padding: 0 35px 0 35px !important;
}
.search-input-prefix {
font-size: 14px;
}
.search-button-container {
padding: 0 20px;
}
.search-button-text {
font-size: 13px;
}
.search-button-icon {
display: none;
}