views
├── home
│ └── index.vue
- 组件名称统一用 PascalCase 法命名,多个单词首字母大写
<template> <AppProvider> <RouterView class="bg-layout" /> </AppProvider> </template>
- iconify 图标组件名称统一用 kebab-case 法命名,多个单词用中划线连接
<template> <icon-mdi-emoticon /> </template>
方便iconify插件直接展示图标
function Person() {
}
class Person {
}
type Person = {
name: string;
};
interface Person {
name: string;
}
let num: number = 1;
function getNum() {
}
const MAX_COUNT = 10;
.container {
}
.container-item {
}
本地开发推荐使用Chrome 90+
浏览器
支持现代浏览器, 不支持 IE
![]() |
![]() |
![]() |
![]() |
![]() |
---|---|---|---|---|
not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
2 nodejs v18 以上,建议使nodejs用多版本管理器 npm 使用方法 谷歌 百度搜一些,或者参照:https://blog.csdn.net/qq_41904629/article/details/123552090
确保你的环境满足以下要求:
- git: 你需要git来克隆和管理项目版本。
- NodeJS: >=18.0.0,推荐 18.19.0 或更高。
- pnpm: >= 8.0.0,推荐最新版本。
- Auto Close Tag - 自动添加 HTML/XML 结束标签
- Auto Complete Tag - 为 HTML/XML 添加关闭标签和自动重命名成对的标签
- Auto Rename Tag - 自动重命名成对的 HTML/XML 标签
- Color Highlight - 颜色高亮插件
- DotENV - 高亮.env 文件
- EditorConfig for VS Code - 统一不同编辑器的一些配置
- ESLint - 代码检查
- Git Graph - Git 图形化操作工具
- GitLens — Git supercharged - 显示具体某行代码的 git 信息
- Icônes - 搜索 iconify 图标的插件
- Iconify IntelliSense - Iconify 图标实时显示的插件
- i18n Ally - i18n 国际化插件
- javascript console utils - 提供快捷键 ctrl+l 直接输入 console.log()
- Material Icon Theme - 图标主题,显示文件和文件多种图标
- One Dark Pro - 主题
- Prettier - Code formatter - 代码格式化插件
- UnoCSS - unocss 写法提示插件
- Vue Language Features (Volar) - Vue 服务插件
- TypeScript Vue Plugin (Volar) - Vue 的 TS 服务插件
- Vue VSCode Snippets - vue2、vue3 写法提示
ThingsPanel
├── .vscode //vscode插件和设置
│ ├── extensions.json //vscode推荐的插件
│ ├── launch.json //debug配置文件(debug Vue 和 TS)
│ └── settings.json //vscode配置(在该项目中生效,可以复制到用户配置文件中)
├── build //vite构建相关配置和插件
│ ├── config //构建打包配置
│ │ └── proxy.ts //网络请求代理
│ └── plugins //构建插件
│ ├── index.ts.432432 //插件汇总
│ ├── router.ts //elegant-router插件
│ ├── unocss.ts //unocss插件
│ └── unplugin.ts //自动导入UI组件、自动解析iconify图标、自动解析本地svg作为图标
├── packages //子项目
│ ├── axios //网络请求封装
│ ├── color-palette //颜色调色板
│ ├── hooks //组合式函数hooks
│ ├── materials //组件物料
│ ├── ofetch //网络请求封装
│ ├── scripts //脚本
│ ├── uno-preset //uno-preset配置
│ └── utils //工具函数
├── public //公共目录(文件夹里面的资源打包后会在根目录下)
│ └── favicon.svg //网站标签图标
├── src
│ ├── assets //静态资源
│ │ ├── imgs //图片
│ │ └── svg-icon //本地svg图标
│ ├── components //全局组件
│ │ ├── advanced //高级组件
│ │ ├── common //公共组件
│ │ └── custom //自定义组件
│ ├── constants //常量
│ │ ├── app.ts //app常量
│ │ ├── business.ts //业务常量
│ │ ├── common.ts //通用常量
│ │ └── reg.ts //正则常量
│ ├── enums //枚举
│ ├── hooks //组合式的函数hooks
│ │ ├── chart //图表
│ │ │ └── use-echarts //echarts
│ │ └── common //通用hooks
│ │ ├── form //表单
│ │ ├── router //路由
│ │ └── table //表格
│ ├── layouts //布局组件
│ │ ├── base-layout //基本布局(包含全局头部、多页签、侧边栏、底部等公共部分)
│ │ ├── blank-layout //空白布局组件(单个页面)
│ │ ├── hooks //布局组件的hooks
│ │ └── modules //布局组件模块
│ │ ├── global-breadcrumb //全局面包屑
│ │ ├── global-content //全局主体内容
│ │ ├── global-footer //全局底部
│ │ ├── global-header //全局头部
│ │ ├── global-logo //全局Logo
│ │ ├── global-menu //全局菜单
│ │ ├── global-sider //全局侧边栏
│ │ ├── global-tab //全局标签页
│ │ └── theme-drawer //主题抽屉
│ ├── locales //国际化配置
│ │ ├── langs //语言文件
│ │ ├── dayjs.ts //dayjs的国际化配置
│ │ ├── locale.ts //语言文件汇总
│ │ └── naive.ts //NaiveUI的国际化配置
│ ├── plugins //插件
│ │ ├── assets.ts //各种依赖的静态资源导入(css、scss等)
│ │ ├── dayjs.ts //dayjs插件
│ │ ├── iconify.ts //iconify插件
│ │ ├── loading.ts //全局初始化时的加载插件
│ │ └── nprogress.ts //顶部加载条nprogress插件
│ ├── router //vue路由
│ │ ├── elegant //elegant-router插件生成的路由声明、导入和转换等文件
│ │ ├── guard //路由守卫
│ │ ├── routes //路由声明入口
│ │ └── index.ts.432432 //路由入口
│ ├── service //网络请求
│ │ ├── api //接口api
│ │ └── request //封装的请求函数
│ ├── store //pinia状态管理
│ │ ├── modules //状态管理划分的模块
│ │ │ ├── app //app状态(页面重载、菜单折叠、项目配置的抽屉)
│ │ │ ├── auth //auth状态(用户信息、用户权益)
│ │ │ ├── route //route状态(动态路由、菜单、路由缓存)
│ │ │ ├── tab //tab状态(多页签、缓存页面的滚动位置)
│ │ │ └── theme //theme状态(项目主题配置)
│ │ └── plugins //状态管理插件
│ ├── styles //全局样式
│ │ ├── css //css
│ │ └── scss //scss
│ ├── theme //主题配置
│ │ ├── settings.ts //主题默认配置及覆盖配置
│ │ └── vars.ts //主题token对应的css变量
│ ├── typings //TS类型声明文件(*.d.ts)
│ │ ├── api.d.ts //请求接口返回的数据的类型声明
│ │ ├── app.d.ts //应用相关的类型声明
│ │ ├── common.d.ts //通用类型声明
│ │ ├── components.d.ts //自动导入的组件的类型声明
│ │ ├── elegant-router.d.ts//插件elegant-router生成的路由声明
│ │ ├── env.d.ts //vue路由描述和请求环境相关的类型声明
│ │ ├── global.d.ts //全局通用类型
│ │ ├── naive-ui.d.ts //NaiveUI类型
│ │ ├── router.d.ts //Vue的路由描述的类型声明
│ │ ├── storage.d.ts //本地缓存的数据类型
│ │ └── union-key.d.ts //联合类型
│ ├── utils //全局工具函数(纯函数,不含状态)
│ │ ├── common //通用工具函数
│ │ ├── icon //图标相关工具函数
│ │ └── storage //存储相关工具函数
│ ├── views //页面
│ │ ├── _builtin //系统内置页面:登录、异常页等
│ │ ├── about //关于
│ │ ├── function //功能
│ │ ├── home //首页
│ │ ├── manage //系统管理
│ │ ├── multi-menu //多级菜单
│ │ └── user-center //用户中心
│ ├── App.vue //vue文件入口
│ └── main.ts //项目入口ts文件
├── .editorconfig //统一编辑器配置
├── .env //环境文件
├── .env.development //开发环境的环境文件
├── .env.production //生产环境的环境文件
├── .eslintignore //忽略eslint检查的配置文件
├── .gitignore //忽略git提交的配置文件
├── .npmrc //npm配置
├── env-config.ts //请求环境的配置文件
├── eslint.config.js //eslint flat配置文件
├── index.html //html文件
├── package.json //npm依赖描述文件
├── pnpm-lock.yaml //npm包管理器pnpm依赖锁定文件
├── README.md //项目介绍文档
├── README.zh-CN.md //项目介绍文档(中文)
├── tsconfig.json //TS配置
├── uno.config.ts //原子css框架unocss配置
└── vite.config.ts //vite配置
在src/views创建页面文件即可,如果不想生成路由或类型的文件,请使用 modules创建文件夹,而不是 components
请尽量使用 https://apifox.com/ ,在 env.config.ts,修改 const mockURL = 'https://mock.apifox.com/m1/4080832-0-default';
01 国际化类型:src/typings/app.d.ts type(App.I18n.Schema)
02 接口类型:src/typings/api.d.ts
03 路由类型:src/typings/elegant-router.d.ts
04 RouteMeta类型:src/typings/elegant-router.d.ts
05 全局类型:src/typings/elegant-router.d.ts
06 缓存数据类型:src/typings/storage.d.ts 缓存的数据类型需要预先在 src/typings/storage.d.ts 里面定义好
基本不用动 的类型
07 组件类型:src/typings/components.d.ts 不要动,只要在views里 基本都是自己生成的
08 naive-ui类型:src/typings/naive-ui.d.ts
基本不会用的类型
09 package类型:src/typings/naive-ui.d.ts 作者有个 package 二次封装了一些库的功能,跟这个有关,不明白就不动即可
10 环境类型:src/typings/env.d.ts 尽量不动 ,估计业务层开发也用不上
11 联合密钥:src/typings/union-key.d.ts 作者用的一些类型吧,应该用不上
本系统的路由基于插件 Elegant Router,详细用法请查看插件文档。
views
├── about
│ └── index.vue
{
name: 'about',
path
:
'/about',
component
:
'layout.base$view.about',
meta
:
{
title: 'about'
}
}
,
{
path: '/about',
component
:
BaseLayout,
children
:
[
{
name: 'about',
path: '',
component: () => import('@/views/about/index.vue'),
meta: {
title: 'about'
}
}
]
}
,
views
├── list
│ ├── home
│ │ └── index.vue
│ ├── detail
│ │ └── index.vue
views
├── list
│ ├── index.vue
│ ├── detail
│ │ └── index.vue
请不要出现上述 index.vue 和文件夹同级的情况,这种情况不在约定的规则中
{
name: 'list',
path
:
'/list',
component
:
'layout.base',
meta
:
{
title: 'list'
}
,
children: [
{
name: 'list_home',
path: '/list/home',
component: 'view.list_home',
meta: {
title: 'list_home'
}
},
{
name: 'list_detail',
path: '/list/detail',
component: 'view.list_detail',
meta: {
title: 'list_detail'
}
}
]
}
{
name: 'list',
path
:
'/list',
component
:
BaseLayout,
redirect
:
{
name: 'list_home'
}
,
meta: {
title: 'list'
}
,
children: [
{
name: 'list_home',
path: '/list/home',
component: () => import('@/views/list/home/index.vue'),
meta: {
title: 'list_home'
}
},
{
name: 'list_detail',
path: '/list/detail',
component: () => import('@/views/list/detail/index.vue'),
meta: {
title: 'list_detail'
}
}
]
}
,
views
├── multi-menu
│ ├── first
│ │ ├── child
│ │ │ └── index.vue
│ ├── second
│ │ ├── child
│ │ │ ├── home
│ │ │ │ └── index.vue
views
├── multi-menu
│ ├── first_child
│ │ └── index.vue
│ ├── second_child_home
│ │ └── index.vue
{
name: 'multi-menu',
path
:
'/multi-menu',
component
:
'layout.base',
meta
:
{
title: 'multi-menu'
}
,
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
meta: {
title: 'multi-menu_first'
},
children: [
{
name: 'multi-menu_first_child',
path: '/multi-menu/first/child',
component: 'view.multi-menu_first_child',
meta: {
title: 'multi-menu_first_child'
}
}
]
},
{
name: 'multi-menu_second',
path: '/multi-menu/second',
meta: {
title: 'multi-menu_second'
},
children: [
{
name: 'multi-menu_second_child',
path: '/multi-menu/second/child',
meta: {
title: 'multi-menu_second_child'
},
children: [
{
name: 'multi-menu_second_child_home',
path: '/multi-menu/second/child/home',
component: 'view.multi-menu_second_child_home',
meta: {
title: 'multi-menu_second_child_home'
}
}
]
}
]
}
]
}
{
name: 'multi-menu',
path
:
'/multi-menu',
component
:
BaseLayout,
redirect
:
{
name: 'multi-menu_first'
}
,
meta: {
title: 'multi-menu'
}
,
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
redirect: {
name: 'multi-menu_first_child'
},
meta: {
title: 'multi-menu_first'
}
},
{
name: 'multi-menu_first_child',
path: '/multi-menu/first/child',
component: () => import('@/views/multi-menu/first_child/index.vue'),
meta: {
title: 'multi-menu_first_child'
}
},
{
name: 'multi-menu_second',
path: '/multi-menu/second',
redirect: {
name: 'multi-menu_second_child'
},
meta: {
title: 'multi-menu_second'
},
},
{
name: 'multi-menu_second_child',
path: '/multi-menu/second/child',
redirect: {
name: 'multi-menu_second_child_home'
},
meta: {
title: 'multi-menu_second_child'
},
},
{
name: 'multi-menu_second_child_home',
path: '/multi-menu/second/child/home',
component: () => import('@/views/multi-menu/second_child_home/index.vue'),
meta: {
title: 'multi-menu_second_child_home'
}
}
]
}
views
├── _error
│ ├── 403
│ │ └── index.vue
│ ├── 404
│ │ └── index.vue
│ ├── 500
│ │ └── index.vue
{
name: '403',
path
:
'/403',
component
:
'layout.base$view.403',
meta
:
{
title: '403'
}
}
,
{
name: '404',
path
:
'/404',
component
:
'layout.base$view.404',
meta
:
{
title: '404'
}
}
,
{
name: '500',
path
:
'/500',
component
:
'layout.base$view.500',
meta
:
{
title: '500'
}
}
views
├── user
│ └── [id].vue
{
name: 'user',
path
:
'/user/:id',
component
:
'layout.base$view.user',
props
:
true,
meta
:
{
title: 'user'
}
}
import type {RouteKey} from "@elegant-router/types";
ElegantVueRouter({
routePathTransformer(routeName, routePath) {
const routeKey = routeName as RouteKey;
if (routeKey === "user") {
return "/user/:id(\\d+)";
}
return routePath;
},
});
自定义路由只用于生成路由声明,不会生成路由文件,需要手动创建路由文件
ElegantVueRouter({
customRoutes: {
map: {
root: "/",
notFound: "/:pathMatch(.*)*",
},
names: ["two-level_route"],
},
});
type RouteMap = {
root: "/";
notFound: "/:pathMatch(.*)*";
"two-level": "/two-level";
"two-level_route": "/two-level/route";
};
type CustomRouteKey = "root" | "notFound" | "two-level" | "two-level_route";
import type {CustomRoute} from "@elegant-router/types";
const customRoutes: CustomRoute[] = [
{
name: "root",
path: "/",
redirect: {
name: "403",
},
},
{
name: "not-found",
path: "/:pathMatch(.*)*",
component: "layout.base$view.404",
},
{
name: "two-level",
path: "/two-level",
component: "layout.base",
children: [
{
name: "two-level_route",
path: "/two-level/route",
component: "view.about",
},
],
},
];
-
layout.base: 具有公共部分的布局,如全局头部、侧边栏、底部等
-
layout.blank: 空白布局
- view.[RouteKey]: 页面组件
例如:
view.home
,view.multi-menu_first_child
- layout.base$view.[RouteKey]: 布局和页面的混合组件
例如:
layout.base$view.home
,layout.base$view.multi-menu_first_child
::: tip 提示 该类型组件表示单级路由 :::
解释:
联合类型 RouteKey 声明所有的路由 key,方便统一管理路由, 该类型由插件 Elegant Router 根据 views 下面的页面文件自动生成
::: tip 代码位置 src/typings/elegant-router.d.ts :::
解释:
路由的路径 path,该类型与 RouteKey 一一对应
// 路由元信息接口
interface RouteMeta {
/**
* 路由标题
*
* 可用于文档标题中
*/
title: string;
/**
* 路由的国际化键值
*
* 如果设置,将用于i18n,此时title将被忽略
*/
i18nKey?: App.I18n.I18nKey;
/**
* 路由的角色列表
*
* 当前用户拥有至少一个角色时,允许访问该路由,角色列表为空时,表示无需权限
*/
roles?: string[];
/** 是否缓存该路由 */
keepAlive?: boolean;
/**
* 是否为常量路由
*
* 无需登录,并且该路由在前端定义
*/
constant?: boolean;
/**
* Iconify 图标
*
* 可用于菜单或面包屑中
*/
icon?: string;
/**
* 本地图标
*
* 存在于 "src/assets/svg-icon" 目录下,如果设置,将忽略icon属性
*/
localIcon?: string;
/** 路由排序顺序 */
order?: number;
/** 路由的外部链接 */
href?: string;
/** 是否在菜单中隐藏该路由 */
hideInMenu?: boolean;
/**
* 进入该路由时激活的菜单键
*
* 该路由不在菜单中
*
* @example
* 假设路由是"user_detail",如果设置为"user_list",则会激活"用户列表"菜单项
*/
activeMenu?: import('@elegant-router/types').RouteKey;
/** 默认情况下,相同路径的路由会共享一个标签页,若设置为true,则使用多个标签页 */
multiTab?: boolean;
/** 若设置,路由将在标签页中固定显示,其值表示固定标签页的顺序 */
fixedIndexInTab?: number;
}
::: tip 提示 icon 图标值从这里获取:https://icones.js.org/ :::
{
name: '403',
path
:
'/403',
component
:
'layout.blank$view.403',
meta
:
{
title: '403',
i18nKey
:
'route.403',
hideInMenu
:
true
}
}
基于 iconify 的 svg 的 json 数据,通过 unplugin-icons 插件,将 svg 数据转换成 vue 组件
-
iconify
-
安装 vscode 智能提示的插件: Iconify IntelliSense
-
找图标:网址 https://icones.js.org/ 或者 vscode 安装 - Icônes
-
确定图标名字:找到图标后复制名字 如:'mdi:emoticon' 或者 'mdi-emoticon',则对应的 vue 的 template 为
<div> <icon-mdi-emoticon class="text-24px text-red" /> <icon-mdi:emoticon style="font-size:24px;color:#f00;" /> </div>
::: tip 提示 'icon-' 为预设的前缀, 在.env 里面设置变量 VITE_ICON_PREFFIX :::
-
设置样式:同 html 标签一样直接应用 style 属性或者 class 属性; 通过设置 color 和 font-size 属性设置对应的颜色和大小
-
-
本地 svg 图标
-
在 src/assets/svg-icon 目录下选择一个 svg,取它的文件名,例如: 'custom-icon.svg'
-
则对应的 vue 的 template 为
<icon-local-custom-icon class="text-24px text-red" />
::: tip 提示 'icon-local' 为预设的前缀, 在.env 里面设置变量 VITE_ICON_LOCAL_PREFFIX :::
-
-
iconify
-
确定图标名字,如:'mdi-emoticon'
-
动态渲染
<svg-icon icon="mdi-emoticon" />
-
多个图标动态渲染
<svg-icon v-for="icon in icons" :key="icon" :icon="icon" class="text-24px text-red" />
-
-
本地 svg 图标
-
确定 svg 文件名,例如: 'custom-icon.svg'
-
动态渲染
<svg-icon local-icon="custom-icon" style="font-size:24px;color:#f00;" />
::: tip 提示 svg-icon 为全局组件,已经注册过了,直接在 template 中应用,icon 属性为 iconify 图标名称, local-icon 为本地 svg 图标的文件名 :::
-
-
确定图标名字,如:iconify: 'mdi-emoticon', 或者本地 svg 图标 'custom-icon.svg'
-
使用 useSvgIconRender
::: tip 代码位置 packages/hooks/src/use-svg-icon-render.ts :::
import { useSvgIconRender } from '@sa/hooks'; import SvgIcon from '@/components/custom/svg-icon.vue'; const { SvgIconVNode } = useSvgIconRender(SvgIcon); SvgIconVNode({ icon: 'ant-design:close-outlined', fontSize: 18 }); // iconify SvgIconVNode({ localIcon: "custom-icon" }); // 本地svg图标
-
系统主题的实现分为两个部分,一部分是组件库的主题配置,另一部分是 UnoCSS 的主题配置。为了统一两个部分的主题配置,在这之上维护了一些主题配置,通过这些主题配置分别控制组件库和 UnoCSS 的主题配置。
- 定义一些主题配置的变量,包括各种主题颜色,布局的参数配置等
- 通过这些配置产出符合组件库的主题变量
- 通过这些配置产出一些主题 tokens 并衍生出对应的 css 变量,再将这些 css 变量传递给 UnoCSS
见 App.Theme.ThemeSetting
::: tip 代码位置 src/typings/app.d.ts :::
export const themeSettings: App.Theme.ThemeSetting = {
//默认配置
}
::: tip 代码位置 src/theme/settings.ts :::
当发布新的版本时,可以通过配置覆盖更新的方式,来更新主题配置
export const overrideThemeSettings: Partial<App.Theme.ThemeSetting> = {
//覆盖配置
};
::: tip 代码位置 src/theme/settings.ts :::
-
当项目处于
开发模式
时,主题配置不会被缓存,可以通过更新src/theme/settings.ts
中的themeSettings
来更新主题配置开发阶段为了能够实时看到主题配置的变化,所以不会缓存主题配置
-
当项目处于
生产模式
时,主题配置会被缓存到 localStorage 中每次发布新版本,可以通过更新
src/theme/settings.ts
中的overrideThemeSettings
来覆盖更新主题配置
根据主题颜色产出组件库的主题变量
/**
* Get naive theme
*
* @param colors Theme colors
*/
function getNaiveTheme(colors: App.Theme.ThemeColor) {
const {primary: colorLoading} = colors;
const theme: GlobalThemeOverrides = {
common: {
...getNaiveThemeColors(colors)
},
LoadingBar: {
colorLoading
}
};
return theme;
}
/** Naive theme */
const naiveTheme = computed(() => getNaiveTheme(themeColors.value));
::: tip 代码位置 src/store/modules/theme/shared.ts
src/store/modules/theme/index.ts.432432 :::
应用主题变量
<template>
<NConfigProvider
:theme="naiveDarkTheme"
:theme-overrides="themeStore.naiveTheme"
:locale="naiveLocale"
:date-locale="naiveDateLocale"
class="h-full"
>
<AppProvider>
<RouterView class="bg-layout"/>
</AppProvider>
</NConfigProvider>
</template>
::: tip 代码位置 src/App.vue :::
根据主题颜色产出组件库的主题变量
/**
* Get antd theme
*
* @param colors Theme colors
* @param darkMode Is dark mode
*/
function getAntdTheme(colors: App.Theme.ThemeColor, darkMode: boolean) {
const {defaultAlgorithm, darkAlgorithm} = antdTheme;
const {primary, info, success, warning, error} = colors;
const theme: ConfigProviderProps['theme'] = {
token: {
colorPrimary: primary,
colorInfo: info,
colorSuccess: success,
colorWarning: warning,
colorError: error
},
algorithm: [darkMode ? darkAlgorithm : defaultAlgorithm],
components: {
Menu: {
colorSubItemBg: 'transparent'
}
}
};
return theme;
}
/** Antd theme */
const antdTheme = computed(() => getAntdTheme(themeColors.value, darkMode.value));
::: tip 代码位置 src/store/modules/theme/shared.ts
src/store/modules/theme/index.ts.432432 :::
应用主题变量
<template>
<ConfigProvider :theme="themeStore.antdTheme" :locale="antdLocale">
<AppProvider>
<RouterView class="bg-layout"/>
</AppProvider>
</ConfigProvider>
</template>
type ThemeToken = {
colors: ThemeTokenColor;
boxShadow: {
header: string;
sider: string;
tab: string;
};
};
::: tip 代码位置 src/typings/app.d.ts :::
初始化时会在 html 上生成一些 css 变量,这些 css 变量是基于主题 tokens 产出的
/** Theme vars */
export const themeVars: App.Theme.ThemeToken = {
colors: {
...colorPaletteVars,
nprogress: 'rgb(var(--nprogress-color))',
container: 'rgb(var(--container-bg-color))',
layout: 'rgb(var(--layout-bg-color))',
inverted: 'rgb(var(--inverted-bg-color))',
base_text: 'rgb(var(--base-text-color))'
},
boxShadow: {
header: 'var(--header-box-shadow)',
sider: 'var(--sider-box-shadow)',
tab: 'var(--tab-box-shadow)'
}
};
::: tip 代码位置 src/theme/vars.ts :::
通过上述的 themeVars
注入到 UnoCSS 的主题配置中
import {themeVars} from './src/theme/vars';
export default defineConfig<Theme>({
theme: {
...themeVars,
}
});
这样,借助于 UnoCSS 的能力,可以使用类似 text-primary bg-primary
等 class 名称进而统一了组件库和 UnoCSS 的主题颜色的应用。
::: tip 代码位置 ./uno.config.ts :::
通过 UnoCSS 提供的预设暗黑模式方案, 只要在 html 上添加 class="dark",则项目中类似于 dark:text-#000 dark:bg-#333
的
class 就会生效,从而达到暗黑模式的效果
export default defineConfig<Theme>({
presets: [presetUno({dark: "class"})],
});
::: tip 代码位置 ./uno.config.ts :::
-
系统初始化时的加载样式通过html代码方式实现
::: tip 组件位置 src/plugins/loading.ts :::
-
系统的 Logo 使用 SystemLogo 组件实现
创建 setupLoading 函数, 它的主要功能是设置页面加载时的动画效果。 这个加载动画包括一个系统Logo、旋转的点阵动画和标题文字,并且所有元素的颜色均基于从本地存储获取的主题色 themeColor 动态生成。 并且在DOM中查找ID为app的元素作为加载动画的挂载点, 如果找到了这个元素,则将其内部HTML替换为刚刚构建的加载动画HTML结构
export function setupLoading() {
const themeColor = localStg.get('themeColor') || '#DB5A6B';
const {r, g, b} = getRgbOfColor(themeColor);
const primaryColor = `--primary-color: ${r} ${g} ${b}`;
const loadingClasses = [
'left-0 top-0',
'left-0 bottom-0 animate-delay-500',
'right-0 top-0 animate-delay-1000',
'right-0 bottom-0 animate-delay-1500'
];
const logoWithClass = systemLogo.replace('<svg', `<svg class="size-128px text-primary"`);
const dot = loadingClasses
.map(item => {
return `<div class="absolute w-16px h-16px bg-primary rounded-8px animate-pulse ${item}"></div>`;
})
.join('\n');
const loading = `
<div class="fixed-center flex-col" style="${primaryColor}">
${logoWithClass}
<div class="w-56px h-56px my-36px">
<div class="relative h-full animate-spin">
${dot}
</div>
</div>
<h2 class="text-28px font-500 text-#646464">${$t('system.title')}</h2>
</div>`;
const app = document.getElementById('app');
if (app) {
app.innerHTML = loading;
}
}
::: tip 代码位置 src/plugins/loading.ts :::
最后要将 setupLoading 函数注册到 main.ts 中
async function setupApp() {
setupLoading();
app.mount('#app');
}
A:还好,挺开心的
的项目配置默认是 localStorage , 初始化时对项目的主题涉及的数据进行持久化
项目的缓存分为两方面
LocalStorage SessionStorage 缓存要点
对于本框架缓存方面的使用主要集中在下列几个方法中: set:通过给方法传递必填参数 key 、value 和可选参数 expire 对数据进行缓存 get:通过给方法传递必填参数 key 获取缓存的数据 remove:通过给方法传递必填参数 key 移除指定的缓存数据 clear:通过调用该方法,清除当前所有的 Storage 相关的缓存数据 缓存的数据类型需要预先在 src/typings/storage.d.ts 里面定义好
- 当修改
.env
等环境文件及vite.config.ts
文件时,vite 会自动重启服务。
但是自动重启有几率出现问题,请重新运行项目即可解决。
- 当修改
.vue
或者.ts
时, vite 进行热部署时有几率造成页面卡顿导致无法看到
实时修改的效果,
F5
刷新即可解决
问题背景
项目初始化路由时,该同学的顶级路由数据 meta 中含有 hideInMenu
属性为 true
所以菜单和页面都无法显示出来
::: tip 组件位置 src/typings/router.d.ts :::
跳转查看 RouteMeta
解决方案:
去除 hideInMenu
属性即可正常显示菜单和页面
问题背景
项目中的权限路由模式分为:
-
静态路由
静态路由指的是前端项目:
src/router/routes.ts
中的路由数据 项目能够根据在这个路径下定义进行路由数据的解析,并自动渲染出菜单信息 -
动态路由
动态路由指的是后台项目传递过来的路由数据
项目使用动态路由模式进行数据渲染时,会自动覆盖路由首页的 name 值
这是由于开启了路由切换动画,且对应的页面组件存在多个根元素时导致的,
可以通过在页面最外层添加一个 <div></div>
( 或者) 即可
❌ 错误示范
<template>
<!-- 注释也算一个标签节点哦 -->
<p1></p1>
<p2></p2>
</template>
✔ 正确示范
<template>
<div>
<p1></p1>
<p2></p2>
</div>
</template>
命名规范
- 文件命名: 统一用小写字母命名,多个单词用中划线连接
views
├── home
├── demo-page
-
Vue 组件名称
- 组件名称统一用 PascalCase 法命名,多个单词首字母大写
<template> <AppProvider> <RouterView class="bg-layout" /> </AppProvider> </template>
- iconify 图标组件名称统一用 kebab-case 法命名,多个单词用中划线连接
<template> <icon-mdi-emoticon /> </template>
方便iconify插件直接展示图标
-
构造函数、class 类、TS 类型命名:统一用 PascalCase 法命名,多个单词首字母大写
function Person() {
}
class Person {
}
type Person = {
name: string;
};
interface Person {
name: string;
}
- 变量、普通函数命名:统一用 camelCase 法命名,多个单词首字母小写
let num: number = 1;
function getNum() {
}
- 常量命名:统一用大写字母命名,多个单词用下划线连接
const MAX_COUNT = 10;
- 样式的命名:统一用小写字母命名,多个单词用中划线连接
.container {
}
.container-item {
}
问题背景
整个项目都是单页面应用,所以从路径里去加载不同的 HTML 本身就不支持,要么创建多页面应用,要么在单页面应用里通过 iframe 去加载其它的 HTML。
解决方案
集成 vite-plugin-mpa 插件。