@@ -59,7 +59,7 @@
diff --git a/manager/package.json b/manager/package.json
index 3687b5d8f..b4fcff22d 100644
--- a/manager/package.json
+++ b/manager/package.json
@@ -26,7 +26,6 @@
"nprogress": "^0.2.0",
"pinia": "^4.0.3",
"sass": "^1.63.6",
- "sass-loader": "^10.4.1",
"sockjs-client": "^1.4.0",
"swiper": "^6.3.5",
"uuid": "^8.3.2",
@@ -40,13 +39,13 @@
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.9",
"@vue/cli-plugin-router": "^5.0.9",
- "@vue/cli-plugin-vuex": "^5.0.9",
"@vue/cli-service": "^5.0.9",
"@vue/compiler-sfc": "^3.5.35",
"compression-webpack-plugin": "^10.0.0",
"css-loader": "^6.10.0",
"less": "^4.2.0",
"less-loader": "^11.1.4",
+ "sass-loader": "^10.4.1",
"style-loader": "^3.3.4",
"style-resources-loader": "^1.5.0",
"vue-cli-plugin-style-resources-loader": "^0.1.5",
diff --git a/manager/src/main.js b/manager/src/main.js
index f90eed1ff..f79e8d5bd 100644
--- a/manager/src/main.js
+++ b/manager/src/main.js
@@ -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);
diff --git a/manager/src/styles/element.scss b/manager/src/styles/element.scss
index 6448267f8..ef9625311 100644
--- a/manager/src/styles/element.scss
+++ b/manager/src/styles/element.scss
@@ -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,");
+ }
+}
diff --git a/manager/src/utils/imgFallback.js b/manager/src/utils/imgFallback.js
new file mode 100644
index 000000000..496e759b5
--- /dev/null
+++ b/manager/src/utils/imgFallback.js
@@ -0,0 +1,31 @@
+/**
+ * 全局图片加载失败占位:
+ * 在捕获阶段监听 的 error 事件(error 不冒泡),失败时替换为内联 SVG 占位图。
+ * - 每张图片只替换一次,占位图自身加载失败不会再次触发,避免死循环
+ * - 空 src 不处理
+ * - 需要自行处理 error 的图片可添加 data-no-fallback 属性跳过
+ */
+const PLACEHOLDER =
+ "data:image/svg+xml;utf8," +
+ encodeURIComponent(
+ ""
+ );
+
+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
+ );
+}
diff --git a/manager/src/views/Main.vue b/manager/src/views/Main.vue
index 358f4948b..b25a329c0 100644
--- a/manager/src/views/Main.vue
+++ b/manager/src/views/Main.vue
@@ -13,7 +13,7 @@
>