This commit is contained in:
XiaoDaiGua-Ray 2023-11-02 17:51:12 +08:00
parent a840693455
commit 36d646d8ba
66 changed files with 644 additions and 432 deletions

13
.vscode/settings.json vendored
View File

@ -17,5 +17,16 @@
"@mock": "/mock"
},
"alias-skip.allowedsuffix": ["ts", "tsx"],
"alias-skip.rootpath": "package.json"
"alias-skip.rootpath": "package.json",
"cSpell.words": [
"Clickoutside",
"macarons",
"menutag",
"ndata",
"persistedstate",
"Popselect",
"siderbar",
"WUJIE",
"zlevel"
]
}

View File

@ -1,5 +1,27 @@
# CHANGE LOG
## 4.2.9
主要更新了命名问题。并且使用单词检查器,扫描整个项目替换了拼写错误的单词。
### Feats
- setVariable 方法新增回调函数参数
- 优化 getVariable ts 类型提示,并且使用 readonly 方法包裹
- 重命名 globalVariableToRefs 为 getVariableToRefs并且使用 readonly 方法包裹
- renderNode 方法新增对于 Slot 类型参数支持
- 新增 BasicTypes 类型
- 提取 RIcon 组件 props 单独维护
- 将 api 管理,提取到 `src` 下维护
- getAppLocalMessages 获取路径方式由相对路径改为绝对路径
- 将 hooks 包中的 variable 方法提取至 `src` 下管理,并且更改名称为 `global-variable`
- 调整 GlobalSearch 样式
### Fixes
- 修复 downloadBase64File 下载后不能正常移除 `a` 标签问题
- 修复 downloadAnyFile 存在的可能未加载完成而提前执行方法的问题
## 4.2.8
我好像犯了一个很愚蠢的错误,那就是使用 useFullscreen 方法的时候总是会弹出提示。所以紧急修复了这个很愚蠢的问题,并且移除了这个方法。
@ -30,7 +52,7 @@
- RChart
- 修复 animation false 状态渲染异常问题
- 修复响应式代理 echart instance 时,导致部分方法异常问题
## 4.2.7
主要是做了一些统一命名的事情,以前由于写的比较放浪形骸现在正在慢慢更改这个大问题。

View File

@ -1,7 +1,7 @@
{
"name": "ray-template",
"private": false,
"version": "4.2.8",
"version": "4.2.9",
"type": "module",
"engines": {
"node": ">=16.0.0",

View File

@ -16,7 +16,7 @@ export default defineComponent({
<AppGlobalSpin>
{{
default: () => <RouterView />,
description: () => 'lodaing...',
description: () => 'loading...',
}}
</AppGlobalSpin>
</AppNaiveGlobalProvider>

View File

@ -27,7 +27,7 @@ import { getStorage } from '@/utils/cache'
import type { PropType } from 'vue'
import type { AvatarProps, SpaceProps } from 'naive-ui'
import type { SigninCallback } from '@/store/modules/signin/type'
import type { SigningCallback } from '@/store/modules/signing/type'
const AppAvatar = defineComponent({
name: 'AppAvatar',
@ -48,7 +48,7 @@ const AppAvatar = defineComponent({
},
},
setup(props) {
const signin = getStorage<SigninCallback>(APP_CATCH_KEY.signin)
const signing = getStorage<SigningCallback>(APP_CATCH_KEY.signing)
const cssVars = computed(() => {
const vars = {
'--app-avatar-cursor': props.cursor,
@ -58,7 +58,7 @@ const AppAvatar = defineComponent({
})
return {
signin,
signing,
cssVars,
}
},
@ -74,12 +74,12 @@ const AppAvatar = defineComponent({
<NAvatar
// eslint-disable-next-line prettier/prettier, @typescript-eslint/no-explicit-any
{...(this.$props as any)}
src={this.signin?.avatar}
src={this.signing?.avatar}
objectFit="cover"
round
size={this.avatarSize}
/>
<div class="app-avatar__name">{this.signin?.name}</div>
<div class="app-avatar__name">{this.signing?.name}</div>
</NSpace>
)
},

View File

@ -15,20 +15,20 @@ import { NInput, NForm, NFormItem, NButton, NSpace } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar/index'
import dayjs from 'dayjs'
import { useSetting, useSignin } from '@/store'
import { useSetting, useSigning } from '@/store'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/hook'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useDevice } from '@/hooks/web/index'
import type { FormInst, InputInst } from 'naive-ui'
const UnlockScreen = defineComponent({
export default defineComponent({
name: 'UnlockScreen',
setup() {
const formRef = ref<FormInst | null>(null)
const inputInstRef = ref<InputInst | null>(null)
const { logout } = useSignin()
const { logout } = useSigning()
const { changeSwitcher } = useSetting()
const { setLockAppScreen } = useAppLockScreen()
const { isTabletOrSmaller } = useDevice()
@ -55,7 +55,7 @@ const UnlockScreen = defineComponent({
}, 86_400_000)
/** 退出登陆并且回到登陆页 */
const backToSignin = () => {
const backToSigning = () => {
window.$dialog.warning({
title: '警告',
content: '是否返回到登陆页?',
@ -89,7 +89,7 @@ const UnlockScreen = defineComponent({
return {
...toRefs(state),
backToSignin,
backToSigning,
unlockScreen,
formRef,
inputInstRef,
@ -138,7 +138,7 @@ const UnlockScreen = defineComponent({
<NButton
type="primary"
text
onClick={this.backToSignin.bind(this)}
onClick={this.backToSigning.bind(this)}
>
</NButton>
@ -165,5 +165,3 @@ const UnlockScreen = defineComponent({
)
},
})
export default UnlockScreen

View File

@ -99,12 +99,12 @@ export const APP_MENU_CONFIG: Readonly<AppMenuConfig> = {
* , key
*
* :
* - signin: 登陆信息缓存 key
* - signing: 登陆信息缓存 key
* - localeLanguage: 国际化默认缓存 key
* - token: token key
*/
export const APP_CATCH_KEY = {
signin: 'signin',
signing: 'signing',
localeLanguage: 'localeLanguage',
token: 'token',
} as const

View File

@ -17,6 +17,6 @@
export const APP_REGEX: Record<string, RegExp> = {
/** css 尺寸单位匹配 */
validerCSSUnit:
validCSSUnit:
/^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/,
}

View File

@ -26,7 +26,7 @@ export default class RequestCanceler {
}
/** 是否需要加入取消请求表中 */
private isApending(config: AppRawRequestConfig) {
private isAppending(config: AppRawRequestConfig) {
return config.cancelConfig?.needCancel ?? true
}
@ -55,7 +55,7 @@ export default class RequestCanceler {
* @remark signal ,
*/
addPendingRequest(config: AppRawRequestConfig) {
if (this.isApending(config)) {
if (this.isAppending(config)) {
const requestKey = this.generateRequestKey(config)
if (!this.pendingRequest.has(requestKey)) {

View File

@ -44,7 +44,7 @@ const axiosFetchError: AxiosFetchError = {
requestError: null,
responseError: null,
}
/** 请求队列(区分 reslove 与 reject 状态) */
/** 请求队列(区分 resolve 与 reject 状态) */
const implement: ImplementQueue = {
implementRequestInterceptorArray: [],
implementResponseInterceptorArray: [],

View File

@ -34,10 +34,10 @@ export const setupChartTheme = () => {
import.meta.glob('@/echart-themes/**/*.json', {
eager: true,
})
const regx = /\/([^/]+)\.json$/
const regex = /\/([^/]+)\.json$/
const rawThemes = Object.keys(themeRawModules).reduce((pre, curr) => {
const name = curr.match(regx)?.[1]
const name = curr.match(regex)?.[1]
if (name) {
pre.push({

View File

@ -56,7 +56,6 @@ import type { AnyFC } from '@/types/modules/utils'
import type { DebouncedFunc } from 'lodash-es'
import type { UseResizeObserverReturn } from '@vueuse/core'
import type { ECharts, EChartsCoreOption } from 'echarts/core'
import type { RenderVNodeType } from '@use-utils/vue/renderNode'
import type { DropdownProps, DropdownOption } from 'naive-ui'
const defaultChartOptions = {
@ -77,7 +76,7 @@ export default defineComponent({
const rayChartWrapperRef = ref<HTMLElement>()
const echartInstanceRef = ref<ECharts>() // echart 实例
let resizeThrottleReturn: DebouncedFunc<AnyFC> | null // resize 防抖方法实例
let resizeOvserverReturn: UseResizeObserverReturn | null
let resizeObserverReturn: UseResizeObserverReturn | null
const { echartTheme } = APP_THEME
let watchCallback: WatchStopHandle | null
let echartInst: ECharts | null // 无代理响应式代理缓存 echart inst
@ -85,19 +84,16 @@ export default defineComponent({
{
label: '下载图片',
key: 'downloadChart',
disabled:
disabled: !(
echartInstanceRef.value && echartInstanceRef.value.getDom()
? false
: true,
),
},
])
const cssVarsRef = computed(() => {
const cssVars = {
return {
'--ray-chart-width': completeSize(props.width),
'--ray-chart-height': completeSize(props.height),
}
return cssVars
})
/**
@ -257,14 +253,16 @@ export default defineComponent({
}
}
const isDispose = () => !!(echartInst && echartInst.getDom())
/**
*
* `chart` ,
*/
const destroyChart = () => {
if (echartInst && echartInst.getDom()) {
echartInst.clear()
echartInst.dispose()
if (isDispose()) {
echartInst!.clear()
echartInst!.dispose()
echartInstanceRef.value = void 0
}
}
@ -277,11 +275,11 @@ export default defineComponent({
}
const dropdownSelect = (key: string | number, option: DropdownOption) => {
if (key === 'downloadChart' && echartInst && echartInst.getDom()) {
if (key === 'downloadChart' && isDispose()) {
const { filename, ...args } = props.downloadOptions
downloadBase64File(
echartInst.getDataURL(args),
echartInst!.getDataURL(args),
filename ?? `${new Date().getTime()}`,
)
}
@ -310,7 +308,7 @@ export default defineComponent({
resizeThrottleReturn = throttle(resizeChart, props.throttleWait)
/** 监听内容区域尺寸变化更新 chart */
resizeOvserverReturn = useResizeObserver(
resizeObserverReturn = useResizeObserver(
props.observer || rayChartWrapperRef,
resizeThrottleReturn,
)
@ -323,7 +321,7 @@ export default defineComponent({
/** 注销防抖 */
resizeThrottleReturn?.cancel()
/** 注销 observer 监听 */
resizeOvserverReturn?.stop?.()
resizeObserverReturn?.stop?.()
}
/** 监听全局主题变化, 然后重新渲染对应主题 echarts */
@ -362,9 +360,9 @@ export default defineComponent({
if (props.watchOptions) {
watchCallback = watch(
() => props.options,
(noptions) => {
(ndata) => {
/** 重新组合 options */
const options = combineChartOptions(noptions)
const options = combineChartOptions(ndata)
const setOpt = Object.assign(
props.setChartOptions,
defaultChartOptions,
@ -432,13 +430,13 @@ export default defineComponent({
bordered={bordered}
>
{{
default: () => (
<div class="ray-chart__container" ref="rayChartRef"></div>
default: renderNode(
<div class="ray-chart__container" ref="rayChartRef"></div>,
),
header: renderNode(title, {
defaultElement: <div style="display: none;"></div>,
}),
'header-extra': renderNode(cardExtra as RenderVNodeType, {
'header-extra': renderNode(cardExtra, {
defaultElement: (
<RMoreDropdown
iconSize={18}
@ -446,6 +444,7 @@ export default defineComponent({
options={dropdownOptions ?? moreDropDownOptions}
trigger="click"
onSelect={dropdownSelect.bind(this)}
placement="bottom-end"
/>
),
}),

View File

@ -27,13 +27,13 @@ import RIcon from '@/components/RIcon'
import { call } from '@/utils/vue/index'
import props from './props'
const RCollapseGrid = defineComponent({
export default defineComponent({
name: 'RCollapseGrid',
props,
setup(props) {
const modelCollapsed = ref(!props.open)
const handleCollapse = () => {
const collapseClick = () => {
modelCollapsed.value = !modelCollapsed.value
const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props
@ -48,7 +48,7 @@ const RCollapseGrid = defineComponent({
}
const CollapseIcon = () => (
<div class="collapse-icon" onClick={handleCollapse.bind(this)}>
<div class="collapse-icon" onClick={collapseClick.bind(this)}>
<span>
{modelCollapsed.value
? props.collapseToggleText[0]
@ -66,7 +66,7 @@ const RCollapseGrid = defineComponent({
return {
modelCollapsed,
handleCollapse,
collapseClick,
CollapseIcon,
}
},
@ -97,5 +97,3 @@ const RCollapseGrid = defineComponent({
)
},
})
export default RCollapseGrid

View File

@ -13,57 +13,11 @@ import './index.scss'
import { call } from '@/utils/vue/index'
import { completeSize } from '@/utils/element'
import props from './props'
import type { PropType } from 'vue'
import type { MaybeArray } from '@/types/modules/utils'
const RIcon = defineComponent({
export default defineComponent({
name: 'RIcon',
props: {
color: {
type: String,
default: 'currentColor',
},
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 14,
},
width: {
type: [Number, String],
default: 0,
},
height: {
type: [Number, String],
default: 0,
},
customClassName: {
/** 自定义 class name */
type: String,
default: null,
},
depth: {
/** 图标深度 */
type: Number,
default: 1,
},
cursor: {
/** 鼠标指针样式 */
type: String,
default: 'default',
},
onClick: {
type: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
default: null,
},
},
props,
setup(props) {
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const cssVars = computed(() => {
@ -111,5 +65,3 @@ const RIcon = defineComponent({
)
},
})
export default RIcon

View File

@ -0,0 +1,61 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-27
*
* @workspace ray-template
*
* @remark
*/
import type { PropType } from 'vue'
import type { MaybeArray } from '@/types/modules/utils'
const props = {
color: {
type: String,
default: 'currentColor',
},
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 14,
},
width: {
type: [Number, String],
default: 0,
},
height: {
type: [Number, String],
default: 0,
},
customClassName: {
/** 自定义 class name */
type: String,
default: null,
},
depth: {
/** 图标深度 */
type: Number,
default: 1,
},
cursor: {
/** 鼠标指针样式 */
type: String,
default: 'default',
},
onClick: {
type: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
default: null,
},
}
export default props

View File

@ -17,7 +17,7 @@ import { completeSize, on, off } from '@use-utils/element'
import { call } from '@/utils/vue/index'
import props from './props'
const RIframe = defineComponent({
export default defineComponent({
name: 'RIframe',
props,
setup(props, { expose }) {
@ -61,7 +61,6 @@ const RIframe = defineComponent({
on(iframeRef.value, 'load', iframeLoadSuccess.bind(this))
on(iframeRef.value, 'error', iframeLoadError)
})
onBeforeUnmount(() => {
off(iframeRef.value, 'load', iframeLoadSuccess)
off(iframeRef.value, 'error', iframeLoadError)
@ -101,5 +100,3 @@ const RIframe = defineComponent({
)
},
})
export default RIframe

View File

@ -13,20 +13,20 @@ import { NDropdown } from 'naive-ui'
import RIcon from '@/components/RIcon/index'
import props from './props'
import { renderNode } from '@use-utils/vue/index'
export default defineComponent({
name: 'RMoreDropdown',
props,
render() {
const { iconSize, cursor } = this
const { default: $default } = this.$slots
return (
<NDropdown {...this.$props} {...this.$attrs}>
{this.$slots.default ? (
this.$slots.default()
) : (
<RIcon name="more" size={iconSize} cursor={cursor} />
)}
<NDropdown {...this.$props} {...this.$attrs} placement="bottom-start">
{renderNode($default, {
defaultElement: <RIcon name="more" size={iconSize} cursor={cursor} />,
})}
</NDropdown>
)
},

View File

@ -27,7 +27,7 @@ const props = {
watchText: {
/**
*
* Atuo watch QR code text
* Auto watch QR code text
* If update text, then re-render QR code
*
* @default true

View File

@ -13,7 +13,7 @@ import './index.scss'
import { NCard, NDataTable, NDropdown, NSpace } from 'naive-ui'
import Size from './components/Size'
import Screenfull from './components/Fullscreen'
import Fullscreen from './components/Fullscreen'
import C from './components/C'
import Print from './components/Print'
@ -118,7 +118,7 @@ export default defineComponent({
<>
<Print {...p} />
<Size {...p} onChangeSize={changeTableSize.bind(this)} />
<Screenfull />
<Fullscreen />
<C {...p} onUpdateColumn={updateTableColumn.bind(this)} />
</>
)

View File

@ -37,12 +37,17 @@ export const setupDirectives = (app: App<Element>) => {
const regexDirectiveName = /^([^-]+-)*[^-]+$/
forIn(directivesModules, (value, key) => {
const dname = key.match(regexExtractDirectiveName)?.[0]
const directiveBindName = key.match(regexExtractDirectiveName)?.[0]
if (typeof dname === 'string' && regexDirectiveName.test(dname)) {
app.directive(dname, value())
if (
typeof directiveBindName === 'string' &&
regexDirectiveName.test(directiveBindName)
) {
app.directive(directiveBindName, value())
} else {
console.error(`[setupDirectives] ${dname} is not a valid directive name`)
console.error(
`[setupDirectives] ${directiveBindName} is not a valid directive name`,
)
}
})
}

View File

@ -0,0 +1,30 @@
## global-variable
简单的管理全局响应式变量,但是不支持缓存。
创建该模块是因为在有时候不需要 `pinia` 这么复杂流程的全局变量。所以该模块管理的都是一些简单的全局变量,并且不支持缓存。
## 添加全局变量
1. 首先在 `variableState` 变量中添加对应变量
2. 调用对应方法获取、更新变量
```ts
// 添加变量
const variableState = reactive({
globalSpinning: false,
globalDrawerValue: false,
yourGlobalValue: 'demo value',
})
// 更新变量
setVariable(key, value)
// 获取非响应式变量
const yourGlobalValue = getVariable('yourGlobalValue') // Readonly<demo value>
// 获取响应式变量
const yourGlobalValue = getVariableToRefs('yourGlobalValue') // Readonly<Ref<demo value>>
```
> 注意,避免滥用该模块。可能会导致管理不当引起内存泄漏。

View File

@ -0,0 +1,18 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-09-11
*
* @workspace ray-template
*
* @remark
*/
import { setVariable, getVariable, getVariableToRefs } from './variable'
import type { VariableState, VariableStateKey } from './variable'
export { setVariable, getVariable, getVariableToRefs }
export type { VariableState, VariableStateKey }

View File

@ -0,0 +1,62 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-09-11
*
* @workspace ray-template
*
* @remark
*/
/**
*
* pinia 使
*
*
* 使
* @example
*
*
* getVariable('target key', 'default value')
*
*
* getVariableToRefs('target key')
*
* state
* setVariable('key', 'value')
*/
import type { AnyFC } from '@/types/modules/utils'
const variableState = reactive({
globalSpinning: false,
globalDrawerValue: false,
})
export type VariableState = typeof variableState
export type VariableStateKey = keyof VariableState
export function setVariable<T extends VariableStateKey>(
key: T,
value: VariableState[T],
cb?: AnyFC,
) {
variableState[key] = value
cb?.()
}
export function getVariable<T extends VariableStateKey>(
key: VariableStateKey,
defaultValue?: VariableState[T],
) {
const v = variableState[key]
return v ? readonly(variableState)[key] : defaultValue
}
export function getVariableToRefs<K extends VariableStateKey>(key: K) {
return readonly(toRef<VariableState, K>(variableState, key))
}

View File

@ -1,18 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-09-11
*
* @workspace ray-template
*
* @remark
*/
import {
setVariable,
getVariable,
globalVariableToRefs,
} from './useGlobalVariable'
export { setVariable, getVariable, globalVariableToRefs }

View File

@ -1,41 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-09-11
*
* @workspace ray-template
*
* @remark
*/
/**
*
* pinia 使
*
*
*
*/
/** 全局响应式变量 */
const variableState = reactive({
globalSpinning: false,
globalDrawerValue: false,
})
type VariableStateKey = keyof typeof variableState
export function setVariable(
key: VariableStateKey,
value: (typeof variableState)[VariableStateKey],
) {
variableState[key] = value
}
export function getVariable(key: VariableStateKey) {
return variableState[key] as (typeof variableState)[VariableStateKey]
}
export function globalVariableToRefs<K extends VariableStateKey>(key: K) {
return readonly(toRef<typeof variableState, K>(variableState, key))
}

View File

@ -23,9 +23,9 @@ import type { DayjsLocal } from '@/dayjs/type'
*/
export const useDayjs = () => {
const locale = (key: DayjsLocal) => {
const mapkey = DAYJS_LOCAL_MAP[key]
const locale = DAYJS_LOCAL_MAP[key]
mapkey ? dayjs.locale(mapkey) : dayjs.locale(DEFAULT_DAYJS_LOCAL)
locale ? dayjs.locale(locale) : dayjs.locale(DEFAULT_DAYJS_LOCAL)
}
return {

6
src/icons/empty.svg Normal file
View File

@ -0,0 +1,6 @@
<svg t="1698916917467" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="10617" width="64" height="64">
<path
d="M776.145455 81.454545l214.10909 296.727273 10.472728 13.963637v552.727272H23.272727V392.145455l10.472728-13.963637L247.854545 81.454545h528.29091zM335.127273 406.109091H51.2v510.836364h921.6V406.109091H684.218182c-5.818182 36.072727-22.109091 69.818182-48.872727 96.581818-33.745455 33.745455-79.127273 52.363636-125.672728 52.363636-47.709091 0-91.927273-18.618182-125.672727-52.363636-25.6-26.763636-43.054545-60.509091-48.872727-96.581818z m621.381818-27.927273L762.181818 109.381818h-500.363636L67.490909 378.181818h293.271273c0 39.563636 16.256 76.8 44.183273 104.727273 27.927273 27.927273 66.327273 44.218182 104.727272 44.218182s76.8-16.290909 104.727273-44.218182c27.927273-27.927273 44.322909-65.163636 44.322909-104.727273H956.509091z"
fill="currentColor" p-id="10618"></path>
</svg>

After

Width:  |  Height:  |  Size: 961 B

View File

@ -17,7 +17,7 @@ import SiderBarLogo from './components/SiderBarLogo/index'
import { useMenu } from '@/store'
import { APP_MENU_CONFIG } from '@/app-config/appConfig'
import { useDevice } from '@/hooks/web/index'
import { globalVariableToRefs, setVariable } from '@/hooks/variable/index'
import { getVariableToRefs, setVariable } from '@/global-variable/index'
import type { MenuInst } from 'naive-ui'
import type { NaiveMenuOptions } from '@/types/modules/component'
@ -49,7 +49,7 @@ export default defineComponent({
const modelCollapsed = computed(() => menuStore.collapsed)
const { isTabletOrSmaller } = useDevice()
const modelGlobalDrawerValue = computed({
get: () => globalVariableToRefs('globalDrawerValue').value,
get: () => getVariableToRefs('globalDrawerValue').value,
set: (val) => {
setVariable('globalDrawerValue', val)
},
@ -63,7 +63,7 @@ export default defineComponent({
})
}
const BaseicMenu = () => (
const BasicMenu = () => (
<NLayoutSider
bordered
showTrigger={!isTabletOrSmaller.value}
@ -93,23 +93,24 @@ export default defineComponent({
return {
menuRef,
isTabletOrSmaller,
BaseicMenu,
BasicMenu: BasicMenu,
modelGlobalDrawerValue,
}
},
render() {
const { isTabletOrSmaller, BaseicMenu } = this
const { isTabletOrSmaller, BasicMenu } = this
return !isTabletOrSmaller ? (
<BaseicMenu />
<BasicMenu />
) : (
<NDrawer
class="app-menu__drawer"
v-model:show={this.modelGlobalDrawerValue}
placement="left"
displayDirective="show"
autoFocus={false}
>
<BaseicMenu />
<BasicMenu />
</NDrawer>
)
},

View File

@ -8,7 +8,7 @@ $menuTagWrapperWidth: 76px;
align-items: center;
padding: 4px 0;
& .menu-tag-sapce {
& .menu-tag-space {
width: calc(100% - $space * 2);
padding: $space;

View File

@ -35,6 +35,7 @@ import { hasClass } from '@/utils/element'
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
import { ROOT_ROUTE } from '@/app-config/appConfig'
import { queryElements } from '@use-utils/element'
import { renderNode } from '@/utils/vue/index'
import type { MenuOption, ScrollbarInst } from 'naive-ui'
import type { MenuTagOptions, AppMenuOption } from '@/types/modules/app'
@ -58,7 +59,10 @@ export default defineComponent({
const { path } = ROOT_ROUTE
const exclude = ['closeAll', 'closeRight', 'closeLeft', 'closeOther']
let currentContentmenuIndex = -1 // 当前右键标签页索引位置
let currentContextmenuIndex = -1 // 当前右键标签页索引位置
const iconConfig = {
size: 16,
}
const modelMenuTagOptions = computed(() =>
menuTagOptions.value.map((curr, _idx, currentArray) => {
if (curr.key === menuKey.value && curr.key !== path) {
@ -86,7 +90,7 @@ export default defineComponent({
h(
RIcon,
{
size: 16,
size: iconConfig.size,
name: 'reload',
},
{},
@ -99,7 +103,7 @@ export default defineComponent({
h(
RIcon,
{
size: 16,
size: iconConfig.size,
name: 'other',
},
{},
@ -112,7 +116,7 @@ export default defineComponent({
h(
RIcon,
{
size: 16,
size: iconConfig.size,
name: 'right_arrow',
},
{},
@ -125,7 +129,7 @@ export default defineComponent({
h(
RIcon,
{
size: 16,
size: iconConfig.size,
name: 'left_arrow',
},
{},
@ -142,7 +146,7 @@ export default defineComponent({
h(
RIcon,
{
size: 16,
size: iconConfig.size,
name: 'close',
},
{},
@ -176,16 +180,16 @@ export default defineComponent({
* menuKey ,
*/
const length = moreOptions.value.length
const routeItem = modelMenuTagOptions.value[currentContentmenuIndex]
const routeItem = modelMenuTagOptions.value[currentContextmenuIndex]
spliceMenTagOptions(currentContentmenuIndex + 1, length - 1)
spliceMenTagOptions(currentContextmenuIndex + 1, length - 1)
if (menuKey.value !== routeItem.key) {
changeMenuModelValue(routeItem.key, routeItem)
}
},
closeLeft: () => {
spliceMenTagOptions(0, currentContentmenuIndex)
spliceMenTagOptions(0, currentContextmenuIndex)
},
closeOther: () => {
/**
@ -194,7 +198,7 @@ export default defineComponent({
*
* menuKey ,
*/
const routeItem = modelMenuTagOptions.value[currentContentmenuIndex]
const routeItem = modelMenuTagOptions.value[currentContextmenuIndex]
if (menuKey.value !== routeItem.key) {
emptyMenuTagOptions()
@ -306,7 +310,7 @@ export default defineComponent({
e.preventDefault()
actionState.actionDropdownShow = false
currentContentmenuIndex = idx
currentContextmenuIndex = idx
nextTick().then(() => {
actionState.actionDropdownShow = true
@ -318,31 +322,31 @@ export default defineComponent({
const setDisabledAccordionToIndex = () => {
const length = modelMenuTagOptions.value.length - 1
if (currentContentmenuIndex === length) {
if (currentContextmenuIndex === length) {
setMoreOptionsDisabled('closeRight', true)
} else if (currentContentmenuIndex < length) {
} else if (currentContextmenuIndex < length) {
setMoreOptionsDisabled('closeRight', false)
}
if (currentContentmenuIndex === 0) {
if (currentContextmenuIndex === 0) {
setMoreOptionsDisabled('closeLeft', true)
} else if (currentContentmenuIndex > 0) {
} else if (currentContextmenuIndex > 0) {
setMoreOptionsDisabled('closeLeft', false)
}
}
/**
*
* , currentContentmenuIndex
* , currentContextmenuIndex
*
*
*/
const setCurrentContentmenuIndex = () => {
const setCurrentContextmenuIndex = () => {
const index = modelMenuTagOptions.value.findIndex(
(curr) => curr.key === menuKey.value,
)
currentContentmenuIndex = index
currentContextmenuIndex = index
setDisabledAccordionToIndex()
}
@ -445,13 +449,18 @@ export default defineComponent({
rootPath: path,
actionState,
handleContextMenu,
setCurrentContentmenuIndex,
setCurrentContextmenuIndex,
menuTagMouseenter,
menuTagMouseleave,
MENU_TAG_DATA,
}
},
render() {
const iconConfig = {
width: 20,
height: 28,
}
return (
<NLayoutHeader>
<div class="menu-tag">
@ -468,7 +477,7 @@ export default defineComponent({
onSelect={this.actionDropdownSelect.bind(this)}
/>
<NSpace
class="menu-tag-sapce"
class="menu-tag-space"
wrap={false}
align="center"
justify="space-between"
@ -477,8 +486,8 @@ export default defineComponent({
>
<RIcon
name="expanded"
width="20"
height="28"
width={iconConfig.width}
height={iconConfig.height}
customClassName="menu-tag__left-arrow"
onClick={this.scrollX.bind(this, 'left')}
/>
@ -511,9 +520,7 @@ export default defineComponent({
[this.MENU_TAG_DATA]: curr.path,
}}
>
{typeof curr.label === 'string'
? curr.label
: curr.label?.()}
{renderNode(curr.label)}
</NTag>
))}
</NSpace>
@ -521,8 +528,8 @@ export default defineComponent({
<div class="menu-tag__right-wrapper">
<RIcon
name="expanded"
width="20"
height="28"
width={iconConfig.width}
height={iconConfig.height}
customClassName="menu-tag__right-arrow"
onClick={this.scrollX.bind(this, 'right')}
/>
@ -534,10 +541,10 @@ export default defineComponent({
>
<RIcon
name="more"
width="20"
height="28"
width={iconConfig.width}
height={iconConfig.height}
customClassName="menu-tag__right-setting"
onClick={this.setCurrentContentmenuIndex.bind(this)}
onClick={this.setCurrentContextmenuIndex.bind(this)}
/>
</RMoreDropdown>
</div>

View File

@ -1,16 +1,16 @@
$globalSearchWidth: 650px;
.global-seach {
.global-search {
position: fixed;
width: $globalSearchWidth;
left: 50%;
margin-left: calc(0px - $globalSearchWidth / 2);
top: 60px;
& .global-seach__wrapper {
& .global-search__wrapper {
box-sizing: border-box;
& .global-seach__card {
& .global-search__card {
width: $globalSearchWidth;
border-radius: 6px;
padding: 12px;
@ -19,13 +19,28 @@ $globalSearchWidth: 650px;
color: var(--ray-theme-primary-color);
}
& .global-seach__card-header {
& .global-search__card-header {
margin-bottom: 12px;
}
& .global-seach__card-content {
& .global-search__card-content {
height: auto;
max-height: calc(100% - 98px);
padding: 8px 0;
& .global-search__empty {
margin: 24px;
}
& .global-search__empty-content {
font-size: 18px;
color: #969faf;
font-weight: 600;
& .ray-icon {
color: #969faf;
}
}
& .content-item {
padding: 12px;
@ -39,7 +54,7 @@ $globalSearchWidth: 650px;
}
}
& .global-seach__card-footer {
& .global-search__card-footer {
width: 100%;
& .card-footer__tip-wrapper {
@ -67,12 +82,12 @@ $globalSearchWidth: 650px;
}
}
.global-seach--dark {
@include useAppTheme("dark") {
& .global-seach__card {
.global-search--dark {
@include useAppTheme('dark') {
& .global-search__card {
background-color: #242424;
& .global-seach__card-content .content-item {
& .global-search__card-content .content-item {
background-color: #2f2f2f;
&.content-item--active,
@ -84,12 +99,12 @@ $globalSearchWidth: 650px;
}
}
.global-seach--light {
@include useAppTheme("light") {
& .global-seach__card {
.global-search--light {
@include useAppTheme('light') {
& .global-search__card {
background-color: #f9f9f9;
& .global-seach__card-content .content-item {
& .global-search__card-content .content-item {
background-color: #ffffff;
&.content-item--active,

View File

@ -11,7 +11,15 @@
import './index.scss'
import { NInput, NModal, NResult, NScrollbar, NSpace } from 'naive-ui'
import {
NInput,
NModal,
NResult,
NScrollbar,
NSpace,
NDivider,
NButton,
} from 'naive-ui'
import RIcon from '@/components/RIcon/index'
import { on, off, queryElements, addClass, removeClass } from '@/utils/element'
@ -24,7 +32,7 @@ import type { AppRouteMeta } from '@/router/type'
import type { AppMenuOption } from '@/types/modules/app'
export default defineComponent({
name: 'GlobalSeach',
name: 'GlobalSearch',
props: {
show: {
type: Boolean,
@ -51,7 +59,7 @@ export default defineComponent({
searchValue: null,
searchOptions: [] as AppMenuOption[],
})
const tiptextOptions = [
const helperTipOptions = [
{
icon: 'cmd / ctrl + k',
label: '唤起',
@ -131,7 +139,7 @@ export default defineComponent({
}
nextTick().then(() => {
autoFouceSearchItem()
autoFocusingSearchItem()
})
}
@ -151,7 +159,7 @@ export default defineComponent({
}
/** 自动聚焦检索项 */
const autoFouceSearchItem = () => {
const autoFocusingSearchItem = () => {
const currentOption = state.searchOptions[searchElementIndex]
const preOption = state.searchOptions[preSearchElementIndex]
@ -239,9 +247,24 @@ export default defineComponent({
break
}
autoFouceSearchItem()
autoFocusingSearchItem()
}
const SearchItem = ({ menuOption }: { menuOption: AppMenuOption }) => (
<NSpace
align="center"
wrapItem={false}
class="content-item"
{...{
onClick: handleSearchItemClick.bind(this, menuOption),
data_path: menuOption.path,
}}
>
<div class="content-item-icon">{RenderPreIcon(menuOption.meta)}</div>
<div class="content-item-label">{menuOption.breadcrumbLabel}</div>
</NSpace>
)
watchEffect(() => {
if (isTabletOrSmaller.value) {
modelShow.value = false
@ -264,24 +287,30 @@ export default defineComponent({
return {
...toRefs(state),
modelShow,
tiptextOptions,
helperTipOptions,
handleSearchMenuOptions: debounce(handleSearchMenuOptions, 300),
handleSearchItemClick,
RenderPreIcon,
isTabletOrSmaller,
SearchItem,
}
},
render() {
const { isTabletOrSmaller } = this
const { isTabletOrSmaller, searchOptions } = this
const { SearchItem } = this
return isTabletOrSmaller ? (
<div></div>
<div style="display: none;"></div>
) : (
<NModal v-model:show={this.modelShow} transform-origin="center" show>
<div class="global-seach global-seach--dark global-seach--light">
<div class="global-seach__wrapper">
<div class="global-seach__card">
<div class="global-seach__card-header">
<NModal
v-model:show={this.modelShow}
transformOrigin="center"
displayDirective="if"
>
<div class="global-search global-search--dark global-search--light">
<div class="global-search__wrapper">
<div class="global-search__card">
<div class="global-search__card-header">
<NInput
size="large"
v-model:value={this.searchValue}
@ -293,44 +322,39 @@ export default defineComponent({
}}
</NInput>
</div>
<NScrollbar class="global-seach__card-content">
{this.searchOptions.length ? (
<NScrollbar class="global-search__card-content">
{searchOptions.length ? (
<NSpace vertical wrapItem={false} size={[8, 8]}>
{this.searchOptions.map((curr) => (
<NSpace
align="center"
wrapItem={false}
class="content-item"
{...{
onClick: this.handleSearchItemClick.bind(this, curr),
data_path: curr.path,
}}
>
<div class="content-item-icon">
{this.RenderPreIcon(curr.meta)}
</div>
<div class="content-item-label">
{curr.breadcrumbLabel}
</div>
</NSpace>
{searchOptions.map((curr) => (
<SearchItem menuOption={curr} />
))}
</NSpace>
) : (
<NResult size="large" description="暂无搜索结果">
<NResult size="large" class="global-search__empty">
{{
icon: () => '',
icon: () => null,
default: () => (
<NSpace
wrapItem={false}
justify="center"
class="global-search__empty-content"
>
<RIcon name="empty" size="24" />
</NSpace>
),
}}
</NResult>
)}
</NScrollbar>
<div class="global-seach__card-footer">
<div class="global-search__card-footer">
<NSpace
class="card-footer__tip-wrapper"
align="center"
wrapItem={false}
size={[24, 8]}
>
{this.tiptextOptions.map((curr) => (
{this.helperTipOptions.map((curr) => (
<div class="tip-wrapper-item">
<div class="item-icon">
{curr.plain ? (
@ -339,7 +363,7 @@ export default defineComponent({
<RIcon name={curr.icon} size="18" />
)}
</div>
<div class="item-laebl">{curr.label}</div>
<div class="item-label">{curr.label}</div>
</div>
))}
</NSpace>

View File

@ -1,4 +1,4 @@
import { useSetting, useSignin } from '@/store'
import { useSetting, useSigning } from '@/store'
import { useI18n } from '@/hooks/web/index'
import type { IconOptionsFC, IconOptions } from './type'
@ -24,8 +24,8 @@ export const createAvatarOptions = () => [
const avatarDropdownActionMap = {
logout: () => {
const signinStore = useSignin()
const { logout } = signinStore
const signingStore = useSigning()
const { logout } = signingStore
window.$dialog.warning({
title: '提示',

View File

@ -21,10 +21,10 @@ import './index.scss'
import { NLayoutHeader, NSpace, NDropdown } from 'naive-ui'
import RIcon from '@/components/RIcon/index'
import TootipIcon from '@/layout/components/SiderBar/components/TooltipIcon/index'
import TooltipIcon from '@/layout/components/SiderBar/components/TooltipIcon/index'
import SettingDrawer from './components/SettingDrawer/index'
import Breadcrumb from './components/Breadcrumb/index'
import GlobalSeach from './components/GlobalSeach/index'
import GlobalSearch from './components/GlobalSearch/index'
import AppAvatar from '@/app-components/app/AppAvatar/index'
import { useSetting } from '@/store'
@ -36,7 +36,7 @@ import {
createRightIconOptions,
} from './hook'
import { useDevice } from '@/hooks/web/index'
import { globalVariableToRefs, setVariable } from '@/hooks/variable/index'
import { getVariableToRefs, setVariable } from '@/global-variable/index'
import { useFullscreen } from 'vue-hooks-plus'
import { useI18n } from '@/hooks/web/index'
@ -61,7 +61,7 @@ export default defineComponent({
}
const globalSearchShown = ref(false)
const { isTabletOrSmaller } = useDevice()
const globalDrawerValue = globalVariableToRefs('globalDrawerValue')
const globalDrawerValue = getVariableToRefs('globalDrawerValue')
/**
*
@ -135,7 +135,7 @@ export default defineComponent({
render() {
return (
<NLayoutHeader class="layout-header" bordered>
<GlobalSeach v-model:show={this.globalSearchShown} />
<GlobalSearch v-model:show={this.globalSearchShown} />
<NSpace
class="layout-header__method"
align="center"
@ -147,7 +147,7 @@ export default defineComponent({
itemStyle={this.spaceItemStyle}
>
{this.leftIconOptions.map((curr) => (
<TootipIcon
<TooltipIcon
iconName={curr.name}
tooltipText={
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip
@ -164,7 +164,7 @@ export default defineComponent({
itemStyle={this.spaceItemStyle}
>
{this.rightTooltipIconOptions.map((curr) => (
<TootipIcon
<TooltipIcon
iconName={curr.name}
tooltipText={
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip

View File

@ -33,7 +33,7 @@ const ContentWrapper = defineComponent({
const { reloadRouteSwitch, contentTransition } = storeToRefs(settingStore)
const spinning = ref(false)
const thmeOverridesSpin: GlobalThemeOverrides['Spin'] = {
const themeOverridesSpin: GlobalThemeOverrides['Spin'] = {
opacitySpinning: '0',
}
@ -52,7 +52,7 @@ const ContentWrapper = defineComponent({
return {
reloadRouteSwitch,
spinning,
thmeOverridesSpin,
themeOverridesSpin,
contentTransition,
}
},
@ -62,7 +62,7 @@ const ContentWrapper = defineComponent({
show={this.spinning || !this.reloadRouteSwitch}
description="loading..."
size="large"
themeOverrides={this.thmeOverridesSpin}
themeOverrides={this.themeOverridesSpin}
>
<AppRequestCancelerProvider />
{this.reloadRouteSwitch ? (

View File

@ -76,7 +76,7 @@ export const getAppLocalMessages = async (
const message = {} as AppCurrentAppMessages
for (const curr of localOptions) {
const msg: AppLocalesModules = await import(`./lang/${curr.key}.ts`)
const msg: AppLocalesModules = await import(`@/locales/lang/${curr.key}.ts`)
const key = curr.key
if (key) {
@ -123,7 +123,7 @@ export const naiveLocales = (key: string) => {
*
* @returns
*
* @remak , `main.ts` , `i18n`
* @remark , `main.ts` , `i18n`
*/
export const getAppDefaultLanguage = () => {
const language = getStorage(

View File

@ -18,5 +18,7 @@
"CalculatePrecision": "Precision",
"Directive": "Directive",
"RouterDemo": "Same Level Router Demo",
"Mock": "Mock"
"Mock": "Mock",
"QRCode": "QRCode",
"SvgIcon": "SVG Icon"
}

View File

@ -18,5 +18,7 @@
"CalculatePrecision": "数字精度",
"Directive": "指令",
"RouterDemo": "页面详情模式",
"Mock": "mock 数据"
"Mock": "mock 数据",
"QRCode": "二维码",
"SvgIcon": "SVG图标"
}

View File

@ -2,7 +2,7 @@ import App from './App'
import '@/styles/base.scss'
import 'virtual:svg-icons-register' // `vite-plugin-svg-icons` 脚本
import 'virtual:svg-icons-register' // vite-plugin-svg-icons 脚本,启用 svg 雪碧图
import { setupRouter } from './router/index'
import { setupStore } from './store/index'
@ -40,8 +40,8 @@ const setupTemplate = async () => {
/**
*
* `wujie-micro`
* 注意: 此处的 `instance` `app`
* wujie-micro
* __WUJIE_MOUNT
*/
const setupWujieTemplate = async () => {
let instance: AppType<Element>
@ -62,7 +62,6 @@ const setupWujieTemplate = async () => {
/**
*
* 使, `setupTemplate`
* ,
*
* @example

View File

@ -11,7 +11,7 @@
import { permissionRouter } from './permission'
import { SETUP_ROUTER_ACTION, SUPER_ADMIN } from '@/app-config/routerConfig'
import { useSignin } from '@/store'
import { useSigning } from '@/store'
import { useVueRouter } from '@/hooks/web/index'
import { ROOT_ROUTE } from '@/app-config/appConfig'
import { setStorage } from '@/utils/cache'
@ -29,14 +29,14 @@ import type { AppMenuOption } from '@/types/modules/app'
* ,
*/
export const validRole = (meta: AppRouteMeta) => {
const { signinCallback } = storeToRefs(useSignin())
const modelRole = computed(() => signinCallback.value.role)
const { signingCallback } = storeToRefs(useSigning())
const modelRole = computed(() => signingCallback.value.role)
const { role: metaRole } = meta
if (SUPER_ADMIN?.length && SUPER_ADMIN.includes(modelRole.value)) {
return true
} else {
// 如果 role 为 undefind 或者空数组, 则认为该路由不做权限过滤
// 如果 role 为 undefined 或者空数组, 则认为该路由不做权限过滤
if (!metaRole || !metaRole?.length) {
return true
}

View File

@ -8,7 +8,7 @@ const qrcode: AppRouteRecordRaw = {
name: 'RQRCode',
component: () => import('@/views/demo/qrcode/index'),
meta: {
noLocalTitle: '二维码',
i18nKey: t('menu.QRCode'),
icon: 'other',
order: 3,
},

View File

@ -8,7 +8,7 @@ const previewSVGIcons: AppRouteRecordRaw = {
name: 'PreviewSVGIcons',
component: () => import('@/views/demo/svg-icons/index'),
meta: {
noLocalTitle: 'SVG图标',
i18nKey: t('menu.SvgIcon'),
icon: 'other',
order: 3,
},

View File

@ -29,7 +29,7 @@
import { NSpin } from 'naive-ui'
import { spinProps } from 'naive-ui'
import { globalVariableToRefs } from '@/hooks/variable/index'
import { getVariableToRefs } from '@/global-variable/index'
const GlobalSpin = defineComponent({
name: 'GlobalSpin',
@ -40,7 +40,7 @@ const GlobalSpin = defineComponent({
const overrides = {
opacitySpinning: '0.3',
}
const spinValue = globalVariableToRefs('globalSpinning')
const spinValue = getVariableToRefs('globalSpinning')
return {
spinValue,

View File

@ -1,6 +1,6 @@
## 描述
> pinia store 仓库包。存放全局公共状态。
> pinia store 仓库包。存放全局公共状态。如果不需要 pinia 但是又希望使用全局变量,可以使用 `global-variable` 管理数据。
## 约束

View File

@ -20,7 +20,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
export { useSetting } from './modules/setting/index' // import { useSetting } from '@/store' 即可使用
export { useMenu } from './modules/menu/index'
export { useSignin } from './modules/signin/index'
export { useSigning } from './modules/signing/index'
export { useKeepAlive } from './modules/keep-alive/index'
import type { App } from 'vue'

View File

@ -171,7 +171,7 @@ export const hasMenuIcon = (option: AppMenuOption) => {
return () => icon
}
/** 获取缓存的 menu key, 如果未获取到则使用 ROOTROUTE path 当作默认激活路由菜单 */
/** 获取缓存的 menu key, 如果未获取到则使用 ROOT_ROUTE path 当作默认激活路由菜单 */
export const getCatchMenuKey = () => {
const { path: rootPath } = ROOT_ROUTE
const cacheMenuKey = getStorage<AppMenuKey>(

View File

@ -108,15 +108,15 @@ export const useMenu = defineStore(
/**
*
* @param optins menu tag option(s)
* @param options menu tag option(s)
* @param isAppend true: (push), false:
*/
const setMenuTagOptions = (
optins: MenuTagOptions | MenuTagOptions[],
options: MenuTagOptions | MenuTagOptions[],
isAppend = true,
) => {
const isArray = Array.isArray(optins)
const arr = isArray ? [...optins] : [optins]
const isArray = Array.isArray(options)
const arr = isArray ? [...options] : [options]
isAppend
? menuState.menuTagOptions.push(...arr)

View File

@ -1,16 +0,0 @@
export interface SigninForm extends UnknownObjectKey {
name: string
pwd: string
}
export interface SigninCallback extends UnknownObjectKey {
role: string
name: string
avatar?: string
}
export interface SigninResponse extends UnknownObjectKey {
code: number
data: SigninCallback
message: string
}

View File

@ -16,20 +16,20 @@
*
* 使 sessionStorage
*
* signinCallback
* SigningCallback
*/
import { isEmpty } from 'lodash-es'
import { removeStorage } from '@/utils/cache'
import type {
SigninForm,
SigninCallback,
SigninResponse,
} from '@/store/modules/signin/type'
SigningForm,
SigningCallback,
SigningResponse,
} from '@/store/modules/signing/type'
export const useSignin = defineStore(
'signin',
export const useSigning = defineStore(
'signing',
() => {
const state = reactive({
/**
@ -37,22 +37,22 @@ export const useSignin = defineStore(
* ()
* role , 如果需要更改请同步更改: router/basic.tsrouter/permission.ts
*/
signinCallback: {} as SigninCallback,
signingCallback: {} as SigningCallback,
})
/**
*
* @param signinForm
* @param SigningForm
* @returns
*
* @remark 0: 登陆成功, 1: 登陆失败
*/
const signin = (signinForm: SigninForm): Promise<SigninResponse> => {
const signing = (SigningForm: SigningForm): Promise<SigningResponse> => {
return new Promise((resolve, reject) => {
if (!isEmpty(signinForm)) {
state.signinCallback = {
if (!isEmpty(SigningForm)) {
state.signingCallback = {
role: 'admin',
name: signinForm.name,
name: SigningForm.name,
avatar:
'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.navigator.png',
}
@ -60,7 +60,7 @@ export const useSignin = defineStore(
resolve({
code: 0,
message: '登陆成功',
data: state.signinCallback,
data: state.signingCallback,
})
} else {
reject({
@ -86,14 +86,14 @@ export const useSignin = defineStore(
return {
...toRefs(state),
signin,
signing,
logout,
}
},
{
persist: {
key: 'piniaSigninStore',
paths: ['signinCallback'],
key: 'piniaSigningStore',
paths: ['signingCallback'],
storage: sessionStorage,
},
},

View File

@ -0,0 +1,16 @@
export interface SigningForm extends UnknownObjectKey {
name: string
pwd: string
}
export interface SigningCallback extends UnknownObjectKey {
role: string
name: string
avatar?: string
}
export interface SigningResponse extends UnknownObjectKey {
code: number
data: SigningCallback
message: string
}

View File

@ -1,10 +1,13 @@
.n-spin-container,
.n-spin-container .n-spin-content {
// layout content 区域默认开启继承宽高避免 global lodaing 丢失高度
.r-layout-full__viewer-content .n-spin-container,
.r-layout-full__viewer-content .n-spin-container .n-spin-content {
width: 100%;
height: 100%;
}
// 拓展 AppMenu Item样式
.r-menu--app:not(.n-menu--collapsed) .n-menu-item-content.n-menu-item-content--selected:before,
.r-menu--app:not(.n-menu--collapsed) .n-menu-item-content:hover:before {
border-left: 4px solid var(--ray-theme-primary-color);
transition: border-left 0.1s;
}

View File

@ -14,33 +14,69 @@ export type EventListenerOrEventListenerObject =
| EventListener
| EventListenerObject
export type ValidteValueType =
| 'Object'
| 'Undefined'
| 'Null'
| 'Boolean'
| 'Number'
| 'String'
| 'Symbol'
| 'Function'
| 'Date'
| 'Array'
| 'RegExp'
| 'Map'
| 'Set'
| 'WeakMap'
| 'WeakSet'
| 'ArrayBuffer'
| 'DataView'
| 'Int8Array'
| 'Uint8Array'
| 'Uint8ClampedArray'
| 'Int16Array'
| 'Uint16Array'
| 'Int32Array'
| 'Uint32Array'
| 'Float32Array'
export type ValidateValueType =
| 'BigUint64Array'
| 'BigInt64Array'
| 'Float64Array'
| 'Float32Array'
| 'Uint32Array'
| 'Int32Array'
| 'Uint16Array'
| 'Int16Array'
| 'Uint8ClampedArray'
| 'Uint8Array'
| 'Int8Array'
| 'DataView'
| 'ArrayBuffer'
| 'WeakSet'
| 'WeakMap'
| 'Set'
| 'Map'
| 'Error'
| 'Date'
| 'RegExp'
| 'Object'
| 'Array'
| 'Function'
| 'BigInt'
| 'Symbol'
| 'String'
| 'Number'
| 'Boolean'
| 'Null'
| 'Undefined'
export type BasicTypes =
| undefined
| null
| boolean
| number
| string
| symbol
| bigint
| Function
| any[]
| object
| RegExp
| Date
| Error
| Map<any, any>
| Set<any>
| WeakMap<object, any>
| WeakSet<object>
| ArrayBuffer
| DataView
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array
export type WordArray = CryptoJS.lib.WordArray

View File

@ -1,6 +1,7 @@
import type {
ValidteValueType,
ValidateValueType,
DownloadAnyFileDataType,
BasicTypes,
} from '@/types/modules/utils'
/**
@ -17,7 +18,7 @@ export const getAppEnvironment = () => {
*
* @param data
*
* @returns formate binary to base64 of the image
* @returns format binary to base64 of the image
*/
export const arrayBufferToBase64Image = (data: ArrayBuffer): string | null => {
if (!data || data.byteLength) {
@ -61,9 +62,9 @@ export const downloadBase64File = (base64: string, fileName: string) => {
* @param value
* @param type
*/
export const isValueType = <T>(
export const isValueType = <T extends BasicTypes>(
value: unknown,
type: ValidteValueType,
type: ValidateValueType,
): value is T => {
const valid = Object.prototype.toString.call(value)
@ -117,32 +118,52 @@ export const downloadAnyFile = (
return new Promise<void>((resolve, reject) => {
let blobData!: Blob
if (typeof data === 'string') {
// 处理 Base64 数据
downloadBase64File(data, fileName)
resolve()
} else if (data instanceof ArrayBuffer) {
// 处理 ArrayBuffer 数据
blobData = new Blob([new Uint8Array(data)], {
type: 'application/octet-stream',
try {
if (typeof data === 'string') {
downloadBase64File(data, fileName)
resolve()
return
}
if (data instanceof ArrayBuffer) {
blobData = new Blob([new Uint8Array(data)], {
type: 'application/octet-stream',
})
} else if (data instanceof File || data instanceof Blob) {
blobData = data
} else {
reject(new Error('Unsupported data type'))
return
}
const url = URL.createObjectURL(blobData)
const link = document.createElement('a')
link.href = url
link.download = fileName
link.style.display = 'none'
const remove = () => {
URL.revokeObjectURL(url)
document.body.removeChild(link)
}
link.addEventListener('load', () => {
remove()
resolve()
})
} else {
// 处理 Blob 和 File 数据
blobData = data
link.addEventListener('error', (error) => {
remove()
reject(error)
})
document.body.appendChild(link)
link.click()
} catch (error) {
reject(error)
}
const url = URL.createObjectURL(blobData)
const link = document.createElement('a')
link.href = url
link.download = fileName
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
resolve()
})
}

View File

@ -305,7 +305,7 @@ export const completeSize = (size: number | string, unit = 'px') => {
return size.toString() + unit
} else if (
isValueType<string>(size, 'String') &&
APP_REGEX.validerCSSUnit.test(size)
APP_REGEX.validCSSUnit.test(size)
) {
return size
} else {

View File

@ -170,7 +170,7 @@ export const divide = (...args: CurrencyArguments[]) => {
/**
*
* ()
* undefind null 0
* undefined null 0
*
* @example
* distribute(0, 1) => [0]

View File

@ -11,32 +11,34 @@
import { isValueType } from '@/utils/basic'
import type { VNode, VNodeChild } from 'vue'
import type { VNode, Slot } from 'vue'
export type RenderVNodeType =
export type RenderVNodeType<T = unknown> =
| VNode
| VNodeChild
| (() => VNode)
| string
| number
| undefined
| null
| JSX.Element
| Slot<T>
export type DefaultElement = NonNullable<
Omit<RenderVNodeType, 'string' | 'number'>
export type DefaultElement<T = unknown> = NonNullable<
Omit<RenderVNodeType<T>, 'string' | 'number'>
>
export interface RenderNodeOptions<T extends DefaultElement> {
defaultElement?: T
}
export type RenderNodeReturn = ReturnType<typeof renderNode>
export function renderNode<T extends DefaultElement>(
vnode: RenderVNodeType,
options?: RenderNodeOptions<T>,
) {
if (!vnode) {
const { defaultElement } = options ?? {}
const { defaultElement = null } = options ?? {}
return typeof defaultElement === 'function'
? defaultElement

View File

@ -1,4 +1,5 @@
import './index.scss'
import {
NCard,
NLayout,

View File

@ -1,7 +1,7 @@
import './index.scss'
import { NCard, NLayout, NSpace, NInput, NButton } from 'naive-ui'
import { getWeather, getTypicode } from '@/axios/api/demo/test'
import { getWeather, getTypicode } from '@/api/demo/test'
import { useRequest, useHookPlusRequest } from '@/axios/index'
const Axios = defineComponent({

View File

@ -14,9 +14,9 @@ import RTable from '@/components/RTable/index'
import RCollapseGrid from '@/components/RCollapseGrid/index'
import { useHookPlusRequest } from '@/axios/index'
import { getPersonList } from '@/axios/api/demo/mock/person'
import { getPersonList } from '@/api/demo/mock/person'
import type { Person } from '@/axios/api/demo/mock/person'
import type { Person } from '@/api/demo/mock/person'
const MockDemo = defineComponent({
name: 'MockDemo',

View File

@ -1,11 +1,11 @@
import { NForm, NFormItem, NInput, NButton } from 'naive-ui'
import { setStorage } from '@/utils/cache'
import { useSignin } from '@/store'
import { useSigning } from '@/store'
import { useI18n } from '@/hooks/web/index'
import { APP_CATCH_KEY, ROOT_ROUTE } from '@/app-config/appConfig'
import { useVueRouter } from '@/hooks/web/index'
import { setVariable, globalVariableToRefs } from '@/hooks/variable/index'
import { setVariable, getVariableToRefs } from '@/global-variable/index'
import type { FormInst } from 'naive-ui'
@ -15,19 +15,19 @@ export default defineComponent({
const loginFormRef = ref<FormInst>()
const { t } = useI18n()
const signinStore = useSignin()
const signingStore = useSigning()
const { signin } = signinStore
const { signing } = signingStore
const { path } = ROOT_ROUTE
const globalSpinning = globalVariableToRefs('globalSpinning')
const globalSpinning = getVariableToRefs('globalSpinning')
const useSigninForm = () => ({
const useSigningForm = () => ({
name: 'Ray Admin',
pwd: '123456',
})
const { router } = useVueRouter()
const signinForm = ref(useSigninForm())
const signingForm = ref(useSigningForm())
const rules = {
name: {
@ -48,16 +48,16 @@ export default defineComponent({
if (!valid) {
setVariable('globalSpinning', true)
signin(signinForm.value)
signing(signingForm.value)
.then((res) => {
if (res.code === 0) {
setTimeout(() => {
setVariable('globalSpinning', false)
window.$message.success(`欢迎${signinForm.value.name}登陆~`)
window.$message.success(`欢迎${signingForm.value.name}登陆~`)
setStorage(APP_CATCH_KEY.token, 'tokenValue')
setStorage(APP_CATCH_KEY.signin, res.data)
setStorage(APP_CATCH_KEY.signing, res.data)
router.push(path)
}, 2 * 1000)
@ -71,7 +71,7 @@ export default defineComponent({
}
return {
signinForm,
signingForm,
loginFormRef,
handleLogin,
rules,
@ -82,16 +82,16 @@ export default defineComponent({
const { $t, globalSpinning } = this
return (
<NForm model={this.signinForm} ref="loginFormRef" rules={this.rules}>
<NForm model={this.signingForm} ref="loginFormRef" rules={this.rules}>
<NFormItem label={$t('views.login.index.Name')} path="name">
<NInput
v-model:value={this.signinForm.name}
v-model:value={this.signingForm.name}
placeholder={$t('views.login.index.NamePlaceholder')}
/>
</NFormItem>
<NFormItem label={$t('views.login.index.Password')} path="pwd">
<NInput
v-model:value={this.signinForm.pwd}
v-model:value={this.signingForm.pwd}
type="password"
showPasswordOn="click"
placeholder={$t('views.login.index.PasswordPlaceholder')}

View File

@ -19,8 +19,8 @@
"@/*": ["src/*"],
"@use-utils": ["src/utils"],
"@use-utils/*": ["src/utils/*"],
"@use-api": ["src/axios/api"],
"@use-api/*": ["src/axios/api/*"],
"@use-api": ["src/api"],
"@use-api/*": ["src/api/*"],
"@use-images": ["src/assets/images"],
"@use-images/*": ["src/assets/images"],
"@use-micro/*": ["src/micro/*"],

View File

@ -19,17 +19,17 @@ export const htmlTitlePlugin = (title: string) => {
* @remark css ,
*/
export const mixinCSSPlugin = (options?: string[]) => {
const defaultOptions = []
if (Array.isArray(options)) {
defaultOptions.push(...options)
if (!Array.isArray(options)) {
throw TypeError(
'mixinCSSPlugin: The mixinCSSPlugin argument must be an array!',
)
}
const mixisString = defaultOptions.reduce((pre, curr) => {
const mixinString = options.reduce((pre, curr) => {
const temp = `@import "${curr}";`
return (pre += temp)
}, '')
return mixisString as string
return mixinString as string
}

View File

@ -86,7 +86,7 @@ export interface LibItem {
/**
*
* whether replace old import statement, default `command === 'build'`,
* that means in vite serve default to `false`, in vite build default to `ture`
* that means in vite serve default to `false`, in vite build default to `true`
*/
replaceOldImport?: boolean
/**
@ -117,7 +117,7 @@ export interface ImpConfig {
/**
*
* By default `vite-plugin-imp` ignores all files inside node_modules.
* You can enable this option to avoid unexpected untranspiled code from third-party dependencies.
* You can enable this option to avoid unexpected untranslated code from third-party dependencies.
*
* Transpiling all the dependencies could slow down the build process, though.
* If build performance is a concern, you can explicitly transpile only some of the dependencies

View File

@ -166,7 +166,6 @@ export default function (mode: string): PluginOption[] {
'echarts',
'xlsx',
'axios',
'screenfull',
'print-js',
'clipboard',
'lodash-es',