feat: 新增全局布局样式并提升组件响应式表现

- 在 global-layout.scss 中引入全局布局样式,统一页面宽度与对齐方式
- 更新多个组件与页面以优化响应式,包括宽度、间距及 flex 布局等调整
- 在 API 请求中增加 loading 状态管理,改善用户体验
- 优化领券中心与商品详情页,提升功能与 UI 一致性
This commit is contained in:
田香琪
2026-06-23 10:17:35 +08:00
parent c1447b4376
commit 8a60e23214
129 changed files with 5236 additions and 2232 deletions

View File

@@ -232,17 +232,12 @@
<script>
import { saveShopCoupon, getShopCoupon, editShopCoupon } from "@/api/promotion";
import {
savePlatformCoupon,
getPlatformCoupon,
editPlatformCoupon,
} from "@/api/promotion";
import { getGoodsCategoryAll } from "@/api/goods";
import { regular } from "@/utils";
import skuSelect from "@/views/lili-dialog";
export default {
name: "edit-platform-coupon",
name: "add-coupon",
components: {
skuSelect,
},
@@ -360,7 +355,7 @@ export default {
},
methods: {
getCoupon() {
getPlatformCoupon(this.id).then((res) => {
getShopCoupon(this.id).then((res) => {
let data = res.result;
if (!data.promotionGoodsList) data.promotionGoodsList = [];
this.rangeTimeType = data.rangeDayType === "DYNAMICTIME" ? 0 : 1;
@@ -453,7 +448,7 @@ export default {
if (this.modalType === 0) {
delete params.id;
savePlatformCoupon(params).then((res) => {
saveShopCoupon(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("优惠券发送成功");
@@ -464,7 +459,7 @@ export default {
delete params.consumeLimit;
delete params.updateTime;
editPlatformCoupon(params).then((res) => {
editShopCoupon(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("优惠券修改成功");
@@ -476,7 +471,7 @@ export default {
});
},
closeCurrentPage() {
this.$store.commit("removeTag", "add-platform-coupon");
this.$store.commit("removeTag", "add-coupon");
localStorage.pageOpenedList = JSON.stringify(this.$store.state.app.pageOpenedList);
this.$router.go(-1);
},
@@ -535,7 +530,7 @@ export default {
this.form.promotionGoodsList = list;
},
async getCagetoryList() {
let data = await getCategoryTree();
let data = await getGoodsCategoryAll();
this.goodsCategoryList = data.result;
this.goodsCategoryList = this.goodsCategoryList.map((item) => {
if (item.children) {

View File

@@ -56,7 +56,7 @@
<div class="operation padding-row" v-if="getType !== 'ACTIVITY'">
<el-button type="primary" @click="add">添加优惠券</el-button>
<el-button @click="delAll">批量关闭</el-button>
<el-button type="info" @click="receivePage()">优惠券领取记录</el-button>
<el-button type="warning" @click="receivePage()">优惠券领取记录</el-button>
</div>
<el-table
@@ -182,11 +182,10 @@
</template>
<script>
import { getShopCouponList, updateCouponStatus } from "@/api/promotion";
import {
getPlatformCouponList,
updatePlatformCouponStatus,
deletePlatformCoupon,
getShopCouponList,
updateCouponStatus,
deleteShopCoupon,
} from "@/api/promotion";
import { formatPromotionCouponValidityHtml } from "@/utils/promotions";
@@ -223,6 +222,7 @@ export default {
selectDate: [],
showActionColumn: true,
showStatusColumn: true,
isSyncingSelection: false,
};
},
watch: {
@@ -233,6 +233,8 @@ export default {
},
selectedList: {
handler(val) {
if (this.isSyncingSelection) return;
if (this.isSameSelection(val, this.selectList)) return;
this.$nextTick(() => {
this.syncTableSelection(val);
});
@@ -312,15 +314,31 @@ export default {
};
return map[type] || "";
},
isSameSelection(next, current) {
const a = next || [];
const b = current || [];
if (a.length !== b.length) return false;
const nextIds = a.map((item) => item.id).sort().join(",");
const currentIds = b.map((item) => item.id).sort().join(",");
return nextIds === currentIds;
},
syncTableSelection(selected) {
const table = this.$refs.table;
if (!table) return;
const nextList = selected ? [...selected] : [];
this.isSyncingSelection = true;
table.clearSelection();
if (!selected || !selected.length) return;
this.data.forEach((row) => {
if (selected.some((item) => item.id === row.id)) {
table.toggleRowSelection(row, true);
}
if (nextList.length) {
this.data.forEach((row) => {
if (nextList.some((item) => item.id === row.id)) {
table.toggleRowSelection(row, true);
}
});
}
this.selectList = nextList;
this.selectCount = nextList.length;
this.$nextTick(() => {
this.isSyncingSelection = false;
});
},
check() {
@@ -337,7 +355,7 @@ export default {
this.getDataList();
},
add() {
this.$router.push({ name: "add-platform-coupon" });
this.$router.push({ name: "add-coupon" });
},
changePage(v) {
this.searchForm.pageNumber = v;
@@ -357,6 +375,7 @@ export default {
this.$refs.table?.clearSelection();
},
changeSelect(e) {
if (this.isSyncingSelection) return;
this.selectList = e;
this.selectCount = e.length;
if (this.getType === "ACTIVITY") this.check();
@@ -370,7 +389,7 @@ export default {
this.searchForm.startTime = null;
this.searchForm.endTime = null;
}
getPlatformCouponList(this.searchForm)
getShopCouponList(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result.records;
@@ -388,7 +407,7 @@ export default {
},
see(v, only) {
const data = only ? { onlyView: true, id: v.id } : { id: v.id };
this.$router.push({ name: "edit-platform-coupon", query: data });
this.$router.push({ name: "add-coupon", query: data });
},
close(v) {
this.$Modal.confirm({
@@ -396,7 +415,7 @@ export default {
content: "确认要关闭此优惠券么?",
loading: true,
onOk: () => {
updatePlatformCouponStatus({
updateCouponStatus({
couponIds: v.id,
effectiveDays: 0,
})
@@ -419,7 +438,7 @@ export default {
content: "确认要删除此优惠券么?",
loading: true,
onOk: () => {
deletePlatformCoupon(v.id)
deleteShopCoupon(v.id)
.then((res) => {
this.$Modal.remove();
if (res.success) {
@@ -444,7 +463,7 @@ export default {
loading: true,
onOk: () => {
const ids = this.selectList.map((e) => e.id);
updatePlatformCouponStatus({
updateCouponStatus({
couponIds: ids.toString(),
promotionStatus: "CLOSE",
}).then((res) => {

View File

@@ -1,14 +1,14 @@
<template>
<div>
<el-card>
<el-form ref="form" :model="form" label-width="120px">
<el-form ref="form" :model="form" label-width="120px" :rules="formRule">
<div class="base-info-item">
<h4>基本信息</h4>
<div class="form-item-view">
<el-form-item label="活动名称" prop="promotionName">
<el-input
v-model="form.promotionName"
disabled
:disabled="fieldDisabled"
placeholder="活动名称"
clearable
style="width: 260px"
@@ -18,7 +18,7 @@
<el-date-picker
type="datetimerange"
v-model="form.rangeTime"
disabled
:disabled="fieldDisabled"
format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
@@ -30,7 +30,7 @@
<el-form-item label="活动描述" prop="description">
<el-input
v-model="form.description"
disabled
:disabled="fieldDisabled"
type="textarea"
:rows="4"
clearable
@@ -44,17 +44,17 @@
<el-form-item label="优惠门槛" prop="fullMoney">
<el-input
v-model="form.fullMoney"
disabled
:disabled="fieldDisabled"
placeholder="优惠门槛"
clearable
style="width: 260px"
/>
<span class="describe">消费达到当前金额可以参与优惠</span>
</el-form-item>
<el-form-item label="赠送优惠券">
<el-radio-group v-model="form.discountType">
<el-radio-button value="fullMinusFlag" disabled>减现金</el-radio-button>
<el-radio-button value="fullRateFlag" disabled>打折</el-radio-button>
<el-form-item label="优惠方式">
<el-radio-group v-model="form.discountType" :disabled="fieldDisabled">
<el-radio-button value="fullMinusFlag">减现金</el-radio-button>
<el-radio-button value="fullRateFlag">打折</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
@@ -63,7 +63,7 @@
prop="fullMinus"
>
<el-input
disabled
:disabled="fieldDisabled"
v-model="form.fullMinus"
placeholder="优惠金额"
clearable
@@ -76,26 +76,27 @@
prop="fullRate"
>
<el-input-number
:disabled="fieldDisabled"
v-model="form.fullRate"
placeholder="优惠折扣"
:max="9.9"
:min="0.1"
:max="9.9"
:step="0.1"
:precision="1"
v-model="form.fullRate"
style="width: 260px"
/>
<span class="describe">优惠折扣为0-10之间数字可有一位小数</span>
</el-form-item>
<el-form-item label="额外赠送">
<el-checkbox v-model="form.freeFreightFlag" disabled>免邮费</el-checkbox>
<el-checkbox v-model="form.couponFlag" disabled>送优惠券</el-checkbox>
<el-checkbox v-model="form.giftFlag" disabled>送赠品</el-checkbox>
<el-checkbox v-model="form.pointFlag" disabled>送积分</el-checkbox>
<el-checkbox v-model="form.freeFreightFlag" :disabled="fieldDisabled">免邮费</el-checkbox>
<el-checkbox v-model="form.couponFlag" :disabled="fieldDisabled">送优惠券</el-checkbox>
<el-checkbox v-model="form.giftFlag" :disabled="fieldDisabled">送赠品</el-checkbox>
<el-checkbox v-model="form.pointFlag" :disabled="fieldDisabled">送积分</el-checkbox>
</el-form-item>
<el-form-item v-if="form.couponFlag" label="赠送优惠券" prop="couponId">
<el-select
v-model="form.couponId"
:disabled="form.promotionStatus != 'NEW'"
:disabled="fieldDisabled"
filterable
remote
:remote-method="getCouponList"
@@ -118,7 +119,7 @@
remote
:remote-method="getGiftList"
placeholder="输入赠品名称搜索"
disabled
:disabled="fieldDisabled"
:loading="giftLoading"
style="width: 260px"
>
@@ -134,23 +135,36 @@
<el-input
v-model="form.point"
type="number"
disabled
:disabled="fieldDisabled"
style="width: 260px"
/>
</el-form-item>
<el-form-item label="使用范围" prop="scopeType">
<el-radio-group v-model="form.scopeType">
<el-radio-button value="ALL" disabled>全品类</el-radio-button>
<el-radio-button value="PORTION_GOODS" disabled>指定商品</el-radio-button>
<el-radio-group v-model="form.scopeType" :disabled="fieldDisabled">
<el-radio-button value="ALL">全品类</el-radio-button>
<el-radio-button value="PORTION_GOODS">指定商品</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
style="width: 100%"
v-if="form.scopeType == 'PORTION_GOODS'"
>
<el-table border :data="form.promotionGoodsList" style="width: 100%">
<el-table-column type="selection" width="60" align="center" />
<el-form-item style="width: 100%" v-if="form.scopeType == 'PORTION_GOODS'">
<div v-if="!fieldDisabled" style="display: flex; margin-bottom: 10px">
<el-button type="primary" @click="openSkuList">选择商品</el-button>
<el-button type="danger" plain style="margin-left: 10px" @click="delSelectGoods">
批量删除
</el-button>
</div>
<el-table
border
:data="form.promotionGoodsList"
style="width: 100%"
@selection-change="changeSelect"
>
<el-table-column
v-if="!fieldDisabled"
type="selection"
width="60"
align="center"
/>
<el-table-column label="商品名称" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
<template v-if="row">
@@ -190,40 +204,95 @@
<span v-if="row">{{ row.quantity }}</span>
</template>
</el-table-column>
<el-table-column v-if="!fieldDisabled" label="操作" width="100" align="center">
<template #default="{ $index }">
<a class="link-text" @click="delGoods($index)">删除</a>
</template>
</el-table-column>
</el-table>
</el-form-item>
<div>
<el-button @click="$router.push({ name: 'promotions/full-discount' })">返回</el-button>
<el-button @click="closeCurrentPage">返回</el-button>
<el-button
v-if="!fieldDisabled"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
提交
</el-button>
</div>
</div>
</div>
</el-form>
</el-card>
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoodsData" />
</div>
</template>
<script>
import { getPlatformCouponList, getFullDiscountById } from "@/api/promotion";
import {
getShopCouponList,
getFullDiscountById,
newFullDiscount,
editFullDiscount,
} from "@/api/promotion";
import { getGoodsSkuListDataSeller } from "@/api/goods";
import { regular } from "@/utils";
import vueQr from "vue-qr";
export default {
name: "add-full-discount",
components: {
"vue-qr": vueQr,
},
data() {
const checkWeight = (rule, value, callback) => {
if (!value && typeof value !== "number") {
callback(new Error("优惠门槛不能为空"));
} else if (!regular.money.test(value)) {
callback(new Error("请输入正整数或者两位小数"));
} else if (parseFloat(value) > 99999999) {
callback(new Error("优惠门槛设置超过上限值"));
} else {
callback();
}
};
const checkDiscount = (rule, value, callback) => {
if (value === null || value === undefined || value === "") {
callback(new Error("请填写优惠折扣"));
} else if (value < 0.1 || value > 9.9) {
callback(new Error("请输入0-10的数字,可有一位小数"));
} else {
callback();
}
};
return {
form: {
discountType: "fullMinusFlag",
scopeType: "ALL",
promotionGoodsList: [],
promotionStatus: "NEW",
},
id: this.$route.query.id,
submitLoading: false,
selectedGoods: [],
couponList: [],
giftList: [],
giftLoading: false,
couponLoading: false,
formRule: {
promotionName: [{ required: true, message: "活动名称不能为空" }],
rangeTime: [{ required: true, message: "请选择活动时间" }],
description: [{ required: true, message: "请填写活动描述" }],
fullMoney: [{ required: true, validator: checkWeight }],
fullMinus: [
{ required: true, message: "请填写优惠金额" },
{ pattern: regular.money, message: "请输入正确金额" },
],
fullRate: [{ required: true, validator: checkDiscount }],
},
options: {
disabledDate(date) {
return date && date.valueOf() < Date.now() - 86400000;
@@ -231,7 +300,12 @@ export default {
},
};
},
async mounted() {
computed: {
fieldDisabled() {
return !!this.id && this.form.promotionStatus !== "NEW";
},
},
mounted() {
if (this.id) {
this.getDetail();
}
@@ -239,36 +313,149 @@ export default {
this.getGiftList();
},
methods: {
closeCurrentPage() {
this.$router.back();
},
openSkuList() {
this.$refs.liliDialog.open("goods");
const data = JSON.parse(JSON.stringify(this.form.promotionGoodsList));
data.forEach((e) => {
e.id = e.skuId;
});
this.$refs.liliDialog.goodsData = data;
},
getDetail() {
getFullDiscountById(this.id).then((res) => {
let data = res.result;
if (!data.scopeType === "ALL") {
const data = res.result;
if (data.scopeType === "ALL") {
data.promotionGoodsList = [];
}
if (data.fullMinusFlag) {
data.discountType = "fullMinusFlag";
delete data.fullMinusFlag;
} else {
data.discountType = "fullMinusFlag";
data.discountType = "fullRateFlag";
delete data.fullRateFlag;
}
data.rangeTime = [];
data.rangeTime.push(new Date(data.startTime), new Date(data.endTime));
data.rangeTime = [new Date(data.startTime), new Date(data.endTime)];
if (data.fullRate !== null && data.fullRate !== undefined && data.fullRate !== "") {
data.fullRate = parseFloat(Number(data.fullRate).toFixed(1));
}
this.form = data;
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
const params = JSON.parse(JSON.stringify(this.form));
params.startTime = this.$filters.unixToDate(this.form.rangeTime[0] / 1000);
params.endTime = this.$filters.unixToDate(this.form.rangeTime[1] / 1000);
if (params.couponFlag && !params.couponId) {
this.$Message.warning("请选择优惠券");
return;
}
if (params.giftFlag && !params.giftId) {
this.$Message.warning("请选择赠品");
return;
}
if (params.pointFlag && !params.point) {
this.$Message.warning("请填写积分");
return;
}
if (
params.scopeType === "PORTION_GOODS" &&
(!params.promotionGoodsList || params.promotionGoodsList.length === 0)
) {
this.$Message.warning("请选择指定商品");
return;
}
if (params.scopeType === "ALL") {
delete params.promotionGoodsList;
params.number = -1;
} else {
const scopeId = [];
params.number = 1;
params.promotionGoodsList.forEach((e) => {
e.startTime = params.startTime;
e.endTime = params.endTime;
scopeId.push(e.skuId);
});
params.scopeId = scopeId.toString();
}
if (params.discountType === "fullMinusFlag") {
params.fullMinusFlag = true;
} else {
params.fullRateFlag = true;
}
delete params.discountType;
delete params.rangeTime;
if (!this.id) {
delete params.id;
} else {
delete params.updateTime;
}
this.submitLoading = true;
const request = this.id ? editFullDiscount(params) : newFullDiscount(params);
request.then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success(this.id ? "编辑活动成功" : "添加活动成功");
this.closeCurrentPage();
}
});
});
},
changeSelect(e) {
this.selectedGoods = e;
},
delSelectGoods() {
if (this.selectedGoods.length <= 0) {
this.$Message.warning("您还未选择要删除的数据");
return;
}
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除所选商品吗?",
onOk: () => {
const ids = this.selectedGoods.map((e) => e.skuId || e.id);
this.form.promotionGoodsList = this.form.promotionGoodsList.filter(
(item) => !ids.includes(item.skuId)
);
},
});
},
delGoods(index) {
this.form.promotionGoodsList.splice(index, 1);
},
selectedGoodsData(item) {
const list = [];
item.forEach((e) => {
list.push({
goodsName: e.goodsName,
price: e.price,
quantity: e.quantity,
storeId: e.storeId,
storeName: e.storeName,
thumbnail: e.thumbnail,
skuId: e.id,
goodsId: e.goodsId,
});
});
this.form.promotionGoodsList = list;
},
getCouponList(query) {
let params = {
const params = {
pageSize: 20,
pageNumber: 1,
getType: "ACTIVITY",
storeId: "",
couponName: query,
promotionStatus: "START",
};
this.couponLoading = true;
getPlatformCouponList(params).then((res) => {
getShopCouponList(params).then((res) => {
this.couponLoading = false;
if (res.success) {
this.couponList = res.result.records;
@@ -276,16 +463,16 @@ export default {
});
},
getGiftList(query) {
let params = {
const params = {
pageSize: 20,
pageNumber: 1,
id: query === this.form.giftId ? this.form.giftId : null,
goodsName: query === this.form.giftId ? null : query,
marketEnable: "UPPER",
authFlag: "PASS"
authFlag: "PASS",
};
this.giftLoading = true;
getGoodsSkuData(params).then((res) => {
getGoodsSkuListDataSeller(params).then((res) => {
this.giftLoading = false;
if (res.success) {
this.giftList = res.result.records;

View File

@@ -47,6 +47,9 @@
</el-card>
<el-card>
<div class="operation padding-row">
<el-button type="primary" @click="add">新增</el-button>
</div>
<el-table
ref="table"
v-loading="loading"
@@ -71,10 +74,12 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="140" fixed="right">
<el-table-column label="操作" align="center" width="160" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a class="link-text" @click="view(row)">查看</a>
<a class="link-text" @click="goDetail(row)">
{{ row.promotionStatus === "NEW" ? "编辑" : "查看" }}
</a>
<template v-if="row.promotionStatus === 'NEW' || row.promotionStatus === 'START'">
<span class="op-split">|</span>
<a class="link-text" @click="openOrClose(row)">关闭</a>
@@ -189,7 +194,10 @@ export default {
}
});
},
view(row) {
add() {
this.$router.push({ name: "full-discount-detail" });
},
goDetail(row) {
this.$router.push({ name: "full-discount-detail", query: { id: row.id } });
},
},
@@ -214,4 +222,12 @@ export default {
.mt_10 {
margin-top: 10px;
}
.operation {
margin-bottom: 10px;
}
.padding-row {
padding: 0 0 10px;
}
</style>

View File

@@ -1,179 +1,195 @@
<template>
<div class="pintuan-goods">
<div class="new-pintuan">
<el-card>
<h4>活动详情</h4>
<el-table border :data="data" style="width: 100%; margin: 10px 0">
<el-table-column prop="promotionName" label="活动名称" min-width="120" />
<el-table-column prop="startTime" label="活动开始时间" min-width="120" />
<el-table-column prop="endTime" label="活动结束时间" min-width="120" />
<el-table-column prop="requiredNum" label="成团人数" min-width="90" />
<el-table-column prop="limitNum" label="限购数量" min-width="90" />
<el-table-column label="状态" min-width="100">
<template #default="{ row }">
<el-tag v-if="row" :type="promotionStatusTagType(row.promotionStatus)">
{{ promotionStatusText(row.promotionStatus) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<h4>商品信息</h4>
<el-table
ref="table"
v-loading="loading"
border
class="operation"
:data="goodsData"
style="width: 100%"
>
<el-table-column label="商品名称" min-width="120">
<template #default="{ row }">
<template v-if="row">
<a class="link-text mr_10" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
<el-popover trigger="hover" title="扫码在手机中查看" placement="top" width="180">
<template #reference>
<img
src="../../../assets/qrcode.svg"
style="vertical-align: middle"
class="hover-pointer"
width="20"
height="20"
alt="qrcode"
/>
</template>
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
color-dark="#000"
color-light="#fff"
:size="150"
/>
</el-popover>
</template>
</template>
</el-table-column>
<el-table-column prop="quantity" label="库存" min-width="80" />
<el-table-column label="拼团价格" min-width="100">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.price, "") }}</span>
</template>
</el-table-column>
</el-table>
<div class="page operation mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
<el-form ref="form" :model="form" label-width="130px" :rules="formValidate">
<el-form-item label="活动名称" prop="promotionName">
<el-input
v-model="form.promotionName"
clearable
style="width: 260px"
maxlength="25"
placeholder="请输入活动名称"
/>
<div class="form-tip">
活动名称将显示在店铺拼团活动列表中供商家管理使用最多输入25个字符
</div>
</el-form-item>
<el-form-item label="活动时间" prop="rangeTime">
<el-date-picker
v-model="form.rangeTime"
type="datetimerange"
format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="请选择"
:disabled-date="options.disabledDate"
style="width: 360px"
/>
</el-form-item>
<el-form-item label="成团人数" prop="requiredNum">
<el-input v-model="form.requiredNum" style="width: 260px" placeholder="请输入成团人数">
<template #append></template>
</el-input>
<span class="form-tip inline-tip">成团人数最少为2人最多不超过10人</span>
</el-form-item>
<el-form-item label="限购数量" prop="limitNum">
<el-input v-model="form.limitNum" type="number" style="width: 260px" placeholder="请输入限购数量">
<template #append>/</template>
</el-input>
<span class="form-tip inline-tip">若设置为0则为不限制购买数量</span>
</el-form-item>
<el-form-item label="虚拟成团" prop="fictitious">
<el-radio-group v-model="form.fictitious">
<el-radio-button :value="1">开启</el-radio-button>
<el-radio-button :value="0">关闭</el-radio-button>
</el-radio-group>
<div class="form-tip">
开启虚拟成团后24小时内人数未满的团系统将会模拟匿名买家凑满人数使该团成团您只需要对真实拼团买家发货建议合理开启以提高成团率
</div>
</el-form-item>
<el-form-item label="拼团规则" prop="pintuanRule">
<el-input
v-model="form.pintuanRule"
type="textarea"
:rows="4"
clearable
maxlength="255"
style="width: 260px"
placeholder="请输入拼团规则"
/>
<div class="form-tip">拼团规则字数不能超过255字将在WAP拼团详情页中显示</div>
</el-form-item>
<el-form-item>
<el-button @click="closeCurrentPage">返回</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import { getPintuanGoodsList, getPintuanDetail } from "@/api/promotion.js";
import vueQr from "vue-qr";
import { savePintuan, editPintuan, getPintuanDetail } from "@/api/promotion";
export default {
components: { vueQr },
data() {
return {
loading: false,
searchForm: {
pageNumber: 1,
pageSize: 20,
id: this.$route.query.id,
form: {
promotionName: "",
promotionTitle: "",
pintuanRule: "",
requiredNum: "",
fictitious: 0,
limitNum: "",
startTime: "",
endTime: "",
rangeTime: [],
},
formValidate: {
promotionName: [{ required: true, message: "活动名称不能为空", trigger: "blur" }],
requiredNum: [
{ required: true, message: "成团人数不能为空", trigger: "blur" },
{
pattern: /^([2-9]|10)?$/,
message: "成团人数不合法",
trigger: "blur",
},
],
limitNum: [
{ required: true, message: "限购数量不能为空", trigger: "blur" },
{
pattern: /^(0|[1-9]\d?|100)$/,
message: "限购数量不合法",
trigger: "blur",
},
],
rangeTime: [{ required: true, message: "请选择活动时间", trigger: "change" }],
},
submitLoading: false,
options: {
disabledDate(date) {
return date && date.valueOf() < Date.now() - 86400000;
},
},
data: [],
total: 0,
goodsData: [],
};
},
mounted() {
if (this.id) {
this.getDetail();
}
},
methods: {
promotionStatusText(status) {
const map = {
NEW: "未开始",
START: "已开始",
END: "已结束",
CLOSE: "已关闭",
};
return map[status] || "未知";
closeCurrentPage() {
this.$router.back();
},
promotionStatusTagType(status) {
const map = {
NEW: "info",
START: "success",
END: "danger",
CLOSE: "danger",
};
return map[status] || "danger";
},
init() {
this.getDataList();
this.getPintuanMsg();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
this.searchForm.pintuanId = this.$route.query.id;
getPintuanGoodsList(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.goodsData = res.result.records;
this.total = res.result.total;
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
const params = JSON.parse(JSON.stringify(this.form));
params.fictitious = !!params.fictitious;
params.startTime = this.$filters.unixToDate(this.form.rangeTime[0] / 1000);
params.endTime = this.$filters.unixToDate(this.form.rangeTime[1] / 1000);
if (!params.startTime || !params.endTime) {
this.$Message.error("活动时间不能为空");
this.submitLoading = false;
return;
}
if (new Date(params.startTime).getTime() < Date.now()) {
this.$Message.error("拼团活动开始时间不能小于当前时间");
this.submitLoading = false;
return;
}
delete params.rangeTime;
if (!this.id) {
delete params.id;
savePintuan(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("拼团活动添加成功");
this.closeCurrentPage();
}
});
} else {
if (params.promotionGoodsList === "") delete params.promotionGoodsList;
editPintuan(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("编辑成功");
this.closeCurrentPage();
}
});
}
});
},
getPintuanMsg() {
getPintuanDetail(this.$route.query.id).then((res) => {
if (res.success) this.data.push(res.result);
getDetail() {
getPintuanDetail(this.id).then((res) => {
if (res.success) {
const data = res.result;
data.rangeTime = [new Date(data.startTime), new Date(data.endTime)];
this.form = data;
this.form.fictitious = data.fictitious ? 1 : 0;
}
});
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" scoped>
h4 {
margin: 20px 0;
padding: 0 10px;
font-weight: bold;
color: #333;
font-size: 14px;
text-align: left;
border-left: 3px solid red;
.form-tip {
color: #cccccc;
font-size: 12px;
line-height: 1.5;
margin-top: 4px;
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.mr_10 {
margin-right: 10px;
}
.hover-pointer {
cursor: pointer;
}
.mt_10 {
margin-top: 10px;
.inline-tip {
display: inline-block;
margin-top: 0;
margin-left: 8px;
}
</style>

View File

@@ -4,8 +4,8 @@
<h4>活动详情</h4>
<el-table border :data="data" style="width: 100%; margin: 10px 0">
<el-table-column prop="promotionName" label="活动名称" min-width="120" />
<el-table-column prop="startTime" label="活动开始时间" min-width="120" />
<el-table-column prop="endTime" label="活动结束时间" min-width="120" />
<el-table-column prop="startTime" label="活动开始时间" min-width="160" />
<el-table-column prop="endTime" label="活动结束时间" min-width="160" />
<el-table-column prop="requiredNum" label="成团人数" min-width="90" />
<el-table-column prop="limitNum" label="限购数量" min-width="90" />
<el-table-column label="状态" min-width="100">
@@ -17,75 +17,130 @@
</el-table-column>
</el-table>
<h4>商品信息</h4>
<el-table
ref="table"
v-loading="loading"
border
class="operation"
:data="goodsData"
style="width: 100%"
>
<el-table-column label="商品名称" min-width="120">
<template #default="{ row }">
<template v-if="row">
<a class="link-text mr_10" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
<el-popover trigger="hover" title="扫码在手机中查看" placement="top" width="180">
<template #reference>
<img
src="../../../assets/qrcode.svg"
style="vertical-align: middle"
class="hover-pointer"
width="20"
height="20"
alt="qrcode"
/>
</template>
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
color-dark="#000"
color-light="#fff"
:size="150"
/>
</el-popover>
<h4>活动商品</h4>
<template v-if="!readonly">
<div class="operation">
<el-button type="primary" @click="openSkuList">选择商品</el-button>
<el-button @click="delAll">批量删除</el-button>
<el-button @click="getDataList">刷新</el-button>
</div>
<el-alert v-if="selectCount > 0" type="info" show-icon :closable="false" class="select-alert">
已选择 <span>{{ selectCount }}</span>
<a class="select-clear" @click="clearSelectAll">清空</a>
</el-alert>
<el-table
ref="table"
v-loading="loading"
border
:data="goodsData"
style="width: 100%"
@selection-change="changeSelect"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="goodsName" label="商品名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="quantity" label="库存" width="90" />
<el-table-column label="拼团价格" width="160">
<template #default="{ row, $index }">
<el-input-number
v-if="row"
v-model="row.price"
:min="0.01"
:precision="2"
controls-position="right"
size="small"
@change="(val) => updateGoodsPrice($index, val)"
/>
</template>
</template>
</el-table-column>
<el-table-column prop="quantity" label="库存" min-width="80" />
<el-table-column label="拼团价格" min-width="100">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.price, "") }}</span>
</template>
</el-table-column>
</el-table>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ $index }">
<a class="link-text" @click="delGoods($index)">删除</a>
</template>
</el-table-column>
</el-table>
</template>
<div class="page operation mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
<template v-else>
<el-table
ref="table"
v-loading="loading"
border
:data="goodsData"
style="width: 100%"
>
<el-table-column label="商品名称" min-width="140">
<template #default="{ row }">
<template v-if="row">
<a class="link-text mr_10" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
<el-popover trigger="hover" title="扫码在手机中查看" placement="top" width="180">
<template #reference>
<img
src="../../../assets/qrcode.svg"
class="hover-pointer qrcode-icon"
width="20"
height="20"
alt="qrcode"
/>
</template>
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
color-dark="#000"
color-light="#fff"
:size="150"
/>
</el-popover>
</template>
</template>
</el-table-column>
<el-table-column prop="quantity" label="库存" width="90" />
<el-table-column label="拼团价格" width="120">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.price, "") }}
</span>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</template>
<div class="foot-btn">
<el-button @click="closeCurrentPage">返回</el-button>
<el-button v-if="!readonly" type="primary" :loading="submitLoading" @click="save">
提交
</el-button>
</div>
</el-card>
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoodsData" />
</div>
</template>
<script>
import { getPintuanGoodsList, getPintuanDetail, editPintuan } from "@/api/promotion.js";
import liliDialog from "@/views/lili-dialog";
import vueQr from "vue-qr";
export default {
components: { vueQr },
components: { liliDialog, vueQr },
data() {
return {
loading: false,
submitLoading: false,
promotionStatus: "",
searchForm: {
pageNumber: 1,
pageSize: 20,
@@ -93,8 +148,15 @@ export default {
data: [],
total: 0,
goodsData: [],
selectList: [],
selectCount: 0,
};
},
computed: {
readonly() {
return this.$route.query.status === "view" || this.promotionStatus !== "NEW";
},
},
methods: {
promotionStatusText(status) {
const map = {
@@ -114,8 +176,10 @@ export default {
};
return map[status] || "danger";
},
closeCurrentPage() {
this.$router.back();
},
init() {
this.getDataList();
this.getPintuanMsg();
},
changePage() {
@@ -125,20 +189,119 @@ export default {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
this.searchForm.pintuanId = this.$route.query.id;
getPintuanGoodsList(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.goodsData = res.result.records;
this.total = res.result.total;
getPintuanMsg() {
getPintuanDetail(this.$route.query.id).then((res) => {
if (res.success && res.result) {
this.data = [res.result];
this.promotionStatus = res.result.promotionStatus;
this.getDataList();
}
});
},
getPintuanMsg() {
getPintuanDetail(this.$route.query.id).then((res) => {
if (res.success) this.data.push(res.result);
getDataList() {
this.loading = true;
const params = {
pintuanId: this.$route.query.id,
pageNumber: this.readonly ? this.searchForm.pageNumber : 1,
pageSize: this.readonly ? this.searchForm.pageSize : 1000,
};
getPintuanGoodsList(params).then((res) => {
this.loading = false;
if (res.success && res.result) {
this.goodsData = res.result.records || [];
this.total = res.result.total || 0;
}
});
},
openSkuList() {
const data = JSON.parse(JSON.stringify(this.goodsData));
data.forEach((e) => {
e.id = e.skuId;
});
this.$refs.liliDialog.goodsData = data;
this.$refs.liliDialog.open("goods");
},
selectedGoodsData(selected) {
const existingMap = new Map(this.goodsData.map((item) => [item.skuId, item]));
this.goodsData = selected.map((item) => {
const existed = existingMap.get(item.id);
if (existed) {
return existed;
}
return {
goodsName: item.goodsName,
price: item.price,
originalPrice: item.price,
quantity: item.quantity,
storeId: item.storeId,
sellerName: item.sellerName,
thumbnail: item.thumbnail,
skuId: item.id,
categoryPath: item.categoryPath,
goodsId: item.goodsId,
goodsType: item.goodsType,
};
});
this.clearSelectAll();
},
updateGoodsPrice(index, value) {
if (this.goodsData[index]) {
this.goodsData[index].price = value;
}
},
changeSelect(selection) {
this.selectList = selection;
this.selectCount = selection.length;
},
clearSelectAll() {
this.$refs.table?.clearSelection();
this.selectList = [];
this.selectCount = 0;
},
delGoods(index) {
this.goodsData.splice(index, 1);
this.selectCount = 0;
},
delAll() {
if (this.selectCount <= 0) {
this.$Message.warning("您还未选择要删除的数据");
return;
}
this.$Modal.confirm({
title: "确认删除",
content: `您确认要删除所选 ${this.selectCount} 项数据?`,
onOk: () => {
const ids = this.selectList.map((e) => e.skuId);
this.goodsData = this.goodsData.filter((item) => !ids.includes(item.skuId));
this.clearSelectAll();
},
});
},
save() {
if (!this.goodsData.length) {
this.$Message.warning("请选择活动商品");
return;
}
for (const item of this.goodsData) {
if (item.price === "" || item.price === null || item.price === undefined) {
this.$Message.warning(`请填写【${item.goodsName}】的价格`);
return;
}
}
const params = JSON.parse(JSON.stringify(this.data[0]));
params.promotionGoodsList = this.goodsData.map((item) => ({
...item,
promotionId: params.id,
startTime: params.startTime,
endTime: params.endTime,
}));
this.submitLoading = true;
editPintuan(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("修改拼团商品成功");
this.closeCurrentPage();
}
});
},
},
@@ -159,6 +322,22 @@ h4 {
border-left: 3px solid red;
}
.operation {
margin-bottom: 10px;
display: flex;
gap: 10px;
}
.select-alert {
margin-bottom: 10px;
.select-clear {
margin-left: 12px;
color: #409eff;
cursor: pointer;
}
}
.link-text {
color: #409eff;
cursor: pointer;
@@ -173,6 +352,16 @@ h4 {
cursor: pointer;
}
.qrcode-icon {
vertical-align: middle;
}
.foot-btn {
margin-top: 16px;
display: flex;
gap: 10px;
}
.mt_10 {
margin-top: 10px;
}

View File

@@ -47,6 +47,9 @@
</el-card>
<el-card>
<div class="operation padding-row mb_10">
<el-button type="primary" @click="newAct">添加</el-button>
</div>
<el-table
ref="table"
v-loading="loading"
@@ -71,13 +74,23 @@
<el-table-column prop="storeName" label="所属店铺" min-width="120" show-overflow-tooltip />
<el-table-column prop="startTime" label="活动开始时间" width="180" />
<el-table-column prop="endTime" label="活动结束时间" width="180" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<el-table-column label="操作" width="220" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a class="link-text" @click="view(row)">查看</a>
<template v-if="row.promotionStatus === 'START' || row.promotionStatus === 'NEW'">
<template v-if="row.promotionStatus === 'NEW'">
<a class="link-text" @click="edit(row)">编辑</a>
<span class="op-split">|</span>
<a class="link-text" @click="close(row)">关闭</a>
<a class="link-text" @click="manage(row, 'manager')">管理</a>
</template>
<template v-else-if="row.promotionStatus !== 'CLOSE'">
<a class="link-text" @click="manage(row, 'view')">查看</a>
<template v-if="row.promotionStatus === 'START'">
<span class="op-split">|</span>
<a class="link-text" @click="close(row)">关闭</a>
</template>
</template>
<template v-else>
<a class="link-text" @click="open(row)">开启</a>
</template>
</template>
</template>
@@ -101,7 +114,7 @@
</template>
<script>
import { getPintuanList, deletePintuan, editPintuanStatus } from "@/api/promotion";
import { getPintuanList, editPintuanStatus } from "@/api/promotion";
export default {
name: "pintuan",
data() {
@@ -176,8 +189,37 @@ export default {
this.loading = false;
});
},
view(v) {
this.$router.push({ name: "pintuan-goods", query: { id: v.id } });
newAct() {
this.$router.push({ name: "pintuan-edit" });
},
edit(v) {
this.$router.push({ name: "pintuan-edit", query: { id: v.id } });
},
manage(v, status) {
this.$router.push({ name: "pintuan-goods", query: { id: v.id, status } });
},
open(v) {
const sTime = new Date();
sTime.setMinutes(sTime.getMinutes() + 10);
const eTime = new Date(new Date().setHours(0, 0, 0, 0) + 24 * 60 * 60 * 1000 - 1);
const params = {
startTime: sTime.getTime(),
endTime: eTime.getTime(),
};
this.$Modal.confirm({
title: "确认开启",
content: "您确认要开启此拼团活动?(默认为当前时间后十分钟至当天结束)",
loading: true,
onOk: () => {
editPintuanStatus(v.id, params).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("开启活动成功");
this.getDataList();
}
});
},
});
},
close(v) {
this.$Modal.confirm({
@@ -185,10 +227,10 @@ export default {
content: "您确认要关闭此拼团活动?",
loading: true,
onOk: () => {
updatePintuanStatus(v.id).then((res) => {
editPintuanStatus(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.$Message.success("关闭活动成功");
this.getDataList();
}
});
@@ -206,4 +248,19 @@ export default {
.mt_10 {
margin-top: 10px;
}
.mb_10 {
margin-bottom: 10px;
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.op-split {
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -25,58 +25,155 @@
</el-table-column>
</el-table>
<el-table
ref="table"
v-loading="loading"
border
class="operation"
:data="goodsList"
style="width: 100%"
>
<el-table-column prop="goodsName" label="商品名称" min-width="120" show-overflow-tooltip />
<el-table-column label="商品价格" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.originalPrice, "") }}</span>
</template>
</el-table-column>
<el-table-column label="库存" width="90">
<template #default="{ row }">
<span v-if="row">{{ row.quantity }}</span>
</template>
</el-table-column>
<el-table-column label="活动价格" width="100">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.price, "") }}</span>
</template>
</el-table-column>
<el-table-column prop="storeName" label="商家名称" min-width="100" show-overflow-tooltip />
<el-table-column label="活动场次" width="100">
<template #default="{ row }">
<el-tag v-if="row">{{ row.timeLine + ":00" }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="{ row, $index }">
<a v-if="row" class="link-text" @click="delGoods($index, row)">删除</a>
</template>
</el-table-column>
</el-table>
<template v-if="!readonly">
<div class="operation">
<el-button type="primary" @click="openSkuList">选择商品</el-button>
</div>
<el-tabs v-model="tabCurrent" type="card" class="operation">
<el-tab-pane
v-for="(tab, tabIndex) in goodsList"
:key="tabIndex"
:label="tab.hour"
:name="String(tabIndex)"
>
<el-table
v-loading="loading"
border
:data="tab.list"
style="width: 100%"
>
<el-table-column prop="goodsName" label="商品名称" min-width="140" show-overflow-tooltip />
<el-table-column label="商品价格" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.originalPrice, "") }}
</span>
</template>
</el-table-column>
<el-table-column label="库存" width="130">
<template #default="{ row, $index }">
<el-input-number
v-if="row"
v-model="row.quantity"
:min="1"
:disabled="row.promotionApplyStatus === 'PASS'"
controls-position="right"
size="small"
@change="(val) => updateGoodsField(tabIndex, $index, 'quantity', val)"
/>
</template>
</el-table-column>
<el-table-column label="活动价格" width="140">
<template #default="{ row, $index }">
<el-input-number
v-if="row"
v-model="row.price"
:min="0.01"
:precision="2"
:disabled="row.promotionApplyStatus === 'PASS'"
controls-position="right"
size="small"
@change="(val) => updateGoodsField(tabIndex, $index, 'price', val)"
/>
</template>
</el-table-column>
<el-table-column label="状态" width="120">
<template #default="{ row }">
<template v-if="row">
<el-tag :type="applyStatusTagType(row.promotionApplyStatus)" size="small">
{{ applyStatusText(row.promotionApplyStatus) }}
</el-tag>
<a
v-if="row.promotionApplyStatus === 'REFUSE' && row.failReason"
class="reason link-text"
@click="showReason(row.failReason)"
>
拒绝原因
</a>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ row, $index }">
<a v-if="row" class="link-text" @click="delGoods(tabIndex, $index, row)">删除</a>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</template>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
<template v-else>
<el-table
ref="table"
v-loading="loading"
border
class="operation"
:data="readonlyGoodsList"
style="width: 100%"
>
<el-table-column prop="goodsName" label="商品名称" min-width="140" show-overflow-tooltip />
<el-table-column label="商品价格" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.originalPrice, "") }}
</span>
</template>
</el-table-column>
<el-table-column label="库存" width="90">
<template #default="{ row }">
<span v-if="row">{{ row.quantity }}</span>
</template>
</el-table-column>
<el-table-column label="活动价格" width="100">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.price, "") }}
</span>
</template>
</el-table-column>
<el-table-column label="活动场次" width="100">
<template #default="{ row }">
<el-tag v-if="row">{{ row.timeLine }}:00</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag v-if="row" :type="applyStatusTagType(row.promotionApplyStatus)" size="small">
{{ applyStatusText(row.promotionApplyStatus) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</template>
<div class="foot-btn">
<el-button @click="closeCurrentPage">返回</el-button>
<el-button
v-if="!readonly"
type="primary"
:loading="submitLoading"
@click="save"
>
提交
</el-button>
</div>
</el-card>
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoodsData" />
</div>
</template>
@@ -84,14 +181,21 @@
import {
seckillGoodsList,
seckillDetail,
setSeckillGoods,
delSeckillGoods,
} from "@/api/promotion.js";
import liliDialog from "@/views/lili-dialog";
export default {
components: {
liliDialog,
},
data() {
return {
tabCurrent: "0",
promotionStatus: "",
loading: false,
submitLoading: false,
searchForm: {
pageNumber: 1,
pageSize: 20,
@@ -99,85 +203,220 @@ export default {
total: 0,
data: [],
goodsList: [],
readonlyGoodsList: [],
};
},
computed: {
readonly() {
return this.$route.query.mode === "view" || this.promotionStatus !== "NEW";
},
tabIndex() {
return Number(this.tabCurrent) || 0;
},
},
methods: {
seckillStatusText(status) {
const map = {
NEW: "新建",
START: "开始",
END: "结束",
CLOSE: "废弃",
NEW: "未开始",
START: "开始",
END: "结束",
CLOSE: "已关闭",
};
return map[status] || status || "-";
},
seckillStatusTagType(status) {
const map = {
NEW: "danger",
NEW: "info",
START: "success",
END: "danger",
CLOSE: "danger",
};
return map[status] || "danger";
},
applyStatusText(status) {
const map = {
APPLY: "申请中",
PASS: "已通过",
REFUSE: "已拒绝",
};
return map[status] || "未申请";
},
applyStatusTagType(status) {
const map = {
APPLY: "warning",
PASS: "success",
REFUSE: "danger",
};
return map[status] || "info";
},
closeCurrentPage() {
this.$router.back();
},
init() {
this.getSeckillMsg();
},
changePage() {
this.getDataList();
this.getReadonlyGoodsList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
this.getReadonlyGoodsList();
},
getDataList() {
getSeckillMsg() {
seckillDetail(this.$route.query.id).then((res) => {
if (res.success && res.result) {
this.data = [res.result];
this.promotionStatus = res.result.promotionStatus;
if (this.readonly) {
this.getReadonlyGoodsList();
} else {
this.getManageGoodsList();
}
}
});
},
getManageGoodsList() {
if (!this.data[0]?.hours) {
return;
}
this.loading = true;
const hourValues = this.data[0].hours.split(",");
const hourLabels = this.unixHours(this.data[0].hours);
this.goodsList = hourLabels.map((hour, index) => ({
hour,
timeLine: hourValues[index],
list: [],
}));
seckillGoodsList({
seckillId: this.$route.query.id,
pageNumber: 1,
pageSize: 1000,
}).then((res) => {
this.loading = false;
if (res.success && res.result?.records?.length) {
res.result.records.forEach((item) => {
const slot = this.goodsList.find(
(g) => String(g.timeLine) === String(item.timeLine)
);
if (slot) {
slot.list.push(item);
}
});
}
});
},
getReadonlyGoodsList() {
this.loading = true;
this.searchForm.seckillId = this.$route.query.id;
seckillGoodsList(this.searchForm).then((res) => {
this.loading = false;
if (res.success && res.result) {
this.goodsList = res.result.records;
this.total = res.result.total;
this.readonlyGoodsList = res.result.records || [];
this.total = res.result.total || 0;
}
});
},
getSeckillMsg() {
seckillDetail(this.$route.query.id).then((res) => {
if (res.success && res.result) {
this.data = [];
this.data.push(res.result);
this.promotionStatus = res.result.promotionStatus;
this.getDataList();
openSkuList() {
const existing = this.goodsList[this.tabIndex]?.list || [];
this.$refs.liliDialog.goodsData = existing.map((item) => ({
...item,
id: item.skuId || item.id,
}));
this.$refs.liliDialog.open("goods");
},
selectedGoodsData(selected) {
const timeLine = this.goodsList[this.tabIndex]?.timeLine;
if (timeLine === undefined) {
return;
}
const existingMap = new Map(
(this.goodsList[this.tabIndex].list || []).map((item) => [item.skuId, item])
);
const list = selected.map((item) => {
const existed = existingMap.get(item.id);
if (existed) {
return existed;
}
return {
goodsName: item.goodsName,
price: item.price,
originalPrice: item.price,
promotionApplyStatus: item.promotionApplyStatus || "",
quantity: item.quantity,
seckillId: this.$route.query.id,
storeId: item.storeId,
storeName: item.storeName,
skuId: item.id,
timeLine,
};
});
this.goodsList[this.tabIndex].list = list;
},
updateGoodsField(tabIndex, index, field, value) {
if (this.goodsList[tabIndex]?.list?.[index]) {
this.goodsList[tabIndex].list[index][field] = value;
}
},
delGoods(tabIndex, index, row) {
if (row.promotionApplyStatus === "PASS") {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除该商品吗?删除后不可恢复",
onOk: () => {
delSeckillGoods({
seckillId: row.seckillId,
id: row.id,
}).then((res) => {
if (res.success) {
this.goodsList[tabIndex].list.splice(index, 1);
this.$Message.success("删除成功");
}
});
},
});
return;
}
this.goodsList[tabIndex].list.splice(index, 1);
this.$Message.success("删除成功");
},
save() {
const applyVos = [];
this.goodsList.forEach((slot) => {
slot.list.forEach((item) => {
applyVos.push(item);
});
});
if (!applyVos.length) {
this.$Message.warning("请先选择活动商品");
return;
}
this.submitLoading = true;
setSeckillGoods({
seckillId: this.$route.query.id,
applyVos,
}).then((res) => {
this.submitLoading = false;
if (res?.success) {
this.$Message.success("提交活动商品成功");
this.closeCurrentPage();
}
});
},
delGoods(index, row) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除该商品吗?删除后不可恢复",
onOk: () => {
const params = {
seckillId: row.seckillId,
id: row.id,
};
delSeckillGoods(params).then((res) => {
if (res.success) {
this.goodsList.splice(index, 1);
this.$Message.success("删除成功!");
}
});
},
showReason(reason) {
this.$Modal.info({
title: "拒绝原因",
content: reason,
});
},
unixDate(time) {
return this.$filters.unixToDate(new Date(time) / 1000);
},
unixHours(item) {
const hourArr = item.split(",");
for (let i = 0; i < hourArr.length; i++) {
hourArr[i] += ":00";
if (!item) {
return [];
}
return hourArr;
return item.split(",").map((hour) => `${hour}:00`);
},
},
mounted() {
@@ -188,7 +427,7 @@ export default {
<style lang="scss" scoped>
.operation {
margin: 10px 0;
margin: 16px 0 10px;
}
.hour-tag {
@@ -202,6 +441,18 @@ export default {
text-decoration: none;
}
.reason {
display: block;
margin-top: 4px;
font-size: 12px;
}
.foot-btn {
margin-top: 16px;
display: flex;
gap: 10px;
}
.mt_10 {
margin-top: 10px;
}

View File

@@ -47,102 +47,74 @@
</el-card>
<el-card>
<el-tabs v-model="activeTab" class="mt_10">
<el-tab-pane label="秒杀活动列表" name="list">
<el-table
ref="table"
v-loading="loading"
border
:data="data"
class="mt_10"
style="width: 100%"
>
<el-table-column
prop="promotionName"
label="活动名称"
min-width="140"
show-overflow-tooltip
/>
<el-table-column prop="startTime" label="开始时间" width="180" />
<el-table-column prop="applyEndTime" label="申请截止时间" width="180" />
<el-table-column label="活动状态" width="110">
<template #default="{ row }">
<el-tag v-if="row" :type="promotionStatusTagType(row.promotionStatus)">
{{ promotionStatusText(row.promotionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="seckillRule"
label="申请规则"
min-width="120"
show-overflow-tooltip
/>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a
v-if="row.promotionStatus === 'CLOSE' || row.promotionStatus === 'NEW'"
class="link-text"
@click="edit(row)"
>
编辑
</a>
<a v-else class="link-text" @click="manage(row)">查看</a>
<span v-if="row.promotionStatus" class="op-split">|</span>
<a
v-if="row.promotionStatus == 'NEW'"
class="link-text"
@click="manage(row)"
>
管理
</a>
<span v-if="row.promotionStatus == 'NEW'" class="op-split">|</span>
<a
v-if="row.promotionStatus == 'START' || row.promotionStatus == 'NEW'"
class="link-text"
@click="off(row)"
>
关闭
</a>
</template>
</template>
</el-table-column>
</el-table>
<el-table
ref="table"
v-loading="loading"
border
:data="data"
class="mt_10"
style="width: 100%"
>
<el-table-column
prop="promotionName"
label="活动名称"
min-width="140"
show-overflow-tooltip
/>
<el-table-column prop="startTime" label="开始时间" width="180" />
<el-table-column prop="applyEndTime" label="申请截止时间" width="180" />
<el-table-column label="活动状态" width="110">
<template #default="{ row }">
<el-tag v-if="row" :type="promotionStatusTagType(row.promotionStatus)">
{{ promotionStatusText(row.promotionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="seckillRule"
label="申请规则"
min-width="120"
show-overflow-tooltip
/>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a
v-if="row.promotionStatus === 'NEW'"
class="link-text"
@click="manage(row)"
>
管理
</a>
<a v-else class="link-text" @click="view(row)">查看</a>
</template>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-tab-pane>
<el-tab-pane label="秒杀活动设置" name="setup" lazy>
<setupSeckill />
</el-tab-pane>
</el-tabs>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import { seckillList } from "@/api/promotion";
import setupSeckill from "@/views/promotion/seckill/seckill-setup";
export default {
name: "seckill",
components: {
setupSeckill,
},
data() {
return {
activeTab: "list",
selectDate: [],
loading: true,
searchForm: {
@@ -191,40 +163,12 @@ export default {
this.searchForm.pageSize = 20;
this.getDataList();
},
edit(v) {
this.$router.push({ name: "manager-seckill-add", query: { id: v.id } });
view(v) {
this.$router.push({ name: "seckill-goods", query: { id: v.id, mode: "view" } });
},
manage(v) {
this.$router.push({ name: "seckill-goods", query: { id: v.id } });
},
off(v) {
this.$Modal.confirm({
title: "提示",
content: "您确定要下架该活动吗?",
onOk: () => {
updateSeckillStatus(v.id).then((res) => {
if (res.success) {
this.$Message.success("下架成功");
this.getDataList();
}
});
},
});
},
expire(v) {
this.$Modal.confirm({
title: "提示",
content: "您确定要作废该活动吗?",
onOk: () => {
delSeckill(v.id).then((res) => {
if (res.success) {
this.$Message.success("作废成功");
this.getDataList();
}
});
},
});
},
getDataList() {
this.loading = true;
if (this.selectDate && this.selectDate[0] && this.selectDate[1]) {
@@ -234,7 +178,7 @@ export default {
this.searchForm.startTime = null;
this.searchForm.endTime = null;
}
getSeckillList(this.searchForm)
seckillList(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result.records;