Compare commits

...

5 Commits
v5.0.9 ... main

Author SHA1 Message Date
XiaoDaiGua-Ray
eb5a6aa9e2 version: v5.1.0 2025-02-25 17:38:30 +08:00
XiaoDaiGua-Ray
674539edd3 fix: 一些已知问题修复 2025-01-19 10:38:13 +08:00
XiaoDaiGua-Ray
ff0bcb5022
Merge pull request #30 from admover/patch-3
Update test.ts
2025-01-15 20:17:19 +08:00
admover
d3d98190a3
Update test.ts
拼写修正
2025-01-15 17:36:05 +08:00
XiaoDaiGua-Ray
0bb707bba0 version: v5.0.10 2025-01-15 16:32:36 +08:00
41 changed files with 1626 additions and 1293 deletions

View File

@ -1,3 +1,30 @@
## 5.1.0
## Feats
- 主流依赖更新
- `RDraggableCard` 组件 `defaultPosition` 配置项新增 `center`, `top-center`, `bottom-center` 配置项,并且该配置项支持动态更新了
- `RDraggableCard` 组件容器 `id``draggable-card-container` 变更为 `r-draggable-card-container`
- `views/demo` 包命名调整
## Fixes
- 修复 `RDraggableCard` 组件设置 `dad``false` 时,初始化位置错误的问题
## 5.0.10
## Feats
- `RDraggableCard` 组件现在不会在抛出获取 `dom` 失败的异常,因为可能存在异步组件加载的可能
- `RModal`, `useModal` 方法,移除 `dad` 相关所有配置,使用 `draggable` 配置项替代
- 刷新的样式现在会跟随主题变化
- 锁屏密码现在会进行加密存储,并且会进行校验处理了
- 新增 `decrypt`, `decrypt` 方法,放置于 `utils/c` 包中
## Fixes
- 修复因为错误的注册全局事件,导致事件污染的问题,但是默认的 `ctrl + k`, `cmd + k` 快捷键依旧保留为全局按键
## 5.0.9
## Feats

View File

@ -15,6 +15,27 @@
--preloading-title-color: <%= preloadingConfig.titleColor %>;
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
--global-loading-bg-color: #ffffff;
}
@media (prefers-color-scheme: dark) {
#pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
}
@media (prefers-color-scheme: light) {
#pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
}
html.dark #pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
html.light #pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
#pre-loading-animation {
@ -23,13 +44,9 @@
right: 0;
top: 0;
bottom: 0;
background-color: #ffffff;
color: var(--preloading-title-color);
text-align: center;
}
.ray-template--dark #pre-loading-animation {
background-color: #2a3146;
background-color: var(--global-loading-bg-color);
}
#pre-loading-animation .pre-loading-animation__wrapper {
@ -95,6 +112,18 @@
}
}
</style>
<script>
;(function () {
const html = document.documentElement
const store = window.localStorage.getItem('piniaSettingStore')
const { _appTheme = false } = store ? JSON.parse(store) : {}
const loadingBgColor = _appTheme ? '#1c1e23' : '#ffffff'
html.classList.add(_appTheme ? 'dark' : 'light')
html.style.setProperty('--global-loading-bg-color', loadingBgColor)
html.style.setProperty('background-color', loadingBgColor)
})()
</script>
<body>
<div id="app"></div>
<div id="pre-loading-animation">

View File

@ -1,7 +1,7 @@
{
"name": "ray-template",
"private": false,
"version": "5.0.9",
"version": "5.1.0",
"type": "module",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
@ -33,26 +33,27 @@
]
},
"dependencies": {
"@logicflow/core": "2.0.6",
"@logicflow/extension": "2.0.10",
"@vueuse/core": "^12.0.0",
"@logicflow/core": "2.0.10",
"@logicflow/extension": "2.0.14",
"@vueuse/core": "^12.4.0",
"axios": "^1.7.9",
"clipboard": "^2.0.11",
"crypto-js": "4.2.0",
"currency.js": "^2.0.4",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"echarts": "^5.6.0",
"html-to-image": "1.11.11",
"interactjs": "1.10.27",
"jsbarcode": "3.11.6",
"lodash-es": "^4.17.21",
"mockjs": "1.1.0",
"naive-ui": "^2.40.4",
"naive-ui": "^2.41.0",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.1.3",
"pinia-plugin-persistedstate": "^4.2.0",
"print-js": "^1.6.0",
"vue": "^3.5.13",
"vue-demi": "0.14.10",
"vue-hooks-plus": "2.2.1",
"vue-hooks-plus": "2.2.3",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.0",
"vue3-next-qrcode": "2.0.10"
@ -68,36 +69,36 @@
"@types/jsbarcode": "3.11.4",
"@types/lodash-es": "4.17.12",
"@types/mockjs": "1.0.10",
"@types/three": "0.169.0",
"@typescript-eslint/eslint-plugin": "8.18.2",
"@typescript-eslint/parser": "8.18.2",
"@types/three": "0.171.0",
"@typescript-eslint/eslint-plugin": "8.20.0",
"@typescript-eslint/parser": "8.20.0",
"@vitejs/plugin-vue": "5.2.1",
"@vitejs/plugin-vue-jsx": "4.1.1",
"@vitest/ui": "1.5.2",
"@vitest/ui": "2.1.8",
"@vue/eslint-config-prettier": "10.1.0",
"@vue/eslint-config-typescript": "14.2.0",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.20",
"depcheck": "1.4.7",
"eslint": "9.11.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint": "9.18.0",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-prettier": "5.2.2",
"eslint-plugin-vue": "9.32.0",
"globals": "15.14.0",
"happy-dom": "14.12.3",
"happy-dom": "16.6.0",
"husky": "8.0.3",
"lint-staged": "15.2.2",
"postcss": "8.4.49",
"lint-staged": "15.3.0",
"postcss": "8.5.1",
"postcss-px-to-viewport-8-with-include": "1.2.2",
"prettier": "3.3.2",
"prettier": "3.4.2",
"rollup-plugin-gzip": "4.0.1",
"sass": "1.83.0",
"sass": "1.83.4",
"svg-sprite-loader": "6.0.11",
"typescript": "5.6.3",
"unplugin-auto-import": "0.19.0",
"unplugin-auto-import": "19.0.0",
"unplugin-vue-components": "0.28.0",
"vite": "6.0.7",
"vite-bundle-analyzer": "0.15.2",
"vite": "6.1.0",
"vite-bundle-analyzer": "0.16.0",
"vite-plugin-cdn2": "1.1.0",
"vite-plugin-ejs": "1.7.0",
"vite-plugin-eslint": "1.8.1",
@ -105,8 +106,8 @@
"vite-plugin-mock-dev-server": "1.8.3",
"vite-plugin-svg-icons": "2.0.1",
"vite-svg-loader": "5.1.0",
"vitest": "2.0.5",
"vue-tsc": "2.1.10"
"vitest": "2.1.8",
"vue-tsc": "2.2.0"
},
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
"main": "index.ts",

2101
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ interface JSONPlaceholder {
*
* @returns
*
* @medthod get
* @method get
*/
export const getWeather = (city: string) => {
return request<AxiosTestResponse>({

View File

@ -7,6 +7,8 @@ import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import { useSettingActions } from '@/store'
import { useTemplateRef } from 'vue'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { setStorage, encrypt } from '@/utils'
import type { InputInst } from 'naive-ui'
@ -27,6 +29,11 @@ const LockScreen = defineComponent({
validate().then(() => {
setLockAppScreen(true)
updateSettingState('lockScreenSwitch', false)
setStorage(
APP_CATCH_KEY.appLockScreenPasswordKey,
encrypt(state.lockCondition.lockPassword),
'localStorage',
)
state.lockCondition = useCondition()
})

View File

@ -10,6 +10,8 @@ import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useDevice } from '@/hooks'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { removeStorage, decrypt, getStorage } from '@/utils'
export default defineComponent({
name: 'UnlockScreen',
@ -42,27 +44,56 @@ export default defineComponent({
state.DDD = dayjs().format(DDD_FORMAT)
}, 86_400_000)
const toSigningFn = () => {
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
updateSettingState('lockScreenSwitch', false)
setTimeout(() => {
logout()
}, 100)
}
const backToSigning = () => {
window.$dialog.warning({
title: '警告',
content: '是否返回到登陆页?',
content: '是否返回到登陆页并且重新登录',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
updateSettingState('lockScreenSwitch', false)
setTimeout(() => {
logout()
}, 100)
},
negativeText: '重新登录',
onPositiveClick: toSigningFn,
})
}
const unlockScreen = () => {
validate().then(() => {
setLockAppScreen(false)
updateSettingState('lockScreenSwitch', false)
const catchPassword = getStorage<string>(
APP_CATCH_KEY.appLockScreenPasswordKey,
'localStorage',
)
state.lockCondition = useCondition()
if (!catchPassword) {
window.$dialog.warning({
title: '警告',
content: () => '检测到锁屏密码被修改,请重新登录',
closable: false,
maskClosable: false,
closeOnEsc: false,
positiveText: '重新登录',
onPositiveClick: toSigningFn,
})
return
}
const dCatchPassword = decrypt(catchPassword)
validate().then(() => {
if (dCatchPassword === state.lockCondition.lockPassword) {
setLockAppScreen(false)
updateSettingState('lockScreenSwitch', false)
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
state.lockCondition = useCondition()
} else {
window.$message.warning('密码错误,请重新输入')
}
})
}

View File

@ -53,7 +53,7 @@ export default defineComponent({
title="发现新版本"
content="当前版本已更新,点击确认加载新版本~"
zIndex={999999999}
dad
draggable
positiveText="确认"
negativeText="取消"
onPositiveClick={logout}

View File

@ -94,6 +94,7 @@ export const APP_CATCH_KEY_PREFIX = ''
* - appPiniaSigningStore: pinia signing store key
* - appVersionProvider: 版本信息缓存 key
* - appMenuTagOptions: 标签页菜单列表
* - appLockScreenPasswordKey: 锁屏密码缓存 key
*/
export const APP_CATCH_KEY = {
signing: 'signing',
@ -108,6 +109,7 @@ export const APP_CATCH_KEY = {
isAppLockScreen: 'isAppLockScreen',
appGlobalSearchOptions: 'appGlobalSearchOptions',
appMenuTagOptions: 'appMenuTagOptions',
appLockScreenPasswordKey: 'appLockScreenPasswordKey',
} as const
/**

View File

@ -1,10 +1,3 @@
/**
*
*
* , inject
*
*/
import axios from 'axios'
import { AXIOS_CONFIG } from '@/app-config'
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
@ -17,23 +10,33 @@ import {
setupRequestErrorInterceptor,
} from '@/axios/axios-interceptor/request'
import type { AxiosInstanceExpand } from './types'
import type { AxiosInstanceExpand, RequestInterceptorConfig } from './types'
// 创建 axios 实例
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
// 获取拦截器实例
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
// 请求拦截器
server.interceptors.request.use(
(request) => {
createAxiosInstance(request, 'requestInstance') // 生成 request instance
setupRequestInterceptor() // 初始化拦截器所有已注入方法
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok') // 执行拦截器所有已注入方法
// 生成 request instance
createAxiosInstance(
request as RequestInterceptorConfig<unknown>,
'requestInstance',
)
// 初始化拦截器所有已注入方法
setupRequestInterceptor()
// 执行拦截器所有已注入方法
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
return request
},
(error) => {
setupRequestErrorInterceptor() // 初始化拦截器所有已注入方法(错误状态)
fetchError('requestError', error, 'implementRequestInterceptorErrorArray') // 执行所有已注入方法
// 初始化拦截器所有已注入方法(错误状态)
setupRequestErrorInterceptor()
// 执行所有已注入方法
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
return Promise.reject(error)
},
@ -42,17 +45,22 @@ server.interceptors.request.use(
// 响应拦截器
server.interceptors.response.use(
(response) => {
createAxiosInstance(response, 'responseInstance') // 创建响应实例
setupResponseInterceptor() // 注入响应成功待执行队列
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok') // 执行响应成功拦截器
// 创建响应实例
createAxiosInstance(response, 'responseInstance')
// 注入响应成功待执行队列
setupResponseInterceptor()
// 执行响应成功拦截器
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok')
const { data } = response
return Promise.resolve(data)
},
(error) => {
setupResponseErrorInterceptor() // 注入响应失败待执行队列
fetchError('responseError', error, 'implementResponseInterceptorErrorArray') // 执行响应失败后拦截器
// 注入响应失败待执行队列
setupResponseErrorInterceptor()
// 执行响应失败后拦截器
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
return Promise.reject(error)
},

View File

@ -1,14 +1,3 @@
/**
*
* axios
*
*
*
*
* axios ,
* 使,
*/
import RequestCanceler from '@/axios/utils/RequestCanceler'
import { getAppEnvironment } from '@/utils'
@ -24,31 +13,36 @@ import type {
import type { AnyFC } from '@/types'
import type { AxiosError } from 'axios'
/** 当前请求的实例 */
// 当前请求的实例
const axiosFetchInstance: AxiosFetchInstance = {
requestInstance: null,
responseInstance: null,
}
/** 请求失败返回值 */
// 请求失败返回值
const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = {
requestError: null,
responseError: null,
}
/** 请求队列(区分 resolve 与 reject 状态) */
// 请求队列(区分 resolve 与 reject 状态)
const implement: ImplementQueue = {
implementRequestInterceptorArray: [],
implementResponseInterceptorArray: [],
}
// 请求失败队列
const errorImplement: ErrorImplementQueue = {
implementRequestInterceptorErrorArray: [],
implementResponseInterceptorErrorArray: [],
}
/** 取消器实例 */
type ImplementKeys = keyof ImplementQueue
type ErrorImplementKeys = keyof ErrorImplementQueue
// 取消器实例
export const axiosCanceler = new RequestCanceler()
export const useAxiosInterceptor = () => {
/** 创建拦截器实例 */
// 创建拦截器实例
const createAxiosInstance = (
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
instanceKey: keyof AxiosFetchInstance,
@ -60,33 +54,33 @@ export const useAxiosInterceptor = () => {
instance as ResponseInterceptorConfig)
}
/** 获取当前实例 */
// 获取当前实例
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
return axiosFetchInstance[instanceKey]
}
/** 设置注入方法队列 */
// 设置注入方法队列
const setImplement = (
key: keyof ImplementQueue | keyof ErrorImplementQueue,
key: ImplementKeys | ErrorImplementKeys,
func: AnyFC[],
fetchType: FetchType,
) => {
fetchType === 'ok'
? (implement[key as keyof ImplementQueue] = func)
: (errorImplement[key as keyof ErrorImplementQueue] = func)
? (implement[key as ImplementKeys] = func)
: (errorImplement[key as ErrorImplementKeys] = func)
}
/** 获取队列中所有的所有拦截器方法 */
// 获取队列中所有的所有拦截器方法
const getImplement = (
key: keyof ImplementQueue | keyof ErrorImplementQueue,
key: ImplementKeys | ErrorImplementKeys,
fetchType: FetchType,
): AnyFC[] => {
return fetchType === 'ok'
? implement[key as keyof ImplementQueue]
: errorImplement[key as keyof ErrorImplementQueue]
? implement[key as ImplementKeys]
: errorImplement[key as ErrorImplementKeys]
}
/** 队列执行器 */
// 队列执行器
const implementer = (funcs: AnyFC[], ...args: any[]) => {
if (Array.isArray(funcs)) {
funcs.forEach((curr) => {
@ -97,16 +91,16 @@ export const useAxiosInterceptor = () => {
}
}
/** 请求、响应前执行拦截器队列中的所有方法 */
// 请求、响应前执行拦截器队列中的所有方法
const beforeFetch = (
key: keyof AxiosFetchInstance,
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
implementKey: ImplementKeys | ErrorImplementKeys,
fetchType: FetchType,
) => {
const funcArr =
fetchType === 'ok'
? implement[implementKey as keyof ImplementQueue]
: errorImplement[implementKey as keyof ErrorImplementQueue]
? implement[implementKey as ImplementKeys]
: errorImplement[implementKey as ErrorImplementKeys]
const instance = getAxiosInstance(key)
const { MODE } = getAppEnvironment()
@ -115,11 +109,11 @@ export const useAxiosInterceptor = () => {
}
}
/** 请求、响应错误时执行队列中所有方法 */
// 请求、响应错误时执行队列中所有方法
const fetchError = (
key: keyof AxiosFetchError,
error: AxiosError<unknown, unknown>,
errorImplementKey: keyof ErrorImplementQueue,
errorImplementKey: ErrorImplementKeys,
) => {
axiosFetchError[key] = error

View File

@ -5,10 +5,11 @@ import { Teleport, Transition } from 'vue'
import interact from 'interactjs'
import { cardProps } from 'naive-ui'
import { unrefElement, completeSize, queryElements, isValueType } from '@/utils'
import { unrefElement, completeSize, queryElements } from '@/utils'
import type { VNode } from 'vue'
import type { MaybeElement, MaybeRefOrGetter } from '@vueuse/core'
import type { AnyFC } from '@/types'
type RestrictRectOptions = Parameters<typeof interact.modifiers.restrictRect>[0]
@ -17,12 +18,15 @@ type Padding = {
y: number
}
type Position =
export type DefaultPosition =
| Padding
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'center'
| 'top-center'
| 'bottom-center'
const props = {
...cardProps,
@ -69,7 +73,7 @@ const props = {
* @default { x: 0, y: 0 }
*/
defaultPosition: {
type: [Object, String] as PropType<Position>,
type: [Object, String] as PropType<DefaultPosition>,
default: () => ({
x: 0,
y: 0,
@ -132,7 +136,7 @@ export default defineComponent({
x: 0,
y: 0,
}
const CONTAINER_ID = 'draggable-card-container'
const CONTAINER_ID = 'r-draggable-card-container'
const cssVars = computed(() => {
return {
'--r-draggable-card-width': completeSize(props.width),
@ -140,7 +144,12 @@ export default defineComponent({
}
})
let isSetup = false
const cacheProps = {
defaultPosition: props.defaultPosition,
dad: props.dad,
}
// 创建 DraggableCard 容器
const createDraggableCardContainer = () => {
if (!document.getElementById(CONTAINER_ID)) {
const container = document.createElement('div')
@ -152,6 +161,7 @@ export default defineComponent({
createDraggableCardContainer()
// 获取 card, restrictionElement 的 dom 信息
const getDom = () => {
const card = unrefElement(cardRef)
const re =
@ -167,21 +177,17 @@ export default defineComponent({
restrictionElement = unrefElement<HTMLElement>(re as any) as HTMLElement
}
if (!restrictionElement) {
throw new Error(
'[RDraggableCard]: if set restrictionElement, it must be a HTMLElement or a ref of HTMLElement.',
)
}
return {
card,
restrictionElement,
}
}
// 获取 container, card 的位置
const getPosition = (containerRect: DOMRect, cardRect: DOMRect) => {
const { defaultPosition, padding } = props
const { x: paddingX = 0, y: paddingY = 0 } = padding ?? {}
// 默认的 body restrictionElement 的偏移量是 0
const {
x: containerX,
y: containerY,
@ -192,6 +198,33 @@ export default defineComponent({
if (typeof defaultPosition === 'string') {
switch (defaultPosition) {
case 'top-center': {
const cx1 = (containerWidth - cardWidth) / 2 + containerX
const cy1 = paddingY + containerY
const cx2 = paddingX + cx1
const cy2 = cy1
return { x: cx2, y: cy2 }
}
case 'bottom-center': {
const cx1 = (containerWidth - cardWidth) / 2 + containerX
const cy1 = containerHeight - cardHeight - paddingY + containerY
const cx2 = paddingX + cx1
const cy2 = cy1
return { x: cx2, y: cy2 }
}
case 'center': {
const cx1 = (containerWidth - cardWidth) / 2 + containerX
const cy1 = (containerHeight - cardHeight) / 2 + containerY
const cx2 = paddingX + cx1
const cy2 = paddingY + cy1
return { x: cx2, y: cy2 }
}
case 'top-left':
return { x: paddingX + containerX, y: paddingY + containerY }
@ -227,10 +260,11 @@ export default defineComponent({
}
}
// 初始化设置 card 的位置,并且根据配置启用拖拽
const setupDraggable = () => {
const { card, restrictionElement } = getDom()
if (!card || !props.dad) {
if (!card) {
return
}
@ -255,6 +289,10 @@ export default defineComponent({
position.y = p.y
}
if (!props.dad) {
return
}
interactInst = interact(card)
.draggable({
inertia: true,
@ -277,15 +315,30 @@ export default defineComponent({
isSetup = true
}
// 取消拖拽
const resetDraggable = () => {
interactInst?.unset()
interactInst = null
}
// 更新拖拽
const refreshDraggableWhenPropsChange = (fn: AnyFC) => {
isSetup = false
fn()
setupDraggable()
}
expose()
watchEffect(() => {
if (props.dad) {
setupDraggable()
} else {
interactInst?.unset()
props.dad ? setupDraggable() : resetDraggable()
interactInst = null
if (props.defaultPosition !== cacheProps.defaultPosition) {
refreshDraggableWhenPropsChange(() => {
cacheProps.defaultPosition = props.defaultPosition
})
}
})

View File

@ -5,7 +5,7 @@
z-index: var(--r-draggable-card-z-index);
}
#draggable-card-container {
#r-draggable-card-container {
position: fixed;
top: 0;
left: 0;

View File

@ -4,14 +4,12 @@ import { NModal } from 'naive-ui'
import props from './props'
import { completeSize, uuid } from '@/utils'
import { setupInteract } from './utils'
import {
FULLSCREEN_CARD_TYPE_CLASS,
R_MODAL_CLASS,
CSS_VARS_KEYS,
} from './constant'
import type interact from 'interactjs'
import type { ModalProps } from 'naive-ui'
export default defineComponent({
@ -24,57 +22,11 @@ export default defineComponent({
[CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446),
}))
const uuidEl = uuid()
let intractable: null | ReturnType<typeof interact>
// 记录拖拽的位置
const position = {
x: 0,
y: 0,
}
// 当前是否为预设 card 类型并且设置了 fullscreen
const isFullscreenCardType = computed(
() => props.preset === 'card' && props.fullscreen,
)
watch(
() => props.show,
(ndata) => {
if (
ndata &&
props.dad &&
(props.preset === 'card' || props.preset === 'dialog')
) {
nextTick(() => {
const target = document.getElementById(uuidEl)
if (target) {
setupInteract(target, {
preset: props.preset,
x: position.x,
y: position.y,
dargCallback: (x, y) => {
position.x = x
position.y = y
},
}).then((res) => {
intractable = res
})
}
if (props.memo && target) {
target.style.transform = `translate(${position.x}px, ${position.y}px)`
}
})
} else {
intractable?.unset()
intractable = null
}
},
{
immediate: true,
},
)
return {
cssVars,
isFullscreenCardType,

View File

@ -1,5 +1,4 @@
import { useModal as useNaiveModal, NScrollbar } from 'naive-ui'
import { setupInteract } from '../utils'
import { queryElements, setStyle, completeSize, setClass } from '@/utils'
import { R_MODAL_CLASS, CSS_VARS_KEYS } from '../constant'
@ -21,10 +20,10 @@ const useModal = () => {
color: 'rgba(0, 0, 0, 0)',
colorHover: 'rgba(0, 0, 0, 0)',
},
trigger: 'none',
trigger: 'hover',
style: {
width: 'auto',
height:
maxHeight:
'calc(var(--html-height) - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
},
},
@ -35,7 +34,7 @@ const useModal = () => {
)
}
const { preset, dad, fullscreen, width, cardWidth, dialogWidth } = options
const { preset, fullscreen, width, cardWidth, dialogWidth } = options
const modalReactive = naiveCreate({
...rest,
content: contentNode,
@ -55,15 +54,6 @@ const useModal = () => {
return
}
// 是否启用拖拽
if (dad) {
setupInteract(modalElement, {
preset,
x: 0,
y: 0,
})
}
// preset 为 cardfullscreen 为 true 时,最大化 modal
if (fullscreen && preset === 'card') {
setStyle(modalElement, {

View File

@ -4,6 +4,11 @@
// 当设置全屏时启用滚动
& .n-card__content {
overflow: scroll;
max-height: calc(
var(--html-height) - var(--n-padding-bottom) - var(--n-padding-bottom) - var(
--n-padding-top
)
);
}
}

View File

@ -3,17 +3,6 @@ import type { PropType } from 'vue'
const props = {
...modalProps,
/**
*
* @description
*
*
* @default true
*/
memo: {
type: Boolean,
default: true,
},
/**
*
* @description
@ -58,18 +47,6 @@ const props = {
type: [String, Number] as PropType<string | number>,
default: 446,
},
/**
*
* @description
*
* header
*
* @default false
*/
dad: {
type: Boolean,
default: false,
},
}
export default props

View File

@ -1,14 +1,6 @@
import type { ModalOptions as NaiveModalOptions } from 'naive-ui'
export interface RModalProps extends NaiveModalOptions {
/**
*
* @description
*
*
* @default true
*/
memo?: boolean
/**
*
* @description
@ -41,13 +33,4 @@ export interface RModalProps extends NaiveModalOptions {
* @default 446
*/
dialogWidth?: number | string
/**
*
* @description
*
* header
*
* @default false
*/
dad?: boolean
}

View File

@ -1,101 +0,0 @@
import interact from 'interactjs'
import type { ModalProps } from 'naive-ui'
import type { RModalProps } from './types'
interface SetupDraggableOptions {
scheduler?: (event: Interact.DragEvent) => void
}
interface SetupInteractOptions {
preset: ModalProps['preset']
memo?: RModalProps['memo']
x: number
y: number
dargCallback?: (x: number, y: number, event: Interact.DragEvent) => void
}
/**
*
* @param bindModal modal
* @param preset
*
* @description
*
* card, dialog
*
* 30ms
*/
export const setupDraggable = (
bindModal: HTMLElement,
preset: ModalProps['preset'],
options?: SetupDraggableOptions,
): Promise<ReturnType<typeof interact>> => {
const { scheduler } = options ?? {}
return new Promise((resolve) => {
setTimeout(() => {
const allowFromStr =
preset === 'card' ? '.n-card-header__main' : '.n-dialog__title'
if (bindModal) {
const dad = interact(bindModal)
.draggable({
inertia: true,
autoScroll: true,
allowFrom: allowFromStr,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true,
}),
],
listeners: {
move: (event) => {
scheduler?.(event)
},
},
})
.resizable(false)
resolve(dad)
}
}, 30)
})
}
export const setupInteract = (
target: HTMLElement | string,
options: SetupInteractOptions,
): Promise<ReturnType<typeof interact>> => {
const _target =
typeof target === 'string'
? (document.querySelector(target) as HTMLElement)
: target
return new Promise((resolve, reject) => {
if (_target) {
_target.setAttribute('can-drag', 'true')
const { preset, dargCallback } = options
let { x, y } = options
setupDraggable(_target, preset, {
scheduler: (event) => {
const target = event.target
x += event.dx
y += event.dy
target.style.transform = `translate(${x}px, ${y}px)`
dargCallback?.(x, y, event)
},
}).then((res) => {
resolve(res)
})
} else {
reject()
}
})
}

View File

@ -94,7 +94,6 @@ export default defineComponent({
return (
<NTabs
{...($props as TabsProps)}
ref="segmentRef"
style={[cssVars]}
class="r-segment"
type="segment"

View File

@ -33,3 +33,4 @@ export type {
FlowGraphData,
FlowOptions,
} from './base/RFlow/src/types'
export type { DefaultPosition } from './base/RDraggableCard/DraggableCard'

View File

@ -84,7 +84,14 @@ export default defineComponent({
let searchElementIndex = 0
// 缓存索引
let preSearchElementIndex = searchElementIndex
const { isTabletOrSmaller } = useDevice()
const { isTabletOrSmaller } = useDevice({
observer: (val) => {
// 当处于小尺寸状态时,自动关闭搜索框
if (val) {
modelShow.value = false
}
},
})
const loading = ref(false)
// 激活样式 class name
const ACTIVE_CLASS = 'content-item--active'
@ -309,22 +316,7 @@ export default defineComponent({
</NFlex>
)
watchEffect(() => {
// 当处于小尺寸状态时,自动关闭搜索框
if (isTabletOrSmaller.value) {
modelShow.value = false
}
})
useEventListener(
window,
'keydown',
(e: KeyboardEvent) => {
registerArouseKeyboard(e)
registerChangeSearchElementIndex(e)
},
true,
)
useEventListener(window, 'keydown', registerArouseKeyboard)
return {
...toRefs(state),
@ -336,11 +328,16 @@ export default defineComponent({
isTabletOrSmaller,
SearchItem,
loading,
registerChangeSearchElementIndex,
}
},
render() {
const { isTabletOrSmaller, searchOptions, loading } = this
const { SearchItem, fuzzySearchMenuOptions } = this
const {
SearchItem,
fuzzySearchMenuOptions,
registerChangeSearchElementIndex,
} = this
return isTabletOrSmaller ? (
<div style="display: none;"></div>
@ -350,7 +347,11 @@ export default defineComponent({
transformOrigin="center"
displayDirective="if"
>
<div class="global-search global-search--dark global-search--light">
<div
class="global-search global-search--dark global-search--light"
tabindex="-1"
onKeydown={registerChangeSearchElementIndex}
>
<div class="global-search__wrapper">
<NCard
class="global-search__card"

View File

@ -37,21 +37,28 @@ import type { VNode } from 'vue'
export default defineComponent({
name: 'AppSiderBar',
setup() {
// 获取 setting 相关值
const { updateLocale, updateSettingState } = useSettingActions()
const { t } = useI18n()
// 获取全屏相关方法
const [isFullscreen, { toggleFullscreen, isEnabled }] = useFullscreen(
document.getElementsByTagName('html')[0],
)
// 获取设置相关方法
const { getDrawerPlacement, getBreadcrumbSwitch } = useSettingGetters()
const showSettings = ref(false) // 是否显示设置抽屉
const globalSearchShown = ref(false) // 是否展示全局搜索
// 是否显示设置抽屉
const showSettings = ref(false)
// 是否展示全局搜索
const globalSearchShown = ref(false)
// 当前是否为平板或者更小的设备
const { isTabletOrSmaller } = useDevice()
// 获取全局 drawer 的值
const globalDrawerValue = getVariableToRefs('globalDrawerValue')
/**
*
*
* @description
*
*/
const leftIconOptions = computed(() =>
createLeftIconOptions({
@ -61,7 +68,8 @@ export default defineComponent({
)
/**
*
*
* @description
*
*/
const rightTooltipIconOptions = computed(() =>
createRightIconOptions({

View File

@ -5,7 +5,7 @@ import type { AppRouteRecordRaw } from '@/router/types'
const barcode: AppRouteRecordRaw = {
path: 'barcode',
component: () => import('@/views/demo/BarcodeDemo'),
component: () => import('@/views/demo/barcode-demo'),
meta: {
i18nKey: t('menu.Barcode'),
icon: 'other',

View File

@ -5,7 +5,7 @@ import type { AppRouteRecordRaw } from '@/router/types'
const r: AppRouteRecordRaw = {
path: '/flow',
component: () => import('@/views/demo/Flow'),
component: () => import('@/views/demo/flow-demo'),
meta: {
i18nKey: t('menu.Flow'),
icon: 'other',

View File

@ -5,7 +5,7 @@ import type { AppRouteRecordRaw } from '@/router/types'
const r: AppRouteRecordRaw = {
path: '/table-pro',
component: () => import('@/views/demo/TablePro'),
component: () => import('@/views/demo/table-pro-demo'),
meta: {
i18nKey: t('menu.TablePro'),
icon: 'other',

View File

@ -16,10 +16,6 @@ export const orderRoutes = (routes: AppRouteRecordRaw[]) => {
const currOrder = curr.meta?.order ?? 1
const nextOrder = next.meta?.order ?? 0
if (typeof currOrder !== 'number' || typeof nextOrder !== 'number') {
throw new TypeError('orderRoutes error: order must be a number!')
}
if (currOrder === nextOrder) {
// 如果两个路由的 order 值相同,则按照路由名进行排序
return curr.name

6
src/utils/c/constant.ts Normal file
View File

@ -0,0 +1,6 @@
/**
*
* @description
* key
*/
export const CRYPTO_KEY = '4cP+dX5FI2EVYzln'

32
src/utils/c/decrypt.ts Normal file
View File

@ -0,0 +1,32 @@
import { AES, enc } from 'crypto-js'
import { CRYPTO_KEY } from './constant'
import type { CipherParams, WordArray } from './types'
/**
*
* @param data AES
* @param key key
*
* @description
* 使 AES
*
* @example
* const data = 'U2FsdGVkX1+3Q
* const key = CRYPTO_KEY
*
* const decrypted = decrypt(data, key) // { name: 'John Doe' }
*/
export const decrypt = (
data: string | CipherParams,
key?: string | WordArray,
) => {
try {
const decrypted = AES.decrypt(data, key || CRYPTO_KEY)
const decryptedData = decrypted.toString(enc.Utf8)
return JSON.parse(decryptedData)
} catch (e) {
console.error(`Unknown error: ${e}`)
}
}

24
src/utils/c/encrypt.ts Normal file
View File

@ -0,0 +1,24 @@
import { AES } from 'crypto-js'
import { CRYPTO_KEY } from './constant'
import type { WordArray } from './types'
/**
*
* @param data
* @param key key
*
* @description
* 使 AES
*
* @example
* const data = { name: 'John Doe' }
* const key = CRYPTO_KEY
*
* const encrypted = encrypt(data, key) // 'U2FsdGVkX1+3Q'
*/
export const encrypt = (data: unknown, key?: string | WordArray) => {
const encrypted = AES.encrypt(JSON.stringify(data), key || CRYPTO_KEY)
return encrypted.toString()
}

5
src/utils/c/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { CRYPTO_KEY } from './constant'
import { decrypt } from './decrypt'
import { encrypt } from './encrypt'
export { CRYPTO_KEY, decrypt, encrypt }

4
src/utils/c/types.ts Normal file
View File

@ -0,0 +1,4 @@
import type CryptoJS from 'crypto-js'
export type WordArray = CryptoJS.lib.WordArray
export type CipherParams = CryptoJS.lib.CipherParams

View File

@ -123,7 +123,7 @@ function getStorage<T = unknown>(
try {
const data = storage.getItem(prefixedKey)
return data === null ? defaultValue ?? null : (JSON.parse(data) as T)
return data === null ? (defaultValue ?? null) : (JSON.parse(data) as T)
} catch (error) {
console.error(
`[getStorage]: Failed to get stored data for key '${key}'`,

View File

@ -7,6 +7,7 @@ export * from './element'
export * from './precision'
export * from './vue'
export * from './app'
export * from './c'
export { positionSelectedMenuItem }
export { updateObjectValue } from './update-object-value'
export { removeDuplicateKeys } from './remove-duplicate-keys'

View File

@ -50,7 +50,7 @@ const Dashboard = defineComponent({
<NFlex align="center">
<NText
as="a"
tag="a"
class="dashboard-link"
type="primary"
{...{

View File

@ -1,19 +1,35 @@
import { RDraggableCard } from '@/components'
import { NButton, NCard, NFlex } from 'naive-ui'
import { NButton, NCard, NFlex, NRadio, NRadioGroup, NSwitch } from 'naive-ui'
import type { DefaultPosition } from '@/components'
export default defineComponent({
name: 'DraggableCardDemo',
setup() {
const card3 = ref(false)
const domRef = useTemplateRef<HTMLElement>('domRef')
const positionRadioOptions = [
{ label: 'center', value: 'center' },
{ label: 'top-center', value: 'top-center' },
{ label: 'bottom-center', value: 'bottom-center' },
{ label: 'top-left', value: 'top-left' },
{ label: 'top-right', value: 'top-right' },
{ label: 'bottom-left', value: 'bottom-left' },
{ label: 'bottom-right', value: 'bottom-right' },
]
const positionRadioValue = ref('center')
const card3Dad = ref(true)
return {
card3,
card3Dad,
domRef,
positionRadioOptions,
positionRadioValue,
}
},
render() {
const { card3, domRef } = this
const { card3, domRef, positionRadioOptions } = this
return (
<div>
@ -22,7 +38,7 @@ export default defineComponent({
style={{
width: '100%',
height: '400px',
backgroundColor: 'red',
backgroundColor: 'rgba(255, 10, 20, 1)',
}}
></div>
<RDraggableCard animation title="Body">
@ -35,6 +51,8 @@ export default defineComponent({
restrictionElement={domRef}
closable
onClose={() => (this.card3 = false)}
defaultPosition={this.positionRadioValue as DefaultPosition}
dad={this.card3Dad}
>
{{
default: () =>
@ -46,9 +64,33 @@ export default defineComponent({
</RDraggableCard>
) : null}
<NCard title="显示与隐藏卡片">
<NButton type="primary" onClick={() => (this.card3 = !this.card3)}>
</NButton>
<NFlex vertical>
<NFlex>
<NSwitch v-model:value={this.card3Dad}>
{{
checked: () => '拖拽',
unchecked: () => '禁用',
}}
</NSwitch>
</NFlex>
<NFlex>
<NRadioGroup v-model:value={this.positionRadioValue}>
{positionRadioOptions.map((curr) => (
<NRadio key={curr.value} value={curr.value}>
{curr.label}
</NRadio>
))}
</NRadioGroup>
</NFlex>
<NFlex>
<NButton
type="primary"
onClick={() => (this.card3 = !this.card3)}
>
</NButton>
</NFlex>
</NFlex>
</NCard>
</div>
)

View File

@ -16,6 +16,8 @@ export default defineComponent({
const getInst = () => {
console.log(getFlowInstance())
window.$message.info('获取实例成功,请在 console 中查看')
}
setTimeout(() => {

View File

@ -16,7 +16,7 @@ export default defineComponent({
const createCardModal = () => {
create({
title: '卡片模态框',
dad: true,
draggable: true,
preset: 'card',
content: () => (
<div style="height: 3000px;">card模态框</div>
@ -30,7 +30,7 @@ export default defineComponent({
title: '模态框',
content: '内容',
preset: 'dialog',
dad: true,
draggable: true,
})
}
@ -47,12 +47,7 @@ export default defineComponent({
<NFlex vertical>
<NCard title="props">
<NFlex vertical>
<h3>
memoryPosition: 是否记住上一次被拖拽的位置
true
</h3>
<h3>fullscreen: 全屏模态框</h3>
<h3>dad: 启用拖拽 false </h3>
</NFlex>
</NCard>
<RModal
@ -67,7 +62,7 @@ export default defineComponent({
v-model:show={this.modal2}
preset="card"
title="可拖拽卡片模态框"
dad
draggable
>
<p></p>
</RModal>
@ -75,7 +70,7 @@ export default defineComponent({
v-model:show={this.modal3}
preset="dialog"
title="可拖拽卡片模态框"
dad
draggable
>
<p></p>
</RModal>

View File

@ -114,7 +114,7 @@ export default defineComponent({
// 表格数据
const tableDataRef = ref<RowData[]>([])
// 表格列
const baseColumns: DataTableColumns<RowData> = [
const baseColumns = ref<DataTableColumns<RowData>>([
{
type: 'selection',
},
@ -159,7 +159,7 @@ export default defineComponent({
title: 'Remark',
key: 'remark',
},
]
])
// 表格分页数据
const itemCountRef = ref(0)
// 查询条件
@ -452,7 +452,7 @@ export default defineComponent({
<RTablePro
onRegister={tableProRegister}
data={tableDataRef}
columns={baseColumns}
v-model:columns={this.baseColumns}
loading={loadingGetPersonList}
// 如果需要设置分页功能,则该参数必传
paginationCount={itemCountRef}

View File

@ -5,7 +5,7 @@ import viteVueJSX from '@vitejs/plugin-vue-jsx'
import viteVeI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import viteInspect from 'vite-plugin-inspect'
import viteSvgLoader from 'vite-svg-loader'
import { analyzer, adapter } from 'vite-bundle-analyzer'
import { analyzer } from 'vite-bundle-analyzer'
import gzipPlugin from 'rollup-plugin-gzip'
import { ViteEjsPlugin as viteEjsPlugin } from 'vite-plugin-ejs'
import viteAutoImport from 'unplugin-auto-import/vite'
@ -33,12 +33,12 @@ function onlyReportOptions(mode: string): PluginOption[] {
}
return [
adapter(
analyzer({
analyzerMode: 'server', // 以默认服务器代理打开文件
openAnalyzer: true, // 以默认服务器代理打开文件
}),
),
analyzer({
// 以默认服务器代理打开文件
analyzerMode: 'server',
// 以默认服务器代理打开文件
openAnalyzer: true,
}),
]
}