style: 代码格式化 Co-authored-by: Copilot <copilot@github.com>笑笑 authored at 2026-05-17 10:00:50
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 {
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>
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);
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
+}
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 {