Parcourir la source

统一参数模板与用户管理页面的 Fresh Mint 样式与交互表现。

本次完善查看弹窗展示结构、列表列布局与按钮间距规则,并修复选择控件 placeholder/已选值颜色冲突,保证全局视觉一致性。

Made-with: Cursor
zlwu il y a 6 mois
Parent
commit
913a79b73d

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

@@ -487,6 +487,15 @@ body {
     justify-content: flex-start;
   }
 
+  /* 列表工具栏按钮间距统一由 gap 控制,清除 Element 默认相邻按钮 margin */
+  .list-toolbar-row .el-button + .el-button,
+  .toolbar .el-button + .el-button,
+  .toolbar-right .el-button + .el-button,
+  .toolbar-actions .el-button + .el-button,
+  .filter-actions-end .el-button + .el-button {
+    margin-left: 0 !important;
+  }
+
   .list-toolbar-selected,
   .toolbar-left {
     display: inline-flex;

+ 34 - 5
web-admin/src/assets/styles/core/el-ui.scss

@@ -21,6 +21,8 @@
   --el-text-color-regular: var(--mint-text) !important;
   // secondary 用于次级信息(说明/弱提示)
   --el-text-color-secondary: var(--mint-text-muted) !important;
+  // placeholder 全局占位符颜色(含弹窗 Teleport 场景)
+  --el-text-color-placeholder: #9ca3af !important;
   --el-mask-color: rgba(15, 23, 42, 0.35) !important;
   // 按钮粗度
   --el-font-weight-primary: 500 !important;
@@ -762,15 +764,42 @@ body .el-table thead {
 .page-container .el-textarea__inner::placeholder,
 .console-container .el-input__inner::placeholder,
 .console-container .el-textarea__inner::placeholder {
-  color: #94a3b8 !important;
+  color: #9ca3af !important;
+  -webkit-text-fill-color: #9ca3af !important;
   font-size: 13px;
   opacity: 1;
 }
 
-.page-container .el-select__placeholder,
-.console-container .el-select__placeholder {
-  color: #94a3b8 !important;
+/* 全局兜底(避免 Teleport 到 body 的弹窗不命中 page-container 作用域) */
+.el-input__inner::placeholder,
+.el-textarea__inner::placeholder {
+  color: #9ca3af !important;
+  -webkit-text-fill-color: #9ca3af !important;
   font-size: 13px;
+  opacity: 1;
+}
+
+/* 下拉框已选值回显:必须保持正文深色 */
+.el-select__selected-item {
+  color: var(--mint-text) !important;
+  -webkit-text-fill-color: var(--mint-text) !important;
+}
+
+/* cascader / tree-select 已选回显同样保持正文深色 */
+.el-cascader .el-input__inner,
+.el-cascader .el-cascader__search-input,
+.el-tree-select .el-select__selected-item {
+  color: var(--mint-text) !important;
+  -webkit-text-fill-color: var(--mint-text) !important;
+}
+
+/* select/cascader/tree-select 的 placeholder 走变量,不直接覆盖节点类,避免误伤已选值 */
+.el-select__wrapper,
+.el-cascader .el-input__wrapper,
+.el-tree-select .el-select__wrapper,
+.el-cascader__suggestion-panel,
+.el-tree-select__popper {
+  --el-text-color-placeholder: #9ca3af !important;
 }
 
 // 兜底:页面内原生 input/textarea placeholder(避免自绘控件不走 Element)
@@ -778,7 +807,7 @@ body .el-table thead {
 .page-container textarea::placeholder,
 .console-container input::placeholder,
 .console-container textarea::placeholder {
-  color: #94a3b8;
+  color: #9ca3af;
   font-size: 13px;
   opacity: 1;
 }

+ 92 - 2
web-admin/src/components/business/product-view-dialog/index.vue

@@ -890,18 +890,108 @@ watch(
 .view-content-html {
   max-height: 300px;
   overflow-y: auto;
-  padding: 12px;
+  padding: 14px 16px;
   background: var(--mint-table-head);
   border: 1px solid var(--mint-border);
   border-radius: 8px;
+  color: var(--mint-text);
+  font-size: 14px;
+  line-height: 1.65;
+
+  :deep(h1),
+  :deep(h2),
+  :deep(h3),
+  :deep(h4) {
+    margin: 0 0 10px;
+    color: var(--mint-text);
+    font-weight: 700;
+    line-height: 1.35;
+  }
+  :deep(h1) {
+    font-size: 20px;
+    margin-top: 4px;
+  }
+  :deep(h2) {
+    font-size: 18px;
+    margin-top: 14px;
+    padding-top: 2px;
+  }
+  :deep(h3) {
+    font-size: 16px;
+    margin-top: 12px;
+  }
+  :deep(h4) {
+    font-size: 14px;
+    margin-top: 10px;
+  }
+  :deep(p) {
+    margin: 0 0 10px;
+    color: var(--mint-text);
+  }
+  :deep(p:last-child) {
+    margin-bottom: 0;
+  }
+  :deep(ul),
+  :deep(ol) {
+    margin: 0 0 10px;
+    padding-left: 20px;
+    color: var(--mint-text);
+  }
+  :deep(li) {
+    margin: 4px 0;
+  }
+  :deep(blockquote) {
+    margin: 10px 0;
+    padding: 8px 12px;
+    border-left: 3px solid var(--mint-primary);
+    background: var(--mint-primary-bg);
+    color: var(--mint-text-muted);
+    border-radius: 0 6px 6px 0;
+  }
+  :deep(a) {
+    color: var(--mint-primary-dark);
+    text-decoration: none;
+    border-bottom: 1px solid rgba(5, 150, 105, 0.25);
+  }
+  :deep(hr) {
+    border: 0;
+    border-top: 1px dashed var(--mint-border);
+    margin: 12px 0;
+  }
+  :deep(table) {
+    width: 100%;
+    border-collapse: collapse;
+    margin: 10px 0;
+    background: var(--mint-surface);
+    border: 1px solid var(--mint-border);
+    border-radius: 8px;
+    overflow: hidden;
+  }
+  :deep(th),
+  :deep(td) {
+    border-bottom: 1px solid var(--mint-border);
+    padding: 8px 10px;
+    text-align: left;
+    font-size: 13px;
+  }
+  :deep(th) {
+    background: #f8fafc;
+    color: var(--mint-text-muted);
+    font-weight: 600;
+  }
+  :deep(tr:last-child td) {
+    border-bottom: none;
+  }
   :deep(img),
   :deep(video) {
     max-width: 100%;
     height: auto;
+    border-radius: 8px;
+    border: 1px solid var(--mint-border);
+    background: #fff;
   }
   :deep(video) {
     max-height: 280px;
-    border-radius: 4px;
     margin: 8px 0;
   }
 }

+ 256 - 81
web-admin/src/views/system/parameter/index.vue

@@ -8,7 +8,10 @@
 
     <section class="panel">
       <div class="panel-top">
-        <h2>参数列表</h2>
+        <div class="panel-title">
+          <el-icon class="title-ic"><Setting /></el-icon>
+          <span>参数列表</span>
+        </div>
       </div>
 
     <div class="filter-block">
@@ -18,7 +21,7 @@
           <el-input v-model="searchForm.keyword" placeholder="参数名称/编码" clearable @keyup.enter="handleSearch" />
         </el-form-item>
         <el-form-item label="状态">
-          <el-select v-model="searchForm.isActive" placeholder="全部" clearable style="width: 120px">
+          <el-select v-model="searchForm.isActive" placeholder="全部" clearable>
             <el-option label="全部" :value="undefined" />
             <el-option label="启用" :value="1" />
             <el-option label="禁用" :value="0" />
@@ -38,12 +41,13 @@
     <!-- 操作栏 -->
     <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>
         新增参数
       </el-button>
 
-      <el-button class="btn-ghost" type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
+      <el-button class="btn-ghost" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
         批量删除
       </el-button>
 
@@ -54,6 +58,7 @@
         :filename="parameterExportFilename"
         sheetName="参数数据"
         :columns="parameterExportColumns"
+        type="default"
       >
         批量导出
       </ArtExcelExport>
@@ -65,12 +70,13 @@
         :columns="parameterImportTemplateColumns"
         :showSuccessMessage="false"
         :showErrorMessage="false"
+        type="default"
       >
         下载导入模板
       </ArtExcelExport>
 
       <ArtExcelImport @import-success="handleParameterImportSuccess" @import-error="handleExcelImportError">
-        批量导入
+        <el-button class="btn-ghost">批量导入</el-button>
       </ArtExcelImport>
       </div>
     </div>
@@ -79,7 +85,7 @@
     <div class="list-body list-wrap">
     <el-table class="list-table" :data="tableData" v-loading="loading" stripe row-key="id" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" />
-      <el-table-column prop="name" label="参数名称" min-width="120" />
+      <el-table-column prop="name" label="参数名称" min-width="100" />
       <el-table-column prop="code" label="参数编码" min-width="120" />
       <el-table-column prop="paramType" label="输入类型" width="120">
         <template #default="{ row }">
@@ -119,7 +125,7 @@
         </template>
       </el-table-column>
       <el-table-column prop="sortOrder" label="排序" width="80" align="center" />
-      <el-table-column label="操作" width="180" fixed="right">
+      <el-table-column label="操作" width="260" fixed="right" align="center" header-align="center">
         <template #default="{ row }">
           <el-button class="btn-op-item primary" @click="handleView(row)">详情</el-button>
           <el-button class="btn-op-item" @click="handleEdit(row)">编辑</el-button>
@@ -143,86 +149,132 @@
     </div>
 
     <!-- 新增/编辑弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" @close="handleDialogClose">
-      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
-        <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="如: grade" :disabled="isEdit" />
-        </el-form-item>
-        <el-form-item label="输入类型" prop="paramType">
-          <el-radio-group v-model="formData.paramType">
-            <el-radio value="text">文本输入</el-radio>
-            <el-radio value="select">单选下拉</el-radio>
-            <el-radio value="multi_select">多选</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item v-if="formData.paramType !== 'text'" label="可选值" prop="options">
-          <el-input
-            v-model="formData.optionsText"
-            type="textarea"
-            :rows="3"
-            placeholder="每行一个选项"
-          />
-        </el-form-item>
-        <el-form-item label="输入提示" prop="placeholder">
-          <el-input v-model="formData.placeholder" placeholder="请输入提示文字" />
-        </el-form-item>
-        <el-form-item label="是否必填" prop="isRequired">
-          <el-switch v-model="formData.isRequired" :active-value="1" :inactive-value="0" />
-        </el-form-item>
-        <el-form-item label="状态" prop="isActive">
-          <el-switch v-model="formData.isActive" :active-value="1" :inactive-value="0" />
-        </el-form-item>
-        <el-form-item label="排序" prop="sortOrder">
-          <el-input-number v-model="formData.sortOrder" :min="0" :max="9999" />
-        </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <el-input v-model="formData.remark" type="textarea" :rows="2" />
-        </el-form-item>
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="640px" @close="handleDialogClose">
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" class="parameter-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="如: grade" :disabled="isEdit" />
+          </el-form-item>
+          <el-form-item label="输入类型" prop="paramType" class="form-col-span-2">
+            <el-radio-group v-model="formData.paramType">
+              <el-radio value="text">文本输入</el-radio>
+              <el-radio value="select">单选下拉</el-radio>
+              <el-radio value="multi_select">多选</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item v-if="formData.paramType !== 'text'" label="可选值" prop="options" class="form-col-span-2">
+            <el-input
+              v-model="formData.optionsText"
+              type="textarea"
+              :rows="3"
+              placeholder="每行一个选项"
+            />
+          </el-form-item>
+          <el-form-item label="输入提示" prop="placeholder">
+            <el-input v-model="formData.placeholder" placeholder="请输入提示文字" />
+          </el-form-item>
+          <el-form-item label="排序" prop="sortOrder">
+            <el-input-number v-model="formData.sortOrder" :min="0" :max="9999" />
+          </el-form-item>
+          <el-form-item label="是否必填" prop="isRequired">
+            <el-switch v-model="formData.isRequired" :active-value="1" :inactive-value="0" />
+          </el-form-item>
+          <el-form-item label="状态" prop="isActive">
+            <el-switch v-model="formData.isActive" :active-value="1" :inactive-value="0" />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark" class="form-col-span-2">
+            <el-input v-model="formData.remark" type="textarea" :rows="2" />
+          </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>
 
     <!-- 查看详情弹窗 -->
-    <el-dialog v-model="viewVisible" title="参数详情" width="600px">
-      <el-descriptions :column="2" border>
-        <el-descriptions-item label="参数名称">{{ viewData.name }}</el-descriptions-item>
-        <el-descriptions-item label="参数编码">{{ viewData.code }}</el-descriptions-item>
-        <el-descriptions-item label="输入类型">
-          <el-tag
-            :type="getParamTypeTagType(viewData.paramType)"
-            :effect="getParamTypeTagEffect(viewData.paramType)"
-            :class="{ 'tag-neutral': viewData.paramType === 'text' }"
-            size="small"
-          >
-            {{ getParamTypeText(viewData.paramType) }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="是否必填">
-          <el-tag
-            :type="viewData.isRequired === 1 ? 'danger' : 'info'"
-            :effect="'light'"
-            :class="{ 'tag-neutral': viewData.isRequired !== 1 }"
-            size="small"
-          >
-            {{ viewData.isRequired === 1 ? '是' : '否' }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="可选值">{{ viewData.options ? formatOptions(viewData.options) : '-' }}</el-descriptions-item>
-        <el-descriptions-item label="输入提示">{{ viewData.placeholder || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="排序">{{ viewData.sortOrder }}</el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag :type="viewData.isActive === 1 ? 'success' : 'info'" size="small">
-            {{ viewData.isActive === 1 ? '启用' : '禁用' }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
-      </el-descriptions>
+    <el-dialog v-model="viewVisible" title="参数详情" width="680px" class="parameter-view-dialog">
+      <div class="parameter-view-body">
+        <div class="view-hero">
+          <div class="view-hero-main">
+            <h2>{{ viewData.name || '参数详情' }}</h2>
+            <div class="view-hero-tags">
+              <el-tag
+                :type="getParamTypeTagType(viewData.paramType)"
+                :effect="getParamTypeTagEffect(viewData.paramType)"
+                :class="{ 'tag-neutral': viewData.paramType === 'text' }"
+                size="small"
+              >
+                {{ getParamTypeText(viewData.paramType) }}
+              </el-tag>
+              <el-tag :type="viewData.isActive === 1 ? 'success' : 'info'" size="small">
+                {{ viewData.isActive === 1 ? '启用' : '禁用' }}
+              </el-tag>
+            </div>
+            <p class="view-hero-meta">参数编码:{{ viewData.code || '—' }}</p>
+          </div>
+        </div>
+
+        <div class="view-section">
+          <h3>基础信息</h3>
+          <dl class="view-kv-grid">
+            <div class="view-kv">
+              <dt>参数名称</dt>
+              <dd>{{ viewData.name || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>参数编码</dt>
+              <dd>{{ viewData.code || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>输入类型</dt>
+              <dd>{{ getParamTypeText(viewData.paramType) }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>是否必填</dt>
+              <dd>{{ viewData.isRequired === 1 ? '是' : '否' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>状态</dt>
+              <dd>
+                <el-switch :model-value="viewData.isActive === 1" disabled />
+              </dd>
+            </div>
+            <div class="view-kv">
+              <dt>排序</dt>
+              <dd>{{ viewData.sortOrder ?? 0 }}</dd>
+            </div>
+            <div class="view-kv view-kv-span-2">
+              <dt>输入提示</dt>
+              <dd>{{ viewData.placeholder || '—' }}</dd>
+            </div>
+            <div class="view-kv view-kv-span-2">
+              <dt>可选值</dt>
+              <dd>{{ viewData.options ? formatOptions(viewData.options) : '—' }}</dd>
+            </div>
+            <div class="view-kv view-kv-span-2">
+              <dt>备注</dt>
+              <dd class="view-multiline">{{ viewData.remark || '—' }}</dd>
+            </div>
+          </dl>
+        </div>
+      </div>
+      <template #footer>
+        <el-button class="btn-ghost" @click="viewVisible = false">
+          <el-icon><Close /></el-icon>
+          关闭
+        </el-button>
+      </template>
     </el-dialog>
     </section>
   </div>
@@ -232,7 +284,7 @@
 import { ref, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus'
 import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
-import { Plus } from '@element-plus/icons-vue'
+import { Check, Close, Plus, Setting } from '@element-plus/icons-vue'
 import {
   fetchProductParameterList,
   fetchCreateProductParameter,
@@ -797,4 +849,127 @@ onMounted(() => {
     border-color: #e4e7ed;
   }
 }
+
+.parameter-dialog-form :deep(.el-form-item__label) {
+  height: auto !important;
+  line-height: 1.25 !important;
+  padding: 0 0 6px !important;
+  font-size: 11px;
+  color: var(--mint-text-muted);
+  font-weight: 600;
+}
+
+.form-grid-2 {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 0 14px;
+}
+
+.form-col-span-2 {
+  grid-column: 1 / -1;
+}
+
+.parameter-view-body {
+  max-height: 62vh;
+  overflow-y: auto;
+  padding-right: 6px;
+}
+
+.view-hero {
+  display: flex;
+  align-items: flex-start;
+  gap: 16px;
+  padding: 16px;
+  margin-bottom: 12px;
+  background: linear-gradient(135deg, var(--mint-primary-bg) 0%, var(--mint-surface) 55%);
+  border: 1px solid var(--mint-border);
+  border-radius: 10px;
+}
+
+.view-hero-main {
+  flex: 1;
+  min-width: 220px;
+}
+
+.view-hero-main h2 {
+  margin: 0 0 8px;
+  font-size: 18px;
+  font-weight: 700;
+  letter-spacing: -0.02em;
+  color: var(--mint-text);
+}
+
+.view-hero-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+}
+
+.view-hero-meta {
+  margin: 10px 0 0;
+  font-size: 13px;
+  color: var(--mint-text-muted);
+  line-height: 1.5;
+}
+
+.view-section {
+  padding: 4px 2px 0;
+}
+
+.view-section h3 {
+  margin: 0 0 12px;
+  font-size: 13px;
+  font-weight: 700;
+  color: var(--mint-text-muted);
+  letter-spacing: 0.06em;
+  text-transform: uppercase;
+}
+
+.view-kv-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px 24px;
+}
+
+.view-kv {
+  margin: 0;
+  display: grid;
+  grid-template-columns: 92px 1fr;
+  gap: 8px 12px;
+  align-items: start;
+  font-size: 13px;
+}
+
+.view-kv dt {
+  margin: 0;
+  color: var(--mint-text-muted);
+  font-weight: 500;
+}
+
+.view-kv dd {
+  margin: 0;
+  color: var(--mint-text);
+  word-break: break-word;
+  font-weight: 500;
+}
+
+.view-kv-span-2 {
+  grid-column: 1 / -1;
+}
+
+.view-multiline {
+  white-space: pre-wrap;
+  line-height: 1.6;
+  color: var(--mint-text);
+}
+
+@media (max-width: 760px) {
+  .view-kv-grid {
+    grid-template-columns: 1fr;
+  }
+  .view-kv-span-2 {
+    grid-column: auto;
+  }
+}
 </style>

+ 301 - 94
web-admin/src/views/system/user/index.vue

@@ -9,12 +9,10 @@
 
     <section class="panel">
       <div class="panel-top">
-        <h2>
-          <span class="title-ic" aria-hidden="true">
-            <el-icon><Operation /></el-icon>
-          </span>
-          用户列表
-        </h2>
+        <div class="panel-title">
+          <el-icon class="title-ic"><UserFilled /></el-icon>
+          <span>用户列表</span>
+        </div>
       </div>
 
     <div class="filter-block">
@@ -70,7 +68,7 @@
         新增用户
       </el-button>
 
-      <el-button class="btn-ghost" type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
+      <el-button class="btn-ghost" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
         批量删除
       </el-button>
 
@@ -81,6 +79,7 @@
         :filename="userExportFilename"
         sheetName="用户数据"
         :columns="userExportColumns"
+        type="default"
       >
         批量导出
       </ArtExcelExport>
@@ -91,13 +90,22 @@
     <div class="list-body list-wrap">
     <el-table class="list-table" :data="tableData" v-loading="loading" stripe row-key="id" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" />
-      <el-table-column label="头像" width="72" align="center">
+      <el-table-column
+        label="姓名"
+        min-width="170"
+        align="left"
+        header-align="center"
+        class-name="user-name-col"
+        header-class-name="user-name-col-header"
+      >
         <template #default="{ row }">
-          <el-avatar :size="36" :src="resolveUserAvatarUrl(row.avatarUrl, row.role)" />
+          <div class="user-name-cell">
+            <el-avatar :size="36" :src="resolveUserAvatarUrl(row.avatarUrl, row.role)" />
+            <span class="user-name-text">{{ row.realName || '-' }}</span>
+          </div>
         </template>
       </el-table-column>
       <el-table-column prop="username" label="用户名" min-width="150" />
-      <el-table-column prop="realName" label="姓名" min-width="120" />
       <el-table-column prop="phone" label="手机号" min-width="130" />
       <el-table-column prop="role" label="角色" min-width="100">
         <template #default="{ row }">
@@ -142,95 +150,144 @@
     <el-dialog
       v-model="dialogVisible"
       :title="dialogTitle"
-      width="600px"
+      width="680px"
       @close="handleDialogClose"
     >
-      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
-        <el-form-item label="用户名" prop="username" v-if="!isEdit">
-          <el-input v-model="formData.username" placeholder="请输入用户名" />
-        </el-form-item>
-        <el-form-item label="密码" prop="password" v-if="!isEdit">
-          <el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password />
-        </el-form-item>
-        <el-form-item label="姓名" prop="realName">
-          <el-input v-model="formData.realName" placeholder="请输入姓名" />
-        </el-form-item>
-        <el-form-item label="手机号" prop="phone">
-          <el-input v-model="formData.phone" placeholder="请输入手机号" />
-        </el-form-item>
-        <el-form-item label="角色" prop="role">
-          <el-select v-model="formData.role" placeholder="请选择角色">
-            <el-option label="运营管理员" value="admin" />
-            <el-option label="供应商" value="supplier" />
-            <el-option label="客户" value="client" />
-            <el-option label="配送员" value="deliver" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="所属区域" prop="regionId">
-          <el-select
-            v-model="formData.regionId"
-            placeholder="请选择区域"
-            @visible-change="(open) => open && loadRegions()"
-          >
-            <el-option v-for="region in regionList" :key="region.id" :label="region.name" :value="region.id" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="头像" prop="avatarUrl">
-          <div class="avatar-field">
-            <el-avatar :size="56" :src="resolveUserAvatarUrl(formData.avatarUrl, formData.role)" />
-            <div class="avatar-actions">
-              <el-upload
-                :show-file-list="false"
-                accept="image/jpeg,image/png,image/gif,image/webp"
-                :before-upload="beforeAvatarUpload"
-              >
-                <el-button type="primary" link>上传图片</el-button>
-              </el-upload>
-              <el-button
-                v-if="formData.avatarUrl"
-                type="danger"
-                link
-                @click="formData.avatarUrl = ''"
-              >
-                清除
-              </el-button>
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" class="user-dialog-form">
+        <div class="form-grid-2">
+          <el-form-item label="用户名" prop="username" v-if="!isEdit">
+            <el-input v-model="formData.username" placeholder="请输入用户名" />
+          </el-form-item>
+          <el-form-item label="密码" prop="password" v-if="!isEdit">
+            <el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password />
+          </el-form-item>
+          <el-form-item label="姓名" prop="realName">
+            <el-input v-model="formData.realName" placeholder="请输入姓名" />
+          </el-form-item>
+          <el-form-item label="手机号" prop="phone">
+            <el-input v-model="formData.phone" placeholder="请输入手机号" />
+          </el-form-item>
+          <el-form-item label="角色" prop="role">
+            <el-select v-model="formData.role" placeholder="请选择角色">
+              <el-option label="运营管理员" value="admin" />
+              <el-option label="供应商" value="supplier" />
+              <el-option label="客户" value="client" />
+              <el-option label="配送员" value="deliver" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="所属区域" prop="regionId">
+            <el-select
+              v-model="formData.regionId"
+              placeholder="请选择区域"
+              @visible-change="(open) => open && loadRegions()"
+            >
+              <el-option v-for="region in regionList" :key="region.id" :label="region.name" :value="region.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="头像" prop="avatarUrl" class="form-col-span-2">
+            <div class="avatar-field">
+              <el-avatar :size="56" :src="resolveUserAvatarUrl(formData.avatarUrl, formData.role)" />
+              <div class="avatar-actions">
+                <el-upload
+                  :show-file-list="false"
+                  accept="image/jpeg,image/png,image/gif,image/webp"
+                  :before-upload="beforeAvatarUpload"
+                >
+                  <el-button type="primary" link>上传图片</el-button>
+                </el-upload>
+                <el-button
+                  v-if="formData.avatarUrl"
+                  type="danger"
+                  link
+                  @click="formData.avatarUrl = ''"
+                >
+                  清除
+                </el-button>
+              </div>
+              <div class="avatar-tip">未上传时显示叁强 logo</div>
             </div>
-            <div class="avatar-tip">未上传时显示叁强 logo</div>
-          </div>
-        </el-form-item>
-        <el-form-item label="状态" prop="isActive" v-if="isEdit">
-          <el-switch v-model="formData.isActive" />
-        </el-form-item>
+          </el-form-item>
+          <el-form-item label="状态" prop="isActive" v-if="isEdit">
+            <el-switch v-model="formData.isActive" />
+          </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>
 
     <!-- 查看详情弹窗 -->
-    <el-dialog v-model="viewVisible" title="用户详情" width="600px">
-      <div class="view-avatar-row">
-        <el-avatar :size="72" :src="resolveUserAvatarUrl(viewData.avatarUrl, viewData.role)" />
+    <el-dialog v-model="viewVisible" title="用户详情" width="680px" class="user-view-dialog">
+      <div class="user-view-body">
+        <div class="view-hero">
+          <el-avatar :size="72" :src="resolveUserAvatarUrl(viewData.avatarUrl, viewData.role)" class="view-hero-avatar" />
+          <div class="view-hero-main">
+            <h2>{{ viewData.realName || viewData.username || '用户详情' }}</h2>
+            <div class="view-hero-tags">
+              <el-tag :type="getRoleTagType(viewData.role)" size="small">
+                {{ getRoleText(viewData.role) }}
+              </el-tag>
+              <el-tag :type="viewData.isActive ? 'success' : 'info'" size="small">
+                {{ viewData.isActive ? '启用' : '禁用' }}
+              </el-tag>
+            </div>
+            <p class="view-hero-meta">用户名:{{ viewData.username || '—' }}</p>
+          </div>
+        </div>
+        <div class="view-section">
+          <h3>基础信息</h3>
+          <dl class="view-kv-grid">
+            <div class="view-kv">
+              <dt>用户名</dt>
+              <dd>{{ viewData.username || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>姓名</dt>
+              <dd>{{ viewData.realName || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>手机号</dt>
+              <dd>{{ viewData.phone || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>角色</dt>
+              <dd>{{ getRoleText(viewData.role) || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>所属区域</dt>
+              <dd>{{ viewData.regionName || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>状态</dt>
+              <dd>
+                <el-switch :model-value="!!viewData.isActive" disabled />
+              </dd>
+            </div>
+            <div class="view-kv">
+              <dt>创建时间</dt>
+              <dd>{{ viewData.createAt || '—' }}</dd>
+            </div>
+            <div class="view-kv">
+              <dt>创建人</dt>
+              <dd>{{ viewData.createName || '—' }}</dd>
+            </div>
+          </dl>
+        </div>
       </div>
-      <el-descriptions :column="2" border>
-        <el-descriptions-item label="用户名">{{ viewData.username }}</el-descriptions-item>
-        <el-descriptions-item label="姓名">{{ viewData.realName }}</el-descriptions-item>
-        <el-descriptions-item label="手机号">{{ viewData.phone }}</el-descriptions-item>
-        <el-descriptions-item label="角色">
-          <el-tag :type="getRoleTagType(viewData.role)" size="small">
-            {{ getRoleText(viewData.role) }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="所属区域">{{ viewData.regionName }}</el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag :type="viewData.isActive ? 'success' : 'danger'" size="small">
-            {{ viewData.isActive ? '启用' : '禁用' }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="创建时间">{{ viewData.createAt }}</el-descriptions-item>
-        <el-descriptions-item label="创建人">{{ viewData.createName }}</el-descriptions-item>
-      </el-descriptions>
+      <template #footer>
+        <el-button class="btn-ghost" @click="viewVisible = false">
+          <el-icon><Close /></el-icon>
+          关闭
+        </el-button>
+      </template>
     </el-dialog>
     </section>
   </div>
@@ -240,7 +297,7 @@
   import { ref, reactive, onMounted, computed, watch } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
-  import { Operation, Plus } from '@element-plus/icons-vue'
+  import { Check, Close, Plus, UserFilled } from '@element-plus/icons-vue'
   import {
     fetchUserList,
     fetchCreateUser,
@@ -389,7 +446,7 @@
       admin: 'primary',
       supplier: 'warning',
       client: 'success',
-      deliver: 'info'
+      deliver: 'danger'
     }
     return typeMap[role] || 'info'
   }
@@ -644,9 +701,159 @@
   color: #909399;
 }
 
-.view-avatar-row {
+/* 用户列表/详情头像去掉默认浅灰底 */
+:deep(.el-avatar) {
+  background-color: transparent !important;
+}
+
+.user-name-cell {
+  display: inline-flex;
+  align-items: center;
+  gap: 10px;
+  max-width: 100%;
+  min-width: 0;
+}
+
+.user-name-text {
+  color: var(--mint-text);
+  font-weight: 600;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+:deep(.user-name-col .cell) {
+  justify-content: flex-start !important;
+  text-align: left !important;
+}
+
+:deep(.user-name-col-header .cell) {
+  justify-content: center !important;
+  text-align: center !important;
+}
+
+:deep(th.user-name-col-header) {
+  text-align: center !important;
+}
+
+:deep(th.user-name-col-header > .cell) {
+  justify-content: center !important;
+  text-align: center !important;
+  width: 100%;
+}
+
+.user-dialog-form :deep(.el-form-item__label) {
+  height: auto !important;
+  line-height: 1.25 !important;
+  padding: 0 0 6px !important;
+  font-size: 11px;
+  color: var(--mint-text-muted);
+  font-weight: 600;
+}
+
+.form-grid-2 {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 0 14px;
+}
+
+.form-col-span-2 {
+  grid-column: 1 / -1;
+}
+
+.user-view-body {
+  max-height: 62vh;
+  overflow-y: auto;
+  padding-right: 6px;
+}
+
+.view-hero {
+  display: flex;
+  align-items: flex-start;
+  gap: 16px;
+  padding: 16px;
+  margin-bottom: 12px;
+  background: linear-gradient(135deg, var(--mint-primary-bg) 0%, var(--mint-surface) 55%);
+  border: 1px solid var(--mint-border);
+  border-radius: 10px;
+}
+
+.view-hero-avatar {
+  flex-shrink: 0;
+  border: 1px solid var(--mint-border);
+}
+
+.view-hero-main {
+  flex: 1;
+  min-width: 220px;
+}
+
+.view-hero-main h2 {
+  margin: 0 0 8px;
+  font-size: 18px;
+  font-weight: 700;
+  letter-spacing: -0.02em;
+  color: var(--mint-text);
+}
+
+.view-hero-tags {
   display: flex;
-  justify-content: center;
-  margin-bottom: 16px;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+}
+
+.view-hero-meta {
+  margin: 10px 0 0;
+  font-size: 13px;
+  color: var(--mint-text-muted);
+  line-height: 1.5;
+}
+
+.view-section {
+  padding: 4px 2px 0;
+}
+
+.view-section h3 {
+  margin: 0 0 12px;
+  font-size: 13px;
+  font-weight: 700;
+  color: var(--mint-text-muted);
+  letter-spacing: 0.06em;
+  text-transform: uppercase;
+}
+
+.view-kv-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px 24px;
+}
+
+.view-kv {
+  margin: 0;
+  display: grid;
+  grid-template-columns: 92px 1fr;
+  gap: 8px 12px;
+  align-items: start;
+  font-size: 13px;
+}
+
+.view-kv dt {
+  margin: 0;
+  color: var(--mint-text-muted);
+  font-weight: 500;
+}
+
+.view-kv dd {
+  margin: 0;
+  color: var(--mint-text);
+  word-break: break-word;
+  font-weight: 500;
+}
+
+@media (max-width: 760px) {
+  .view-kv-grid {
+    grid-template-columns: 1fr;
+  }
 }
 </style>