Преглед изворни кода

基本重构订单功能;全面更新UI

Huanyi пре 1 дан
родитељ
комит
d2e799241c

+ 8 - 0
src/api/system/applet/slideshow/types.ts

@@ -3,7 +3,15 @@ export interface SlideshowVO {
   ossId: string;
   /** 翻译后的URL */
   ossUrl: string;
+  /** 创建者ID */
+  createBy: string;
+  /** 翻译后的创建者名称 */
+  createByName: string;
   createTime: string;
+  /** 更新者ID */
+  updateBy: string;
+  /** 翻译后的更新者名称 */
+  updateByName: string;
   updateTime: string;
 }
 

+ 82 - 0
src/assets/styles/page-common.scss

@@ -0,0 +1,82 @@
+// ==================== 现代化列表页通用样式 Mixin ====================
+// 使用方法:在页面 <style scoped lang="scss"> 中
+//   @use '@/assets/styles/page-common.scss' as *;
+//   .xxx-page { @include page-common-list; }
+
+@mixin page-common-list {
+  padding: 20px 24px;
+  background: #f7f8fc;
+  min-height: 100%;
+  box-sizing: border-box;
+
+  :deep(.el-card) {
+    border-radius: 12px !important;
+    border: none !important;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03) !important;
+  }
+
+  :deep(.el-card__header) {
+    border-bottom: 1px solid #edf2f7;
+    padding: 16px 20px;
+  }
+
+  :deep(.el-card__body) {
+    padding: 20px;
+  }
+
+  :deep(.el-table) {
+    border: none !important;
+  }
+
+  :deep(.el-table::before) {
+    display: none;
+  }
+
+  :deep(.el-table__inner-wrapper::before) {
+    display: none;
+  }
+
+  :deep(.el-table__header th) {
+    background: #fafbfc !important;
+    color: #4a5568;
+    font-weight: 600;
+    font-size: 13px;
+    border-bottom: 2px solid #e2e8f0 !important;
+    border-right: none !important;
+  }
+
+  :deep(.el-table__body td) {
+    border-right: none !important;
+    border-bottom: 1px solid #f1f5f9 !important;
+  }
+
+  :deep(.el-table__row:hover > td) {
+    background: #f8fafc !important;
+  }
+
+  :deep(.el-button) {
+    border-radius: 8px;
+    font-weight: 500;
+  }
+
+  :deep(.el-button + .el-button) {
+    margin-left: 8px !important;
+  }
+
+  :deep(.el-table .el-button + .el-button) {
+    margin-left: 0 !important;
+  }
+
+  :deep(.el-input__wrapper) {
+    border-radius: 8px;
+    box-shadow: 0 0 0 1px #e2e8f0 inset;
+  }
+
+  :deep(.el-input__wrapper:hover) {
+    box-shadow: 0 0 0 1px #cbd5e1 inset;
+  }
+
+  :deep(.el-input__wrapper.is-focus) {
+    box-shadow: 0 0 0 1px #4f46e5 inset, 0 0 0 3px rgba(79, 70, 229, 0.08);
+  }
+}

+ 4 - 1
src/assets/styles/ruoyi.scss

@@ -99,7 +99,10 @@ h6 {
     }
   }
   .el-table__body-wrapper {
-    .el-button [class*='el-icon-'] + span {
+      .el-button + .el-button {
+        margin-left: 0 !important;
+      }
+      .el-button [class*='el-icon-'] + span {
       margin-left: 1px;
     }
   }

+ 6 - 20
src/components/FileUpload/index.vue

@@ -1,21 +1,9 @@
 <template>
   <div class="upload-file">
-    <el-upload
-      ref="fileUploadRef"
-      multiple
-      :action="uploadFileUrl"
-      :before-upload="handleBeforeUpload"
-      :file-list="fileList"
-      :limit="limit"
-      :accept="fileAccept"
-      :on-error="handleUploadError"
-      :on-exceed="handleExceed"
-      :on-success="handleUploadSuccess"
-      :show-file-list="false"
-      :headers="headers"
-      class="upload-file-uploader"
-      v-if="!disabled"
-    >
+    <el-upload ref="fileUploadRef" multiple :action="uploadFileUrl" :before-upload="handleBeforeUpload"
+      :file-list="fileList" :limit="limit" :accept="fileAccept" :on-error="handleUploadError" :on-exceed="handleExceed"
+      :on-success="handleUploadSuccess" :show-file-list="false" :headers="headers" class="upload-file-uploader"
+      v-if="!disabled">
       <!-- 上传按钮 -->
       <el-button type="primary">选取文件</el-button>
     </el-upload>
@@ -46,7 +34,7 @@
 
 <script setup lang="ts">
 import { propTypes } from '@/utils/propTypes';
-import { delOss, listByIds } from '@/api/system/oss';
+import { listByIds } from '@/api/system/oss';
 import { globalHeaders } from '@/utils/request';
 
 const props = defineProps({
@@ -174,10 +162,8 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
   }
 };
 
-// 删除文件
+// 删除文件(假删:仅从列表移除,不调用后端删除接口)
 const handleDelete = (index: number) => {
-  const ossId = fileList.value[index].ossId;
-  delOss(ossId);
   fileList.value.splice(index, 1);
   emit('update:modelValue', listToString(fileList.value));
 };

+ 7 - 22
src/components/ImageUpload/index.vue

@@ -1,23 +1,10 @@
 <template>
   <div class="component-upload-image">
-    <el-upload
-      ref="imageUploadRef"
-      multiple
-      :action="uploadImgUrl"
-      list-type="picture-card"
-      :on-success="handleUploadSuccess"
-      :before-upload="handleBeforeUpload"
-      :limit="limit"
-      :accept="fileAccept"
-      :on-error="handleUploadError"
-      :on-exceed="handleExceed"
-      :before-remove="handleDelete"
-      :show-file-list="true"
-      :headers="headers"
-      :file-list="fileList"
-      :on-preview="handlePictureCardPreview"
-      :class="{ hide: fileList.length >= limit }"
-    >
+    <el-upload ref="imageUploadRef" multiple :action="uploadImgUrl" list-type="picture-card"
+      :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :limit="limit" :accept="fileAccept"
+      :on-error="handleUploadError" :on-exceed="handleExceed" :before-remove="handleDelete" :show-file-list="true"
+      :headers="headers" :file-list="fileList" :on-preview="handlePictureCardPreview"
+      :class="{ hide: fileList.length >= limit }">
       <el-icon class="avatar-uploader-icon">
         <plus />
       </el-icon>
@@ -41,7 +28,7 @@
 </template>
 
 <script setup lang="ts">
-import { listByIds, delOss } from '@/api/system/oss';
+import { listByIds } from '@/api/system/oss';
 import { OssVO } from '@/api/system/oss/types';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
@@ -185,12 +172,10 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
   }
 };
 
-// 删除图片
+// 删除图片(假删:仅从列表移除,不调用后端删除接口)
 const handleDelete = (file: UploadFile): boolean => {
   const findex = fileList.value.map((f) => f.name).indexOf(file.name);
   if (findex > -1 && uploadList.value.length === number.value) {
-    const ossId = fileList.value[findex].ossId;
-    delOss(ossId);
     fileList.value.splice(findex, 1);
     emit('update:modelValue', listToString(fileList.value));
     return false;

+ 302 - 73
src/views/complaint/index.vue

@@ -1,6 +1,6 @@
 <!-- @Author: Antigravity -->
 <template>
-  <div class="p-2">
+  <div class="complaint-page">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
       :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="search">
@@ -32,7 +32,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="complaintList" border @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" :data="complaintList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="序号" align="center" width="70" type="index" />
         <el-table-column label="类型" align="center" prop="feedbackType" width="100">
@@ -62,14 +62,14 @@
         <el-table-column label="用户姓名" align="center" prop="employeeName" width="120" />
         <el-table-column label="用户手机" align="center" prop="employeePhone" width="130" />
         <el-table-column label="提交时间" align="center" prop="createTime" width="170" />
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
           <template #default="scope">
             <div class="action-btns">
-              <el-button v-hasPermi="['complaint:complaint:query']" link type="primary" icon="View"
+              <el-button v-hasPermi="['complaint:complaint:query']" link type="primary"
                 @click="handleDetail(scope.row)">详情</el-button>
               <el-button v-if="scope.row.status !== '1'" v-hasPermi="['complaint:complaint:deal']" link type="warning"
-                icon="Finished" @click="handleDeal(scope.row)">处理</el-button>
-              <el-button v-hasPermi="['system:complaint:remove']" link type="danger" icon="Delete"
+                @click="handleDeal(scope.row)">处理</el-button>
+              <el-button v-hasPermi="['system:complaint:remove']" link type="danger"
                 @click="handleDelete(scope.row)">删除</el-button>
             </div>
           </template>
@@ -81,52 +81,113 @@
     </el-card>
 
     <!-- 投诉详情对话框 -->
-    <el-dialog v-model="detailDialog.visible" title="投诉详情" width="680px" append-to-body>
+    <el-dialog v-model="detailDialog.visible" width="620px" :close-on-click-modal="false" append-to-body
+      class="detail-dialog">
+      <template #header>
+        <div class="detail-dialog-title">
+          <el-icon :size="20" class="title-icon">
+            <Document />
+          </el-icon>
+          <span>投诉详情</span>
+        </div>
+      </template>
+
       <template v-if="detailDialog.data">
-        <div class="detail-header">
-          <div class="detail-user-info">
-            <el-avatar :size="48" icon="UserFilled" />
-            <div>
-              <div class="detail-nickname">{{ detailDialog.data.employeeName }}</div>
-              <div class="detail-phone">{{ detailDialog.data.employeePhone || '-' }}</div>
+        <div class="detail-card">
+          <div class="detail-card__user">
+            <el-avatar :size="52" class="detail-avatar">
+              <el-icon :size="24">
+                <UserFilled />
+              </el-icon>
+            </el-avatar>
+            <div class="detail-card__meta">
+              <div class="detail-card__name">{{ detailDialog.data.employeeName }}</div>
+              <div class="detail-card__phone">{{ detailDialog.data.employeePhone || '-' }}</div>
+            </div>
+            <div class="detail-card__badge">
+              <span class="status-dot"
+                :class="detailDialog.data.status === '0' ? 'status-pending' : 'status-done'"></span>
+              {{ detailDialog.data.status === '0' ? '待处理' : '已处理' }}
             </div>
           </div>
-          <el-tag :type="detailDialog.data.status === '0' ? 'warning' : 'success'" size="small">
-            {{ detailDialog.data.status === '0' ? '待处理' : '已处理' }}
-          </el-tag>
         </div>
-        <div class="detail-divider"></div>
-        <el-descriptions :column="2" border size="small" class="detail-descriptions">
-          <el-descriptions-item label="类型">
-            <dict-tag :options="sys_complaint_type" :value="detailDialog.data.feedbackType" />
-          </el-descriptions-item>
-          <el-descriptions-item label="投诉ID">{{ detailDialog.data.id }}</el-descriptions-item>
-          <el-descriptions-item label="投诉内容" :span="2">{{ detailDialog.data.content }}</el-descriptions-item>
-          <el-descriptions-item label="投诉图片" :span="2">
-            <div v-if="detailDialog.data.imageUrls" class="descriptions-images">
-              <el-image v-for="(url, idx) in detailDialog.data.imageUrls.split(',')" :key="idx" :src="url"
-                :preview-src-list="detailDialog.data.imageUrls.split(',')" :initial-index="(idx as number)" fit="cover"
-                style="width:60px;height:60px;border-radius:4px;margin-right:6px" preview-teleported />
+
+        <div class="detail-section">
+          <div class="detail-section__head">
+            <el-icon :size="16">
+              <InfoFilled />
+            </el-icon>
+            <span>基本信息</span>
+          </div>
+          <div class="detail-grid">
+            <div class="detail-item">
+              <span class="detail-label">投诉编号</span>
+              <span class="detail-value">{{ detailDialog.data.id }}</span>
             </div>
-            <span v-else>-</span>
-          </el-descriptions-item>
-          <el-descriptions-item label="提交时间">{{ detailDialog.data.createTime }}</el-descriptions-item>
-        </el-descriptions>
+            <div class="detail-item">
+              <span class="detail-label">反馈类型</span>
+              <dict-tag :options="sys_complaint_type" :value="detailDialog.data.feedbackType" />
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">提交时间</span>
+              <span class="detail-value">{{ detailDialog.data.createTime }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="detail-section">
+          <div class="detail-section__head">
+            <el-icon :size="16">
+              <ChatLineSquare />
+            </el-icon>
+            <span>投诉内容</span>
+          </div>
+          <div class="detail-content-text">{{ detailDialog.data.content }}</div>
+        </div>
+
+        <div class="detail-section" v-if="detailDialog.data.imageUrls">
+          <div class="detail-section__head">
+            <el-icon :size="16">
+              <PictureFilled />
+            </el-icon>
+            <span>投诉图片</span>
+          </div>
+          <div class="detail-images">
+            <div class="detail-image-item" v-for="(url, idx) in detailDialog.data.imageUrls.split(',')" :key="idx">
+              <el-image :src="url" :preview-src-list="detailDialog.data.imageUrls.split(',')"
+                :initial-index="(idx as number)" fit="cover" preview-teleported />
+            </div>
+          </div>
+        </div>
 
-        <!-- 已处理时展示处理结果和凭证 -->
         <template v-if="detailDialog.data.status === '1'">
-          <div class="section-title">处理结果</div>
-          <el-descriptions :column="1" border size="small">
-            <el-descriptions-item label="处理结果">{{ detailDialog.data.dealResult }}</el-descriptions-item>
-            <el-descriptions-item label="处理凭证">
-              <div v-if="detailDialog.data.dealImageUrls" class="descriptions-images">
-                <el-image v-for="(url, idx) in detailDialog.data.dealImageUrls.split(',')" :key="idx" :src="url"
-                  :preview-src-list="detailDialog.data.dealImageUrls.split(',')" :initial-index="(idx as number)"
-                  fit="cover" style="width:60px;height:60px;border-radius:4px;margin-right:6px" preview-teleported />
+          <div class="detail-divider"></div>
+
+          <div class="detail-section">
+            <div class="detail-section__head highlight">
+              <el-icon :size="16">
+                <CircleCheckFilled />
+              </el-icon>
+              <span>处理结果</span>
+            </div>
+            <div class="detail-content-text deal-text">{{ detailDialog.data.dealResult }}</div>
+          </div>
+
+          <div class="detail-section" v-if="detailDialog.data.dealImageUrls">
+            <div class="detail-section__head">
+              <el-icon :size="16">
+                <PictureFilled />
+              </el-icon>
+              <span>处理凭证</span>
+            </div>
+            <div class="detail-images">
+              <div class="detail-image-item" v-for="(url, idx) in detailDialog.data.dealImageUrls.split(',')"
+                :key="idx">
+                <el-image :src="url" :preview-src-list="detailDialog.data.dealImageUrls.split(',')"
+                  :initial-index="(idx as number)" fit="cover" preview-teleported />
               </div>
-              <span v-else>-</span>
-            </el-descriptions-item>
-          </el-descriptions>
+            </div>
+          </div>
         </template>
       </template>
     </el-dialog>
@@ -156,6 +217,7 @@
 </template>
 
 <script setup name="Complaint" lang="ts">
+import { Document, UserFilled, InfoFilled, ChatLineSquare, PictureFilled, CircleCheckFilled } from '@element-plus/icons-vue';
 import { listComplaint, getComplaintDetail, dealComplaint, delComplaint } from '@/api/system/complaint';
 import { ComplaintVO, ComplaintQuery, ComplaintDealForm } from '@/api/system/complaint/types';
 
@@ -308,7 +370,9 @@ onMounted(() => {
 });
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
 /* 操作按钮一行显示 */
 .action-btns {
   display: flex;
@@ -333,56 +397,217 @@ onMounted(() => {
   margin-left: 2px;
 }
 
-/* 详情弹窗样式 */
-.detail-header {
+/* ==================== 详情弹窗优化 ==================== */
+.detail-dialog :deep(.el-dialog) {
+  border-radius: 14px;
+  overflow: hidden;
+}
+
+.detail-dialog :deep(.el-dialog__header) {
+  padding: 18px 24px 16px;
+  margin-right: 0;
+  border-bottom: 1px solid #eef1f6;
+}
+
+.detail-dialog-title {
   display: flex;
   align-items: center;
-  justify-content: space-between;
-  padding-bottom: 20px;
+  gap: 8px;
+  font-size: 16px;
+  font-weight: 700;
+  color: #1a202c;
 }
 
-.detail-user-info {
+.detail-dialog-title .title-icon {
+  color: #4f46e5;
+}
+
+.detail-dialog :deep(.el-dialog__body) {
+  padding: 24px;
+}
+
+/* 用户信息卡片 */
+.detail-card {
+  background: linear-gradient(135deg, #f8fafc 0%, #eef2ff 100%);
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 24px;
+}
+
+.detail-card__user {
   display: flex;
   align-items: center;
-  gap: 14px;
+  gap: 16px;
 }
 
-.detail-nickname {
-  font-size: 16px;
-  font-weight: 600;
-  color: #1d1d1f;
+.detail-avatar {
+  background: linear-gradient(135deg, #4f46e5, #7c3aed) !important;
+  flex-shrink: 0;
 }
 
-.detail-phone {
-  font-size: 13px;
-  color: #999;
-  margin-top: 2px;
+.detail-card__meta {
+  flex: 1;
+  min-width: 0;
 }
 
-.detail-divider {
-  height: 1px;
-  background: linear-gradient(90deg, #eee 0%, transparent 100%);
-  margin-bottom: 20px;
+.detail-card__name {
+  font-size: 17px;
+  font-weight: 700;
+  color: #1a202c;
+  line-height: 1.3;
 }
 
-.detail-descriptions {
-  margin-bottom: 8px;
+.detail-card__phone {
+  font-size: 13px;
+  color: #718096;
+  margin-top: 3px;
 }
 
-.descriptions-images {
+.detail-card__badge {
   display: flex;
-  flex-wrap: wrap;
+  align-items: center;
   gap: 6px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #4a5568;
+  background: #fff;
+  padding: 6px 14px 6px 10px;
+  border-radius: 20px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+  white-space: nowrap;
 }
 
-/* 处理结果、授权客户 分区标题 */
-.section-title {
+.status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+.status-pending {
+  background: #f59e0b;
+  box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.15);
+}
+
+.status-done {
+  background: #10b981;
+  box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
+}
+
+/* 分区 */
+.detail-section {
+  margin-bottom: 22px;
+}
+
+.detail-section:last-child {
+  margin-bottom: 0;
+}
+
+.detail-section__head {
+  display: flex;
+  align-items: center;
+  gap: 6px;
   font-size: 14px;
   font-weight: 600;
-  color: #303133;
-  margin: 20px 0 12px 0;
-  padding-left: 10px;
-  border-left: 3px solid #e6a23c;
+  color: #4a5568;
+  margin-bottom: 12px;
+  padding-left: 2px;
+}
+
+.detail-section__head .el-icon {
+  color: #4f46e5;
+}
+
+.detail-section__head.highlight {
+  color: #059669;
+}
+
+.detail-section__head.highlight .el-icon {
+  color: #10b981;
+}
+
+/* 信息网格 */
+.detail-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 14px;
+  background: #f8fafc;
+  border-radius: 10px;
+  padding: 16px 18px;
+}
+
+.detail-item {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.detail-label {
+  font-size: 12px;
+  font-weight: 500;
+  color: #a0aec0;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.detail-value {
+  font-size: 14px;
+  font-weight: 500;
+  color: #1a202c;
+}
+
+/* 内容文本 */
+.detail-content-text {
+  background: #f8fafc;
+  border: 1px solid #eef1f6;
+  border-radius: 10px;
+  padding: 14px 16px;
+  font-size: 14px;
+  color: #4a5568;
+  line-height: 1.7;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+
+.deal-text {
+  background: #ecfdf5;
+  border-color: #a7f3d0;
+  color: #065f46;
+}
+
+/* 图片展示 */
+.detail-images {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.detail-image-item {
+  width: 80px;
+  height: 80px;
+  border-radius: 10px;
+  overflow: hidden;
+  cursor: pointer;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
+  transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.detail-image-item:hover {
+  transform: scale(1.05);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.detail-image-item .el-image {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+/* 已处理分隔线 */
+.detail-divider {
+  height: 1px;
+  background: linear-gradient(90deg, #e2e8f0 0%, transparent 100%);
+  margin: 28px 0 24px;
 }
 
 /* 处理弹窗 */
@@ -396,4 +621,8 @@ onMounted(() => {
   max-height: 120px;
   overflow-y: auto;
 }
+
+.complaint-page {
+  @include page-common-list;
+}
 </style>

+ 17 - 37
src/views/employee/components/SelectClientDialog.vue

@@ -1,54 +1,32 @@
 <template>
-  <el-dialog
-    :model-value="modelValue"
-    @update:model-value="$emit('update:modelValue', $event)"
-    :title="title"
-    width="560px"
-    append-to-body
-    class="select-client-dialog"
-    @opened="onOpened"
-  >
+  <el-dialog :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)" :title="title"
+    width="560px" append-to-body class="select-client-dialog" @opened="onOpened">
     <div class="select-client-body">
       <div class="sc-selected-bar" v-if="localSelected.length > 0">
         <span class="sc-label">{{ selectedLabel }}</span>
-        <el-tag
-          v-for="(client, idx) in localSelected"
-          :key="idx"
-          closable
-          size="default"
-          @close="removeClient(idx)"
-          class="sc-tag"
-        >
+        <el-tag v-for="(client, idx) in localSelected" :key="idx" closable size="default" @close="removeClient(idx)"
+          class="sc-tag">
           {{ client.name }}
         </el-tag>
       </div>
 
       <div class="sc-search">
-        <el-input
-          v-model="keyword"
-          :placeholder="searchPlaceholder"
-          clearable
-          size="large"
-          class="sc-search-input"
-          @keyup.enter="handleSearch"
-        >
+        <el-input v-model="keyword" :placeholder="searchPlaceholder" clearable size="large" class="sc-search-input"
+          @keyup.enter="handleSearch">
           <template #prefix>
-            <el-icon><Search /></el-icon>
+            <el-icon>
+              <Search />
+            </el-icon>
           </template>
         </el-input>
-        <el-button type="primary" size="large" :loading="searchLoading" @click="handleSearch">
+        <el-button type="primary" size="large" icon="Search" :loading="searchLoading" @click="handleSearch">
           搜索
         </el-button>
       </div>
 
       <div class="sc-results" v-if="searchResults.length > 0">
-        <div
-          v-for="item in searchResults"
-          :key="item.rowId"
-          class="sc-result-card"
-          :class="{ 'is-selected': isSelected(item.rowId) }"
-          @click="addClient(item)"
-        >
+        <div v-for="item in searchResults" :key="item.rowId" class="sc-result-card"
+          :class="{ 'is-selected': isSelected(item.rowId) }" @click="addClient(item)">
           <div class="sc-result-left">
             <div class="sc-result-avatar">{{ item.name?.charAt(0) }}</div>
             <div class="sc-result-info">
@@ -67,14 +45,16 @@
       <el-empty v-else-if="searched" description="未找到匹配的客户" :image-size="60" />
 
       <div v-if="!searched && searchResults.length === 0" class="sc-placeholder">
-        <el-icon class="sc-placeholder-icon"><Search /></el-icon>
+        <el-icon class="sc-placeholder-icon">
+          <Search />
+        </el-icon>
         <p>{{ placeholderText }}</p>
       </div>
     </div>
     <template #footer>
       <div class="add-dialog-footer">
-        <el-button size="large" @click="$emit('update:modelValue', false)">取 消</el-button>
-        <el-button size="large" type="primary" :loading="confirmLoading" @click="$emit('confirm')">
+        <el-button size="large" icon="Close" @click="$emit('update:modelValue', false)">取 消</el-button>
+        <el-button size="large" type="primary" icon="Check" :loading="confirmLoading" @click="$emit('confirm')">
           {{ confirmText }}({{ localSelected.length }})
         </el-button>
       </div>

+ 158 - 99
src/views/employee/index.vue

@@ -1,6 +1,6 @@
 <!-- @Author: Antigravity -->
 <template>
-  <div class="p-2">
+  <div class="employee-page">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
       :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="search">
@@ -30,7 +30,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="employeeList" border @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" :data="employeeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="序号" align="center" width="70" type="index" />
         <el-table-column label="姓名" align="center" prop="name" min-width="120" />
@@ -50,11 +50,11 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
           <template #default="scope">
-            <el-button v-hasPermi="['employee:employee:auth']" link type="success" icon="User"
+            <el-button v-hasPermi="['employee:employee:auth']" link type="success"
               @click="handleAuth(scope.row)">授权</el-button>
-            <el-button v-hasPermi="['employee:employee:query']" link type="primary" icon="View"
+            <el-button v-hasPermi="['employee:employee:query']" link type="primary"
               @click="handleDetail(scope.row)">详情</el-button>
           </template>
         </el-table-column>
@@ -122,8 +122,9 @@
       </div>
       <template #footer>
         <div class="add-dialog-footer">
-          <el-button size="large" @click="addDialog.visible = false">取 消</el-button>
-          <el-button size="large" type="primary" :loading="addBtnLoading" @click="confirmAdd">确认新增</el-button>
+          <el-button size="large" icon="Close" @click="addDialog.visible = false">取 消</el-button>
+          <el-button size="large" type="primary" icon="Check" :loading="addBtnLoading"
+            @click="confirmAdd">确认新增</el-button>
         </div>
       </template>
     </el-dialog>
@@ -134,40 +135,43 @@
       @search="handleSelectClientSearch" @confirm="selectClientVisible = false" />
 
     <!-- 员工详情对话框 -->
-    <el-dialog v-model="detailDialog.visible" title="员工详情" width="680px" append-to-body>
+    <el-dialog v-model="detailDialog.visible" title="员工详情" width="640px" append-to-body class="detail-employee-dialog">
       <template v-if="detailDialog.data">
-        <div class="detail-header">
-          <el-avatar :size="72" :src="detailDialog.data.avatarUrl" icon="UserFilled" class="detail-avatar" />
-          <div class="detail-name-box">
-            <text class="detail-name">{{ detailDialog.data.name }}</text>
-            <el-tag :type="detailDialog.data.status === '0' ? 'danger' : 'success'" size="small"
-              class="detail-status-tag">
-              {{ detailDialog.data.status === '0' ? '已禁用' : '已启用' }}
-            </el-tag>
+        <div class="detail-top-card">
+          <el-avatar :size="64" :src="detailDialog.data.avatarUrl" icon="UserFilled" class="detail-avatar" />
+          <div class="detail-top-info">
+            <span class="detail-top-name">{{ detailDialog.data.name }}</span>
+            <div class="detail-top-meta">
+              <span class="detail-top-phone">{{ detailDialog.data.phone || '-' }}</span>
+              <el-tag :type="detailDialog.data.status === '0' ? 'danger' : 'success'" size="small" round>
+                {{ detailDialog.data.status === '0' ? '已禁用' : '已启用' }}
+              </el-tag>
+            </div>
+          </div>
+        </div>
+        <div class="detail-grid">
+          <div class="detail-cell">
+            <span class="detail-cell-label">员工ID</span>
+            <span class="detail-cell-value">{{ detailDialog.data.id }}</span>
+          </div>
+          <div class="detail-cell">
+            <span class="detail-cell-label">注册时间</span>
+            <span class="detail-cell-value">{{ detailDialog.data.createTime }}</span>
           </div>
         </div>
-        <div class="detail-divider"></div>
-        <el-descriptions :column="2" border size="small" class="detail-descriptions">
-          <el-descriptions-item label="员工ID">{{ detailDialog.data.id }}</el-descriptions-item>
-          <el-descriptions-item label="手机号">{{ detailDialog.data.phone || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="注册时间">{{ detailDialog.data.createTime }}</el-descriptions-item>
-        </el-descriptions>
-
-        <div class="section-title">授权客户</div>
-        <div v-if="detailDialog.data.authClientList && detailDialog.data.authClientList.length > 0">
-          <div class="auth-client-card" v-for="(client, idx) in detailDialog.data.authClientList" :key="idx">
-            <span class="client-index">{{ idx + 1 }}</span>
-            <div class="client-info">
-              <div class="client-row"><span class="client-label">名称</span><span class="client-value">{{ client.name
-              }}</span></div>
-              <div class="client-row"><span class="client-label">类型</span><span class="client-value">{{
-                client.clientClass || '-' }}</span></div>
-              <div class="client-row"><span class="client-label">加入时间</span><span class="client-value">{{
-                client.enterDate || '-' }}</span></div>
+
+        <div class="detail-section-title">授权客户</div>
+        <div v-if="detailDialog.data.authClientList && detailDialog.data.authClientList.length > 0"
+          class="detail-client-list">
+          <div class="detail-client-item" v-for="(client, idx) in detailDialog.data.authClientList" :key="idx">
+            <div class="detail-client-avatar">{{ client.name?.charAt(0) }}</div>
+            <div class="detail-client-body">
+              <span class="detail-client-name">{{ client.name }}</span>
+              <span class="detail-client-meta">{{ client.clientClass || '-' }} · {{ client.enterDate || '-' }}</span>
             </div>
           </div>
         </div>
-        <el-empty v-else description="暂无授权客户" :image-size="60" />
+        <el-empty v-else description="暂无授权客户" :image-size="48" />
       </template>
     </el-dialog>
   </div>
@@ -497,117 +501,167 @@ onMounted(() => {
 });
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
 /* ========== 详情弹窗 ========== */
-.section-title {
-  font-size: 14px;
+.detail-employee-dialog :deep(.el-dialog__header) {
+  padding: 24px 28px 0;
+  margin: 0;
+  border-bottom: none;
+}
+
+.detail-employee-dialog :deep(.el-dialog__title) {
+  font-size: 18px;
   font-weight: 600;
-  color: #303133;
-  margin: 20px 0 12px 0;
-  padding-left: 10px;
-  border-left: 3px solid #C1001C;
+  color: #1f2937;
+  letter-spacing: 0.5px;
 }
 
-:deep(.el-dialog .pagination-container) {
-  display: flex !important;
-  justify-content: center !important;
-  background-color: transparent !important;
-  padding: 20px 0 10px 0 !important;
-  margin-top: 0 !important;
+.detail-employee-dialog :deep(.el-dialog__body) {
+  padding: 20px 28px 28px;
 }
 
-.detail-header {
+.detail-top-card {
   display: flex;
   align-items: center;
-  gap: 24px;
-  padding-bottom: 20px;
+  gap: 20px;
+  padding: 24px;
+  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  border-radius: 14px;
+  margin-bottom: 24px;
 }
 
 .detail-avatar {
   flex-shrink: 0;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  border: 3px solid #fff;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
+.detail-top-info {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
 }
 
-.detail-name-box {
+.detail-top-name {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1f2937;
+  letter-spacing: 0.5px;
+}
+
+.detail-top-meta {
   display: flex;
   align-items: center;
   gap: 12px;
 }
 
-.detail-name {
-  font-size: 22px;
-  font-weight: 600;
-  color: #1d1d1f;
+.detail-top-phone {
+  font-size: 13px;
+  color: #6b7280;
+}
+
+.detail-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 16px;
+  margin-bottom: 28px;
+}
+
+.detail-cell {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  padding: 14px 16px;
+  background: #f9fafb;
+  border-radius: 10px;
+  border: 1px solid #f0f2f5;
+}
+
+.detail-cell-label {
+  font-size: 12px;
+  font-weight: 500;
+  color: #9ca3af;
+  text-transform: uppercase;
   letter-spacing: 0.5px;
 }
 
-.detail-status-tag {
-  flex-shrink: 0;
+.detail-cell-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
 }
 
-.detail-divider {
-  height: 1px;
-  background: linear-gradient(90deg, #eee 0%, transparent 100%);
-  margin-bottom: 20px;
+.detail-section-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
 }
 
-.detail-descriptions {
-  margin-bottom: 8px;
+.detail-section-title::before {
+  content: '';
+  width: 4px;
+  height: 16px;
+  background: #3b82f6;
+  border-radius: 2px;
 }
 
-.auth-client-card {
+.detail-client-list {
   display: flex;
-  align-items: flex-start;
-  gap: 16px;
-  background: #fafafa;
-  border-radius: 12px;
-  padding: 16px 20px;
-  margin-bottom: 12px;
-  border: 1px solid #f0f0f0;
-  transition: box-shadow 0.2s;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.detail-client-item {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+  padding: 14px 16px;
+  background: #fff;
+  border-radius: 10px;
+  border: 1px solid #f0f2f5;
+  transition: box-shadow 0.2s, border-color 0.2s;
 }
 
-.auth-client-card:hover {
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+.detail-client-item:hover {
+  border-color: #e0e5ea;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
 }
 
-.client-index {
-  width: 28px;
-  height: 28px;
-  background: #C1001C;
+.detail-client-avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 10px;
+  background: linear-gradient(135deg, #3b82f6, #2563eb);
   color: #fff;
-  border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
-  font-size: 13px;
+  font-size: 16px;
   font-weight: 600;
   flex-shrink: 0;
-  margin-top: 2px;
 }
 
-.client-info {
-  flex: 1;
+.detail-client-body {
   display: flex;
   flex-direction: column;
-  gap: 6px;
+  gap: 4px;
 }
 
-.client-row {
-  display: flex;
-  gap: 12px;
-  font-size: 13px;
-}
-
-.client-label {
-  color: #999;
-  min-width: 60px;
-  flex-shrink: 0;
+.detail-client-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
 }
 
-.client-value {
-  color: #333;
-  font-weight: 500;
+.detail-client-meta {
+  font-size: 12px;
+  color: #9ca3af;
 }
 
 /* ========== 新增员工对话框 ========== */
@@ -813,4 +867,9 @@ onMounted(() => {
 .add-dialog-footer .el-button--primary:hover {
   box-shadow: 0 4px 16px rgba(64, 158, 255, 0.35);
 }
+
+/* ==================== 页面级样式优化 ==================== */
+.employee-page {
+  @include page-common-list;
+}
 </style>

+ 19 - 2
src/views/goodsStock/index.vue

@@ -1,8 +1,9 @@
-<template>
   <!-- 
     成品库存管理主页面
     @Author: Antigravity
   -->
+<template>
+
   <div class="app-container goods-stock-container">
     <!-- 过滤搜索控制栏 -->
     <el-card class="search-card mb20 shadow-sm" border="false">
@@ -32,7 +33,7 @@
 
     <!-- 数据呈现主表格 -->
     <el-card class="table-card shadow-sm" border="false">
-      <el-table v-loading="loading" :data="stockList" stripe row-key="itemNo" border highlight-current-row
+      <el-table v-loading="loading" :data="stockList" stripe row-key="itemNo" highlight-current-row
         class="premium-table">
         <el-table-column type="index" label="序号" align="center" width="55" fixed />
         <el-table-column label="单据编号" align="center" prop="docCode" width="130" show-overflow-tooltip fixed />
@@ -180,6 +181,10 @@ onMounted(() => {
 
     .btn-group {
       margin-left: 10px;
+
+      :deep(.el-button + .el-button) {
+        margin-left: 8px;
+      }
     }
 
     .glow-btn {
@@ -202,6 +207,18 @@ onMounted(() => {
     .premium-table {
       font-size: 13px;
 
+      :deep(.el-table) {
+        border: none !important;
+      }
+
+      :deep(.el-table::before) {
+        display: none;
+      }
+
+      :deep(.el-table__inner-wrapper::before) {
+        display: none;
+      }
+
       :deep(.el-table__header-wrapper) th {
         background-color: #f8f9fc !important;
         color: #606266;

+ 170 - 421
src/views/index.vue

@@ -1,93 +1,49 @@
 <template>
   <div class="dashboard">
-    <div class="welcome-section">
-      <div class="welcome-bg-decor">
-        <div class="decor-ring ring-1"></div>
-        <div class="decor-ring ring-2"></div>
-        <div class="decor-ring ring-3"></div>
-        <div class="decor-grid"></div>
-        <div class="decor-particle p1"></div>
-        <div class="decor-particle p2"></div>
-        <div class="decor-particle p3"></div>
-      </div>
-      <div class="welcome-content">
-        <div class="welcome-text">
-          <div class="welcome-tag">华晟ERP订单管理系统</div>
-          <h2>{{ greeting }},{{ userStore.nickname || userStore.name }}</h2>
-          <p class="welcome-sub">
-            <span class="date-icon">📅</span>
-            {{ currentDate }}
-          </p>
+    <div class="welcome-bar">
+      <div class="welcome-left">
+        <div class="welcome-avatar">
+          <span class="welcome-avatar-text">{{ (userStore.nickname || userStore.name || 'U').charAt(0) }}</span>
         </div>
-        <div class="welcome-actions">
-          <el-button plain round icon="Refresh" @click="loadStatistics" :loading="loading">刷新数据</el-button>
+        <div class="welcome-info">
+          <h2 class="welcome-greeting">{{ greeting }},{{ userStore.nickname || userStore.name }}</h2>
+          <p class="welcome-date">{{ currentDate }}</p>
         </div>
       </div>
+      <el-button class="refresh-btn" icon="Refresh" @click="loadStatistics" :loading="loading">刷新数据</el-button>
     </div>
 
     <div class="stats-grid">
-      <div
-        v-for="(stat, idx) in statistics"
-        :key="stat.label"
-        class="stat-card"
-        :style="{ '--card-color': stat.color, '--card-bg': stat.bgColor, '--delay': idx * 0.08 + 's' }"
-        @click="router.push('/order')"
-      >
-        <div class="stat-card-glow" :style="{ background: stat.color }"></div>
-        <div class="stat-card-inner">
-          <div class="stat-top">
-            <div class="stat-icon-wrap">
-              <svg-icon :icon-class="stat.icon" />
-            </div>
-            <div class="stat-badge" :style="{ background: stat.bgColor, color: stat.color }">
-              {{ stat.label }}
-            </div>
-          </div>
-          <div class="stat-body">
-            <div class="stat-value">{{ stat.count }}</div>
-            <div class="stat-unit">笔</div>
-          </div>
-          <div class="stat-footer">
-            <span class="stat-hint">点击查看详情</span>
-            <span class="stat-arrow">→</span>
-          </div>
+      <div v-for="(stat, idx) in statistics" :key="stat.label" class="stat-card"
+        :style="{ '--card-color': stat.color, '--card-bg': stat.bgColor, '--delay': idx * 0.06 + 's' }">
+        <div class="stat-icon" :style="{ background: stat.bgColor, color: stat.color }">
+          <svg-icon :icon-class="stat.icon" />
+        </div>
+        <div class="stat-content">
+          <span class="stat-label">{{ stat.label }}</span>
+          <span class="stat-value" :style="{ color: stat.label === '全部订单' ? '#1f2937' : stat.color }">
+            {{ stat.count.toLocaleString() }}
+          </span>
         </div>
       </div>
     </div>
 
     <div class="chart-section">
       <div class="chart-header">
-        <div class="chart-header-left">
-          <div class="chart-title">订单分布</div>
-          <div class="chart-subtitle">各状态订单数量占比</div>
-        </div>
-        <div class="chart-legend">
-          <div v-for="stat in statistics.slice(1)" :key="stat.label" class="legend-item">
-            <span class="legend-dot" :style="{ background: stat.color }"></span>
-            <span class="legend-label">{{ stat.label }}</span>
-          </div>
-        </div>
+        <h3 class="chart-title">订单分布</h3>
+        <span class="chart-subtitle">各状态订单数量占比</span>
       </div>
       <div class="chart-body">
         <div v-for="(stat, idx) in statistics.slice(1)" :key="stat.label" class="chart-row">
-          <div class="chart-row-label">
-            <span class="chart-row-dot" :style="{ background: stat.color }"></span>
-            {{ stat.label }}
+          <span class="chart-row-label">{{ stat.label }}</span>
+          <div class="chart-row-track">
+            <div class="chart-row-fill" :style="{
+              width: barWidth(stat.count),
+              background: stat.color,
+              '--delay': idx * 0.08 + 's'
+            }"></div>
           </div>
-          <div class="chart-row-bar-wrap">
-            <div class="chart-row-bar-bg">
-              <div
-                class="chart-row-bar-fill"
-                :style="{
-                  width: barWidth(stat.count),
-                  background: `linear-gradient(90deg, ${stat.color}, ${stat.color}dd)`,
-                  '--delay': idx * 0.1 + 's'
-                }"
-              ></div>
-            </div>
-          </div>
-          <div class="chart-row-value">{{ stat.count }}<span class="chart-row-unit">笔</span></div>
-          <div class="chart-row-pct">{{ barPercent(stat.count) }}</div>
+          <span class="chart-row-count">{{ stat.count }}</span>
         </div>
       </div>
     </div>
@@ -97,12 +53,10 @@
 <script setup name="Index" lang="ts">
 import { countOrder } from '@/api/erp/order';
 import { useUserStore } from '@/store/modules/user';
-import { useRouter } from 'vue-router';
 
 /** @Author: Antigravity */
 
 const userStore = useUserStore();
-const router = useRouter();
 const loading = ref(false);
 
 const statistics = ref([
@@ -137,12 +91,6 @@ function barWidth(count: number) {
   return (count / max * 100).toFixed(1) + '%';
 }
 
-function barPercent(count: number) {
-  const total = statistics.value.reduce((s, v) => s + v.count, 0);
-  if (!total) return '0%';
-  return (count / total * 100).toFixed(1) + '%';
-}
-
 function loadStatistics() {
   loading.value = true;
   countOrder().then(res => {
@@ -168,405 +116,218 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 .dashboard {
-  padding: 24px;
+  padding: 28px 32px;
   min-height: calc(100vh - 100px);
-  background: #f5f7fa;
+  background: #f7f8fc;
+  max-width: 1200px;
+  margin: 0 auto;
 }
 
-.welcome-section {
-  background: linear-gradient(135deg, #4F6EF7 0%, #7C5CFC 50%, #A855F7 100%);
-  border-radius: 16px;
-  padding: 36px 44px;
-  margin-bottom: 28px;
-  position: relative;
-  overflow: hidden;
-
-  .welcome-bg-decor {
-    position: absolute;
-    inset: 0;
-    pointer-events: none;
-
-    .decor-ring {
-      position: absolute;
-      border-radius: 50%;
-      border: 1px solid rgba(255, 255, 255, 0.06);
-
-      &.ring-1 {
-        top: -30%;
-        right: -3%;
-        width: 450px;
-        height: 450px;
-      }
-
-      &.ring-2 {
-        bottom: -20%;
-        right: 8%;
-        width: 300px;
-        height: 300px;
-        border-color: rgba(255, 255, 255, 0.04);
-      }
-
-      &.ring-3 {
-        top: 15%;
-        left: 50%;
-        width: 160px;
-        height: 160px;
-        border-color: rgba(255, 255, 255, 0.03);
-      }
-    }
-
-    .decor-grid {
-      position: absolute;
-      top: 24px;
-      left: 32px;
-      width: 72px;
-      height: 72px;
-      background-image:
-        linear-gradient(rgba(255,255,255,0.06) 1px, transparent 1px),
-        linear-gradient(90deg, rgba(255,255,255,0.06) 1px, transparent 1px);
-      background-size: 12px 12px;
-    }
-
-    .decor-particle {
-      position: absolute;
-      border-radius: 50%;
-      background: rgba(255, 255, 255, 0.08);
-
-      &.p1 {
-        top: 25%;
-        left: 38%;
-        width: 6px;
-        height: 6px;
-      }
-
-      &.p2 {
-        top: 55%;
-        left: 42%;
-        width: 4px;
-        height: 4px;
-        background: rgba(255, 255, 255, 0.05);
-      }
-
-      &.p3 {
-        top: 35%;
-        left: 60%;
-        width: 5px;
-        height: 5px;
-      }
-    }
-  }
+/* ==================== Welcome Bar ==================== */
+.welcome-bar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 32px;
+}
 
-  .welcome-content {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    position: relative;
-    z-index: 1;
-  }
+.welcome-left {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
 
-  .welcome-text {
-    .welcome-tag {
-      display: inline-block;
-      font-size: 12px;
-      color: rgba(255, 255, 255, 0.7);
-      background: rgba(255, 255, 255, 0.1);
-      padding: 3px 12px;
-      border-radius: 20px;
-      margin-bottom: 12px;
-      letter-spacing: 1px;
-      backdrop-filter: blur(4px);
-      border: 1px solid rgba(255, 255, 255, 0.08);
-    }
+.welcome-avatar {
+  width: 48px;
+  height: 48px;
+  border-radius: 14px;
+  background: linear-gradient(135deg, #3b82f6, #2563eb);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
+}
 
-    h2 {
-      margin: 0 0 10px 0;
-      font-size: 26px;
-      font-weight: 700;
-      color: #fff;
-      letter-spacing: 0.5px;
-    }
+.welcome-avatar-text {
+  font-size: 20px;
+  font-weight: 700;
+  color: #fff;
+  letter-spacing: 1px;
+}
 
-    .welcome-sub {
-      margin: 0;
-      font-size: 14px;
-      color: rgba(255, 255, 255, 0.7);
-      display: flex;
-      align-items: center;
-      gap: 6px;
+.welcome-greeting {
+  margin: 0;
+  font-size: 22px;
+  font-weight: 700;
+  color: #1f2937;
+  letter-spacing: 0.3px;
+}
 
-      .date-icon {
-        font-size: 14px;
-      }
-    }
-  }
+.welcome-date {
+  margin: 4px 0 0;
+  font-size: 13px;
+  color: #9ca3af;
+}
 
-  .welcome-actions {
-    .el-button {
-      background: rgba(255, 255, 255, 0.12);
-      border-color: rgba(255, 255, 255, 0.2);
-      color: #fff;
-      font-size: 13px;
-      padding: 9px 22px;
-      backdrop-filter: blur(6px);
-      border-radius: 20px;
+.refresh-btn {
+  padding: 8px 20px;
+  border-radius: 10px;
+  font-size: 13px;
+  font-weight: 500;
+  color: #3b82f6;
+  background: rgba(59, 130, 246, 0.08);
+  border: 1px solid rgba(59, 130, 246, 0.15);
+  transition: all 0.25s ease;
+}
 
-      &:hover {
-        background: rgba(255, 255, 255, 0.22);
-        border-color: rgba(255, 255, 255, 0.3);
-      }
-    }
-  }
+.refresh-btn:hover {
+  color: #fff;
+  background: #3b82f6;
+  border-color: #3b82f6;
+  box-shadow: 0 2px 10px rgba(59, 130, 246, 0.3);
 }
 
+/* ==================== Stats Grid ==================== */
 .stats-grid {
   display: grid;
   grid-template-columns: repeat(4, 1fr);
-  gap: 20px;
+  gap: 16px;
   margin-bottom: 28px;
 
   .stat-card {
     background: #fff;
-    border-radius: 16px;
-    cursor: pointer;
-    transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
-    border: 1px solid #edf0f5;
-    position: relative;
-    overflow: hidden;
-    animation: cardFadeIn 0.6s ease both;
+    border-radius: 14px;
+    padding: 20px 22px;
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    border: 1px solid #f0f2f5;
+    transition: all 0.3s ease;
+    animation: cardFadeIn 0.5s ease both;
     animation-delay: var(--delay);
 
     &:hover {
-      transform: translateY(-8px);
-      box-shadow: 0 20px 48px -8px rgba(0, 0, 0, 0.1);
-      border-color: var(--card-color);
-
-      .stat-card-glow {
-        opacity: 1;
-        transform: scale(1);
-      }
-
-      .stat-arrow {
-        transform: translateX(4px);
-        opacity: 1;
-      }
-    }
-
-    .stat-card-glow {
-      position: absolute;
-      top: -80px;
-      right: -80px;
-      width: 200px;
-      height: 200px;
-      border-radius: 50%;
-      opacity: 0;
-      transform: scale(0.3);
-      transition: all 0.5s ease;
-      filter: blur(50px);
-    }
-
-    .stat-card-inner {
-      position: relative;
-      z-index: 1;
-      padding: 24px 24px 18px;
-    }
-
-    .stat-top {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 20px;
+      border-color: #e0e5ea;
+      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+      transform: translateY(-2px);
     }
 
-    .stat-icon-wrap {
-      width: 46px;
-      height: 46px;
-      border-radius: 14px;
-      background: var(--card-bg);
+    .stat-icon {
+      width: 48px;
+      height: 48px;
+      border-radius: 12px;
       display: flex;
       align-items: center;
       justify-content: center;
-      font-size: 24px;
-      color: var(--card-color);
-      transition: all 0.4s ease;
-    }
-
-    &:hover .stat-icon-wrap {
-      transform: scale(1.08) rotate(-6deg);
-      box-shadow: 0 8px 24px rgba(79, 110, 247, 0.15);
+      font-size: 22px;
+      flex-shrink: 0;
+      transition: transform 0.3s ease;
     }
 
-    .stat-badge {
-      font-size: 12px;
-      font-weight: 500;
-      padding: 4px 14px;
-      border-radius: 20px;
-      letter-spacing: 0.5px;
+    &:hover .stat-icon {
+      transform: scale(1.08);
     }
 
-    .stat-body {
+    .stat-content {
       display: flex;
-      align-items: baseline;
+      flex-direction: column;
       gap: 4px;
-      margin-bottom: 16px;
-
-      .stat-value {
-        font-size: 32px;
-        font-weight: 800;
-        color: #1a1a2e;
-        line-height: 1;
-        font-variant-numeric: tabular-nums;
-        letter-spacing: -0.5px;
-      }
-
-      .stat-unit {
-        font-size: 14px;
-        color: #bfc0c4;
-        font-weight: 400;
-      }
+      min-width: 0;
     }
 
-    .stat-footer {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding-top: 14px;
-      border-top: 1px solid #f0f2f5;
-
-      .stat-hint {
-        font-size: 12px;
-        color: #c0c4cc;
-      }
+    .stat-label {
+      font-size: 13px;
+      color: #9ca3af;
+      font-weight: 500;
+    }
 
-      .stat-arrow {
-        font-size: 14px;
-        color: var(--card-color);
-        transition: all 0.3s ease;
-        opacity: 0.5;
-      }
+    .stat-value {
+      font-size: 26px;
+      font-weight: 800;
+      line-height: 1;
+      letter-spacing: -0.5px;
+      font-variant-numeric: tabular-nums;
     }
   }
 }
 
+/* ==================== Chart ==================== */
 .chart-section {
   background: #fff;
-  border-radius: 16px;
+  border-radius: 14px;
   padding: 28px 32px;
-  border: 1px solid #edf0f5;
+  border: 1px solid #f0f2f5;
 
   .chart-header {
     display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 28px;
-
-    .chart-header-left {
-      .chart-title {
-        font-size: 16px;
-        font-weight: 600;
-        color: #1a1a2e;
-        margin-bottom: 4px;
-      }
+    align-items: baseline;
+    gap: 12px;
+    margin-bottom: 24px;
 
-      .chart-subtitle {
-        font-size: 13px;
-        color: #bfc0c4;
-      }
+    .chart-title {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #1f2937;
     }
 
-    .chart-legend {
-      display: flex;
-      gap: 20px;
-
-      .legend-item {
-        display: flex;
-        align-items: center;
-        gap: 6px;
-
-        .legend-dot {
-          width: 8px;
-          height: 8px;
-          border-radius: 50%;
-        }
-
-        .legend-label {
-          font-size: 12px;
-          color: #606266;
-        }
-      }
+    .chart-subtitle {
+      font-size: 13px;
+      color: #9ca3af;
     }
   }
 
   .chart-body {
     display: flex;
     flex-direction: column;
-    gap: 18px;
+    gap: 14px;
 
     .chart-row {
       display: grid;
-      grid-template-columns: 100px 1fr 70px 60px;
+      grid-template-columns: 90px 1fr 60px;
       align-items: center;
       gap: 16px;
 
       .chart-row-label {
-        display: flex;
-        align-items: center;
-        gap: 8px;
         font-size: 13px;
-        color: #606266;
+        color: #6b7280;
         font-weight: 500;
-
-        .chart-row-dot {
-          width: 6px;
-          height: 6px;
-          border-radius: 50%;
-          flex-shrink: 0;
-        }
+        white-space: nowrap;
       }
 
-      .chart-row-bar-wrap {
-        .chart-row-bar-bg {
-          height: 8px;
-          background: #f0f2f5;
-          border-radius: 4px;
-          overflow: hidden;
-
-          .chart-row-bar-fill {
-            height: 100%;
-            border-radius: 4px;
-            animation: barGrow 1s ease both;
-            animation-delay: var(--delay);
-          }
+      .chart-row-track {
+        height: 10px;
+        background: #f3f4f6;
+        border-radius: 5px;
+        overflow: hidden;
+
+        .chart-row-fill {
+          height: 100%;
+          border-radius: 5px;
+          animation: barGrow 0.8s ease both;
+          animation-delay: var(--delay);
+          opacity: 0.85;
         }
       }
 
-      .chart-row-value {
+      .chart-row-count {
         font-size: 14px;
         font-weight: 600;
-        color: #1a1a2e;
-        text-align: right;
-
-        .chart-row-unit {
-          font-size: 11px;
-          color: #bfc0c4;
-          font-weight: 400;
-          margin-left: 2px;
-        }
-      }
-
-      .chart-row-pct {
-        font-size: 12px;
-        color: #909399;
+        color: #4b5563;
         text-align: right;
       }
     }
   }
 }
 
+/* ==================== Animations ==================== */
 @keyframes cardFadeIn {
   from {
     opacity: 0;
-    transform: translateY(24px);
+    transform: translateY(16px);
   }
+
   to {
     opacity: 1;
     transform: translateY(0);
@@ -579,6 +340,7 @@ onMounted(() => {
   }
 }
 
+/* ==================== Responsive ==================== */
 @media (max-width: 1200px) {
   .stats-grid {
     grid-template-columns: repeat(2, 1fr);
@@ -586,39 +348,26 @@ onMounted(() => {
 }
 
 @media (max-width: 768px) {
-  .stats-grid {
-    grid-template-columns: repeat(2, 1fr);
-    gap: 12px;
+  .dashboard {
+    padding: 16px;
   }
 
-  .welcome-section {
-    padding: 24px;
-
-    .welcome-content {
-      flex-direction: column;
-      align-items: flex-start;
-      gap: 16px;
-    }
+  .stats-grid {
+    grid-template-columns: 1fr;
+    gap: 12px;
   }
 
   .chart-section {
     padding: 20px;
 
-    .chart-header {
-      flex-direction: column;
-      align-items: flex-start;
-      gap: 12px;
-
-      .chart-legend {
-        flex-wrap: wrap;
-        gap: 12px;
-      }
-    }
-
     .chart-body .chart-row {
-      grid-template-columns: 80px 1fr 50px 50px;
+      grid-template-columns: 70px 1fr 50px;
       gap: 10px;
     }
   }
+
+  .welcome-greeting {
+    font-size: 18px;
+  }
 }
 </style>

+ 158 - 111
src/views/monitor/cache/index.vue

@@ -1,122 +1,38 @@
 <template>
-  <div class="p-2">
-    <el-row :gutter="10">
-      <el-col :span="24" class="card-box">
-        <el-card shadow="hover">
-          <template #header>
-            <Monitor style="width: 1em; height: 1em; vertical-align: middle" />
-            <span style="vertical-align: middle">基本信息</span>
-          </template>
+  <div class="cache-page">
+    <div class="info-grid">
+      <div class="info-cell" v-for="item in infoItems" :key="item.label">
+        <span class="info-cell-label">{{ item.label }}</span>
+        <span v-if="cache.info || cache.dbSize !== undefined" class="info-cell-value">{{ item.value() }}</span>
+        <el-skeleton v-else animated :rows="1" />
+      </div>
+    </div>
 
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <table style="width: 100%">
-              <tbody>
-                <tr>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">Redis版本</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.redis_version }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">运行模式</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.redis_mode === 'standalone' ? '单机' : '集群' }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">端口</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.tcp_port }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">客户端数</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.connected_clients }}</div>
-                  </td>
-                </tr>
-                <tr>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">运行时间(天)</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.uptime_in_days }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">使用内存</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.used_memory_human }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">使用CPU</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">内存配置</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.maxmemory_human }}</div>
-                  </td>
-                </tr>
-                <tr>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">AOF是否开启</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.aof_enabled === '0' ? '否' : '是' }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">RDB是否成功</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">{{ cache.info.rdb_last_bgsave_status }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">Key数量</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.dbSize" class="cell">{{ cache.dbSize }}</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div class="cell">网络入口/出口</div>
-                  </td>
-                  <td class="el-table__cell is-leaf">
-                    <div v-if="cache.info" class="cell">
-                      {{ cache.info.instantaneous_input_kbps }}kps/{{ cache.info.instantaneous_output_kbps }}kps
-                    </div>
-                  </td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :span="12" class="card-box">
-        <el-card shadow="hover">
+    <el-row :gutter="20" class="chart-row">
+      <el-col :span="12">
+        <el-card shadow="hover" class="chart-card">
           <template #header>
-            <PieChart style="width: 1em; height: 1em; vertical-align: middle" />
-            <span style="vertical-align: middle">命令统计</span>
+            <div class="chart-card-header">
+              <el-icon class="chart-card-icon">
+                <PieChart />
+              </el-icon>
+              <span>命令统计</span>
+            </div>
           </template>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <div ref="commandstats" style="height: 420px" />
-          </div>
+          <div ref="commandstats" class="chart-container" />
         </el-card>
       </el-col>
-
-      <el-col :span="12" class="card-box">
-        <el-card shadow="hover">
+      <el-col :span="12">
+        <el-card shadow="hover" class="chart-card">
           <template #header>
-            <Odometer style="width: 1em; height: 1em; vertical-align: middle" /> <span style="vertical-align: middle">内存信息</span>
+            <div class="chart-card-header">
+              <el-icon class="chart-card-icon">
+                <Odometer />
+              </el-icon>
+              <span>内存信息</span>
+            </div>
           </template>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <div ref="usedmemory" style="height: 420px" />
-          </div>
+          <div ref="usedmemory" class="chart-container" />
         </el-card>
       </el-col>
     </el-row>
@@ -133,6 +49,21 @@ const commandstats = ref();
 const usedmemory = ref();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
+const infoItems = computed(() => [
+  { label: 'Redis版本', value: () => cache.value.info?.redis_version || '-' },
+  { label: '运行模式', value: () => cache.value.info?.redis_mode === 'standalone' ? '单机' : '集群' },
+  { label: '端口', value: () => cache.value.info?.tcp_port || '-' },
+  { label: '客户端数', value: () => cache.value.info?.connected_clients ?? '-' },
+  { label: '运行时间(天)', value: () => cache.value.info?.uptime_in_days ?? '-' },
+  { label: '使用内存', value: () => cache.value.info?.used_memory_human || '-' },
+  { label: '使用CPU', value: () => cache.value.info ? parseFloat(cache.value.info.used_cpu_user_children).toFixed(2) : '-' },
+  { label: '内存配置', value: () => cache.value.info?.maxmemory_human || '-' },
+  { label: 'AOF是否开启', value: () => cache.value.info?.aof_enabled === '0' ? '否' : '是' },
+  { label: 'RDB是否成功', value: () => cache.value.info?.rdb_last_bgsave_status || '-' },
+  { label: 'Key数量', value: () => cache.value.dbSize ?? '-' },
+  { label: '网络入口/出口', value: () => cache.value.info ? `${cache.value.info.instantaneous_input_kbps}kps/${cache.value.info.instantaneous_output_kbps}kps` : '-' }
+]);
+
 const getList = async () => {
   proxy?.$modal.loading('正在加载缓存监控数据,请稍候!');
   const res = await getCache();
@@ -190,3 +121,119 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+.cache-page {
+  padding: 24px 28px;
+  background: #f7f8fc;
+  min-height: calc(100vh - 100px);
+}
+
+/* ==================== 信息网格 ==================== */
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 16px;
+  margin-bottom: 28px;
+}
+
+.info-cell {
+  background: #fff;
+  border-radius: 14px;
+  padding: 18px 22px;
+  border: 1px solid #f0f2f5;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  transition: box-shadow 0.25s, border-color 0.25s;
+
+  &:hover {
+    border-color: #e0e5ea;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
+  }
+}
+
+.info-cell-label {
+  font-size: 12px;
+  font-weight: 500;
+  color: #9ca3af;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.info-cell-value {
+  font-size: 18px;
+  font-weight: 700;
+  color: #1f2937;
+  word-break: break-all;
+}
+
+/* ==================== 图表卡片 ==================== */
+.chart-row {
+  margin: 0 !important;
+}
+
+.chart-card {
+  border-radius: 14px !important;
+  border: 1px solid #f0f2f5 !important;
+
+  :deep(.el-card__header) {
+    padding: 18px 24px;
+    border-bottom: 1px solid #f3f4f6;
+  }
+
+  :deep(.el-card__body) {
+    padding: 16px 20px 20px;
+  }
+}
+
+.chart-card-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.chart-card-icon {
+  font-size: 18px;
+  color: #3b82f6;
+}
+
+.chart-container {
+  height: 400px;
+  width: 100%;
+}
+
+/* ==================== Skeleton ==================== */
+:deep(.el-skeleton) {
+  padding: 0;
+}
+
+/* ==================== Responsive ==================== */
+@media (max-width: 1200px) {
+  .info-grid {
+    grid-template-columns: repeat(3, 1fr);
+  }
+}
+
+@media (max-width: 768px) {
+  .cache-page {
+    padding: 16px;
+  }
+
+  .info-grid {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+  }
+
+  .chart-row {
+    .el-col {
+      flex: 0 0 100%;
+      max-width: 100%;
+      margin-bottom: 16px;
+    }
+  }
+}
+</style>

+ 36 - 39
src/views/monitor/logininfor/index.vue

@@ -1,8 +1,9 @@
 <template>
-  <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+  <div class="logininfor-page">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+      :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
+        <el-card shadow="never">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
             <el-form-item label="登录地址" prop="ipaddr">
               <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
@@ -12,71 +13,57 @@
             </el-form-item>
             <el-form-item label="状态" prop="status">
               <el-select v-model="queryParams.status" placeholder="登录状态" clearable>
-                <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+                <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label"
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
             <el-form-item label="登录时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRange"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              ></el-date-picker>
+              <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
             </el-form-item>
             <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              <el-button type="primary" @click="handleQuery">搜索</el-button>
+              <el-button @click="resetQuery">重置</el-button>
             </el-form-item>
           </el-form>
         </el-card>
       </div>
     </transition>
 
-    <el-card shadow="hover">
+    <el-card shadow="never">
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
+            <el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain :disabled="multiple"
+              @click="handleDelete()">
               删除
             </el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain icon="Delete" @click="handleClean">清空</el-button>
+            <el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain
+              @click="handleClean">清空</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:logininfor:unlock']" type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock">
+            <el-button v-hasPermi="['monitor:logininfor:unlock']" type="primary" plain :disabled="single"
+              @click="handleUnlock">
               解锁
             </el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:logininfor:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
+            <el-button v-hasPermi="['monitor:logininfor:export']" type="warning" plain
+              @click="handleExport">导出</el-button>
           </el-col>
           <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
         </el-row>
       </template>
 
-      <el-table
-        ref="loginInfoTableRef"
-        v-loading="loading"
-        :data="loginInfoList"
-        :default-sort="defaultSort"
-        border
-        @selection-change="handleSelectionChange"
-        @sort-change="handleSortChange"
-      >
+      <el-table ref="loginInfoTableRef" v-loading="loading" :data="loginInfoList" :default-sort="defaultSort"
+        @selection-change="handleSelectionChange" @sort-change="handleSortChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="访问编号" align="center" prop="infoId" />
-        <el-table-column
-          label="用户名称"
-          align="center"
-          prop="userName"
-          :show-overflow-tooltip="true"
-          sortable="custom"
-          :sort-orders="['descending', 'ascending']"
-        />
+        <el-table-column label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom"
+          :sort-orders="['descending', 'ascending']" />
         <el-table-column label="客户端" align="center" prop="clientKey" :show-overflow-tooltip="true" />
         <el-table-column label="设备类型" align="center">
           <template #default="scope">
@@ -93,14 +80,16 @@
           </template>
         </el-table-column>
         <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
-        <el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
+        <el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom"
+          :sort-orders="['descending', 'ascending']" width="180">
           <template #default="scope">
             <span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        :total="total" @pagination="getList" />
     </el-card>
   </div>
 </template>
@@ -207,3 +196,11 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.logininfor-page {
+  @include page-common-list;
+}
+</style>

+ 15 - 7
src/views/monitor/online/index.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="p-2">
+  <div class="online-page">
     <div class="mb-[10px]">
-      <el-card shadow="hover">
+      <el-card shadow="never">
         <el-form ref="queryFormRef" :model="queryParams" :inline="true">
           <el-form-item label="登录地址" prop="ipaddr">
             <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
@@ -10,14 +10,14 @@
             <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            <el-button type="primary" @click="handleQuery">搜索</el-button>
+            <el-button @click="resetQuery">重置</el-button>
           </el-form-item>
         </el-form>
       </el-card>
     </div>
-    <el-card shadow="hover">
-      <el-table v-loading="loading" border
+    <el-card shadow="never">
+      <el-table v-loading="loading"
         :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
         style="width: 100%">
         <el-table-column label="序号" width="50" type="index" align="center">
@@ -45,7 +45,7 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="80" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button v-hasPermi="['monitor:online:forceLogout']" link type="danger" icon="Delete"
+            <el-button v-hasPermi="['monitor:online:forceLogout']" link type="danger"
               @click="handleForceLogout(scope.row)">强退</el-button>
           </template>
         </el-table-column>
@@ -111,3 +111,11 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.online-page {
+  @include page-common-list;
+}
+</style>

+ 36 - 47
src/views/monitor/operlog/index.vue

@@ -1,8 +1,9 @@
 <template>
-  <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+  <div class="operlog-page">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+      :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
+        <el-card shadow="never">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
             <el-form-item label="操作地址" prop="operIp">
               <el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" />
@@ -20,19 +21,14 @@
             </el-form-item>
             <el-form-item label="状态" prop="status">
               <el-select v-model="queryParams.status" placeholder="操作状态" clearable>
-                <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+                <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label"
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
             <el-form-item label="操作时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRange"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              ></el-date-picker>
+              <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
             </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -43,33 +39,29 @@
       </div>
     </transition>
 
-    <el-card shadow="hover">
+    <el-card shadow="never">
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
+            <el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple"
+              @click="handleDelete()">
               删除
             </el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="WarnTriangleFilled" @click="handleClean">清空</el-button>
+            <el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete"
+              @click="handleClean">清空</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
+            <el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download"
+              @click="handleExport">导出</el-button>
           </el-col>
           <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
         </el-row>
       </template>
 
-      <el-table
-        ref="operLogTableRef"
-        v-loading="loading"
-        :data="operlogList"
-        border
-        :default-sort="defaultSort"
-        @selection-change="handleSelectionChange"
-        @sort-change="handleSortChange"
-      >
+      <el-table ref="operLogTableRef" v-loading="loading" :data="operlogList" :default-sort="defaultSort"
+        @selection-change="handleSelectionChange" @sort-change="handleSortChange">
         <el-table-column type="selection" width="50" align="center" />
         <el-table-column label="日志编号" align="center" prop="operId" />
         <el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
@@ -78,15 +70,8 @@
             <dict-tag :options="sys_oper_type" :value="scope.row.businessType" />
           </template>
         </el-table-column>
-        <el-table-column
-          label="操作人员"
-          align="center"
-          width="110"
-          prop="operName"
-          :show-overflow-tooltip="true"
-          sortable="custom"
-          :sort-orders="['descending', 'ascending']"
-        />
+        <el-table-column label="操作人员" align="center" width="110" prop="operName" :show-overflow-tooltip="true"
+          sortable="custom" :sort-orders="['descending', 'ascending']" />
         <el-table-column label="部门" align="center" prop="deptName" width="130" :show-overflow-tooltip="true" />
         <el-table-column label="操作地址" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
         <el-table-column label="操作状态" align="center" prop="status">
@@ -94,32 +79,28 @@
             <dict-tag :options="sys_common_status" :value="scope.row.status" />
           </template>
         </el-table-column>
-        <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
+        <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom"
+          :sort-orders="['descending', 'ascending']">
           <template #default="scope">
             <span>{{ proxy.parseTime(scope.row.operTime) }}</span>
           </template>
         </el-table-column>
-        <el-table-column
-          label="消耗时间"
-          align="center"
-          prop="costTime"
-          width="110"
-          :show-overflow-tooltip="true"
-          sortable="custom"
-          :sort-orders="['descending', 'ascending']"
-        >
+        <el-table-column label="消耗时间" align="center" prop="costTime" width="110" :show-overflow-tooltip="true"
+          sortable="custom" :sort-orders="['descending', 'ascending']">
           <template #default="scope">
             <span>{{ scope.row.costTime }}毫秒</span>
           </template>
         </el-table-column>
         <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button v-hasPermi="['monitor:operlog:query']" link type="warning" icon="View" @click="handleView(scope.row)">详细</el-button>
+            <el-button v-hasPermi="['monitor:operlog:query']" link type="warning"
+              @click="handleView(scope.row)">详细</el-button>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        :total="total" @pagination="getList" />
     </el-card>
     <!-- 操作日志详细 -->
     <OperInfoDialog ref="operInfoDialogRef" />
@@ -257,3 +238,11 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.operlog-page {
+  @include page-common-list;
+}
+</style>

+ 271 - 69
src/views/monitor/operlog/oper-info-dialog.vue

@@ -1,54 +1,64 @@
 <template>
-  <el-dialog v-model="open" title="操作日志详细" width="700px" append-to-body close-on-click-modal @closed="info = null">
-    <el-descriptions v-if="info" :column="1" border>
-      <el-descriptions-item label="操作状态">
-        <template #default>
-          <el-tag v-if="info.status === 0" type="success">正常</el-tag>
-          <el-tag v-else-if="info.status === 1" type="danger">失败</el-tag>
-        </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="登录信息">
-        <template #default> {{ info.operName }} / {{ info.deptName }} / {{ info.operIp }} / {{ info.operLocation }} </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="请求信息">
-        <template #default> {{ info.requestMethod }} {{ info.operUrl }} </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="操作模块">
-        <template #default> {{ info.title }} / {{ typeFormat(info) }} </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="操作方法">
-        <template #default>
-          {{ info.method }}
-        </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="请求参数">
-        <template #default>
-          <div class="max-h-300px overflow-y-auto">
-            <VueJsonPretty :data="formatToJsonObject(info.operParam)" />
-          </div>
-        </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="返回参数">
-        <template #default>
-          <div class="max-h-300px overflow-y-auto">
-            <VueJsonPretty :data="formatToJsonObject(info.jsonResult)" />
-          </div>
-        </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="消耗时间">
-        <template #default>
-          <span> {{ info.costTime }}ms </span>
-        </template>
-      </el-descriptions-item>
-      <el-descriptions-item label="操作时间">
-        <template #default> {{ proxy.parseTime(info.operTime) }}</template>
-      </el-descriptions-item>
-      <el-descriptions-item v-if="info.status === 1" label="异常信息">
-        <template #default>
-          <span class="text-danger"> {{ info.errorMsg }}</span>
-        </template>
-      </el-descriptions-item>
-    </el-descriptions>
+  <el-dialog v-model="open" title="操作日志详细" width="720px" append-to-body close-on-click-modal class="oper-info-dialog" @closed="info = null">
+    <template v-if="info">
+      <div class="info-top-card">
+        <div class="info-status-badge" :class="info.status === 0 ? 'is-success' : 'is-error'">
+          <span class="info-status-dot"></span>
+          {{ info.status === 0 ? '正常' : '失败' }}
+        </div>
+        <span class="info-method-tag">{{ info.requestMethod }} {{ info.operUrl }}</span>
+      </div>
+
+      <div class="info-grid">
+        <div class="info-grid-cell">
+          <span class="info-grid-label">操作人员</span>
+          <span class="info-grid-value">{{ info.operName }} / {{ info.deptName }}</span>
+        </div>
+        <div class="info-grid-cell">
+          <span class="info-grid-label">操作地址</span>
+          <span class="info-grid-value">{{ info.operIp }} / {{ info.operLocation }}</span>
+        </div>
+        <div class="info-grid-cell">
+          <span class="info-grid-label">操作模块</span>
+          <span class="info-grid-value">{{ info.title }} / {{ typeFormat(info) }}</span>
+        </div>
+        <div class="info-grid-cell">
+          <span class="info-grid-label">消耗时间</span>
+          <span class="info-grid-value">{{ info.costTime }}ms</span>
+        </div>
+        <div class="info-grid-cell">
+          <span class="info-grid-label">操作时间</span>
+          <span class="info-grid-value">{{ proxy.parseTime(info.operTime) }}</span>
+        </div>
+      </div>
+
+      <div class="info-section">
+        <div class="info-section-title">操作方法</div>
+        <div class="info-code-block">{{ info.method }}</div>
+      </div>
+
+      <div class="info-section">
+        <div class="info-section-title">请求参数</div>
+        <div class="info-json-block">
+          <VueJsonPretty :data="formatToJsonObject(info.operParam)" />
+        </div>
+      </div>
+
+      <div class="info-section">
+        <div class="info-section-title">返回参数</div>
+        <div class="info-json-block">
+          <VueJsonPretty :data="formatToJsonObject(info.jsonResult)" />
+        </div>
+      </div>
+
+      <div v-if="info.status === 1" class="info-error-card">
+        <div class="info-error-title">
+          <el-icon><CircleCloseFilled /></el-icon>
+          异常信息
+        </div>
+        <div class="info-error-msg">{{ info.errorMsg }}</div>
+      </div>
+    </template>
   </el-dialog>
 </template>
 
@@ -73,10 +83,6 @@ defineExpose({
   closeDialog
 });
 
-/**
- * json转为对象
- * @param data 原始数据
- */
 function formatToJsonObject(data: string) {
   try {
     return JSON.parse(data);
@@ -85,9 +91,6 @@ function formatToJsonObject(data: string) {
   }
 }
 
-/**
- * 字典信息
- */
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { sys_oper_type } = toRefs<any>(proxy?.useDict('sys_oper_type'));
 const typeFormat = (row: OperLogForm) => {
@@ -95,17 +98,216 @@ const typeFormat = (row: OperLogForm) => {
 };
 </script>
 
-<style lang="scss" scoped>
-/**
-label宽度固定
-*/
-:deep(.el-descriptions__label) {
-  min-width: 100px;
-}
-/**
-文字超过 换行显示
-*/
-:deep(.el-descriptions__content) {
-  max-width: 300px;
+<style scoped lang="scss">
+/* ==================== 弹窗容器 ==================== */
+:global(.oper-info-dialog .el-dialog__header) {
+  padding: 24px 28px 0;
+  margin: 0;
+  border-bottom: none;
+}
+
+:global(.oper-info-dialog .el-dialog__title) {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1f2937;
+  letter-spacing: 0.5px;
+}
+
+:global(.oper-info-dialog .el-dialog__body) {
+  padding: 20px 28px 28px;
+}
+
+/* ==================== 顶部状态条 ==================== */
+.info-top-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  background: #f9fafb;
+  border-radius: 12px;
+  margin-bottom: 24px;
+  border: 1px solid #f0f2f5;
+}
+
+.info-status-badge {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  padding: 6px 16px;
+  border-radius: 8px;
+}
+
+.info-status-badge.is-success {
+  color: #059669;
+  background: #ecfdf5;
+}
+
+.info-status-badge.is-error {
+  color: #dc2626;
+  background: #fef2f2;
+}
+
+.info-status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+.is-success .info-status-dot {
+  background: #10b981;
+}
+
+.is-error .info-status-dot {
+  background: #ef4444;
+}
+
+.info-method-tag {
+  font-size: 13px;
+  color: #6b7280;
+  background: #fff;
+  padding: 5px 12px;
+  border-radius: 6px;
+  border: 1px solid #e5e7eb;
+  font-family: 'SF Mono', 'Monaco', 'Menlo', monospace;
+  max-width: 380px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* ==================== 信息网格 ==================== */
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 14px;
+  margin-bottom: 28px;
+}
+
+.info-grid-cell {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  padding: 14px 16px;
+  background: #f9fafb;
+  border-radius: 10px;
+  border: 1px solid #f0f2f5;
+}
+
+.info-grid-label {
+  font-size: 12px;
+  font-weight: 500;
+  color: #9ca3af;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.info-grid-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+/* ==================== 分区 ==================== */
+.info-section {
+  margin-bottom: 22px;
+}
+
+.info-section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+  margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+
+  &::before {
+    content: '';
+    width: 4px;
+    height: 14px;
+    background: #3b82f6;
+    border-radius: 2px;
+  }
+}
+
+/* ==================== 代码块 ==================== */
+.info-code-block {
+  padding: 14px 18px;
+  background: #1e293b;
+  color: #e2e8f0;
+  border-radius: 10px;
+  font-size: 13px;
+  font-family: 'SF Mono', 'Monaco', 'Menlo', monospace;
+  line-height: 1.6;
+  word-break: break-all;
+  overflow-x: auto;
+}
+
+/* ==================== JSON 块 ==================== */
+.info-json-block {
+  background: #1e293b;
+  border-radius: 10px;
+  padding: 16px 20px;
+  max-height: 260px;
+  overflow-y: auto;
+
+  :deep(.vjs-tree) {
+    color: #e2e8f0;
+    font-size: 13px;
+  }
+
+  :deep(.vjs-key) {
+    color: #93c5fd;
+  }
+
+  :deep(.vjs-value__string) {
+    color: #86efac;
+  }
+
+  :deep(.vjs-value__number) {
+    color: #fde68a;
+  }
+
+  :deep(.vjs-value__boolean) {
+    color: #c4b5fd;
+  }
+
+  :deep(.vjs-value__null) {
+    color: #fca5a5;
+  }
+}
+
+/* ==================== 错误卡片 ==================== */
+.info-error-card {
+  margin-top: 4px;
+  padding: 16px 20px;
+  background: #fef2f2;
+  border-radius: 12px;
+  border: 1px solid #fecaca;
+}
+
+.info-error-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #dc2626;
+  margin-bottom: 10px;
+}
+
+.info-error-msg {
+  font-size: 13px;
+  color: #991b1b;
+  line-height: 1.6;
+  font-family: 'SF Mono', 'Monaco', 'Menlo', monospace;
+  padding: 12px 16px;
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #fecaca;
+  word-break: break-all;
 }
 </style>

+ 399 - 87
src/views/order/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-2">
+  <div class="order-page">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
       :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="search">
@@ -15,8 +15,8 @@
           </el-form-item>
 
           <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            <el-button type="primary" @click="handleQuery">搜索</el-button>
+            <el-button @click="resetQuery">重置</el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -36,18 +36,13 @@
         </div>
       </div>
 
-      <el-table v-loading="loading" :data="orderList" border>
+      <el-table v-loading="loading" :data="orderList" :row-class-name="urgentRowClassName">
         <el-table-column label="订单单号" align="center" prop="code" />
         <el-table-column label="单据编号" align="center" prop="docCode">
           <template #default="scope">
             <span>{{ scope.row.docCode || '-' }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="ERP 单号" align="center" prop="erpDocCode">
-          <template #default="scope">
-            <span>{{ scope.row.erpDocCode || '-' }}</span>
-          </template>
-        </el-table-column>
         <el-table-column label="下单人" align="center" prop="placerName" />
         <el-table-column label="下单时间" align="center" prop="placeTime" width="180">
           <template #default="scope">
@@ -55,12 +50,13 @@
           </template>
         </el-table-column>
         <el-table-column label="总支数" align="center" prop="totalCount" />
-        <el-table-column label="订单状态" align="center" prop="status">
+        <el-table-column label="订单状态" align="center" prop="status" min-width="110">
           <template #default="scope">
-            <el-tag :color="orderStatusJson[scope.row.status]?.color"
-              style="color: #fff; border: none; font-weight: bold;">
+            <span class="status-pill" :style="statusPillStyle(scope.row.status)">
+              <span class="status-pill__dot"
+                :style="{ background: orderStatusJson[scope.row.status]?.color || '#909399' }"></span>
               {{ orderStatusJson[scope.row.status]?.text || '待确认' }}
-            </el-tag>
+            </span>
           </template>
         </el-table-column>
         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
@@ -68,11 +64,11 @@
             <span>{{ parseTime(scope.row.createTime) }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
+        <el-table-column label="操作" align="center" width="130" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-button v-if="scope.row.urgentFlag !== 1 && checkPermi(['order:order:urgent'])" link type="danger"
-              icon="Warning" @click="handleUrgent(scope.row)">加急</el-button>
-            <el-button v-hasPermi="['order:order:query']" link type="warning" icon="View"
+              @click="handleUrgent(scope.row)">加急</el-button>
+            <el-button v-hasPermi="['order:order:query']" link type="warning"
               @click="handleViewOrder(scope.row)">查看</el-button>
           </template>
         </el-table-column>
@@ -83,52 +79,121 @@
     </el-card>
 
     <!-- 订单详情对话框 -->
-    <el-dialog title="订单详情" v-model="detailOpen" width="800px" append-to-body>
+    <el-dialog v-model="detailOpen" width="820px" append-to-body class="order-detail-dialog">
+      <template #header>
+        <div class="detail-dialog-header">
+          <span class="detail-dialog-title">订单详情</span>
+          <span v-if="detailOrder" class="detail-code-badge">#{{ detailOrder.code }}</span>
+        </div>
+      </template>
+
       <template v-if="detailOrder">
-        <div class="section-title">订单信息</div>
-        <el-descriptions :column="2" border size="small">
-          <el-descriptions-item label="订单单号" :span="2">{{ detailOrder.code }}</el-descriptions-item>
-          <el-descriptions-item label="单据编号" :span="2">{{ detailOrder.docCode || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="下单人">{{ detailOrder.placerName }}</el-descriptions-item>
-          <el-descriptions-item label="订单状态">
-            <el-tag :color="orderStatusJson[detailOrder.status]?.color"
-              style="color: #fff; border: none; font-weight: bold;">
-              {{ orderStatusJson[detailOrder.status]?.text || '待确认' }}
-            </el-tag>
-            <el-tag v-if="detailOrder.urgentFlag === 1" type="danger" effect="dark"
-              style="margin-left: 8px; font-weight: bold;">
-              🔥 加急
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="下单时间">{{ parseTime(detailOrder.placeTime) }}</el-descriptions-item>
-          <el-descriptions-item label="创建时间">{{ parseTime(detailOrder.createTime) }}</el-descriptions-item>
-          <el-descriptions-item label="总支数">{{ detailOrder.totalCount }}</el-descriptions-item>
-        </el-descriptions>
-
-        <div class="section-title" style="margin-top: 20px">订单明细</div>
-        <div class="detail-scroll" v-if="detailOrder.details && detailOrder.details.length > 0">
-          <div class="detail-card" v-for="(detail, dIdx) in detailOrder.details" :key="dIdx">
-            <div class="detail-card-head">规格清单 #{{ dIdx + 1 }}</div>
-            <el-descriptions :column="2" border size="small">
-              <el-descriptions-item label="产品型号" :span="2">{{ detail.modelNum || '未知型号' }}</el-descriptions-item>
-              <el-descriptions-item label="型号名称" :span="2">{{ detail.modelName || '铝型材主料' }}</el-descriptions-item>
-              <el-descriptions-item label="单据编号">{{ detail.docCode || '-' }}</el-descriptions-item>
-              <el-descriptions-item label="项目号">{{ detail.itemNo || '-' }}</el-descriptions-item>
-              <el-descriptions-item label="型材材质">{{ detail.material || '6063-T5' }}</el-descriptions-item>
-              <el-descriptions-item label="需求支数">
-                <span class="count-red">{{ detail.count || 0 }} 支</span>
-              </el-descriptions-item>
-              <el-descriptions-item label="表面处理">{{ detail.surfaceName || '无' }}</el-descriptions-item>
-              <el-descriptions-item label="包装方式">{{ detail.packName || '普通包装' }}</el-descriptions-item>
-              <el-descriptions-item label="订单长度">{{ formatNum(detail.length) }} mm</el-descriptions-item>
-              <el-descriptions-item label="型材壁厚">{{ formatNum(detail.wallThickness, '1.2') }} mm</el-descriptions-item>
-            </el-descriptions>
-            <div class="detail-remark" v-if="detail.remark">
-              <span class="remark-label">备注:</span>{{ detail.remark }}
+        <div v-if="detailOrder.urgentFlag === 1" class="urgent-banner">
+          <span class="urgent-banner__icon">🔥</span>
+          <span class="urgent-banner__text">此订单已标记为加急,请优先处理</span>
+        </div>
+
+        <div class="detail-info-card" :class="{ 'is-urgent': detailOrder.urgentFlag === 1 }">
+          <div class="detail-card-title">
+            <span class="detail-card-title__icon">📋</span>
+            <span>基本信息</span>
+          </div>
+          <div class="detail-info-grid">
+            <div class="info-item">
+              <span class="info-label">订单单号</span>
+              <span class="info-value">{{ detailOrder.code }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">单据编号</span>
+              <span class="info-value">{{ detailOrder.docCode || '-' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">下单人</span>
+              <span class="info-value">{{ detailOrder.placerName }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">订单状态</span>
+              <span class="info-value">
+                <span class="status-pill" :style="statusPillStyle(detailOrder.status)">
+                  <span class="status-pill__dot"
+                    :style="{ background: orderStatusJson[detailOrder.status]?.color || '#909399' }"></span>
+                  {{ orderStatusJson[detailOrder.status]?.text || '待确认' }}
+                </span>
+              </span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">总支数</span>
+              <span class="info-value info-value--highlight">{{ detailOrder.totalCount }} 支</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">下单时间</span>
+              <span class="info-value">{{ parseTime(detailOrder.placeTime) }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">创建时间</span>
+              <span class="info-value">{{ parseTime(detailOrder.createTime) }}</span>
             </div>
           </div>
         </div>
-        <el-empty v-else description="暂无明细" :image-size="60" />
+
+        <div class="detail-info-card">
+          <div class="detail-card-title">
+            <span class="detail-card-title__icon">📦</span>
+            <span>订单明细</span>
+            <span class="detail-card-title__count">{{ detailOrder.details?.length || 0 }} 项</span>
+          </div>
+          <div class="detail-scroll" v-if="detailOrder.details && detailOrder.details.length > 0">
+            <div class="detail-spec-card" v-for="(detail, dIdx) in detailOrder.details" :key="dIdx">
+              <div class="spec-card-header">
+                <span class="spec-no">#{{ dIdx + 1 }}</span>
+                <span class="spec-model">{{ detail.modelNum || '未知型号' }}</span>
+              </div>
+              <div class="spec-info-grid">
+                <div class="spec-item">
+                  <span class="spec-label">型号名称</span>
+                  <span class="spec-value">{{ detail.modelName || '铝型材主料' }}</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">单据编号</span>
+                  <span class="spec-value">{{ detail.docCode || '-' }}</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">项目号</span>
+                  <span class="spec-value">{{ detail.itemNo || '-' }}</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">型材材质</span>
+                  <span class="spec-value">{{ detail.material || '6063-T5' }}</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">表面处理</span>
+                  <span class="spec-value">{{ detail.surfaceName || '无' }}</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">包装方式</span>
+                  <span class="spec-value">{{ detail.packName || '普通包装' }}</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">订单长度</span>
+                  <span class="spec-value">{{ formatNum(detail.length) }} mm</span>
+                </div>
+                <div class="spec-item">
+                  <span class="spec-label">型材壁厚</span>
+                  <span class="spec-value">{{ formatNum(detail.wallThickness, '1.2') }} mm</span>
+                </div>
+                <div class="spec-item spec-item--full">
+                  <span class="spec-label">需求支数</span>
+                  <span class="spec-value spec-value--count">{{ detail.count || 0 }} 支</span>
+                </div>
+              </div>
+              <div class="spec-remark" v-if="detail.remark">
+                <span class="spec-remark__icon">💬</span>
+                <span>{{ detail.remark }}</span>
+              </div>
+            </div>
+          </div>
+          <el-empty v-else description="暂无明细数据" :image-size="60" />
+        </div>
       </template>
     </el-dialog>
   </div>
@@ -233,6 +298,21 @@ function handleUrgent(row) {
   }).catch(() => { });
 }
 
+/** 加急订单行高亮 */
+const urgentRowClassName = ({ row }) => {
+  if (row.urgentFlag === 1) return 'row-urgent';
+  return '';
+};
+
+/** 状态标签 pill 样式 */
+const statusPillStyle = (status) => {
+  const color = orderStatusJson[status]?.color || '#909399';
+  return {
+    '--status-color': color,
+    color: color
+  };
+};
+
 /** 格式化数值,匹配小程序端的 toFixed(4) 显示 */
 function formatNum(val, fallback = '0') {
   if (val === null || val === undefined || val === '') return fallback;
@@ -243,7 +323,13 @@ function formatNum(val, fallback = '0') {
 getList();
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.order-page {
+  @include page-common-list;
+}
+
 /* ========== 状态筛选标签栏 ========== */
 .status-tabs {
   display: flex;
@@ -288,52 +374,278 @@ getList();
   transform: translateX(-50%) scaleX(1);
 }
 
-/* ========== 详情弹框样式 ========== */
-.section-title {
-  font-size: 14px;
-  font-weight: 600;
-  color: #303133;
-  margin-bottom: 12px;
-  padding-left: 10px;
-  border-left: 3px solid #C1001C;
+/* ========== 详情弹窗样式 ========== */
+.order-detail-dialog :deep(.el-dialog) {
+  display: flex;
+  flex-direction: column;
+  max-height: 90vh;
 }
 
-.detail-scroll {
-  max-height: 420px;
+.order-detail-dialog :deep(.el-dialog__header) {
+  padding: 20px 24px 16px;
+  margin-right: 0;
+  border-bottom: 1px solid #f1f5f9;
+  flex-shrink: 0;
+}
+
+.order-detail-dialog :deep(.el-dialog__body) {
+  padding: 20px 24px;
   overflow-y: auto;
-  padding-right: 2px;
+  flex: 1;
+  min-height: 0;
 }
 
-.detail-card {
-  background: #fff;
-  border: 1px solid #ebeef5;
+.detail-dialog-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.detail-dialog-title {
+  font-size: 18px;
+  font-weight: 700;
+  color: #1a202c;
+}
+
+.detail-code-badge {
+  padding: 2px 10px;
   border-radius: 6px;
-  padding: 14px;
-  margin-bottom: 12px;
+  font-size: 13px;
+  font-weight: 500;
+  color: #4f46e5;
+  background: #eef2ff;
+}
+
+/* 加急横幅 */
+.urgent-banner {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 16px;
+  margin-bottom: 16px;
+  border-radius: 8px;
+  background: linear-gradient(135deg, #fff5f5 0%, #fff0f0 100%);
+  border: 1px solid #fecaca;
+}
+
+.urgent-banner__icon {
+  font-size: 18px;
 }
 
-.detail-card-head {
+.urgent-banner__text {
   font-size: 13px;
   font-weight: 600;
-  color: #303133;
+  color: #dc2626;
+}
+
+/* 信息卡片 */
+.detail-info-card {
+  background: #fff;
+  border: 1px solid #edf2f7;
+  border-radius: 12px;
+  padding: 20px 24px;
+  margin-bottom: 16px;
+  transition: border-color 0.2s;
+}
+
+.detail-info-card.is-urgent {
+  border-color: #fecaca;
+  box-shadow: 0 0 0 1px #fecaca;
+}
+
+.detail-card-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f7f8fc;
+  font-size: 15px;
+  font-weight: 600;
+  color: #1a202c;
+}
+
+.detail-card-title__icon {
+  font-size: 16px;
+}
+
+.detail-card-title__count {
+  margin-left: auto;
+  font-size: 12px;
+  font-weight: 500;
+  color: #a0aec0;
+  background: #f7f8fc;
+  padding: 2px 10px;
+  border-radius: 10px;
+}
+
+/* 信息网格 */
+.detail-info-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 12px 32px;
+}
+
+.info-item {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.info-label {
+  font-size: 12px;
+  color: #a0aec0;
+  font-weight: 500;
+  text-transform: uppercase;
+  letter-spacing: 0.3px;
+}
+
+.info-value {
+  font-size: 14px;
+  color: #2d3748;
+  font-weight: 500;
+}
+
+.info-value--highlight {
+  font-weight: 700;
+  color: #4f46e5;
+}
+
+/* 明细滚动区 */
+.detail-scroll {
+  padding-right: 4px;
+}
+
+/* 明细规格卡片 */
+.detail-spec-card {
+  background: #fafbfc;
+  border: 1px solid #edf2f7;
+  border-radius: 10px;
+  padding: 16px 20px;
   margin-bottom: 10px;
+  transition: border-color 0.2s, box-shadow 0.2s;
 }
 
-.count-red {
-  color: #ff3b30;
+.detail-spec-card:hover {
+  border-color: #e2e8f0;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+}
+
+.spec-card-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 14px;
+  padding-bottom: 10px;
+  border-bottom: 1px dashed #e2e8f0;
+}
+
+.spec-no {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 24px;
+  height: 24px;
+  border-radius: 6px;
+  background: #4f46e5;
+  color: #fff;
+  font-size: 12px;
   font-weight: 600;
+  flex-shrink: 0;
+}
+
+.spec-model {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1a202c;
+}
+
+.spec-info-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 8px 24px;
 }
 
-.detail-remark {
-  margin-top: 8px;
-  padding: 6px 10px;
-  background: #fafafa;
-  border-radius: 4px;
+.spec-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 4px 0;
+}
+
+.spec-item--full {
+  grid-column: span 2;
+  margin-top: 4px;
+  padding-top: 8px;
+  border-top: 1px dashed #e2e8f0;
+}
+
+.spec-label {
   font-size: 12px;
-  color: #606266;
+  color: #8899aa;
+}
+
+.spec-value {
+  font-size: 13px;
+  color: #2d3748;
+  font-weight: 500;
+}
+
+.spec-value--count {
+  font-size: 15px;
+  font-weight: 700;
+  color: #ff3b30;
+}
+
+.spec-remark {
+  display: flex;
+  align-items: flex-start;
+  gap: 6px;
+  margin-top: 10px;
+  padding: 8px 12px;
+  background: #fff;
+  border-radius: 6px;
+  font-size: 12px;
+  color: #718096;
+  line-height: 1.5;
+}
+
+.spec-remark__icon {
+  flex-shrink: 0;
+  font-size: 13px;
+}
+
+/* ========== 加急订单行高亮 ========== */
+:deep(.row-urgent > td) {
+  background: #fff5f5 !important;
+}
+
+:deep(.row-urgent > td:first-child) {
+  box-shadow: inset 3px 0 0 #ff3b30;
+}
+
+:deep(.row-urgent:hover > td) {
+  background: #ffefef !important;
+}
+
+/* ========== 状态标签 pill ========== */
+.status-pill {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 3px 12px;
+  border-radius: 20px;
+  font-size: 13px;
+  font-weight: 500;
+  background: color-mix(in srgb, var(--status-color) 10%, #fff);
+  border: 1px solid color-mix(in srgb, var(--status-color) 20%, #fff);
+  white-space: nowrap;
 }
 
-.remark-label {
-  color: #909399;
+.status-pill__dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  flex-shrink: 0;
 }
 </style>

+ 190 - 18
src/views/system/agreement/index.vue

@@ -1,24 +1,44 @@
 <template>
-    <div class="p-2">
-        <el-card shadow="never">
-            <template #header>
-                <span class="font-bold text-base">协议管理</span>
-            </template>
-
-            <!-- 协议切换 Tab -->
-            <el-tabs v-model="activeTab" @tab-change="handleTabChange">
-                <el-tab-pane label="用户协议" name="1" />
-                <el-tab-pane label="隐私政策" name="2" />
+    <div class="agreement-page">
+        <div class="page-header">
+            <div class="header-title">
+                <span class="header-icon">📄</span>
+                <span class="header-text">协议管理</span>
+            </div>
+            <span class="header-sub">管理小程序端用户协议与隐私政策</span>
+        </div>
+
+        <el-card shadow="never" class="agreement-card">
+            <el-tabs v-model="activeTab" class="agreement-tabs" @tab-change="handleTabChange">
+                <el-tab-pane name="1">
+                    <template #label>
+                        <span class="tab-label">
+                            <el-icon>
+                                <Document />
+                            </el-icon>
+                            用户协议
+                        </span>
+                    </template>
+                </el-tab-pane>
+                <el-tab-pane name="2">
+                    <template #label>
+                        <span class="tab-label">
+                            <el-icon>
+                                <Lock />
+                            </el-icon>
+                            隐私政策
+                        </span>
+                    </template>
+                </el-tab-pane>
             </el-tabs>
 
-            <!-- 编辑区 -->
-            <el-form ref="formRef" :model="form" :rules="rules" label-width="80px" class="mt-4">
+            <el-form ref="formRef" :model="form" :rules="rules" label-width="90px" class="agreement-form">
                 <el-form-item label="协议标题" prop="title">
-                    <el-input v-model="form.title" placeholder="请输入协议标题" style="width: 400px" />
+                    <el-input v-model="form.title" placeholder="请输入协议标题" maxlength="100" />
                 </el-form-item>
                 <el-form-item label="协议内容" prop="content">
-                    <div style="width: 100%; border: 1px solid #dcdfe6; border-radius: 4px;">
-                        <editor v-model="form.content" :min-height="300" />
+                    <div class="editor-wrapper">
+                        <editor v-model="form.content" :min-height="380" />
                     </div>
                 </el-form-item>
                 <el-form-item>
@@ -33,6 +53,7 @@
 </template>
 
 <script setup lang="ts">
+import { Document, Lock } from '@element-plus/icons-vue';
 import { getAgreement, updateAgreement } from '@/api/system/agreement';
 import { AgreementForm } from '@/api/system/agreement/types';
 
@@ -53,7 +74,6 @@ const rules = {
     content: [{ required: true, message: '协议内容不能为空', trigger: 'change' }]
 };
 
-/** 加载指定协议 */
 const loadAgreement = async (id: number) => {
     const res = await getAgreement(id);
     form.id = res.data.id;
@@ -61,12 +81,10 @@ const loadAgreement = async (id: number) => {
     form.content = res.data.content;
 };
 
-/** 切换 Tab */
 const handleTabChange = (name: string | number) => {
     loadAgreement(Number(name));
 };
 
-/** 保存(content base64 编码后提交) */
 const handleSave = () => {
     formRef.value?.validate(async (valid: boolean) => {
         if (!valid) return;
@@ -85,3 +103,157 @@ onMounted(() => {
     loadAgreement(1);
 });
 </script>
+
+<style scoped>
+.agreement-page {
+    width: 100%;
+    height: 100%;
+    padding: 20px 24px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    background: #f7f8fc;
+}
+
+/* ==================== 页头 ==================== */
+.page-header {
+    display: flex;
+    align-items: baseline;
+    gap: 14px;
+    flex-shrink: 0;
+}
+
+.header-title {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.header-icon {
+    font-size: 22px;
+    line-height: 1;
+}
+
+.header-text {
+    font-size: 18px;
+    font-weight: 700;
+    color: #1a202c;
+    letter-spacing: 0.3px;
+}
+
+.header-sub {
+    font-size: 13px;
+    color: #a0aec0;
+}
+
+/* ==================== 卡片 ==================== */
+.agreement-card {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    border-radius: 12px !important;
+    border: none !important;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03) !important;
+    overflow: hidden;
+}
+
+.agreement-card :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: 0 24px 24px;
+    overflow: hidden;
+}
+
+/* ==================== Tabs ==================== */
+.agreement-tabs {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    overflow: hidden;
+}
+
+.agreement-tabs :deep(.el-tabs__header) {
+    margin-bottom: 24px;
+    padding: 0 4px;
+    border-bottom: 1px solid #edf2f7;
+}
+
+.agreement-tabs :deep(.el-tabs__nav-wrap::after) {
+    display: none;
+}
+
+.agreement-tabs :deep(.el-tabs__item) {
+    height: 44px;
+    line-height: 44px;
+    font-size: 14px;
+    color: #718096;
+    padding: 0 20px;
+    font-weight: 500;
+}
+
+.agreement-tabs :deep(.el-tabs__item.is-active) {
+    color: #2563eb;
+    font-weight: 600;
+}
+
+.agreement-tabs :deep(.el-tabs__active-bar) {
+    background-color: #2563eb;
+    height: 3px;
+    border-radius: 3px 3px 0 0;
+}
+
+.tab-label {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+}
+
+.agreement-tabs :deep(.el-tabs__content) {
+    flex: 1;
+    overflow-y: auto;
+}
+
+/* ==================== 表单 ==================== */
+.agreement-form {
+    max-width: 100%;
+}
+
+.agreement-form :deep(.el-form-item__label) {
+    font-weight: 500;
+    color: #4a5568;
+}
+
+.agreement-form :deep(.el-input__wrapper) {
+    border-radius: 8px;
+    box-shadow: 0 0 0 1px #e2e8f0 inset;
+}
+
+.agreement-form :deep(.el-input__wrapper:hover) {
+    box-shadow: 0 0 0 1px #cbd5e1 inset;
+}
+
+.agreement-form :deep(.el-input__wrapper.is-focus) {
+    box-shadow: 0 0 0 1px #2563eb inset, 0 0 0 3px rgba(37, 99, 235, 0.08);
+}
+
+.editor-wrapper {
+    width: 100%;
+    border: 1px solid #e2e8f0;
+    border-radius: 10px;
+    overflow: hidden;
+    transition: border-color 0.25s, box-shadow 0.25s;
+}
+
+.editor-wrapper:focus-within {
+    border-color: #2563eb;
+    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.08);
+}
+
+.agreement-form :deep(.el-button--primary) {
+    border-radius: 8px;
+    padding: 8px 28px;
+    font-weight: 500;
+}
+</style>

+ 425 - 57
src/views/system/applet/index/index.vue

@@ -1,107 +1,160 @@
 <template>
-    <div class="p-2">
-        <el-card shadow="never">
-            <template #header>
-                <span class="font-bold text-base">小程序首页配置</span>
-            </template>
+    <div class="applet-page">
+        <div class="page-header">
+            <div class="header-title">
+                <span class="header-icon">🎯</span>
+                <span class="header-text">小程序首页配置</span>
+            </div>
+            <span class="header-sub">管理小程序端首页展示内容</span>
+        </div>
 
-            <el-tabs v-model="activeTab">
-                <el-tab-pane label="轮播图管理" name="slideshow" />
-                <el-tab-pane label="精选分类管理" name="categories" />
+        <el-card shadow="never" class="applet-card">
+            <el-tabs v-model="activeTab" class="applet-tabs">
+                <el-tab-pane name="slideshow">
+                    <template #label>
+                        <span class="tab-label">
+                            <el-icon>
+                                <PictureFilled />
+                            </el-icon>
+                            轮播图管理
+                        </span>
+                    </template>
+                </el-tab-pane>
+                <el-tab-pane name="categories">
+                    <template #label>
+                        <span class="tab-label">
+                            <el-icon>
+                                <Grid />
+                            </el-icon>
+                            精选分类管理
+                        </span>
+                    </template>
+                </el-tab-pane>
             </el-tabs>
 
             <!-- ========== 轮播图管理 ========== -->
-            <div v-if="activeTab === 'slideshow'" v-loading="slideshowLoading">
-                <el-button type="primary" icon="Plus" class="mb-3" @click="handleSlideshowAdd">新增轮播图</el-button>
-                <el-table :data="slideshowList" border stripe>
-                    <el-table-column label="序号" type="index" width="60" align="center" />
-                    <el-table-column label="预览" align="center" width="120">
+            <div v-if="activeTab === 'slideshow'" v-loading="slideshowLoading" class="tab-content">
+                <div class="toolbar">
+                    <el-button type="primary" :icon="Plus" @click="handleSlideshowAdd">新增轮播图</el-button>
+                    <span class="toolbar-count" v-if="slideshowList.length">共 {{ slideshowList.length }} 条</span>
+                </div>
+
+                <el-table :data="slideshowList" style="width: 100%" class="modern-table"
+                    :header-cell-style="tableHeaderStyle">
+                    <el-table-column label="序号" type="index" width="70" align="center" />
+                    <el-table-column label="预览" align="center" width="140">
                         <template #default="scope">
-                            <el-image v-if="scope.row.ossUrl" :src="scope.row.ossUrl" fit="cover"
-                                style="width: 80px; height: 60px; border-radius: 4px"
-                                :preview-src-list="[scope.row.ossUrl]" />
-                            <span v-else class="text-gray-400">暂无图片</span>
+                            <div class="img-preview" v-if="scope.row.ossUrl">
+                                <el-image :src="scope.row.ossUrl" fit="cover" :preview-src-list="[scope.row.ossUrl]" />
+                            </div>
+                            <div class="img-empty" v-else>
+                                <el-icon>
+                                    <WarningFilled />
+                                </el-icon>
+                            </div>
                         </template>
                     </el-table-column>
-                    <el-table-column prop="ossId" label="OSS资源ID" align="center" />
-                    <el-table-column prop="createTime" label="创建时间" align="center" />
-                    <el-table-column label="操作" align="center" width="180">
+                    <el-table-column prop="createByName" label="创建者" align="center" min-width="100" />
+                    <el-table-column prop="createTime" label="创建时间" align="center" min-width="170" />
+                    <el-table-column prop="updateByName" label="更新者" align="center" min-width="100" />
+                    <el-table-column prop="updateTime" label="更新时间" align="center" min-width="170" />
+                    <el-table-column label="操作" align="center" width="160" fixed="right">
                         <template #default="scope">
-                            <el-button link type="warning" icon="Edit"
-                                @click="handleSlideshowEdit(scope.row)">编辑</el-button>
-                            <el-button link type="danger" icon="Delete"
-                                @click="handleSlideshowDelete(scope.row)">删除</el-button>
+                            <el-button link type="primary" @click="handleSlideshowEdit(scope.row)">编辑</el-button>
+                            <el-divider direction="vertical" />
+                            <el-button link type="danger" @click="handleSlideshowDelete(scope.row)">删除</el-button>
                         </template>
                     </el-table-column>
+                    <template #empty>
+                        <div class="empty-block">
+                            <el-icon :size="48">
+                                <PictureFilled />
+                            </el-icon>
+                            <p>暂无轮播图</p>
+                            <el-button type="primary" size="small" @click="handleSlideshowAdd">立即添加</el-button>
+                        </div>
+                    </template>
                 </el-table>
             </div>
 
             <!-- ========== 精选分类管理 ========== -->
-            <div v-if="activeTab === 'categories'" v-loading="categoriesLoading">
-                <el-alert title="精选分类固定为四项,仅可修改不可增删" type="info" :closable="false" show-icon class="mb-3" />
-                <el-row :gutter="16">
-                    <el-col :span="6" v-for="item in categoriesList" :key="item.id">
-                        <el-card shadow="hover" class="category-card mb-3 cursor-pointer"
-                            @click="handleCategoriesEdit(item)">
-                            <div class="category-preview">
-                                <el-image v-if="item.backgroundUrl" :src="item.backgroundUrl" fit="cover"
-                                    style="width: 100%; height: 120px; border-radius: 8px" />
-                                <div v-else class="category-placeholder">
-                                    <el-icon :size="40">
-                                        <Picture />
+            <div v-if="activeTab === 'categories'" v-loading="categoriesLoading" class="tab-content">
+                <div class="toolbar">
+                    <span class="toolbar-count" v-if="categoriesList.length">共 {{ categoriesList.length }} 个分类</span>
+                </div>
+                <el-row :gutter="20" v-if="categoriesList.length">
+                    <el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="item in categoriesList" :key="item.id">
+                        <div class="category-card" @click="handleCategoriesEdit(item)">
+                            <div class="category-media">
+                                <el-image v-if="item.backgroundUrl" :src="item.backgroundUrl" fit="cover" />
+                                <div class="category-placeholder" v-else>
+                                    <el-icon :size="36">
+                                        <PictureFilled />
+                                    </el-icon>
+                                </div>
+                                <div class="category-overlay">
+                                    <el-icon :size="28">
+                                        <Edit />
                                     </el-icon>
-                                    <span class="text-gray-400">暂无背景图</span>
                                 </div>
                             </div>
-                            <div class="category-info mt-2">
-                                <div class="font-bold text-base">{{ item.title }}</div>
-                                <div class="text-sm text-gray-500 mt-1">{{ item.remark || '暂无备注' }}</div>
+                            <div class="category-body">
+                                <p class="category-title">{{ item.title }}</p>
+                                <p class="category-remark">{{ item.remark || '暂无备注' }}</p>
                             </div>
-                        </el-card>
+                        </div>
                     </el-col>
                 </el-row>
+                <div class="empty-block" v-else>
+                    <el-icon :size="48">
+                        <Grid />
+                    </el-icon>
+                    <p>暂未配置精选分类</p>
+                </div>
             </div>
         </el-card>
 
         <!-- ========== 轮播图弹窗 ========== -->
-        <el-dialog v-model="slideshowDialog.visible" :title="slideshowDialog.title" width="500px" append-to-body
-            @close="resetSlideshowForm">
+        <el-dialog v-model="slideshowDialog.visible" :title="slideshowDialog.title" width="480px"
+            :close-on-click-modal="false" append-to-body @close="resetSlideshowForm">
             <el-form ref="slideshowFormRef" :model="slideshowForm" :rules="slideshowRules" label-width="90px">
                 <el-form-item label="轮播图片" prop="ossId">
                     <image-upload v-model="slideshowForm.ossId" :limit="1" />
                 </el-form-item>
             </el-form>
             <template #footer>
-                <el-button :loading="slideshowDialog.loading" type="primary" icon="Check"
-                    @click="submitSlideshowForm">确定</el-button>
                 <el-button icon="Close" @click="slideshowDialog.visible = false">取消</el-button>
+                <el-button :loading="slideshowDialog.loading" type="primary" icon="Check"
+                    @click="submitSlideshowForm">保存</el-button>
             </template>
         </el-dialog>
 
         <!-- ========== 分类编辑弹窗 ========== -->
-        <el-dialog v-model="categoriesDialog.visible" :title="'编辑分类:' + categoriesForm.title" width="550px"
+        <el-dialog v-model="categoriesDialog.visible" :title="'编辑分类'" width="520px" :close-on-click-modal="false"
             append-to-body @close="resetCategoriesForm">
             <el-form ref="categoriesFormRef" :model="categoriesForm" :rules="categoriesRules" label-width="90px">
                 <el-form-item label="分类标题" prop="title">
-                    <el-input v-model="categoriesForm.title" placeholder="请输入分类标题" />
+                    <el-input v-model="categoriesForm.title" placeholder="请输入分类标题" maxlength="50" show-word-limit />
                 </el-form-item>
                 <el-form-item label="备注说明" prop="remark">
-                    <el-input v-model="categoriesForm.remark" type="textarea" :rows="3" placeholder="请输入备注说明" />
+                    <el-input v-model="categoriesForm.remark" type="textarea" :rows="3" placeholder="请输入备注说明"
+                        maxlength="200" show-word-limit />
                 </el-form-item>
                 <el-form-item label="背景图" prop="background">
                     <image-upload v-model="categoriesForm.background" :limit="1" />
                 </el-form-item>
             </el-form>
             <template #footer>
-                <el-button :loading="categoriesDialog.loading" type="primary" icon="Check"
-                    @click="submitCategoriesForm">确定</el-button>
                 <el-button icon="Close" @click="categoriesDialog.visible = false">取消</el-button>
+                <el-button :loading="categoriesDialog.loading" type="primary" icon="Check"
+                    @click="submitCategoriesForm">保存</el-button>
             </template>
         </el-dialog>
     </div>
 </template>
 
 <script setup lang="ts">
+import { Plus, PictureFilled, Grid, Edit, WarningFilled } from '@element-plus/icons-vue';
 import {
     listSlideshow, getSlideshow, addSlideshow, editSlideshow, delSlideshow
 } from '@/api/system/applet/slideshow';
@@ -115,6 +168,14 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const activeTab = ref<string>('slideshow');
 
+const tableHeaderStyle = {
+    background: '#fafbfc',
+    color: '#4a5568',
+    fontWeight: 600,
+    fontSize: '13px',
+    borderBottom: '2px solid #e2e8f0'
+};
+
 /* ==================== 轮播图 ==================== */
 const slideshowLoading = ref(false);
 const slideshowList = ref<SlideshowVO[]>([]);
@@ -244,23 +305,330 @@ onMounted(() => {
 </script>
 
 <style scoped>
+.applet-page {
+    width: 100%;
+    height: 100%;
+    padding: 20px 24px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    background: #f7f8fc;
+}
+
+/* ==================== 页头 ==================== */
+.page-header {
+    display: flex;
+    align-items: baseline;
+    gap: 14px;
+    flex-shrink: 0;
+}
+
+.header-title {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.header-icon {
+    font-size: 22px;
+    line-height: 1;
+}
+
+.header-text {
+    font-size: 18px;
+    font-weight: 700;
+    color: #1a202c;
+    letter-spacing: 0.3px;
+}
+
+.header-sub {
+    font-size: 13px;
+    color: #a0aec0;
+}
+
+/* ==================== 卡片 ==================== */
+.applet-card {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    border-radius: 12px !important;
+    border: none !important;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03) !important;
+    overflow: hidden;
+}
+
+.applet-card :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: 0 24px 20px;
+    overflow: hidden;
+}
+
+/* ==================== Tabs ==================== */
+.applet-tabs {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    overflow: hidden;
+}
+
+.applet-tabs :deep(.el-tabs__header) {
+    margin-bottom: 20px;
+    padding: 0 4px;
+    border-bottom: 1px solid #edf2f7;
+}
+
+.applet-tabs :deep(.el-tabs__nav-wrap::after) {
+    display: none;
+}
+
+.applet-tabs :deep(.el-tabs__item) {
+    height: 44px;
+    line-height: 44px;
+    font-size: 14px;
+    color: #718096;
+    padding: 0 20px;
+    font-weight: 500;
+}
+
+.applet-tabs :deep(.el-tabs__item.is-active) {
+    color: #2563eb;
+    font-weight: 600;
+}
+
+.applet-tabs :deep(.el-tabs__active-bar) {
+    background-color: #2563eb;
+    height: 3px;
+    border-radius: 3px 3px 0 0;
+}
+
+.tab-label {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+}
+
+.applet-tabs :deep(.el-tabs__content) {
+    flex: 1;
+    overflow-y: auto;
+}
+
+/* ==================== 工具栏 ==================== */
+.toolbar {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 16px;
+}
+
+.toolbar .el-button {
+    border-radius: 8px;
+    font-weight: 500;
+}
+
+.toolbar-count {
+    font-size: 13px;
+    color: #a0aec0;
+}
+
+/* ==================== 现代化表格 ==================== */
+.modern-table {
+    border-radius: 10px;
+    overflow: hidden;
+}
+
+.modern-table :deep(.el-table__header th) {
+    border-bottom: 2px solid #e2e8f0 !important;
+    border-right: none !important;
+}
+
+.modern-table :deep(.el-table__body td) {
+    border-right: none !important;
+    border-bottom: 1px solid #f1f5f9 !important;
+}
+
+.modern-table :deep(.el-table__row:hover > td) {
+    background: #f8fafc !important;
+}
+
+.modern-table :deep(.el-table__body-wrapper) {
+    border-bottom: 1px solid #f1f5f9;
+}
+
+.modern-table :deep(.el-table) {
+    border: none !important;
+}
+
+.modern-table :deep(.el-table::before) {
+    display: none;
+}
+
+.modern-table :deep(.el-table__inner-wrapper::before) {
+    display: none;
+}
+
+/* 图片预览 */
+.img-preview {
+    width: 88px;
+    height: 56px;
+    border-radius: 8px;
+    overflow: hidden;
+    display: inline-block;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+}
+
+.img-preview .el-image {
+    width: 100%;
+    height: 100%;
+    display: block;
+}
+
+.img-empty {
+    width: 88px;
+    height: 56px;
+    border-radius: 8px;
+    background: #f1f5f9;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    color: #cbd5e1;
+}
+
+/* 操作列分割线 */
+.modern-table .el-divider--vertical {
+    height: 14px;
+    margin: 0 4px;
+    border-color: #e2e8f0;
+}
+
+/* ==================== 空状态 ==================== */
+.empty-block {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 0;
+    color: #cbd5e1;
+}
+
+.empty-block p {
+    margin: 14px 0 18px;
+    font-size: 14px;
+    color: #a0aec0;
+}
+
+/* ==================== 分类卡片 ==================== */
 .category-card {
-    transition: transform 0.2s;
+    background: #fff;
+    border-radius: 12px;
+    overflow: hidden;
+    cursor: pointer;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 2px 8px rgba(0, 0, 0, 0.03);
+    transition: all 0.28s cubic-bezier(0.25, 0.8, 0.25, 1.2);
+    margin-bottom: 20px;
 }
 
 .category-card:hover {
-    transform: translateY(-2px);
+    transform: translateY(-4px);
+    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(37, 99, 235, 0.06);
+}
+
+.category-media {
+    position: relative;
+    width: 100%;
+    padding-top: 64%;
+    background: #f1f5f9;
+    overflow: hidden;
+}
+
+.category-media .el-image {
+    position: absolute;
+    inset: 0;
+    width: 100%;
+    height: 100%;
 }
 
 .category-placeholder {
+    position: absolute;
+    inset: 0;
     display: flex;
-    flex-direction: column;
     align-items: center;
     justify-content: center;
-    height: 120px;
-    background: #f5f7fa;
+    color: #cbd5e1;
+    background: #f8fafc;
+}
+
+.category-overlay {
+    position: absolute;
+    inset: 0;
+    background: linear-gradient(to top, rgba(0, 0, 0, 0.45) 0%, transparent 50%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    opacity: 0;
+    transition: opacity 0.25s;
+    color: #fff;
+}
+
+.category-card:hover .category-overlay {
+    opacity: 1;
+}
+
+.category-body {
+    padding: 14px 16px 16px;
+}
+
+.category-title {
+    font-size: 15px;
+    font-weight: 600;
+    color: #1a202c;
+    margin: 0 0 4px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.category-remark {
+    font-size: 13px;
+    color: #a0aec0;
+    margin: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+/* ==================== 弹窗优化 ==================== */
+:deep(.el-dialog) {
+    border-radius: 14px;
+    overflow: hidden;
+}
+
+:deep(.el-dialog__header) {
+    padding: 20px 24px 16px;
+    border-bottom: 1px solid #f1f5f9;
+    margin-right: 0;
+}
+
+:deep(.el-dialog__title) {
+    font-size: 16px;
+    font-weight: 700;
+    color: #1a202c;
+}
+
+:deep(.el-dialog__body) {
+    padding: 24px;
+}
+
+:deep(.el-dialog__footer) {
+    padding: 12px 24px 20px;
+    text-align: right;
+}
+
+:deep(.el-dialog__footer .el-button) {
     border-radius: 8px;
-    color: #c0c4cc;
-    gap: 6px;
+    padding: 8px 24px;
+    font-weight: 500;
 }
 </style>

+ 105 - 9
src/views/system/dept/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-2">
+  <div class="dept-page">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
       :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
@@ -37,7 +37,7 @@
         </el-row>
       </template>
 
-      <el-table ref="deptTableRef" v-loading="loading" :data="deptList" row-key="deptId" border
+      <el-table ref="deptTableRef" v-loading="loading" :data="deptList" row-key="deptId"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :default-expand-all="isExpandAll">
         <el-table-column prop="deptName" label="部门名称" min-width="200"></el-table-column>
         <el-table-column prop="orderNum" align="center" label="排序" width="120"></el-table-column>
@@ -51,22 +51,21 @@
             <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
           </template>
         </el-table-column>
-        <el-table-column fixed="right" align="center" label="操作" width="220">
+        <el-table-column fixed="right" align="center" label="操作" width="190">
           <template #default="scope">
-            <el-button v-hasPermi="['system:dept:edit']" link type="primary" icon="Edit"
+            <el-button v-hasPermi="['system:dept:edit']" link type="primary"
               @click="handleUpdate(scope.row)">修改</el-button>
-            <el-button v-hasPermi="['system:dept:add']" link type="success" icon="Plus"
-              @click="handleAdd(scope.row)">新增</el-button>
-            <el-button v-hasPermi="['system:dept:remove']" link type="danger" icon="Delete"
+            <el-button v-hasPermi="['system:dept:add']" link type="success" @click="handleAdd(scope.row)">新增</el-button>
+            <el-button v-hasPermi="['system:dept:remove']" link type="danger"
               @click="handleDelete(scope.row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
     </el-card>
 
-    <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-body width="600px">
+    <el-dialog v-model="dialog.visible" destroy-on-close :title="dialog.title" width="600px" class="dept-edit-dialog">
       <el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-row>
+        <el-row :gutter="24">
           <el-col v-if="form.parentId !== 0" :span="24">
             <el-form-item label="上级部门" prop="parentId">
               <el-tree-select v-model="form.parentId" :data="deptOptions"
@@ -74,6 +73,8 @@
                 placeholder="选择上级部门" check-strictly />
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row :gutter="24">
           <el-col :span="12">
             <el-form-item label="部门名称" prop="deptName">
               <el-input v-model="form.deptName" placeholder="请输入部门名称" />
@@ -84,6 +85,8 @@
               <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row :gutter="24">
           <el-col :span="12">
             <el-form-item label="负责人" prop="leader">
               <el-select v-model="form.leader" placeholder="请选择负责人">
@@ -97,6 +100,8 @@
               <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row :gutter="24">
           <el-col :span="12">
             <el-form-item label="邮箱" prop="email">
               <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
@@ -299,3 +304,94 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.dept-page {
+  @include page-common-list;
+
+  :deep(.el-button--primary:not(.is-link)) {
+    padding: 8px 18px;
+  }
+}
+
+/* ==================== 弹窗样式 ==================== */
+:global(.dept-edit-dialog) {
+  display: flex;
+  flex-direction: column;
+  max-height: 90vh;
+  overflow: hidden;
+  border-radius: 12px;
+}
+
+:global(.dept-edit-dialog .el-dialog__header) {
+  flex-shrink: 0;
+  padding: 20px 28px 0;
+  border-bottom: none;
+}
+
+:global(.dept-edit-dialog .el-dialog__header .el-dialog__title) {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1f2937;
+  letter-spacing: 0.5px;
+}
+
+:global(.dept-edit-dialog .el-dialog__body) {
+  padding: 28px 32px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+:global(.dept-edit-dialog .el-dialog__footer) {
+  border-top: 1px solid #f0f2f5;
+  padding: 16px 28px;
+  flex-shrink: 0;
+}
+
+:global(.dept-edit-dialog .dialog-footer) {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+:global(.dept-edit-dialog .dialog-footer .el-button) {
+  min-width: 100px;
+  height: 38px;
+  border-radius: 8px;
+  font-weight: 500;
+  letter-spacing: 0.5px;
+}
+
+:global(.dept-edit-dialog .el-form-item) {
+  margin-bottom: 24px;
+}
+
+:global(.dept-edit-dialog .el-form-item__label) {
+  font-weight: 500;
+  color: #4b5563;
+  font-size: 14px;
+}
+
+:global(.dept-edit-dialog .el-form-item:last-child) {
+  margin-bottom: 0;
+}
+
+:global(.dept-edit-dialog .el-row) {
+  margin-bottom: 16px;
+}
+
+:global(.dept-edit-dialog .el-input .el-input__wrapper),
+:global(.dept-edit-dialog .el-select .el-input__wrapper),
+:global(.dept-edit-dialog .el-tree-select .el-input__wrapper) {
+  border-radius: 8px;
+  box-shadow: 0 0 0 1px #e5e7eb inset;
+  transition: box-shadow 0.2s ease;
+}
+
+:global(.dept-edit-dialog .el-input .el-input__wrapper:hover) {
+  box-shadow: 0 0 0 1px #c4c7cc inset;
+}
+</style>

+ 117 - 15
src/views/system/dict/index.vue

@@ -47,7 +47,7 @@
           </div>
 
           <div class="dict-table-wrap">
-            <el-table ref="typeTableRef" v-loading="typeLoading" border :data="typeList" highlight-current-row
+            <el-table ref="typeTableRef" v-loading="typeLoading" :data="typeList" highlight-current-row
               style="width: 100%" @row-click="handleTypeRowClick" @selection-change="handleTypeSelectionChange">
               <el-table-column type="selection" width="55" align="center" />
               <el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
@@ -63,12 +63,12 @@
                   <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="操作" fixed="right" align="center" width="180"
+              <el-table-column label="操作" fixed="right" align="center" width="120"
                 class-name="small-padding fixed-width">
                 <template #default="scope">
-                  <el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit"
+                  <el-button v-hasPermi="['system:dict:edit']" link type="primary"
                     @click="handleTypeUpdate(scope.row)">修改</el-button>
-                  <el-button v-hasPermi="['system:dict:remove']" link type="danger" icon="Delete"
+                  <el-button v-hasPermi="['system:dict:remove']" link type="danger"
                     @click="handleTypeDelete(scope.row)">删除</el-button>
                 </template>
               </el-table-column>
@@ -119,7 +119,7 @@
           </div>
 
           <div class="dict-table-wrap">
-            <el-table v-loading="dataLoading" border :data="dataList" style="width: 100%"
+            <el-table v-loading="dataLoading" :data="dataList" style="width: 100%"
               @selection-change="handleDataSelectionChange">
               <el-table-column type="selection" width="55" align="center" />
               <el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
@@ -141,12 +141,12 @@
                   <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="操作" fixed="right" align="center" width="180"
+              <el-table-column label="操作" fixed="right" align="center" width="120"
                 class-name="small-padding fixed-width">
                 <template #default="scope">
-                  <el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit"
+                  <el-button v-hasPermi="['system:dict:edit']" link type="primary"
                     @click="handleDataUpdate(scope.row)">修改</el-button>
-                  <el-button v-hasPermi="['system:dict:remove']" link type="danger" icon="Delete"
+                  <el-button v-hasPermi="['system:dict:remove']" link type="danger"
                     @click="handleDataDelete(scope.row)">删除</el-button>
                 </template>
               </el-table-column>
@@ -160,19 +160,21 @@
     </el-row>
 
     <!-- 字典类型对话框 -->
-    <el-dialog v-model="typeDialog.visible" :title="typeDialog.title" width="500px" append-to-body>
+    <el-dialog v-model="typeDialog.visible" :title="typeDialog.title" width="500px" class="dict-type-dialog">
       <el-form ref="typeFormRef" :model="typeForm" :rules="typeRules" label-width="100px">
         <el-form-item label="字典名称" prop="dictName">
           <el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
         </el-form-item>
         <el-form-item prop="dictType">
-          <el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
-          <span slot="label">
+          <template #label>
+            <span>字典类型</span>
             <el-tooltip content="数据存储中的Key值,如:sys_user_sex" placement="top">
-              <i class="el-icon-question"></i>
+              <el-icon style="margin-left: 4px; font-size: 14px; color: #909399; cursor: help;">
+                <QuestionFilled />
+              </el-icon>
             </el-tooltip>
-            字典类型
-          </span>
+          </template>
+          <el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
         </el-form-item>
         <el-form-item label="备注" prop="remark">
           <el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容"></el-input>
@@ -187,7 +189,7 @@
     </el-dialog>
 
     <!-- 字典数据对话框 -->
-    <el-dialog v-model="dataDialog.visible" :title="dataDialog.title" width="500px" append-to-body>
+    <el-dialog v-model="dataDialog.visible" :title="dataDialog.title" width="500px" class="dict-data-dialog">
       <el-form ref="dataFormRef" :model="dataForm" :rules="dataRules" label-width="80px">
         <el-form-item label="字典类型">
           <el-input v-model="dataForm.dictType" :disabled="true" />
@@ -230,6 +232,7 @@ import { listType, getType, delType, addType, updateType, refreshCache } from '@
 import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
 import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
 import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
+import { QuestionFilled } from '@element-plus/icons-vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -559,6 +562,8 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
+@use '@/assets/styles/page-common.scss' as *;
+
 .dict-grid {
   row-gap: 16px;
 }
@@ -620,4 +625,101 @@ onMounted(() => {
 .dict-table-wrap {
   overflow-x: auto;
 }
+
+
+.dict-page {
+  @include page-common-list;
+}
+
+/* ==================== 弹窗样式 ==================== */
+:global(.dict-type-dialog),
+:global(.dict-data-dialog) {
+  display: flex;
+  flex-direction: column;
+  max-height: 90vh;
+  overflow: hidden;
+  border-radius: 12px;
+}
+
+:global(.dict-type-dialog .el-dialog__header),
+:global(.dict-data-dialog .el-dialog__header) {
+  flex-shrink: 0;
+  padding: 20px 28px 0;
+  border-bottom: none;
+}
+
+:global(.dict-type-dialog .el-dialog__header .el-dialog__title),
+:global(.dict-data-dialog .el-dialog__header .el-dialog__title) {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1f2937;
+  letter-spacing: 0.5px;
+}
+
+:global(.dict-type-dialog .el-dialog__body),
+:global(.dict-data-dialog .el-dialog__body) {
+  padding: 28px 32px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+:global(.dict-type-dialog .el-dialog__footer),
+:global(.dict-data-dialog .el-dialog__footer) {
+  border-top: 1px solid #f0f2f5;
+  padding: 16px 28px;
+  flex-shrink: 0;
+}
+
+:global(.dict-type-dialog .dialog-footer),
+:global(.dict-data-dialog .dialog-footer) {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+:global(.dict-type-dialog .dialog-footer .el-button),
+:global(.dict-data-dialog .dialog-footer .el-button) {
+  min-width: 100px;
+  height: 38px;
+  border-radius: 8px;
+  font-weight: 500;
+  letter-spacing: 0.5px;
+}
+
+:global(.dict-type-dialog .el-form-item),
+:global(.dict-data-dialog .el-form-item) {
+  margin-bottom: 24px;
+}
+
+:global(.dict-type-dialog .el-form-item__label),
+:global(.dict-data-dialog .el-form-item__label) {
+  font-weight: 500;
+  color: #4b5563;
+  font-size: 14px;
+}
+
+:global(.dict-type-dialog .el-form-item:last-child),
+:global(.dict-data-dialog .el-form-item:last-child) {
+  margin-bottom: 0;
+}
+
+:global(.dict-type-dialog .el-input .el-input__wrapper),
+:global(.dict-data-dialog .el-input .el-input__wrapper),
+:global(.dict-type-dialog .el-select .el-input__wrapper),
+:global(.dict-data-dialog .el-select .el-input__wrapper) {
+  border-radius: 8px;
+  box-shadow: 0 0 0 1px #e5e7eb inset;
+  transition: box-shadow 0.2s ease;
+}
+
+:global(.dict-type-dialog .el-input .el-input__wrapper:hover),
+:global(.dict-data-dialog .el-input .el-input__wrapper:hover) {
+  box-shadow: 0 0 0 1px #c4c7cc inset;
+}
+
+:global(.dict-type-dialog .el-textarea__inner),
+:global(.dict-data-dialog .el-textarea__inner) {
+  border-radius: 8px;
+}
 </style>

+ 17 - 5
src/views/system/oss/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-2">
+  <div class="oss-page">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
       :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
@@ -52,7 +52,7 @@
         </el-row>
       </template>
 
-      <el-table v-if="showTable" v-loading="loading" :data="ossList" border :header-cell-class-name="handleHeaderClass"
+      <el-table v-if="showTable" v-loading="loading" :data="ossList" :header-cell-class-name="handleHeaderClass"
         @selection-change="handleSelectionChange" @header-click="handleHeaderCLick">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="对象存储主键" align="center" prop="ossId" />
@@ -73,11 +73,11 @@
         </el-table-column>
         <el-table-column label="上传人" align="center" prop="createByName" />
         <el-table-column label="服务商" align="center" prop="service" sortable="custom" />
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <el-table-column label="操作" align="center" width="130" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button v-hasPermi="['system:oss:download']" link type="warning" icon="Download"
+            <el-button v-hasPermi="['system:oss:download']" link type="warning"
               @click="handleDownload(scope.row)">下载</el-button>
-            <el-button v-hasPermi="['system:oss:remove']" link type="danger" icon="Delete"
+            <el-button v-hasPermi="['system:oss:remove']" link type="danger"
               @click="handleDelete(scope.row)">删除</el-button>
           </template>
         </el-table-column>
@@ -287,3 +287,15 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.oss-page {
+  @include page-common-list;
+
+  :deep(.el-button--primary:not(.is-link)) {
+    padding: 8px 18px;
+  }
+}
+</style>

+ 134 - 13
src/views/system/phone/index.vue

@@ -1,25 +1,45 @@
 <template>
-  <div class="p-2">
-    <el-card shadow="never">
-      <template #header>
-        <span class="font-bold text-base">联系电话管理</span>
-      </template>
+  <div class="phone-page">
+    <div class="page-header">
+      <div class="header-title">
+        <span class="header-icon">📞</span>
+        <span class="header-text">联系电话管理</span>
+      </div>
+      <span class="header-sub">配置小程序端展示的各类联系电话</span>
+    </div>
 
-      <el-form ref="formRef" :model="form" :rules="rules" label-width="140px" class="mt-4">
+    <el-card shadow="never" class="phone-card">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="160px" class="phone-form">
         <el-form-item label="华晟型材专家电话" prop="expertPhone">
-          <el-input v-model="form.expertPhone" placeholder="请输入专家电话" style="width: 300px" maxlength="20" />
+          <el-input v-model="form.expertPhone" placeholder="请输入专家电话" maxlength="20" class="phone-input">
+            <template #prefix>
+              <el-icon><Phone /></el-icon>
+            </template>
+          </el-input>
         </el-form-item>
         <el-form-item label="客服电话" prop="servicePhone">
-          <el-input v-model="form.servicePhone" placeholder="请输入客服电话" style="width: 300px" maxlength="20" />
+          <el-input v-model="form.servicePhone" placeholder="请输入客服电话" maxlength="20" class="phone-input">
+            <template #prefix>
+              <el-icon><Headset /></el-icon>
+            </template>
+          </el-input>
         </el-form-item>
         <el-form-item label="业务员电话" prop="salesPhone">
-          <el-input v-model="form.salesPhone" placeholder="请输入业务员电话" style="width: 300px" maxlength="20" />
+          <el-input v-model="form.salesPhone" placeholder="请输入业务员电话" maxlength="20" class="phone-input">
+            <template #prefix>
+              <el-icon><UserFilled /></el-icon>
+            </template>
+          </el-input>
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" icon="Check" :loading="saving" @click="handleSave">
+          <el-button type="primary" :loading="saving" @click="handleSave">
+            <el-icon><Check /></el-icon>
             保存
           </el-button>
-          <el-button icon="Refresh" @click="loadPhone">重置</el-button>
+          <el-button @click="loadPhone">
+            <el-icon><Refresh /></el-icon>
+            重置
+          </el-button>
         </el-form-item>
       </el-form>
     </el-card>
@@ -27,6 +47,7 @@
 </template>
 
 <script setup lang="ts">
+import { Phone, Headset, UserFilled, Check, Refresh } from '@element-plus/icons-vue';
 import { getPhone, updatePhone, PhoneForm } from '@/api/system/phone';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -47,7 +68,6 @@ const rules = {
   salesPhone: [{ required: true, message: '业务员电话不能为空', trigger: 'blur' }]
 };
 
-/** 加载联系电话 */
 const loadPhone = async () => {
   const res = await getPhone(1);
   form.id = res.data.id;
@@ -56,7 +76,6 @@ const loadPhone = async () => {
   form.salesPhone = res.data.salesPhone;
 };
 
-/** 保存 */
 const handleSave = () => {
   formRef.value?.validate(async (valid: boolean) => {
     if (!valid) return;
@@ -74,3 +93,105 @@ onMounted(() => {
   loadPhone();
 });
 </script>
+
+<style scoped>
+.phone-page {
+    width: 100%;
+    height: 100%;
+    padding: 20px 24px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    background: #f7f8fc;
+}
+
+/* ==================== 页头 ==================== */
+.page-header {
+    display: flex;
+    align-items: baseline;
+    gap: 14px;
+    flex-shrink: 0;
+}
+
+.header-title {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.header-icon {
+    font-size: 22px;
+    line-height: 1;
+}
+
+.header-text {
+    font-size: 18px;
+    font-weight: 700;
+    color: #1a202c;
+    letter-spacing: 0.3px;
+}
+
+.header-sub {
+    font-size: 13px;
+    color: #a0aec0;
+}
+
+/* ==================== 卡片 ==================== */
+.phone-card {
+    border-radius: 12px !important;
+    border: none !important;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03) !important;
+}
+
+.phone-card :deep(.el-card__body) {
+    padding: 28px 32px 24px;
+}
+
+/* ==================== 表单 ==================== */
+.phone-form {
+    max-width: 520px;
+}
+
+.phone-form :deep(.el-form-item__label) {
+    font-weight: 500;
+    color: #4a5568;
+}
+
+.phone-input {
+    --el-input-border-radius: 8px;
+}
+
+.phone-input :deep(.el-input__wrapper) {
+    border-radius: 8px;
+    box-shadow: 0 0 0 1px #e2e8f0 inset;
+    padding-left: 10px;
+}
+
+.phone-input :deep(.el-input__wrapper:hover) {
+    box-shadow: 0 0 0 1px #cbd5e1 inset;
+}
+
+.phone-input :deep(.el-input__wrapper.is-focus) {
+    box-shadow: 0 0 0 1px #4f46e5 inset, 0 0 0 3px rgba(79, 70, 229, 0.08);
+}
+
+.phone-input :deep(.el-input__prefix) {
+    color: #a0aec0;
+    margin-right: 6px;
+}
+
+.phone-input :deep(.el-input__prefix .el-icon) {
+    font-size: 16px;
+}
+
+.phone-form :deep(.el-button) {
+    border-radius: 8px;
+    padding: 8px 22px;
+    font-weight: 500;
+}
+
+.phone-form :deep(.el-button .el-icon) {
+    margin-right: 4px;
+}
+</style>

+ 65 - 6
src/views/system/post/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-2">
+  <div class="post-page">
     <el-row :gutter="20">
       <!-- 部门树 -->
       <el-col :lg="4" :xs="24" style="">
@@ -62,7 +62,7 @@
               <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
             </el-row>
           </template>
-          <el-table v-loading="loading" border :data="postList" @selection-change="handleSelectionChange">
+          <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
             <el-table-column type="selection" width="55" align="center" />
             <el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
             <el-table-column label="岗位名称" align="center" prop="postName" />
@@ -78,11 +78,11 @@
                 <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
               </template>
             </el-table-column>
-            <el-table-column label="操作" width="150" align="center" class-name="small-padding fixed-width">
+            <el-table-column label="操作" width="120" align="center" class-name="small-padding fixed-width">
               <template #default="scope">
-                <el-button v-hasPermi="['system:post:edit']" link type="primary" icon="Edit"
+                <el-button v-hasPermi="['system:post:edit']" link type="primary"
                   @click="handleUpdate(scope.row)">修改</el-button>
-                <el-button v-hasPermi="['system:post:remove']" link type="danger" icon="Delete"
+                <el-button v-hasPermi="['system:post:remove']" link type="danger"
                   @click="handleDelete(scope.row)">删除</el-button>
               </template>
             </el-table-column>
@@ -93,7 +93,7 @@
         </el-card>
 
         <!-- 添加或修改岗位对话框 -->
-        <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+        <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" class="post-edit-dialog">
           <el-form ref="postFormRef" :model="form" :rules="rules" label-width="80px">
             <el-form-item label="岗位名称" prop="postName">
               <el-input v-model="form.postName" placeholder="请输入岗位名称" />
@@ -315,3 +315,62 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.post-page {
+  @include page-common-list;
+
+  :deep(.el-button--primary:not(.is-link)) {
+    padding: 8px 18px;
+  }
+}
+
+/* ==================== 弹窗样式 ==================== */
+:global(.post-edit-dialog) {
+  display: flex;
+  flex-direction: column;
+  max-height: 90vh;
+  overflow: hidden;
+}
+
+:global(.post-edit-dialog .el-dialog__header) {
+  flex-shrink: 0;
+}
+
+:global(.post-edit-dialog .el-dialog__body) {
+  padding: 24px 28px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+:global(.post-edit-dialog .el-dialog__footer) {
+  border-top: 1px solid #edf2f7;
+  flex-shrink: 0;
+}
+
+:global(.post-edit-dialog .dialog-footer) {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+:global(.post-edit-dialog .dialog-footer .el-button) {
+  min-width: 88px;
+}
+
+:global(.post-edit-dialog .el-form-item) {
+  margin-bottom: 20px;
+}
+
+:global(.post-edit-dialog .el-form-item__label) {
+  font-weight: 600;
+  color: #374151;
+}
+
+:global(.post-edit-dialog .el-form-item:last-child) {
+  margin-bottom: 0;
+}
+</style>

+ 44 - 25
src/views/system/role/authUser.vue

@@ -1,31 +1,35 @@
 <template>
-  <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-      <div v-show="showSearch" class="search">
-        <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-          <el-form-item label="用户名称" prop="userName">
-            <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
-          </el-form-item>
-          <el-form-item label="手机号码" prop="phonenumber">
-            <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-          </el-form-item>
-        </el-form>
+  <div class="auth-user-page">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+      :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="用户名称" prop="userName">
+              <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="手机号码" prop="phonenumber">
+              <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
       </div>
     </transition>
-    <el-card shadow="never">
+
+    <el-card shadow="hover">
       <template #header>
         <el-row :gutter="10">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="openSelectUser">添加用户</el-button>
+            <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus"
+              @click="openSelectUser">添加用户</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll">
-              批量取消授权
-            </el-button>
+            <el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple"
+              @click="cancelAuthUserAll">批量取消授权</el-button>
           </el-col>
           <el-col :span="1.5">
             <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
@@ -33,8 +37,9 @@
           <right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
         </el-row>
       </template>
-      <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
+
+      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="50" align="center" />
         <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
         <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
         <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
@@ -51,14 +56,16 @@
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button v-hasPermi="['system:role:remove']" link type="danger" icon="CircleClose" @click="cancelAuthUser(scope.row)">取消授权</el-button>
+            <el-button v-hasPermi="['system:role:remove']" link type="danger" icon="CircleClose"
+              @click="cancelAuthUser(scope.row)">取消授权</el-button>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
-      <select-user ref="selectRef" :role-id="queryParams.roleId" @ok="handleQuery" />
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        :total="total" @pagination="getList" />
     </el-card>
+    <select-user ref="selectRef" :role-id="queryParams.roleId" @ok="handleQuery" />
   </div>
 </template>
 
@@ -154,3 +161,15 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.auth-user-page {
+  @include page-common-list;
+
+  :deep(.el-button--primary:not(.is-link)) {
+    padding: 8px 18px;
+  }
+}
+</style>

+ 91 - 14
src/views/system/role/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-2">
+  <div class="role-page">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
       :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
@@ -53,8 +53,7 @@
         </el-row>
       </template>
 
-      <el-table ref="roleTableRef" border v-loading="loading" :data="roleList"
-        @selection-change="handleSelectionChange">
+      <el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
         <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
@@ -72,15 +71,15 @@
           </template>
         </el-table-column>
 
-        <el-table-column fixed="right" label="操作" width="360">
+        <el-table-column fixed="right" label="操作" width="300">
           <template #default="scope">
-            <el-button v-if="scope.row.roleId !== 1" v-hasPermi="['system:role:edit']" link type="primary" icon="Edit"
+            <el-button v-if="scope.row.roleId !== 1" v-hasPermi="['system:role:edit']" link type="primary"
               @click="handleUpdate(scope.row)">修改</el-button>
             <el-button v-if="scope.row.roleId !== 1" v-hasPermi="['system:role:remove']" link type="danger"
-              icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+              @click="handleDelete(scope.row)">删除</el-button>
             <el-button v-if="scope.row.roleId !== 1" v-hasPermi="['system:role:edit']" link type="warning"
-              icon="CircleCheck" @click="handleDataScope(scope.row)">数据权限</el-button>
-            <el-button v-if="scope.row.roleId !== 1" v-hasPermi="['system:role:edit']" link type="success" icon="User"
+              @click="handleDataScope(scope.row)">数据权限</el-button>
+            <el-button v-if="scope.row.roleId !== 1" v-hasPermi="['system:role:edit']" link type="success"
               @click="handleAuthUser(scope.row)">分配用户</el-button>
           </template>
         </el-table-column>
@@ -90,7 +89,7 @@
         v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
 
-    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" class="role-edit-dialog">
       <el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
         <el-form-item label="角色名称" prop="roleName">
           <el-input v-model="form.roleName" placeholder="请输入角色名称" />
@@ -102,7 +101,7 @@
         <el-form-item label="状态">
           <el-radio-group v-model="form.status">
             <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label
-              }}</el-radio>
+            }}</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="菜单权限">
@@ -127,14 +126,11 @@
     </el-dialog>
 
     <!-- 分配角色数据权限对话框 -->
-    <el-dialog v-model="openDataScope" :title="dialog.title" width="500px" append-to-body>
+    <el-dialog v-model="openDataScope" :title="dialog.title" width="500px" class="role-scope-dialog">
       <el-form ref="dataScopeRef" :model="form" label-width="80px">
         <el-form-item label="角色名称">
           <el-input v-model="form.roleName" :disabled="true" />
         </el-form-item>
-        <el-form-item label="权限字符">
-          <el-input v-model="form.roleKey" :disabled="true" />
-        </el-form-item>
         <el-form-item label="权限范围">
           <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
             <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label"
@@ -484,3 +480,84 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.role-page {
+  @include page-common-list;
+
+  :deep(.el-button--primary:not(.is-link)) {
+    padding: 8px 18px;
+  }
+}
+
+/* ==================== 弹窗样式 ==================== */
+:global(.role-edit-dialog),
+:global(.role-scope-dialog) {
+  display: flex;
+  flex-direction: column;
+  max-height: 90vh;
+  overflow: hidden;
+}
+
+:global(.role-edit-dialog .el-dialog__header),
+:global(.role-scope-dialog .el-dialog__header) {
+  flex-shrink: 0;
+}
+
+:global(.role-edit-dialog .el-dialog__body),
+:global(.role-scope-dialog .el-dialog__body) {
+  padding: 24px 28px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+:global(.role-edit-dialog .el-dialog__footer),
+:global(.role-scope-dialog .el-dialog__footer) {
+  border-top: 1px solid #edf2f7;
+  flex-shrink: 0;
+}
+
+:global(.role-edit-dialog .dialog-footer),
+:global(.role-scope-dialog .dialog-footer) {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+:global(.role-edit-dialog .dialog-footer .el-button),
+:global(.role-scope-dialog .dialog-footer .el-button) {
+  min-width: 88px;
+}
+
+:global(.role-edit-dialog .el-form-item),
+:global(.role-scope-dialog .el-form-item) {
+  margin-bottom: 20px;
+}
+
+:global(.role-edit-dialog .el-form-item__label),
+:global(.role-scope-dialog .el-form-item__label) {
+  font-weight: 600;
+  color: #374151;
+}
+
+:global(.role-edit-dialog .el-form-item:last-child),
+:global(.role-scope-dialog .el-form-item:last-child) {
+  margin-bottom: 0;
+}
+
+:global(.role-edit-dialog .el-tree),
+:global(.role-scope-dialog .el-tree) {
+  border-radius: 10px;
+  border: 1px solid #e2e8f0;
+  padding: 8px;
+  margin-top: 8px;
+}
+
+:global(.role-edit-dialog .el-checkbox),
+:global(.role-scope-dialog .el-checkbox) {
+  margin-right: 16px;
+}
+</style>

+ 154 - 30
src/views/system/user/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="p-2">
+  <div class="user-page">
     <el-row :gutter="20">
       <!-- 部门树 -->
       <el-col :lg="4" :xs="24" style="">
@@ -88,7 +88,7 @@
             </el-row>
           </template>
 
-          <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
+          <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
             <el-table-column type="selection" width="50" align="center" />
             <el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
             <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName"
@@ -112,16 +112,16 @@
               </template>
             </el-table-column>
 
-            <el-table-column label="操作" fixed="right" width="280" class-name="small-padding fixed-width">
+            <el-table-column label="操作" fixed="right" width="240" class-name="small-padding fixed-width">
               <template #default="scope">
                 <el-button v-if="scope.row.userId !== 1" v-hasPermi="['system:user:edit']" link type="primary"
-                  icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
+                  @click="handleUpdate(scope.row)">修改</el-button>
                 <el-button v-if="scope.row.userId !== 1" v-hasPermi="['system:user:remove']" link type="danger"
-                  icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+                  @click="handleDelete(scope.row)">删除</el-button>
                 <el-button v-if="scope.row.userId !== 1" v-hasPermi="['system:user:resetPwd']" link type="danger"
-                  icon="Key" @click="handleResetPwd(scope.row)">重置密码</el-button>
+                  @click="handleResetPwd(scope.row)">重置密码</el-button>
                 <el-button v-if="scope.row.userId !== 1" v-hasPermi="['system:user:edit']" link type="success"
-                  icon="CircleCheck" @click="handleAuthRole(scope.row)">分配角色</el-button>
+                  @click="handleAuthRole(scope.row)">分配角色</el-button>
               </template>
             </el-table-column>
           </el-table>
@@ -133,10 +133,22 @@
     </el-row>
 
     <!-- 添加或修改用户配置对话框 -->
-    <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body
+    <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="680px" class="usr-edit-dialog"
       @close="closeDialog">
-      <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-row>
+      <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px" class="usr-edit-form">
+        <el-row :gutter="24">
+          <el-col :span="12" v-if="form.userId == undefined">
+            <el-form-item label="用户名称" prop="userName">
+              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.userId == undefined">
+            <el-form-item label="用户密码" prop="password">
+              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="24">
           <el-col :span="12">
             <el-form-item label="用户昵称" prop="nickName">
               <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
@@ -150,7 +162,7 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row>
+        <el-row :gutter="24">
           <el-col :span="12">
             <el-form-item label="手机号码" prop="phonenumber">
               <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
@@ -162,19 +174,7 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
-              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
+        <el-row :gutter="24">
           <el-col :span="12">
             <el-form-item label="用户性别">
               <el-select v-model="form.sex" placeholder="请选择">
@@ -192,8 +192,8 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
+        <el-row :gutter="24" v-if="form.userId == null || form.userId != useUserStore().userId">
+          <el-col :span="12">
             <el-form-item label="岗位">
               <el-select v-model="form.postIds" multiple placeholder="请选择">
                 <el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId"
@@ -201,7 +201,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
+          <el-col :span="12">
             <el-form-item label="角色" prop="roleIds">
               <el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
                 <el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId"
@@ -210,10 +210,10 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row>
+        <el-row :gutter="24">
           <el-col :span="24">
             <el-form-item label="备注">
-              <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
+              <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" :rows="3" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -227,7 +227,7 @@
     </el-dialog>
 
     <!-- 用户导入对话框 -->
-    <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
+    <el-dialog v-model="upload.open" :title="upload.title" width="400px" class="usr-import-dialog">
       <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers"
         :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading"
         :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
@@ -643,3 +643,127 @@ async function handleDeptChange(value: number | string) {
   form.value.postIds = [];
 }
 </script>
+
+<style scoped lang="scss">
+@use '@/assets/styles/page-common.scss' as *;
+
+.user-page {
+  @include page-common-list;
+
+  :deep(.el-button--primary:not(.is-link)) {
+    padding: 8px 18px;
+  }
+}
+
+/* ==================== 弹窗样式 ==================== */
+:global(.usr-edit-dialog),
+:global(.usr-import-dialog) {
+  display: flex;
+  flex-direction: column;
+  max-height: 90vh;
+  overflow: hidden;
+  border-radius: 12px;
+}
+
+:global(.usr-edit-dialog .el-dialog__header),
+:global(.usr-import-dialog .el-dialog__header) {
+  flex-shrink: 0;
+  padding: 20px 28px 0;
+  border-bottom: none;
+}
+
+:global(.usr-edit-dialog .el-dialog__header .el-dialog__title),
+:global(.usr-import-dialog .el-dialog__header .el-dialog__title) {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1f2937;
+  letter-spacing: 0.5px;
+}
+
+:global(.usr-edit-dialog .el-dialog__body),
+:global(.usr-import-dialog .el-dialog__body) {
+  padding: 28px 32px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+:global(.usr-edit-dialog .el-dialog__footer),
+:global(.usr-import-dialog .el-dialog__footer) {
+  border-top: 1px solid #f0f2f5;
+  padding: 16px 28px;
+  flex-shrink: 0;
+}
+
+:global(.usr-edit-dialog .dialog-footer),
+:global(.usr-import-dialog .dialog-footer) {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+:global(.usr-edit-dialog .dialog-footer .el-button),
+:global(.usr-import-dialog .dialog-footer .el-button) {
+  min-width: 100px;
+  height: 38px;
+  border-radius: 8px;
+  font-weight: 500;
+  letter-spacing: 0.5px;
+}
+
+:global(.usr-edit-dialog .el-form),
+:global(.usr-import-dialog .el-form) {
+  max-width: 100%;
+}
+
+:global(.usr-edit-dialog .el-form-item),
+:global(.usr-import-dialog .el-form-item) {
+  margin-bottom: 24px;
+}
+
+:global(.usr-edit-dialog .el-form-item__label),
+:global(.usr-import-dialog .el-form-item__label) {
+  font-weight: 500;
+  color: #4b5563;
+  font-size: 14px;
+}
+
+:global(.usr-edit-dialog .el-form-item:last-child),
+:global(.usr-import-dialog .el-form-item:last-child) {
+  margin-bottom: 0;
+}
+
+:global(.usr-edit-dialog .el-row),
+:global(.usr-import-dialog .el-row) {
+  margin-bottom: 16px;
+}
+
+:global(.usr-edit-dialog .el-input .el-input__wrapper),
+:global(.usr-import-dialog .el-input .el-input__wrapper) {
+  border-radius: 8px;
+  box-shadow: 0 0 0 1px #e5e7eb inset;
+  transition: box-shadow 0.2s ease;
+}
+
+:global(.usr-edit-dialog .el-input .el-input__wrapper:hover),
+:global(.usr-import-dialog .el-input .el-input__wrapper:hover) {
+  box-shadow: 0 0 0 1px #c4c7cc inset;
+}
+
+:global(.usr-edit-dialog .el-select .el-input__wrapper),
+:global(.usr-import-dialog .el-select .el-input__wrapper),
+:global(.usr-edit-dialog .el-tree-select .el-input__wrapper),
+:global(.usr-import-dialog .el-tree-select .el-input__wrapper) {
+  border-radius: 8px;
+}
+
+:global(.usr-edit-dialog .el-textarea__inner),
+:global(.usr-import-dialog .el-textarea__inner) {
+  border-radius: 8px;
+}
+
+:global(.usr-edit-dialog .el-radio-group .el-radio .el-radio__inner),
+:global(.usr-import-dialog .el-radio-group .el-radio .el-radio__inner) {
+  border-radius: 50%;
+}
+</style>

+ 466 - 53
src/views/system/user/profile/index.vue

@@ -1,63 +1,126 @@
 <template>
-  <div class="p-2">
-    <el-row :gutter="20">
-      <el-col :span="6" :xs="24">
-        <el-card class="box-card">
-          <template #header>
-            <div class="clearfix">
-              <span>个人信息</span>
-            </div>
-          </template>
-          <div>
-            <div class="text-center">
+  <div class="profile-page">
+    <div class="page-header">
+      <div class="header-title">
+        <span class="header-icon">👤</span>
+        <span class="header-text">个人中心</span>
+      </div>
+      <span class="header-sub">管理您的账户信息与安全设置</span>
+    </div>
+
+    <el-row :gutter="20" class="profile-row">
+      <el-col :span="6" :xs="24" class="profile-sidebar">
+        <div class="profile-card">
+          <div class="profile-card__banner">
+            <div class="profile-card__avatar">
               <userAvatar />
             </div>
-            <ul class="list-group list-group-striped">
-              <li class="list-group-item">
-                <svg-icon icon-class="user" />用户名称
-                <div class="pull-right">{{ state.user.userName }}</div>
-              </li>
-              <li class="list-group-item">
-                <svg-icon icon-class="phone" />手机号码
-                <div class="pull-right">{{ state.user.phonenumber }}</div>
-              </li>
-              <li class="list-group-item">
-                <svg-icon icon-class="email" />用户邮箱
-                <div class="pull-right">{{ state.user.email }}</div>
-              </li>
-              <li class="list-group-item">
-                <svg-icon icon-class="tree" />所属部门
-                <div v-if="state.user.deptName" class="pull-right">{{ state.user.deptName }} / {{ state.postGroup }}
-                </div>
-              </li>
-              <li class="list-group-item">
-                <svg-icon icon-class="peoples" />所属角色
-                <div class="pull-right">{{ state.roleGroup }}</div>
-              </li>
-              <li class="list-group-item">
-                <svg-icon icon-class="date" />创建日期
-                <div class="pull-right">{{ state.user.createTime }}</div>
-              </li>
-            </ul>
+            <div class="profile-card__name">{{ state.user.userName }}</div>
+            <div class="profile-card__dept">{{ state.user.deptName || '未分配部门' }}</div>
           </div>
-        </el-card>
-      </el-col>
-      <el-col :span="18" :xs="24">
-        <el-card>
-          <template #header>
-            <div class="clearfix">
-              <span>基本资料</span>
+
+          <div class="profile-card__body">
+            <div class="profile-item" v-if="state.user.phonenumber">
+              <div class="profile-item__icon">
+                <el-icon :size="16">
+                  <Phone />
+                </el-icon>
+              </div>
+              <div class="profile-item__info">
+                <span class="profile-item__label">手机号码</span>
+                <span class="profile-item__value">{{ state.user.phonenumber }}</span>
+              </div>
+            </div>
+            <div class="profile-item" v-if="state.user.email">
+              <div class="profile-item__icon">
+                <el-icon :size="16">
+                  <Message />
+                </el-icon>
+              </div>
+              <div class="profile-item__info">
+                <span class="profile-item__label">用户邮箱</span>
+                <span class="profile-item__value">{{ state.user.email }}</span>
+              </div>
+            </div>
+            <div class="profile-item" v-if="state.user.deptName || state.postGroup">
+              <div class="profile-item__icon">
+                <el-icon :size="16">
+                  <OfficeBuilding />
+                </el-icon>
+              </div>
+              <div class="profile-item__info">
+                <span class="profile-item__label">所属部门</span>
+                <span class="profile-item__value">{{ state.user.deptName || '-' }}{{ state.postGroup ? ' / ' +
+                  state.postGroup : '' }}</span>
+              </div>
+            </div>
+            <div class="profile-item" v-if="state.roleGroup">
+              <div class="profile-item__icon">
+                <el-icon :size="16">
+                  <UserFilled />
+                </el-icon>
+              </div>
+              <div class="profile-item__info">
+                <span class="profile-item__label">所属角色</span>
+                <span class="profile-item__value">{{ state.roleGroup }}</span>
+              </div>
             </div>
-          </template>
-          <el-tabs v-model="activeTab">
-            <el-tab-pane label="基本资料" name="userinfo">
-              <userInfo :user="userForm" />
+            <div class="profile-item" v-if="state.user.createTime">
+              <div class="profile-item__icon">
+                <el-icon :size="16">
+                  <Calendar />
+                </el-icon>
+              </div>
+              <div class="profile-item__info">
+                <span class="profile-item__label">创建日期</span>
+                <span class="profile-item__value">{{ state.user.createTime }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-col>
+
+      <el-col :span="18" :xs="24" class="profile-main">
+        <el-card shadow="never" class="profile-main-card">
+          <el-tabs v-model="activeTab" class="profile-tabs">
+            <el-tab-pane name="userinfo">
+              <template #label>
+                <span class="tab-label">
+                  <el-icon>
+                    <User />
+                  </el-icon>
+                  基本资料
+                </span>
+              </template>
+              <div class="profile-tab-content">
+                <userInfo :user="userForm" />
+              </div>
             </el-tab-pane>
-            <el-tab-pane label="修改密码" name="resetPwd">
-              <resetPwd />
+            <el-tab-pane name="resetPwd">
+              <template #label>
+                <span class="tab-label">
+                  <el-icon>
+                    <Lock />
+                  </el-icon>
+                  修改密码
+                </span>
+              </template>
+              <div class="profile-tab-content">
+                <resetPwd />
+              </div>
             </el-tab-pane>
-            <el-tab-pane label="在线设备" name="onlineDevice">
-              <onlineDevice :devices="state.devices" />
+            <el-tab-pane name="onlineDevice">
+              <template #label>
+                <span class="tab-label">
+                  <el-icon>
+                    <Monitor />
+                  </el-icon>
+                  在线设备
+                </span>
+              </template>
+              <div class="profile-tab-content profile-tab-content--full">
+                <onlineDevice :devices="state.devices" />
+              </div>
             </el-tab-pane>
           </el-tabs>
         </el-card>
@@ -67,6 +130,7 @@
 </template>
 
 <script setup name="Profile" lang="ts">
+import { Phone, Message, UserFilled, Calendar, User, Lock, Monitor, OfficeBuilding } from '@element-plus/icons-vue';
 import UserAvatar from './userAvatar.vue';
 import UserInfo from './userInfo.vue';
 import ResetPwd from './resetPwd.vue';
@@ -109,3 +173,352 @@ onMounted(() => {
   getOnlines();
 });
 </script>
+
+<style scoped>
+.profile-page {
+  width: 100%;
+  height: 100%;
+  padding: 20px 24px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  background: #f7f8fc;
+  overflow-y: auto;
+}
+
+/* ==================== 页头 ==================== */
+.page-header {
+  display: flex;
+  align-items: baseline;
+  gap: 14px;
+  flex-shrink: 0;
+}
+
+.header-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.header-icon {
+  font-size: 22px;
+  line-height: 1;
+}
+
+.header-text {
+  font-size: 18px;
+  font-weight: 700;
+  color: #1a202c;
+  letter-spacing: 0.3px;
+}
+
+.header-sub {
+  font-size: 13px;
+  color: #a0aec0;
+}
+
+/* ==================== 布局 ==================== */
+.profile-row {
+  flex: 1;
+  min-height: 0;
+}
+
+.profile-sidebar,
+.profile-sidebar .el-col {
+  height: 100%;
+}
+
+.profile-main,
+.profile-main .el-col {
+  height: 100%;
+}
+
+/* ==================== 个人信息卡片 ==================== */
+.profile-card {
+  background: #fff;
+  border-radius: 14px;
+  overflow: hidden;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03);
+}
+
+.profile-card__banner {
+  background: linear-gradient(150deg, #4f46e5 0%, #7c3aed 40%, #a78bfa 100%);
+  padding: 28px 20px 24px;
+  text-align: center;
+  position: relative;
+  overflow: hidden;
+}
+
+.profile-card__banner::after {
+  content: '';
+  position: absolute;
+  top: -40%;
+  right: -30%;
+  width: 200px;
+  height: 200px;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.06);
+  pointer-events: none;
+}
+
+.profile-card__avatar {
+  margin-bottom: 14px;
+  display: flex;
+  justify-content: center;
+}
+
+.profile-card__avatar :deep(.user-info-head) {
+  width: 78px;
+  height: 78px;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.profile-card__avatar :deep(.user-info-head:hover:after) {
+  width: 78px;
+  height: 78px;
+  left: 0;
+  right: auto;
+  top: 0;
+  bottom: auto;
+  line-height: 78px;
+  border-radius: 50%;
+}
+
+.profile-card__avatar :deep(.img-lg) {
+  width: 78px !important;
+  height: 78px !important;
+}
+
+.profile-card__avatar :deep(img) {
+  border-radius: 50%;
+  border: 3px solid rgba(255, 255, 255, 0.4);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+  object-fit: cover;
+  transition: transform 0.25s;
+}
+
+.profile-card__avatar :deep(img:hover) {
+  transform: scale(1.05);
+}
+
+.profile-card__name {
+  font-size: 17px;
+  font-weight: 700;
+  color: #fff;
+  letter-spacing: 0.3px;
+}
+
+.profile-card__dept {
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.75);
+  margin-top: 4px;
+}
+
+/* 信息列表 */
+.profile-card__body {
+  padding: 8px 0;
+  flex: 1;
+}
+
+.profile-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 14px 20px;
+  transition: background 0.2s;
+}
+
+.profile-item:hover {
+  background: #f8fafc;
+}
+
+.profile-item__icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #eef2ff;
+  color: #4f46e5;
+  flex-shrink: 0;
+}
+
+.profile-item__info {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  min-width: 0;
+}
+
+.profile-item__label {
+  font-size: 11px;
+  font-weight: 500;
+  color: #a0aec0;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.profile-item__value {
+  font-size: 13px;
+  font-weight: 500;
+  color: #2d3748;
+  word-break: break-all;
+}
+
+/* ==================== 主卡片 ==================== */
+.profile-main-card {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  border-radius: 14px !important;
+  border: none !important;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03) !important;
+  overflow: hidden;
+}
+
+.profile-main-card :deep(.el-card__body) {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  padding: 0 24px 24px;
+  overflow: hidden;
+}
+
+/* ==================== Tabs ==================== */
+.profile-tabs {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  overflow: hidden;
+}
+
+.profile-tabs :deep(.el-tabs__header) {
+  margin-bottom: 24px;
+  padding: 6px 4px 0;
+  border-bottom: 1px solid #edf2f7;
+}
+
+.profile-tabs :deep(.el-tabs__nav-wrap::after) {
+  display: none;
+}
+
+.profile-tabs :deep(.el-tabs__item) {
+  height: 44px;
+  line-height: 44px;
+  font-size: 14px;
+  color: #718096;
+  padding: 0 20px;
+  font-weight: 500;
+}
+
+.profile-tabs :deep(.el-tabs__item.is-active) {
+  color: #4f46e5;
+  font-weight: 600;
+}
+
+.profile-tabs :deep(.el-tabs__active-bar) {
+  background-color: #4f46e5;
+  height: 3px;
+  border-radius: 3px 3px 0 0;
+}
+
+.tab-label {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.profile-tabs :deep(.el-tabs__content) {
+  flex: 1;
+  overflow-y: auto;
+}
+
+/* ==================== Tab内容区 ==================== */
+.profile-tab-content {
+  max-width: 560px;
+}
+
+.profile-tab-content--full {
+  max-width: none;
+}
+
+/* ==================== 穿透子组件表单样式 ==================== */
+.profile-tab-content :deep(.el-form-item__label) {
+  font-weight: 500;
+  color: #4a5568;
+}
+
+.profile-tab-content :deep(.el-input__wrapper) {
+  border-radius: 8px;
+  box-shadow: 0 0 0 1px #e2e8f0 inset;
+}
+
+.profile-tab-content :deep(.el-input__wrapper:hover) {
+  box-shadow: 0 0 0 1px #cbd5e1 inset;
+}
+
+.profile-tab-content :deep(.el-input__wrapper.is-focus) {
+  box-shadow: 0 0 0 1px #4f46e5 inset, 0 0 0 3px rgba(79, 70, 229, 0.08);
+}
+
+.profile-tab-content :deep(.el-button) {
+  border-radius: 8px;
+  font-weight: 500;
+}
+
+.profile-tab-content :deep(.el-button--primary) {
+  padding: 8px 24px;
+}
+
+/* ==================== 穿透表格样式(在线设备) ==================== */
+.profile-tab-content :deep(.el-table) {
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.profile-tab-content :deep(.el-table__header th) {
+  background: #fafbfc !important;
+  color: #4a5568;
+  font-weight: 600;
+  font-size: 13px;
+  border-bottom: 2px solid #e2e8f0 !important;
+}
+
+.profile-tab-content :deep(.el-table__body td) {
+  border-bottom: 1px solid #f1f5f9 !important;
+}
+
+.profile-tab-content :deep(.el-table__row:hover > td) {
+  background: #f8fafc !important;
+}
+
+.profile-tab-content :deep(.el-table::before) {
+  display: none;
+}
+
+.profile-tab-content :deep(.el-table__inner-wrapper::before) {
+  display: none;
+}
+
+/* ==================== 响应式 ==================== */
+@media (max-width: 767px) {
+  .profile-page {
+    padding: 12px;
+  }
+
+  .profile-row {
+    flex-direction: column;
+  }
+
+  .profile-sidebar {
+    margin-bottom: 16px;
+  }
+}
+</style>

+ 3 - 3
src/views/system/user/profile/onlineDevice.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <el-table :data="devices" border style="width: 100%; height: 100%; font-size: 14px">
+    <el-table :data="devices" style="width: 100%; height: 100%; font-size: 14px">
       <el-table-column label="设备类型" align="center">
         <template #default="scope">
           <dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
@@ -17,7 +17,7 @@
       </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template #default="scope">
-          <el-button link type="danger" icon="Delete" @click="handldDelOnline(scope.row)">删除</el-button>
+          <el-button link type="danger" @click="handldDelOnline(scope.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -50,6 +50,6 @@ const handldDelOnline = (row: any) => {
         proxy?.$modal.msgError(res.msg);
       }
     })
-    .catch(() => {});
+    .catch(() => { });
 };
 </script>