Files
lilishop-ui/manager/src/views/promotions/points-goods/points-goods.vue
pikachu1995@126.com ba964ff478 refactor(manager): 重构统计报表、促销活动与工单审核等12个列表页为 useTable
- 重构统计报表(销售订单明细、商品销售汇总、店铺业绩)列表页为 useTable

- 重构营销活动(秒杀、第N件优惠、积分商品)列表页为 useTable

- 重构财务流水(钱包日志)、服务工单、分销导入、店铺审核、商品参数、订单投诉列表页

- 消除约 210+ 行分页/防抖/竞态重复样板代码,统一加载与查询状态规范
2026-10-20 14:20:36 +08:00

300 lines
8.6 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.

<template>
<div class="search">
<el-card>
<el-form
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="商品名称">
<el-input
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="积分区间">
<el-input
v-model="searchForm.pointsS"
placeholder="请输入开始区间"
clearable
style="width: 110px"
/>
<span style="margin: 0 6px">-</span>
<el-input
v-model="searchForm.pointsE"
placeholder="请输入结束区间"
clearable
style="width: 110px"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.promotionStatus" clearable style="width: 240px">
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="SKU编码">
<el-input
v-model="searchForm.skuId"
placeholder="请输入SKU编码"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<div class="operation padding-row">
<el-button type="primary" @click="addPointsGoods">添加积分商品</el-button>
</div>
<el-table
ref="tableRef"
v-loading="loading"
border
:data="data"
class="mt_10"
style="width: 100%"
>
<el-table-column prop="goodsId" label="商品编号" width="180" show-overflow-tooltip />
<el-table-column label="商品图片" width="100" align="center">
<template #default="{ row }">
<img alt="商品图片"
v-if="row && row.thumbnail"
:src="row.thumbnail"
style="height: 50px; width: 50px; object-fit: cover"
/>
</template>
</el-table-column>
<el-table-column label="商品名称" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<a v-if="row" class="link-text" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
</template>
</el-table-column>
<el-table-column label="市场价" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.originalPrice, "¥") }}</span>
</template>
</el-table-column>
<el-table-column label="结算价" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.settlementPrice, "¥") }}</span>
</template>
</el-table-column>
<el-table-column label="库存数量" width="100">
<template #default="{ row }">
<span v-if="row">{{ row.activeStock }}</span>
</template>
</el-table-column>
<el-table-column prop="activeStock" label="活动剩余库存" width="130" />
<el-table-column prop="points" label="兑换积分" width="100" />
<el-table-column prop="storeName" label="所属店铺" width="120" show-overflow-tooltip />
<el-table-column label="活动开始时间" min-width="160">
<template #default="{ row }">
<template v-if="row">
<div>{{ row.startTime }}</div>
<div>{{ row.endTime }}</div>
</template>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag v-if="row" :type="promotionStatusTagType(row.promotionStatus)">
{{ promotionStatusText(row.promotionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="pointsGoodsCategoryName" label="分类" width="100" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a
v-if="row.promotionStatus === 'CLOSE' || row.promotionStatus === 'NEW'"
class="link-text"
@click="edit(row.id)"
>
编辑
</a>
<template v-if="row.promotionStatus === 'START' || row.promotionStatus === 'NEW'">
<span class="op-split">|</span>
<a class="link-text" @click="statusChanged(row.id, 'CLOSE')">关闭</a>
</template>
</template>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { useTable } from "@/utils/useTable";
import { useRouter } from "vue-router";
import {
getPointsGoodsList,
editPointsGoodsStatus,
deletePointsGoodsStatus,
} from "@/api/promotion";
import { Message, Modal } from "@/utils/message";
defineOptions({ name: "pointsGoods" });
const router = useRouter();
// 模板 ref,与 data 字段同名会互相覆盖,改为 searchFormRef / tableRef
const searchFormRef = ref(null);
const tableRef = ref(null);
const { loading, data, total, searchForm, getDataList } = useTable(
getPointsGoodsList,
{
pageSize: 20,
immediate: false,
form: { sort: "createTime", order: "desc" },
}
);
const statusList = ref([
{ label: "未开始", value: "NEW" },
{ label: "已开始", value: "START" },
{ label: "已结束", value: "END" },
{ label: "已关闭", value: "CLOSE" },
]);
onMounted(() => {
init();
});
function promotionStatusText(status) {
const map = {
NEW: "未开始",
START: "已开始",
END: "已结束",
CLOSE: "已关闭",
};
return map[status] || "未知";
}
function promotionStatusTagType(status) {
const map = {
NEW: "info",
START: "success",
END: "danger",
CLOSE: "danger",
};
return map[status] || "danger";
}
function init() {
getDataList();
}
function addPointsGoods() {
router.push({ name: "add-points-goods" });
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
if (searchForm.pointsS) {
searchForm.points =
searchForm.pointsS + "_" + (searchForm.pointsE ? searchForm.pointsE : "");
}
getDataList();
}
function edit(id) {
router.push({ name: "edit-points-goods", query: { id } });
}
function statusChanged(id, status, startTime, endTime) {
let text = "";
let params = {};
if (status == "START") {
text = "开启";
params = { startTime, endTime };
} else if (status == "CLOSE") {
text = "关闭";
}
Modal.confirm({
title: "确认" + text,
content: "您确认要" + text + "此积分商品?",
loading: true,
onOk: () => {
editPointsGoodsStatus(id, params).then((res) => {
Modal.remove();
if (res.success) {
Message.success(text + "成功");
getDataList();
}
});
},
});
}
function close(id) {
Modal.confirm({
title: "确认删除",
content: "您确认要删除此积分商品?",
loading: true,
onOk: () => {
deletePointsGoodsStatus(id).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
});
}
</script>
<style scoped>
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.op-split {
margin: 0 8px;
color: #dcdee2;
}
.padding-row {
margin-bottom: 10px;
}
.mt_10 {
margin-top: 10px;
}
</style>