删除商品

This commit is contained in:
dqz 2025-03-04 17:17:46 +08:00
parent 454615e8f4
commit 547db61248
7 changed files with 217 additions and 50 deletions

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

66
src/api/shop/category.ts Normal file
View File

@ -0,0 +1,66 @@
import { http } from "@/utils/http";
export interface CategoryQuery extends BasePageQuery {
categoryName?: string;
sort?: number;
}
/** 分类DTO */
export interface CategoryDTO {
categoryId?: number;
categoryName?: string;
sort?: number;
description?: string;
creatorId?: number;
createTime?: Date;
updaterId?: number;
updateTime?: Date;
deleted?: number;
}
/** 分类请求参数 */
export interface CategoryRequest {
categoryName: string;
sort: number;
description?: string;
}
/** 获取分类列表 */
export const getCategoryListApi = (params?: CategoryQuery) => {
return http.request<ResponseData<PageDTO<CategoryDTO>>>("get", "/shop/category/list", {
params
});
};
/** 获取分类列表 */
export const getCategoryAllApi = () => {
return http.request<ResponseData<Array<CategoryDTO>>>("get", "/shop/category/all", {
});
};
/** 新增分类 */
export const addCategoryApi = (data: CategoryRequest) => {
return http.request<ResponseData<void>>("post", "/shop/category", {
data
});
};
/** 编辑分类 */
export const updateCategoryApi = (categoryId: number, data: CategoryRequest) => {
return http.request<ResponseData<void>>("put", `/shop/category/${categoryId}`, {
data
});
};
/** 删除分类 */
export const deleteCategoryApi = (categoryId: number) => {
return http.request<ResponseData<void>>("delete", `/shop/category/${categoryId}`);
};
/** 批量导出分类 */
export const exportCategoryExcelApi = (params: CategoryQuery, fileName: string) => {
return http.download("/shop/category/excel", fileName, {
params
});
};

View File

@ -59,7 +59,7 @@ export const updateGoodsApi = (goodsId: number, data: GoodsRequest) => {
};
/** 删除商品 */
export const deleteGoodsApi = (goodsId: number) => {
export const deleteGoodsApi = (goodsId: number | string) => {
return http.request<ResponseData<void>>("delete", `/shop/goods/${goodsId}`);
};

View File

@ -1,9 +1,12 @@
<script setup lang="ts">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
import { ref, reactive, onMounted, watch } from "vue";
import { ElMessage, FormRules } from "element-plus";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { addGoodsApi } from "@/api/shop/goods";
import { CategoryDTO, getCategoryAllApi } from "@/api/shop/category";
import Confirm from "@iconify-icons/ep/check";
import Upload from "@iconify-icons/ep/upload";
const { VITE_APP_BASE_API } = import.meta.env;
const props = defineProps({
visible: {
@ -19,10 +22,13 @@ const formData = reactive({
goodsName: "",
price: 0,
stock: 0,
status: 1
status: 1,
categoryId: 0,
goodsDetail: "",
coverImg: ""
});
const rules = reactive({
const rules = reactive<FormRules>({
goodsName: [{ required: true, message: "商品名称必填", trigger: "blur" }],
price: [
{ required: true, message: "价格必填", trigger: "blur" },
@ -31,6 +37,9 @@ const rules = reactive({
stock: [
{ required: true, message: "库存必填", trigger: "blur" },
{ type: 'number', min: 0, message: '库存不能小于0' }
],
coverImg: [
{ required: true, message: "请上传商品封面图", trigger: "change" }
]
});
@ -50,44 +59,70 @@ const closeDialog = () => {
formRef.value.resetFields();
emit("update:visible", false);
};
//
const categoryOptions = ref<Array<CategoryDTO>>([]);
//
const loadCategories = async () => {
try {
const { data } = await getCategoryAllApi();
categoryOptions.value = data;
if (data.length > 0) {
formData.categoryId = data[0].categoryId;
}
} catch (e) {
console.error("分类加载失败", e);
}
};
const handleAvatarSuccess = (response, uploadFile) => {
// API
formData.coverImg = response.data.url;
};
const beforeAvatarUpload = (rawFile) => {
if (!['image/jpeg', 'image/png'].includes(rawFile.type)) {
ElMessage.error('封面图必须是 JPG/PNG 格式!')
return false
}
if (rawFile.size > 50 * 1024 * 1024) {
ElMessage.error('封面图大小不能超过 50MB!')
return false
}
return true
};
</script>
<template>
<el-dialog
title="新增商品"
:model-value="visible"
width="600px"
@close="closeDialog"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="80px"
label-position="right"
>
<el-dialog title="新增商品" :model-value="visible" width="600px" @close="closeDialog" @open="loadCategories">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px" label-position="right">
<el-form-item label="商品名称" prop="goodsName">
<el-input
v-model="formData.goodsName"
placeholder="请输入商品名称"
clearable
/>
<el-input v-model="formData.goodsName" placeholder="请输入商品名称" clearable />
</el-form-item>
<el-form-item label="封面图" prop="coverImg">
<el-upload class="avatar-uploader" :action="VITE_APP_BASE_API + '/file/upload'" :show-file-list="false"
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="formData.coverImg" :src="formData.coverImg" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Icon :icon="Upload" />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input-number
v-model="formData.price"
:min="0"
:precision="2"
controls-position="right"
/>
<el-input-number v-model="formData.price" :min="0" :precision="2" controls-position="right" />
</el-form-item>
<el-form-item label="库存" prop="stock">
<el-input-number
v-model="formData.stock"
:min="0"
controls-position="right"
/>
<el-input-number v-model="formData.stock" :min="0" controls-position="right" />
</el-form-item>
<!-- 在状态表单项前添加分类选择 -->
<el-form-item label="商品分类" prop="categoryId">
<el-select v-model="formData.categoryId" placeholder="请选择商品分类" clearable class="w-full">
<el-option v-for="category in categoryOptions" :key="category.categoryId" :label="category.categoryName"
:value="category.categoryId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">上架</el-radio>
@ -95,16 +130,35 @@ const closeDialog = () => {
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="closeDialog">取消</el-button>
<el-button
type="primary"
:icon="useRenderIcon(Confirm)"
@click="handleConfirm"
>
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
确认
</el-button>
</template>
</el-dialog>
</template>
</template>
<style scoped>
.avatar-uploader {
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
&:hover {
border-color: var(--el-color-primary);
}
}
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>

View File

@ -9,6 +9,8 @@ import AddFill from "@iconify-icons/ri/add-circle-line";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
import GoodsFormModal from "./goods-form-modal.vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { deleteGoodsApi } from "@/api/shop/goods";
defineOptions({
name: "ShopGoods"
@ -34,6 +36,7 @@ const pagination = ref({
//
const loading = ref(false);
const dataList = ref([]);
const multipleSelection = ref<number[]>([]);
const getList = async () => {
try {
@ -45,6 +48,7 @@ const getList = async () => {
});
dataList.value = data.rows;
pagination.value.total = data.total;
} finally {
loading.value = false;
}
@ -77,6 +81,40 @@ const onCurrentChange = (val: number) => {
//
getList();
const handleDelete = async (row: any) => {
try {
await deleteGoodsApi(row.goodsId);
ElMessage.success("删除成功");
getList();
} catch (error) {
console.error("删除失败", error);
}
};
const handleBulkDelete = async () => {
if (multipleSelection.value.length === 0) return;
try {
await ElMessageBox.confirm(
`确认删除选中的${multipleSelection.value.length}项商品吗?`,
"警告",
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }
);
await deleteGoodsApi(multipleSelection.value.join(","));
ElMessage.success("批量删除成功");
multipleSelection.value = [];
getList();
} catch (error) {
console.error("删除取消或失败", error);
}
};
//
const handleSelectionChange = (rows: any[]) => {
multipleSelection.value = rows.map(row => row.goodsId);
};
</script>
<template>
@ -107,15 +145,18 @@ getList();
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true">
新增商品
</el-button>
<el-button type="danger" :icon="useRenderIcon(Delete)" :disabled="multipleSelection.length === 0"
@click="handleBulkDelete">
批量删除
</el-button>
</template>
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="id" border>
<el-table-column type="index" align="center" width="60" />
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="id"
@selection-change="handleSelectionChange" border>
<el-table-column type="selection" width="55" />
<el-table-column label="商品图片" width="120">
<template #default="{ row }">
<el-image :src="'/img/' + ((row.goodsId % 33) + 1) + '.jpg'"
:preview-src-list="['/img/' + ((row.goodsId % 33) + 1) + '.jpg']" :z-index="9999"
:preview-teleported="true" :hide-on-click-modal="true" fit="cover" class="rounded" width="60"
height="60" />
<el-image :src="row.coverImg" :preview-src-list="[row.coverImg]" :z-index="9999" :preview-teleported="true"
:hide-on-click-modal="true" fit="cover" class="rounded" width="60" height="60" />
</template>
</el-table-column>
<el-table-column label="商品名称" prop="goodsName" />
@ -133,9 +174,13 @@ getList();
<el-button type="primary" link :icon="useRenderIcon(EditPen)">
编辑
</el-button>
<el-button type="danger" link :icon="useRenderIcon(Delete)">
删除
</el-button>
<el-popconfirm :title="`确认删除【${row.goodsName}】?`" @confirm="handleDelete(row)">
<template #reference>
<el-button type="danger" link :icon="useRenderIcon(Delete)">
删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@ -143,6 +188,8 @@ getList();
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="onSizeChange" @current-change="onCurrentChange" />
</PureTableBar>
<!-- 新增商品弹窗 -->
<goods-form-modal v-model:visible="modalVisible" @refresh="getList" />
</div>
</template>
<style scoped>