Bläddra i källkod

用户管理、角色管理bug修改

lingmin_package@163.com 4 veckor sedan
förälder
incheckning
ccceda1613
4 ändrade filer med 498 tillägg och 148 borttagningar
  1. 1 1
      src/components/MenuPermissionTree.vue
  2. 167 53
      src/views/admin/Roles.vue
  3. 276 94
      src/views/admin/Users.vue
  4. 54 0
      test_datetime_format.html

+ 1 - 1
src/components/MenuPermissionTree.vue

@@ -35,7 +35,7 @@
         node-key="id"
         :default-expand-all="false"
         :default-checked-keys="defaultCheckedKeys"
-        :check-strictly="true"
+        :check-strictly="false"
         @check="handleCheck"
         @check-change="handleCheckChange"
         class="permission-tree"

+ 167 - 53
src/views/admin/Roles.vue

@@ -36,17 +36,17 @@
         @selection-change="handleSelectionChange"
       >
         <el-table-column type="selection" width="55" />
-        <el-table-column prop="display_name" label="角色名称" min-width="150" fixed="left">
+        <el-table-column prop="code" label="角色标识" min-width="150" fixed="left">
           <template #default="{ row }">
             <div class="role-info">
-              <span class="role-name">{{ row.display_name }}</span>
+              <span class="role-name">{{ row.code }}</span>
               <el-tag v-if="row.is_system" type="info" size="small" style="margin-left: 8px;">
                 系统角色
               </el-tag>
             </div>
           </template>
         </el-table-column>
-        <el-table-column prop="name" label="角色标识" width="120" />
+        <el-table-column prop="name" label="角色名称" width="120" />
         <el-table-column prop="description" label="描述" min-width="180" show-overflow-tooltip />
         <el-table-column prop="user_count" label="用户数" width="80" align="center">
           <template #default="{ row }">
@@ -63,35 +63,52 @@
             />
           </template>
         </el-table-column>
-        <el-table-column prop="created_at" label="创建时间" width="140">
+        <el-table-column prop="created_by" label="创建人" min-width="100">
+          <template #default="{ row }">
+            <div class="user-info-simple">
+              {{ row.created_by_name || '-' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="created_time" label="创建时间" min-width="140">
           <template #default="{ row }">
             <div class="time-info">
-              {{ formatDateTime(row.created_at) }}
+              {{ formatDateTime(row.created_time) }}
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="180" fixed="right">
+        <el-table-column prop="updated_by" label="修改人" min-width="100">
           <template #default="{ row }">
-            <el-button type="primary" size="small" @click="editRole(row)">
-              编辑
-            </el-button>
-            <el-dropdown @command="(command) => handleRoleDropdown(command, row)">
-              <el-button size="small">
-                更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
+            <div class="user-info-simple">
+              {{ row.updated_by_name || '-' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updated_time" label="修改时间" min-width="140">
+          <template #default="{ row }">
+            <div class="time-info">
+              {{ formatDateTime(row.updated_time) }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="300" fixed="right">
+          <template #default="{ row }">
+            <div class="action-buttons">
+              <el-button type="primary" size="small" @click="editRole(row)">
+                编辑角色
               </el-button>
-              <template #dropdown>
-                <el-dropdown-menu>
-                  <el-dropdown-item command="permissions">权限管理</el-dropdown-item>
-                  <el-dropdown-item 
-                    command="delete" 
-                    :disabled="row.is_system"
-                    divided
-                  >
-                    删除角色
-                  </el-dropdown-item>
-                </el-dropdown-menu>
-              </template>
-            </el-dropdown>
+              <el-button type="success" size="small" @click="managePermissions(row)">
+                权限管理
+              </el-button>
+              <el-button 
+                type="danger" 
+                size="small" 
+                @click="deleteRole(row)"
+                :disabled="row.is_system"
+              >
+                删除角色
+              </el-button>
+            </div>
           </template>
         </el-table-column>
       </el-table>
@@ -123,12 +140,12 @@
         :rules="roleRules"
         label-width="100px"
       >
-        <el-form-item label="角色名称" prop="display_name">
-          <el-input v-model="roleForm.display_name" placeholder="请输入角色显示名称" />
+        <el-form-item label="角色名称" prop="name">
+          <el-input v-model="roleForm.name" placeholder="请输入角色名称" />
         </el-form-item>
-        <el-form-item label="角色标识" prop="name">
+        <el-form-item label="角色标识" prop="code">
           <el-input 
-            v-model="roleForm.name" 
+            v-model="roleForm.code" 
             placeholder="请输入角色标识(英文)"
             :disabled="editingRole && editingRole.is_system"
           />
@@ -159,7 +176,7 @@
     >
       <div v-if="currentRole" class="permission-dialog-content">
         <div class="dialog-header">
-          <h4>为角色 "{{ currentRole.display_name }}" 分配菜单权限</h4>
+          <h4>为角色 "{{ currentRole.name }}" 分配菜单权限</h4>
           <p v-if="isSuperAdminRole" class="dialog-description super-admin-notice">
             <el-icon><InfoFilled /></el-icon>
             超级管理员角色默认拥有所有菜单权限,无需手动分配。
@@ -238,7 +255,7 @@
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, Search, ArrowDown, Check, InfoFilled } from '@element-plus/icons-vue'
+import { Plus, Search, Check, InfoFilled } from '@element-plus/icons-vue'
 import request from '@/api/request'
 import MenuPermissionTree from '@/components/MenuPermissionTree.vue'
 
@@ -261,8 +278,8 @@ const currentRole = ref<any>(null)
 
 // 表单数据
 const roleForm = reactive({
-  display_name: '',
   name: '',
+  code: '',
   description: ''
 })
 
@@ -302,10 +319,10 @@ const isSuperAdminRole = computed(() => {
 
 // 表单验证规则
 const roleRules = {
-  display_name: [
+  name: [
     { required: true, message: '请输入角色名称', trigger: 'blur' }
   ],
-  name: [
+  code: [
     { required: true, message: '请输入角色标识', trigger: 'blur' },
     { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '角色标识只能包含字母、数字和下划线,且以字母或下划线开头', trigger: 'blur' }
   ]
@@ -382,8 +399,8 @@ const handleStatusChange = async (role: any) => {
 // 编辑角色
 const editRole = (role: any) => {
   editingRole.value = role
-  roleForm.display_name = role.display_name
   roleForm.name = role.name
+  roleForm.code = role.code
   roleForm.description = role.description
   showCreateDialog.value = true
 }
@@ -392,7 +409,7 @@ const editRole = (role: any) => {
 const deleteRole = async (role: any) => {
   try {
     await ElMessageBox.confirm(
-      `确定要删除角色 "${role.display_name}" 吗?此操作不可恢复。`,
+      `确定要删除角色 "${role.name}" 吗?此操作不可恢复。`,
       '确认删除',
       {
         confirmButtonText: '确定',
@@ -413,18 +430,6 @@ const deleteRole = async (role: any) => {
   }
 }
 
-// 下拉菜单处理
-const handleRoleDropdown = (command: string, role: any) => {
-  switch (command) {
-    case 'permissions':
-      managePermissions(role)
-      break
-    case 'delete':
-      deleteRole(role)
-      break
-  }
-}
-
 // 权限管理
 const managePermissions = async (role: any) => {
   currentRole.value = role
@@ -514,7 +519,7 @@ const saveRole = async () => {
     if (editingRole.value) {
       // 更新角色
       await request.put(`/api/v1/system/admin/roles/${editingRole.value.id}`, {
-        display_name: roleForm.display_name,
+        name: roleForm.name,
         description: roleForm.description
       })
       ElMessage.success('角色更新成功')
@@ -554,11 +559,17 @@ const savePermissions = async () => {
     const checkedKeys = permissionTreeRef.value?.getCheckedKeys() || []
     
     // 保存角色菜单权限
-    await request.put(`/api/v1/system/admin/roles/${currentRole.value.id}/menus`, {
+    const result = await request.put(`/api/v1/system/admin/roles/${currentRole.value.id}/menus`, {
       menu_ids: checkedKeys
     })
     
-    ElMessage.success('权限保存成功')
+    // 检查是否自动添加了父菜单
+    if (result.data && result.data.auto_added_parents > 0) {
+      ElMessage.success(`权限保存成功!已自动添加 ${result.data.auto_added_parents} 个父菜单以确保菜单层级完整`)
+    } else {
+      ElMessage.success('权限保存成功')
+    }
+    
     showPermissionDialog.value = false
     
     // 刷新角色列表
@@ -574,8 +585,8 @@ const savePermissions = async () => {
 // 重置表单
 const resetForm = () => {
   editingRole.value = null
-  roleForm.display_name = ''
   roleForm.name = ''
+  roleForm.code = ''
   roleForm.description = ''
   roleFormRef.value?.clearValidate()
 }
@@ -583,7 +594,17 @@ const resetForm = () => {
 // 格式化日期时间
 const formatDateTime = (dateTime: string) => {
   if (!dateTime) return '-'
-  return new Date(dateTime).toLocaleString('zh-CN')
+  const date = new Date(dateTime)
+  
+  // 格式化为 YYYY-MM-DD HH:mm:ss
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  const seconds = String(date.getSeconds()).padStart(2, '0')
+  
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
 }
 
 // 组件挂载
@@ -656,6 +677,22 @@ onMounted(() => {
   color: #666;
 }
 
+.user-info-simple {
+  font-size: 12px;
+  color: #666;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 6px;
+  align-items: center;
+  justify-content: flex-start;
+  flex-wrap: nowrap;
+}
+
 .pagination {
   margin-top: 20px;
   display: flex;
@@ -747,4 +784,81 @@ onMounted(() => {
   justify-content: flex-end;
   gap: 12px;
 }
+
+/* 响应式设计 */
+@media (max-width: 1400px) {
+  .table-container {
+    min-width: 1200px;
+  }
+}
+
+@media (max-width: 1200px) {
+  .table-container {
+    min-width: 1100px;
+  }
+  
+  .toolbar {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+  
+  .toolbar-left, .toolbar-right {
+    justify-content: center;
+  }
+  
+  .action-buttons {
+    gap: 4px;
+  }
+  
+  .action-buttons .el-button {
+    font-size: 12px;
+    padding: 4px 8px;
+  }
+}
+
+@media (max-width: 1000px) {
+  .table-container {
+    min-width: 1000px;
+  }
+  
+  .action-buttons {
+    flex-direction: column;
+    gap: 2px;
+  }
+  
+  .action-buttons .el-button {
+    font-size: 11px;
+    padding: 2px 6px;
+    min-width: 70px;
+  }
+}
+
+@media (max-width: 768px) {
+  .roles-management {
+    padding: 10px;
+  }
+  
+  .table-container {
+    min-width: 900px;
+  }
+  
+  .toolbar {
+    padding: 12px;
+  }
+  
+  .toolbar-left {
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+  
+  .pagination {
+    margin-top: 15px;
+  }
+  
+  :deep(.el-pagination) {
+    justify-content: center;
+    flex-wrap: wrap;
+  }
+}
 </style>

+ 276 - 94
src/views/admin/Users.vue

@@ -12,14 +12,14 @@
           <el-icon><Plus /></el-icon>
           创建用户
         </el-button>
-        <el-button 
+        <!--<el-button 
           type="danger" 
           :disabled="selectedUsers.length === 0"
           @click="batchDelete"
         >
           <el-icon><Delete /></el-icon>
           批量删除
-        </el-button>
+        </el-button> -->
       </div>
       <div class="toolbar-right">
         <el-input
@@ -45,7 +45,7 @@
         :scroll-x="true"
       >
       <el-table-column type="selection" width="55" />
-      <el-table-column prop="username" label="用户信息" width="140" fixed="left">
+      <el-table-column prop="username" label="用户信息" min-width="160" fixed="left">
         <template #default="{ row }">
           <div class="user-info">
             <el-avatar :size="28" :src="row.avatar_url">
@@ -58,9 +58,9 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="email" label="邮箱" width="180" show-overflow-tooltip />
-      <el-table-column prop="phone" label="手机号" width="120" />
-      <el-table-column prop="roles" label="角色" width="130">
+      <el-table-column prop="email" label="邮箱" min-width="200" show-overflow-tooltip />
+      <el-table-column prop="phone" label="手机号" min-width="130" />
+      <el-table-column prop="roles" label="角色" min-width="140">
         <template #default="{ row }">
           <div class="roles-container">
             <el-tag 
@@ -74,14 +74,14 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="is_superuser" label="管理员" width="70" align="center">
+      <el-table-column prop="is_superuser" label="管理员" width="80" align="center">
         <template #default="{ row }">
           <el-tag :type="row.is_superuser ? 'danger' : 'info'" size="small">
             {{ row.is_superuser ? '是' : '否' }}
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column prop="is_active" label="状态" width="70" align="center">
+      <el-table-column prop="is_active" label="状态" width="80" align="center">
         <template #default="{ row }">
           <el-switch
             v-model="row.is_active"
@@ -90,36 +90,58 @@
           />
         </template>
       </el-table-column>
-      <el-table-column prop="last_login_at" label="最后登录" width="130">
+      <el-table-column prop="last_login_at" label="最后登录" min-width="140">
         <template #default="{ row }">
           <div class="time-info">
             {{ formatDateTime(row.last_login_at) }}
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="160" fixed="right">
+      <el-table-column prop="created_by" label="创建人" min-width="100">
+        <template #default="{ row }">
+          <div class="user-info-simple">
+            {{ row.created_by_name || '-' }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="created_time" label="创建时间" min-width="140">
+        <template #default="{ row }">
+          <div class="time-info">
+            {{ formatDateTime(row.created_time) }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="updated_by" label="修改人" min-width="100">
+        <template #default="{ row }">
+          <div class="user-info-simple">
+            {{ row.updated_by_name || '-' }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="updated_time" label="修改时间" min-width="140">
+        <template #default="{ row }">
+          <div class="time-info">
+            {{ formatDateTime(row.updated_time) }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="280" fixed="right">
         <template #default="{ row }">
           <div class="action-buttons">
             <el-button type="primary" size="small" @click="editUser(row)">
               编辑
             </el-button>
-            <el-dropdown @command="(command) => handleDropdownCommand(command, row)">
-              <el-button size="small">
-                更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
-              </el-button>
-              <template #dropdown>
-                <el-dropdown-menu>
-                  <el-dropdown-item command="roles">分配角色</el-dropdown-item>
-                  <el-dropdown-item 
-                    command="delete" 
-                    :disabled="row.is_superuser || isCurrentUser(row.id)"
-                    divided
-                  >
-                    删除用户
-                  </el-dropdown-item>
-                </el-dropdown-menu>
-              </template>
-            </el-dropdown>
+            <el-button type="success" size="small" @click="assignRoles(row)">
+              分配角色
+            </el-button>
+            <el-button 
+              type="danger" 
+              size="small" 
+              @click="deleteUser(row)"
+              :disabled="row.is_superuser || isCurrentUser(row.id)"
+            >
+              删除
+            </el-button>
           </div>
         </template>
       </el-table-column>
@@ -143,33 +165,38 @@
     <el-dialog
       v-model="showCreateDialog"
       :title="editingUser ? '编辑用户' : '创建用户'"
-      width="600px"
+      width="660px"
       @close="resetForm"
     >
       <el-form
         ref="userFormRef"
         :model="userForm"
         :rules="userRules"
-        label-width="80px"
+        label-width="88px"
       >
-        <el-row :gutter="16">
+        <el-row :gutter="18">
           <el-col :span="12">
             <el-form-item label="用户名" prop="username">
               <el-input 
                 v-model="userForm.username" 
                 placeholder="请输入用户名"
                 :disabled="!!editingUser"
+                size="default"
               />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="邮箱" prop="email">
-              <el-input v-model="userForm.email" placeholder="请输入邮箱" />
+              <el-input 
+                v-model="userForm.email" 
+                placeholder="请输入邮箱"
+                size="default"
+              />
             </el-form-item>
           </el-col>
         </el-row>
         
-        <el-row :gutter="16">
+        <el-row :gutter="18">
           <el-col :span="12">
             <el-form-item label="密码" :prop="editingUser ? '' : 'password'">
               <el-input 
@@ -177,33 +204,50 @@
                 type="password" 
                 :placeholder="editingUser ? '留空则不修改密码' : '请输入密码'"
                 show-password
+                size="default"
               />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="手机号" prop="phone">
-              <el-input v-model="userForm.phone" placeholder="请输入手机号" />
+              <el-input 
+                v-model="userForm.phone" 
+                placeholder="请输入手机号"
+                size="default"
+              />
             </el-form-item>
           </el-col>
         </el-row>
 
-        <el-row :gutter="16">
+        <el-row :gutter="18">
           <el-col :span="12">
             <el-form-item label="真实姓名" prop="real_name">
-              <el-input v-model="userForm.real_name" placeholder="请输入真实姓名" />
+              <el-input 
+                v-model="userForm.real_name" 
+                placeholder="请输入真实姓名"
+                size="default"
+              />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="公司" prop="company">
-              <el-input v-model="userForm.company" placeholder="请输入公司名称" />
+              <el-input 
+                v-model="userForm.company" 
+                placeholder="请输入公司名称"
+                size="default"
+              />
             </el-form-item>
           </el-col>
         </el-row>
 
-        <el-row :gutter="16">
+        <el-row :gutter="18">
           <el-col :span="12">
             <el-form-item label="部门" prop="department">
-              <el-input v-model="userForm.department" placeholder="请输入部门" />
+              <el-input 
+                v-model="userForm.department" 
+                placeholder="请输入部门"
+                size="default"
+              />
             </el-form-item>
           </el-col>
           <el-col :span="12">
@@ -213,11 +257,12 @@
                 multiple 
                 placeholder="请选择角色"
                 style="width: 100%"
+                size="default"
               >
                 <el-option
                   v-for="role in allRoles"
                   :key="role.id"
-                  :label="role.display_name"
+                  :label="role.name"
                   :value="role.id"
                 />
               </el-select>
@@ -225,10 +270,10 @@
           </el-col>
         </el-row>
 
-        <el-row :gutter="16">
+        <el-row :gutter="18">
           <el-col :span="12">
             <el-form-item label="管理员">
-              <el-switch v-model="userForm.is_superuser" />
+              <el-switch v-model="userForm.is_superuser" size="default" />
               <span style="margin-left: 8px; font-size: 12px; color: #666;">
                 拥有系统所有权限
               </span>
@@ -236,7 +281,7 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="启用状态">
-              <el-switch v-model="userForm.is_active" />
+              <el-switch v-model="userForm.is_active" size="default" />
               <span style="margin-left: 8px; font-size: 12px; color: #666;">
                 用户是否可以登录
               </span>
@@ -256,7 +301,7 @@
     <el-dialog
       v-model="showRoleDialog"
       title="分配角色"
-      width="500px"
+      width="550px"
     >
       <div v-if="currentUser">
         <h4>为用户 "{{ currentUser.username }}" 分配角色</h4>
@@ -267,7 +312,7 @@
             :label="role.id"
             :value="role.id"
           >
-            {{ role.display_name }}
+            {{ role.name }}
             <el-tag v-if="role.is_system" type="info" size="small" style="margin-left: 8px;">
               系统角色
             </el-tag>
@@ -287,7 +332,7 @@
 <script setup lang="ts">
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, Search, Delete, ArrowDown } from '@element-plus/icons-vue'
+import { Plus, Search, Delete } from '@element-plus/icons-vue'
 import { useAuthStore } from '@/stores/auth'
 import request from '@/api/request'
 
@@ -339,6 +384,9 @@ const userRules = {
   password: [
     { required: true, message: '请输入密码', trigger: 'blur' },
     { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
+  ],
+  real_name: [
+    { required: true, message: '请输入真实姓名', trigger: 'blur' }
   ]
 }
 
@@ -428,20 +476,48 @@ const handleStatusChange = async (user: any) => {
 }
 
 // 编辑用户
-const editUser = (user: any) => {
+const editUser = async (user: any) => {
   editingUser.value = user
-  Object.assign(userForm, {
-    username: user.username,
-    email: user.email,
-    password: '',
-    phone: user.phone || '',
-    real_name: user.real_name || '',
-    company: user.company || '',
-    department: user.department || '',
-    role_ids: [], // 需要从用户角色关联中获取
-    is_superuser: user.is_superuser,
-    is_active: user.is_active
-  })
+  
+  try {
+    // 获取用户详情(包含角色信息)
+    const result = await request.get(`/api/v1/system/admin/users/${user.id}`)
+    
+    if (result.code === 0) {
+      const userDetail = result.data
+      Object.assign(userForm, {
+        username: userDetail.username,
+        email: userDetail.email,
+        password: '',
+        phone: userDetail.phone || '',
+        real_name: userDetail.real_name || '',
+        company: userDetail.company || '',
+        department: userDetail.department || '',
+        role_ids: userDetail.role_ids || [],
+        is_superuser: userDetail.is_superuser,
+        is_active: userDetail.is_active
+      })
+    } else {
+      throw new Error(result.message)
+    }
+  } catch (error) {
+    console.error('获取用户详情失败:', error)
+    ElMessage.error('获取用户详情失败')
+    // 如果获取详情失败,使用列表中的基本信息
+    Object.assign(userForm, {
+      username: user.username,
+      email: user.email,
+      password: '',
+      phone: user.phone || '',
+      real_name: user.real_name || '',
+      company: user.company || '',
+      department: user.department || '',
+      role_ids: [],
+      is_superuser: user.is_superuser,
+      is_active: user.is_active
+    })
+  }
+  
   showCreateDialog.value = true
 }
 
@@ -497,23 +573,26 @@ const batchDelete = async () => {
   }
 }
 
-// 下拉菜单处理
-const handleDropdownCommand = (command: string, user: any) => {
-  switch (command) {
-    case 'roles':
-      assignRoles(user)
-      break
-    case 'delete':
-      deleteUser(user)
-      break
-  }
-}
-
 // 分配角色
 const assignRoles = async (user: any) => {
   currentUser.value = user
-  // TODO: 获取用户当前角色
-  selectedRoleIds.value = []
+  
+  try {
+    // 获取用户详情(包含角色信息)
+    const result = await request.get(`/api/v1/system/admin/users/${user.id}`)
+    
+    if (result.code === 0) {
+      const userDetail = result.data
+      selectedRoleIds.value = userDetail.role_ids || []
+    } else {
+      throw new Error(result.message)
+    }
+  } catch (error) {
+    console.error('获取用户角色失败:', error)
+    ElMessage.error('获取用户角色失败')
+    selectedRoleIds.value = []
+  }
+  
   showRoleDialog.value = true
 }
 
@@ -528,17 +607,17 @@ const saveUser = async () => {
       // 更新用户
       const updateData: any = {
         email: userForm.email,
-        phone: userForm.phone,
+        phone: userForm.phone || null, // 确保空字符串转为null
         real_name: userForm.real_name,
-        company: userForm.company,
-        department: userForm.department,
+        company: userForm.company || null,
+        department: userForm.department || null,
         role_ids: userForm.role_ids,
         is_superuser: userForm.is_superuser,
         is_active: userForm.is_active
       }
       
       // 如果填写了密码,则更新密码
-      if (userForm.password) {
+      if (userForm.password && userForm.password.trim()) {
         updateData.password = userForm.password
       }
       
@@ -546,7 +625,14 @@ const saveUser = async () => {
       ElMessage.success('用户更新成功')
     } else {
       // 创建用户
-      await request.post('/api/v1/system/admin/users', userForm)
+      const createData = {
+        ...userForm,
+        phone: userForm.phone || null, // 确保空字符串转为null
+        company: userForm.company || null,
+        department: userForm.department || null
+      }
+      
+      await request.post('/api/v1/system/admin/users', createData)
       ElMessage.success('用户创建成功')
     }
     
@@ -606,23 +692,16 @@ const resetForm = () => {
 const formatDateTime = (dateTime: string) => {
   if (!dateTime) return '-'
   const date = new Date(dateTime)
-  const now = new Date()
-  const diffTime = now.getTime() - date.getTime()
-  const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
   
-  if (diffDays === 0) {
-    return date.toLocaleTimeString('zh-CN', { 
-      hour: '2-digit', 
-      minute: '2-digit' 
-    })
-  } else if (diffDays < 7) {
-    return `${diffDays}天前`
-  } else {
-    return date.toLocaleDateString('zh-CN', { 
-      month: '2-digit', 
-      day: '2-digit' 
-    })
-  }
+  // 格式化为 YYYY-MM-DD HH:mm:ss
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  const seconds = String(date.getSeconds()).padStart(2, '0')
+  
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
 }
 
 // 组件挂载
@@ -679,12 +758,22 @@ onMounted(() => {
   background: #fff;
   border-radius: 8px;
   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  min-width: 1200px; /* 设置最小宽度确保表格不会过度压缩 */
 }
 
 .user-info {
   display: flex;
   align-items: center;
   gap: 8px;
+  min-width: 120px; /* 确保用户信息列有足够宽度 */
+}
+
+.user-info-simple {
+  font-size: 12px;
+  color: #666;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 .user-details {
@@ -701,6 +790,7 @@ onMounted(() => {
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  max-width: 100px; /* 限制用户名最大宽度 */
 }
 
 .real-name {
@@ -709,6 +799,7 @@ onMounted(() => {
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  max-width: 100px; /* 限制真实姓名最大宽度 */
 }
 
 .roles-container {
@@ -716,6 +807,7 @@ onMounted(() => {
   flex-wrap: wrap;
   gap: 2px;
   align-items: center;
+  min-width: 80px; /* 确保角色列有最小宽度 */
 }
 
 .time-info {
@@ -728,6 +820,8 @@ onMounted(() => {
   display: flex;
   gap: 6px;
   align-items: center;
+  justify-content: flex-start; /* 左对齐操作按钮 */
+  flex-wrap: nowrap; /* 不允许换行 */
 }
 
 .pagination {
@@ -747,7 +841,7 @@ onMounted(() => {
 }
 
 :deep(.el-dialog__body) {
-  padding: 20px;
+  padding: 22px; /* 增加对话框内边距 10% */
 }
 
 :deep(.el-checkbox-group) {
@@ -759,4 +853,92 @@ onMounted(() => {
 :deep(.el-checkbox) {
   margin-right: 0;
 }
+
+/* 响应式设计 */
+@media (max-width: 1400px) {
+  .table-container {
+    min-width: 1300px; /* 增加最小宽度以适应更多列 */
+  }
+  
+  .username, .real-name {
+    max-width: 80px;
+  }
+}
+
+@media (max-width: 1200px) {
+  .table-container {
+    min-width: 1200px; /* 增加最小宽度 */
+  }
+  
+  .username, .real-name {
+    max-width: 70px;
+  }
+  
+  .toolbar {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+  
+  .toolbar-left, .toolbar-right {
+    justify-content: center;
+  }
+  
+  /* 中等屏幕下操作按钮调整 */
+  .action-buttons {
+    gap: 4px; /* 减少间距 */
+  }
+  
+  .action-buttons .el-button {
+    font-size: 12px;
+    padding: 4px 8px;
+  }
+}
+
+@media (max-width: 1000px) {
+  .table-container {
+    min-width: 1000px;
+  }
+  
+  /* 小屏幕下操作按钮垂直排列 */
+  .action-buttons {
+    flex-direction: column;
+    gap: 2px;
+    flex-wrap: nowrap;
+  }
+  
+  .action-buttons .el-button {
+    font-size: 11px;
+    padding: 2px 6px;
+    min-width: 70px;
+  }
+}
+
+@media (max-width: 768px) {
+  .users-management {
+    padding: 10px;
+  }
+  
+  .table-container {
+    min-width: 900px;
+  }
+  
+  .toolbar {
+    padding: 12px;
+  }
+  
+  .toolbar-left {
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+  
+  .pagination {
+    margin-top: 15px;
+  }
+  
+  :deep(.el-pagination) {
+    justify-content: center;
+    flex-wrap: wrap;
+  }
+}
 </style>

+ 54 - 0
test_datetime_format.html

@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>DateTime Format Test</title>
+</head>
+<body>
+    <h1>DateTime Format Test</h1>
+    <div id="results"></div>
+
+    <script>
+        // 格式化日期时间函数
+        const formatDateTime = (dateTime) => {
+            if (!dateTime) return '-'
+            const date = new Date(dateTime)
+            
+            // 格式化为 YYYY-MM-DD HH:mm:ss
+            const year = date.getFullYear()
+            const month = String(date.getMonth() + 1).padStart(2, '0')
+            const day = String(date.getDate()).padStart(2, '0')
+            const hours = String(date.getHours()).padStart(2, '0')
+            const minutes = String(date.getMinutes()).padStart(2, '0')
+            const seconds = String(date.getSeconds()).padStart(2, '0')
+            
+            return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+        }
+
+        // 测试数据
+        const testDates = [
+            '2026-01-06T17:46:31.000Z',
+            '2026-01-26T10:30:15.123Z',
+            '2025-12-25T23:59:59.999Z',
+            null,
+            undefined,
+            ''
+        ]
+
+        const resultsDiv = document.getElementById('results')
+        
+        testDates.forEach((date, index) => {
+            const formatted = formatDateTime(date)
+            const p = document.createElement('p')
+            p.innerHTML = `<strong>Test ${index + 1}:</strong> Input: ${date} → Output: ${formatted}`
+            resultsDiv.appendChild(p)
+        })
+
+        // 期望的输出格式示例
+        const expectedP = document.createElement('p')
+        expectedP.innerHTML = '<strong>Expected format:</strong> 2026-01-06 17:46:31'
+        expectedP.style.color = 'green'
+        expectedP.style.fontWeight = 'bold'
+        resultsDiv.appendChild(expectedP)
+    </script>
+</body>
+</html>