界面美化

This commit is contained in:
2026-02-25 12:38:53 +08:00
parent 7a50853cac
commit 1662bdab3a
6 changed files with 2181 additions and 177 deletions

View File

@@ -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>

View File

@@ -2,39 +2,116 @@
<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">
<el-option
v-for="item in categories"
:key="item.id"
:label="item.name"
:value="item.id"
<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"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
<div class="search-group">
<el-input
v-model="keyword"
placeholder="搜索心仪的花束..."
class="search-input"
@keyup.enter.native="filterProducts"
prefix-icon="el-icon-search"
/>
</el-select>
<el-input v-model="keyword" placeholder="搜索花束" class="search" @keyup.enter.native="filterProducts" />
<el-button type="primary" @click="filterProducts">搜索</el-button>
<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)">
<img :src="item.coverUrl || placeholder" class="product-cover" />
<div class="product-name">{{ item.name }}</div>
<div class="product-price">{{ item.price }}</div>
</el-card>
<!-- 产品列表 -->
<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-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>

View File

@@ -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>

View File

@@ -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">
<el-button
v-if="scope.row.order.status === 'CREATED'"
type="text"
@click="pay(scope.row.order.id)"
>支付</el-button>
<el-button
v-if="scope.row.order.status === 'CREATED'"
type="text"
@click="cancel(scope.row.order.id)"
>取消</el-button>
<el-button
v-if="canConfession(scope.row.order.status)"
type="text"
@click="goConfession(scope.row.order.id)"
>告白弹幕</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>
<!-- 页面头部 -->
<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="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="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(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(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-form-item label="评分">
<el-rate v-model="reviewForm.rating" />
</el-form-item>
<el-form-item label="内容">
<el-input type="textarea" v-model="reviewForm.content" />
</el-form-item>
</el-form>
<div slot="footer">
<!-- 评价对话框 -->
<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"
:colors="['#E8B4B8', '#D4959A', '#C47A80']"
show-text
:texts="['非常差', '差', '一般', '好', '非常好']"
/>
</el-form-item>
<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>
<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>

View File

@@ -1,38 +1,129 @@
<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>
<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="选择收货地址">
<el-option
v-for="addr in addresses"
:key="addr.id"
:label="formatAddress(addr)"
:value="addr.id"
/>
</el-select>
<el-button type="primary" @click="submitOrder">立即购买</el-button>
<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>
<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"
:label="formatAddress(addr)"
:value="addr.id"
/>
</el-select>
<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">
<el-rate :value="item.rating" disabled />
<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>

View File

@@ -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>