feat(device): add device management feature
- Created device list page with loading states - Implemented device service with API integration - Added device response and request types - Updated sidebar navigation with device management section - Added laptop-settings icon - Modified user table component to accept props - Updated user service to return array of user profiles - Changed app language to Chinese (zh-CN)
This commit is contained in:
15
.idea/dataSources.xml
generated
Normal file
15
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="chaos@10.91.3.253" uuid="1f27a48f-618d-4971-b39e-c88644e7d55d">
|
||||||
|
<driver-ref>mariadb</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mariadb://10.91.3.253:3306/chaos</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="database.introspection.mysql.dbe5060" value="true" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|||||||
46
src/lib/api/services/deviceService.ts
Normal file
46
src/lib/api/services/deviceService.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { api } from '$lib/api/httpClient.ts';
|
||||||
|
import type { PageResult } from '$lib/types/dataTable.ts';
|
||||||
|
import type { CreateDeviceRequest, DeviceResponse } from '$lib/types/api.ts';
|
||||||
|
|
||||||
|
|
||||||
|
export const deviceService = {
|
||||||
|
getAllDevices: async ({ page, size,type,keyword,token}:{
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
type?: number,
|
||||||
|
keyword?: string,
|
||||||
|
token:string
|
||||||
|
}) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('pageNum', page.toString());
|
||||||
|
formData.append('pageSize', size.toString());
|
||||||
|
if ( type){
|
||||||
|
formData.append('type', type.toString());
|
||||||
|
}
|
||||||
|
if ( keyword){
|
||||||
|
formData.append('keyword', keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await api.get<PageResult<DeviceResponse[]>>('/devices',{
|
||||||
|
body: formData,
|
||||||
|
headers:{Authorization: `${token}`}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.code != 200 || !result.data){
|
||||||
|
throw new Error(result.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
},
|
||||||
|
createDevice: async (device: CreateDeviceRequest,token:string) => {
|
||||||
|
|
||||||
|
const result = await api.post<DeviceResponse>('/devices',{
|
||||||
|
body: JSON.stringify(device),
|
||||||
|
headers:{Authorization: `${token}`}
|
||||||
|
});
|
||||||
|
if (result.code != 200 || !result.data){
|
||||||
|
throw new Error(result.msg);
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ export const userService = {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('pageNum', page.toString());
|
formData.append('pageNum', page.toString());
|
||||||
formData.append('pageSize', size.toString());
|
formData.append('pageSize', size.toString());
|
||||||
const response = await api.get<PageResult<UserProfile>>(
|
const response = await api.get<PageResult<UserProfile[]>>(
|
||||||
'/user/all',
|
'/user/all',
|
||||||
{
|
{
|
||||||
body: formData,
|
body: formData,
|
||||||
|
|||||||
@@ -127,4 +127,7 @@
|
|||||||
<symbol id="auth" viewBox="0 0 16 16"><path fill="currentColor" d="M9.07 6.746a2 2 0 0 0 2.91.001l.324-.344c.297.14.577.316.835.519l-.126.422a2 2 0 0 0 1.456 2.518l.348.083a4.7 4.7 0 0 1 .011 1.017l-.46.117a2 2 0 0 0-1.431 2.479l.156.556q-.383.294-.822.497l-.337-.357a2 2 0 0 0-2.91-.002l-.325.345a4.3 4.3 0 0 1-.835-.519l.126-.423a2 2 0 0 0-1.456-2.518l-.35-.083a4.7 4.7 0 0 1-.01-1.016l.462-.118a2 2 0 0 0 1.43-2.478l-.156-.557q.383-.294.822-.497zm-1.423-4.6a.5.5 0 0 1 .707 0C9.594 3.39 10.97 4 12.5 4a.5.5 0 0 1 .5.5v1.1a5.5 5.5 0 0 0-7.494 7.204C3.846 11.59 3 9.812 3 7.502V4.5a.5.5 0 0 1 .5-.5c1.53 0 2.904-.61 4.147-1.854M10.501 9.5a1 1 0 1 0 0 2a1 1 0 0 0 0-2"/></symbol>
|
<symbol id="auth" viewBox="0 0 16 16"><path fill="currentColor" d="M9.07 6.746a2 2 0 0 0 2.91.001l.324-.344c.297.14.577.316.835.519l-.126.422a2 2 0 0 0 1.456 2.518l.348.083a4.7 4.7 0 0 1 .011 1.017l-.46.117a2 2 0 0 0-1.431 2.479l.156.556q-.383.294-.822.497l-.337-.357a2 2 0 0 0-2.91-.002l-.325.345a4.3 4.3 0 0 1-.835-.519l.126-.423a2 2 0 0 0-1.456-2.518l-.35-.083a4.7 4.7 0 0 1-.01-1.016l.462-.118a2 2 0 0 0 1.43-2.478l-.156-.557q.383-.294.822-.497zm-1.423-4.6a.5.5 0 0 1 .707 0C9.594 3.39 10.97 4 12.5 4a.5.5 0 0 1 .5.5v1.1a5.5 5.5 0 0 0-7.494 7.204C3.846 11.59 3 9.812 3 7.502V4.5a.5.5 0 0 1 .5-.5c1.53 0 2.904-.61 4.147-1.854M10.501 9.5a1 1 0 1 0 0 2a1 1 0 0 0 0-2"/></symbol>
|
||||||
|
|
||||||
<symbol id="chevron-up-down" viewBox="0 0 16 16"><path fill="currentColor" d="M4.22 6.53a.75.75 0 0 0 1.06 0L8 3.81l2.72 2.72a.75.75 0 1 0 1.06-1.06L8.53 2.22a.75.75 0 0 0-1.06 0L4.22 5.47a.75.75 0 0 0 0 1.06m0 2.94a.75.75 0 0 1 1.06 0L8 12.19l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0l-3.25-3.25a.75.75 0 0 1 0-1.06"/></symbol>
|
<symbol id="chevron-up-down" viewBox="0 0 16 16"><path fill="currentColor" d="M4.22 6.53a.75.75 0 0 0 1.06 0L8 3.81l2.72 2.72a.75.75 0 1 0 1.06-1.06L8.53 2.22a.75.75 0 0 0-1.06 0L4.22 5.47a.75.75 0 0 0 0 1.06m0 2.94a.75.75 0 0 1 1.06 0L8 12.19l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0l-3.25-3.25a.75.75 0 0 1 0-1.06"/></symbol>
|
||||||
|
|
||||||
|
|
||||||
|
<symbol id="laptop-settings" viewBox="0 0 20 20"><path fill="currentColor" d="M4.5 5A1.5 1.5 0 0 0 3 6.5v6A1.5 1.5 0 0 0 4.5 14h4.522A5.5 5.5 0 0 1 17 9.6V6.5A1.5 1.5 0 0 0 15.5 5zm-2 10h6.522q.047.516.185 1H2.5a.5.5 0 0 1 0-1m9.565-3.558a2 2 0 0 1-1.43 2.478l-.462.118a4.7 4.7 0 0 0 .01 1.016l.35.083a2 2 0 0 1 1.456 2.519l-.127.423q.388.306.835.517l.325-.344a2 2 0 0 1 2.91.002l.337.358q.44-.203.822-.498l-.156-.556a2 2 0 0 1 1.43-2.478l.46-.118a4.7 4.7 0 0 0-.01-1.017l-.348-.082a2 2 0 0 1-1.456-2.52l.126-.421a4.3 4.3 0 0 0-.835-.519l-.325.344a2 2 0 0 1-2.91-.001l-.337-.358a4.3 4.3 0 0 0-.821.497zm2.434 4.058a1 1 0 1 1 0-2a1 1 0 0 1 0 2"/></symbol>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
@@ -11,135 +10,88 @@
|
|||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
// const sidebarState = getContext<SidebarState>(SIDEBAR_KEY);
|
// const sidebarState = getContext<SidebarState>(SIDEBAR_KEY);
|
||||||
|
|
||||||
|
|
||||||
// 1. 模拟数据:包含三层结构
|
// 1. 模拟数据:包含三层结构
|
||||||
|
|
||||||
const rawNavItems: NavItem[] = [
|
const rawNavItems: NavItem[] = [
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'dashboard',
|
id: 'dashboard',
|
||||||
|
|
||||||
label: '仪表盘',
|
label: '仪表盘',
|
||||||
|
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
|
|
||||||
href: '/app/dashboard'
|
href: '/app/dashboard'
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'statistics',
|
id: 'statistics',
|
||||||
|
|
||||||
label: '数据看板',
|
label: '数据看板',
|
||||||
|
|
||||||
icon: 'data',
|
icon: 'data',
|
||||||
|
|
||||||
href: '/app/statistics'
|
href: '/app/statistics'
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
|
|
||||||
label: '系统设置',
|
label: '系统设置',
|
||||||
|
|
||||||
icon: 'settings',
|
icon: 'settings',
|
||||||
|
|
||||||
href: '/app/settings',
|
href: '/app/settings',
|
||||||
|
|
||||||
subItems: [
|
subItems: [
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'auth',
|
id: 'auth',
|
||||||
|
|
||||||
label: '认证管理',
|
label: '认证管理',
|
||||||
|
|
||||||
href: '/app/settings/auth',
|
href: '/app/settings/auth',
|
||||||
|
|
||||||
icon: 'auth',
|
icon: 'auth',
|
||||||
|
|
||||||
subItems: [
|
subItems: [
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'users',
|
id: 'users',
|
||||||
|
|
||||||
label: '用户管理',
|
label: '用户管理',
|
||||||
|
|
||||||
href: '/app/settings/auth/users'
|
href: '/app/settings/auth/users'
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'roles',
|
id: 'roles',
|
||||||
|
|
||||||
label: '角色权限',
|
label: '角色权限',
|
||||||
|
|
||||||
href: '/app/settings/auth/roles'
|
href: '/app/settings/auth/roles'
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'permissions',
|
id: 'permissions',
|
||||||
|
|
||||||
label: '权限管理',
|
label: '权限管理',
|
||||||
|
|
||||||
href: '/app/settings/auth/permissions'
|
href: '/app/settings/auth/permissions'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: "device",
|
||||||
id: 'advanced',
|
label: '设备管理',
|
||||||
|
icon: 'laptop-settings',
|
||||||
label: '高级设置',
|
|
||||||
|
|
||||||
href: '/app/settings/advanced',
|
|
||||||
|
|
||||||
subItems: [
|
subItems: [
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id: 'device',
|
||||||
id: 'logs',
|
label: '设备管理',
|
||||||
|
href: '/app/settings/device/list'
|
||||||
label: '安全日志',
|
},
|
||||||
|
{
|
||||||
href: '/app/settings/advanced/logs'
|
id: 'type',
|
||||||
|
label: '类型管理',
|
||||||
|
href: '/app/settings/device/type'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'advanced',
|
||||||
|
label: '高级设置',
|
||||||
|
href: '/app/settings/advanced',
|
||||||
|
subItems: [
|
||||||
|
{
|
||||||
|
id: 'logs',
|
||||||
|
label: '安全日志',
|
||||||
|
href: '/app/settings/advanced/logs'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
id: 'backup',
|
id: 'backup',
|
||||||
|
|
||||||
label: '备份恢复',
|
label: '备份恢复',
|
||||||
|
|
||||||
href: '/app/settings/advanced/backup'
|
href: '/app/settings/advanced/backup'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
* 递归计算高亮状态 (强类型版本)
|
* 递归计算高亮状态 (强类型版本)
|
||||||
@@ -147,21 +99,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function processNavItems(items: NavItem[], currentPath: string): ProcessedNavItem[] {
|
function processNavItems(items: NavItem[], currentPath: string): ProcessedNavItem[] {
|
||||||
|
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
|
|
||||||
const isSelfActive =
|
const isSelfActive =
|
||||||
|
|
||||||
item.href === '/' ? currentPath === '/' : currentPath.startsWith(item.href);
|
item.href === '/' ? currentPath === '/' : currentPath.startsWith(item.href);
|
||||||
|
|
||||||
|
|
||||||
let processedSubItems: ProcessedNavItem[] | undefined = undefined;
|
let processedSubItems: ProcessedNavItem[] | undefined = undefined;
|
||||||
|
|
||||||
let isChildActive = false;
|
let isChildActive = false;
|
||||||
|
|
||||||
|
|
||||||
if (item.subItems) {
|
if (item.subItems) {
|
||||||
|
|
||||||
// 递归调用
|
// 递归调用
|
||||||
|
|
||||||
processedSubItems = processNavItems(item.subItems, currentPath);
|
processedSubItems = processNavItems(item.subItems, currentPath);
|
||||||
@@ -169,12 +115,9 @@
|
|||||||
// 检查子项激活状态
|
// 检查子项激活状态
|
||||||
|
|
||||||
isChildActive = processedSubItems.some((sub) => sub.isActive || sub.isChildActive);
|
isChildActive = processedSubItems.some((sub) => sub.isActive || sub.isChildActive);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
...item,
|
...item,
|
||||||
|
|
||||||
subItems: processedSubItems, // 这里类型现在是 ProcessedNavItem[]
|
subItems: processedSubItems, // 这里类型现在是 ProcessedNavItem[]
|
||||||
@@ -182,180 +125,104 @@
|
|||||||
isActive: isSelfActive,
|
isActive: isSelfActive,
|
||||||
|
|
||||||
isChildActive: isChildActive
|
isChildActive: isChildActive
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 使用 $derived 动态计算,类型自动推断为 ProcessedNavItem[]
|
// 使用 $derived 动态计算,类型自动推断为 ProcessedNavItem[]
|
||||||
|
|
||||||
let navItems = $derived(processNavItems(rawNavItems, page.url.pathname));
|
let navItems = $derived(processNavItems(rawNavItems, page.url.pathname));
|
||||||
|
|
||||||
|
|
||||||
// 获取 Toast 以便提示用户
|
// 获取 Toast 以便提示用户
|
||||||
|
|
||||||
const toast = getContext<ToastState>(TOAST_KEY);
|
const toast = getContext<ToastState>(TOAST_KEY);
|
||||||
|
|
||||||
|
|
||||||
// 处理提交结果的回调
|
// 处理提交结果的回调
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
|
|
||||||
toast.info('正在退出登录...');
|
toast.info('正在退出登录...');
|
||||||
|
|
||||||
return async ({ result, update }) => {
|
return async ({ result, update }) => {
|
||||||
|
|
||||||
|
|
||||||
// result.type 可能是 'redirect', 'success', 'failure'
|
// result.type 可能是 'redirect', 'success', 'failure'
|
||||||
|
|
||||||
if (result.type === 'redirect') {
|
if (result.type === 'redirect') {
|
||||||
|
|
||||||
toast.success('您已安全退出');
|
toast.success('您已安全退出');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// update() 会触发默认行为(也就是执行 redirect 跳转)
|
// update() 会触发默认行为(也就是执行 redirect 跳转)
|
||||||
|
|
||||||
await update();
|
await update();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let logoutForm: HTMLFormElement;
|
let logoutForm: HTMLFormElement;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- 定义递归 Snippet,显式指定类型 -->
|
<!-- 定义递归 Snippet,显式指定类型 -->
|
||||||
|
|
||||||
{#snippet menuItem(item: ProcessedNavItem)}
|
{#snippet menuItem(item: ProcessedNavItem)}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
{#if item.subItems && item.subItems.length > 0}
|
{#if item.subItems && item.subItems.length > 0}
|
||||||
|
|
||||||
<details open={item.isChildActive}>
|
<details open={item.isChildActive}>
|
||||||
|
<summary class="group {item.isActive ? 'font-medium text-primary' : ''}">
|
||||||
<summary class="group {item.isActive ? 'text-primary font-medium' : ''}">
|
|
||||||
|
|
||||||
{#if item.icon}
|
{#if item.icon}
|
||||||
|
|
||||||
<Icon id={item.icon} size="20" />
|
<Icon id={item.icon} size="20" />
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="truncate">{item.label}</span>
|
<span class="truncate">{item.label}</span>
|
||||||
|
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
|
||||||
{#each item.subItems as subItem (subItem.id)}
|
{#each item.subItems as subItem (subItem.id)}
|
||||||
|
|
||||||
<!-- 递归渲染子项 -->
|
<!-- 递归渲染子项 -->
|
||||||
|
|
||||||
{@render menuItem(subItem)}
|
{@render menuItem(subItem)}
|
||||||
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
|
<a href={resolve(item.href)} class="group {item.isActive ? 'active font-medium' : ''}">
|
||||||
<a
|
|
||||||
|
|
||||||
href={resolve(item.href)}
|
|
||||||
|
|
||||||
class="group {item.isActive ? 'active font-medium' : ''}"
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
{#if item.icon}
|
{#if item.icon}
|
||||||
|
|
||||||
<Icon id={item.icon} size="20" />
|
<Icon id={item.icon} size="20" />
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
|
|
||||||
<!-- 无图标时的占位符,保持对齐 -->
|
<!-- 无图标时的占位符,保持对齐 -->
|
||||||
|
|
||||||
<span class="w-5 text-center text-xs opacity-50">•</span>
|
<span class="w-5 text-center text-xs opacity-50">•</span>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="truncate">{item.label}</span>
|
<span class="truncate">{item.label}</span>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
||||||
class="fixed inset-0 z-20 cursor-pointer bg-black/50 backdrop-blur-sm md:hidden"
|
class="fixed inset-0 z-20 cursor-pointer bg-black/50 backdrop-blur-sm md:hidden"
|
||||||
|
|
||||||
role="button"
|
role="button"
|
||||||
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
||||||
|
|
||||||
transition:fade={{ duration: 200 }}
|
transition:fade={{ duration: 200 }}
|
||||||
|
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
|
class="fixed z-30 flex h-full w-64 flex-shrink-0 flex-col border-r border-base-100/70 bg-base-200 md:relative"
|
||||||
class="bg-base-200 border-base-100/70 fixed z-30 flex h-full w-64 flex-shrink-0 flex-col border-r md:relative"
|
|
||||||
|
|
||||||
in:fly={{ duration: 200, x: -100 }}
|
in:fly={{ duration: 200, x: -100 }}
|
||||||
|
|
||||||
out:fly={{ duration: 200, x: -100 }}
|
out:fly={{ duration: 200, x: -100 }}
|
||||||
|
|
||||||
>
|
>
|
||||||
|
<div class="flex h-18 flex-shrink-0 items-center p-4">
|
||||||
<div class="h-18 flex flex-shrink-0 items-center p-4">
|
<a class="flex items-center gap-3" href={resolve('/app/dashboard')}>
|
||||||
|
|
||||||
<a
|
|
||||||
|
|
||||||
class="flex items-center gap-3"
|
|
||||||
|
|
||||||
href={resolve('/app/dashboard')}
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
<Icon className="flex-shrink-0 rounded-box" id="logo" size="32" />
|
<Icon className="flex-shrink-0 rounded-box" id="logo" size="32" />
|
||||||
|
|
||||||
<p class="truncate font-serif text-lg font-bold">IT DTMS</p>
|
<p class="truncate font-serif text-lg font-bold">IT DTMS</p>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="custom-scrollbar flex-1 overflow-y-auto">
|
<div class="custom-scrollbar flex-1 overflow-y-auto">
|
||||||
|
|
||||||
<ul class="menu menu-vertical w-full gap-1 px-2">
|
<ul class="menu menu-vertical w-full gap-1 px-2">
|
||||||
|
|
||||||
{#each navItems as item (item.id)}
|
{#each navItems as item (item.id)}
|
||||||
|
|
||||||
<!-- 初始渲染调用 -->
|
<!-- 初始渲染调用 -->
|
||||||
|
|
||||||
{@render menuItem(item)}
|
{@render menuItem(item)}
|
||||||
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<!-- 状态: {sidebarState.isSidebarExpanded ? '展开' : '收起'}-->
|
<!-- 状态: {sidebarState.isSidebarExpanded ? '展开' : '收起'}-->
|
||||||
<!-- <button-->
|
<!-- <button-->
|
||||||
@@ -365,149 +232,90 @@
|
|||||||
<!-- </button>-->
|
<!-- </button>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{#if page.data.user}
|
{#if page.data.user}
|
||||||
|
<div class="flex-shrink-0 border-t border-base-content/10 bg-base-200/50 p-3">
|
||||||
<div class="border-base-content/10 bg-base-200/50 flex-shrink-0 border-t p-3">
|
<div class="dropdown dropdown-end dropdown-top w-full">
|
||||||
|
|
||||||
<div class="dropdown dropdown-top dropdown-end w-full">
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
||||||
role="button"
|
role="button"
|
||||||
|
class="flex w-full cursor-pointer items-center gap-3 rounded-lg p-2 transition-colors hover:bg-base-300"
|
||||||
class="hover:bg-base-300 flex w-full cursor-pointer items-center gap-3 rounded-lg p-2 transition-colors"
|
|
||||||
|
|
||||||
>
|
>
|
||||||
|
<div class="placeholder avatar">
|
||||||
<div class="avatar placeholder">
|
<div class="w-10 rounded-full bg-neutral text-neutral-content">
|
||||||
|
|
||||||
<div class="bg-neutral text-neutral-content w-10 rounded-full">
|
|
||||||
|
|
||||||
<img src={page.data.user.avatar} alt="avatar" />
|
<img src={page.data.user.avatar} alt="avatar" />
|
||||||
|
|
||||||
<span class="text-xs">User</span>
|
<span class="text-xs">User</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex min-w-0 flex-1 flex-col">
|
<div class="flex min-w-0 flex-1 flex-col">
|
||||||
|
|
||||||
<span class="truncate text-sm font-bold">{page.data.user.nickname}</span>
|
<span class="truncate text-sm font-bold">{page.data.user.nickname}</span>
|
||||||
|
|
||||||
<span class="text-base-content/60 truncate text-xs"
|
<span class="truncate text-xs text-base-content/60">@{page.data.user.username}</span>
|
||||||
|
|
||||||
>@{page.data.user.username}</span
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<Icon id="chevron-up-down" size="16" className="opacity-50" />
|
<Icon id="chevron-up-down" size="16" className="opacity-50" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
class="dropdown-content menu z-[1] mb-2 w-60 rounded-box border border-base-content/10 bg-base-100 p-2 shadow-lg"
|
||||||
class="dropdown-content z-[1] menu rounded-box border-base-content/10 mb-2 w-60 border bg-base-100 p-2 shadow-lg"
|
|
||||||
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<li class="menu-title px-4 py-2">我的账户</li>
|
<li class="menu-title px-4 py-2">我的账户</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<a href="/app/user">
|
<a href="/app/user">
|
||||||
<Icon id="user-profile" size="16" />
|
<Icon id="user-profile" size="16" />
|
||||||
个人资料</a>
|
个人资料</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<a href="/app/settings">
|
<a href="/app/settings">
|
||||||
<Icon id="settings" size="16" />
|
<Icon id="settings" size="16" />
|
||||||
设置</a>
|
设置</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
<div class="divider my-1"></div>
|
||||||
|
|
||||||
|
|
||||||
<li class="">
|
<li class="">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
class="flex w-full items-center gap-2 text-left text-error"
|
||||||
class="text-error w-full text-left flex items-center gap-2"
|
|
||||||
|
|
||||||
onclick={() => logoutForm.requestSubmit()}
|
onclick={() => logoutForm.requestSubmit()}
|
||||||
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<Icon id="sign-out" size="16" />
|
<Icon id="sign-out" size="16" />
|
||||||
退出登录
|
退出登录
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
|
|
||||||
action="/auth/logout"
|
action="/auth/logout"
|
||||||
|
|
||||||
method="POST"
|
method="POST"
|
||||||
|
|
||||||
use:enhance={handleLogout}
|
use:enhance={handleLogout}
|
||||||
|
|
||||||
bind:this={logoutForm}
|
bind:this={logoutForm}
|
||||||
|
|
||||||
hidden
|
hidden
|
||||||
|
></form>
|
||||||
>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
/* 保持原有样式 */
|
/* 保持原有样式 */
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar {
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
|
||||||
width: 5px;
|
width: 5px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-track {
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
|
||||||
background-color: rgba(156, 163, 175, 0.3);
|
background-color: rgba(156, 163, 175, 0.3);
|
||||||
|
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
0
src/lib/components/table/DeviceTable.svelte
Normal file
0
src/lib/components/table/DeviceTable.svelte
Normal file
160
src/lib/components/table/UserTable.svelte
Normal file
160
src/lib/components/table/UserTable.svelte
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
|
||||||
|
import type { PageResult } from '$lib/types/dataTable.ts';
|
||||||
|
import type { RoleResponse, UserProfile } from '$lib/types/user.ts';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import Icon from '$lib/components/icon/Icon.svelte';
|
||||||
|
|
||||||
|
let { users , roles } = $props<{
|
||||||
|
users: PageResult<UserProfile[]>,
|
||||||
|
roles: RoleResponse[]
|
||||||
|
}>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let x ;
|
||||||
|
|
||||||
|
const handleRoleChange = (e) => {
|
||||||
|
console.log(e.target.value);
|
||||||
|
x = e.target.value;
|
||||||
|
}
|
||||||
|
const newRowTitles = [
|
||||||
|
{ title: 'ID', width: 5}
|
||||||
|
, { title: '用户名', width: 15 }
|
||||||
|
, { title: '昵称', width: 20 }
|
||||||
|
, { title: '头像', width: 10 }
|
||||||
|
, { title: '用户组', width: 45 }
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log("users", users);
|
||||||
|
</script>
|
||||||
|
<div>
|
||||||
|
{#if users.total > 0}
|
||||||
|
<div class="">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="overflow-x-auto rounded-box shadow bg-base-100 mt-1 ">
|
||||||
|
<div class="flex items-center justify-between px-4 pt-4 pb-2">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<label class="input">
|
||||||
|
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<path d="m21 21-4.3-4.3"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="search" required placeholder="Search" />
|
||||||
|
<button class="btn btn-xs btn-primary">搜索</button>
|
||||||
|
</label>
|
||||||
|
{#if roles}
|
||||||
|
<div class="filter w-64">
|
||||||
|
<input class="btn filter-reset " type="radio" value='' name="metaframeworks" aria-label="All" onchange={handleRoleChange} />
|
||||||
|
{#each roles as role(role.id)}
|
||||||
|
<input class="btn " type="radio" name="metaframeworks" aria-label="{role.name}" value={role.id} onchange={handleRoleChange} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class=" flex items-center justify-center gap-4">
|
||||||
|
<button class="btn btn-primary">添加用户</button>
|
||||||
|
|
||||||
|
<div class="dropdown dropdown-bottom dropdown-end">
|
||||||
|
<div tabindex="0" role="button" class="btn" ><Icon id="menu" size="24" /></div>
|
||||||
|
<ul tabindex="-1" class="dropdown-content menu bg-base-200 rounded-box z-1 w-52 p-2 mt-2 shadow-sm" >
|
||||||
|
<li><div>删除</div></li>
|
||||||
|
<li><div>封禁</div></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5%">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="checkbox" />
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
{#each newRowTitles as item,index(index)}
|
||||||
|
<th style="width: {item.width}%" >{item.title}</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{#if users.records}
|
||||||
|
<tbody>
|
||||||
|
{#each users.records as record(record.id)}
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="checkbox" />
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<td>{record.id}</td>
|
||||||
|
<td>{record.username}</td>
|
||||||
|
<td>{record.nickname}</td>
|
||||||
|
<td>
|
||||||
|
<div class="w-8 h-8 rounded-box bg-primary-content/10 border-0">
|
||||||
|
{#if record.avatar}
|
||||||
|
<img class="w-8 h-8 rounded-box bg-primary-content/10 border-0" src="{record.avatar}" alt=" ">
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="">
|
||||||
|
{#each record.roles as role (role.id)}
|
||||||
|
<span class="badge select-none mr-2 last:mr-0 {role.id === 1 ? 'badge-primary' : 'badge-secondary'}">{role.name}</span>
|
||||||
|
{/each}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
||||||
|
<div class=" flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
page {users.current} of {users.pages}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="join">
|
||||||
|
<button class="join-item btn">1</button>
|
||||||
|
<button class="join-item btn">2</button>
|
||||||
|
<button class="join-item btn btn-disabled">...</button>
|
||||||
|
<button class="join-item btn">99</button>
|
||||||
|
<button class="join-item btn">100</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary">下一页</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{:else }
|
||||||
|
<p>No users found</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
201
src/lib/components/table/UserTableOld.svelte
Normal file
201
src/lib/components/table/UserTableOld.svelte
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import Icon from '$lib/components/icon/Icon.svelte';
|
||||||
|
import type { UserProfile } from '$lib/types/user.ts';
|
||||||
|
|
||||||
|
const { data } = $props();
|
||||||
|
|
||||||
|
console.log("data", data);
|
||||||
|
const newRowTitles = [
|
||||||
|
{ title: 'ID', width: 5}
|
||||||
|
, { title: '用户名', width: 15 }
|
||||||
|
, { title: '昵称', width: 20 }
|
||||||
|
, { title: '头像', width: 10 }
|
||||||
|
, { title: '用户组', width: 45 }
|
||||||
|
];
|
||||||
|
|
||||||
|
let x ;
|
||||||
|
|
||||||
|
const handleRoleChange = (e) => {
|
||||||
|
console.log(e.target.value);
|
||||||
|
x = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let users: UserProfile[] ;
|
||||||
|
|
||||||
|
if (data.streamed.userList){
|
||||||
|
users = data.streamed.userList.records;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class=" ">
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center ">
|
||||||
|
<p class="font-bold">用户管理</p>
|
||||||
|
<div class="breadcrumbs ">
|
||||||
|
<ul>
|
||||||
|
<li><a href={resolve('/app/dashboard')}>仪表盘</a></li>
|
||||||
|
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
||||||
|
<li><a href={resolve('/app/settings/auth')}>认证管理</a></li>
|
||||||
|
<li><a href={resolve('/app/settings/auth/users')}>用户管理</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="overflow-x-auto rounded-box shadow bg-base-100 mt-1 ">
|
||||||
|
<div class="flex items-center justify-between px-4 pt-4 pb-2">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<label class="input">
|
||||||
|
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<path d="m21 21-4.3-4.3"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="search" required placeholder="Search" />
|
||||||
|
<button class="btn btn-xs btn-primary">搜索</button>
|
||||||
|
</label>
|
||||||
|
{#await data.streamed.roles}
|
||||||
|
<div></div>
|
||||||
|
{:then roles }
|
||||||
|
<div class="filter w-64">
|
||||||
|
<input class="btn filter-reset " type="radio" value='' name="metaframeworks" aria-label="All" onchange={handleRoleChange} />
|
||||||
|
{#each roles as role(role.id)}
|
||||||
|
<input class="btn " type="radio" name="metaframeworks" aria-label="{role.name}" value={role.id} onchange={handleRoleChange} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
<div class=" flex items-center justify-center gap-4">
|
||||||
|
<button class="btn btn-primary">添加用户</button>
|
||||||
|
|
||||||
|
<div class="dropdown dropdown-bottom dropdown-end">
|
||||||
|
<div tabindex="0" role="button" class="btn" ><Icon id="menu" size="24" /></div>
|
||||||
|
<ul tabindex="-1" class="dropdown-content menu bg-base-200 rounded-box z-1 w-52 p-2 mt-2 shadow-sm" >
|
||||||
|
<li><div>删除</div></li>
|
||||||
|
<li><div>封禁</div></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5%">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="checkbox" />
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
{#each newRowTitles as item,index(index)}
|
||||||
|
<th style="width: {item.width}%" >{item.title}</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
{#await data.streamed.userList}
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
||||||
|
<div class="min-h-96 flex items-center justify-center">
|
||||||
|
<div class="loading text-base-content"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
{:then userList}
|
||||||
|
<tbody>
|
||||||
|
{#each userList.records as record(record.id)}
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="checkbox" />
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<td>{record.id}</td>
|
||||||
|
<td>{record.username}</td>
|
||||||
|
<td>{record.nickname}</td>
|
||||||
|
<td>
|
||||||
|
<div class="w-8 h-8 rounded-box bg-primary-content/10 border-0">
|
||||||
|
{#if record.avatar}
|
||||||
|
<img class="w-8 h-8 rounded-box bg-primary-content/10 border-0" src="{record.avatar}" alt=" ">
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="">
|
||||||
|
{#each record.roles as role (role.id)}
|
||||||
|
<span class="badge mr-2 last:mr-0 {role.id === 1 ? 'badge-primary' : 'badge-secondary'}">{role.name}</span>
|
||||||
|
{/each}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
||||||
|
<div class=" flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
page {userList.current} of {userList.pages}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="join">
|
||||||
|
<button class="join-item btn">1</button>
|
||||||
|
<button class="join-item btn">2</button>
|
||||||
|
<button class="join-item btn btn-disabled">...</button>
|
||||||
|
<button class="join-item btn">99</button>
|
||||||
|
<button class="join-item btn">100</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary">下一页</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
{:catch error}
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
||||||
|
<div class="min-h-96 flex items-center justify-center">
|
||||||
|
<p class="error">组件加载失败: {error.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<style lang="scss">
|
||||||
|
.loading {
|
||||||
|
padding: 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -24,3 +24,27 @@ export interface LoginSuccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface DeviceResponse {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
model: string;
|
||||||
|
typeId: number;
|
||||||
|
locationId: number;
|
||||||
|
snmpCommunity: string;
|
||||||
|
manufacturer: string;
|
||||||
|
purchaseDate: Date;
|
||||||
|
status: number;
|
||||||
|
remark: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateDeviceRequest {
|
||||||
|
name: string;
|
||||||
|
model: string;
|
||||||
|
typeId: number;
|
||||||
|
locationId: number;
|
||||||
|
snmpCommunity: string;
|
||||||
|
manufacturer: string;
|
||||||
|
purchaseDate: Date;
|
||||||
|
status: number;
|
||||||
|
remark: string;
|
||||||
|
}
|
||||||
@@ -16,5 +16,6 @@ export type IconId =
|
|||||||
"user-settings" |
|
"user-settings" |
|
||||||
"user-profile"|
|
"user-profile"|
|
||||||
"auth"|
|
"auth"|
|
||||||
"chevron-up-down"
|
"chevron-up-down"|
|
||||||
|
"laptop-settings"
|
||||||
;
|
;
|
||||||
@@ -5,7 +5,7 @@ export interface NavItem {
|
|||||||
id: string;
|
id: string;
|
||||||
icon?: IconId;
|
icon?: IconId;
|
||||||
label: string;
|
label: string;
|
||||||
href: RouteId;
|
href?: RouteId;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
|
|||||||
@@ -1,18 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { hasRole } from '$lib/utils/auth.ts';
|
|
||||||
|
|
||||||
const isAdmin = hasRole(['admin'])
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<div class="h-screen w-screen">
|
<div class="h-screen w-screen">
|
||||||
|
|
||||||
{#if isAdmin}
|
|
||||||
<div>
|
|
||||||
是管理员
|
|
||||||
</div>
|
|
||||||
{:else }
|
|
||||||
<div>
|
|
||||||
没有权限
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,28 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import UserTable from '$lib/components/table/UserTable.svelte';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import Icon from '$lib/components/icon/Icon.svelte';
|
|
||||||
|
|
||||||
const {data} = $props();
|
const {data} = $props();
|
||||||
|
|
||||||
console.log("data", data);
|
|
||||||
const newRowTitles = [
|
|
||||||
{ title: 'ID', width: 5}
|
|
||||||
, { title: '用户名', width: 15 }
|
|
||||||
, { title: '昵称', width: 20 }
|
|
||||||
, { title: '头像', width: 10 }
|
|
||||||
, { title: '用户组', width: 45 }
|
|
||||||
];
|
|
||||||
|
|
||||||
let x ;
|
|
||||||
|
|
||||||
const handleRoleChange = (e) => {
|
|
||||||
console.log(e.target.value);
|
|
||||||
x = e.target.value;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class=" ">
|
|
||||||
|
|
||||||
<div class="flex justify-between items-center ">
|
<div class="flex justify-between items-center ">
|
||||||
<p class="font-bold">用户管理</p>
|
<p class="font-bold">用户管理</p>
|
||||||
<div class="breadcrumbs ">
|
<div class="breadcrumbs ">
|
||||||
@@ -31,162 +13,19 @@
|
|||||||
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
||||||
<li><a href={resolve('/app/settings/auth')}>认证管理</a></li>
|
<li><a href={resolve('/app/settings/auth')}>认证管理</a></li>
|
||||||
<li><a href={resolve('/app/settings/auth/users')}>用户管理</a></li>
|
<li><a href={resolve('/app/settings/auth/users')}>用户管理</a></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="overflow-x-auto rounded-box shadow bg-base-100 mt-1 ">
|
|
||||||
<div class="flex items-center justify-between px-4 pt-4 pb-2">
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<label class="input">
|
|
||||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
||||||
<g
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-width="2.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
|
||||||
<path d="m21 21-4.3-4.3"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<input type="search" required placeholder="Search" />
|
|
||||||
<button class="btn btn-xs btn-primary">搜索</button>
|
|
||||||
</label>
|
|
||||||
{#await data.streamed.roles}
|
|
||||||
<div></div>
|
|
||||||
{:then roles }
|
|
||||||
<div class="filter w-64">
|
|
||||||
<input class="btn filter-reset " type="radio" value='' name="metaframeworks" aria-label="All" onchange={handleRoleChange} />
|
|
||||||
{#each roles as role(role.id)}
|
|
||||||
<input class="btn " type="radio" name="metaframeworks" aria-label="{role.name}" value={role.id} onchange={handleRoleChange} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
<div class=" flex items-center justify-center gap-4">
|
|
||||||
<button class="btn btn-primary">添加用户</button>
|
|
||||||
|
|
||||||
<div class="dropdown dropdown-bottom dropdown-end">
|
|
||||||
<div tabindex="0" role="button" class="btn" ><Icon id="menu" size="24" /></div>
|
|
||||||
<ul tabindex="-1" class="dropdown-content menu bg-base-200 rounded-box z-1 w-52 p-2 mt-2 shadow-sm" >
|
|
||||||
<li><div>删除</div></li>
|
|
||||||
<li><div>封禁</div></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="width: 5%">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" class="checkbox" />
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
{#each newRowTitles as item,index(index)}
|
|
||||||
<th style="width: {item.width}%" >{item.title}</th>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
{#await data.streamed.userList}
|
{#await data.streamed.userList}
|
||||||
<tbody>
|
加载中
|
||||||
<tr>
|
{:then result}
|
||||||
<td colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
{#await data.streamed.roles}
|
||||||
<div class="min-h-96 flex items-center justify-center">
|
加载中
|
||||||
<div class="loading text-base-content"></div>
|
{:then roles}
|
||||||
|
<UserTable users={result} roles={roles}/>
|
||||||
</div>
|
{:catch err}
|
||||||
</td>
|
<p>出错了: {err.message}</p>
|
||||||
</tr>
|
{/await}
|
||||||
</tbody>
|
{:catch err}
|
||||||
{:then userList}
|
<p>出错了: {err.message}</p>
|
||||||
<tbody>
|
|
||||||
{#each userList.records as record(record.id)}
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" class="checkbox" />
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<td>{record.id}</td>
|
|
||||||
<td>{record.username}</td>
|
|
||||||
<td>{record.nickname}</td>
|
|
||||||
<td>
|
|
||||||
<div class="w-8 h-8 rounded-box bg-primary-content/10 border-0">
|
|
||||||
{#if record.avatar}
|
|
||||||
<img class="w-8 h-8 rounded-box bg-primary-content/10 border-0" src="{record.avatar}" alt=" ">
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="">
|
|
||||||
{#each record.roles as role (role.id)}
|
|
||||||
<span class="badge mr-2 last:mr-0 {role.id === 1 ? 'badge-primary' : 'badge-secondary'}">{role.name}</span>
|
|
||||||
{/each}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<th colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
|
||||||
<div class=" flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
page {userList.current} of {userList.pages}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="join">
|
|
||||||
<button class="join-item btn">1</button>
|
|
||||||
<button class="join-item btn">2</button>
|
|
||||||
<button class="join-item btn btn-disabled">...</button>
|
|
||||||
<button class="join-item btn">99</button>
|
|
||||||
<button class="join-item btn">100</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button class="btn btn-primary">下一页</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
{:catch error}
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colspan={newRowTitles.length + 1} class="text-center py-4 ">
|
|
||||||
<div class="min-h-96 flex items-center justify-center">
|
|
||||||
<p class="error">组件加载失败: {error.message}</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<style lang="scss">
|
|
||||||
.loading {
|
|
||||||
padding: 20px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
25
src/routes/app/settings/device/list/+page.server.ts
Normal file
25
src/routes/app/settings/device/list/+page.server.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { deviceService } from '$lib/api/services/deviceService.ts';
|
||||||
|
|
||||||
|
export const load:PageServerLoad = async ({ cookies }) => {
|
||||||
|
|
||||||
|
const token = cookies.get(COOKIE_TOKEN_KEY);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw redirect(302, '/auth/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await deviceService.getAllDevices({ page: 1, size: 10 ,token:token});
|
||||||
|
console.log('result', result);
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
streamed:{
|
||||||
|
result: {
|
||||||
|
list: result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
30
src/routes/app/settings/device/list/+page.svelte
Normal file
30
src/routes/app/settings/device/list/+page.svelte
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
|
const {data} = $props();
|
||||||
|
|
||||||
|
console.log( data.streamed.deviceList);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center ">
|
||||||
|
<p class="font-bold">设备管理</p>
|
||||||
|
<div class="breadcrumbs ">
|
||||||
|
<ul>
|
||||||
|
<li><a href={resolve('/app/dashboard')}>仪表盘</a></li>
|
||||||
|
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
||||||
|
<li><a href={resolve('/app/settings/device/list')}>设备管理</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#await data.streamed.deviceList}
|
||||||
|
<p class="text-center">正在加载设备列表...</p>
|
||||||
|
<p class="text-center">请稍后...</p>
|
||||||
|
{:then result}
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
加载成功
|
||||||
|
</div>
|
||||||
|
{:catch error}
|
||||||
|
<p class="text-center">{error}</p>
|
||||||
|
<p class="text-center">请稍后...</p>
|
||||||
|
{/await}
|
||||||
0
src/routes/app/settings/device/type/+page.svelte
Normal file
0
src/routes/app/settings/device/type/+page.svelte
Normal file
Reference in New Issue
Block a user