Compare commits

..

1 Commits

Author SHA1 Message Date
qingfeng1121
2d34823985 refactor(项目结构): 重构前端项目结构并优化功能
重构项目目录结构,将Views重命名为views,统一命名规范
新增多个服务类(ProductService, ShopService等)实现API调用
优化路由配置,添加404页面和路由守卫
完善类型定义(Type.ts),增加详细接口类型
修复登录逻辑,正确处理API响应
优化商品详情页,对接后端API获取真实数据
重构用户中心页面,对接订单和用户信息接口
统一CSS文件位置,修复样式问题
2026-01-28 10:24:32 +08:00
25 changed files with 932 additions and 331 deletions

View File

@@ -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/ # 图片资源

View File

@@ -1,13 +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>

View File

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

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

View File

@@ -1,58 +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 User from '../Views/User/User.vue'
import Chat from '../Views/Chat.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: '/user', path: '/:pathMatch(.*)*',
name: 'user', name: 'not-found',
component: User component: () => import('../views/404/index.vue'),
}, meta: {
{ requiresAuth: false,
path: '/chat', title: '页面不存在'
name: 'chat', }
component: Chat }
},
] ]
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

View File

@@ -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) => {
// 处理响应错误 // 处理响应错误

View 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()

View 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()

View 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()

View File

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

View File

@@ -1,44 +1,194 @@
// 项目中使用的类型定义 // 项目中使用的类型定义
// 响应结果类型
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 Cart = { // 店铺请求类型
id: string export type ShopRequest = {
name: string id?: number
price: string page?: number
img: string size?: number
count: number
} }
// 商品类型 // 店铺响应类型
export type Product = { export type ShopResponse = {
id: string shop: Shop
name: string; // 商品名称 products: ProductsResponse[]
description: string; // 商品描述
price: number; // 商品价格
quantity: number; // 商品数量
image: string; // 商品图片
model: string; // 商品型号
} }
// 订单商品类型 // 订单商品类型
export type OrderProduct = { export type OrderItem = {
productId: string | number // 商品 ID id?: number
quantity: 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 = { export type OrderDetail = {
id: string // 订单 ID id: string // 订单 ID
shopName: string // 店铺名称 shopName: string // 店铺名称
@@ -51,18 +201,56 @@ export type OrderDetail = {
paymentMethod: string // 支付方式 paymentMethod: string // 支付方式
goods: Product[] // 商品列表 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 = {
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 rememberMe: boolean
} }
// 地址类型 // 地址类型
export type Address = { export type Address = {
id: number; // 修改时有 ID新增时无 id: number; // 修改时有 ID新增时无

119
src/Views/404/index.vue Normal file
View 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>

View File

@@ -2,7 +2,6 @@
body { body {
height: 100%; height: 100%;
padding: 0 var(--spacing-lg); padding: 0 var(--spacing-lg);
background-color: var(--bg-color);
font-family: var(--font-family); font-family: var(--font-family);
font-size: var(--font-size-base); font-size: var(--font-size-base);
color: var(--text-primary); color: var(--text-primary);

View File

@@ -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[]>([

View File

@@ -78,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)

View File

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

View File

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

View File

@@ -93,17 +93,17 @@
<!-- 用户统计数据 --> <!-- 用户统计数据 -->
<div class="user-stats"> <div class="user-stats">
<div class="stat-item"> <div class="stat-item">
<span class="stat-value">{{ user.followCount }}</span> <span class="stat-value">0</span>
<span class="stat-label">关注</span> <span class="stat-label">关注</span>
</div> </div>
<div class="stat-divider"></div> <div class="stat-divider"></div>
<div class="stat-item"> <div class="stat-item">
<span class="stat-value">{{ user.fansCount }}</span> <span class="stat-value">0</span>
<span class="stat-label">粉丝</span> <span class="stat-label">粉丝</span>
</div> </div>
<div class="stat-divider"></div> <div class="stat-divider"></div>
<div class="stat-item"> <div class="stat-item">
<span class="stat-value">{{ user.collectionCount }}</span> <span class="stat-value">0</span>
<span class="stat-label">收藏</span> <span class="stat-label">收藏</span>
</div> </div>
</div> </div>
@@ -215,28 +215,28 @@
<GiftOutlined /> <GiftOutlined />
</div> </div>
<div class="function-label">红包</div> <div class="function-label">红包</div>
<div class="function-badge" v-if="user.couponCount > 0">{{ user.couponCount }}</div> <div class="function-badge">0</div>
</div> </div>
<div class="function-item" @click="navigateTo('/user/discounts')"> <div class="function-item" @click="navigateTo('/user/discounts')">
<div class="function-icon"> <div class="function-icon">
<TagOutlined /> <TagOutlined />
</div> </div>
<div class="function-label">优惠券</div> <div class="function-label">优惠券</div>
<div class="function-badge" v-if="user.discountCount > 0">{{ user.discountCount }}</div> <div class="function-badge">0</div>
</div> </div>
<div class="function-item" @click="navigateTo('/user/tao_coin')"> <div class="function-item" @click="navigateTo('/user/tao_coin')">
<div class="function-icon"> <div class="function-icon">
<GoldOutlined /> <GoldOutlined />
</div> </div>
<div class="function-label">淘币</div> <div class="function-label">淘币</div>
<div class="function-value">{{ user.taoCoin }}</div> <div class="function-value">0</div>
</div> </div>
<div class="function-item" @click="navigateTo('/user/points')"> <div class="function-item" @click="navigateTo('/user/points')">
<div class="function-icon"> <div class="function-icon">
<TrophyOutlined /> <TrophyOutlined />
</div> </div>
<div class="function-label">积分</div> <div class="function-label">积分</div>
<div class="function-value">{{ user.points }}</div> <div class="function-value">0</div>
</div> </div>
</div> </div>
</div> </div>
@@ -334,33 +334,33 @@
<div class="order-header"> <div class="order-header">
<div class="order-header-left"> <div class="order-header-left">
<span class="order-time">下单时间{{ order.createTime }}</span> <span class="order-time">下单时间{{ order.createTime }}</span>
<span class="order-id">订单编号{{ order.orderId }}</span> <span class="order-id">订单编号{{ order.orderNo }}</span>
<a class="shop-name" @click="visitShop(order.shopId)">{{ order.shopName }}</a> <a class="shop-name" @click="visitShop(order.shopId)">店铺名称</a>
</div> </div>
<span class="order-status">{{ order.statusText }}</span> <span class="order-status">{{ getOrderStatusText(order.orderStatus) }}</span>
</div> </div>
<!-- 订单商品信息 --> <!-- 订单商品信息 -->
<div class="order-goods"> <div class="order-goods">
<div class="goods-item"> <div class="goods-item">
<div class="goods-image"> <div class="goods-image">
<img :src="order.goods.image" :alt="order.goods.name"> <img src="/0.png" alt="商品图片">
</div> </div>
<div class="goods-info"> <div class="goods-info">
<h4 class="goods-name">{{ order.goods.name }}</h4> <h4 class="goods-name">商品名称</h4>
<p class="goods-desc">{{ order.goods.description }}</p> <p class="goods-desc">商品描述</p>
</div> </div>
<div class="goods-quantity">x{{ order.goods.quantity }}</div> <div class="goods-quantity">x1</div>
<div class="goods-receiver">{{ order.receiverName }}</div> <div class="goods-receiver">{{ order.receiverName }}</div>
<div class="goods-amount"> <div class="goods-amount">
¥{{ order.totalAmount.toFixed(2) }} ¥{{ order.totalAmount.toFixed(2) }}
<!-- 显示支付方式线上还是到付 --> <!-- 显示支付方式线上还是到付 -->
<div class="order-amount">{{ order.paymentMethod === 'online' ? '线上支付' : '到付' }}</div> <div class="order-amount">{{ order.paymentMethod === '1' ? '线上支付' : '到付' }}</div>
</div> </div>
<div class="goods-status">{{ order.statusText }}</div> <div class="goods-status">{{ getOrderStatusText(order.orderStatus) }}</div>
<div class="goods-actions"> <div class="goods-actions">
<!-- 未完成状态操作按钮 --> <!-- 未完成状态操作按钮 -->
<template v-if="order.status === 'pending_receipt' || order.status === 'pending_review'"> <template v-if="order.orderStatus === 3 || order.orderStatus === 4">
<button class="order-action-btn" @click="applyAfterSale(order.id)">申请售后</button> <button class="order-action-btn" @click="applyAfterSale(order.id)">申请售后</button>
<button class="order-action-btn" @click="viewInvoice(order.id)">查看发票</button> <button class="order-action-btn" @click="viewInvoice(order.id)">查看发票</button>
<button class="order-action-btn" @click="reviewOrder(order.id)">评价</button> <button class="order-action-btn" @click="reviewOrder(order.id)">评价</button>
@@ -368,14 +368,14 @@
</template> </template>
<!-- 配送中状态操作按钮 --> <!-- 配送中状态操作按钮 -->
<template v-else-if="order.status === 'pending_shipping'"> <template v-else-if="order.orderStatus === 2">
<button class="order-action-btn" @click="cancelOrder(order.id)">取消</button> <button class="order-action-btn" @click="cancelOrder(order.id)">取消</button>
<button class="order-action-btn" @click="viewInvoice(order.id)">查看发票</button> <button class="order-action-btn" @click="viewInvoice(order.id)">查看发票</button>
<button class="order-action-btn primary" @click="remindShipping(order.id)">催单</button> <button class="order-action-btn primary" @click="remindShipping(order.id)">催单</button>
</template> </template>
<!-- 已完成状态操作按钮 --> <!-- 已完成状态操作按钮 -->
<template v-else-if="order.status === 'completed'"> <template v-else-if="order.orderStatus === 5">
<button class="order-action-btn" @click="viewInvoice(order.id)">查看发票</button> <button class="order-action-btn" @click="viewInvoice(order.id)">查看发票</button>
<button class="order-action-btn primary" @click="buyAgain(order.id)">再来一份</button> <button class="order-action-btn primary" @click="buyAgain(order.id)">再来一份</button>
</template> </template>
@@ -538,9 +538,11 @@
import { ref, onMounted, watch } from 'vue' import { ref, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } 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' import { ShoppingCartOutlined, HeartOutlined, HistoryOutlined, ShopOutlined, GiftOutlined, GoldOutlined, TrophyOutlined, SettingOutlined, EnvironmentOutlined, CustomerServiceOutlined, QuestionCircleOutlined, CreditCardOutlined, CarOutlined, LoadingOutlined, CheckOutlined, SolutionOutlined, TagOutlined } from '@ant-design/icons-vue'
import UpdataAddress from '@/Views/User/UpdataAddress.vue' import UpdataAddress from '@/views/User/UpdataAddress.vue'
import Search from '@/Component/common/Search.vue' import Search from '@/Component/common/Search.vue'
import type { Address } from '@/Util/Type' import UserService from '@/Service/UserService'
import OrderService from '@/Service/OrderService'
import type { Address, User, Order } from '@/Util/Type'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@@ -582,19 +584,22 @@ const updateaddress = ref<Address>({
// UpdataAddress组件控制 // UpdataAddress组件控制
const visible = ref(false) const visible = ref(false)
// 用户信息 // 用户信息
const user = ref({ const user = ref<User>({
id: 0,
username: '用户名', username: '用户名',
password: '',
email: '',
phone: '',
avatar: '', avatar: '',
level: 3, status: 0,
followCount: 12, createTime: '',
fansCount: 8, updateTime: ''
collectionCount: 25,
couponCount: 3,
discountCount: 5,
taoCoin: 1280,
points: 560
}) })
// 加载状态
const loading = ref(false)
const error = ref('')
// 订单状态数量 // 订单状态数量
const orderCounts = ref({ const orderCounts = ref({
pending_payment: 2, pending_payment: 2,
@@ -812,62 +817,7 @@ const navigateToOrder = (status: string) => {
} }
// 订单数据模型 // 订单数据模型
const orders = ref([ const orders = ref<Order[]>([])
{
id: 1,
orderId: '20240101123456',
createTime: '2024-01-01 10:00:00',
shopId: 1,
shopName: 'Apple官方旗舰店',
receiverName: '张三',
status: 'completed',
statusText: '已完成',
totalAmount: 5999,
paymentMethod: 'online',
goods: {
image: 'https://neeko-copilot.bytedance.net/api/text2image?prompt=iPhone%2015%20pro%20product%20image&image_size=square',
name: 'iPhone 15 Pro 256GB 钛金属色',
description: 'A17 Pro芯片钛金属机身全天候显示屏',
quantity: 1
}
},
{
id: 2,
orderId: '20240102123456',
createTime: '2024-01-02 15:30:00',
shopId: 2,
shopName: '小米官方旗舰店',
receiverName: '李四',
status: 'pending_receipt',
statusText: '待收货',
totalAmount: 3999,
paymentMethod: 'online',
goods: {
image: 'https://neeko-copilot.bytedance.net/api/text2image?prompt=Xiaomi%2014%20ultra%20product%20image&image_size=square',
name: '小米14 Ultra 512GB 黑色',
description: '骁龙8 Gen 3徕卡四摄120W快充',
quantity: 1
}
},
{
id: 3,
orderId: '20240103123456',
createTime: '2024-01-03 09:15:00',
shopId: 3,
shopName: '华为官方旗舰店',
receiverName: '王五',
status: 'pending_shipping',
statusText: '待发货',
totalAmount: 4999,
paymentMethod: 'online',
goods: {
image: 'https://neeko-copilot.bytedance.net/api/text2image?prompt=Huawei%20Mate%2060%20pro%20product%20image&image_size=square',
name: '华为Mate 60 Pro 512GB 玄黑',
description: '麒麟9000S超感知徕卡三摄66W快充',
quantity: 1
}
}
])
// 申请售后 // 申请售后
@@ -905,6 +855,64 @@ const remindShipping = (orderId: number) => {
console.log('催单:', orderId) console.log('催单:', orderId)
// 这里可以添加催单的逻辑 // 这里可以添加催单的逻辑
} }
// 获取订单状态文本
const getOrderStatusText = (status: number): string => {
switch (status) {
case 0:
return '待付款'
case 1:
return '待发货'
case 2:
return '待收货'
case 3:
return '待评价'
case 4:
return '已完成'
case 5:
return '已取消'
default:
return '未知状态'
}
}
// 加载用户信息
const loadUserInfo = async () => {
loading.value = true
error.value = ''
try {
// 这里假设用户ID为1实际项目中应该从本地存储或登录状态中获取
const userId = 1
const response = await UserService.getUserInfo(userId)
if (response.data && response.data.data) {
user.value = response.data.data
}
} catch (err) {
error.value = '获取用户信息失败'
console.error('获取用户信息失败:', err)
} finally {
loading.value = false
}
}
// 加载订单数据
const loadOrders = async () => {
loading.value = true
error.value = ''
try {
// 这里假设用户ID为1实际项目中应该从本地存储或登录状态中获取
const userId = 1
const response = await OrderService.getOrdersByUser(userId, current.value, 10)
if (response.data && response.data.data) {
orders.value = response.data.data
}
} catch (err) {
error.value = '获取订单数据失败'
console.error('获取订单数据失败:', err)
} finally {
loading.value = false
}
}
// 如果路由参数更改,更新活跃导航项和订单标签 // 如果路由参数更改,更新活跃导航项和订单标签
watch(() => route.query, (newQuery) => { watch(() => route.query, (newQuery) => {
updateActiveNav() updateActiveNav()
@@ -913,6 +921,8 @@ watch(() => route.query, (newQuery) => {
// 初始化时检查URL参数设置活跃导航项 // 初始化时检查URL参数设置活跃导航项
onMounted(() => { onMounted(() => {
updateActiveNav() updateActiveNav()
loadUserInfo()
loadOrders()
}) })
</script> </script>

View File

@@ -24,49 +24,21 @@
</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 {

View File

@@ -35,18 +35,18 @@
<Dropdown :menu="{ items: rightMenu }" trigger="hover"> <Dropdown :menu="{ items: rightMenu }" trigger="hover">
<Button type="text" class="header-more-btn">更多 <i class="iconfont icon-zhankaishouqi"></i></Button> <Button type="text" class="header-more-btn">更多 <i class="iconfont icon-zhankaishouqi"></i></Button>
</Dropdown> </Dropdown>
<a href="/" class="header-nav-link">首页</a> <router-link to="/home" class="header-nav-link">首页</router-link>
<Button type="primary" @click="router.push('/cart')" class="header-cart-btn"> <Button type="primary" @click="router.push('/cart')" class="header-cart-btn">
<i class="iconfont icon-gouwuche"></i> 购物车 <i class="iconfont icon-gouwuche"></i> 购物车
</Button> </Button>
<Button type="primary" @click="router.push('/order')" class="header-order-btn"> <Button type="primary" @click="router.push({ path: '/user', query: { nav: 'order' } })" class="header-order-btn">
<i class="iconfont icon-dingdan"></i> 订单 <i class="iconfont icon-dingdan"></i> 订单
</Button> </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 }">
@@ -98,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>
@@ -107,7 +107,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } 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'; import Input from 'ant-design-vue/es/input';
const router = useRouter() const router = useRouter()
@@ -122,7 +122,9 @@ const booleanSearch = ref(true)
// 控制Header是否显示 // 控制Header是否显示
const booleanHeader = ref(true) const booleanHeader = ref(true)
// 监听路由变化 当路由变化时 更新搜索框的显示状态 // 监听路由变化 当路由变化时 更新搜索框的显示状态
watch(() => router.currentRoute.value.name, (newName) => { watch(() => router.currentRoute.value.path, (newPath) => {
// 获取当前路由名称
const newName = router.currentRoute.value.name
// 如果是搜索页面 则更新查询参数 // 如果是搜索页面 则更新查询参数
if (newName === 'search') { if (newName === 'search') {
searchValue.value = router.currentRoute.value.query.keyword as string searchValue.value = router.currentRoute.value.query.keyword as string
@@ -131,11 +133,13 @@ watch(() => router.currentRoute.value.name, (newName) => {
if (newName === 'productDetail' || newName === 'chat') { if (newName === 'productDetail' || newName === 'chat') {
booleanSearch.value = true booleanSearch.value = true
} }
// 如果在登录页面 则隐藏搜索框 // 如果在登录页面 则隐藏整个header
if (newName === 'login') { if (newPath === '/login') {
booleanHeader.value = false booleanHeader.value = false
} else {
booleanHeader.value = true
} }
console.log(newName) console.log('当前路由:', newName, newPath)
}) })
// 定义搜索框的搜索事件 // 定义搜索框的搜索事件
const onSearch = (value: string) => { const onSearch = (value: string) => {

View File

@@ -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()
// 按钮数据数组 // 按钮数据数组

View File

@@ -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')
} }

View File

@@ -7,15 +7,15 @@
<!-- 店铺信息 --> <!-- 店铺信息 -->
<div> <div>
<!-- 店铺头像 --> <!-- 店铺头像 -->
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=user123" /> <Avatar size="large" :src="shopData?.shopLogo || 'https://api.dicebear.com/7.x/avataaars/svg?seed=user123'" />
</div> </div>
<!-- 店铺名称 店铺评分 --> <!-- 店铺名称 店铺评分 -->
<div class="shop-info-item"> <div class="shop-info-item">
<div class="shop-info-label">店铺名称 <div class="shop-info-label">{{ shopData?.shopName || '店铺名称' }}
<div class="shop-info-detail"> <div class="shop-info-detail">
<div class="shop-info-detail-item"> <div class="shop-info-detail-item">
<div class="shop-info-detail-label">店铺详情</div> <div class="shop-info-detail-label">店铺详情</div>
<div class="shop-info-detail-value">店铺详情内容</div> <div class="shop-info-detail-value">{{ shopData?.shopDescription || '店铺详情内容' }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -301,12 +301,12 @@
<!-- 右侧内容 --> <!-- 右侧内容 -->
<div class="right-panel" > <div class="right-panel" >
<div class="product-info"> <div class="product-info">
<h1 class="product-title">BenQ 明基投影仪 商务办公会议培训用 1080P高清智能投影机</h1> <h1 class="product-title">{{ productData?.products.productName || '商品名称' }}</h1>
<div class="price-section"> <div class="price-section">
<div class="price-row"> <div class="price-row">
<span class="price-label">券后价</span> <span class="price-label">券后价</span>
<span class="price-value">{{ selectedModelInfo?.price }}</span> <span class="price-value">{{ selectedModelInfo?.modelPrice || productData?.products.currentPrice }}</span>
<span class="original-price">{{ selectedModelInfo?.originalPrice }}</span> <span class="original-price">{{ productData?.products.originalPrice }}</span>
</div> </div>
<div class="tags"> <div class="tags">
<span class="tag">官方正品</span> <span class="tag">官方正品</span>
@@ -324,23 +324,23 @@
</div> </div>
<!-- 文从左到右 --> <!-- 文从左到右 -->
<div v-if="isSmallView" class="model-options-small"> <div v-if="isSmallView" class="model-options-small">
<div v-for="model in models" :key="model.id" class="model-option" <div v-for="model in productData?.models" :key="model.id" class="model-option"
:class="{ active: selectedModel === model.id }" @click="selectModel(model.id)"> :class="{ active: selectedModel === model.id }" @click="selectedModel = model.id">
<div class="model-small-item"> <div class="model-small-item">
<img :src="model.img" alt="{{ model.name }}"> <img :src="model.modelImage" :alt="model.modelName">
<span> {{ model.name }}</span> <span> {{ model.modelName }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 图上文下--> <!-- 图上文下-->
<div v-if="!isSmallView" class="model-options-big"> <div v-if="!isSmallView" class="model-options-big">
<div v-for="model in models" :key="model.id" class="model-option" <div v-for="model in productData?.models" :key="model.id" class="model-option"
:class="{ active: selectedModel === model.id }" @click="selectModel(model.id)"> :class="{ active: selectedModel === model.id }" @click="selectedModel = model.id">
<!-- 图片 --> <!-- 图片 -->
<div class="model-image"> <div class="model-image">
<img :src="model.img" alt="{{ model.name }}"> <img :src="model.modelImage" :alt="model.modelName">
</div> </div>
<div class="model-name">{{ model.name }}</div> <div class="model-name">{{ model.modelName }}</div>
</div> </div>
</div> </div>
<!-- 数量 --> <!-- 数量 -->
@@ -368,44 +368,42 @@
<SettlementLite ref="settlementLite" v-model:visible="visible" /> <SettlementLite ref="settlementLite" v-model:visible="visible" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import SettlementLite from '../../Component/business/SettlementLite.vue' import SettlementLite from '../../Component/business/SettlementLite.vue'
import type { OrderProduct } from '@/Util/Type'; import ProductService from '@/Service/ProductService'
import ShopService from '@/Service/ShopService'
import type { ProductsResponse, Shop } from '@/Util/Type';
const router = useRouter() const router = useRouter()
const productId = ref(router.currentRoute.value.params.productId)
const visible = ref(false) const visible = ref(false)
const currentIndex = ref(0) // 商品数据
const images = ref([ const productData = ref<ProductsResponse | null>(null)
'/0.png', const shopData = ref<Shop | null>(null)
'/0.png', const loading = ref(true)
'/0.png',
'/0.png',
'/0.png'
])
const currentImage = computed(() => images.value[currentIndex.value]) // 图片数据
const currentIndex = ref(0)
const images = ref<string[]>([])
const currentImage = computed(() => images.value[currentIndex.value] || '/0.png')
const selectImage = (index: number) => { const selectImage = (index: number) => {
currentIndex.value = index currentIndex.value = index
} }
// 型号选择
const selectedModel = ref<number | null>(null)
const selectedModelInfo = computed(() => {
if (!productData.value?.models) return null
return selectedModel.value ? productData.value.models.find(model => model.id === selectedModel.value) : productData.value.models[0]
})
// 提交表单 商品基础信息 型号 数量 // 提交表单 商品基础信息 型号 数量
const formOrderProduct = ref<OrderProduct>({ const formOrderProduct = ref({
productId: 0, productId: 0,
quantity: 1, quantity: 1,
}) })
const selectedModel = ref('x500')
const models = ref([
{ id: 'x500', name: 'X500', price: '999', originalPrice: '1999', img: '/0.png' },
{ id: 'w600', name: 'W600', price: '1299', originalPrice: '2499', img: '/0.png' },
{ id: 'e800', name: 'E800', price: '1599', originalPrice: '3299', img: '/0.png' }
])
const selectModel = (id: string) => {
selectedModel.value = id
}
const selectedModelInfo = computed(() => models.value.find(model => model.id === selectedModel.value))
const activeTab = ref('reviews') const activeTab = ref('reviews')
const setActiveTab = (tab: string) => { const setActiveTab = (tab: string) => {
@@ -453,34 +451,82 @@ const buyProduct = () => {
visible.value = true visible.value = true
console.log('领券购买') console.log('领券购买')
} }
// 加载商品数据
const loadProductData = () => {
const encryptedId = router.currentRoute.value.query.id as string
if (!encryptedId) return
try {
const productId = parseInt(atob(encryptedId))
loading.value = true
// 获取商品详情
ProductService.getProductInfo(productId).then(res => {
if (res.data.code === 200) {
productData.value = res.data.data
formOrderProduct.value.productId = productId
// 处理图片
if (productData.value.products && productData.value.products.mainImage) {
images.value = [productData.value.products.mainImage]
}
if (productData.value.images) {
images.value = productData.value.images.map(img => img.imageUrl)
}
// 处理型号
if (productData.value.models && productData.value.models.length > 0) {
selectedModel.value = productData.value.models[0].id
}
// 获取店铺信息
if (productData.value.products && productData.value.products.shopId) {
ShopService.getShopInfo(productData.value.products.shopId).then(shopRes => {
if (shopRes.data.code === 200) {
shopData.value = shopRes.data.data
}
})
}
}
}).finally(() => {
loading.value = false
})
} catch (error) {
console.error('解析商品ID失败:', error)
loading.value = false
}
}
// 屏幕滚动 超过300px 右侧内容根据屏幕固定位置 // 屏幕滚动 超过300px 右侧内容根据屏幕固定位置
window.addEventListener('scroll', () => { window.addEventListener('scroll', () => {
const scrollTop = window.scrollY; const scrollTop = window.scrollY;
const rightPanel = document.querySelector('.right-panel') as HTMLElement; const rightPanel = document.querySelector('.right-panel') as HTMLElement;
console.log("scrollTop:", scrollTop)
console.log("rightPanel:", rightPanel) if (rightPanel) {
if (scrollTop > 150) {
if (scrollTop > 150) { rightPanel.classList.add('fixed');
rightPanel.classList.add('fixed'); } else {
} else { rightPanel.classList.remove('fixed');
rightPanel.classList.remove('fixed'); }
} }
}); });
// 组件挂载时加载数据
onMounted(() => {
loadProductData()
})
</script> </script>
<style scoped> <style scoped>
#product-detail { #product-detail {
width: 100%; width: 100%;
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5;
} }
.product-detail-container { .product-detail-container {
display: flex; display: flex;
margin-bottom: 20px; margin-bottom: 20px;
overflow: hidden; overflow: hidden;
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
border: 1px solid #f0f0f0;
} }
.left-panel { .left-panel {
@@ -644,7 +690,7 @@ window.addEventListener('scroll', () => {
.right-panel.fixed { .right-panel.fixed {
position: fixed; position: fixed;
top: 0; top: 0;
right: 24px; right: 59px;
} }
.product-info { .product-info {
width: 765px; width: 765px;

View File

@@ -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.image" 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.image" 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
View 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
}