refactor(manager): 优化全局空状态、错误图片占位并升级遗留消息语法

- 增加表格与树组件全局空状态 (el-empty) 样式规范
- 补齐视图图片语义化 alt,并添加全局图片加载异常 SVG 占位兜底
- 将砍价/秒杀/自定义面板中的 $Message 兼容语法替换为 ElMessage
- 清理冗余依赖 vuex,将 sass-loader 调整至 devDependencies
This commit is contained in:
pikachu1995@126.com
2026-10-20 13:51:21 +08:00
parent 3cb60139d5
commit 8fd353a766
22 changed files with 123 additions and 63 deletions

View File

@@ -8,6 +8,7 @@ import { router } from "./router/index";
import i18n from "@/locale";
import { setupElementPlus } from "@/plugins/element";
import { setupLegacyMessage } from "@/utils/message";
import { setupImgFallback } from "@/utils/imgFallback";
import liliDialog from "@/components/lili-dialog";
import PriceColorScheme from "@/components/price-color-scheme.vue";
import { install as installVueQr } from "vue-qr";
@@ -43,6 +44,7 @@ const app = createApp(App);
setupElementPlus(app);
setupLegacyMessage(app);
setupImgFallback();
installVueQr(app);
app.use(router);

View File

@@ -119,3 +119,30 @@
#nprogress .peg {
box-shadow: 0 0 4px var(--el-color-primary, #409eff), 0 0 2px var(--el-color-primary, #409eff);
}
/* 统一空状态:表格 / 树 / 列表无数据时的展示(未自定义 #empty 插槽的页面均生效) */
.el-table__empty-block {
min-height: 160px;
}
.el-table__empty-text,
.el-tree__empty-text {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
line-height: 1.5;
color: #909399;
font-size: 14px;
&::before {
content: "";
display: block;
width: 64px;
height: 64px;
margin-bottom: 8px;
opacity: 0.7;
background: no-repeat center / contain
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23c0c4cc' stroke-width='2' stroke-linejoin='round'><path d='M8 36l8-20h32l8 20v12a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V36z'/><path d='M8 36h16a8 8 0 0 0 16 0h16'/></svg>");
}
}

View File

@@ -0,0 +1,31 @@
/**
* 全局图片加载失败占位:
* 在捕获阶段监听 <img> 的 error 事件(error 不冒泡),失败时替换为内联 SVG 占位图。
* - 每张图片只替换一次,占位图自身加载失败不会再次触发,避免死循环
* - 空 src 不处理
* - 需要自行处理 error 的图片可添加 data-no-fallback 属性跳过
*/
const PLACEHOLDER =
"data:image/svg+xml;utf8," +
encodeURIComponent(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='#c0c4cc' stroke-width='2' stroke-linejoin='round'>" +
"<rect width='64' height='64' fill='#f5f7fa' stroke='none'/>" +
"<rect x='14' y='18' width='36' height='28' rx='3'/>" +
"<circle cx='25' cy='28' r='3'/>" +
"<path d='M14 42l10-9 8 7 6-5 12 9'/></svg>"
);
export function setupImgFallback() {
document.addEventListener(
"error",
(event) => {
const el = event.target;
if (!el || el.tagName !== "IMG") return;
if (el.dataset.noFallback !== undefined || el.dataset.fallbackApplied) return;
if (!el.getAttribute("src")) return;
el.dataset.fallbackApplied = "1";
el.src = PLACEHOLDER;
},
true
);
}

View File

@@ -13,7 +13,7 @@
>
<div class="main-header">
<div class="header-logo-con">
<img :src="domainLogo" key="max-logo" />
<img alt="平台 Logo" :src="domainLogo" key="max-logo" />
</div>
<div class="header-avator-con">
<div class="flex flex-a-c user-module" style="height: 100%">

View File

@@ -140,7 +140,7 @@
<el-table-column prop="id" label="商品ID" width="200" show-overflow-tooltip />
<el-table-column label="商品图片" width="100" align="center">
<template #default="{ row }">
<img
<img alt="商品图片"
v-if="row && row.original"
:src="row.original"
style="height: 50px; width: 50px; object-fit: cover"

View File

@@ -94,7 +94,7 @@
<div class="info-list" style="overflow: hidden">
<div class="left-container">
<div class="product">
<img class="img" :src="infoData.goodsImage" />
<img alt="商品图片" class="img" :src="infoData.goodsImage" />
</div>
<div class="show">
<label>页面展示:</label>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-row class="header">
<img v-if="domainLogo" :src="domainLogo" class="logo" width="220px" />
<img alt="平台 Logo" v-if="domainLogo" :src="domainLogo" class="logo" width="220px" />
</el-row>
</div>
</template>

View File

@@ -213,7 +213,7 @@
<el-form ref="formRef" :model="form" :rules="ruleValidate" label-width="80px">
<el-input v-model="form.id" style="display: none" />
<el-form-item label="头像">
<img :src="form.face || defaultPic" class="face" />
<img alt="会员头像" :src="form.face || defaultPic" class="face" />
<el-button
type="primary"
link

View File

@@ -98,7 +98,7 @@
<el-dialog v-model="descFlag" :title="descTitle" width="500px" destroy-on-close>
<el-form ref="formValidateRef" :model="formValidate" :rules="ruleValidate" label-width="80px">
<el-form-item label="头像">
<img :src="formValidate.face" class="face" />
<img alt="会员头像" :src="formValidate.face" class="face" />
<el-button
type="primary"
link

View File

@@ -9,7 +9,7 @@
<dt>退货商品</dt>
<dd>
<div>
<img :src="afterSaleInfo.goodsImage" style="height: 60px">
<img alt="商品图片" :src="afterSaleInfo.goodsImage" style="height: 60px">
</div>
<a @click="linkTo(afterSaleInfo.goodsId, afterSaleInfo.skuId)">{{ afterSaleInfo.goodsName }}
</a><br>
@@ -59,7 +59,7 @@
<dd v-if="afterSaleImage == ''">暂无凭证</dd>
<dd v-else>
<div class="div-img" v-for="(item, index) in afterSaleImage" :key="index">
<img class="complain-img" :src="item" />
<img alt="投诉凭证" class="complain-img" :src="item" />
</div>
</dd>
</dl>

View File

@@ -195,7 +195,7 @@
<template #default="{ row }">
<div v-if="row" style="margin-top: 5px; height: 80px; display: flex">
<div>
<img :src="row.image" style="height: 60px; margin-top: 1px; width: 60px" />
<img alt="商品图片" :src="row.image" style="height: 60px; margin-top: 1px; width: 60px" />
</div>
<div style="margin-left: 13px">
<div class="div-zoom">

View File

@@ -16,9 +16,8 @@
</div>
<template v-if="activeTab === 'content'">
<div class="panel-card">
<div class="panel-card panel-card-fill">
<div class="section-title">内容设置</div>
<div class="section-tip">选定布局区域,在下方添加图片,建议添加比例一致的图片</div>
<div class="img-preview" v-if="item.img">
<img :src="item.img" alt="" />
</div>
@@ -57,7 +56,7 @@
v-model="res.options.paddingX"
label="左右边距"
:min="0"
:max="30"
:max="40"
/>
<slider-field
v-model="res.options.pageGap"
@@ -66,19 +65,19 @@
:max="100"
/>
</div>
<div class="panel-card">
<div class="panel-card panel-card-fill">
<div class="section-title">圆角设置</div>
<slider-field
v-model="res.options.borderRadius"
label="背景圆角"
:min="0"
:max="50"
:max="30"
/>
<slider-field
v-model="res.options.imgRadius"
label="内容圆角"
:min="0"
:max="50"
:max="30"
/>
</div>
</template>
@@ -102,6 +101,7 @@ import ColorField from "../controls/ColorField.vue";
import SliderField from "../controls/SliderField.vue";
import hotzone from "@/components/hotzone";
import ossManage from "@/views/sys/oss-manage/ossManage";
import { ElMessage } from "element-plus";
const DEFAULTS = {
bgColor: "#FFFFFF",
@@ -163,7 +163,7 @@ export default {
},
openHotzone() {
if (!this.item.img) {
this.$Message.warning("请先选择照片");
ElMessage.warning("请先选择照片");
return;
}
this.$refs.hotzone.open(this.item);
@@ -176,11 +176,12 @@ export default {
</script>
<style scoped lang="scss">
@import "../controls/panel-common.scss";
.panel-flex-one {
background: #f5f7fa;
margin: 0 -20px -120px;
padding: 0 12px 120px;
min-height: calc(100vh - 70px);
margin: 0 -20px;
padding: 0 12px 12px;
min-height: 100%;
box-sizing: border-box;
}
.panel-header {
@@ -195,7 +196,7 @@ export default {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
box-sizing: border-box;
.panel-name {
font-size: 14px;
font-size: 16px;
font-weight: bold;
color: #303133;
}
@@ -228,13 +229,13 @@ export default {
}
}
.section-title {
font-size: 13px;
font-size: 14px;
font-weight: 600;
color: #303133;
margin: 0 0 12px;
}
.section-tip {
font-size: 12px;
font-size: 13px;
color: #909399;
margin-bottom: 10px;
}

View File

@@ -108,7 +108,7 @@ import { saveKanJiaActivityGoods } from "@/api/promotion";
import { regular } from "@/utils";
import skuSelect from "@/components/lili-dialog";
import { isECoupon } from "@/constants/goodsType";
import { Message as $Message } from "@/utils/message";
import { ElMessage } from "element-plus";
import { unixToDate } from "@/utils/filters";
import { useAppStore } from "@/stores/app";
@@ -169,7 +169,7 @@ function handleSubmit() {
return;
}
if (!form.promotionGoodsList.length) {
$Message.warning("请至少选择一个商品");
ElMessage.warning("请至少选择一个商品");
return;
}
@@ -186,13 +186,13 @@ function handleSubmit() {
const eCoupon = isECoupon(res.goodsType);
if (res.stock <= 0 || res.stock > res.quantity) {
checkResult = false;
$Message.error("活动库存不能为0且不能超过商品库存");
ElMessage.error("活动库存不能为0且不能超过商品库存");
return;
}
if (!regular.money.test(res.settlementPrice)) {
checkResult = false;
$Message.error("结算价格金额格式不正确");
ElMessage.error("结算价格金额格式不正确");
return;
}
if (
@@ -200,7 +200,7 @@ function handleSubmit() {
res.settlementPrice > res.price
) {
checkResult = false;
$Message.error(
ElMessage.error(
eCoupon
? "结算价格不能小于0且不能超过商品价格"
: "结算价格金额不能为0且不能超过商品价格"
@@ -209,27 +209,27 @@ function handleSubmit() {
}
if (!regular.money.test(res.highestPrice)) {
checkResult = false;
$Message.error("最高可砍金额格式错误");
ElMessage.error("最高可砍金额格式错误");
return;
}
if (res.highestPrice <= 0 || res.highestPrice > res.price) {
checkResult = false;
$Message.error("最高可砍金额不能为0且不能超过商品价格");
ElMessage.error("最高可砍金额不能为0且不能超过商品价格");
return;
}
if (!regular.money.test(res.lowestPrice)) {
checkResult = false;
$Message.error("最低可砍金额格式错误");
ElMessage.error("最低可砍金额格式错误");
return;
}
if (res.lowestPrice < 0 || (!eCoupon && res.lowestPrice <= 0) || res.lowestPrice > res.price) {
checkResult = false;
$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
ElMessage.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
return;
}
if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) {
checkResult = false;
$Message.error("最低砍价金额不能大于最高砍价金额");
ElMessage.error("最低砍价金额不能大于最高砍价金额");
return;
}
});
@@ -241,7 +241,7 @@ function handleSubmit() {
saveKanJiaActivityGoods(params).then((res) => {
submitLoading.value = false;
if (res.success) {
$Message.success(
ElMessage.success(
modalType.value === 1 ? "砍价活动修改成功" : "砍价活动添加成功"
);
closeCurrentPage();

View File

@@ -93,7 +93,7 @@ import { useRoute, useRouter } from "vue-router";
import { getKanJiaActivityGoodsById, editKanJiaActivityGoods } from "@/api/promotion";
import { regular } from "@/utils";
import { isECoupon } from "@/constants/goodsType";
import { Message as $Message } from "@/utils/message";
import { ElMessage } from "element-plus";
import { unixToDate } from "@/utils/filters";
import { useAppStore } from "@/stores/app";
@@ -202,7 +202,7 @@ function handleSubmit() {
const params = JSON.parse(JSON.stringify(form.value));
const [start, end] = form.value.rangeTime || [];
if (!start || !end) {
$Message.error("请选择活动时间");
ElMessage.error("请选择活动时间");
return;
}
const startDate = start instanceof Date ? start : new Date(start);
@@ -214,18 +214,18 @@ function handleSubmit() {
const eCoupon = isECoupon(params.goodsType || params.goodsSku?.goodsType);
if (params.stock <= 0 || params.stock > params.goodsSku.quantity) {
$Message.error("活动库存不能为0且不能超过商品库存");
ElMessage.error("活动库存不能为0且不能超过商品库存");
return;
}
if (!regular.money.test(params.settlementPrice)) {
$Message.error("结算价格金额格式不正确");
ElMessage.error("结算价格金额格式不正确");
return;
}
if (
(eCoupon ? params.settlementPrice < 0 : params.settlementPrice <= 0) ||
params.settlementPrice > params.price
) {
$Message.error(
ElMessage.error(
eCoupon
? "结算价格不能小于0且不能超过商品价格"
: "结算价格金额不能为0且不能超过商品价格"
@@ -233,15 +233,15 @@ function handleSubmit() {
return;
}
if (!regular.money.test(params.highestPrice)) {
$Message.error("最高可砍金额格式错误");
ElMessage.error("最高可砍金额格式错误");
return;
}
if (params.highestPrice <= 0 || params.highestPrice > params.price) {
$Message.error("最高可砍金额不能为0且不能超过商品价格");
ElMessage.error("最高可砍金额不能为0且不能超过商品价格");
return;
}
if (!regular.money.test(params.lowestPrice)) {
$Message.error("最低可砍金额格式错误");
ElMessage.error("最低可砍金额格式错误");
return;
}
if (
@@ -249,11 +249,11 @@ function handleSubmit() {
(!eCoupon && params.lowestPrice <= 0) ||
params.lowestPrice > params.price
) {
$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
ElMessage.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
return;
}
if (params.lowestPrice > params.highestPrice) {
$Message.error("最低砍价金额不能大于最高砍价金额");
ElMessage.error("最低砍价金额不能大于最高砍价金额");
return;
}
@@ -261,7 +261,7 @@ function handleSubmit() {
editKanJiaActivityGoods(params).then((res) => {
submitLoading.value = false;
if (res.success) {
$Message.success("砍价活动修改成功");
ElMessage.success("砍价活动修改成功");
closeCurrentPage();
}
});

View File

@@ -63,7 +63,7 @@
<el-table-column prop="goodsId" label="商品编号" width="180" show-overflow-tooltip />
<el-table-column label="商品图片" width="100" align="center">
<template #default="{ row }">
<img
<img alt="商品图片"
v-if="row && row.thumbnail"
:src="row.thumbnail"
style="height: 50px; width: 50px; object-fit: cover"

View File

@@ -41,7 +41,7 @@
<el-form-item label="分享卡片封面" prop="feedsImg">
<div class="form-field-block">
<div class="upload-list" v-if="liveForm.feedsImg">
<img :src="liveForm.feedsImg">
<img alt="直播间列表图" :src="liveForm.feedsImg">
<div class="upload-list-cover" @click="handleView(liveForm.feedsImg)">
<span class="view-icon">查看</span>
</div>
@@ -55,7 +55,7 @@
<el-form-item label="直播间背景墙" prop="coverImg">
<div class="form-field-block">
<div class="upload-list" v-if="liveForm.coverImg">
<img :src="liveForm.coverImg">
<img alt="直播间背景图" :src="liveForm.coverImg">
<div class="upload-list-cover" @click="handleView(liveForm.coverImg)">
<span class="view-icon">查看</span>
</div>
@@ -67,7 +67,7 @@
<el-form-item label="直播间分享图" prop="shareImg">
<div class="form-field-block">
<div class="upload-list" v-if="liveForm.shareImg">
<img :src="liveForm.shareImg">
<img alt="直播间分享图" :src="liveForm.shareImg">
<div class="upload-list-cover" @click="handleView(liveForm.shareImg)">
<span class="view-icon">查看</span>
</div>
@@ -83,7 +83,7 @@
<template #default="{ row, $index }">
<div v-if="row" class="flex-goods">
<el-badge v-if="$index == 0 || $index == 1" is-dot type="danger" />
<img class="thumbnail" :src="row.thumbnail || row.goodsImage">
<img alt="商品图片" class="thumbnail" :src="row.thumbnail || row.goodsImage">
{{ row.goodsName || row.name }}
</div>
</template>
@@ -119,7 +119,7 @@
</el-card>
<el-dialog v-model="imageVisible" title="查看图片" width="600px">
<img :src="imageSrc" v-if="imageVisible" style="width: 100%">
<img alt="预览图片" :src="imageSrc" v-if="imageVisible" style="width: 100%">
</el-dialog>
</div>
</template>

View File

@@ -72,7 +72,7 @@
<el-table-column prop="goodsId" label="商品编号" width="180" show-overflow-tooltip />
<el-table-column label="商品图片" width="100" align="center">
<template #default="{ row }">
<img
<img alt="商品图片"
v-if="row && row.thumbnail"
:src="row.thumbnail"
style="height: 50px; width: 50px; object-fit: cover"

View File

@@ -39,7 +39,7 @@
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { getSetting, setSetting } from "@/api/index";
import { Message as $Message } from "@/utils/message";
import { ElMessage } from "element-plus";
import { useAppStore } from "@/stores/app";
const router = useRouter();
@@ -72,7 +72,7 @@ async function handleSubmit() {
hours,
});
if (result.success) {
$Message.success("设置成功!");
ElMessage.success("设置成功!");
init();
}
}

View File

@@ -429,7 +429,7 @@
<template #default="{ row }">
<div v-if="row" style="margin-top: 5px; height: 80px; display: flex">
<div>
<img :src="row.goodsImage" style="height: 60px; margin-top: 3px" />
<img alt="商品图片" :src="row.goodsImage" style="height: 60px; margin-top: 3px" />
</div>
<div style="margin-left: 13px; margin-top: 3px">
<div class="div-zoom">

View File

@@ -11,7 +11,7 @@
<el-form-item label="后台Logo" prop="domainLogo">
<div class="label-item-upload">
<img v-if="formValidate.domainLogo" class="img" :src="formValidate.domainLogo" />
<img alt="平台 Logo" v-if="formValidate.domainLogo" class="img" :src="formValidate.domainLogo" />
<img v-else class="img" src="../../../../assets/emptyImg.png" alt="" />
<el-button @click="onClickImg('domainLogo')">选择图片</el-button>
<span class="desc">后台管理左上角logo展示 17∶6</span>
@@ -19,7 +19,7 @@
</el-form-item>
<el-form-item label="后台Icon" prop="domainIcon">
<div class="label-item-upload">
<img v-if="formValidate.domainIcon" class="img" :src="formValidate.domainIcon" />
<img alt="平台图标" v-if="formValidate.domainIcon" class="img" :src="formValidate.domainIcon" />
<img v-else class="img" src="../../../../assets/emptyImg.png" alt="" />
<el-button @click="onClickImg('domainIcon')">选择图片</el-button>
<span class="desc">后台管理网站icon展示 1∶1</span>
@@ -27,7 +27,7 @@
</el-form-item>
<el-form-item label="买家端Logo" prop="buyerSideLogo">
<div class="label-item-upload">
<img v-if="formValidate.buyerSideLogo" class="img" :src="formValidate.buyerSideLogo" />
<img alt="买家端 Logo" v-if="formValidate.buyerSideLogo" class="img" :src="formValidate.buyerSideLogo" />
<img v-else class="img" src="../../../../assets/emptyImg.png" alt="" />
<el-button @click="onClickImg('buyerSideLogo')">选择图片</el-button>
<span class="desc">买家端logo展示 17∶6</span>
@@ -35,7 +35,7 @@
</el-form-item>
<el-form-item label="买家端Icon" prop="buyerSideIcon">
<div class="label-item-upload">
<img v-if="formValidate.buyerSideIcon" class="img" :src="formValidate.buyerSideIcon" />
<img alt="买家端图标" v-if="formValidate.buyerSideIcon" class="img" :src="formValidate.buyerSideIcon" />
<img v-else class="img" src="../../../../assets/emptyImg.png" alt="" />
<el-button @click="onClickImg('buyerSideIcon')">选择图片</el-button>
<span class="desc">买家端网站icon展示 1∶1</span>
@@ -43,7 +43,7 @@
</el-form-item>
<el-form-item label="商家端Logo" prop="storeSideLogo">
<div class="label-item-upload">
<img v-if="formValidate.storeSideLogo" class="img" :src="formValidate.storeSideLogo" />
<img alt="商家端 Logo" v-if="formValidate.storeSideLogo" class="img" :src="formValidate.storeSideLogo" />
<img v-else class="img" src="../../../../assets/emptyImg.png" alt="" />
<el-button @click="onClickImg('storeSideLogo')">选择图片</el-button>
<span class="desc">商家端logo展示 17∶6</span>
@@ -51,7 +51,7 @@
</el-form-item>
<el-form-item label="商家端Icon" prop="storeSideIcon">
<div class="label-item-upload">
<img v-if="formValidate.storeSideIcon" class="img" :src="formValidate.storeSideIcon" />
<img alt="商家端图标" v-if="formValidate.storeSideIcon" class="img" :src="formValidate.storeSideIcon" />
<img v-else class="img" src="../../../../assets/emptyImg.png" alt="" />
<el-button @click="onClickImg('storeSideIcon')">选择图片</el-button>
<span class="desc">商家端icon展示 1∶1</span>

View File

@@ -3,7 +3,7 @@
<el-card class="sms-setting-card">
<div class="cardBox">
<div class="methodItem">
<img src="../../../assets/aliyun.png" height="172" width="440" />
<img alt="阿里云短信" src="../../../assets/aliyun.png" height="172" width="440" />
<h4>阿里云短信</h4>
</div>
<div class="bar">