This commit is contained in:
ray_wuhao 2023-06-12 15:39:25 +08:00
parent 5d70662b87
commit f5ce953b83
14 changed files with 360 additions and 26 deletions

View File

@ -1,5 +1,17 @@
# CHANGE LOG
## 3.3.4
### Feats
- 新增 RayIframe 组件
- 同步更新 `naive-ui` 版本至最新版本(2.34.3 => 2.34.4)
- 支持更多 appConfig 配置
### TODO
- MenuTag: 切换页面时, 同步更新该标签的所在位置
## 3.3.3
### Feats

View File

@ -55,7 +55,7 @@
## 前言
> 该项目模板采用 `vue3.x` `vite4.0` `pinia` `tsx` 进行开发。
> 该项目模板采用 `vue3.x` `vite4.x` `pinia` `tsx` 进行开发。
> 使用 `naive ui` 作为组件库。
> 预设了最佳构建体验的配置与常用搬砖工具。意在提供一个简洁、快速上手的模板。

View File

@ -0,0 +1,20 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-12
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
* ,
*/
/** css 尺寸单位匹配 */
export const ELEMENT_UNIT =
/^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/

View File

@ -0,0 +1,3 @@
import RayIframe from './src/index'
export default RayIframe

View File

@ -0,0 +1,13 @@
.ray-iframe {
width: var(--ray-iframe-width);
height: var(--ray-iframe-height);
box-sizing: border-box;
border: var(--ray-iframe-frameborder);
& .ray-iframe__container {
width: 100%;
height: 100%;
border: 0;
outline: 0;
}
}

View File

@ -0,0 +1,170 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-09
*
* @workspace ray-template
*
* @remark
*/
import './index.scss'
import { NSpin } from 'naive-ui'
import { completeSize, on, off } from '@use-utils/element'
import type { PropType } from 'vue'
import type { SpinProps } from 'naive-ui'
const RayIframe = defineComponent({
name: 'RayIframe',
props: {
src: {
/** iframe url */
type: String,
required: true,
},
iframeWrapperClass: {
/** 自定义类名 */
type: String,
default: '',
},
frameborder: {
/** 边框尺寸, 0 则不显示 */
type: Number,
default: 0,
},
width: {
/** iframe 宽度 */
type: [String, Number],
default: '100%',
},
height: {
/** iframe 高度 */
type: [String, Number],
default: '100%',
},
allow: {
/**
*
* iframe
*
* ```
* 全屏激活: allow = 'fullscreen'
* 允许跨域: allow = 'payment'
* ```
*
* , 使
*/
type: String,
default: null,
},
name: {
/** iframe 定位嵌入的浏览上下文的名称 */
type: String,
default: null,
},
title: {
/** 标识 iframe 的主要内容 */
type: String,
default: null,
},
success: {
/**
*
* iframe
* 返回值: iframe , Event
*/
type: Function,
default: null,
},
error: {
/**
*
* iframe
* 返回值: iframe , Event
*/
type: Function,
default: null,
},
customSpinProps: {
type: Object as PropType<SpinProps>,
default: () => ({}),
},
},
setup(props) {
const cssVars = computed(() => {
const cssVar = {
'--ray-iframe-frameborder': completeSize(props.frameborder),
'--ray-iframe-width': completeSize(props.width),
'--ray-iframe-height': completeSize(props.height),
}
return cssVar
})
const iframeRef = ref<HTMLIFrameElement>()
const spinShow = ref(true)
const iframeLoadSuccess = (e: Event) => {
spinShow.value = false
props.success?.(iframeRef.value, e)
}
const iframeLoadError = (e: Event) => {
spinShow.value = false
props.error?.(iframeRef.value, e)
}
const getIframeRef = () => {
const iframeEl = iframeRef.value as HTMLElement
return iframeEl
}
onMounted(() => {
on(getIframeRef(), 'load', iframeLoadSuccess.bind(this))
on(getIframeRef(), 'error', iframeLoadError)
})
onBeforeUnmount(() => {
off(getIframeRef(), 'load', iframeLoadSuccess)
off(getIframeRef(), 'error', iframeLoadError)
})
return {
cssVars,
iframeRef,
spinShow,
}
},
render() {
return (
<div
class={['ray-iframe', this.iframeWrapperClass]}
style={[this.cssVars]}
>
<NSpin {...this.customSpinProps} show={this.spinShow}>
{{
...this.$slots,
default: () => (
<iframe
class="ray-iframe__container"
ref="iframeRef"
src={this.src}
allow={this.allow}
name={this.name}
title={this.title}
></iframe>
),
}}
</NSpin>
</div>
)
},
})
export default RayIframe

View File

@ -36,7 +36,7 @@ import type { MenuOption, ScrollbarInst } from 'naive-ui'
const MenuTag = defineComponent({
name: 'MenuTag',
setup() {
setup(_, { expose }) {
const scrollRef = ref<ScrollbarInst | null>(null)
const menuStore = useMenu()
@ -246,22 +246,33 @@ const MenuTag = defineComponent({
menuModelValueChange(item.key as string, item)
}
const handleScrollX = (type: 'left' | 'right') => {
const getScrollElement = () => {
const scroll = document.getElementById(scrollBarUUID) // 获取滚动条容器
if (scroll) {
/**
*
* (class: n-scrollbar-container)
* scrollLeft ,
*/
const scrollContentElement = Array.from(
scroll.childNodes,
) as HTMLElement[]
const findElement = scrollContentElement.find((el) =>
hasClass(el, 'n-scrollbar-container'),
)
const scrollX = findElement!.scrollLeft || 0
return findElement
}
return undefined
}
const handleScrollX = (type: 'left' | 'right') => {
const el = getScrollElement()
if (el) {
/**
*
* (class: n-scrollbar-container)
* scrollLeft ,
*/
const scrollX = el!.scrollLeft || 0
const rolling =
type === 'left' ? Math.max(0, scrollX - 200) : scrollX + 200
@ -344,10 +355,28 @@ const MenuTag = defineComponent({
}
}
/**
*
* ,
* 使 nextTick
*/
const updateScrollBarPosition = () => {
const el = getScrollElement()
if (el) {
nextTick().then(() => {
scrollRef.value?.scrollTo({
left: 99999,
behavior: 'smooth',
})
})
}
}
/** 如果有且只有一个标签页时, 禁止全部关闭操作 */
watch(
() => modelMenuTagOptions.value,
(newData) => {
(newData, oldData) => {
moreOptions.value.forEach((curr) => {
if (exclude.includes(curr.key)) {
newData.length > 1
@ -355,10 +384,15 @@ const MenuTag = defineComponent({
: (curr.disabled = true)
}
})
if (oldData?.length) {
if (newData.length > oldData?.length) {
updateScrollBarPosition()
}
}
},
{
immediate: true,
deep: true,
},
)
@ -370,6 +404,8 @@ const MenuTag = defineComponent({
},
)
expose({})
return {
modelMenuTagOptions,
menuModelValueChange,
@ -426,10 +462,6 @@ const MenuTag = defineComponent({
{...{
id: this.scrollBarUUID,
}}
themeOverrides={{
color: 'rgba(0, 0, 0, 0)',
colorHover: 'rgba(0, 0, 0, 0)',
}}
>
<NSpace
class="menu-tag-wrapper"
@ -450,6 +482,7 @@ const MenuTag = defineComponent({
onContextmenu: this.handleContextMenu.bind(this, idx),
onMouseenter: this.menuTagMouseenter.bind(this, curr),
onMouseleave: this.menuTagMouseleave.bind(this, curr),
tag_data: curr.path,
}}
>
{typeof curr.label === 'function'

View File

@ -23,6 +23,7 @@
import { getCache, setCache } from '@/utils/cache'
import { useSignin } from '@/store'
import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig'
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
import type { Router, NavigationGuardNext } from 'vue-router'
@ -31,13 +32,6 @@ export const permissionRouter = (router: Router) => {
const { path } = ROOT_ROUTE
/** 如果没有权限, 则重定向至首页 */
const redirectToDashboard = (next: NavigationGuardNext) => {
next(path)
setCache('menuKey', path)
}
beforeEach((to, from, next) => {
const token = getCache(APP_CATCH_KEY.token)
const route = getCache('menuKey')
@ -70,13 +64,13 @@ export const permissionRouter = (router: Router) => {
if (route !== 'no') {
next(route)
} else {
redirectToDashboard(next)
redirectRouterToDashboard(true)
}
} else {
next()
}
} else {
redirectToDashboard(next)
redirectRouterToDashboard(true)
}
} else {
if (to.path === '/' || from.path === '/login') {

View File

@ -20,6 +20,7 @@ import {
import { useSignin } from '@/store'
import { useVueRouter } from '@/router/helper/useVueRouter'
import { ROOT_ROUTE } from '@/appConfig/appConfig'
import { setCache } from '@/utils/cache'
import type { Router } from 'vue-router'
@ -92,13 +93,15 @@ export const vueRouterRegister = (router: Router) => {
*
* @param replace 使
*
* @remark
* @remark ,
*/
export const redirectRouterToDashboard = (isReplace?: boolean) => {
export const redirectRouterToDashboard = (isReplace = true) => {
const { router } = useVueRouter()
const { push, replace } = router
const { path } = ROOT_ROUTE
isReplace ? push(path) : replace(path)
setCache('menuKey', path)
}

View File

@ -0,0 +1,14 @@
import type { AppRouteRecordRaw } from '@/router/type'
const iframe: AppRouteRecordRaw = {
path: '/iframe',
name: 'IframeDemo',
component: () => import('@/views/iframe/index'),
meta: {
icon: 'rely',
order: 2,
noLocalTitle: 'iframe',
},
}
export default iframe

View File

@ -1,5 +1,6 @@
@import "@/styles/animate.scss";
@import "@/styles/root.scss";
@import "@/styles/naive.scss";
body,
h1,

5
src/styles/naive.scss Normal file
View File

@ -0,0 +1,5 @@
.n-spin-container,
.n-spin-container .n-spin-content {
width: 100%;
height: 100%;
}

View File

@ -1,4 +1,6 @@
import { validteValueType } from '@use-utils/hook'
import { ELEMENT_UNIT } from '@/appConfig/regConfig'
/**
*
* @param element Target element dom
@ -242,3 +244,19 @@ export const getElement = (element: string) => {
return []
}
}
/**
*
* @param size css size
*
* @remark
*/
export const completeSize = (size: number | string) => {
if (typeof size === 'number') {
return size.toString() + 'px'
} else if (ELEMENT_UNIT.test(size)) {
return size
} else {
return size + 'px'
}
}

View File

@ -0,0 +1,48 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-09
*
* @workspace ray-template
*
* @remark
*/
/**
*
* RayIframe 使
*
* 使 props
* ,
*/
import { NSpace } from 'naive-ui'
import RayIframe from '@/components/RayIframe/index'
const IframeDemo = defineComponent({
name: 'IframeDemo',
setup() {
return {}
},
render() {
return (
<NSpace vertical size={[20, 20]}>
<NSpace vertical size={[20, 20]}>
<h2>naive ui</h2>
<RayIframe
src="https://www.naiveui.com/zh-CN/dark"
height="500"
allow="fullscreen"
/>
</NSpace>
<NSpace vertical size={[20, 20]}>
<h2>vueuse</h2>
<RayIframe src="https://www.vueusejs.com/" height="500" />
</NSpace>
</NSpace>
)
},
})
export default IframeDemo