删除商品
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}`);
|
return http.request<ResponseData<void>>("delete", `/shop/goods/${goodsId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from "vue";
|
import { ref, reactive, onMounted, watch } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage, FormRules } from "element-plus";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import { addGoodsApi } from "@/api/shop/goods";
|
import { addGoodsApi } from "@/api/shop/goods";
|
||||||
|
import { CategoryDTO, getCategoryAllApi } from "@/api/shop/category";
|
||||||
import Confirm from "@iconify-icons/ep/check";
|
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({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
|
@ -19,10 +22,13 @@ const formData = reactive({
|
||||||
goodsName: "",
|
goodsName: "",
|
||||||
price: 0,
|
price: 0,
|
||||||
stock: 0,
|
stock: 0,
|
||||||
status: 1
|
status: 1,
|
||||||
|
categoryId: 0,
|
||||||
|
goodsDetail: "",
|
||||||
|
coverImg: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive<FormRules>({
|
||||||
goodsName: [{ required: true, message: "商品名称必填", trigger: "blur" }],
|
goodsName: [{ required: true, message: "商品名称必填", trigger: "blur" }],
|
||||||
price: [
|
price: [
|
||||||
{ required: true, message: "价格必填", trigger: "blur" },
|
{ required: true, message: "价格必填", trigger: "blur" },
|
||||||
|
@ -31,6 +37,9 @@ const rules = reactive({
|
||||||
stock: [
|
stock: [
|
||||||
{ required: true, message: "库存必填", trigger: "blur" },
|
{ required: true, message: "库存必填", trigger: "blur" },
|
||||||
{ type: 'number', min: 0, message: '库存不能小于0' }
|
{ type: 'number', min: 0, message: '库存不能小于0' }
|
||||||
|
],
|
||||||
|
coverImg: [
|
||||||
|
{ required: true, message: "请上传商品封面图", trigger: "change" }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,44 +59,70 @@ const closeDialog = () => {
|
||||||
formRef.value.resetFields();
|
formRef.value.resetFields();
|
||||||
emit("update:visible", false);
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog title="新增商品" :model-value="visible" width="600px" @close="closeDialog" @open="loadCategories">
|
||||||
title="新增商品"
|
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px" label-position="right">
|
||||||
:model-value="visible"
|
|
||||||
width="600px"
|
|
||||||
@close="closeDialog"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="rules"
|
|
||||||
label-width="80px"
|
|
||||||
label-position="right"
|
|
||||||
>
|
|
||||||
<el-form-item label="商品名称" prop="goodsName">
|
<el-form-item label="商品名称" prop="goodsName">
|
||||||
<el-input
|
<el-input v-model="formData.goodsName" placeholder="请输入商品名称" clearable />
|
||||||
v-model="formData.goodsName"
|
</el-form-item>
|
||||||
placeholder="请输入商品名称"
|
<el-form-item label="封面图" prop="coverImg">
|
||||||
clearable
|
<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>
|
||||||
<el-form-item label="价格" prop="price">
|
<el-form-item label="价格" prop="price">
|
||||||
<el-input-number
|
<el-input-number v-model="formData.price" :min="0" :precision="2" controls-position="right" />
|
||||||
v-model="formData.price"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
controls-position="right"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="库存" prop="stock">
|
<el-form-item label="库存" prop="stock">
|
||||||
<el-input-number
|
<el-input-number v-model="formData.stock" :min="0" controls-position="right" />
|
||||||
v-model="formData.stock"
|
|
||||||
:min="0"
|
|
||||||
controls-position="right"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</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-form-item label="状态" prop="status">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
<el-radio :label="1">上架</el-radio>
|
<el-radio :label="1">上架</el-radio>
|
||||||
|
@ -98,13 +133,32 @@ const closeDialog = () => {
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="closeDialog">取消</el-button>
|
<el-button @click="closeDialog">取消</el-button>
|
||||||
<el-button
|
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
|
||||||
type="primary"
|
|
||||||
:icon="useRenderIcon(Confirm)"
|
|
||||||
@click="handleConfirm"
|
|
||||||
>
|
|
||||||
确认
|
确认
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</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 Search from "@iconify-icons/ep/search";
|
||||||
import Refresh from "@iconify-icons/ep/refresh";
|
import Refresh from "@iconify-icons/ep/refresh";
|
||||||
import GoodsFormModal from "./goods-form-modal.vue";
|
import GoodsFormModal from "./goods-form-modal.vue";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import { deleteGoodsApi } from "@/api/shop/goods";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "ShopGoods"
|
name: "ShopGoods"
|
||||||
|
@ -34,6 +36,7 @@ const pagination = ref({
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const dataList = ref([]);
|
const dataList = ref([]);
|
||||||
|
const multipleSelection = ref<number[]>([]);
|
||||||
|
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -45,6 +48,7 @@ const getList = async () => {
|
||||||
});
|
});
|
||||||
dataList.value = data.rows;
|
dataList.value = data.rows;
|
||||||
pagination.value.total = data.total;
|
pagination.value.total = data.total;
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -77,6 +81,40 @@ const onCurrentChange = (val: number) => {
|
||||||
|
|
||||||
// 初始化加载
|
// 初始化加载
|
||||||
getList();
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -107,15 +145,18 @@ getList();
|
||||||
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true">
|
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true">
|
||||||
新增商品
|
新增商品
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="danger" :icon="useRenderIcon(Delete)" :disabled="multipleSelection.length === 0"
|
||||||
|
@click="handleBulkDelete">
|
||||||
|
批量删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="id" border>
|
<el-table ref="tableRef" v-loading="loading" :data="dataList" row-key="id"
|
||||||
<el-table-column type="index" align="center" width="60" />
|
@selection-change="handleSelectionChange" border>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="商品图片" width="120">
|
<el-table-column label="商品图片" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-image :src="'/img/' + ((row.goodsId % 33) + 1) + '.jpg'"
|
<el-image :src="row.coverImg" :preview-src-list="[row.coverImg]" :z-index="9999" :preview-teleported="true"
|
||||||
:preview-src-list="['/img/' + ((row.goodsId % 33) + 1) + '.jpg']" :z-index="9999"
|
:hide-on-click-modal="true" fit="cover" class="rounded" width="60" height="60" />
|
||||||
:preview-teleported="true" :hide-on-click-modal="true" fit="cover" class="rounded" width="60"
|
|
||||||
height="60" />
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="商品名称" prop="goodsName" />
|
<el-table-column label="商品名称" prop="goodsName" />
|
||||||
|
@ -133,9 +174,13 @@ getList();
|
||||||
<el-button type="primary" link :icon="useRenderIcon(EditPen)">
|
<el-button type="primary" link :icon="useRenderIcon(EditPen)">
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" link :icon="useRenderIcon(Delete)">
|
<el-popconfirm :title="`确认删除【${row.goodsName}】?`" @confirm="handleDelete(row)">
|
||||||
删除
|
<template #reference>
|
||||||
</el-button>
|
<el-button type="danger" link :icon="useRenderIcon(Delete)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
@ -143,6 +188,8 @@ getList();
|
||||||
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
|
:page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
|
||||||
@size-change="onSizeChange" @current-change="onCurrentChange" />
|
@size-change="onSizeChange" @current-change="onCurrentChange" />
|
||||||
</PureTableBar>
|
</PureTableBar>
|
||||||
|
<!-- 新增商品弹窗 -->
|
||||||
|
<goods-form-modal v-model:visible="modalVisible" @refresh="getList" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
Loading…
Reference in New Issue