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;
}
posted @ 2025-09-07 00:10  QixunQiu  阅读(11)  评论(0)    收藏  举报