DM3实际开发例子
Product
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 商品实体类
* </p>
*
* @author qi
* @since 2025-03-13
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="Product对象", description="商品信息表")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "商品ID")
@TableId(value = "product_id")
private String productId;
@ApiModelProperty(value = "商品名称")
private String productName;
@ApiModelProperty(value = "商品价格")
private Double productPrice;
@ApiModelProperty(value = "商品型号ID")
private Integer productModel;
@ApiModelProperty(value = "商品数量")
private Integer productQuantity;
@ApiModelProperty(value = "商品品牌ID")
private Integer productBrand;
@ApiModelProperty(value = "商品详情")
private String productDetail;
@ApiModelProperty(value = "商品图片")
private String productPhoto;
@ApiModelProperty(value = "用户ID")
private String userId;
// 非数据库字段,用于前端展示
@ApiModelProperty(value = "型号名称")
private transient String modelName;
// 非数据库字段,用于前端展示
@ApiModelProperty(value = "品牌名称")
private transient String brandName;
// 非数据库字段,用于前端展示
@ApiModelProperty(value = "用户账号")
private transient String userAccount;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Double getProductPrice() {
return productPrice;
}
public void setProductPrice(Double productPrice) {
this.productPrice = productPrice;
}
public Integer getProductModel() {
return productModel;
}
public void setProductModel(Integer productModel) {
this.productModel = productModel;
}
public Integer getProductQuantity() {
return productQuantity;
}
public void setProductQuantity(Integer productQuantity) {
this.productQuantity = productQuantity;
}
public Integer getProductBrand() {
return productBrand;
}
public void setProductBrand(Integer productBrand) {
this.productBrand = productBrand;
}
public String getProductDetail() {
return productDetail;
}
public void setProductDetail(String productDetail) {
this.productDetail = productDetail;
}
public String getProductPhoto() {
return productPhoto;
}
public void setProductPhoto(String productPhoto) {
this.productPhoto = productPhoto;
}
public String getModelName() {
return modelName;
}
public void setModelName(String modelName) {
this.modelName = modelName;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserAccount() {
return userAccount;
}
public void setUserAccount(String userAccount) {
this.userAccount = userAccount;
}
}
ProductMapper
package com.example.demo.mapper;
import com.example.demo.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* <p>
* 商品Mapper接口
* </p>
*
* @author qi
* @since 2025-03-13
*/
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
// 继承自BaseMapper,已经包含了基本的CRUD方法
// 自定义插入方法
int insertProduct(Product product);
// 查询商品详情(包含型号名称和品牌名称)
Product selectProductDetail(String productId);
// 根据用户ID查询商品列表
List<Product> selectProductsByUserId(String userId);
}
ProductServiceImpl
package com.example.demo.service.impl;
import com.example.demo.entity.Product;
import com.example.demo.mapper.ProductMapper;
import com.example.demo.service.ProductService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>
* 商品服务实现类
* </p>
*
* @author qi
* @since 2025-03-13
*/
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
// 继承自ServiceImpl,已经实现了IService接口中的基本CRUD方法
// 用于生成序号的计数器,使用AtomicInteger保证线程安全
private static final AtomicInteger counter = new AtomicInteger(1);
@Override
public String generateProductId() {
// 生成商品ID:P + 年月日 + 4位序号
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dateStr = sdf.format(new Date());
// 获取计数器的值,并确保是4位数
int sequence = counter.getAndIncrement();
if (counter.get() > 9999) {
counter.set(1); // 重置计数器
}
return "P" + dateStr + String.format("%04d", sequence);
}
@Override
public boolean save(Product product) {
// 检查必填字段
if (product.getProductName() == null || product.getProductName().trim().isEmpty() ||
product.getProductPrice() == null || product.getProductPrice() <= 0 ||
product.getProductModel() == null || product.getProductQuantity() == null ||
product.getProductBrand() == null || product.getProductDetail() == null ||
product.getProductPhoto() == null || product.getUserId() == null ||
product.getUserId().trim().isEmpty()) {
return false;
}
// 生成商品ID
if (product.getProductId() == null || product.getProductId().trim().isEmpty()) {
product.setProductId(generateProductId());
}
// 插入商品
return baseMapper.insertProduct(product) > 0;
}
@Override
public Product getProductDetail(String productId) {
return ((ProductMapper) baseMapper).selectProductDetail(productId);
}
@Override
public List<Product> getProductsByUserId(String userId) {
return ((ProductMapper) baseMapper).selectProductsByUserId(userId);
}
}
ProductService
package com.example.demo.service;
import com.example.demo.entity.Product;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 商品服务接口
* </p>
*
* @author qi
* @since 2025-03-13
*/
public interface ProductService extends IService<Product> {
// 继承自IService接口,已经包含了基本的CRUD方法
// 生成商品ID
String generateProductId();
// 获取商品详情(包含型号名称和品牌名称)
Product getProductDetail(String productId);
// 根据用户ID查询商品列表
List<Product> getProductsByUserId(String userId);
}
Product.vue
<template>
<div class="product-container">
<div class="search-container">
<el-input
v-model="searchForm.productName"
placeholder="请输入商品名称"
style="width: 200px; margin-right: 10px"
/>
<el-button type="primary" @click="loadProducts">搜索</el-button>
<el-button type="success" @click="handleAdd">新增商品</el-button>
</div>
<el-table :data="tableData" border style="width: 100%; margin-top: 20px">
<el-table-column prop="productId" label="商品ID" width="120" />
<el-table-column prop="productName" label="商品名称" width="150" />
<el-table-column prop="productPrice" label="价格" width="100">
<template #default="scope">
¥{{ scope.row.productPrice }}
</template>
</el-table-column>
<el-table-column prop="modelName" label="型号" width="120" />
<el-table-column prop="brandName" label="品牌" width="120" />
<el-table-column prop="productQuantity" label="库存" width="80" />
<el-table-column prop="productPhoto" label="商品图片" width="120">
<template #default="scope">
<el-image
v-if="scope.row.productPhoto"
:src="scope.row.productPhoto"
style="width: 80px; height: 80px; cursor: pointer"
:preview-src-list="[scope.row.productPhoto]"
fit="cover"
@click="goToDetail(scope.row.productId)"
/>
<span v-else>无图片</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 新增/编辑商品对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增商品' : '编辑商品'"
width="50%"
>
<el-form :model="form" label-width="100px">
<el-form-item label="商品名称" required>
<el-input v-model="form.productName" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="商品价格" required>
<el-input-number v-model="form.productPrice" :precision="2" :min="0" :step="0.01" />
</el-form-item>
<el-form-item label="商品型号" required>
<el-select v-model="form.productModel" placeholder="请选择型号">
<el-option
v-for="item in modelOptions"
:key="item.modelId"
:label="item.modelName"
:value="item.modelId"
/>
</el-select>
</el-form-item>
<el-form-item label="商品品牌" required>
<el-select v-model="form.productBrand" placeholder="请选择品牌">
<el-option
v-for="item in brandOptions"
:key="item.brandId"
:label="item.brandName"
:value="item.brandId"
/>
</el-select>
</el-form-item>
<el-form-item label="库存数量" required>
<el-input-number v-model="form.productQuantity" :min="0" :step="1" />
</el-form-item>
<el-form-item label="商品详情">
<el-input
v-model="form.productDetail"
type="textarea"
:rows="3"
placeholder="请输入商品详情"
/>
</el-form-item>
<el-form-item label="商品图片">
<div class="upload-container">
<div class="image-preview" v-if="form.productPhoto">
<el-image
:src="form.productPhoto"
style="width: 150px; height: 150px"
fit="cover"
/>
<el-button
type="danger"
size="small"
icon="Delete"
circle
class="delete-btn"
@click="removeImage"
/>
</div>
<el-upload
v-else
class="image-uploader"
:auto-upload="false"
:show-file-list="false"
:on-change="handleImageChange"
accept="image/jpeg,image/png,image/jpg,image/gif"
>
<el-icon class="upload-icon"><Plus /></el-icon>
<div class="upload-text">点击上传图片</div>
</el-upload>
</div>
<div class="upload-tip">只能上传jpg/png/gif文件,且不超过2MB</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import productApi from '@/api/product'
import modelsApi from '@/api/models'
import brandApi from '@/api/brand'
// 路由实例
const router = useRouter()
// 获取当前登录用户信息
const currentUser = JSON.parse(localStorage.getItem('user') || '{}')
// 判断用户角色
const isStoreStaff = computed(() => {
return currentUser.userRole === 4 // 假设roleId为4是店员
})
// 表格数据
const tableData = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
// 下拉选项
const modelOptions = ref([])
const brandOptions = ref([])
// 搜索表单
const searchForm = reactive({
productName: ''
})
// 新增/编辑表单
const form = reactive({
productId: null,
productName: '',
productPrice: 0,
productModel: null,
productQuantity: 0,
productBrand: null,
productDetail: '',
productPhoto: ''
})
// 对话框控制
const dialogVisible = ref(false)
const dialogType = ref('add') // 'add' 或 'edit'
// 加载商品数据
const loadProducts = async () => {
try {
const params = {
current: currentPage.value,
size: pageSize.value,
productName: searchForm.productName
}
const res = await productApi.getProductPage(params)
if (res.code === 200) {
// 获取商品列表
let products = res.data.records
total.value = res.data.total
// 如果是店员,只显示用户ID与自己ID一致的商品
if (isStoreStaff.value) {
products = products.filter(product => product.userId === currentUser.userId)
total.value = products.length // 更新总数为筛选后的数量
}
// 处理每个商品,获取型号和品牌名称
const productPromises = products.map(async (product) => {
try {
const detailRes = await productApi.getProductDetail(product.productId)
if (detailRes.code === 200) {
return {
...product,
modelName: detailRes.data.modelName,
brandName: detailRes.data.brandName
}
}
return product
} catch (error) {
console.error(`获取商品${product.productId}详情出错:`, error)
return product
}
})
// 等待所有请求完成
tableData.value = await Promise.all(productPromises)
} else {
ElMessage.error(res.message || '获取商品列表失败')
}
} catch (error) {
console.error('获取商品列表出错:', error)
ElMessage.error('获取商品列表失败')
}
}
// 处理编辑商品
const handleEdit = async (row) => {
dialogType.value = 'edit'
try {
// 获取商品详情
const res = await productApi.getProductDetail(row.productId)
if (res.code === 200) {
Object.assign(form, res.data)
// 如果没有用户ID,则使用当前登录用户的ID
if (!form.userId) {
const userInfo = JSON.parse(localStorage.getItem('user') || '{}')
form.userId = userInfo.userId || ''
}
} else {
ElMessage.error(res.message || '获取商品详情失败')
}
} catch (error) {
console.error('获取商品详情出错:', error)
ElMessage.error('获取商品详情失败')
}
dialogVisible.value = true
}
// 加载型号和品牌数据
const loadOptions = async () => {
try {
// 加载型号选项
const modelsRes = await modelsApi.getAllModels()
if (modelsRes.code === 200) {
modelOptions.value = modelsRes.data
}
// 加载品牌选项
const brandsRes = await brandApi.getAllBrands()
if (brandsRes.code === 200) {
brandOptions.value = brandsRes.data
}
} catch (error) {
console.error('加载选项数据出错:', error)
ElMessage.error('加载选项数据失败')
}
}
// 处理分页变化
const handleSizeChange = (val) => {
pageSize.value = val
loadProducts()
}
const handleCurrentChange = (val) => {
currentPage.value = val
loadProducts()
}
// 处理新增商品
const handleAdd = () => {
dialogType.value = 'add'
// 获取当前登录用户信息
const userInfo = JSON.parse(localStorage.getItem('user') || '{}')
Object.assign(form, {
productId: null,
productName: '',
productPrice: 0,
productModel: null,
productQuantity: 0,
productBrand: null,
productDetail: '',
productPhoto: '',
userId: userInfo.userId || '' // 设置用户ID为当前登录用户ID
})
dialogVisible.value = true
}
// 处理删除商品
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该商品吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
const res = await productApi.deleteProduct(row.productId)
if (res.code === 200) {
ElMessage.success('删除成功')
loadProducts()
} else {
ElMessage.error(res.message || '删除失败')
}
} catch (error) {
console.error('删除商品出错:', error)
ElMessage.error('删除失败')
}
})
.catch(() => {
// 取消删除操作
})
}
// 处理图片上传并转换为base64
const handleImageChange = (file) => {
const isImage = file.raw.type.startsWith('image/')
const isLt2M = file.raw.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('上传图片只能是图片格式!')
return
}
if (!isLt2M) {
ElMessage.error('上传图片大小不能超过 2MB!')
return
}
// 将图片转换为base64
const reader = new FileReader()
reader.readAsDataURL(file.raw)
reader.onload = () => {
form.productPhoto = reader.result
}
}
// 移除图片
const removeImage = () => {
form.productPhoto = ''
}
// 提交表单
const submitForm = async () => {
// 表单验证
if (!form.productName) {
ElMessage.warning('请输入商品名称')
return
}
if (form.productPrice === null || form.productPrice === undefined) {
ElMessage.warning('请输入商品价格')
return
}
if (!form.productModel) {
ElMessage.warning('请选择商品型号')
return
}
if (!form.productBrand) {
ElMessage.warning('请选择商品品牌')
return
}
if (form.productQuantity === null || form.productQuantity === undefined) {
ElMessage.warning('请输入库存数量')
return
}
// 确保用户ID存在,如果不存在则获取当前登录用户ID
if (!form.userId) {
const userInfo = JSON.parse(localStorage.getItem('user') || '{}')
if (!userInfo.userId) {
ElMessage.warning('用户未登录或登录信息已失效,请重新登录')
return
}
form.userId = userInfo.userId
}
try {
let res
if (dialogType.value === 'add') {
// 新增商品
res = await productApi.addProduct(form)
} else {
// 编辑商品
res = await productApi.updateProduct(form)
}
if (res.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '编辑成功')
dialogVisible.value = false
loadProducts()
} else {
ElMessage.error(res.message || (dialogType.value === 'add' ? '新增失败' : '编辑失败'))
}
} catch (error) {
console.error(dialogType.value === 'add' ? '新增商品出错:' : '编辑商品出错:', error)
ElMessage.error(dialogType.value === 'add' ? '新增失败' : '编辑失败')
}
}
// 页面加载时获取数据
onMounted(() => {
loadProducts()
loadOptions()
})
// 跳转到商品详情页
const goToDetail = (productId) => {
router.push(`/manager/product-detail/${productId}`)
}
</script>
<style scoped>
.product-container {
padding: 20px;
}
.search-container {
display: flex;
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.upload-container {
display: flex;
justify-content: center;
align-items: center;
}
.image-uploader {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 150px;
height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.image-uploader:hover {
border-color: #409EFF;
}
.upload-icon {
font-size: 28px;
color: #8c939d;
}
.upload-text {
color: #8c939d;
margin-top: 8px;
font-size: 12px;
}
.upload-tip {
font-size: 12px;
color: #606266;
margin-top: 5px;
}
.image-preview {
position: relative;
width: 150px;
height: 150px;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
}
</style>
ProductDetail.vue
<template>
<div class="product-detail-container" v-loading="loading">
<div class="back-button">
<el-button @click="goBack" icon="ArrowLeft">返回</el-button>
</div>
<div v-if="product" class="product-detail">
<div class="product-image">
<el-image
v-if="product.productPhoto"
:src="product.productPhoto"
fit="contain"
:preview-src-list="[product.productPhoto]"
/>
<div v-else class="no-image">暂无图片</div>
</div>
<div class="product-info">
<h1 class="product-name">{{ product.productName }}</h1>
<div class="product-price">
<span class="label">价格:</span>
<span class="price">¥{{ product.productPrice }}</span>
</div>
<div class="product-meta">
<div class="meta-item">
<span class="label">品牌:</span>
<span>{{ product.brandName }}</span>
</div>
<div class="meta-item">
<span class="label">型号:</span>
<span>{{ product.modelName }}</span>
</div>
<div class="meta-item">
<span class="label">库存:</span>
<span>{{ product.productQuantity }} 件</span>
</div>
<div class="meta-item">
<span class="label">卖家ID:</span>
<span>{{ product.userId }}</span>
</div>
</div>
<div class="product-actions">
<el-input-number
v-model="quantity"
:min="1"
:max="product.productQuantity"
size="large"
class="quantity-input"
/>
<el-button
type="primary"
size="large"
@click="addToCart"
:disabled="!currentUser"
class="action-button"
>
<el-icon><ShoppingCart /></el-icon> 添加购物车
</el-button>
<el-button
type="danger"
size="large"
@click="buyNow"
:disabled="!currentUser"
class="action-button"
>
立即购买
</el-button>
</div>
<div class="product-detail-section">
<h3>商品详情</h3>
<p v-if="product.productDetail">{{ product.productDetail }}</p>
<p v-else>暂无详情描述</p>
</div>
</div>
</div>
<div v-else-if="!loading" class="not-found">
<el-empty description="商品不存在或已被删除"></el-empty>
</div>
<!-- 地址选择对话框 -->
<el-dialog v-model="addressDialogVisible" title="选择收货地址" width="50%">
<div class="address-actions">
<el-button type="primary" @click="handleAddAddress">新增地址</el-button>
</div>
<el-radio-group v-model="selectedAddressId" class="address-list">
<el-radio
v-for="address in addressList"
:key="address.addressId"
:label="address.addressId"
class="address-item"
>
<div class="address-info">
<div class="address-name">{{ address.addressName }} {{ address.addressPhone }}</div>
<div class="address-detail">{{ address.addressDetail }}</div>
</div>
</el-radio>
</el-radio-group>
<div v-if="addressList.length === 0" class="empty-address">
<el-empty description="您还没有添加收货地址" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="addressDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmPurchase" :disabled="!selectedAddressId && addressList.length > 0">
确认购买
</el-button>
</span>
</template>
</el-dialog>
<!-- 新增地址对话框 -->
<el-dialog
v-model="newAddressDialogVisible"
title="新增地址"
width="40%"
>
<el-form :model="addressForm" label-width="100px">
<el-form-item label="收件人姓名">
<el-input v-model="addressForm.addressName" placeholder="请输入收件人姓名" />
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="addressForm.addressPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="详细地址">
<el-input
v-model="addressForm.addressDetail"
type="textarea"
:rows="3"
placeholder="请输入详细地址"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="newAddressDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddressForm">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ArrowLeft, ShoppingCart } from '@element-plus/icons-vue'
import productApi from '@/api/product'
import ordersApi from '@/api/orders'
import addressApi from '@/api/address'
const route = useRoute()
const router = useRouter()
const productId = route.params.id
const product = ref(null)
const loading = ref(true)
const quantity = ref(1)
const currentUser = ref(null)
// 地址相关
const addressList = ref([])
const selectedAddressId = ref('')
const addressDialogVisible = ref(false)
const newAddressDialogVisible = ref(false)
// 新增地址表单
const addressForm = reactive({
addressName: '',
addressPhone: '',
addressDetail: '',
userId: ''
})
// 获取商品详情
const loadProductDetail = async () => {
loading.value = true
try {
const res = await productApi.getProductDetail(productId)
if (res.code === 200) {
product.value = res.data
} else {
ElMessage.error(res.message || '获取商品详情失败')
}
} catch (error) {
console.error('获取商品详情出错:', error)
ElMessage.error('获取商品详情失败')
} finally {
loading.value = false
}
}
// 获取当前登录用户信息
const getCurrentUser = () => {
const userInfo = localStorage.getItem('user')
if (userInfo) {
currentUser.value = JSON.parse(userInfo)
} else {
ElMessage.warning('请先登录')
}
}
// 添加到购物车
const addToCart = async () => {
if (!currentUser.value) {
ElMessage.warning('请先登录')
router.push('/login')
return
}
// 创建订单对象
const order = {
productNum: quantity.value,
orderPrice: product.value.productPrice * quantity.value,
userId: currentUser.value.userId,
productId: product.value.productId,
// 订单状态:0-待付款
orderStatus: 0
}
try {
// 调用添加订单的API
const res = await ordersApi.addOrder(order)
if (res.code === 200) {
ElMessage.success(`已添加 ${quantity.value} 件 ${product.value.productName} 到购物车`)
} else {
ElMessage.error(res.message || '添加购物车失败')
}
} catch (error) {
console.error('添加购物车出错:', error)
ElMessage.error('添加购物车失败')
}
}
// 立即购买
const buyNow = () => {
if (!currentUser.value) {
ElMessage.warning('请先登录')
router.push('/login')
return
}
// 打开地址选择对话框
addressDialogVisible.value = true
loadAddresses()
}
// 确认购买
const confirmPurchase = async () => {
if (!selectedAddressId.value) {
ElMessage.warning('请选择收货地址')
return
}
try {
// 创建订单对象
const order = {
productNum: quantity.value,
orderPrice: product.value.productPrice * quantity.value,
userId: currentUser.value.userId,
productId: product.value.productId,
addressId: selectedAddressId.value,
// 订单状态:1-待发货
orderStatus: 1
}
// 添加订单
const orderRes = await ordersApi.addOrder(order)
if (orderRes.code === 200) {
// 获取商品信息并减少库存
const productRes = await productApi.getProductDetail(product.value.productId)
if (productRes.code === 200) {
const productData = productRes.data
// 减少商品库存
if (productData.productQuantity >= quantity.value) {
productData.productQuantity -= quantity.value
// 更新商品信息
await productApi.updateProduct(productData)
ElMessage.success('购买成功,订单已提交')
addressDialogVisible.value = false
// 跳转到订单页面或其他页面
router.push('/manager/orders')
} else {
ElMessage.error('商品库存不足')
}
} else {
ElMessage.error(productRes.message || '获取商品信息失败')
}
} else {
ElMessage.error(orderRes.message || '创建订单失败')
}
} catch (error) {
console.error('提交订单出错:', error)
ElMessage.error('提交订单失败')
}
}
// 加载地址列表
const loadAddresses = async () => {
try {
const res = await addressApi.getAddressByUserId(currentUser.value.userId)
if (res.code === 200) {
addressList.value = res.data
// 如果有地址,默认选中第一个
if (addressList.value.length > 0) {
selectedAddressId.value = addressList.value[0].addressId
}
} else {
ElMessage.error(res.message || '获取地址列表失败')
}
} catch (error) {
console.error('获取地址列表出错:', error)
ElMessage.error('获取地址列表失败')
}
}
// 处理新增地址
const handleAddAddress = () => {
addressForm.addressName = ''
addressForm.addressPhone = ''
addressForm.addressDetail = ''
addressForm.userId = currentUser.value.userId
newAddressDialogVisible.value = true
}
// 提交地址表单
const submitAddressForm = async () => {
// 表单验证
if (!addressForm.addressName) {
ElMessage.warning('请输入收件人姓名')
return
}
if (!addressForm.addressPhone) {
ElMessage.warning('请输入联系电话')
return
}
if (!addressForm.addressDetail) {
ElMessage.warning('请输入详细地址')
return
}
try {
const res = await addressApi.addAddress(addressForm)
if (res.code === 200) {
ElMessage.success('新增地址成功')
newAddressDialogVisible.value = false
await loadAddresses()
// 自动选中新增的地址
if (res.data && res.data.addressId) {
selectedAddressId.value = res.data.addressId
}
} else {
ElMessage.error(res.message || '新增地址失败')
}
} catch (error) {
console.error('新增地址出错:', error)
ElMessage.error('新增地址失败')
}
}
// 返回上一页
const goBack = () => {
router.back()
}
onMounted(() => {
loadProductDetail()
getCurrentUser()
})
</script>
<style scoped>
.product-detail-container {
padding: 20px;
}
.back-button {
margin-bottom: 20px;
}
.product-detail {
display: flex;
gap: 30px;
}
.product-image {
width: 400px;
height: 400px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
.product-image .el-image {
width: 100%;
height: 100%;
}
.no-image {
color: #909399;
font-size: 14px;
}
.product-info {
flex: 1;
}
.product-name {
font-size: 24px;
margin-top: 0;
margin-bottom: 20px;
color: #303133;
}
.product-price {
margin-bottom: 20px;
}
.product-price .price {
font-size: 28px;
color: #f56c6c;
font-weight: bold;
}
.product-meta {
margin-bottom: 30px;
padding: 15px 0;
border-top: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
}
.meta-item {
margin-bottom: 10px;
font-size: 14px;
}
.label {
color: #606266;
margin-right: 10px;
}
.product-actions {
display: flex;
align-items: center;
margin-bottom: 30px;
gap: 15px;
}
.quantity-input {
width: 120px;
}
.action-button {
flex: 1;
}
.product-detail-section h3 {
font-size: 18px;
margin-bottom: 15px;
color: #303133;
}
.product-detail-section p {
font-size: 14px;
line-height: 1.6;
color: #606266;
white-space: pre-wrap;
}
.not-found {
padding: 60px 0;
text-align: center;
}
@media (max-width: 768px) {
.product-detail {
flex-direction: column;
}
.product-image {
width: 100%;
height: 300px;
}
.product-actions {
flex-direction: column;
align-items: stretch;
}
.action-button {
margin-top: 10px;
}
}
</style>
/* 地址相关样式 */
.address-actions {
margin-bottom: 20px;
}
.address-list {
display: flex;
flex-direction: column;
gap: 15px;
max-height: 300px;
overflow-y: auto;
}
.address-item {
width: 100%;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
margin-right: 0;
height: auto;
}
.address-info {
margin-left: 30px;
}
.address-name {
font-weight: bold;
margin-bottom: 5px;
}
.address-detail {
color: #606266;
font-size: 14px;
}
.empty-address {
padding: 20px 0;
}
浙公网安备 33010602011771号