refactor(settings): 重构用户管理和设备管理页面
- 调整用户管理页面角色数据获取方法,使用 getRolesOptions 替代 getAllRoles - 更新用户表格组件接收的角色数据属性名及类型 - 修改设备管理页面路由路径,从 /device/list 调整为 /devices - 移除调试用 console.log 输出语句 - 添加选项类型 Options 接口定义 - 优化侧边栏导航结构与交互逻辑,支持父级菜单带链接可点击 - 引入日志模块用于 API 请求与响应记录 - 升级依赖包配置,移除 peer 标记 - 微调样式类名增强布局效果和用户体验
This commit is contained in:
@@ -1,14 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import { fade, fly } from 'svelte/transition';
|
|
||||||
import Icon from '$lib/components/icon/Icon.svelte';
|
|
||||||
import type { NavItem, ProcessedNavItem } from '$lib/types/layout';
|
|
||||||
import { TOAST_KEY, type ToastState } from '$lib/stores/toast.svelte.ts';
|
|
||||||
import { enhance } from '$app/forms';
|
|
||||||
|
|
||||||
// 1. 模拟数据:现在给父级 "device" 也加上了 href
|
import {resolve as _resolve} from '$app/paths'
|
||||||
|
import type { NavItem } from '$lib/types/layout.ts';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
import Icon from '$lib/components/icon/Icon.svelte';
|
||||||
|
import { TOAST_KEY, type ToastState } from '$lib/stores/toast.svelte.ts';
|
||||||
|
import type { RouteId } from '$app/types';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
|
||||||
const rawNavItems: NavItem[] = [
|
const rawNavItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
id: 'dashboard',
|
id: 'dashboard',
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
id: 'device',
|
id: 'device',
|
||||||
label: '设备管理',
|
label: '设备管理',
|
||||||
icon: 'laptop-settings',
|
icon: 'laptop-settings',
|
||||||
href: '/app/settings/devices', // 【修改点】父级现在有链接了,指向列表页
|
href: '/app/settings/devices',
|
||||||
subItems: [
|
subItems: [
|
||||||
{
|
{
|
||||||
id: 'device-type',
|
id: 'device-type',
|
||||||
@@ -64,228 +63,137 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'advanced',
|
|
||||||
label: '高级设置',
|
|
||||||
href: '/app/settings/advanced',
|
|
||||||
subItems: [
|
|
||||||
{
|
|
||||||
id: 'logs',
|
|
||||||
label: '安全日志',
|
|
||||||
href: '/app/settings/advanced/logs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'backup',
|
|
||||||
label: '备份恢复',
|
|
||||||
href: '/app/settings/advanced/backup'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* 递归计算高亮状态 (逻辑修复版)
|
|
||||||
* 修复了当 href 为 undefined 时的潜在报错,并增强了激活判断
|
|
||||||
*/
|
|
||||||
function processNavItems(items: NavItem[], currentPath: string): ProcessedNavItem[] {
|
|
||||||
return items.map((item) => {
|
|
||||||
// 安全获取 href
|
|
||||||
const href = item.href || '';
|
|
||||||
|
|
||||||
// 判断自身是否激活
|
|
||||||
// 如果 href 存在,且 (是根路径全等 OR 当前路径以 href 开头)
|
|
||||||
const isSelfActive = href
|
|
||||||
? (href === '/' ? currentPath === '/' : currentPath.startsWith(href))
|
|
||||||
: false;
|
|
||||||
|
|
||||||
let processedSubItems: ProcessedNavItem[] | undefined = undefined;
|
|
||||||
let isChildActive = false;
|
|
||||||
|
|
||||||
if (item.subItems) {
|
|
||||||
processedSubItems = processNavItems(item.subItems, currentPath);
|
|
||||||
// 只要有一个子项激活,或者子项的子项激活,父级就算 ChildActive
|
|
||||||
isChildActive = processedSubItems.some((sub) => sub.isActive || sub.isChildActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
subItems: processedSubItems,
|
|
||||||
isActive: isSelfActive,
|
|
||||||
isChildActive: isChildActive
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 $derived 动态计算
|
|
||||||
let navItems = $derived(processNavItems(rawNavItems, page.url.pathname));
|
|
||||||
|
|
||||||
// Toast & Logout 逻辑
|
|
||||||
const toast = getContext<ToastState>(TOAST_KEY);
|
const toast = getContext<ToastState>(TOAST_KEY);
|
||||||
|
// @ts-expect-error : ES + TS 混合报错手动忽略
|
||||||
|
const resolve = (href: RouteId) => _resolve(href);
|
||||||
|
|
||||||
const handleLogout = () => {
|
let expandedIds = $state<string[]>([]);
|
||||||
toast.info('正在退出登录...');
|
|
||||||
return async ({ result, update }) => {
|
const toggleExpand = (id: string) => {
|
||||||
if (result.type === 'redirect') {
|
if (expandedIds.includes(id)) {
|
||||||
toast.success('您已安全退出');
|
expandedIds = expandedIds.filter(item => item !== id);
|
||||||
|
} else {
|
||||||
|
expandedIds = [...expandedIds, id];
|
||||||
}
|
}
|
||||||
await update();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let logoutForm: HTMLFormElement;
|
|
||||||
|
const handleClick = (item: NavItem) => {
|
||||||
|
|
||||||
|
|
||||||
|
if (item.subItems && item.subItems.length > 0) {
|
||||||
|
toggleExpand(item.id);
|
||||||
|
} else {
|
||||||
|
// 叶子节点逻辑
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const computeNavState = (items: NavItem[], currentPath: string, openIds: string[]): NavItem[] => {
|
||||||
|
return items.map((item) => {
|
||||||
|
const newItem = { ...item };
|
||||||
|
|
||||||
|
newItem.isActive = currentPath === newItem.href;
|
||||||
|
|
||||||
|
// 递归处理子项
|
||||||
|
let hasActiveChild = false;
|
||||||
|
if (newItem.subItems && newItem.subItems.length > 0) {
|
||||||
|
newItem.subItems = computeNavState(newItem.subItems, currentPath, openIds);
|
||||||
|
hasActiveChild = newItem.subItems.some(child => child.isActive || child.isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isManuallyOpen = openIds.includes(newItem.id);
|
||||||
|
|
||||||
|
// 如果你希望“进入父级页面自动展开子菜单”,请保留 `|| newItem.isActive`
|
||||||
|
newItem.isOpen = isManuallyOpen || hasActiveChild || newItem.isActive;
|
||||||
|
|
||||||
|
return newItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let menuItems = $derived(computeNavState(rawNavItems, page.url.pathname, expandedIds));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!--
|
<div class="h-screen relative bg-base-200">
|
||||||
定义递归 Snippet
|
<aside class="custom-scrollbar h-full w-full overflow-hidden flex flex-col ">
|
||||||
【修改点】:在 summary 内部增加了 conditional rendering,支持父级点击跳转
|
<div class="h-14 shadow-2xl shadow-base-100 ">
|
||||||
-->
|
123
|
||||||
{#snippet menuItem(item: ProcessedNavItem)}
|
</div>
|
||||||
<li>
|
<div class="overflow-y-auto flex-1 ">
|
||||||
|
<ul class="menu bg-base-200 w-64 p-4 text-base-content flex-nowrap ">
|
||||||
|
{#each menuItems as item (item.id)}
|
||||||
|
<li class="{item.isActive ? 'menu-active' : ''} w-full rounded-box ">
|
||||||
{#if item.subItems && item.subItems.length > 0}
|
{#if item.subItems && item.subItems.length > 0}
|
||||||
<!--
|
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
||||||
open 属性控制:
|
<a href={resolve(item.href)} class="p-2" onclick="{() => handleClick(item)}">
|
||||||
如果是自身激活(点了父级链接) 或者 子项激活(点了子级),都保持展开状态
|
|
||||||
-->
|
|
||||||
<details open={item.isActive || item.isChildActive}>
|
|
||||||
<summary class="group {item.isActive ? 'text-primary' : ''}">
|
|
||||||
{#if item.icon}
|
{#if item.icon}
|
||||||
<Icon id={item.icon} size="20" />
|
<Icon id="{item.icon}" size="24"/>
|
||||||
{/if}
|
{/if}
|
||||||
|
<span class="menu-dropdown-toggle {item.isOpen ? 'menu-dropdown-show ' : ''}">{item.label}</span>
|
||||||
|
</a>
|
||||||
|
<ul class="menu-dropdown rounded-box {item.isOpen ? 'menu-dropdown-show' : ''}">
|
||||||
|
{#each item.subItems as subItem (subItem.id)}
|
||||||
|
<li class="{subItem.isActive ? 'menu-active' : ''} rounded-box ">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
||||||
|
<a href={resolve(subItem.href)} class="p-2 " onclick={() => handleClick(subItem)}>
|
||||||
|
{#if subItem.icon}
|
||||||
|
<Icon id="{subItem.icon}" size="24"/>
|
||||||
|
{/if}
|
||||||
|
<span class="menu-dropdown-toggle">
|
||||||
|
{subItem.label}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{#if subItem.subItems && subItem.subItems.length > 0}
|
||||||
|
<ul class="menu-dropdown {subItem.isOpen ? 'menu-dropdown-show' : ''}" >
|
||||||
|
{#each subItem.subItems as childItem (childItem.id)}
|
||||||
|
<li class="{childItem.isActive ? 'menu-active' : ''} rounded-box">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
||||||
|
<a href={resolve(childItem.href)} class="p-2">
|
||||||
|
{#if childItem.icon}
|
||||||
|
<Icon id="{childItem.icon}" size="24"/>
|
||||||
|
{:else}
|
||||||
|
<div class="w-0.5/2 h-1">
|
||||||
|
|
||||||
<!-- 【核心修改】:父级如果有 href,渲染为链接 -->
|
</div>
|
||||||
{#if item.href}
|
{/if}
|
||||||
<!--
|
{childItem.label}
|
||||||
onclick stopPropagation 是关键:
|
</a>
|
||||||
阻止冒泡,防止点击链接时触发 <details> 的 toggle 行为。
|
</li>
|
||||||
这样点击文字是跳转,点击右侧箭头/空白是展开折叠。
|
{/each}
|
||||||
-->
|
</ul>
|
||||||
<a
|
{/if}
|
||||||
href={resolve(item.href)}
|
</li>
|
||||||
class="flex-1 truncate hover:text-primary-focus transition-colors"
|
{/each}
|
||||||
onclick={(e) => e.stopPropagation()}
|
</ul>
|
||||||
>
|
|
||||||
|
{:else }
|
||||||
|
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
||||||
|
<a href={resolve(item.href)} class="p-2">
|
||||||
|
{#if item.icon}
|
||||||
|
<Icon id="{item.icon}" size="24"/>
|
||||||
|
{/if}
|
||||||
{item.label}
|
{item.label}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
|
||||||
<!-- 没有 href,纯文本,点击整行触发展开折叠 -->
|
|
||||||
<span class="truncate">{item.label}</span>
|
|
||||||
{/if}
|
{/if}
|
||||||
</summary>
|
</li>
|
||||||
|
|
||||||
<ul>
|
|
||||||
{#each item.subItems as subItem (subItem.id)}
|
|
||||||
{@render menuItem(subItem)}
|
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
|
||||||
{:else}
|
|
||||||
<!-- 无子菜单的普通叶子节点 -->
|
|
||||||
<a href={resolve(item.href || '#')} class="group {item.isActive ? 'active font-medium' : ''}">
|
|
||||||
{#if item.icon}
|
|
||||||
<Icon id={item.icon} size="20" />
|
|
||||||
{:else}
|
|
||||||
<span class="w-5 text-center text-xs opacity-50">•</span>
|
|
||||||
{/if}
|
|
||||||
<span class="truncate">{item.label}</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</li>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
<!-- Mobile Backdrop -->
|
|
||||||
<div
|
|
||||||
class="fixed inset-0 z-20 cursor-pointer bg-black/50 backdrop-blur-sm md:hidden"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
transition:fade={{ duration: 200 }}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- Sidebar Container -->
|
|
||||||
<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"
|
|
||||||
in:fly={{ duration: 200, x: -100 }}
|
|
||||||
out:fly={{ duration: 200, x: -100 }}
|
|
||||||
>
|
|
||||||
<div class="flex h-18 flex-shrink-0 items-center p-4">
|
|
||||||
<a class="flex items-center gap-3" href={resolve('/app/dashboard')}>
|
|
||||||
<Icon className="flex-shrink-0 rounded-box" id="logo" size="32" />
|
|
||||||
<p class="truncate font-serif text-lg font-bold">IT DTMS</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-24 w-full shadow-2xl ">
|
||||||
<div class="custom-scrollbar flex-1 overflow-y-auto">
|
12312
|
||||||
<ul class="menu menu-vertical w-full gap-1 px-2">
|
|
||||||
{#each navItems as item (item.id)}
|
|
||||||
{@render menuItem(item)}
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Profile Section -->
|
|
||||||
{#if page.data.user}
|
|
||||||
<div class="flex-shrink-0 border-t border-base-content/10 bg-base-200/50 p-3">
|
|
||||||
<div class="dropdown dropdown-end dropdown-top w-full">
|
|
||||||
<div
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
class="flex w-full cursor-pointer items-center gap-3 rounded-lg p-2 transition-colors hover:bg-base-300"
|
|
||||||
>
|
|
||||||
<div class="placeholder avatar">
|
|
||||||
<div class="w-10 rounded-full bg-neutral text-neutral-content">
|
|
||||||
<img src={page.data.user.avatar} alt="avatar" />
|
|
||||||
<span class="text-xs">User</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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-xs text-base-content/60">@{page.data.user.username}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Icon id="chevron-up-down" size="16" className="opacity-50" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<li class="menu-title px-4 py-2">我的账户</li>
|
|
||||||
<li>
|
|
||||||
<a href="/app/user">
|
|
||||||
<Icon id="user-profile" size="16" />
|
|
||||||
个人资料</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/app/settings">
|
|
||||||
<Icon id="settings" size="16" />
|
|
||||||
设置</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<div class="divider my-1"></div>
|
|
||||||
<li class="">
|
|
||||||
<button
|
|
||||||
class="flex w-full items-center gap-2 text-left text-error"
|
|
||||||
onclick={() => logoutForm.requestSubmit()}
|
|
||||||
>
|
|
||||||
<Icon id="sign-out" size="16" />
|
|
||||||
退出登录
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<form
|
|
||||||
action="/auth/logout"
|
|
||||||
method="POST"
|
|
||||||
use:enhance={handleLogout}
|
|
||||||
bind:this={logoutForm}
|
|
||||||
hidden
|
|
||||||
></form>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</aside>
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.custom-scrollbar::-webkit-scrollbar {
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
|||||||
147
src/lib/components/table/DevicesTable.svelte
Normal file
147
src/lib/components/table/DevicesTable.svelte
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
|
||||||
|
import type { PageResult } from '$lib/types/dataTable.ts';
|
||||||
|
import Icon from '$lib/components/icon/Icon.svelte';
|
||||||
|
import type { DeviceResponse, } from '$lib/types/api.ts';
|
||||||
|
|
||||||
|
let { devices } = $props<{
|
||||||
|
devices: PageResult<DeviceResponse[]>,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const newRowTitles = [
|
||||||
|
{ title: 'ID', width: 5}
|
||||||
|
, { title: '用户名', width: 15 }
|
||||||
|
, { title: '昵称', width: 20 }
|
||||||
|
, { title: '头像', width: 10 }
|
||||||
|
, { title: '用户组', width: 45 }
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="h-full">
|
||||||
|
<div class="overflow-x-auto rounded-box shadow bg-base-100 mt-1 min-h-1/2">
|
||||||
|
<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 rolesOptions}-->
|
||||||
|
<!-- <div class="filter w-64">-->
|
||||||
|
<!-- <input class="btn filter-reset " type="radio" value='' name="metaframeworks" aria-label="All" onchange={handleRoleChange} />-->
|
||||||
|
<!-- {#each rolesOptions as role(role.value)}-->
|
||||||
|
<!-- <input class="btn" type="radio" name="metaframeworks" aria-label="{role.label}" value={role.value} 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>
|
||||||
|
{#if devices.total > 0}
|
||||||
|
<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>
|
||||||
|
{:else }
|
||||||
|
<p>No users found</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -5,10 +5,11 @@ 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;
|
||||||
|
isOpen?: boolean;
|
||||||
subItems?: NavItem[];
|
subItems?: NavItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
src/routes/app/settings/auth/+page.svelte
Normal file
3
src/routes/app/settings/auth/+page.svelte
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
234
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import UserTable from '$lib/components/table/UserTable.svelte';
|
import UsersTable from '$lib/components/table/UsersTable.svelte';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
const {data} = $props();
|
const {data} = $props();
|
||||||
@@ -32,7 +32,7 @@ const {data} = $props();
|
|||||||
<p class="text-base-content mt-4">加载中...</p>
|
<p class="text-base-content mt-4">加载中...</p>
|
||||||
</div>
|
</div>
|
||||||
{:then rolesOptions}
|
{:then rolesOptions}
|
||||||
<UserTable users={result} rolesOptions={rolesOptions}/>
|
<UsersTable users={result} rolesOptions={rolesOptions}/>
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<p>出错了: {err.message}</p>
|
<p>出错了: {err.message}</p>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ export const load:PageServerLoad = async ({ cookies }) => {
|
|||||||
throw redirect(302, '/auth/login');
|
throw redirect(302, '/auth/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await deviceService.getAllDevices({ page: 1, size: 10 ,token:token});
|
const result = deviceService.getAllDevices({ page: 1, size: 10 ,token:token});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
streamed:{
|
streamed:{
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
import { log } from '$lib/log.ts';
|
||||||
|
import DevicesTable from '$lib/components/table/DevicesTable.svelte';
|
||||||
|
|
||||||
const {data} = $props();
|
const {data} = $props();
|
||||||
|
|
||||||
console.log( data.streamed.list);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between items-center ">
|
<div class="flex justify-between items-center ">
|
||||||
@@ -17,12 +19,14 @@ console.log( data.streamed.list);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#await data.streamed.list}
|
{#await data.streamed.result.list}
|
||||||
|
<div class="">
|
||||||
<p class="text-center">正在加载设备列表...</p>
|
<p class="text-center">正在加载设备列表...</p>
|
||||||
<p class="text-center">请稍后...</p>
|
<p class="text-center">请稍后...</p>
|
||||||
{:then result}
|
</div>
|
||||||
|
{:then list}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
加载成功
|
<DevicesTable devices={list} />
|
||||||
</div>
|
</div>
|
||||||
{:catch error}
|
{:catch error}
|
||||||
<p class="text-center">{error}</p>
|
<p class="text-center">{error}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user