This commit is contained in:
XiaoDaiGua-Ray 2023-10-24 17:42:01 +08:00
parent 9e3f199d21
commit b05edfeaeb
34 changed files with 480 additions and 257 deletions

View File

@ -1,5 +1,24 @@
# CHANGE LOG
## 4.2.6
### Feats
做了一点大的更新改动,支持小尺寸设备、显示器展示了。但是仅仅是在布局上做了一些兼容!
补充了一些代码的注释。
- RChart 改动
- 重构了 RChart 组件,现在支持自定义主题色了
- 支持配置 theme 为 default 属性,则可以启用 echart 默认样式
- 更新了 echart-themes 包中的说明文件
- 更改 canceler 方法名为 RequestCanceler
- 更改 app-config 包中的一些属性命名,现在将更加统一命名规则
### Fixes
- 修复 axios request error 状态时不能正确取消拦截器问题
## 4.2.5
### Feats

2
cfg.ts
View File

@ -51,7 +51,7 @@ const config: AppConfigExport = {
/** 配置首屏加载信息 */
preloadingConfig: PRE_LOADING_CONFIG,
/** 默认主题色(不可省略, 必填), 也用于 ejs 注入 */
appPrimaryColor: APP_THEME.APP_PRIMARY_COLOR,
appPrimaryColor: APP_THEME.appPrimaryColor,
sideBarLogo: SIDE_BAR_LOGO,
/**
*

View File

@ -1,7 +1,7 @@
{
"name": "ray-template",
"private": false,
"version": "4.2.5",
"version": "4.2.6",
"type": "module",
"engines": {
"node": ">=16.0.0",

View File

@ -76,21 +76,21 @@ export const SIDE_BAR_LOGO: LayoutSideBarLogo | undefined = {
*
*
*
* MENU_COLLAPSED_WIDTH MENU_COLLAPSED_MODE width
* menuCollapsedWidth menuCollapsedMode width
*
* MENU_COLLAPSED_MODE:
* menuCollapsedMode:
* - transform: 边栏将只会移动它的位置而不会改变宽度
* - width: Sider
* MENU_COLLAPSED_ICON_SIZE
* MENU_COLLAPSED_INDENT
* MENU_ACCORDION
* menuCollapsedIconSize
* menuCollapsedIndent
* menuAccordion
*/
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,
menuCollapsedWidth: 64,
menuCollapsedMode: 'width',
menuCollapsedIconSize: 22,
menuCollapsedIndent: 24,
menuAccordion: false,
}
/**

View File

@ -19,7 +19,7 @@ export const APP_THEME: AppTheme = {
*
* RGBARGB
*/
APP_THEME_COLOR: [
appThemeColors: [
'#2d8cf0',
'#0960bd',
'#536dfe',
@ -30,7 +30,7 @@ export const APP_THEME: AppTheme = {
'#18A058',
],
/** 系统主题色 */
APP_PRIMARY_COLOR: {
appPrimaryColor: {
/** 主题色 */
primaryColor: '#2d8cf0',
/** 主题辅助色(用于整体 hover、active 等之类颜色) */
@ -42,7 +42,7 @@ export const APP_THEME: AppTheme = {
* : <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>
*
* :
* - APP_PRIMARY_COLOR common
* - appPrimaryColor common
*
* ,
* , peers
@ -60,7 +60,7 @@ export const APP_THEME: AppTheme = {
* }
* ```
*/
APP_NAIVE_UI_THEME_OVERRIDES: {},
appNaiveUIThemeOverrides: {},
/**
*
* echart

View File

@ -37,7 +37,7 @@ export default class RequestCanceler {
*
* @remark config request key
*/
generateRequestKey(config: AppRawRequestConfig): string {
generateRequestKey(config: AppRawRequestConfig) {
const { method, url } = config
return [
@ -82,7 +82,6 @@ export default class RequestCanceler {
if (this.pendingRequest.has(requestKey)) {
this.pendingRequest.get(requestKey)!.abort()
this.pendingRequest.delete(requestKey)
}
}

View File

@ -20,7 +20,7 @@
* 使,
*/
import RequestCanceler from '@/axios/helper/canceler'
import RequestCanceler from '@/axios/helper/RequestCanceler'
import { getAppEnvironment } from '@use-utils/hook'
import type {

View File

@ -0,0 +1,35 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-23
*
* @workspace ray-template
*
* @remark
*/
import { useAxiosInterceptor } from '@/axios/helper/interceptor'
import implement from './provider'
const { setImplement } = useAxiosInterceptor()
export const setupRequestInterceptor = () => {
const { implementRequestInterceptorArray } = implement
setImplement(
'implementRequestInterceptorArray',
implementRequestInterceptorArray,
'ok',
)
}
export const setupRequestErrorInterceptor = () => {
const { implementRequestInterceptorErrorArray } = implement
setImplement(
'implementRequestInterceptorErrorArray',
implementRequestInterceptorErrorArray,
'error',
)
}

View File

@ -16,9 +16,11 @@
* ,
*
* , 便
*
* injectRequestCanceler requestErrorCanceler axios request interceptor
*/
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import { axiosCanceler } from '@/axios/helper/interceptor'
import { appendRequestHeaders } from '@/axios/helper/axiosCopilot'
import { APP_CATCH_KEY } from '@/app-config/appConfig'
import { getStorage } from '@/utils/cache'
@ -28,8 +30,7 @@ import type {
BeforeFetchFunction,
FetchErrorFunction,
} from '@/axios/type'
const { setImplement } = useAxiosInterceptor()
import type { Recordable } from '@/types/modules/helper'
/**
*
@ -66,8 +67,14 @@ const injectRequestHeaders: BeforeFetchFunction<RequestInterceptorConfig> = (
])
}
/** 注入重复请求拦截器 */
const injectCanceler: BeforeFetchFunction<RequestInterceptorConfig> = (
/**
*
* @param ins
* @param mode
*
*
*/
const injectRequestCanceler: BeforeFetchFunction<RequestInterceptorConfig> = (
ins,
mode,
) => {
@ -75,9 +82,15 @@ const injectCanceler: BeforeFetchFunction<RequestInterceptorConfig> = (
axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中
}
/** 请求发生错误示例 */
const requestError: FetchErrorFunction<unknown> = (error, mode) => {
console.log(error, mode)
/**
*
* @param error
* @param mode
*
*
*/
const requestErrorCanceler: FetchErrorFunction<Recordable> = (error, mode) => {
axiosCanceler.removePendingRequest(error)
}
/**
@ -85,19 +98,12 @@ const requestError: FetchErrorFunction<unknown> = (error, mode) => {
*
*
*/
export const setupRequestInterceptor = () => {
setImplement(
'implementRequestInterceptorArray',
[injectRequestHeaders, injectCanceler],
'ok',
)
}
/**
*
*
*
*/
export const setupRequestErrorInterceptor = () => {
setImplement('implementRequestInterceptorErrorArray', [requestError], 'error')
export default {
// 请求正常
implementRequestInterceptorArray: [
injectRequestHeaders,
injectRequestCanceler,
],
// 请求错误
implementRequestInterceptorErrorArray: [requestErrorCanceler],
}

View File

@ -0,0 +1,35 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-23
*
* @workspace ray-template
*
* @remark
*/
import { useAxiosInterceptor } from '@/axios/helper/interceptor'
import implement from './provider'
const { setImplement } = useAxiosInterceptor()
export const setupResponseInterceptor = () => {
const { implementResponseInterceptorArray } = implement
setImplement(
'implementResponseInterceptorArray',
implementResponseInterceptorArray,
'ok',
)
}
export const setupResponseErrorInterceptor = () => {
const { implementResponseInterceptorErrorArray } = implement
setImplement(
'implementResponseInterceptorErrorArray',
implementResponseInterceptorErrorArray,
'error',
)
}

View File

@ -16,19 +16,26 @@
* ,
*
* , 便
*
* injectResponseCanceler responseErrorCanceler axios response interceptor
*/
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import { axiosCanceler } from '@/axios/helper/interceptor'
import type {
ResponseInterceptorConfig,
BeforeFetchFunction,
FetchErrorFunction,
} from '@/axios/type'
import type { Recordable } from '@/types/modules/helper'
const { setImplement } = useAxiosInterceptor()
/** 响应成功后移除缓存请求 url */
/**
*
* @param ins
* @param mode
*
*
*/
const injectResponseCanceler: BeforeFetchFunction<ResponseInterceptorConfig> = (
ins,
mode,
@ -41,13 +48,10 @@ const injectResponseCanceler: BeforeFetchFunction<ResponseInterceptorConfig> = (
* @param error
* @param mode
*
*
*
*
* ,
*
*/
const responseError: FetchErrorFunction<unknown> = (error, mode) => {
console.log(error, mode)
const responseErrorCanceler: FetchErrorFunction<Recordable> = (error, mode) => {
axiosCanceler.removePendingRequest(error.config)
}
/**
@ -55,23 +59,9 @@ const responseError: FetchErrorFunction<unknown> = (error, mode) => {
*
*
*/
export const setupResponseInterceptor = () => {
setImplement(
'implementResponseInterceptorArray',
[injectResponseCanceler],
'ok',
)
}
/**
*
*
*
*/
export const setupResponseErrorInterceptor = () => {
setImplement(
'implementResponseInterceptorErrorArray',
[responseError],
'error',
)
export default {
// 响应正常
implementResponseInterceptorArray: [injectResponseCanceler],
// 响应错误
implementResponseInterceptorErrorArray: [responseErrorCanceler],
}

View File

@ -13,20 +13,20 @@
*
*
* , inject
*
*
*/
import axios from 'axios'
import { AXIOS_CONFIG } from '@/app-config/requestConfig'
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import { useAxiosInterceptor } from '@/axios/helper/interceptor'
import {
setupResponseInterceptor,
setupResponseErrorInterceptor,
} from '@/axios/inject/response/provide'
} from '@/axios/inject/response'
import {
setupRequestInterceptor,
setupRequestErrorInterceptor,
} from '@/axios/inject/request/provide'
} from '@/axios/inject/request'
import type { AxiosInstanceExpand } from './type'
@ -70,9 +70,6 @@ server.interceptors.response.use(
setupResponseErrorInterceptor()
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
// 注销该失败请求的取消器
axiosCanceler.removePendingRequest(error.config || {})
return Promise.reject(error)
},
)

View File

@ -69,7 +69,7 @@ export default defineComponent({
props,
setup(props, { expose }) {
const settingStore = useSetting()
const { themeValue } = storeToRefs(settingStore)
const { themeValue: currentTheme } = storeToRefs(settingStore)
const rayChartRef = ref<HTMLElement>() // `echart` 容器实例
const rayChartWrapperRef = ref<HTMLElement>()
const echartInstanceRef = ref<ECharts>() // `echart` 实例
@ -130,6 +130,30 @@ export default defineComponent({
}
}
/**
*
* chart
*/
const updateChartTheme = () => {
if (props.theme === 'default') {
props.autoChangeTheme ? renderChart('dark') : renderChart('')
return
}
if (!props.theme) {
const theme = props.autoChangeTheme
? currentTheme.value
? `${echartTheme}-dark`
: echartTheme
: echartTheme
renderChart(theme)
} else {
renderChart(props.theme)
}
}
/**
*
* @returns `chart options`
@ -171,7 +195,7 @@ export default defineComponent({
*
* 使, `legend`
*/
const renderChart = (theme: ChartTheme = echartTheme) => {
const renderChart = (theme: string = echartTheme) => {
/** 获取 dom 容器 */
const element = rayChartRef.value as HTMLElement
/** 获取配置项 */
@ -217,24 +241,6 @@ export default defineComponent({
}
}
/**
*
* @param bool
*
*
*/
const renderThemeChart = (bool?: boolean) => {
if (props.autoChangeTheme) {
bool ? renderChart(`${echartTheme}-dark`) : renderChart()
return
}
if (!props.theme) {
renderChart()
}
}
/**
*
* `chart` ,
@ -249,7 +255,10 @@ export default defineComponent({
/** 重置 echarts 尺寸 */
const resizeChart = () => {
if (echartInstanceRef.value) {
echartInstanceRef.value.resize()
try {
echartInstanceRef.value.resize()
// eslint-disable-next-line no-empty
} catch (e) {}
}
}
@ -263,12 +272,7 @@ export default defineComponent({
return
}
if (props.autoChangeTheme) {
/** 注册 echarts */
renderThemeChart(themeValue.value)
} else {
props.theme ? renderChart(`${echartTheme}-dark`) : renderChart()
}
updateChartTheme()
/** 注册事件 */
if (props.autoResize) {
@ -296,8 +300,8 @@ export default defineComponent({
/** 监听全局主题变化, 然后重新渲染对应主题 echarts */
watch(
() => themeValue.value,
(theme) => {
() => currentTheme.value,
() => {
/**
*
* Q: 为什么需要重新卸载再渲染
@ -306,8 +310,7 @@ export default defineComponent({
*/
if (props.autoChangeTheme) {
destroyChart()
renderThemeChart(theme)
updateChartTheme()
}
},
)
@ -322,12 +325,7 @@ export default defineComponent({
() => props.showAria,
() => {
destroyChart()
if (props.autoChangeTheme || props.theme) {
themeValue.value ? renderChart(`${echartTheme}-dark`) : renderChart()
} else {
renderChart()
}
updateChartTheme()
},
)
@ -369,13 +367,11 @@ export default defineComponent({
/** 注册 echarts 组件与渲染器 */
await registerChartCore()
})
onMounted(() => {
nextTick(() => {
mount()
})
})
onBeforeUnmount(() => {
unmount()
watchCallback?.()

View File

@ -47,6 +47,7 @@ const props = {
canvasRender: {
/**
*
* @deprecated
* `chart` , 使 `canvas`
*
* , `SVGRenderer`
@ -91,8 +92,12 @@ const props = {
default: null,
},
theme: {
type: [String, Object] as PropType<ChartTheme>,
default: '',
/**
*
* chart theme
*/
type: String as PropType<ChartTheme>,
default: null,
},
autoChangeTheme: {
/**

View File

@ -43,7 +43,12 @@ export type AutoResize =
height: number
}
export type ChartTheme = 'macarons-dark' | string | object | 'macarons'
export type ChartTheme =
| 'macarons-dark'
| 'macarons'
| 'default'
| string
| null
export interface RayChartInst {
/**

View File

@ -7,4 +7,14 @@
1. 配置、选择主题
2. 点击下载主题
3. 选择 json 类型,然后复制
4. 在 @/echart-themes 包中创建对应的 json 文件,文件名为主题名称
4. 在 src/echart-themes 包中创建对应的 json 文件,文件名为主题名称
## 注意
### 一份主题
如果有且仅有一份 echart theme则会视为明暗主题色都共用一套主题色。
### 两份主题
下载好的主题应该分为xxx 与 xxx-dark 两份。这样模板会自动根据配置主题色切换明暗主题。

View File

@ -20,6 +20,7 @@
/** 全局响应式变量 */
const variableState = reactive({
globalSpinning: false,
globalDrawerValue: false,
})
type VariableStateKey = keyof typeof variableState

View File

@ -12,5 +12,6 @@
import { useI18n, t } from './useI18n'
import { useVueRouter } from '../web/useVueRouter'
import { useDayjs } from '../web/useDayjs'
import { useDevice } from './useDevice'
export { useI18n, useVueRouter, useDayjs, t }
export { useI18n, useVueRouter, useDayjs, t, useDevice }

View File

@ -0,0 +1,32 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-24
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
*/
import { useWindowSize } from '@vueuse/core'
export function useDevice() {
const { width, height } = useWindowSize()
const isTabletOrSmaller = ref(false)
watchEffect(() => {
isTabletOrSmaller.value = width.value <= 768
})
return {
width,
height,
isTabletOrSmaller,
}
}

6
src/icons/menu.svg Normal file
View File

@ -0,0 +1,6 @@
<svg t="1698137684048" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="3267" width="64" height="64">
<path
d="M495.65696 499.15904H250.80832C115.79392 499.15904 5.95968 389.3248 5.95968 254.3104S115.79392 9.46176 250.80832 9.46176s244.8384 109.83424 244.8384 244.84864c0 1.1776-0.03072 2.34496-0.07168 3.51232l-0.04096 1.52576 0.12288 239.81056zM250.80832 50.42176c-112.42496 0-203.88864 91.46368-203.88864 203.88864s91.46368 203.88864 203.88864 203.88864h203.8784l-0.1024-199.22944 0.11264-4.6592c-0.01024-112.42496-91.46368-203.88864-203.88864-203.88864zM776.00768 499.15904H531.15904l0.12288-240.20992-0.04096-1.11616a99.95264 99.95264 0 0 1-0.07168-3.51232c0-135.0144 109.83424-244.84864 244.8384-244.84864s244.84864 109.824 244.84864 244.8384-109.83424 244.84864-244.84864 244.84864z m-203.86816-40.96h203.86816c112.42496 0 203.88864-91.46368 203.88864-203.88864S888.44288 50.42176 776.00768 50.42176c-112.42496 0-203.8784 91.46368-203.8784 203.88864l0.11264 4.2496-0.1024 199.63904zM247.98208 1024C112.97792 1024 3.14368 914.15552 3.14368 779.15136s109.83424-244.8384 244.84864-244.8384H492.8512l-0.13312 240.20992 0.04096 1.00352c0.04096 1.19808 0.08192 2.4064 0.08192 3.62496C492.83072 914.15552 382.99648 1024 247.98208 1024z m0-448.72704c-112.42496 0-203.88864 91.46368-203.88864 203.8784C44.10368 891.57632 135.55712 983.04 247.98208 983.04s203.88864-91.46368 203.88864-203.88864l-0.11264-4.17792 0.1024-199.70048h-203.8784zM773.20192 1024c-135.0144 0-244.84864-109.84448-244.84864-244.84864 0-1.28 0.04096-2.53952 0.08192-3.79904l0.0512-1.37216-0.14336-239.6672h244.85888c135.0144 0 244.8384 109.83424 244.8384 244.8384S908.20608 1024 773.20192 1024z m-203.8784-448.72704l0.11264 199.22944-0.12288 4.64896C569.31328 891.57632 660.76672 983.04 773.20192 983.04c112.42496 0 203.8784-91.46368 203.8784-203.88864 0-112.42496-91.46368-203.8784-203.8784-203.8784h-203.8784z"
fill="currentColor" p-id="3268"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,7 @@
.n-drawer.app-menu__drawer {
width: auto !important;
& .n-layout-sider {
height: 100%;
}
}

View File

@ -9,11 +9,15 @@
* @remark
*/
import { NMenu, NLayoutSider } from 'naive-ui'
import './index.scss'
import { NMenu, NLayoutSider, NDrawer } from 'naive-ui'
import SiderBarLogo from './components/SiderBarLogo/index'
import { useMenu } from '@/store'
import { APP_MENU_CONFIG } from '@/app-config/appConfig'
import { useDevice } from '@/hooks/web/index'
import { globalVariableToRefs, setVariable } from '@/hooks/variable/index'
import type { MenuInst } from 'naive-ui'
import type { NaiveMenuOptions } from '@/types/modules/component'
@ -35,11 +39,21 @@ const LayoutMenu = defineComponent({
return menuStore.menuKey
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
set: () => {},
set: () => {
if (isTabletOrSmaller.value) {
setVariable('globalDrawerValue', false)
}
},
})
const modelMenuOptions = computed(() => menuStore.options)
const modelCollapsed = computed(() => menuStore.collapsed)
const { isTabletOrSmaller } = useDevice()
const modelGlobalDrawerValue = computed({
get: () => globalVariableToRefs('globalDrawerValue').value,
set: (val) => {
setVariable('globalDrawerValue', val)
},
})
const showMenuOption = () => {
const key = modelMenuKey.value as string
@ -49,42 +63,55 @@ const LayoutMenu = defineComponent({
})
}
return {
modelMenuKey,
changeMenuModelValue,
modelMenuOptions,
modelCollapsed,
collapsedMenu,
menuRef,
}
},
render() {
return (
const BaseicMenu = () => (
<NLayoutSider
bordered
showTrigger
collapseMode={APP_MENU_CONFIG.MENU_COLLAPSED_MODE}
collapsedWidth={APP_MENU_CONFIG.MENU_COLLAPSED_WIDTH}
onUpdateCollapsed={this.collapsedMenu.bind(this)}
showTrigger={!isTabletOrSmaller.value}
collapseMode={APP_MENU_CONFIG.menuCollapsedMode}
collapsedWidth={APP_MENU_CONFIG.menuCollapsedWidth}
onUpdateCollapsed={collapsedMenu.bind(this)}
nativeScrollbar={false}
>
<SiderBarLogo collapsed={this.modelCollapsed} />
<SiderBarLogo collapsed={modelCollapsed.value} />
<NMenu
ref="menuRef"
class="r-menu--app"
v-model:value={this.modelMenuKey}
options={this.modelMenuOptions as NaiveMenuOptions[]}
indent={APP_MENU_CONFIG.MENU_COLLAPSED_INDENT}
collapsed={this.modelCollapsed}
collapsedIconSize={APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE}
collapsedWidth={APP_MENU_CONFIG.MENU_COLLAPSED_WIDTH}
v-model:value={modelMenuKey.value}
options={modelMenuOptions.value as NaiveMenuOptions[]}
indent={APP_MENU_CONFIG.menuCollapsedIndent}
collapsed={modelCollapsed.value}
collapsedIconSize={APP_MENU_CONFIG.menuCollapsedIconSize}
collapsedWidth={APP_MENU_CONFIG.menuCollapsedWidth}
onUpdateValue={(key, op) => {
this.changeMenuModelValue(key, op as unknown as AppMenuOption)
changeMenuModelValue(key, op as unknown as AppMenuOption)
}}
accordion={APP_MENU_CONFIG.MENU_ACCORDION}
accordion={APP_MENU_CONFIG.menuAccordion}
/>
</NLayoutSider>
)
return {
menuRef,
isTabletOrSmaller,
BaseicMenu,
modelGlobalDrawerValue,
}
},
render() {
const { isTabletOrSmaller, BaseicMenu } = this
return !isTabletOrSmaller ? (
<BaseicMenu />
) : (
<NDrawer
class="app-menu__drawer"
v-model:show={this.modelGlobalDrawerValue}
placement="left"
displayDirective="show"
>
<BaseicMenu />
</NDrawer>
)
},
})

View File

@ -21,6 +21,7 @@
import { NDropdown, NBreadcrumb, NBreadcrumbItem } from 'naive-ui'
import { useMenu } from '@/store'
import { useDevice } from '@/hooks/web/index'
import type { DropdownOption } from 'naive-ui'
import type {
@ -29,7 +30,7 @@ import type {
AppMenuKey,
} from '@/types/modules/app'
const Breadcrumb = defineComponent({
export default defineComponent({
name: 'RBreadcrumb',
setup() {
const menuStore = useMenu()
@ -37,15 +38,13 @@ const Breadcrumb = defineComponent({
const { changeMenuModelValue } = menuStore
const { breadcrumbOptions } = storeToRefs(menuStore)
const modelBreadcrumbOptions = computed(() => breadcrumbOptions.value)
const { isTabletOrSmaller } = useDevice()
const handleDropdownSelect = (
key: string | number,
option: DropdownOption,
) => {
const dropdownSelect = (key: string | number, option: DropdownOption) => {
changeMenuModelValue(key, option as unknown as AppMenuOption)
}
const handleBreadcrumbItemClick = (option: AppMenuOption) => {
const breadcrumbItemClick = (option: AppMenuOption) => {
if (!option.children?.length) {
const { meta = {} } = option
@ -57,24 +56,29 @@ const Breadcrumb = defineComponent({
return {
modelBreadcrumbOptions,
handleDropdownSelect,
handleBreadcrumbItemClick,
dropdownSelect,
breadcrumbItemClick,
isTabletOrSmaller,
}
},
render() {
return (
const { isTabletOrSmaller } = this
return isTabletOrSmaller ? (
<div></div>
) : (
<NBreadcrumb>
{this.modelBreadcrumbOptions.map((curr) => (
<NBreadcrumbItem
key={curr.key}
onClick={this.handleBreadcrumbItemClick.bind(this, curr)}
onClick={this.breadcrumbItemClick.bind(this, curr)}
>
<NDropdown
labelField="breadcrumbLabel"
options={
curr.children && curr.children?.length > 1 ? curr.children : []
}
onSelect={this.handleDropdownSelect.bind(this)}
onSelect={this.dropdownSelect.bind(this)}
>
{{
default: () => (
@ -92,5 +96,3 @@ const Breadcrumb = defineComponent({
)
},
})
export default Breadcrumb

View File

@ -18,11 +18,12 @@ import { on, off, queryElements, addClass, removeClass } from '@/utils/element'
import { debounce } from 'lodash-es'
import { useMenu } from '@/store'
import { validMenuItemShow } from '@/router/helper/routerCopilot'
import { useDevice } from '@/hooks/web/index'
import type { AppRouteMeta } from '@/router/type'
import type { AppMenuOption } from '@/types/modules/app'
const GlobalSeach = defineComponent({
export default defineComponent({
name: 'GlobalSeach',
props: {
show: {
@ -76,6 +77,7 @@ const GlobalSeach = defineComponent({
let searchElementIndex = 0
/** 缓存索引 */
let preSearchElementIndex = searchElementIndex
const { isTabletOrSmaller } = useDevice()
/** 初始化一些值 */
const resetSearchSomeValue = () => {
@ -240,13 +242,18 @@ const GlobalSeach = defineComponent({
autoFouceSearchItem()
}
watchEffect(() => {
if (isTabletOrSmaller.value) {
modelShow.value = false
}
})
onMounted(() => {
on(window, 'keydown', (e: Event) => {
registerArouseKeyboard(e as KeyboardEvent)
registerChangeSearchElementIndex(e as KeyboardEvent)
})
})
onBeforeUnmount(() => {
off(window, 'keydown', (e: Event) => {
registerArouseKeyboard(e as KeyboardEvent)
@ -261,10 +268,15 @@ const GlobalSeach = defineComponent({
handleSearchMenuOptions: debounce(handleSearchMenuOptions, 300),
handleSearchItemClick,
RenderPreIcon,
isTabletOrSmaller,
}
},
render() {
return (
const { isTabletOrSmaller } = this
return isTabletOrSmaller ? (
<div></div>
) : (
<NModal v-model:show={this.modelShow} transform-origin="center" show>
<div class="global-seach global-seach--dark global-seach--light">
<div class="global-seach__wrapper">
@ -339,5 +351,3 @@ const GlobalSeach = defineComponent({
)
},
})
export default GlobalSeach

View File

@ -113,7 +113,7 @@ const SettingDrawer = defineComponent({
{t('headerSettingOptions.ThemeOptions.PrimaryColorConfig')}
</NDivider>
<NColorPicker
swatches={APP_THEME.APP_THEME_COLOR}
swatches={APP_THEME.appThemeColors}
v-model:value={this.primaryColorOverride.common!.primaryColor}
onUpdateValue={this.changePrimaryColor.bind(this)}
/>

View File

@ -32,8 +32,10 @@ import { LOCAL_OPTIONS } from '@/app-config/localConfig'
import { useAvatarOptions, avatarDropdownClick } from './hook'
import { useI18n } from '@/hooks/web/index'
import { useFullscreen } from 'vue-hooks-plus'
import { useDevice } from '@/hooks/web/index'
import { globalVariableToRefs, setVariable } from '@/hooks/variable/index'
import type { IconEventMapOptions, IconEventMap } from './type'
import type { LeftIconOptions, IconEventMapOptions, IconEventMap } from './type'
const SiderBar = defineComponent({
name: 'SiderBar',
@ -53,55 +55,76 @@ const SiderBar = defineComponent({
display: 'flex',
}
const globalSearchShown = ref(false)
const { isTabletOrSmaller } = useDevice()
const globalDrawerValue = globalVariableToRefs('globalDrawerValue')
/**
*
*
*/
const leftIconOptions = computed(() => [
{
name: 'reload',
size: 18,
tooltip: t('headerTooltip.Reload'),
iconClass: computed(() =>
!reloadRouteSwitch.value ? 'ray-icon__reload--loading' : '',
),
},
])
const leftIconOptions = computed(() => {
const options: LeftIconOptions[] = [
{
name: 'reload',
size: 18,
tooltip: t('headerTooltip.Reload'),
iconClass: computed(() =>
!reloadRouteSwitch.value ? 'ray-icon__reload--loading' : '',
),
},
]
if (isTabletOrSmaller.value) {
options[0] = {
name: 'menu',
size: 18,
}
}
return options
})
/**
*
*
*/
const rightTooltipIconOptions = computed(() => [
{
name: 'search',
size: 18,
tooltip: t('headerTooltip.Search'),
eventKey: 'search',
},
{
name: 'fullscreen',
size: 18,
tooltip: computed(() =>
isFullscreen.value
? t('headerTooltip.CancelFullScreen')
: t('headerTooltip.FullScreen'),
),
eventKey: 'screen',
},
{
name: 'github',
size: 18,
tooltip: t('headerTooltip.Github'),
eventKey: 'github',
},
{
name: 'setting',
size: 18,
tooltip: t('headerTooltip.Setting'),
eventKey: 'setting',
},
])
const rightTooltipIconOptions = computed(() => {
const options = [
{
name: 'search',
size: 18,
tooltip: t('headerTooltip.Search'),
eventKey: 'search',
},
{
name: 'fullscreen',
size: 18,
tooltip: computed(() =>
isFullscreen.value
? t('headerTooltip.CancelFullScreen')
: t('headerTooltip.FullScreen'),
),
eventKey: 'screen',
},
{
name: 'github',
size: 18,
tooltip: t('headerTooltip.Github'),
eventKey: 'github',
},
{
name: 'setting',
size: 18,
tooltip: t('headerTooltip.Setting'),
eventKey: 'setting',
},
]
if (isTabletOrSmaller.value) {
options.shift()
}
return options
})
const iconEventMap: IconEventMapOptions = {
// 刷新组件重新加载,手动设置 800ms loading 时长
reload: () => {
@ -124,26 +147,46 @@ const SiderBar = defineComponent({
lock: () => {
changeSwitcher(true, 'lockScreenSwitch')
},
menu: () => {
setVariable('globalDrawerValue', !globalDrawerValue.value)
},
}
const handleIconClick = (key: IconEventMap) => {
const toolIconClick = (key: IconEventMap) => {
iconEventMap[key]?.()
}
const LeftToolIcon = (props: (typeof leftIconOptions.value)[0]) => {
const { iconClass, name, size } = props
return (
<RIcon
customClassName={`${isRef(iconClass) ? iconClass.value : iconClass}`}
name={name}
size={size}
cursor="pointer"
onClick={toolIconClick.bind(this, name)}
/>
)
}
return {
leftIconOptions,
rightTooltipIconOptions,
t,
handleIconClick,
toolIconClick,
showSettings,
updateLocale,
spaceItemStyle,
drawerPlacement,
breadcrumbSwitch,
globalSearchShown,
LeftToolIcon,
}
},
render() {
const { LeftToolIcon } = this
return (
<NLayoutHeader class="layout-header" bordered>
<GlobalSeach v-model:show={this.globalSearchShown} />
@ -157,26 +200,18 @@ const SiderBar = defineComponent({
wrapItem={false}
itemStyle={this.spaceItemStyle}
>
{this.leftIconOptions.map((curr) => (
<NTooltip>
{{
trigger: () => (
<RIcon
customClassName={`${
isRef(curr.iconClass)
? curr.iconClass.value
: curr.iconClass
}`}
name={curr.name}
size={curr.size}
cursor="pointer"
onClick={this.handleIconClick.bind(this, curr.name)}
/>
),
default: () => curr.tooltip,
}}
</NTooltip>
))}
{this.leftIconOptions.map((curr) =>
curr.tooltip ? (
<NTooltip>
{{
trigger: () => <LeftToolIcon {...curr} />,
default: () => curr.tooltip,
}}
</NTooltip>
) : (
<LeftToolIcon {...curr} />
),
)}
{this.breadcrumbSwitch ? <Breadcrumb /> : null}
</NSpace>
<NSpace
@ -190,7 +225,7 @@ const SiderBar = defineComponent({
tooltipText={
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip
}
onClick={this.handleIconClick.bind(this, curr.name)}
onClick={this.toolIconClick.bind(this, curr.name)}
/>
))}
<NDropdown

View File

@ -1,4 +1,5 @@
import type { DropdownOption } from 'naive-ui'
import type { ComputedRef } from 'vue'
export interface IconEventMapOptions {
[propName: string]: (...args: unknown[]) => unknown
@ -20,3 +21,10 @@ export interface IconOptions {
eventKey?: string
dropdown?: IconDropdownOptions
}
export interface LeftIconOptions {
name: string
size: number
tooltip?: string
iconClass?: ComputedRef
}

View File

@ -126,7 +126,7 @@ export const naiveLocales = (key: string) => {
* @remak , `main.ts` , `i18n`
*/
export const getAppDefaultLanguage = () => {
const language = getStorage<string>(
const language = getStorage(
APP_CATCH_KEY.localeLanguage,
'localStorage',
SYSTEM_DEFAULT_LOCAL,

View File

@ -163,7 +163,7 @@ export const hasMenuIcon = (option: AppMenuOption) => {
RIcon,
{
name: meta!.icon as string,
size: APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE,
size: APP_MENU_CONFIG.menuCollapsedIconSize,
cursor: 'pointer',
},
{},

View File

@ -22,7 +22,7 @@ export const useSetting = defineStore(
const settingState = reactive<SettingState>({
drawerPlacement: 'right',
primaryColorOverride: {
...APP_THEME.APP_NAIVE_UI_THEME_OVERRIDES,
...APP_THEME.appNaiveUIThemeOverrides,
common: {
primaryColor: primaryColor, // 主题色
primaryColorHover: primaryColor,

View File

@ -3,11 +3,11 @@ import type { CreateAxiosDefaults } from 'axios'
export type CollapsedMode = 'transform' | 'width'
export interface AppMenuConfig {
MENU_COLLAPSED_WIDTH: number
MENU_COLLAPSED_MODE: CollapsedMode
MENU_COLLAPSED_ICON_SIZE: number
MENU_COLLAPSED_INDENT: number
MENU_ACCORDION: boolean
menuCollapsedWidth: number
menuCollapsedMode: CollapsedMode
menuCollapsedIconSize: number
menuCollapsedIndent: number
menuAccordion: boolean
}
export interface AppKeepAlive {

View File

@ -76,8 +76,8 @@ 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
appThemeColors: string[]
appPrimaryColor: AppPrimaryColor
appNaiveUIThemeOverrides: GlobalThemeOverrides
echartTheme: string
}

View File

@ -9,8 +9,6 @@
* @remark
*/
/** vue3 项目里建议直接用 vueuse useStorage 方法 */
import type { StorageLike, RemoveStorageKey } from '@/types/modules/utils'
/**
@ -40,15 +38,13 @@ function setStorage<T = unknown>(
}
}
/** 重载函数 getStorage */
function getStorage<T>(
function getStorage<T = unknown>(
key: string,
storageType: StorageLike,
defaultValue: T,
): T
/** 重载函数 getStorage */
function getStorage<T>(
function getStorage<T = unknown>(
key: string,
storageType?: StorageLike,
defaultValue?: T,
@ -59,7 +55,7 @@ function getStorage<T>(
* @param key key
* @returns
*/
function getStorage<T>(
function getStorage<T = unknown>(
key: string,
storageType: StorageLike = 'sessionStorage',
defaultValue?: T,

View File

@ -242,22 +242,23 @@ const Echart = defineComponent({
<NCard title="chart 组件">
<ul>
<li>
<h3> 200*200 </h3>
<h3>1. 200*200 </h3>
</li>
<li>
<h3>
autoChangeThemeRayTemplate
2. autoChangeTheme
false APP_THEME.echartTheme RayTemplate
</h3>
</li>
<li>
<h3> watchOptions</h3>
<h3>3. watchOptions</h3>
</li>
<li>
<h3> animation</h3>
<h3>4. animation</h3>
</li>
<li>
<h3> setChartOptions </h3>
<h3>5. setChartOptions </h3>
</li>
</ul>
</NCard>
@ -277,11 +278,11 @@ const Echart = defineComponent({
showAria={this.chartAria}
/>
</div>
<NH2></NH2>
<NH2></NH2>
<div class="chart--container">
<RChart
autoChangeTheme={false}
theme="dark"
theme="default"
options={this.baseOptions}
/>
</div>