Compare commits
3 Commits
0c07d33bf9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d34823985 | ||
|
|
73cf25e586 | ||
|
|
c287650fbb |
278
Api.text
Normal file
278
Api.text
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
# 前端数据需求分析
|
||||||
|
|
||||||
|
## 组件数据需求
|
||||||
|
|
||||||
|
### 1. Address.vue (地址选择组件)
|
||||||
|
- 数据需求:省市区级联选择数据
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
address {
|
||||||
|
province: string, // 省份名称
|
||||||
|
city: string, // 城市名称
|
||||||
|
district: string // 区县名称
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Search.vue (搜索组件)
|
||||||
|
- 数据需求:搜索关键词、搜索类型、搜索历史
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
search {
|
||||||
|
keyword: string, // 搜索关键词
|
||||||
|
type: string, // 搜索类型(如"宝贝"、"店铺")
|
||||||
|
history: string[] // 搜索历史记录
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. page.vue (分页组件)
|
||||||
|
- 数据需求:总页数
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
pagination {
|
||||||
|
totalPages: number // 总页数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 页面数据需求
|
||||||
|
|
||||||
|
### 1. Login.vue (登录页面)
|
||||||
|
- 数据需求:用户登录信息
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
login {
|
||||||
|
username: string, // 用户名
|
||||||
|
password: string, // 密码
|
||||||
|
rememberMe: boolean // 是否记住密码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Userinfo.vue (用户信息页面)
|
||||||
|
- 数据需求:用户信息、订单状态数量
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
user {
|
||||||
|
id: number, // 用户ID
|
||||||
|
username: string, // 用户名
|
||||||
|
avatar: string, // 头像URL
|
||||||
|
level: number, // 用户等级
|
||||||
|
followCount: number, // 关注数
|
||||||
|
fansCount: number, // 粉丝数
|
||||||
|
collectionCount: number, // 收藏数
|
||||||
|
couponCount: number, // 红包数
|
||||||
|
discountCount: number, // 优惠券数
|
||||||
|
taoCoin: number, // 淘币数
|
||||||
|
points: number // 积分
|
||||||
|
}
|
||||||
|
orderCounts {
|
||||||
|
pending_payment: number, // 待付款
|
||||||
|
pending_shipment: number, // 待发货
|
||||||
|
pending_receipt: number, // 待收货
|
||||||
|
pending_review: number, // 待评价
|
||||||
|
completed: number // 已完成
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. productdetil.vue (商品详情页面)
|
||||||
|
- 数据需求:商品信息、商品图片、商品型号、评论、推荐商品
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
product {
|
||||||
|
id: number, // 商品ID
|
||||||
|
title: string, // 商品标题
|
||||||
|
images: string[], // 商品图片URL
|
||||||
|
price: number, // 价格
|
||||||
|
originalPrice: number, // 原价
|
||||||
|
models: [ // 商品型号
|
||||||
|
{
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
price: string,
|
||||||
|
originalPrice: string,
|
||||||
|
img: string
|
||||||
|
}
|
||||||
|
],
|
||||||
|
description: string, // 商品描述
|
||||||
|
features: string[], // 商品特点
|
||||||
|
specs: [ // 商品参数
|
||||||
|
{
|
||||||
|
category: string, // 参数类别
|
||||||
|
value: string // 参数值
|
||||||
|
}
|
||||||
|
],
|
||||||
|
reviews: [ // 用户评论
|
||||||
|
{
|
||||||
|
id: number,
|
||||||
|
reviewerName: string, // 评论者姓名
|
||||||
|
rating: number, // 评分
|
||||||
|
time: string, // 评论时间
|
||||||
|
content: string, // 评论内容
|
||||||
|
images: string[] // 评论图片
|
||||||
|
}
|
||||||
|
],
|
||||||
|
recommendations: [ // 推荐商品
|
||||||
|
{
|
||||||
|
id: number,
|
||||||
|
name: string, // 商品名称
|
||||||
|
price: number, // 价格
|
||||||
|
rating: number, // 评分
|
||||||
|
image: string // 商品图片
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. SettlementLite.vue (结算页面)
|
||||||
|
- 数据需求:地址列表、默认地址、商品信息、价格计算
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
address {
|
||||||
|
id: number, // 地址ID
|
||||||
|
name: string, // 收货人姓名
|
||||||
|
phone: string, // 联系电话
|
||||||
|
province: string, // 省份
|
||||||
|
city: string, // 城市
|
||||||
|
district: string, // 区县
|
||||||
|
detail: string, // 详细地址
|
||||||
|
isDefault: boolean // 是否默认地址
|
||||||
|
}[]
|
||||||
|
order {
|
||||||
|
products: [ // 订单商品
|
||||||
|
{
|
||||||
|
id: number, // 商品ID
|
||||||
|
name: string, // 商品名称
|
||||||
|
price: number, // 商品价格
|
||||||
|
quantity: number, // 购买数量
|
||||||
|
image: string // 商品图片
|
||||||
|
}
|
||||||
|
],
|
||||||
|
totalPrice: number, // 总价格
|
||||||
|
shippingFee: number, // 运费
|
||||||
|
finalPrice: number // 最终价格
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. User/UpdataAddress.vue (地址修改页面)
|
||||||
|
- 数据需求:地址信息
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
address {
|
||||||
|
id: number, // 地址ID
|
||||||
|
name: string, // 收货人姓名
|
||||||
|
phone: string, // 联系电话
|
||||||
|
province: string, // 省份
|
||||||
|
city: string, // 城市
|
||||||
|
district: string, // 区县
|
||||||
|
detail: string, // 详细地址
|
||||||
|
isDefault: boolean // 是否默认地址
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. User/User.vue (用户中心页面)
|
||||||
|
- 数据需求:用户信息、订单列表、地址列表、收藏列表
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
user {
|
||||||
|
id: number, // 用户ID
|
||||||
|
username: string, // 用户名
|
||||||
|
avatar: string // 头像URL
|
||||||
|
}
|
||||||
|
orders {
|
||||||
|
id: number, // 订单ID
|
||||||
|
status: string, // 订单状态
|
||||||
|
totalPrice: number, // 订单总价
|
||||||
|
createTime: string, // 创建时间
|
||||||
|
products: [ // 订单商品
|
||||||
|
{
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
price: number,
|
||||||
|
quantity: number,
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}[]
|
||||||
|
addresses: address[] // 地址列表,结构同SettlementLite.vue
|
||||||
|
collections: [ // 收藏列表
|
||||||
|
{
|
||||||
|
id: number, // 收藏ID
|
||||||
|
productId: number, // 商品ID
|
||||||
|
productName: string, // 商品名称
|
||||||
|
productPrice: number, // 商品价格
|
||||||
|
productImage: string // 商品图片
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Cart.vue (购物车页面)
|
||||||
|
- 数据需求:购物车商品列表
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
cart {
|
||||||
|
items: [ // 购物车商品
|
||||||
|
{
|
||||||
|
id: number, // 商品ID
|
||||||
|
name: string, // 商品名称
|
||||||
|
price: number, // 商品价格
|
||||||
|
quantity: number, // 购买数量
|
||||||
|
image: string, // 商品图片
|
||||||
|
selected: boolean // 是否选中
|
||||||
|
}
|
||||||
|
],
|
||||||
|
totalPrice: number, // 总价格
|
||||||
|
selectedCount: number // 选中数量
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. product/productList.vue (商品列表页面)
|
||||||
|
- 数据需求:商品列表、分页信息
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
products {
|
||||||
|
items: [ // 商品列表
|
||||||
|
{
|
||||||
|
id: number, // 商品ID
|
||||||
|
name: string, // 商品名称
|
||||||
|
price: number, // 商品价格
|
||||||
|
originalPrice: number, // 原价
|
||||||
|
image: string, // 商品图片
|
||||||
|
rating: number, // 评分
|
||||||
|
sales: number // 销量
|
||||||
|
}
|
||||||
|
],
|
||||||
|
total: number, // 总商品数
|
||||||
|
page: number, // 当前页码
|
||||||
|
pageSize: number, // 每页数量
|
||||||
|
totalPages: number // 总页数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Home.vue (首页)
|
||||||
|
- 数据需求:轮播图、推荐商品、分类信息
|
||||||
|
- 数据结构:
|
||||||
|
```
|
||||||
|
home {
|
||||||
|
banners: [ // 轮播图
|
||||||
|
{
|
||||||
|
id: number,
|
||||||
|
image: string, // 轮播图图片
|
||||||
|
link: string // 跳转链接
|
||||||
|
}
|
||||||
|
],
|
||||||
|
recommendedProducts: [ // 推荐商品
|
||||||
|
{
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
price: number,
|
||||||
|
image: string,
|
||||||
|
rating: number
|
||||||
|
}
|
||||||
|
],
|
||||||
|
categories: [ // 商品分类
|
||||||
|
{
|
||||||
|
id: number,
|
||||||
|
name: string, // 分类名称
|
||||||
|
icon: string // 分类图标
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
48
Plain.text
48
Plain.text
@@ -5,39 +5,39 @@ e:\TaoTaoWang\pc-frontend\
|
|||||||
│ └── favicon.ico # 网站图标
|
│ └── favicon.ico # 网站图标
|
||||||
└── src/
|
└── src/
|
||||||
├── pages/
|
├── pages/
|
||||||
│ ├── _app.js # 应用入口
|
│ ├── _app.ts # 应用入口
|
||||||
│ ├── _document.js # 文档模板
|
│ ├── _document.ts # 文档模板
|
||||||
│ ├── index.js # 首页
|
│ ├── index.ts # 首页
|
||||||
│ ├── login.js # 登录页面
|
│ ├── login.ts # 登录页面
|
||||||
│ ├── register.js # 注册页面
|
│ ├── register.ts # 注册页面
|
||||||
│ ├── products/ # 商品相关页面
|
│ ├── products/ # 商品相关页面
|
||||||
│ │ ├── [id].js # 商品详情
|
│ │ ├── [id].ts # 商品详情
|
||||||
│ │ └── list.js # 商品列表
|
│ │ └── list.ts # 商品列表
|
||||||
│ ├── cart.js # 购物车页面
|
│ ├── cart.ts # 购物车页面
|
||||||
│ ├── checkout.js # 结算页面
|
│ ├── checkout.ts # 结算页面
|
||||||
│ ├── payment.js # 支付页面
|
│ ├── payment.ts # 支付页面
|
||||||
│ └── user/ # 用户中心
|
│ └── user/ # 用户中心
|
||||||
│ ├── index.js # 用户中心首页
|
│ ├── index.ts # 用户中心首页
|
||||||
│ ├── orders.js # 订单列表
|
│ ├── orders.ts # 订单列表
|
||||||
│ └── profile.js # 个人信息
|
│ └── profile.ts # 个人信息
|
||||||
├── components/
|
├── components/
|
||||||
│ ├── common/ # 通用组件
|
│ ├── common/ # 通用组件
|
||||||
│ ├── layout/ # 布局组件
|
│ ├── layout/ # 布局组件
|
||||||
│ └── business/ # 业务组件
|
│ └── business/ # 业务组件
|
||||||
├── api/
|
├── service/
|
||||||
│ ├── index.js # API基础配置
|
│ ├── api.ts # API基础配置
|
||||||
│ ├── auth.js # 认证相关API
|
│ ├── auth.ts # 认证相关API
|
||||||
│ ├── product.js # 商品相关API
|
│ ├── product.ts # 商品相关API
|
||||||
│ └── order.js # 订单相关API
|
│ └── order.ts # 订单相关API
|
||||||
├── store/
|
├── store/
|
||||||
│ ├── index.js # Redux配置
|
│ ├── index.ts # Redux配置
|
||||||
│ └── slices/
|
│ └── slices/
|
||||||
│ ├── userSlice.js # 用户状态切片
|
│ ├── userSlice.ts # 用户状态切片
|
||||||
│ ├── productSlice.js # 商品状态切片
|
│ ├── productSlice.ts # 商品状态切片
|
||||||
│ └── cartSlice.js # 购物车状态切片
|
│ └── cartSlice.ts # 购物车状态切片
|
||||||
├── utils/
|
├── utils/
|
||||||
│ ├── request.js # 请求工具
|
│ ├── request.ts # 请求工具
|
||||||
│ └── auth.js # 认证工具
|
│ └── auth.ts # 认证工具
|
||||||
└── assets/
|
└── assets/
|
||||||
├── css/ # 样式文件
|
├── css/ # 样式文件
|
||||||
└── images/ # 图片资源
|
└── images/ # 图片资源
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<Herde></Herde>
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterView } from "vue-router";
|
import { RouterView } from "vue-router";
|
||||||
import Herde from "@/Views/Herde.vue";
|
import './views/App.css'
|
||||||
import './Style/App.css'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
927
src/Component/business/SettlementLite.vue
Normal file
927
src/Component/business/SettlementLite.vue
Normal file
@@ -0,0 +1,927 @@
|
|||||||
|
<!-- 结算页 用于展示购物车中的商品信息 并确认结算 -->
|
||||||
|
<template>
|
||||||
|
<a-modal v-model:visible="visible" width="800px" @ok="afterClose" @cancel="cancel" class="settlement-modal"
|
||||||
|
:footer-extra="null" :footer="null">
|
||||||
|
<!-- 结算界面 左右结构 -->
|
||||||
|
<div class="settlement-container">
|
||||||
|
<!-- 左侧 结算列表 -->
|
||||||
|
<div class="settlement-left">
|
||||||
|
<!-- 地址信息 -->
|
||||||
|
<div class="address-info">
|
||||||
|
<div class="address-item" @click="showAddressModal = true">
|
||||||
|
<div class="address-header">
|
||||||
|
<span class="address-name">{{ selectedAddress.name }}</span>
|
||||||
|
<span class="address-phone">{{ selectedAddress.phone }}</span>
|
||||||
|
<span v-if="selectedAddress.isDefault" class="address-tag default">默认</span>
|
||||||
|
</div>
|
||||||
|
<div class="address-detail">
|
||||||
|
{{ selectedAddress.province }}{{ selectedAddress.city }}{{ selectedAddress.district }}{{
|
||||||
|
selectedAddress.detail }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-modal v-model:visible="showAddressModal" width="600px" @ok="afterCloseAddressModal"
|
||||||
|
:after-close="afterCloseAddressModal" :footer-extra="null" :footer="null">
|
||||||
|
<!-- 顶部导航 地址列表 添加地址 -->
|
||||||
|
<div class="address-nav">
|
||||||
|
<span class="nav-item" :class="{ active: activeAddressTab === 'list' }"
|
||||||
|
@click="switchAddressTab('list')">
|
||||||
|
地址列表
|
||||||
|
</span>
|
||||||
|
<span class="nav-item" :class="{ active: activeAddressTab === 'add' }"
|
||||||
|
@click="switchAddressTab('add')">
|
||||||
|
添加地址
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- 地址列表 -->
|
||||||
|
<div class="address-list" v-if="activeAddressTab === 'list'">
|
||||||
|
<div class="address-item" :class="{ active: selectedAddress.id === address.id }"
|
||||||
|
v-for="address in addresses" :key="address.id" @click="selectAddress(address)">
|
||||||
|
<div class="address-header">
|
||||||
|
<span class="address-name">{{ address.name }}</span>
|
||||||
|
<span class="address-phone">{{ address.phone }}</span>
|
||||||
|
<span v-if="address.isDefault" class="address-tag default">默认</span>
|
||||||
|
</div>
|
||||||
|
<div class="address-detail">
|
||||||
|
{{ address.province }}{{ address.city }}{{ address.district }}{{ address.detail
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="addresses.length === 0" class="empty-address">
|
||||||
|
<p>暂无地址,请添加新地址</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加地址表单 -->
|
||||||
|
<div class="add-address-form" v-if="activeAddressTab === 'add'">
|
||||||
|
<div class="form-item">
|
||||||
|
<label>收货人</label>
|
||||||
|
<input type="text" v-model="newAddressForm.name" placeholder="请输入收货人姓名">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-item">
|
||||||
|
<label>手机号</label>
|
||||||
|
<input type="tel" v-model="newAddressForm.phone" placeholder="请输入手机号码">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-item">
|
||||||
|
<label>所在地区</label>
|
||||||
|
<!-- 省份选择 -->
|
||||||
|
<input type="text" :readonly="true" @click="showAddressComponent = true"
|
||||||
|
v-model="address" placeholder="省市县">
|
||||||
|
</div>
|
||||||
|
<!-- 所在省份被选择后,显示城市选择器 -->
|
||||||
|
<AddressComponent @address="handleAddressChange"
|
||||||
|
v-model:visible="showAddressComponent" />
|
||||||
|
|
||||||
|
<div class="form-item">
|
||||||
|
<label>详细地址</label>
|
||||||
|
<textarea v-model="newAddressForm.detail" placeholder="请输入详细地址信息"
|
||||||
|
rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-item checkbox-item">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" v-model="newAddressForm.isDefault">
|
||||||
|
设为默认地址
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
<!-- 商品信息 -->
|
||||||
|
<div class="settlement-section goods-section">
|
||||||
|
<h3 class="section-title">商品信息</h3>
|
||||||
|
<div class="goods-list">
|
||||||
|
<div v-for="product in products" :key="product.id" class="goods-item">
|
||||||
|
<div class="goods-image">
|
||||||
|
<img :src="product.image" :alt="product.name">
|
||||||
|
</div>
|
||||||
|
<div class="goods-info">
|
||||||
|
<h4 class="goods-name">{{ product.name }}</h4>
|
||||||
|
<p class="goods-desc">{{ product.description }}</p>
|
||||||
|
<div class="goods-price-quantity">
|
||||||
|
<span class="goods-price">¥{{ product.price.toFixed(2) }}</span>
|
||||||
|
<span class="goods-quantity">x{{ product.quantity }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 价格信息 -->
|
||||||
|
<div class="settlement-section price-section">
|
||||||
|
<h3 class="section-title">价格明细</h3>
|
||||||
|
<div class="price-details">
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">商品总价</span>
|
||||||
|
<span class="price-value">¥{{ totalPrice.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">运费</span>
|
||||||
|
<span class="price-value">¥{{ shippingFee.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="price-item discount">
|
||||||
|
<span class="price-label">优惠</span>
|
||||||
|
<span class="price-value discount">-¥{{ discountAmount.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="price-item total">
|
||||||
|
<span class="price-label">实付金额</span>
|
||||||
|
<span class="price-value total">¥{{ actualPrice.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 发票信息 -->
|
||||||
|
<div class="settlement-section invoice-section">
|
||||||
|
<h3 class="section-title">发票信息</h3>
|
||||||
|
<div class="invoice-info">
|
||||||
|
<div class="invoice-type">
|
||||||
|
<label>
|
||||||
|
<input type="radio" v-model="invoiceType" value="personal"> 个人发票
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" v-model="invoiceType" value="company"> 企业发票
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div v-if="invoiceType === 'company'" class="company-info">
|
||||||
|
<input type="text" v-model="companyName" placeholder="请输入企业名称">
|
||||||
|
<input type="text" v-model="taxNumber" placeholder="请输入税号">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧 结算信息 -->
|
||||||
|
<div class="settlement-right">
|
||||||
|
<div class="payment-section">
|
||||||
|
<h3 class="section-title">支付方式</h3>
|
||||||
|
<div class="payment-methods">
|
||||||
|
<div class="payment-item" :class="{ active: selectedPayment === 'balance' }"
|
||||||
|
@click="selectedPayment = 'balance'">
|
||||||
|
<div class="payment-icon balance-icon"></div>
|
||||||
|
<span>余额支付</span>
|
||||||
|
</div>
|
||||||
|
<div class="payment-item" :class="{ active: selectedPayment === 'bank' }"
|
||||||
|
@click="selectedPayment = 'bank'">
|
||||||
|
<div class="payment-icon bank-icon"></div>
|
||||||
|
<span>银行卡</span>
|
||||||
|
</div>
|
||||||
|
<div class="payment-item" :class="{ active: selectedPayment === 'wechat' }"
|
||||||
|
@click="selectedPayment = 'wechat'">
|
||||||
|
<div class="payment-icon wechat-icon"></div>
|
||||||
|
<span>微信支付</span>
|
||||||
|
</div>
|
||||||
|
<div class="payment-item" :class="{ active: selectedPayment === 'alipay' }"
|
||||||
|
@click="selectedPayment = 'alipay'">
|
||||||
|
<div class="payment-icon alipay-icon"></div>
|
||||||
|
<span>支付宝</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 最终价格 -->
|
||||||
|
<div class="settlement-section final-price-section">
|
||||||
|
<div class="settlement-footer">
|
||||||
|
<div class="footer-price">
|
||||||
|
<span class="footer-price-value">¥{{ actualPrice.toFixed(2) }}</span>
|
||||||
|
<span class="footer-price-value discount">共减:¥{{ discountAmount.toFixed(2) }}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="footer-buttons">
|
||||||
|
<a-button type="primary" @click="submit" class="submit-btn">确认订单并支付</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import type { Address, Product } from '@/Util/Type'
|
||||||
|
import AddressComponent from '@/Component/common/Address.vue';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
products: {
|
||||||
|
type: Array as () => Product[],
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['submit', 'update:visible'])
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const visible = ref(false)
|
||||||
|
const addresses = ref<Address[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '张三',
|
||||||
|
phone: '138****8888',
|
||||||
|
province: '北京市',
|
||||||
|
city: '北京市',
|
||||||
|
district: '朝阳区',
|
||||||
|
detail: '三里屯SOHO A座 2001室',
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
id: 1,
|
||||||
|
name: '张三',
|
||||||
|
phone: '138****8888',
|
||||||
|
province: '北京市',
|
||||||
|
city: '北京市',
|
||||||
|
district: '朝阳区',
|
||||||
|
detail: '三里屯SOHO A座 2001室',
|
||||||
|
isDefault: false
|
||||||
|
}
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
const invoiceType = ref('personal')
|
||||||
|
const companyName = ref('')
|
||||||
|
const taxNumber = ref('')
|
||||||
|
const selectedPayment = ref('wechat')
|
||||||
|
const orderRemark = ref('')
|
||||||
|
// 地址弹窗显示状态
|
||||||
|
const showAddressModal = ref(false)
|
||||||
|
// 地址组件显示状态
|
||||||
|
const showAddressComponent = ref(false)
|
||||||
|
//省市区拼接
|
||||||
|
const address = computed(() => {
|
||||||
|
if (newAddressForm.value.province && newAddressForm.value.city && newAddressForm.value.district) {
|
||||||
|
return newAddressForm.value.province + ' / ' + newAddressForm.value.city + ' / ' + newAddressForm.value.district
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 地址弹窗相关数据
|
||||||
|
const activeAddressTab = ref('list') // 'list' 或 'add'
|
||||||
|
const newAddressForm = ref({
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 方法 选择isDefault为true的数据
|
||||||
|
const selectedAddress = ref<Address>(addresses.value.find(addr => addr.isDefault) as Address)
|
||||||
|
|
||||||
|
// 地址组件选择地址后,更新表单数据
|
||||||
|
const handleAddressChange = (address: Address) => {
|
||||||
|
newAddressForm.value.province = address.province
|
||||||
|
newAddressForm.value.city = address.city
|
||||||
|
newAddressForm.value.district = address.district
|
||||||
|
console.log('选择的地址:', address)
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchAddressTab = (tab: string) => {
|
||||||
|
activeAddressTab.value = tab
|
||||||
|
if (tab === 'add') {
|
||||||
|
// 重置表单
|
||||||
|
newAddressForm.value = {
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectAddress = (address: Address) => {
|
||||||
|
showAddressModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const afterCloseAddressModal = () => {
|
||||||
|
// 重置状态
|
||||||
|
activeAddressTab.value = 'list'
|
||||||
|
newAddressForm.value = {
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// 计算属性
|
||||||
|
const totalPrice = computed(() => {
|
||||||
|
return props.products.reduce((sum, product) => sum + product.price * product.quantity, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const shippingFee = computed(() => {
|
||||||
|
return totalPrice.value >= 99 ? 0 : 10
|
||||||
|
})
|
||||||
|
|
||||||
|
const discountAmount = computed(() => {
|
||||||
|
return Math.min(totalPrice.value * 0.1, 50)
|
||||||
|
})
|
||||||
|
|
||||||
|
const actualPrice = computed(() => {
|
||||||
|
return totalPrice.value + shippingFee.value - discountAmount.value
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
console.log('确认结算')
|
||||||
|
console.log('选中的地址:', addresses.value)
|
||||||
|
console.log('选中的支付方式:', selectedPayment.value)
|
||||||
|
console.log('订单备注:', orderRemark.value)
|
||||||
|
console.log('实付金额:', actualPrice.value)
|
||||||
|
|
||||||
|
// 发送结算事件
|
||||||
|
emit('submit', {
|
||||||
|
address: addresses.value,
|
||||||
|
payment: selectedPayment.value,
|
||||||
|
remark: orderRemark.value,
|
||||||
|
totalAmount: actualPrice.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const afterClose = () => {
|
||||||
|
// 重置表单
|
||||||
|
companyName.value = ''
|
||||||
|
taxNumber.value = ''
|
||||||
|
orderRemark.value = ''
|
||||||
|
console.log('afterClose')
|
||||||
|
console.log('visible.value:', visible.value)
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听商品变化
|
||||||
|
watch(() => props.products, (newProducts) => {
|
||||||
|
if (newProducts.length > 0) {
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
// 监听可见性变化
|
||||||
|
watch(() => visible.value, (newVal) => {
|
||||||
|
visible.value = newVal
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.settlement-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-container {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
background-color: var(--bg-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧结算列表 */
|
||||||
|
.settlement-left {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
max-height: 550px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧结算信息 */
|
||||||
|
.settlement-right {
|
||||||
|
width: 350px;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用区块样式 */
|
||||||
|
.settlement-section {
|
||||||
|
margin: var(--spacing-md) 0;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
margin: 0 0 var(--spacing-md);
|
||||||
|
padding-bottom: var(--spacing-xs);
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 地址区块 */
|
||||||
|
.address-item {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-item:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.address-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-phone {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-tag {
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-tag.default {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--bg-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-detail {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-address-btn {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border: 1px dashed var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-address-btn:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
background-color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 地址弹窗样式 */
|
||||||
|
.address-nav {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-bottom-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-list {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-list .address-item {
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-address {
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加地址表单样式 */
|
||||||
|
.add-address-form {
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item input[type="text"],
|
||||||
|
.form-item input[type="tel"],
|
||||||
|
.form-item select,
|
||||||
|
.form-item textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
background-color: var(--bg-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item input[type="text"]:focus,
|
||||||
|
.form-item input[type="tel"]:focus,
|
||||||
|
.form-item select:focus,
|
||||||
|
.form-item textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.form-item.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item.checkbox-item label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item.checkbox-item input[type="checkbox"] {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
padding-top: var(--spacing-lg);
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions button {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: var(--bg-white);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--bg-white);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
border-color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品区块 */
|
||||||
|
.goods-item {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-item:hover {
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
margin: 0 0 var(--spacing-xs);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0 0 var(--spacing-xs);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price-quantity {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-quantity {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 价格区块 */
|
||||||
|
.price-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value {
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-item.discount .price-value {
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-item.total {
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
padding-top: var(--spacing-sm);
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-item.total .price-value {
|
||||||
|
color: var(--error-color);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发票区块 */
|
||||||
|
.invoice-type {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-type label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-info input {
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-info input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 支付方式区块 */
|
||||||
|
.payment-methods {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-item:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-item.active {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
background-color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-icon {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-icon {
|
||||||
|
background-color: var(--info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechat-icon {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alipay-icon {
|
||||||
|
background-color: var(--warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 订单备注区块 */
|
||||||
|
.remark-section textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
font-size: 14px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-section textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 最终价格区块 */
|
||||||
|
.final-price {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.final-price-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.final-price-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
/* 最终价格区块样式 */
|
||||||
|
.settlement-section.final-price-section {
|
||||||
|
padding: 0;
|
||||||
|
margin: 8px 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
/* 底部区块 */
|
||||||
|
.settlement-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-price-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-price-value.discount {
|
||||||
|
margin-left: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
min-width: 300px;
|
||||||
|
height: 48px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.settlement-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-right {
|
||||||
|
width: 100%;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
379
src/Component/common/Address.vue
Normal file
379
src/Component/common/Address.vue
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
<!-- 地址组件 当visible为true时 才会显示类似于弹窗 省>市>县 只有选择前一个后一个选项才会显示 -->
|
||||||
|
<!-- 省市县选择器 返回选中的省市县信息 -->
|
||||||
|
<template>
|
||||||
|
<a-modal :visible.sync="visible" title="选择地址" width="300px" :close-on-click-modal="false" :after-close="close" :footer-extra="null" :footer="null">
|
||||||
|
<div class="address-form-content">
|
||||||
|
<!-- 上下结构 -->
|
||||||
|
<div class="form-tab">
|
||||||
|
<!-- 选项卡 省>市>县只有选择前一个后一个选项才会显示 -->
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'province' }">
|
||||||
|
省份
|
||||||
|
</div>
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'city', disabled: !selectedProvince }">
|
||||||
|
城市
|
||||||
|
</div>
|
||||||
|
<div class="tab-item" :class="{ active: activeTab === 'district', disabled: !selectedCity }">
|
||||||
|
区县
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-content">
|
||||||
|
<!-- 选项卡内容 根据选项卡显示不同的内容 -->
|
||||||
|
<div v-if="activeTab === 'province'" class="tab-content">
|
||||||
|
<div class="region-item" v-for="province in provinces" :key="province.value"
|
||||||
|
@click="selectProvince(province)">
|
||||||
|
{{ province.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="activeTab === 'city'" class="tab-content">
|
||||||
|
<div class="region-item" v-for="city in cities" :key="city.value" @click="selectCity(city)">
|
||||||
|
{{ city.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="activeTab === 'district'" class="tab-content">
|
||||||
|
<div class="region-item" v-for="district in districts" :key="district.value"
|
||||||
|
@click="selectDistrict(district)">
|
||||||
|
{{ district.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:visible', 'address'])
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const activeTab = ref('province')
|
||||||
|
const selectedProvince = ref('')
|
||||||
|
const selectedCity = ref('')
|
||||||
|
const selectedDistrict = ref('')
|
||||||
|
|
||||||
|
// 省份数据
|
||||||
|
const provinces = ref([
|
||||||
|
{ value: '北京市', label: '北京市' },
|
||||||
|
{ value: '上海市', label: '上海市' },
|
||||||
|
{ value: '广东省', label: '广东省' },
|
||||||
|
{ value: '江苏省', label: '江苏省' },
|
||||||
|
{ value: '浙江省', label: '浙江省' },
|
||||||
|
{ value: '四川省', label: '四川省' },
|
||||||
|
{ value: '湖北省', label: '湖北省' },
|
||||||
|
{ value: '湖南省', label: '湖南省' },
|
||||||
|
{ value: '山东省', label: '山东省' },
|
||||||
|
{ value: '河南省', label: '河南省' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 城市数据(根据省份动态生成)
|
||||||
|
const cities = computed(() => {
|
||||||
|
const cityMap: Record<string, Array<{ value: string; label: string }>> = {
|
||||||
|
'北京市': [{ value: '北京市', label: '北京市' }],
|
||||||
|
'上海市': [{ value: '上海市', label: '上海市' }],
|
||||||
|
'广东省': [
|
||||||
|
{ value: '广州市', label: '广州市' },
|
||||||
|
{ value: '深圳市', label: '深圳市' },
|
||||||
|
{ value: '东莞市', label: '东莞市' },
|
||||||
|
{ value: '佛山市', label: '佛山市' },
|
||||||
|
{ value: '珠海市', label: '珠海市' }
|
||||||
|
],
|
||||||
|
'江苏省': [
|
||||||
|
{ value: '南京市', label: '南京市' },
|
||||||
|
{ value: '苏州市', label: '苏州市' },
|
||||||
|
{ value: '无锡市', label: '无锡市' },
|
||||||
|
{ value: '常州市', label: '常州市' },
|
||||||
|
{ value: '南通市', label: '南通市' }
|
||||||
|
],
|
||||||
|
'浙江省': [
|
||||||
|
{ value: '杭州市', label: '杭州市' },
|
||||||
|
{ value: '宁波市', label: '宁波市' },
|
||||||
|
{ value: '温州市', label: '温州市' },
|
||||||
|
{ value: '嘉兴市', label: '嘉兴市' },
|
||||||
|
{ value: '湖州市', label: '湖州市' }
|
||||||
|
],
|
||||||
|
'四川省': [
|
||||||
|
{ value: '成都市', label: '成都市' },
|
||||||
|
{ value: '绵阳市', label: '绵阳市' },
|
||||||
|
{ value: '德阳市', label: '德阳市' },
|
||||||
|
{ value: '自贡市', label: '自贡市' },
|
||||||
|
{ value: '泸州市', label: '泸州市' }
|
||||||
|
],
|
||||||
|
'湖北省': [
|
||||||
|
{ value: '武汉市', label: '武汉市' },
|
||||||
|
{ value: '黄石市', label: '黄石市' },
|
||||||
|
{ value: '十堰市', label: '十堰市' },
|
||||||
|
{ value: '宜昌市', label: '宜昌市' },
|
||||||
|
{ value: '襄阳市', label: '襄阳市' }
|
||||||
|
],
|
||||||
|
'湖南省': [
|
||||||
|
{ value: '长沙市', label: '长沙市' },
|
||||||
|
{ value: '株洲市', label: '株洲市' },
|
||||||
|
{ value: '湘潭市', label: '湘潭市' },
|
||||||
|
{ value: '衡阳市', label: '衡阳市' },
|
||||||
|
{ value: '邵阳市', label: '邵阳市' }
|
||||||
|
],
|
||||||
|
'山东省': [
|
||||||
|
{ value: '济南市', label: '济南市' },
|
||||||
|
{ value: '青岛市', label: '青岛市' },
|
||||||
|
{ value: '烟台市', label: '烟台市' },
|
||||||
|
{ value: '潍坊市', label: '潍坊市' },
|
||||||
|
{ value: '临沂市', label: '临沂市' }
|
||||||
|
],
|
||||||
|
'河南省': [
|
||||||
|
{ value: '郑州市', label: '郑州市' },
|
||||||
|
{ value: '开封市', label: '开封市' },
|
||||||
|
{ value: '洛阳市', label: '洛阳市' },
|
||||||
|
{ value: '平顶山市', label: '平顶山市' },
|
||||||
|
{ value: '安阳市', label: '安阳市' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return cityMap[selectedProvince.value] || []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 区县数据(根据城市动态生成)
|
||||||
|
const districts = computed(() => {
|
||||||
|
const districtMap: Record<string, Array<{ value: string; label: string }>> = {
|
||||||
|
'北京市': [
|
||||||
|
{ value: '东城区', label: '东城区' },
|
||||||
|
{ value: '西城区', label: '西城区' },
|
||||||
|
{ value: '朝阳区', label: '朝阳区' },
|
||||||
|
{ value: '海淀区', label: '海淀区' },
|
||||||
|
{ value: '丰台区', label: '丰台区' }
|
||||||
|
],
|
||||||
|
'上海市': [
|
||||||
|
{ value: '黄浦区', label: '黄浦区' },
|
||||||
|
{ value: '徐汇区', label: '徐汇区' },
|
||||||
|
{ value: '长宁区', label: '长宁区' },
|
||||||
|
{ value: '静安区', label: '静安区' },
|
||||||
|
{ value: '普陀区', label: '普陀区' }
|
||||||
|
],
|
||||||
|
'广州市': [
|
||||||
|
{ value: '天河区', label: '天河区' },
|
||||||
|
{ value: '越秀区', label: '越秀区' },
|
||||||
|
{ value: '海珠区', label: '海珠区' },
|
||||||
|
{ value: '荔湾区', label: '荔湾区' },
|
||||||
|
{ value: '白云区', label: '白云区' }
|
||||||
|
],
|
||||||
|
'深圳市': [
|
||||||
|
{ value: '福田区', label: '福田区' },
|
||||||
|
{ value: '罗湖区', label: '罗湖区' },
|
||||||
|
{ value: '南山区', label: '南山区' },
|
||||||
|
{ value: '宝安区', label: '宝安区' },
|
||||||
|
{ value: '龙岗区', label: '龙岗区' }
|
||||||
|
],
|
||||||
|
'南京市': [
|
||||||
|
{ value: '玄武区', label: '玄武区' },
|
||||||
|
{ value: '秦淮区', label: '秦淮区' },
|
||||||
|
{ value: '建邺区', label: '建邺区' },
|
||||||
|
{ value: '鼓楼区', label: '鼓楼区' },
|
||||||
|
{ value: '浦口区', label: '浦口区' }
|
||||||
|
],
|
||||||
|
'苏州市': [
|
||||||
|
{ value: '姑苏区', label: '姑苏区' },
|
||||||
|
{ value: '虎丘区', label: '虎丘区' },
|
||||||
|
{ value: '吴中区', label: '吴中区' },
|
||||||
|
{ value: '相城区', label: '相城区' },
|
||||||
|
{ value: '吴江区', label: '吴江区' }
|
||||||
|
],
|
||||||
|
'杭州市': [
|
||||||
|
{ value: '上城区', label: '上城区' },
|
||||||
|
{ value: '下城区', label: '下城区' },
|
||||||
|
{ value: '江干区', label: '江干区' },
|
||||||
|
{ value: '拱墅区', label: '拱墅区' },
|
||||||
|
{ value: '西湖区', label: '西湖区' }
|
||||||
|
],
|
||||||
|
'宁波市': [
|
||||||
|
{ value: '海曙区', label: '海曙区' },
|
||||||
|
{ value: '江北区', label: '江北区' },
|
||||||
|
{ value: '北仑区', label: '北仑区' },
|
||||||
|
{ value: '镇海区', label: '镇海区' },
|
||||||
|
{ value: '鄞州区', label: '鄞州区' }
|
||||||
|
],
|
||||||
|
'成都市': [
|
||||||
|
{ value: '锦江区', label: '锦江区' },
|
||||||
|
{ value: '青羊区', label: '青羊区' },
|
||||||
|
{ value: '金牛区', label: '金牛区' },
|
||||||
|
{ value: '武侯区', label: '武侯区' },
|
||||||
|
{ value: '成华区', label: '成华区' }
|
||||||
|
],
|
||||||
|
'武汉市': [
|
||||||
|
{ value: '江岸区', label: '江岸区' },
|
||||||
|
{ value: '江汉区', label: '江汉区' },
|
||||||
|
{ value: '硚口区', label: '硚口区' },
|
||||||
|
{ value: '汉阳区', label: '汉阳区' },
|
||||||
|
{ value: '武昌区', label: '武昌区' }
|
||||||
|
],
|
||||||
|
'长沙市': [
|
||||||
|
{ value: '芙蓉区', label: '芙蓉区' },
|
||||||
|
{ value: '天心区', label: '天心区' },
|
||||||
|
{ value: '岳麓区', label: '岳麓区' },
|
||||||
|
{ value: '开福区', label: '开福区' },
|
||||||
|
{ value: '雨花区', label: '雨花区' }
|
||||||
|
],
|
||||||
|
'济南市': [
|
||||||
|
{ value: '历下区', label: '历下区' },
|
||||||
|
{ value: '市中区', label: '市中区' },
|
||||||
|
{ value: '槐荫区', label: '槐荫区' },
|
||||||
|
{ value: '天桥区', label: '天桥区' },
|
||||||
|
{ value: '历城区', label: '历城区' }
|
||||||
|
],
|
||||||
|
'郑州市': [
|
||||||
|
{ value: '中原区', label: '中原区' },
|
||||||
|
{ value: '二七区', label: '二七区' },
|
||||||
|
{ value: '管城回族区', label: '管城回族区' },
|
||||||
|
{ value: '金水区', label: '金水区' },
|
||||||
|
{ value: '上街区', label: '上街区' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return districtMap[selectedCity.value] || []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const selectProvince = (province: { value: string; label: string }) => {
|
||||||
|
selectedProvince.value = province.value
|
||||||
|
selectedCity.value = ''
|
||||||
|
selectedDistrict.value = ''
|
||||||
|
activeTab.value = 'city'
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectCity = (city: { value: string; label: string }) => {
|
||||||
|
selectedCity.value = city.value
|
||||||
|
selectedDistrict.value = ''
|
||||||
|
activeTab.value = 'district'
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectDistrict = (district: { value: string; label: string }) => {
|
||||||
|
selectedDistrict.value = district.value
|
||||||
|
// 完成选择,提交结果
|
||||||
|
emit('address', {
|
||||||
|
province: selectedProvince.value,
|
||||||
|
city: selectedCity.value,
|
||||||
|
district: selectedDistrict.value
|
||||||
|
})
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
// 重置状态
|
||||||
|
activeTab.value = 'province'
|
||||||
|
selectedProvince.value = ''
|
||||||
|
selectedCity.value = ''
|
||||||
|
selectedDistrict.value = ''
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.address-form-content {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999;
|
||||||
|
padding: 0;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-tab {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
flex: 1;
|
||||||
|
padding: 14px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
color: #1890ff;
|
||||||
|
border-bottom-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.disabled {
|
||||||
|
color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-item {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-item:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tab-content {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tab-content {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-content {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-item {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
444
src/Component/common/Search.vue
Normal file
444
src/Component/common/Search.vue
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
<!-- 搜索组件 -->
|
||||||
|
<template>
|
||||||
|
<div class="search-component">
|
||||||
|
<!-- 搜索类型选择 -->
|
||||||
|
<div v-if="false" class="search-type-selector">
|
||||||
|
<ul>
|
||||||
|
<li class="search-type-item" :class="{ active: currentSearchType === '宝贝' }"
|
||||||
|
@click="setSearchType('宝贝', $event)">
|
||||||
|
宝贝
|
||||||
|
</li>
|
||||||
|
<li class="search-type-item" :class="{ active: currentSearchType === '店铺' }"
|
||||||
|
@click="setSearchType('店铺', $event)">
|
||||||
|
店铺
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索输入区域 -->
|
||||||
|
<div class="search-input-wrapper">
|
||||||
|
<div class="search-input-prefix">
|
||||||
|
<i class="iconfont icon-sousuo prefix-icon"></i>
|
||||||
|
</div>
|
||||||
|
<Input v-model:value="searchValue" placeholder="商品昵称/商品编号/订单编号" style="width: 100%" @search="onSearch"
|
||||||
|
@focus="onInputFocus" @blur="onInputBlur" class="custom-search-input" />
|
||||||
|
<div class="search-input-suffix" v-if="searchValue">
|
||||||
|
<i class="iconfont icon-guanbi" @click="clearSearch"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索按钮 -->
|
||||||
|
<div class="search-button-container" @click="onSearch()">
|
||||||
|
<span class="search-button-text">搜索</span>
|
||||||
|
<div class="search-button-icon">
|
||||||
|
<i class="iconfont icon-sousuo"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索历史记录 -->
|
||||||
|
<div class="search-history-container" v-if="showHistory">
|
||||||
|
<div class="search-history-header">
|
||||||
|
<span class="history-title">搜索历史</span>
|
||||||
|
<a class="clear-history" @click="clearHistory" href="javascript:void(0)">
|
||||||
|
清空历史
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="search-history-tags" v-if="historyList.length > 0">
|
||||||
|
<Tag v-for="(item, index) in historyList" :key="index" class="history-tag"
|
||||||
|
@click="onClickHistory(item)">
|
||||||
|
{{ item }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
<div class="no-history" v-else>
|
||||||
|
暂无搜索历史
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { Input, Tag } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const emit = defineEmits(['search'])
|
||||||
|
// 搜索关键词
|
||||||
|
const searchValue = ref('')
|
||||||
|
// 控制历史记录显示状态
|
||||||
|
const showHistory = ref(false)
|
||||||
|
// 标志:是否正在清空历史记录
|
||||||
|
const isClearingHistory = ref(false)
|
||||||
|
// 当前搜索类型
|
||||||
|
const currentSearchType = ref('宝贝')
|
||||||
|
// 搜索历史记录
|
||||||
|
const historyList = ref(['手机', '电脑', '耳机', '键盘', '鼠标'])
|
||||||
|
|
||||||
|
// 搜索事件
|
||||||
|
const onSearch = (value?: string) => {
|
||||||
|
const keyword = value || searchValue.value
|
||||||
|
if (!keyword.trim()) return
|
||||||
|
|
||||||
|
console.log('搜索关键词:', keyword)
|
||||||
|
console.log('搜索类型:', currentSearchType.value)
|
||||||
|
|
||||||
|
// 添加到历史记录
|
||||||
|
// if (!historyList.value.includes(keyword)) {
|
||||||
|
// historyList.value.unshift(keyword)
|
||||||
|
// // 限制历史记录数量
|
||||||
|
// if (historyList.value.length > 10) {
|
||||||
|
// historyList.value.pop()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 跳转到搜索结果页面
|
||||||
|
// router.push({
|
||||||
|
// name: 'search',
|
||||||
|
// query: {
|
||||||
|
// keyword: keyword,
|
||||||
|
// type: currentSearchType.value
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
emit('search', keyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索框获得焦点
|
||||||
|
const onInputFocus = () => {
|
||||||
|
showHistory.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索框失去焦点
|
||||||
|
const onInputBlur = () => {
|
||||||
|
// 使用setTimeout确保点击历史记录的事件能先执行
|
||||||
|
setTimeout(() => {
|
||||||
|
// 如果正在清空历史记录,则不隐藏历史记录容器
|
||||||
|
if (!isClearingHistory.value) {
|
||||||
|
showHistory.value = false
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置搜索类型
|
||||||
|
const setSearchType = (type: string, event: MouseEvent) => {
|
||||||
|
const typeItems = document.querySelectorAll('.search-type-item')
|
||||||
|
// 找到当前活跃的元素
|
||||||
|
const currentActiveItem = document.querySelector('.search-type-item.active')
|
||||||
|
// 保存点击的元素引用
|
||||||
|
const clickedTarget = event.currentTarget as HTMLElement
|
||||||
|
|
||||||
|
// 为当前活跃的元素添加消失动画
|
||||||
|
if (currentActiveItem && currentActiveItem !== clickedTarget) {
|
||||||
|
if (currentSearchType.value === '宝贝' && type === '店铺') {
|
||||||
|
// 宝贝 -> 店铺:宝贝元素从左往右消失
|
||||||
|
currentActiveItem.classList.add('animate-right-out')
|
||||||
|
} else if (currentSearchType.value === '店铺' && type === '宝贝') {
|
||||||
|
// 店铺 -> 宝贝:店铺元素从右往左消失
|
||||||
|
currentActiveItem.classList.add('animate-left-out')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待消失动画完成后再继续
|
||||||
|
setTimeout(() => {
|
||||||
|
// 移除所有元素的类
|
||||||
|
typeItems.forEach(item => {
|
||||||
|
item.classList.remove('active', 'animate-left', 'animate-right', 'animate-left-out', 'animate-right-out')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加active类到点击的元素
|
||||||
|
if (clickedTarget) {
|
||||||
|
// 判断切换方向
|
||||||
|
if (currentSearchType.value === '宝贝' && type === '店铺') {
|
||||||
|
// 宝贝 -> 店铺:从左往右出现
|
||||||
|
clickedTarget.classList.add('active', 'animate-right')
|
||||||
|
} else if (currentSearchType.value === '店铺' && type === '宝贝') {
|
||||||
|
// 店铺 -> 宝贝:从右往左出现
|
||||||
|
clickedTarget.classList.add('active', 'animate-left')
|
||||||
|
} else {
|
||||||
|
// 默认动画
|
||||||
|
clickedTarget.classList.add('active')
|
||||||
|
}
|
||||||
|
currentSearchType.value = type
|
||||||
|
}
|
||||||
|
console.log('搜索类型:', type)
|
||||||
|
|
||||||
|
}, 300)
|
||||||
|
} else {
|
||||||
|
// 如果点击的是当前活跃的元素,直接返回
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除搜索输入
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchValue.value = ''
|
||||||
|
console.log('已清除搜索输入')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击历史记录
|
||||||
|
const onClickHistory = (item: string) => {
|
||||||
|
searchValue.value = item
|
||||||
|
onSearch(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空历史记录
|
||||||
|
const clearHistory = () => {
|
||||||
|
isClearingHistory.value = true
|
||||||
|
historyList.value = []
|
||||||
|
// 300ms后重置标志,确保onInputBlur的200ms延迟执行完毕
|
||||||
|
setTimeout(() => {
|
||||||
|
isClearingHistory.value = false
|
||||||
|
}, 300)
|
||||||
|
console.log('已清空搜索历史')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-component {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索类型选择 */
|
||||||
|
.search-type-selector {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-type-selector ul {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-type-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-type-item:hover {
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-type-item.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
.animate-left {
|
||||||
|
animation: slideInLeft 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-right {
|
||||||
|
animation: slideInRight 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-left-out {
|
||||||
|
animation: slideOutLeft 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-right-out {
|
||||||
|
animation: slideOutRight 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
transform: translateX(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutLeft {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索输入区域 */
|
||||||
|
.search-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bg-white);
|
||||||
|
border: 2px solid var(--border-light);
|
||||||
|
border-right: none;
|
||||||
|
border-radius: var(--border-radius-md) 0 0 var(--border-radius-md);
|
||||||
|
padding: 0 12px 0 5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper:focus-within {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-prefix {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefix-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-input {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-input:focus {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-suffix {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-suffix:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索按钮 */
|
||||||
|
.search-button-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--bg-white);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0 var(--border-radius-md) var(--border-radius-md) 0;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button-container:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button-text {
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索历史记录 */
|
||||||
|
.search-history-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--bg-white);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
padding: 16px;
|
||||||
|
margin-top: 4px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-history-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-history {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-history:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-history-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tag:hover {
|
||||||
|
background-color: var(--primary-light) !important;
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-history {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.search-type-selector {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-type-item {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
63
src/Component/common/page.vue
Normal file
63
src/Component/common/page.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<!-- 分页组件 -->
|
||||||
|
<template>
|
||||||
|
<div class="pagination">
|
||||||
|
<ul>
|
||||||
|
<li v-for="page in totalPages" :key="page">
|
||||||
|
<a href="#" @click="changePage(page)">{{ page }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
totalPages: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['changePage'])
|
||||||
|
|
||||||
|
const changePage = (page: number) => {
|
||||||
|
emit('changePage', page)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination ul {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination ul li {
|
||||||
|
margin: 0 var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination ul li a {
|
||||||
|
display: block;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
transition: all var(--transition-normal) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination ul li a:hover {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Main from "@/Views/Main.vue";
|
import Main from "@/views/main.vue";
|
||||||
import Footer from "@/Views/Footer.vue";
|
import Footer from "@/views/footer.vue";
|
||||||
</script>
|
</script>
|
||||||
18
src/Component/layout/MainLayout.vue
Normal file
18
src/Component/layout/MainLayout.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 主布局组件 -->
|
||||||
|
<div class="main-layout">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="header">
|
||||||
|
<herde></herde>
|
||||||
|
</div>
|
||||||
|
<!-- 路由出口 -->
|
||||||
|
<div class="content">
|
||||||
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from "vue-router";
|
||||||
|
import herde from '@/views/herde.vue'
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,51 +1,105 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
// 导入组件
|
// 导入组件
|
||||||
import Home from '../Views/Home.vue'
|
|
||||||
import Login from '../Views/Login.vue'
|
|
||||||
import Search from '../Views/Search.vue'
|
|
||||||
import ProductDetail from '../Views/product/productdetil.vue'
|
|
||||||
import Cart from '../Views/Cart.vue'
|
|
||||||
import Order from '../Views/Order.vue'
|
|
||||||
|
|
||||||
|
const Home = () => import('../Component/layout/Home.vue')
|
||||||
|
const Login = () => import('../views/Login.vue')
|
||||||
|
const Search = () => import('../views/Search.vue')
|
||||||
|
const ProductDetail = () => import('../views/product/productdetil.vue')
|
||||||
|
const Cart = () => import('../views/Cart.vue')
|
||||||
|
const User = () => import('../views/User/User.vue')
|
||||||
|
const Chat = () => import('../views/Chat.vue')
|
||||||
|
const MainLayout = () => import('../Component/layout/MainLayout.vue')
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
// 重定向
|
||||||
path: '/',
|
{ path: '/', redirect: '/home' },
|
||||||
name: 'home',
|
// 登录路由
|
||||||
component: Home
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/search',
|
|
||||||
name: 'search',
|
|
||||||
component: Search
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/product',
|
|
||||||
name: 'productDetail',
|
|
||||||
component: ProductDetail
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Login
|
component: Login,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
component: Login
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/cart',
|
path: '',
|
||||||
name: 'cart',
|
name: 'mainLayout',
|
||||||
component: Cart
|
component: MainLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
name: 'home',
|
||||||
|
component: Home,
|
||||||
|
meta: {
|
||||||
|
title: '首页'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/search',
|
||||||
|
name: 'search',
|
||||||
|
component: Search
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/product',
|
||||||
|
name: 'productDetail',
|
||||||
|
component: ProductDetail
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/cart',
|
||||||
|
name: 'cart',
|
||||||
|
component: Cart
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/user',
|
||||||
|
name: 'user',
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '用户中心'
|
||||||
|
},
|
||||||
|
component: User
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/chat',
|
||||||
|
name: 'chat',
|
||||||
|
component: Chat
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
// 404页面 - 匹配所有未定义的路由
|
||||||
{
|
{
|
||||||
path: '/order',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'order',
|
name: 'not-found',
|
||||||
component: Order
|
component: () => import('../views/404/index.vue'),
|
||||||
},
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
title: '页面不存在'
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
// 路由守卫 - 检查是否需要登录
|
||||||
|
// 如果需要登录,且没有登录,重定向到登录页
|
||||||
|
// 如果没有登录,继续导航
|
||||||
|
// 如果需要登录,且已登录,继续导航
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.meta.requiresAuth) {
|
||||||
|
if (!localStorage.getItem('token')) {
|
||||||
|
next({ name: 'login' })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ api.interceptors.request.use(
|
|||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
// 返回响应数据
|
// 返回响应数据
|
||||||
return response.data
|
return response
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
// 处理响应错误
|
// 处理响应错误
|
||||||
|
|||||||
77
src/Service/OrderService.ts
Normal file
77
src/Service/OrderService.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import api from "./ApiService";
|
||||||
|
import type { Order, OrderRequest, ApiResponse, PageRequest } from "@/Util/Type";
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
// 获取订单详情
|
||||||
|
getOrderInfo(orderId: number, userId: number) {
|
||||||
|
return api.post<ApiResponse<Order>>('/orders/getorderinfo', {
|
||||||
|
id: orderId,
|
||||||
|
userId: userId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建订单
|
||||||
|
createOrder(params: OrderRequest) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/orders/create', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新订单信息
|
||||||
|
updateOrder(params: OrderRequest) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/orders/update', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除订单
|
||||||
|
deleteOrder(orderId: number, shopId: number) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/orders/delete', {
|
||||||
|
id: orderId,
|
||||||
|
shopId: shopId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询订单
|
||||||
|
listOrdersByPage(page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Order[]>>('/orders/list', {
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据用户ID查询订单
|
||||||
|
getOrdersByUser(userId: number, page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Order[]>>('/orders/byuser', {
|
||||||
|
userId: userId,
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据店铺ID查询订单
|
||||||
|
getOrdersByShop(shopId: number, page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Order[]>>('/orders/byshop', {
|
||||||
|
shopId: shopId,
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据订单状态查询订单
|
||||||
|
getOrdersByStatus(status: number, shopId: number, page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Order[]>>('/orders/bystatus', {
|
||||||
|
orderStatus: status,
|
||||||
|
shopId: shopId,
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新订单状态
|
||||||
|
updateOrderStatus(orderId: number, status: number, shopId: number) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/orders/updatestatus', {
|
||||||
|
id: orderId,
|
||||||
|
orderStatus: status,
|
||||||
|
shopId: shopId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new OrderService()
|
||||||
56
src/Service/ProductService.ts
Normal file
56
src/Service/ProductService.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import api from "./ApiService";
|
||||||
|
import type { Product, ProductRequest, ApiResponse, PageRequest } from "@/Util/Type";
|
||||||
|
|
||||||
|
class ProductService {
|
||||||
|
// 获取商品详情
|
||||||
|
getProductInfo(productId: number) {
|
||||||
|
return api.post<ApiResponse<Product>>('/products/getproductinfo', {
|
||||||
|
id: productId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除商品
|
||||||
|
deleteProduct(productId: number) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/products/delete', {
|
||||||
|
id: productId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询商品
|
||||||
|
listProductsByPage(page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Product[]>>('/products/list', {
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据分类ID查询商品
|
||||||
|
getProductsByCategory(categoryId: number, page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Product[]>>('/products/bycategory', {
|
||||||
|
categoryId: categoryId,
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据店铺ID查询商品
|
||||||
|
getProductsByShop(shopId: number, page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Product[]>>('/products/byshop', {
|
||||||
|
shopId: shopId,
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索商品
|
||||||
|
searchProducts(keyword: string, page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<Product[]>>('/products/search', {
|
||||||
|
keyword: keyword,
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ProductService()
|
||||||
26
src/Service/ShopService.ts
Normal file
26
src/Service/ShopService.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import api from "./ApiService";
|
||||||
|
import type { Shop, ShopRequest, ShopResponse, ApiResponse } from "@/Util/Type";
|
||||||
|
|
||||||
|
class ShopService {
|
||||||
|
// 获取店铺信息
|
||||||
|
getShopInfo(shopId: number) {
|
||||||
|
return api.post<ApiResponse<Shop>>('/shop/info', {
|
||||||
|
id: shopId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 根据店铺Id获取店铺信息
|
||||||
|
getShopInfoById(shopId: number) {
|
||||||
|
return api.post<ApiResponse<Shop>>('/shop/info', {
|
||||||
|
id: shopId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 分页查询店铺
|
||||||
|
getShopsPage(page: number = 1, size: number = 10) {
|
||||||
|
return api.post<ApiResponse<ShopResponse[]>>('/shop/page', {
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ShopService()
|
||||||
@@ -1,12 +1,54 @@
|
|||||||
import api from "./ApiService";
|
import api from "./ApiService";
|
||||||
import router from '../Route/route'
|
import router from '../Route/route'
|
||||||
import type { Login } from "@/Util/Type";
|
import type { Login, User, UserRequest, LoginResponse, ApiResponse } from "@/Util/Type";
|
||||||
|
|
||||||
class UserService {
|
class UserService {
|
||||||
|
// 登录
|
||||||
login(params: Login) {
|
login(params: Login) {
|
||||||
return api.post('/auth/login', {
|
return api.post<ApiResponse<LoginResponse>>('/auth/login', {
|
||||||
username: params.username,
|
username: params.username,
|
||||||
password: params.password
|
password: params.password
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
getUserInfo(userId: number) {
|
||||||
|
return api.post<ApiResponse<User>>('/user/getuserinfo', {
|
||||||
|
id: userId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
updateUserInfo(params: UserRequest) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/user/info', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注销登录
|
||||||
|
logout(userId: number, status: number = 2) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/user/logout', {
|
||||||
|
id: userId,
|
||||||
|
status: status
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密码
|
||||||
|
resetPassword(userId: number, newPassword: string) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/user/resetpassword', {
|
||||||
|
id: userId,
|
||||||
|
password: newPassword
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册用户
|
||||||
|
register(params: {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
avatar?: string
|
||||||
|
}) {
|
||||||
|
return api.post<ApiResponse<boolean>>('/user/register', params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new UserService()
|
export default new UserService()
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/* 全局样式 */
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #000;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
254
src/Util/Type.ts
254
src/Util/Type.ts
@@ -1,18 +1,233 @@
|
|||||||
// 项目中使用的类型定义
|
// 项目中使用的类型定义
|
||||||
|
|
||||||
|
// 响应结果类型
|
||||||
|
export type ApiResponse<T> = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录响应类型
|
||||||
|
export type LoginResponse = {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
roles: string[]
|
||||||
|
permissions: string[]
|
||||||
|
token: string
|
||||||
|
tokenType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户响应类型
|
||||||
|
export type UserResponse = {
|
||||||
|
users: User
|
||||||
|
roles: string[]
|
||||||
|
permissions: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户类型
|
||||||
|
export type User = {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
password?: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
avatar?: string
|
||||||
|
status?: number
|
||||||
|
createTime?: string
|
||||||
|
updateTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户请求类型
|
||||||
|
export type UserRequest = {
|
||||||
|
id: number
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
avatar?: string
|
||||||
|
status?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品类型
|
||||||
|
export type Product = {
|
||||||
|
id: number
|
||||||
|
productName: string
|
||||||
|
shopId: number
|
||||||
|
categoryId: number
|
||||||
|
description: string
|
||||||
|
originalPrice: number
|
||||||
|
currentPrice: number
|
||||||
|
stock: number
|
||||||
|
status: number
|
||||||
|
mainImage: string
|
||||||
|
salesCount: number
|
||||||
|
createTime?: string
|
||||||
|
updateTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品响应类型
|
||||||
|
export type ProductsResponse = {
|
||||||
|
products: Product
|
||||||
|
models?: ProductModel[]
|
||||||
|
images?: ProductImage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品型号
|
||||||
|
export type ProductModel = {
|
||||||
|
id: number
|
||||||
|
productId: number
|
||||||
|
modelName: string
|
||||||
|
modelPrice: number
|
||||||
|
modelStock: number
|
||||||
|
modelStatus: number
|
||||||
|
modelImage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品img
|
||||||
|
export type ProductImage = {
|
||||||
|
id: number
|
||||||
|
productId: number
|
||||||
|
imageUrl: string
|
||||||
|
sort: number
|
||||||
|
isMain: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品属性值
|
||||||
|
export type AttributeValues = {
|
||||||
|
id: number
|
||||||
|
attributeId: number
|
||||||
|
attributeValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品请求类型
|
||||||
|
export type ProductRequest = {
|
||||||
|
id?: number
|
||||||
|
productName?: string
|
||||||
|
shopId?: number
|
||||||
|
categoryId?: number
|
||||||
|
description?: string
|
||||||
|
originalPrice?: number
|
||||||
|
currentPrice?: number
|
||||||
|
status?: number
|
||||||
|
mainImage?: string
|
||||||
|
keyword?: string
|
||||||
|
page?: number
|
||||||
|
size?: number
|
||||||
|
ids?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 商品详情类型
|
// 商品详情类型
|
||||||
export type ProductDetail = {
|
export type ProductDetail = {
|
||||||
id: string
|
product: Product
|
||||||
name: string
|
models: ProductModel[]
|
||||||
price: string
|
shop: Shop
|
||||||
img: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 店铺类型
|
||||||
|
export type Shop = {
|
||||||
|
id: number
|
||||||
|
shopName: string
|
||||||
|
shopOwner: number
|
||||||
|
shopDescription: string
|
||||||
|
shopLogo: string
|
||||||
|
status: number
|
||||||
|
}
|
||||||
|
|
||||||
// 店铺详情类型
|
// 店铺详情类型
|
||||||
export type ShopDetail = {
|
export type ShopDetail = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
img: string
|
img: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 店铺请求类型
|
||||||
|
export type ShopRequest = {
|
||||||
|
id?: number
|
||||||
|
page?: number
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 店铺响应类型
|
||||||
|
export type ShopResponse = {
|
||||||
|
shop: Shop
|
||||||
|
products: ProductsResponse[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单商品类型
|
||||||
|
export type OrderItem = {
|
||||||
|
id?: number
|
||||||
|
orderId: number
|
||||||
|
productId: number
|
||||||
|
quantity: number
|
||||||
|
price: number
|
||||||
|
productName: string
|
||||||
|
productImage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单类型
|
||||||
|
export type Order = {
|
||||||
|
id: number
|
||||||
|
orderNo: string
|
||||||
|
userId: number
|
||||||
|
shopId: number
|
||||||
|
totalAmount: number
|
||||||
|
actualAmount: number
|
||||||
|
shippingFee: number
|
||||||
|
orderStatus: number
|
||||||
|
shippingAddress: string
|
||||||
|
receiverName: string
|
||||||
|
receiverPhone: string
|
||||||
|
paymentMethod: string
|
||||||
|
paymentTime?: string
|
||||||
|
createTime?: string
|
||||||
|
updateTime?: string
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单详情类型
|
||||||
|
export type OrderDetail = {
|
||||||
|
id: string // 订单 ID
|
||||||
|
shopName: string // 店铺名称
|
||||||
|
receiverName: string // 收货人
|
||||||
|
orderId: string // 订单 ID
|
||||||
|
createTime: string // 创建时间
|
||||||
|
status: string // 订单状态
|
||||||
|
statusText: string // 订单状态文本
|
||||||
|
totalAmount: number // 订单总金额
|
||||||
|
paymentMethod: string // 支付方式
|
||||||
|
goods: Product[] // 商品列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单请求类型
|
||||||
|
export type OrderRequest = {
|
||||||
|
id?: number
|
||||||
|
orderNo?: string
|
||||||
|
userId?: number
|
||||||
|
shopId?: number
|
||||||
|
totalAmount?: number
|
||||||
|
actualAmount?: number
|
||||||
|
shippingFee?: number
|
||||||
|
orderStatus?: number
|
||||||
|
shippingAddress?: string
|
||||||
|
receiverName?: string
|
||||||
|
receiverPhone?: string
|
||||||
|
paymentMethod?: string
|
||||||
|
remark?: string
|
||||||
|
page?: number
|
||||||
|
size?: number
|
||||||
|
orderItems?: OrderItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页请求类型
|
||||||
|
export type PageRequest = {
|
||||||
|
page: number
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
// 购物车类型
|
// 购物车类型
|
||||||
export type Cart = {
|
export type Cart = {
|
||||||
id: string
|
id: string
|
||||||
@@ -22,31 +237,28 @@ export type Cart = {
|
|||||||
count: number
|
count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 商品类型
|
|
||||||
export type Product = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
price: string
|
|
||||||
img: string
|
|
||||||
model: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 订单详情类型
|
|
||||||
export type OrderDetail = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
price: string
|
|
||||||
img: string
|
|
||||||
count: number
|
|
||||||
}
|
|
||||||
// 注册类型
|
// 注册类型
|
||||||
export type Register = {
|
export type Register = {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
confirmPassword: string
|
confirmPassword: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录类型
|
// 登录类型
|
||||||
export type Login = {
|
export type Login = {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
|
rememberMe: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址类型
|
||||||
|
export type Address = {
|
||||||
|
id: number; // 修改时有 ID,新增时无
|
||||||
|
name: string; // 收货人
|
||||||
|
phone: string; // 手机号
|
||||||
|
province: string; // 省
|
||||||
|
city: string; // 市
|
||||||
|
district: string; // 区
|
||||||
|
detail: string; // 详细地址(街道、门牌号等)
|
||||||
|
isDefault: boolean; // 是否默认地址
|
||||||
}
|
}
|
||||||
119
src/Views/404/index.vue
Normal file
119
src/Views/404/index.vue
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<div id="error-404">
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="error-code">404</div>
|
||||||
|
<div class="error-title">页面未找到</div>
|
||||||
|
<div class="error-description">抱歉,您访问的页面不存在或已被移除</div>
|
||||||
|
<div class="error-actions">
|
||||||
|
<Button type="primary" size="large" @click="router.push('/home')">
|
||||||
|
<i class="iconfont icon-shouye"></i> 返回首页
|
||||||
|
</Button>
|
||||||
|
<Button size="large" @click="handleGoBack">
|
||||||
|
<i class="iconfont icon-fanhui"></i> 返回上一页
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const handleGoBack = () => {
|
||||||
|
if (window.history.length > 1) {
|
||||||
|
router.back()
|
||||||
|
} else {
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#error-404 {
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100vh - 200px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 600px;
|
||||||
|
padding: var(--spacing-3xl);
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-radius: var(--border-radius-2xl);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
font-size: 120px;
|
||||||
|
font-weight: 800;
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-description {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.error-container {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
font-size: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-description {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-actions Button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
111
src/Views/App.css
Normal file
111
src/Views/App.css
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/* 全局样式 */
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 var(--spacing-lg);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
/* 主色调 */
|
||||||
|
--primary-color: #ff5000;
|
||||||
|
--primary-hover: #ff7a45;
|
||||||
|
--primary-active: #ff3a00;
|
||||||
|
--primary-light: rgba(255, 80, 0, 0.05);
|
||||||
|
--primary-light-rgb: 255, 80, 0;
|
||||||
|
|
||||||
|
/* 辅助色 */
|
||||||
|
--secondary-color: #1890ff;
|
||||||
|
--secondary-hover: #40a9ff;
|
||||||
|
--success-color: #52c41a;
|
||||||
|
--success-light: #f6ffed;
|
||||||
|
--warning-color: #faad14;
|
||||||
|
--warning-light: #fff7e6;
|
||||||
|
--error-color: #ff4d4f;
|
||||||
|
--error-light: #fff2f0;
|
||||||
|
|
||||||
|
/* 登录按钮渐变色 */
|
||||||
|
--login-gradient-start: #667eea;
|
||||||
|
--login-gradient-end: #764ba2;
|
||||||
|
|
||||||
|
/* 中性色 */
|
||||||
|
--bg-color: #f5f5f5;
|
||||||
|
--bg-light: #fafafa;
|
||||||
|
--bg-primary-light: #fff5f5;
|
||||||
|
--bg-primary-lighter: #fff0f0;
|
||||||
|
--bg-primary-lightest: #fff0e8;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--text-primary: #333333;
|
||||||
|
--text-dark: #2d3436;
|
||||||
|
--text-secondary: #666666;
|
||||||
|
--text-tertiary: #999999;
|
||||||
|
--border-color: #e0e0e0;
|
||||||
|
--border-light: #e8e8e8;
|
||||||
|
|
||||||
|
/* 字体 */
|
||||||
|
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-size-xs: 12px;
|
||||||
|
--font-size-sm: 14px;
|
||||||
|
--font-size-base: 16px;
|
||||||
|
--font-size-lg: 18px;
|
||||||
|
--font-size-xl: 20px;
|
||||||
|
--font-size-2xl: 24px;
|
||||||
|
--font-size-3xl: 32px;
|
||||||
|
--font-size-4xl: 36px;
|
||||||
|
|
||||||
|
/* 间距 */
|
||||||
|
--spacing-xs: 4px;
|
||||||
|
--spacing-sm: 8px;
|
||||||
|
--spacing-md: 16px;
|
||||||
|
--spacing-lg: 24px;
|
||||||
|
--spacing-xl: 32px;
|
||||||
|
--spacing-xxl: 40px;
|
||||||
|
|
||||||
|
/* 圆角 */
|
||||||
|
--border-radius-sm: 4px;
|
||||||
|
--border-radius-md: 8px;
|
||||||
|
--border-radius-lg: 12px;
|
||||||
|
--border-radius-xl: 16px;
|
||||||
|
--border-radius-2xl: 24px;
|
||||||
|
--border-radius-3xl: 28px;
|
||||||
|
--border-radius-full: 50%;
|
||||||
|
|
||||||
|
/* 阴影 */
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
--shadow-xl: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
--shadow-2xl: 0 12px 32px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
/* 过渡 */
|
||||||
|
--transition-fast: 0.2s ease;
|
||||||
|
--transition-normal: 0.3s ease;
|
||||||
|
--transition-slow: 0.5s ease;
|
||||||
|
--transition-transform: 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
|
||||||
|
/* 搜索框相关 */
|
||||||
|
--search-height: 52px;
|
||||||
|
--search-radius: 28px;
|
||||||
|
|
||||||
|
/* 消息相关 */
|
||||||
|
--message-radius: 18px;
|
||||||
|
|
||||||
|
/* 输入框相关 */
|
||||||
|
--input-bg: #f9fafb;
|
||||||
|
--input-error: #ef4444;
|
||||||
|
--error-bg: #fee2e2;
|
||||||
|
--error-text: #dc2626;
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Button } from 'ant-design-vue'
|
import { Button } from 'ant-design-vue'
|
||||||
import ProductModal from '@/Views/product/productmodal.vue'
|
import ProductModal from '@/views/product/productmodal.vue'
|
||||||
import type { Product } from '@/Util/Type'
|
import type { Product } from '@/Util/Type'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -119,16 +119,20 @@ const productmodal = ref<Product[]>([
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'BenQ 明基投影仪 商务办公会议培训用 1080P高清智能投影机',
|
name: 'BenQ 明基投影仪 商务办公会议培训用 1080P高清智能投影机',
|
||||||
price: '999',
|
price: 999,
|
||||||
img: 'https://picsum.photos/id/237/800/300',
|
image: 'https://picsum.photos/id/237/800/300',
|
||||||
model: 'X500'
|
model: 'X500',
|
||||||
|
description: '这是一个高分辨率的投影仪,支持1080P高清视频播放。',
|
||||||
|
quantity: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
name: '明基投影仪 商务办公会议培训用 高清智能投影机',
|
name: '明基投影仪 商务办公会议培训用 高清智能投影机',
|
||||||
price: '1299',
|
price: 1299,
|
||||||
img: 'https://picsum.photos/id/238/800/300',
|
image: 'https://picsum.photos/id/238/800/300',
|
||||||
model: 'W600'
|
model: 'W600',
|
||||||
|
description: '这是一个高分辨率的投影仪,支持1080P高清视频播放。',
|
||||||
|
quantity: 100
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
const cartList = ref<CartItem[]>([
|
const cartList = ref<CartItem[]>([
|
||||||
@@ -263,24 +267,24 @@ const removeItem = (id: number) => {
|
|||||||
#cart {
|
#cart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, #f5f5f5 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--bg-color) 0%, var(--bg-light) 100%);
|
||||||
padding: 20px;
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-container {
|
.cart-container {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
border-radius: 16px;
|
border-radius: var(--border-radius-xl);
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-md);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-header {
|
.cart-header {
|
||||||
padding: 20px 40px;
|
padding: var(--spacing-md) var(--spacing-xxl);
|
||||||
border-bottom: 3px solid #f0f0f0;
|
border-bottom: 3px solid var(--border-color);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
display: block;
|
display: block;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -294,7 +298,7 @@ const removeItem = (id: number) => {
|
|||||||
left: -100%;
|
left: -100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(90deg, transparent, rgba(255, 80, 0, 0.05), transparent);
|
background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
|
||||||
transition: left 0.6s ease;
|
transition: left 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,94 +307,94 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cart-title {
|
.cart-title {
|
||||||
font-size: 32px;
|
font-size: var(--font-size-3xl);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #2d3436;
|
color: var(--text-dark);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
gap: var(--spacing-md);
|
||||||
position: relative;
|
position: relative;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-title:hover {
|
.cart-title:hover {
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
transform: translateX(8px);
|
transform: translateX(var(--spacing-sm));
|
||||||
text-shadow: 0 4px 8px rgba(255, 80, 0, 0.2);
|
text-shadow: 0 4px 8px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-title i {
|
.cart-title i {
|
||||||
font-size: 36px;
|
font-size: var(--font-size-4xl);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-title:hover i {
|
.cart-title:hover i {
|
||||||
transform: rotate(10deg) scale(1.2);
|
transform: rotate(10deg) scale(1.2);
|
||||||
filter: drop-shadow(0 2px 6px rgba(255, 80, 0, 0.3));
|
filter: drop-shadow(0 2px 6px rgba(var(--primary-light-rgb), 0.3));
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-buttons {
|
.filter-buttons {
|
||||||
margin-top: 20px;
|
margin-top: var(--spacing-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: var(--spacing-md);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-btn {
|
.filter-btn {
|
||||||
padding: 12px 36px;
|
padding: var(--spacing-sm) var(--spacing-xxl);
|
||||||
border-radius: 28px;
|
border-radius: var(--border-radius-3xl);
|
||||||
font-size: 15px;
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
border: 2px solid #e0e0e0;
|
border: 2px solid var(--border-color);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-md);
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-btn.active {
|
.filter-btn.active {
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: 0 6px 20px rgba(255, 80, 0, 0.35);
|
box-shadow: 0 6px 20px rgba(var(--primary-light-rgb), 0.35);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-btn:not(.active):hover {
|
.filter-btn:not(.active):hover {
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
background: linear-gradient(135deg, #fff5f5 0%, #fff0f0 100%);
|
background: linear-gradient(135deg, var(--bg-primary-light) 0%, var(--bg-primary-lighter) 100%);
|
||||||
box-shadow: 0 4px 16px rgba(255, 80, 0, 0.15);
|
box-shadow: 0 4px 16px rgba(var(--primary-light-rgb), 0.15);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-btn:active {
|
.filter-btn:active {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
box-shadow: 0 2px 8px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 2px 8px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-list {
|
.cart-list {
|
||||||
padding: 20px 40px;
|
padding: var(--spacing-md) var(--spacing-xxl);
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item {
|
.cart-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 15px;
|
padding: var(--spacing-md);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
border-radius: 24px;
|
border-radius: var(--border-radius-2xl);
|
||||||
margin-bottom: 30px;
|
margin-bottom: var(--spacing-xl);
|
||||||
border: 2px solid #f5f5f5;
|
border: 2px solid var(--border-color);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-md);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
gap: 30px;
|
gap: var(--spacing-xl);
|
||||||
animation: float 6s ease-in-out infinite;
|
animation: float 6s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,55 +410,55 @@ const removeItem = (id: number) => {
|
|||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
accent-color: #ff5000;
|
accent-color: var(--primary-color);
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 6px;
|
border-radius: var(--border-radius-md);
|
||||||
background-color: #ffffff;
|
background-color: var(--card-bg);
|
||||||
border: 2px solid #e0e0e0;
|
border: 2px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-input:hover {
|
.checkbox-input:hover {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
box-shadow: 0 0 0 10px rgba(255, 80, 0, 0.12);
|
box-shadow: 0 0 0 10px rgba(var(--primary-light-rgb), 0.12);
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-input:checked {
|
.checkbox-input:checked {
|
||||||
box-shadow: 0 0 0 8px rgba(255, 80, 0, 0.25);
|
box-shadow: 0 0 0 8px rgba(var(--primary-light-rgb), 0.25);
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
animation: checkbox-pulse 0.4s ease;
|
animation: checkbox-pulse 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-input:focus {
|
.checkbox-input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 6px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 0 0 6px rgba(var(--primary-light-rgb), 0.2);
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes checkbox-pulse {
|
@keyframes checkbox-pulse {
|
||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 4px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 0 0 4px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
box-shadow: 0 0 0 12px rgba(255, 80, 0, 0.1);
|
box-shadow: 0 0 0 12px rgba(var(--primary-light-rgb), 0.1);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 8px rgba(255, 80, 0, 0.25);
|
box-shadow: 0 0 0 8px rgba(var(--primary-light-rgb), 0.25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-img-container {
|
.cart-item-img-container {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
border-radius: 20px;
|
border-radius: var(--border-radius-xl);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 3px solid #f0f0f0;
|
border: 3px solid var(--border-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-img-container::after {
|
.cart-item-img-container::after {
|
||||||
@@ -464,10 +468,10 @@ const removeItem = (id: number) => {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border-radius: 20px;
|
border-radius: var(--border-radius-xl);
|
||||||
border: 3px solid transparent;
|
border: 3px solid transparent;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
background: linear-gradient(135deg, rgba(255, 80, 0, 0.08) 0%, rgba(255, 80, 0, 0.03) 100%);
|
background: linear-gradient(135deg, rgba(var(--primary-light-rgb), 0.08) 0%, rgba(var(--primary-light-rgb), 0.03) 100%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,21 +482,21 @@ const removeItem = (id: number) => {
|
|||||||
left: -50%;
|
left: -50%;
|
||||||
width: 200%;
|
width: 200%;
|
||||||
height: 200%;
|
height: 200%;
|
||||||
background: linear-gradient(45deg, transparent, rgba(255, 80, 0, 0.1), transparent);
|
background: linear-gradient(45deg, transparent, rgba(var(--primary-light-rgb), 0.1), transparent);
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
transition: all 0.6s ease;
|
transition: all var(--transition-slow);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .cart-item-img-container {
|
.cart-item:hover .cart-item-img-container {
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
transform: scale(1.1) translateY(-4px);
|
transform: scale(1.1) translateY(-4px);
|
||||||
box-shadow: 0 12px 32px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 12px 32px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .cart-item-img-container::after {
|
.cart-item:hover .cart-item-img-container::after {
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
box-shadow: 0 0 0 8px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 0 0 8px rgba(var(--primary-light-rgb), 0.2);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,9 +528,9 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-name {
|
.cart-item-name {
|
||||||
font-size: 18px;
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* 限制显示两行 */
|
/* 限制显示两行 */
|
||||||
@@ -535,15 +539,15 @@ const removeItem = (id: number) => {
|
|||||||
-webkit-line-clamp: 5;
|
-webkit-line-clamp: 5;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .cart-item-name {
|
.cart-item:hover .cart-item-name {
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
transform: translateX(8px);
|
transform: translateX(var(--spacing-sm));
|
||||||
text-shadow: 0 2px 6px rgba(255, 80, 0, 0.2);
|
text-shadow: 0 2px 6px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-name::after {
|
.cart-item-name::after {
|
||||||
@@ -580,17 +584,17 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-model {
|
.cart-item-model {
|
||||||
font-size: 16px;
|
font-size: var(--font-size-base);
|
||||||
padding: 18px 32px;
|
padding: var(--spacing-lg) var(--spacing-xl);
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
border-radius: 18px;
|
border-radius: var(--border-radius-lg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid var(--border-color);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-md);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,8 +605,8 @@ const removeItem = (id: number) => {
|
|||||||
left: -100%;
|
left: -100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(90deg, transparent, rgba(255, 80, 0, 0.15), transparent);
|
background: linear-gradient(90deg, transparent, rgba(var(--primary-light-rgb), 0.15), transparent);
|
||||||
transition: left 0.6s ease;
|
transition: left var(--transition-slow);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,18 +617,18 @@ const removeItem = (id: number) => {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: linear-gradient(135deg, rgba(255, 80, 0, 0.08) 0%, rgba(255, 80, 0, 0.04) 100%);
|
background: linear-gradient(135deg, rgba(var(--primary-light-rgb), 0.08) 0%, rgba(var(--primary-light-rgb), 0.04) 100%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.4s ease;
|
transition: opacity var(--transition-transform);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
border-radius: 18px;
|
border-radius: var(--border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-model:hover {
|
.cart-item-model:hover {
|
||||||
border: 2px dashed #ff5000;
|
border: 2px dashed var(--primary-color);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
transform: translateY(-5px) scale(1.05);
|
transform: translateY(-5px) scale(1.05);
|
||||||
box-shadow: 0 10px 24px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 10px 24px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-model:hover::before {
|
.cart-item-model:hover::before {
|
||||||
@@ -659,13 +663,13 @@ const removeItem = (id: number) => {
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: var(--spacing-md);
|
||||||
padding: 15px;
|
padding: var(--spacing-md);
|
||||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||||
border-radius: 20px;
|
border-radius: var(--border-radius-xl);
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid var(--border-color);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-md);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,16 +680,16 @@ const removeItem = (id: number) => {
|
|||||||
left: -2px;
|
left: -2px;
|
||||||
right: -2px;
|
right: -2px;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
border-radius: 20px;
|
border-radius: var(--border-radius-xl);
|
||||||
background: linear-gradient(135deg, #ff5000, #ff8500, #ff5000);
|
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover), var(--primary-color));
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: opacity var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .cart-item-price {
|
.cart-item:hover .cart-item-price {
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
box-shadow: 0 10px 30px rgba(255, 80, 0, 0.2);
|
box-shadow: 0 10px 30px rgba(var(--primary-light-rgb), 0.2);
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,25 +722,25 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.price-label {
|
.price-label {
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: linear-gradient(135deg, #fff5f5 0%, #fff0f0 100%);
|
background: linear-gradient(135deg, var(--bg-primary-light) 0%, var(--bg-primary-lighter) 100%);
|
||||||
padding: 8px 20px;
|
padding: var(--spacing-xs) var(--spacing-lg);
|
||||||
border-radius: 20px;
|
border-radius: var(--border-radius-xl);
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
border: 1px solid #ffece5;
|
border: 1px solid var(--border-color);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
box-shadow: 0 3px 12px rgba(255, 80, 0, 0.1);
|
box-shadow: 0 3px 12px rgba(var(--primary-light-rgb), 0.1);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .price-label {
|
.cart-item:hover .price-label {
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
box-shadow: 0 6px 20px rgba(255, 80, 0, 0.35);
|
box-shadow: 0 6px 20px rgba(var(--primary-light-rgb), 0.35);
|
||||||
transform: scale(1.08);
|
transform: scale(1.08);
|
||||||
animation: label-pulse 0.6s ease;
|
animation: label-pulse 0.6s ease;
|
||||||
}
|
}
|
||||||
@@ -751,10 +755,10 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.price-symbol {
|
.price-symbol {
|
||||||
font-size: 16px;
|
font-size: var(--font-size-base);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .price-symbol {
|
.cart-item:hover .price-symbol {
|
||||||
@@ -762,42 +766,42 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.price-value {
|
.price-value {
|
||||||
font-size: 26px;
|
font-size: var(--font-size-2xl);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
position: relative;
|
position: relative;
|
||||||
text-shadow: 0 2px 4px rgba(255, 80, 0, 0.2);
|
text-shadow: 0 2px 4px rgba(var(--primary-light-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .price-value {
|
.cart-item:hover .price-value {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
text-shadow: 0 4px 8px rgba(255, 80, 0, 0.35);
|
text-shadow: 0 4px 8px rgba(var(--primary-light-rgb), 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.original-price .price-symbol {
|
.original-price .price-symbol {
|
||||||
font-size: 13px;
|
font-size: var(--font-size-xs);
|
||||||
color: #999;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.original-price .price-value {
|
.original-price .price-value {
|
||||||
font-size: 16px;
|
font-size: var(--font-size-base);
|
||||||
color: #999;
|
color: var(--text-tertiary);
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discount-badge {
|
.discount-badge {
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: 11px;
|
font-size: var(--font-size-xs);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 4px 12px;
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-sm);
|
||||||
margin-left: 6px;
|
margin-left: var(--spacing-xs);
|
||||||
box-shadow: 0 3px 6px rgba(255, 80, 0, 0.25);
|
box-shadow: 0 3px 6px rgba(var(--primary-light-rgb), 0.25);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -1006,12 +1010,12 @@ const removeItem = (id: number) => {
|
|||||||
.cart-item-quantity {
|
.cart-item-quantity {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: var(--spacing-sm);
|
||||||
background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%);
|
background: linear-gradient(135deg, var(--bg-light) 0%, var(--card-bg) 100%);
|
||||||
padding: 10px 20px;
|
padding: var(--spacing-sm) var(--spacing-lg);
|
||||||
border-radius: 32px;
|
border-radius: var(--border-radius-2xl);
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid var(--border-color);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -1023,13 +1027,13 @@ const removeItem = (id: number) => {
|
|||||||
left: -100%;
|
left: -100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(90deg, transparent, rgba(255, 80, 0, 0.08), transparent);
|
background: linear-gradient(90deg, transparent, rgba(var(--primary-light-rgb), 0.08), transparent);
|
||||||
transition: left 0.6s ease;
|
transition: left var(--transition-slow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item:hover .cart-item-quantity {
|
.cart-item:hover .cart-item-quantity {
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
box-shadow: 0 6px 16px rgba(255, 80, 0, 0.15);
|
box-shadow: 0 6px 16px rgba(var(--primary-light-rgb), 0.15);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1040,14 +1044,14 @@ const removeItem = (id: number) => {
|
|||||||
.quantity-btn {
|
.quantity-btn {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid var(--border-color);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #f8f8f8 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1063,9 +1067,9 @@ const removeItem = (id: number) => {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff7300 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
@@ -1076,10 +1080,10 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quantity-btn:hover {
|
.quantity-btn:hover {
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
box-shadow: 0 6px 16px rgba(255, 80, 0, 0.35);
|
box-shadow: 0 6px 16px rgba(var(--primary-light-rgb), 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-btn:active {
|
.quantity-btn:active {
|
||||||
@@ -1124,22 +1128,22 @@ const removeItem = (id: number) => {
|
|||||||
width: 80px;
|
width: 80px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid var(--border-color);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-md);
|
||||||
font-size: 18px;
|
font-size: var(--font-size-lg);
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-input:focus {
|
.quantity-input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
box-shadow: 0 0 0 4px rgba(255, 80, 0, 0.2), inset 0 2px 4px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 0 0 4px rgba(var(--primary-light-rgb), 0.2), inset 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fff5f5 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-primary-light) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-input::-webkit-inner-spin-button,
|
.quantity-input::-webkit-inner-spin-button,
|
||||||
@@ -1174,28 +1178,28 @@ const removeItem = (id: number) => {
|
|||||||
width: 160px;
|
width: 160px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: var(--spacing-md);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 0;
|
padding: var(--spacing-sm) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-action-btn {
|
.cart-action-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: var(--spacing-sm);
|
||||||
padding: 12px 30px;
|
padding: var(--spacing-sm) var(--spacing-xl);
|
||||||
border-radius: 32px;
|
border-radius: var(--border-radius-2xl);
|
||||||
font-size: 15px;
|
font-size: var(--font-size-base);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-action-btn::before {
|
.cart-action-btn::before {
|
||||||
@@ -1205,8 +1209,8 @@ const removeItem = (id: number) => {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-slow);
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1222,7 @@ const removeItem = (id: number) => {
|
|||||||
|
|
||||||
.cart-action-btn:hover {
|
.cart-action-btn:hover {
|
||||||
transform: translateY(-3px) scale(1.05);
|
transform: translateY(-3px) scale(1.05);
|
||||||
box-shadow: 0 8px 24px rgba(255, 80, 0, 0.25);
|
box-shadow: 0 8px 24px rgba(var(--primary-light-rgb), 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-action-btn:active {
|
.cart-action-btn:active {
|
||||||
@@ -1242,43 +1246,43 @@ const removeItem = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.delete-btn {
|
.delete-btn {
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
border-color: #ff4d4f;
|
border-color: var(--error-color);
|
||||||
color: #ff4d4f;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-btn::before {
|
.delete-btn::before {
|
||||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
|
background: linear-gradient(135deg, var(--error-color) 0%, #ff7875 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-btn:hover {
|
.delete-btn:hover {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border-color: #ff4d4f;
|
border-color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-btn {
|
.favorite-btn {
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-btn::before {
|
.favorite-btn::before {
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff7300 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-btn:hover {
|
.favorite-btn:hover {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-btn.favorited {
|
.favorite-btn.favorited {
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff7300 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border-color: #ff5000;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-btn.favorited:hover {
|
.favorite-btn.favorited:hover {
|
||||||
background: linear-gradient(135deg, #ff7300 0%, #ff5000 100%);
|
background: linear-gradient(135deg, var(--primary-hover) 0%, var(--primary-color) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-btn.favorited i::before {
|
.favorite-btn.favorited i::before {
|
||||||
@@ -1290,9 +1294,9 @@ const removeItem = (id: number) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 25px 40px;
|
padding: var(--spacing-lg) var(--spacing-xxl);
|
||||||
border-top: 2px solid #f0f0f0;
|
border-top: 2px solid var(--border-color);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
|
background: linear-gradient(135deg, var(--card-bg) 0%, var(--bg-light) 100%);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@@ -1301,77 +1305,77 @@ const removeItem = (id: number) => {
|
|||||||
.footer-left {
|
.footer-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 30px;
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-all {
|
.select-all {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: var(--spacing-xs);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-all:hover {
|
.select-all:hover {
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-count {
|
.selected-count {
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.count {
|
.count {
|
||||||
font-size: 18px;
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-right {
|
.footer-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 30px;
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-price {
|
.total-price {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 5px;
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-label {
|
.total-label {
|
||||||
font-size: 16px;
|
font-size: var(--font-size-base);
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-symbol {
|
.total-symbol {
|
||||||
font-size: 20px;
|
font-size: var(--font-size-lg);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-value {
|
.total-value {
|
||||||
font-size: 32px;
|
font-size: var(--font-size-3xl);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-btn {
|
.checkout-btn {
|
||||||
padding: 12px 40px;
|
padding: var(--spacing-sm) var(--spacing-xxl);
|
||||||
font-size: 16px;
|
font-size: var(--font-size-base);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: 24px;
|
border-radius: var(--border-radius-xl);
|
||||||
background: linear-gradient(135deg, #ff5000 0%, #ff6b00 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 4px 12px rgba(255, 80, 0, 0.3);
|
box-shadow: 0 4px 12px rgba(var(--primary-light-rgb), 0.3);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-btn:hover {
|
.checkout-btn:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 6px 20px rgba(255, 80, 0, 0.4);
|
box-shadow: 0 6px 20px rgba(var(--primary-light-rgb), 0.4);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
703
src/Views/Chat.vue
Normal file
703
src/Views/Chat.vue
Normal file
@@ -0,0 +1,703 @@
|
|||||||
|
<!-- 聊天界面 -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div id="chat">
|
||||||
|
<!-- 左右结构 左边聊天导航栏(用户1 用户2 用户3 等 从上到下分布 一个用户一个li标签) 右边聊天内容(根据导航栏切换) -->
|
||||||
|
<div id="chat-nav">
|
||||||
|
<div class="nav-header">
|
||||||
|
<h2>消息</h2>
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="search-container">
|
||||||
|
<input type="text" placeholder="搜索用户" v-model="searchTerm" class="search-input">
|
||||||
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-list-container">
|
||||||
|
<ul class="user-list">
|
||||||
|
<li v-for="user in chatUsers" :key="user.id" :class="{ active: activeUserId === user.id }"
|
||||||
|
@click="selectUser(user.id)">
|
||||||
|
<div class="user-avatar">
|
||||||
|
<img :src="user.avatar" :alt="user.name">
|
||||||
|
</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<div class="user-header">
|
||||||
|
<span class="user-name">{{ user.name }}</span>
|
||||||
|
<span class="message-time">{{ user.lastMessageTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-last-message">
|
||||||
|
{{ user.lastMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="user.unreadCount > 0" class="unread-badge">
|
||||||
|
{{ user.unreadCount }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右边聊天内容 -->
|
||||||
|
<div id="chat-content">
|
||||||
|
<!-- 聊天头部 -->
|
||||||
|
<div v-if="activeUser" class="chat-header">
|
||||||
|
<div class="chat-user-info">
|
||||||
|
<div>
|
||||||
|
<h3>{{ activeUser.name }}</h3>
|
||||||
|
<p class="user-status">在线</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 聊天历史 -->
|
||||||
|
<div v-if="activeUser" class="chat-history">
|
||||||
|
<div v-for="(message, index) in messages" :key="index"
|
||||||
|
:class="{ 'chat-message': true, 'sent': message.sender === 'me', 'received': message.sender !== 'me' }">
|
||||||
|
<div v-if="message.sender !== 'me'" class="user-avatar small">
|
||||||
|
<img :src="activeUser.avatar" :alt="activeUser.name">
|
||||||
|
</div>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text">{{ message.text }}</div>
|
||||||
|
<div class="message-time">{{ message.time }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 消息输入区 -->
|
||||||
|
<div v-if="activeUser" class="chat-input-area">
|
||||||
|
<div class="input-container">
|
||||||
|
<button class="input-btn">😊</button>
|
||||||
|
<button class="input-btn">📎</button>
|
||||||
|
<input type="text" v-model="newMessage" placeholder="输入消息..." class="message-input"
|
||||||
|
@keyup.enter="sendMessage">
|
||||||
|
<button class="input-btn">🎤</button>
|
||||||
|
</div>
|
||||||
|
<button class="send-btn" @click="sendMessage">发送</button>
|
||||||
|
</div>
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-else class="empty-state">
|
||||||
|
<img src="#" alt="选择聊天">
|
||||||
|
<h3>选择一个聊天</h3>
|
||||||
|
<p>开始与您的联系人聊天</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
// 搜索用户的关键词
|
||||||
|
const searchTerm = ref('')
|
||||||
|
|
||||||
|
// 聊天用户数据
|
||||||
|
const chatUsers = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '用户1',
|
||||||
|
avatar: '?prompt=user%20avatar%20friendly%20smile&image_size=square',
|
||||||
|
lastMessage: '你好,我想购买商品1',
|
||||||
|
lastMessageTime: '10:00',
|
||||||
|
unreadCount: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '用户2',
|
||||||
|
avatar: '?prompt=user%20avatar%20professional%20business&image_size=square',
|
||||||
|
lastMessage: '订单什么时候发货?',
|
||||||
|
lastMessageTime: '09:30',
|
||||||
|
unreadCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '用户3',
|
||||||
|
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
|
||||||
|
lastMessage: '商品质量怎么样?',
|
||||||
|
lastMessageTime: '昨天',
|
||||||
|
unreadCount: 1
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 消息数据
|
||||||
|
const messages = ref([
|
||||||
|
{
|
||||||
|
sender: 'user1',
|
||||||
|
text: '你好,我想购买商品1',
|
||||||
|
time: '10:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: 'me',
|
||||||
|
text: '您好,欢迎咨询,请问有什么可以帮助您的?',
|
||||||
|
time: '10:01'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: 'user1',
|
||||||
|
text: '商品1有货吗?',
|
||||||
|
time: '10:02'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 当前活跃用户ID
|
||||||
|
const activeUserId = ref(1)
|
||||||
|
|
||||||
|
// 新消息
|
||||||
|
const newMessage = ref('')
|
||||||
|
|
||||||
|
// 计算当前活跃用户
|
||||||
|
const activeUser = computed(() => {
|
||||||
|
return chatUsers.value.find(user => user.id === activeUserId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选择用户
|
||||||
|
const selectUser = (userId: number) => {
|
||||||
|
activeUserId.value = userId
|
||||||
|
// 清除未读消息数
|
||||||
|
const user = chatUsers.value.find(user => user.id === userId)
|
||||||
|
if (user) {
|
||||||
|
user.unreadCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
const sendMessage = () => {
|
||||||
|
if (newMessage.value.trim()) {
|
||||||
|
messages.value.push({
|
||||||
|
sender: 'me',
|
||||||
|
text: newMessage.value,
|
||||||
|
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
})
|
||||||
|
// 更新最后一条消息
|
||||||
|
if (activeUser.value) {
|
||||||
|
activeUser.value.lastMessage = newMessage.value
|
||||||
|
activeUser.value.lastMessageTime = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
}
|
||||||
|
// 清空输入框
|
||||||
|
newMessage.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#chat {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
padding: var(--spacing-xl) 150px 0 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧聊天导航栏 */
|
||||||
|
#chat-nav {
|
||||||
|
width: 320px;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||||
|
padding: var(--spacing-sm) 0 0 var(--spacing-sm);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-header {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
margin-left: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.user-list-container {
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
overflow-x: auto;
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-btn:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-nav .user-list-container ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-nav .user-list-container li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-md) var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-nav .user-list-container li:hover {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-nav .user-list-container li.active {
|
||||||
|
background-color: var(--bg-primary-lightest);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: var(--spacing-md);
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar.small {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin-right: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-last-message {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-badge {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧聊天内容 */
|
||||||
|
#chat-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-left: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 聊天头部 */
|
||||||
|
.chat-header {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
padding: var(--spacing-md) var(--spacing-md);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0 var(--border-radius-lg) 0 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-user-info h3 {
|
||||||
|
margin: 0 0 2px;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--success-color);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
background-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 聊天历史 */
|
||||||
|
.chat-history {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
background-image: url('?prompt=chat%20background%20pattern%20subtle%20light&image_size=landscape_16_9');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.sent {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.sent .user-avatar {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
max-width: 70%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.sent .message-content {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border-radius: var(--message-radius);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.received .message-text {
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.sent .message-text {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #ffffff;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息输入区 */
|
||||||
|
.chat-input-area {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
padding: var(--spacing-md) var(--spacing-xl);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
border-radius: 0 0 var(--border-radius-lg) 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input::placeholder {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
padding: var(--spacing-xs) var(--spacing-xl);
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态 */
|
||||||
|
.empty-state {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
padding: var(--spacing-xxl);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state img {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state h3 {
|
||||||
|
margin: 0 0 var(--spacing-md);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#chat-nav {
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-history {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
#chat-nav {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 100;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-nav.active {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area {
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -24,12 +24,20 @@
|
|||||||
<div v-if="errorMessage" class="error-message">
|
<div v-if="errorMessage" class="error-message">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 记住我 如果用户登录成功,记住用户信息,5天内无需重新登录 -->
|
||||||
|
<div class="form-group remember-me">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" v-model="loginform.rememberMe" class="checkbox-input">
|
||||||
|
<span class="checkbox-text">记住我</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 登录按钮 -->
|
||||||
<button type="submit" class="login-button" :disabled="isLoading">
|
<button type="submit" class="login-button" :disabled="isLoading">
|
||||||
<span v-if="isLoading" class="loading-spinner"></span>
|
<span v-if="isLoading" class="loading-spinner"></span>
|
||||||
{{ isLoading ? '登录中...' : '登录' }}
|
{{ isLoading ? '登录中...' : '登录' }}
|
||||||
</button>
|
</button>
|
||||||
|
<!-- 注册链接 -->
|
||||||
<div class="login-footer">
|
<div class="login-footer">
|
||||||
<p class="register-link">
|
<p class="register-link">
|
||||||
还没有账号? <a href="#" @click.prevent="router.push({ name: 'register' })">立即注册</a>
|
还没有账号? <a href="#" @click.prevent="router.push({ name: 'register' })">立即注册</a>
|
||||||
@@ -51,7 +59,8 @@ const router = useRouter()
|
|||||||
|
|
||||||
const loginform = ref<Login>({
|
const loginform = ref<Login>({
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: '',
|
||||||
|
rememberMe: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
@@ -69,17 +78,18 @@ const login = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
const userLoginResponse = await userService.login(loginform.value)
|
const userLoginResponse = await userService.login(loginform.value).then(res => res.data)
|
||||||
|
console.log(userLoginResponse)
|
||||||
|
|
||||||
// 注意:根据响应拦截器,userLoginResponse已经是response.data
|
// 注意:根据响应拦截器,userLoginResponse已经是response.data
|
||||||
if (userLoginResponse.data.code === 200) {
|
if (userLoginResponse.code === 200) {
|
||||||
// 登录成功后,跳转到首页
|
// 登录成功后,跳转到首页
|
||||||
router.push({ name: 'home' })
|
router.push({ name: 'home' })
|
||||||
// 登录成功后,将token存储到全局状态管理中
|
// 登录成功后,将token存储到全局状态管理中
|
||||||
useGlobalStore().setToken(userLoginResponse.data.token)
|
useGlobalStore().setToken(userLoginResponse.data.token)
|
||||||
} else {
|
} else {
|
||||||
// 登录失败后,显示错误信息给用户
|
// 登录失败后,显示错误信息给用户
|
||||||
errorMessage.value = userLoginResponse.data.msg || '登录失败,请检查用户名和密码'
|
errorMessage.value = userLoginResponse.message || '登录失败,请检查用户名和密码'
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('登录失败:', error)
|
console.error('登录失败:', error)
|
||||||
@@ -140,6 +150,37 @@ const login = async () => {
|
|||||||
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 记住我复选框样式 */
|
||||||
|
.remember-me {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input {
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-text:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
/* 登录头部 */
|
/* 登录头部 */
|
||||||
.login-header {
|
.login-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -7,34 +7,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import productList from './product/productList.vue'
|
import productList from './product/productList.vue'
|
||||||
import Herde from "@/Views/Herde.vue";
|
import Herde from "@/views/herde.vue";
|
||||||
import router from '@/Route/route';
|
import router from '@/Route/route';
|
||||||
|
import ProductService from '@/Service/ProductService';
|
||||||
|
import type { Product } from '@/Util/Type';
|
||||||
|
|
||||||
|
const productLists = ref<Product[]>([]);
|
||||||
|
|
||||||
const productLists = ref([
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: '商品1',
|
|
||||||
price: '100',
|
|
||||||
img: 'https://example.com/product1.jpg',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: '商品2',
|
|
||||||
price: '200',
|
|
||||||
img: 'https://example.com/product2.jpg',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
name: '商品3',
|
|
||||||
price: '300',
|
|
||||||
img: 'https://example.com/product3.jpg',
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
// 监听路由参数变化
|
// 监听路由参数变化
|
||||||
watch(() => router.currentRoute.value.query.keyword, (newValue) => {
|
watch(() => router.currentRoute.value.query.keyword, (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
// 根据关键词搜索商品
|
// 根据关键词搜索商品
|
||||||
//data(newValue)
|
ProductService.searchProducts(newValue as string).then(res => {
|
||||||
|
productLists.value = res.data.data || []
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!-- 店铺详情 -->
|
<!-- 店铺详情 -->
|
||||||
<template>
|
<template>
|
||||||
<div id="shop-detail">
|
<div id="shop-detail">
|
||||||
|
<a href="https://www.bilibili.com/video/BV1a2k5BiEBx/?spm_id_from=333.1007.top_right_bar_window_history.content.click" target="_blank">原帖</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
446
src/Views/User/UpdataAddress.vue
Normal file
446
src/Views/User/UpdataAddress.vue
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
<!-- 更新地址弹窗 上下结构 上方有一个输入框可以识别地址更新到下方 下方有可以更新地址信息(省份、城市、区县、详细地址) -->
|
||||||
|
<!-- 父组件会传递一个地址对象 子组件需要根据这个地址对象更新弹窗的地址信息 -->
|
||||||
|
<template>
|
||||||
|
<a-modal :open="visible" title="更新地址" :footer="null" width="500px">
|
||||||
|
<div class="modal-content">
|
||||||
|
<!-- 地址识别输入框 -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="address-input">地址识别</label>
|
||||||
|
<a-input v-model:value="addressInput" placeholder="请输入完整地址,系统会自动识别省份、城市、区县"
|
||||||
|
@change="handleAddressRecognition" />
|
||||||
|
<div v-if="recognitionError" class="error-message">{{ recognitionError }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 地址信息表单 -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="province">省份 <span class="required">*</span></label>
|
||||||
|
<a-select v-model:value="form.province" :options="provinceOptions" placeholder="请选择省份"
|
||||||
|
@change="handleProvinceChange" />
|
||||||
|
<div v-if="errors.province" class="error-message">{{ errors.province }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="city">城市 <span class="required">*</span></label>
|
||||||
|
<a-select v-model:value="form.city" :options="cityOptions" placeholder="请选择城市"
|
||||||
|
@change="handleCityChange" />
|
||||||
|
<div v-if="errors.city" class="error-message">{{ errors.city }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="district">区县 <span class="required">*</span></label>
|
||||||
|
<a-select v-model:value="form.district" :options="districtOptions" placeholder="请选择区县" />
|
||||||
|
<div v-if="errors.district" class="error-message">{{ errors.district }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="detailAddress">详细地址 <span class="required">*</span></label>
|
||||||
|
<a-input v-model:value="form.detail" placeholder="请输入详细地址(街道、楼栋、门牌号等)" :rows="3" type="textarea" />
|
||||||
|
<div v-if="errors.detail" class="error-message">{{ errors.detail }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a-button @click="handleCancel">取消</a-button>
|
||||||
|
<a-button type="primary" @click="handleOk">确认更新</a-button>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
import type { Address } from '@/Util/Type'
|
||||||
|
const props = defineProps({
|
||||||
|
// 设置弹窗显示状态控制
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 传递的地址对象
|
||||||
|
address: {
|
||||||
|
type: Object as () => Address,
|
||||||
|
default: () => ({
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: false
|
||||||
|
}) as Address,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits(['update:visible', 'update:updateaddress'])
|
||||||
|
// 弹窗显示状态控制
|
||||||
|
const visible = ref(props.visible)
|
||||||
|
// 表单数据
|
||||||
|
const form = ref<Address>({
|
||||||
|
id:props.address.id ,
|
||||||
|
name: props.address.name,
|
||||||
|
phone: props.address.phone,
|
||||||
|
province: props.address.province,
|
||||||
|
city: props.address.city,
|
||||||
|
district: props.address.district,
|
||||||
|
detail: props.address.detail,
|
||||||
|
isDefault: props.address.isDefault
|
||||||
|
})
|
||||||
|
|
||||||
|
// 地址识别输入框
|
||||||
|
const addressInput = ref('')
|
||||||
|
const recognitionError = ref('')
|
||||||
|
|
||||||
|
// 表单验证错误
|
||||||
|
const errors = ref({
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义类型
|
||||||
|
interface Data {
|
||||||
|
[key: string]: { label: string; value: string }[] | undefined;
|
||||||
|
}
|
||||||
|
// 城市选项(根据省份动态生成)
|
||||||
|
const cityOptions = ref<any[]>([])
|
||||||
|
|
||||||
|
// 区县选项(根据城市动态生成)
|
||||||
|
const districtOptions = ref<any[]>([])
|
||||||
|
|
||||||
|
// 省份选项
|
||||||
|
const provinceOptions = ref([
|
||||||
|
{ label: '北京市', value: '北京市' },
|
||||||
|
{ label: '上海市', value: '上海市' },
|
||||||
|
{ label: '广东省', value: '广东省' },
|
||||||
|
{ label: '浙江省', value: '浙江省' },
|
||||||
|
{ label: '江苏省', value: '江苏省' },
|
||||||
|
{ label: '四川省', value: '四川省' },
|
||||||
|
{ label: '湖北省', value: '湖北省' },
|
||||||
|
{ label: '湖南省', value: '湖南省' },
|
||||||
|
{ label: '山东省', value: '山东省' },
|
||||||
|
{ label: '河南省', value: '河南省' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 城市数据
|
||||||
|
const cityData: Data = {
|
||||||
|
'北京市': [{ label: '北京市', value: '北京市' }],
|
||||||
|
'上海市': [{ label: '上海市', value: '上海市' }],
|
||||||
|
'广东省': [
|
||||||
|
{ label: '广州市', value: '广州市' },
|
||||||
|
{ label: '深圳市', value: '深圳市' },
|
||||||
|
{ label: '东莞市', value: '东莞市' },
|
||||||
|
{ label: '佛山市', value: '佛山市' }
|
||||||
|
],
|
||||||
|
'浙江省': [
|
||||||
|
{ label: '杭州市', value: '杭州市' },
|
||||||
|
{ label: '宁波市', value: '宁波市' },
|
||||||
|
{ label: '温州市', value: '温州市' },
|
||||||
|
{ label: '嘉兴市', value: '嘉兴市' }
|
||||||
|
],
|
||||||
|
'江苏省': [
|
||||||
|
{ label: '南京市', value: '南京市' },
|
||||||
|
{ label: '苏州市', value: '苏州市' },
|
||||||
|
{ label: '无锡市', value: '无锡市' },
|
||||||
|
{ label: '常州市', value: '常州市' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 区县数据
|
||||||
|
const districtData: Data = {
|
||||||
|
'北京市-北京市': [
|
||||||
|
{ label: '东城区', value: '东城区' },
|
||||||
|
{ label: '西城区', value: '西城区' },
|
||||||
|
{ label: '朝阳区', value: '朝阳区' },
|
||||||
|
{ label: '海淀区', value: '海淀区' },
|
||||||
|
{ label: '丰台区', value: '丰台区' },
|
||||||
|
{ label: '石景山区', value: '石景山区' },
|
||||||
|
{ label: '门头沟区', value: '门头沟区' },
|
||||||
|
{ label: '房山区', value: '房山区' },
|
||||||
|
{ label: '通州区', value: '通州区' },
|
||||||
|
{ label: '顺义区', value: '顺义区' }
|
||||||
|
],
|
||||||
|
'上海市-上海市': [
|
||||||
|
{ label: '黄浦区', value: '黄浦区' },
|
||||||
|
{ label: '徐汇区', value: '徐汇区' },
|
||||||
|
{ label: '长宁区', value: '长宁区' },
|
||||||
|
{ label: '静安区', value: '静安区' },
|
||||||
|
{ label: '普陀区', value: '普陀区' },
|
||||||
|
{ label: '虹口区', value: '虹口区' },
|
||||||
|
{ label: '杨浦区', value: '杨浦区' },
|
||||||
|
{ label: '浦东新区', value: '浦东新区' }
|
||||||
|
],
|
||||||
|
'广东省-广州市': [
|
||||||
|
{ label: '越秀区', value: '越秀区' },
|
||||||
|
{ label: '海珠区', value: '海珠区' },
|
||||||
|
{ label: '荔湾区', value: '荔湾区' },
|
||||||
|
{ label: '天河区', value: '天河区' },
|
||||||
|
{ label: '白云区', value: '白云区' },
|
||||||
|
{ label: '黄埔区', value: '黄埔区' },
|
||||||
|
{ label: '番禺区', value: '番禺区' },
|
||||||
|
{ label: '花都区', value: '花都区' }
|
||||||
|
],
|
||||||
|
'浙江省-杭州市': [
|
||||||
|
{ label: '上城区', value: '上城区' },
|
||||||
|
{ label: '下城区', value: '下城区' },
|
||||||
|
{ label: '江干区', value: '江干区' },
|
||||||
|
{ label: '拱墅区', value: '拱墅区' },
|
||||||
|
{ label: '西湖区', value: '西湖区' },
|
||||||
|
{ label: '滨江区', value: '滨江区' },
|
||||||
|
{ label: '萧山区', value: '萧山区' },
|
||||||
|
{ label: '余杭区', value: '余杭区' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 address 变化 用于更新弹窗的地址信息
|
||||||
|
watch(() => props.address, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
form.value = { ...newVal }
|
||||||
|
// 更新城市和区县选项
|
||||||
|
handleProvinceChange(newVal.province)
|
||||||
|
if (newVal.city) {
|
||||||
|
handleCityChange(newVal.city)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { deep: true })
|
||||||
|
// 监听 visible 变化 用于更新弹窗的显示状态
|
||||||
|
watch(() => props.visible, (newVal) => {
|
||||||
|
visible.value = newVal
|
||||||
|
})
|
||||||
|
// 监听弹窗关闭事件
|
||||||
|
watch(() => props.visible, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
// 重置表单
|
||||||
|
form.value = {
|
||||||
|
id:0,
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: false
|
||||||
|
}
|
||||||
|
addressInput.value = ''
|
||||||
|
recognitionError.value = ''
|
||||||
|
errors.value = {
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: ''
|
||||||
|
}
|
||||||
|
cityOptions.value = []
|
||||||
|
districtOptions.value = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 地址识别处理
|
||||||
|
const handleAddressRecognition = () => {
|
||||||
|
if (!addressInput.value) {
|
||||||
|
recognitionError.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的地址识别逻辑(实际项目中可以使用更复杂的算法或API)
|
||||||
|
const address = addressInput.value
|
||||||
|
let province = ''
|
||||||
|
let city = ''
|
||||||
|
let district = ''
|
||||||
|
let detail = ''
|
||||||
|
|
||||||
|
// 识别省份
|
||||||
|
const provinces = provinceOptions.value.map(p => p.value)
|
||||||
|
for (const p of provinces) {
|
||||||
|
if (address.includes(p)) {
|
||||||
|
province = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 识别城市
|
||||||
|
// if (province && (cityData as Record<string, { label: string; value: string }[]>)[province]) {
|
||||||
|
// const cities = cityData[province].map((c: { value: any }) => c.value)
|
||||||
|
// for (const c of cities) {
|
||||||
|
// if (address.includes(c)) {
|
||||||
|
// city = c
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 识别区县
|
||||||
|
// if (province && city && districtData[`${province}-${city}`]) {
|
||||||
|
// const districts = districtData[`${province}-${city}`].map((d: { value: any }) => d.value)
|
||||||
|
// for (const d of districts) {
|
||||||
|
// if (address.includes(d)) {
|
||||||
|
// district = d
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 提取详细地址
|
||||||
|
if (province && city && district) {
|
||||||
|
detail = address.replace(province, '').replace(city, '').replace(district, '').trim()
|
||||||
|
} else if (province && city) {
|
||||||
|
detail = address.replace(province, '').replace(city, '').trim()
|
||||||
|
} else if (province) {
|
||||||
|
detail = address.replace(province, '').trim()
|
||||||
|
} else {
|
||||||
|
detail = address
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新表单
|
||||||
|
if (province) {
|
||||||
|
form.value.province = province
|
||||||
|
handleProvinceChange(province)
|
||||||
|
if (city) {
|
||||||
|
form.value.city = city
|
||||||
|
handleCityChange(city)
|
||||||
|
if (district) {
|
||||||
|
form.value.district = district
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form.value.detail = detail
|
||||||
|
recognitionError.value = ''
|
||||||
|
} else {
|
||||||
|
recognitionError.value = '未能识别出省份,请手动选择'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 省份变化处理
|
||||||
|
const handleProvinceChange = (province: string) => {
|
||||||
|
form.value.province = province
|
||||||
|
form.value.city = ''
|
||||||
|
form.value.district = ''
|
||||||
|
|
||||||
|
// 更新城市选项
|
||||||
|
if (province && cityData[province]) {
|
||||||
|
cityOptions.value = cityData[province]
|
||||||
|
} else {
|
||||||
|
cityOptions.value = []
|
||||||
|
}
|
||||||
|
districtOptions.value = []
|
||||||
|
|
||||||
|
// 清除验证错误
|
||||||
|
errors.value.province = ''
|
||||||
|
errors.value.city = ''
|
||||||
|
errors.value.district = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 城市变化处理
|
||||||
|
const handleCityChange = (city: string) => {
|
||||||
|
form.value.city = city
|
||||||
|
form.value.district = ''
|
||||||
|
|
||||||
|
// 更新区县选项
|
||||||
|
if (form.value.province && city && districtData[`${form.value.province}-${city}`]) {
|
||||||
|
districtOptions.value = districtData[`${form.value.province}-${city}`] ?? []
|
||||||
|
} else {
|
||||||
|
districtOptions.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除验证错误
|
||||||
|
errors.value.city = ''
|
||||||
|
errors.value.district = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
const validateForm = (): boolean => {
|
||||||
|
let isValid = true
|
||||||
|
|
||||||
|
// 重置错误信息
|
||||||
|
errors.value = {
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
detail: '',
|
||||||
|
isDefault: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证省份
|
||||||
|
if (!form.value.province) {
|
||||||
|
errors.value.province = '请选择省份'
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证城市
|
||||||
|
if (!form.value.city) {
|
||||||
|
errors.value.city = '请选择城市'
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证区县
|
||||||
|
if (!form.value.district) {
|
||||||
|
errors.value.district = '请选择区县'
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证详细地址
|
||||||
|
if (!form.value.detail) {
|
||||||
|
errors.value.detail = '请输入详细地址'
|
||||||
|
isValid = false
|
||||||
|
} else if (form.value.detail.length < 5) {
|
||||||
|
errors.value.detail = '详细地址至少需要5个字符'
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击确认按钮 触发事件 用于更新父组件的 address
|
||||||
|
const handleOk = () => {
|
||||||
|
// 表单验证
|
||||||
|
if (!validateForm()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('update:updateaddress', form.value)
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击取消按钮
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.modal-content {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: var(--error-color, #ff4d4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--error-color, #ff4d4f);
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border-top: 1px solid var(--border-light, #e8e8e8);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为textarea添加最小高度 */
|
||||||
|
:deep(.ant-input-textarea) {
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
2330
src/Views/User/User.vue
Normal file
2330
src/Views/User/User.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,28 +2,901 @@
|
|||||||
<!-- 布局 从上到下 user信息:用户头像 用户名 -->
|
<!-- 布局 从上到下 user信息:用户头像 用户名 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
|
<!-- 用户信息头部 -->
|
||||||
<div class="user-info-top">
|
<div class="user-info-top">
|
||||||
<div class="user-avatar">
|
<div class="user-avatar-wrapper">
|
||||||
<!-- <img :src="user.avatar" alt="用户头像"> -->
|
<div class="user-avatar">
|
||||||
|
<img :src="user.avatar" alt="用户头像" class="avatar-img">
|
||||||
|
<span class="avatar-edit-badge">编辑</span>
|
||||||
|
</div>
|
||||||
|
<!-- <span class="user-online-indicator"></span> -->
|
||||||
|
<div class="user-name">
|
||||||
|
<span class="name-text">{{ user.username }}</span>
|
||||||
|
<span class="user-level">Lv.{{ user.level }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-name">
|
<div class="user-name-section">
|
||||||
<div>用户名:</div>
|
<div class="user-stats">
|
||||||
<div>关注店铺 收货地址</div>
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ user.followCount }}</span>
|
||||||
|
<span class="stat-label">关注</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ user.fansCount }}</span>
|
||||||
|
<span class="stat-label">粉丝</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ user.collectionCount }}</span>
|
||||||
|
<span class="stat-label">收藏</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-actions">
|
||||||
|
<a href="/user?nav=address" class="action-link">收货地址</a>
|
||||||
|
<a href="/user?nav=follow" class="action-link">关注店铺</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-info-main">
|
|
||||||
<div>
|
<!-- 订单状态卡片 -->
|
||||||
购物信息 :购物车 待收货 代发货 待付款
|
<div class="order-status-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">我的订单</h3>
|
||||||
|
<!-- <a href="/user/orders" class="card-more">查看全部 <SettingOutlined style="transform: rotate(90deg);" /></a> -->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="order-status-grid">
|
||||||
订单信息 :已完成 待评价 待付款 待发货
|
<div class="status-item" @click="navigateToOrder('pending_payment')">
|
||||||
|
<div class="status-icon">
|
||||||
|
<CreditCardOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="status-label">待付款</div>
|
||||||
|
<div class="status-badge" v-if="orderCounts.pending_payment > 0">{{ orderCounts.pending_payment }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-item" @click="navigateToOrder('pending_shipment')">
|
||||||
|
<div class="status-icon">
|
||||||
|
<LoadingOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="status-label">待发货</div>
|
||||||
|
<div class="status-badge" v-if="orderCounts.pending_shipment > 0">{{ orderCounts.pending_shipment }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-item" @click="navigateToOrder('pending_receipt')">
|
||||||
|
<div class="status-icon">
|
||||||
|
<CarOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="status-label">待收货</div>
|
||||||
|
<div class="status-badge" v-if="orderCounts.pending_receipt > 0">{{ orderCounts.pending_receipt }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-item" @click="navigateToOrder('pending_review')">
|
||||||
|
<div class="status-icon">
|
||||||
|
<SolutionOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="status-label">待评价</div>
|
||||||
|
<div class="status-badge" v-if="orderCounts.pending_review > 0">{{ orderCounts.pending_review }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-item" @click="navigateToOrder('completed')">
|
||||||
|
<div class="status-icon">
|
||||||
|
<CheckOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="status-label">已完成</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
优惠 :红包 优惠卷 淘币
|
|
||||||
|
<!-- 功能菜单网格 -->
|
||||||
|
<div class="user-function-grid">
|
||||||
|
<!-- 购物相关 -->
|
||||||
|
<div class="function-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">购物相关</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function-grid">
|
||||||
|
<div class="function-item" @click="navigateTo('/cart')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<ShoppingCartOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">购物车</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/wishlist')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<HeartOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">收藏夹</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/history')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<HistoryOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">浏览历史</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/followed_shops')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<ShopOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">关注店铺</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
足迹信息 : 已买完 收藏夹 购买过的店 足迹信息
|
<!-- 优惠相关 -->
|
||||||
|
<div class="function-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">优惠相关</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function-grid">
|
||||||
|
<div class="function-item" @click="navigateTo('/user/coupons')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<GiftOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">红包</div>
|
||||||
|
<div class="function-badge" v-if="user.couponCount > 0">{{ user.couponCount }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/discounts')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<TagOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">优惠券</div>
|
||||||
|
<div class="function-badge" v-if="user.discountCount > 0">{{ user.discountCount }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/tao_coin')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<GoldOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">淘币</div>
|
||||||
|
<div class="function-value">{{ user.taoCoin }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/points')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<TrophyOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">积分</div>
|
||||||
|
<div class="function-value">{{ user.points }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 账户相关 -->
|
||||||
|
<div class="function-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">账户相关</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function-grid">
|
||||||
|
<div class="function-item" @click="navigateTo('/user/profile')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<SettingOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">账户设置</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/address')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<EnvironmentOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">收货地址</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/customer_service')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<CustomerServiceOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">客服中心</div>
|
||||||
|
</div>
|
||||||
|
<div class="function-item" @click="navigateTo('/user/help')">
|
||||||
|
<div class="function-icon">
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="function-label">帮助中心</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ShoppingCartOutlined, HeartOutlined, HistoryOutlined, ShopOutlined, GiftOutlined, GoldOutlined, TrophyOutlined, SettingOutlined, EnvironmentOutlined, CustomerServiceOutlined, QuestionCircleOutlined, CreditCardOutlined, CarOutlined, LoadingOutlined, CheckOutlined, SolutionOutlined, TagOutlined } from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
const user = ref({
|
||||||
|
username: '用户名',
|
||||||
|
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user123',
|
||||||
|
level: 3,
|
||||||
|
followCount: 12,
|
||||||
|
fansCount: 8,
|
||||||
|
collectionCount: 25,
|
||||||
|
couponCount: 3,
|
||||||
|
discountCount: 5,
|
||||||
|
taoCoin: 1280,
|
||||||
|
points: 560
|
||||||
|
})
|
||||||
|
|
||||||
|
// 订单状态数量
|
||||||
|
const orderCounts = ref({
|
||||||
|
pending_payment: 2,
|
||||||
|
pending_shipment: 1,
|
||||||
|
pending_receipt: 3,
|
||||||
|
pending_review: 0,
|
||||||
|
completed: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 导航到指定页面
|
||||||
|
const navigateTo = (path: string) => {
|
||||||
|
// 根据路径跳转到User.vue中对应的导航项
|
||||||
|
if (path === '/user/address') {
|
||||||
|
router.push({ path: '/user', query: { nav: 'address' } })
|
||||||
|
} else if (path === '/user/wishlist' || path === '/user/favorite') {
|
||||||
|
router.push({ path: '/user', query: { nav: 'favorite' } })
|
||||||
|
} else if (path === '/user/customer_service') {
|
||||||
|
router.push({ path: '/user', query: { nav: 'customerService' } })
|
||||||
|
} else if (path === '/user/help') {
|
||||||
|
router.push({ path: '/user', query: { nav: 'help' } })
|
||||||
|
} else if (path === '/user/profile' || path === '/user/settings') {
|
||||||
|
router.push({ path: '/user', query: { nav: 'userInfo' } })
|
||||||
|
} else {
|
||||||
|
// 其他路径保持不变
|
||||||
|
router.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航到订单页面并带上状态参数
|
||||||
|
const navigateToOrder = (status: string) => {
|
||||||
|
router.push({
|
||||||
|
path: '/user',
|
||||||
|
query: {
|
||||||
|
nav: 'order',
|
||||||
|
status: status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-info {
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户信息头部 */
|
||||||
|
.user-info-top {
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(135deg, #ff6b6b 0%, #4ecdc4 50%, #45b7d1 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-top::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
position: relative;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
|
transition: all 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar:hover {
|
||||||
|
transform: scale(1.1) rotate(360deg);
|
||||||
|
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
|
||||||
|
border-color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 6px;
|
||||||
|
padding: 4px 0;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-online-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: #52c41a;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.4);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name-section {
|
||||||
|
padding: 0 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-level {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
margin: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-link {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-link:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: translateY(-2px) scale(1.05);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 订单状态卡片 */
|
||||||
|
.order-status-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: rgba(255, 107, 107, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card .card-header {
|
||||||
|
background: linear-gradient(90deg, #ff9ff3 0%, #feca57 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
margin: -10px -10px 5px -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card .card-title {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card .card-more {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card .card-more:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-more {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-more:hover {
|
||||||
|
color: #ff5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-grid {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
padding: 16px 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #666666;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:hover .status-icon {
|
||||||
|
background-color: rgba(255, 107, 107, 0.1);
|
||||||
|
color: #ff6b6b;
|
||||||
|
transform: scale(1.1) rotate(5deg);
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-size: 8px;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:hover .status-label {
|
||||||
|
color: #ff6b6b;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 功能菜单网格 */
|
||||||
|
.user-function-grid {
|
||||||
|
position: absolute;
|
||||||
|
left: -300%;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info:hover .user-function-grid {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
left: -215%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
width: 160px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整user-info的宽度,确保左侧有足够空间显示功能菜单 */
|
||||||
|
.user-info {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整功能网格布局,使其在窄列中更紧凑 */
|
||||||
|
.function-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card:hover {
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: rgba(255, 107, 107, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 功能卡片头部公共样式 */
|
||||||
|
.function-card .card-header {
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
margin: -16px -16px 16px -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card .card-title {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 购物相关卡片 */
|
||||||
|
.function-card:nth-child(1) .card-header {
|
||||||
|
background: linear-gradient(90deg, #ff6b6b 0%, #ff8e53 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优惠相关卡片 */
|
||||||
|
.function-card:nth-child(2) .card-header {
|
||||||
|
background: linear-gradient(90deg, #4ecdc4 0%, #45b7d1 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 账户相关卡片 */
|
||||||
|
.function-card:nth-child(3) .card-header {
|
||||||
|
background: linear-gradient(90deg, #96ceb4 0%, #feca57 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
padding: 12px 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #666666;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item:hover .function-icon {
|
||||||
|
background-color: rgba(78, 205, 196, 0.1);
|
||||||
|
color: #4ecdc4;
|
||||||
|
transform: scale(1.1) rotate(5deg);
|
||||||
|
box-shadow: 0 4px 12px rgba(78, 205, 196, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item:hover .function-label {
|
||||||
|
color: #4ecdc4;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-value {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ff5000;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.user-info {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-top {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar-wrapper {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-stats {
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-actions {
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-grid {
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
padding: 12px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-function-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item {
|
||||||
|
padding: 12px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整卡片头部样式 */
|
||||||
|
.function-card .card-header {
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin: -16px -16px 16px -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-card .card-header {
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin: -16px -16px 16px -16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.user-info-top {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-text {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-stats {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status-grid {
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
padding: 8px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item {
|
||||||
|
padding: 8px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-label {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 375px) {
|
||||||
|
.order-status-grid {
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
padding: 6px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item {
|
||||||
|
padding: 6px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,67 +6,121 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="footer-copyright">
|
<div id="footer-copyright">
|
||||||
<!-- 版权信息 -->
|
<!-- 版权信息 -->
|
||||||
<h2>Copyright @ 2023 淘淘王</h2>
|
<div class="copyright-content">
|
||||||
|
<p class="copyright-text">© 2026 TaoTaoWang. 保留所有权利</p>
|
||||||
|
<div class="copyright-links">
|
||||||
|
<a href="/about">关于我们</a>
|
||||||
|
<span class="copyright-separator">|</span>
|
||||||
|
<a href="/privacy">隐私政策</a>
|
||||||
|
<span class="copyright-separator">|</span>
|
||||||
|
<a href="/terms">服务条款</a>
|
||||||
|
<span class="copyright-separator">|</span>
|
||||||
|
<a href="/contact">联系我们</a>
|
||||||
|
</div>
|
||||||
|
<p class="copyright-icp">京ICP备12345678号-1 | 京公网安备11010502030123号</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import productList from './product/productList.vue'
|
import productList from './product/productList.vue'
|
||||||
|
import ProductService from '@/Service/ProductService'
|
||||||
|
import type { Product } from '@/Util/Type'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
// 定义商品列表
|
// 定义商品列表
|
||||||
const productLists = ref([
|
const productLists = ref<Product[]>([])
|
||||||
{
|
|
||||||
id: '1',
|
// 加载商品数据
|
||||||
name: '商品1',
|
onMounted(() => {
|
||||||
price: '100',
|
ProductService.listProductsByPage(1, 6).then(res => {
|
||||||
img: '',
|
productLists.value = res.data.data || []
|
||||||
},
|
})
|
||||||
{
|
})
|
||||||
id: '2',
|
|
||||||
name: '商品2',
|
|
||||||
price: '200',
|
|
||||||
img: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
name: '商品3',
|
|
||||||
price: '300',
|
|
||||||
img: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
name: '商品4',
|
|
||||||
price: '400',
|
|
||||||
img: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
name: '商品4',
|
|
||||||
price: '400',
|
|
||||||
img: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
name: '商品4',
|
|
||||||
price: '400',
|
|
||||||
img: '',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#footer {
|
#footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 20px;
|
padding: 0 var(--spacing-md);
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
color: #42b983;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer-contact {
|
#footer-contact {
|
||||||
/* 高度根据内容自适应 */
|
/* 高度根据内容自适应 */
|
||||||
height: initial;
|
height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 版权信息样式 */
|
||||||
|
#footer-copyright {
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-content {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-text {
|
||||||
|
margin: 0 0 var(--spacing-sm) 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-links {
|
||||||
|
margin: 0 0 var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-links a {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 var(--spacing-sm);
|
||||||
|
transition: color var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-links a:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-separator {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
margin: 0 var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-icp {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式调整 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.copyright-content {
|
||||||
|
padding: 0 var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-links a {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright-separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,29 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="header">
|
<div id="header" v-if="booleanHeader">
|
||||||
<div id="header-profile">
|
<div id="header-profile">
|
||||||
<div id="header-profile-left">
|
<div id="header-profile-left">
|
||||||
<!-- 用户信息悬浮菜单 -->
|
<!-- 用户信息悬浮菜单 -->
|
||||||
<Dropdown :menu="{ items: userMenu }" trigger="hover">
|
<Dropdown :menu="{ items: userMenu }" trigger="hover">
|
||||||
<div class="user-info-container">
|
<div class="user-info-container">
|
||||||
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=user123" />
|
<div class="user-avatar-wrapper">
|
||||||
<span class="user-name">用户名</span>
|
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=user123" class="user-avatar" />
|
||||||
|
<span class="user-online-indicator"></span>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-text">
|
||||||
|
<span class="user-name">用户名</span>
|
||||||
|
<span class="user-level">Lv.3</span>
|
||||||
|
</div>
|
||||||
<span class="user-arrow">▼</span>
|
<span class="user-arrow">▼</span>
|
||||||
</div>
|
</div>
|
||||||
|
<template #overlay>
|
||||||
|
<div class="user-info-menu">
|
||||||
|
<div class="user-info-menu-item">
|
||||||
|
<a href="javascript:;">1st menu item</a>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-menu-item">
|
||||||
|
<a href="javascript:;">2nd menu item</a>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-menu-item">
|
||||||
|
<a href="javascript:;">3rd menu item</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div id="header-profile-right">
|
<div id="header-profile-right">
|
||||||
<!-- 右侧功能菜单 -->
|
<!-- 右侧功能菜单 -->
|
||||||
<Dropdown :menu="{ items: rightMenu }" trigger="hover">
|
<Dropdown :menu="{ items: rightMenu }" trigger="hover">
|
||||||
<Button type="text">更多</Button>
|
<Button type="text" class="header-more-btn">更多 <i class="iconfont icon-zhankaishouqi"></i></Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<a href="/">首页</a>
|
<router-link to="/home" class="header-nav-link">首页</router-link>
|
||||||
<Button type="primary" @click="router.push('/cart')">购物车</Button>
|
<Button type="primary" @click="router.push('/cart')" class="header-cart-btn">
|
||||||
<Button type="primary" @click="router.push('/order')">订单</Button>
|
<i class="iconfont icon-gouwuche"></i> 购物车
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" @click="router.push({ path: '/user', query: { nav: 'order' } })" class="header-order-btn">
|
||||||
|
<i class="iconfont icon-dingdan"></i> 订单
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Row id="header-nav-row" v-if="booleanSearch">
|
<Row id="header-nav-row" v-if="booleanSearch">
|
||||||
<Col class="header-nav-Logo" :span="4">
|
<Col class="header-nav-Logo" :span="4">
|
||||||
<a href="/">TaoTaoWang</a>
|
<router-link to="/home">TaoTaoWang</router-link>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="header-nav-search" :span="16">
|
<Col class="header-nav-search" :span="16">
|
||||||
<div class="search-input-container" :class="{ 'search-focused': showHistory }">
|
<div class="search-input-container" :class="{ 'search-focused': showHistory }">
|
||||||
@@ -31,30 +54,23 @@
|
|||||||
<!-- 搜索类型选择 -->
|
<!-- 搜索类型选择 -->
|
||||||
<div class="search-type-selector">
|
<div class="search-type-selector">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="search-type-item active" @click="setSearchType('宝贝')">宝贝</li>
|
<li class="search-type-item active" @click="setSearchType('宝贝', $event)">宝贝</li>
|
||||||
<li class="search-type-item" @click="setSearchType('店铺')">店铺</li>
|
<li class="search-type-item" @click="setSearchType('店铺', $event)">店铺</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 搜索输入区域 -->
|
<!-- 搜索输入区域 -->
|
||||||
<div class="search-input-wrapper">
|
<div class="search-input-wrapper">
|
||||||
<div class="search-input-prefix">
|
<div class="search-input-prefix">
|
||||||
<i class="iconfont icon-sousuo prefix-icon"></i>
|
<i class="iconfont icon-sousuo prefix-icon"></i>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input v-model:value="searchValue" placeholder="请输入搜索关键词" style="width: 100%" @search="onSearch"
|
||||||
v-model:value="searchValue"
|
@focus="onInputFocus" @blur="onInputBlur" class="custom-search-input" />
|
||||||
placeholder="请输入搜索关键词"
|
|
||||||
style="width: 100%"
|
|
||||||
@search="onSearch"
|
|
||||||
@focus="onInputFocus"
|
|
||||||
@blur="onInputBlur"
|
|
||||||
class="custom-search-input"
|
|
||||||
/>
|
|
||||||
<div class="search-input-suffix" v-if="searchValue">
|
<div class="search-input-suffix" v-if="searchValue">
|
||||||
<i class="iconfont icon-guanbi" @click="clearSearch"></i>
|
<i class="iconfont icon-guanbi" @click="clearSearch"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 搜索按钮 -->
|
<!-- 搜索按钮 -->
|
||||||
<div class="search-button-container" @onclick="onSearch">
|
<div class="search-button-container" @onclick="onSearch">
|
||||||
<span class="search-button-text">搜索</span>
|
<span class="search-button-text">搜索</span>
|
||||||
@@ -82,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="header-nav-product" :span="4">
|
<Col class="header-nav-product" :span="4">
|
||||||
<a href="#">随便放点东西显得对称</a>
|
<router-link to="/home">随便放点东西显得对称</router-link>
|
||||||
<!-- 其他页面的时候修改布局 -->
|
<!-- 其他页面的时候修改布局 -->
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -90,9 +106,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, RouterLink } from 'vue-router'
|
||||||
import { Button, Col, Row, Space, Dropdown, Avatar, Menu, Divider, Tag } from 'ant-design-vue';
|
import { Button, Col, Row, Space, Dropdown, Avatar, Menu, Divider, Tag } from 'ant-design-vue';
|
||||||
|
import Input from 'ant-design-vue/es/input';
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
// 定义搜索框绑定的变量
|
// 定义搜索框绑定的变量
|
||||||
const searchValue = ref('')
|
const searchValue = ref('')
|
||||||
@@ -100,21 +117,29 @@ const searchValue = ref('')
|
|||||||
const showHistory = ref(false)
|
const showHistory = ref(false)
|
||||||
// 标志:是否正在清空历史记录
|
// 标志:是否正在清空历史记录
|
||||||
const isClearingHistory = ref(false)
|
const isClearingHistory = ref(false)
|
||||||
// 标志:是否在搜索界面
|
// 控制搜索框是否显示
|
||||||
const booleanSearch = ref(true)
|
const booleanSearch = ref(true)
|
||||||
// 路由事件
|
// 控制Header是否显示
|
||||||
router.beforeEach((to, from, next) => {
|
const booleanHeader = ref(true)
|
||||||
if (to.name === 'search' && to.query.keyword) {
|
// 监听路由变化 当路由变化时 更新搜索框的显示状态
|
||||||
searchValue.value = to.query.keyword as string
|
watch(() => router.currentRoute.value.path, (newPath) => {
|
||||||
|
// 获取当前路由名称
|
||||||
|
const newName = router.currentRoute.value.name
|
||||||
|
// 如果是搜索页面 则更新查询参数
|
||||||
|
if (newName === 'search') {
|
||||||
|
searchValue.value = router.currentRoute.value.query.keyword as string
|
||||||
}
|
}
|
||||||
console.log(to.name)
|
// 如果是商品页面 则显示搜索框
|
||||||
// 如果在商品页面 隐藏搜索框
|
if (newName === 'productDetail' || newName === 'chat') {
|
||||||
if (to.name === 'productDetail') {
|
|
||||||
booleanSearch.value = false
|
|
||||||
} else {
|
|
||||||
booleanSearch.value = true
|
booleanSearch.value = true
|
||||||
}
|
}
|
||||||
next()
|
// 如果在登录页面 则隐藏整个header
|
||||||
|
if (newPath === '/login') {
|
||||||
|
booleanHeader.value = false
|
||||||
|
} else {
|
||||||
|
booleanHeader.value = true
|
||||||
|
}
|
||||||
|
console.log('当前路由:', newName, newPath)
|
||||||
})
|
})
|
||||||
// 定义搜索框的搜索事件
|
// 定义搜索框的搜索事件
|
||||||
const onSearch = (value: string) => {
|
const onSearch = (value: string) => {
|
||||||
@@ -173,15 +198,56 @@ const onInputBlur = () => {
|
|||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当前搜索类型
|
||||||
|
const currentSearchType = ref('宝贝')
|
||||||
|
|
||||||
// 设置搜索类型
|
// 设置搜索类型
|
||||||
const setSearchType = (type: string) => {
|
const setSearchType = (type: string, event: MouseEvent) => {
|
||||||
const typeItems = document.querySelectorAll('.search-type-item')
|
const typeItems = document.querySelectorAll('.search-type-item')
|
||||||
typeItems.forEach(item => {
|
// 找到当前活跃的元素
|
||||||
item.classList.remove('active')
|
const currentActiveItem = document.querySelector('.search-type-item.active')
|
||||||
})
|
// 保存点击的元素引用
|
||||||
// 添加active类到点击的元素
|
const clickedTarget = event.currentTarget as HTMLElement
|
||||||
// event?.currentTarget?.classList.add('active')
|
|
||||||
console.log('搜索类型:', type)
|
// 为当前活跃的元素添加消失动画
|
||||||
|
if (currentActiveItem && currentActiveItem !== clickedTarget) {
|
||||||
|
if (currentSearchType.value === '宝贝' && type === '店铺') {
|
||||||
|
// 宝贝 -> 店铺:宝贝元素从左往右消失
|
||||||
|
currentActiveItem.classList.add('animate-right-out')
|
||||||
|
} else if (currentSearchType.value === '店铺' && type === '宝贝') {
|
||||||
|
// 店铺 -> 宝贝:店铺元素从右往左消失
|
||||||
|
currentActiveItem.classList.add('animate-left-out')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待消失动画完成后再继续
|
||||||
|
setTimeout(() => {
|
||||||
|
// 移除所有元素的类
|
||||||
|
typeItems.forEach(item => {
|
||||||
|
item.classList.remove('active', 'animate-left', 'animate-right', 'animate-left-out', 'animate-right-out')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加active类到点击的元素
|
||||||
|
if (clickedTarget) {
|
||||||
|
// 判断切换方向
|
||||||
|
if (currentSearchType.value === '宝贝' && type === '店铺') {
|
||||||
|
// 宝贝 -> 店铺:从左往右出现
|
||||||
|
clickedTarget.classList.add('active', 'animate-right')
|
||||||
|
} else if (currentSearchType.value === '店铺' && type === '宝贝') {
|
||||||
|
// 店铺 -> 宝贝:从右往左出现
|
||||||
|
clickedTarget.classList.add('active', 'animate-left')
|
||||||
|
} else {
|
||||||
|
// 默认动画
|
||||||
|
clickedTarget.classList.add('active')
|
||||||
|
}
|
||||||
|
currentSearchType.value = type
|
||||||
|
}
|
||||||
|
console.log('搜索类型:', type)
|
||||||
|
|
||||||
|
}, 300)
|
||||||
|
} else {
|
||||||
|
// 如果点击的是当前活跃的元素,直接返回
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除搜索输入
|
// 清除搜索输入
|
||||||
@@ -226,9 +292,7 @@ const rightMenu = [
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
#header {
|
#header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #ffffff;
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
padding: 10px 20px;
|
|
||||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
@@ -242,7 +306,6 @@ const rightMenu = [
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#header-profile-left {
|
#header-profile-left {
|
||||||
@@ -255,38 +318,317 @@ const rightMenu = [
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 用户信息容器样式 */
|
/* 用户信息容器样式 */
|
||||||
.user-info-container {
|
.user-info-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: var(--spacing-md);
|
||||||
padding: 8px 12px;
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
border-radius: 20px;
|
border-radius: var(--border-radius-3xl);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info-container:hover {
|
.user-info-container:hover {
|
||||||
background-color: #f0f2f5;
|
background-color: var(--bg-light);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户头像区域 */
|
||||||
|
.user-avatar-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
border: 2px solid #ffffff;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-container:hover .user-avatar {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-online-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: var(--success-color);
|
||||||
|
border: 2px solid var(--card-bg);
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户信息文本区域 */
|
||||||
|
.user-info-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-name {
|
.user-name {
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-level {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: var(--warning-color);
|
||||||
|
background-color: #fff7e6;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-arrow {
|
.user-arrow {
|
||||||
font-size: 12px;
|
font-size: var(--font-size-xs);
|
||||||
color: #999;
|
color: var(--text-tertiary);
|
||||||
transition: transform 0.3s ease;
|
transition: transform var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info-container:hover .user-arrow {
|
.user-info-container:hover .user-arrow {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg) scale(1.1);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
/* 用户信息菜单样式 */
|
||||||
|
.user-info-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-menu-item {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius-2xl);
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-menu-item:hover {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧功能菜单样式 */
|
||||||
|
.header-more-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius-2xl);
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-more-btn:hover {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav-link {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius-2xl);
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav-link:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: var(--bg-primary-lightest);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav-link::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2px;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-radius: 1px;
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav-link:hover::after {
|
||||||
|
width: 80%;
|
||||||
|
left: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 购物车和订单按钮样式 */
|
||||||
|
.header-cart-btn,
|
||||||
|
.header-order-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius-3xl);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all var(--transition-transform);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-order-btn {
|
||||||
|
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-hover));
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn:hover,
|
||||||
|
.header-order-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn:active,
|
||||||
|
.header-order-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn::before,
|
||||||
|
.header-order-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: shine 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
transform: rotate(45deg) translateX(-100%) translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(45deg) translateX(100%) translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#header-profile {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-container {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-level {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header-profile-right {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav-link {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn,
|
||||||
|
.header-order-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn i,
|
||||||
|
.header-order-btn i,
|
||||||
|
.header-more-btn i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.user-info-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-container {
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav-link {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn span,
|
||||||
|
.header-order-btn span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-btn,
|
||||||
|
.header-order-btn {
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logo样式 */
|
/* Logo样式 */
|
||||||
@@ -311,7 +653,7 @@ const rightMenu = [
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 700px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,22 +736,83 @@ const rightMenu = [
|
|||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
left: 0;
|
left: -10%;
|
||||||
width: 100%;
|
width: 120%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: #ff5000;
|
background-color: #ff5000;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
animation: slideIn 0.3s ease-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideIn {
|
/* 从左往右动画 */
|
||||||
|
.search-type-item.animate-right::after {
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 从右往左动画 */
|
||||||
|
.search-type-item.animate-left::after {
|
||||||
|
animation: slideInLeft 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 从左往右消失动画 */
|
||||||
|
.search-type-item.animate-right-out::after {
|
||||||
|
animation: slideOutRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 从右往左消失动画 */
|
||||||
|
.search-type-item.animate-left-out::after {
|
||||||
|
animation: slideOutLeft 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索类型选择器从左往右动画 */
|
||||||
|
@keyframes slideInRight {
|
||||||
from {
|
from {
|
||||||
width: 0;
|
width: 0;
|
||||||
left: 50%;
|
left: -10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
|
width: 120%;
|
||||||
|
left: -10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索类型选择器从右往左动画 */
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
width: 0;
|
||||||
|
left: 120%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
width: 120%;
|
||||||
|
left: -10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 从左往右消失动画 */
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
width: 120%;
|
||||||
|
left: -10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
width: 0;
|
||||||
|
left: 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 从右往左消失动画 */
|
||||||
|
@keyframes slideOutLeft {
|
||||||
|
from {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0;
|
right: -10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
width: 0%;
|
||||||
|
right: -200%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,15 +845,17 @@ const rightMenu = [
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
padding: 0 50px 0 48px;
|
padding: 0 25px 0 15px;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
box-sizing: border-box; /* 关键! */
|
box-sizing: border-box;
|
||||||
outline: none; /* 避免默认焦点轮廓 */
|
/* 关键! */
|
||||||
|
outline: none;
|
||||||
|
/* 避免默认焦点轮廓 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 可选:焦点状态 */
|
/* 可选:焦点状态 */
|
||||||
@@ -499,6 +904,7 @@ const rightMenu = [
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-50%) scale(0.8);
|
transform: translateY(-50%) scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(-50%) scale(1);
|
transform: translateY(-50%) scale(1);
|
||||||
@@ -517,8 +923,7 @@ const rightMenu = [
|
|||||||
|
|
||||||
/* 搜索按钮 */
|
/* 搜索按钮 */
|
||||||
.search-button-container {
|
.search-button-container {
|
||||||
margin-right: 1px;
|
padding: 16.5px 30px;
|
||||||
padding: 15.5px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(135deg, #ff5000, #ff8c00);
|
background: linear-gradient(135deg, #ff5000, #ff8c00);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@@ -597,42 +1002,42 @@ const rightMenu = [
|
|||||||
.search-type-selector {
|
.search-type-selector {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-type-selector ul {
|
.search-type-selector ul {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-type-item {
|
.search-type-item {
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input-wrapper {
|
.search-input-wrapper {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-search-input {
|
.custom-search-input {
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
height: 48px !important;
|
height: 48px !important;
|
||||||
padding: 0 40px 0 40px !important;
|
padding: 0 40px 0 40px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input-prefix {
|
.search-input-prefix {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-container {
|
.search-button-container {
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-text {
|
.search-button-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-icon {
|
.search-button-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-container:hover .search-button-icon {
|
.search-button-container:hover .search-button-icon {
|
||||||
transform: scale(1.05) rotate(10deg);
|
transform: scale(1.05) rotate(10deg);
|
||||||
}
|
}
|
||||||
@@ -642,36 +1047,36 @@ const rightMenu = [
|
|||||||
.search-type-selector {
|
.search-type-selector {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-type-selector ul {
|
.search-type-selector ul {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-type-item {
|
.search-type-item {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input-wrapper {
|
.search-input-wrapper {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-search-input {
|
.custom-search-input {
|
||||||
font-size: 13px !important;
|
font-size: 13px !important;
|
||||||
padding: 0 35px 0 35px !important;
|
padding: 0 35px 0 35px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input-prefix {
|
.search-input-prefix {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-container {
|
.search-button-container {
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-text {
|
.search-button-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-icon {
|
.search-button-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Button, Col, Row, Carousel } from 'ant-design-vue';
|
import { Button, Col, Row, Carousel } from 'ant-design-vue';
|
||||||
import UserInfo from '@/Views/User/Userinfo.vue'
|
import UserInfo from '@/views/User/Userinfo.vue'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// 按钮数据数组
|
// 按钮数据数组
|
||||||
@@ -83,11 +83,11 @@ const adList = [
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
#main {
|
#main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 20px;
|
padding: 0 var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: #42b983;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-header {
|
#main-header {
|
||||||
@@ -95,8 +95,8 @@ h1 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: var(--spacing-md);
|
||||||
padding: 16px 0;
|
padding: var(--spacing-md) 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
@@ -104,41 +104,41 @@ h1 {
|
|||||||
#main-header .ant-btn {
|
#main-header .ant-btn {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-md);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-header .ant-btn-primary {
|
#main-header .ant-btn-primary {
|
||||||
background-color: #1890ff;
|
background-color: var(--secondary-color);
|
||||||
border-color: #1890ff;
|
border-color: var(--secondary-color);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-header .ant-btn-primary:hover {
|
#main-header .ant-btn-primary:hover {
|
||||||
background-color: #40a9ff;
|
background-color: var(--secondary-hover);
|
||||||
border-color: #40a9ff;
|
border-color: var(--secondary-hover);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||||
}
|
}
|
||||||
/* 商品分类样式 */
|
/* 商品分类样式 */
|
||||||
.main-content-col-top {
|
.main-content-col-top {
|
||||||
padding: 20px;
|
padding: var(--spacing-md);
|
||||||
background-color: #ffffff;
|
background-color: var(--card-bg);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-lg);
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-lg);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top:hover {
|
.main-content-col-top:hover {
|
||||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
box-shadow: var(--shadow-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -161,36 +161,35 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top li {
|
.main-content-col-top li {
|
||||||
margin-bottom: 12px;
|
margin-bottom: var(--spacing-sm);
|
||||||
padding: 8px;
|
padding: var(--spacing-sm);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-md);
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分类详情样式 */
|
/* 分类详情样式 */
|
||||||
.main-content-col-top li:hover {
|
.main-content-col-top li:hover {
|
||||||
background-color: #fff5f5;
|
background-color: var(--bg-primary-light);
|
||||||
transform: translateX(8px);
|
transform: translateX(var(--spacing-sm));
|
||||||
box-shadow: 0 2px 8px rgba(255, 80, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(var(--primary-light-rgb), 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分类项样式 */
|
/* 分类项样式 */
|
||||||
.category-item {
|
.category-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: var(--spacing-xs);
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 12px;
|
border-radius: var(--border-radius-md);
|
||||||
border-radius: 8px;
|
transition: all var(--transition-transform);
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-item:hover {
|
/* .category-item:hover {
|
||||||
background-color: rgba(255, 80, 0, 0.05);
|
background-color: rgba(255, 80, 0, 0.05);
|
||||||
}
|
} */
|
||||||
|
|
||||||
/* 分类详情样式 */
|
/* 分类详情样式 */
|
||||||
.category-detail {
|
.category-detail {
|
||||||
@@ -199,17 +198,17 @@ h1 {
|
|||||||
top: -60px;
|
top: -60px;
|
||||||
width: 600px;
|
width: 600px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background-color: #ffffff;
|
background-color: var(--card-bg);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-lg);
|
||||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: var(--shadow-xl);
|
||||||
padding: 20px;
|
padding: var(--spacing-md);
|
||||||
margin-left: 10px;
|
margin-left: var(--spacing-sm);
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transform: translateX(-20px);
|
transform: translateX(-20px);
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid var(--border-color);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,16 +228,16 @@ h1 {
|
|||||||
.category-detail-item {
|
.category-detail-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: var(--spacing-sm);
|
||||||
padding: 12px 0;
|
padding: var(--spacing-sm) 0;
|
||||||
border-bottom: 1px solid #f5f5f5;
|
border-bottom: 1px solid var(--bg-light);
|
||||||
transition: all 0.2s ease;
|
transition: all var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-detail-item:hover {
|
.category-detail-item:hover {
|
||||||
background-color: #f9f9f9;
|
background-color: var(--bg-light);
|
||||||
padding-left: 8px;
|
padding-left: var(--spacing-xs);
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-detail-item:last-child {
|
.category-detail-item:last-child {
|
||||||
@@ -246,16 +245,16 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.category-detail-item a {
|
.category-detail-item a {
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-detail-item a:hover {
|
.category-detail-item a:hover {
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,8 +265,8 @@ h1 {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: #ff5000;
|
background-color: var(--primary-color);
|
||||||
transition: width 0.3s ease;
|
transition: width var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-detail-item a:hover::after {
|
.category-detail-item a:hover::after {
|
||||||
@@ -275,103 +274,104 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.category-detail-item span {
|
.category-detail-item span {
|
||||||
color: #999;
|
color: var(--text-tertiary);
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
transition: color 0.3s ease;
|
transition: color var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top .iconfont {
|
.main-content-col-top .iconfont {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top li:hover .iconfont {
|
.main-content-col-top li:hover .iconfont {
|
||||||
transform: scale(1.1) rotate(5deg);
|
transform: scale(1.1) rotate(5deg);
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top a {
|
.main-content-col-top a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
transition: all var(--transition-transform);
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top a:hover {
|
.main-content-col-top a:hover {
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
background-color: rgba(255, 80, 0, 0.1);
|
background-color: var(--primary-light);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top span {
|
.main-content-col-top span {
|
||||||
color: #999;
|
color: var(--text-tertiary);
|
||||||
font-size: 14px;
|
font-size: var(--font-size-sm);
|
||||||
transition: color 0.3s ease;
|
transition: color var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-top li:hover span {
|
.main-content-col-top li:hover span {
|
||||||
color: #ff5000;
|
color: var(--primary-color);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 主内容区域样式 */
|
/* 主内容区域样式 */
|
||||||
#main-content {
|
#main-content {
|
||||||
margin-top: 20px;
|
margin-top: var(--spacing-xl);
|
||||||
gap: 32px;
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-center{
|
.main-content-col-center{
|
||||||
background-color: #ffffff;
|
background-color: var(--card-bg);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-md);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content-col-center h2{
|
.main-content-col-center h2{
|
||||||
font-size: 18px;
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: var(--text-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 16px;
|
padding: var(--spacing-md);
|
||||||
background-color: #fafafa;
|
background-color: var(--bg-light);
|
||||||
border-radius: 6px;
|
border-radius: var(--border-radius-sm);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-content-ad,
|
#main-content-ad,
|
||||||
#main-content-hot-goods {
|
#main-content-hot-goods {
|
||||||
padding: 0 20px;
|
padding: 0 var(--spacing-xl);
|
||||||
margin-bottom: 16px;
|
margin-bottom: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-content-ad .ant-col,
|
#main-content-ad .ant-col,
|
||||||
#main-content-hot-goods .ant-col {
|
#main-content-hot-goods .ant-col {
|
||||||
background-color: #fafafa;
|
background-color: var(--bg-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-md);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-content-ad .ant-col:hover,
|
#main-content-ad .ant-col:hover,
|
||||||
#main-content-hot-goods .ant-col:hover {
|
#main-content-hot-goods .ant-col:hover {
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: var(--shadow-lg);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
/* 轮播广告样式 */
|
/* 轮播广告样式 */
|
||||||
#carousel-container {
|
#carousel-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 8px;
|
height: 100%;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-item {
|
.carousel-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ h1 {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
transition: transform 0.5s ease;
|
transition: transform var(--transition-slow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-item:hover .carousel-image {
|
.carousel-item:hover .carousel-image {
|
||||||
@@ -391,22 +391,22 @@ h1 {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: var(--spacing-xl);
|
||||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: 24px;
|
font-size: var(--font-size-2xl);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-carousel .slick-dots {
|
.ant-carousel .slick-dots {
|
||||||
bottom: 20px;
|
bottom: var(--spacing-xl);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-carousel .slick-dots li button {
|
.ant-carousel .slick-dots li button {
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
@@ -419,20 +419,5 @@ h1 {
|
|||||||
|
|
||||||
/* 个人信息样式 */
|
/* 个人信息样式 */
|
||||||
.main-content-col-bottom {
|
.main-content-col-bottom {
|
||||||
padding: 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content-col-bottom h2 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
padding: 16px;
|
|
||||||
background-color: #fafafa;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<!-- 根据父类传递的商品列表渲染商品列表 -->
|
<!-- 根据父类传递的商品列表渲染商品列表 -->
|
||||||
<template>
|
<template>
|
||||||
<div id="product-list">
|
<div id="product-list">
|
||||||
<div class="product-item" v-for="product in productList" :key="product.id">
|
<div class="product-item" v-for="item in productList" :key="(item as Product).id || (item as ProductDetail).product.id">
|
||||||
<!-- 这个商品的id是加密 -->
|
<!-- 这个商品的id是加密 -->
|
||||||
<div class="product-img-container" @click="handleClick(product)">
|
<div class="product-img-container" @click="handleClick(item)">
|
||||||
<img class="product-img" :src="product.img || '/0.png'" alt="商品图片">
|
<img class="product-img" :src="((item as Product).mainImage || (item as ProductDetail).product.mainImage) || '/0.png'" :alt="((item as Product).productName || (item as ProductDetail).product.productName)">
|
||||||
<div class="product-img-mask">
|
<div class="product-img-mask">
|
||||||
<div class="product-img-actions">
|
<div class="product-img-actions">
|
||||||
<span class="action-btn">查看详情</span>
|
<span class="action-btn">查看详情</span>
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-content">
|
<div class="product-content">
|
||||||
<h3 class="product-name">{{ product.name }}</h3>
|
<h3 class="product-name">{{ (item as Product).productName || (item as ProductDetail).product.productName }}</h3>
|
||||||
<div class="product-price">
|
<div class="product-price">
|
||||||
<span class="price-symbol">¥</span>
|
<span class="price-symbol">¥</span>
|
||||||
<span class="price-value">{{ product.price }}</span>
|
<span class="price-value">{{ (item as Product).currentPrice || (item as ProductDetail).product.currentPrice }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,20 +23,22 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type PropType } from 'vue'
|
import { type PropType } from 'vue'
|
||||||
import type { ProductDetail } from '@/Util/Type'
|
import type { Product, ProductDetail } from '@/Util/Type'
|
||||||
import { useGlobalStore } from '@/Util/globalStore'
|
import { useGlobalStore } from '@/Util/globalStore'
|
||||||
import router from '@/Route/route'
|
import router from '@/Route/route'
|
||||||
// 接收父类传递的商品列表
|
// 接收父类传递的商品列表
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
productList: {
|
productList: {
|
||||||
type: Array as PropType<ProductDetail[]>,
|
type: Array as PropType<(Product | ProductDetail)[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// 点击商品跳转详情页
|
// 点击商品跳转详情页
|
||||||
const handleClick = (product: ProductDetail) => {
|
const handleClick = (product: any) => {
|
||||||
|
// 获取商品ID
|
||||||
|
const productId = product.id || product.product?.id
|
||||||
// id 加密
|
// id 加密
|
||||||
const encryptedId = btoa(product.id)
|
const encryptedId = btoa(productId.toString())
|
||||||
// 跳转详情页 (新开页面)
|
// 跳转详情页 (新开页面)
|
||||||
window.open(`/product?id=${encryptedId}`, '_blank')
|
window.open(`/product?id=${encryptedId}`, '_blank')
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,14 +5,14 @@
|
|||||||
<div class="product-modal-container">
|
<div class="product-modal-container">
|
||||||
<div class="product-modal-right">
|
<div class="product-modal-right">
|
||||||
<div class="product-modal-img">
|
<div class="product-modal-img">
|
||||||
<img :src="currentProduct.img" alt="产品图片" class="product-modal-image">
|
<img :src="currentProduct.mainImage" alt="产品图片" class="product-modal-image">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-modal-left">
|
<div class="product-modal-left">
|
||||||
<div class="product-modal-top">
|
<div class="product-modal-top">
|
||||||
<div class="product-modal-price">
|
<div class="product-modal-price">
|
||||||
<span class="price-label">价格:</span>
|
<span class="price-label">价格:</span>
|
||||||
<span class="price-value">¥{{ currentProduct.price }}</span>
|
<span class="price-value">¥{{ currentProduct.currentPrice }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-modal-bottom">
|
<div class="product-modal-bottom">
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
<div v-for="item in currentItems" :key="item.id" class="model-item"
|
<div v-for="item in currentItems" :key="item.id" class="model-item"
|
||||||
:class="{ active: currentProduct.id === item.id }" @click="selectProduct(item)">
|
:class="{ active: currentProduct.id === item.id }" @click="selectProduct(item)">
|
||||||
<div class="model-img">
|
<div class="model-img">
|
||||||
<img :src="item.img" alt="型号图片" class="model-image">
|
<img :src="item.mainImage" alt="型号图片" class="model-image">
|
||||||
</div>
|
</div>
|
||||||
<div class="model-name">{{ item.model }}</div>
|
<div class="model-name">{{ item.productName }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
6
src/shims-vue.d.ts
vendored
Normal file
6
src/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// src/shims-vue.d.ts
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "../admin_frontend/src/shims-vue.d.ts"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|||||||
Reference in New Issue
Block a user