NewLife/cube-front

style: 代码格式化

Co-authored-by: Copilot <copilot@github.com>
笑笑 authored at 2026-05-17 10:00:50
4e31384
Tree
1 Parent(s) ca71933
Summary: 5 changed files with 619 additions and 200 deletions.
Modified +280 -107
Modified +147 -61
Modified +46 -9
Modified +1 -1
Modified +145 -22
Modified +280 -107
diff --git a/apps/cube-admin/src/views/admin/user/index.vue b/apps/cube-admin/src/views/admin/user/index.vue
index 3ff4a3a..c620d9c 100644
--- a/apps/cube-admin/src/views/admin/user/index.vue
+++ b/apps/cube-admin/src/views/admin/user/index.vue
@@ -1,11 +1,17 @@
 <template>
   <div class="user-page">
-
     <!-- ─── 页头 ─── -->
     <div class="page-header">
       <div class="header-brand">
         <div class="brand-icon">
-          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+          <svg
+            width="20"
+            height="20"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="1.5"
+          >
             <path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
             <circle cx="9" cy="7" r="4" />
             <path d="M23 21v-2a4 4 0 00-3-3.87" />
@@ -23,7 +29,14 @@
           <span class="count-label">用户总数</span>
         </div>
         <button class="hdr-btn" @click="queryUser">
-          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <svg
+            width="13"
+            height="13"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
             <polyline points="23 4 23 10 17 10" />
             <polyline points="1 20 1 14 7 14" />
             <path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" />
@@ -31,7 +44,14 @@
           刷新
         </button>
         <button class="hdr-btn prime" @click="handleAdd">
-          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+          <svg
+            width="13"
+            height="13"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2.5"
+          >
             <line x1="12" y1="5" x2="12" y2="19" />
             <line x1="5" y1="12" x2="19" y2="12" />
           </svg>
@@ -43,7 +63,15 @@
     <!-- ─── 搜索栏 ─── -->
     <div class="search-bar">
       <div class="search-input-wrap">
-        <svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <svg
+          class="search-icon"
+          width="14"
+          height="14"
+          viewBox="0 0 24 24"
+          fill="none"
+          stroke="currentColor"
+          stroke-width="2"
+        >
           <circle cx="11" cy="11" r="8" />
           <line x1="21" y1="21" x2="16.65" y2="16.65" />
         </svg>
@@ -53,7 +81,16 @@
           placeholder="搜索用户名、显示名称、邮箱..."
           @keyup.enter="doSearch"
         />
-        <button v-if="searchQ" class="search-clear" @click="searchQ = ''; doSearch()">✕</button>
+        <button
+          v-if="searchQ"
+          class="search-clear"
+          @click="
+            searchQ = '';
+            doSearch();
+          "
+        >
+          ✕
+        </button>
       </div>
       <button class="search-btn primary" @click="doSearch">搜索</button>
       <button class="search-btn ghost" @click="doReset">重置</button>
@@ -61,7 +98,6 @@
 
     <!-- ─── 数据表格 ─── -->
     <div class="data-panel" v-loading="loading">
-
       <!-- 表头 -->
       <div class="row-head">
         <div class="col col-user">用户信息</div>
@@ -88,7 +124,9 @@
                 :src="getAvatarUrl(user.avatar)"
                 @error="avatarError[user.id] = true"
               />
-              <span v-else class="avatar-init">{{ (user.displayName || user.name || '?').charAt(0) }}</span>
+              <span v-else class="avatar-init">{{
+                (user.displayName || user.name || '?').charAt(0)
+              }}</span>
             </div>
             <div class="user-info">
               <span class="user-dname">{{ user.displayName || user.name }}</span>
@@ -105,14 +143,30 @@
           </div>
           <div class="col col-contact">
             <div v-if="user.mail" class="contact-line">
-              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-                <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
+              <svg
+                width="11"
+                height="11"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="currentColor"
+                stroke-width="2"
+              >
+                <path
+                  d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
+                />
                 <polyline points="22,6 12,13 2,6" />
               </svg>
               {{ user.mail }}
             </div>
             <div v-if="user.mobile" class="contact-line">
-              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <svg
+                width="11"
+                height="11"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="currentColor"
+                stroke-width="2"
+              >
                 <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
                 <line x1="12" y1="18" x2="12.01" y2="18" />
               </svg>
@@ -131,14 +185,28 @@
           </div>
           <div class="col col-ops">
             <button class="op-btn edit-btn" @click="handleEdit(user)">
-              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <svg
+                width="11"
+                height="11"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="currentColor"
+                stroke-width="2"
+              >
                 <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7" />
                 <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" />
               </svg>
               编辑
             </button>
             <button class="op-btn del-btn" @click="handleDelete(user)">
-              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <svg
+                width="11"
+                height="11"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="currentColor"
+                stroke-width="2"
+              >
                 <polyline points="3 6 5 6 21 6" />
                 <path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6" />
                 <path d="M10 11v6M14 11v6" />
@@ -151,7 +219,14 @@
 
       <!-- 空状态 -->
       <div v-if="!loading && tableData.length === 0" class="empty-state">
-        <svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
+        <svg
+          width="52"
+          height="52"
+          viewBox="0 0 24 24"
+          fill="none"
+          stroke="currentColor"
+          stroke-width="1"
+        >
           <path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
           <circle cx="9" cy="7" r="4" />
           <path d="M23 21v-2a4 4 0 00-3-3.87" />
@@ -163,7 +238,9 @@
 
     <!-- ─── 分页 ─── -->
     <div class="pager-bar">
-      <span class="pager-info">共 <b>{{ queryParams.total || 0 }}</b> 条记录</span>
+      <span class="pager-info"
+        >共 <b>{{ queryParams.total || 0 }}</b> 条记录</span
+      >
       <CubeListPager
         :total="queryParams.total"
         :current-page="queryParams.pageIndex"
@@ -201,11 +278,21 @@
         </el-form-item>
         <el-form-item label="角色" prop="roleID">
           <el-select v-model="userForm.roleID" placeholder="请选择角色" style="width: 100%">
-            <el-option v-for="role in roleOptions" :key="role.value" :label="role.label" :value="role.value" />
+            <el-option
+              v-for="role in roleOptions"
+              :key="role.value"
+              :label="role.label"
+              :value="role.value"
+            />
           </el-select>
         </el-form-item>
         <el-form-item label="密码" prop="password" v-if="formType === 'add'">
-          <el-input v-model="userForm.password" type="password" placeholder="请输入密码" show-password />
+          <el-input
+            v-model="userForm.password"
+            type="password"
+            placeholder="请输入密码"
+            show-password
+          />
         </el-form-item>
         <el-form-item label="状态" prop="enable">
           <el-switch v-model="userForm.enable" :active-value="true" :inactive-value="false" />
@@ -217,7 +304,13 @@
       <template #footer>
         <div class="dialog-footer">
           <div>
-            <el-button v-if="formType === 'edit1111'" type="warning" size="small" @click="handleChangePasswordInEdit">修改密码</el-button>
+            <el-button
+              v-if="formType === 'edit1111'"
+              type="warning"
+              size="small"
+              @click="handleChangePasswordInEdit"
+              >修改密码</el-button
+            >
           </div>
           <div>
             <el-button @click="dialogVisible = false">取消</el-button>
@@ -229,18 +322,38 @@
 
     <!-- ─── 修改密码弹窗 ─── -->
     <el-dialog v-model="changePasswordDialogVisible" title="修改密码" width="400px">
-      <el-form ref="changePasswordFormRef" :model="changePasswordForm" :rules="changePasswordFormRules" label-width="90px">
+      <el-form
+        ref="changePasswordFormRef"
+        :model="changePasswordForm"
+        :rules="changePasswordFormRules"
+        label-width="90px"
+      >
         <el-form-item label="用户名">
           <el-input v-model="changePasswordForm.name" disabled />
         </el-form-item>
         <el-form-item label="旧密码" prop="oldPassword">
-          <el-input v-model="changePasswordForm.oldPassword" type="password" placeholder="请输入旧密码" show-password />
+          <el-input
+            v-model="changePasswordForm.oldPassword"
+            type="password"
+            placeholder="请输入旧密码"
+            show-password
+          />
         </el-form-item>
         <el-form-item label="新密码" prop="newPassword">
-          <el-input v-model="changePasswordForm.newPassword" type="password" placeholder="请输入新密码" show-password />
+          <el-input
+            v-model="changePasswordForm.newPassword"
+            type="password"
+            placeholder="请输入新密码"
+            show-password
+          />
         </el-form-item>
         <el-form-item label="确认密码" prop="newPassword2">
-          <el-input v-model="changePasswordForm.newPassword2" type="password" placeholder="请再次输入新密码" show-password />
+          <el-input
+            v-model="changePasswordForm.newPassword2"
+            type="password"
+            placeholder="请再次输入新密码"
+            show-password
+          />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -256,7 +369,12 @@ import { ref, reactive, onMounted } from 'vue';
 import type { FormInstance, FormRules } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { request } from 'cube-front/core/utils/request';
-import { apiDataToList, apiDataToSingle, handleDeleteOperation, handleFormSubmit } from 'cube-front/core/utils/api-helpers';
+import {
+  apiDataToList,
+  apiDataToSingle,
+  handleDeleteOperation,
+  handleFormSubmit,
+} from 'cube-front/core/utils/api-helpers';
 import CubeListPager from 'cube-front/core/components/CubeListPager.vue';
 import type { SelectOption, BaseEntity, EnableStatus } from 'cube-front/core/types/common';
 import { pageInfoDefault } from 'cube-front/core/types/common';
@@ -398,7 +516,6 @@ const callback = (e?: Record<string, unknown>) => {
 const queryUser = async () => {
   loading.value = true;
   try {
-
     const c = await request.get('/Admin/User/', { params: queryParams });
     const { list, page } = apiDataToList<User>(c);
     tableData.value = list;
@@ -409,63 +526,43 @@ const queryUser = async () => {
   } finally {
     loading.value = false;
   }
-}
+};
 
 // 表单验证规则
 const userFormRules = reactive<FormRules>({
   name: [
     { required: true, message: '请输入用户名', trigger: 'blur' },
-    { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
-  ],
-  displayName: [
-    { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
+    { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' },
   ],
+  displayName: [{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }],
   password: [
     { required: formType.value === 'add', message: '请输入密码', trigger: 'blur' },
-    { min: 8, max: 200, message: '长度在 8 到 200 个字符', trigger: 'blur' }
-  ],
-  sex: [
-    { required: true, message: '请选择性别', trigger: 'change' }
+    { min: 8, max: 200, message: '长度在 8 到 200 个字符', trigger: 'blur' },
   ],
+  sex: [{ required: true, message: '请选择性别', trigger: 'change' }],
   mail: [
     { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' },
-    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }
+    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' },
   ],
   mobile: [
     { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' },
-    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }
-  ],
-  code: [
-    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }
-  ],
-  avatar: [
-    { max: 200, message: '长度不能超过 200 个字符', trigger: 'blur' }
-  ],
-  roleIds: [
-    { max: 200, message: '长度不能超过 200 个字符', trigger: 'blur' }
-  ],
-  lastLoginIP: [
-    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }
-  ],
-  registerIP: [
-    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }
-  ],
-  ex4: [
-    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }
+    { max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' },
   ],
-  remark: [
-    { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' }
-  ]
+  code: [{ max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }],
+  avatar: [{ max: 200, message: '长度不能超过 200 个字符', trigger: 'blur' }],
+  roleIds: [{ max: 200, message: '长度不能超过 200 个字符', trigger: 'blur' }],
+  lastLoginIP: [{ max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }],
+  registerIP: [{ max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }],
+  ex4: [{ max: 50, message: '长度不能超过 50 个字符', trigger: 'blur' }],
+  remark: [{ max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' }],
 });
 
 // 修改密码表单验证规则
 const changePasswordFormRules = reactive<FormRules>({
-  oldPassword: [
-    { required: true, message: '请输入旧密码', trigger: 'blur' }
-  ],
+  oldPassword: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
   newPassword: [
     { required: true, message: '请输入新密码', trigger: 'blur' },
-    { min: 8, max: 200, message: '长度在 8 到 200 个字符', trigger: 'blur' }
+    { min: 8, max: 200, message: '长度在 8 到 200 个字符', trigger: 'blur' },
   ],
   newPassword2: [
     { required: true, message: '请再次输入新密码', trigger: 'blur' },
@@ -477,9 +574,9 @@ const changePasswordFormRules = reactive<FormRules>({
           callback();
         }
       },
-      trigger: 'blur'
-    }
-  ]
+      trigger: 'blur',
+    },
+  ],
 });
 
 // 获取头像完整URL
@@ -487,17 +584,22 @@ const getAvatarUrl = (avatar: string): string => {
   if (!avatar) return '';
   // 如果头像路径以"/"开头,拼接当前域名
   // if (avatar.startsWith('/')) { return `${window.location.origin}${avatar}`; }
-  return avatar;// 如果是完整的URL(http或https开头),直接返回
+  return avatar; // 如果是完整的URL(http或https开头),直接返回
 };
 
 // 加载角色数据
 const loadRoleOptions = async (forceRefresh = false) => {
   // 如果不是强制刷新且已经加载过角色数据,直接返回
-  if (!forceRefresh && roleOptionsLoaded.value) { return; }
+  if (!forceRefresh && roleOptionsLoaded.value) {
+    return;
+  }
   try {
     const data = await request.get('/Admin/Role');
     const { list } = apiDataToList<{ id: number; name: string }>(data);
-    roleOptions.value = list.map((role: { id: number; name: string }) => ({ value: role.id, label: role.name }));
+    roleOptions.value = list.map((role: { id: number; name: string }) => ({
+      value: role.id,
+      label: role.name,
+    }));
     roleOptionsLoaded.value = true; // 标记为已加载
   } catch (error) {
     console.error('加载角色数据失败:', error);
@@ -585,27 +687,29 @@ const handleClearPasswordInEdit = () => {
       confirmButtonText: '确定',
       cancelButtonText: '取消',
       type: 'warning',
-    }
-  ).then(async () => {
-    try {
-      await request.post('/Admin/User/ClearPassword', null, { params: { id: userForm.id } });
-      ElMessage.success('密码清空成功');
-      dialogVisible.value = false; // 关闭用户编辑对话框
-    } catch (error) {
-      console.error('清空密码失败:', error);
-      ElMessage.error('清空密码失败');
-    }
-  }).catch(() => {
-    // 用户取消操作
-  });
+    },
+  )
+    .then(async () => {
+      try {
+        await request.post('/Admin/User/ClearPassword', null, { params: { id: userForm.id } });
+        ElMessage.success('密码清空成功');
+        dialogVisible.value = false; // 关闭用户编辑对话框
+      } catch (error) {
+        console.error('清空密码失败:', error);
+        ElMessage.error('清空密码失败');
+      }
+    })
+    .catch(() => {
+      // 用户取消操作
+    });
 };
 
 // 删除用户
 const handleDelete = (row: User) => {
   handleDeleteOperation(
     () => request.delete('/Admin/User', { params: { id: row.id } }),
-    queryUser,   //() => null,
-    '确认删除[' + (row.displayName || row.name) + ']用户吗?'
+    queryUser, //() => null,
+    '确认删除[' + (row.displayName || row.name) + ']用户吗?',
   );
 };
 
@@ -653,7 +757,7 @@ const submitChangePassword = async () => {
   };
 
   const onSuccess = () => {
-    changePasswordDialogVisible.value = false;// 关闭修改密码对话框
+    changePasswordDialogVisible.value = false; // 关闭修改密码对话框
     ElMessage.success('密码修改成功');
     // 重置表单
     changePasswordForm.oldPassword = '';
@@ -698,7 +802,9 @@ onMounted(() => {
   &::before {
     content: '';
     position: absolute;
-    top: 0; left: 0; right: 0;
+    top: 0;
+    left: 0;
+    right: 0;
     height: 1px;
     background: linear-gradient(90deg, transparent 0%, var(--accent) 50%, transparent 100%);
     opacity: 0.5;
@@ -820,7 +926,9 @@ onMounted(() => {
   background: rgba(255, 255, 255, 0.03);
   border: 1px solid var(--border-subtle);
   border-radius: var(--radius-sm);
-  transition: border-color 0.15s, background 0.15s;
+  transition:
+    border-color 0.15s,
+    background 0.15s;
 
   &:focus-within {
     border-color: rgba(74, 222, 128, 0.5);
@@ -828,7 +936,10 @@ onMounted(() => {
   }
 }
 
-.search-icon { color: var(--text-muted); flex-shrink: 0; }
+.search-icon {
+  color: var(--text-muted);
+  flex-shrink: 0;
+}
 
 .search-input {
   flex: 1;
@@ -839,7 +950,9 @@ onMounted(() => {
   font-size: 13px;
   color: var(--text-primary);
 
-  &::placeholder { color: var(--text-muted); }
+  &::placeholder {
+    color: var(--text-muted);
+  }
 }
 
 .search-clear {
@@ -852,7 +965,9 @@ onMounted(() => {
   padding: 3px 5px;
   border-radius: 4px;
 
-  &:hover { color: var(--text-primary); }
+  &:hover {
+    color: var(--text-primary);
+  }
 }
 
 .search-btn {
@@ -913,7 +1028,9 @@ onMounted(() => {
   }
 }
 
-.rows-body { display: contents; }
+.rows-body {
+  display: contents;
+}
 
 .data-row {
   display: grid;
@@ -923,19 +1040,30 @@ onMounted(() => {
   transition: background 0.12s;
   align-items: center;
 
-  &:last-child { border-bottom: none; }
+  &:last-child {
+    border-bottom: none;
+  }
 
   &:hover {
     background: rgba(74, 222, 128, 0.03);
 
-    .op-btn { opacity: 1; }
+    .op-btn {
+      opacity: 1;
+    }
   }
 
-  &.is-off { opacity: 0.5; }
+  &.is-off {
+    opacity: 0.5;
+  }
 }
 
-.col { display: flex; align-items: center; }
-.col-user { gap: 12px; }
+.col {
+  display: flex;
+  align-items: center;
+}
+.col-user {
+  gap: 12px;
+}
 
 /* ─── 头像 ─── */
 .avatar {
@@ -948,10 +1076,19 @@ onMounted(() => {
   justify-content: center;
   overflow: hidden;
 
-  img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    display: block;
+  }
 
-  &.avatar--male { background: linear-gradient(135deg, #0ea5e9 0%, #38bdf8 100%); }
-  &.avatar--female { background: linear-gradient(135deg, #f43f5e 0%, #fb7185 100%); }
+  &.avatar--male {
+    background: linear-gradient(135deg, #0ea5e9 0%, #38bdf8 100%);
+  }
+  &.avatar--female {
+    background: linear-gradient(135deg, #f43f5e 0%, #fb7185 100%);
+  }
 }
 
 .avatar-init {
@@ -1012,7 +1149,10 @@ onMounted(() => {
   max-width: 100%;
 }
 
-.nil { color: var(--text-muted); font-size: 14px; }
+.nil {
+  color: var(--text-muted);
+  font-size: 14px;
+}
 
 /* ─── 联系方式 ─── */
 .col-contact {
@@ -1032,7 +1172,10 @@ onMounted(() => {
   text-overflow: ellipsis;
   white-space: nowrap;
 
-  svg { flex-shrink: 0; color: var(--text-muted); }
+  svg {
+    flex-shrink: 0;
+    color: var(--text-muted);
+  }
 }
 
 /* ─── 状态 ─── */
@@ -1055,7 +1198,9 @@ onMounted(() => {
 
   &.status-off {
     color: var(--text-muted);
-    .status-dot { background: var(--text-muted); }
+    .status-dot {
+      background: var(--text-muted);
+    }
   }
 }
 
@@ -1068,14 +1213,27 @@ onMounted(() => {
 }
 
 @keyframes dot-pulse {
-  0%, 100% { opacity: 1; transform: scale(1); }
-  50% { opacity: 0.6; transform: scale(1.3); }
+  0%,
+  100% {
+    opacity: 1;
+    transform: scale(1);
+  }
+  50% {
+    opacity: 0.6;
+    transform: scale(1.3);
+  }
 }
 
-.time-val { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
+.time-val {
+  font-size: 12px;
+  color: var(--text-muted);
+  white-space: nowrap;
+}
 
 /* ─── 操作按钮 ─── */
-.col-ops { gap: 6px; }
+.col-ops {
+  gap: 6px;
+}
 
 .op-btn {
   display: inline-flex;
@@ -1124,7 +1282,10 @@ onMounted(() => {
   padding: 64px 20px;
   color: var(--text-muted);
 
-  p { font-size: 14px; margin: 0; }
+  p {
+    font-size: 14px;
+    margin: 0;
+  }
 }
 
 /* ─── 分页条 ─── */
@@ -1142,12 +1303,24 @@ onMounted(() => {
   font-size: 13px;
   color: var(--text-muted);
 
-  b { color: var(--text-primary); font-weight: 600; }
+  b {
+    color: var(--text-primary);
+    font-weight: 600;
+  }
 }
 
 /* ─── 行过渡动画 ─── */
-.rows-enter-active, .rows-leave-active { transition: opacity 0.18s ease, transform 0.18s ease; }
-.rows-enter-from, .rows-leave-to { opacity: 0; transform: translateY(-6px); }
+.rows-enter-active,
+.rows-leave-active {
+  transition:
+    opacity 0.18s ease,
+    transform 0.18s ease;
+}
+.rows-enter-from,
+.rows-leave-to {
+  opacity: 0;
+  transform: translateY(-6px);
+}
 
 /* ─── 弹窗底部 ─── */
 .dialog-footer {
Modified +147 -61
diff --git a/core/components/MenuItem.vue b/core/components/MenuItem.vue
index fe07449..f502060 100644
--- a/core/components/MenuItem.vue
+++ b/core/components/MenuItem.vue
@@ -86,68 +86,152 @@ const handleClick = () => {
     <template v-else>
       <!-- 菜单行 -->
       <div
-        :class="['menu-row', { active: isActive, 'ancestor-active': isAncestorOfActive && isExpanded }]"
+        :class="[
+          'menu-row',
+          { active: isActive, 'ancestor-active': isAncestorOfActive && isExpanded },
+        ]"
         :style="{ paddingLeft: `${depth * 16 + 16}px` }"
         @click="handleClick"
       >
-      <!-- 展开箭头 -->
-      <span v-if="hasChildrenMenu" class="menu-arrow" :class="{ expanded: isExpanded }">
-        <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
-          <path d="M9 18l6-6-6-6" />
-        </svg>
-      </span>
-      <span v-else class="menu-arrow-placeholder" />
-
-      <!-- 图标 -->
-      <span v-if="menu.icon" class="menu-icon">
-        <svg v-if="menu.icon === 'dashboard'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <rect x="3" y="3" width="7" height="7" rx="1" />
-          <rect x="14" y="3" width="7" height="7" rx="1" />
-          <rect x="14" y="14" width="7" height="7" rx="1" />
-          <rect x="3" y="14" width="7" height="7" rx="1" />
-        </svg>
-        <svg v-else-if="menu.icon === 'device'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <rect x="4" y="4" width="16" height="16" rx="2" />
-          <circle cx="12" cy="12" r="3" />
-          <path d="M12 2v2M12 20v2M2 12h2M20 12h2" />
-        </svg>
-        <svg v-else-if="menu.icon === 'layers'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <path d="M12 2L2 7l10 5 10-5-10-5z" />
-          <path d="M2 17l10 5 10-5" />
-          <path d="M2 12l10 5 10-5" />
-        </svg>
-        <svg v-else-if="menu.icon === 'lock'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <rect x="3" y="11" width="18" height="11" rx="2" />
-          <path d="M7 11V7a5 5 0 0110 0v4" />
-        </svg>
-        <svg v-else-if="menu.icon === 'bell'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9" />
-          <path d="M13.73 21a2 2 0 01-3.46 0" />
-        </svg>
-        <svg v-else-if="menu.icon === 'activity'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <polyline points="22,12 18,12 15,21 9,3 6,12 2,12" />
-        </svg>
-        <svg v-else-if="menu.icon === 'settings'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <circle cx="12" cy="12" r="3" />
-          <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
-        </svg>
-        <svg v-else-if="menu.icon === 'users'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
-          <circle cx="9" cy="7" r="4" />
-          <path d="M23 21v-2a4 4 0 00-3-3.87" />
-          <path d="M16 3.13a4 4 0 010 7.75" />
-        </svg>
-        <svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <circle cx="12" cy="12" r="10" />
-        </svg>
-      </span>
-
-      <!-- 标题 -->
-      <span class="menu-title">{{ renderMenuTitle(menu) }}</span>
-
-      <!-- 子菜单计数 -->
-      <span v-if="hasChildrenMenu" class="menu-badge">{{ menu.children?.length }}</span>
-    </div>
+        <!-- 展开箭头 -->
+        <span v-if="hasChildrenMenu" class="menu-arrow" :class="{ expanded: isExpanded }">
+          <svg
+            width="10"
+            height="10"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2.5"
+          >
+            <path d="M9 18l6-6-6-6" />
+          </svg>
+        </span>
+        <span v-else class="menu-arrow-placeholder" />
+
+        <!-- 图标 -->
+        <span v-if="menu.icon" class="menu-icon">
+          <svg
+            v-if="menu.icon === 'dashboard'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <rect x="3" y="3" width="7" height="7" rx="1" />
+            <rect x="14" y="3" width="7" height="7" rx="1" />
+            <rect x="14" y="14" width="7" height="7" rx="1" />
+            <rect x="3" y="14" width="7" height="7" rx="1" />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'device'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <rect x="4" y="4" width="16" height="16" rx="2" />
+            <circle cx="12" cy="12" r="3" />
+            <path d="M12 2v2M12 20v2M2 12h2M20 12h2" />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'layers'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <path d="M12 2L2 7l10 5 10-5-10-5z" />
+            <path d="M2 17l10 5 10-5" />
+            <path d="M2 12l10 5 10-5" />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'lock'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <rect x="3" y="11" width="18" height="11" rx="2" />
+            <path d="M7 11V7a5 5 0 0110 0v4" />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'bell'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9" />
+            <path d="M13.73 21a2 2 0 01-3.46 0" />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'activity'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <polyline points="22,12 18,12 15,21 9,3 6,12 2,12" />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'settings'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <circle cx="12" cy="12" r="3" />
+            <path
+              d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
+            />
+          </svg>
+          <svg
+            v-else-if="menu.icon === 'users'"
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
+            <circle cx="9" cy="7" r="4" />
+            <path d="M23 21v-2a4 4 0 00-3-3.87" />
+            <path d="M16 3.13a4 4 0 010 7.75" />
+          </svg>
+          <svg
+            v-else
+            width="16"
+            height="16"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <circle cx="12" cy="12" r="10" />
+          </svg>
+        </span>
+
+        <!-- 标题 -->
+        <span class="menu-title">{{ renderMenuTitle(menu) }}</span>
+
+        <!-- 子菜单计数 -->
+        <span v-if="hasChildrenMenu" class="menu-badge">{{ menu.children?.length }}</span>
+      </div>
 
       <!-- 子菜单 -->
       <div v-if="hasChildrenMenu" class="menu-children" :class="{ expanded: isExpanded }">
@@ -206,7 +290,9 @@ const handleClick = () => {
   font-weight: 700;
   color: var(--text-secondary);
   background: rgba(255, 255, 255, 0.06);
-  transition: background 0.15s, color 0.15s;
+  transition:
+    background 0.15s,
+    color 0.15s;
 }
 
 .menu-row {
@@ -313,4 +399,4 @@ const handleClick = () => {
 .menu-children-inner {
   overflow: hidden;
 }
-</style>
\ No newline at end of file
+</style>
Modified +46 -9
diff --git a/core/components/SearchBar.vue b/core/components/SearchBar.vue
index 67d5187..b2065d0 100644
--- a/core/components/SearchBar.vue
+++ b/core/components/SearchBar.vue
@@ -58,9 +58,7 @@ function highlight(text: string, kw: string): string {
   const i = text.toLowerCase().indexOf(kw.toLowerCase());
   if (i === -1) return text;
   return (
-    text.slice(0, i) +
-    `<mark>${text.slice(i, i + kw.length)}</mark>` +
-    text.slice(i + kw.length)
+    text.slice(0, i) + `<mark>${text.slice(i, i + kw.length)}</mark>` + text.slice(i + kw.length)
   );
 }
 
@@ -97,7 +95,14 @@ onUnmounted(() => document.removeEventListener('mousedown', onClickOutside, true
 <template>
   <!-- 图标模式 -->
   <ActionButton v-if="mode === 'icon'" title="全局搜索">
-    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+    <svg
+      width="15"
+      height="15"
+      viewBox="0 0 24 24"
+      fill="none"
+      stroke="currentColor"
+      stroke-width="2"
+    >
       <circle cx="11" cy="11" r="8" />
       <path d="M21 21l-4.35-4.35" />
     </svg>
@@ -107,7 +112,15 @@ onUnmounted(() => document.removeEventListener('mousedown', onClickOutside, true
   <div v-else ref="containerRef" class="sb-wrap">
     <!-- 输入框 -->
     <div class="sb-box" :class="{ focused: showResults }">
-      <svg class="sb-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+      <svg
+        class="sb-icon"
+        width="14"
+        height="14"
+        viewBox="0 0 24 24"
+        fill="none"
+        stroke="currentColor"
+        stroke-width="2"
+      >
         <circle cx="11" cy="11" r="8" />
         <path d="M21 21l-4.35-4.35" />
       </svg>
@@ -120,7 +133,14 @@ onUnmounted(() => document.removeEventListener('mousedown', onClickOutside, true
         @focus="showResults = searchText.trim().length > 0"
       />
       <button v-if="searchText" class="sb-clear" @mousedown.prevent="clearSearch">
-        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+        <svg
+          width="11"
+          height="11"
+          viewBox="0 0 24 24"
+          fill="none"
+          stroke="currentColor"
+          stroke-width="2.5"
+        >
           <path d="M18 6L6 18M6 6l12 12" />
         </svg>
       </button>
@@ -131,7 +151,14 @@ onUnmounted(() => document.removeEventListener('mousedown', onClickOutside, true
       <div v-if="showResults" class="sb-results">
         <!-- 空状态 -->
         <div v-if="searchResults.length === 0" class="sb-empty">
-          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+          <svg
+            width="22"
+            height="22"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="1.5"
+          >
             <circle cx="11" cy="11" r="8" />
             <path d="M21 21l-4.35-4.35" />
           </svg>
@@ -150,7 +177,15 @@ onUnmounted(() => document.removeEventListener('mousedown', onClickOutside, true
             <span class="sb-item-name" v-html="highlight(getDisplayName(menu), searchText)" />
             <span class="sb-item-path">{{ getBreadcrumb(menu) }}</span>
           </span>
-          <svg class="sb-item-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <svg
+            class="sb-item-arrow"
+            width="12"
+            height="12"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
             <path d="M9 18l6-6-6-6" />
           </svg>
         </button>
@@ -218,7 +253,9 @@ onUnmounted(() => document.removeEventListener('mousedown', onClickOutside, true
   align-items: center;
   flex-shrink: 0;
   border-radius: 4px;
-  transition: color 0.15s, background 0.15s;
+  transition:
+    color 0.15s,
+    background 0.15s;
 
   &:hover {
     color: var(--text-primary);
Modified +1 -1
diff --git a/core/global.css b/core/global.css
index 59b10f9..1899a1f 100644
--- a/core/global.css
+++ b/core/global.css
@@ -680,4 +680,4 @@ body {
   --color-border: var(--border-default);
   --color-border-light: var(--border-subtle);
   --color-border-hover: var(--border-emphasis);
-}
\ No newline at end of file
+}
Modified +145 -22
diff --git a/core/layouts/CyberLayout/Sidebar/index.vue b/core/layouts/CyberLayout/Sidebar/index.vue
index 2b214c8..9a59674 100644
--- a/core/layouts/CyberLayout/Sidebar/index.vue
+++ b/core/layouts/CyberLayout/Sidebar/index.vue
@@ -49,7 +49,9 @@ const menuGroups = computed((): MenuGroup[] => {
 
 // 侧边栏整体折叠(220px ↔ 64px)
 const collapsed = ref(false);
-const toggleCollapse = () => { collapsed.value = !collapsed.value; };
+const toggleCollapse = () => {
+  collapsed.value = !collapsed.value;
+};
 
 // 各分组独立折叠(默认全部折叠)
 const groupCollapsed = ref<Record<string, boolean>>({});
@@ -76,12 +78,18 @@ const flyoutL1 = ref<TreeMenuItem | null>(null); // 当前展开 L2 的 L1 项
 let _ftTimer: ReturnType<typeof setTimeout> | null = null;
 
 function startFlyout(group: MenuGroup) {
-  if (_ftTimer) { clearTimeout(_ftTimer); _ftTimer = null; }
+  if (_ftTimer) {
+    clearTimeout(_ftTimer);
+    _ftTimer = null;
+  }
   if (flyoutGroup.value?.title !== group.title) flyoutL1.value = null;
   flyoutGroup.value = group;
 }
 function keepFlyout() {
-  if (_ftTimer) { clearTimeout(_ftTimer); _ftTimer = null; }
+  if (_ftTimer) {
+    clearTimeout(_ftTimer);
+    _ftTimer = null;
+  }
 }
 function endFlyout() {
   _ftTimer = setTimeout(() => {
@@ -155,7 +163,6 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
 
     <!-- 导航菜单 -->
     <nav class="sidebar-nav">
-
       <!-- ── 折叠模式:每组一个图标按钮 + 飞出面板 ── -->
       <template v-if="collapsed">
         <div
@@ -169,13 +176,97 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
           <!-- 图标按钮 -->
           <div class="ci-btn" :title="group.title">
             <!-- 已知图标 -->
-            <svg v-if="group.source.icon === 'dashboard'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/></svg>
-            <svg v-else-if="group.source.icon === 'device'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/><circle cx="12" cy="12" r="3"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/></svg>
-            <svg v-else-if="group.source.icon === 'layers'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
-            <svg v-else-if="group.source.icon === 'settings'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
-            <svg v-else-if="group.source.icon === 'users'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
-            <svg v-else-if="group.source.icon === 'bell'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
-            <svg v-else-if="group.source.icon === 'activity'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/></svg>
+            <svg
+              v-if="group.source.icon === 'dashboard'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <rect x="3" y="3" width="7" height="7" rx="1" />
+              <rect x="14" y="3" width="7" height="7" rx="1" />
+              <rect x="14" y="14" width="7" height="7" rx="1" />
+              <rect x="3" y="14" width="7" height="7" rx="1" />
+            </svg>
+            <svg
+              v-else-if="group.source.icon === 'device'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <rect x="4" y="4" width="16" height="16" rx="2" />
+              <circle cx="12" cy="12" r="3" />
+              <path d="M12 2v2M12 20v2M2 12h2M20 12h2" />
+            </svg>
+            <svg
+              v-else-if="group.source.icon === 'layers'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <path d="M12 2L2 7l10 5 10-5-10-5z" />
+              <path d="M2 17l10 5 10-5" />
+              <path d="M2 12l10 5 10-5" />
+            </svg>
+            <svg
+              v-else-if="group.source.icon === 'settings'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <circle cx="12" cy="12" r="3" />
+              <path
+                d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
+              />
+            </svg>
+            <svg
+              v-else-if="group.source.icon === 'users'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
+              <circle cx="9" cy="7" r="4" />
+              <path d="M23 21v-2a4 4 0 00-3-3.87" />
+              <path d="M16 3.13a4 4 0 010 7.75" />
+            </svg>
+            <svg
+              v-else-if="group.source.icon === 'bell'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9" />
+              <path d="M13.73 21a2 2 0 01-3.46 0" />
+            </svg>
+            <svg
+              v-else-if="group.source.icon === 'activity'"
+              width="16"
+              height="16"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+            >
+              <polyline points="22,12 18,12 15,21 9,3 6,12 2,12" />
+            </svg>
             <!-- 无图标:首字符 -->
             <span v-else class="ci-char">{{ group.title.charAt(0) }}</span>
           </div>
@@ -200,7 +291,16 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
                 @click="handleFlyoutL1Click(item)"
               >
                 <span class="fi-text">{{ renderMenuTitle(item) }}</span>
-                <svg v-if="item.children?.length" class="fi-arrow" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
+                <svg
+                  v-if="item.children?.length"
+                  class="fi-arrow"
+                  width="10"
+                  height="10"
+                  viewBox="0 0 24 24"
+                  fill="none"
+                  stroke="currentColor"
+                  stroke-width="2.5"
+                >
                   <path d="M9 18l6-6-6-6" />
                 </svg>
               </div>
@@ -231,8 +331,12 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
               <svg
                 class="nav-label-arrow"
                 :class="{ closed: groupCollapsed[group.title] }"
-                width="10" height="10" viewBox="0 0 24 24"
-                fill="none" stroke="currentColor" stroke-width="2.5"
+                width="10"
+                height="10"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="currentColor"
+                stroke-width="2.5"
               >
                 <path d="M6 9l6 6 6-6" />
               </svg>
@@ -248,7 +352,6 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
           </div>
         </template>
       </template>
-
     </nav>
 
     <!-- 底部用户区域 -->
@@ -260,7 +363,15 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
         :title="collapsed ? '展开侧边栏' : '折叠侧边栏'"
         @click="toggleCollapse"
       >
-        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
+        <svg
+          width="14"
+          height="14"
+          viewBox="0 0 24 24"
+          fill="none"
+          stroke="currentColor"
+          stroke-width="2"
+          stroke-linecap="round"
+        >
           <path d="M15 18l-6-6 6-6" />
         </svg>
         <span v-if="!collapsed" class="collapse-label">折叠侧边栏</span>
@@ -395,8 +506,12 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
   }
   scrollbar-color: var(--border-subtle) transparent;
 
-  &::-webkit-scrollbar { width: 4px; }
-  &::-webkit-scrollbar-track { background: transparent; }
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
   &::-webkit-scrollbar-thumb {
     background: var(--border-subtle);
     border-radius: 4px;
@@ -469,7 +584,9 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
   border-radius: var(--radius-sm);
   cursor: pointer;
   color: var(--text-secondary);
-  transition: background 0.15s, color 0.15s;
+  transition:
+    background 0.15s,
+    color 0.15s;
 
   &:hover {
     background: var(--sidebar-item-hover);
@@ -501,8 +618,12 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
   scrollbar-width: thin;
   scrollbar-color: var(--border-subtle) transparent;
 
-  &::-webkit-scrollbar { width: 4px; }
-  &::-webkit-scrollbar-track { background: transparent; }
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
   &::-webkit-scrollbar-thumb {
     background: var(--border-subtle);
     border-radius: 4px;
@@ -529,7 +650,9 @@ function isAncestorActive(menu: TreeMenuItem): boolean {
   color: var(--text-secondary);
   font-size: 13px;
   font-weight: 500;
-  transition: background 0.12s, color 0.12s;
+  transition:
+    background 0.12s,
+    color 0.12s;
   white-space: nowrap;
 
   &:hover {