mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-06-10 09:49:17 +08:00
version: v4.4.2
This commit is contained in:
parent
9e3cbac091
commit
f272832a8c
@ -33,6 +33,7 @@ module.exports = {
|
||||
defineExpose: 'readonly',
|
||||
withDefaults: 'readonly',
|
||||
defineOptions: 'readonly',
|
||||
defineModel: 'readonly',
|
||||
},
|
||||
rules: {
|
||||
'no-undefined': ['error'],
|
||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@ -1,5 +1,36 @@
|
||||
# CHANGE LOG
|
||||
|
||||
## 4.4.2
|
||||
|
||||
这是一个具有破坏性更新的版本,如果你使用了该模板,那么你需要做一些改动。
|
||||
|
||||
详细拆分 `hooks` 包的方法。以前的划分方式不太合理,所以进行了一次大的重构。并且新增了一些方法。现在按照方法功能进行分包,更加详细。
|
||||
|
||||
剔除 `h` 函数渲染,因为该方法不会受到 `vue` 的编译优化。
|
||||
|
||||
补充了一些代码的注释(慢慢还账-,-)。
|
||||
|
||||
### Feats
|
||||
|
||||
- 重新划分 `hooks` 包,按照功能进行拆分,并且新增一些包
|
||||
- `useAppMenu` 方法更名为 `useAppNavigation`
|
||||
- `useMenuTag` 方法更名为 `useSiderBar`
|
||||
- `useRootRoute` 方法更名为 `useAppRoot`
|
||||
- `useMainPage` 包移除
|
||||
- 新增 `useMaximize`, `useSpinning`, `useTheme`, `useWatermark` 方法
|
||||
- 新增 `components` 包,用于存放模板二次封装组件、`NaiveUI` 组件的一些 `hooks` 方法
|
||||
- 每个方法包导出对应的 `ReturnType` 类型
|
||||
- `Breadcrumb` 组件新增过渡效果,现在切换路由时会有过渡动画,视觉效果更友好
|
||||
- 移除 `getVariable` 方法
|
||||
- 移除 `utils/element` 包部分方法
|
||||
- `on`
|
||||
- `off`
|
||||
- 移除 `changeSwitcher` 方法,使用 `updateSettingState` 方法代替
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `setRootRoute` 方法执行时提示只读错误导致不能正常修改的问题
|
||||
|
||||
## 4.4.1
|
||||
|
||||
更新 `vite` 版本至 `5.0.4`。同步修复了一些小问题。
|
||||
@ -77,7 +108,7 @@
|
||||
|
||||
紧跟尤大大脚步,更新 `vite` 版本至 `5.0.0` 版本!与此同时,更新了配套所有插件!
|
||||
|
||||
更新 ROOT_ROUTE 的一些使用方法,该配置方法与原有的方式不变,但是有一个新的功能点则是,该配置项会传递给 global-variable 的 globalRootRoute 属性。并且更改模板原有获取 path 的方法,改为响应式获取。当你要进行动态的维护 Root Route 的时候,该方法可能可以帮助到你 `useRootRoute`。
|
||||
更新 ROOT_ROUTE 的一些使用方法,该配置方法与原有的方式不变,但是有一个新的功能点则是,该配置项会传递给 global-variable 的 globalRootRoute 属性。并且更改模板原有获取 path 的方法,改为响应式获取。当你要进行动态的维护 Root Route 的时候,该方法可能可以帮助到你 `useAppRoot`。
|
||||
|
||||
如果你在更新版本后出现一些奇奇怪怪的问题,不要犹豫,直接删除 `node_modules` 后再重新安装依赖,这是缓存导致的问题。
|
||||
|
||||
|
@ -9,14 +9,14 @@
|
||||
|
||||
简体中文 | [English](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/README.md)
|
||||
|
||||
一个 `免费`、`高效`、`特性完整` 并且基于 vite4.x & ts(x) & pinia & vue3.x 等最新技术的中后台模板。
|
||||
一个 `免费`、`高效`、`特性完整` 并且基于 vite5.x & ts(x) & pinia & vue3.x 等最新技术的中后台模板。
|
||||
|
||||
</div>
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- **靠爱发电**:几乎包含市面常见的模板特性并且全部免费使用
|
||||
- **最新技术栈**:使用 vue3.x/vite4.x/pinia 等前端前沿技术开发
|
||||
- **最新技术栈**:使用 vue3.x/vite5.x/pinia 等前端前沿技术开发
|
||||
- **TypeScript**:应用程序级 JavaScript 的语言
|
||||
- **主题**:可配置的主题
|
||||
- **国际化**:内置完善的国际化方案
|
||||
|
@ -9,14 +9,14 @@
|
||||
|
||||
English | [简体中文](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/README-ZH.md)
|
||||
|
||||
A `free`, `efficient`, `complete with features` middle and backend template based on the latest technologies such as vite4.x & ts(x) & pinia & vue3.x.
|
||||
A `free`, `efficient`, `complete with features` middle and backend template based on the latest technologies such as vite5.x & ts(x) & pinia & vue3.x.
|
||||
|
||||
</div>
|
||||
|
||||
## ✨ Feature
|
||||
|
||||
- **Power by love**: Contains almost all common template features on the market and all are free to use.
|
||||
- **Latest Technology Stack**:Developed using front-end cutting-edge technologies such as vue3.x/vite4.x/pinia.
|
||||
- **Latest Technology Stack**:Developed using front-end cutting-edge technologies such as vue3.x/vite5.x/pinia.
|
||||
- **TypeScript**:The language for application-level JavaScript.
|
||||
- **App Theme**:Configurable themes.
|
||||
- **Globalization**:Built-in complete internationalization solution.
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ray-template",
|
||||
"private": false,
|
||||
"version": "4.4.1",
|
||||
"version": "4.4.2",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0",
|
||||
|
@ -27,7 +27,7 @@ const LockScreen = defineComponent({
|
||||
const inputInstRef = ref<InputInst | null>(null)
|
||||
|
||||
const { setLockAppScreen } = useAppLockScreen()
|
||||
const { changeSwitcher } = useSettingActions()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
const state = reactive({
|
||||
lockCondition: useCondition(),
|
||||
@ -38,7 +38,7 @@ const LockScreen = defineComponent({
|
||||
formInstRef.value?.validate((error) => {
|
||||
if (!error) {
|
||||
setLockAppScreen(true)
|
||||
changeSwitcher(true, 'lockScreenSwitch')
|
||||
updateSettingState('lockScreenSwitch', true)
|
||||
|
||||
state.lockCondition = useCondition()
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export default defineComponent({
|
||||
const inputInstRef = ref<InputInst | null>(null)
|
||||
|
||||
const { logout } = useSigningActions()
|
||||
const { changeSwitcher } = useSettingActions()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
const { setLockAppScreen } = useAppLockScreen()
|
||||
const { isTabletOrSmaller } = useDevice()
|
||||
|
||||
@ -64,7 +64,7 @@ export default defineComponent({
|
||||
onPositiveClick: () => {
|
||||
logout()
|
||||
setTimeout(() => {
|
||||
changeSwitcher(false, 'lockScreenSwitch')
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -75,7 +75,7 @@ export default defineComponent({
|
||||
formRef.value?.validate((error) => {
|
||||
if (!error) {
|
||||
setLockAppScreen(false)
|
||||
changeSwitcher(false, 'lockScreenSwitch')
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
|
||||
state.lockCondition = useCondition()
|
||||
}
|
||||
|
@ -28,12 +28,12 @@ const AppLockScreen = defineComponent({
|
||||
name: 'AppLockScreen',
|
||||
setup() {
|
||||
const { getLockAppScreen } = useAppLockScreen()
|
||||
const { changeSwitcher } = useSettingActions()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
const { getLockScreenSwitch } = useSettingGetters()
|
||||
const lockScreenSwitchRef = computed({
|
||||
get: () => getLockScreenSwitch.value,
|
||||
set: (val) => {
|
||||
changeSwitcher(val, 'lockScreenSwitch')
|
||||
updateSettingState('lockScreenSwitch', val)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -15,25 +15,29 @@
|
||||
*
|
||||
* 该组件启用时,会在全局(包括首页)展示
|
||||
* 如果你不希望在登录页显示,可以手动将该组件放置于 Layout 中
|
||||
*
|
||||
* 当然你也可以通过 useWatermark hook 自定义控制水印的显示以及内容
|
||||
*/
|
||||
|
||||
import { NWatermark } from 'naive-ui'
|
||||
|
||||
import { APP_WATERMARK_CONFIG } from '@/app-config/appConfig'
|
||||
import { useSettingGetters } from '@/store'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppWatermarkProvider',
|
||||
setup() {
|
||||
const { getWatermarkSwitch } = useSettingGetters()
|
||||
const { getWatermarkSwitch, getWatermarkConfig } = useSettingGetters()
|
||||
|
||||
return {
|
||||
getWatermarkSwitch,
|
||||
getWatermarkConfig,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return this.getWatermarkSwitch ? (
|
||||
<NWatermark cross fullscreen {...APP_WATERMARK_CONFIG} />
|
||||
const { getWatermarkConfig, getWatermarkSwitch } = this
|
||||
|
||||
return getWatermarkSwitch ? (
|
||||
<NWatermark cross fullscreen {...getWatermarkConfig} />
|
||||
) : null
|
||||
},
|
||||
})
|
||||
|
@ -52,7 +52,7 @@ export const PRE_LOADING_CONFIG: PreloadingConfig = {
|
||||
* 该变量的值,会传递给 globalRootRoute
|
||||
* 这么做也是为了能够在兼容老版本的模板,并且也是为了能够动态的维护根路由信息
|
||||
*
|
||||
* 有些时候,如果你希望动态的维护 Root Route 信息,可以使用 useRootRoute 方法
|
||||
* 有些时候,如果你希望动态的维护 Root Route 信息,可以使用 useAppRoot 方法
|
||||
*/
|
||||
export const ROOT_ROUTE: RootRoute = {
|
||||
name: 'Dashboard',
|
||||
|
@ -13,9 +13,10 @@ import './index.scss'
|
||||
|
||||
import { NSpin } from 'naive-ui'
|
||||
|
||||
import { completeSize, on, off } from '@use-utils/element'
|
||||
import { completeSize } from '@use-utils/element'
|
||||
import { call } from '@/utils/vue'
|
||||
import props from './props'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RIframe',
|
||||
@ -53,19 +54,13 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
useEventListener(iframeRef, 'load', iframeLoadSuccess)
|
||||
useEventListener(iframeRef, 'error', iframeLoadError)
|
||||
|
||||
expose({
|
||||
iframeInst: iframeRef,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
on(iframeRef.value, 'load', iframeLoadSuccess.bind(this))
|
||||
on(iframeRef.value, 'error', iframeLoadError)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
off(iframeRef.value, 'load', iframeLoadSuccess)
|
||||
off(iframeRef.value, 'error', iframeLoadError)
|
||||
})
|
||||
|
||||
return {
|
||||
cssVars,
|
||||
iframeRef,
|
||||
|
@ -9,6 +9,8 @@ import type { ModalProps } from 'naive-ui'
|
||||
*
|
||||
* 根据预设模态框设置拖拽效果
|
||||
* 但是该效果有且仅有 card, dialog 有效
|
||||
*
|
||||
* 默认添加 30ms 延迟,避免诡异问题
|
||||
*/
|
||||
export const setupDraggable = (
|
||||
bindModal: HTMLElement,
|
||||
|
@ -35,13 +35,23 @@ export default defineComponent({
|
||||
const rTableInst = ref<DataTableInst | null>(null)
|
||||
const wrapperRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const uuidWrapper = uuid(16)
|
||||
const uuidTable = uuid(16)
|
||||
const uuidWrapper = uuid(16) // wrapper id
|
||||
const uuidTable = uuid(16) // table id
|
||||
/**
|
||||
*
|
||||
* x: 横坐标
|
||||
* y: 纵坐标
|
||||
* showContextMenu: 是否显示右键菜单
|
||||
*/
|
||||
const contextMenuReactive = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
showContextMenu: false,
|
||||
})
|
||||
/**
|
||||
*
|
||||
* size: table size,内部私有状态管理
|
||||
*/
|
||||
const privateReactive = reactive({
|
||||
size: props.size,
|
||||
})
|
||||
@ -90,10 +100,22 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param size table size
|
||||
*
|
||||
* 修改 table size
|
||||
*/
|
||||
const changeTableSize = (size: ComponentSize) => {
|
||||
privateReactive.size = size
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options table columns
|
||||
*
|
||||
* 更新 table columns,同时触发 onUpdateColumns 和 onUpdate:columns 事件
|
||||
*/
|
||||
const updateTableColumn = (options: CType[]) => {
|
||||
const { onUpdateColumns, 'onUpdate:columns': $onUpdateColumns } = props
|
||||
|
||||
@ -105,6 +127,11 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 处理自定义的 toolOptions
|
||||
* 匹配所有符合条件的 toolOptions,然后执行
|
||||
*/
|
||||
const renderToolOptions = () => {
|
||||
const { toolOptions } = props
|
||||
|
||||
@ -113,6 +140,12 @@ export default defineComponent({
|
||||
.map((curr) => (typeof curr === 'function' ? curr() : curr))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param p props
|
||||
*
|
||||
* 处理 toolOptions,合并渲染所有的 toolOptions
|
||||
*/
|
||||
const tool = (p: typeof props) => {
|
||||
const renderDefaultToolOptions = () => (
|
||||
<>
|
||||
@ -163,7 +196,6 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
render() {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const { tool } = this
|
||||
|
||||
return (
|
||||
@ -208,6 +240,7 @@ export default defineComponent({
|
||||
}),
|
||||
'header-extra': () => (
|
||||
<NSpace wrapItem={false} align="center">
|
||||
{/* eslint-disable @typescript-eslint/no-explicit-any */}
|
||||
{tool(this.$props as any)}
|
||||
</NSpace>
|
||||
),
|
||||
|
@ -32,11 +32,9 @@ import type { MaybeArray } from '@/types/modules/utils'
|
||||
|
||||
type FixedClick = (type: 'left' | 'right', option: C, index: number) => void
|
||||
|
||||
const renderSwitcherIcon = () =>
|
||||
h(RIcon, {
|
||||
name: 'draggable',
|
||||
size: config.tableIconSize,
|
||||
})
|
||||
const renderSwitcherIcon = () => (
|
||||
<RIcon name="draggable" size={config.tableIconSize} />
|
||||
)
|
||||
|
||||
const RowIconRender = ({
|
||||
icon,
|
||||
|
@ -1,3 +1,4 @@
|
||||
// 导出所有自定义组件
|
||||
export * from './RChart'
|
||||
export * from './RCollapseGrid'
|
||||
export * from './RIcon'
|
||||
@ -7,3 +8,11 @@ export * from './RMoreDropdown'
|
||||
export * from './RQRCode'
|
||||
export * from './RTable'
|
||||
export * from './RTransitionComponent'
|
||||
|
||||
// 导出自定义组件类型
|
||||
export type * from './RChart/src/type'
|
||||
export type * from './RCollapseGrid/src/type'
|
||||
export type * from './RIframe/src/type'
|
||||
export type * from './RQRCode/src/type'
|
||||
export type * from './RTable/src/type'
|
||||
export type * from './RTransitionComponent/src/type'
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { debounce } from 'lodash-es'
|
||||
import { on, off } from '@use-utils/element'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import type { DebounceBindingOptions } from './type'
|
||||
import type { AnyFC } from '@/types/modules/utils'
|
||||
@ -27,6 +27,7 @@ const debounceDirective: CustomDirectiveFC<
|
||||
DebounceBindingOptions
|
||||
> = () => {
|
||||
let debounceFunction: DebouncedFunc<AnyFC> | null
|
||||
let cleanup: () => void
|
||||
|
||||
return {
|
||||
beforeMount: (el, { value }) => {
|
||||
@ -38,14 +39,14 @@ const debounceDirective: CustomDirectiveFC<
|
||||
|
||||
debounceFunction = debounce(func, wait, Object.assign({}, options))
|
||||
|
||||
on(el, trigger, debounceFunction)
|
||||
cleanup = useEventListener(el, trigger, debounceFunction)
|
||||
},
|
||||
beforeUnmount: (el, { value }) => {
|
||||
const { trigger = 'click' } = value
|
||||
|
||||
if (debounceFunction) {
|
||||
debounceFunction.cancel()
|
||||
off(el, trigger, debounceFunction)
|
||||
cleanup?.()
|
||||
}
|
||||
|
||||
debounceFunction = null
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { throttle } from 'lodash-es'
|
||||
import { on, off } from '@use-utils/element'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import type { ThrottleBindingOptions } from './type'
|
||||
import type { AnyFC } from '@/types/modules/utils'
|
||||
@ -27,6 +27,7 @@ const throttleDirective: CustomDirectiveFC<
|
||||
ThrottleBindingOptions
|
||||
> = () => {
|
||||
let throttleFunction: DebouncedFunc<AnyFC> | null
|
||||
let cleanup: () => void
|
||||
|
||||
return {
|
||||
beforeMount: (el, { value }) => {
|
||||
@ -38,14 +39,12 @@ const throttleDirective: CustomDirectiveFC<
|
||||
|
||||
throttleFunction = throttle(func, wait, Object.assign({}, options))
|
||||
|
||||
on(el, trigger, throttleFunction)
|
||||
useEventListener(el, trigger, throttleFunction)
|
||||
},
|
||||
beforeUnmount: (el, { value }) => {
|
||||
const { trigger = 'click' } = value
|
||||
|
||||
beforeUnmount: () => {
|
||||
if (throttleFunction) {
|
||||
throttleFunction.cancel()
|
||||
off(el, trigger, throttleFunction)
|
||||
cleanup?.()
|
||||
}
|
||||
|
||||
throttleFunction = null
|
||||
|
@ -9,10 +9,6 @@
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { setVariable, getVariable, getVariableToRefs } from './variable'
|
||||
export * from './variable'
|
||||
|
||||
import type { VariableState, VariableStateKey } from './variable'
|
||||
|
||||
export { setVariable, getVariable, getVariableToRefs }
|
||||
|
||||
export type { VariableState, VariableStateKey }
|
||||
export type * from './variable'
|
||||
|
@ -11,27 +11,37 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* 存放全局临时变量,脱离 pinia 使用的变量
|
||||
* 存放全局响应式变量,脱离 pinia 使用的变量
|
||||
* 简单的全局变量,并不需要复杂的控制流程
|
||||
*
|
||||
* 使用方法
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* 获取普通变量
|
||||
* getVariable('target key', 'default value')
|
||||
*
|
||||
* 获取响应式变量
|
||||
* getVariableToRefs('target key')
|
||||
*
|
||||
* 更改 state 变量
|
||||
* setVariable('key', 'value')
|
||||
*
|
||||
* 创建响应式说仓库
|
||||
* createVariableState({ your state })
|
||||
*/
|
||||
|
||||
import { ROOT_ROUTE } from '@/app-config/appConfig'
|
||||
import { ROOT_ROUTE, APP_WATERMARK_CONFIG } from '@/app-config/appConfig'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type { AnyFC } from '@/types/modules/utils'
|
||||
import type { Mutable } from '@/types/modules/helper'
|
||||
|
||||
/**
|
||||
*
|
||||
* 全局响应式变量
|
||||
* 不推荐直接访问、设置该 state。并且更不建议在组件中直接使用该 state
|
||||
* 请使用 `getVariable`、`getVariableToRefs`、`setVariable` 方法进行操作
|
||||
*
|
||||
* 为什么会有该变量存在,是因为在有时候你希望在 vue 声明之后就调用该变量,但是又不想使用 pinia
|
||||
* 你可以使用该变量,但是请注意,该变量不会被 pinia 管理,所以你需要自己手动管理
|
||||
*/
|
||||
const variableState = reactive({
|
||||
globalSpinning: false, // 全局加载控制器
|
||||
globalDrawerValue: false, // 全局抽屉控制器(小尺寸设备可用)
|
||||
@ -45,6 +55,18 @@ export type VariableState = typeof variableState
|
||||
|
||||
export type VariableStateKey = keyof VariableState
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key variable key
|
||||
* @param value variable value
|
||||
* @param cb 设置完成后的回调函数
|
||||
*
|
||||
* 手动设置 variableState 的值
|
||||
*
|
||||
* @example
|
||||
* setVariable('globalSpinning', true) // 设置全局加载状态为 true
|
||||
* setVariable('globalSpinning', true, () => {}) // 设置全局加载状态为 true,并且在设置完成后执行回调函数
|
||||
*/
|
||||
export function setVariable<T extends VariableStateKey, FC extends AnyFC>(
|
||||
key: T,
|
||||
value: VariableState[T],
|
||||
@ -55,15 +77,15 @@ export function setVariable<T extends VariableStateKey, FC extends AnyFC>(
|
||||
cb?.()
|
||||
}
|
||||
|
||||
export function getVariable<T extends VariableStateKey>(
|
||||
key: VariableStateKey,
|
||||
defaultValue?: VariableState[T],
|
||||
) {
|
||||
const v = variableState[key]
|
||||
|
||||
return v ? readonly(variableState)[key] : defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key 目标值 key
|
||||
*
|
||||
* 返回目标值的响应式 ref
|
||||
*
|
||||
* @example
|
||||
* getVariableToRefs('globalSpinning') // 返回 ref<boolean>
|
||||
*/
|
||||
export function getVariableToRefs<K extends VariableStateKey>(key: K) {
|
||||
return readonly(toRef<VariableState, K>(variableState, key))
|
||||
}
|
||||
|
1
src/hooks/components/index.ts
Normal file
1
src/hooks/components/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './useContextmenuCoordinate'
|
86
src/hooks/components/useContextmenuCoordinate.ts
Normal file
86
src/hooks/components/useContextmenuCoordinate.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-12-01
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { useEventListener, onClickOutside } from '@vueuse/core'
|
||||
|
||||
import type { BasicTarget } from '@/types/modules/vue'
|
||||
import type { MaybeElementRef, MaybeElement } from '@vueuse/core'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target 绑定元素
|
||||
*
|
||||
* 右键点击元素时,获取鼠标坐标。该方法结合 NDropdown 组件使用,可以实现右键菜单功能
|
||||
*
|
||||
* @example
|
||||
* const target = ref<HTMLElement | null>(null)
|
||||
* const { x, y, show, stop } = useContextmenuCoordinate(target)
|
||||
*
|
||||
* 如果需要手动停止右键菜单,可以调用 stop 方法
|
||||
* stop()
|
||||
*
|
||||
* <NDropdown show={show} x={x} y={y} trigger="manual" placement="bottom-start" />
|
||||
*/
|
||||
export const useContextmenuCoordinate = (target: BasicTarget) => {
|
||||
const x = ref(0) // 鼠标 x 坐标
|
||||
const y = ref(0) // 鼠标 y 坐标
|
||||
const show = ref(false) // 是否显示右键菜单
|
||||
|
||||
/**
|
||||
*
|
||||
* @param evt 鼠标事件
|
||||
*
|
||||
* 鼠标右键点击事件,并且阻止默认事件
|
||||
* 设置坐标后激活右键菜单
|
||||
*/
|
||||
const bindContextMenuEvent = (evt: Event) => {
|
||||
evt.preventDefault()
|
||||
|
||||
show.value = false
|
||||
|
||||
nextTick().then(() => {
|
||||
const { clientX, clientY } = evt as MouseEvent
|
||||
|
||||
x.value = clientX
|
||||
y.value = clientY
|
||||
show.value = true
|
||||
})
|
||||
}
|
||||
|
||||
onClickOutside(target as MaybeElementRef<MaybeElement>, () => {
|
||||
show.value = false
|
||||
})
|
||||
|
||||
const cleanupContextmenu = useEventListener(
|
||||
target,
|
||||
'contextmenu',
|
||||
bindContextMenuEvent,
|
||||
)
|
||||
const cleanupClick = useEventListener(target, 'click', () => {
|
||||
show.value = false
|
||||
})
|
||||
|
||||
const stop = () => {
|
||||
cleanupContextmenu()
|
||||
cleanupClick()
|
||||
}
|
||||
|
||||
return {
|
||||
stop,
|
||||
x: readonly(x),
|
||||
y: readonly(y),
|
||||
show,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseContextmenuCoordinateReturnType = ReturnType<
|
||||
typeof useContextmenuCoordinate
|
||||
>
|
@ -1,11 +1,7 @@
|
||||
import { useAppMenu } from './useAppMenu'
|
||||
import { useMainPage } from './useMainPage'
|
||||
import { useMenuTag } from './useMenuTag'
|
||||
import { useRootRoute } from './useRootRoute'
|
||||
import { useAppSetting } from './useAppSetting'
|
||||
|
||||
export type { MaximizeOptions } from './useMainPage'
|
||||
export type { Target } from './useAppMenu'
|
||||
export type { CloseMenuTag } from './useMenuTag'
|
||||
|
||||
export { useAppMenu, useMainPage, useMenuTag, useRootRoute, useAppSetting }
|
||||
export * from './useMaximize'
|
||||
export * from './useSpinning'
|
||||
export * from './useWatermark'
|
||||
export * from './useTheme'
|
||||
export * from './useSiderBar'
|
||||
export * from './useAppNavigation'
|
||||
export * from './useAppRoot'
|
||||
|
@ -20,7 +20,7 @@ export type Target = number | AppMenuOption
|
||||
* 导航函数
|
||||
* 但是该方法仅限于在登入后调用
|
||||
*/
|
||||
export function useAppMenu() {
|
||||
export function useAppNavigation() {
|
||||
const { changeMenuModelValue } = useMenuActions()
|
||||
|
||||
/**
|
||||
@ -32,6 +32,10 @@ export function useAppMenu() {
|
||||
* - 如果传递参数需要导航的菜单项为非根菜单项,会自动的递归导航至第一个子菜单项
|
||||
*
|
||||
* 当传递参数为 AppMenuOption 类型,会直接导航至目标页面。该方法可以不区分菜单层级
|
||||
*
|
||||
* @example
|
||||
* navigationTo(1) // 导航至第二个菜单项,如果为根菜单项,会自动的递归导航至第一个子菜单项
|
||||
* navigationTo({ AppMenuOption }) // 导航至目标菜单项
|
||||
*/
|
||||
const navigationTo = (target: Target) => {
|
||||
if (typeof target === 'number') {
|
||||
@ -80,3 +84,5 @@ export function useAppMenu() {
|
||||
navigationTo,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseAppNavigationReturnType = ReturnType<typeof useAppNavigation>
|
@ -10,10 +10,11 @@
|
||||
*/
|
||||
|
||||
import { setVariable, getVariableToRefs } from '@/global-variable'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type { DeepMutable } from '@/types/modules/helper'
|
||||
|
||||
export function useRootRoute() {
|
||||
export function useAppRoot() {
|
||||
const globalRootRoute = getVariableToRefs('globalRootRoute')
|
||||
|
||||
/**
|
||||
@ -32,11 +33,20 @@ export function useRootRoute() {
|
||||
*/
|
||||
const getRootName = computed(() => globalRootRoute.value.name)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param route 根路由配置内容
|
||||
*
|
||||
* 设置根路由
|
||||
*
|
||||
* @example
|
||||
* setRootRoute({ path: '/your root path', name: 'your root name' })
|
||||
*/
|
||||
const setRootRoute = (route: DeepMutable<typeof globalRootRoute.value>) => {
|
||||
setVariable(
|
||||
'globalRootRoute',
|
||||
Object.assign({}, globalRootRoute.value, route),
|
||||
)
|
||||
const routeRef = getVariableToRefs('globalRootRoute')
|
||||
const assignRoute = Object.assign(cloneDeep(routeRef.value), route)
|
||||
|
||||
setVariable('globalRootRoute', assignRoute)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -46,3 +56,5 @@ export function useRootRoute() {
|
||||
setRootRoute,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseAppRootReturnType = ReturnType<typeof useAppRoot>
|
@ -1,34 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-11-16
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { useSettingActions } from '@/store'
|
||||
|
||||
export function useAppSetting() {
|
||||
/**
|
||||
*
|
||||
* @param theme 当前主题色
|
||||
*
|
||||
* 当前主题有明暗两套
|
||||
*
|
||||
* @example
|
||||
* changeTheme(true) 切换至暗色主题
|
||||
* changeTheme(false) 切换至明色主题
|
||||
*/
|
||||
const changeTheme = (theme: boolean) => {
|
||||
const { changeSwitcher } = useSettingActions()
|
||||
|
||||
changeSwitcher(theme, 'appTheme')
|
||||
}
|
||||
|
||||
return {
|
||||
changeTheme,
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-11-03
|
||||
* @date 2023-11-30
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
@ -15,50 +15,11 @@ import { addStyle, removeStyle } from '@/utils/element'
|
||||
import { unrefElement } from '@/utils/vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export interface MaximizeOptions {
|
||||
zIndex?: string
|
||||
}
|
||||
|
||||
export function useMainPage() {
|
||||
/**
|
||||
*
|
||||
* @param wait 延迟时长
|
||||
*
|
||||
* 刷新当前路由(强制触发刷新)
|
||||
*
|
||||
* @example
|
||||
* reload(1200)
|
||||
*/
|
||||
const reload = (wait = 800) => {
|
||||
setVariable('globalMainLayoutLoad', false)
|
||||
|
||||
setTimeout(() => setVariable('globalMainLayoutLoad', true), wait)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 打开加载动画(不触发强制刷新)
|
||||
*
|
||||
* @example
|
||||
* openSpin()
|
||||
*/
|
||||
const openSpin = () => {
|
||||
setVariable('layoutContentSpinning', true)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 关闭加载动画(不触发强制刷新)
|
||||
*
|
||||
* @example
|
||||
* closeSpin()
|
||||
*/
|
||||
const closeSpin = () => {
|
||||
setVariable('layoutContentSpinning', false)
|
||||
}
|
||||
|
||||
export const useMaximize = () => {
|
||||
/**
|
||||
*
|
||||
* 当前 LayoutContent 是处于否全屏状态
|
||||
@ -68,8 +29,9 @@ export function useMainPage() {
|
||||
* @example
|
||||
* isLayoutContentMaximized() // true or false
|
||||
*/
|
||||
const isLayoutContentMaximized = () =>
|
||||
computed(() => getVariableToRefs('layoutContentMaximize').value)
|
||||
const isLayoutContentMaximized = computed(
|
||||
() => getVariableToRefs('layoutContentMaximize').value,
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
@ -113,10 +75,9 @@ export function useMainPage() {
|
||||
}
|
||||
|
||||
return {
|
||||
reload,
|
||||
maximize,
|
||||
isLayoutContentMaximized,
|
||||
openSpin,
|
||||
closeSpin,
|
||||
maximize,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseMaximizeReturnType = ReturnType<typeof useMaximize>
|
@ -80,7 +80,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fc: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
export function useMenuTag() {
|
||||
export function useSiderBar() {
|
||||
const { getMenuTagOptions, getMenuKey } = useMenuGetters()
|
||||
const {
|
||||
changeMenuModelValue,
|
||||
@ -287,3 +287,5 @@ export function useMenuTag() {
|
||||
checkCloseLeft,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseSiderBarReturnType = ReturnType<typeof useSiderBar>
|
59
src/hooks/template/useSpinning.ts
Normal file
59
src/hooks/template/useSpinning.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-11-30
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { setVariable } from '@/global-variable'
|
||||
|
||||
export const useSpinning = () => {
|
||||
/**
|
||||
*
|
||||
* @param wait 延迟时长
|
||||
*
|
||||
* 刷新当前路由(强制触发刷新)
|
||||
*
|
||||
* @example
|
||||
* reload(1200)
|
||||
*/
|
||||
const reload = (wait = 800) => {
|
||||
setVariable('globalMainLayoutLoad', false)
|
||||
|
||||
setTimeout(() => setVariable('globalMainLayoutLoad', true), wait)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 打开加载动画(不触发强制刷新)
|
||||
*
|
||||
* @example
|
||||
* openSpin()
|
||||
*/
|
||||
const openSpin = () => {
|
||||
setVariable('layoutContentSpinning', true)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 关闭加载动画(不触发强制刷新)
|
||||
*
|
||||
* @example
|
||||
* closeSpin()
|
||||
*/
|
||||
const closeSpin = () => {
|
||||
setVariable('layoutContentSpinning', false)
|
||||
}
|
||||
|
||||
return {
|
||||
reload,
|
||||
openSpin,
|
||||
closeSpin,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseSpinningReturnType = ReturnType<typeof useSpinning>
|
90
src/hooks/template/useTheme.ts
Normal file
90
src/hooks/template/useTheme.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-11-30
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { useSettingActions, useSettingGetters } from '@/store'
|
||||
import { useI18n } from '@/hooks/web'
|
||||
|
||||
export const useTheme = () => {
|
||||
/**
|
||||
*
|
||||
* 获取当前主题色与主题色描述
|
||||
* 并且描述会根据当前语言环境自动切换
|
||||
*
|
||||
* @example
|
||||
* getAppTheme() // { theme: true, themeLabel: '暗色' | 'Dark' }
|
||||
* getAppTheme() // { theme: false, themeLabel: '亮色' | 'Light' }
|
||||
*/
|
||||
const getAppTheme = () => {
|
||||
const { getAppTheme } = useSettingGetters()
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
theme: getAppTheme.value,
|
||||
themeLabel: getAppTheme.value
|
||||
? t('headerSettingOptions.ThemeOptions.Dark')
|
||||
: t('headerSettingOptions.ThemeOptions.Light'),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 切换至暗色主题
|
||||
*
|
||||
* @example
|
||||
* changeDarkTheme()
|
||||
*/
|
||||
const changeDarkTheme = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('appTheme', true)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 切换至明色主题
|
||||
*
|
||||
* @example
|
||||
* changeLightTheme()
|
||||
*/
|
||||
const changeLightTheme = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('appTheme', false)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param theme 当前主题色
|
||||
*
|
||||
* 当前主题有明暗两套
|
||||
*
|
||||
* @example
|
||||
* 假设当前主题为暗色主题
|
||||
* toggleTheme() // 切换至明色主题
|
||||
* 假设当前主题为明色主题
|
||||
* toggleTheme() // 切换至暗色主题
|
||||
*/
|
||||
const toggleTheme = () => {
|
||||
const { theme } = getAppTheme()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('appTheme', !theme)
|
||||
}
|
||||
|
||||
return {
|
||||
changeDarkTheme,
|
||||
changeLightTheme,
|
||||
toggleTheme,
|
||||
getAppTheme,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseThemeReturnType = ReturnType<typeof useTheme>
|
92
src/hooks/template/useWatermark.ts
Normal file
92
src/hooks/template/useWatermark.ts
Normal file
@ -0,0 +1,92 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-11-30
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { useSettingActions, useSettingGetters } from '@/store'
|
||||
import { setVariable, getVariableToRefs } from '@/global-variable'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
export const useWatermark = () => {
|
||||
/**
|
||||
*
|
||||
* @param content 水印内容
|
||||
*
|
||||
* 设置水印内容
|
||||
* 如果覆盖的水印内容为: '', undefined, null, 0, false, NaN,则会沿用上次一次的水印内容
|
||||
*
|
||||
* @example
|
||||
* setWatermarkContent('Ray Template Yes!')
|
||||
* setWatermarkContent('') // 沿用上次一次的水印内容
|
||||
* setWatermarkContent(undefined) // 沿用上次一次的水印内容
|
||||
*/
|
||||
const setWatermarkContent = (content: string) => {
|
||||
const { getWatermarkConfig } = useSettingGetters()
|
||||
const assignWatermark = Object.assign(getWatermarkConfig.value, {
|
||||
content,
|
||||
})
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('watermarkConfig', assignWatermark)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 显示水印
|
||||
*
|
||||
* @example
|
||||
* showWatermark()
|
||||
*/
|
||||
const showWatermark = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('watermarkSwitch', true)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 隐藏水印
|
||||
*
|
||||
* @example
|
||||
* hiddenWatermark()
|
||||
*/
|
||||
const hiddenWatermark = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('watermarkSwitch', false)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value 是否显示水印
|
||||
*
|
||||
* 切换水印显示状态
|
||||
*
|
||||
* @example
|
||||
* 假设当前水印显示状态为隐藏
|
||||
* toggleWatermark() // 显示水印
|
||||
* 假设当前水印显示状态为显示
|
||||
* toggleWatermark() // 隐藏水印
|
||||
*/
|
||||
const toggleWatermark = () => {
|
||||
const { getWatermarkSwitch } = useSettingGetters()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('watermarkSwitch', !getWatermarkSwitch.value)
|
||||
}
|
||||
|
||||
return {
|
||||
setWatermarkContent,
|
||||
showWatermark,
|
||||
hiddenWatermark,
|
||||
toggleWatermark,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseWatermarkReturnType = ReturnType<typeof useWatermark>
|
@ -9,11 +9,7 @@
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { useI18n, t } from './useI18n'
|
||||
import { useVueRouter } from '../web/useVueRouter'
|
||||
import { useDayjs } from '../web/useDayjs'
|
||||
import { useDevice } from './useDevice'
|
||||
|
||||
export type { FormatOption, DateRange, LocalKey } from './useDayjs'
|
||||
|
||||
export { useI18n, useVueRouter, useDayjs, t, useDevice }
|
||||
export * from './useI18n'
|
||||
export * from './useVueRouter'
|
||||
export * from './useDayjs'
|
||||
export * from './useDevice'
|
||||
|
@ -155,3 +155,5 @@ export const useDayjs = () => {
|
||||
isDateInRange,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseDayjsReturnType = ReturnType<typeof useDayjs>
|
||||
|
@ -33,3 +33,5 @@ export function useDevice() {
|
||||
isTabletOrSmaller,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseDeviceReturnType = ReturnType<typeof useDevice>
|
||||
|
@ -63,3 +63,5 @@ export const useI18n = (namespace?: string) => {
|
||||
* 该插件识别 t 方法包裹 path 进行提示文案内容
|
||||
*/
|
||||
export const t = (key: string) => key
|
||||
|
||||
export type UseI18nReturnType = ReturnType<typeof useI18n>
|
||||
|
@ -33,3 +33,5 @@ export const useVueRouter = () => {
|
||||
throw new Error('router is not defined')
|
||||
}
|
||||
}
|
||||
|
||||
export type UseVueRouterReturnType = ReturnType<typeof useVueRouter>
|
||||
|
@ -48,10 +48,10 @@ import { useMenuGetters, useMenuActions } from '@/store'
|
||||
import { uuid } from '@/utils/basic'
|
||||
import { hasClass } from '@/utils/element'
|
||||
import { queryElements } from '@use-utils/element'
|
||||
import { useMainPage } from '@/hooks/template'
|
||||
import { useMenuTag } from '@/hooks/template'
|
||||
import { useMaximize, useSpinning } from '@/hooks/template'
|
||||
import { useSiderBar } from '@/hooks/template'
|
||||
import { throttle } from 'lodash-es'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
import type { ScrollbarInst } from 'naive-ui'
|
||||
import type { MenuTagOptions, AppMenuOption } from '@/types/modules/app'
|
||||
@ -63,15 +63,16 @@ export default defineComponent({
|
||||
|
||||
const { getMenuKey, getMenuTagOptions } = useMenuGetters()
|
||||
const { changeMenuModelValue } = useMenuActions()
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { reload, maximize } = useMainPage()
|
||||
const { getRootPath } = useAppRoot()
|
||||
const { maximize } = useMaximize()
|
||||
const { reload } = useSpinning()
|
||||
const {
|
||||
close,
|
||||
closeAll: $closeAll,
|
||||
closeRight: $closeRight,
|
||||
closeLeft: $closeLeft,
|
||||
closeOther: $closeOther,
|
||||
} = useMenuTag()
|
||||
} = useSiderBar()
|
||||
|
||||
const canDisabledOptions = [
|
||||
'closeAll',
|
||||
|
@ -0,0 +1,20 @@
|
||||
.n-breadcrumb .n-breadcrumb-item {
|
||||
&.breadcrumb-enter-active,
|
||||
&.breadcrumb-leave-active {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
& .breadcrumb-move {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
&.breadcrumb-enter-from,
|
||||
&.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
&.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
@ -18,7 +18,10 @@
|
||||
* 添加 <span> 标签, 避免 Runtime directive used on component... 警告
|
||||
*/
|
||||
|
||||
import './index.scss'
|
||||
|
||||
import { NDropdown, NBreadcrumb, NBreadcrumbItem } from 'naive-ui'
|
||||
import { TransitionGroup } from 'vue'
|
||||
|
||||
import { useMenuGetters, useMenuActions } from '@/store'
|
||||
import { useDevice } from '@/hooks/web'
|
||||
@ -61,23 +64,27 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { isTabletOrSmaller } = this
|
||||
const { isTabletOrSmaller, getBreadcrumbOptions } = this
|
||||
const { dropdownSelect, breadcrumbItemClick } = this
|
||||
|
||||
return isTabletOrSmaller ? (
|
||||
<div></div>
|
||||
<div style="display: none;"></div>
|
||||
) : (
|
||||
<NBreadcrumb>
|
||||
{this.getBreadcrumbOptions.map((curr) => (
|
||||
<TransitionGroup tag="li" name="breadcrumb" appear>
|
||||
{getBreadcrumbOptions.map((curr) => (
|
||||
<NBreadcrumbItem
|
||||
key={curr.key}
|
||||
onClick={this.breadcrumbItemClick.bind(this, curr)}
|
||||
key={curr.path}
|
||||
onClick={breadcrumbItemClick.bind(this, curr)}
|
||||
>
|
||||
<NDropdown
|
||||
labelField="breadcrumbLabel"
|
||||
options={
|
||||
curr.children && curr.children?.length > 1 ? curr.children : []
|
||||
curr.children && curr.children?.length > 1
|
||||
? curr.children
|
||||
: []
|
||||
}
|
||||
onSelect={this.dropdownSelect.bind(this)}
|
||||
onSelect={dropdownSelect.bind(this)}
|
||||
>
|
||||
{{
|
||||
default: () => (
|
||||
@ -91,6 +98,7 @@ export default defineComponent({
|
||||
</NDropdown>
|
||||
</NBreadcrumbItem>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
</NBreadcrumb>
|
||||
)
|
||||
},
|
||||
|
@ -23,11 +23,12 @@ import './index.scss'
|
||||
import { NInput, NModal, NResult, NScrollbar, NSpace } from 'naive-ui'
|
||||
import { RIcon } from '@/components'
|
||||
|
||||
import { on, off, queryElements, addClass, removeClass } from '@/utils/element'
|
||||
import { queryElements, addClass, removeClass } from '@/utils/element'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { useMenuGetters, useMenuActions } from '@/store'
|
||||
import { validMenuItemShow } from '@/router/helper/routerCopilot'
|
||||
import { useDevice } from '@/hooks/web'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import type { AppRouteMeta } from '@/router/type'
|
||||
import type { AppMenuOption } from '@/types/modules/app'
|
||||
@ -275,17 +276,9 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
on(window, 'keydown', (e: Event) => {
|
||||
registerArouseKeyboard(e as KeyboardEvent)
|
||||
registerChangeSearchElementIndex(e as KeyboardEvent)
|
||||
})
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
off(window, 'keydown', (e: Event) => {
|
||||
registerArouseKeyboard(e as KeyboardEvent)
|
||||
registerChangeSearchElementIndex(e as KeyboardEvent)
|
||||
})
|
||||
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
||||
registerArouseKeyboard(e)
|
||||
registerChangeSearchElementIndex(e)
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -12,16 +12,17 @@
|
||||
import { NSpace, NSwitch, NTooltip } from 'naive-ui'
|
||||
import { RIcon } from '@/components'
|
||||
|
||||
import { useSettingGetters, useSettingActions } from '@/store'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { useTheme } from '@/hooks/template'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ThemeSwitch',
|
||||
setup() {
|
||||
const { changeSwitcher } = useSettingActions()
|
||||
const { changeDarkTheme, changeLightTheme } = useTheme()
|
||||
const { getAppTheme } = useSettingGetters()
|
||||
const modelAppThemeRef = ref(getAppTheme.value)
|
||||
|
||||
const handleRailStyle = ({ checked }: { checked: boolean }) => {
|
||||
const railStyle = ({ checked }: { checked: boolean }) => {
|
||||
return checked
|
||||
? {
|
||||
backgroundColor: '#000000',
|
||||
@ -32,14 +33,15 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
changeSwitcher,
|
||||
changeDarkTheme,
|
||||
changeLightTheme,
|
||||
getAppTheme,
|
||||
handleRailStyle,
|
||||
railStyle,
|
||||
modelAppThemeRef,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { $t } = this
|
||||
const { $t, changeDarkTheme, changeLightTheme, railStyle } = this
|
||||
|
||||
return (
|
||||
<NSpace justify="center">
|
||||
@ -48,28 +50,14 @@ export default defineComponent({
|
||||
trigger: () => (
|
||||
<NSwitch
|
||||
v-model:value={this.modelAppThemeRef}
|
||||
railStyle={this.handleRailStyle.bind(this)}
|
||||
railStyle={railStyle.bind(this)}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.changeSwitcher(bool, 'appTheme')
|
||||
bool ? changeDarkTheme() : changeLightTheme()
|
||||
}
|
||||
>
|
||||
{{
|
||||
'checked-icon': () =>
|
||||
h(
|
||||
RIcon,
|
||||
{
|
||||
name: 'dark',
|
||||
},
|
||||
{},
|
||||
),
|
||||
'unchecked-icon': () =>
|
||||
h(
|
||||
RIcon,
|
||||
{
|
||||
name: 'light',
|
||||
},
|
||||
{},
|
||||
),
|
||||
'checked-icon': () => <RIcon name="dark" />,
|
||||
'unchecked-icon': () => <RIcon name="light" />,
|
||||
checked: () => '亮',
|
||||
unchecked: () => '暗',
|
||||
}}
|
||||
|
@ -47,8 +47,7 @@ const SettingDrawer = defineComponent({
|
||||
},
|
||||
emits: ['update:show'],
|
||||
setup(props, { emit }) {
|
||||
const { changePrimaryColor, changeSwitcher, updateContentTransition } =
|
||||
useSettingActions()
|
||||
const { changePrimaryColor, updateSettingState } = useSettingActions()
|
||||
const {
|
||||
getAppTheme,
|
||||
getPrimaryColorOverride,
|
||||
@ -97,9 +96,8 @@ const SettingDrawer = defineComponent({
|
||||
changePrimaryColor,
|
||||
getAppTheme,
|
||||
getPrimaryColorOverride,
|
||||
changeSwitcher,
|
||||
contentTransitionOptions,
|
||||
updateContentTransition,
|
||||
updateSettingState,
|
||||
modelSwitchReactive,
|
||||
}
|
||||
},
|
||||
@ -133,7 +131,7 @@ const SettingDrawer = defineComponent({
|
||||
v-model:value={this.modelSwitchReactive.getContentTransition}
|
||||
options={this.contentTransitionOptions}
|
||||
onUpdateValue={(value) => {
|
||||
this.updateContentTransition(value)
|
||||
this.updateSettingState('contentTransition', value)
|
||||
}}
|
||||
/>
|
||||
<NDivider titlePlacement="center">
|
||||
@ -144,7 +142,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getMenuTagSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.changeSwitcher(bool, 'menuTagSwitch')
|
||||
this.updateSettingState('menuTagSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -152,7 +150,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getBreadcrumbSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.changeSwitcher(bool, 'breadcrumbSwitch')
|
||||
this.updateSettingState('breadcrumbSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -160,7 +158,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getWatermarkSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.changeSwitcher(bool, 'watermarkSwitch')
|
||||
this.updateSettingState('watermarkSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -168,7 +166,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getCopyrightSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.changeSwitcher(bool, 'copyrightSwitch')
|
||||
this.updateSettingState('copyrightSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
|
@ -38,7 +38,7 @@ import { useDevice } from '@/hooks/web'
|
||||
import { getVariableToRefs, setVariable } from '@/global-variable'
|
||||
import { useFullscreen } from 'vue-hooks-plus'
|
||||
import { useI18n } from '@/hooks/web'
|
||||
import { useMainPage } from '@/hooks/template'
|
||||
import { useSpinning } from '@/hooks/template'
|
||||
import { useSettingGetters, useSettingActions } from '@/store'
|
||||
|
||||
import type { IconEventMapOptions, IconEventMap } from './type'
|
||||
@ -46,9 +46,9 @@ import type { IconEventMapOptions, IconEventMap } from './type'
|
||||
export default defineComponent({
|
||||
name: 'AppSiderBar',
|
||||
setup() {
|
||||
const { updateLocale, changeSwitcher } = useSettingActions()
|
||||
const { updateLocale, updateSettingState } = useSettingActions()
|
||||
const { t } = useI18n()
|
||||
const { reload } = useMainPage()
|
||||
const { reload } = useSpinning()
|
||||
|
||||
const [isFullscreen, { toggleFullscreen, isEnabled }] = useFullscreen(
|
||||
document.getElementsByTagName('html')[0],
|
||||
@ -107,7 +107,7 @@ export default defineComponent({
|
||||
globalSearchShown.value = true
|
||||
},
|
||||
lock: () => {
|
||||
changeSwitcher(true, 'lockScreenSwitch')
|
||||
updateSettingState('lockScreenSwitch', true)
|
||||
},
|
||||
menu: () => {
|
||||
setVariable('globalDrawerValue', !globalDrawerValue.value)
|
||||
|
@ -54,9 +54,9 @@ const avatarDropdownActionMap = {
|
||||
* 锁定屏幕
|
||||
*/
|
||||
lockScreen: () => {
|
||||
const { changeSwitcher } = useSettingActions()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
changeSwitcher(true, 'lockScreenSwitch')
|
||||
updateSettingState('lockScreenSwitch', true)
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import AppRequestCancelerProvider from '@/app-components/provider/AppRequestCanc
|
||||
|
||||
import { getVariableToRefs } from '@/global-variable'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { useMainPage } from '@/hooks/template'
|
||||
import { useMaximize } from '@/hooks/template'
|
||||
|
||||
import type { GlobalThemeOverrides } from 'naive-ui'
|
||||
|
||||
@ -32,7 +32,7 @@ export default defineComponent({
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
|
||||
const { maximize } = useMainPage()
|
||||
const { maximize } = useMaximize()
|
||||
const { getContentTransition } = useSettingGetters()
|
||||
const spinning = ref(false)
|
||||
const themeOverridesSpin: GlobalThemeOverrides['Spin'] = {
|
||||
|
@ -22,5 +22,6 @@
|
||||
"QRCode": "QRCode",
|
||||
"SvgIcon": "SVG Icon",
|
||||
"TemplateHooks": "Template Api",
|
||||
"Modal": "Modal"
|
||||
"Modal": "Modal",
|
||||
"ContextMenu": "Right Click Menu"
|
||||
}
|
||||
|
@ -22,5 +22,6 @@
|
||||
"QRCode": "二维码",
|
||||
"SvgIcon": "SVG 图标",
|
||||
"TemplateHooks": "模板内置 Api",
|
||||
"Modal": "模态框"
|
||||
"Modal": "模态框",
|
||||
"ContextMenu": "右键菜单"
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
|
||||
import { WHITE_ROUTES } from '@/app-config/routerConfig'
|
||||
import { validRole } from '@/router/helper/routerCopilot'
|
||||
import { isValueType } from '@/utils/basic'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
import type { Router, RouteLocationNormalized } from 'vue-router'
|
||||
import type { AppRouteMeta } from '@/router/type'
|
||||
@ -34,7 +34,7 @@ import type { AppRouteMeta } from '@/router/type'
|
||||
/** 路由守卫 */
|
||||
export const permissionRouter = (router: Router) => {
|
||||
const { beforeEach } = router
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { getRootPath } = useAppRoot()
|
||||
|
||||
const isToLogin = (
|
||||
to: RouteLocationNormalized,
|
||||
|
@ -15,7 +15,7 @@ import { useVueRouter } from '@/hooks/web'
|
||||
import { setStorage } from '@/utils/cache'
|
||||
import { getAppEnvironment } from '@/utils/basic'
|
||||
import { useSigningGetters } from '@/store'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
import type { Router } from 'vue-router'
|
||||
import type { AppRouteMeta } from '@/router/type'
|
||||
@ -132,7 +132,7 @@ export const redirectRouterToDashboard = (isReplace = true) => {
|
||||
const { router } = useVueRouter()
|
||||
|
||||
const { push, replace } = router
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { getRootPath } = useAppRoot()
|
||||
|
||||
setStorage('menuKey', getRootPath.value)
|
||||
|
||||
|
16
src/router/modules/demo/context-menu.ts
Normal file
16
src/router/modules/demo/context-menu.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { t } from '@/hooks/web'
|
||||
|
||||
import type { AppRouteRecordRaw } from '@/router/type'
|
||||
|
||||
const contextMenu: AppRouteRecordRaw = {
|
||||
path: '/context-menu',
|
||||
name: 'ContextMenuDemo',
|
||||
component: () => import('@/views/demo/context-menu/index'),
|
||||
meta: {
|
||||
i18nKey: t('menu.ContextMenu'),
|
||||
icon: 'other',
|
||||
order: 2,
|
||||
},
|
||||
}
|
||||
|
||||
export default contextMenu
|
@ -1,9 +1,9 @@
|
||||
import Layout from '@/layout'
|
||||
import { appExpandRoutes } from './appRouteModules'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
export default async () => {
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { getRootPath } = useAppRoot()
|
||||
|
||||
return [
|
||||
/**
|
||||
|
@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { piniaMenuStore } from '../modules/menu'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
export const useMenuGetters = () => {
|
||||
const variable = piniaMenuStore()
|
||||
@ -35,7 +35,7 @@ export const useMenuGetters = () => {
|
||||
* @remark 获取菜单标签列表
|
||||
*/
|
||||
const getMenuTagOptions = computed(() => {
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { getRootPath } = useAppRoot()
|
||||
|
||||
return variable.menuTagOptions.map((curr, _idx, currentArray) => {
|
||||
if (curr.key === getMenuKey.value && curr.key !== getRootPath.value) {
|
||||
|
@ -65,6 +65,12 @@ export const useSettingGetters = () => {
|
||||
*/
|
||||
const getWatermarkSwitch = computed(() => variable.watermarkSwitch)
|
||||
|
||||
/**
|
||||
*
|
||||
* @remark 获取水印配置
|
||||
*/
|
||||
const getWatermarkConfig = computed(() => variable.watermarkConfig)
|
||||
|
||||
return {
|
||||
getDrawerPlacement,
|
||||
getPrimaryColorOverride,
|
||||
@ -76,21 +82,17 @@ export const useSettingGetters = () => {
|
||||
getCopyrightSwitch,
|
||||
getContentTransition,
|
||||
getWatermarkSwitch,
|
||||
getWatermarkConfig,
|
||||
}
|
||||
}
|
||||
|
||||
export const useSettingActions = () => {
|
||||
const {
|
||||
updateLocale,
|
||||
changePrimaryColor,
|
||||
changeSwitcher,
|
||||
updateContentTransition,
|
||||
} = piniaSettingStore()
|
||||
const { updateLocale, changePrimaryColor, updateSettingState } =
|
||||
piniaSettingStore()
|
||||
|
||||
return {
|
||||
updateLocale,
|
||||
changePrimaryColor,
|
||||
changeSwitcher,
|
||||
updateContentTransition,
|
||||
updateSettingState,
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { APP_MENU_CONFIG } from '@/app-config/appConfig'
|
||||
import { RIcon } from '@/components'
|
||||
import { isValueType } from '@/utils/basic'
|
||||
import { getStorage } from '@/utils/cache'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
import type {
|
||||
AppMenuOption,
|
||||
@ -174,7 +174,7 @@ export const hasMenuIcon = (option: AppMenuOption) => {
|
||||
|
||||
/** 获取缓存的 menu key, 如果未获取到则使用 getRootPath 当作默认激活路由菜单 */
|
||||
export const getCatchMenuKey = () => {
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { getRootPath } = useAppRoot()
|
||||
const cacheMenuKey = getStorage<AppMenuKey>(
|
||||
'menuKey',
|
||||
'sessionStorage',
|
||||
|
@ -5,10 +5,12 @@ import { colorToRgba } from '@/utils/element'
|
||||
import { useI18n } from '@/hooks/web'
|
||||
import { APP_THEME } from '@/app-config/designConfig'
|
||||
import { useDayjs } from '@/hooks/web'
|
||||
import { APP_WATERMARK_CONFIG } from '@/app-config/appConfig'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type { ConditionalPick } from '@/types/modules/helper'
|
||||
import type { SettingState } from '@/store/modules/setting/type'
|
||||
import type { LocalKey } from '@/hooks/web'
|
||||
import type { AnyFC } from '@/types/modules/utils'
|
||||
|
||||
export const piniaSettingStore = defineStore(
|
||||
'setting',
|
||||
@ -36,13 +38,9 @@ export const piniaSettingStore = defineStore(
|
||||
copyrightSwitch: true, // 底部区域开关
|
||||
contentTransition: 'scale', // 切换过渡效果
|
||||
watermarkSwitch: false, // 水印开关,
|
||||
watermarkConfig: cloneDeep(APP_WATERMARK_CONFIG),
|
||||
})
|
||||
|
||||
/** 更新过渡效果 */
|
||||
const updateContentTransition = (value: string) => {
|
||||
settingState.contentTransition = value
|
||||
}
|
||||
|
||||
/** 修改当前语言 */
|
||||
const updateLocale = (key: string) => {
|
||||
locale(key)
|
||||
@ -73,29 +71,38 @@ export const piniaSettingStore = defineStore(
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bool 开关当前值
|
||||
* @param key `settingState` 对应开关属性值
|
||||
* @param key settingState 的 key
|
||||
* @param value settingState 的 value
|
||||
* @param cb 回调函数
|
||||
*
|
||||
* @remark 仅适用于值为 `boolean` 的切换
|
||||
* 更新 settingState 的值,如果 key 不存在与 settingState 中,则不会更新
|
||||
* 但是不论是否更新成功,都会执行回调函数
|
||||
*
|
||||
* @example
|
||||
* updateSettingState('drawerPlacement', 'left')
|
||||
* updateSettingState('appTheme', true)
|
||||
*/
|
||||
const changeSwitcher = (
|
||||
bool: boolean,
|
||||
key: keyof ConditionalPick<SettingState, boolean>,
|
||||
const updateSettingState = <
|
||||
T extends keyof SettingState,
|
||||
V extends typeof settingState,
|
||||
C extends AnyFC,
|
||||
>(
|
||||
key: T,
|
||||
value: V[T],
|
||||
cb?: C,
|
||||
) => {
|
||||
if (
|
||||
Object.hasOwn(settingState, key) &&
|
||||
typeof settingState[key] === 'boolean'
|
||||
) {
|
||||
settingState[key] = bool
|
||||
if (Object.hasOwn(settingState, key)) {
|
||||
settingState[key] = value
|
||||
}
|
||||
|
||||
cb?.()
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(settingState),
|
||||
updateLocale,
|
||||
changePrimaryColor,
|
||||
changeSwitcher,
|
||||
updateContentTransition,
|
||||
updateSettingState,
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { GlobalThemeOverrides } from 'naive-ui'
|
||||
import type { Placement } from '@/types/modules/component'
|
||||
import type { APP_WATERMARK_CONFIG } from '@/app-config/appConfig'
|
||||
|
||||
export interface SettingState {
|
||||
drawerPlacement: Placement
|
||||
@ -12,4 +13,5 @@ export interface SettingState {
|
||||
watermarkSwitch: boolean
|
||||
copyrightSwitch: boolean
|
||||
contentTransition: string
|
||||
watermarkConfig: typeof APP_WATERMARK_CONFIG
|
||||
}
|
||||
|
@ -1,23 +1,66 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
*
|
||||
* 从目标类型中挑选出符合条件的属性名
|
||||
*
|
||||
* @example
|
||||
* ConditionalKeys<{ a: string, b: number }, string> // 'a'
|
||||
*/
|
||||
export type ConditionalKeys<Base, Condition> = NonNullable<
|
||||
{
|
||||
[Key in keyof Base]: Base[Key] extends Condition ? Key : never
|
||||
}[keyof Base]
|
||||
>
|
||||
|
||||
/**
|
||||
*
|
||||
* 从目标类型中挑选出符合条件的属性
|
||||
*
|
||||
* @example
|
||||
* ConditionalPick<{ a: string, b: number }, string> // { a: string }
|
||||
*/
|
||||
export type ConditionalPick<Base, Condition> = Pick<
|
||||
Base,
|
||||
ConditionalKeys<Base, Condition>
|
||||
>
|
||||
|
||||
/**
|
||||
*
|
||||
* 声明一个任意类型的对象
|
||||
*
|
||||
* @example
|
||||
* const A: Recordable = { a: 1, b: [] }
|
||||
*/
|
||||
export type Recordable<T = any> = Record<string, T>
|
||||
|
||||
/**
|
||||
*
|
||||
* 获取目标类型的所有属性名
|
||||
*
|
||||
* @example
|
||||
* Keys<{ a: string, b: number }> // 'a' | 'b'
|
||||
*/
|
||||
export type ValueOf<T extends object> = T[keyof T]
|
||||
|
||||
/**
|
||||
*
|
||||
* 将目标所有属性变为可修改
|
||||
*
|
||||
* @example
|
||||
* Mutable<{ readonly a: string }> // { a: string }
|
||||
*/
|
||||
export type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 递归将目标所有属性变为可修改
|
||||
*
|
||||
* @example
|
||||
* DeepMutable<{ readonly a: { readonly b: { readonly c: string } } }> // { a: { b: { c: string } } }
|
||||
*/
|
||||
export type DeepMutable<T> = {
|
||||
-readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U>
|
||||
? Array<DeepMutable<U>>
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type CryptoJS from 'crypto-js'
|
||||
import type { BasicTarget } from './vue'
|
||||
|
||||
export type StorageLike = 'sessionStorage' | 'localStorage'
|
||||
|
||||
@ -10,10 +9,6 @@ export type RemoveStorageKey =
|
||||
| 'all-sessionStorage'
|
||||
| 'all-localStorage'
|
||||
|
||||
export type EventListenerOrEventListenerObject =
|
||||
| EventListener
|
||||
| EventListenerObject
|
||||
|
||||
export type ValidateValueType =
|
||||
| 'BigUint64Array'
|
||||
| 'BigInt64Array'
|
||||
@ -93,7 +88,3 @@ export type ElementSelector = string | `attr:${string}`
|
||||
export type MaybeArray<T> = T | T[]
|
||||
|
||||
export type DownloadAnyFileDataType = Blob | File | string | ArrayBuffer
|
||||
|
||||
export type EventListenerTarget = BasicTarget<
|
||||
HTMLElement | Element | Window | Document
|
||||
>
|
||||
|
@ -232,3 +232,27 @@ export function print<T extends BasicTarget<HTMLElement>>(
|
||||
|
||||
watchEffectWithTarget(watcher)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param targetObject 对象
|
||||
* @param targetKeys 待删除的 key
|
||||
*
|
||||
* 删除对象中的指定 key
|
||||
*
|
||||
* @example
|
||||
* omit({ a: 1, b: 2, c: 3 }, 'a') => { b: 2, c: 3 }
|
||||
* omit({ a: 1, b: 2, c: 3 }, ['a', 'b']) => { c: 3 }
|
||||
*/
|
||||
export const omit = <T extends Record<string, unknown>, K extends keyof T>(
|
||||
targetObject: T,
|
||||
targetKeys: K | K[],
|
||||
): Omit<T, K> => {
|
||||
const keys = Array.isArray(targetKeys) ? targetKeys : [targetKeys]
|
||||
|
||||
keys.forEach((key) => {
|
||||
delete targetObject[key]
|
||||
})
|
||||
|
||||
return targetObject
|
||||
}
|
||||
|
@ -2,83 +2,14 @@ import { isValueType } from '@/utils/basic'
|
||||
import { APP_REGEX } from '@/app-config/regexConfig'
|
||||
import { unrefElement } from '@/utils/vue'
|
||||
import { watchEffectWithTarget } from '@/utils/vue'
|
||||
import { useCurrentElement } from '@vueuse/core'
|
||||
|
||||
import type {
|
||||
EventListenerOrEventListenerObject,
|
||||
PartialCSSStyleDeclaration,
|
||||
ElementSelector,
|
||||
} from '@/types/modules/utils'
|
||||
import type { EventListenerTarget } from '@/types/modules/utils'
|
||||
import type { BasicTarget, TargetValue } from '@/types/modules/vue'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target Target element dom
|
||||
* @param event 绑定事件类型
|
||||
* @param handler 事件触发方法
|
||||
* @param useCapture 是否冒泡
|
||||
*
|
||||
* @remark 给元素绑定某个事件柄方法
|
||||
*/
|
||||
export const on = (
|
||||
target: EventListenerTarget,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject,
|
||||
useCapture: boolean | AddEventListenerOptions = false,
|
||||
) => {
|
||||
const targetElement = computed(() => unrefElement(target, window))
|
||||
|
||||
const update = <
|
||||
T extends TargetValue<HTMLElement | Element | Window | Document>,
|
||||
>(
|
||||
element: T,
|
||||
) => {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, useCapture)
|
||||
}
|
||||
}
|
||||
|
||||
const watcher = watch(targetElement, (ndata) => update(ndata), {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
watchEffectWithTarget(watcher)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target Target element dom
|
||||
* @param event 卸载事件类型
|
||||
* @param handler 所需卸载方法
|
||||
* @param useCapture 是否冒泡
|
||||
*
|
||||
* @remark 卸载元素上某个事件柄方法
|
||||
*/
|
||||
export const off = (
|
||||
target: EventListenerTarget,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject,
|
||||
useCapture: boolean | AddEventListenerOptions = false,
|
||||
) => {
|
||||
const targetElement = computed(() => unrefElement(target, window))
|
||||
|
||||
const update = <
|
||||
T extends TargetValue<HTMLElement | Element | Window | Document>,
|
||||
>(
|
||||
element: T,
|
||||
) => {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, useCapture)
|
||||
}
|
||||
}
|
||||
|
||||
const watcher = watch(targetElement, (ndata) => update(ndata), {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
watchEffectWithTarget(watcher)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target Target element dom
|
||||
|
@ -90,15 +90,7 @@ const Dashboard = defineComponent({
|
||||
<NLayout class="dashboard-layout layout-full">
|
||||
<NCard>
|
||||
{{
|
||||
header: () =>
|
||||
h(
|
||||
RIcon,
|
||||
{
|
||||
name: 'ray',
|
||||
size: '64',
|
||||
},
|
||||
{},
|
||||
),
|
||||
header: () => <RIcon name="ray" size="64" />,
|
||||
default: () => '当你看见这个页面后, 就说明项目已经启动成功了~',
|
||||
}}
|
||||
</NCard>
|
||||
|
78
src/views/demo/context-menu/index.tsx
Normal file
78
src/views/demo/context-menu/index.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-12-01
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { NSpace, NCard, NDropdown } from 'naive-ui'
|
||||
|
||||
import { useContextmenuCoordinate } from '@/hooks/components'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ContextMenuDemo',
|
||||
setup() {
|
||||
const demoOneRef = ref<HTMLElement | null>(null)
|
||||
const demoOneShow = ref(false)
|
||||
const options = ref([
|
||||
{
|
||||
label: '杰·盖茨比',
|
||||
key: 'jay gatsby',
|
||||
},
|
||||
{
|
||||
label: '黛西·布坎南',
|
||||
key: 'daisy buchanan',
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1',
|
||||
},
|
||||
{
|
||||
label: '尼克·卡拉威',
|
||||
key: 'nick carraway',
|
||||
},
|
||||
])
|
||||
|
||||
const { x, y, show } = useContextmenuCoordinate(demoOneRef)
|
||||
|
||||
return {
|
||||
demoOneRef,
|
||||
demoOneShow,
|
||||
x,
|
||||
y,
|
||||
show,
|
||||
options,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { x, y, show } = this
|
||||
|
||||
return (
|
||||
<NSpace vertical wrapItem={false}>
|
||||
<NCard title="useContextmenuCoordinate + NDropdown 实现右键菜单">
|
||||
<NSpace vertical>
|
||||
<h3>默认点击元素外部会关闭菜单。</h3>
|
||||
<div
|
||||
ref="demoOneRef"
|
||||
style="width: 100%; height: 200px; background-color: rgba(0, 128, 0, 0.5)"
|
||||
>
|
||||
右击
|
||||
</div>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NDropdown
|
||||
show={show}
|
||||
x={x}
|
||||
y={y}
|
||||
options={this.options}
|
||||
trigger="manual"
|
||||
placement="bottom-start"
|
||||
/>
|
||||
</NSpace>
|
||||
)
|
||||
},
|
||||
})
|
@ -55,18 +55,10 @@ const TableView = defineComponent({
|
||||
key: 'tags',
|
||||
render: (row: RowData) => {
|
||||
const tags = row.tags.map((tagKey) => {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'info',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => tagKey,
|
||||
},
|
||||
return (
|
||||
<NTag type="info" bordered={false} style="margin-right: 6px">
|
||||
{tagKey}
|
||||
</NTag>
|
||||
)
|
||||
})
|
||||
|
||||
@ -138,7 +130,7 @@ const TableView = defineComponent({
|
||||
key: 'edit',
|
||||
},
|
||||
{
|
||||
label: () => h('span', { style: { color: 'red' } }, '删除'),
|
||||
label: () => <span style="color: red;">删除</span>,
|
||||
key: 'delete',
|
||||
},
|
||||
]
|
||||
|
@ -9,19 +9,36 @@
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
import { NSpace, NCard, NButton } from 'naive-ui'
|
||||
import { NSpace, NCard, NButton, NInput } from 'naive-ui'
|
||||
|
||||
import { useAppMenu, useMainPage } from '@/hooks/template'
|
||||
import {
|
||||
useAppNavigation,
|
||||
useMaximize,
|
||||
useSpinning,
|
||||
useWatermark,
|
||||
useTheme,
|
||||
} from '@/hooks/template'
|
||||
import { getVariableToRefs } from '@/global-variable'
|
||||
import { useSettingGetters } from '@/store'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TemplateHooks',
|
||||
setup() {
|
||||
const currentMenuOption = ref('')
|
||||
const maximizeRef = getVariableToRefs('layoutContentMaximize')
|
||||
const watermark = ref(useSettingGetters().getWatermarkConfig.value.content)
|
||||
|
||||
const { navigationTo } = useAppMenu()
|
||||
const { reload, maximize, openSpin, closeSpin } = useMainPage()
|
||||
const { navigationTo } = useAppNavigation()
|
||||
const { maximize, isLayoutContentMaximized } = useMaximize()
|
||||
const { reload, openSpin, closeSpin } = useSpinning()
|
||||
const {
|
||||
showWatermark,
|
||||
hiddenWatermark,
|
||||
setWatermarkContent,
|
||||
toggleWatermark,
|
||||
} = useWatermark()
|
||||
const { changeDarkTheme, changeLightTheme, toggleTheme, getAppTheme } =
|
||||
useTheme()
|
||||
|
||||
return {
|
||||
navigationTo,
|
||||
@ -31,10 +48,35 @@ export default defineComponent({
|
||||
maximizeRef,
|
||||
openSpin,
|
||||
closeSpin,
|
||||
showWatermark,
|
||||
hiddenWatermark,
|
||||
setWatermarkContent,
|
||||
watermark,
|
||||
toggleWatermark,
|
||||
changeDarkTheme,
|
||||
changeLightTheme,
|
||||
toggleTheme,
|
||||
getAppTheme,
|
||||
isLayoutContentMaximized,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { navigationTo, reload, maximize, openSpin, closeSpin } = this
|
||||
const {
|
||||
navigationTo,
|
||||
reload,
|
||||
maximize,
|
||||
openSpin,
|
||||
closeSpin,
|
||||
showWatermark,
|
||||
hiddenWatermark,
|
||||
setWatermarkContent,
|
||||
toggleWatermark,
|
||||
changeDarkTheme,
|
||||
changeLightTheme,
|
||||
toggleTheme,
|
||||
getAppTheme,
|
||||
isLayoutContentMaximized,
|
||||
} = this
|
||||
|
||||
return (
|
||||
<NSpace wrapItem={false} vertical>
|
||||
@ -44,21 +86,38 @@ export default defineComponent({
|
||||
方法。这里不做过多的赘述,可以查看文档具体描述。
|
||||
</h3>
|
||||
</NCard>
|
||||
<NCard title="useAppMenu 导航方法">
|
||||
<h3>
|
||||
navigationTo
|
||||
参数为正整数时,会更具当前的菜单顺序进行自动导航匹配。但是此方法仅能导航一级菜单。并且如果导航菜单非根菜单项,会自动递归导航至一子菜单。
|
||||
</h3>
|
||||
<br />
|
||||
<NButton onClick={() => navigationTo(15)}>跳转至多级菜单</NButton>
|
||||
<NCard title="useTheme 主题">
|
||||
<NSpace vertical>
|
||||
<h3>getAppTheme 获取当前主题色: {getAppTheme().themeLabel}</h3>
|
||||
<NSpace>
|
||||
<NButton onClick={() => changeDarkTheme()}>切换暗黑主题</NButton>
|
||||
<NButton onClick={() => changeLightTheme()}>切换明亮主题</NButton>
|
||||
<NButton onClick={() => toggleTheme()}>切换主题</NButton>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NCard title="useMainPage 主页面方法">
|
||||
<NCard title="reload 加载函数">
|
||||
<NCard title="useWatermark 水印">
|
||||
<NSpace vertical>
|
||||
<NInput
|
||||
v-model:value={this.watermark}
|
||||
onInput={(val) => {
|
||||
setWatermarkContent(val)
|
||||
}}
|
||||
/>
|
||||
<NSpace>
|
||||
<NButton onClick={() => showWatermark()}>显示水印</NButton>
|
||||
<NButton onClick={() => hiddenWatermark()}>隐藏水印</NButton>
|
||||
<NButton onClick={() => toggleWatermark()}>切换水印</NButton>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NCard title="useSpinning">
|
||||
<h3>
|
||||
手动刷新内容区域,会使得当前路由页面内容强制重新加载(会执行完整的
|
||||
vue 生命周期)。默认 800ms 延迟。
|
||||
</h3>
|
||||
<br />
|
||||
<NSpace>
|
||||
<NButton
|
||||
onClick={() => {
|
||||
reload()
|
||||
@ -77,8 +136,13 @@ export default defineComponent({
|
||||
>
|
||||
触发加载动画(不强制刷新)
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NCard title="maximize 内容区域最大化">
|
||||
<NCard title="useMaximize 内容区域最大化">
|
||||
<h3>
|
||||
isLayoutContentMaximized 检测当前内容区域是否最大化:
|
||||
{isLayoutContentMaximized ? '最大化' : '正常尺寸'}
|
||||
</h3>
|
||||
<NButton
|
||||
onClick={() => {
|
||||
maximize(!this.maximizeRef)
|
||||
@ -87,6 +151,13 @@ export default defineComponent({
|
||||
最大化内容区域
|
||||
</NButton>
|
||||
</NCard>
|
||||
<NCard title="useAppNavigation 导航方法">
|
||||
<h3>
|
||||
navigationTo
|
||||
参数为正整数时,会更具当前的菜单顺序进行自动导航匹配。但是此方法仅能导航一级菜单。并且如果导航菜单非根菜单项,会自动递归导航至一子菜单。
|
||||
</h3>
|
||||
<br />
|
||||
<NButton onClick={() => navigationTo(16)}>跳转至多级菜单</NButton>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ import { useI18n } from '@/hooks/web'
|
||||
import { APP_CATCH_KEY } from '@/app-config/appConfig'
|
||||
import { setVariable, getVariableToRefs } from '@/global-variable'
|
||||
import { useSigningActions } from '@/store'
|
||||
import { useRootRoute } from '@/hooks/template'
|
||||
import { useAppRoot } from '@/hooks/template'
|
||||
|
||||
import type { FormInst } from 'naive-ui'
|
||||
|
||||
@ -16,7 +16,7 @@ export default defineComponent({
|
||||
|
||||
const { t } = useI18n()
|
||||
const { signing } = useSigningActions()
|
||||
const { getRootPath } = useRootRoute()
|
||||
const { getRootPath } = useAppRoot()
|
||||
const globalSpinning = getVariableToRefs('globalSpinning')
|
||||
|
||||
const useSigningForm = () => ({
|
||||
|
@ -33,8 +33,8 @@ import config from './cfg'
|
||||
|
||||
import type { PluginOption } from 'vite'
|
||||
|
||||
// 仅适用于构建模式(任何构建模式:preview、build、report...)
|
||||
function onlyBuildOptions(mode: string) {
|
||||
// 仅适用于报告模式
|
||||
function onlyReportOptions(mode: string) {
|
||||
return [
|
||||
visualizer({
|
||||
gzipSize: true, // 搜集 `gzip` 压缩包
|
||||
@ -43,6 +43,12 @@ function onlyBuildOptions(mode: string) {
|
||||
filename: 'visualizer.html',
|
||||
open: mode === 'report' ? true : false, // 以默认服务器代理打开文件
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
// 仅适用于构建模式(任何构建模式:preview、build、report...)
|
||||
function onlyBuildOptions(mode: string) {
|
||||
return [
|
||||
viteCDNPlugin({
|
||||
// modules 顺序 vue, vue-demi 必须保持当前顺序加载,否则会出现加载错误问题
|
||||
modules: [
|
||||
@ -180,6 +186,7 @@ function baseOptions(mode: string) {
|
||||
export default function (mode: string): PluginOption[] {
|
||||
const plugins =
|
||||
mode === 'development' ? onlyDevOptions(mode) : onlyBuildOptions(mode)
|
||||
const reportPlugins = mode === 'report' ? onlyReportOptions(mode) : []
|
||||
|
||||
return [...baseOptions(mode), ...plugins]
|
||||
return [...baseOptions(mode), ...plugins, ...reportPlugins]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user