diff --git a/App.uvue b/App.uvue
index c394e95c..8bc9ebcf 100644
--- a/App.uvue
+++ b/App.uvue
@@ -9,6 +9,9 @@
diff --git a/layouts/admin/store/adminNavStore.uts b/layouts/admin/store/adminNavStore.uts
index 62305e0c..4779587e 100644
--- a/layouts/admin/store/adminNavStore.uts
+++ b/layouts/admin/store/adminNavStore.uts
@@ -38,8 +38,18 @@ export const tabs = ref([])
/** 是否折叠主侧边栏 */
export const isMainAsideCollapsed = ref(false)
+/** 屏幕宽度 */
+export const windowWidth = ref(1024)
+
+/** 是否为移动端布局 (width < 768) */
+export const isMobile = computed(() => windowWidth.value < 768)
+
+/** 移动端菜单是否展开 */
+export const isMobileMenuOpen = ref(false)
+
/** 是否显示二级侧边栏 */
export const showSubSider = computed(() => {
+ if (isMobile.value) return false // 移动端不显式二级侧边栏在主体区域
const topMenus = getTopMenus()
const activeMenu = topMenus.find(m => m.id === activeTopMenuId.value)
return activeMenu ? activeMenu.groups.length > 0 : false
diff --git a/layouts/admin/styles/admin-responsive.css b/layouts/admin/styles/admin-responsive.css
new file mode 100644
index 00000000..4148ecb5
--- /dev/null
+++ b/layouts/admin/styles/admin-responsive.css
@@ -0,0 +1,36 @@
+/* 统一 KPI 统计网格响应式规范 */
+
+.kpi-grid {
+ display: grid !important;
+ gap: 16px;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+/* 规则 1:屏幕宽度 >= 1200px -> 固定 4 列 */
+@media (min-width: 1200px) {
+ .kpi-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
+ }
+}
+
+/* 规则 2:768px <= 屏幕宽度 < 1200px -> 固定 2 列 */
+@media (min-width: 768px) and (max-width: 1199.98px) {
+ .kpi-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
+ }
+}
+
+/* 规则 3:屏幕宽度 < 768px -> 固定 1 列 */
+@media (max-width: 767.98px) {
+ .kpi-grid {
+ grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
+ }
+}
+
+/* 强制子项允许收缩,防止内部长文本撑爆网格 */
+.kpi-grid > * {
+ min-width: 0 !important;
+ flex: none !important; /* 覆盖旧的 flex 逻辑 */
+ width: auto !important; /* 让 grid 控制宽度 */
+}
diff --git a/pages/mall/admin/docs/RESPONSIVE_LAYOUT_GUIDE.md b/pages/mall/admin/docs/RESPONSIVE_LAYOUT_GUIDE.md
new file mode 100644
index 00000000..b7618cce
--- /dev/null
+++ b/pages/mall/admin/docs/RESPONSIVE_LAYOUT_GUIDE.md
@@ -0,0 +1,85 @@
+# 管理后台响应式布局实现指南
+
+本文档总结了商城管理后台从固定布局到响应式布局的改造过程及核心技术点。
+
+## 1. 核心目标
+
+- **多终端适配**:确保后台在桌面端(宽屏)、平板端(中等屏幕)和移动端(窄屏)均能良好展示。
+- **自动适配状态管理**:系统能自动感知屏幕宽度并调整侧边栏显隐逻辑。
+- **布局平滑过渡**:侧边栏的收起、拉出及内容区的重排应具有良好的动画效果。
+
+## 2. 状态管理 (Store) 扩展
+
+在 [adminNavStore.uts](layouts/admin/store/adminNavStore.uts) 中引入了响应式状态:
+
+- `windowWidth`: 实时存储当前窗口宽度。
+- `isMobile`: 计算属性,当宽度小于 768px 时判定为移动端。
+- `isMobileMenuOpen`: 控制移动端模式下侧边栏的展开状态。
+
+```typescript
+export const windowWidth = ref(1024);
+export const isMobile = computed(() => windowWidth.value < 768);
+export const isMobileMenuOpen = ref(false);
+```
+
+## 3. 布局架构调整 (AdminLayout)
+
+[AdminLayout.uvue](layouts/admin/AdminLayout.uvue) 负责整体结构的响应式策略:
+
+### 3.1 监听并初始化
+
+在 `onMounted` 中初始化宽度并监听 `uni.onWindowResize`:
+
+```typescript
+onMounted(() => {
+ windowWidth.value = uni.getWindowInfo().windowWidth;
+ uni.onWindowResize((res) => {
+ windowWidth.value = res.size.windowWidth;
+ });
+});
+```
+
+### 3.2 侧边栏处理
+
+- **桌面端**:侧边栏常规展示,通过 `marginLeft` 为内容区腾出空间。
+- **移动端**:侧边栏通过 `position: absolute` 隐藏在屏幕外,通过 `translateX` 动画滑入。同时禁用二级侧边栏的常驻显示。
+
+### 3.3 移动端遮罩与切换
+
+- 引入 `mobile-mask` 遮罩层,点击遮罩自动关闭菜单。
+- [AdminHeader](layouts/admin/components/AdminHeader.uvue) 在移动端会显示 “☰” 切换按钮。
+
+## 4. 页面内容重排 (HomeIndex)
+
+[HomeIndex.uvue](layouts/admin/pages/HomeIndex.uvue) 利用 CSS 媒体查询实现内容区域的自适应:
+
+### 4.1 KPI 卡片流式布局
+
+使用 `flex-wrap: wrap` 配合 `min-width`:
+
+- **布局策略**:设置 `min-width: 250px` 作为安全边界,确保在 1200px 分辨率下依然能并排展示 4 个卡片。
+- **动态列数**:
+ - **宽屏 (>1200px)**: 4个卡片并排。
+ - **中屏 (768px - 1200px)**: 强制 2 个并排。
+ - **窄屏 (<768px)**: 1个卡片占满整行。
+- **高度控制**:统一固定为 `200px`。
+
+### 4.2 图表响应式
+
+- **垂直排列**:底部两个并排的图表在小屏下自动切换为垂直排列(`flex-direction: column`)。
+- **组件自适应**:图表组件内部利用父容器宽度自动伸缩。
+- **头部压缩**:移动端下,图表的配置项(如日期切换标签)由横向改为纵向,避免溢出。
+
+## 5. 组件化实践 (KpiMiniCard)
+
+统一使用了 [KpiMiniCard](pages/mall/admin/homePage/components/KpiMiniCard.uvue) 组件,确保:
+
+- 样式一致性。
+- 代码复用性。
+- 内部样式的可维护性。
+
+## 6. 使用建议
+
+- 后续开发新页面时,请优先使用 `stats-row` 和 `stat-card` 进行布局。
+- 对于复杂的表格页面,建议在移动端隐藏非核心列,或使用横向滚动条展示。
+- 所有的宽度判定建议遵循 768px 这一标准断点。
diff --git a/pages/mall/admin/docs/UNI_APP_X_PAGE_FIX_GUIDE.md b/pages/mall/admin/docs/UNI_APP_X_PAGE_FIX_GUIDE.md
index 7d2facf7..202bb409 100644
--- a/pages/mall/admin/docs/UNI_APP_X_PAGE_FIX_GUIDE.md
+++ b/pages/mall/admin/docs/UNI_APP_X_PAGE_FIX_GUIDE.md
@@ -11,6 +11,20 @@
- **解决方案**: 确保替换操作覆盖文件的完整生命周期,或者在发现 500 错误时检查文件末尾是否有残留的旧标签。
- **预防**: 优先使用 `create_file` 或子代理重写整个文件,而非局部替换复杂的 SFC 结构。
+#### **原因十二:KPI 统计网格响应式不一致 (用户体验红线)**
+
+- **现象**: 某些宽度下出现一行 3 个卡片,导致视觉不平衡或数据展示拥挤。
+- **原因**: 使用了 `repeat(auto-fit/auto-fill, ...)` 或基于 `min-width` 的 flex 自动布局。
+- **解决方案**:
+ 1. 使用全局统一类 `.kpi-grid`。
+ 2. 严禁使用 `auto-fit/auto-fill`。
+ 3. 必须显式使用视图断点拦截:
+ - `>= 1200px`: 固定 4 列 (`grid-template-columns: repeat(4, minmax(0, 1fr))`)。
+ - `768px - 1199px`: 固定 2 列 (`grid-template-columns: repeat(2, minmax(0, 1fr))`)。
+ - `< 768px`: 固定 1 列 (`grid-template-columns: repeat(1, minmax(0, 1fr))`)。
+ 4. 使用 `minmax(0, 1fr)` 配分子项 `min-width: 0` 确保在任何容器宽度下网格不被撑爆。
+- **强制规则**: 任何页面都不允许出现一行 3 个卡片的情况。
+
## 🛠️ 完整修复流程
```
diff --git a/pages/mall/admin/homePage/components/KpiMiniCard.uvue b/pages/mall/admin/homePage/components/KpiMiniCard.uvue
index 89b9aee9..b7456b8e 100644
--- a/pages/mall/admin/homePage/components/KpiMiniCard.uvue
+++ b/pages/mall/admin/homePage/components/KpiMiniCard.uvue
@@ -81,7 +81,7 @@ const props = withDefaults(defineProps<{
const trendArrow = computed((): string => {
if (props.trend === 'up') return '▲'
if (props.trend === 'down') return '▼'
- return '•'
+ return ''
})
const trendClass = computed((): string => {
@@ -94,33 +94,43 @@ const trendClass = computed((): string => {
diff --git a/pages/mall/admin/order/list.uvue b/pages/mall/admin/order/list.uvue
index ffbcbe2a..842d8ace 100644
--- a/pages/mall/admin/order/list.uvue
+++ b/pages/mall/admin/order/list.uvue
@@ -160,7 +160,7 @@ const orderData = ref([
typeColor: 'blue',
cancelStatus: '用户已取消',
product: {
- img: 'https://img.crmeb.com/crmeb_demo/75211.png',
+ img: '/static/logo.png',
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室 超高清手机便携投影机 (4K超清 支持...'
},
user: { phone: '188****4074', id: '82694' },
@@ -176,7 +176,7 @@ const orderData = ref([
typeColor: 'purple',
cancelStatus: '',
product: {
- img: 'https://img.crmeb.com/crmeb_demo/75211.png',
+ img: '/static/logo.png',
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水...'
},
user: { phone: '你就给', id: '82703' },
@@ -192,7 +192,7 @@ const orderData = ref([
typeColor: 'green',
cancelStatus: '',
product: {
- img: 'https://img.crmeb.com/crmeb_demo/75211.png',
+ img: '/static/logo.png',
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060'
},
user: { phone: '王毅不睡了', id: '82689' },
@@ -208,7 +208,7 @@ const orderData = ref([
typeColor: 'blue',
cancelStatus: '',
product: {
- img: 'https://img.crmeb.com/crmeb_demo/75211.png',
+ img: '/static/logo.png',
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室 超高清手机便携投影机 (4K超清 支持...'
},
user: { phone: '177****8361', id: '82697' },
diff --git a/pages/mall/admin/product/product-statistics/index.uvue b/pages/mall/admin/product/product-statistics/index.uvue
index 2acbfe8e..07760492 100644
--- a/pages/mall/admin/product/product-statistics/index.uvue
+++ b/pages/mall/admin/product/product-statistics/index.uvue
@@ -16,8 +16,8 @@
-
-
+
+
@@ -367,19 +367,12 @@ function initChart() {
.btn-query { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
.btn-export { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
-.stat-grid {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 16px;
- margin-bottom: 16px;
-}
-
+/* stat-grid 已废弃,由全局 kpi-grid 接管 */
.stat-card {
- width: calc(33.33% - 11px);
background: #fff;
border-radius: 8px;
padding: 20px;
+ min-width: 0;
}
.stat-main {
diff --git a/pages/mall/admin/user/MemberConfig.uvue b/pages/mall/admin/user/MemberConfig.uvue
index 02bed9c3..f563fac0 100644
--- a/pages/mall/admin/user/MemberConfig.uvue
+++ b/pages/mall/admin/user/MemberConfig.uvue
@@ -74,7 +74,7 @@
-
+
+
diff --git a/pages/mall/admin/user/Statistic.uvue b/pages/mall/admin/user/Statistic.uvue
index 838febcd..9c49f420 100644
--- a/pages/mall/admin/user/Statistic.uvue
+++ b/pages/mall/admin/user/Statistic.uvue
@@ -24,14 +24,14 @@
-
+
-
+
{{ item.icon }}
@@ -221,22 +221,14 @@ function onExport() {
color: #bfbfbf;
}
-.kpi-row {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 20px;
- margin-bottom: 40px;
-}
-
+/* kpi-row 已废弃,采用全局 kpi-grid */
.kpi-card {
- flex: 1;
- min-width: 200px;
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
padding: 8px;
+ min-width: 0; /* 允许收缩 */
}
.kpi-icon-box {
@@ -296,6 +288,31 @@ function onExport() {
width: 100%;
}
+@media (max-width: 1200px) {
+ .filter-card {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 16px;
+ }
+
+ .filter-item {
+ width: 100%;
+ }
+
+ .select-box, .date-picker-box {
+ flex: 1;
+ }
+
+ .analysis-row {
+ flex-direction: column;
+ }
+
+ .map-col, .gender-col {
+ width: 100%;
+ flex: none;
+ }
+}
+
.map-col {
flex: 7;
}
diff --git a/pages/mall/admin/user/list.uvue b/pages/mall/admin/user/list.uvue
index 3e094337..36de2d25 100644
--- a/pages/mall/admin/user/list.uvue
+++ b/pages/mall/admin/user/list.uvue
@@ -154,15 +154,15 @@ const isAllChecked = ref(false)
const activeDropdownId = ref(null)
const userList = ref([
- { id: '77414', avatar: 'https://img.crmeb.com/crmeb_demo/77414.png', nickname: '199****0268', isMember: '否', level: '无', group: '无', spreadLevel: '', phone: '199****0268', userType: '公众号', balance: '88888.00', checked: false },
- { id: '75311', avatar: 'https://img.crmeb.com/crmeb_demo/75311.png', nickname: 'wljbhg', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100002.00', checked: false },
- { id: '75305', avatar: 'https://img.crmeb.com/crmeb_demo/75305.png', nickname: '相见欢', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
- { id: '75296', avatar: 'https://img.crmeb.com/crmeb_demo/75296.png', nickname: '..', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
- { id: '75293', avatar: 'https://img.crmeb.com/crmeb_demo/75293.png', nickname: '钟(钏)华', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
- { id: '75289', avatar: 'https://img.crmeb.com/crmeb_demo/75289.png', nickname: '小二上酒', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
- { id: '75257', avatar: 'https://img.crmeb.com/crmeb_demo/75257.png', nickname: '5+7', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
- { id: '75226', avatar: 'https://img.crmeb.com/crmeb_demo/75226.png', nickname: '慢步前行', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
- { id: '75211', avatar: 'https://img.crmeb.com/crmeb_demo/75211.png', nickname: '难得糊涂', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false }
+ { id: '77414', avatar: '/static/logo.png', nickname: '199****0268', isMember: '否', level: '无', group: '无', spreadLevel: '', phone: '199****0268', userType: '公众号', balance: '88888.00', checked: false },
+ { id: '75311', avatar: '/static/logo.png', nickname: 'wljbhg', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100002.00', checked: false },
+ { id: '75305', avatar: '/static/logo.png', nickname: '相见欢', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
+ { id: '75296', avatar: '/static/logo.png', nickname: '..', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
+ { id: '75293', avatar: '/static/logo.png', nickname: '钟(钏)华', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
+ { id: '75289', avatar: '/static/logo.png', nickname: '小二上酒', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
+ { id: '75257', avatar: '/static/logo.png', nickname: '5+7', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
+ { id: '75226', avatar: '/static/logo.png', nickname: '慢步前行', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
+ { id: '75211', avatar: '/static/logo.png', nickname: '难得糊涂', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false }
])
function onSearch() {