Explorar el Código

完善区域管理与商品分类列表及弹窗的 Fresh Mint 对齐规则。

统一两页及全局列表的分隔线与操作列展示策略,补齐商品分类工具栏、表格和弹窗交互细节,并将可复用约束回写到 UI 规范文档,确保后续页面改造保持一致。

Made-with: Cursor
zlwu hace 6 meses
padre
commit
8c9a191b5b

+ 3 - 0
docs/web-admin-ui-spec-fresh-mint.md

@@ -333,10 +333,12 @@
 - **已选与操作按钮同一行**:**「已选 N 项」**(或 **`.list-toolbar-selected`**)与 **新增 / 批量 / 导入导出** 等按钮 **从左起顺序排列**,紧跟在选中摘要之后;**禁止** 使用 **`justify-content: space-between`** 将按钮区整体推到行末(与区域管理等页一致)。
 - **区域管理对齐点(强约束)**:如 `#/system/region` 的列表工具栏,**「已选 N 项」计数必须出现在最前**,其后紧跟 **「新增区域」**(主按钮)等操作按钮,形成「计数 → 动作」的扫描节奏;不要把新增按钮单独放到右侧或另起一行(对齐 `test/fresh-mint-green.html` 的列表工具栏信息层级)。
 - **工具栏按钮色彩规则(强约束)**:列表工具栏从左到右的按钮中,**仅第一个主行动作**使用主色实心(`btn-main` / `type="primary"`)。其余如 **批量删除 / 导入导出 / 下载模板 / 批量导入** 等,统一使用 **中性描边按钮**(`btn-ghost` / default),避免出现同一行多颗绿色主按钮(与设计图一致)。
+- **列表操作列全局规则(强约束)**:所有列表的“操作”列需 **固定在右侧**(`fixed="right"`)并预留足够宽度(建议 `min-width >= 260px`,按按钮数量增大),保证同一行操作按钮可完整展示,禁止因列宽不足出现按钮被裁切或省略。
 - **`.list-toolbar-selected`**:`font-size: 12px`,`font-weight: 600`,`color: var(--text-muted)`,`padding: 0 10px 0 0`,`margin-right: 4px`,**右边框** `1px solid var(--border)`,`line-height`/`min-height` **36px**,`inline-flex` 居中
 - **`.sel-num`**:`color: var(--primary-dark)`,`font-weight: 700`,`margin: 0 2px`
 - **工具栏与列表体分割线(必需)**:工具栏下方、列表上方需有 **`1px solid var(--border)`** 分隔(建议在 **`.list-body`** 统一设置 `border-top`);避免工具栏背景与表格首行直接连在一起,区域管理与商品分类均按此执行。
 - **跨页面统一执行**:凡是采用「筛选区 → 工具栏 → 列表」结构的页面,均需套用该分割线规则,不得按页面单独省略;后续改造页默认在 **`.list-body`** 保持该 `border-top`。
+- **单分隔线规则(全局)**:菜单页与弹窗内列表均遵循“相邻区块只保留一条边线”。即:**工具栏 ↔ 列表** 使用 `list-body` 的顶边线(`list-toolbar` 不再加底边线);**列表 ↔ 分页** 保留列表底边线(`list-pager-bar` 不再加顶边线),避免双线叠加。
 
 ### 7.4 通用 `table`(非 `.data-table` 时的基线)
 
@@ -642,6 +644,7 @@
 33. **第六轮结构统一(第八批,2026-04-17)**:继续按 `#/delivery/delivery-demo` 模板推进 `#/system/api-request-log` 与 `#/system/user`:统一为 `page-head + panel + panel-top + filter-block + filter-grid + filter-actions-row + list-toolbar + list-body/list-wrap + list-pager-bar`;操作列链接按钮改为 `btn-op-item`;新增/编辑弹窗 footer 统一 `btn-ghost / btn-main`,并清理页面内 `search-bar/toolbar/pagination` 的重复 scoped 壳层样式。
 34. **第六轮结构统一(第九批,2026-04-17)**:完成 `#/system/statistics` 的旧 `filter-bar/card` 体系替换,整体对齐 `#/delivery/delivery-demo`:新增 `page-head` 与 `panel` 分段结构,筛选区切为 `filter-block + filter-grid + filter-actions-row`(补齐“搜索/重置”动作),运营概览改为统一统计卡网格(`stats-grid`),图表与明细改为 `panel + panel-top + list-body/list-wrap + list-table` 组合,显著降低该页与其他业务页的视觉割裂感。
 35. **第六轮结构统一(第十批,2026-04-17)**:推进 `#/system/product`、`#/system/inquiry`、`#/system/supplier-settlement`、`#/system/client-settlement` 对齐 `#/delivery/delivery-demo`。本批聚焦“高流量复杂页”的外层一致性:统一页头说明(`page-head/page-desc`)、筛选动作文案(“搜索/重置”)、主次按钮语义类(`btn-main/btn-ghost`)、列表操作按钮(`btn-op-item`)、分页条(`list-pager-bar`)与弹窗 footer 按钮语义。`product` 与 `inquiry` 同步补齐 `panel/panel-top/list-toolbar` 骨架;`supplier/client-settlement` 保持双 Tab 与树形明细业务结构不变,仅做 Mint 交互壳层与动作区收口,确保改造不触及结算逻辑。
+36. **商品分类对齐区域管理流程(2026-04-17)**:`#/base/category` 参考 `#/system/region` 的改造方法完成收口:列表工具栏补齐 **`list-toolbar-selected + sel-num`**,并保持“首按钮主色、其余中性描边”(含批量导入);表格列头/列值统一居中对齐,状态标签语义统一为 **启用=success、禁用=info**;操作列去除 link 形态,统一 **`btn-op-item`** 体系;新增/编辑弹窗统一 **`label-position="top"`** 与两列排布,footer 统一 **`btn-ghost/btn-main + Close/Check`** 图标,确保与区域管理及全局 Fresh Mint 规范一致。
 
 ---
 

+ 44 - 0
web-admin/src/assets/styles/core/el-ui.scss

@@ -588,6 +588,35 @@ body .el-table thead {
   }
 }
 
+// 全局:操作列兜底(按钮较多时避免被截断)
+// 约定:列表操作列保持 fixed="right",并通过以下规则确保按钮完整展示
+.page-container .el-table td.el-table__cell:has(.btn-op-item),
+.console-container .el-table td.el-table__cell:has(.btn-op-item),
+.page-container .el-table td.el-table-fixed-column--right:has(.btn-op-item),
+.console-container .el-table td.el-table-fixed-column--right:has(.btn-op-item) {
+  min-width: 260px !important;
+}
+
+.page-container .el-table th.el-table-fixed-column--right,
+.console-container .el-table th.el-table-fixed-column--right {
+  min-width: 260px !important;
+}
+
+.page-container .el-table td.el-table__cell:has(.btn-op-item) > .cell,
+.console-container .el-table td.el-table__cell:has(.btn-op-item) > .cell,
+.page-container .el-table td.el-table-fixed-column--right:has(.btn-op-item) > .cell,
+.console-container .el-table td.el-table-fixed-column--right:has(.btn-op-item) > .cell {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  width: auto;
+  min-width: max-content;
+  white-space: nowrap;
+  overflow: visible;
+  text-overflow: clip;
+}
+
 // 修复固定列与滚动列之间的边框
 .el-table__fixed-column--right {
   border-left: 1px solid var(--el-table-border-color) !important;
@@ -847,6 +876,21 @@ body .el-table thead {
   background: #d1ac44;
 }
 
+// 列表分割线统一:每个分区只保留一条线
+// 工具栏与列表之间:保留 .list-body 顶部线,去掉 .list-toolbar 底部线
+.page-container .list-toolbar,
+.console-container .list-toolbar,
+.el-dialog .list-toolbar {
+  border-bottom: none !important;
+}
+
+// 列表与分页之间:保留列表底部线,去掉分页顶部线
+.page-container .pagination.list-pager-bar,
+.console-container .pagination.list-pager-bar,
+.el-dialog .pagination.list-pager-bar {
+  border-top: none !important;
+}
+
 .page-container .el-button.auto-icon-plus,
 .console-container .el-button.auto-icon-plus {
   --auto-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E");

+ 70 - 29
web-admin/src/views/system/category/index.vue

@@ -38,6 +38,9 @@
 
     <div class="list-toolbar">
       <div class="list-toolbar-row">
+      <div class="list-toolbar-selected">
+        已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
+      </div>
       <el-button class="btn-main" type="primary" @click="handleAdd">
         <el-icon><Plus /></el-icon>
         新增分类
@@ -73,7 +76,7 @@
         @import-success="handleCategoryImportSuccess"
         @import-error="handleExcelImportError"
       >
-        批量导入
+        <el-button class="btn-ghost">批量导入</el-button>
       </ArtExcelImport>
       </div>
     </div>
@@ -87,8 +90,15 @@
       :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
       @selection-change="handleCategoryTableSelectionChange"
     >
-      <el-table-column type="selection" width="55" />
-      <el-table-column prop="name" label="分类名称" min-width="150" class-name="col-category-name">
+      <el-table-column type="selection" width="55" align="center" header-align="center" />
+      <el-table-column
+        prop="name"
+        label="分类名称"
+        min-width="150"
+        class-name="col-category-name"
+        align="center"
+        header-align="center"
+      >
         <template #default="{ row }">
           <div class="category-name-cell" :class="{ 'is-child': row.parentId }">
             <span class="category-name-text">{{ row.name }}</span>
@@ -96,7 +106,7 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="图片" width="80">
+      <el-table-column label="图片" width="80" align="center" header-align="center">
         <template #default="{ row }">
           <el-image
             v-if="row.imageUrl"
@@ -118,19 +128,21 @@
           <span>{{ formatPriceCoefficient(row.priceCoefficient) }}</span>
         </template>
       </el-table-column>
-      <el-table-column prop="sortOrder" label="排序" width="80" />
-      <el-table-column prop="isActive" label="状态" width="80">
+      <el-table-column prop="sortOrder" label="排序" width="80" align="center" header-align="center" />
+      <el-table-column prop="isActive" label="状态" width="80" align="center" header-align="center">
         <template #default="{ row }">
-          <el-tag :type="row.isActive ? 'success' : 'danger'" size="small">
+          <el-tag :type="row.isActive ? 'success' : 'info'" size="small">
             {{ row.isActive ? '启用' : '禁用' }}
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column prop="createAt" label="创建时间" width="160" />
-      <el-table-column label="操作" width="200" fixed="right">
+      <el-table-column prop="createAt" label="创建时间" width="160" align="center" header-align="center" />
+      <el-table-column label="操作" width="320" fixed="right" align="center" header-align="center">
         <template #default="{ row }">
-          <el-button type="primary" link @click="handleAddChild(row)" v-if="!row.parentId">新增子分类</el-button>
-          <el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
+          <el-button class="btn-op-item primary" @click="handleAddChild(row)" v-if="!row.parentId">
+            新增子分类
+          </el-button>
+          <el-button class="btn-op-item" :class="{ primary: row.parentId }" @click="handleEdit(row)">编辑</el-button>
           <el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
@@ -153,7 +165,13 @@
         <!-- 基本信息区域 -->
         <div class="form-section">
           <div class="section-title">基本信息</div>
-          <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
+          <el-form
+            ref="formRef"
+            :model="formData"
+            :rules="formRules"
+            label-position="top"
+            class="category-dialog-form"
+          >
             <div class="form-row">
               <el-form-item label="上级分类" class="flex-1">
                 <el-select v-model="formData.parentId" placeholder="请选择上级分类(空为顶级)" clearable>
@@ -179,8 +197,6 @@
                   <span class="price-coefficient-hint">填写数字表示对应百分比,例如 5 表示 5%</span>
                 </div>
               </el-form-item>
-            </div>
-            <div class="form-row">
               <el-form-item label="分类图片" class="flex-1">
                 <div class="category-image-row">
                   <el-image
@@ -209,7 +225,7 @@
               </el-form-item>
             </div>
             <div class="form-row">
-              <el-form-item label="状态" v-if="isEdit" class="status-item">
+              <el-form-item label="状态" v-if="isEdit" class="flex-1">
                 <el-switch v-model="formData.isActive" />
               </el-form-item>
             </div>
@@ -319,8 +335,14 @@
       </div>
 
       <template #footer>
-        <el-button class="btn-ghost" @click="dialogVisible = false">取消</el-button>
-        <el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
+        <el-button class="btn-ghost" @click="dialogVisible = false">
+          <el-icon><Close /></el-icon>
+          取消
+        </el-button>
+        <el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
+          <el-icon><Check /></el-icon>
+          确定
+        </el-button>
       </template>
     </el-dialog>
 
@@ -343,7 +365,6 @@
           <el-table
             ref="parameterTableRef"
             :data="filteredAvailableParameters"
-            border
             stripe
             row-key="id"
             height="400"
@@ -377,8 +398,14 @@
         </div>
       </div>
       <template #footer>
-        <el-button @click="selectParameterVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmSelectParameters">确定</el-button>
+        <el-button class="btn-ghost" @click="selectParameterVisible = false">
+          <el-icon><Close /></el-icon>
+          取消
+        </el-button>
+        <el-button class="btn-main" type="primary" @click="confirmSelectParameters">
+          <el-icon><Check /></el-icon>
+          确定
+        </el-button>
       </template>
     </el-dialog>
     </section>
@@ -390,7 +417,7 @@
   import { ref, reactive, onMounted, nextTick, watch, computed } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
-  import { Plus, Search, Rank } from '@element-plus/icons-vue'
+  import { Plus, Search, Rank, Close, Check } from '@element-plus/icons-vue'
   import { VueDraggable as draggable } from 'vue-draggable-plus'
   import type { UploadFile } from 'element-plus'
   import {
@@ -1186,6 +1213,24 @@
 </script>
 
 <style lang="scss" scoped>
+.list-toolbar {
+  border-bottom: none !important;
+}
+
+.pagination.list-pager-bar {
+  border-top: none !important;
+}
+
+.category-dialog-form {
+  :deep(.el-form-item__label) {
+    font-size: 11px;
+    font-weight: 500;
+    color: var(--mint-text-muted);
+    padding: 0 0 4px;
+    line-height: 1.2;
+  }
+}
+
 .category-image-row { display: flex; gap: 12px; align-items: center; }
 .category-image-placeholder {
   width: 80px; height: 80px; border-radius: 8px;
@@ -1230,10 +1275,6 @@
     flex: 1;
   }
 
-  .status-item {
-    width: 100px;
-    flex-shrink: 0;
-  }
 }
 
 .form-row:last-child {
@@ -1404,8 +1445,9 @@
 }
 
 .parameter-select-list {
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
+  border: 1px solid var(--mint-border);
+  border-radius: var(--mint-radius);
+  overflow: hidden;
 }
 
 /* 选择参数弹窗:可选值列多行完整展示 */
@@ -1429,8 +1471,7 @@
 
 .parameter-select-filter {
   padding: 12px;
-  border-bottom: 1px solid #ebeef5;
-  background: #fafafa;
+  background: transparent;
 }
 
 .text-gray {

+ 8 - 0
web-admin/src/views/system/region/index.vue

@@ -648,6 +648,14 @@
 </script>
 
 <style lang="scss" scoped>
+.list-toolbar {
+  border-bottom: none !important;
+}
+
+.pagination.list-pager-bar {
+  border-top: none !important;
+}
+
 .region-dialog-form {
   :deep(.el-form-item) {
     margin-bottom: 0;