Compare commits
1 Commits
73cf25e586
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d34823985 |
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,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>
|
||||||
|
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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()
|
||||||
236
src/Util/Type.ts
236
src/Util/Type.ts
@@ -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
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>
|
||||||
@@ -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);
|
||||||
@@ -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[]>([
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
// 按钮数据数组
|
// 按钮数据数组
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user