diff --git a/src/pages/admin/ProductPage.vue b/src/pages/admin/ProductPage.vue
index ad560f0..81aed6a 100644
--- a/src/pages/admin/ProductPage.vue
+++ b/src/pages/admin/ProductPage.vue
@@ -88,6 +88,7 @@
:title="isEditing ? '编辑商品' : '添加商品'"
v-model="showAddProductDialog"
width="600px"
+ @close="resetProductForm"
>
-
-
-
+ style="width: 100%; margin-bottom: 10px"
+ clearable
+ @change="onCategoryChange"
+ />
+
+ 创建新分类
+
-
+
+
+
+
+
+ 选择图片
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -162,9 +220,18 @@ import {
updateProduct,
updateProductStatus,
deleteProductAPI,
+ addProductCategory,
+ uploadFile,
+ getProductSubCategories,
+ getProductDetail,
} from "@/apis/product";
import { getUserList } from "@/apis/user"; // 导入获取用户列表的API
-import { ProductListItem, ProductCategory, ProductDTO } from "@/types/product";
+import {
+ ProductListItem,
+ ProductCategory,
+ ProductDTO,
+ ProductCategoryDTO,
+} from "@/types/product";
import { UserDTO } from "@/types/user"; // 导入用户类型
import ManageLayout from "@/layouts/ManageLayout.vue";
import { useRouter } from "vue-router";
@@ -178,8 +245,11 @@ const router = useRouter();
const loading = ref(false);
const searchKeyword = ref("");
const showAddProductDialog = ref(false);
+const showCategoryDialog = ref(false);
const isEditing = ref(false);
const productFormRef = ref();
+const categoryFormRef = ref();
+const pendingImageFile = ref(null); // 新增:暂存待上传的图片文件
// 分页数据
const currentPage = ref(1);
@@ -198,11 +268,25 @@ const productForm = ref({
sort: 0,
});
+// 分类表单
+const categoryForm = ref({
+ name: "",
+ parentId: 0, // 修改为数字类型,支持级联选择
+ sort: 0,
+});
+
// 所有商品数据
const products = ref([]);
-// 商品分类
-const categories = ref([]);
+// 所有分类
+const allCategories = ref([]);
+const categoriesTree = ref<
+ (ProductCategory & { children?: ProductCategory[] })[]
+>([]); // 新增:树形结构的分类数据
+
+// 当前选中的分类
+const selectedCategory = ref(null);
+const selectedCategoryPath = ref([]); // 新增:选中的分类路径(用于级联选择器)
// 所有用户数据(用于获取商家名称)
const users = ref([]);
@@ -229,6 +313,19 @@ const productFormRules = reactive({
],
});
+// 分类表单验证规则
+const categoryFormRules = reactive({
+ name: [
+ { required: true, message: "请输入分类名称", trigger: "blur" },
+ {
+ min: 1,
+ max: 20,
+ message: "分类名称长度应在1-20个字符之间",
+ trigger: "blur",
+ },
+ ],
+});
+
// 计算属性:根据搜索关键词过滤商品
const filteredProducts = computed(() => {
if (!searchKeyword.value) return products.value;
@@ -272,9 +369,39 @@ const fetchProducts = async () => {
// 获取商品分类
const fetchCategories = async () => {
try {
+ // 获取所有顶级分类
const response = await getProductCategories();
if (response.code === 200) {
- categories.value = response.data || [];
+ const topLevelCategories = response.data || [];
+
+ // 递归获取子分类
+ const allCategoryData: ProductCategory[] = [...topLevelCategories];
+ const treeData = [...topLevelCategories] as CategoryTreeNode[];
+
+ const fetchSubCategories = async (categories: CategoryTreeNode[]) => {
+ for (const category of categories) {
+ try {
+ const subResponse = await getProductSubCategories(category.id);
+ if (subResponse.code === 200) {
+ const subCategories = subResponse.data || [];
+ if (subCategories.length > 0) {
+ allCategoryData.push(...subCategories);
+
+ // 将子分类添加到父分类的子分类数组中
+ category.children = subCategories as CategoryTreeNode[];
+
+ await fetchSubCategories(category.children); // 递归获取子分类
+ }
+ }
+ } catch (error) {
+ console.error(`获取分类 ${category.id} 的子分类失败:`, error);
+ }
+ }
+ };
+
+ await fetchSubCategories(treeData);
+ allCategories.value = [...allCategoryData];
+ categoriesTree.value = [...treeData]; // 用于树形选择器
}
} catch (error) {
console.error("获取商品分类错误:", error);
@@ -311,18 +438,73 @@ const handleCurrentChange = (page: number) => {
currentPage.value = page;
};
+// 为树形结构定义类型
+type CategoryTreeNode = ProductCategory & { children?: CategoryTreeNode[] };
+
+// 递归查找分类路径
+const findCategoryPath = (
+ categories: CategoryTreeNode[],
+ targetId: number,
+ path: number[] = [],
+): number[] | null => {
+ for (const category of categories) {
+ const currentPath = [...path, category.id];
+ if (category.id === targetId) {
+ return currentPath;
+ }
+ if (category.children && category.children.length > 0) {
+ const result = findCategoryPath(category.children, targetId, currentPath);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ return null;
+};
+
// 编辑商品
-const editProduct = (product: ProductListItem) => {
+const editProduct = async (product: ProductListItem) => {
isEditing.value = true;
+
+ // 首先尝试从列表数据获取,如果categoryId为空则获取详细信息
+ let fullProduct = product;
+ if (!product.categoryId) {
+ try {
+ // 获取商品详细信息以获取完整的分类ID
+ const detailResponse = await getProductDetail(product.id);
+ if (detailResponse.code === 200) {
+ fullProduct = detailResponse.data as ProductListItem;
+ }
+ } catch (error) {
+ console.error("获取商品详情失败:", error);
+ ElMessage.error("获取商品详情失败");
+ }
+ }
+
// 复制商品数据,处理可能的undefined值
productForm.value = {
- id: product.id,
- categoryId: product.categoryId || 0, // 确保是number类型
- name: product.name,
- mainImage: product.mainImage,
- price: product.price,
- stock: product.stock || 0,
+ id: fullProduct.id,
+ categoryId: fullProduct.categoryId || 0, // 确保是number类型
+ name: fullProduct.name,
+ mainImage: fullProduct.mainImage,
+ price: fullProduct.price,
+ stock: fullProduct.stock || 0,
+ subtitle: fullProduct.subtitle || "",
+ description: fullProduct.description || "",
+ sort: fullProduct.sort || 0,
};
+
+ // 设置选中的分类路径 - 仿照分类管理页面的方式
+ if (fullProduct.categoryId) {
+ selectedCategory.value = fullProduct.categoryId;
+ // 根据商品的分类ID找到对应的分类路径
+ const path = findCategoryPath(categoriesTree.value, fullProduct.categoryId);
+ selectedCategoryPath.value = path || [fullProduct.categoryId];
+ } else {
+ selectedCategory.value = null;
+ selectedCategoryPath.value = [];
+ }
+
showAddProductDialog.value = true;
};
@@ -406,9 +588,31 @@ const submitProductForm = async () => {
await form.validate();
+ // 设置分类ID
+ const finalProductData = { ...productForm.value };
+ finalProductData.categoryId = selectedCategory.value || 0;
+
+ // 如果有待上传的图片文件,先上传
+ if (pendingImageFile.value) {
+ try {
+ const response = await uploadFile(pendingImageFile.value);
+ if (response.code === 200) {
+ finalProductData.mainImage = response.data.fileUrl;
+ ElMessage.success("图片上传成功");
+ } else {
+ ElMessage.error(response.message || "图片上传失败");
+ return;
+ }
+ } catch (error) {
+ console.error("图片上传错误:", error);
+ ElMessage.error("图片上传失败");
+ return;
+ }
+ }
+
if (isEditing.value) {
// 更新商品
- const response = await updateProduct(productForm.value);
+ const response = await updateProduct(finalProductData);
if (response.code === 200) {
// 更新本地数据 - 重新获取商品列表以确保数据同步
@@ -421,8 +625,8 @@ const submitProductForm = async () => {
} else {
// 添加新商品
const response = await saveProduct({
- ...productForm.value,
- userId: userStore.userInfo?.id, // 假设当前用户为商家
+ ...finalProductData,
+ userId: userStore.userInfo?.id, // 当前用户为商家
});
if (response.code === 200) {
@@ -434,6 +638,7 @@ const submitProductForm = async () => {
}
showAddProductDialog.value = false;
+ resetProductForm(); // 关闭对话框后重置表单
fetchProducts(); // 重新获取商品列表
} catch (error) {
console.error("提交商品表单错误:", error);
@@ -445,6 +650,124 @@ const submitProductForm = async () => {
}
};
+// 重置商品表单
+const resetProductForm = () => {
+ productForm.value = {
+ id: undefined,
+ categoryId: 0,
+ name: "",
+ subtitle: "",
+ mainImage: "",
+ description: "",
+ price: 0,
+ stock: 0,
+ sort: 0,
+ };
+ selectedCategory.value = null;
+ selectedCategoryPath.value = []; // 重置路径
+ isEditing.value = false;
+ pendingImageFile.value = null; // 清除暂存的文件
+};
+
+// 取消添加/编辑商品
+const cancelProductForm = () => {
+ showAddProductDialog.value = false;
+ resetProductForm(); // 取消时也重置表单
+};
+
+// 提交分类表单
+const submitCategoryForm = async () => {
+ try {
+ // 验证表单
+ const form = categoryFormRef.value;
+ if (!form) return;
+
+ await form.validate();
+
+ // 添加新分类 - 创建符合 ProductCategoryDTO 类型的对象
+ const newCategory: ProductCategoryDTO = {
+ name: categoryForm.value.name,
+ sort: categoryForm.value.sort,
+ parentId: categoryForm.value.parentId || 0, // 使用级联选择器的值
+ level: 1, // 初始设置为1,后端会根据父分类自动计算
+ };
+
+ const response = await addProductCategory(newCategory);
+
+ if (response.code === 200) {
+ ElMessage.success("分类创建成功");
+
+ // 重新获取分类列表
+ await fetchCategories();
+
+ // 选中新创建的分类
+ const newCategoryItem = allCategories.value.find(
+ (cat) =>
+ cat.name === categoryForm.value.name &&
+ cat.parentId === (categoryForm.value.parentId || 0),
+ );
+ if (newCategoryItem) {
+ selectedCategory.value = newCategoryItem.id;
+ const path = findCategoryPath(categoriesTree.value, newCategoryItem.id);
+ selectedCategoryPath.value = path || [newCategoryItem.id];
+ }
+
+ // 清空表单并关闭对话框
+ categoryForm.value = {
+ name: "",
+ parentId: 0,
+ sort: 0,
+ };
+ showCategoryDialog.value = false;
+ } else {
+ ElMessage.error(response.message || "分类创建失败");
+ }
+ } catch (error) {
+ console.error("提交分类表单错误:", error);
+ ElMessage.error("提交失败");
+ }
+};
+
+// 分类选择改变事件
+const onCategoryChange = (value: number[]) => {
+ if (value && value.length > 0) {
+ // 获取路径中最后一个ID作为实际选中的分类ID
+ selectedCategory.value = value[value.length - 1];
+ productForm.value.categoryId = selectedCategory.value;
+ } else {
+ selectedCategory.value = null;
+ productForm.value.categoryId = 0;
+ }
+};
+
+// 上传图片前的钩子
+const beforeImageUpload = (file: File) => {
+ const isImage = file.type.startsWith("image/");
+ const isLt2M = file.size / 1024 / 1024 < 2;
+
+ if (!isImage) {
+ ElMessage.error("上传图片只能是图片格式!");
+ }
+ if (!isLt2M) {
+ ElMessage.error("上传图片大小不能超过 2MB!");
+ }
+ return isImage && isLt2M;
+};
+
+// 处理图片选择变化
+const handleImageChange = async (file: { raw: File }) => {
+ if (!beforeImageUpload(file.raw)) {
+ return;
+ }
+
+ // 暂存文件,等待提交时上传
+ pendingImageFile.value = file.raw;
+
+ // 创建预览URL用于显示
+ const previewUrl = URL.createObjectURL(file.raw);
+ productForm.value.mainImage = previewUrl;
+};
+
// 初始化
onMounted(() => {
if (userStore.userInfo?.isAdmin !== 1) {
@@ -468,4 +791,11 @@ onMounted(() => {
.dialog-footer {
text-align: right;
}
+
+.avatar-uploader .avatar {
+ width: 178px;
+ height: 178px;
+ display: block;
+ object-fit: cover;
+}