基於svelte3.x+svelteKit+svelte-ui網頁後臺管理系統SvelteAdmin。
Svelte-Ui-Admin 基於svelte3.x+svelteKit+vite3+echarts搭配使用Svelte UI元件庫開發的一套輕量級前端中後臺管理系統解決方案。Svelte Ui Admin遵循Svelte Ui元件設計和開發規範,高顏值的元件讓整體風格細膩統一。
+layout.svelte整體分為頂部欄+左側選單+右側主體內容三大板塊。
<div class="svadmin__container" style="--themeSkin: {$skin}"> <div class="svadmin__wrapper-layout flexbox flex-col"> <div class="sv__layout-header"> <Header /> </div> <div class="sv__layout-body flex1 flexbox"> <!-- //側邊欄 --> {#if rootRouteEnable} <div class="sv__bd-sidebar"> <SideMenu routes={mainRoutes} {activeRoute} /> </div> {/if} {#if (rootRouteEnable && route != '/') || !rootRouteEnable} <div class="sv__bd-menus" class:collapsed={collapsed&&!rootRouteEnable}> <RouteMenu routes={getAllRoutes} {activeRoute} {activeRootRoute} {rootRouteEnable} {collapsed} /> </div> {/if} <div class="sv__bd-main flex1 flexbox flex-col"> <!-- 麵包屑導航 --> <BreadCrumb routes={getAllRoutes} {activeRoute} {activeRootRoute} /> <!-- 主內容區 --> <Scrollbar autohide gap={2}> <div class="sv__main-wrapper"> <slot /> </div> </Scrollbar> </div> </div> </div> </div>
+error.svelte錯誤頁處理。
<script> import { page } from '$app/stores' import { goto } from '$app/navigation' import { Button } from '$lib/svelte-ui' function goHome() { goto('/home/index') } </script> <svelte:head> <title>{$page.status} Error!</title> </svelte:head> <div class="svadmin__pageErr flexbox flex-col flex-alignc flex-justifyc"> <div class="svadmin__pageErr-img"><i class="sv-icon-round_close_fill_light"></i></div> <div class="svadmin__pageErr-content"> <div class="c-red fs-18">┗| {$page.status} |┛ Page Error~~</div> <div class="c-999 mt-10 mb-10">{$page.error.message}</div> <Button size="small" on:click={goHome}>Go Home</Button> </div> </div>
如上圖:使用了svelte-ui元件庫中的Menu元件來動態生成選單。
<Menu class="svadmin__menu-list" data={getNewRoutes} active={activeRoute} trigger="click" collapse={collapsed} backgroundHover="#e2f2ff" > {#each getNewRoutes as route} <svelte:component this={routeMenuItem} item={route} {activeRootRoute} {rootRouteEnable} /> {/each} </Menu>
<!-- 二級選單 --> <script> import { goto } from '$app/navigation' import { _ } from 'svelte-i18n' import { Menu, MenuItem, MenuSub } from '$lib/svelte-ui' import routeMenuItem from './routeMenuItem.svelte' import utils from '@/utils' import { getCurrentRootRoute, hasChildrenRoute } from '@/utils/routes' export let item = [] // 根選單 export let activeRootRoute = '' // 是否開啟一級路由選單 export let rootRouteEnable = true function changeRoute(path) { if(utils.isExternal(path)) { window.open(path, '_blank') }else { goto(path) } } </script> {#if !item.meta.hidden} {#if activeRootRoute !== getCurrentRootRoute(item) && rootRouteEnable === true} <div></div> {:else} {#if item.children && Array.isArray(item.children) && hasChildrenRoute(item.children)} <MenuSub key={item.key}> <span slot="icon"><i class={item.meta.icon}></i></span> <div slot="title">{$_(`menu.${item.meta.title}`)}</div> {#each item.children || [] as route2} <svelte:component this={routeMenuItem} item={route2} {activeRootRoute} {rootRouteEnable} /> {/each} </MenuSub> {:else} <MenuItem key={item.key} title={$_(`menu.${item.meta.title}`)} on:click={changeRoute(item.path)}></MenuItem> {/if} {/if} {/if}
/** * 路由選單Layout.js */ export function load() { return { mainRoutes: [ // 主頁模組 { key: 'home', // 標識Menu元件匹配路徑 path: '/home', // 跳轉路由 redirect: '/home/index', // 重定向路由 meta: { auth: true, // 是否驗證狀態 icon: 'sv-icon-homefill', // 路由圖示 title: 'layouts__main-menu__home', // 路由標題 hidden: false, //是否隱藏選單項 }, children: [ // 首頁 { key: 'home_index', path: 'index', meta: { auth: true, icon: 'sv-icon-home', title: 'layouts__main-menu__home_index' } }, // 工作臺 { key: 'home_workplace', path: 'workplace', meta: { auth: true, icon: 'sv-icon-dashboard', title: 'layouts__main-menu__home_dashboard' } }, // 自定義麵包屑 { key: 'home_breadcrumb', path: 'breadcrumb', meta: { auth: true, icon: 'sv-icon-breadcrumb', title: 'layouts__main-menu__home_breadcrumb', // 自定義麵包屑 breadcrumb: [ { meta: {title: 'layouts__main-menu__home_breadcrumb'}, path: '/home/breadcrumb', }, { meta: {title: 'layouts__main-menu__home'}, path: '/home', }, { meta: {title: 'layouts__main-menu__home_breadcrumb-links'}, } ] } }, // 外部連結 { key: 'https://svelte.dev/', path: 'https://svelte.dev/', meta: { icon: 'sv-icon-openlink', title: 'layouts__main-menu__home_apidocs', rootRoute: '/home' } } ] }, // 元件模組 { key: 'component', path: '/component', redirect: '/component/table/all', meta: { auth: true, //是否驗證狀態 icon: 'sv-icon-apps-fill', title: 'layouts__main-menu__component', hidden: false, //是否隱藏選單項 }, children: [ { key: 'component_table', path: 'table', redirect: '/component/table/all', meta: { auth: true, icon: 'sv-icon-table', title: 'layouts__main-menu__component_table', }, children: [ { key: 'component_table_all', path: 'all', meta: { title: 'layouts__main-menu__component_table-all' } }, { key: 'component_table_custom', path: 'custom', meta: { title: 'layouts__main-menu__component_table-custom' } }, { key: 'component_table_search', path: 'search', redirect: '/component/table/search/list', meta: { title: 'layouts__main-menu__component_table-search', }, children: [ { key: 'component_table_search_list', path: 'list', meta: { title: 'layouts__main-menu__component_table-search-list' } } ] } ] }, { key: 'component_list', path: 'list', meta: { icon: 'sv-icon-sort', title: 'layouts__main-menu__component_list', } }, { key: 'component_form', path: 'form', redirect: '/component/form/all', meta: { auth: true, icon: 'sv-icon-forms', title: 'layouts__main-menu__component_form', }, children: [ { key: 'component_form_all', path: 'all', meta: { title: 'layouts__main-menu__component_form-all', } }, { key: 'component_form_custom', path: 'custom', meta: { title: 'layouts__main-menu__component_form-custom', } } ] }, { key: 'component_editor', path: 'editor', meta: { icon: 'sv-icon-editor', title: 'layouts__main-menu__component_editor', } } ] }, // 設定模組 { key: 'setting', path: '/setting', redirect: '/setting/mine', meta: { icon: 'sv-icon-setting', title: 'layouts__main-menu__setting', hidden: false, }, children: [ ... ] }, // 許可權驗證模組 { key: 'permission', path: '/permission', redirect: '/permission/all', meta: { auth: true, icon: 'sv-icon-secret', title: 'layouts__main-menu__permission', hidden: false, }, children: [ ... ] }, // 錯誤頁面模組 { key: 'error', path: '/error', redirect: '/error/403', meta: { icon: 'sv-icon-roundclosefill', title: 'layouts__main-menu__error', hidden: false, }, children: [ ... ] }, ] } }
npm i svelte-i18n -D
/** * 國際化語言設定 * @author YXY */ import { addMessages, init, getLocaleFromNavigator } from 'svelte-i18n' import { browser } from '$app/env' import Storage from '@/utils/storage' // 引入語言設定 import cn from '@/locale/zh-CN' import tw from '@/locale/zh-TW' import en from '@/locale/en-US' export const langKey = 'lang' export const langVal = 'cn' addMessages('cn', cn) addMessages('tw', tw) addMessages('en', en) const lang = getLang() console.log('當前國際化:', lang) init({ fallbackLocale: lang, initialLocale: getLocaleFromNavigator() }) setHtmlLang(lang) /* 獲取語言 */ export function getLang() { const lang = Storage.get(langKey) return lang || langVal } /* 持久化儲存 */ export function setLang(lang, reload = false) { if(lang != getLang()) { Storage.set(langKey, lang || '') setHtmlLang(lang) // 過載頁面 if(reload) { window.location.reload() } } }
由於專案中多個地方使用了Echarts圖表元件,於是單獨抽離了一個hooks檔案來初始化echarts元件。
針對自適應圖表,則使用了 "element-resize-detector": "^1.2.4" 來實時監聽DOM尺寸改變。
/** * @title 動態圖表Hooks * @author YXY */ import * as echarts from 'echarts' import elementResizeDetector from "element-resize-detector" import utils from '@/utils' export const useCharts = async(node, options) => { let chartInstance let chartNode = null let erd = elementResizeDetector() const resizeFn = utils.debounce(() => { chartInstance.resize() }, 100) if(node) { chartInstance = echarts.init(node) chartInstance.setOption(options) chartNode = chartInstance } erd.listenTo(node, resizeFn) }
通過如下方式即可快速呼叫圖表hooks。
<script> import { useCharts } from '@/hooks.js' function useBarChart(node) { useCharts(node, { ... }) } function useStackChart(node) { useCharts(node, { ... }) } </script> <div class="card-charts" use:useBarChart></div> <div class="card-charts" use:useStackChart></div>
專案中還有一大亮點就是Table表格元件,支援固定表頭/列,單選及多選,邊框/隔行換色,支援動態slot插槽等功能。
<script> /** * 表格元件 * @author YXY * Q:282310962 WX:xy190310 */ let tableNode let curpage = 1 let limit = 10 let selectRows = [] let tableData = Mock.mock({ total: 100, page: 1, pagesize: 5, 'list|10': [ { id: '@integer(1, 1000)', author: '@cname()', title: '@ctitle(10, 15)', image: `https://cdn2.thecatapi.com/images/@integer(200, 300).jpg`, summary: '@ctitle(20, 70)', 'role|1': ['admin', 'test', 'dev'], topmost: '@boolean()', progress: '@integer(30, 90)', date: '@datetime()' } ] }) let tableColumns = [ {type: 'selection', align: 'center', width: 60, fixed: true}, // 多選 // {type: 'index', align: 'center', width: 60}, // 索引序號 {prop: 'id', label: 'id', align: 'center', width: 60}, // 索引序號 {prop: 'author', label: '作者', align: 'center', width: 100}, {slot: 'title', label: '標題', align: 'left', width: 250}, {slot: 'image', label: '影象', align: 'center', width: 120}, {slot: 'summary', label: '詳細內容', align: 'left', width: 450}, {slot: 'role', label: '角色', align: 'center', width: 100}, {slot: 'topmost', label: '置頂', align: 'center', width: 100}, {slot: 'progress', label: '熱度', align: 'center', width: 150}, {prop: 'date', label: '釋出時間', align: 'left', width: 200}, // 時間 {slot: 'btns', label: '操作', align: 'center', width: 200, fixed: 'right'}, // 操作 ] // 獲取選中行資料 function getSelectionRow() { svLayer({ title: '資訊', content: JSON.stringify(selectRows), xclose: true, area: '640px' }) } // 選中第3行 function updateRow() { tableNode.setCurrent(2) } // 取消選擇 function cancelSelection() { tableNode.setCurrent() } // 當前頁 function handleChangePage(e) { console.log('當前頁:', e.detail) curpage = Number(e.detail) } // 頁碼 function handleChangeSize(e) { console.log('每頁:', e.detail) limit = Number(e.detail) } // 點選行 function handleSelectionChange(e) { console.log('selection change選中行資料>>:', e.detail) selectRows = e.detail } </script> <Table dataSource={tableData.list} columns={tableColumns} stripe={isStripe} border={isBorder} size={tableSizeCmd} highlightCurrentRow let:row let:col let:index on:selectionChange={handleSelectionChange} on:headerClick={handleHeaderClick} bind:this={tableNode} style="height: 500px; margin-bottom: 15px;" > {#if col.slot == 'title'} <Link href="https://svelte.dev/" target="_blank" isUnderline color="#06f">{row.title}</Link> {:else if col.slot == 'image'} <img src={row.image} style="height: 50px; width: 50px;" alt="" /> {:else if col.slot == 'summary'} <Tooltip content={row.summary} placement="top-start"> <div class="clamp2">{row.summary}</div> </Tooltip> {:else if col.slot == 'role'} {#if row.role == 'admin'} <Tag type="success" effect="dark" size="mini">{row.role}</Tag> {:else if row.role == 'test'} <Tag type="primary" size="mini">{row.role}</Tag> {:else} <Tag type="warning" effect="dark" size="mini">{row.role}</Tag> {/if} {:else if col.slot == 'topmost'} <Switch checked={row.topmost} activeColor="#ff4079" size="small" /> {:else if col.slot == 'progress'} <Progress percent={row.progress} color="#ffa222" showtext="false" strokeWidth={10} style="width: 100px;" /> {:else if col.slot == 'btns'} <Link type="primary" icon="sv-icon-attention_light" gap="5" on:click={handleTableView}>檢視</Link> <Link type="success" icon="sv-icon-edit" gap="5" on:click={handleTableEdit(row)}>編輯</Link> <Link type="danger" icon="sv-icon-delete" gap="5" on:click={handleTableDel(row)}>刪除</Link> {/if} </Table> <Pagination layout="total, sizes, prev, pager, next, jumper" currentPage={curpage} pageSize={limit} pageSizes={[10, 50, 100]} total="500" size="mini" position="center" on:changePage={handleChangePage} on:changeSize={handleChangeSize} />
Ok,基於svelte+svelteUI開發後臺管理系統就分享到這裡,希望對大家有所幫助~~
最後附上一個svelte.js網頁聊天範例專案
https://www.cnblogs.com/xiaoyan2017/p/16272097.html