Svelte Ui Admin後台管理系統|svelte3+svelteUI中後台前端解決方案
- 2022 年 9 月 16 日
- 筆記
- Svelte+SvelteKit實例, svelte-admin, svelte後台管理, Svelte Ui Admin管理系統, SvelteAdmin管理後台, Svelte-Admin後台框架
基於svelte3.x+svelteKit+svelte-ui網頁後台管理系統SvelteAdmin。
Svelte-Ui-Admin 基於svelte3.x+svelteKit+vite3+echarts搭配使用Svelte UI組件庫開發的一套輕量級前端中後台管理系統解決方案。Svelte Ui Admin遵循Svelte Ui組件設計和開發規範,高顏值的組件讓整體風格細膩統一。

技術框架
- 開發工具:Vscode
- 框架技術:svelte3.x+svelteKit+vite3
- UI組件庫:svelte-ui (基於svelte自定義pc端UI組件庫)
- 樣式處理:sass^1.54.4
- 圖表組件:echarts^5.3.3
- 編輯器組件:wangeditor^4.7.15
- 國際化方案:svelte-i18n^3.4.0
- 數據模擬:mockjs^1.1.0

功能特性
- 最新前端技術棧Svelte3、SvelteKit、Vite3、SvelteUI、Svelte-i18n、Echarts5.x、MockJs。
- 支援中文/英文/繁體多語言解決方案。
- 支援表格單選/多選、邊框/隔行換色、橫向/縱向虛擬滾動條等功能。
- 整體搭配高顏值的Svelte Ui組件庫,風格更加統一。
- 高效率開發,整個框架已經搭建完畢,只需新增相應模組即可。
- 動態路由及菜單聯動控制。


項目目錄結構
整體遵循標準的svelteKit項目結構目錄及編碼規範。

效果預覽



















Svelte Ui組件庫
基於svelte3.x開發的桌面pc端UI組件庫SvelteUI。超過30+組件,覆蓋了大多數組件應用場景,遵循svelte.js語法規範開發。


通過如下方式快速引入組件。
import {
Button,
Input,
Radio,
Select,
Checkbox,
...
} from 'svelte-ui'
具體的用法及介紹,可以去看看下面的這篇分享文章。
//www.cnblogs.com/xiaoyan2017/p/16585254.html
Svelte Ui Admin頁面布局結構
項目公共布局模板+layout.svelte,錯誤頁+error.svelte。

+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>
自定義路由菜單Menu


如上圖:使用了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: '//svelte.dev/', path: '//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: [ ... ] }, ] } }

svelte-i18n國際化解決方案
項目中採用svelte-i18n實現國際化,支援中文/英文/繁體字三種語言。

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() } } }
svelte動態圖表Hooks
由於項目中多個地方使用了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="//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網頁聊天實例項目
//www.cnblogs.com/xiaoyan2017/p/16272097.html


