472 lines
14 KiB
Vue
472 lines
14 KiB
Vue
<template>
|
||
<ManageLayout>
|
||
<div class="product-manage-page">
|
||
<!-- 搜索和操作按钮 -->
|
||
<div class="mb-6 flex justify-between items-center">
|
||
<div class="flex items-center">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="搜索商品名称或分类"
|
||
style="width: 300px; margin-right: 16px"
|
||
@keyup.enter="handleSearch"
|
||
>
|
||
<template #prefix>
|
||
<i class="fa fa-search"></i>
|
||
</template>
|
||
</el-input>
|
||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||
</div>
|
||
<el-button type="success" @click="showAddProductDialog = true">
|
||
<i class="fa fa-plus mr-1"></i> 添加商品
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 商品表格 -->
|
||
<el-table
|
||
:data="paginatedProducts"
|
||
style="width: 100%"
|
||
v-loading="loading"
|
||
border
|
||
>
|
||
<el-table-column prop="id" label="ID" width="80" />
|
||
<el-table-column prop="name" label="商品名称" />
|
||
<el-table-column prop="categoryName" label="分类" />
|
||
<el-table-column prop="price" label="价格" width="100">
|
||
<template #default="{ row }"> ¥{{ row.price }} </template>
|
||
</el-table-column>
|
||
<el-table-column prop="stock" label="库存" width="100" />
|
||
<el-table-column prop="sales" label="销量" width="100" />
|
||
<el-table-column prop="userId" label="商家ID" width="100">
|
||
<template #default="{ row }">
|
||
{{ row.userId }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="商家名称" width="120">
|
||
<template #default="{ row }">
|
||
{{ getUserById(row.userId || 0)?.username || "未知商家" }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
||
{{ row.status === 1 ? "上架" : "下架" }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="380">
|
||
<template #default="{ row }">
|
||
<el-button size="small" @click="editProduct(row)">编辑</el-button>
|
||
<el-button
|
||
size="small"
|
||
:type="row.status === 1 ? 'warning' : 'success'"
|
||
@click="toggleProductStatus(row)"
|
||
>
|
||
{{ row.status === 1 ? "下架" : "上架" }}
|
||
</el-button>
|
||
<el-button size="small" type="danger" @click="deleteProduct(row)">
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="mt-6 flex justify-center">
|
||
<el-pagination
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
:current-page="currentPage"
|
||
:page-sizes="[5, 10, 20, 50]"
|
||
:page-size="pageSize"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="filteredProducts.length"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 添加/编辑商品对话框 -->
|
||
<el-dialog
|
||
:title="isEditing ? '编辑商品' : '添加商品'"
|
||
v-model="showAddProductDialog"
|
||
width="600px"
|
||
>
|
||
<el-form
|
||
:model="productForm"
|
||
:rules="productFormRules"
|
||
ref="productFormRef"
|
||
label-width="100px"
|
||
>
|
||
<el-form-item label="商品名称" prop="name">
|
||
<el-input v-model="productForm.name" />
|
||
</el-form-item>
|
||
<el-form-item label="分类" prop="categoryId">
|
||
<el-select
|
||
v-model="productForm.categoryId"
|
||
placeholder="请选择分类"
|
||
>
|
||
<el-option
|
||
v-for="category in categories"
|
||
:key="category.id"
|
||
:label="category.name"
|
||
:value="category.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="副标题" prop="subtitle">
|
||
<el-input v-model="productForm.subtitle" />
|
||
</el-form-item>
|
||
<el-form-item label="主图" prop="mainImage">
|
||
<el-input v-model="productForm.mainImage" placeholder="图片URL" />
|
||
</el-form-item>
|
||
<el-form-item label="价格" prop="price">
|
||
<el-input-number
|
||
v-model="productForm.price"
|
||
:precision="2"
|
||
:min="0"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="库存" prop="stock">
|
||
<el-input-number v-model="productForm.stock" :min="0" />
|
||
</el-form-item>
|
||
<el-form-item label="描述" prop="description">
|
||
<el-input
|
||
v-model="productForm.description"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="商品描述"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="排序" prop="sort">
|
||
<el-input-number v-model="productForm.sort" :min="0" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="showAddProductDialog = false">取消</el-button>
|
||
<el-button type="primary" @click="submitProductForm"
|
||
>确定</el-button
|
||
>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</ManageLayout>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, reactive, computed, onMounted } from "vue";
|
||
import { ElMessage, ElMessageBox } from "element-plus";
|
||
import {
|
||
getProductList,
|
||
getProductCategories,
|
||
saveProduct,
|
||
updateProduct,
|
||
updateProductStatus,
|
||
deleteProductAPI,
|
||
} from "@/apis/product";
|
||
import { getUserList } from "@/apis/user"; // 导入获取用户列表的API
|
||
import { ProductListItem, ProductCategory, ProductDTO } from "@/types/product";
|
||
import { UserDTO } from "@/types/user"; // 导入用户类型
|
||
import ManageLayout from "@/layouts/ManageLayout.vue";
|
||
import { useRouter } from "vue-router";
|
||
import { useUserStore } from "@/stores/UserStore";
|
||
|
||
// 获取用户存储和路由
|
||
const userStore = useUserStore();
|
||
const router = useRouter();
|
||
|
||
// 响应式数据
|
||
const loading = ref(false);
|
||
const searchKeyword = ref("");
|
||
const showAddProductDialog = ref(false);
|
||
const isEditing = ref(false);
|
||
const productFormRef = ref();
|
||
|
||
// 分页数据
|
||
const currentPage = ref(1);
|
||
const pageSize = ref(10);
|
||
|
||
// 商品表单
|
||
const productForm = ref<ProductDTO>({
|
||
id: undefined,
|
||
categoryId: 0, // 设置默认值为0
|
||
name: "",
|
||
subtitle: "",
|
||
mainImage: "",
|
||
description: "",
|
||
price: 0,
|
||
stock: 0,
|
||
sort: 0,
|
||
});
|
||
|
||
// 所有商品数据
|
||
const products = ref<ProductListItem[]>([]);
|
||
|
||
// 商品分类
|
||
const categories = ref<ProductCategory[]>([]);
|
||
|
||
// 所有用户数据(用于获取商家名称)
|
||
const users = ref<UserDTO[]>([]);
|
||
|
||
// 商品表单验证规则
|
||
const productFormRules = reactive({
|
||
name: [
|
||
{ required: true, message: "请输入商品名称", trigger: "blur" },
|
||
{
|
||
min: 2,
|
||
max: 50,
|
||
message: "商品名称长度应在2-50个字符之间",
|
||
trigger: "blur",
|
||
},
|
||
],
|
||
categoryId: [{ required: true, message: "请选择分类", trigger: "change" }],
|
||
price: [
|
||
{ required: true, message: "请输入价格", trigger: "blur" },
|
||
{ type: "number", min: 0, message: "价格不能小于0", trigger: "blur" },
|
||
],
|
||
stock: [
|
||
{ required: true, message: "请输入库存", trigger: "blur" },
|
||
{ type: "number", min: 0, message: "库存不能小于0", trigger: "blur" },
|
||
],
|
||
});
|
||
|
||
// 计算属性:根据搜索关键词过滤商品
|
||
const filteredProducts = computed(() => {
|
||
if (!searchKeyword.value) return products.value;
|
||
|
||
return products.value.filter(
|
||
(product) =>
|
||
product.name?.includes(searchKeyword.value) ||
|
||
product.categoryName?.includes(searchKeyword.value),
|
||
);
|
||
});
|
||
|
||
// 计算属性:当前页的商品数据
|
||
const paginatedProducts = computed(() => {
|
||
const start = (currentPage.value - 1) * pageSize.value;
|
||
const end = start + pageSize.value;
|
||
return filteredProducts.value.slice(start, end);
|
||
});
|
||
|
||
// 根据用户ID获取用户信息
|
||
const getUserById = (userId: number) => {
|
||
return users.value.find((user) => user.id === userId);
|
||
};
|
||
|
||
// 获取商品列表
|
||
const fetchProducts = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const response = await getProductList({
|
||
page: 1,
|
||
size: 1000, // 获取所有商品
|
||
});
|
||
products.value = response.data.list || [];
|
||
} catch (error) {
|
||
console.error("获取商品列表错误:", error);
|
||
ElMessage.error("获取商品列表失败");
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
// 获取商品分类
|
||
const fetchCategories = async () => {
|
||
try {
|
||
const response = await getProductCategories();
|
||
if (response.code === 200) {
|
||
categories.value = response.data || [];
|
||
}
|
||
} catch (error) {
|
||
console.error("获取商品分类错误:", error);
|
||
ElMessage.error("获取商品分类失败");
|
||
}
|
||
};
|
||
|
||
// 获取用户列表(用于显示商家名称)
|
||
const fetchUsers = async () => {
|
||
try {
|
||
const response = await getUserList();
|
||
if (response.code === 200) {
|
||
users.value = response.data || [];
|
||
}
|
||
} catch (error) {
|
||
console.error("获取用户列表错误:", error);
|
||
ElMessage.error("获取用户列表失败");
|
||
}
|
||
};
|
||
|
||
// 搜索处理
|
||
const handleSearch = () => {
|
||
currentPage.value = 1; // 重置到第一页
|
||
};
|
||
|
||
// 分页大小改变
|
||
const handleSizeChange = (size: number) => {
|
||
pageSize.value = size;
|
||
currentPage.value = 1;
|
||
};
|
||
|
||
// 当前页改变
|
||
const handleCurrentChange = (page: number) => {
|
||
currentPage.value = page;
|
||
};
|
||
|
||
// 编辑商品
|
||
const editProduct = (product: ProductListItem) => {
|
||
isEditing.value = true;
|
||
// 复制商品数据,处理可能的undefined值
|
||
productForm.value = {
|
||
id: product.id,
|
||
categoryId: product.categoryId || 0, // 确保是number类型
|
||
name: product.name,
|
||
mainImage: product.mainImage,
|
||
price: product.price,
|
||
stock: product.stock || 0,
|
||
};
|
||
showAddProductDialog.value = true;
|
||
};
|
||
|
||
// 切换商品状态(上下架)
|
||
const toggleProductStatus = async (product: ProductListItem) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要${product.status === 1 ? "下架" : "上架"}商品 "${product.name}" 吗?`,
|
||
"确认操作",
|
||
{
|
||
confirmButtonText: "确定",
|
||
cancelButtonText: "取消",
|
||
type: "warning",
|
||
},
|
||
);
|
||
|
||
// 调用更新商品状态API
|
||
const response = await updateProductStatus(
|
||
product.id,
|
||
product.status === 1 ? 0 : 1,
|
||
);
|
||
|
||
if (response.code === 200) {
|
||
// 更新本地数据
|
||
const index = products.value.findIndex((p) => p.id === product.id);
|
||
if (index !== -1) {
|
||
products.value[index] = {
|
||
...product,
|
||
status: product.status === 1 ? 0 : 1,
|
||
};
|
||
}
|
||
ElMessage.success(`${product.status === 1 ? "下架" : "上架"}成功`);
|
||
} else {
|
||
ElMessage.error(response.message || "操作失败");
|
||
}
|
||
} catch (error) {
|
||
console.error("切换商品状态错误:", error);
|
||
if (error !== "cancel") {
|
||
ElMessage.error("操作失败");
|
||
}
|
||
}
|
||
};
|
||
|
||
// 删除商品
|
||
const deleteProduct = async (product: ProductListItem) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除商品 "${product.name}" 吗?此操作不可恢复!`,
|
||
"警告",
|
||
{
|
||
confirmButtonText: "确定",
|
||
cancelButtonText: "取消",
|
||
type: "warning",
|
||
},
|
||
);
|
||
|
||
// 调用删除商品API
|
||
const response = await deleteProductAPI(product.id);
|
||
|
||
if (response.code === 200) {
|
||
// 从本地数据中删除商品
|
||
products.value = products.value.filter((p) => p.id !== product.id);
|
||
ElMessage.success("删除成功");
|
||
} else {
|
||
ElMessage.error(response.message || "删除失败");
|
||
}
|
||
} catch (error) {
|
||
console.error("删除商品错误:", error);
|
||
if (error !== "cancel") {
|
||
ElMessage.error("操作失败");
|
||
}
|
||
}
|
||
};
|
||
|
||
// 提交商品表单
|
||
const submitProductForm = async () => {
|
||
try {
|
||
// 验证表单
|
||
const form = productFormRef.value;
|
||
if (!form) return;
|
||
|
||
await form.validate();
|
||
|
||
if (isEditing.value) {
|
||
// 更新商品
|
||
const response = await updateProduct(productForm.value);
|
||
|
||
if (response.code === 200) {
|
||
// 更新本地数据 - 重新获取商品列表以确保数据同步
|
||
fetchProducts();
|
||
ElMessage.success("更新成功");
|
||
} else {
|
||
ElMessage.error(response.message || "更新失败");
|
||
return;
|
||
}
|
||
} else {
|
||
// 添加新商品
|
||
const response = await saveProduct({
|
||
...productForm.value,
|
||
userId: userStore.userInfo?.id, // 假设当前用户为商家
|
||
});
|
||
|
||
if (response.code === 200) {
|
||
ElMessage.success("添加成功");
|
||
} else {
|
||
ElMessage.error(response.message || "添加失败");
|
||
return;
|
||
}
|
||
}
|
||
|
||
showAddProductDialog.value = false;
|
||
fetchProducts(); // 重新获取商品列表
|
||
} catch (error) {
|
||
console.error("提交商品表单错误:", error);
|
||
if (error) {
|
||
console.log(error);
|
||
} else {
|
||
ElMessage.error("提交失败");
|
||
}
|
||
}
|
||
};
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
if (userStore.userInfo?.isAdmin !== 1) {
|
||
ElMessage.error("您没有管理员权限!");
|
||
router.push("/");
|
||
}
|
||
fetchProducts();
|
||
fetchCategories();
|
||
fetchUsers(); // 获取用户列表
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.product-manage-page {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.dialog-footer {
|
||
text-align: right;
|
||
}
|
||
</style>
|