基于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'
具体的用法及介绍,可以去看看下面的这篇分享文章。
https://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: '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: [ ... ] }, ] } }
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="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