shop-front-end/src/views/shop/goods/index.vue

324 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref } from "vue";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getGoodsListApi, GoodsDTO } from "@/api/shop/goods";
import EditPen from "@iconify-icons/ep/edit-pen";
import Delete from "@iconify-icons/ep/delete";
import AddFill from "@iconify-icons/ri/add-circle-line";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
import Setting from "@iconify-icons/ep/setting";
import View from "@iconify-icons/ep/view";
import GoodsFormModal from "./goods-form-modal.vue";
import GoodsEditModal from "./goods-edit-modal.vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { deleteGoodsApi } from "@/api/shop/goods";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { useRouter } from "vue-router";
defineOptions({
name: "ShopGoods"
});
const router = useRouter();
const formRef = ref();
const tableRef = ref();
const modalVisible = ref(false);
// 搜索表单
const searchFormParams = ref({
goodsName: "",
status: null
});
// 分页参数
const pagination = ref({
pageSize: 12,
currentPage: 1,
total: 0
});
// 加载数据
const loading = ref(false);
const dataList = ref<GoodsDTO[]>([]);
const multipleSelection = ref<number[]>([]);
const editVisible = ref(false);
const currentRow = ref<GoodsDTO>();
const getList = async () => {
try {
loading.value = true;
const { data } = await getGoodsListApi({
...searchFormParams.value,
pageSize: pagination.value.pageSize,
pageNum: pagination.value.currentPage
});
dataList.value = data.rows;
pagination.value.total = data.total;
} finally {
loading.value = false;
}
};
const handleAddSuccess = () => {
getList();
};
// 搜索
const onSearch = () => {
pagination.value.currentPage = 1;
getList();
};
// 重置
const resetForm = () => {
formRef.value.resetFields();
onSearch();
};
// 分页变化
const onSizeChange = (val: number) => {
pagination.value.pageSize = val;
getList();
};
const onCurrentChange = (val: number) => {
pagination.value.currentPage = val;
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);
};
const handleEdit = (row: GoodsDTO) => {
currentRow.value = row;
editVisible.value = true;
};
const handleViewDetail = (row: GoodsDTO) => {
// 保存信息到标签页
useMultiTagsStoreHook().handleTags("push", {
path: `/shop/goods/detail`,
name: "GoodsDetail",
query: { id: row.goodsId },
meta: {
title: `${row.goodsName}`,
dynamicLevel: 3
}
});
router.push({
path: '/shop/goods/detail',
query: {
id: row.goodsId
}
});
};
</script>
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="searchFormParams"
class="search-form bg-bg_color flex w-[99/100] pl-[22px] pt-[12px]">
<el-form-item prop="goodsName">
<el-input @keydown.enter.prevent="onSearch" v-model="searchFormParams.goodsName" placeholder="请输入商品名称" clearable
class="!w-[200px]" />
</el-form-item>
<!-- <el-form-item label="状态:" prop="status">
<el-select v-model="searchFormParams.status" placeholder="请选择状态" clearable class="!w-[180px]">
<el-option label="已上架" :value="1" />
<el-option label="已下架" :value="2" />
</el-select>
</el-form-item> -->
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(Search)" @click="onSearch">
搜索
</el-button>
</el-form-item>
<el-form-item class="space-item">
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true"
style="margin-right: 10px;">
新增商品
</el-button>
</el-form-item>
</el-form>
<div class="grid-container">
<el-row :gutter="12">
<el-col v-for="(item, index) in dataList" :key="item.goodsId" :xs="24" :sm="12" :md="8" :lg="4" :xl="4">
<el-card class="goods-card" :body-style="{ padding: '8px 10px' }">
<div class="card-content">
<el-image :src="item.coverImg" :preview-src-list="[item.coverImg]" class="goods-image" fit="contain" />
<div class="goods-info">
<div class="price">价格{{ item.price }}</div>
<div class="stock">库存{{ item.stock }}</div>
<div class="info-item">状态{{ item.status === 1 ? '已上架' : '已下架' }}</div>
</div>
</div>
<div class="detail-btn" @click="handleViewDetail(item)">
{{ item.goodsName }}
</div>
</el-card>
</el-col>
</el-row>
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[12, 18, 24, 30, 36, 42]" layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total" @size-change="onSizeChange" @current-change="onCurrentChange" class="pagination" />
</div>
<!-- 新增商品弹窗 -->
<goods-form-modal v-model:visible="modalVisible" @refresh="getList" />
<goods-edit-modal v-model:visible="editVisible" :row="currentRow" @refresh="getList" />
</div>
</template>
<style scoped lang="scss">
/* 覆盖预览层样式 */
:deep(.el-image-viewer__wrapper) {
z-index: 9999 !important;
}
:deep(.el-image-viewer__mask) {
opacity: 1;
background-color: rgba(0, 0, 0, 0.8);
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
margin-right: 12px;
}
}
.space-item {
flex: 1;
width: 100%;
}
.pagination {
margin-top: 10px;
text-align: center;
:deep(.el-pagination) {
margin: 0;
padding: 4px 0;
}
}
.goods-card {
margin-bottom: 12px;
min-height: 210px;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: all 0.3s ease;
/* &:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
} */
}
.card-content {
display: flex;
flex-direction: row;
margin: 0px;
.goods-image {
width: 55%;
height: 180px;
object-fit: contain;
border-radius: 4px;
margin-right: 10px;
}
.goods-info {
flex: 1;
padding: 16px 0 0 0;
.name,
.info-item,
.price,
.stock {
font-size: 14px;
font-weight: 500;
margin-bottom: 6px;
color: #606266;
}
/* .price,
.info-item,
.stock {
color: #909399;
} */
}
}
.divider {
margin: 5px 0px;
}
.detail-btn {
&:hover {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
transition: all 0.3s ease;
}
width: 100%;
margin-top: auto;
padding: 4px 0;
text-align: center;
color: #409eff;
font-weight: 500;
cursor: pointer;
}
.grid-container {
margin-top: 8px;
background-color: var(--el-bg-color);
border-radius: 4px;
padding: 16px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
margin-bottom: 12px;
min-height: 300px;
.el-row {
margin-bottom: -12px;
}
}
</style>