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

@@ -78,15 +78,35 @@
}
.sku-val {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
.sku-val-label {
margin-bottom: 10px;
font-weight: bold;
}
.sku-val-form {
flex-wrap: wrap !important;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
}
>.ivu-form {
flex-wrap: wrap !important;
}
:deep(.sku-item-content-val) {
margin-right: 20px;
margin-left: 0 !important;
width: auto !important;
min-width: auto !important;
.el-form-item__content {
margin-left: 0 !important;
}
}
}
@@ -151,9 +171,23 @@ div.base-info-item {
.sku-item-content-name {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
margin-bottom: 10px;
.sku-item-label {
font-weight: bold;
margin-bottom: 8px;
line-height: 1.5;
}
.sku-item-input-row {
display: flex;
align-items: center;
flex-wrap: wrap;
}
}
}
}

View File

@@ -114,14 +114,6 @@
>
批量下架
</el-button>
<el-button
v-if="currentStatus === 'TOBEAUDITED'"
type="primary"
:disabled="selectedRows.length === 0"
@click="batchAudit"
>
批量审核
</el-button>
</div>
<el-table
@@ -178,20 +170,15 @@
<el-table-column prop="storeName" label="店铺名称" width="200" show-overflow-tooltip />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row.authFlag === 'TOBEAUDITED'">
<a class="link-text" @click="openAuditModal(row)">审核</a>
<span class="op-split">|</span>
<a class="link-text" @click="showDetail(row)">查看</a>
</template>
<template v-else-if="row.marketEnable === 'DOWN'">
<template v-if="row.marketEnable === 'DOWN'">
<a class="link-text" @click="upper(row)">上架</a>
<span class="op-split">|</span>
<a class="link-text" @click="showDetail(row)">查看</a>
<a class="link-text" @click="editGoods(row)">编辑</a>
</template>
<template v-else>
<a class="link-text" @click="edit(row)">下架</a>
<span class="op-split">|</span>
<a class="link-text" @click="showDetail(row)">查看</a>
<a class="link-text" @click="editGoods(row)">编辑</a>
</template>
</template>
</el-table-column>
@@ -223,49 +210,6 @@
</template>
</el-dialog>
<el-dialog v-model="auditModalVisible" title="商品审核" width="500px" :close-on-click-modal="false">
<el-form ref="auditForm" :model="goodsAuditForm" label-width="100px">
<el-form-item label="审核结果" prop="auth_flag">
<el-radio-group v-model="goodsAuditForm.auth_flag">
<el-radio :value="1">审核通过</el-radio>
<el-radio :value="2">审核拒绝</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditModalVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAudit">提交审核</el-button>
</template>
</el-dialog>
<el-dialog
v-model="batchAuditModalVisible"
title="批量商品审核"
width="500px"
:close-on-click-modal="false"
>
<el-form ref="batchAuditForm" :model="batchAuditForm" label-width="100px">
<el-form-item label="审核结果" prop="auth_flag">
<el-radio-group v-model="batchAuditForm.auth_flag">
<el-radio :value="1">审核通过</el-radio>
<el-radio :value="2">审核拒绝</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="batchAuditForm.auth_flag === 2" label="审核备注" prop="reason">
<el-input v-model="batchAuditForm.reason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
</el-form-item>
<el-form-item label="选中商品">
<div style="max-height: 200px; overflow-y: auto">
<el-tag v-for="item in selectedRows" :key="item.id" style="margin: 2px">{{ item.goodsName }}</el-tag>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="batchAuditModalVisible = false">取消</el-button>
<el-button type="primary" @click="submitBatchAudit">提交审核</el-button>
</template>
</el-dialog>
<el-dialog v-model="goodsGroupFlag" title="批量设置商品分组" width="420px">
<el-form ref="goodsGroupForm" :model="goodsGroupForm" :rules="goodsGroupRule" label-width="90px">
<el-form-item label="商品分组" prop="groupId">
@@ -295,7 +239,6 @@ import {
getGoodsNumerData,
upGoods,
lowGoods,
authGoods,
getGoodsGroupByPage,
addGoodsGroupItems
} from "@/api/goods";
@@ -318,12 +261,6 @@ export default {
// 下架原因
reason: "",
},
goodsAuditForm: {
// 商品审核表单
auth_flag: 1,
},
auditModalVisible: false, // 审核弹框显示状态
currentAuditGoods: null, // 当前审核的商品
submitLoading: false, // 添加或编辑提交状态
data: [], // 表单数据
total: 0, // 表单数据总数
@@ -331,11 +268,6 @@ export default {
goodsNumerData: {},
selectedRows: [], // 选中的行数据
selectAll: false, // 全选状态
batchAuditModalVisible: false, // 批量审核弹框显示状态
batchAuditForm: {
auth_flag: 1,
reason: ''
},
goodsGroupFlag: false,
goodsGroupLoading: false,
goodsGroupList: [],
@@ -477,10 +409,10 @@ export default {
});
},
//查看商品详情
showDetail(v) {
// 编辑商品
editGoods(v) {
this.$filters.customRouterPush({
name: "goods-detail",
name: "goods-operation-edit",
query: { id: v.id },
});
},
@@ -498,73 +430,6 @@ export default {
this.clearTableSelection();
this.getDataList();
},
examine(v, authFlag) {
// 审核商品
let examine = "通过";
this.goodsAuditForm.authFlag = "PASS";
if (authFlag != 1) {
examine = "拒绝";
this.goodsAuditForm.authFlag = "REFUSE";
}
this.$Modal.confirm({
title: "确认审核",
content: "您确认要审核" + examine + " " + v.goodsName + " ?",
loading: true,
onOk: () => {
this.goodsAuditForm.goodsIds=v.id;
let formData = new FormData();
formData.append('goodsIds', v.id);
formData.append('authFlag', this.goodsAuditForm.authFlag);
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("审核成功");
this.getDataList();
this.getNumberData();
}
});
},
});
},
// 打开审核弹框
openAuditModal(goods) {
this.currentAuditGoods = goods;
this.goodsAuditForm.auth_flag = 1;
this.goodsAuditForm.reason = '';
this.auditModalVisible = true;
},
// 确认审核(二次确认)
confirmAudit() {
const auditText = this.goodsAuditForm.auth_flag === 1 ? '通过' : '拒绝';
this.$Modal.confirm({
title: '确认审核',
content: `您确认要审核${auditText} "${this.currentAuditGoods.goodsName}" 吗?`,
loading: true,
onOk: () => {
this.submitAudit();
},
});
},
// 提交审核
submitAudit() {
let formData = new FormData();
formData.append('goodsIds', this.currentAuditGoods.id);
formData.append('authFlag', this.goodsAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success('审核成功');
this.auditModalVisible = false;
this.getDataList();
this.getNumberData();
}
});
},
onSelectionChange(selection) {
this.selectedRows = selection;
@@ -640,68 +505,6 @@ export default {
}
});
},
// 批量审核
batchAudit() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要审核的商品');
return;
}
// 重置批量审核表单
this.batchAuditForm = {
auth_flag: 1,
reason: ''
};
this.batchAuditModalVisible = true;
},
// 提交批量审核
submitBatchAudit() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要审核的商品');
return;
}
// 如果是拒绝审核,必须填写原因
if (this.batchAuditForm.auth_flag === 2 && !this.batchAuditForm.reason.trim()) {
this.$Message.warning('审核拒绝时必须填写拒绝原因');
return;
}
const actionText = this.batchAuditForm.auth_flag === 1 ? '通过' : '拒绝';
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
this.$Modal.confirm({
title: `确认批量审核${actionText}`,
content: `您确认要${actionText}以下商品的审核吗?\n${goodsNames}`,
loading: true,
onOk: () => {
// 提取所有选中商品的ID
const goodsIds = this.selectedRows.map(item => item.id);
let formData = new FormData();
formData.append('goodsId', goodsIds);
formData.append('authFlag', this.batchAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
formData.append('reason', this.batchAuditForm.reason || '');
// 修正直接调用authGoods不传递'batch'参数
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success(`批量审核${actionText}成功`);
this.selectedRows = [];
this.selectAll = false;
this.batchAuditModalVisible = false;
this.getDataList();
this.getNumberData();
}
}).catch(() => {
this.$Modal.remove();
});
}
});
},
loadGoodsGroupList() {
getGoodsGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {

View File

@@ -17,6 +17,12 @@
</div>
</div>
</div>
<template #footer>
<div class="goods-type-actions">
<el-button @click="cancelGoodsType">取消</el-button>
<el-button type="primary" @click="confirmGoodsType">确认</el-button>
</div>
</template>
</el-dialog>
<!-- 商品分类 -->
<div class="content-goods-publish">
@@ -64,7 +70,7 @@
<!-- 底部按钮 -->
<div class="footer">
<div class="footer-btns">
<el-button type="primary" @click="selectGoodsType = true">商品类型</el-button>
<el-button type="primary" @click="openGoodsTypeDialog">商品类型</el-button>
<el-button type="primary" @click="next">下一步</el-button>
</div>
</div>
@@ -100,6 +106,7 @@ export default {
],
// 商品类型
goodsType: "",
pendingGoodsType: "",
/** 1级分类列表*/
categoryListLevel1: [],
/** 2级分类列表*/
@@ -108,15 +115,41 @@ export default {
categoryListLevel3: [],
};
},
watch: {
selectGoodsType(val) {
if (val) {
this.syncGoodsTypeSelection();
}
},
},
methods: {
// 点击商品类型
handleClickGoodsType(val) {
this.goodsTypeWay.map((item) => {
return (item.check = false);
syncGoodsTypeSelection() {
this.pendingGoodsType = this.goodsType;
this.goodsTypeWay.forEach((item) => {
item.check = item.type === this.goodsType;
});
val.check = !val.check;
this.goodsType = val.type;
},
openGoodsTypeDialog() {
this.selectGoodsType = true;
},
// 点击商品类型(仅临时选中,确认后才生效)
handleClickGoodsType(val) {
this.goodsTypeWay.forEach((item) => {
item.check = item.type === val.type;
});
this.pendingGoodsType = val.type;
},
cancelGoodsType() {
this.syncGoodsTypeSelection();
this.selectGoodsType = false;
},
confirmGoodsType() {
if (!this.pendingGoodsType) {
this.$Message.error("请选择商品类型");
return;
}
this.goodsType = this.pendingGoodsType;
this.selectGoodsType = false;
},
/** 选择商城商品分类 */
handleSelectCategory(row, index, level) {
@@ -177,4 +210,22 @@ export default {
</script>
<style lang="scss" scoped>
@import "./addGoods.scss";
.footer {
display: flex;
justify-content: center;
}
.footer-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.goods-type-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
width: 100%;
}
</style>

View File

@@ -120,19 +120,23 @@
<div class="form-item-view">
<el-form-item class="form-item-view-el required" label="主图" prop="goodsGalleryFiles">
<div style="display: flex; flex-wrap: wrap;">
<vuedraggable :animation="200" :list="baseInfoForm.goodsGalleryFiles">
<div v-for="(item, __index) in baseInfoForm.goodsGalleryFiles" :key="__index"
class="demo-upload-list">
<template>
<img :src="item" />
<vuedraggable
v-model="baseInfoForm.goodsGalleryFiles"
:animation="200"
:item-key="draggableItemKey"
style="display: flex; flex-wrap: wrap"
>
<template #item="{ element }">
<div class="demo-upload-list">
<img :src="element" />
<div class="demo-upload-list-cover">
<div>
<el-button link type="primary" @click="handleViewGoodsPicture(item)">预览</el-button>
<el-button link type="danger" @click="handleRemoveGoodsPicture(item)">删除</el-button>
<el-button link type="primary" @click="handleViewGoodsPicture(element)">预览</el-button>
<el-button link type="danger" @click="handleRemoveGoodsPicture(element)">删除</el-button>
</div>
</div>
</template>
</div>
</div>
</template>
</vuedraggable>
<!--<Upload ref="upload"-->
<!--:action="uploadFileUrl" :before-upload="handleBeforeUploadGoodsPicture"-->
@@ -190,39 +194,34 @@
<template #header>
<el-button link type="danger" @click="handleCloseSkuItem($index, item)">删除规格项</el-button>
</template>
<div>
<div style="display: flex;margin-bottom: 10px;font-weight: bold">规格项</div>
<el-form-item class="sku-item-content-val flex" label="">
<div>
<el-autocomplete
v-model="item.name"
:fetch-suggestions="(q, cb) => cb([])"
maxlength="30"
placeholder="请输入规格项名称"
style="width: 150px"
@focus="changeSkuItem(item.name)"
@change="editSkuItem(item.name, $index, item)"
/>
<el-switch
v-if="$index === 0"
v-model="openImage"
style="margin-left: 10px"
@change="changeSkuOpenImage"
/>
<span v-if="$index === 0" style="margin-left: 5px">添加规格图片</span>
</div>
</el-form-item>
<div class="sku-item-content-name">
<div class="sku-item-label">规格项</div>
<div class="sku-item-input-row">
<el-autocomplete
v-model="item.name"
:fetch-suggestions="(q, cb) => cb([])"
maxlength="30"
placeholder="请输入规格项名称"
style="width: 150px"
@focus="changeSkuItem(item.name)"
@change="editSkuItem(item.name, $index, item)"
/>
<el-switch
v-if="$index === 0"
v-model="openImage"
style="margin-left: 10px"
@change="changeSkuOpenImage"
/>
<span v-if="$index === 0" style="margin-left: 5px">添加规格图片</span>
</div>
</div>
<div class="sku-val">
<div style="margin-bottom: 10px;font-weight: bold;display: flex">规格值 (输入完成后,鼠标点击其他地方后生效)</div>
<el-form :model="item" class="flex">
<div class="sku-val-label">规格值 (输入完成后,鼠标点击其他地方后生效)</div>
<el-form :model="item" class="sku-val-form flex">
<!--规格值文本列表-->
<el-form-item v-for="(val, index) in item.spec_values" :key="index"
class="sku-item-content-val flex" label="" style="line-height: 32px;">
<div style="display: flex; justify-content: center; align-items: center;">
<div style="display: flex; justify-content: flex-start; align-items: center;">
<el-autocomplete
@@ -244,19 +243,26 @@
<!-- 内联错误提示 -->
<div v-if="val._error" class="sku-inline-error">{{ val._error }}</div>
<div v-if="$index === 0 && openImage" style="margin-top: 10px">
<vuedraggable :animation="200" :list="val.images">
<div v-for="(img, __index) in val.images" :key="__index" class="sku-upload-list"
style="width: 180px;height: 140px">
<template>
<img :src="img" style="width: 180px;height: 140px" />
<vuedraggable
v-model="val.images"
:animation="200"
:item-key="draggableItemKey"
style="display: flex; flex-wrap: wrap"
>
<template #item="{ element, index: imgIndex }">
<div
class="sku-upload-list"
style="width: 180px;height: 140px"
>
<img :src="element" style="width: 180px;height: 140px" />
<div class="sku-upload-list-cover">
<div style="margin-top: 50px">
<el-icon :size="25" style="cursor:pointer;margin-right:8px" @click="handleView(img)"><ZoomIn /></el-icon>
<el-icon :size="25" style="cursor:pointer" @click="handleRemove(val.images, __index)"><Delete /></el-icon>
<el-icon :size="25" style="cursor:pointer;margin-right:8px" @click="handleView(element)"><ZoomIn /></el-icon>
<el-icon :size="25" style="cursor:pointer" @click="handleRemove(val.images, imgIndex)"><Delete /></el-icon>
</div>
</div>
</template>
</div>
</div>
</template>
</vuedraggable>
<el-upload
v-if="val.images.length < 1"
@@ -311,21 +317,21 @@
:prop="col.key"
min-width="120"
>
<template #default="{ row }">
<template #default="{ row, $index }">
<span v-if="col.key && !col.slot">{{ row[col.key] }}</span>
<el-input
v-else-if="col.slot === 'sn'"
v-model="row.sn"
clearable
placeholder="请输入货号"
@change="updateSkuTable(row, 'sn')"
@change="updateSkuTable(row, 'sn', $index)"
/>
<el-input
v-else-if="col.slot === 'weight' && baseInfoForm.goodsType !== 'VIRTUAL_GOODS'"
v-model="row.weight"
clearable
placeholder="请输入重量"
@change="updateSkuTable(row, 'weight')"
@change="updateSkuTable(row, 'weight', $index)"
>
<template #append>kg</template>
</el-input>
@@ -334,7 +340,7 @@
v-model="row.quantity"
clearable
placeholder="请输入库存"
@change="updateSkuTable(row, 'quantity')"
@change="updateSkuTable(row, 'quantity', $index)"
>
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
</el-input>
@@ -343,7 +349,7 @@
v-model="row.cost"
clearable
placeholder="请输入成本价"
@change="updateSkuTable(row, 'cost')"
@change="updateSkuTable(row, 'cost', $index)"
>
<template #append>元</template>
</el-input>
@@ -352,7 +358,7 @@
v-model="row.price"
clearable
placeholder="请输入价格"
@change="updateSkuTable(row, 'price')"
@change="updateSkuTable(row, 'price', $index)"
>
<template #append>元</template>
</el-input>
@@ -414,15 +420,19 @@
</el-form-item>
</div>
<el-form-item class="form-item-view-el" label="PC商品描述" prop="intro" style="width: 100%">
<editor ref="editor" v-model="baseInfoForm.intro" height="800px" openXss></editor>
<div class="promise-intro-btn">
<el-button type="primary" @click="promiseIntroEditor">将PC商品描述同步到移动端描述
</el-button>
<div class="intro-editor-field">
<editor ref="editor" v-model="baseInfoForm.intro" height="800px" openXss></editor>
<div class="promise-intro-btn">
<el-button type="primary" @click="promiseIntroEditor">将PC商品描述同步到移动端描述
</el-button>
</div>
</div>
</el-form-item>
<el-form-item class="form-item-view-el" label="移动端描述" prop="skuList" style="width: 100%">
<editor ref="introEditor" v-model="baseInfoForm.mobileIntro" height="800px" openXss></editor>
<div class="intro-editor-field">
<editor ref="introEditor" v-model="baseInfoForm.mobileIntro" height="800px" openXss></editor>
</div>
</el-form-item>
</div>
<div v-if="baseInfoForm.goodsType != 'VIRTUAL_GOODS'">
@@ -669,7 +679,7 @@ export default {
// 某一规格名下的规格值
skuVal: [],
// 规格展开的项
open_panel: [1, 2],
open_panel: ["1", "2"],
/** 要提交的规格数据*/
skuInfo: [],
/** 物流模板 **/
@@ -736,12 +746,18 @@ export default {
}
},
methods: {
draggableItemKey(item) {
return item;
},
// 选择图片modal
handleCLickImg(val, index) {
this.$refs.ossManage.selectImage = true;
this.picModelFlag = true;
this.selectedFormBtnName = val;
// this.picIndex = index;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
handleLoadingSkuData() {
this.needToloadSku = false
@@ -908,11 +924,21 @@ export default {
this.baseInfoForm.goodsGalleryFiles =
this.baseInfoForm.goodsGalleryFiles.filter((i) => i !== file);
},
assignSkuTableIndex() {
this.skuTableData.forEach((row, index) => {
row._index = index;
});
},
// 更新sku图片
updateSkuPicture() {
this.baseInfoForm.regeneratorSkuFlag = true;
let _index = this.selectedSku._index;
this.skuTableData[_index] = this.selectedSku;
if (_index === undefined || _index === null) {
_index = this.skuTableData.indexOf(this.selectedSku);
}
if (_index >= 0) {
this.skuTableData[_index] = this.selectedSku;
}
},
// sku图片上传成功
handleSuccess(res, file, images) {
@@ -1251,13 +1277,12 @@ export default {
},
// 将pc商品描述同步给移动端
promiseIntroEditor() {
const pcContent = this.$refs.editor?.getContent?.() ?? this.baseInfoForm.intro ?? "";
this.baseInfoForm.intro = pcContent;
this.baseInfoForm.mobileIntro = pcContent;
this.$nextTick(() => {
this.$refs.introEditor.setContent(this.baseInfoForm.intro);
})
this.baseInfoForm.mobileIntro = this.baseInfoForm.intro;
this.$forceUpdate();
this.$refs.introEditor?.setContent?.(pcContent);
});
},
/** 根据当前分类id查询商品应包含的参数 */
@@ -1830,6 +1855,7 @@ export default {
};
}
});
this.assignSkuTableIndex();
}
},
@@ -1967,8 +1993,18 @@ export default {
return option.toUpperCase().indexOf(value.toUpperCase()) !== -1;
},
/** 数据改变之后 抛出数据 */
updateSkuTable(row, item, type = "default") {
let index = row._index;
updateSkuTable(row, item, rowIndex) {
let index = rowIndex;
if (index === undefined || index === null) {
index = row._index;
}
if (index === undefined || index === null) {
index = this.skuTableData.indexOf(row);
}
if (index < 0 || !this.skuTableData[index]) {
return;
}
row._index = index;
this.baseInfoForm.regeneratorSkuFlag = true;
/** 进行自定义校验 判断是否是数字(小数也能通过)重量 */
if (item === "weight") {
@@ -1994,35 +2030,11 @@ export default {
return;
}
}
// else if (item === "alertQuantity") {
// if (
// !/^[0-9]\d*$/.test(row[item]) ||
// parseInt(row[item]) < 0 ||
// parseInt(row[item]) > 99999999
// ) {
// // 库存预警
// this.validateError.push([index, item]);
// this.validatatxt = "请输入0~99999999之间的整数";
// return;
// }
// }
// else if (item === "cost" || item === "price") {
// if (
// !regular.money.test(row[item]) ||
// parseInt(row[item]) < 0 ||
// parseInt(row[item]) > 99999999
// ) {
// // 成本价 价格
// this.validateError.push([index, item]);
// this.validatatxt = "请输入0~99999999之间的价格";
// return;
// }
// }
this.$nextTick(() => {
this.skuTableData[index][item] = row[item];
if (this.skuTableData[index]) {
this.skuTableData[index][item] = row[item];
}
});
// // migrated
(this.skuTableData,[index][item],row[item])
},
// 店内分类选择
selectTree(v) {
@@ -2285,6 +2297,26 @@ export default {
text-align: left;
}
.intro-editor-field {
width: calc(100% - 400px);
}
.promise-intro-btn {
margin-top: 12px;
text-align: left;
}
.footer {
display: flex;
justify-content: center;
}
.footer-btns {
display: flex;
gap: 12px;
justify-content: center;
}
/* .tox-notifications-container{