mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-04-05 19:42:07 +08:00
v4.0.1
This commit is contained in:
parent
5d4e23149d
commit
0411ec7277
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,19 @@
|
||||
# CHANGE LOG
|
||||
|
||||
## 4.0.1
|
||||
|
||||
### Feats
|
||||
|
||||
- 更改自定义路由暴露形式(由变量暴露改为方法获取)
|
||||
- 模板所有方法进行检查,重命名部分方法(使其更加贴切其逻辑)
|
||||
- 部分逻辑进行重写,使代码更容易阅读与维护
|
||||
- 模板类型进一步完善
|
||||
|
||||
### Fixes
|
||||
|
||||
- 修复了内存高占用问题(路由模块)
|
||||
- 修复类型导入错误问题
|
||||
|
||||
## 4.0.0
|
||||
|
||||
### Feats
|
||||
|
6
cfg.ts
6
cfg.ts
@ -18,7 +18,7 @@
|
||||
* - 系统: 根路由、标题、浏览器标题、别名等
|
||||
* - 请求: 代理配置
|
||||
*
|
||||
* 如果需要新增相关内容, 需要在 src/types/cfg.ts 中进行类型配置
|
||||
* 如果需要新增相关内容, 需要在 src/types/modules/cfg.ts 中进行类型配置
|
||||
* ```
|
||||
* interface Config // config 内容类型配置
|
||||
*
|
||||
@ -43,7 +43,7 @@ import {
|
||||
buildOptions,
|
||||
mixinCSSPlugin,
|
||||
} from './vite-plugin/index'
|
||||
import { APP_PRIMARY_COLOR } from './src/appConfig/designConfig'
|
||||
import { APP_THEME } from './src/appConfig/designConfig'
|
||||
import {
|
||||
PRE_LOADING_CONFIG,
|
||||
ROOT_ROUTE,
|
||||
@ -58,7 +58,7 @@ const config: AppConfigExport = {
|
||||
/** 配置首屏加载信息 */
|
||||
preloadingConfig: PRE_LOADING_CONFIG,
|
||||
/** 默认主题色(不可省略, 必填), 也用于 ejs 注入 */
|
||||
appPrimaryColor: APP_PRIMARY_COLOR,
|
||||
appPrimaryColor: APP_THEME.APP_PRIMARY_COLOR,
|
||||
sideBarLogo: SIDE_BAR_LOGO,
|
||||
/**
|
||||
*
|
||||
|
@ -16,10 +16,7 @@ import type {
|
||||
PreloadingConfig,
|
||||
RootRoute,
|
||||
} from '@/types/modules/cfg'
|
||||
import type {
|
||||
MenuCollapsedConfig,
|
||||
AppKeepAlive,
|
||||
} from '@/types/modules/appConfig'
|
||||
import type { AppMenuConfig, AppKeepAlive } from '@/types/modules/appConfig'
|
||||
|
||||
/**
|
||||
*
|
||||
@ -80,21 +77,18 @@ export const SIDE_BAR_LOGO: LayoutSideBarLogo = {
|
||||
* MENU_COLLAPSED_MODE:
|
||||
* - transform: 边栏将只会移动它的位置而不会改变宽度
|
||||
* - width: Sider 的内容宽度将会被实际改变
|
||||
*
|
||||
* MENU_COLLAPSED_ICON_SIZE 配置菜单未折叠时图标的大小
|
||||
*
|
||||
* MENU_COLLAPSED_INDENT 配置菜单每级的缩进
|
||||
* MENU_ACCORDION 手风琴模式
|
||||
*/
|
||||
export const MENU_COLLAPSED_CONFIG: Readonly<MenuCollapsedConfig> = {
|
||||
export const APP_MENU_CONFIG: Readonly<AppMenuConfig> = {
|
||||
MENU_COLLAPSED_WIDTH: 64,
|
||||
MENU_COLLAPSED_MODE: 'width',
|
||||
MENU_COLLAPSED_ICON_SIZE: 22,
|
||||
MENU_COLLAPSED_INDENT: 24,
|
||||
MENU_ACCORDION: false,
|
||||
}
|
||||
|
||||
/** 是否启用菜单手风琴模式 */
|
||||
export const MENU_ACCORDION = false
|
||||
|
||||
/**
|
||||
*
|
||||
* 系统默认缓存 key 配置
|
||||
|
@ -11,54 +11,53 @@
|
||||
|
||||
/** 系统颜色风格配置入口 */
|
||||
|
||||
import type { AppPrimaryColor } from '@/types/modules/cfg'
|
||||
import type { GlobalThemeOverrides } from 'naive-ui'
|
||||
import type { AppTheme } from '@/types/modules/cfg'
|
||||
|
||||
/**
|
||||
*
|
||||
* 系统主题颜色预设色盘
|
||||
* 支持 RGBA、RGB、十六进制
|
||||
*/
|
||||
export const APP_THEME_COLOR = [
|
||||
'#2d8cf0',
|
||||
'#0960bd',
|
||||
'#536dfe',
|
||||
'#ff5c93',
|
||||
'#ee4f12',
|
||||
'#9c27b0',
|
||||
'#ff9800',
|
||||
'#18A058',
|
||||
]
|
||||
|
||||
/** 系统主题色 */
|
||||
export const APP_PRIMARY_COLOR: AppPrimaryColor = {
|
||||
/** 主题色 */
|
||||
primaryColor: '#2d8cf0',
|
||||
/** 主题辅助色(用于整体 hover、active 等之类颜色) */
|
||||
primaryFadeColor: 'rgba(45, 140, 240, 0.3)',
|
||||
export const APP_THEME: AppTheme = {
|
||||
/**
|
||||
*
|
||||
* 系统主题颜色预设色盘
|
||||
* 支持 RGBA、RGB、十六进制
|
||||
*/
|
||||
APP_THEME_COLOR: [
|
||||
'#2d8cf0',
|
||||
'#0960bd',
|
||||
'#536dfe',
|
||||
'#ff5c93',
|
||||
'#ee4f12',
|
||||
'#9c27b0',
|
||||
'#ff9800',
|
||||
'#18A058',
|
||||
],
|
||||
/** 系统主题色 */
|
||||
APP_PRIMARY_COLOR: {
|
||||
/** 主题色 */
|
||||
primaryColor: '#2d8cf0',
|
||||
/** 主题辅助色(用于整体 hover、active 等之类颜色) */
|
||||
primaryFadeColor: 'rgba(45, 140, 240, 0.3)',
|
||||
},
|
||||
/**
|
||||
*
|
||||
* 配置系统 naive-ui 主题色
|
||||
* 官网文档地址: <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>
|
||||
*
|
||||
* 注意:
|
||||
* - APP_PRIMARY_COLOR common 配置优先级大于该配置
|
||||
*
|
||||
* 如果需要定制化整体组件样式, 配置示例
|
||||
* ```
|
||||
* const themeOverrides: GlobalThemeOverrides = {
|
||||
* common: {
|
||||
* primaryColor: '#FF0000',
|
||||
* },
|
||||
* Button: {
|
||||
* textColor: '#FF0000',
|
||||
* },
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 具体自行查看官网, 还有模式更佳丰富的 peers 主题变量配置
|
||||
* 地址: <https://www.naiveui.com/zh-CN/dark/docs/customize-theme#%E4%BD%BF%E7%94%A8-peers-%E4%B8%BB%E9%A2%98%E5%8F%98%E9%87%8F>
|
||||
*/
|
||||
APP_NAIVE_UI_THEME_OVERRIDES: {},
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 配置系统 naive-ui 主题色
|
||||
* 官网文档地址: <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>
|
||||
*
|
||||
* 注意:
|
||||
* - APP_PRIMARY_COLOR common 配置优先级大于该配置
|
||||
*
|
||||
* 如果需要定制化整体组件样式, 配置示例
|
||||
* ```
|
||||
* const themeOverrides: GlobalThemeOverrides = {
|
||||
* common: {
|
||||
* primaryColor: '#FF0000',
|
||||
* },
|
||||
* Button: {
|
||||
* textColor: '#FF0000',
|
||||
* },
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 具体自行查看官网, 还有模式更佳丰富的 peers 主题变量配置
|
||||
* 地址: <https://www.naiveui.com/zh-CN/dark/docs/customize-theme#%E4%BD%BF%E7%94%A8-peers-%E4%B8%BB%E9%A2%98%E5%8F%98%E9%87%8F>
|
||||
*/
|
||||
export const APP_NAIVE_UI_THEME_OVERRIDES: GlobalThemeOverrides = {}
|
||||
|
@ -15,6 +15,8 @@
|
||||
* 系统公共正则, 配置在该文件中
|
||||
*/
|
||||
|
||||
/** css 尺寸单位匹配 */
|
||||
export const ELEMENT_UNIT =
|
||||
/^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/
|
||||
export const APP_REGEX: Record<string, RegExp> = {
|
||||
/** css 尺寸单位匹配 */
|
||||
validerCSSUnit:
|
||||
/^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/,
|
||||
}
|
||||
|
@ -13,9 +13,6 @@
|
||||
|
||||
import type { LayoutInst } from 'naive-ui'
|
||||
|
||||
/** 路由切换容器区域 id 配置 */
|
||||
export const viewScrollContainerId = 'rayLayoutContentWrapperScopeSelector'
|
||||
|
||||
/**
|
||||
*
|
||||
* 内容区域 ref 注册
|
||||
|
@ -21,8 +21,8 @@ import type { RequestHeaderOptions } from '../type'
|
||||
*
|
||||
* @remark 自定义 `axios` 请求头配置
|
||||
*/
|
||||
export const appendRequestHeaders = (
|
||||
instance: AxiosRequestConfig<unknown>,
|
||||
export const appendRequestHeaders = <T = unknown>(
|
||||
instance: AxiosRequestConfig<T>,
|
||||
options: RequestHeaderOptions[],
|
||||
) => {
|
||||
if (instance) {
|
||||
|
@ -56,12 +56,11 @@ export default class RequestCanceler {
|
||||
const controller = new AbortController()
|
||||
|
||||
config.signal = controller.signal
|
||||
|
||||
this.pendingRequest.set(requestKey, controller)
|
||||
} else {
|
||||
// 如果已经有该 key 则重新挂载 signal
|
||||
config.signal = (
|
||||
this.pendingRequest.get(requestKey) as AbortController
|
||||
).signal
|
||||
config.signal = this.pendingRequest.get(requestKey)?.signal
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,17 +29,20 @@ import type {
|
||||
ImplementQueue,
|
||||
ErrorImplementQueue,
|
||||
FetchType,
|
||||
AxiosFetchInstance,
|
||||
AxiosFetchError,
|
||||
} from '@/axios/type'
|
||||
import type { AnyFunc } from '@/types/modules/utils'
|
||||
|
||||
/** 当前请求的实例 */
|
||||
const axiosFetchInstance = {
|
||||
requestInstance: null as RequestInterceptorConfig | null,
|
||||
responseInstance: null as ResponseInterceptorConfig | null,
|
||||
const axiosFetchInstance: AxiosFetchInstance = {
|
||||
requestInstance: null,
|
||||
responseInstance: null,
|
||||
}
|
||||
const axiosFetchError = {
|
||||
requestError: null as null | unknown,
|
||||
responseError: null as null | unknown,
|
||||
/** 请求失败返回值 */
|
||||
const axiosFetchError: AxiosFetchError = {
|
||||
requestError: null,
|
||||
responseError: null,
|
||||
}
|
||||
/** 请求队列(区分 reslove 与 reject 状态) */
|
||||
const implement: ImplementQueue = {
|
||||
@ -57,7 +60,7 @@ export const useAxiosInterceptor = () => {
|
||||
/** 创建拦截器实例 */
|
||||
const createAxiosInstance = (
|
||||
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
|
||||
instanceKey: keyof typeof axiosFetchInstance,
|
||||
instanceKey: keyof AxiosFetchInstance,
|
||||
) => {
|
||||
instanceKey === 'requestInstance'
|
||||
? (axiosFetchInstance['requestInstance'] =
|
||||
@ -67,7 +70,7 @@ export const useAxiosInterceptor = () => {
|
||||
}
|
||||
|
||||
/** 获取当前实例 */
|
||||
const getAxiosInstance = (instanceKey: keyof typeof axiosFetchInstance) => {
|
||||
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
|
||||
return axiosFetchInstance[instanceKey]
|
||||
}
|
||||
|
||||
@ -101,7 +104,7 @@ export const useAxiosInterceptor = () => {
|
||||
|
||||
/** 请求、响应前执行拦截器队列中的所有方法 */
|
||||
const beforeFetch = (
|
||||
key: keyof typeof axiosFetchInstance,
|
||||
key: keyof AxiosFetchInstance,
|
||||
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
fetchType: FetchType,
|
||||
) => {
|
||||
@ -119,7 +122,7 @@ export const useAxiosInterceptor = () => {
|
||||
|
||||
/** 请求、响应错误时执行队列中所有方法 */
|
||||
const fetchError = (
|
||||
key: keyof typeof axiosFetchError,
|
||||
key: keyof AxiosFetchError,
|
||||
error: unknown,
|
||||
errorImplementKey: keyof ErrorImplementQueue,
|
||||
) => {
|
||||
|
@ -28,6 +28,7 @@ import type {
|
||||
BeforeFetchFunction,
|
||||
FetchErrorFunction,
|
||||
} from '@/axios/type'
|
||||
|
||||
const { setImplement } = useAxiosInterceptor()
|
||||
|
||||
/**
|
||||
@ -74,6 +75,7 @@ const injectCanceler: BeforeFetchFunction<RequestInterceptorConfig> = (
|
||||
axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中
|
||||
}
|
||||
|
||||
/** 请求发生错误示例 */
|
||||
const requestError: FetchErrorFunction<unknown> = (error, mode) => {
|
||||
console.log(error, mode)
|
||||
}
|
||||
|
@ -33,22 +33,29 @@ import type { AxiosInstanceExpand } from './type'
|
||||
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
|
||||
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
|
||||
|
||||
// 请求拦截器
|
||||
server.interceptors.request.use(
|
||||
(request) => {
|
||||
// 生成 request instance
|
||||
createAxiosInstance(request, 'requestInstance')
|
||||
// 初始化拦截器所有已注入方法
|
||||
setupRequestInterceptor()
|
||||
// 执行拦截器所有已注入方法
|
||||
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
|
||||
|
||||
return request
|
||||
},
|
||||
(error) => {
|
||||
// 初始化拦截器所有已注入方法(错误状态)
|
||||
setupRequestErrorInterceptor()
|
||||
// 执行所有已注入方法
|
||||
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
server.interceptors.response.use(
|
||||
(response) => {
|
||||
createAxiosInstance(response, 'responseInstance')
|
||||
@ -63,6 +70,7 @@ server.interceptors.response.use(
|
||||
setupResponseErrorInterceptor()
|
||||
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
|
||||
|
||||
// 注销该失败请求的取消器
|
||||
axiosCanceler.removePendingRequest(error.config || {})
|
||||
|
||||
return Promise.reject(error)
|
||||
|
@ -104,3 +104,13 @@ export type FetchErrorFunction<T = any> = <K extends T>(
|
||||
error: K,
|
||||
mode: string,
|
||||
) => void
|
||||
|
||||
export interface AxiosFetchInstance {
|
||||
requestInstance: RequestInterceptorConfig | null
|
||||
responseInstance: ResponseInterceptorConfig | null
|
||||
}
|
||||
|
||||
export interface AxiosFetchError<T = unknown> {
|
||||
requestError: T | null
|
||||
responseError: T | null
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
import type { DirectiveModules } from '@/directives/type'
|
||||
|
||||
export const mergerDirective = <
|
||||
export const combineDirective = <
|
||||
T extends Record<string, DirectiveModules>,
|
||||
K extends keyof T,
|
||||
>(
|
@ -9,23 +9,41 @@
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { mergerDirective } from './helper/merger'
|
||||
import { combineDirective } from './helper/combine'
|
||||
import { forIn } from 'lodash-es'
|
||||
import { isValueType } from '@/utils/hook'
|
||||
|
||||
import type { App } from 'vue'
|
||||
import type { DirectiveModules } from '@/directives/type'
|
||||
|
||||
/** 初始化全局自定义指令 */
|
||||
/**
|
||||
*
|
||||
* 初始化全局自定义指令
|
||||
*
|
||||
* 该方法会将 modules 下每个文件夹视为一个指令
|
||||
* 并且会将文件夹名称识别为指令名称
|
||||
* 每个文件下的 index.ts 文件视为每个指令的入口(也就是指令的处理逻辑, 需要暴露出一个 Directive 类型的对象)
|
||||
*/
|
||||
export const setupDirective = (app: App<Element>) => {
|
||||
const modules = import.meta.glob('./modules/**/index.ts', {
|
||||
eager: true,
|
||||
}) as Record<string, DirectiveModules>
|
||||
const directives = mergerDirective(modules)
|
||||
// 获取 modules 包下所有的 index.ts 文件
|
||||
const directiveRawModules: Record<string, DirectiveModules> =
|
||||
import.meta.glob('./modules/**/index.ts', {
|
||||
eager: true,
|
||||
})
|
||||
// 将所有的包提取出来(./modules/[file-name]/index.ts)
|
||||
const directivesModules = combineDirective(directiveRawModules)
|
||||
// 提取文件名(./modules/copy/index.ts => copy)
|
||||
const reg = /(?<=modules\/).*(?=\/index\.ts)/
|
||||
|
||||
forIn(directives, (value, key) => {
|
||||
const directiveName = key.match(reg)?.[0] as string
|
||||
forIn(directivesModules, (value, key) => {
|
||||
const dname = key.match(reg)?.[0]
|
||||
|
||||
app.directive(directiveName, value)
|
||||
if (isValueType<string>(dname, 'String')) {
|
||||
app.directive(dname, value)
|
||||
} else {
|
||||
throw new Error(
|
||||
'directiveName is not string, please check your directive file name',
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { NMenu, NLayoutSider, NEllipsis } from 'naive-ui'
|
||||
import RayIcon from '@/components/RayIcon/index'
|
||||
|
||||
import { useMenu } from '@/store'
|
||||
import { MENU_COLLAPSED_CONFIG, MENU_ACCORDION } from '@/appConfig/appConfig'
|
||||
import { APP_MENU_CONFIG } from '@/appConfig/appConfig'
|
||||
import { useVueRouter } from '@/router/helper/useVueRouter'
|
||||
|
||||
import type { MenuInst } from 'naive-ui'
|
||||
@ -18,7 +18,7 @@ const LayoutMenu = defineComponent({
|
||||
const menuStore = useMenu()
|
||||
const { router } = useVueRouter()
|
||||
|
||||
const { menuModelValueChange, collapsedMenu } = menuStore
|
||||
const { changeMenuModelValue, collapsedMenu } = menuStore
|
||||
const modelMenuKey = computed({
|
||||
get: () => {
|
||||
nextTick().then(() => {
|
||||
@ -46,7 +46,7 @@ const LayoutMenu = defineComponent({
|
||||
|
||||
return {
|
||||
modelMenuKey,
|
||||
menuModelValueChange,
|
||||
changeMenuModelValue,
|
||||
modelMenuOptions,
|
||||
modelCollapsed,
|
||||
collapsedMenu,
|
||||
@ -60,8 +60,8 @@ const LayoutMenu = defineComponent({
|
||||
<NLayoutSider
|
||||
bordered
|
||||
showTrigger
|
||||
collapseMode={MENU_COLLAPSED_CONFIG.MENU_COLLAPSED_MODE}
|
||||
collapsedWidth={MENU_COLLAPSED_CONFIG.MENU_COLLAPSED_WIDTH}
|
||||
collapseMode={APP_MENU_CONFIG.MENU_COLLAPSED_MODE}
|
||||
collapsedWidth={APP_MENU_CONFIG.MENU_COLLAPSED_WIDTH}
|
||||
onUpdateCollapsed={this.collapsedMenu.bind(this)}
|
||||
nativeScrollbar={false}
|
||||
>
|
||||
@ -94,12 +94,12 @@ const LayoutMenu = defineComponent({
|
||||
ref="menuRef"
|
||||
v-model:value={this.modelMenuKey}
|
||||
options={this.modelMenuOptions as NaiveMenuOptions[]}
|
||||
indent={MENU_COLLAPSED_CONFIG.MENU_COLLAPSED_INDENT}
|
||||
indent={APP_MENU_CONFIG.MENU_COLLAPSED_INDENT}
|
||||
collapsed={this.modelCollapsed}
|
||||
collapsedIconSize={MENU_COLLAPSED_CONFIG.MENU_COLLAPSED_ICON_SIZE}
|
||||
collapsedWidth={MENU_COLLAPSED_CONFIG.MENU_COLLAPSED_WIDTH}
|
||||
onUpdateValue={this.menuModelValueChange.bind(this)}
|
||||
accordion={MENU_ACCORDION}
|
||||
collapsedIconSize={APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE}
|
||||
collapsedWidth={APP_MENU_CONFIG.MENU_COLLAPSED_WIDTH}
|
||||
onUpdateValue={this.changeMenuModelValue.bind(this)}
|
||||
accordion={APP_MENU_CONFIG.MENU_ACCORDION}
|
||||
/>
|
||||
</NLayoutSider>
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ const MenuTag = defineComponent({
|
||||
|
||||
const { menuKey, menuTagOptions } = storeToRefs(menuStore)
|
||||
const {
|
||||
menuModelValueChange,
|
||||
changeMenuModelValue,
|
||||
spliceMenTagOptions,
|
||||
emptyMenuTagOptions,
|
||||
setMenuTagOptions,
|
||||
@ -180,7 +180,7 @@ const MenuTag = defineComponent({
|
||||
spliceMenTagOptions(currentContentmenuIndex + 1, length - 1)
|
||||
|
||||
if (menuKey.value !== routeItem.key) {
|
||||
menuModelValueChange(routeItem.key, routeItem)
|
||||
changeMenuModelValue(routeItem.key, routeItem)
|
||||
}
|
||||
},
|
||||
closeLeft: () => {
|
||||
@ -197,7 +197,7 @@ const MenuTag = defineComponent({
|
||||
|
||||
if (menuKey.value !== routeItem.key) {
|
||||
emptyMenuTagOptions()
|
||||
menuModelValueChange(routeItem.key, routeItem)
|
||||
changeMenuModelValue(routeItem.key, routeItem)
|
||||
} else {
|
||||
setMenuTagOptions(routeItem, false)
|
||||
}
|
||||
@ -226,7 +226,7 @@ const MenuTag = defineComponent({
|
||||
|
||||
const tag = options[length - 1]
|
||||
|
||||
menuModelValueChange(tag.key as string, tag)
|
||||
changeMenuModelValue(tag.key as string, tag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +248,7 @@ const MenuTag = defineComponent({
|
||||
* @param item 当前菜单值
|
||||
*/
|
||||
const handleTagClick = (item: MenuOption) => {
|
||||
menuModelValueChange(item.key as string, item)
|
||||
changeMenuModelValue(item.key as string, item)
|
||||
}
|
||||
|
||||
const getScrollElement = () => {
|
||||
@ -430,7 +430,7 @@ const MenuTag = defineComponent({
|
||||
|
||||
return {
|
||||
modelMenuTagOptions,
|
||||
menuModelValueChange,
|
||||
changeMenuModelValue,
|
||||
closeCurrentMenuTag,
|
||||
menuKey,
|
||||
handleTagClick,
|
||||
|
@ -29,14 +29,14 @@ const Breadcrumb = defineComponent({
|
||||
setup() {
|
||||
const menuStore = useMenu()
|
||||
|
||||
const { menuModelValueChange } = menuStore
|
||||
const { changeMenuModelValue } = menuStore
|
||||
const modelBreadcrumbOptions = computed(() => menuStore.breadcrumbOptions)
|
||||
|
||||
const handleDropdownSelect = (
|
||||
key: string | number,
|
||||
option: DropdownOption,
|
||||
) => {
|
||||
menuModelValueChange(key, option)
|
||||
changeMenuModelValue(key, option)
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -35,7 +35,7 @@ const GlobalSeach = defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const menuStore = useMenu()
|
||||
|
||||
const { menuModelValueChange } = menuStore
|
||||
const { changeMenuModelValue } = menuStore
|
||||
const modelShow = computed({
|
||||
get: () => props.show,
|
||||
set: (val) => {
|
||||
@ -117,7 +117,7 @@ const GlobalSeach = defineComponent({
|
||||
} else {
|
||||
modelShow.value = false
|
||||
|
||||
menuModelValueChange(option.key as string, option)
|
||||
changeMenuModelValue(option.key as string, option)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
} from 'naive-ui'
|
||||
import ThemeSwitch from '@/layout/components/SiderBar/components/SettingDrawer/components/ThemeSwitch/index'
|
||||
|
||||
import { APP_THEME_COLOR } from '@/appConfig/designConfig'
|
||||
import { APP_THEME } from '@/appConfig/designConfig'
|
||||
import { useSetting } from '@/store'
|
||||
import { useI18n } from '@/locales/useI18n'
|
||||
|
||||
@ -86,7 +86,7 @@ const SettingDrawer = defineComponent({
|
||||
{t('headerSettingOptions.ThemeOptions.PrimaryColorConfig')}
|
||||
</NDivider>
|
||||
<NColorPicker
|
||||
swatches={APP_THEME_COLOR}
|
||||
swatches={APP_THEME.APP_THEME_COLOR}
|
||||
v-model:value={this.primaryColorOverride.common!.primaryColor}
|
||||
onUpdateValue={this.changePrimaryColor.bind(this)}
|
||||
/>
|
||||
|
@ -26,7 +26,7 @@ const FooterWrapper = defineComponent({
|
||||
return this.copyright ? (
|
||||
<div class="layout-footer-wrapper">{this.copyright}</div>
|
||||
) : (
|
||||
<></>
|
||||
''
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -19,10 +19,7 @@ import ContentWrapper from '@/layout/default/ContentWrapper'
|
||||
import FooterWrapper from '@/layout/default/FooterWrapper'
|
||||
|
||||
import { useSetting, useMenu } from '@/store'
|
||||
import {
|
||||
viewScrollContainerId,
|
||||
LAYOUT_CONTENT_REF,
|
||||
} from '@/appConfig/routerConfig'
|
||||
import { LAYOUT_CONTENT_REF } from '@/appConfig/routerConfig'
|
||||
import { layoutHeaderCssVars } from '@/layout/layoutResize'
|
||||
import useAppLockScreen from '@/components/AppComponents/AppLockScreen/appLockVar'
|
||||
|
||||
@ -33,19 +30,18 @@ const Layout = defineComponent({
|
||||
const layoutMenuTagRef = ref<HTMLElement>()
|
||||
|
||||
const settingStore = useSetting()
|
||||
const menuStore = useMenu()
|
||||
|
||||
const { height: windowHeight } = useWindowSize()
|
||||
const { menuTagSwitch: modelMenuTagSwitch } = storeToRefs(settingStore)
|
||||
const { setupAppRoutes } = menuStore
|
||||
const { getLockAppScreen } = useAppLockScreen()
|
||||
const cssVarsRef = layoutHeaderCssVars([
|
||||
layoutSiderBarRef,
|
||||
layoutMenuTagRef,
|
||||
])
|
||||
const { setupAppMenu } = useMenu()
|
||||
|
||||
nextTick().then(() => {
|
||||
setupAppRoutes()
|
||||
setupAppMenu()
|
||||
})
|
||||
|
||||
return {
|
||||
@ -78,7 +74,6 @@ const Layout = defineComponent({
|
||||
ref="LAYOUT_CONTENT_REF"
|
||||
class="layout-content__router-view"
|
||||
nativeScrollbar={false}
|
||||
{...{ id: viewScrollContainerId }}
|
||||
>
|
||||
<ContentWrapper />
|
||||
<FooterWrapper />
|
||||
|
@ -17,14 +17,14 @@
|
||||
#### 拓展方法
|
||||
|
||||
- 配置语言包文件(文件名为语言包名称)
|
||||
- 配置文件入口(使用 `mergeMessage` 方法进行自动合并处理)
|
||||
- 配置文件入口(使用 `combineI18nMessages` 方法进行自动合并处理)
|
||||
- 语言包名称应该全局唯一
|
||||
|
||||
### helper 文件说明
|
||||
|
||||
- `getAppLocales` 获取系统所有语言包(该方法强制依赖 LOCAL_OPTIONS key 配置,意味着你在配置语言包的时候,key 必须与 `src/locales/lang/xxx.ts` 一一对应匹配)
|
||||
- `getAppLocalMessages` 获取系统所有语言包(该方法强制依赖 LOCAL_OPTIONS key 配置,意味着你在配置语言包的时候,key 必须与 `src/locales/lang/xxx.ts` 一一对应匹配)
|
||||
- `naiveLocales` 获取 `naive-ui` 组件库对应语言包文件
|
||||
- `getDefaultLocal` 获取系统当前默认语言包
|
||||
- `getAppDefaultLanguage` 获取系统当前默认语言包
|
||||
|
||||
### useI18n 文件说明
|
||||
|
||||
|
@ -12,8 +12,8 @@
|
||||
/**
|
||||
*
|
||||
* 国际化辅助方法:
|
||||
* - mergeMessage: 合并对应文件下语言包
|
||||
* - getAppLocales: 获取所有语言
|
||||
* - combineI18nMessages: 合并对应文件下语言包
|
||||
* - getAppLocalMessages: 获取所有语言
|
||||
*/
|
||||
|
||||
import { set } from 'lodash-es'
|
||||
@ -27,6 +27,7 @@ import type {
|
||||
AppLocalesModules,
|
||||
AppLocalesDropdownMixedOption,
|
||||
CurrentAppMessages,
|
||||
I18nModules,
|
||||
} from '@/locales/type'
|
||||
|
||||
/**
|
||||
@ -36,13 +37,12 @@ import type {
|
||||
*
|
||||
* @remark 合并处理语言包内容, prefix 必填
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const mergeMessage = (langs: Record<string, any>, prefix: string) => {
|
||||
if (!prefix) {
|
||||
throw new Error('Expected prefix to be string, got undefined instead')
|
||||
export const combineI18nMessages = (langs: I18nModules, prefix: string) => {
|
||||
if (typeof prefix !== 'string' || !prefix.trim()) {
|
||||
throw new Error('Expected prefix to be a non-empty string')
|
||||
}
|
||||
|
||||
const langsGather: Recordable = {}
|
||||
const langsGather: Record<string, Recordable> = {}
|
||||
|
||||
Object.keys(langs).forEach((key) => {
|
||||
const langFileModule = langs[key].default
|
||||
@ -70,13 +70,13 @@ export const mergeMessage = (langs: Record<string, any>, prefix: string) => {
|
||||
}
|
||||
|
||||
/** 获取所有语言 */
|
||||
export const getAppLocales = async (
|
||||
export const getAppLocalMessages = async (
|
||||
LOCAL_OPTIONS: AppLocalesDropdownMixedOption[],
|
||||
) => {
|
||||
const message = {} as CurrentAppMessages
|
||||
|
||||
for (const curr of LOCAL_OPTIONS) {
|
||||
const msg = (await import(`./lang/${curr.key}.ts`)) as AppLocalesModules
|
||||
const msg: AppLocalesModules = await import(`./lang/${curr.key}.ts`)
|
||||
const key = curr.key
|
||||
|
||||
if (key) {
|
||||
@ -125,13 +125,11 @@ export const naiveLocales = (key: string) => {
|
||||
*
|
||||
* @remak 未避免出现加载语言错误问题, 故而在 `main.ts` 注册时, 应优先加载 `i18n` 避免出现该问题
|
||||
*/
|
||||
export const getDefaultLocal = () => {
|
||||
const catchLanguage = getCache<string>(
|
||||
export const getAppDefaultLanguage = () => {
|
||||
const language = getCache<string>(
|
||||
APP_CATCH_KEY.localeLanguage,
|
||||
'localStorage',
|
||||
)
|
||||
|
||||
const locale = catchLanguage ? catchLanguage : SYSTEM_DEFAULT_LOCAL
|
||||
|
||||
return locale
|
||||
return language ? language : SYSTEM_DEFAULT_LOCAL
|
||||
}
|
||||
|
@ -25,9 +25,9 @@
|
||||
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { LOCAL_OPTIONS } from '@/appConfig/localConfig'
|
||||
import { getDefaultLocal } from '@/locales/helper'
|
||||
import { getAppDefaultLanguage } from '@/locales/helper'
|
||||
|
||||
import { getAppLocales } from '@/locales/helper'
|
||||
import { getAppLocalMessages } from '@/locales/helper'
|
||||
|
||||
import type { App } from 'vue'
|
||||
import type { I18n, I18nOptions } from 'vue-i18n'
|
||||
@ -37,8 +37,8 @@ export let i18n: I18n
|
||||
|
||||
/** 创建 i18n 实例 */
|
||||
const createI18nOptions = async () => {
|
||||
const locale = getDefaultLocal()
|
||||
const messages = await getAppLocales(LOCAL_OPTIONS)
|
||||
const locale = getAppDefaultLanguage()
|
||||
const messages = await getAppLocalMessages(LOCAL_OPTIONS)
|
||||
|
||||
const i18nInstance = createI18n({
|
||||
legacy: false,
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { mergeMessage } from '@/locales/helper'
|
||||
import { combineI18nMessages } from '@/locales/helper'
|
||||
|
||||
const modules = import.meta.glob('./en-US/**/*.json', {
|
||||
import type { I18nModules } from '@/locales/type'
|
||||
|
||||
const modules: I18nModules = import.meta.glob('./en-US/**/*.json', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
export default {
|
||||
message: {
|
||||
...mergeMessage(modules, 'en-US'),
|
||||
...combineI18nMessages(modules, 'en-US'),
|
||||
},
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { mergeMessage } from '@/locales/helper'
|
||||
import { combineI18nMessages } from '@/locales/helper'
|
||||
|
||||
const modules = import.meta.glob('./zh-CN/**/*.json', {
|
||||
import type { I18nModules } from '@/locales/type'
|
||||
|
||||
const modules: I18nModules = import.meta.glob('./zh-CN/**/*.json', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
export default {
|
||||
message: {
|
||||
...mergeMessage(modules, 'zh-CN'),
|
||||
...combineI18nMessages(modules, 'zh-CN'),
|
||||
},
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import type {
|
||||
DropdownDividerOption,
|
||||
DropdownRenderOption,
|
||||
} from 'naive-ui'
|
||||
import type { Recordable } from '@/types/modules/helper'
|
||||
|
||||
export interface CurrentAppMessages {
|
||||
'zh-CN': object
|
||||
@ -21,3 +22,5 @@ export interface AppLocalesModules {
|
||||
message: UnknownObjectKey
|
||||
}
|
||||
}
|
||||
|
||||
export type I18nModules = Record<string, { default: Recordable }>
|
||||
|
@ -30,6 +30,7 @@ export const useI18n = (namespace?: string) => {
|
||||
return (t as any)(getI18nKey(namespace, key), ...args)
|
||||
}
|
||||
|
||||
/** 重写 locale 方法 */
|
||||
const overrideLocaleFunc = (lang: string) => {
|
||||
const localeRef = locale as WritableComputedRef<string>
|
||||
|
||||
@ -50,4 +51,4 @@ export const useI18n = (namespace?: string) => {
|
||||
*
|
||||
* 该插件识别 t 方法包裹 path 进行提示文案内容
|
||||
*/
|
||||
export const t = <T = unknown>(key: T) => key
|
||||
export const t = (key: string) => key
|
||||
|
@ -9,7 +9,7 @@
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import type { AppRouteRecordRaw, AutoImportRouteModule } from '@/router/type'
|
||||
import type { AppRouteRecordRaw, RouteModules } from '@/router/type'
|
||||
|
||||
/**
|
||||
*
|
||||
@ -17,15 +17,21 @@ import type { AppRouteRecordRaw, AutoImportRouteModule } from '@/router/type'
|
||||
*
|
||||
* @remark 自动合并所有路由模块, 每一个 ts 文件都视为一个 route module 与 views 一一对应
|
||||
*/
|
||||
export const autoMergeRoute = () => {
|
||||
const modulesFiles = import.meta.glob('../modules/**/*.ts', {
|
||||
export const combineRawRouteModules = () => {
|
||||
const modulesFiles: RouteModules = import.meta.glob('../modules/**/*.ts', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
const modules = Object.keys(modulesFiles).reduce((modules, modulePath) => {
|
||||
const value = modulesFiles[modulePath] as AutoImportRouteModule
|
||||
const route = modulesFiles[modulePath].default
|
||||
|
||||
modules.push(value.default)
|
||||
if (route) {
|
||||
modules.push(route)
|
||||
} else {
|
||||
throw new Error(
|
||||
'router helper combine: an exception occurred while parsing the routing file!',
|
||||
)
|
||||
}
|
||||
|
||||
return modules
|
||||
}, [] as AppRouteRecordRaw[])
|
@ -141,7 +141,6 @@ export const redirectRouterToDashboard = (isReplace = true) => {
|
||||
const { path } = ROOT_ROUTE
|
||||
|
||||
setCache('menuKey', path)
|
||||
console.log('path', path)
|
||||
|
||||
isReplace ? push(path) : replace(path)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export let router: Router
|
||||
const createVueRouter = () => {
|
||||
return createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: constantRoutes as unknown as RouteRecordRaw[],
|
||||
routes: constantRoutes() as unknown as RouteRecordRaw[],
|
||||
scrollBehavior: (to) => {
|
||||
scrollViewToTop(to)
|
||||
|
||||
|
@ -20,11 +20,8 @@
|
||||
* 如果不设置 order 属性, 则会默认排在前面
|
||||
*/
|
||||
|
||||
import { autoMergeRoute } from '@/router/helper/merge'
|
||||
import { combineRawRouteModules } from '@/router/helper/combine'
|
||||
import { orderRoutes } from '@/router/helper/orderRoutes'
|
||||
|
||||
import type { AppRouteRecordRaw } from '@/router/type'
|
||||
|
||||
const routes: AppRouteRecordRaw[] = orderRoutes(autoMergeRoute())
|
||||
|
||||
export default routes
|
||||
/** 获取所有被合并与排序的路由 */
|
||||
export const getAppRawRoutes = () => orderRoutes(combineRawRouteModules())
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Layout from '@/layout/index'
|
||||
import childrenRoutes from './routeModules'
|
||||
import { getAppRawRoutes } from './routeModules'
|
||||
import { ROOT_ROUTE } from '@/appConfig/appConfig'
|
||||
import { expandRoutes } from '@/router/helper/expandRoutes'
|
||||
|
||||
const { path } = ROOT_ROUTE
|
||||
|
||||
export default [
|
||||
export default () => [
|
||||
{
|
||||
path: '/',
|
||||
name: 'login',
|
||||
@ -16,7 +16,7 @@ export default [
|
||||
name: 'layout',
|
||||
redirect: path,
|
||||
component: Layout,
|
||||
children: expandRoutes(childrenRoutes),
|
||||
children: expandRoutes(getAppRawRoutes()),
|
||||
},
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
|
@ -32,6 +32,8 @@ export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
fullPath?: string
|
||||
}
|
||||
|
||||
export interface AutoImportRouteModule extends Object {
|
||||
default: AppRouteRecordRaw
|
||||
export interface RouteModules {
|
||||
[propName: string]: {
|
||||
default: AppRouteRecordRaw
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
/** 本方法感谢 <https://yunkuangao.me/> 的支持 */
|
||||
|
||||
import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig'
|
||||
import { APP_MENU_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig'
|
||||
import RayIcon from '@/components/RayIcon/index'
|
||||
import { isValueType } from '@/utils/hook'
|
||||
import { getCache, setCache } from '@/utils/cache'
|
||||
@ -31,12 +31,20 @@ import type {
|
||||
*
|
||||
* @remark 检查是否为所需项
|
||||
*/
|
||||
const check = (
|
||||
const isMatch = (
|
||||
node: AppMenuOption,
|
||||
key: string | number,
|
||||
value: string | number,
|
||||
) => {
|
||||
return node[key] === value || node.key === value
|
||||
if (!node || typeof node !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (node[key] === value) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,7 +55,7 @@ const check = (
|
||||
*
|
||||
* @remark 匹配所有节点
|
||||
*/
|
||||
const process = (
|
||||
const findMatchingNodes = (
|
||||
options: AppMenuOption,
|
||||
key: string | number,
|
||||
value: string | number,
|
||||
@ -55,7 +63,7 @@ const process = (
|
||||
const temp: AppMenuOption[] = []
|
||||
|
||||
// 检查当前节点是否匹配值
|
||||
if (check(options, key, value)) {
|
||||
if (isMatch(options, key, value)) {
|
||||
temp.push(options)
|
||||
|
||||
return temp
|
||||
@ -65,7 +73,7 @@ const process = (
|
||||
if (options.children && options.children.length > 0) {
|
||||
for (const it of options.children) {
|
||||
// 子节点递归调用
|
||||
const innerTemp = process(it, key, value)
|
||||
const innerTemp = findMatchingNodes(it, key, value)
|
||||
|
||||
// 如果子节点匹配到了,则将当前节点加入数组
|
||||
if (innerTemp.length > 0) {
|
||||
@ -83,7 +91,7 @@ const process = (
|
||||
* @param key 动态字段
|
||||
* @param value 匹配值
|
||||
*/
|
||||
export const parse = (
|
||||
export const parseAndFindMatchingNodes = (
|
||||
options: AppMenuOption[],
|
||||
key: string | number,
|
||||
value: string | number,
|
||||
@ -91,7 +99,7 @@ export const parse = (
|
||||
const temp = []
|
||||
|
||||
for (const it of options) {
|
||||
const innerTemp = process(it, key, value)
|
||||
const innerTemp = findMatchingNodes(it, key, value)
|
||||
|
||||
if (innerTemp.length > 0) {
|
||||
temp.push(...innerTemp)
|
||||
@ -155,7 +163,7 @@ export const hasMenuIcon = (option: AppMenuOption) => {
|
||||
RayIcon,
|
||||
{
|
||||
name: meta!.icon as string,
|
||||
size: MENU_COLLAPSED_CONFIG.MENU_COLLAPSED_ICON_SIZE,
|
||||
size: APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE,
|
||||
},
|
||||
{},
|
||||
)
|
||||
|
@ -27,20 +27,21 @@ import { NEllipsis } from 'naive-ui'
|
||||
import { getCache, setCache } from '@/utils/cache'
|
||||
import { validMenuItemShow } from '@/router/helper/routerCopilot'
|
||||
import {
|
||||
parse,
|
||||
parseAndFindMatchingNodes,
|
||||
matchMenuOption,
|
||||
updateDocumentTitle,
|
||||
hasMenuIcon,
|
||||
getCatchMenuKey,
|
||||
} from './helper'
|
||||
import { useI18n } from '@/locales/useI18n'
|
||||
import routeModules from '@/router/routeModules'
|
||||
import { getAppRawRoutes } from '@/router/routeModules'
|
||||
import { useKeepAlive } from '@/store'
|
||||
import { useVueRouter } from '@/router/helper/useVueRouter'
|
||||
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
import type { AppRouteMeta } from '@/router/type'
|
||||
import type { AppRouteMeta, AppRouteRecordRaw } from '@/router/type'
|
||||
import type { AppMenuOption, MenuTagOptions } from '@/types/modules/app'
|
||||
import type { MenuState } from '@/store/modules/menu/type'
|
||||
|
||||
export const useMenu = defineStore(
|
||||
'menu',
|
||||
@ -50,12 +51,12 @@ export const useMenu = defineStore(
|
||||
const { t } = useI18n()
|
||||
const { setKeepAliveInclude } = useKeepAlive()
|
||||
|
||||
const menuState = reactive({
|
||||
const menuState = reactive<MenuState>({
|
||||
menuKey: getCatchMenuKey(), // 当前菜单 `key`
|
||||
options: [] as AppMenuOption[], // 菜单列表
|
||||
options: [], // 菜单列表
|
||||
collapsed: false, // 是否折叠菜单
|
||||
menuTagOptions: [] as MenuTagOptions[], // tag 标签菜单
|
||||
breadcrumbOptions: [] as AppMenuOption[], // 面包屑菜单
|
||||
menuTagOptions: [], // tag 标签菜单
|
||||
breadcrumbOptions: [], // 面包屑菜单
|
||||
})
|
||||
|
||||
/**
|
||||
@ -69,21 +70,21 @@ export const useMenu = defineStore(
|
||||
options: AppMenuOption[],
|
||||
key: string | number,
|
||||
) => {
|
||||
const ops = parse(options, 'key', key)
|
||||
const ops = parseAndFindMatchingNodes(options, 'key', key)
|
||||
|
||||
return ops
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key 菜单更新后的 `key`
|
||||
* @param item 菜单当前 `item`
|
||||
* @param key 菜单更新后的 key
|
||||
* @param option 菜单当前 option 项
|
||||
*
|
||||
* @remark 修改 `menu key` 后的回调函数
|
||||
* @remark 修改后, 缓存当前选择 key 并且存储标签页与跳转页面(router push 操作)
|
||||
*/
|
||||
const menuModelValueChange = (key: string | number, item: MenuOption) => {
|
||||
const meta = item.meta as AppRouteMeta
|
||||
const changeMenuModelValue = (key: string | number, option: MenuOption) => {
|
||||
const { meta, path } = option as unknown as AppRouteRecordRaw
|
||||
|
||||
if (meta.windowOpen) {
|
||||
window.open(meta.windowOpen)
|
||||
@ -91,26 +92,30 @@ export const useMenu = defineStore(
|
||||
// 防止重复点击做重复操作处理
|
||||
if (menuState.menuKey !== key) {
|
||||
matchMenuOption(
|
||||
item as unknown as MenuTagOptions,
|
||||
option as unknown as MenuTagOptions,
|
||||
menuState.menuKey,
|
||||
menuState.menuTagOptions,
|
||||
)
|
||||
updateDocumentTitle(item as unknown as AppMenuOption)
|
||||
setKeepAliveInclude(item as unknown as AppMenuOption)
|
||||
updateDocumentTitle(option as unknown as AppMenuOption)
|
||||
setKeepAliveInclude(option as unknown as AppMenuOption)
|
||||
|
||||
menuState.breadcrumbOptions = parse(menuState.options, 'key', key) // 获取面包屑
|
||||
menuState.breadcrumbOptions = parseAndFindMatchingNodes(
|
||||
menuState.options,
|
||||
'key',
|
||||
key,
|
||||
) // 获取面包屑
|
||||
|
||||
/** 是否为根路由 */
|
||||
if (key[0] !== '/') {
|
||||
if (!String(key).startsWith('/')) {
|
||||
/** 如果不是根路由, 则拼接完整路由并跳转 */
|
||||
const path = getCompleteRoutePath(menuState.options, key)
|
||||
const _path = getCompleteRoutePath(menuState.options, key)
|
||||
.map((curr) => curr.key)
|
||||
.join('/')
|
||||
|
||||
router.push(path)
|
||||
router.push(_path)
|
||||
} else {
|
||||
/** 根路由直接跳转 */
|
||||
router.push(item.path as string)
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
menuState.menuKey = key
|
||||
@ -136,7 +141,7 @@ export const useMenu = defineStore(
|
||||
}
|
||||
|
||||
if (path === i.path) {
|
||||
menuModelValueChange(i.path, i)
|
||||
changeMenuModelValue(i.path, i)
|
||||
|
||||
break
|
||||
}
|
||||
@ -168,69 +173,76 @@ export const useMenu = defineStore(
|
||||
* @remark 初始化菜单列表, 并且按照权限过滤
|
||||
* @remark 如果权限发生变动, 则会触发强制弹出页面并且重新登陆
|
||||
*/
|
||||
const setupAppRoutes = () => {
|
||||
const resolveOption = (option: AppMenuOption) => {
|
||||
const { meta } = option
|
||||
const setupAppMenu = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const resolveOption = (option: AppMenuOption) => {
|
||||
const { meta } = option
|
||||
|
||||
/** 设置 label, i18nKey 优先级最高 */
|
||||
const label = computed(() =>
|
||||
meta?.i18nKey ? t(`${meta!.i18nKey}`) : meta?.noLocalTitle,
|
||||
)
|
||||
/** 拼装菜单项 */
|
||||
const route = {
|
||||
...option,
|
||||
key: option.path,
|
||||
label: () =>
|
||||
h(NEllipsis, null, {
|
||||
default: () => label.value,
|
||||
}),
|
||||
breadcrumbLabel: label.value,
|
||||
} as AppMenuOption
|
||||
/** 合并 icon */
|
||||
const attr: AppMenuOption = Object.assign({}, route, {
|
||||
icon: hasMenuIcon(option),
|
||||
})
|
||||
/** 设置 label, i18nKey 优先级最高 */
|
||||
const label = computed(() =>
|
||||
meta?.i18nKey ? t(`${meta!.i18nKey}`) : meta?.noLocalTitle,
|
||||
)
|
||||
/** 拼装菜单项 */
|
||||
const route = {
|
||||
...option,
|
||||
key: option.path,
|
||||
label: () =>
|
||||
h(NEllipsis, null, {
|
||||
default: () => label.value,
|
||||
}),
|
||||
breadcrumbLabel: label.value,
|
||||
} as AppMenuOption
|
||||
/** 合并 icon */
|
||||
const attr: AppMenuOption = Object.assign({}, route, {
|
||||
icon: hasMenuIcon(option),
|
||||
})
|
||||
|
||||
if (option.path === getCatchMenuKey()) {
|
||||
/** 设置菜单标签 */
|
||||
setMenuTagOptions(attr)
|
||||
/** 设置浏览器标题 */
|
||||
updateDocumentTitle(attr)
|
||||
}
|
||||
|
||||
/** 检查该菜单项是否展示 */
|
||||
attr.show = validMenuItemShow(option)
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
const resolveRoutes = (routes: AppMenuOption[], index: number) => {
|
||||
const catchArr: AppMenuOption[] = []
|
||||
|
||||
for (const curr of routes) {
|
||||
if (curr.children?.length && validMenuItemShow(curr)) {
|
||||
curr.children = resolveRoutes(curr.children, index++)
|
||||
} else if (!validMenuItemShow(curr)) {
|
||||
/** 如果校验失败, 则不会添加进 menu options */
|
||||
continue
|
||||
if (option.path === getCatchMenuKey()) {
|
||||
/** 设置菜单标签 */
|
||||
setMenuTagOptions(attr)
|
||||
/** 设置浏览器标题 */
|
||||
updateDocumentTitle(attr)
|
||||
}
|
||||
|
||||
catchArr.push(resolveOption(curr))
|
||||
/** 检查该菜单项是否展示 */
|
||||
attr.show = validMenuItemShow(option)
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
return catchArr
|
||||
}
|
||||
const resolveRoutes = (routes: AppMenuOption[], index: number) => {
|
||||
const catchArr: AppMenuOption[] = []
|
||||
|
||||
/** 缓存菜单列表 */
|
||||
menuState.options = resolveRoutes(routeModules as AppMenuOption[], 0)
|
||||
for (const curr of routes) {
|
||||
if (curr.children?.length && validMenuItemShow(curr)) {
|
||||
curr.children = resolveRoutes(curr.children, index++)
|
||||
} else if (!validMenuItemShow(curr)) {
|
||||
/** 如果校验失败, 则不会添加进 menu options */
|
||||
continue
|
||||
}
|
||||
|
||||
/** 初始化后渲染面包屑 */
|
||||
nextTick(() => {
|
||||
menuState.breadcrumbOptions = parse(
|
||||
menuState.options,
|
||||
'key',
|
||||
menuState.menuKey as string,
|
||||
catchArr.push(resolveOption(curr))
|
||||
}
|
||||
|
||||
return catchArr
|
||||
}
|
||||
|
||||
/** 缓存菜单列表 */
|
||||
menuState.options = resolveRoutes(
|
||||
getAppRawRoutes() as AppMenuOption[],
|
||||
0,
|
||||
)
|
||||
|
||||
resolve()
|
||||
|
||||
/** 初始化后渲染面包屑 */
|
||||
nextTick(() => {
|
||||
menuState.breadcrumbOptions = parseAndFindMatchingNodes(
|
||||
menuState.options,
|
||||
'key',
|
||||
menuState.menuKey as string,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -275,8 +287,8 @@ export const useMenu = defineStore(
|
||||
|
||||
return {
|
||||
...toRefs(menuState),
|
||||
menuModelValueChange,
|
||||
setupAppRoutes,
|
||||
changeMenuModelValue,
|
||||
setupAppMenu,
|
||||
collapsedMenu,
|
||||
spliceMenTagOptions,
|
||||
emptyMenuTagOptions,
|
||||
|
13
src/store/modules/menu/type.ts
Normal file
13
src/store/modules/menu/type.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import type {
|
||||
AppMenuOption,
|
||||
MenuTagOptions,
|
||||
AppMenuKey,
|
||||
} from '@/types/modules/app'
|
||||
|
||||
export interface MenuState {
|
||||
menuKey: AppMenuKey
|
||||
options: AppMenuOption[]
|
||||
collapsed: boolean
|
||||
menuTagOptions: MenuTagOptions[]
|
||||
breadcrumbOptions: AppMenuOption[]
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { getDefaultLocal } from '@/locales/helper'
|
||||
import { getAppDefaultLanguage } from '@/locales/helper'
|
||||
import { setCache } from '@use-utils/cache'
|
||||
import { set } from 'lodash-es'
|
||||
import { addClass, removeClass, colorToRgba } from '@/utils/element'
|
||||
import { useI18n } from '@/locales/useI18n'
|
||||
import { APP_NAIVE_UI_THEME_OVERRIDES } from '@/appConfig/designConfig'
|
||||
import { APP_THEME } from '@/appConfig/designConfig'
|
||||
import { useDayjs } from '@/dayjs/index'
|
||||
|
||||
import type { ConditionalPick } from '@/types/modules/helper'
|
||||
@ -22,7 +22,7 @@ export const useSetting = defineStore(
|
||||
const settingState = reactive<SettingState>({
|
||||
drawerPlacement: 'right',
|
||||
primaryColorOverride: {
|
||||
...APP_NAIVE_UI_THEME_OVERRIDES,
|
||||
...APP_THEME.APP_NAIVE_UI_THEME_OVERRIDES,
|
||||
common: {
|
||||
primaryColor: primaryColor, // 主题色
|
||||
primaryColorHover: primaryColor,
|
||||
@ -34,7 +34,7 @@ export const useSetting = defineStore(
|
||||
spinSwitch: false, // 全屏加载
|
||||
invertSwitch: false, // 反转色模式
|
||||
breadcrumbSwitch: true, // 面包屑开关
|
||||
localeLanguage: getDefaultLocal(),
|
||||
localeLanguage: getAppDefaultLanguage(),
|
||||
lockScreenSwitch: false, // 锁屏开关
|
||||
lockScreenInputSwitch: false, // 锁屏输入状态开关(预留该字段是为了方便拓展用, 但是舍弃了该字段, 改为使用 useAppLockScreen 方法)
|
||||
})
|
||||
|
@ -2,11 +2,12 @@ import type { CreateAxiosDefaults } from 'axios'
|
||||
|
||||
export type CollapsedMode = 'transform' | 'width'
|
||||
|
||||
export interface MenuCollapsedConfig {
|
||||
export interface AppMenuConfig {
|
||||
MENU_COLLAPSED_WIDTH: number
|
||||
MENU_COLLAPSED_MODE: CollapsedMode
|
||||
MENU_COLLAPSED_ICON_SIZE: number
|
||||
MENU_COLLAPSED_INDENT: number
|
||||
MENU_ACCORDION: boolean
|
||||
}
|
||||
|
||||
export interface AppKeepAlive {
|
||||
|
@ -6,6 +6,7 @@ import type {
|
||||
UserConfigExport,
|
||||
} from 'vite'
|
||||
import type { Recordable } from '@/types/modules/helper'
|
||||
import type { GlobalThemeOverrides } from 'naive-ui'
|
||||
|
||||
export interface LayoutSideBarLogo {
|
||||
icon?: string
|
||||
@ -73,3 +74,9 @@ export interface AppConfig {
|
||||
}
|
||||
|
||||
export type AppConfigExport = Config & UserConfigExport
|
||||
|
||||
export interface AppTheme {
|
||||
APP_THEME_COLOR: string[]
|
||||
APP_PRIMARY_COLOR: AppPrimaryColor
|
||||
APP_NAIVE_UI_THEME_OVERRIDES: GlobalThemeOverrides
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { isValueType } from '@use-utils/hook'
|
||||
import { ELEMENT_UNIT } from '@/appConfig/regConfig'
|
||||
import { APP_REGEX } from '@/appConfig/regConfig'
|
||||
|
||||
import type { EventListenerOrEventListenerObject } from '@/types/modules/utils'
|
||||
|
||||
@ -132,9 +132,9 @@ export const hasClass = (element: HTMLElement, className: string) => {
|
||||
* addStyle(styles)
|
||||
* ```
|
||||
*/
|
||||
export const addStyle = (
|
||||
export const addStyle = <K extends keyof CSSStyleDeclaration & string>(
|
||||
el: HTMLElement,
|
||||
styles: string | Partial<CSSStyleDeclaration>,
|
||||
styles: K | Partial<CSSStyleDeclaration>,
|
||||
) => {
|
||||
if (el) {
|
||||
if (isValueType<object>(styles, 'Object')) {
|
||||
@ -160,10 +160,13 @@ export const addStyle = (
|
||||
* @param el Target element dom
|
||||
* @param styles 所需卸载样式
|
||||
*/
|
||||
export const removeStyle = (el: HTMLElement, styles: string[]) => {
|
||||
export const removeStyle = <K extends keyof CSSStyleDeclaration & string>(
|
||||
el: HTMLElement,
|
||||
styles: K[],
|
||||
) => {
|
||||
if (el) {
|
||||
styles.forEach((item) => {
|
||||
el.style[item] = null
|
||||
styles.forEach((curr) => {
|
||||
el.style.removeProperty(curr)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -259,7 +262,10 @@ export const getElement = <T extends Element>(element: string) => {
|
||||
export const completeSize = (size: number | string, unit = 'px') => {
|
||||
if (typeof size === 'number') {
|
||||
return size.toString() + unit
|
||||
} else if (isValueType<string>(size, 'String') && ELEMENT_UNIT.test(size)) {
|
||||
} else if (
|
||||
isValueType<string>(size, 'String') &&
|
||||
APP_REGEX.validerCSSUnit.test(size)
|
||||
) {
|
||||
return size
|
||||
} else {
|
||||
return size + unit
|
||||
|
Loading…
x
Reference in New Issue
Block a user