version: v4.4.2

This commit is contained in:
XiaoDaiGua-Ray 2023-12-01 23:18:55 +08:00
parent 9e3cbac091
commit f272832a8c
65 changed files with 961 additions and 429 deletions

View File

@ -33,6 +33,7 @@ module.exports = {
defineExpose: 'readonly',
withDefaults: 'readonly',
defineOptions: 'readonly',
defineModel: 'readonly',
},
rules: {
'no-undefined': ['error'],

View File

@ -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` 后再重新安装依赖,这是缓存导致的问题。

View File

@ -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 的语言
- **主题**:可配置的主题
- **国际化**:内置完善的国际化方案

View File

@ -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.

View File

@ -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",

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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)
},
})

View File

@ -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
},
})

View File

@ -52,7 +52,7 @@ export const PRE_LOADING_CONFIG: PreloadingConfig = {
* globalRootRoute
*
*
* Root Route 使 useRootRoute
* Root Route 使 useAppRoot
*/
export const ROOT_ROUTE: RootRoute = {
name: 'Dashboard',

View File

@ -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,

View File

@ -9,6 +9,8 @@ import type { ModalProps } from 'naive-ui'
*
*
* card, dialog
*
* 30ms
*/
export const setupDraggable = (
bindModal: HTMLElement,

View File

@ -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>
),

View File

@ -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,

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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))
}

View File

@ -0,0 +1 @@
export * from './useContextmenuCoordinate'

View 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
>

View File

@ -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'

View File

@ -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>

View File

@ -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>

View File

@ -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,
}
}

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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'

View File

@ -155,3 +155,5 @@ export const useDayjs = () => {
isDateInRange,
}
}
export type UseDayjsReturnType = ReturnType<typeof useDayjs>

View File

@ -33,3 +33,5 @@ export function useDevice() {
isTabletOrSmaller,
}
}
export type UseDeviceReturnType = ReturnType<typeof useDevice>

View File

@ -63,3 +63,5 @@ export const useI18n = (namespace?: string) => {
* t path
*/
export const t = (key: string) => key
export type UseI18nReturnType = ReturnType<typeof useI18n>

View File

@ -33,3 +33,5 @@ export const useVueRouter = () => {
throw new Error('router is not defined')
}
}
export type UseVueRouterReturnType = ReturnType<typeof useVueRouter>

View File

@ -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',

View File

@ -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;
}
}

View File

@ -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,36 +64,41 @@ 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) => (
<NBreadcrumbItem
key={curr.key}
onClick={this.breadcrumbItemClick.bind(this, curr)}
>
<NDropdown
labelField="breadcrumbLabel"
options={
curr.children && curr.children?.length > 1 ? curr.children : []
}
onSelect={this.dropdownSelect.bind(this)}
<TransitionGroup tag="li" name="breadcrumb" appear>
{getBreadcrumbOptions.map((curr) => (
<NBreadcrumbItem
key={curr.path}
onClick={breadcrumbItemClick.bind(this, curr)}
>
{{
default: () => (
<span>
{curr.label && typeof curr.label === 'function'
? curr.label()
: curr.breadcrumbLabel}
</span>
),
}}
</NDropdown>
</NBreadcrumbItem>
))}
<NDropdown
labelField="breadcrumbLabel"
options={
curr.children && curr.children?.length > 1
? curr.children
: []
}
onSelect={dropdownSelect.bind(this)}
>
{{
default: () => (
<span>
{curr.label && typeof curr.label === 'function'
? curr.label()
: curr.breadcrumbLabel}
</span>
),
}}
</NDropdown>
</NBreadcrumbItem>
))}
</TransitionGroup>
</NBreadcrumb>
)
},

View File

@ -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 {

View File

@ -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: () => '暗',
}}

View File

@ -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>

View File

@ -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)

View File

@ -54,9 +54,9 @@ const avatarDropdownActionMap = {
*
*/
lockScreen: () => {
const { changeSwitcher } = useSettingActions()
const { updateSettingState } = useSettingActions()
changeSwitcher(true, 'lockScreenSwitch')
updateSettingState('lockScreenSwitch', true)
},
}

View File

@ -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'] = {

View File

@ -22,5 +22,6 @@
"QRCode": "QRCode",
"SvgIcon": "SVG Icon",
"TemplateHooks": "Template Api",
"Modal": "Modal"
"Modal": "Modal",
"ContextMenu": "Right Click Menu"
}

View File

@ -22,5 +22,6 @@
"QRCode": "二维码",
"SvgIcon": "SVG 图标",
"TemplateHooks": "模板内置 Api",
"Modal": "模态框"
"Modal": "模态框",
"ContextMenu": "右键菜单"
}

View File

@ -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,

View File

@ -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)

View 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

View File

@ -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 [
/**

View File

@ -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) {

View File

@ -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,
}
}

View File

@ -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',

View File

@ -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,
}
},
{

View File

@ -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
}

View File

@ -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>>

View File

@ -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
>

View File

@ -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
}

View File

@ -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

View File

@ -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>

View 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>
)
},
})

View File

@ -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',
},
]

View File

@ -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 导航方法">
<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="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>
navigationTo
使
vue 800ms
</h3>
<br />
<NButton onClick={() => navigationTo(15)}></NButton>
</NCard>
<NCard title="useMainPage 主页面方法">
<NCard title="reload 加载函数">
<h3>
使
vue 800ms
</h3>
<br />
<NSpace>
<NButton
onClick={() => {
reload()
@ -77,16 +136,28 @@ export default defineComponent({
>
</NButton>
</NCard>
<NCard title="maximize 内容区域最大化">
<NButton
onClick={() => {
maximize(!this.maximizeRef)
}}
>
</NButton>
</NCard>
</NSpace>
</NCard>
<NCard title="useMaximize 内容区域最大化">
<h3>
isLayoutContentMaximized :
{isLayoutContentMaximized ? '最大化' : '正常尺寸'}
</h3>
<NButton
onClick={() => {
maximize(!this.maximizeRef)
}}
>
</NButton>
</NCard>
<NCard title="useAppNavigation 导航方法">
<h3>
navigationTo
</h3>
<br />
<NButton onClick={() => navigationTo(16)}></NButton>
</NCard>
</NSpace>
)

View File

@ -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 = () => ({

View File

@ -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]
}