提交 3c9f394a 编写于 作者: V vben

chore: remove unless code

上级 df4276a9
......@@ -33,7 +33,7 @@
"@logicflow/core": "^1.0.7",
"@logicflow/extension": "^1.0.7",
"@zxcvbn-ts/core": "^1.2.0",
"ant-design-vue": "3.0.0-beta.7",
"ant-design-vue": "3.0.0-beta.8",
"codemirror": "^5.65.1",
"cropperjs": "^1.5.12",
"echarts": "^5.2.2",
......@@ -47,15 +47,15 @@
"showdown": "^1.9.1",
"sortablejs": "^1.14.0",
"tinymce": "^5.10.2",
"vditor": "^3.8.10",
"vue": "^3.2.27",
"vditor": "^3.8.11",
"vue": "^3.2.29",
"vue-json-pretty": "^1.8.2",
"vue-router": "^4.0.12",
"xlsx": "^0.17.5"
},
"devDependencies": {
"@admin/types": "workspace:*",
"@iconify/json": "^2.0.26",
"@iconify/json": "^2.0.28",
"@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.5",
"@types/inquirer": "^8.1.3",
......@@ -67,9 +67,9 @@
"@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7",
"@vitejs/plugin-legacy": "^1.6.4",
"@vitejs/plugin-vue": "^2.0.1",
"@vitejs/plugin-vue": "^2.1.0",
"@vitejs/plugin-vue-jsx": "^1.3.3",
"@vue/compiler-sfc": "3.2.27",
"@vue/compiler-sfc": "3.2.29",
"@vue/test-utils": "^2.0.0-rc.18",
"autoprefixer": "^10.4.2",
"cross-env": "^7.0.3",
......@@ -81,7 +81,7 @@
"vite": "^2.8.0-beta.3",
"vite-plugin-compression": "^0.4.0",
"vite-plugin-html": "^2.1.2",
"vite-plugin-imagemin": "^0.5.1",
"vite-plugin-imagemin": "^0.5.2",
"vite-plugin-mkcert": "^1.5.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.7.0",
......@@ -89,7 +89,7 @@
"vite-plugin-svg-icons": "^1.1.0",
"vite-plugin-theme": "^0.8.1",
"vite-plugin-vue-setup-extend": "^0.3.0",
"vue-tsc": "^0.30.6"
"vue-tsc": "^0.31.1"
},
"repository": {
"type": "git",
......
......@@ -7,7 +7,7 @@ import { getGlobalConfig } from '@/internal/config'
import 'dayjs/locale/zh-cn'
// support Multi-language
const { antdLocale } = useLocale()
const { uiFrameLocale } = useLocale()
// Listening to page changes and dynamically changing site titles
const { title } = getGlobalConfig()
......@@ -15,7 +15,7 @@ useTitle(title, (route) => route.name !== REDIRECT_NAME)
</script>
<template>
<a-config-provider :locale="antdLocale">
<a-config-provider :locale="uiFrameLocale">
<app-provider>
<router-view />
</app-provider>
......
<!--
* @Author: Vben
* @Description: Arrow component with animation
-->
<template>
<span :class="getClass">
<Icon icon="ion:chevron-forward" :style="$attrs.iconStyle" />
</span>
</template>
<script lang="ts" setup>
import type { CSSProperties } from 'vue'
import { computed } from 'vue'
import { Icon } from '@/components/Icon'
import { useDesign } from '@/hooks/web/useDesign'
import { RightOutlined } from '@ant-design/icons-vue'
const props = defineProps({
/**
......@@ -29,12 +21,14 @@ const props = defineProps({
* Cancel padding/margin for inline
*/
inset: { type: Boolean },
iconStyle: { type: Object as PropType<CSSProperties> },
})
const { prefixCls } = useDesign('basic-arrow')
// get component class
const getClass = computed(() => {
const classes = computed(() => {
const { expand, up, down, inset } = props
return [
prefixCls,
......@@ -82,3 +76,9 @@ const getClass = computed(() => {
}
}
</style>
<template>
<span :class="classes">
<right-outlined :style="iconStyle" />
</span>
</template>
import { withInstall } from '@admin/utils'
import clickOutSide from './src/ClickOutSide.vue'
export const ClickOutSide = withInstall(clickOutSide)
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { onClickOutside } from '@admin/use'
const emit = defineEmits(['mounted', 'clickOutside'])
const wrap = ref<ElRef>(null)
onClickOutside(wrap, () => {
emit('clickOutside')
})
onMounted(() => {
emit('mounted')
})
</script>
import { withInstall } from '@admin/utils'
import collapseContainer from './src/collapse/CollapseContainer.vue'
import scrollContainer from './src/ScrollContainer.vue'
import lazyContainer from './src/LazyContainer.vue'
export const CollapseContainer = withInstall(collapseContainer)
export const ScrollContainer = withInstall(scrollContainer)
export const LazyContainer = withInstall(lazyContainer)
export * from './src/typing'
<template>
<transition-group
class="h-full w-full"
v-bind="$attrs"
ref="elRef"
:name="transitionName"
:tag="tag"
mode="out-in"
>
<div key="component" v-if="isInit">
<slot :loading="loading"></slot>
</div>
<div key="skeleton" v-else>
<slot name="skeleton" v-if="$slots.skeleton"></slot>
<Skeleton v-else />
</div>
</transition-group>
</template>
<script lang="ts">
import type { PropType } from 'vue'
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'
import { Skeleton } from 'ant-design-vue'
import { useTimeoutFn } from '@admin/use'
import { useIntersectionObserver } from '@/hooks/event/useIntersectionObserver'
interface State {
isInit: boolean
loading: boolean
intersectionObserverInstance: IntersectionObserver | null
}
const props = {
/**
* Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
*/
timeout: { type: Number },
/**
* The viewport where the component is located.
* If the component is scrolling in the page container, the viewport is the container
*/
viewport: {
type: (typeof window !== 'undefined'
? window.HTMLElement
: Object) as PropType<HTMLElement>,
default: () => null,
},
/**
* Preload threshold, css unit
*/
threshold: { type: String, default: '0px' },
/**
* The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
*/
direction: {
type: String,
default: 'vertical',
validator: (v) => ['vertical', 'horizontal'].includes(v),
},
/**
* The label name of the outer container that wraps the component
*/
tag: { type: String, default: 'div' },
maxWaitingTime: { type: Number, default: 80 },
/**
* transition name
*/
transitionName: { type: String, default: 'lazy-container' },
}
export default defineComponent({
name: 'LazyContainer',
components: { Skeleton },
inheritAttrs: false,
props,
emits: ['init'],
setup(props, { emit }) {
const elRef = ref()
const state = reactive<State>({
isInit: false,
loading: false,
intersectionObserverInstance: null,
})
onMounted(() => {
immediateInit()
initIntersectionObserver()
})
// If there is a set delay time, it will be executed immediately
function immediateInit() {
const { timeout } = props
timeout &&
useTimeoutFn(() => {
init()
}, timeout)
}
function init() {
state.loading = true
useTimeoutFn(() => {
if (state.isInit) return
state.isInit = true
emit('init')
}, props.maxWaitingTime || 80)
}
function initIntersectionObserver() {
const { timeout, direction, threshold } = props
if (timeout) return
// According to the scrolling direction to construct the viewport margin, used to load in advance
let rootMargin = '0px'
switch (direction) {
case 'vertical':
rootMargin = `${threshold} 0px`
break
case 'horizontal':
rootMargin = `0px ${threshold}`
break
}
try {
const { stop, observer } = useIntersectionObserver({
rootMargin,
target: toRef(elRef.value, '$el'),
onIntersect: (entries: any[]) => {
const isIntersecting =
entries[0].isIntersecting || entries[0].intersectionRatio
if (isIntersecting) {
init()
if (observer) {
stop()
}
}
},
root: toRef(props, 'viewport'),
})
} catch (e) {
init()
}
}
return {
elRef,
...toRefs(state),
}
},
})
</script>
......@@ -9,7 +9,6 @@ import {
Ref,
} from 'vue'
import { on, off } from '@admin/utils'
import { renderThumbStyle, BAR_MAP } from './util'
export default defineComponent({
......
import {
createSimpleTransition,
createJavascriptTransition,
} from './src/CreateTransition'
import ExpandTransitionGenerator from './src/ExpandTransition'
export { default as CollapseTransition } from './src/CollapseTransition.vue'
export const FadeTransition = createSimpleTransition('fade-transition')
export const ScaleTransition = createSimpleTransition('scale-transition')
export const SlideYTransition = createSimpleTransition('slide-y-transition')
export const ScrollYTransition = createSimpleTransition('scroll-y-transition')
export const SlideYReverseTransition = createSimpleTransition(
'slide-y-reverse-transition',
)
export const ScrollYReverseTransition = createSimpleTransition(
'scroll-y-reverse-transition',
)
export const SlideXTransition = createSimpleTransition('slide-x-transition')
export const ScrollXTransition = createSimpleTransition('scroll-x-transition')
export const SlideXReverseTransition = createSimpleTransition(
'slide-x-reverse-transition',
)
export const ScrollXReverseTransition = createSimpleTransition(
'scroll-x-reverse-transition',
)
export const ScaleRotateTransition = createSimpleTransition(
'scale-rotate-transition',
)
export const ExpandXTransition = createJavascriptTransition(
'expand-x-transition',
ExpandTransitionGenerator('', true),
)
export const ExpandTransition = createJavascriptTransition(
'expand-transition',
ExpandTransitionGenerator(''),
)
import type { PropType } from 'vue'
import { defineComponent, Transition, TransitionGroup } from 'vue'
import { getSlot } from '@admin/utils'
type Mode = 'in-out' | 'out-in' | 'default' | undefined
export function createSimpleTransition(
name: string,
origin = 'top center 0',
mode?: Mode,
) {
return defineComponent({
name,
props: {
group: {
type: Boolean as PropType<boolean>,
default: false,
},
mode: {
type: String as PropType<Mode>,
default: mode,
},
origin: {
type: String as PropType<string>,
default: origin,
},
},
setup(props, { slots, attrs }) {
const onBeforeEnter = (el: HTMLElement) => {
el.style.transformOrigin = props.origin
}
return () => {
const Tag = !props.group ? Transition : TransitionGroup
return (
<Tag
name={name}
mode={props.mode}
{...attrs}
onBeforeEnter={onBeforeEnter}
>
{() => getSlot(slots)}
</Tag>
)
}
},
})
}
export function createJavascriptTransition(
name: string,
functions: Recordable,
mode: Mode = 'in-out',
) {
return defineComponent({
name,
props: {
mode: {
type: String as PropType<Mode>,
default: mode,
},
},
setup(props, { attrs, slots }) {
return () => {
return (
<Transition
name={name}
mode={props.mode}
{...attrs}
onBeforeEnter={functions.beforeEnter}
onEnter={functions.enter}
onLeave={functions.leave}
onAfterLeave={functions.afterLeave}
onLeaveCancelled={functions.afterLeave}
>
{() => getSlot(slots)}
</Transition>
)
}
},
})
}
/**
* Makes the first character of a string uppercase
*/
export function upperFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
interface HTMLExpandElement extends HTMLElement {
_parent?: (Node & ParentNode & HTMLElement) | null
_initialStyle: {
transition: string
overflow: string | null
height?: string | null
width?: string | null
}
}
export default function (expandedParentClass = '', x = false) {
const sizeProperty = x ? 'width' : ('height' as 'width' | 'height')
const offsetProperty = `offset${upperFirst(sizeProperty)}` as
| 'offsetHeight'
| 'offsetWidth'
return {
beforeEnter(el: HTMLExpandElement) {
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null
el._initialStyle = {
transition: el.style.transition,
overflow: el.style.overflow,
[sizeProperty]: el.style[sizeProperty],
}
},
enter(el: HTMLExpandElement) {
const initialStyle = el._initialStyle
el.style.setProperty('transition', 'none', 'important')
el.style.overflow = 'hidden'
// const offset = `${el[offsetProperty]}px`;
// el.style[sizeProperty] = '0';
void el.offsetHeight // force reflow
el.style.transition = initialStyle.transition
if (expandedParentClass && el._parent) {
el._parent.classList.add(expandedParentClass)
}
requestAnimationFrame(() => {
// el.style[sizeProperty] = offset;
})
},
afterEnter: resetStyles,
enterCancelled: resetStyles,
leave(el: HTMLExpandElement) {
el._initialStyle = {
transition: '',
overflow: el.style.overflow,
[sizeProperty]: el.style[sizeProperty],
}
el.style.overflow = 'hidden'
el.style[sizeProperty] = `${el[offsetProperty]}px`
/* eslint-disable-next-line */
void el.offsetHeight // force reflow
requestAnimationFrame(() => (el.style[sizeProperty] = '0'))
},
afterLeave,
leaveCancelled: afterLeave,
}
function afterLeave(el: HTMLExpandElement) {
if (expandedParentClass && el._parent) {
el._parent.classList.remove(expandedParentClass)
}
resetStyles(el)
}
function resetStyles(el: HTMLExpandElement) {
const size = el._initialStyle[sizeProperty]
el.style.overflow = el._initialStyle.overflow!
if (size != null) el.style[sizeProperty] = size
Reflect.deleteProperty(el, '_initialStyle')
}
}
......@@ -8,7 +8,6 @@ import type {
CheckKeys,
TreeActionType,
} from './tree'
import {
defineComponent,
reactive,
......@@ -38,7 +37,6 @@ import {
getSlot,
createBEM,
} from '@admin/utils'
import { useTree } from './useTree'
import { useContextMenu } from '@/hooks/web/useContextMenu'
import { CreateContextOptions } from '@/components/ContextMenu'
......@@ -294,7 +292,7 @@ export default defineComponent({
})
onMounted(() => {
const level = parseInt(props.defaultExpandLevel)
const level = parseInt(`${props.defaultExpandLevel}`)
if (level > 0) {
state.expandedKeys = filterByLevel(level)
} else if (props.defaultExpandAll) {
......
<template>
<div :class="bem()" class="flex px-2 py-1.5 items-center">
<slot name="headerTitle" v-if="slots.headerTitle"></slot>
<BasicTitle :helpMessage="helpMessage" v-if="!slots.headerTitle && title">
{{ title }}
</BasicTitle>
<div
class="flex items-center flex-1 cursor-pointer justify-self-stretch"
v-if="search || toolbar"
>
<div :class="getInputSearchCls" v-if="search">
<InputSearch
:placeholder="t('common.searchText')"
size="small"
allowClear
v-model:value="searchValue"
/>
</div>
<Dropdown @click.prevent v-if="toolbar">
<Icon icon="ion:ellipsis-vertical" />
<template #overlay>
<Menu @click="handleMenuClick">
<template v-for="item in toolbarList" :key="item.value">
<MenuItem v-bind="{ key: item.value }">
{{ item.label }}
</MenuItem>
<MenuDivider v-if="item.divider" />
</template>
</Menu>
</template>
</Dropdown>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch, useSlots } from 'vue'
import {
......@@ -183,3 +149,38 @@ watch(
},
)
</script>
<template>
<div :class="bem()" class="flex px-2 py-1.5 items-center">
<slot name="headerTitle" v-if="slots.headerTitle"></slot>
<BasicTitle :helpMessage="helpMessage" v-if="!slots.headerTitle && title">
{{ title }}
</BasicTitle>
<div
class="flex items-center flex-1 cursor-pointer justify-self-stretch"
v-if="search || toolbar"
>
<div :class="getInputSearchCls" v-if="search">
<InputSearch
:placeholder="t('common.searchText')"
size="small"
allowClear
v-model:value="searchValue"
/>
</div>
<Dropdown @click.prevent v-if="toolbar">
<Icon icon="ion:ellipsis-vertical" />
<template #overlay>
<Menu @click="handleMenuClick">
<template v-for="item in toolbarList" :key="item.value">
<MenuItem v-bind="{ key: item.value }">
{{ item.label }}
</MenuItem>
<MenuDivider v-if="item.divider" />
</template>
</Menu>
</template>
</Dropdown>
</div>
</div>
</template>
import type { VNode, FunctionalComponent } from 'vue'
import { h } from 'vue'
import { isString } from '@admin/utils'
import { Icon } from '@/components/Icon'
......
import { withInstall } from '@admin/utils'
import basicDragVerify from './src/DragVerify.vue'
import rotateDragVerify from './src/ImgRotate.vue'
export const BasicDragVerify = withInstall(basicDragVerify)
export const RotateDragVerify = withInstall(rotateDragVerify)
export * from './src/typing'
<script lang="tsx">
import type { Ref } from 'vue'
import {
defineComponent,
ref,
computed,
unref,
reactive,
watch,
watchEffect,
} from 'vue'
import { useEventListener } from '@/hooks/event/useEventListener'
import { basicProps } from './props'
import { getSlot } from '@admin/utils'
import { useTimeoutFn } from '@admin/use'
import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
export default defineComponent({
name: 'BaseDargVerify',
props: basicProps,
emits: ['success', 'update:value', 'change', 'start', 'move', 'end'],
setup(props, { emit, slots, expose }) {
const state = reactive({
isMoving: false,
isPassing: false,
moveDistance: 0,
toLeft: false,
startTime: 0,
endTime: 0,
})
const wrapElRef = ref<HTMLDivElement | null>(null)
const barElRef = ref<HTMLDivElement | null>(null)
const contentElRef = ref<HTMLDivElement | null>(null)
const actionElRef = ref(null) as Ref<HTMLDivElement | null>
useEventListener({
el: document,
name: 'mouseup',
listener: () => {
if (state.isMoving) {
resume()
}
},
})
const getActionStyleRef = computed(() => {
const { height, actionStyle } = props
const h = `${parseInt(height as string)}px`
return {
left: 0,
width: h,
height: h,
...actionStyle,
}
})
const getWrapStyleRef = computed(() => {
const { height, width, circle, wrapStyle } = props
const h = parseInt(height as string)
const w = `${parseInt(width as string)}px`
return {
width: w,
height: `${h}px`,
lineHeight: `${h}px`,
borderRadius: circle ? h / 2 + 'px' : 0,
...wrapStyle,
}
})
const getBarStyleRef = computed(() => {
const { height, circle, barStyle } = props
const h = parseInt(height as string)
return {
height: `${h}px`,
borderRadius: circle ? h / 2 + 'px 0 0 ' + h / 2 + 'px' : 0,
...barStyle,
}
})
const getContentStyleRef = computed(() => {
const { height, width, contentStyle } = props
const h = `${parseInt(height as string)}px`
const w = `${parseInt(width as string)}px`
return {
height: h,
width: w,
...contentStyle,
}
})
watch(
() => state.isPassing,
(isPassing) => {
if (isPassing) {
const { startTime, endTime } = state
const time = (endTime - startTime) / 1000
emit('success', { isPassing, time: time.toFixed(1) })
emit('update:value', isPassing)
emit('change', isPassing)
}
},
)
watchEffect(() => {
state.isPassing = !!props.value
})
function getEventPageX(e: MouseEvent | TouchEvent) {
return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX
}
function handleDragStart(e: MouseEvent | TouchEvent) {
if (state.isPassing) {
return
}
const actionEl = unref(actionElRef)
if (!actionEl) return
emit('start', e)
state.moveDistance =
getEventPageX(e) - parseInt(actionEl.style.left.replace('px', ''), 10)
state.startTime = new Date().getTime()
state.isMoving = true
}
function getOffset(el: HTMLDivElement) {
const actionWidth = parseInt(el.style.width)
const { width } = props
const widthNum = parseInt(width as string)
const offset = widthNum - actionWidth - 6
return { offset, widthNum, actionWidth }
}
function handleDragMoving(e: MouseEvent | TouchEvent) {
const { isMoving, moveDistance } = state
if (isMoving) {
const actionEl = unref(actionElRef)
const barEl = unref(barElRef)
if (!actionEl || !barEl) return
const { offset, widthNum, actionWidth } = getOffset(actionEl)
const moveX = getEventPageX(e) - moveDistance
emit('move', {
event: e,
moveDistance,
moveX,
})
if (moveX > 0 && moveX <= offset) {
actionEl.style.left = `${moveX}px`
barEl.style.width = `${moveX + actionWidth / 2}px`
} else if (moveX > offset) {
actionEl.style.left = `${widthNum - actionWidth}px`
barEl.style.width = `${widthNum - actionWidth / 2}px`
if (!props.isSlot) {
checkPass()
}
}
}
}
function handleDragOver(e: MouseEvent | TouchEvent) {
const { isMoving, isPassing, moveDistance } = state
if (isMoving && !isPassing) {
emit('end', e)
const actionEl = unref(actionElRef)
const barEl = unref(barElRef)
if (!actionEl || !barEl) return
const moveX = getEventPageX(e) - moveDistance
const { offset, widthNum, actionWidth } = getOffset(actionEl)
if (moveX < offset) {
if (!props.isSlot) {
resume()
} else {
setTimeout(() => {
if (!props.value) {
resume()
} else {
const contentEl = unref(contentElRef)
if (contentEl) {
contentEl.style.width = `${parseInt(barEl.style.width)}px`
}
}
}, 0)
}
} else {
actionEl.style.left = `${widthNum - actionWidth}px`
barEl.style.width = `${widthNum - actionWidth / 2}px`
checkPass()
}
state.isMoving = false
}
}
function checkPass() {
if (props.isSlot) {
resume()
return
}
state.endTime = new Date().getTime()
state.isPassing = true
state.isMoving = false
}
function resume() {
state.isMoving = false
state.isPassing = false
state.moveDistance = 0
state.toLeft = false
state.startTime = 0
state.endTime = 0
const actionEl = unref(actionElRef)
const barEl = unref(barElRef)
const contentEl = unref(contentElRef)
if (!actionEl || !barEl || !contentEl) return
state.toLeft = true
useTimeoutFn(() => {
state.toLeft = false
actionEl.style.left = '0'
barEl.style.width = '0'
// The time is consistent with the animation time
}, 300)
contentEl.style.width = unref(getContentStyleRef).width
}
expose({
resume,
})
return () => {
const renderBar = () => {
const cls = [`darg-verify-bar`]
if (state.toLeft) {
cls.push('to-left')
}
return <div class={cls} ref={barElRef} style={unref(getBarStyleRef)} />
}
const renderContent = () => {
const cls = [`darg-verify-content`]
const { isPassing } = state
const { text, successText } = props
isPassing && cls.push('success')
return (
<div class={cls} ref={contentElRef} style={unref(getContentStyleRef)}>
{getSlot(slots, 'text', isPassing) ||
(isPassing ? successText : text)}
</div>
)
}
const renderAction = () => {
const cls = [`darg-verify-action`]
const { toLeft, isPassing } = state
if (toLeft) {
cls.push('to-left')
}
return (
<div
class={cls}
onMousedown={handleDragStart}
onTouchstart={handleDragStart}
style={unref(getActionStyleRef)}
ref={actionElRef}
>
{getSlot(slots, 'actionIcon', isPassing) ||
(isPassing ? (
<CheckOutlined class={`darg-verify-action__icon`} />
) : (
<DoubleRightOutlined class={`darg-verify-action__icon`} />
))}
</div>
)
}
return (
<div
class="darg-verify"
ref={wrapElRef}
style={unref(getWrapStyleRef)}
onMousemove={handleDragMoving}
onTouchmove={handleDragMoving}
onMouseleave={handleDragOver}
onMouseup={handleDragOver}
onTouchend={handleDragOver}
>
{renderBar()}
{renderContent()}
{renderAction()}
</div>
)
}
},
})
</script>
<style lang="less">
@radius: 4px;
.darg-verify {
position: relative;
overflow: hidden;
text-align: center;
background-color: rgb(238 238 238);
border: 1px solid #ddd;
border-radius: @radius;
&-bar {
position: absolute;
width: 0;
height: 36px;
background-color: @success-color;
border-radius: @radius;
&.to-left {
width: 0 !important;
transition: width 0.3s;
}
}
&-content {
position: absolute;
top: 0;
font-size: 12px;
text-size-adjust: none;
background-color: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, #333),
color-stop(0.4, #333),
color-stop(0.5, #fff),
color-stop(0.6, #333),
color-stop(1, #333)
);
animation: slidetounlock 3s infinite;
background-clip: text;
user-select: none;
&.success {
-webkit-text-fill-color: @white;
}
& > * {
-webkit-text-fill-color: #333;
}
}
&-action {
position: absolute;
top: 0;
left: 0;
display: flex;
cursor: move;
background-color: @white;
border-radius: @radius;
justify-content: center;
align-items: center;
&__icon {
cursor: inherit;
}
&.to-left {
left: 0 !important;
transition: left 0.3s;
}
}
}
@keyframes slidetounlock {
0% {
background-position: -120px 0;
}
100% {
background-position: 120px 0;
}
}
</style>
<script lang="tsx">
import type { MoveData, DragVerifyActionType } from './typing'
import { defineComponent, computed, unref, reactive, watch, ref } from 'vue'
import { useTimeoutFn } from '@admin/use'
import BasicDragVerify from './DragVerify.vue'
import { hackCss } from '@admin/utils'
import { rotateProps } from './props'
import { useI18n } from '@admin/locale'
export default defineComponent({
name: 'ImgRotateDragVerify',
inheritAttrs: false,
props: rotateProps,
emits: ['success', 'change', 'update:value'],
setup(props, { emit, attrs, expose }) {
const basicRef = ref<Nullable<DragVerifyActionType>>(null)
const state = reactive({
showTip: false,
isPassing: false,
imgStyle: {},
randomRotate: 0,
currentRotate: 0,
toOrigin: false,
startTime: 0,
endTime: 0,
draged: false,
})
const { t } = useI18n()
watch(
() => state.isPassing,
(isPassing) => {
if (isPassing) {
const { startTime, endTime } = state
const time = (endTime - startTime) / 1000
emit('success', { isPassing, time: time.toFixed(1) })
emit('change', isPassing)
emit('update:value', isPassing)
}
},
)
const getImgWrapStyleRef = computed(() => {
const { imgWrapStyle, imgWidth } = props
return {
width: `${imgWidth}px`,
height: `${imgWidth}px`,
...imgWrapStyle,
}
})
const getFactorRef = computed(() => {
const { minDegree, maxDegree } = props
if (minDegree === maxDegree) {
return Math.floor(1 + Math.random() * 1) / 10 + 1
}
return 1
})
function handleStart() {
state.startTime = new Date().getTime()
}
function handleDragBarMove(data: MoveData) {
state.draged = true
const { imgWidth, height, maxDegree } = props
const { moveX } = data
const currentRotate = Math.ceil(
(moveX / (imgWidth! - parseInt(height as string))) *
maxDegree! *
unref(getFactorRef),
)
state.currentRotate = currentRotate
state.imgStyle = hackCss(
'transform',
`rotateZ(${state.randomRotate - currentRotate}deg)`,
)
}
function handleImgOnLoad() {
const { minDegree, maxDegree } = props
const ranRotate = Math.floor(
minDegree! + Math.random() * (maxDegree! - minDegree!),
) // 生成随机角度
state.randomRotate = ranRotate
state.imgStyle = hackCss('transform', `rotateZ(${ranRotate}deg)`)
}
function handleDragEnd() {
const { randomRotate, currentRotate } = state
const { diffDegree } = props
if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) {
state.imgStyle = hackCss('transform', `rotateZ(${randomRotate}deg)`)
state.toOrigin = true
useTimeoutFn(() => {
state.toOrigin = false
state.showTip = true
// 时间与动画时间保持一致
}, 300)
} else {
checkPass()
}
state.showTip = true
}
function checkPass() {
state.isPassing = true
state.endTime = new Date().getTime()
}
function resume() {
state.showTip = false
const basicEl = unref(basicRef)
if (!basicEl) {
return
}
state.isPassing = false
basicEl.resume()
handleImgOnLoad()
}
expose({ resume })
// handleImgOnLoad();
return () => {
const { src } = props
const { toOrigin, isPassing, startTime, endTime } = state
const imgCls: string[] = []
if (toOrigin) {
imgCls.push('to-origin')
}
const time = (endTime - startTime) / 1000
return (
<div class="ir-dv">
<div class={`ir-dv-img__wrap`} style={unref(getImgWrapStyleRef)}>
<img
src={src}
onLoad={handleImgOnLoad}
width={parseInt(props.width as string)}
class={imgCls}
style={state.imgStyle}
onClick={() => {
resume()
}}
alt="verify"
/>
{state.showTip && (
<span
class={[
`ir-dv-img__tip`,
state.isPassing ? 'success' : 'error',
]}
>
{state.isPassing
? t('component.verify.time', { time: time.toFixed(1) })
: t('component.verify.error')}
</span>
)}
{!state.showTip && !state.draged && (
<span class={[`ir-dv-img__tip`, 'normal']}>
{t('component.verify.redoTip')}
</span>
)}
</div>
<BasicDragVerify
class={`ir-dv-drag__bar`}
onMove={handleDragBarMove}
onEnd={handleDragEnd}
onStart={handleStart}
ref={basicRef}
{...{ ...attrs, ...props }}
value={isPassing}
isSlot={true}
/>
</div>
)
}
},
})
</script>
<style lang="less">
.ir-dv {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
&-img__wrap {
position: relative;
overflow: hidden;
border-radius: 50%;
img {
width: 100%;
border-radius: 50%;
&.to-origin {
transition: transform 0.3s;
}
}
}
&-img__tip {
position: absolute;
bottom: 10px;
left: 0;
z-index: 1;
display: block;
width: 100%;
height: 30px;
font-size: 12px;
line-height: 30px;
color: @white;
text-align: center;
&.success {
background-color: fade(@success-color, 60%);
}
&.error {
background-color: fade(@error-color, 60%);
}
&.normal {
background-color: rgb(0 0 0 / 30%);
}
}
&-drag__bar {
margin-top: 20px;
}
}
</style>
import type { PropType } from 'vue'
import { useI18n } from '@admin/locale'
const { t } = useI18n()
export const basicProps = {
value: {
type: Boolean as PropType<boolean>,
default: false,
},
isSlot: {
type: Boolean as PropType<boolean>,
default: false,
},
text: {
type: [String] as PropType<string>,
default: t('component.verify.dragText'),
},
successText: {
type: [String] as PropType<string>,
default: t('component.verify.successText'),
},
height: {
type: [Number, String] as PropType<number | string>,
default: 40,
},
width: {
type: [Number, String] as PropType<number | string>,
default: 220,
},
circle: {
type: Boolean as PropType<boolean>,
default: false,
},
wrapStyle: {
type: Object as PropType<any>,
default: {},
},
contentStyle: {
type: Object as PropType<any>,
default: {},
},
barStyle: {
type: Object as PropType<any>,
default: {},
},
actionStyle: {
type: Object as PropType<any>,
default: {},
},
}
export const rotateProps = {
...basicProps,
src: {
type: String as PropType<string>,
},
imgWidth: {
type: Number as PropType<number>,
default: 260,
},
imgWrapStyle: {
type: Object as PropType<any>,
default: {},
},
minDegree: {
type: Number as PropType<number>,
default: 90,
},
maxDegree: {
type: Number as PropType<number>,
default: 270,
},
diffDegree: {
type: Number as PropType<number>,
default: 20,
},
}
export interface DragVerifyActionType {
resume: () => void
}
export interface PassingData {
isPassing: boolean
time: number
}
export interface MoveData {
event: MouseEvent | TouchEvent
moveDistance: number
moveX: number
}
import { withInstall } from '@admin/utils'
import vScroll from './src/VirtualScroll.vue'
export const VScroll = withInstall(vScroll)
<script lang="tsx">
import {
defineComponent,
computed,
ref,
unref,
reactive,
onMounted,
watch,
nextTick,
CSSProperties,
} from 'vue'
import { useEventListener } from '@/hooks/event/useEventListener'
import { getSlot } from '@admin/utils'
type NumberOrNumberString = PropType<string | number | undefined>
const props = {
height: [Number, String] as NumberOrNumberString,
maxHeight: [Number, String] as NumberOrNumberString,
maxWidth: [Number, String] as NumberOrNumberString,
minHeight: [Number, String] as NumberOrNumberString,
minWidth: [Number, String] as NumberOrNumberString,
width: [Number, String] as NumberOrNumberString,
bench: {
type: [Number, String] as NumberOrNumberString,
default: 0,
},
itemHeight: {
type: [Number, String] as NumberOrNumberString,
required: true,
},
items: {
type: Array as PropType<any[]>,
default: () => [],
},
}
const prefixCls = 'virtual-scroll'
function convertToUnit(
str: string | number | null | undefined,
unit = 'px',
): string | undefined {
if (str == null || str === '') {
return undefined
} else if (isNaN(+str!)) {
return String(str)
} else {
return `${Number(str)}${unit}`
}
}
export default defineComponent({
name: 'VirtualScroll',
props,
setup(props, { slots }) {
const wrapElRef = ref<HTMLDivElement | null>(null)
const state = reactive({
first: 0,
last: 0,
scrollTop: 0,
})
const getBenchRef = computed(() => {
return parseInt(props.bench as string, 10)
})
const getItemHeightRef = computed(() => {
return parseInt(props.itemHeight as string, 10)
})
const getFirstToRenderRef = computed(() => {
return Math.max(0, state.first - unref(getBenchRef))
})
const getLastToRenderRef = computed(() => {
return Math.min(
(props.items || []).length,
state.last + unref(getBenchRef),
)
})
const getContainerStyleRef = computed((): CSSProperties => {
return {
height: convertToUnit(
(props.items || []).length * unref(getItemHeightRef),
),
}
})
const getWrapStyleRef = computed((): CSSProperties => {
const styles: Recordable<string> = {}
const height = convertToUnit(props.height)
const minHeight = convertToUnit(props.minHeight)
const minWidth = convertToUnit(props.minWidth)
const maxHeight = convertToUnit(props.maxHeight)
const maxWidth = convertToUnit(props.maxWidth)
const width = convertToUnit(props.width)
if (height) styles.height = height
if (minHeight) styles.minHeight = minHeight
if (minWidth) styles.minWidth = minWidth
if (maxHeight) styles.maxHeight = maxHeight
if (maxWidth) styles.maxWidth = maxWidth
if (width) styles.width = width
return styles
})
watch([() => props.itemHeight, () => props.height], () => {
onScroll()
})
function getLast(first: number): number {
const wrapEl = unref(wrapElRef)
if (!wrapEl) {
return 0
}
const height =
parseInt(`${props.height}` || '0', 10) || wrapEl.clientHeight
return first + Math.ceil(height / unref(getItemHeightRef))
}
function getFirst(): number {
return Math.floor(state.scrollTop / unref(getItemHeightRef))
}
function onScroll() {
const wrapEl = unref(wrapElRef)
if (!wrapEl) {
return
}
state.scrollTop = wrapEl.scrollTop
state.first = getFirst()
state.last = getLast(state.first)
}
function renderChildren() {
const { items = [] } = props
return items
.slice(unref(getFirstToRenderRef), unref(getLastToRenderRef))
.map(genChild)
}
function genChild(item: any, index: number) {
index += unref(getFirstToRenderRef)
const top = convertToUnit(index * unref(getItemHeightRef))
return (
<div class={`${prefixCls}__item`} style={{ top }} key={index}>
{getSlot(slots, 'default', { index, item })}
</div>
)
}
onMounted(() => {
state.last = getLast(0)
nextTick(() => {
const wrapEl = unref(wrapElRef)
if (!wrapEl) {
return
}
useEventListener({
el: wrapEl,
name: 'scroll',
listener: onScroll,
wait: 0,
})
})
})
return () => (
<div class={prefixCls} style={unref(getWrapStyleRef)} ref={wrapElRef}>
<div
class={`${prefixCls}__container`}
style={unref(getContainerStyleRef)}
>
{renderChildren()}
</div>
</div>
)
},
})
</script>
<style scoped lang="less">
.virtual-scroll {
position: relative;
display: block;
width: 100%;
max-width: 100%;
overflow: auto;
flex: 1 1 auto;
&__container {
display: block;
}
&__item {
position: absolute;
right: 0;
left: 0;
}
}
</style>
import { Ref, watchEffect, ref } from 'vue'
interface IntersectionObserverProps {
target: Ref<Element | null | undefined>
root?: Ref<any>
onIntersect: IntersectionObserverCallback
rootMargin?: string
threshold?: number
}
export function useIntersectionObserver({
target,
root,
onIntersect,
rootMargin = '0px',
threshold = 0.1,
}: IntersectionObserverProps) {
let cleanup = () => {}
const observer: Ref<Nullable<IntersectionObserver>> = ref(null)
const stopEffect = watchEffect(() => {
cleanup()
observer.value = new IntersectionObserver(onIntersect, {
root: root ? root.value : null,
rootMargin,
threshold,
})
const current = target.value
current && observer.value.observe(current)
cleanup = () => {
if (observer.value) {
observer.value.disconnect()
target.value && observer.value.unobserve(target.value)
}
}
})
return {
observer,
stop: () => {
cleanup()
stopEffect()
},
}
}
......@@ -43,10 +43,6 @@ export function useRootSetting() {
() => appStore.getProjectConfig.showSettingButton,
)
const getUseErrorHandle = computed(
() => appStore.getProjectConfig.useErrorHandle,
)
const getShowFooter = computed(() => appStore.getProjectConfig.showFooter)
const getShowBreadCrumb = computed(
......@@ -99,7 +95,6 @@ export function useRootSetting() {
getCanEmbedIFramePage,
getPermissionMode,
getShowLogo,
getUseErrorHandle,
getShowBreadCrumb,
getShowBreadCrumbIcon,
getUseOpenBackTop,
......
......@@ -8,8 +8,4 @@ export const LayoutBreadcrumb = createAsyncComponent(
export const Notify = createAsyncComponent(() => import('./notify/index.vue'))
export const ErrorAction = createAsyncComponent(
() => import('./ErrorAction.vue'),
)
export { FullScreen, UserDropDown }
......@@ -42,11 +42,6 @@
<div :class="`${prefixCls}-action`">
<AppSearch :class="`${prefixCls}-action__item `" v-if="getShowSearch" />
<ErrorAction
v-if="getUseErrorHandle"
:class="`${prefixCls}-action__item error-action`"
/>
<Notify
v-if="getShowNotice"
:class="`${prefixCls}-action__item notify-item`"
......@@ -97,7 +92,6 @@ import {
LayoutBreadcrumb,
FullScreen,
Notify,
ErrorAction,
} from './components'
import { useAppInject } from '@/hooks/web/useAppInject'
import { useDesign } from '@/hooks/web/useDesign'
......@@ -118,7 +112,6 @@ export default defineComponent({
FullScreen,
Notify,
AppSearch,
ErrorAction,
SettingDrawer,
},
props: {
......@@ -134,11 +127,7 @@ export default defineComponent({
getMenuWidth,
getIsMixSidebar,
} = useMenuSetting()
const {
getUseErrorHandle,
getShowSettingButton,
getSettingButtonPosition,
} = useRootSetting()
const { getShowSettingButton, getSettingButtonPosition } = useRootSetting()
const {
getHeaderTheme,
......@@ -211,7 +200,6 @@ export default defineComponent({
showLocalePicker,
getShowFullScreen,
getShowNotice,
getUseErrorHandle,
getLogoWidth,
getIsMixSidebar,
getShowSettingButton,
......
<script lang="ts" setup>
import { unref, computed } from 'vue'
import FramePage from '@/views/sys/iframe/index.vue'
import { useFrameKeepAlive } from './useFrameKeepAlive'
const { getFramePages, hasRenderFrame, showIframe } = useFrameKeepAlive()
const showFrame = computed(() => unref(getFramePages).length > 0)
</script>
<template>
<div v-if="showFrame">
<template v-for="frame in getFramePages" :key="frame.path">
<FramePage
<frame-page
v-if="frame.meta.frameSrc && hasRenderFrame(frame.name)"
v-show="showIframe(frame)"
:frameSrc="frame.meta.frameSrc"
......@@ -9,21 +19,3 @@
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, unref, computed } from 'vue'
import FramePage from '@/views/sys/iframe/index.vue'
import { useFrameKeepAlive } from './useFrameKeepAlive'
export default defineComponent({
name: 'FrameLayout',
components: { FramePage },
setup() {
const { getFramePages, hasRenderFrame, showIframe } = useFrameKeepAlive()
const showFrame = computed(() => unref(getFramePages).length > 0)
return { getFramePages, hasRenderFrame, showIframe, showFrame }
},
})
</script>
<template>
<RouterView>
<template #default="{ Component, route }">
<transition name="fade-slide" mode="out-in" appear>
<keep-alive v-if="openCache" :include="getCaches">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
</RouterView>
<FrameLayout v-if="getCanEmbedIFramePage" />
</template>
<script lang="ts">
import { computed, defineComponent, unref } from 'vue'
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'
import { useMultipleTabStore } from '@/store/multipleTab'
import FrameLayout from '@/layouts/iframe/index.vue'
export default defineComponent({
name: 'PageLayout',
components: { FrameLayout },
setup() {
const { getShowMultipleTab } = useMultipleTabSetting()
const tabStore = useMultipleTabStore()
const { getShowMultipleTab } = useMultipleTabSetting()
const tabStore = useMultipleTabStore()
const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting()
const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting()
const openCache = computed(
() => unref(getOpenKeepAlive) && unref(getShowMultipleTab),
)
const openCache = computed(
() => unref(getOpenKeepAlive) && unref(getShowMultipleTab),
)
const getCaches = computed((): string[] => {
if (!unref(getOpenKeepAlive)) {
return []
}
return tabStore.getCachedTabList
})
return {
openCache,
getCaches,
getCanEmbedIFramePage,
}
},
const getCaches = computed((): string[] => {
if (!unref(getOpenKeepAlive)) {
return []
}
return tabStore.getCachedTabList
})
</script>
<template>
<router-view>
<template #default="{ Component, route }">
<transition name="fade-slide" mode="out-in" appear>
<keep-alive v-if="openCache" :include="getCaches">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
</router-view>
<frame-layout v-if="getCanEmbedIFramePage" />
</template>
/**
* Used to configure the global error handling function, which can monitor vue errors, script errors, static resource errors and Promise errors
*/
import type { ErrorLogInfo } from '@admin/types'
import type { App } from 'vue'
import { useErrorLogStoreWithOut } from '@/store/errorLog'
import { ErrorTypeEnum } from '@admin/tokens'
import { projectSetting } from '@admin/setting'
/**
* Handling error stack information
* @param error
*/
function processStackMsg(error: Error) {
if (!error.stack) {
return ''
}
let stack = error.stack
.replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content
.replace(/\bat\b/gi, '@') // At in chrome, @ in ff
.split('@') // Split information with @
.slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10
.map((v) => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces
.join('~') // Manually add separators for later display
.replace(/\?[^:]+/gi, '') // Remove redundant parameters of js file links (?x=1 and the like)
const msg = error.toString()
if (stack.indexOf(msg) < 0) {
stack = msg + '@' + stack
}
return stack
}
/**
* get comp name
* @param vm
*/
function formatComponentName(vm: any) {
if (vm.$root === vm) {
return {
name: 'root',
path: 'root',
}
}
const options = vm.$options as any
if (!options) {
return {
name: 'anonymous',
path: 'anonymous',
}
}
const name = options.name || options._componentTag
return {
name: name,
path: options.__file,
}
}
/**
* Configure Vue error handling function
*/
function vueErrorHandler(err: Error, vm: any, info: string) {
const errorLogStore = useErrorLogStoreWithOut()
const { name, path } = formatComponentName(vm)
errorLogStore.addErrorLogInfo({
type: ErrorTypeEnum.VUE,
name,
file: path,
message: err.message,
stack: processStackMsg(err),
detail: info,
url: window.location.href,
})
}
/**
* Configure script error handling function
*/
export function scriptErrorHandler(
event: Event | string,
source?: string,
lineno?: number,
colno?: number,
error?: Error,
) {
if (event === 'Script error.' && !source) {
return false
}
const errorInfo: Partial<ErrorLogInfo> = {}
colno = colno || (window.event && (window.event as any).errorCharacter) || 0
errorInfo.message = event as string
if (error?.stack) {
errorInfo.stack = error.stack
} else {
errorInfo.stack = ''
}
const name = source ? source.substr(source.lastIndexOf('/') + 1) : 'script'
const errorLogStore = useErrorLogStoreWithOut()
errorLogStore.addErrorLogInfo({
type: ErrorTypeEnum.SCRIPT,
name: name,
file: source as string,
detail: 'lineno' + lineno,
url: window.location.href,
...(errorInfo as Pick<ErrorLogInfo, 'message' | 'stack'>),
})
return true
}
/**
* Configure Promise error handling function
*/
function registerPromiseErrorHandler() {
window.addEventListener(
'unhandledrejection',
function (event) {
const errorLogStore = useErrorLogStoreWithOut()
errorLogStore.addErrorLogInfo({
type: ErrorTypeEnum.PROMISE,
name: 'Promise Error!',
file: 'none',
detail: 'promise error!',
url: window.location.href,
stack: 'promise error!',
message: event.reason,
})
},
true,
)
}
/**
* Configure monitoring resource loading error handling function
*/
function registerResourceErrorHandler() {
// Monitoring resource loading error(img,script,css,and jsonp)
window.addEventListener(
'error',
function (e: Event) {
const target = e.target ? e.target : (e.srcElement as any)
const errorLogStore = useErrorLogStoreWithOut()
errorLogStore.addErrorLogInfo({
type: ErrorTypeEnum.RESOURCE,
name: 'Resource Error!',
file: (e.target || ({} as any)).currentSrc,
detail: JSON.stringify({
tagName: target.localName,
html: target.outerHTML,
type: e.type,
}),
url: window.location.href,
stack: 'resource is not found',
message: (e.target || ({} as any)).localName + ' is load error',
})
},
true,
)
}
/**
* Configure global error handling
* @param app
*/
export function setupErrorHandle(app: App) {
const { useErrorHandle } = projectSetting
if (!useErrorHandle) {
return
}
// Vue exception monitoring;
app.config.errorHandler = vueErrorHandler
// script error
window.onerror = scriptErrorHandler
// promise exception
registerPromiseErrorHandler()
// Static resource exception
registerResourceErrorHandler()
}
import 'ant-design-vue/dist/antd.less'
import './tailwind.css'
import '@/styles/index.less'
// Register icon sprite
import 'virtual:svg-icons-register'
......@@ -8,13 +7,10 @@ import App from './App.vue'
import { initAdminModules } from './initAdminModules'
import { createApp } from 'vue'
import { initAppConfigStore } from '@/logics/initAppConfig'
import { setupErrorHandle } from '@/logics/error-handle'
import { router, setupRouter } from '@/router'
import { setupRouterGuard } from '@/router/guard'
import { pinia, registerGlobalComponents } from '@/internal'
import { setupI18n } from '@admin/locale'
import { namespace } from '@admin/setting'
import { createBEMPlugin } from '@admin/utils/bem'
import { registerGlobalDirective } from '@admin/directives'
const bootstrap = async () => {
......@@ -26,8 +22,6 @@ const bootstrap = async () => {
// ! 需要注意调用时机
await initAdminModules()
app.use(createBEMPlugin(namespace))
// Initialize internal system configuration
initAppConfigStore()
......@@ -49,9 +43,6 @@ const bootstrap = async () => {
// Register global directive
registerGlobalDirective(app)
// Configure global error handling
setupErrorHandle(app)
await router.isReady()
app.mount('#app')
......
......@@ -8,7 +8,5 @@ export const LAYOUT = () => import('@/layouts/default/index.vue')
*/
export const getParentLayout = () => () =>
new Promise((resolve) => {
resolve({
name: 'ParentLayout',
})
resolve({ name: 'ParentLayout' })
})
import type { App } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import { basicRoutes } from './routes'
// 白名单应该包含基本静态路由
const WHITE_NAME_LIST: string[] = []
const getRouteNames = (array: any[]) =>
array.forEach((item) => {
WHITE_NAME_LIST.push(item.name)
getRouteNames(item.children || [])
})
;(() => {
const getRouteNames = (array: any[]) =>
array.forEach((item) => {
WHITE_NAME_LIST.push(item.name)
getRouteNames(item.children || [])
})
getRouteNames(basicRoutes)
getRouteNames(basicRoutes)
})()
// app router
export const router = createRouter({
......
import { t } from '@admin/locale'
import { REDIRECT_NAME, PAGE_NOT_FOUND_NAME } from '@admin/tokens'
import { LAYOUT } from '@/router/constant'
......@@ -50,27 +49,3 @@ export const REDIRECT_ROUTE: RouteRecordItem = {
},
],
}
export const ERROR_LOG_ROUTE: RouteRecordItem = {
path: '/error-log',
name: 'ErrorLog',
component: LAYOUT,
redirect: '/error-log/list',
meta: {
title: 'ErrorLog',
hideBreadcrumb: true,
hideChildrenInMenu: true,
},
children: [
{
path: 'list',
name: 'ErrorLogList',
component: () => import('@/views/sys/error-log/index.vue'),
meta: {
title: t('routes.basic.errorLogList'),
hideBreadcrumb: true,
currentActiveMenu: '/error-log',
},
},
],
}
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'
import { mainOutRoutes } from './mainOut'
import { PageEnum } from '@admin/tokens'
import { t } from '@admin/locale'
const modules = import.meta.globEager('./modules/**/*.ts')
const routeModuleRecord = import.meta.globEager('./modules/**/*.ts')
const routeModuleList: RouteRecordItem[] = []
const routeModules: RouteRecordItem[] = []
Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {}
const modList = Array.isArray(mod) ? [...mod] : [mod]
routeModuleList.push(...modList)
Object.keys(routeModuleRecord).forEach((key) => {
const routeModule = routeModuleRecord[key].default || {}
routeModules.push(
...(Array.isArray(routeModule) ? [...routeModule] : [routeModule]),
)
})
export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]
export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModules]
export const RootRoute: RouteRecordItem = {
path: '/',
......
......@@ -2,24 +2,26 @@
The routing of this file will not show the layout.
It is an independent new page.
the contents of the file still need to log in to access
Test with annotations turned on
这个文件的路由不会显示布局。
这是一个独立的新页面。
文件内容仍需登录才能访问
打开注释后进行测试
*/
// test
// http:ip:port/main-out
export const mainOutRoutes: RouteRecordItem[] = [
{
path: '/main-out',
name: 'MainOut',
component: () => import('@/views/demo/main-out/index.vue'),
meta: {
title: 'MainOut',
ignoreAuth: true,
},
},
// {
// path: '/main-out',
// name: 'MainOut',
// component: () => import('@/views/demo/main-out/index.vue'),
// meta: {
// title: 'MainOut',
// ignoreAuth: true,
// },
// },
]
export const mainOutRouteNames = mainOutRoutes.map((item) => item.name)
......@@ -411,14 +411,6 @@ const comp: RouteRecordItem = {
title: t('routes.demo.comp.scrollAction'),
},
},
{
path: 'virtualScroll',
name: 'VirtualScrollDemo',
component: () => import('@/views/demo/comp/scroll/VirtualScroll.vue'),
meta: {
title: t('routes.demo.comp.virtualScroll'),
},
},
],
},
......@@ -447,60 +439,6 @@ const comp: RouteRecordItem = {
},
},
{
path: 'lazy',
name: 'LazyDemo',
component: getParentLayout(),
redirect: '/comp/lazy/basic',
meta: {
title: t('routes.demo.comp.lazy'),
},
children: [
{
path: 'basic',
name: 'BasicLazyDemo',
component: () => import('@/views/demo/comp/lazy/index.vue'),
meta: {
title: t('routes.demo.comp.lazyBasic'),
},
},
{
path: 'transition',
name: 'BasicTransitionDemo',
component: () => import('@/views/demo/comp/lazy/Transition.vue'),
meta: {
title: t('routes.demo.comp.lazyTransition'),
},
},
],
},
{
path: 'verify',
name: 'VerifyDemo',
component: getParentLayout(),
redirect: '/comp/verify/drag',
meta: {
title: t('routes.demo.comp.verify'),
},
children: [
{
path: 'drag',
name: 'VerifyDragDemo',
component: () => import('@/views/demo/comp/verify/index.vue'),
meta: {
title: t('routes.demo.comp.verifyDrag'),
},
},
{
path: 'rotate',
name: 'VerifyRotateDemo',
component: () => import('@/views/demo/comp/verify/Rotate.vue'),
meta: {
title: t('routes.demo.comp.verifyRotate'),
},
},
],
},
//
{
......
......@@ -140,14 +140,7 @@ const feat: RouteRecordItem = {
title: t('routes.demo.feat.download'),
},
},
{
path: 'click-out-side',
name: 'ClickOutSideDemo',
component: () => import('@/views/demo/feat/click-out-side/index.vue'),
meta: {
title: t('routes.demo.feat.clickOutSide'),
},
},
{
path: 'img-preview',
name: 'ImgPreview',
......@@ -180,14 +173,7 @@ const feat: RouteRecordItem = {
title: t('routes.demo.feat.watermark'),
},
},
{
path: 'ripple',
name: 'RippleDemo',
component: () => import('@/views/demo/feat/ripple/index.vue'),
meta: {
title: t('routes.demo.feat.ripple'),
},
},
{
path: 'full-screen',
name: 'FullScreenDemo',
......@@ -196,14 +182,6 @@ const feat: RouteRecordItem = {
title: t('routes.demo.feat.fullScreen'),
},
},
{
path: '/error-log',
name: 'ErrorLog',
component: () => import('@/views/sys/error-log/index.vue'),
meta: {
title: t('routes.demo.feat.errorLog'),
},
},
{
path: 'excel',
name: 'Excel',
......
......@@ -2,7 +2,6 @@ import type {
ProjectConfig,
HeaderSetting,
MenuSetting,
TransitionSetting,
MultiTabsSetting,
BeforeMiniState,
} from '@admin/types'
......@@ -63,10 +62,6 @@ export const useAppStore = defineStore({
return this.getProjectConfig.menuSetting
},
getTransitionSetting(): TransitionSetting {
return this.getProjectConfig.transitionSetting
},
getMultiTabsSetting(): MultiTabsSetting {
return this.getProjectConfig.multiTabsSetting
},
......
......@@ -14,7 +14,7 @@ import { transformRouteToMenu } from '@/router/helper/menuHelper'
import { projectSetting } from '@admin/setting'
import { PermissionModeEnum, PageEnum } from '@admin/tokens'
import { asyncRoutes } from '@/router/routes'
import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
import { filterTree } from '@admin/utils'
import { getMenuList } from '@service/sys/menu'
import { getPermCode } from '@service/sys/user'
......@@ -201,7 +201,6 @@ export const usePermissionStore = defineStore({
break
}
routes.push(ERROR_LOG_ROUTE)
patchHomeAffix(routes)
return routes
},
......
......@@ -44,10 +44,6 @@
.ant-table {
.ant-table-content {
.ant-table-scroll {
.ant-table-hide-scrollbar {
//overflow-x: auto !important;
}
.ant-table-body {
overflow: auto !important;
}
......
......@@ -3,6 +3,7 @@
@import 'public.less';
@import 'ant/index.less';
@import './theme.less';
@import './tailwind.css';
input:-webkit-autofill {
box-shadow: 0 0 0 1000px white inset !important;
......
<template>
<Card hoverable :style="{ width: '240px', background: '#fff' }">
<template #cover>
<img
alt="example"
src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png"
/>
</template>
<CardMeta title="懒加载组件" />
</Card>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { Card } from 'ant-design-vue'
export default defineComponent({
components: { CardMeta: Card.Meta, Card },
setup() {
return {}
},
})
</script>
<template>
<PageWrapper title="懒加载自定义动画示例" content="懒加载组件显示动画">
<div class="lazy-base-demo-wrap">
<h1>向下滚动</h1>
<div class="lazy-base-demo-box">
<LazyContainer transitionName="custom">
<TargetContent />
</LazyContainer>
</div>
</div>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import TargetContent from './TargetContent.vue'
import { LazyContainer } from '@/components/Container/index'
import { PageWrapper } from '@/components/Page'
export default defineComponent({
components: { LazyContainer, TargetContent, PageWrapper },
})
</script>
<style lang="less">
.lazy-base-demo {
&-wrap {
display: flex;
width: 50%;
height: 2000px;
margin: 20px auto;
text-align: center;
background-color: @component-background;
justify-content: center;
flex-direction: column;
align-items: center;
}
&-box {
width: 300px;
height: 300px;
}
h1 {
height: 1300px;
margin: 20px 0;
}
}
.custom-enter {
opacity: 0%;
transform: scale(0.4) translate(100%);
}
.custom-enter-to {
opacity: 100%;
}
.custom-enter-active {
position: absolute;
top: 0;
width: 100%;
transition: all 0.5s;
}
.custom-leave {
opacity: 100%;
}
.custom-leave-to {
opacity: 0%;
transform: scale(0.4) translate(-100%);
}
.custom-leave-active {
transition: all 0.5s;
}
</style>
<template>
<PageWrapper title="懒加载基础示例" content="向下滚动到可见区域才会加载组件">
<div class="lazy-base-demo-wrap">
<h1>向下滚动</h1>
<div class="lazy-base-demo-box">
<LazyContainer>
<TargetContent />
<template #skeleton>
<Skeleton :rows="10" />
</template>
</LazyContainer>
</div>
</div>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { Skeleton } from 'ant-design-vue'
import TargetContent from './TargetContent.vue'
import { LazyContainer } from '@/components/Container/index'
import { PageWrapper } from '@/components/Page'
export default defineComponent({
components: { LazyContainer, PageWrapper, TargetContent, Skeleton },
})
</script>
<style lang="less">
.lazy-base-demo {
&-wrap {
display: flex;
width: 50%;
height: 2000px;
margin: 20px auto;
text-align: center;
background-color: @component-background;
justify-content: center;
flex-direction: column;
align-items: center;
}
&-box {
width: 300px;
height: 300px;
}
h1 {
height: 1300px;
margin: 20px 0;
}
}
</style>
<template>
<PageWrapper class="virtual-scroll-demo">
<Divider>基础滚动示例</Divider>
<div class="virtual-scroll-demo-wrap">
<VScroll :itemHeight="41" :items="data" :height="300" :width="300">
<template #default="{ item }">
<div class="virtual-scroll-demo__item">
{{ item.title }}
</div>
</template>
</VScroll>
</div>
<Divider>即使不可见,也预先加载50条数据,防止空白</Divider>
<div class="virtual-scroll-demo-wrap">
<VScroll
:itemHeight="41"
:items="data"
:height="300"
:width="300"
:bench="50"
>
<template #default="{ item }">
<div class="virtual-scroll-demo__item">
{{ item.title }}
</div>
</template>
</VScroll>
</div>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { VScroll } from '@/components/VirtualScroll/index'
import { Divider } from 'ant-design-vue'
import { PageWrapper } from '@/components/Page'
const data: Recordable[] = (() => {
const arr: Recordable[] = []
for (let index = 1; index < 20000; index++) {
arr.push({
title: '列表项' + index,
})
}
return arr
})()
export default defineComponent({
components: { VScroll: VScroll, Divider, PageWrapper },
setup() {
return { data: data }
},
})
</script>
<style lang="less" scoped>
.virtual-scroll-demo {
&-wrap {
display: flex;
margin: 0 30%;
background-color: @component-background;
justify-content: center;
}
&__item {
height: 40px;
padding: 0 20px;
line-height: 40px;
border-bottom: 1px solid @border-color-base;
}
}
</style>
<template>
<PageWrapper title="旋转校验示例">
<div class="flex justify-center p-4 items-center bg-gray-700">
<RotateDragVerify :src="img" ref="el" @success="handleSuccess" />
</div>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { RotateDragVerify } from '@/components/Verify/index'
import img from '@/assets/images/header.jpg'
import { PageWrapper } from '@/components/Page'
export default defineComponent({
components: { RotateDragVerify, PageWrapper },
setup() {
const handleSuccess = () => {
console.log('success!')
}
return {
handleSuccess,
img,
}
},
})
</script>
<style lang="less" scoped>
.bg-gray-700 {
background-color: #4a5568;
}
</style>
<template>
<PageWrapper title="拖动校验示例">
<div class="flex justify-center p-4 items-center bg-gray-700">
<BasicDragVerify ref="el1" @success="handleSuccess" />
<a-button type="primary" class="ml-2" @click="handleBtnClick(el1)">
还原
</a-button>
</div>
<div class="flex justify-center p-4 items-center bg-gray-700">
<BasicDragVerify ref="el2" @success="handleSuccess" circle />
<a-button type="primary" class="ml-2" @click="handleBtnClick(el2)">
还原
</a-button>
</div>
<div class="flex justify-center p-4 items-center bg-gray-700">
<BasicDragVerify
ref="el3"
@success="handleSuccess"
text="拖动以进行校验"
successText="校验成功"
:barStyle="{
backgroundColor: '#018ffb',
}"
/>
<a-button type="primary" class="ml-2" @click="handleBtnClick(el3)">
还原
</a-button>
</div>
<div class="flex justify-center p-4 items-center bg-gray-700">
<BasicDragVerify ref="el4" @success="handleSuccess">
<template #actionIcon="isPassing">
<BugOutlined v-if="isPassing" />
<RightOutlined v-else />
</template>
</BasicDragVerify>
<a-button type="primary" class="ml-2" @click="handleBtnClick(el4)">
还原
</a-button>
</div>
<div class="flex justify-center p-4 items-center bg-gray-700">
<BasicDragVerify ref="el5" @success="handleSuccess">
<template #text="isPassing">
<div v-if="isPassing">
<BugOutlined />
成功
</div>
<div v-else>
拖动
<RightOutlined />
</div>
</template>
</BasicDragVerify>
<a-button type="primary" class="ml-2" @click="handleBtnClick(el5)">
还原
</a-button>
</div>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import {
BasicDragVerify,
DragVerifyActionType,
PassingData,
} from '@/components/Verify/index'
import { useMessage } from '@/hooks/web/useMessage'
import { BugOutlined, RightOutlined } from '@ant-design/icons-vue'
import { PageWrapper } from '@/components/Page'
export default defineComponent({
components: { BasicDragVerify, BugOutlined, RightOutlined, PageWrapper },
setup() {
const { createMessage } = useMessage()
const el1 = ref<Nullable<DragVerifyActionType>>(null)
const el2 = ref<Nullable<DragVerifyActionType>>(null)
const el3 = ref<Nullable<DragVerifyActionType>>(null)
const el4 = ref<Nullable<DragVerifyActionType>>(null)
const el5 = ref<Nullable<DragVerifyActionType>>(null)
function handleSuccess(data: PassingData) {
const { time } = data
createMessage.success(`校验成功,耗时${time}秒`)
}
function handleBtnClick(elRef: Nullable<DragVerifyActionType>) {
if (!elRef) {
return
}
elRef.resume()
}
return {
handleSuccess,
el1,
el2,
el3,
el4,
el5,
handleBtnClick,
}
},
})
</script>
<style lang="less" scoped>
.bg-gray-700 {
background-color: #4a5568;
}
</style>
<template>
<PageWrapper title="点内外部触发事件">
<ClickOutSide
@clickOutside="handleClickOutside"
class="flex justify-center"
>
<div @click="innerClick" class="demo-box">
{{ text }}
</div>
</ClickOutSide>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { ClickOutSide } from '@/components/ClickOutSide'
import { PageWrapper } from '@/components/Page'
export default defineComponent({
components: { ClickOutSide, PageWrapper },
setup() {
const text = ref('Click')
function handleClickOutside() {
text.value = 'Click Out Side'
}
function innerClick() {
text.value = 'Click Inner'
}
return { innerClick, handleClickOutside, text }
},
})
</script>
<style lang="less" scoped>
.demo-box {
display: flex;
width: 100%;
height: 300px;
font-size: 24px;
color: #fff;
background-color: #408ede;
border-radius: 10px;
justify-content: center;
align-items: center;
}
</style>
<template>
<PageWrapper title="Ripple示例">
<div class="demo-box" v-ripple>content</div>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { ripple } from '@admin/directives'
import { PageWrapper } from '@/components/Page'
export default defineComponent({
components: { PageWrapper },
directives: {
ripple: ripple,
},
})
</script>
<style lang="less" scoped>
.demo-box {
display: flex;
width: 300px;
height: 300px;
font-size: 24px;
color: #fff;
background-color: #408ede;
border-radius: 10px;
justify-content: center;
align-items: center;
}
</style>
<template>
<BasicModal
:width="800"
:title="t('sys.errorLog.tableActionDesc')"
v-bind="$attrs"
>
<Description :data="info" @register="register" />
</BasicModal>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type { ErrorLogInfo } from '@admin/types'
import { BasicModal } from '@/components/Modal/index'
import { Description, useDescription } from '@/components/Description/index'
import { useI18n } from '@admin/locale'
import { getDescSchema } from './data'
defineProps({
info: {
type: Object as PropType<ErrorLogInfo>,
default: null,
},
})
const { t } = useI18n()
const [register] = useDescription({
column: 2,
schema: getDescSchema()!,
})
</script>
import { Tag } from 'ant-design-vue'
import { BasicColumn } from '@/components/Table/index'
import { ErrorTypeEnum } from '@admin/tokens'
import { useI18n } from '@admin/locale'
const { t } = useI18n()
export function getColumns(): BasicColumn[] {
return [
{
dataIndex: 'type',
title: t('sys.errorLog.tableColumnType'),
width: 80,
customRender: ({ text }) => {
const color =
text === ErrorTypeEnum.VUE
? 'green'
: text === ErrorTypeEnum.RESOURCE
? 'cyan'
: text === ErrorTypeEnum.PROMISE
? 'blue'
: ErrorTypeEnum.AJAX
? 'red'
: 'purple'
return <Tag color={color}>{() => text}</Tag>
},
},
{
dataIndex: 'url',
title: 'URL',
width: 200,
},
{
dataIndex: 'time',
title: t('sys.errorLog.tableColumnDate'),
width: 160,
},
{
dataIndex: 'file',
title: t('sys.errorLog.tableColumnFile'),
width: 200,
},
{
dataIndex: 'name',
title: 'Name',
width: 200,
},
{
dataIndex: 'message',
title: t('sys.errorLog.tableColumnMsg'),
width: 300,
},
{
dataIndex: 'stack',
title: t('sys.errorLog.tableColumnStackMsg'),
},
]
}
export function getDescSchema(): any {
return getColumns().map((column) => {
return {
field: column.dataIndex!,
label: column.title,
}
})
}
<template>
<div class="p-4">
<template v-for="src in imgList" :key="src">
<img :src="src" v-show="false" />
</template>
<DetailModal :info="rowInfo" @register="registerModal" />
<BasicTable @register="register" class="error-handle-table">
<template #toolbar>
<a-button @click="fireVueError" type="primary">
{{ t('sys.errorLog.fireVueError') }}
</a-button>
<a-button @click="fireResourceError" type="primary">
{{ t('sys.errorLog.fireResourceError') }}
</a-button>
<a-button @click="fireAjaxError" type="primary">
{{ t('sys.errorLog.fireAjaxError') }}
</a-button>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{
label: t('sys.errorLog.tableActionDesc'),
onClick: handleDetail.bind(null, record),
},
]"
/>
</template>
</BasicTable>
</div>
</template>
<script lang="ts" setup>
import type { ErrorLogInfo } from '@admin/types'
import { watch, ref, nextTick } from 'vue'
import DetailModal from './DetailModal.vue'
import { BasicTable, useTable, TableAction } from '@/components/Table/index'
import { useModal } from '@/components/Modal'
import { useMessage } from '@/hooks/web/useMessage'
import { useI18n } from '@admin/locale'
import { useErrorLogStore } from '@/store/errorLog'
import { fireErrorApi } from '@service/demo/error'
import { getColumns } from './data'
import { cloneDeep } from '@admin/utils'
const rowInfo = ref<ErrorLogInfo>()
const imgList = ref<string[]>([])
const { t } = useI18n()
const errorLogStore = useErrorLogStore()
const [register, { setTableData }] = useTable({
title: t('sys.errorLog.tableTitle'),
columns: getColumns(),
actionColumn: {
width: 80,
title: 'Action',
dataIndex: 'action',
slots: { customRender: 'action' },
},
})
const [registerModal, { openModal }] = useModal()
watch(
() => errorLogStore.getErrorLogInfoList,
(list) => {
nextTick(() => {
setTableData(cloneDeep(list))
})
},
{
immediate: true,
},
)
const { createMessage } = useMessage()
if (import.meta.env.DEV) {
createMessage.info(t('sys.errorLog.enableMessage'))
}
// 查看详情
function handleDetail(row: ErrorLogInfo) {
rowInfo.value = row
openModal(true)
}
function fireVueError() {
throw new Error('fire vue error!')
}
function fireResourceError() {
imgList.value.push(`${new Date().getTime()}.png`)
}
async function fireAjaxError() {
await fireErrorApi()
}
</script>
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'FrameBlank',
})
</script>
<template>
<div
:class="prefixCls"
class="fixed inset-0 flex h-screen w-screen bg-black items-center justify-center"
class="fixed inset-0 flex items-center justify-center w-screen h-screen bg-black"
>
<div
:class="`${prefixCls}__unlock`"
class="absolute top-0 left-1/2 flex pt-5 h-16 items-center justify-center sm:text-md xl:text-xl text-white flex-col cursor-pointer transform translate-x-1/2"
class="absolute top-0 flex flex-col items-center justify-center h-16 pt-5 text-white transform translate-x-1/2 cursor-pointer left-1/2 sm:text-md xl:text-xl"
@click="handleShowForm(false)"
v-show="showDate"
>
......@@ -13,14 +13,14 @@
<span>{{ t('sys.lock.unlock') }}</span>
</div>
<div class="flex w-screen h-screen justify-center items-center">
<div class="flex items-center justify-center w-screen h-screen">
<div
:class="`${prefixCls}__hour`"
class="relative mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5"
class="relative w-2/5 mr-5 md:mr-20 h-2/5 md:h-4/5"
>
<span>{{ hour }}</span>
<span
class="meridiem absolute left-5 top-5 text-md xl:text-xl"
class="absolute meridiem left-5 top-5 text-md xl:text-xl"
v-show="showDate"
>
{{ meridiem }}
......@@ -84,9 +84,9 @@
</transition>
<div
class="absolute bottom-5 w-full text-gray-300 xl:text-xl 2xl:text-3xl text-center enter-y"
class="absolute w-full text-center text-gray-300 bottom-5 xl:text-xl 2xl:text-3xl enter-y"
>
<div class="text-5xl mb-4 enter-x" v-show="!showDate">
<div class="mb-4 text-5xl enter-x" v-show="!showDate">
{{ hour }}:{{ minute }} <span class="text-3xl">{{ meridiem }}</span>
</div>
<div class="text-2xl">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>
......
<template>
<transition name="fade-bottom" mode="out-in">
<LockPage v-if="getIsLock" />
<lock-page v-if="getIsLock" />
</transition>
</template>
<script lang="ts" setup>
......
......@@ -31,6 +31,7 @@ const title = computed(() => globSetting?.title ?? '')
:show-text="false"
v-if="!sessionTimeout && showLocalePicker"
/>
<AppDarkModeToggle
class="absolute top-3 right-7 enter-x"
v-if="!sessionTimeout"
......@@ -55,9 +56,7 @@ const title = computed(() => globSetting?.title ?? '')
{{ t('sys.login.signInTitle') }}</span
>
</div>
<div
class="mt-5 font-normal text-white text-md dark:text-gray-500 -enter-x"
>
<div class="mt-5 font-normal text-white text-md -enter-x">
{{ t('sys.login.signInDesc') }}
</div>
</div>
......
......@@ -3,7 +3,6 @@ import type { App } from 'vue'
import { loading } from './src/loading'
import { auth } from './src/auth'
export { ripple } from './src/ripple'
export { clickOutside } from './src/clickOutside'
export { auth, loading }
......
......@@ -3,7 +3,6 @@ import type {
DirectiveBinding,
ObjectDirective,
} from 'vue'
import { on, isClient } from '@admin/utils'
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void
......
.ripple-container {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
overflow: hidden;
pointer-events: none;
}
.ripple-effect {
position: relative;
z-index: 9999;
width: 1px;
height: 1px;
margin-top: 0;
margin-left: 0;
pointer-events: none;
border-radius: 50%;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
import type { Directive } from 'vue'
import './index.css'
export interface RippleOptions {
event: string
transition: number
}
export interface RippleProto {
background?: string
zIndex?: string
}
export type EventType = Event & MouseEvent & TouchEvent
const options: RippleOptions = {
event: 'mousedown',
transition: 400,
}
const ripple: Directive & RippleProto = {
beforeMount: (el: HTMLElement, binding) => {
if (binding.value === false) return
const bg = el.getAttribute('ripple-background')
setProps(Object.keys(binding.modifiers), options)
const background = bg || ripple.background
const zIndex = ripple.zIndex
el.addEventListener(options.event, (event: EventType) => {
rippler({
event,
el,
background,
zIndex,
})
})
},
updated(el, binding) {
if (!binding.value) {
el?.clearRipple?.()
return
}
const bg = el.getAttribute('ripple-background')
el?.setBackground?.(bg)
},
}
const rippler = ({
event,
el,
zIndex,
background,
}: { event: EventType; el: HTMLElement } & RippleProto) => {
const targetBorder = parseInt(
getComputedStyle(el).borderWidth.replace('px', ''),
)
const clientX = event.clientX || event.touches[0].clientX
const clientY = event.clientY || event.touches[0].clientY
const rect = el.getBoundingClientRect()
const { left, top } = rect
const { offsetWidth: width, offsetHeight: height } = el
const { transition } = options
const dx = clientX - left
const dy = clientY - top
const maxX = Math.max(dx, width - dx)
const maxY = Math.max(dy, height - dy)
const style = window.getComputedStyle(el)
const radius = Math.sqrt(maxX * maxX + maxY * maxY)
const border = targetBorder > 0 ? targetBorder : 0
const ripple = document.createElement('div')
const rippleContainer = document.createElement('div')
// Styles for ripple
ripple.className = 'ripple'
Object.assign(ripple.style ?? {}, {
marginTop: '0px',
marginLeft: '0px',
width: '1px',
height: '1px',
transition: `all ${transition}ms cubic-bezier(0.4, 0, 0.2, 1)`,
borderRadius: '50%',
pointerEvents: 'none',
position: 'relative',
zIndex: zIndex ?? '9999',
backgroundColor: background ?? 'rgba(0, 0, 0, 0.12)',
})
// Styles for rippleContainer
rippleContainer.className = 'ripple-container'
Object.assign(rippleContainer.style ?? {}, {
position: 'absolute',
left: `${0 - border}px`,
top: `${0 - border}px`,
height: '0',
width: '0',
pointerEvents: 'none',
overflow: 'hidden',
})
const storedTargetPosition =
el.style.position.length > 0
? el.style.position
: getComputedStyle(el).position
if (storedTargetPosition !== 'relative') {
el.style.position = 'relative'
}
rippleContainer.appendChild(ripple)
el.appendChild(rippleContainer)
Object.assign(ripple.style, {
marginTop: `${dy}px`,
marginLeft: `${dx}px`,
})
const {
borderTopLeftRadius,
borderTopRightRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
} = style
Object.assign(rippleContainer.style, {
width: `${width}px`,
height: `${height}px`,
direction: 'ltr',
borderTopLeftRadius,
borderTopRightRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
})
setTimeout(() => {
const wh = `${radius * 2}px`
Object.assign(ripple.style ?? {}, {
width: wh,
height: wh,
marginLeft: `${dx - radius}px`,
marginTop: `${dy - radius}px`,
})
}, 0)
function clearRipple() {
setTimeout(() => {
ripple.style.backgroundColor = 'rgba(0, 0, 0, 0)'
}, 250)
setTimeout(() => {
rippleContainer?.parentNode?.removeChild(rippleContainer)
}, 850)
el.removeEventListener('mouseup', clearRipple, false)
el.removeEventListener('mouseleave', clearRipple, false)
el.removeEventListener('dragstart', clearRipple, false)
setTimeout(() => {
let clearPosition = true
for (let i = 0; i < el.childNodes.length; i++) {
if ((el.childNodes[i] as Recordable).className === 'ripple-container') {
clearPosition = false
}
}
if (clearPosition) {
el.style.position =
storedTargetPosition !== 'static' ? storedTargetPosition : ''
}
}, options.transition + 260)
}
if (event.type === 'mousedown') {
el.addEventListener('mouseup', clearRipple, false)
el.addEventListener('mouseleave', clearRipple, false)
el.addEventListener('dragstart', clearRipple, false)
} else {
clearRipple()
}
;(el as Recordable).setBackground = (bgColor: string) => {
if (!bgColor) {
return
}
ripple.style.backgroundColor = bgColor
}
}
const setProps = (modifiers: Recordable, props: Recordable) => {
modifiers.forEach((item: Recordable) => {
if (isNaN(Number(item))) props.event = item
else props.transition = item
})
}
export { ripple }
......@@ -8,14 +8,14 @@
"clean": "pnpm rimraf node_modules"
},
"optionalDependencies": {
"vue": "^3.2.27"
"vue": "^3.2.29"
},
"dependencies": {
"@admin/setting": "workspace:*",
"@admin/tokens": "workspace:*",
"@admin/types": "workspace:*",
"@admin/utils": "workspace:*",
"@vueuse/core": "^7.5.3",
"@vueuse/core": "^7.5.4",
"vue-i18n": "^9.1.9"
}
}
import { genMessage } from '../helper'
import antdLocale from 'ant-design-vue/es/locale/en_US'
import uiFrameLocale from 'ant-design-vue/es/locale/en_US'
const modules = import.meta.globEager('./en/**/*.ts')
export default {
message: {
...genMessage(modules, 'en'),
antdLocale,
uiFrameLocale,
},
dateLocale: null,
dateLocaleName: 'en',
......
export default {
login: 'Login',
errorLogList: 'Error Log',
}
......@@ -16,7 +16,6 @@ export default {
scroll: 'Scroll',
scrollBasic: 'Basic',
scrollAction: 'Scroll Function',
virtualScroll: 'Virtual Scroll',
tree: 'Tree',
......@@ -28,14 +27,6 @@ export default {
drawer: 'Drawer',
desc: 'Desc',
lazy: 'Lazy',
lazyBasic: 'Basic',
lazyTransition: 'Animation',
verify: 'Verify',
verifyDrag: 'Drag ',
verifyRotate: 'Picture Restore',
qrcode: 'QR code',
strength: 'Password strength',
upload: 'Upload',
......@@ -71,14 +62,11 @@ export default {
print: 'Print',
contextMenu: 'Context Menu',
download: 'Download',
clickOutSide: 'ClickOutSide',
imgPreview: 'Picture Preview',
copy: 'Clipboard',
msg: 'Message prompt',
watermark: 'Watermark',
ripple: 'Ripple',
fullScreen: 'Full Screen',
errorLog: 'Error Log',
tab: 'Tab with parameters',
tab1: 'Tab with parameter 1',
tab2: 'Tab with parameter 2',
......
export default {
login: '登录',
errorLogList: '错误日志列表',
}
......@@ -16,7 +16,6 @@ export default {
scroll: '滚动组件',
scrollBasic: '基础滚动',
scrollAction: '滚动函数',
virtualScroll: '虚拟滚动',
tree: 'Tree',
treeBasic: '基础树',
......@@ -27,14 +26,6 @@ export default {
drawer: '抽屉扩展',
desc: '详情组件',
lazy: '懒加载组件',
lazyBasic: '基础示例',
lazyTransition: '动画效果',
verify: '验证组件',
verifyDrag: '拖拽校验',
verifyRotate: '图片还原',
qrcode: '二维码组件',
strength: '密码强度组件',
upload: '上传组件',
......@@ -70,14 +61,11 @@ export default {
print: '打印',
contextMenu: '右键菜单',
download: '文件下载',
clickOutSide: 'ClickOutSide组件',
imgPreview: '图片预览',
copy: '剪切板',
msg: '消息提示',
watermark: '水印',
ripple: '水波纹',
fullScreen: '全屏',
errorLog: '错误日志',
tab: 'Tab带参',
tab1: 'Tab带参1',
tab2: 'Tab带参2',
......
import { genMessage } from '../helper'
import antdLocale from 'ant-design-vue/es/locale/zh_CN'
import uiFrameLocale from 'ant-design-vue/es/locale/zh_CN'
const modules = import.meta.globEager('./zh-CN/**/*.ts')
export default {
message: {
...genMessage(modules, 'zh-CN'),
antdLocale,
uiFrameLocale,
},
}
......@@ -21,8 +21,8 @@ function setI18nLanguage(locale: LocaleType) {
}
export const useLocale = () => {
const antdLocale = computed((): any => {
return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale ?? {}
const uiFrameLocale = computed((): any => {
return i18n.global.getLocaleMessage(unref(getLocale))?.uiFrameLocale ?? {}
})
// Switching the language will change the locale of useI18n
......@@ -60,6 +60,6 @@ export const useLocale = () => {
getLocale,
showLocalePicker,
changeLocale,
antdLocale,
uiFrameLocale,
}
}
......@@ -10,5 +10,8 @@
"dependencies": {
"@admin/types": "workspace:*",
"@admin/tokens": "workspace:*"
},
"optionalDependencies": {
"vue": "^3.2.29"
}
}
......@@ -152,9 +152,6 @@ export const projectSetting: ProjectConfig = {
// Whether to show the breadcrumb icon
showBreadCrumbIcon: false,
// Use error-handler-plugin
useErrorHandle: false,
// Whether to open back to top
useOpenBackTop: true,
......
......@@ -113,8 +113,6 @@ export interface ProjectConfig {
showBreadCrumb: boolean
// Show breadcrumb icon
showBreadCrumbIcon: boolean
// Use error-handler-plugin
useErrorHandle: boolean
// Whether to open back to top
useOpenBackTop: boolean
// Is it possible to embed iframe pages
......
......@@ -13,9 +13,9 @@
"dependencies": {
"@admin/locale": "workspace:*",
"@admin/utils": "workspace:*",
"@vueuse/core": "^7.5.3"
"@vueuse/core": "^7.5.4"
},
"optionalDependencies": {
"vue": "^3.2.27"
"vue": "^3.2.29"
}
}
import type { Plugin } from 'vue'
import { namespace } from '@admin/setting'
let namespace = ''
type Mod = string | { [key: string]: any }
type Mods = Mod | Mod[]
......@@ -54,9 +53,3 @@ export const createNamespace = (name: string) => {
const prefixedName = `${namespace}-${name}`
return [prefixedName, buildBEM(prefixedName)] as const
}
export const createBEMPlugin = (_namespace: string): Plugin => ({
install: () => {
namespace = _namespace
},
})
......@@ -11,10 +11,11 @@
"clean": "rimraf node_modules"
},
"dependencies": {
"@vue/runtime-core": "^3.2.27",
"@vue/shared": "^3.2.27",
"@vueuse/core": "^7.5.3",
"@vueuse/shared": "^7.5.3",
"@admin/setting": "workspace:*",
"@vue/runtime-core": "^3.2.29",
"@vue/shared": "^3.2.29",
"@vueuse/core": "^7.5.4",
"@vueuse/shared": "^7.5.4",
"dayjs": "^1.10.7",
"lodash-es": "^4.17.21"
},
......@@ -22,6 +23,6 @@
"@types/lodash-es": "^4.17.5"
},
"optionalDependencies": {
"vue": "^3.2.27"
"vue": "^3.2.29"
}
}
此差异已折叠。
......@@ -34,9 +34,9 @@
"debug": "4.3.3",
"dotenv": "14.2.0",
"express": "^4.17.2",
"express-rate-limit": "6.1.0",
"express-rate-limit": "6.2.0",
"fs-extra": "10.0.0",
"helmet": "5.0.1",
"helmet": "5.0.2",
"lodash": "4.17.21",
"markdown-it": "12.3.2",
"reflect-metadata": "^0.1.13",
......@@ -44,7 +44,7 @@
"rxjs": "^7.5.2",
"swagger-ui-express": "4.3.0",
"winston": "3.4.0",
"winston-daily-rotate-file": "4.5.5"
"winston-daily-rotate-file": "4.6.0"
},
"devDependencies": {
"@nestjs/cli": "^8.2.0",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册