feat: 合并1.0.7
This commit is contained in:
parent
d84ebef239
commit
2f714da4a2
2
.npmrc
2
.npmrc
@ -1,4 +1,4 @@
|
||||
registry=https://registry.npmmirror.com/
|
||||
shamefully-hoist=true
|
||||
ignore-workspace-root-check=true
|
||||
engine-strict=true
|
||||
link-workspace-packages=true
|
||||
|
@ -9,7 +9,18 @@ export function setupElegantRouter() {
|
||||
blank: 'src/layouts/blank-layout/index.vue'
|
||||
},
|
||||
customRoutes: {
|
||||
names: ['exception_403', 'exception_404', 'exception_500']
|
||||
names: [
|
||||
'exception_403',
|
||||
'exception_404',
|
||||
'exception_500',
|
||||
'document_project',
|
||||
'document_project-link',
|
||||
'document_vue',
|
||||
'document_vite',
|
||||
'document_unocss',
|
||||
'document_naive',
|
||||
'document_antd'
|
||||
]
|
||||
},
|
||||
routePathTransformer(routeName, routePath) {
|
||||
const key = routeName as RouteKey;
|
||||
|
@ -7,7 +7,7 @@ export default defineConfig(
|
||||
'vue/multi-word-component-names': [
|
||||
'warn',
|
||||
{
|
||||
ignores: ['index', 'App', '[id]']
|
||||
ignores: ['index', 'App', '[id]', '[url]']
|
||||
}
|
||||
],
|
||||
'vue/component-name-in-template-casing': [
|
||||
|
19
package.json
19
package.json
@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "snail-job",
|
||||
"type": "module",
|
||||
"version": "1.0.5",
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"version": "1.0.8",
|
||||
"description": "A flexible, reliable, and fast platform for distributed task retry and distributed task scheduling.",
|
||||
"license": "Apache",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://gitee.com/aizuda/snail-job",
|
||||
"repository": {
|
||||
"url": "https://gitee.com/aizuda/snail-job.git"
|
||||
@ -25,8 +24,8 @@
|
||||
"UnoCSS"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 18.19.0",
|
||||
"pnpm": ">= 8.15.0"
|
||||
"node": ">=18.12.0",
|
||||
"pnpm": ">=8.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build --mode prod",
|
||||
@ -61,17 +60,17 @@
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.1.7",
|
||||
"ts-md5": "1.3.1",
|
||||
"vue": "3.4.23",
|
||||
"vue": "3.4.25",
|
||||
"vue-draggable-plus": "0.4.0",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elegant-router/vue": "0.3.6",
|
||||
"@iconify/json": "2.2.203",
|
||||
"@iconify/json": "2.2.204",
|
||||
"@sa/scripts": "workspace:*",
|
||||
"@sa/uno-preset": "workspace:*",
|
||||
"@soybeanjs/eslint-config": "1.3.2",
|
||||
"@soybeanjs/eslint-config": "1.3.3",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/nprogress": "0.2.3",
|
||||
@ -88,14 +87,14 @@
|
||||
"lint-staged": "15.2.2",
|
||||
"sass": "1.75.0",
|
||||
"simple-git-hooks": "2.11.1",
|
||||
"tsx": "4.7.2",
|
||||
"tsx": "4.7.3",
|
||||
"typescript": "5.4.5",
|
||||
"unplugin-icons": "0.18.5",
|
||||
"unplugin-vue-components": "0.26.0",
|
||||
"vite": "5.2.10",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-devtools": "7.1.2",
|
||||
"vite-plugin-vue-devtools": "7.1.3",
|
||||
"vue-eslint-parser": "9.4.2",
|
||||
"vue-tsc": "2.0.14"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/axios",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/color-palette",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
20
packages/color-palette/tsconfig.json
Normal file
20
packages/color-palette/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/hooks",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -7,5 +7,5 @@ import useHookTable from './use-table';
|
||||
|
||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
||||
|
||||
export * from './use-state';
|
||||
export * from './use-signal';
|
||||
export * from './use-table';
|
||||
|
144
packages/hooks/src/use-signal.ts
Normal file
144
packages/hooks/src/use-signal.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { computed, ref, shallowRef, triggerRef } from 'vue';
|
||||
import type {
|
||||
ComputedGetter,
|
||||
DebuggerOptions,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
WritableComputedOptions,
|
||||
WritableComputedRef
|
||||
} from 'vue';
|
||||
|
||||
type Updater<T> = (value: T) => T;
|
||||
type Mutator<T> = (value: T) => void;
|
||||
|
||||
/**
|
||||
* Signal is a reactive value that can be set, updated or mutated
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const count = useSignal(0);
|
||||
*
|
||||
* // `watchEffect`
|
||||
* watchEffect(() => {
|
||||
* console.log(count());
|
||||
* });
|
||||
*
|
||||
* // watch
|
||||
* watch(count, value => {
|
||||
* console.log(value);
|
||||
* });
|
||||
*
|
||||
* // useComputed
|
||||
* const double = useComputed(() => count() * 2);
|
||||
* const writeableDouble = useComputed({
|
||||
* get: () => count() * 2,
|
||||
* set: value => count.set(value / 2)
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface Signal<T> {
|
||||
(): Readonly<T>;
|
||||
/**
|
||||
* Set the value of the signal
|
||||
*
|
||||
* It recommend use `set` for primitive values
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
set(value: T): void;
|
||||
/**
|
||||
* Update the value of the signal using an updater function
|
||||
*
|
||||
* It recommend use `update` for non-primitive values, only the first level of the object will be reactive.
|
||||
*
|
||||
* @param updater
|
||||
*/
|
||||
update(updater: Updater<T>): void;
|
||||
/**
|
||||
* Mutate the value of the signal using a mutator function
|
||||
*
|
||||
* this action will call `triggerRef`, so the value will be tracked on `watchEffect`.
|
||||
*
|
||||
* It recommend use `mutate` for non-primitive values, all levels of the object will be reactive.
|
||||
*
|
||||
* @param mutator
|
||||
*/
|
||||
mutate(mutator: Mutator<T>): void;
|
||||
/**
|
||||
* Get the reference of the signal
|
||||
*
|
||||
* Sometimes it can be useful to make `v-model` work with the signal
|
||||
*
|
||||
* ```vue
|
||||
* <template>
|
||||
* <input v-model="model.count" />
|
||||
* </template>;
|
||||
*
|
||||
* <script setup lang="ts">
|
||||
* const state = useSignal({ count: 0 }, { useRef: true });
|
||||
*
|
||||
* const model = state.getRef();
|
||||
* </script>
|
||||
* ```
|
||||
*/
|
||||
getRef(): Readonly<ShallowRef<Readonly<T>>>;
|
||||
}
|
||||
|
||||
export interface ReadonlySignal<T> {
|
||||
(): Readonly<T>;
|
||||
}
|
||||
|
||||
export interface SignalOptions {
|
||||
/**
|
||||
* Whether to use `ref` to store the value
|
||||
*
|
||||
* @default false use `sharedRef` to store the value
|
||||
*/
|
||||
useRef?: boolean;
|
||||
}
|
||||
|
||||
export function useSignal<T>(initialValue: T, options?: SignalOptions): Signal<T> {
|
||||
const { useRef } = options || {};
|
||||
|
||||
const state = useRef ? (ref(initialValue) as Ref<T>) : shallowRef(initialValue);
|
||||
|
||||
return createSignal(state);
|
||||
}
|
||||
|
||||
export function useComputed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ReadonlySignal<T>;
|
||||
export function useComputed<T>(options: WritableComputedOptions<T>, debugOptions?: DebuggerOptions): Signal<T>;
|
||||
export function useComputed<T>(
|
||||
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
|
||||
debugOptions?: DebuggerOptions
|
||||
) {
|
||||
const isGetter = typeof getterOrOptions === 'function';
|
||||
|
||||
const computedValue = computed(getterOrOptions as any, debugOptions);
|
||||
|
||||
if (isGetter) {
|
||||
return () => computedValue.value as ReadonlySignal<T>;
|
||||
}
|
||||
|
||||
return createSignal(computedValue);
|
||||
}
|
||||
|
||||
function createSignal<T>(state: ShallowRef<T> | WritableComputedRef<T>): Signal<T> {
|
||||
const signal = () => state.value;
|
||||
|
||||
signal.set = (value: T) => {
|
||||
state.value = value;
|
||||
};
|
||||
|
||||
signal.update = (updater: Updater<T>) => {
|
||||
state.value = updater(state.value);
|
||||
};
|
||||
|
||||
signal.mutate = (mutator: Mutator<T>) => {
|
||||
mutator(state.value);
|
||||
triggerRef(state);
|
||||
};
|
||||
|
||||
signal.getRef = () => state as Readonly<ShallowRef<Readonly<T>>>;
|
||||
|
||||
return signal;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/materials",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/scripts",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"bin": {
|
||||
"sa": "./bin.ts"
|
||||
},
|
||||
@ -13,7 +13,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@soybeanjs/changelog": "0.3.22",
|
||||
"@soybeanjs/changelog": "0.3.23",
|
||||
"bumpp": "9.4.0",
|
||||
"c12": "1.10.0",
|
||||
"cac": "6.7.14",
|
||||
@ -21,7 +21,7 @@
|
||||
"enquirer": "2.4.1",
|
||||
"execa": "8.0.1",
|
||||
"kolorist": "1.8.0",
|
||||
"npm-check-updates": "16.14.18",
|
||||
"npm-check-updates": "16.14.20",
|
||||
"rimraf": "5.0.5"
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ const defaultOptions: CliOption = {
|
||||
['style', 'Changes that do not affect the meaning of the code'],
|
||||
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
|
||||
['perf', 'A code change that improves performance'],
|
||||
['optimize', 'A code change that optimizes code quality'],
|
||||
['test', 'Adding missing tests or correcting existing tests'],
|
||||
['build', 'Changes that affect the build system or external dependencies'],
|
||||
['ci', 'Changes to our CI configuration files and scripts'],
|
||||
@ -27,6 +28,7 @@ const defaultOptions: CliOption = {
|
||||
],
|
||||
gitCommitScopes: [
|
||||
['projects', 'project'],
|
||||
['packages', 'packages'],
|
||||
['components', 'components'],
|
||||
['hooks', 'hook functions'],
|
||||
['utils', 'utils functions'],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/uno-preset",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/utils",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
1023
pnpm-lock.yaml
1023
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
60
src/assets/svg-icon/full-logo.svg
Normal file
60
src/assets/svg-icon/full-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
@ -36,7 +36,11 @@ const icon = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonIcon :tooltip-content="collapsed ? $t('icon.expand') : $t('icon.collapse')" tooltip-placement="bottom-start">
|
||||
<ButtonIcon
|
||||
:tooltip-content="collapsed ? $t('icon.expand') : $t('icon.collapse')"
|
||||
tooltip-placement="bottom-start"
|
||||
:z-index="99"
|
||||
>
|
||||
<SvgIcon :icon="icon" />
|
||||
</ButtonIcon>
|
||||
</template>
|
||||
|
@ -15,7 +15,7 @@ const icon = computed(() => (props.pin ? 'mdi-pin-off' : 'mdi-pin'));
|
||||
|
||||
<template>
|
||||
<ButtonIcon
|
||||
:tooltip-content="pin ? $t('icon.pin') : $t('icon.unpin')"
|
||||
:tooltip-content="pin ? $t('icon.unpin') : $t('icon.pin')"
|
||||
tooltip-placement="bottom-start"
|
||||
:z-index="100"
|
||||
>
|
||||
|
@ -18,6 +18,7 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { menus } = setupMixMenuContext();
|
||||
|
||||
const layoutMode = computed(() => {
|
||||
const vertical: LayoutMode = 'vertical';
|
||||
@ -65,7 +66,7 @@ function getSiderWidth() {
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed) {
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
|
||||
w += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
@ -77,14 +78,12 @@ function getSiderCollapsedWidth() {
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed) {
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
|
||||
w += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
setupMixMenuContext();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,4 +1,47 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useContext } from '@sa/hooks';
|
||||
import { useMixMenu } from '../hooks';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
|
||||
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
|
||||
|
||||
function useMixMenu() {
|
||||
const route = useRoute();
|
||||
const routeStore = useRouteStore();
|
||||
|
||||
const activeFirstLevelMenuKey = ref('');
|
||||
|
||||
function setActiveFirstLevelMenuKey(key: string) {
|
||||
activeFirstLevelMenuKey.value = key;
|
||||
}
|
||||
|
||||
function getActiveFirstLevelMenuKey() {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
const [firstLevelRouteName] = routeName.split('_');
|
||||
|
||||
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
||||
}
|
||||
|
||||
const menus = computed(
|
||||
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
getActiveFirstLevelMenuKey();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
getActiveFirstLevelMenuKey,
|
||||
menus
|
||||
};
|
||||
}
|
||||
|
@ -2,11 +2,10 @@
|
||||
import { computed } from 'vue';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { $t } from '@/locales';
|
||||
import { useMixMenu } from '../../hooks';
|
||||
import { useMixMenuContext } from '../../context';
|
||||
import FirstLevelMenu from './first-level-menu.vue';
|
||||
import BaseMenu from './base-menu.vue';
|
||||
|
||||
@ -16,16 +15,15 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
||||
const { activeFirstLevelMenuKey, setActiveFirstLevelMenuKey, getActiveFirstLevelMenuKey } = useMixMenu();
|
||||
const { menus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey, getActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||
|
||||
const siderInverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const menus = computed(() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []);
|
||||
const hasMenus = computed(() => menus.value.length > 0);
|
||||
|
||||
const showDrawer = computed(() => (drawerVisible.value && menus.value.length) || appStore.mixSiderFixed);
|
||||
const showDrawer = computed(() => hasMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
@ -50,7 +48,7 @@ function handleResetActiveMenu() {
|
||||
</FirstLevelMenu>
|
||||
<div
|
||||
class="relative h-full transition-width-300"
|
||||
:style="{ width: appStore.mixSiderFixed ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||
:style="{ width: appStore.mixSiderFixed && hasMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||
>
|
||||
<DarkModeContainer
|
||||
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
||||
|
@ -242,7 +242,16 @@ const local: App.I18n.Schema = {
|
||||
403: 'No Permission',
|
||||
404: 'Page Not Found',
|
||||
500: 'Server Error',
|
||||
'iframe-page': 'Iframe',
|
||||
home: 'Home',
|
||||
document: 'Document',
|
||||
document_project: 'Project Document',
|
||||
'document_project-link': 'Project Document(External Link)',
|
||||
document_vue: 'Vue Document',
|
||||
document_vite: 'Vite Document',
|
||||
document_unocss: 'UnoCSS Document',
|
||||
document_naive: 'Naive UI Document',
|
||||
document_antd: 'Ant Design Vue Document',
|
||||
'user-center': 'User Center',
|
||||
about: 'About',
|
||||
function: 'System Function',
|
||||
|
@ -242,7 +242,16 @@ const local: App.I18n.Schema = {
|
||||
403: '无权限',
|
||||
404: '页面不存在',
|
||||
500: '服务器错误',
|
||||
'iframe-page': '外链页面',
|
||||
home: '首页',
|
||||
document: '文档',
|
||||
document_project: '项目文档',
|
||||
'document_project-link': '项目文档(外链)',
|
||||
document_vue: 'Vue文档',
|
||||
document_vite: 'Vite文档',
|
||||
document_unocss: 'UnoCSS文档',
|
||||
document_naive: 'Naive UI文档',
|
||||
document_antd: 'Ant Design Vue文档',
|
||||
'user-center': '个人中心',
|
||||
about: '关于',
|
||||
function: '系统功能',
|
||||
|
@ -18,6 +18,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
403: () => import("@/views/_builtin/403/index.vue"),
|
||||
404: () => import("@/views/_builtin/404/index.vue"),
|
||||
500: () => import("@/views/_builtin/500/index.vue"),
|
||||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||
login: () => import("@/views/_builtin/login/index.vue"),
|
||||
about: () => import("@/views/about/index.vue"),
|
||||
"function_hide-child_one": () => import("@/views/function/hide-child/one/index.vue"),
|
||||
|
@ -191,6 +191,19 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
order: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'iframe-page',
|
||||
path: '/iframe-page/:url',
|
||||
component: 'layout.base$view.iframe-page',
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'iframe-page',
|
||||
i18nKey: 'route.iframe-page',
|
||||
constant: true,
|
||||
hideInMenu: true,
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'job',
|
||||
path: '/job',
|
||||
|
@ -147,6 +147,14 @@ const routeMap: RouteMap = {
|
||||
"exception_403": "/exception/403",
|
||||
"exception_404": "/exception/404",
|
||||
"exception_500": "/exception/500",
|
||||
"document": "/document",
|
||||
"document_project": "/document/project",
|
||||
"document_project-link": "/document/project-link",
|
||||
"document_vue": "/document/vue",
|
||||
"document_vite": "/document/vite",
|
||||
"document_unocss": "/document/unocss",
|
||||
"document_naive": "/document/naive",
|
||||
"document_antd": "/document/antd",
|
||||
"403": "/403",
|
||||
"404": "/404",
|
||||
"500": "/500",
|
||||
@ -163,6 +171,7 @@ const routeMap: RouteMap = {
|
||||
"function_toggle-auth": "/function/toggle-auth",
|
||||
"group": "/group",
|
||||
"home": "/home",
|
||||
"iframe-page": "/iframe-page/:url",
|
||||
"job": "/job",
|
||||
"job_batch": "/job/batch",
|
||||
"job_task": "/job/task",
|
||||
|
@ -52,6 +52,115 @@ const customRoutes: CustomRoute[] = [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'document',
|
||||
path: '/document',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'document',
|
||||
i18nKey: 'route.document',
|
||||
order: 2,
|
||||
icon: 'mdi:file-document-multiple-outline'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'document_antd',
|
||||
path: '/document/antd',
|
||||
component: 'view.iframe-page',
|
||||
props: {
|
||||
url: 'https://antdv.com/components/overview-cn'
|
||||
},
|
||||
meta: {
|
||||
title: 'document_antd',
|
||||
i18nKey: 'route.document_antd',
|
||||
order: 7,
|
||||
icon: 'logos:ant-design'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'document_naive',
|
||||
path: '/document/naive',
|
||||
component: 'view.iframe-page',
|
||||
props: {
|
||||
url: 'https://www.naiveui.com/zh-CN/os-theme/docs/introduction'
|
||||
},
|
||||
meta: {
|
||||
title: 'document_naive',
|
||||
i18nKey: 'route.document_naive',
|
||||
order: 6,
|
||||
icon: 'logos:naiveui'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'document_project',
|
||||
path: '/document/project',
|
||||
component: 'view.iframe-page',
|
||||
props: {
|
||||
url: 'https://docs.soybeanjs.cn/zh'
|
||||
},
|
||||
meta: {
|
||||
title: 'document_project',
|
||||
i18nKey: 'route.document_project',
|
||||
order: 1,
|
||||
localIcon: 'logo'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'document_project-link',
|
||||
path: '/document/project-link',
|
||||
component: 'view.iframe-page',
|
||||
meta: {
|
||||
title: 'document_project-link',
|
||||
i18nKey: 'route.document_project-link',
|
||||
order: 2,
|
||||
localIcon: 'logo',
|
||||
href: 'https://docs.soybeanjs.cn/zh'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'document_unocss',
|
||||
path: '/document/unocss',
|
||||
component: 'view.iframe-page',
|
||||
props: {
|
||||
url: 'https://unocss.dev/'
|
||||
},
|
||||
meta: {
|
||||
title: 'document_unocss',
|
||||
i18nKey: 'route.document_unocss',
|
||||
order: 5,
|
||||
icon: 'logos:unocss'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'document_vite',
|
||||
path: '/document/vite',
|
||||
component: 'view.iframe-page',
|
||||
props: {
|
||||
url: 'https://cn.vitejs.dev/'
|
||||
},
|
||||
meta: {
|
||||
title: 'document_vite',
|
||||
i18nKey: 'route.document_vite',
|
||||
order: 4,
|
||||
icon: 'logos:vitejs'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'document_vue',
|
||||
path: '/document/vue',
|
||||
component: 'view.iframe-page',
|
||||
props: {
|
||||
url: 'https://cn.vuejs.org/'
|
||||
},
|
||||
meta: {
|
||||
title: 'document_vue',
|
||||
i18nKey: 'route.document_vue',
|
||||
order: 3,
|
||||
icon: 'logos:vue'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { effectScope, onScopeDispose, ref, watch } from 'vue';
|
||||
import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { breakpointsTailwind, useBreakpoints, useTitle } from '@vueuse/core';
|
||||
import { breakpointsTailwind, useBreakpoints, useEventListener, useTitle } from '@vueuse/core';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
import { router } from '@/router';
|
||||
@ -22,7 +22,11 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
||||
const { bool: fullContent, toggle: toggleFullContent } = useBoolean();
|
||||
const { bool: contentXScrollable, setBool: setContentXScrollable } = useBoolean();
|
||||
const { bool: siderCollapse, setBool: setSiderCollapse, toggle: toggleSiderCollapse } = useBoolean();
|
||||
const { bool: mixSiderFixed, setBool: setMixSiderFixed, toggle: toggleMixSiderFixed } = useBoolean();
|
||||
const {
|
||||
bool: mixSiderFixed,
|
||||
setBool: setMixSiderFixed,
|
||||
toggle: toggleMixSiderFixed
|
||||
} = useBoolean(localStg.get('mixSiderFixed') === 'Y');
|
||||
|
||||
/** Is mobile layout */
|
||||
const isMobile = breakpoints.smaller('sm');
|
||||
@ -83,9 +87,26 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
||||
isMobile,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
setSiderCollapse(true);
|
||||
// backup theme setting before is mobile
|
||||
localStg.set('backupThemeSettingBeforeIsMobile', {
|
||||
layout: themeStore.layout.mode,
|
||||
siderCollapse: siderCollapse.value
|
||||
});
|
||||
|
||||
themeStore.setThemeLayout('vertical');
|
||||
setSiderCollapse(true);
|
||||
} else {
|
||||
// when is not mobile, recover the backup theme setting
|
||||
const backup = localStg.get('backupThemeSettingBeforeIsMobile');
|
||||
|
||||
if (backup) {
|
||||
nextTick(() => {
|
||||
themeStore.setThemeLayout(backup.layout);
|
||||
setSiderCollapse(backup.siderCollapse);
|
||||
|
||||
localStg.remove('backupThemeSettingBeforeIsMobile');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@ -107,6 +128,11 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
||||
});
|
||||
});
|
||||
|
||||
// cache mixSiderFixed
|
||||
useEventListener(window, 'beforeunload', () => {
|
||||
localStg.set('mixSiderFixed', mixSiderFixed.value ? 'Y' : 'N');
|
||||
});
|
||||
|
||||
/** On scope dispose */
|
||||
onScopeDispose(() => {
|
||||
scope.stop();
|
||||
|
@ -16,17 +16,24 @@ export function getAllTabs(tabs: App.Global.Tab[], homeTab?: App.Global.Tab) {
|
||||
|
||||
const filterHomeTabs = tabs.filter(tab => tab.id !== homeTab.id);
|
||||
|
||||
const fixedTabs = filterHomeTabs
|
||||
.filter(tab => tab.fixedIndex !== undefined)
|
||||
.sort((a, b) => a.fixedIndex! - b.fixedIndex!);
|
||||
const fixedTabs = filterHomeTabs.filter(isFixedTab).sort((a, b) => a.fixedIndex! - b.fixedIndex!);
|
||||
|
||||
const remainTabs = filterHomeTabs.filter(tab => tab.fixedIndex === undefined);
|
||||
const remainTabs = filterHomeTabs.filter(tab => !isFixedTab(tab));
|
||||
|
||||
const allTabs = [homeTab, ...fixedTabs, ...remainTabs];
|
||||
|
||||
return updateTabsLabel(allTabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is fixed tab
|
||||
*
|
||||
* @param tab
|
||||
*/
|
||||
function isFixedTab(tab: App.Global.Tab) {
|
||||
return tab.fixedIndex !== undefined && tab.fixedIndex !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tab id by route
|
||||
*
|
||||
@ -177,7 +184,7 @@ export function extractTabsByAllRoutes(router: Router, tabs: App.Global.Tab[]) {
|
||||
* @param tabs
|
||||
*/
|
||||
export function getFixedTabs(tabs: App.Global.Tab[]) {
|
||||
return tabs.filter(tab => tab.fixedIndex !== undefined);
|
||||
return tabs.filter(isFixedTab);
|
||||
}
|
||||
|
||||
/**
|
||||
|
2
src/typings/app.d.ts
vendored
2
src/typings/app.d.ts
vendored
@ -198,7 +198,7 @@ declare namespace App {
|
||||
/** The tab route full path */
|
||||
fullPath: string;
|
||||
/** The tab fixed index */
|
||||
fixedIndex?: number;
|
||||
fixedIndex?: number | null;
|
||||
/**
|
||||
* Tab icon
|
||||
*
|
||||
|
2
src/typings/router.d.ts
vendored
2
src/typings/router.d.ts
vendored
@ -58,7 +58,7 @@ declare module 'vue-router' {
|
||||
/** By default, the same route path will use one tab, if set to true, it will use multiple tabs */
|
||||
multiTab?: boolean;
|
||||
/** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */
|
||||
fixedIndexInTab?: number;
|
||||
fixedIndexInTab?: number | null;
|
||||
/** if set query parameters, it will be automatically carried when entering the route */
|
||||
query?: Record<string, string>;
|
||||
}
|
||||
|
7
src/typings/storage.d.ts
vendored
7
src/typings/storage.d.ts
vendored
@ -14,6 +14,8 @@ declare namespace StorageType {
|
||||
lang: App.I18n.LangType;
|
||||
/** The token */
|
||||
token: string;
|
||||
/** Fixed sider with mix-menu */
|
||||
mixSiderFixed: CommonType.YesOrNo;
|
||||
/** The refresh token */
|
||||
refreshToken: string;
|
||||
/** The version */
|
||||
@ -34,5 +36,10 @@ declare namespace StorageType {
|
||||
overrideThemeFlag: string;
|
||||
/** The global tabs */
|
||||
globalTabs: App.Global.Tab[];
|
||||
/** The backup theme setting before is mobile */
|
||||
backupThemeSettingBeforeIsMobile: {
|
||||
layout: UnionKey.ThemeLayoutMode;
|
||||
siderCollapse: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
25
src/views/_builtin/iframe-page/[url].vue
Normal file
25
src/views/_builtin/iframe-page/[url].vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { onActivated, onMounted } from 'vue';
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
onMounted(() => {
|
||||
console.log('mounted');
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
console.log('activated');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<iframe id="iframePage" class="size-full" :src="url"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user