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

@@ -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;
}