mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-13 04:18:55 +08:00
feat: 优化商品促销页面并独立展示定时上架
- 新增平台、商家定时上架独立页面,平台列表仅供查看 - 虚拟销量支持状态筛选及右侧抽屉设置,修复编码显示问题 - 统一列表操作栏,优化商品选择器、砍价、积分及优惠券页面 - 两端新增活动优惠券入口,隐藏创建表单中的活动类型字段 - 移除限时直降、第 N 件优惠页面、路由、接口调用及缓存菜单入口 验证:26 个改动前端文件编译或语法检查通过,暂存差异格式检查通过。
This commit is contained in:
@@ -283,11 +283,6 @@ export const getCouponReceiveList = (params) => {
|
|||||||
return getRequest("/promotion/coupon/received", params);
|
return getRequest("/promotion/coupon/received", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFlashDiscountList = (params) => getRequest("/promotion/flashDiscount", params);
|
|
||||||
export const getFlashDiscountDetail = (id) => getRequest(`/promotion/flashDiscount/${id}`);
|
|
||||||
export const updateFlashDiscountStatus = (id, params) => putRequest(`/promotion/flashDiscount/status/${id}`, params);
|
|
||||||
export const deleteFlashDiscount = (id) => deleteRequest(`/promotion/flashDiscount/${id}`);
|
|
||||||
export const getNthItemDiscountList = (params) => getRequest("/promotion/nthItemDiscount", params);
|
|
||||||
|
|
||||||
// 现金礼品卡活动分页
|
// 现金礼品卡活动分页
|
||||||
export const getGiftCardCashActivityPage = (params) => {
|
export const getGiftCardCashActivityPage = (params) => {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
align="center"
|
align="center"
|
||||||
reserve-selection
|
reserve-selection
|
||||||
/>
|
/>
|
||||||
<el-table-column label="商品图片" width="76" align="center">
|
<el-table-column label="图片" width="76" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-image
|
<el-image
|
||||||
class="goods-thumb"
|
class="goods-thumb"
|
||||||
@@ -124,6 +124,8 @@
|
|||||||
v-model:page-size="goodsParams.pageSize"
|
v-model:page-size="goodsParams.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
:page-sizes="[15, 30, 50, 100]"
|
:page-sizes="[15, 30, 50, 100]"
|
||||||
|
popper-class="goods-dialog-pagination-popper"
|
||||||
|
:teleported="true"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
size="small"
|
size="small"
|
||||||
@current-change="changePage"
|
@current-change="changePage"
|
||||||
|
|||||||
@@ -214,7 +214,8 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 商品分类下拉挂载到 body,需要高于抽屉的 10000 层级
|
// 商品分类下拉挂载到 body,需要高于抽屉的 10000 层级
|
||||||
.goods-dialog-cascader-popper {
|
.goods-dialog-cascader-popper,
|
||||||
|
.goods-dialog-pagination-popper {
|
||||||
z-index: 10001 !important;
|
z-index: 10001 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
30
manager/src/components/list-action-bar.vue
Normal file
30
manager/src/components/list-action-bar.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-action-bar" role="group" aria-label="列表操作">
|
||||||
|
<div class="list-action-bar__primary"><slot /></div>
|
||||||
|
<div v-if="$slots.right" class="list-action-bar__secondary"><slot name="right" /></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-action-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&__primary,
|
||||||
|
&__secondary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button + .el-button) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,6 +10,8 @@ const DEPRECATED_FRONT_ROUTES = new Set([
|
|||||||
"goods/goods-manage/spec",
|
"goods/goods-manage/spec",
|
||||||
"member/list/memberRecycle",
|
"member/list/memberRecycle",
|
||||||
"order/order/fictitiousOrderList",
|
"order/order/fictitiousOrderList",
|
||||||
|
"promotions/flash-discount",
|
||||||
|
"promotions/nth-item-discount",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function isDeprecatedMenu(menu) {
|
function isDeprecatedMenu(menu) {
|
||||||
|
|||||||
@@ -126,6 +126,12 @@ export const otherRouter = {
|
|||||||
name: "goods-category",
|
name: "goods-category",
|
||||||
component: () => import("@/views/goods/goods-manage/category.vue")
|
component: () => import("@/views/goods/goods-manage/category.vue")
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "goods-scheduled-upper",
|
||||||
|
title: "定时上架",
|
||||||
|
name: "goods-scheduled-upper",
|
||||||
|
component: () => import("@/views/goods/scheduled-upper/index.vue")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "goods-group",
|
path: "goods-group",
|
||||||
title: "商品分组",
|
title: "商品分组",
|
||||||
@@ -360,18 +366,6 @@ export const otherRouter = {
|
|||||||
name: "full-discount-detail",
|
name: "full-discount-detail",
|
||||||
component: () => import("@/views/promotions/full-discount/full-discount-detail.vue")
|
component: () => import("@/views/promotions/full-discount/full-discount-detail.vue")
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "promotions/flash-discount",
|
|
||||||
title: "限时直降",
|
|
||||||
name: "flash-discount",
|
|
||||||
component: () => import("@/views/promotions/flash-discount/flash-discount.vue")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "promotions/nth-item-discount",
|
|
||||||
title: "第N件优惠",
|
|
||||||
name: "nth-item-discount",
|
|
||||||
component: () => import("@/views/promotions/nth-item-discount/nth-item-discount.vue")
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "promotions/seckill/manager-seckill-add",
|
path: "promotions/seckill/manager-seckill-add",
|
||||||
title: "编辑秒杀活动",
|
title: "编辑秒杀活动",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<el-card>
|
<el-card>
|
||||||
<div class="operation">
|
<list-action-bar>
|
||||||
<el-button type="primary" @click="add">添加</el-button>
|
<el-button type="primary" @click="add">添加</el-button>
|
||||||
<el-button @click="delAll">批量删除</el-button>
|
<el-button @click="delAll">批量删除</el-button>
|
||||||
</div>
|
</list-action-bar>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
ref="table"
|
ref="table"
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ListActionBar from "@/components/list-action-bar.vue";
|
||||||
import { ref, reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import {
|
import {
|
||||||
addGoodsUnit,
|
addGoodsUnit,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card>
|
<el-card>
|
||||||
<div class="goods-tab">
|
<div v-if="!scheduledOnly" class="goods-tab">
|
||||||
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
v-for="item in goodsStatusWithCount"
|
v-for="item in goodsStatusWithCount"
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="batch-operations" style="margin: 10px 0">
|
<div v-if="!readOnly" class="batch-operations" style="margin: 10px 0">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="selectedRows.length === 0"
|
:disabled="selectedRows.length === 0"
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
@selection-change="onSelectionChange"
|
@selection-change="onSelectionChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="100" align="center" />
|
<el-table-column v-if="!readOnly" type="selection" width="100" align="center" />
|
||||||
<el-table-column prop="id" label="SPU编码" width="200" show-overflow-tooltip />
|
<el-table-column prop="id" label="SPU编码" width="200" show-overflow-tooltip />
|
||||||
<el-table-column label="商品图片" width="100" align="center">
|
<el-table-column label="商品图片" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -141,6 +141,7 @@
|
|||||||
<a class="link-text" @click="linkTo(row.id, row.skuId)">{{ row.goodsName }}</a>
|
<a class="link-text" @click="linkTo(row.id, row.skuId)">{{ row.goodsName }}</a>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column v-if="currentStatus === 'SCHEDULED_UPPER'" prop="scheduledUpperTime" label="计划上架时间" width="190" />
|
||||||
<el-table-column label="价格" width="200">
|
<el-table-column label="价格" width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span :style="{ color: $mainColor }">{{ unitPrice(row.price, '¥') }}</span>
|
<span :style="{ color: $mainColor }">{{ unitPrice(row.price, '¥') }}</span>
|
||||||
@@ -164,7 +165,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="storeName" label="店铺名称" width="200" show-overflow-tooltip />
|
<el-table-column prop="storeName" label="店铺名称" width="200" show-overflow-tooltip />
|
||||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
<el-table-column v-if="!readOnly" label="操作" width="200" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<template v-if="row.authFlag === 'TOBEAUDITED'">
|
<template v-if="row.authFlag === 'TOBEAUDITED'">
|
||||||
<a class="link-text" @click="openAuditModal(row)">审核</a>
|
<a class="link-text" @click="openAuditModal(row)">审核</a>
|
||||||
@@ -298,6 +299,10 @@ import { ElMessage, ElMessageBox } from "element-plus";
|
|||||||
import { unitPrice, customRouterPush } from "@/utils/filters";
|
import { unitPrice, customRouterPush } from "@/utils/filters";
|
||||||
|
|
||||||
defineOptions({ name: "goods" });
|
defineOptions({ name: "goods" });
|
||||||
|
const props = defineProps({
|
||||||
|
scheduledOnly: { type: Boolean, default: false },
|
||||||
|
readOnly: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
|
||||||
// 模板 ref(searchFormRef/underFormRef/batchAuditFormRef/goodsGroupFormRef 因与 data 同名改为 xxxRef)
|
// 模板 ref(searchFormRef/underFormRef/batchAuditFormRef/goodsGroupFormRef 因与 data 同名改为 xxxRef)
|
||||||
const searchFormRef = ref(null);
|
const searchFormRef = ref(null);
|
||||||
@@ -315,7 +320,7 @@ const auditModalVisible = ref(false);
|
|||||||
const auditSubmitLoading = ref(false);
|
const auditSubmitLoading = ref(false);
|
||||||
const currentAuditGoods = ref(null);
|
const currentAuditGoods = ref(null);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
const currentStatus = ref("ALL");
|
const currentStatus = ref(props.scheduledOnly ? "SCHEDULED_UPPER" : "ALL");
|
||||||
|
|
||||||
const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch } = useTable(
|
const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch } = useTable(
|
||||||
getGoodsListData,
|
getGoodsListData,
|
||||||
@@ -333,8 +338,13 @@ const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch
|
|||||||
},
|
},
|
||||||
beforeRequest: (p) => {
|
beforeRequest: (p) => {
|
||||||
const params = { ...p };
|
const params = { ...p };
|
||||||
if (currentStatus.value && currentStatus.value !== "ALL") {
|
const status = props.scheduledOnly ? "SCHEDULED_UPPER" : currentStatus.value;
|
||||||
params.goodsStatus = currentStatus.value;
|
if (status && status !== "ALL") {
|
||||||
|
params.goodsStatus = status;
|
||||||
|
if (status === "SCHEDULED_UPPER") {
|
||||||
|
params.sort = "scheduled_upper_time";
|
||||||
|
params.order = "asc";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete params.goodsStatus;
|
delete params.goodsStatus;
|
||||||
}
|
}
|
||||||
@@ -347,7 +357,7 @@ const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch
|
|||||||
);
|
);
|
||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
currentStatus.value = "ALL";
|
currentStatus.value = props.scheduledOnly ? "SCHEDULED_UPPER" : "ALL";
|
||||||
resetSearch();
|
resetSearch();
|
||||||
}
|
}
|
||||||
const goodsNumerData = ref({});
|
const goodsNumerData = ref({});
|
||||||
@@ -415,6 +425,7 @@ function init() {
|
|||||||
// 搜索
|
// 搜索
|
||||||
// 获取数据
|
// 获取数据
|
||||||
function getNumberData() {
|
function getNumberData() {
|
||||||
|
if (props.scheduledOnly) return;
|
||||||
// 创建一个不包含goodsStatus字段的搜索参数
|
// 创建一个不包含goodsStatus字段的搜索参数
|
||||||
const { goodsStatus, ...searchParams } = searchForm;
|
const { goodsStatus, ...searchParams } = searchForm;
|
||||||
getGoodsNumerData(searchParams).then((res) => {
|
getGoodsNumerData(searchParams).then((res) => {
|
||||||
@@ -488,6 +499,7 @@ function goodsStatusClick(name) {
|
|||||||
}
|
}
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
clearTableSelection();
|
clearTableSelection();
|
||||||
|
searchForm.pageNumber = 1;
|
||||||
getDataList();
|
getDataList();
|
||||||
}
|
}
|
||||||
function examine(v, authFlag) {
|
function examine(v, authFlag) {
|
||||||
|
|||||||
@@ -28,17 +28,12 @@
|
|||||||
|
|
||||||
<!-- 品牌数据表格卡片 -->
|
<!-- 品牌数据表格卡片 -->
|
||||||
<el-card shadow="never" class="table-card mt_10">
|
<el-card shadow="never" class="table-card mt_10">
|
||||||
<template #header>
|
<list-action-bar>
|
||||||
<div class="card-header-bar">
|
<el-button type="primary" @click="add">添加品牌</el-button>
|
||||||
<div class="header-title">
|
<template #right>
|
||||||
<span>商品品牌列表</span>
|
<el-button @click="getDataList">刷新</el-button>
|
||||||
</div>
|
</template>
|
||||||
<div class="header-actions">
|
</list-action-bar>
|
||||||
<el-button size="small" @click="getDataList">刷新</el-button>
|
|
||||||
<el-button type="primary" size="small" @click="add">添加品牌</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
ref="table"
|
ref="table"
|
||||||
@@ -197,6 +192,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ListActionBar from "@/components/list-action-bar.vue";
|
||||||
import { ref, reactive, nextTick, getCurrentInstance } from "vue";
|
import { ref, reactive, nextTick, getCurrentInstance } from "vue";
|
||||||
import { useTable } from "@/utils/useTable";
|
import { useTable } from "@/utils/useTable";
|
||||||
import {
|
import {
|
||||||
@@ -429,27 +425,6 @@ function edit(v) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-mono {
|
.font-mono {
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-card>
|
<el-card>
|
||||||
<div class="mb_10">
|
<list-action-bar>
|
||||||
<el-button type="primary" @click="addParent">添加一级分类</el-button>
|
<el-button type="primary" @click="addParent">添加一级分类</el-button>
|
||||||
</div>
|
</list-action-bar>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
@@ -121,6 +121,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ListActionBar from "@/components/list-action-bar.vue";
|
||||||
import { ref, reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import {
|
import {
|
||||||
delCategory,
|
delCategory,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<el-card>
|
<el-card class="parameter-filter-card">
|
||||||
<el-form ref="searchFormRef" :model="searchForm" inline>
|
<el-form ref="searchFormRef" :model="searchForm" inline class="search-form" @keyup.enter="handleSearch">
|
||||||
<el-form-item label="参数名称" prop="paramName">
|
<el-form-item label="参数名称" prop="paramName">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchForm.paramName"
|
v-model="searchForm.paramName"
|
||||||
@@ -16,9 +16,12 @@
|
|||||||
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-row class="operation" style="margin-bottom: 10px;">
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="parameter-list-card" style="margin-top: 10px">
|
||||||
|
<list-action-bar>
|
||||||
<el-button type="primary" @click="goAdd">添加</el-button>
|
<el-button type="primary" @click="goAdd">添加</el-button>
|
||||||
</el-row>
|
</list-action-bar>
|
||||||
<el-table
|
<el-table
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
@@ -72,6 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ListActionBar from "@/components/list-action-bar.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useTable } from "@/utils/useTable";
|
import { useTable } from "@/utils/useTable";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|||||||
@@ -18,24 +18,17 @@
|
|||||||
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div class="toolbar-right">
|
|
||||||
<el-button type="primary" @click="openAdd">添加分组</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 分组数据表格卡片 -->
|
<!-- 分组数据表格卡片 -->
|
||||||
<el-card shadow="never" class="table-card mt_10">
|
<el-card shadow="never" class="table-card mt_10">
|
||||||
<template #header>
|
<list-action-bar>
|
||||||
<div class="card-header-bar">
|
<el-button type="primary" @click="openAdd">添加分组</el-button>
|
||||||
<div class="header-title">
|
<template #right>
|
||||||
<span>商品分组列表</span>
|
<el-button @click="getData">刷新</el-button>
|
||||||
</div>
|
</template>
|
||||||
<div class="header-actions">
|
</list-action-bar>
|
||||||
<el-button size="small" @click="getData">刷新</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
ref="table"
|
ref="table"
|
||||||
@@ -166,6 +159,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ListActionBar from "@/components/list-action-bar.vue";
|
||||||
import { ref, reactive, nextTick, onMounted } from "vue";
|
import { ref, reactive, nextTick, onMounted } from "vue";
|
||||||
import * as API_Goods from "@/api/goods.js";
|
import * as API_Goods from "@/api/goods.js";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
@@ -373,25 +367,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-wrap {
|
.pagination-wrap {
|
||||||
|
|||||||
14
manager/src/views/goods/scheduled-upper/index.vue
Normal file
14
manager/src/views/goods/scheduled-upper/index.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<goods-list scheduled-only read-only />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 平台商品定时上架列表。
|
||||||
|
* @author Bulbasaur
|
||||||
|
* @since 2026-11-03
|
||||||
|
*/
|
||||||
|
import GoodsList from "@/views/goods/goods-info/goods.vue";
|
||||||
|
|
||||||
|
defineOptions({ name: "goodsScheduledUpper" });
|
||||||
|
</script>
|
||||||
@@ -49,6 +49,11 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="mt_10">
|
<el-card class="mt_10">
|
||||||
|
<el-tabs v-model="salesTab" @tab-change="handleSalesTabChange">
|
||||||
|
<el-tab-pane label="全部" name="all" />
|
||||||
|
<el-tab-pane label="已设置" name="configured" />
|
||||||
|
<el-tab-pane label="未设置" name="unconfigured" />
|
||||||
|
</el-tabs>
|
||||||
<el-alert
|
<el-alert
|
||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
@@ -104,8 +109,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="goodsId" label="SPU编码" width="160" show-overflow-tooltip />
|
<el-table-column prop="goodsId" label="SPU编码" width="220" show-overflow-tooltip />
|
||||||
<el-table-column prop="id" label="SKU ID" width="160" show-overflow-tooltip />
|
<el-table-column prop="id" label="SKU ID" width="220" show-overflow-tooltip />
|
||||||
<el-table-column label="真实销量" width="110" align="right">
|
<el-table-column label="真实销量" width="110" align="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row">{{ row.buyCount || 0 }}</span>
|
<span v-if="row">{{ row.buyCount || 0 }}</span>
|
||||||
@@ -156,14 +161,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-dialog
|
<el-drawer
|
||||||
v-model="editModalVisible"
|
v-model="editModalVisible"
|
||||||
:title="modalMode === 'batch' ? '批量设置虚拟销量' : '设置虚拟销量'"
|
:title="modalMode === 'batch' ? '批量设置虚拟销量' : '设置虚拟销量'"
|
||||||
width="480px"
|
class="virtual-sales-drawer"
|
||||||
|
direction="rtl"
|
||||||
|
size="min(480px, 100vw)"
|
||||||
|
append-to-body
|
||||||
|
:before-close="handleBeforeCloseVirtualSales"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
:close-on-press-escape="!modalSubmitting"
|
:close-on-press-escape="!modalSubmitting"
|
||||||
:show-close="!modalSubmitting"
|
:show-close="!modalSubmitting"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
|
@closed="resetVirtualSalesModal"
|
||||||
>
|
>
|
||||||
<div v-if="modalMode === 'single' && currentSku" class="virtual-sales-modal">
|
<div v-if="modalMode === 'single' && currentSku" class="virtual-sales-modal">
|
||||||
<div class="sku-info-item">
|
<div class="sku-info-item">
|
||||||
@@ -208,12 +218,14 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button :disabled="modalSubmitting" @click="handleCancelVirtualSales">取消</el-button>
|
<div class="drawer-actions">
|
||||||
<el-button type="primary" :loading="modalSubmitting" @click="handleSubmitVirtualSales">
|
<el-button :disabled="modalSubmitting" @click="handleCancelVirtualSales">取消</el-button>
|
||||||
提交
|
<el-button type="primary" :loading="modalSubmitting" @click="handleSubmitVirtualSales">
|
||||||
</el-button>
|
提交
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -248,6 +260,7 @@ const modalMode = ref("single");
|
|||||||
const currentSku = ref(null);
|
const currentSku = ref(null);
|
||||||
const currentIndex = ref(-1);
|
const currentIndex = ref(-1);
|
||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
|
const salesTab = ref("all");
|
||||||
const editForm = reactive({
|
const editForm = reactive({
|
||||||
virtualSales: 0,
|
virtualSales: 0,
|
||||||
});
|
});
|
||||||
@@ -263,6 +276,7 @@ const searchForm = ref({
|
|||||||
});
|
});
|
||||||
const data = ref([]);
|
const data = ref([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
let dataRequestId = 0;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
getDataList();
|
getDataList();
|
||||||
@@ -280,6 +294,12 @@ function handleSearch() {
|
|||||||
searchForm.value.pageNumber = 1;
|
searchForm.value.pageNumber = 1;
|
||||||
getDataList();
|
getDataList();
|
||||||
}
|
}
|
||||||
|
function handleSalesTabChange() {
|
||||||
|
searchForm.value.pageNumber = 1;
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.value = {
|
searchForm.value = {
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
@@ -307,9 +327,15 @@ function handleSelectionChange(selection) {
|
|||||||
selectedRows.value = selection;
|
selectedRows.value = selection;
|
||||||
}
|
}
|
||||||
function getDataList() {
|
function getDataList() {
|
||||||
|
const requestId = ++dataRequestId;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
getGoodsSkuData(searchForm.value)
|
const params = { ...searchForm.value };
|
||||||
|
if (salesTab.value !== "all") {
|
||||||
|
params.virtualSalesSet = salesTab.value === "configured";
|
||||||
|
}
|
||||||
|
getGoodsSkuData(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
if (requestId !== dataRequestId) return;
|
||||||
if (res && res.success && res.result) {
|
if (res && res.success && res.result) {
|
||||||
data.value = (res.result.records || []).map((item) => ({
|
data.value = (res.result.records || []).map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -317,6 +343,10 @@ function getDataList() {
|
|||||||
saving: false,
|
saving: false,
|
||||||
}));
|
}));
|
||||||
total.value = res.result.total || 0;
|
total.value = res.result.total || 0;
|
||||||
|
if (data.value.length === 0 && total.value > 0 && searchForm.value.pageNumber > 1) {
|
||||||
|
searchForm.value.pageNumber = Math.max(1, Math.ceil(total.value / searchForm.value.pageSize));
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
} else {
|
} else {
|
||||||
data.value = [];
|
data.value = [];
|
||||||
@@ -326,13 +356,14 @@ function getDataList() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
if (requestId !== dataRequestId) return;
|
||||||
data.value = [];
|
data.value = [];
|
||||||
total.value = 0;
|
total.value = 0;
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
ElMessage.error("加载规格列表失败");
|
ElMessage.error("加载规格列表失败");
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false;
|
if (requestId === dataRequestId) loading.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getVirtualSalesValue(row) {
|
function getVirtualSalesValue(row) {
|
||||||
@@ -363,6 +394,10 @@ function handleCancelVirtualSales() {
|
|||||||
if (modalSubmitting.value) return;
|
if (modalSubmitting.value) return;
|
||||||
resetVirtualSalesModal();
|
resetVirtualSalesModal();
|
||||||
}
|
}
|
||||||
|
function handleBeforeCloseVirtualSales(done) {
|
||||||
|
if (modalSubmitting.value) return;
|
||||||
|
done();
|
||||||
|
}
|
||||||
function resetVirtualSalesModal() {
|
function resetVirtualSalesModal() {
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
modalMode.value = "single";
|
modalMode.value = "single";
|
||||||
@@ -397,6 +432,7 @@ function handleSubmitVirtualSales() {
|
|||||||
}
|
}
|
||||||
ElMessage.success("虚拟销量设置成功");
|
ElMessage.success("虚拟销量设置成功");
|
||||||
resetVirtualSalesModal();
|
resetVirtualSalesModal();
|
||||||
|
getDataList();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error((res && res.message) || "规格虚拟销量设置失败");
|
ElMessage.error((res && res.message) || "规格虚拟销量设置失败");
|
||||||
}
|
}
|
||||||
@@ -433,6 +469,7 @@ function handleBatchSubmitVirtualSales(virtualSales) {
|
|||||||
tableRef.value?.clearSelection();
|
tableRef.value?.clearSelection();
|
||||||
ElMessage.success("虚拟销量设置成功");
|
ElMessage.success("虚拟销量设置成功");
|
||||||
resetVirtualSalesModal();
|
resetVirtualSalesModal();
|
||||||
|
getDataList();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error((res && res.message) || "虚拟销量设置失败");
|
ElMessage.error((res && res.message) || "虚拟销量设置失败");
|
||||||
}
|
}
|
||||||
@@ -454,6 +491,28 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.virtual-sales-drawer {
|
||||||
|
:deep(.el-drawer__header) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-drawer__footer) {
|
||||||
|
border-top: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
:deep(.el-button + .el-button) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.goods-info {
|
.goods-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -12,22 +12,12 @@
|
|||||||
<el-tag v-else-if="id" type="warning" size="small" effect="plain" class="mode-tag">
|
<el-tag v-else-if="id" type="warning" size="small" effect="plain" class="mode-tag">
|
||||||
编辑模式
|
编辑模式
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-tag v-else type="success" size="small" effect="plain" class="mode-tag">
|
|
||||||
新建模式
|
|
||||||
</el-tag>
|
|
||||||
<span v-if="id" class="coupon-id-tip font-mono">ID: {{ id }}</span>
|
<span v-if="id" class="coupon-id-tip font-mono">ID: {{ id }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="page-description">配置优惠内容、发放规则及适用商品范围。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<el-button @click="closeCurrentPage">返回</el-button>
|
<el-button :icon="ArrowLeft" @click="closeCurrentPage">返回优惠券列表</el-button>
|
||||||
<el-button
|
|
||||||
v-if="!isOnlyView"
|
|
||||||
type="primary"
|
|
||||||
:loading="submitLoading"
|
|
||||||
@click="handleSubmit"
|
|
||||||
>
|
|
||||||
保存提交
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -44,7 +34,7 @@
|
|||||||
<!-- 模块一:基本信息 -->
|
<!-- 模块一:基本信息 -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span class="section-indicator"></span>
|
<span class="section-number">01</span>
|
||||||
<span class="section-title">基本信息</span>
|
<span class="section-title">基本信息</span>
|
||||||
<span class="section-subtitle">配置活动名称、券面额及发放规则</span>
|
<span class="section-subtitle">配置活动名称、券面额及发放规则</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,13 +97,6 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="活动类型" prop="getType">
|
|
||||||
<el-select :disabled="disabled" v-model="form.getType" style="width: 240px">
|
|
||||||
<el-option label="免费领取" value="FREE" />
|
|
||||||
<el-option label="活动赠送" value="ACTIVITY" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="店铺承担比例" prop="storeCommission">
|
<el-form-item label="店铺承担比例" prop="storeCommission">
|
||||||
<el-input
|
<el-input
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@@ -151,7 +134,7 @@
|
|||||||
<span class="form-describe">若设置为 0,则代表不限制每人领取数量</span>
|
<span class="form-describe">若设置为 0,则代表不限制每人领取数量</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="范围描述" prop="description">
|
<el-form-item label="范围描述" prop="description" class="form-item-wide">
|
||||||
<el-input
|
<el-input
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
@@ -169,12 +152,12 @@
|
|||||||
<!-- 模块二:使用限制 -->
|
<!-- 模块二:使用限制 -->
|
||||||
<div class="form-section mt_10">
|
<div class="form-section mt_10">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span class="section-indicator"></span>
|
<span class="section-number">02</span>
|
||||||
<span class="section-title">使用限制</span>
|
<span class="section-title">使用限制</span>
|
||||||
<span class="section-subtitle">配置消费门槛、有效时间以及指定适用商品范围</span>
|
<span class="section-subtitle">配置消费门槛、有效时间以及指定适用商品范围</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-body">
|
<div class="section-body usage-fields">
|
||||||
<el-form-item label="消费门槛" prop="consumeThreshold">
|
<el-form-item label="消费门槛" prop="consumeThreshold">
|
||||||
<el-input
|
<el-input
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@@ -189,37 +172,41 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="有效期" prop="rangeTime">
|
<el-form-item label="有效期" prop="rangeTime">
|
||||||
<div v-if="form.getType == 'ACTIVITY'" class="mb_10">
|
<div class="validity-field">
|
||||||
<el-radio-group v-model="rangeTimeType">
|
<div v-if="form.getType == 'ACTIVITY'" class="validity-type">
|
||||||
<el-radio :disabled="disabled" :value="1" v-if="form.getType !== 'ACTIVITY'">起止时间</el-radio>
|
<el-radio-group v-model="rangeTimeType">
|
||||||
<el-radio :disabled="disabled" :value="0">固定有效天数</el-radio>
|
<el-radio :disabled="disabled" :value="1" v-if="form.getType !== 'ACTIVITY'">起止时间</el-radio>
|
||||||
</el-radio-group>
|
<el-radio :disabled="disabled" :value="0">固定有效天数</el-radio>
|
||||||
</div>
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="rangeTimeType == 1">
|
<div v-if="rangeTimeType == 1">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
v-model="form.rangeTime"
|
v-model="form.rangeTime"
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始时间"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束时间"
|
||||||
:disabled-date="options.disabledDate"
|
range-separator="至"
|
||||||
style="width: 380px"
|
:disabled-date="options.disabledDate"
|
||||||
/>
|
style="width: 380px"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="effectiveDays-row" v-if="rangeTimeType == 0">
|
<div class="effectiveDays-row" v-if="rangeTimeType == 0">
|
||||||
<span class="days-label">领取当天开始</span>
|
<span class="days-label">领取当天开始</span>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-model="form.effectiveDays"
|
v-model="form.effectiveDays"
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="365"
|
:max="365"
|
||||||
style="width: 130px"
|
style="width: 130px"
|
||||||
/>
|
/>
|
||||||
<span class="days-unit">天内有效(1-365天的整数)</span>
|
<span class="days-unit">天内有效</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="rangeTimeType == 0" class="validity-help">有效天数为 1–365 天的整数</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -234,29 +221,36 @@
|
|||||||
<!-- 指定适用商品表格 -->
|
<!-- 指定适用商品表格 -->
|
||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="form.scopeType == 'PORTION_GOODS'"
|
v-if="form.scopeType == 'PORTION_GOODS'"
|
||||||
label="指定商品"
|
:label-width="0"
|
||||||
style="width: 100%"
|
class="portion-goods-form-item"
|
||||||
>
|
>
|
||||||
<div class="portion-goods-wrapper">
|
<div class="portion-goods-wrapper">
|
||||||
<div v-if="!isOnlyView" class="portion-goods-toolbar">
|
<div class="portion-goods-toolbar">
|
||||||
<el-button type="primary" @click="openSkuList">选择商品</el-button>
|
<div class="portion-goods-heading">
|
||||||
<el-button
|
<h3>指定商品</h3>
|
||||||
type="danger"
|
<span>已选 {{ form.promotionGoodsList.length }} 件商品</span>
|
||||||
plain
|
</div>
|
||||||
:disabled="selectedGoods.length === 0"
|
<div v-if="!isOnlyView && form.promotionGoodsList.length" class="goods-toolbar-actions">
|
||||||
@click="delSelectGoods"
|
<el-button
|
||||||
>
|
v-if="selectedGoods.length"
|
||||||
批量删除 ({{ selectedGoods.length }})
|
type="danger"
|
||||||
</el-button>
|
plain
|
||||||
|
@click="delSelectGoods"
|
||||||
|
>
|
||||||
|
批量删除 ({{ selectedGoods.length }})
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" :icon="Plus" @click="openSkuList">选择商品</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
border
|
v-if="form.promotionGoodsList.length"
|
||||||
stripe
|
|
||||||
:data="form.promotionGoodsList"
|
:data="form.promotionGoodsList"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
empty-text="暂无指定商品数据"
|
empty-text="暂无指定商品数据"
|
||||||
class="portion-goods-table"
|
class="portion-goods-table"
|
||||||
|
row-key="skuId"
|
||||||
|
scrollbar-always-on
|
||||||
@selection-change="changeSelect"
|
@selection-change="changeSelect"
|
||||||
>
|
>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -265,7 +259,7 @@
|
|||||||
width="50"
|
width="50"
|
||||||
align="center"
|
align="center"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="商品图片" width="76" align="center">
|
<el-table-column label="图片" width="76" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="row && row.thumbnail"
|
v-if="row && row.thumbnail"
|
||||||
@@ -285,9 +279,13 @@
|
|||||||
<el-table-column
|
<el-table-column
|
||||||
prop="goodsName"
|
prop="goodsName"
|
||||||
label="商品名称"
|
label="商品名称"
|
||||||
min-width="220"
|
min-width="280"
|
||||||
show-overflow-tooltip
|
>
|
||||||
/>
|
<template #default="{ row }">
|
||||||
|
<div class="selected-goods-name" :title="row.goodsName">{{ row.goodsName || '-' }}</div>
|
||||||
|
<div class="selected-goods-sku">SKU:{{ row.skuId }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="商品单价" width="130" align="right">
|
<el-table-column label="商品单价" width="130" align="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row" class="price-text font-mono font-semibold">
|
<span v-if="row" class="price-text font-mono font-semibold">
|
||||||
@@ -319,6 +317,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div v-else class="portion-goods-empty">
|
||||||
|
<div class="empty-icon"><el-icon :size="28"><ShoppingBag /></el-icon></div>
|
||||||
|
<h3>选择优惠券适用的商品</h3>
|
||||||
|
<p>{{ isOnlyView ? '暂无指定商品' : '仅选中的商品可使用该优惠券,可一次添加多个商品' }}</p>
|
||||||
|
<el-button v-if="!isOnlyView" type="primary" :icon="Plus" @click="openSkuList">选择商品</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -359,6 +363,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||||
|
import { ArrowLeft, Plus, ShoppingBag } from "@element-plus/icons-vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import {
|
import {
|
||||||
savePlatformCoupon,
|
savePlatformCoupon,
|
||||||
@@ -417,7 +422,7 @@ const form = ref({
|
|||||||
couponType: "PRICE",
|
couponType: "PRICE",
|
||||||
couponName: "",
|
couponName: "",
|
||||||
promotionName: "",
|
promotionName: "",
|
||||||
getType: "FREE",
|
getType: route.query.getType === "ACTIVITY" ? "ACTIVITY" : "FREE",
|
||||||
promotionGoodsList: [],
|
promotionGoodsList: [],
|
||||||
scopeIdGoods: [],
|
scopeIdGoods: [],
|
||||||
rangeDayType: "",
|
rangeDayType: "",
|
||||||
@@ -470,6 +475,7 @@ watch(
|
|||||||
(val) => {
|
(val) => {
|
||||||
if (val == "FREE") {
|
if (val == "FREE") {
|
||||||
rangeTimeType.value = 1;
|
rangeTimeType.value = 1;
|
||||||
|
formRule.rangeTime = [{ required: true, message: "请选择优惠券有效期" }];
|
||||||
} else {
|
} else {
|
||||||
rangeTimeType.value = 0;
|
rangeTimeType.value = 0;
|
||||||
}
|
}
|
||||||
@@ -477,7 +483,7 @@ watch(
|
|||||||
delete formRule.rangeTime;
|
delete formRule.rangeTime;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -488,6 +494,8 @@ watch(
|
|||||||
getCoupon();
|
getCoupon();
|
||||||
} else {
|
} else {
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
|
modalType.value = 0;
|
||||||
|
form.value.getType = e.getType === "ACTIVITY" ? "ACTIVITY" : "FREE";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -728,9 +736,14 @@ function filterCategoryId(list, idArr) {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.coupon-publish-page {
|
.coupon-publish-page {
|
||||||
padding-bottom: 20px;
|
max-width: 1640px;
|
||||||
|
padding: 12px 20px 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
.header-card {
|
.header-card {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
:deep(.el-card__body) { padding: 8px 0 4px; }
|
||||||
.page-header-row {
|
.page-header-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -741,8 +754,8 @@ function filterCategoryId(list, idArr) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
font-size: 16px;
|
font-size: 22px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
|
|
||||||
.mode-tag {
|
.mode-tag {
|
||||||
@@ -769,15 +782,19 @@ function filterCategoryId(list, idArr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-card {
|
.form-card {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
:deep(.el-card__body) { padding: 0; }
|
||||||
.custom-coupon-form {
|
.custom-coupon-form {
|
||||||
padding: 10px 10px 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
border-radius: 6px;
|
border-radius: 10px;
|
||||||
padding: 18px 20px;
|
padding: 24px;
|
||||||
background-color: #ffffff;
|
background-color: var(--el-bg-color);
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -787,16 +804,9 @@ function filterCategoryId(list, idArr) {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
border-bottom: 1px solid #f0f2f5;
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
|
||||||
.section-indicator {
|
|
||||||
width: 4px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,26 +818,55 @@ function filterCategoryId(list, idArr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-body {
|
.section-body {
|
||||||
max-width: 900px;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
column-gap: 24px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.section-body.usage-fields { display: block; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-describe {
|
.form-describe {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-left: 10px;
|
margin-top: 6px;
|
||||||
|
width: 100%;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.validity-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.validity-help {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.effectiveDays-row {
|
.effectiveDays-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
flex: 0 0 130px;
|
||||||
|
width: 130px !important;
|
||||||
|
max-width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
.days-label,
|
.days-label,
|
||||||
.days-unit {
|
.days-unit {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,8 +876,10 @@ function filterCategoryId(list, idArr) {
|
|||||||
.portion-goods-toolbar {
|
.portion-goods-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
justify-content: space-between;
|
||||||
margin-bottom: 10px;
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portion-goods-table {
|
.portion-goods-table {
|
||||||
@@ -867,9 +908,12 @@ function filterCategoryId(list, idArr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-card {
|
.footer-card {
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
.footer-actions {
|
.footer-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
@@ -882,5 +926,38 @@ function filterCategoryId(list, idArr) {
|
|||||||
.font-semibold {
|
.font-semibold {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.page-description { margin: 8px 0 0; color: var(--el-text-color-secondary); font-size: 13px; }
|
||||||
|
.section-number { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 28px; height: 28px; border-radius: 7px; background: var(--el-color-primary-light-9); color: var(--el-color-primary); font-size: 12px; font-weight: 500; }
|
||||||
|
.section-header { flex-wrap: wrap; }
|
||||||
|
.section-body > .el-form-item { min-width: 0; margin-bottom: 22px; align-self: start; }
|
||||||
|
.form-item-wide { grid-column: 1 / -1; }
|
||||||
|
:deep(.el-form-item__content) { min-width: 0; }
|
||||||
|
.section-body :deep(.el-input), .section-body :deep(.el-select), .section-body :deep(.el-input-number), .section-body :deep(.el-cascader) { width: 100% !important; max-width: 360px; }
|
||||||
|
.section-body :deep(.el-textarea) { width: 100% !important; max-width: 720px; }
|
||||||
|
.usage-fields :deep(.el-date-editor.el-date-editor--datetimerange) { width: 520px !important; max-width: 100%; }
|
||||||
|
|
||||||
|
.portion-goods-form-item :deep(.el-form-item__content) { margin-left: 0 !important; }
|
||||||
|
.portion-goods-heading, .goods-toolbar-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
|
||||||
|
.portion-goods-heading h3 { margin: 0; font-size: 15px; font-weight: 500; }
|
||||||
|
.portion-goods-heading span { color: var(--el-text-color-secondary); font-size: 13px; }
|
||||||
|
.goods-toolbar-actions :deep(.el-button + .el-button), .footer-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||||
|
.selected-goods-name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.6; }
|
||||||
|
.selected-goods-sku { margin-top: 4px; color: var(--el-text-color-secondary); font: 12px/1.5 Consolas, monospace; overflow-wrap: anywhere; }
|
||||||
|
.portion-goods-table :deep(.el-table__cell) { padding: 12px 0; }
|
||||||
|
.portion-goods-table :deep(th.el-table__cell) { background: var(--el-fill-color-light); font-weight: 400; }
|
||||||
|
.portion-goods-empty { display: flex; align-items: center; justify-content: center; flex-direction: column; min-height: 230px; padding: 24px 20px; box-sizing: border-box; border: 1px dashed var(--el-border-color); border-radius: 8px; background: var(--el-fill-color-lighter); text-align: center; }
|
||||||
|
.portion-goods-empty h3 { margin: 0; font-size: 16px; font-weight: 500; }
|
||||||
|
.portion-goods-empty p { margin: 8px 0 20px; font-size: 13px; color: var(--el-text-color-secondary); }
|
||||||
|
.empty-icon { display: grid; place-items: center; width: 60px; height: 60px; margin-bottom: 16px; border-radius: 16px; color: var(--el-color-primary); background: var(--el-color-primary-light-9); }
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.form-card .form-section .section-body { grid-template-columns: minmax(0, 1fr); }
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 8px 8px 20px;
|
||||||
|
.page-header-row { flex-wrap: wrap; gap: 16px; }
|
||||||
|
.form-card .form-section { padding: 16px; }
|
||||||
|
.section-subtitle { width: 100%; }
|
||||||
|
.effectiveDays-row { flex-wrap: wrap; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -72,18 +72,18 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card>
|
<el-card>
|
||||||
<div class="operation padding-row" v-if="getType !== 'ACTIVITY'">
|
<list-action-bar v-if="getType !== 'ACTIVITY'">
|
||||||
<el-button type="primary" @click="add">添加优惠券</el-button>
|
<el-button type="primary" @click="add">添加优惠券</el-button>
|
||||||
|
<el-button class="activity-coupon-button" @click="addActivityCoupon">添加活动优惠券</el-button>
|
||||||
<el-button @click="delAll">批量关闭</el-button>
|
<el-button @click="delAll">批量关闭</el-button>
|
||||||
<el-button type="warning" @click="receivePage()">优惠券领取记录</el-button>
|
<el-button type="warning" @click="receivePage()">优惠券领取记录</el-button>
|
||||||
</div>
|
</list-action-bar>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
ref="table"
|
ref="table"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
border
|
border
|
||||||
:data="data"
|
:data="data"
|
||||||
class="mt_10"
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
@selection-change="changeSelect"
|
@selection-change="changeSelect"
|
||||||
@@ -201,6 +201,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ListActionBar from "@/components/list-action-bar.vue";
|
||||||
import { ref, computed, watch, nextTick, onMounted } from "vue";
|
import { ref, computed, watch, nextTick, onMounted } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import {
|
import {
|
||||||
@@ -423,6 +424,10 @@ function init() {
|
|||||||
function add() {
|
function add() {
|
||||||
router.push({ name: "add-platform-coupon" });
|
router.push({ name: "add-platform-coupon" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addActivityCoupon() {
|
||||||
|
router.push({ name: "add-platform-coupon", query: { getType: "ACTIVITY" } });
|
||||||
|
}
|
||||||
function changePage(v) {
|
function changePage(v) {
|
||||||
searchForm.value.pageNumber = v;
|
searchForm.value.pageNumber = v;
|
||||||
getDataList();
|
getDataList();
|
||||||
@@ -561,6 +566,19 @@ defineExpose({ check, getSelection, clearSelectAll });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.activity-coupon-button {
|
||||||
|
--el-button-text-color: #b35c12;
|
||||||
|
--el-button-bg-color: #fff7ed;
|
||||||
|
--el-button-border-color: #fed7aa;
|
||||||
|
--el-button-hover-text-color: #9a4b0b;
|
||||||
|
--el-button-hover-bg-color: #ffedd5;
|
||||||
|
--el-button-hover-border-color: #fdba74;
|
||||||
|
--el-button-active-text-color: #9a4b0b;
|
||||||
|
--el-button-active-bg-color: #fed7aa;
|
||||||
|
--el-button-active-border-color: #fb923c;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.link-text {
|
.link-text {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -580,9 +598,6 @@ defineExpose({ check, getSelection, clearSelectAll });
|
|||||||
.mt_10 {
|
.mt_10 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
.padding-row {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-card>
|
|
||||||
<el-form inline @keyup.enter="load">
|
|
||||||
<el-form-item label="活动名称"><el-input v-model="query.promotionName" clearable /></el-form-item>
|
|
||||||
<el-form-item><el-button type="primary" @click="load">搜索</el-button></el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<el-card class="mt_10">
|
|
||||||
<el-table v-loading="loading" :data="rows" border>
|
|
||||||
<el-table-column prop="promotionName" label="活动名称" />
|
|
||||||
<el-table-column prop="storeName" label="店铺" />
|
|
||||||
<el-table-column prop="startTime" label="开始" width="170" />
|
|
||||||
<el-table-column prop="endTime" label="结束" width="170" />
|
|
||||||
<el-table-column prop="promotionStatus" label="状态" width="100" />
|
|
||||||
</el-table>
|
|
||||||
<el-pagination class="mt_10" v-model:current-page="query.pageNumber" :total="total" @current-change="load" />
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { useTable } from "@/utils/useTable";
|
|
||||||
import { getFlashDiscountList } from "@/api/promotion.js";
|
|
||||||
|
|
||||||
const { loading, data: rows, total, searchForm: query, getDataList: load } = useTable(
|
|
||||||
getFlashDiscountList,
|
|
||||||
{ pageSize: 20, form: { promotionName: "" } }
|
|
||||||
);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -7,21 +7,13 @@
|
|||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<span>{{ pageTitle }}</span>
|
<span>{{ pageTitle }}</span>
|
||||||
<el-tag
|
<el-tag
|
||||||
|
v-if="onlyView || isEditMode"
|
||||||
size="small"
|
size="small"
|
||||||
effect="plain"
|
effect="plain"
|
||||||
:type="onlyView ? 'info' : (isEditMode ? 'warning' : 'primary')"
|
:type="onlyView ? 'info' : (isEditMode ? 'warning' : 'primary')"
|
||||||
class="mode-tag"
|
class="mode-tag"
|
||||||
>
|
>
|
||||||
{{ onlyView ? "查看模式" : (isEditMode ? "编辑模式" : "新建模式") }}
|
{{ onlyView ? "查看模式" : "编辑模式" }}
|
||||||
</el-tag>
|
|
||||||
<el-tag
|
|
||||||
v-if="form.cardType"
|
|
||||||
size="small"
|
|
||||||
effect="dark"
|
|
||||||
type="primary"
|
|
||||||
class="type-tag"
|
|
||||||
>
|
|
||||||
{{ form.cardType === "CASH" ? "现金卡" : "提货卡" }}
|
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<span v-if="route.query.id" class="activity-id font-mono">
|
<span v-if="route.query.id" class="activity-id font-mono">
|
||||||
ID: {{ route.query.id }}
|
ID: {{ route.query.id }}
|
||||||
@@ -499,8 +491,7 @@ onMounted(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1f2d3d;
|
color: #1f2d3d;
|
||||||
|
|
||||||
.mode-tag,
|
.mode-tag {
|
||||||
.type-tag {
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
<div class="page-header-row">
|
<div class="page-header-row">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<span>添加砍价活动商品</span>
|
<span>添加砍价活动</span>
|
||||||
<el-tag size="small" effect="plain" type="primary" class="mode-tag">
|
|
||||||
新增活动
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="page-description">设置活动时间,选择参与商品并配置砍价规则。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<el-button @click="closeCurrentPage">返回砍价列表</el-button>
|
<el-button :icon="ArrowLeft" @click="closeCurrentPage">返回砍价列表</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -27,7 +25,7 @@
|
|||||||
<!-- 活动时间设置卡片 -->
|
<!-- 活动时间设置卡片 -->
|
||||||
<el-card shadow="never" class="info-card mt_10">
|
<el-card shadow="never" class="info-card mt_10">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span class="section-indicator"></span>
|
<span class="section-number">01</span>
|
||||||
<span class="section-title">活动时间设置</span>
|
<span class="section-title">活动时间设置</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -45,9 +43,10 @@
|
|||||||
end-placeholder="结束时间"
|
end-placeholder="结束时间"
|
||||||
placeholder="请选择砍价活动有效时间范围"
|
placeholder="请选择砍价活动有效时间范围"
|
||||||
:disabled-date="options.disabledDate"
|
:disabled-date="options.disabledDate"
|
||||||
style="width: 380px"
|
class="activity-date-picker"
|
||||||
|
range-separator="至"
|
||||||
/>
|
/>
|
||||||
<span class="time-tip-text">请设置砍价活动开展的时间段</span>
|
<span class="time-tip-text">所有参与商品将使用同一活动时间</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
<el-card shadow="never" class="info-card mt_10">
|
<el-card shadow="never" class="info-card mt_10">
|
||||||
<div class="card-header-bar">
|
<div class="card-header-bar">
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="section-indicator"></span>
|
<span class="section-number">02</span>
|
||||||
<span class="section-title">砍价活动商品</span>
|
<span class="section-title">砍价活动商品</span>
|
||||||
<span class="total-badge">已选 {{ form.promotionGoodsList.length }} 件商品</span>
|
<span class="total-badge">已选 {{ form.promotionGoodsList.length }} 件商品</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +77,7 @@
|
|||||||
>
|
>
|
||||||
清空商品
|
清空商品
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" size="small" @click="openSkuList">
|
<el-button v-if="form.promotionGoodsList.length" type="primary" :icon="Plus" @click="openSkuList">
|
||||||
选择商品
|
选择商品
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,8 +85,8 @@
|
|||||||
|
|
||||||
<el-form-item prop="promotionGoodsList" :label-width="0" class="table-form-item">
|
<el-form-item prop="promotionGoodsList" :label-width="0" class="table-form-item">
|
||||||
<el-table
|
<el-table
|
||||||
border
|
v-if="form.promotionGoodsList.length"
|
||||||
stripe
|
scrollbar-always-on
|
||||||
class="kanjia-goods-table"
|
class="kanjia-goods-table"
|
||||||
:data="form.promotionGoodsList"
|
:data="form.promotionGoodsList"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@@ -97,8 +96,9 @@
|
|||||||
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column type="selection" width="50" align="center" />
|
||||||
|
|
||||||
<!-- 商品图片 -->
|
<!-- 商品图片 -->
|
||||||
<el-table-column label="商品图片" width="76" align="center">
|
<el-table-column label="商品信息" min-width="290">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<div class="product-info">
|
||||||
<div v-if="row && row.thumbnail" class="goods-thumb-box">
|
<div v-if="row && row.thumbnail" class="goods-thumb-box">
|
||||||
<el-image
|
<el-image
|
||||||
:src="row.thumbnail"
|
:src="row.thumbnail"
|
||||||
@@ -113,23 +113,13 @@
|
|||||||
</el-image>
|
</el-image>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="goods-thumb-box thumb-slot">无图</div>
|
<div v-else class="goods-thumb-box thumb-slot">无图</div>
|
||||||
</template>
|
<div v-if="row" class="product-details">
|
||||||
</el-table-column>
|
<div class="product-name text-dark" :title="row.goodsName">{{ row.goodsName || "-" }}</div>
|
||||||
|
|
||||||
<!-- 商品名称与SKU -->
|
|
||||||
<el-table-column
|
|
||||||
prop="goodsName"
|
|
||||||
label="商品名称"
|
|
||||||
min-width="180"
|
|
||||||
show-overflow-tooltip
|
|
||||||
>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div v-if="row">
|
|
||||||
<div class="font-semibold text-dark">{{ row.goodsName || "-" }}</div>
|
|
||||||
<div v-if="row.skuId" class="sku-sub-text font-mono">
|
<div v-if="row.skuId" class="sku-sub-text font-mono">
|
||||||
SKU: {{ row.skuId }}
|
SKU: {{ row.skuId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
@@ -168,7 +158,7 @@
|
|||||||
v-model="form.promotionGoodsList[$index].settlementPrice"
|
v-model="form.promotionGoodsList[$index].settlementPrice"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
controls-position="right"
|
:controls="false"
|
||||||
class="kanjia-goods-input"
|
class="kanjia-goods-input"
|
||||||
placeholder="结算价"
|
placeholder="结算价"
|
||||||
/>
|
/>
|
||||||
@@ -182,7 +172,7 @@
|
|||||||
v-model="form.promotionGoodsList[$index].lowestPrice"
|
v-model="form.promotionGoodsList[$index].lowestPrice"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
controls-position="right"
|
:controls="false"
|
||||||
class="kanjia-goods-input"
|
class="kanjia-goods-input"
|
||||||
placeholder="最低可砍"
|
placeholder="最低可砍"
|
||||||
/>
|
/>
|
||||||
@@ -196,7 +186,7 @@
|
|||||||
v-model="form.promotionGoodsList[$index].highestPrice"
|
v-model="form.promotionGoodsList[$index].highestPrice"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
controls-position="right"
|
:controls="false"
|
||||||
class="kanjia-goods-input"
|
class="kanjia-goods-input"
|
||||||
placeholder="最高可砍"
|
placeholder="最高可砍"
|
||||||
/>
|
/>
|
||||||
@@ -210,7 +200,7 @@
|
|||||||
v-model="form.promotionGoodsList[$index].stock"
|
v-model="form.promotionGoodsList[$index].stock"
|
||||||
:min="1"
|
:min="1"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
controls-position="right"
|
:controls="false"
|
||||||
class="kanjia-goods-input"
|
class="kanjia-goods-input"
|
||||||
placeholder="活动库存"
|
placeholder="活动库存"
|
||||||
/>
|
/>
|
||||||
@@ -230,12 +220,20 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div v-else class="goods-empty">
|
||||||
|
<div class="empty-icon"><el-icon :size="28"><ShoppingBag /></el-icon></div>
|
||||||
|
<h3>选择参与砍价的商品</h3>
|
||||||
|
<p>添加商品后,可逐项设置结算价格、可砍金额和活动库存</p>
|
||||||
|
<el-button type="primary" :icon="Plus" @click="openSkuList">选择活动商品</el-button>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
<el-card shadow="never" class="footer-card mt_10">
|
<el-card shadow="never" class="footer-card mt_10">
|
||||||
<div class="footer-actions">
|
<div class="footer-actions">
|
||||||
|
<span class="footer-summary">已选 <strong>{{ form.promotionGoodsList.length }}</strong> 件商品</span>
|
||||||
|
<div class="footer-buttons">
|
||||||
<el-button @click="closeCurrentPage">取消</el-button>
|
<el-button @click="closeCurrentPage">取消</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -244,6 +242,7 @@
|
|||||||
>
|
>
|
||||||
提交创建
|
提交创建
|
||||||
</el-button>
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -254,6 +253,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { nextTick, reactive, ref } from "vue";
|
import { nextTick, reactive, ref } from "vue";
|
||||||
|
import { ArrowLeft, Plus, ShoppingBag } from "@element-plus/icons-vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { saveKanJiaActivityGoods } from "@/api/promotion";
|
import { saveKanJiaActivityGoods } from "@/api/promotion";
|
||||||
import { regular } from "@/utils";
|
import { regular } from "@/utils";
|
||||||
@@ -268,9 +268,16 @@ defineOptions({ name: "add-kanjia-activity-goods" });
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
function getDefaultActivityTime() {
|
||||||
|
const start = new Date();
|
||||||
|
const end = new Date(start);
|
||||||
|
end.setDate(end.getDate() + 7);
|
||||||
|
return [unixToDate(start.getTime() / 1000), unixToDate(end.getTime() / 1000)];
|
||||||
|
}
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
promotionGoodsList: [],
|
promotionGoodsList: [],
|
||||||
rangeTime: [],
|
rangeTime: getDefaultActivityTime(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
@@ -485,13 +492,13 @@ function selectedGoodsData(item) {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.kanjia-add-page {
|
.kanjia-add-page {
|
||||||
padding: 16px 20px;
|
padding: 12px 20px 24px;
|
||||||
background-color: #f0f2f5;
|
max-width: 1640px;
|
||||||
min-height: 100vh;
|
margin: 0 auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.mt_10 {
|
.mt_10 {
|
||||||
margin-top: 12px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-mono {
|
.font-mono {
|
||||||
@@ -513,9 +520,10 @@ function selectedGoodsData(item) {
|
|||||||
.header-card {
|
.header-card {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
padding: 16px 20px;
|
padding: 8px 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header-row {
|
.page-header-row {
|
||||||
@@ -528,8 +536,8 @@ function selectedGoodsData(item) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
font-size: 18px;
|
font-size: 22px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: #1f2d3d;
|
color: #1f2d3d;
|
||||||
|
|
||||||
.mode-tag {
|
.mode-tag {
|
||||||
@@ -551,11 +559,11 @@ function selectedGoodsData(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-card {
|
.info-card {
|
||||||
border: none;
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
padding: 20px;
|
padding: 20px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
@@ -573,7 +581,7 @@ function selectedGoodsData(item) {
|
|||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -581,7 +589,8 @@ function selectedGoodsData(item) {
|
|||||||
.time-tip-text {
|
.time-tip-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
margin-left: 12px;
|
margin-top: 6px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header-bar {
|
.card-header-bar {
|
||||||
@@ -604,7 +613,7 @@ function selectedGoodsData(item) {
|
|||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,22 +633,24 @@ function selectedGoodsData(item) {
|
|||||||
|
|
||||||
.kanjia-goods-table {
|
.kanjia-goods-table {
|
||||||
:deep(th.el-table__cell) {
|
:deep(th.el-table__cell) {
|
||||||
background-color: #fafafa;
|
background-color: var(--el-fill-color-light);
|
||||||
color: #303133;
|
color: var(--el-text-color-secondary);
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-thumb-box {
|
.goods-thumb-box {
|
||||||
width: 44px;
|
width: 48px;
|
||||||
height: 44px;
|
height: 48px;
|
||||||
border-radius: 4px;
|
flex: 0 0 48px;
|
||||||
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
margin: 0 auto;
|
margin: 0;
|
||||||
|
|
||||||
.goods-thumb-img {
|
.goods-thumb-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -669,8 +680,9 @@ function selectedGoodsData(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-card {
|
.footer-card {
|
||||||
border: none;
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 18px rgb(31 45 61 / 5%);
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
@@ -678,9 +690,37 @@ function selectedGoodsData(item) {
|
|||||||
|
|
||||||
.footer-actions {
|
.footer-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-description { margin: 8px 0 0; color: var(--el-text-color-secondary); font-size: 13px; }
|
||||||
|
.section-number { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 28px; height: 28px; border-radius: 7px; background: var(--el-color-primary-light-9); color: var(--el-color-primary); font-size: 12px; font-weight: 500; }
|
||||||
|
.kanjia-activity-time-form-item { margin: 16px 0 0 36px; }
|
||||||
|
.kanjia-activity-time-form-item :deep(.el-form-item__content) { min-width: 0; }
|
||||||
|
:deep(.activity-date-picker.el-date-editor) { flex: 0 1 520px; width: 520px; max-width: 100%; }
|
||||||
|
.goods-empty { width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 230px; border: 1px dashed var(--el-border-color); border-radius: 8px; background: var(--el-fill-color-lighter); padding: 24px 20px; text-align: center; }
|
||||||
|
.empty-icon { display: grid; place-items: center; width: 60px; height: 60px; border-radius: 16px; color: var(--el-color-primary); background: var(--el-color-primary-light-9); margin-bottom: 16px; }
|
||||||
|
.goods-empty h3 { margin: 0; font-size: 16px; font-weight: 500; color: var(--el-text-color-primary); }
|
||||||
|
.goods-empty p { margin: 8px 0 20px; font-size: 13px; color: var(--el-text-color-secondary); line-height: 1.6; }
|
||||||
|
.product-info { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
|
||||||
|
.product-details { min-width: 0; }
|
||||||
|
.product-name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.6; }
|
||||||
|
.sku-sub-text { overflow-wrap: anywhere; }
|
||||||
|
.footer-summary { font-size: 13px; color: var(--el-text-color-secondary); }
|
||||||
|
.footer-summary strong { color: var(--el-color-primary); font-weight: 500; padding: 0 4px; }
|
||||||
|
.footer-buttons { display: flex; gap: 12px; }
|
||||||
|
.footer-buttons :deep(.el-button + .el-button) { margin-left: 0; }
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 8px 8px 20px;
|
||||||
|
.page-header-row, .card-header-bar { flex-wrap: wrap; gap: 16px; }
|
||||||
|
.info-card :deep(.el-card__body) { padding: 16px; }
|
||||||
|
.kanjia-activity-time-form-item { margin-left: 0; }
|
||||||
|
.kanjia-activity-time-form-item :deep(.el-form-item__label) { width: 80px !important; }
|
||||||
|
.header-title-group { flex-wrap: wrap; }
|
||||||
|
.footer-card :deep(.el-card__body) { padding: 14px 16px; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-card>
|
|
||||||
<el-form inline @keyup.enter="load">
|
|
||||||
<el-form-item label="活动名称"><el-input v-model="query.promotionName" clearable /></el-form-item>
|
|
||||||
<el-form-item><el-button type="primary" @click="load">搜索</el-button></el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<el-card class="mt_10">
|
|
||||||
<el-table v-loading="loading" :data="rows" border>
|
|
||||||
<el-table-column prop="promotionName" label="活动名称" />
|
|
||||||
<el-table-column prop="storeName" label="店铺" />
|
|
||||||
<el-table-column prop="nthNum" label="第N件" width="80" />
|
|
||||||
<el-table-column prop="discountType" label="优惠方式" width="100" />
|
|
||||||
<el-table-column prop="startTime" label="开始" width="170" />
|
|
||||||
<el-table-column prop="endTime" label="结束" width="170" />
|
|
||||||
<el-table-column prop="promotionStatus" label="状态" width="100" />
|
|
||||||
</el-table>
|
|
||||||
<el-pagination class="mt_10" v-model:current-page="query.pageNumber" :total="total" @current-change="load" />
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { useTable } from "@/utils/useTable";
|
|
||||||
import { getNthItemDiscountList } from "@/api/promotion.js";
|
|
||||||
|
|
||||||
const { loading, data: rows, total, searchForm: query, getDataList: load } = useTable(
|
|
||||||
getNthItemDiscountList,
|
|
||||||
{ pageSize: 20, form: { promotionName: "" } }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
@@ -1,126 +1,163 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<div class="points-goods-add-page">
|
||||||
<el-card>
|
<div class="page-header">
|
||||||
<el-form ref="formRef" :model="form" :rules="formRule" label-width="120px">
|
<div>
|
||||||
<div class="base-info-item">
|
<h1>添加积分商品</h1>
|
||||||
<div class="form-item-view">
|
<p>设置兑换时间,选择商品并配置兑换积分、价格与库存。</p>
|
||||||
<el-form-item style="width: 100%">
|
</div>
|
||||||
<div style="display: flex; margin-bottom: 10px">
|
<el-button :icon="ArrowLeft" @click="closeCurrentPage">返回积分商品列表</el-button>
|
||||||
<el-button type="primary" @click="openSkuList">选择商品</el-button>
|
</div>
|
||||||
<el-button
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
style="margin-left: 10px"
|
|
||||||
@click="delSelectGoods"
|
|
||||||
>批量删除</el-button>
|
|
||||||
</div>
|
|
||||||
<el-table
|
|
||||||
border
|
|
||||||
v-if="showTable"
|
|
||||||
:data="promotionGoodsList"
|
|
||||||
style="width: 100%"
|
|
||||||
@selection-change="changeSelect"
|
|
||||||
>
|
|
||||||
<el-table-column type="selection" width="60" align="center" />
|
|
||||||
<el-table-column prop="goodsName" label="商品名称" min-width="120" show-overflow-tooltip />
|
|
||||||
<el-table-column label="类型" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row">{{ goodsTypeLabel(row.goodsType) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="SKU编码" min-width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row">{{ row.skuId }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="storeName" label="店铺名称" min-width="100" show-overflow-tooltip />
|
|
||||||
<el-table-column label="商品价格" width="110">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row" :style="{ color: $mainColor }">
|
|
||||||
{{ $filters.unitPrice(row.price, "¥") }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="库存" width="90">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row">{{ row.quantity }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="结算价格" min-width="130">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-input-number
|
|
||||||
:min="0"
|
|
||||||
v-model="promotionGoodsList[$index].settlementPrice"
|
|
||||||
style="width: 110px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="分类" min-width="160">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-select
|
|
||||||
v-model="promotionGoodsList[$index].pointsGoodsCategoryId"
|
|
||||||
@change="(val) => changeCategory(val, $index)"
|
|
||||||
style="width: 140px"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in categoryList"
|
|
||||||
:value="item.id"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="活动库存" min-width="130">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-input-number
|
|
||||||
:min="0"
|
|
||||||
v-model="promotionGoodsList[$index].activeStock"
|
|
||||||
style="width: 110px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="兑换积分" min-width="130">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-input-number
|
|
||||||
:min="0"
|
|
||||||
v-model="promotionGoodsList[$index].points"
|
|
||||||
style="width: 110px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="100" align="center">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-button type="danger" size="small" plain @click="delGoods($index)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="活动时间" prop="rangeTime" class="points-goods-time-form-item">
|
<el-form ref="formRef" :model="form" :rules="formRule" label-width="90px">
|
||||||
<el-date-picker
|
<el-card shadow="never" class="section-card">
|
||||||
type="datetimerange"
|
<div class="section-heading">
|
||||||
v-model="form.rangeTime"
|
<span class="section-number">01</span>
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
<h2>活动时间设置</h2>
|
||||||
start-placeholder="开始时间"
|
</div>
|
||||||
end-placeholder="结束时间"
|
<el-form-item label="活动时间" prop="rangeTime" class="points-goods-time-form-item">
|
||||||
placeholder="请选择"
|
<el-date-picker
|
||||||
:disabled-date="options.disabledDate"
|
type="datetimerange"
|
||||||
/>
|
v-model="form.rangeTime"
|
||||||
</el-form-item>
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
<div>
|
start-placeholder="开始时间"
|
||||||
<el-button link @click="closeCurrentPage">返回</el-button>
|
end-placeholder="结束时间"
|
||||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交</el-button>
|
range-separator="至"
|
||||||
</div>
|
:disabled-date="options.disabledDate"
|
||||||
|
/>
|
||||||
|
<span class="field-hint">所有参与商品将使用同一兑换时间</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never" class="section-card">
|
||||||
|
<div class="goods-section-heading">
|
||||||
|
<div class="section-heading">
|
||||||
|
<span class="section-number">02</span>
|
||||||
|
<h2>积分商品</h2>
|
||||||
|
<span class="selected-count">已选 {{ promotionGoodsList.length }} 件商品</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="promotionGoodsList.length" class="goods-actions">
|
||||||
|
<el-button v-if="selectedGoods.length" type="danger" plain @click="delSelectGoods">批量删除({{ selectedGoods.length }})</el-button>
|
||||||
|
<el-button type="primary" :icon="Plus" @click="openSkuList">选择商品</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
<el-table
|
||||||
</el-card>
|
v-if="promotionGoodsList.length"
|
||||||
|
:data="promotionGoodsList"
|
||||||
|
class="points-goods-table"
|
||||||
|
style="width: 100%"
|
||||||
|
row-key="skuId"
|
||||||
|
scrollbar-always-on
|
||||||
|
@selection-change="changeSelect"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="48" align="center" />
|
||||||
|
<el-table-column label="商品信息" min-width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="product-info">
|
||||||
|
<el-image :src="row.thumbnail" fit="cover" class="product-image" preview-teleported :preview-src-list="row.thumbnail ? [row.thumbnail] : []">
|
||||||
|
<template #error><span class="image-placeholder">无图</span></template>
|
||||||
|
</el-image>
|
||||||
|
<div class="product-details">
|
||||||
|
<div class="product-name" :title="row.goodsName">{{ row.goodsName || '-' }}</div>
|
||||||
|
<div class="product-code">SKU:{{ row.skuId }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="类型" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row">{{ goodsTypeLabel(row.goodsType) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="storeName" label="所属店铺" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="商品原价" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row" :style="{ color: $mainColor }">
|
||||||
|
{{ $filters.unitPrice(row.price, "¥") }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="可用库存" width="90" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row">{{ row.quantity }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="结算价格(元)" width="140" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:controls="false"
|
||||||
|
v-model="promotionGoodsList[$index].settlementPrice"
|
||||||
|
class="goods-number-input"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="积分分类" width="160">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-select
|
||||||
|
v-model="promotionGoodsList[$index].pointsGoodsCategoryId"
|
||||||
|
@change="(val) => changeCategory(val, $index)"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择分类"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in categoryList"
|
||||||
|
:value="item.id"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="活动库存(件)" width="130" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:controls="false"
|
||||||
|
v-model="promotionGoodsList[$index].activeStock"
|
||||||
|
class="goods-number-input"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="兑换积分" width="130" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:controls="false"
|
||||||
|
v-model="promotionGoodsList[$index].points"
|
||||||
|
class="goods-number-input"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-button type="danger" link @click="delGoods($index)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div v-else class="goods-empty">
|
||||||
|
<div class="empty-icon"><el-icon :size="28"><ShoppingBag /></el-icon></div>
|
||||||
|
<h3>选择参与积分兑换的商品</h3>
|
||||||
|
<p>添加商品后,可逐项设置积分分类、兑换积分和活动库存</p>
|
||||||
|
<el-button type="primary" :icon="Plus" @click="openSkuList">选择活动商品</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never" class="footer-card">
|
||||||
|
<div class="footer-content">
|
||||||
|
<span class="selected-count">已选 <strong>{{ promotionGoodsList.length }}</strong> 件商品</span>
|
||||||
|
<div class="footer-buttons">
|
||||||
|
<el-button @click="closeCurrentPage">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交创建</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
<sku-select ref="skuSelectRef" @selectedGoodsData="selectedGoodsData"></sku-select>
|
<sku-select ref="skuSelectRef" @selectedGoodsData="selectedGoodsData"></sku-select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, onMounted } from "vue";
|
import { reactive, ref, onMounted } from "vue";
|
||||||
|
import { ArrowLeft, Plus, ShoppingBag } from "@element-plus/icons-vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { addPointsGoods, getPointsGoodsCategoryList } from "@/api/promotion";
|
import { addPointsGoods, getPointsGoodsCategoryList } from "@/api/promotion";
|
||||||
import skuSelect from "@/components/lili-dialog";
|
import skuSelect from "@/components/lili-dialog";
|
||||||
@@ -141,7 +178,6 @@ const form = reactive({
|
|||||||
rangeTime: null,
|
rangeTime: null,
|
||||||
promotionGoodsList: [],
|
promotionGoodsList: [],
|
||||||
});
|
});
|
||||||
const showTable = ref(true);
|
|
||||||
const promotionGoodsList = ref([]);
|
const promotionGoodsList = ref([]);
|
||||||
const categoryList = ref([]);
|
const categoryList = ref([]);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
@@ -185,6 +221,10 @@ function handleSubmit() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const row of params) {
|
for (const row of params) {
|
||||||
|
if (!row.pointsGoodsCategoryId) {
|
||||||
|
Message.error(`请为【${row.goodsName}】选择积分分类`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const stock = Number(row.quantity) || 0;
|
const stock = Number(row.quantity) || 0;
|
||||||
if (isECoupon(row.goodsType) && Number(row.activeStock) > stock) {
|
if (isECoupon(row.goodsType) && Number(row.activeStock) > stock) {
|
||||||
Message.error(`【${row.goodsName}】活动库存不能超过卡池库存(${stock})`);
|
Message.error(`【${row.goodsName}】活动库存不能超过卡池库存(${stock})`);
|
||||||
@@ -265,7 +305,7 @@ function selectedGoodsData(item) {
|
|||||||
item.forEach((e) => {
|
item.forEach((e) => {
|
||||||
const obj = {
|
const obj = {
|
||||||
settlementPrice: e.settlementPrice || e.price,
|
settlementPrice: e.settlementPrice || e.price,
|
||||||
pointsGoodsCategoryId: e.pointsGoodsCategoryId || 0,
|
pointsGoodsCategoryId: e.pointsGoodsCategoryId || "",
|
||||||
pointsGoodsCategoryName: e.pointsGoodsCategoryName || "",
|
pointsGoodsCategoryName: e.pointsGoodsCategoryName || "",
|
||||||
activeStock: e.activeStock || 1,
|
activeStock: e.activeStock || 1,
|
||||||
points: e.points || 1,
|
points: e.points || 1,
|
||||||
@@ -286,34 +326,59 @@ function selectedGoodsData(item) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
h4 {
|
.points-goods-add-page {
|
||||||
margin-bottom: 10px;
|
max-width: 1640px;
|
||||||
padding: 0 10px;
|
margin: 0 auto;
|
||||||
border: 1px solid #ddd;
|
padding: 12px 20px 24px;
|
||||||
background-color: #f8f8f8;
|
color: var(--el-text-color-primary);
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 40px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
.page-header, .goods-section-heading, .footer-content {
|
||||||
.describe {
|
display: flex;
|
||||||
font-size: 12px;
|
align-items: center;
|
||||||
margin-left: 10px;
|
justify-content: space-between;
|
||||||
color: #999;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
.page-header { padding: 8px 0 4px; margin-bottom: 16px; }
|
||||||
.wrapper {
|
.page-header h1 { margin: 0; font-size: 22px; font-weight: 500; }
|
||||||
min-height: 800px;
|
.page-header p { margin: 8px 0 0; font-size: 13px; color: var(--el-text-color-secondary); }
|
||||||
|
.section-card, .footer-card {
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
.section-card :deep(.el-card__body) { padding: 20px 24px; }
|
||||||
.points-goods-time-form-item {
|
.section-heading { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
||||||
:deep(.el-date-editor.el-date-editor--datetimerange) {
|
.section-heading h2 { margin: 0; font-size: 15px; font-weight: 500; }
|
||||||
width: 420px !important;
|
.section-number { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 28px; height: 28px; border-radius: 7px; background: var(--el-color-primary-light-9); color: var(--el-color-primary); font-size: 12px; font-weight: 500; }
|
||||||
min-width: 420px;
|
.points-goods-time-form-item { margin: 16px 0 0 36px; }
|
||||||
max-width: 420px;
|
.points-goods-time-form-item :deep(.el-form-item__content) { min-width: 0; }
|
||||||
flex-shrink: 0;
|
.points-goods-time-form-item :deep(.el-date-editor) { flex: 0 1 520px; width: 520px; max-width: 100%; }
|
||||||
}
|
.field-hint { width: 100%; margin-top: 6px; color: var(--el-text-color-secondary); font-size: 13px; }
|
||||||
|
.goods-section-heading { margin-bottom: 16px; flex-wrap: wrap; }
|
||||||
|
.selected-count { color: var(--el-text-color-secondary); font-size: 13px; }
|
||||||
|
.selected-count strong { padding: 0 4px; color: var(--el-color-primary); font-weight: 500; }
|
||||||
|
.goods-actions, .footer-buttons { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
|
||||||
|
.goods-actions :deep(.el-button + .el-button), .footer-buttons :deep(.el-button + .el-button) { margin-left: 0; }
|
||||||
|
.goods-empty { width: 100%; min-height: 230px; padding: 24px 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; background: var(--el-fill-color-lighter); border: 1px dashed var(--el-border-color); border-radius: 8px; box-sizing: border-box; }
|
||||||
|
.empty-icon { display: grid; place-items: center; width: 60px; height: 60px; border-radius: 16px; color: var(--el-color-primary); background: var(--el-color-primary-light-9); margin-bottom: 16px; }
|
||||||
|
.goods-empty h3 { margin: 0; font-size: 16px; font-weight: 500; }
|
||||||
|
.goods-empty p { margin: 8px 0 20px; font-size: 13px; line-height: 1.6; color: var(--el-text-color-secondary); }
|
||||||
|
.product-info { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
|
||||||
|
.product-image { width: 48px; height: 48px; flex: 0 0 48px; border: 1px solid var(--el-border-color-lighter); border-radius: 6px; }
|
||||||
|
.image-placeholder { display: grid; place-items: center; width: 100%; height: 100%; font-size: 12px; color: var(--el-text-color-placeholder); background: var(--el-fill-color-light); }
|
||||||
|
.product-details { min-width: 0; }
|
||||||
|
.product-name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.6; }
|
||||||
|
.product-code { margin-top: 4px; font-size: 12px; color: var(--el-text-color-secondary); font-family: Consolas, monospace; overflow-wrap: anywhere; }
|
||||||
|
.goods-number-input { width: 100%; }
|
||||||
|
.points-goods-table :deep(th.el-table__cell) { background: var(--el-fill-color-light); font-weight: 400; padding: 12px 0; }
|
||||||
|
.footer-card { box-shadow: 0 4px 18px rgb(31 45 61 / 5%); }
|
||||||
|
.footer-card :deep(.el-card__body) { padding: 16px 24px; }
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.points-goods-add-page { padding: 8px 8px 20px; }
|
||||||
|
.page-header { flex-wrap: wrap; }
|
||||||
|
.section-card :deep(.el-card__body) { padding: 16px; }
|
||||||
|
.points-goods-time-form-item { margin-left: 0; }
|
||||||
|
.points-goods-time-form-item :deep(.el-form-item__label) { width: 80px !important; }
|
||||||
|
.footer-card :deep(.el-card__body) { padding: 14px 16px; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -200,20 +200,7 @@ export const getCouponReceiveList = (params) => {
|
|||||||
return getRequest("/promotion/coupon/received", params);
|
return getRequest("/promotion/coupon/received", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== 限时直降 ==========
|
|
||||||
export const getFlashDiscountList = (params) => getRequest('/promotion/flashDiscount', params)
|
|
||||||
export const getFlashDiscountDetail = (id) => getRequest(`/promotion/flashDiscount/${id}`)
|
|
||||||
export const saveFlashDiscount = (params) => postRequest('/promotion/flashDiscount', params, { 'Content-type': 'application/json' })
|
|
||||||
export const editFlashDiscount = (params) => putRequest('/promotion/flashDiscount', params, { 'Content-type': 'application/json' })
|
|
||||||
export const deleteFlashDiscount = (id) => deleteRequest(`/promotion/flashDiscount/${id}`)
|
|
||||||
export const updateFlashDiscountStatus = (id, params) => putRequest(`/promotion/flashDiscount/status/${id}`, params)
|
|
||||||
|
|
||||||
// ========== 第N件优惠 ==========
|
|
||||||
export const getNthItemDiscountList = (params) => getRequest('/promotion/nthItemDiscount', params)
|
|
||||||
export const getNthItemDiscountDetail = (id) => getRequest(`/promotion/nthItemDiscount/${id}`)
|
|
||||||
export const saveNthItemDiscount = (params) => postRequest('/promotion/nthItemDiscount', params, { 'Content-type': 'application/json' })
|
|
||||||
export const editNthItemDiscount = (params) => putRequest('/promotion/nthItemDiscount', params, { 'Content-type': 'application/json' })
|
|
||||||
export const updateNthItemDiscountStatus = (id, params) => putRequest(`/promotion/nthItemDiscount/status/${id}`, params)
|
|
||||||
|
|
||||||
// 兼容平台优惠券命名(商家端实际调用店铺优惠券接口)
|
// 兼容平台优惠券命名(商家端实际调用店铺优惠券接口)
|
||||||
export const getPlatformCouponList = getShopCouponList
|
export const getPlatformCouponList = getShopCouponList
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import { useAppStore } from "@/stores/app";
|
|||||||
|
|
||||||
let util = {};
|
let util = {};
|
||||||
|
|
||||||
|
// 避免旧缓存中的已下线菜单重新注册路由。
|
||||||
|
const DEPRECATED_FRONT_ROUTES = new Set([
|
||||||
|
"promotion/flash-discount/flash-discount",
|
||||||
|
"promotion/nth-item-discount/nth-item-discount",
|
||||||
|
]);
|
||||||
|
|
||||||
/** 店铺主页一级菜单 */
|
/** 店铺主页一级菜单 */
|
||||||
export const HOME_NAV_NAME = "home";
|
export const HOME_NAV_NAME = "home";
|
||||||
export const HOME_NAV_ITEM = { name: HOME_NAV_NAME, title: "主页" };
|
export const HOME_NAV_ITEM = { name: HOME_NAV_NAME, title: "主页" };
|
||||||
@@ -489,6 +495,9 @@ util.initMenuData = function (data) {
|
|||||||
|
|
||||||
util.initRouterNode = function (routers, data) {
|
util.initRouterNode = function (routers, data) {
|
||||||
for (let item of data) {
|
for (let item of data) {
|
||||||
|
if (DEPRECATED_FRONT_ROUTES.has(item.frontRoute)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const menu = Object.assign({}, item);
|
const menu = Object.assign({}, item);
|
||||||
const hasChildren = item.children && item.children.length > 0;
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ export const otherRouter = {
|
|||||||
name: "goods-operation",
|
name: "goods-operation",
|
||||||
component: () => import("@/views/goods/goods-seller/goodsOperation.vue")
|
component: () => import("@/views/goods/goods-seller/goodsOperation.vue")
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "goods-scheduled-upper",
|
||||||
|
title: "定时上架",
|
||||||
|
name: "goods-scheduled-upper",
|
||||||
|
component: () => import("@/views/goods/scheduled-upper/index.vue")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "goods-operation-edit",
|
path: "goods-operation-edit",
|
||||||
title: "编辑商品",
|
title: "编辑商品",
|
||||||
@@ -196,18 +202,6 @@ export const otherRouter = {
|
|||||||
name: "full-discount-detail",
|
name: "full-discount-detail",
|
||||||
component: () => import("@/views/promotion/full-discount/full-discount-add.vue")
|
component: () => import("@/views/promotion/full-discount/full-discount-add.vue")
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "flash-discount-add",
|
|
||||||
title: "限时直降",
|
|
||||||
name: "flash-discount-add",
|
|
||||||
component: () => import("@/views/promotion/flash-discount/flash-discount-add.vue")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "nth-item-discount-add",
|
|
||||||
title: "第N件优惠",
|
|
||||||
name: "nth-item-discount-add",
|
|
||||||
component: () => import("@/views/promotion/nth-item-discount/nth-item-discount-add.vue")
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "export-order-deliver",
|
path: "export-order-deliver",
|
||||||
title: "发货",
|
title: "发货",
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card>
|
<el-card>
|
||||||
<div class="goods-tab">
|
<div v-if="!scheduledOnly" class="goods-tab">
|
||||||
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
v-for="(item, index) in goodsStatusWithCount"
|
v-for="(item, index) in goodsStatusWithCount"
|
||||||
@@ -169,6 +169,7 @@
|
|||||||
>{{ row.goodsName }}</a>
|
>{{ row.goodsName }}</a>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column v-if="currentStatus === 'SCHEDULED_UPPER'" prop="scheduledUpperTime" label="计划上架时间" width="190" />
|
||||||
<el-table-column label="价格" width="120">
|
<el-table-column label="价格" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span :style="{ color: $mainColor }">{{ $filters.unitPrice(row.price, '¥') }}</span>
|
<span :style="{ color: $mainColor }">{{ $filters.unitPrice(row.price, '¥') }}</span>
|
||||||
@@ -265,6 +266,8 @@ import { customRouterPush } from "@/utils/filters";
|
|||||||
|
|
||||||
defineOptions({ name: "goods" });
|
defineOptions({ name: "goods" });
|
||||||
|
|
||||||
|
const props = defineProps({ scheduledOnly: { type: Boolean, default: false } });
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -315,7 +318,7 @@ const underForm = ref({
|
|||||||
const submitLoading = ref(false); // 添加或编辑提交状态
|
const submitLoading = ref(false); // 添加或编辑提交状态
|
||||||
const data = ref([]); // 表单数据
|
const data = ref([]); // 表单数据
|
||||||
const total = ref(0); // 表单数据总数
|
const total = ref(0); // 表单数据总数
|
||||||
const currentStatus = ref("ALL");
|
const currentStatus = ref(props.scheduledOnly ? "SCHEDULED_UPPER" : "ALL");
|
||||||
const goodsNumerData = ref({});
|
const goodsNumerData = ref({});
|
||||||
const selectedRows = ref([]); // 选中的行数据
|
const selectedRows = ref([]); // 选中的行数据
|
||||||
const selectAll = ref(false); // 全选状态
|
const selectAll = ref(false); // 全选状态
|
||||||
@@ -433,6 +436,12 @@ function buildSearchParams(includeGoodsStatus = true) {
|
|||||||
|
|
||||||
if (!includeGoodsStatus) {
|
if (!includeGoodsStatus) {
|
||||||
delete params.goodsStatus;
|
delete params.goodsStatus;
|
||||||
|
} else if (props.scheduledOnly) {
|
||||||
|
params.goodsStatus = "SCHEDULED_UPPER";
|
||||||
|
}
|
||||||
|
if (includeGoodsStatus && currentStatus.value === "SCHEDULED_UPPER") {
|
||||||
|
params.sort = "scheduled_upper_time";
|
||||||
|
params.order = "asc";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createRange.value && createRange.value.length === 2) {
|
if (createRange.value && createRange.value.length === 2) {
|
||||||
@@ -502,6 +511,7 @@ function getDataList() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getNumberData() {
|
function getNumberData() {
|
||||||
|
if (props.scheduledOnly) return;
|
||||||
getGoodsNumerData(buildSearchParams(false)).then((res) => {
|
getGoodsNumerData(buildSearchParams(false)).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
goodsNumerData.value = res.result;
|
goodsNumerData.value = res.result;
|
||||||
@@ -583,6 +593,7 @@ function goodsStatusClick(name) {
|
|||||||
}
|
}
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
clearTableSelection();
|
clearTableSelection();
|
||||||
|
searchForm.value.pageNumber = 1;
|
||||||
getDataList();
|
getDataList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,7 +674,7 @@ function batchLower() {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const goodsStatus = route.query.goodsStatus;
|
const goodsStatus = route.query.goodsStatus;
|
||||||
if (goodsStatus) {
|
if (goodsStatus && !props.scheduledOnly && goodsStatus !== "SCHEDULED_UPPER") {
|
||||||
currentStatus.value = goodsStatus;
|
currentStatus.value = goodsStatus;
|
||||||
searchForm.value.goodsStatus = goodsStatus;
|
searchForm.value.goodsStatus = goodsStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
14
seller/src/views/goods/scheduled-upper/index.vue
Normal file
14
seller/src/views/goods/scheduled-upper/index.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<goods-list scheduled-only />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 商家商品定时上架列表,复用统一的商品筛选和列表布局。
|
||||||
|
* @author Bulbasaur
|
||||||
|
* @since 2026-11-03
|
||||||
|
*/
|
||||||
|
import GoodsList from "@/views/goods/goods-seller/goods.vue";
|
||||||
|
|
||||||
|
defineOptions({ name: "goodsScheduledUpper" });
|
||||||
|
</script>
|
||||||
@@ -142,13 +142,6 @@
|
|||||||
style="width: 260px"
|
style="width: 260px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="活动类型" prop="getType">
|
|
||||||
<el-select :disabled="disabled" v-model="form.getType" style="width: 260px">
|
|
||||||
<el-option label="免费领取" value="FREE" />
|
|
||||||
<el-option label="活动赠送" value="ACTIVITY" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="发放数量" prop="publishNum" v-if="form.getType === 'FREE'">
|
<el-form-item label="发放数量" prop="publishNum" v-if="form.getType === 'FREE'">
|
||||||
<div class="form-field-block">
|
<div class="form-field-block">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -369,7 +362,7 @@ const form = ref({
|
|||||||
couponType: "PRICE",
|
couponType: "PRICE",
|
||||||
couponName: "",
|
couponName: "",
|
||||||
promotionName: "",
|
promotionName: "",
|
||||||
getType: "FREE",
|
getType: route.query.getType === "ACTIVITY" ? "ACTIVITY" : "FREE",
|
||||||
promotionGoodsList: [],
|
promotionGoodsList: [],
|
||||||
scopeIdGoods: [],
|
scopeIdGoods: [],
|
||||||
rangeDayType: "",
|
rangeDayType: "",
|
||||||
@@ -448,6 +441,7 @@ watch(
|
|||||||
(val) => {
|
(val) => {
|
||||||
if (val == "FREE") {
|
if (val == "FREE") {
|
||||||
rangeTimeType.value = 1;
|
rangeTimeType.value = 1;
|
||||||
|
formRule.rangeTime = [{ required: true, message: "请选择优惠券有效期" }];
|
||||||
} else {
|
} else {
|
||||||
rangeTimeType.value = 0;
|
rangeTimeType.value = 0;
|
||||||
}
|
}
|
||||||
@@ -455,7 +449,7 @@ watch(
|
|||||||
delete formRule.rangeTime;
|
delete formRule.rangeTime;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
watch(
|
watch(
|
||||||
() => route.query,
|
() => route.query,
|
||||||
@@ -464,7 +458,9 @@ watch(
|
|||||||
if (id.value) {
|
if (id.value) {
|
||||||
getCoupon();
|
getCoupon();
|
||||||
} else {
|
} else {
|
||||||
formRef.value.resetFields();
|
formRef.value?.resetFields();
|
||||||
|
modalType.value = 0;
|
||||||
|
form.value.getType = e.getType === "ACTIVITY" ? "ACTIVITY" : "FREE";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<el-card>
|
<el-card>
|
||||||
<div class="operation padding-row" v-if="getType !== 'ACTIVITY'">
|
<div class="operation padding-row" v-if="getType !== 'ACTIVITY'">
|
||||||
<el-button type="primary" @click="add">添加优惠券</el-button>
|
<el-button type="primary" @click="add">添加优惠券</el-button>
|
||||||
|
<el-button class="activity-coupon-button" @click="addActivityCoupon">添加活动优惠券</el-button>
|
||||||
<el-button @click="delAll">批量关闭</el-button>
|
<el-button @click="delAll">批量关闭</el-button>
|
||||||
<el-button type="warning" @click="receivePage()">优惠券领取记录</el-button>
|
<el-button type="warning" @click="receivePage()">优惠券领取记录</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -383,6 +384,10 @@ function add() {
|
|||||||
router.push({ name: "add-coupon" });
|
router.push({ name: "add-coupon" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addActivityCoupon() {
|
||||||
|
router.push({ name: "add-coupon", query: { getType: "ACTIVITY" } });
|
||||||
|
}
|
||||||
|
|
||||||
function changePage(v) {
|
function changePage(v) {
|
||||||
searchForm.pageNumber = v;
|
searchForm.pageNumber = v;
|
||||||
getDataList();
|
getDataList();
|
||||||
@@ -506,6 +511,19 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.activity-coupon-button {
|
||||||
|
--el-button-text-color: #b35c12;
|
||||||
|
--el-button-bg-color: #fff7ed;
|
||||||
|
--el-button-border-color: #fed7aa;
|
||||||
|
--el-button-hover-text-color: #9a4b0b;
|
||||||
|
--el-button-hover-bg-color: #ffedd5;
|
||||||
|
--el-button-hover-border-color: #fdba74;
|
||||||
|
--el-button-active-text-color: #9a4b0b;
|
||||||
|
--el-button-active-bg-color: #fed7aa;
|
||||||
|
--el-button-active-border-color: #fb923c;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.link-text {
|
.link-text {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-card>
|
|
||||||
<el-form ref="formRef" :model="form" label-width="120px">
|
|
||||||
<el-form-item label="活动名称" required>
|
|
||||||
<el-input v-model="form.promotionName" style="width: 320px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动时间" required>
|
|
||||||
<el-date-picker
|
|
||||||
v-model="form.rangeTime"
|
|
||||||
class="flash-discount-range-picker"
|
|
||||||
type="datetimerange"
|
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
start-placeholder="开始时间"
|
|
||||||
end-placeholder="结束时间"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="限购数量">
|
|
||||||
<el-input-number v-model="form.limitNum" :min="0" /> <span class="tip">0 表示不限</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动说明">
|
|
||||||
<el-input v-model="form.description" type="textarea" style="width: 360px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动商品">
|
|
||||||
<el-button type="primary" @click="openGoods">选择商品</el-button>
|
|
||||||
<el-table
|
|
||||||
v-if="form.promotionGoodsList.length"
|
|
||||||
:data="form.promotionGoodsList"
|
|
||||||
border
|
|
||||||
class="mt_10 flash-discount-goods-table"
|
|
||||||
>
|
|
||||||
<el-table-column prop="goodsName" label="商品" width="180" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="originalPrice" label="原价" width="90" align="center" />
|
|
||||||
<el-table-column label="活动价" width="150" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.price"
|
|
||||||
:min="promotionPriceMin(row.goodsType)"
|
|
||||||
:precision="2"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="submit">保存</el-button>
|
|
||||||
<el-button @click="$router.back()">返回</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<liliDialog ref="liliDialogRef" @selectedGoodsData="selectedGoods" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { onMounted, reactive, ref } from "vue";
|
|
||||||
import { useRoute, useRouter } from "vue-router";
|
|
||||||
import { getFlashDiscountDetail, saveFlashDiscount, editFlashDiscount } from "@/api/promotion.js";
|
|
||||||
import { promotionPriceMin } from "@/constants/goodsType";
|
|
||||||
import { unixToDate } from "@/utils/filters";
|
|
||||||
import { Message } from "@/utils/message";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
promotionName: "",
|
|
||||||
rangeTime: [],
|
|
||||||
limitNum: 0,
|
|
||||||
description: "",
|
|
||||||
scopeType: "PORTION_GOODS",
|
|
||||||
promotionGoodsList: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
// formRef / liliDialogRef:模板 ref 改名避免与 form 变量、liliDialog 全局组件互相覆盖
|
|
||||||
const formRef = ref(null);
|
|
||||||
const liliDialogRef = ref(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (route.query.id) {
|
|
||||||
getFlashDiscountDetail(route.query.id).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
Object.assign(form, res.result);
|
|
||||||
form.rangeTime = [new Date(res.result.startTime), new Date(res.result.endTime)];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function openGoods() {
|
|
||||||
liliDialogRef.value.open("goods");
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectedGoods(list) {
|
|
||||||
list.forEach((g) => {
|
|
||||||
if (!form.promotionGoodsList.find((i) => i.skuId === g.id)) {
|
|
||||||
form.promotionGoodsList.push({
|
|
||||||
skuId: g.id,
|
|
||||||
goodsId: g.goodsId,
|
|
||||||
goodsName: g.goodsName,
|
|
||||||
originalPrice: g.price,
|
|
||||||
price: g.price,
|
|
||||||
quantity: g.quantity,
|
|
||||||
goodsType: g.goodsType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function submit() {
|
|
||||||
const payload = { ...form };
|
|
||||||
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
|
||||||
const start =
|
|
||||||
payload.rangeTime[0] instanceof Date
|
|
||||||
? payload.rangeTime[0]
|
|
||||||
: new Date(payload.rangeTime[0]);
|
|
||||||
const end =
|
|
||||||
payload.rangeTime[1] instanceof Date
|
|
||||||
? payload.rangeTime[1]
|
|
||||||
: new Date(payload.rangeTime[1]);
|
|
||||||
payload.startTime = unixToDate(start.getTime() / 1000);
|
|
||||||
payload.endTime = unixToDate(end.getTime() / 1000);
|
|
||||||
}
|
|
||||||
delete payload.rangeTime;
|
|
||||||
const req = payload.id ? editFlashDiscount(payload) : saveFlashDiscount(payload);
|
|
||||||
req.then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
Message.success("保存成功");
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.tip { margin-left: 8px; color: #999; }
|
|
||||||
.mt_10 { margin-top: 10px; }
|
|
||||||
|
|
||||||
:deep(.flash-discount-range-picker.el-date-editor--datetimerange) {
|
|
||||||
width: 380px;
|
|
||||||
max-width: 380px;
|
|
||||||
flex-grow: 0;
|
|
||||||
--el-date-editor-width: 380px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flash-discount-goods-table {
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
<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="活动名称" prop="promotionName">
|
|
||||||
<el-input v-model="searchForm.promotionName" placeholder="请输入活动名称" clearable style="width: 240px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动状态" prop="promotionStatus">
|
|
||||||
<el-select v-model="searchForm.promotionStatus" placeholder="请选择" clearable style="width: 240px">
|
|
||||||
<el-option label="未开始" value="NEW" />
|
|
||||||
<el-option label="已开始" value="START" />
|
|
||||||
<el-option label="已结束" value="END" />
|
|
||||||
<el-option label="已关闭" value="CLOSE" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
||||||
<el-button type="primary" @click="goAdd">新建活动</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<el-card>
|
|
||||||
<el-table v-loading="loading" border :data="data" class="mt_10">
|
|
||||||
<el-table-column prop="promotionName" label="活动名称" min-width="140" />
|
|
||||||
<el-table-column prop="startTime" label="开始时间" width="170" />
|
|
||||||
<el-table-column prop="endTime" label="结束时间" width="170" />
|
|
||||||
<el-table-column label="状态" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="statusType(row.promotionStatus)">{{ statusText(row.promotionStatus) }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="right" label="操作" width="160" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<a class="link-text" @click="goEdit(row)">编辑</a>
|
|
||||||
<span class="op-split">|</span>
|
|
||||||
<a class="link-text" @click="toggleStatus(row)">关闭</a>
|
|
||||||
</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"
|
|
||||||
:total="total"
|
|
||||||
layout="total, prev, pager, next"
|
|
||||||
@current-change="getDataList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from "vue";
|
|
||||||
import { useTable } from "@/utils/useTable";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { getFlashDiscountList, updateFlashDiscountStatus } from "@/api/promotion.js";
|
|
||||||
import { Message } from "@/utils/message";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
data,
|
|
||||||
total,
|
|
||||||
searchForm,
|
|
||||||
getDataList,
|
|
||||||
handleSearch,
|
|
||||||
} = useTable(getFlashDiscountList, { pageSize: 20, sort: "createTime", order: "desc" });
|
|
||||||
|
|
||||||
// searchFormRef:模板 ref 改名避免与顶层变量 searchForm 互相覆盖
|
|
||||||
const searchFormRef = ref(null);
|
|
||||||
|
|
||||||
function statusText(s) {
|
|
||||||
return { NEW: "未开始", START: "进行中", END: "已结束", CLOSE: "已关闭" }[s] || s;
|
|
||||||
}
|
|
||||||
function statusType(s) {
|
|
||||||
return { NEW: "info", START: "success", END: "danger", CLOSE: "danger" }[s] || "info";
|
|
||||||
}
|
|
||||||
function goAdd() {
|
|
||||||
router.push({ name: "flash-discount-add" });
|
|
||||||
}
|
|
||||||
function goEdit(row) {
|
|
||||||
router.push({ name: "flash-discount-add", query: { id: row.id } });
|
|
||||||
}
|
|
||||||
function toggleStatus(row) {
|
|
||||||
updateFlashDiscountStatus(row.id).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
Message.success("操作成功");
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.link-text { color: #409eff; cursor: pointer; }
|
|
||||||
.op-split { margin: 0 8px; color: #dcdfe6; }
|
|
||||||
.mt_10 { margin-top: 10px; }
|
|
||||||
</style>
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-card>
|
|
||||||
<el-form ref="form" :model="form" label-width="120px">
|
|
||||||
<el-form-item label="活动名称" required>
|
|
||||||
<el-input v-model="form.promotionName" style="width: 320px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动时间" required>
|
|
||||||
<el-date-picker
|
|
||||||
v-model="form.rangeTime"
|
|
||||||
class="nth-item-range-picker"
|
|
||||||
type="datetimerange"
|
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
start-placeholder="开始时间"
|
|
||||||
end-placeholder="结束时间"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="第N件" required>
|
|
||||||
<el-input-number v-model="form.nthNum" :min="2" />
|
|
||||||
<span class="tip">例如 2 表示第 2 件享受优惠</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="优惠方式" required>
|
|
||||||
<el-select v-model="form.discountType" style="width: 200px">
|
|
||||||
<el-option label="半价" value="HALF" />
|
|
||||||
<el-option label="免单" value="FREE" />
|
|
||||||
<el-option label="打折" value="RATE" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="form.discountType === 'RATE'" label="折扣" required>
|
|
||||||
<el-input-number v-model="form.discountValue" :min="0.1" :max="9.9" :precision="1" />
|
|
||||||
<span class="tip">如 8 表示 8 折</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动说明">
|
|
||||||
<el-input v-model="form.description" type="textarea" style="width: 360px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动商品">
|
|
||||||
<el-button type="primary" @click="openGoods">选择商品</el-button>
|
|
||||||
<el-table v-if="form.promotionGoodsList.length" :data="form.promotionGoodsList" border class="mt_10">
|
|
||||||
<el-table-column prop="goodsName" label="商品" />
|
|
||||||
<el-table-column prop="originalPrice" label="原价" width="100" />
|
|
||||||
</el-table>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="submit">保存</el-button>
|
|
||||||
<el-button @click="$router.back()">返回</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoods" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { onMounted, reactive, ref } from "vue";
|
|
||||||
import { useRoute, useRouter } from "vue-router";
|
|
||||||
import { getNthItemDiscountDetail, saveNthItemDiscount, editNthItemDiscount } from "@/api/promotion.js";
|
|
||||||
import { Message } from "@/utils/message";
|
|
||||||
import { unixToDate } from "@/utils/filters";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const liliDialog = ref(null);
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
promotionName: "",
|
|
||||||
rangeTime: [],
|
|
||||||
nthNum: 2,
|
|
||||||
discountType: "HALF",
|
|
||||||
discountValue: 8,
|
|
||||||
description: "",
|
|
||||||
scopeType: "PORTION_GOODS",
|
|
||||||
promotionGoodsList: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (route.query.id) {
|
|
||||||
getNthItemDiscountDetail(route.query.id).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
Object.assign(form, res.result);
|
|
||||||
form.rangeTime = [new Date(res.result.startTime), new Date(res.result.endTime)];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function openGoods() {
|
|
||||||
liliDialog.value.open("goods");
|
|
||||||
}
|
|
||||||
function selectedGoods(list) {
|
|
||||||
list.forEach((g) => {
|
|
||||||
if (!form.promotionGoodsList.find((i) => i.skuId === g.id)) {
|
|
||||||
form.promotionGoodsList.push({
|
|
||||||
skuId: g.id,
|
|
||||||
goodsId: g.goodsId,
|
|
||||||
goodsName: g.goodsName,
|
|
||||||
originalPrice: g.price,
|
|
||||||
price: g.price,
|
|
||||||
quantity: g.quantity,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function submit() {
|
|
||||||
const payload = { ...form };
|
|
||||||
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
|
||||||
const start =
|
|
||||||
payload.rangeTime[0] instanceof Date
|
|
||||||
? payload.rangeTime[0]
|
|
||||||
: new Date(payload.rangeTime[0]);
|
|
||||||
const end =
|
|
||||||
payload.rangeTime[1] instanceof Date
|
|
||||||
? payload.rangeTime[1]
|
|
||||||
: new Date(payload.rangeTime[1]);
|
|
||||||
payload.startTime = unixToDate(start.getTime() / 1000);
|
|
||||||
payload.endTime = unixToDate(end.getTime() / 1000);
|
|
||||||
}
|
|
||||||
delete payload.rangeTime;
|
|
||||||
const req = payload.id ? editNthItemDiscount(payload) : saveNthItemDiscount(payload);
|
|
||||||
req.then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
Message.success("保存成功");
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.tip { margin-left: 8px; color: #999; }
|
|
||||||
.mt_10 { margin-top: 10px; }
|
|
||||||
|
|
||||||
:deep(.nth-item-range-picker.el-date-editor--datetimerange) {
|
|
||||||
width: 380px;
|
|
||||||
max-width: 380px;
|
|
||||||
flex-grow: 0;
|
|
||||||
--el-date-editor-width: 380px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<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="活动名称" prop="promotionName">
|
|
||||||
<el-input v-model="searchForm.promotionName" placeholder="请输入活动名称" clearable style="width: 240px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="活动状态" prop="promotionStatus">
|
|
||||||
<el-select v-model="searchForm.promotionStatus" placeholder="请选择" clearable style="width: 240px">
|
|
||||||
<el-option label="未开始" value="NEW" />
|
|
||||||
<el-option label="已开始" value="START" />
|
|
||||||
<el-option label="已结束" value="END" />
|
|
||||||
<el-option label="已关闭" value="CLOSE" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
||||||
<el-button type="primary" @click="goAdd">新建活动</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
<el-card>
|
|
||||||
<el-table v-loading="loading" border :data="data" class="mt_10">
|
|
||||||
<el-table-column prop="promotionName" label="活动名称" min-width="140" />
|
|
||||||
<el-table-column prop="nthNum" label="第N件" width="80" />
|
|
||||||
<el-table-column label="优惠方式" width="100">
|
|
||||||
<template #default="{ row }">{{ discountTypeText(row.discountType) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="startTime" label="开始时间" width="170" />
|
|
||||||
<el-table-column prop="endTime" label="结束时间" width="170" />
|
|
||||||
<el-table-column label="状态" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="statusType(row.promotionStatus)">{{ statusText(row.promotionStatus) }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="right" label="操作" width="160" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<a class="link-text" @click="goEdit(row)">编辑</a>
|
|
||||||
<span class="op-split">|</span>
|
|
||||||
<a class="link-text" @click="toggleStatus(row)">关闭</a>
|
|
||||||
</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"
|
|
||||||
:total="total"
|
|
||||||
layout="total, prev, pager, next"
|
|
||||||
@current-change="getDataList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from "vue";
|
|
||||||
import { useTable } from "@/utils/useTable";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { getNthItemDiscountList, updateNthItemDiscountStatus } from "@/api/promotion.js";
|
|
||||||
import { Message } from "@/utils/message";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
data,
|
|
||||||
total,
|
|
||||||
searchForm,
|
|
||||||
getDataList,
|
|
||||||
handleSearch,
|
|
||||||
} = useTable(getNthItemDiscountList, { pageSize: 20, sort: "createTime", order: "desc" });
|
|
||||||
|
|
||||||
// searchFormRef:模板 ref 改名避免与顶层变量 searchForm 互相覆盖
|
|
||||||
const searchFormRef = ref(null);
|
|
||||||
|
|
||||||
function statusText(s) {
|
|
||||||
return { NEW: "未开始", START: "进行中", END: "已结束", CLOSE: "已关闭" }[s] || s;
|
|
||||||
}
|
|
||||||
function statusType(s) {
|
|
||||||
return { NEW: "info", START: "success", END: "danger", CLOSE: "danger" }[s] || "info";
|
|
||||||
}
|
|
||||||
function discountTypeText(t) {
|
|
||||||
return { HALF: "半价", FREE: "免单", RATE: "打折" }[t] || t;
|
|
||||||
}
|
|
||||||
function goAdd() {
|
|
||||||
router.push({ name: "nth-item-discount-add" });
|
|
||||||
}
|
|
||||||
function goEdit(row) {
|
|
||||||
router.push({ name: "nth-item-discount-add", query: { id: row.id } });
|
|
||||||
}
|
|
||||||
function toggleStatus(row) {
|
|
||||||
updateNthItemDiscountStatus(row.id).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
Message.success("操作成功");
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.link-text { color: #409eff; cursor: pointer; }
|
|
||||||
.op-split { margin: 0 8px; color: #dcdfe6; }
|
|
||||||
.mt_10 { margin-top: 10px; }
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user