Переглянути джерело

统一区域管理与 Fresh Mint 全局样式规范。

对齐区域管理列表与新增/编辑弹窗的设计稿细节,补齐工具栏按钮层级、表格与标签语义色、placeholder 字体、弹窗两列高密布局与图标规范,并同步完善全局自动图标与规范文档,确保后续页面可按同一标准复用。

Made-with: Cursor
zlwu 6 місяців тому
батько
коміт
1a2094a07b

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

@@ -230,6 +230,7 @@
 
 - **高度**:`36px`,水平 `padding: 0 12px`,**圆角**:`8px`,**边框**:`1px solid var(--border)`,**字号**:`14px`,**背景**:`var(--surface)`,**字色**:`var(--text)`
 - **`.select`**:`appearance: none`,自定义下拉箭头 SVG 背景,**右 padding** `32px`,箭头距右 `12px` center
+- **placeholder(强约束)**:颜色使用更轻的 Slate(建议 `#94a3b8`),字号 **`13px`**(略小于正文),且 `opacity: 1`(避免浏览器默认变淡导致不同端不一致)。
 
 ### 5.3 展开区与操作行
 
@@ -330,6 +331,8 @@
 - `padding: 0`,背景 `var(--table-head)`,下边框 `1px solid var(--border)`
 - **`.list-toolbar-row`**:`padding: 8px 16px`,`gap: 8px`,`flex-wrap`,`align-items: center`,`justify-content: flex-start`
 - **已选与操作按钮同一行**:**「已选 N 项」**(或 **`.list-toolbar-selected`**)与 **新增 / 批量 / 导入导出** 等按钮 **从左起顺序排列**,紧跟在选中摘要之后;**禁止** 使用 **`justify-content: space-between`** 将按钮区整体推到行末(与区域管理等页一致)。
+- **区域管理对齐点(强约束)**:如 `#/system/region` 的列表工具栏,**「已选 N 项」计数必须出现在最前**,其后紧跟 **「新增区域」**(主按钮)等操作按钮,形成「计数 → 动作」的扫描节奏;不要把新增按钮单独放到右侧或另起一行(对齐 `test/fresh-mint-green.html` 的列表工具栏信息层级)。
+- **工具栏按钮色彩规则(强约束)**:列表工具栏从左到右的按钮中,**仅第一个主行动作**使用主色实心(`btn-main` / `type="primary"`)。其余如 **批量删除 / 导入导出 / 下载模板 / 批量导入** 等,统一使用 **中性描边按钮**(`btn-ghost` / default),避免出现同一行多颗绿色主按钮(与设计图一致)。
 - **`.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`);避免工具栏背景与表格首行直接连在一起,区域管理与商品分类均按此执行。
@@ -474,6 +477,9 @@
 与 HTML demo 类名可不同,下列为 **Element Plus** 落地约定,供各业务列表页统一改造:
 
 - **表单主区两列**:内容可用 **`display: grid`**,`grid-template-columns: repeat(2, minmax(0, 1fr))`,`gap: 12px 20px`(类名示例:**`.form-grid-2`**);占满整行的字段(如多行 **描述**)使用 **`grid-column: 1 / -1`**(示例:**`.form-col-span-2`**)。栅格内 **`el-form-item`** 宜 **`margin-bottom: 0`**,纵向间距交给 **`gap`**。
+- **label 在上(强约束)**:弹窗表单统一 `label-position="top"`,并将 `label-width` 相关规则清零,避免出现「左侧 label 空出一大块」的不对齐(区域管理/单位/参数模板等页同构)。
+- **底栏按钮图标(强约束)**:弹窗 footer 的「取消 / 确定」建议显式使用 `Close / Check` 图标(Element Plus Icons),图标与文字间距 **6px**;避免触发全局按钮自动图标兜底导致的“黑色方块”。
+- **高密两列优先(节约高度)**:新增/编辑弹窗应尽量让字段在两列内排满(如 `排序|状态`、`预计配送时长|预警提前时间`),减少单字段独占整行;仅在长文本(如备注/描述)或复杂联动控件时使用 `form-col-span-2` 跨整行。
 - **壳层 padding**:**`.el-dialog` 根节点**不设内边距(`web-admin` 全局 **`--el-dialog-padding-primary: 0`** 与 **`padding: 0`**,避免与 EP 默认 **`var(--el-dialog-padding-primary)`** 叠出双份空白);**标题 / 主体 / 底栏** 仍分别由 **`.el-dialog__header`**、**`.el-dialog__body`**、**`.el-dialog__footer`** 控制,建议与 §9.3 一致:**`16px 22px`**(header,项目里常见为 **`16px 22px 14px`**)、**`18px 22px`**(body)、**`12px 22px`**(footer)。业务页滚动主体 **`.dialog-scroll`** 可另加 **`padding-right: 2px`** 避免滚动条贴内容。
 - **底栏「取消 / 确定」带前缀图标**:与列表工具栏主/次按钮一致:**`gap: 0`**,**`.el-icon`** **`margin-right: 6px`**(与 **「新增」** 等 **`btn-main` / `btn-ghost`** 图标间距统一)。
 - **长表单校验失败**:**`el-form`** 建议开启 **`scroll-to-error`**,并设置 **`scroll-into-view-options`**(如 **`behavior: 'smooth'`**、**`block: 'center'`**),提交前 **`validate()`** 失败时再配合 **`ElMessageBox.alert`** 说明「已定位到第一处标红项」,避免用户在长弹窗里不知道为何无法提交(商品分类等页已采用)。

+ 96 - 0
web-admin/src/App.vue

@@ -13,6 +13,8 @@
 </template>
 
 <script setup lang="ts">
+  import { onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
+  import { storeToRefs } from 'pinia'
   import { useUserStore } from './store/modules/user'
   import zh from 'element-plus/es/locale/lang/zh-cn'
   import en from 'element-plus/es/locale/lang/en'
@@ -29,6 +31,75 @@
     en: en
   }
 
+  let buttonIconObserver: MutationObserver | null = null
+  let panelTitleObserver: MutationObserver | null = null
+
+  const iconClassByText: Array<[RegExp, string]> = [
+    [/(新增|新建|创建|添加)/, 'auto-icon-plus'],
+    [/(删除|移除|清空|关闭)/, 'auto-icon-delete'],
+    [/(导入|上传)/, 'auto-icon-upload'],
+    [/(导出|下载)/, 'auto-icon-download'],
+    [/(搜索|查询|筛选)/, 'auto-icon-search'],
+    [/(重置|刷新)/, 'auto-icon-refresh'],
+    [/(编辑|修改|配置|设置)/, 'auto-icon-edit'],
+    [/(查看|详情)/, 'auto-icon-view'],
+    [/(保存|提交|确定|确认)/, 'auto-icon-check']
+  ]
+
+  const autoIconClassList = iconClassByText.map(([, cls]) => cls)
+
+  const refreshButtonAutoIcons = () => {
+    const selectors = [
+      '.page-container .el-button:not(.is-circle):not(.el-button--text):not(:has(.el-icon))',
+      '.console-container .el-button:not(.is-circle):not(.el-button--text):not(:has(.el-icon))'
+    ]
+    const buttons = document.querySelectorAll<HTMLElement>(selectors.join(','))
+    buttons.forEach((button) => {
+      autoIconClassList.forEach((className) => button.classList.remove(className))
+      const text = (button.textContent || '').replace(/\s+/g, '')
+      const matched = iconClassByText.find(([rule]) => rule.test(text))
+      if (matched) button.classList.add(matched[1])
+    })
+  }
+
+  const panelTitleIconByText: Array<[RegExp, string]> = [
+    [/(任务|调度)/, 'auto-title-operation'],
+    [/(区域)/, 'auto-title-location'],
+    [/(用户|账号)/, 'auto-title-user'],
+    [/(参数|配置)/, 'auto-title-settings'],
+    [/(分类)/, 'auto-title-category'],
+    [/(商品|产品)/, 'auto-title-box'],
+    [/(订单)/, 'auto-title-receipt'],
+    [/(发票)/, 'auto-title-file'],
+    [/(询价|报价)/, 'auto-title-quote'],
+    [/(结算)/, 'auto-title-calc']
+  ]
+
+  const panelTitleClassList = panelTitleIconByText.map(([, cls]) => cls)
+
+  const refreshPanelTitleIcons = () => {
+    const headings = document.querySelectorAll<HTMLHeadingElement>(
+      '.page-container .panel-top h2, .console-container .panel-top h2'
+    )
+
+    headings.forEach((h2) => {
+      // 已手写 title-ic(例如 delivery-demo)则不干预
+      if (h2.querySelector('.title-ic')) return
+
+      // 确保存在容器
+      const span = document.createElement('span')
+      span.className = 'title-ic'
+      span.setAttribute('aria-hidden', 'true')
+      h2.prepend(span)
+
+      // 根据标题文本匹配图标类型
+      panelTitleClassList.forEach((className) => h2.classList.remove(className))
+      const text = (h2.textContent || '').replace(/\s+/g, '')
+      const matched = panelTitleIconByText.find(([rule]) => rule.test(text))
+      if (matched) h2.classList.add(matched[1])
+    })
+  }
+
   onBeforeMount(() => {
     toggleTransition(true)
     initializeTheme()
@@ -38,5 +109,30 @@
     checkStorageCompatibility()
     toggleTransition(false)
     systemUpgrade()
+    refreshButtonAutoIcons()
+    refreshPanelTitleIcons()
+
+    buttonIconObserver = new MutationObserver(() => {
+      refreshButtonAutoIcons()
+    })
+    buttonIconObserver.observe(document.body, {
+      childList: true,
+      subtree: true
+    })
+
+    panelTitleObserver = new MutationObserver(() => {
+      refreshPanelTitleIcons()
+    })
+    panelTitleObserver.observe(document.body, {
+      childList: true,
+      subtree: true
+    })
+  })
+
+  onBeforeUnmount(() => {
+    buttonIconObserver?.disconnect()
+    buttonIconObserver = null
+    panelTitleObserver?.disconnect()
+    panelTitleObserver = null
   })
 </script>

+ 68 - 0
web-admin/src/assets/styles/core/app.scss

@@ -356,11 +356,79 @@ body {
   border-bottom: 1px solid var(--mint-border);
 
   h2 {
+    display: inline-flex;
+    align-items: center;
+    gap: 2px;
     margin: 0;
     font-size: 14px;
     font-weight: 600;
     color: var(--mint-text);
   }
+
+  h2 .title-ic {
+    width: 22px;
+    height: 22px;
+    border-radius: 8px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    background: var(--mint-primary-bg);
+    color: var(--mint-primary-dark);
+    border: 1px solid rgba(16, 185, 129, 0.16);
+    margin-right: 4px;
+    flex-shrink: 0;
+  }
+
+  h2 .title-ic::before {
+    content: '';
+    width: 14px;
+    height: 14px;
+    background-color: currentColor;
+    opacity: 0.95;
+    mask: var(--panel-title-icon-mask) center / contain no-repeat;
+    -webkit-mask: var(--panel-title-icon-mask) center / contain no-repeat;
+  }
+}
+
+.page-container .panel-top h2.auto-title-operation,
+.console-container .panel-top h2.auto-title-operation {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 20h9'/%3E%3Cpath d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-location,
+.console-container .panel-top h2.auto-title-location {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 21s-7-4.5-7-11a7 7 0 1 1 14 0c0 6.5-7 11-7 11z'/%3E%3Ccircle cx='12' cy='10' r='2.5'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-user,
+.console-container .panel-top h2.auto-title-user {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-settings,
+.console-container .panel-top h2.auto-title-settings {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z'/%3E%3Cpath d='M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-1.5 2.6-0.2-.1a2 2 0 0 0-2.1.4 1.8 1.8 0 0 0-.6 2v.2H9.6v-.2a1.8 1.8 0 0 0-.6-2 2 2 0 0 0-2.1-.4l-.2.1-1.5-2.6.1-.1A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.6-1.1H3v-3h.0a1.7 1.7 0 0 0 1.6-1.1 1.7 1.7 0 0 0-.3-1.9l-.1-.1 1.5-2.6.2.1a2 2 0 0 0 2.1-.4 1.8 1.8 0 0 0 .6-2V2h4.8v.2a1.8 1.8 0 0 0 .6 2 2 2 0 0 0 2.1.4l.2-.1 1.5 2.6-.1.1a1.7 1.7 0 0 0-.3 1.9A1.7 1.7 0 0 0 21 10.9h.0v3H21a1.7 1.7 0 0 0-1.6 1.1z'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-category,
+.console-container .panel-top h2.auto-title-category {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6h16'/%3E%3Cpath d='M4 12h16'/%3E%3Cpath d='M4 18h16'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-box,
+.console-container .panel-top h2.auto-title-box {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z'/%3E%3Cpath d='M3.3 7L12 12l8.7-5'/%3E%3Cpath d='M12 22V12'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-receipt,
+.console-container .panel-top h2.auto-title-receipt {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 2h12v20l-2-1-2 1-2-1-2 1-2-1-2 1V2z'/%3E%3Cpath d='M8 6h8'/%3E%3Cpath d='M8 10h8'/%3E%3Cpath d='M8 14h6'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-file,
+.console-container .panel-top h2.auto-title-file {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpath d='M14 2v6h6'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-quote,
+.console-container .panel-top h2.auto-title-quote {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a4 4 0 0 1-4 4H7l-4 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z'/%3E%3C/svg%3E");
+}
+.page-container .panel-top h2.auto-title-calc,
+.console-container .panel-top h2.auto-title-calc {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='2' width='14' height='20' rx='2'/%3E%3Cpath d='M9 6h6'/%3E%3Cpath d='M8 10h2'/%3E%3Cpath d='M14 10h2'/%3E%3Cpath d='M8 14h2'/%3E%3Cpath d='M14 14h2'/%3E%3Cpath d='M8 18h8'/%3E%3C/svg%3E");
 }
 
 .page-container,

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

@@ -498,6 +498,9 @@ html.dark .el-card {
 .el-button > span {
   position: relative;
   z-index: 10;
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
 }
 
 // 优化颜色选择器圆角
@@ -616,6 +619,9 @@ body .el-table thead {
   --el-table-header-bg-color: var(--mint-table-head);
   --el-table-border-color: var(--mint-border);
   --el-table-row-hover-bg-color: var(--mint-row-hover);
+  --el-table-text-color: var(--mint-text);
+  --el-table-header-text-color: var(--mint-text-muted);
+  font-size: 14px;
 }
 
 .page-container .el-table th.el-table__cell,
@@ -625,6 +631,53 @@ body .el-table thead {
   font-weight: 600;
 }
 
+.page-container .el-table td.el-table__cell,
+.console-container .el-table td.el-table__cell {
+  color: var(--mint-text);
+  font-size: 14px;
+  font-weight: 500;
+}
+
+// Fresh Mint:状态标签(映射 design spec 的浅底 + 语义色字)
+.page-container .el-tag,
+.console-container .el-tag {
+  border: none;
+  border-radius: 6px;
+  padding: 2px 8px;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.page-container .el-tag--success,
+.console-container .el-tag--success {
+  background: #d1fae5 !important;
+  color: #047857 !important;
+}
+
+.page-container .el-tag--warning,
+.console-container .el-tag--warning {
+  background: #fffbeb !important;
+  color: #d97706 !important;
+}
+
+.page-container .el-tag--danger,
+.console-container .el-tag--danger {
+  background: #fef2f2 !important;
+  color: #dc2626 !important;
+}
+
+.page-container .el-tag--info,
+.console-container .el-tag--info {
+  background: #f8fafc !important;
+  color: #64748b !important;
+}
+
+.page-container .el-tag--primary,
+.console-container .el-tag--primary {
+  background: #ecfdf5 !important;
+  color: #059669 !important;
+}
+
 .page-container .el-button--primary,
 .console-container .el-button--primary {
   --el-button-bg-color: var(--mint-primary);
@@ -651,6 +704,32 @@ body .el-table thead {
   box-shadow: 0 0 0 1px var(--mint-border) inset !important;
 }
 
+// Fresh Mint:placeholder 统一(设计图:更轻的 slate,字号略小)
+.page-container .el-input__inner::placeholder,
+.page-container .el-textarea__inner::placeholder,
+.console-container .el-input__inner::placeholder,
+.console-container .el-textarea__inner::placeholder {
+  color: #94a3b8 !important;
+  font-size: 13px;
+  opacity: 1;
+}
+
+.page-container .el-select__placeholder,
+.console-container .el-select__placeholder {
+  color: #94a3b8 !important;
+  font-size: 13px;
+}
+
+// 兜底:页面内原生 input/textarea placeholder(避免自绘控件不走 Element)
+.page-container input::placeholder,
+.page-container textarea::placeholder,
+.console-container input::placeholder,
+.console-container textarea::placeholder {
+  color: #94a3b8;
+  font-size: 13px;
+  opacity: 1;
+}
+
 .page-container .el-input__wrapper:hover,
 .page-container .el-select__wrapper:hover,
 .console-container .el-input__wrapper:hover,
@@ -670,6 +749,7 @@ body .el-table thead {
 .page-container .btn-main.el-button,
 .console-container .btn-main.el-button {
   height: 36px;
+  padding: 0 14px;
   border-radius: 8px;
   font-weight: 600;
   --el-button-bg-color: var(--mint-primary);
@@ -681,6 +761,7 @@ body .el-table thead {
 .page-container .btn-ghost.el-button,
 .console-container .btn-ghost.el-button {
   height: 36px;
+  padding: 0 14px;
   border-radius: 8px;
   font-weight: 600;
   color: var(--mint-text);
@@ -691,12 +772,42 @@ body .el-table thead {
 .page-container .btn-op-item.el-button,
 .console-container .btn-op-item.el-button {
   height: 28px;
+  padding: 0 8px;
   border-radius: 6px;
   font-size: 12px;
   font-weight: 600;
   border-color: var(--mint-border);
 }
 
+.page-container .el-button:not(.is-circle):not(.el-button--text),
+.console-container .el-button:not(.is-circle):not(.el-button--text) {
+  height: 36px;
+  border-radius: 8px;
+  font-weight: 600;
+}
+
+.page-container .btn-op-item.el-button > span,
+.console-container .btn-op-item.el-button > span {
+  gap: 4px;
+}
+
+.page-container .el-button .el-icon,
+.console-container .el-button .el-icon {
+  margin-right: 0;
+}
+
+.page-container .el-button[class*='auto-icon-']:not(.is-circle):not(.el-button--text):not(:has(.el-icon)) > span::before,
+.console-container .el-button[class*='auto-icon-']:not(.is-circle):not(.el-button--text):not(:has(.el-icon)) > span::before {
+  content: '';
+  width: 14px;
+  height: 14px;
+  background-color: currentColor;
+  opacity: 0.72;
+  flex-shrink: 0;
+  mask: var(--auto-icon-mask) center / contain no-repeat;
+  -webkit-mask: var(--auto-icon-mask) center / contain no-repeat;
+}
+
 .page-container .btn-op-item.primary.el-button,
 .console-container .btn-op-item.primary.el-button {
   color: #fff;
@@ -704,6 +815,75 @@ body .el-table thead {
   background: var(--mint-primary);
 }
 
+.page-container .list-toolbar-row > .el-button:not(.el-button--text):not(.el-button--link),
+.console-container .list-toolbar-row > .el-button:not(.el-button--text):not(.el-button--link),
+.page-container .toolbar-right > .el-button:not(.el-button--text):not(.el-button--link),
+.console-container .toolbar-right > .el-button:not(.el-button--text):not(.el-button--link) {
+  color: var(--mint-text);
+  border-color: var(--mint-border);
+  background: var(--mint-surface);
+}
+
+.page-container .list-toolbar-row > .el-button:not(.el-button--text):not(.el-button--link):first-of-type,
+.console-container .list-toolbar-row > .el-button:not(.el-button--text):not(.el-button--link):first-of-type,
+.page-container .toolbar-right > .el-button:not(.el-button--text):not(.el-button--link):first-of-type,
+.console-container .toolbar-right > .el-button:not(.el-button--text):not(.el-button--link):first-of-type {
+  color: #fff;
+  border-color: var(--mint-primary);
+  background: var(--mint-primary);
+}
+
+.page-container .el-table__cell .btn-op-item.el-button,
+.console-container .el-table__cell .btn-op-item.el-button {
+  color: var(--mint-text);
+  border-color: var(--mint-border);
+  background: var(--mint-surface);
+}
+
+.page-container .el-table__cell .btn-op-item.el-button:first-of-type,
+.console-container .el-table__cell .btn-op-item.el-button:first-of-type {
+  color: #fff;
+  border-color: #d1ac44;
+  background: #d1ac44;
+}
+
+.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");
+}
+.page-container .el-button.auto-icon-delete,
+.console-container .el-button.auto-icon-delete {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M3 6h18'/%3E%3Cpath d='M8 6V4h8v2'/%3E%3Cpath d='M19 6l-1 14H6L5 6'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-upload,
+.console-container .el-button.auto-icon-upload {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpath d='M12 16V4'/%3E%3Cpath d='M8 8l4-4 4 4'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-download,
+.console-container .el-button.auto-icon-download {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpath d='M12 4v12'/%3E%3Cpath d='M8 12l4 4 4-4'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-search,
+.console-container .el-button.auto-icon-search {
+  --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%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-4-4'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-refresh,
+.console-container .el-button.auto-icon-refresh {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36'/%3E%3Cpath d='M21 3v6h-6'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-edit,
+.console-container .el-button.auto-icon-edit {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M12 20h9'/%3E%3Cpath d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-view,
+.console-container .el-button.auto-icon-view {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E");
+}
+.page-container .el-button.auto-icon-check,
+.console-container .el-button.auto-icon-check {
+  --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' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
+}
+
 .page-container .el-dialog__footer .el-button,
 .console-container .el-dialog__footer .el-button {
   height: 36px;

+ 3 - 3
web-admin/src/components/core/forms/art-excel-import/index.vue

@@ -7,9 +7,9 @@
       :show-file-list="false"
       @change="handleFileChange"
     >
-      <ElButton type="primary" v-ripple>
-        <slot>导入 Excel</slot>
-      </ElButton>
+      <slot>
+        <ElButton type="primary" v-ripple>导入 Excel</ElButton>
+      </slot>
     </ElUpload>
   </div>
 </template>

+ 12 - 2
web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss

@@ -128,7 +128,8 @@ $popup-menu-radius: 6px;
     span {
       font-size: $menu-font-size !important;
       font-weight: 500;
-      color: var(--mint-text, #1e293b);
+      // Fresh Mint:默认菜单项为中性色(demo `.nav a`)
+      color: var(--mint-text-muted, #64748b);
       text-rendering: geometricprecision;
       -webkit-font-smoothing: antialiased;
       -moz-osx-font-smoothing: grayscale;
@@ -186,8 +187,10 @@ $popup-menu-radius: 6px;
       color: var(--mint-text, #1e293b) !important;
     }
 
-    .el-menu-item.is-active {
+    // 选中态:以浅底+图标强化为主,文字保持中性色(对齐 fresh-mint demo)
+    .el-menu-item.is-active span {
       color: var(--mint-primary-dark, #059669) !important;
+      font-weight: 600 !important;
     }
 
     .el-sub-menu__icon-arrow {
@@ -217,6 +220,13 @@ $popup-menu-radius: 6px;
 
     .el-menu-item.is-active {
       background-color: var(--mint-primary-bg, #ecfdf5);
+      color: var(--mint-primary-dark, #059669) !important;
+      font-weight: 600;
+
+      span {
+        color: var(--mint-primary-dark, #059669) !important;
+        font-weight: 600 !important;
+      }
 
       .art-svg-icon {
         color: var(--mint-primary-dark, #059669) !important;

+ 148 - 86
web-admin/src/views/system/region/index.vue

@@ -36,45 +36,54 @@
         </el-form>
       </div>
 
-    <!-- 操作栏 -->
-    <div class="list-toolbar">
-      <div class="list-toolbar-row">
-      <el-button class="btn-main" type="primary" @click="handleAdd">
-        <el-icon><Plus /></el-icon>
-        新增区域
-      </el-button>
-
-      <el-button class="btn-ghost" type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
-        批量删除
-      </el-button>
-
-      <ArtExcelExport
-        :data="batchExportData"
-        :list-total="pagination.total"
-        :load-all-data="loadAllRegionsForExport"
-        :filename="regionExportFilename"
-        sheetName="区域数据"
-        :columns="regionExportColumns"
-      >
-        批量导出
-      </ArtExcelExport>
-
-      <ArtExcelExport
-        :data="regionImportTemplateData"
-        :filename="regionImportTemplateFilename"
-        sheetName="区域导入模板"
-        :columns="regionImportTemplateColumns"
-        :showSuccessMessage="false"
-        :showErrorMessage="false"
-      >
-        下载导入模板
-      </ArtExcelExport>
+      <!-- 操作栏 -->
+      <div class="list-toolbar">
+        <div class="list-toolbar-row">
+          <div class="list-toolbar-selected">
+            已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
+          </div>
 
-      <ArtExcelImport @import-success="handleRegionImportSuccess" @import-error="handleExcelImportError">
-        批量导入
-      </ArtExcelImport>
+          <el-button class="btn-main" type="primary" @click="handleAdd">
+            <el-icon><Plus /></el-icon>
+            新增区域
+          </el-button>
+
+          <el-button
+            class="btn-ghost"
+            type="danger"
+            :disabled="selectedRows.length === 0"
+            @click="handleBatchDelete"
+          >
+            批量删除
+          </el-button>
+
+          <ArtExcelExport
+            :data="batchExportData"
+            :list-total="pagination.total"
+            :load-all-data="loadAllRegionsForExport"
+            :filename="regionExportFilename"
+            sheetName="区域数据"
+            :columns="regionExportColumns"
+          >
+            批量导出
+          </ArtExcelExport>
+
+          <ArtExcelExport
+            :data="regionImportTemplateData"
+            :filename="regionImportTemplateFilename"
+            sheetName="区域导入模板"
+            :columns="regionImportTemplateColumns"
+            :showSuccessMessage="false"
+            :showErrorMessage="false"
+          >
+            下载导入模板
+          </ArtExcelExport>
+
+          <ArtExcelImport @import-success="handleRegionImportSuccess" @import-error="handleExcelImportError">
+            <el-button class="btn-ghost">批量导入</el-button>
+          </ArtExcelImport>
+        </div>
       </div>
-    </div>
 
     <!-- 数据表格 -->
     <div class="list-body list-wrap">
@@ -109,7 +118,7 @@
       </el-table-column>
       <el-table-column prop="isActive" label="状态" min-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>
@@ -144,56 +153,79 @@
       width="640px"
       @close="handleDialogClose"
     >
-      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="128px">
-        <el-form-item label="区域名称" prop="name">
-          <el-input v-model="formData.name" placeholder="请输入区域名称" />
-        </el-form-item>
-        <el-form-item label="区域编码" prop="code">
-          <el-input v-model="formData.code" placeholder="请输入区域编码" />
-        </el-form-item>
-        <el-form-item label="描述">
-          <el-input v-model="formData.description" type="textarea" placeholder="请输入描述" :rows="3" />
-        </el-form-item>
-        <el-form-item label="排序">
-          <el-input-number v-model="formData.sortOrder" :min="0" />
-        </el-form-item>
-        <el-form-item label="预计配送时长">
-          <div class="form-item-column">
-            <div class="form-item-row">
-              <el-input-number
-                v-model="formData.expectedDeliveryMinutes"
-                :min="0"
-                :precision="0"
-                :step="5"
-                :value-on-clear="null"
-                controls-position="right"
-                class="dialog-input-number"
-              />
-              <span class="form-suffix-unit">分钟</span>
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="formRules"
+        label-position="top"
+        class="region-dialog-form"
+      >
+        <div class="form-grid-2">
+          <el-form-item label="区域名称" prop="name">
+            <el-input v-model="formData.name" placeholder="请输入区域名称" />
+          </el-form-item>
+          <el-form-item label="区域编码" prop="code">
+            <el-input v-model="formData.code" placeholder="请输入区域编码" />
+          </el-form-item>
+
+          <el-form-item label="排序">
+            <el-input-number v-model="formData.sortOrder" :min="0" />
+          </el-form-item>
+
+          <el-form-item label="状态" v-if="isEdit">
+            <el-switch v-model="formData.isActive" />
+          </el-form-item>
+
+          <el-form-item label="预计配送时长">
+            <div class="form-item-column">
+              <div class="form-item-row">
+                <el-input-number
+                  v-model="formData.expectedDeliveryMinutes"
+                  :min="0"
+                  :precision="0"
+                  :step="5"
+                  :value-on-clear="null"
+                  controls-position="right"
+                  class="dialog-input-number"
+                />
+                <span class="form-suffix-unit">分钟</span>
+              </div>
+              <div class="form-item-hint compact">参考配送用时</div>
             </div>
-            <div class="form-item-hint">本区域参考配送用时,供下单或调度展示</div>
-          </div>
-        </el-form-item>
-        <el-form-item label="预警提前时间">
-          <el-input-number
-            v-model="formData.deliveryWarnAdvanceMinutes"
-            :min="0"
-            :precision="0"
-            :step="1"
-            :value-on-clear="null"
-            controls-position="right"
-            class="dialog-input-number"
-          />
-          <span class="form-suffix-unit">分钟</span>
-          <div class="form-item-hint">运营约定到货时间前,超过此时长仍未送达则向运营预警</div>
-        </el-form-item>
-        <el-form-item label="状态" v-if="isEdit">
-          <el-switch v-model="formData.isActive" />
-        </el-form-item>
+          </el-form-item>
+
+          <el-form-item label="预警提前时间">
+            <div class="form-item-column">
+              <div class="form-item-row">
+                <el-input-number
+                  v-model="formData.deliveryWarnAdvanceMinutes"
+                  :min="0"
+                  :precision="0"
+                  :step="1"
+                  :value-on-clear="null"
+                  controls-position="right"
+                  class="dialog-input-number"
+                />
+                <span class="form-suffix-unit">分钟</span>
+              </div>
+              <div class="form-item-hint compact">超时触发运营预警</div>
+            </div>
+          </el-form-item>
+
+          <el-form-item label="描述" class="form-col-span-2">
+            <el-input v-model="formData.description" type="textarea" placeholder="请输入描述" :rows="3" />
+          </el-form-item>
+        </div>
       </el-form>
       <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>
     </section>
@@ -204,7 +236,7 @@
   import { ref, reactive, onMounted, computed } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
-  import { Plus } from '@element-plus/icons-vue'
+  import { Plus, Close, Check } from '@element-plus/icons-vue'
   import { fetchRegionList, fetchCreateRegion, fetchUpdateRegion, fetchDeleteRegion } from '../../../api/admin'
   import { fetchNextSortOrder } from '../../../utils/sort-order'
   import { fetchAllPagedRecords } from '../../../utils/fetch-paged-list'
@@ -616,6 +648,30 @@
 </script>
 
 <style lang="scss" scoped>
+.region-dialog-form {
+  :deep(.el-form-item) {
+    margin-bottom: 0;
+  }
+
+  :deep(.el-form-item__label) {
+    font-size: 11px;
+    font-weight: 500;
+    color: var(--mint-text-muted);
+    padding: 0 0 4px;
+    line-height: 1.2;
+  }
+}
+
+.form-grid-2 {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px 20px;
+}
+
+.form-col-span-2 {
+  grid-column: 1 / -1;
+}
+
 .dialog-input-number {
   width: 200px;
   vertical-align: middle;
@@ -635,6 +691,12 @@
   line-height: 1.5;
 }
 
+.form-item-hint.compact {
+  margin-top: 4px;
+  font-size: 11px;
+  line-height: 1.35;
+}
+
 .form-item-column {
   display: flex;
   flex-direction: column;