删除商品
This commit is contained in:
parent
454615e8f4
commit
547db61248
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
@ -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
|
||||
});
|
||||
};
|
|
@ -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}`);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue