界面美化
This commit is contained in:
@@ -11,9 +11,139 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 花店主题色彩系统 */
|
||||
:root {
|
||||
--primary-color: #E8B4B8;
|
||||
--primary-dark: #D4959A;
|
||||
--secondary-color: #4A7C59;
|
||||
--secondary-light: #6B9B7A;
|
||||
--accent-color: #F5D5C8;
|
||||
--bg-color: #FDF8F3;
|
||||
--card-bg: #FFFFFF;
|
||||
--text-primary: #2D3748;
|
||||
--text-secondary: #718096;
|
||||
--price-color: #E53E3E;
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "PingFang SC", "Microsoft YaHei", Arial, sans-serif;
|
||||
background: #f7f7f7;
|
||||
font-family: "PingFang SC", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* Element UI 主题覆盖 */
|
||||
.el-button--primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 24px;
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.el-button--primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
|
||||
}
|
||||
|
||||
.el-button--danger {
|
||||
background: linear-gradient(135deg, #FC8181 0%, #E53E3E 100%);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.el-button--text {
|
||||
color: var(--secondary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-button--text:hover {
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
border: none;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.el-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid #E2E8F0;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.el-input__inner:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(232, 180, 184, 0.2);
|
||||
}
|
||||
|
||||
.el-select .el-input__inner:focus {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 全局动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
/* 滚动条美化 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,19 +2,50 @@
|
||||
<div class="page">
|
||||
<el-container>
|
||||
<el-header class="header">
|
||||
<div class="logo">植愈线上花店</div>
|
||||
<div>
|
||||
<el-button v-if="isLoggedIn" type="text" @click="$router.push('/orders')">我的订单</el-button>
|
||||
<el-button v-if="isLoggedIn" type="text" @click="$router.push('/profile')">个人中心</el-button>
|
||||
<el-button v-if="isAdmin" type="text" @click="$router.push('/admin')">后台管理</el-button>
|
||||
<el-button v-if="!isLoggedIn" type="primary" @click="$router.push('/login')">登录</el-button>
|
||||
<el-button v-else type="danger" @click="handleLogout">注销</el-button>
|
||||
<div class="logo">
|
||||
<span class="logo-icon">🌸</span>
|
||||
<span class="logo-text">植愈线上花店</span>
|
||||
</div>
|
||||
<div class="nav-actions">
|
||||
<el-button v-if="isLoggedIn" type="text" class="nav-btn" @click="$router.push('/orders')">
|
||||
<i class="el-icon-s-order"></i>我的订单
|
||||
</el-button>
|
||||
<el-button v-if="isLoggedIn" type="text" class="nav-btn" @click="$router.push('/profile')">
|
||||
<i class="el-icon-user"></i>个人中心
|
||||
</el-button>
|
||||
<el-button v-if="isAdmin" type="text" class="nav-btn" @click="$router.push('/admin')">
|
||||
<i class="el-icon-s-tools"></i>后台管理
|
||||
</el-button>
|
||||
<el-button v-if="!isLoggedIn" type="primary" class="login-btn" @click="$router.push('/login')">
|
||||
<i class="el-icon-user-solid"></i>登录
|
||||
</el-button>
|
||||
<el-button v-else type="danger" class="logout-btn" @click="handleLogout">
|
||||
<i class="el-icon-switch-button"></i>退出
|
||||
</el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<el-card>
|
||||
<!-- Banner区域 -->
|
||||
<div class="hero-banner">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">用鲜花传递爱意</h1>
|
||||
<p class="hero-subtitle">每一束花都承载着独特的情感</p>
|
||||
</div>
|
||||
<div class="hero-decoration">
|
||||
<span class="flower-decoration">🌺</span>
|
||||
<span class="flower-decoration">🌻</span>
|
||||
<span class="flower-decoration">🌷</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索过滤区域 -->
|
||||
<el-card class="filter-card">
|
||||
<div class="filters">
|
||||
<el-select v-model="categoryId" clearable placeholder="全部分类" @change="loadProducts">
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">
|
||||
<i class="el-icon-s-grid"></i>分类
|
||||
</span>
|
||||
<el-select v-model="categoryId" clearable placeholder="全部分类" @change="loadProducts" class="category-select">
|
||||
<el-option
|
||||
v-for="item in categories"
|
||||
:key="item.id"
|
||||
@@ -22,19 +53,65 @@
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input v-model="keyword" placeholder="搜索花束" class="search" @keyup.enter.native="filterProducts" />
|
||||
<el-button type="primary" @click="filterProducts">搜索</el-button>
|
||||
</div>
|
||||
<div class="search-group">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
placeholder="搜索心仪的花束..."
|
||||
class="search-input"
|
||||
@keyup.enter.native="filterProducts"
|
||||
prefix-icon="el-icon-search"
|
||||
/>
|
||||
<el-button type="primary" class="search-btn" @click="filterProducts">
|
||||
<i class="el-icon-search"></i>搜索
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-row :gutter="16" class="product-list">
|
||||
<el-col v-for="item in filteredProducts" :key="item.id" :span="6">
|
||||
<el-card :body-style="{ padding: '12px' }" class="product-card" @click.native="goDetail(item.id)">
|
||||
|
||||
<!-- 产品列表 -->
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="title-icon">✿</span>
|
||||
精选花束
|
||||
</h2>
|
||||
<span class="product-count">共 {{ filteredProducts.length }} 款</span>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20" class="product-list">
|
||||
<el-col v-for="(item, index) in filteredProducts" :key="item.id" :span="6" :xs="12" :sm="8" :md="8" :lg="6">
|
||||
<div class="product-card-wrapper" :style="{ animationDelay: index * 0.1 + 's' }">
|
||||
<el-card class="product-card" @click.native="goDetail(item.id)">
|
||||
<div class="card-image-wrapper">
|
||||
<img :src="item.coverUrl || placeholder" class="product-cover" />
|
||||
<div class="card-overlay">
|
||||
<span class="view-detail">查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="product-category" v-if="item.categoryName">{{ item.categoryName }}</div>
|
||||
<div class="product-name">{{ item.name }}</div>
|
||||
<div class="product-price">¥{{ item.price }}</div>
|
||||
<div class="product-footer">
|
||||
<div class="product-price">
|
||||
<span class="price-symbol">¥</span>
|
||||
<span class="price-value">{{ item.price }}</span>
|
||||
</div>
|
||||
<el-button type="primary" size="mini" class="buy-btn" @click.stop="goDetail(item.id)">
|
||||
立即选购
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredProducts.length === 0" class="empty-state">
|
||||
<div class="empty-icon">🌱</div>
|
||||
<p class="empty-text">没有找到相关花束</p>
|
||||
<el-button type="text" @click="keyword = ''; filterProducts()">清除搜索</el-button>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
@@ -111,43 +188,382 @@ export default {
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Header 样式 */
|
||||
.header {
|
||||
background: #fff;
|
||||
background: linear-gradient(135deg, #fff 0%, var(--bg-color) 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 32px;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--secondary-color) 0%, var(--primary-dark) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.nav-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
background: rgba(232, 180, 184, 0.1);
|
||||
}
|
||||
|
||||
.login-btn, .logout-btn {
|
||||
border-radius: 20px;
|
||||
padding: 10px 24px;
|
||||
}
|
||||
|
||||
/* Banner 区域 */
|
||||
.hero-banner {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 50%, var(--secondary-light) 100%);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 50px 40px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-banner::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, transparent 60%);
|
||||
animation: rotate 20s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin: 0 0 12px 0;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 16px;
|
||||
color: rgba(255,255,255,0.9);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-decoration {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.flower-decoration {
|
||||
font-size: 48px;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.flower-decoration:nth-child(2) {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.flower-decoration:nth-child(3) {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
/* 过滤区域 */
|
||||
.filter-card {
|
||||
margin-bottom: 24px;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
.search {
|
||||
margin: 0 12px;
|
||||
width: 240px;
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.search-input >>> .el-input__inner {
|
||||
border-radius: 25px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
border-radius: 25px;
|
||||
padding: 10px 24px;
|
||||
}
|
||||
|
||||
/* 区域标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
color: var(--primary-color);
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.product-count {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 产品列表 */
|
||||
.product-list {
|
||||
margin-top: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.product-card-wrapper {
|
||||
margin-bottom: 20px;
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
transition: var(--transition);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.card-image-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.product-cover {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.card-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 50%);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: var(--transition);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.product-card:hover .card-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.product-card:hover .product-cover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.view-detail {
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
padding: 8px 20px;
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-radius: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 16px 0 0 0;
|
||||
}
|
||||
|
||||
.product-category {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-color);
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
margin-top: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
color: #f56c6c;
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.price-symbol {
|
||||
font-size: 14px;
|
||||
color: var(--price-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--price-color);
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
border-radius: 15px;
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.hero-banner {
|
||||
padding: 30px 20px;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,57 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="auth-page">
|
||||
<el-card class="card">
|
||||
<h3>登录</h3>
|
||||
<el-form :model="form">
|
||||
<el-form-item label="账号">
|
||||
<el-input v-model="form.username" />
|
||||
<div class="auth-background">
|
||||
<div class="floating-flowers">
|
||||
<span class="flower">🌸</span>
|
||||
<span class="flower">🌺</span>
|
||||
<span class="flower">🌻</span>
|
||||
<span class="flower">🌷</span>
|
||||
<span class="flower">🌹</span>
|
||||
<span class="flower">🌼</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-card class="auth-card">
|
||||
<div class="auth-header">
|
||||
<div class="logo-icon">🌸</div>
|
||||
<h2 class="auth-title">欢迎回来</h2>
|
||||
<p class="auth-subtitle">登录植愈线上花店</p>
|
||||
</div>
|
||||
<el-form :model="form" class="auth-form">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请输入账号"
|
||||
prefix-icon="el-icon-user"
|
||||
size="large"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" />
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
size="large"
|
||||
@keyup.enter.native="submit"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
class="submit-btn"
|
||||
:loading="loading"
|
||||
@click="submit"
|
||||
>
|
||||
<i class="el-icon-right"></i>立即登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="submit">登录</el-button>
|
||||
<el-button type="text" @click="$router.push('/register')">没有账号?注册</el-button>
|
||||
</el-form>
|
||||
<div class="auth-footer">
|
||||
<p>还没有账号?</p>
|
||||
<el-button type="text" class="link-btn" @click="$router.push('/register')">
|
||||
立即注册 <i class="el-icon-arrow-right"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,23 +62,33 @@ import { login } from '../api/auth';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: { username: '', password: '' }
|
||||
form: { username: '', password: '' },
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (!this.form.username || !this.form.password) {
|
||||
this.$message.warning('请填写完整信息');
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
login(this.form).then((res) => {
|
||||
if (!res || !res.data || res.data.code !== 0) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
const data = res.data.data;
|
||||
if (!data) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
this.$message.success('登录成功');
|
||||
this.$message.success('登录成功,欢迎回来!');
|
||||
this.$router.push('/');
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -47,12 +97,166 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.auth-page {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card {
|
||||
width: 400px;
|
||||
|
||||
.auth-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #fdf6f0 0%, #f8e8e8 50%, #e8f4f0 100%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.floating-flowers {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flower {
|
||||
position: absolute;
|
||||
font-size: 40px;
|
||||
opacity: 0.3;
|
||||
animation: float-up 15s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.flower:nth-child(1) { left: 10%; animation-delay: 0s; }
|
||||
.flower:nth-child(2) { left: 25%; animation-delay: 2s; }
|
||||
.flower:nth-child(3) { left: 45%; animation-delay: 4s; }
|
||||
.flower:nth-child(4) { left: 65%; animation-delay: 1s; }
|
||||
.flower:nth-child(5) { left: 80%; animation-delay: 3s; }
|
||||
.flower:nth-child(6) { left: 90%; animation-delay: 5s; }
|
||||
|
||||
@keyframes float-up {
|
||||
0%, 100% {
|
||||
transform: translateY(100vh) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
90% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px) rotate(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border: none;
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-form >>> .el-input__inner {
|
||||
border-radius: 25px;
|
||||
padding-left: 45px;
|
||||
height: 50px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.auth-form >>> .el-input__prefix {
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.auth-form >>> .el-input__icon {
|
||||
font-size: 18px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.auth-footer p {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.link-btn {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.link-btn:hover {
|
||||
color: var(--secondary-light);
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 480px) {
|
||||
.auth-card {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,56 +1,191 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="page">
|
||||
<el-card>
|
||||
<el-button type="text" @click="$router.push('/')">返回首页</el-button>
|
||||
</el-card>
|
||||
<el-card class="content">
|
||||
<el-table :data="orders">
|
||||
<el-table-column prop="order.orderNo" label="订单号" />
|
||||
<el-table-column label="状态">
|
||||
<template slot-scope="scope">
|
||||
{{ statusText(scope.row.order.status) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="order.totalAmount" label="金额" />
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<el-button type="text" class="back-btn" @click="$router.push('/')">
|
||||
<i class="el-icon-arrow-left"></i>返回首页
|
||||
</el-button>
|
||||
<h2 class="page-title">我的订单</h2>
|
||||
<div class="header-spacer"></div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" class="stats-row">
|
||||
<el-col :span="6" :xs="12">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon pending">⏳</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ pendingCount }}</div>
|
||||
<div class="stat-label">待支付</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" :xs="12">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon paid">💳</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ paidCount }}</div>
|
||||
<div class="stat-label">已支付</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" :xs="12">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon shipped">🚚</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ shippedCount }}</div>
|
||||
<div class="stat-label">配送中</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" :xs="12">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon completed">✨</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ completedCount }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<el-card class="orders-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<i class="el-icon-s-order"></i>
|
||||
订单列表
|
||||
</h3>
|
||||
<el-radio-group v-model="filterStatus" size="small" @change="filterOrders">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="CREATED">待支付</el-radio-button>
|
||||
<el-radio-button label="PAID">已支付</el-radio-button>
|
||||
<el-radio-button label="COMPLETED">已完成</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredOrders.length === 0" class="empty-orders">
|
||||
<div class="empty-icon">📦</div>
|
||||
<p class="empty-text">暂无相关订单</p>
|
||||
<el-button type="primary" @click="$router.push('/')">去逛逛</el-button>
|
||||
</div>
|
||||
|
||||
<div v-else class="orders-list">
|
||||
<div
|
||||
v-for="(order, index) in filteredOrders"
|
||||
:key="order.order.id"
|
||||
class="order-item"
|
||||
:class="{ 'highlight': $route.query.highlight == order.order.id }"
|
||||
:style="{ animationDelay: index * 0.1 + 's' }"
|
||||
>
|
||||
<div class="order-header">
|
||||
<div class="order-meta">
|
||||
<span class="order-no">订单号: {{ order.order.orderNo }}</span>
|
||||
<span class="order-date">{{ order.order.createTime }}</span>
|
||||
</div>
|
||||
<el-tag :type="statusType(order.order.status)" effect="dark" class="status-tag">
|
||||
{{ statusText(order.order.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="order-products">
|
||||
<div v-for="item in order.items" :key="item.id" class="product-item">
|
||||
<img :src="item.coverUrl || 'https://via.placeholder.com/80x80?text=Flower'" class="product-thumb" />
|
||||
<div class="product-details">
|
||||
<div class="product-name">{{ item.productName }}</div>
|
||||
<div class="product-specs">
|
||||
<span class="product-price">¥{{ item.unitPrice }}</span>
|
||||
<span class="product-quantity">x{{ item.quantity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="order-footer">
|
||||
<div class="order-total">
|
||||
<span class="total-label">订单金额</span>
|
||||
<span class="total-value">¥{{ order.order.totalAmount }}</span>
|
||||
</div>
|
||||
<div class="order-actions">
|
||||
<el-button
|
||||
v-if="scope.row.order.status === 'CREATED'"
|
||||
type="text"
|
||||
@click="pay(scope.row.order.id)"
|
||||
>支付</el-button>
|
||||
v-if="order.order.status === 'CREATED'"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="action-btn pay-btn"
|
||||
@click="pay(order.order.id)"
|
||||
>
|
||||
<i class="el-icon-wallet"></i>立即支付
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.order.status === 'CREATED'"
|
||||
type="text"
|
||||
@click="cancel(scope.row.order.id)"
|
||||
>取消</el-button>
|
||||
v-if="order.order.status === 'CREATED'"
|
||||
size="small"
|
||||
class="action-btn cancel-btn"
|
||||
@click="cancel(order.order.id)"
|
||||
>
|
||||
<i class="el-icon-close"></i>取消订单
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canConfession(scope.row.order.status)"
|
||||
type="text"
|
||||
@click="goConfession(scope.row.order.id)"
|
||||
>告白弹幕</el-button>
|
||||
v-if="canConfession(order.order.status)"
|
||||
type="success"
|
||||
size="small"
|
||||
class="action-btn"
|
||||
@click="goConfession(order.order.id)"
|
||||
>
|
||||
<i class="el-icon-magic-stick"></i>告白弹幕
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canReview(scope.row.order.status)"
|
||||
type="text"
|
||||
@click="openReview(scope.row)"
|
||||
>评价</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
v-if="canReview(order.order.status)"
|
||||
type="warning"
|
||||
size="small"
|
||||
class="action-btn"
|
||||
@click="openReview(order)"
|
||||
>
|
||||
<i class="el-icon-star-on"></i>评价
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="订单评价" :visible.sync="showReview">
|
||||
<el-form :model="reviewForm">
|
||||
<!-- 评价对话框 -->
|
||||
<el-dialog
|
||||
title="订单评价"
|
||||
:visible.sync="showReview"
|
||||
width="500px"
|
||||
custom-class="review-dialog"
|
||||
>
|
||||
<div class="review-dialog-content">
|
||||
<div class="review-product-info" v-if="reviewProduct">
|
||||
<img :src="reviewProduct.coverUrl || 'https://via.placeholder.com/60x60?text=Flower'" class="review-product-img" />
|
||||
<span class="review-product-name">{{ reviewProduct.productName }}</span>
|
||||
</div>
|
||||
<el-form :model="reviewForm" label-position="top">
|
||||
<el-form-item label="评分">
|
||||
<el-rate v-model="reviewForm.rating" />
|
||||
<el-rate
|
||||
v-model="reviewForm.rating"
|
||||
:colors="['#E8B4B8', '#D4959A', '#C47A80']"
|
||||
show-text
|
||||
:texts="['非常差', '差', '一般', '好', '非常好']"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容">
|
||||
<el-input type="textarea" v-model="reviewForm.content" />
|
||||
<el-form-item label="评价内容">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="reviewForm.content"
|
||||
:rows="4"
|
||||
placeholder="分享您的购物体验..."
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="showReview = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitReview">提交</el-button>
|
||||
<el-button type="primary" @click="submitReview" class="submit-btn">
|
||||
<i class="el-icon-check"></i>提交评价
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -64,15 +199,32 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
orders: [],
|
||||
filteredOrders: [],
|
||||
filterStatus: 'all',
|
||||
showReview: false,
|
||||
reviewForm: {
|
||||
orderId: null,
|
||||
productId: null,
|
||||
rating: 5,
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
reviewProduct: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pendingCount() {
|
||||
return this.orders.filter(o => o.order.status === 'CREATED').length;
|
||||
},
|
||||
paidCount() {
|
||||
return this.orders.filter(o => o.order.status === 'PAID').length;
|
||||
},
|
||||
shippedCount() {
|
||||
return this.orders.filter(o => o.order.status === 'SHIPPED').length;
|
||||
},
|
||||
completedCount() {
|
||||
return this.orders.filter(o => o.order.status === 'COMPLETED').length;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadOrders();
|
||||
},
|
||||
@@ -81,17 +233,35 @@ export default {
|
||||
const map = {
|
||||
CREATED: '待支付',
|
||||
PAID: '已支付',
|
||||
SHIPPED: '已发货',
|
||||
SHIPPED: '配送中',
|
||||
COMPLETED: '已完成',
|
||||
CANCELED: '已取消'
|
||||
};
|
||||
return map[status] || status;
|
||||
},
|
||||
statusType(status) {
|
||||
const map = {
|
||||
CREATED: 'danger',
|
||||
PAID: 'warning',
|
||||
SHIPPED: 'primary',
|
||||
COMPLETED: 'success',
|
||||
CANCELED: 'info'
|
||||
};
|
||||
return map[status] || 'info';
|
||||
},
|
||||
loadOrders() {
|
||||
listOrders().then((res) => {
|
||||
this.orders = res.data.data || [];
|
||||
this.filteredOrders = this.orders;
|
||||
});
|
||||
},
|
||||
filterOrders() {
|
||||
if (this.filterStatus === 'all') {
|
||||
this.filteredOrders = this.orders;
|
||||
} else {
|
||||
this.filteredOrders = this.orders.filter(o => o.order.status === this.filterStatus);
|
||||
}
|
||||
},
|
||||
pay(id) {
|
||||
payOrder(id).then(() => {
|
||||
this.$message.success('支付成功');
|
||||
@@ -125,12 +295,14 @@ export default {
|
||||
rating: 5,
|
||||
content: ''
|
||||
};
|
||||
this.reviewProduct = firstItem;
|
||||
this.showReview = true;
|
||||
},
|
||||
submitReview() {
|
||||
createReview(this.reviewForm).then(() => {
|
||||
this.$message.success('提交成功,等待审核');
|
||||
this.showReview = false;
|
||||
this.reviewProduct = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -140,8 +312,403 @@ export default {
|
||||
<style scoped>
|
||||
.page {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.content {
|
||||
margin-top: 12px;
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 15px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-spacer {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.stat-icon.pending {
|
||||
background: linear-gradient(135deg, #fee 0%, #fcc 100%);
|
||||
}
|
||||
|
||||
.stat-icon.paid {
|
||||
background: linear-gradient(135deg, #fef3cd 0%, #ffeaa7 100%);
|
||||
}
|
||||
|
||||
.stat-icon.shipped {
|
||||
background: linear-gradient(135deg, #d1ecf1 0%, #a8d8ea 100%);
|
||||
}
|
||||
|
||||
.stat-icon.completed {
|
||||
background: linear-gradient(135deg, #d4edda 0%, #a8e6cf 100%);
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 订单卡片 */
|
||||
.orders-card {
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-title i {
|
||||
color: var(--primary-color);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-orders {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 72px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 订单列表 */
|
||||
.orders-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
transition: var(--transition);
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.order-item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.order-item.highlight {
|
||||
border: 2px solid var(--primary-color);
|
||||
box-shadow: 0 0 0 4px rgba(232, 180, 184, 0.2);
|
||||
animation: pulse-border 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-border {
|
||||
0%, 100% { box-shadow: 0 0 0 4px rgba(232, 180, 184, 0.2); }
|
||||
50% { box-shadow: 0 0 0 8px rgba(232, 180, 184, 0.1); }
|
||||
}
|
||||
|
||||
.order-header {
|
||||
background: #f8f9fa;
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.order-meta {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.order-date {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 产品列表 */
|
||||
.order-products {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.product-thumb {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.product-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.product-specs {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
color: var(--price-color);
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.product-quantity {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 订单底部 */
|
||||
.order-footer {
|
||||
background: #f8f9fa;
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.order-total {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--price-color);
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
color: var(--price-color);
|
||||
border-color: var(--price-color);
|
||||
}
|
||||
|
||||
/* 评价对话框 */
|
||||
.review-dialog >>> .el-dialog__header {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.review-dialog >>> .el-dialog__title {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.review-dialog >>> .el-dialog__headerbtn .el-dialog__close {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.review-dialog-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.review-product-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.review-product-img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.review-product-name {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
border-radius: 20px;
|
||||
padding: 10px 24px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
order: -1;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.header-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.order-header,
|
||||
.order-footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,66 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="page">
|
||||
<el-card>
|
||||
<el-button type="text" @click="$router.push('/')">返回首页</el-button>
|
||||
</el-card>
|
||||
<el-card class="content">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<img :src="product.coverUrl || placeholder" class="cover" />
|
||||
<!-- 导航栏 -->
|
||||
<div class="detail-header">
|
||||
<el-button type="text" class="back-btn" @click="$router.push('/')">
|
||||
<i class="el-icon-arrow-left"></i>返回首页
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 商品详情卡片 -->
|
||||
<el-card class="product-detail-card">
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12" :xs="24">
|
||||
<div class="image-gallery">
|
||||
<div class="main-image-wrapper">
|
||||
<img :src="product.coverUrl || placeholder" class="main-cover" />
|
||||
<div class="image-badge" v-if="product.stock && product.stock < 10">
|
||||
<i class="el-icon-warning"></i>库存紧张
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<h2>{{ product.name }}</h2>
|
||||
<el-col :span="12" :xs="24">
|
||||
<div class="product-info">
|
||||
<div class="product-header">
|
||||
<span class="product-tag" v-if="product.categoryName">{{ product.categoryName }}</span>
|
||||
<h1 class="product-title">{{ product.name }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="product-desc">
|
||||
<i class="el-icon-info desc-icon"></i>
|
||||
<p>{{ product.description }}</p>
|
||||
<div class="price">¥{{ product.price }}</div>
|
||||
<el-input-number v-model="quantity" :min="1" :max="product.stock || 99" />
|
||||
<div class="section">
|
||||
<el-select v-model="addressId" placeholder="选择收货地址">
|
||||
</div>
|
||||
|
||||
<div class="price-section">
|
||||
<div class="price-label">优惠价</div>
|
||||
<div class="price-display">
|
||||
<span class="price-symbol">¥</span>
|
||||
<span class="price-value">{{ product.price }}</span>
|
||||
</div>
|
||||
<div class="price-original" v-if="product.originalPrice">
|
||||
原价 ¥{{ product.originalPrice }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="purchase-section">
|
||||
<div class="quantity-selector">
|
||||
<span class="selector-label">数量</span>
|
||||
<el-input-number
|
||||
v-model="quantity"
|
||||
:min="1"
|
||||
:max="product.stock || 99"
|
||||
size="medium"
|
||||
class="custom-number-input"
|
||||
/>
|
||||
<span class="stock-hint" v-if="product.stock">
|
||||
库存 {{ product.stock }} 件
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="address-selector">
|
||||
<span class="selector-label">配送地址</span>
|
||||
<el-select v-model="addressId" placeholder="请选择收货地址" class="address-select">
|
||||
<el-option
|
||||
v-for="addr in addresses"
|
||||
:key="addr.id"
|
||||
@@ -22,17 +68,62 @@
|
||||
:value="addr.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="submitOrder">立即购买</el-button>
|
||||
<el-button type="text" @click="$router.push('/profile')" class="add-address-btn">
|
||||
<i class="el-icon-plus"></i>添加地址
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" class="buy-now-btn" @click="submitOrder">
|
||||
<i class="el-icon-shopping-cart-full"></i>
|
||||
立即购买
|
||||
</el-button>
|
||||
<el-button class="gift-btn" @click="$router.push('/confession')">
|
||||
<i class="el-icon-magic-stick"></i>
|
||||
写寄语
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-tags">
|
||||
<span class="service-tag"><i class="el-icon-check"></i>新鲜保证</span>
|
||||
<span class="service-tag"><i class="el-icon-check"></i>同城配送</span>
|
||||
<span class="service-tag"><i class="el-icon-check"></i>精美包装</span>
|
||||
<span class="service-tag"><i class="el-icon-check"></i>售后无忧</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card class="reviews">
|
||||
<div class="review-title">用户评价</div>
|
||||
<div v-if="reviews.length === 0" class="review-empty">暂无评价</div>
|
||||
<div v-else>
|
||||
|
||||
<!-- 评价区域 -->
|
||||
<el-card class="reviews-card">
|
||||
<div class="reviews-header">
|
||||
<h3 class="reviews-title">
|
||||
<i class="el-icon-chat-line-round"></i>
|
||||
用户评价
|
||||
<span class="reviews-count">({{ reviews.length }})</span>
|
||||
</h3>
|
||||
<el-rate :value="averageRating" disabled show-score v-if="reviews.length > 0" />
|
||||
</div>
|
||||
|
||||
<div v-if="reviews.length === 0" class="empty-reviews">
|
||||
<div class="empty-icon">💬</div>
|
||||
<p>暂无评价,快来成为第一个评价的人吧!</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="reviews-list">
|
||||
<div v-for="item in reviews" :key="item.id" class="review-item">
|
||||
<div class="review-header">
|
||||
<div class="reviewer-info">
|
||||
<div class="reviewer-avatar">{{ item.username ? item.username[0] : '用' }}</div>
|
||||
<div class="reviewer-meta">
|
||||
<div class="reviewer-name">{{ item.username || '匿名用户' }}</div>
|
||||
<div class="review-date">{{ item.createTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-rate :value="item.rating" disabled />
|
||||
</div>
|
||||
<div class="review-content">{{ item.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,9 +146,16 @@ export default {
|
||||
addresses: [],
|
||||
addressId: null,
|
||||
reviews: [],
|
||||
placeholder: 'https://via.placeholder.com/400x300?text=Flower'
|
||||
placeholder: 'https://via.placeholder.com/600x600?text=Flower'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
averageRating() {
|
||||
if (this.reviews.length === 0) return 0;
|
||||
const sum = this.reviews.reduce((acc, review) => acc + review.rating, 0);
|
||||
return Math.round((sum / this.reviews.length) * 10) / 10;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDetail();
|
||||
this.loadAddresses();
|
||||
@@ -121,46 +219,417 @@ export default {
|
||||
<style scoped>
|
||||
.page {
|
||||
padding: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.content {
|
||||
margin-top: 16px;
|
||||
|
||||
/* 头部导航 */
|
||||
.detail-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.cover {
|
||||
|
||||
.back-btn {
|
||||
font-size: 15px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: rgba(232, 180, 184, 0.1);
|
||||
}
|
||||
|
||||
/* 商品详情卡片 */
|
||||
.product-detail-card {
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 图片展示 */
|
||||
.image-gallery {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-image-wrapper {
|
||||
position: relative;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.main-cover {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
height: 500px;
|
||||
object-fit: cover;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.price {
|
||||
color: #f56c6c;
|
||||
font-size: 20px;
|
||||
margin: 12px 0;
|
||||
|
||||
.main-image-wrapper:hover .main-cover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
.section {
|
||||
margin-top: 20px;
|
||||
|
||||
.image-badge {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
.section .el-select {
|
||||
margin-right: 12px;
|
||||
width: 300px;
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.8; }
|
||||
}
|
||||
.reviews {
|
||||
margin-top: 16px;
|
||||
|
||||
/* 商品信息 */
|
||||
.product-info {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.review-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
.product-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.product-tag {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, var(--secondary-light) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.product-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.desc-icon {
|
||||
color: var(--primary-color);
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-desc p {
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* 价格区域 */
|
||||
.price-section {
|
||||
background: linear-gradient(135deg, rgba(232, 180, 184, 0.1) 0%, rgba(245, 213, 200, 0.1) 100%);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid rgba(232, 180, 184, 0.3);
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.review-empty {
|
||||
color: #909399;
|
||||
|
||||
.price-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
.review-item {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.price-symbol {
|
||||
font-size: 20px;
|
||||
color: var(--price-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
.review-content {
|
||||
|
||||
.price-value {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: var(--price-color);
|
||||
}
|
||||
|
||||
.price-original {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: line-through;
|
||||
margin-top: 6px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 购买区域 */
|
||||
.purchase-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.quantity-selector,
|
||||
.address-selector {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.selector-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.custom-number-input >>> .el-input-number__decrease,
|
||||
.custom-number-input >>> .el-input-number__increase {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stock-hint {
|
||||
margin-left: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.address-select {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.add-address-btn {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.buy-now-btn {
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gift-btn {
|
||||
height: 50px;
|
||||
padding: 0 30px;
|
||||
border-radius: 25px;
|
||||
border: 2px solid var(--primary-color);
|
||||
color: var(--primary-dark);
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gift-btn:hover {
|
||||
background: rgba(232, 180, 184, 0.1);
|
||||
border-color: var(--primary-dark);
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
|
||||
/* 服务标签 */
|
||||
.service-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.service-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
background: #f5f5f5;
|
||||
padding: 6px 12px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.service-tag i {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
/* 评价区域 */
|
||||
.reviews-card {
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.reviews-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.reviews-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.reviews-title i {
|
||||
color: var(--primary-color);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.reviews-count {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.empty-reviews {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-reviews p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.reviews-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.review-item {
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: var(--radius-md);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.review-item:hover {
|
||||
background: #f0f1f2;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.review-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.reviewer-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.reviewer-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.reviewer-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.reviewer-name {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.review-date {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.review-content {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
padding-left: 52px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.main-cover {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.product-title {
|
||||
font-size: 22px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.buy-now-btn,
|
||||
.gift-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.review-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.review-content {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,65 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="auth-page">
|
||||
<el-card class="card">
|
||||
<h3>注册</h3>
|
||||
<el-form :model="form">
|
||||
<el-form-item label="账号">
|
||||
<el-input v-model="form.username" />
|
||||
<div class="auth-background">
|
||||
<div class="floating-flowers">
|
||||
<span class="flower">🌸</span>
|
||||
<span class="flower">🌺</span>
|
||||
<span class="flower">🌻</span>
|
||||
<span class="flower">🌷</span>
|
||||
<span class="flower">🌹</span>
|
||||
<span class="flower">🌼</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-card class="auth-card">
|
||||
<div class="auth-header">
|
||||
<div class="logo-icon">🌱</div>
|
||||
<h2 class="auth-title">创建账号</h2>
|
||||
<p class="auth-subtitle">加入植愈线上花店</p>
|
||||
</div>
|
||||
<el-form :model="form" class="auth-form">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请输入账号"
|
||||
prefix-icon="el-icon-user"
|
||||
size="large"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" />
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
size="large"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="form.nickname" />
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.nickname"
|
||||
placeholder="请输入昵称"
|
||||
prefix-icon="el-icon-edit"
|
||||
size="large"
|
||||
@keyup.enter.native="submit"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
class="submit-btn"
|
||||
:loading="loading"
|
||||
@click="submit"
|
||||
>
|
||||
<i class="el-icon-check"></i>立即注册
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="submit">注册</el-button>
|
||||
<el-button type="text" @click="$router.push('/login')">已有账号?登录</el-button>
|
||||
</el-form>
|
||||
<div class="auth-footer">
|
||||
<p>已有账号?</p>
|
||||
<el-button type="text" class="link-btn" @click="$router.push('/login')">
|
||||
立即登录 <i class="el-icon-arrow-right"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -25,23 +70,37 @@ import { register } from '../api/auth';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: { username: '', password: '', nickname: '' }
|
||||
form: { username: '', password: '', nickname: '' },
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (!this.form.username || !this.form.password || !this.form.nickname) {
|
||||
this.$message.warning('请填写完整信息');
|
||||
return;
|
||||
}
|
||||
if (this.form.password.length < 6) {
|
||||
this.$message.warning('密码至少需要6位');
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
register(this.form).then((res) => {
|
||||
if (!res || !res.data || res.data.code !== 0) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
const data = res.data.data;
|
||||
if (!data) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
this.$message.success('注册成功');
|
||||
this.$message.success('注册成功,欢迎加入!');
|
||||
this.$router.push('/');
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -50,12 +109,171 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.auth-page {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card {
|
||||
width: 400px;
|
||||
|
||||
.auth-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #f0fdf4 0%, #e8f4f0 50%, #fdf6f0 100%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.floating-flowers {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flower {
|
||||
position: absolute;
|
||||
font-size: 40px;
|
||||
opacity: 0.3;
|
||||
animation: float-up 15s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.flower:nth-child(1) { left: 8%; animation-delay: 1s; }
|
||||
.flower:nth-child(2) { left: 22%; animation-delay: 3s; }
|
||||
.flower:nth-child(3) { left: 42%; animation-delay: 0s; }
|
||||
.flower:nth-child(4) { left: 62%; animation-delay: 2s; }
|
||||
.flower:nth-child(5) { left: 78%; animation-delay: 4s; }
|
||||
.flower:nth-child(6) { left: 92%; animation-delay: 5s; }
|
||||
|
||||
@keyframes float-up {
|
||||
0%, 100% {
|
||||
transform: translateY(100vh) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
90% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px) rotate(-360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border: none;
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
animation: grow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes grow {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-form >>> .el-input__inner {
|
||||
border-radius: 25px;
|
||||
padding-left: 45px;
|
||||
height: 50px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.auth-form >>> .el-input__prefix {
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.auth-form >>> .el-input__icon {
|
||||
font-size: 18px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.auth-footer p {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.link-btn {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.link-btn:hover {
|
||||
color: var(--secondary-light);
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 480px) {
|
||||
.auth-card {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user