mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-21 02:56:38 +08:00
refactor(projects): perf code
This commit is contained in:
parent
187098136e
commit
f91ef30bd5
@ -8,21 +8,21 @@ defineOptions({
|
|||||||
<svg class="size-full">
|
<svg class="size-full">
|
||||||
<defs>
|
<defs>
|
||||||
<symbol id="geometry-left" viewBox="0 0 214 36">
|
<symbol id="geometry-left" viewBox="0 0 214 36">
|
||||||
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z"></path>
|
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="geometry-right" viewBox="0 0 214 36">
|
<symbol id="geometry-right" viewBox="0 0 214 36">
|
||||||
<use xlink:href="#geometry-left"></use>
|
<use xlink:href="#geometry-left" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<clipPath>
|
<clipPath>
|
||||||
<rect width="100%" height="100%" x="0"></rect>
|
<rect width="100%" height="100%" x="0" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
<svg width="51%" height="100%">
|
<svg width="51%" height="100%">
|
||||||
<use xlink:href="#geometry-left" width="214" height="36" fill="currentColor"></use>
|
<use xlink:href="#geometry-left" width="214" height="36" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<g transform="scale(-1, 1)">
|
<g transform="scale(-1, 1)">
|
||||||
<svg width="51%" height="100%" x="-100%" y="0">
|
<svg width="51%" height="100%" x="-100%" y="0">
|
||||||
<use xlink:href="#geometry-right" width="214" height="36" fill="currentColor"></use>
|
<use xlink:href="#geometry-right" width="214" height="36" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -23,7 +23,7 @@ function handleClick() {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
||||||
></path>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -11,14 +11,15 @@ export function presetSoybeanAdmin(): Preset<Theme> {
|
|||||||
'flex-center': 'flex justify-center items-center',
|
'flex-center': 'flex justify-center items-center',
|
||||||
'flex-x-center': 'flex justify-center',
|
'flex-x-center': 'flex justify-center',
|
||||||
'flex-y-center': 'flex items-center',
|
'flex-y-center': 'flex items-center',
|
||||||
'flex-vertical': 'flex flex-col',
|
'flex-col': 'flex flex-col',
|
||||||
'flex-vertical-center': 'flex-center flex-col',
|
'flex-col-center': 'flex-center flex-col',
|
||||||
'flex-vertical-stretch': 'flex-vertical items-stretch',
|
'flex-col-stretch': 'flex-col items-stretch',
|
||||||
'i-flex-center': 'inline-flex justify-center items-center',
|
'i-flex-center': 'inline-flex justify-center items-center',
|
||||||
'i-flex-x-center': 'inline-flex justify-center',
|
'i-flex-x-center': 'inline-flex justify-center',
|
||||||
'i-flex-y-center': 'inline-flex items-center',
|
'i-flex-y-center': 'inline-flex items-center',
|
||||||
'i-flex-vertical': 'inline-flex flex-col',
|
'i-flex-col': 'flex-col inline-flex',
|
||||||
'i-flex-vertical-stretch': 'i-flex-vertical items-stretch',
|
'i-flex-col-center': 'flex-col i-flex-center',
|
||||||
|
'i-flex-col-stretch': 'i-flex-col items-stretch',
|
||||||
'flex-1-hidden': 'flex-1 overflow-hidden'
|
'flex-1-hidden': 'flex-1 overflow-hidden'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@ function refresh() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace :align="itemAlign" wrap justify="end" class="<sm:w-200px">
|
<NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px">
|
||||||
<slot name="prefix"></slot>
|
<slot name="prefix"></slot>
|
||||||
<slot name="default">
|
<slot name="default">
|
||||||
<NButton size="small" ghost type="primary" @click="add">
|
<NButton size="small" ghost type="primary" @click="add">
|
||||||
|
@ -29,7 +29,7 @@ const icon = computed(() => iconMap[props.type]);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="size-full min-h-520px flex-vertical-center gap-24px overflow-hidden">
|
<div class="size-full min-h-520px flex-col-center gap-24px overflow-hidden">
|
||||||
<div class="flex text-400px text-primary">
|
<div class="flex text-400px text-primary">
|
||||||
<SvgIcon :local-icon="icon" />
|
<SvgIcon :local-icon="icon" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,8 +14,8 @@ defineProps<Props>();
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ButtonIcon :key="String(full)" :tooltip-content="full ? $t('icon.fullscreenExit') : $t('icon.fullscreen')">
|
<ButtonIcon :key="String(full)" :tooltip-content="full ? $t('icon.fullscreenExit') : $t('icon.fullscreen')">
|
||||||
<IconGridiconsFullscreenExit v-if="full" />
|
<icon-gridicons-fullscreen-exit v-if="full" />
|
||||||
<IconGridiconsFullscreen v-else />
|
<icon-gridicons-fullscreen v-else />
|
||||||
</ButtonIcon>
|
</ButtonIcon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ defineProps<Props>();
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ButtonIcon :tooltip-content="$t('icon.reload')">
|
<ButtonIcon :tooltip-content="$t('icon.reload')">
|
||||||
<IconAntDesignReloadOutlined :class="{ 'animate-spin animate-duration-750': loading }" />
|
<icon-ant-design-reload-outlined :class="{ 'animate-spin animate-duration-750': loading }" />
|
||||||
</ButtonIcon>
|
</ButtonIcon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ defineOptions({ name: 'SystemLogo' });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<IconLocalLogo />
|
<icon-local-logo />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -17,17 +17,17 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const bsWrap = ref<HTMLElement>();
|
const bsWrapper = ref<HTMLElement>();
|
||||||
const bsContent = ref<HTMLElement>();
|
const bsContent = ref<HTMLElement>();
|
||||||
const { width: wrapWidth } = useElementSize(bsWrap);
|
const { width: wrapWidth } = useElementSize(bsWrapper);
|
||||||
const { width, height } = useElementSize(bsContent);
|
const { width, height } = useElementSize(bsContent);
|
||||||
|
|
||||||
const instance = ref<BScroll>();
|
const instance = ref<BScroll>();
|
||||||
const isScrollY = computed(() => Boolean(props.options.scrollY));
|
const isScrollY = computed(() => Boolean(props.options.scrollY));
|
||||||
|
|
||||||
function initBetterScroll() {
|
function initBetterScroll() {
|
||||||
if (!bsWrap.value) return;
|
if (!bsWrapper.value) return;
|
||||||
instance.value = new BScroll(bsWrap.value, props.options);
|
instance.value = new BScroll(bsWrapper.value, props.options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh BS when scroll element size changed
|
// refresh BS when scroll element size changed
|
||||||
@ -43,7 +43,7 @@ defineExpose({ instance });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="bsWrap" class="h-full text-left">
|
<div ref="bsWrapper" class="h-full text-left">
|
||||||
<div ref="bsContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
|
<div ref="bsContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,7 +48,7 @@ const cls = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- define component: Button -->
|
<!-- define component start: Button -->
|
||||||
<DefineButton v-slot="{ $slots, className }">
|
<DefineButton v-slot="{ $slots, className }">
|
||||||
<NButton quaternary :class="className">
|
<NButton quaternary :class="className">
|
||||||
<div class="flex-center gap-8px">
|
<div class="flex-center gap-8px">
|
||||||
@ -56,8 +56,8 @@ const cls = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NButton>
|
</NButton>
|
||||||
</DefineButton>
|
</DefineButton>
|
||||||
|
<!-- define component end: Button -->
|
||||||
|
|
||||||
<!-- template -->
|
|
||||||
<NTooltip v-if="tooltipContent" :placement="tooltipPlacement" :z-index="98">
|
<NTooltip v-if="tooltipContent" :placement="tooltipPlacement" :z-index="98">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<Button :class-name="cls" v-bind="$attrs">
|
<Button :class-name="cls" v-bind="$attrs">
|
||||||
|
@ -7,7 +7,7 @@ defineOptions({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="size-full min-h-520px flex-vertical-center gap-24px overflow-hidden">
|
<div class="size-full min-h-520px flex-col-center gap-24px overflow-hidden">
|
||||||
<div class="flex text-400px text-primary">
|
<div class="flex text-400px text-primary">
|
||||||
<SvgIcon local-icon="expectation" />
|
<SvgIcon local-icon="expectation" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@ const darkColor = computed(() => getColorPalette(props.themeColor, 6));
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute-lt z-1 size-full overflow-hidden">
|
<div class="absolute-lt z-1 size-full overflow-hidden">
|
||||||
<div class="absolute -right-300px -top-900px <sm:(-right-100px -top-1170px)">
|
<div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
|
||||||
<svg height="1337" width="1337">
|
<svg height="1337" width="1337">
|
||||||
<defs>
|
<defs>
|
||||||
<path
|
<path
|
||||||
@ -36,7 +36,7 @@ const darkColor = computed(() => getColorPalette(props.themeColor, 6));
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute -bottom-400px -left-200px <sm:(-bottom-760px -left-100px)">
|
<div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
|
||||||
<svg height="896" width="967.8852157128662">
|
<svg height="896" width="967.8852157128662">
|
||||||
<defs>
|
<defs>
|
||||||
<path
|
<path
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ref } from 'vue';
|
import { ref, toValue } from 'vue';
|
||||||
|
import type { ComputedRef, Ref } from 'vue';
|
||||||
import type { FormInst } from 'naive-ui';
|
import type { FormInst } from 'naive-ui';
|
||||||
import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg';
|
import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
@ -50,11 +51,30 @@ export function useFormRules() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** create a rule for confirming the password */
|
||||||
|
function createConfirmPwdRule(pwd: string | Ref<string> | ComputedRef<string>) {
|
||||||
|
const confirmPwdRule: App.Global.FormRule[] = [
|
||||||
|
{ required: true, message: $t('form.confirmPwd.required') },
|
||||||
|
{
|
||||||
|
asyncValidator: (rule, value) => {
|
||||||
|
if (value.trim() !== '' && value !== toValue(pwd)) {
|
||||||
|
return Promise.reject(rule.message);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
message: $t('form.confirmPwd.invalid'),
|
||||||
|
trigger: 'input'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return confirmPwdRule;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
patternRules,
|
patternRules,
|
||||||
formRules,
|
formRules,
|
||||||
defaultRequiredRule,
|
defaultRequiredRule,
|
||||||
createRequiredRule
|
createRequiredRule,
|
||||||
|
createConfirmPwdRule
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/hooks/common/icon.ts
Normal file
10
src/hooks/common/icon.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { useSvgIconRender } from '@sa/hooks';
|
||||||
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
|
|
||||||
|
export function useSvgIcon() {
|
||||||
|
const { SvgIconVNode } = useSvgIconRender(SvgIcon);
|
||||||
|
|
||||||
|
return {
|
||||||
|
SvgIconVNode
|
||||||
|
};
|
||||||
|
}
|
@ -10,7 +10,7 @@ import GlobalTab from '../modules/global-tab/index.vue';
|
|||||||
import GlobalContent from '../modules/global-content/index.vue';
|
import GlobalContent from '../modules/global-content/index.vue';
|
||||||
import GlobalFooter from '../modules/global-footer/index.vue';
|
import GlobalFooter from '../modules/global-footer/index.vue';
|
||||||
import ThemeDrawer from '../modules/theme-drawer/index.vue';
|
import ThemeDrawer from '../modules/theme-drawer/index.vue';
|
||||||
import { setupMixMenuContext } from '../hooks/use-mix-menu';
|
import { setupMixMenuContext } from '../context';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'BaseLayout'
|
name: 'BaseLayout'
|
||||||
|
4
src/layouts/context/index.ts
Normal file
4
src/layouts/context/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { useContext } from '@sa/hooks';
|
||||||
|
import { useMixMenu } from '../hooks';
|
||||||
|
|
||||||
|
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
|
@ -1,6 +1,5 @@
|
|||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useContext } from '@sa/hooks';
|
|
||||||
import { useRouteStore } from '@/store/modules/route';
|
import { useRouteStore } from '@/store/modules/route';
|
||||||
|
|
||||||
export function useMixMenu() {
|
export function useMixMenu() {
|
||||||
@ -43,5 +42,3 @@ export function useMixMenu() {
|
|||||||
menus
|
menus
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
|
|
@ -26,14 +26,14 @@ function handleClickMenu(key: RouteKey) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NBreadcrumb v-if="themeStore.header.breadcrumb.visible">
|
<NBreadcrumb v-if="themeStore.header.breadcrumb.visible">
|
||||||
<!-- define component: BreadcrumbContent -->
|
<!-- define component start: BreadcrumbContent -->
|
||||||
<DefineBreadcrumbContent v-slot="{ breadcrumb }">
|
<DefineBreadcrumbContent v-slot="{ breadcrumb }">
|
||||||
<div class="i-flex-y-center align-middle">
|
<div class="i-flex-y-center align-middle">
|
||||||
<component :is="breadcrumb.icon" v-if="themeStore.header.breadcrumb.showIcon" class="mr-4px text-icon" />
|
<component :is="breadcrumb.icon" v-if="themeStore.header.breadcrumb.showIcon" class="mr-4px text-icon" />
|
||||||
{{ breadcrumb.label }}
|
{{ breadcrumb.label }}
|
||||||
</div>
|
</div>
|
||||||
</DefineBreadcrumbContent>
|
</DefineBreadcrumbContent>
|
||||||
<!-- define component: BreadcrumbContent -->
|
<!-- define component end: BreadcrumbContent -->
|
||||||
|
|
||||||
<NBreadcrumbItem v-for="item in routeStore.breadcrumbs" :key="item.key">
|
<NBreadcrumbItem v-for="item in routeStore.breadcrumbs" :key="item.key">
|
||||||
<NDropdown v-if="item.options?.length" :options="item.options" @select="handleClickMenu">
|
<NDropdown v-if="item.options?.length" :options="item.options" @select="handleClickMenu">
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { VNode } from 'vue';
|
import type { VNode } from 'vue';
|
||||||
import { useSvgIconRender } from '@sa/hooks';
|
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import { useSvgIcon } from '@/hooks/common/icon';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'UserAvatar'
|
name: 'UserAvatar'
|
||||||
@ -13,7 +12,7 @@ defineOptions({
|
|||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const { routerPushByKey, toLogin } = useRouterPush();
|
const { routerPushByKey, toLogin } = useRouterPush();
|
||||||
const { SvgIconVNode } = useSvgIconRender(SvgIcon);
|
const { SvgIconVNode } = useSvgIcon();
|
||||||
|
|
||||||
function loginOrRegister() {
|
function loginOrRegister() {
|
||||||
toLogin();
|
toLogin();
|
||||||
|
@ -8,7 +8,7 @@ import HorizontalMenu from '../global-menu/base-menu.vue';
|
|||||||
import GlobalLogo from '../global-logo/index.vue';
|
import GlobalLogo from '../global-logo/index.vue';
|
||||||
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
||||||
import GlobalSearch from '../global-search/index.vue';
|
import GlobalSearch from '../global-search/index.vue';
|
||||||
import { useMixMenuContext } from '../../hooks/use-mix-menu';
|
import { useMixMenuContext } from '../../context';
|
||||||
import ThemeButton from './components/theme-button.vue';
|
import ThemeButton from './components/theme-button.vue';
|
||||||
import UserAvatar from './components/user-avatar.vue';
|
import UserAvatar from './components/user-avatar.vue';
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ function handleClickMixMenu(menu: App.Global.Menu) {
|
|||||||
<!-- define component: MixMenuItem -->
|
<!-- define component: MixMenuItem -->
|
||||||
<DefineMixMenuItem v-slot="{ label, icon, active, isMini }">
|
<DefineMixMenuItem v-slot="{ label, icon, active, isMini }">
|
||||||
<div
|
<div
|
||||||
class="mx-4px mb-6px flex-vertical-center cursor-pointer rounded-8px bg-transparent px-4px py-8px transition-300 hover:bg-[rgb(0,0,0,0.08)]"
|
class="mx-4px mb-6px flex-col-center cursor-pointer rounded-8px bg-transparent px-4px py-8px transition-300 hover:bg-[rgb(0,0,0,0.08)]"
|
||||||
:class="{
|
:class="{
|
||||||
'text-primary selected-mix-menu': active,
|
'text-primary selected-mix-menu': active,
|
||||||
'text-white:65 hover:text-white': inverted,
|
'text-white:65 hover:text-white': inverted,
|
||||||
@ -74,9 +74,9 @@ function handleClickMixMenu(menu: App.Global.Menu) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DefineMixMenuItem>
|
</DefineMixMenuItem>
|
||||||
|
<!-- define component end: MixMenuItem -->
|
||||||
|
|
||||||
<!-- template -->
|
<div class="h-full flex-col-stretch flex-1-hidden">
|
||||||
<div class="h-full flex-vertical-stretch flex-1-hidden">
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<SimpleScrollbar>
|
<SimpleScrollbar>
|
||||||
<MixMenuItem
|
<MixMenuItem
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { useMixMenuContext } from '../../hooks/use-mix-menu';
|
import { useMixMenuContext } from '../../context';
|
||||||
import FirstLevelMenu from './first-level-menu.vue';
|
import FirstLevelMenu from './first-level-menu.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
@ -5,7 +5,7 @@ import { useAppStore } from '@/store/modules/app';
|
|||||||
import { useRouteStore } from '@/store/modules/route';
|
import { useRouteStore } from '@/store/modules/route';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { useMixMenu } from '../../hooks/use-mix-menu';
|
import { useMixMenu } from '../../hooks';
|
||||||
import FirstLevelMenu from './first-level-menu.vue';
|
import FirstLevelMenu from './first-level-menu.vue';
|
||||||
import BaseMenu from './base-menu.vue';
|
import BaseMenu from './base-menu.vue';
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ function handleResetActiveMenu() {
|
|||||||
:style="{ width: appStore.mixSiderFixed ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
:style="{ width: appStore.mixSiderFixed ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||||
>
|
>
|
||||||
<DarkModeContainer
|
<DarkModeContainer
|
||||||
class="absolute-lt h-full flex-vertical-stretch nowrap-hidden shadow-sm transition-all-300"
|
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
||||||
:inverted="siderInverted"
|
:inverted="siderInverted"
|
||||||
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||||
>
|
>
|
||||||
|
@ -31,6 +31,6 @@ defineOptions({ name: 'SearchFooter' });
|
|||||||
}
|
}
|
||||||
|
|
||||||
.operate-item {
|
.operate-item {
|
||||||
--at-apply: mr-6px p-2px text-20px;
|
--uno: mr-6px p-2px text-20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -23,7 +23,7 @@ const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DarkModeContainer class="size-full flex-vertical-stretch shadow-sider" :inverted="darkMenu">
|
<DarkModeContainer class="size-full flex-col-stretch shadow-sider" :inverted="darkMenu">
|
||||||
<GlobalLogo
|
<GlobalLogo
|
||||||
v-if="showLogo"
|
v-if="showLogo"
|
||||||
:show-title="!appStore.siderCollapse"
|
:show-title="!appStore.siderCollapse"
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { VNode } from 'vue';
|
import type { VNode } from 'vue';
|
||||||
import { useSvgIconRender } from '@sa/hooks';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { useTabStore } from '@/store/modules/tab';
|
import { useTabStore } from '@/store/modules/tab';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import { useSvgIcon } from '@/hooks/common/icon';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ContextMenu'
|
name: 'ContextMenu'
|
||||||
@ -28,7 +27,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
const visible = defineModel<boolean>('visible');
|
const visible = defineModel<boolean>('visible');
|
||||||
|
|
||||||
const { removeTab, clearTabs, clearLeftTabs, clearRightTabs } = useTabStore();
|
const { removeTab, clearTabs, clearLeftTabs, clearRightTabs } = useTabStore();
|
||||||
const { SvgIconVNode } = useSvgIconRender(SvgIcon);
|
const { SvgIconVNode } = useSvgIcon();
|
||||||
|
|
||||||
type DropdownOption = {
|
type DropdownOption = {
|
||||||
key: App.Global.DropdownKey;
|
key: App.Global.DropdownKey;
|
||||||
|
@ -199,7 +199,7 @@ init();
|
|||||||
:x="dropdown.x"
|
:x="dropdown.x"
|
||||||
:y="dropdown.y"
|
:y="dropdown.y"
|
||||||
@update:visible="handleDropdownVisible"
|
@update:visible="handleDropdownVisible"
|
||||||
></ContextMenu>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -80,7 +80,7 @@ function handleChangeMode(mode: UnionKey.ThemeLayoutMode) {
|
|||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div
|
<div
|
||||||
class="h-64px w-96px gap-6px rd-4px p-6px shadow dark:shadow-coolGray-5"
|
class="h-64px w-96px gap-6px rd-4px p-6px shadow dark:shadow-coolGray-5"
|
||||||
:class="[key.includes('vertical') ? 'flex' : 'flex-vertical']"
|
:class="[key.includes('vertical') ? 'flex' : 'flex-col']"
|
||||||
>
|
>
|
||||||
<slot :name="key"></slot>
|
<slot :name="key"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,7 +26,7 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NDivider>{{ $t('theme.themeSchema.title') }}</NDivider>
|
<NDivider>{{ $t('theme.themeSchema.title') }}</NDivider>
|
||||||
<div class="flex-vertical-stretch gap-16px">
|
<div class="flex-col-stretch gap-16px">
|
||||||
<div class="i-flex-center">
|
<div class="i-flex-center">
|
||||||
<NTabs
|
<NTabs
|
||||||
:key="themeStore.themeScheme"
|
:key="themeStore.themeScheme"
|
||||||
@ -50,20 +50,13 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sider-inverted-enter-active {
|
.sider-inverted-enter-active,
|
||||||
height: 22px;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sider-inverted-leave-active {
|
.sider-inverted-leave-active {
|
||||||
height: 22px;
|
--uno: h-22px transition-all-300;
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sider-inverted-enter-from,
|
.sider-inverted-enter-from,
|
||||||
.sider-inverted-leave-to {
|
.sider-inverted-leave-to {
|
||||||
transform: translateX(20px);
|
--uno: translate-x-20px opacity-0 h-0;
|
||||||
opacity: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -60,7 +60,7 @@ const themeStore = useThemeStore();
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vertical-wrapper {
|
.vertical-wrapper {
|
||||||
--uno: flex-1 flex-vertical gap-6px;
|
--uno: flex-1 flex-col gap-6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.horizontal-wrapper {
|
.horizontal-wrapper {
|
||||||
|
@ -21,14 +21,14 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NDivider>{{ $t('theme.pageFunTitle') }}</NDivider>
|
<NDivider>{{ $t('theme.pageFunTitle') }}</NDivider>
|
||||||
<TransitionGroup tag="div" name="setting-list" class="flex-vertical-stretch gap-12px">
|
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||||
<SettingItem key="1" :label="$t('theme.scrollMode.title')">
|
<SettingItem key="1" :label="$t('theme.scrollMode.title')">
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="themeStore.layout.scrollMode"
|
v-model:value="themeStore.layout.scrollMode"
|
||||||
:options="translateOptions(themeScrollModeOptions)"
|
:options="translateOptions(themeScrollModeOptions)"
|
||||||
size="small"
|
size="small"
|
||||||
class="w-120px"
|
class="w-120px"
|
||||||
></NSelect>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
<SettingItem key="1-1" :label="$t('theme.page.animate')">
|
<SettingItem key="1-1" :label="$t('theme.page.animate')">
|
||||||
<NSwitch v-model:value="themeStore.page.animate" />
|
<NSwitch v-model:value="themeStore.page.animate" />
|
||||||
@ -108,16 +108,15 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
|
|||||||
.setting-list-move,
|
.setting-list-move,
|
||||||
.setting-list-enter-active,
|
.setting-list-enter-active,
|
||||||
.setting-list-leave-active {
|
.setting-list-leave-active {
|
||||||
transition: all 0.3s ease-in-out;
|
--uno: transition-all-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-list-enter-from,
|
.setting-list-enter-from,
|
||||||
.setting-list-leave-to {
|
.setting-list-leave-to {
|
||||||
opacity: 0;
|
--uno: opacity-0 -translate-x-30px;
|
||||||
transform: translateX(-30px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-list-leave-active {
|
.setting-list-leave-active {
|
||||||
position: absolute;
|
--uno: absolute;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -35,7 +35,7 @@ const swatches: string[] = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NDivider>{{ $t('theme.themeColor.title') }}</NDivider>
|
<NDivider>{{ $t('theme.themeColor.title') }}</NDivider>
|
||||||
<div class="flex-vertical-stretch gap-12px">
|
<div class="flex-col-stretch gap-12px">
|
||||||
<SettingItem v-for="(_, key) in themeStore.themeColors" :key="key" :label="$t(`theme.themeColor.${key}`)">
|
<SettingItem v-for="(_, key) in themeStore.themeColors" :key="key" :label="$t(`theme.themeColor.${key}`)">
|
||||||
<template v-if="key === 'info'" #suffix>
|
<template v-if="key === 'info'" #suffix>
|
||||||
<NCheckbox v-model:checked="themeStore.isInfoFollowPrimary">
|
<NCheckbox v-model:checked="themeStore.isInfoFollowPrimary">
|
||||||
|
@ -393,6 +393,10 @@ const local: App.I18n.Schema = {
|
|||||||
required: 'Please enter password',
|
required: 'Please enter password',
|
||||||
invalid: 'Password format is incorrect'
|
invalid: 'Password format is incorrect'
|
||||||
},
|
},
|
||||||
|
confirmPwd: {
|
||||||
|
required: 'Please enter password again',
|
||||||
|
invalid: 'The two passwords are inconsistent'
|
||||||
|
},
|
||||||
code: {
|
code: {
|
||||||
required: 'Please enter verification code',
|
required: 'Please enter verification code',
|
||||||
invalid: 'Verification code format is incorrect'
|
invalid: 'Verification code format is incorrect'
|
||||||
|
@ -393,6 +393,10 @@ const local: App.I18n.Schema = {
|
|||||||
required: '请输入密码',
|
required: '请输入密码',
|
||||||
invalid: '密码格式不正确'
|
invalid: '密码格式不正确'
|
||||||
},
|
},
|
||||||
|
confirmPwd: {
|
||||||
|
required: '请输入确认密码',
|
||||||
|
invalid: '两次输入密码不一致'
|
||||||
|
},
|
||||||
code: {
|
code: {
|
||||||
required: '请输入验证码',
|
required: '请输入验证码',
|
||||||
invalid: '验证码格式不正确'
|
invalid: '验证码格式不正确'
|
||||||
|
@ -32,7 +32,7 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
|||||||
*
|
*
|
||||||
* @param duration Duration time
|
* @param duration Duration time
|
||||||
*/
|
*/
|
||||||
async function reloadPage(duration = 0) {
|
async function reloadPage(duration = 300) {
|
||||||
setReloadFlag(false);
|
setReloadFlag(false);
|
||||||
|
|
||||||
if (duration > 0) {
|
if (duration > 0) {
|
||||||
|
@ -81,7 +81,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
// 2. store user info
|
// 2. store user info
|
||||||
localStg.set('userInfo', info);
|
localStg.set('userInfo', info);
|
||||||
|
|
||||||
// 3. update auth route
|
// 3. update store
|
||||||
token.value = loginToken.token;
|
token.value = loginToken.token;
|
||||||
Object.assign(userInfo, info);
|
Object.assign(userInfo, info);
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-cache routes by route key
|
* Re cache routes by route key
|
||||||
*
|
*
|
||||||
* @param routeKey
|
* @param routeKey
|
||||||
*/
|
*/
|
||||||
@ -117,7 +117,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-cache routes by route keys
|
* Re cache routes by route keys
|
||||||
*
|
*
|
||||||
* @param routeKeys
|
* @param routeKeys
|
||||||
*/
|
*/
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw, _RouteRecordBase } from 'vue-router';
|
import type { RouteLocationNormalizedLoaded, RouteRecordRaw, _RouteRecordBase } from 'vue-router';
|
||||||
import type { ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
|
import type { ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
|
||||||
import { useSvgIconRender } from '@sa/hooks';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import { useSvgIcon } from '@/hooks/common/icon';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter auth routes by roles
|
* Filter auth routes by roles
|
||||||
@ -130,7 +129,7 @@ export function updateLocaleOfGlobalMenus(menus: App.Global.Menu[]) {
|
|||||||
* @param route
|
* @param route
|
||||||
*/
|
*/
|
||||||
function getGlobalMenuByBaseRoute(route: RouteLocationNormalizedLoaded | ElegantConstRoute) {
|
function getGlobalMenuByBaseRoute(route: RouteLocationNormalizedLoaded | ElegantConstRoute) {
|
||||||
const { SvgIconVNode } = useSvgIconRender(SvgIcon);
|
const { SvgIconVNode } = useSvgIcon();
|
||||||
|
|
||||||
const { name, path } = route;
|
const { name, path } = route;
|
||||||
const { title, i18nKey, icon = import.meta.env.VITE_MENU_ICON, localIcon } = route.meta ?? {};
|
const { title, i18nKey, icon = import.meta.env.VITE_MENU_ICON, localIcon } = route.meta ?? {};
|
||||||
|
@ -78,7 +78,7 @@ export function getTabByRoute(route: App.Global.TabRoute) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The vue router will automatically merge the metas of all matched items, and the icons here may be affected by other
|
* The vue router will automatically merge the meta of all matched items, and the icons here may be affected by other
|
||||||
* matching items, so they need to be processed separately
|
* matching items, so they need to be processed separately
|
||||||
*
|
*
|
||||||
* @param route
|
* @param route
|
||||||
|
1
src/typings/app.d.ts
vendored
1
src/typings/app.d.ts
vendored
@ -567,6 +567,7 @@ declare namespace App {
|
|||||||
userName: FormMsg;
|
userName: FormMsg;
|
||||||
phone: FormMsg;
|
phone: FormMsg;
|
||||||
pwd: FormMsg;
|
pwd: FormMsg;
|
||||||
|
confirmPwd: FormMsg;
|
||||||
code: FormMsg;
|
code: FormMsg;
|
||||||
email: FormMsg;
|
email: FormMsg;
|
||||||
};
|
};
|
||||||
|
22
src/typings/union-key.d.ts
vendored
22
src/typings/union-key.d.ts
vendored
@ -3,11 +3,11 @@ declare namespace UnionKey {
|
|||||||
/**
|
/**
|
||||||
* The login module
|
* The login module
|
||||||
*
|
*
|
||||||
* - Pwd-login: password login
|
* - pwd-login: password login
|
||||||
* - Code-login: phone code login
|
* - code-login: phone code login
|
||||||
* - Register: register
|
* - register: register
|
||||||
* - Reset-pwd: reset password
|
* - reset-pwd: reset password
|
||||||
* - Bind-wechat: bind wechat
|
* - bind-wechat: bind wechat
|
||||||
*/
|
*/
|
||||||
type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat';
|
type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat';
|
||||||
|
|
||||||
@ -17,10 +17,10 @@ declare namespace UnionKey {
|
|||||||
/**
|
/**
|
||||||
* The layout mode
|
* The layout mode
|
||||||
*
|
*
|
||||||
* - Vertical: the vertical menu in left
|
* - vertical: the vertical menu in left
|
||||||
* - Horizontal: the horizontal menu in top
|
* - horizontal: the horizontal menu in top
|
||||||
* - Vertical-mix: two vertical mixed menus in left
|
* - vertical-mix: two vertical mixed menus in left
|
||||||
* - Horizontal-mix: the vertical menu in left and horizontal menu in top
|
* - horizontal-mix: the vertical menu in left and horizontal menu in top
|
||||||
*/
|
*/
|
||||||
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';
|
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';
|
||||||
|
|
||||||
@ -38,8 +38,8 @@ declare namespace UnionKey {
|
|||||||
/**
|
/**
|
||||||
* Tab mode
|
* Tab mode
|
||||||
*
|
*
|
||||||
* - Chrome: chrome style
|
* - chrome: chrome style
|
||||||
* - Button: button style
|
* - button: button style
|
||||||
*/
|
*/
|
||||||
type ThemeTabMode = import('@sa/materials').PageTabMode;
|
type ThemeTabMode = import('@sa/materials').PageTabMode;
|
||||||
|
|
||||||
|
@ -25,23 +25,19 @@ const appStore = useAppStore();
|
|||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
interface LoginModule {
|
interface LoginModule {
|
||||||
key: UnionKey.LoginModule;
|
|
||||||
label: string;
|
label: string;
|
||||||
component: Component;
|
component: Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules: LoginModule[] = [
|
const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
||||||
{ key: 'pwd-login', label: loginModuleRecord['pwd-login'], component: PwdLogin },
|
'pwd-login': { label: loginModuleRecord['pwd-login'], component: PwdLogin },
|
||||||
{ key: 'code-login', label: loginModuleRecord['code-login'], component: CodeLogin },
|
'code-login': { label: loginModuleRecord['code-login'], component: CodeLogin },
|
||||||
{ key: 'register', label: loginModuleRecord.register, component: Register },
|
register: { label: loginModuleRecord.register, component: Register },
|
||||||
{ key: 'reset-pwd', label: loginModuleRecord['reset-pwd'], component: ResetPwd },
|
'reset-pwd': { label: loginModuleRecord['reset-pwd'], component: ResetPwd },
|
||||||
{ key: 'bind-wechat', label: loginModuleRecord['bind-wechat'], component: BindWechat }
|
'bind-wechat': { label: loginModuleRecord['bind-wechat'], component: BindWechat }
|
||||||
];
|
};
|
||||||
|
|
||||||
const activeModule = computed(() => {
|
const activeModule = computed(() => moduleMap[props.module]);
|
||||||
const findItem = modules.find(item => item.key === props.module);
|
|
||||||
return findItem || modules[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
const bgThemeColor = computed(() =>
|
const bgThemeColor = computed(() =>
|
||||||
themeStore.darkMode ? getColorPalette(themeStore.themeColor, 7) : themeStore.themeColor
|
themeStore.darkMode ? getColorPalette(themeStore.themeColor, 7) : themeStore.themeColor
|
||||||
@ -60,15 +56,15 @@ const bgColor = computed(() => {
|
|||||||
<div class="relative size-full flex-center overflow-hidden" :style="{ backgroundColor: bgColor }">
|
<div class="relative size-full flex-center overflow-hidden" :style="{ backgroundColor: bgColor }">
|
||||||
<WaveBg :theme-color="bgThemeColor" />
|
<WaveBg :theme-color="bgThemeColor" />
|
||||||
<NCard :bordered="false" class="relative z-4 w-auto rd-12px">
|
<NCard :bordered="false" class="relative z-4 w-auto rd-12px">
|
||||||
<div class="w-400px <sm:w-300px">
|
<div class="w-400px lt-sm:w-300px">
|
||||||
<header class="flex-y-center justify-between">
|
<header class="flex-y-center justify-between">
|
||||||
<SystemLogo class="text-64px text-primary <sm:text-48px" />
|
<SystemLogo class="text-64px text-primary lt-sm:text-48px" />
|
||||||
<h3 class="text-28px text-primary font-500 <sm:text-22px">{{ $t('system.title') }}</h3>
|
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
|
||||||
<div class="i-flex-vertical">
|
<div class="i-flex-col">
|
||||||
<ThemeSchemaSwitch
|
<ThemeSchemaSwitch
|
||||||
:theme-schema="themeStore.themeScheme"
|
:theme-schema="themeStore.themeScheme"
|
||||||
:show-tooltip="false"
|
:show-tooltip="false"
|
||||||
class="text-20px <sm:text-18px"
|
class="text-20px lt-sm:text-18px"
|
||||||
@switch="themeStore.toggleThemeScheme"
|
@switch="themeStore.toggleThemeScheme"
|
||||||
/>
|
/>
|
||||||
<LangSwitch
|
<LangSwitch
|
||||||
|
@ -25,7 +25,8 @@ const model: FormModel = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
||||||
const { formRules } = useFormRules(); // inside computed to make locale reactive
|
// inside computed to make locale reactive, if not apply i18n, you can define it without computed
|
||||||
|
const { formRules } = useFormRules();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userName: formRules.userName,
|
userName: formRules.userName,
|
||||||
@ -48,13 +49,16 @@ async function handleSubmit() {
|
|||||||
<NInput
|
<NInput
|
||||||
v-model:value="model.password"
|
v-model:value="model.password"
|
||||||
type="password"
|
type="password"
|
||||||
|
show-password-on="mousedown"
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NSpace vertical :size="24">
|
<NSpace vertical :size="24">
|
||||||
<div class="flex-y-center justify-between">
|
<div class="flex-y-center justify-between">
|
||||||
<NCheckbox>{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
<NCheckbox>{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
||||||
<NButton quaternary>{{ $t('page.login.pwdLogin.forgetPassword') }}</NButton>
|
<NButton quaternary @click="toggleLoginModule('reset-pwd')">
|
||||||
|
{{ $t('page.login.pwdLogin.forgetPassword') }}
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
<NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit">
|
<NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit">
|
||||||
{{ $t('common.confirm') }}
|
{{ $t('common.confirm') }}
|
||||||
|
@ -1,11 +1,81 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed, reactive } from 'vue';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ResetPwd'
|
name: 'ResetPwd'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { toggleLoginModule } = useRouterPush();
|
||||||
|
const { formRef, validate } = useNaiveForm();
|
||||||
|
|
||||||
|
interface FormModel {
|
||||||
|
phone: string;
|
||||||
|
code: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model: FormModel = reactive({
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
type RuleRecord = Partial<Record<keyof FormModel, App.Global.FormRule[]>>;
|
||||||
|
|
||||||
|
const rules = computed<RuleRecord>(() => {
|
||||||
|
const { formRules, createConfirmPwdRule } = useFormRules();
|
||||||
|
|
||||||
|
return {
|
||||||
|
phone: formRules.phone,
|
||||||
|
password: formRules.pwd,
|
||||||
|
confirmPassword: createConfirmPwdRule(model.password)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
await validate();
|
||||||
|
// request to reset password
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div></div>
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||||
|
<NFormItem path="phone">
|
||||||
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="code">
|
||||||
|
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="password">
|
||||||
|
<NInput
|
||||||
|
v-model:value="model.password"
|
||||||
|
type="password"
|
||||||
|
show-password-on="mousedown"
|
||||||
|
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="confirmPassword">
|
||||||
|
<NInput
|
||||||
|
v-model:value="model.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
show-password-on="mousedown"
|
||||||
|
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
<NSpace vertical :size="18" class="w-full">
|
||||||
|
<NButton type="primary" size="large" round block @click="handleSubmit">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</NButton>
|
||||||
|
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
||||||
|
{{ $t('page.login.common.back') }}
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</NForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>one</div>
|
<LookForward />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>three</div>
|
<LookForward />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>two</div>
|
<LookForward />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -9,7 +9,7 @@ defineOptions({
|
|||||||
<template>
|
<template>
|
||||||
<NCard :title="$t('page.home.creativity')" :bordered="false" size="small" class="h-full card-wrapper">
|
<NCard :title="$t('page.home.creativity')" :bordered="false" size="small" class="h-full card-wrapper">
|
||||||
<div class="h-full flex-center">
|
<div class="h-full flex-center">
|
||||||
<IconLocalBanner class="text-400px text-primary sm:text-320px" />
|
<icon-local-banner class="text-400px text-primary sm:text-320px" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useEcharts } from '@/hooks/chart/use-echarts';
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'LineChart'
|
name: 'LineChart'
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useEcharts } from '@/hooks/chart/use-echarts';
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'PieChart'
|
name: 'PieChart'
|
||||||
|
@ -227,7 +227,7 @@ init();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="wrapperRef" class="flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
|
<div ref="wrapperRef" class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||||
<NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
<NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<TableHeaderOperation
|
<TableHeaderOperation
|
||||||
|
@ -128,7 +128,7 @@ function edit(id: number) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-500px flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
|
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||||
<RoleSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
|
<RoleSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
|
||||||
<NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
<NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
|
@ -158,7 +158,7 @@ function edit(id: number) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-500px flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
|
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||||
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
|
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
|
||||||
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
|
Loading…
Reference in New Issue
Block a user