mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	Merge branch 'main' of github.com:pure-admin/vue-pure-admin into gitee
This commit is contained in:
		
						commit
						b9c1d8e351
					
				@ -1,3 +1,23 @@
 | 
			
		||||
# 4.1.0 (2023-05-12)
 | 
			
		||||
 | 
			
		||||
### 🎫 Feat
 | 
			
		||||
 | 
			
		||||
- Add a `demo` example combined with `Form` for the functional pop-up box component
 | 
			
		||||
- wrapper `el-col` component of `element-plus`
 | 
			
		||||
- Add `beforeCancel` and `beforeSure` callbacks to the functional popup component, which can suspend the closing of the popup
 | 
			
		||||
- Improve `System Management-Department Management` page
 | 
			
		||||
- Optimize `PureTableBar` component, add drag and drop function for column display
 | 
			
		||||
 | 
			
		||||
### 🐞 Bug fixes
 | 
			
		||||
 | 
			
		||||
- Fix the problem that the page cache still exists when you click the tab to reload after turning on `keepAlive`
 | 
			
		||||
- Fix the problem that the left menu will flicker after refreshing the tab in the mixed mode menu
 | 
			
		||||
 | 
			
		||||
### 🍏 Perf
 | 
			
		||||
 | 
			
		||||
- Optimize home page layout
 | 
			
		||||
- Dependency update to `vue3.3+` and remove `unplugin-vue-define-options` plugin
 | 
			
		||||
 | 
			
		||||
# 4.0.0 (2023-05-09)
 | 
			
		||||
 | 
			
		||||
[View 4.0.0 version optimization details](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -1,3 +1,23 @@
 | 
			
		||||
# 4.1.0 (2023-05-12)
 | 
			
		||||
 | 
			
		||||
### 🎫 Feat
 | 
			
		||||
 | 
			
		||||
- Add a `demo` example combined with `Form` for the functional pop-up box component
 | 
			
		||||
- wrapper `el-col` component of `element-plus`
 | 
			
		||||
- Add `beforeCancel` and `beforeSure` callbacks to the functional popup component, which can suspend the closing of the popup
 | 
			
		||||
- Improve `System Management-Department Management` page
 | 
			
		||||
- Optimize `PureTableBar` component, add drag and drop function for column display
 | 
			
		||||
 | 
			
		||||
### 🐞 Bug fixes
 | 
			
		||||
 | 
			
		||||
- Fix the problem that the page cache still exists when you click the tab to reload after turning on `keepAlive`
 | 
			
		||||
- Fix the problem that the left menu will flicker after refreshing the tab in the mixed mode menu
 | 
			
		||||
 | 
			
		||||
### 🍏 Perf
 | 
			
		||||
 | 
			
		||||
- Optimize home page layout
 | 
			
		||||
- Dependency update to `vue3.3+` and remove `unplugin-vue-define-options` plugin
 | 
			
		||||
 | 
			
		||||
# 4.0.0 (2023-05-09)
 | 
			
		||||
 | 
			
		||||
[View 4.0.0 version optimization details](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,23 @@
 | 
			
		||||
# 4.1.0 (2023-05-12)
 | 
			
		||||
 | 
			
		||||
### 🎫 Feat
 | 
			
		||||
 | 
			
		||||
- 函数式弹框组件添加结合 `Form` 的 `demo` 示例
 | 
			
		||||
- 封装 `element-plus` 的 `el-col` 组件
 | 
			
		||||
- 函数式弹框组件添加 `beforeCancel` 和 `beforeSure` 回调,可暂停弹框的关闭
 | 
			
		||||
- 完善 `系统管理-部门管理` 页面
 | 
			
		||||
- 优化 `PureTableBar` 组件,列展示添加拖拽功能
 | 
			
		||||
 | 
			
		||||
### 🐞 Bug fixes
 | 
			
		||||
 | 
			
		||||
- 修复开启 `keepAlive` 后点击标签页的重新加载,页面缓存还存在的问题
 | 
			
		||||
- 修复混合模式菜单下刷新页签后左侧菜单会闪烁一下的问题
 | 
			
		||||
 | 
			
		||||
### 🍏 Perf
 | 
			
		||||
 | 
			
		||||
- 优化首页布局
 | 
			
		||||
- 依赖更新到 `vue3.3+` 以及删除 `unplugin-vue-define-options` 插件
 | 
			
		||||
 | 
			
		||||
# 4.0.0 (2023-05-09)
 | 
			
		||||
 | 
			
		||||
[查看 4.0.0 版本优化细节](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,7 @@ const include = [
 | 
			
		||||
  "element-resize-detector",
 | 
			
		||||
  "@amap/amap-jsapi-loader",
 | 
			
		||||
  "el-table-infinite-scroll",
 | 
			
		||||
  "vue-waterfall-plugin-next",
 | 
			
		||||
  "@wangeditor/editor-for-vue",
 | 
			
		||||
  "vuedraggable/src/vuedraggable"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@ menus:
 | 
			
		||||
  hsdialog: Dialog Components
 | 
			
		||||
  hsmessage: Message Tips Components
 | 
			
		||||
  hsvideo: Video Components
 | 
			
		||||
  hswaterfall: Waterfall Components
 | 
			
		||||
  hsmap: Map Components
 | 
			
		||||
  hsdraggable: Draggable Components
 | 
			
		||||
  hssplitPane: Split Pane
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@ menus:
 | 
			
		||||
  hsdialog: 函数式弹框组件
 | 
			
		||||
  hsmessage: 消息提示组件
 | 
			
		||||
  hsvideo: 视频组件
 | 
			
		||||
  hswaterfall: 瀑布流组件
 | 
			
		||||
  hsmap: 地图组件
 | 
			
		||||
  hsdraggable: 拖拽组件
 | 
			
		||||
  hssplitPane: 切割面板
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								package.json
									
									
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "vue-pure-admin",
 | 
			
		||||
  "version": "4.0.0",
 | 
			
		||||
  "version": "4.1.0",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
 | 
			
		||||
@ -30,14 +30,14 @@
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@amap/amap-jsapi-loader": "^1.0.1",
 | 
			
		||||
    "@howdyjs/mouse-menu": "^2.0.7",
 | 
			
		||||
    "@logicflow/core": "^1.2.5",
 | 
			
		||||
    "@logicflow/extension": "^1.2.5",
 | 
			
		||||
    "@logicflow/core": "^1.2.6",
 | 
			
		||||
    "@logicflow/extension": "^1.2.6",
 | 
			
		||||
    "@pureadmin/descriptions": "^1.1.1",
 | 
			
		||||
    "@pureadmin/table": "^2.1.0",
 | 
			
		||||
    "@pureadmin/utils": "^1.8.9",
 | 
			
		||||
    "@vueuse/core": "^10.1.2",
 | 
			
		||||
    "@vueuse/motion": "2.0.0-beta.12",
 | 
			
		||||
    "@wangeditor/editor": "^5.1.21",
 | 
			
		||||
    "@wangeditor/editor": "^5.1.23",
 | 
			
		||||
    "@wangeditor/editor-for-vue": "^5.1.12",
 | 
			
		||||
    "animate.css": "^4.1.1",
 | 
			
		||||
    "axios": "^1.4.0",
 | 
			
		||||
@ -61,16 +61,17 @@
 | 
			
		||||
    "qs": "^6.11.1",
 | 
			
		||||
    "responsive-storage": "^2.2.0",
 | 
			
		||||
    "sortablejs": "^1.15.0",
 | 
			
		||||
    "swiper": "^9.3.0",
 | 
			
		||||
    "swiper": "^9.3.1",
 | 
			
		||||
    "typeit": "^8.7.1",
 | 
			
		||||
    "v-contextmenu": "3.0.0",
 | 
			
		||||
    "vue": "^3.3.1",
 | 
			
		||||
    "vue": "^3.3.2",
 | 
			
		||||
    "vue-i18n": "^9.2.2",
 | 
			
		||||
    "vue-json-pretty": "^2.2.4",
 | 
			
		||||
    "vue-pdf-embed": "^1.1.6",
 | 
			
		||||
    "vue-router": "^4.1.6",
 | 
			
		||||
    "vue-router": "^4.2.0",
 | 
			
		||||
    "vue-types": "^5.0.2",
 | 
			
		||||
    "vue-virtual-scroller": "2.0.0-beta.7",
 | 
			
		||||
    "vue-waterfall-plugin-next": "^2.2.1",
 | 
			
		||||
    "vue3-danmaku": "1.4.0-beta.1",
 | 
			
		||||
    "vuedraggable": "^4.1.0",
 | 
			
		||||
    "xgplayer": "^3.0.2",
 | 
			
		||||
@ -95,7 +96,7 @@
 | 
			
		||||
    "@types/sortablejs": "^1.15.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.59.5",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.59.5",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.2.2",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.2.3",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.0.1",
 | 
			
		||||
    "@vue/eslint-config-prettier": "^7.1.0",
 | 
			
		||||
    "@vue/eslint-config-typescript": "^11.0.3",
 | 
			
		||||
@ -131,7 +132,7 @@
 | 
			
		||||
    "stylelint-scss": "^5.0.0",
 | 
			
		||||
    "svgo": "^3.0.2",
 | 
			
		||||
    "tailwindcss": "^3.3.2",
 | 
			
		||||
    "terser": "^5.17.1",
 | 
			
		||||
    "terser": "^5.17.3",
 | 
			
		||||
    "typescript": "^5.0.4",
 | 
			
		||||
    "vite": "^4.3.5",
 | 
			
		||||
    "vite-plugin-cdn-import": "^0.3.5",
 | 
			
		||||
@ -139,8 +140,8 @@
 | 
			
		||||
    "vite-plugin-mock": "^2.9.6",
 | 
			
		||||
    "vite-plugin-remove-console": "^2.1.1",
 | 
			
		||||
    "vite-svg-loader": "^4.0.0",
 | 
			
		||||
    "vue-eslint-parser": "^9.2.1",
 | 
			
		||||
    "vue-tsc": "^1.6.4"
 | 
			
		||||
    "vue-eslint-parser": "^9.3.0",
 | 
			
		||||
    "vue-tsc": "^1.6.5"
 | 
			
		||||
  },
 | 
			
		||||
  "pnpm": {
 | 
			
		||||
    "peerDependencyRules": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1121
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1121
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "Version": "4.0.0",
 | 
			
		||||
  "Version": "4.1.0",
 | 
			
		||||
  "Title": "PureAdmin",
 | 
			
		||||
  "FixedHeader": true,
 | 
			
		||||
  "HiddenSideBar": false,
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,17 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { isFunction } from "@pureadmin/utils";
 | 
			
		||||
import {
 | 
			
		||||
  type DialogOptions,
 | 
			
		||||
  type ButtonProps,
 | 
			
		||||
  type EventType,
 | 
			
		||||
  closeDialog,
 | 
			
		||||
  dialogStore,
 | 
			
		||||
  closeDialog
 | 
			
		||||
  type EventType,
 | 
			
		||||
  type ButtonProps,
 | 
			
		||||
  type DialogOptions
 | 
			
		||||
} from "./index";
 | 
			
		||||
import { ref, computed } from "vue";
 | 
			
		||||
import { isFunction } from "@pureadmin/utils";
 | 
			
		||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
 | 
			
		||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
 | 
			
		||||
 | 
			
		||||
const fullscreen = ref(false);
 | 
			
		||||
 | 
			
		||||
const footerButtons = computed(() => {
 | 
			
		||||
  return (options: DialogOptions) => {
 | 
			
		||||
@ -47,11 +51,22 @@ const footerButtons = computed(() => {
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fullscreenClass = computed(() => {
 | 
			
		||||
  return [
 | 
			
		||||
    "el-icon",
 | 
			
		||||
    "el-dialog__close",
 | 
			
		||||
    "-translate-x-2",
 | 
			
		||||
    "cursor-pointer",
 | 
			
		||||
    "hover:!text-[red]"
 | 
			
		||||
  ];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function eventsCallBack(
 | 
			
		||||
  event: EventType,
 | 
			
		||||
  options: DialogOptions,
 | 
			
		||||
  index: number
 | 
			
		||||
) {
 | 
			
		||||
  fullscreen.value = options?.fullscreen ?? false;
 | 
			
		||||
  if (options?.[event] && isFunction(options?.[event])) {
 | 
			
		||||
    return options?.[event]({ options, index });
 | 
			
		||||
  }
 | 
			
		||||
@ -69,25 +84,49 @@ function handleClose(
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog
 | 
			
		||||
    class="pure-dialog"
 | 
			
		||||
    v-for="(options, index) in dialogStore"
 | 
			
		||||
    :key="index"
 | 
			
		||||
    v-bind="options"
 | 
			
		||||
    v-model="options.visible"
 | 
			
		||||
    @opened="eventsCallBack('open', options, index)"
 | 
			
		||||
    :fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
 | 
			
		||||
    @close="handleClose(options, index)"
 | 
			
		||||
    @opened="eventsCallBack('open', options, index)"
 | 
			
		||||
    @openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
 | 
			
		||||
    @closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- header -->
 | 
			
		||||
    <template
 | 
			
		||||
      v-if="options?.headerRenderer"
 | 
			
		||||
      v-if="options?.fullscreenIcon || options?.headerRenderer"
 | 
			
		||||
      #header="{ close, titleId, titleClass }"
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="options?.fullscreenIcon"
 | 
			
		||||
        class="flex items-center justify-between"
 | 
			
		||||
      >
 | 
			
		||||
        <span :id="titleId" :class="titleClass">{{ options?.title }}</span>
 | 
			
		||||
        <i
 | 
			
		||||
          v-if="!options?.fullscreen"
 | 
			
		||||
          :class="fullscreenClass"
 | 
			
		||||
          @click="fullscreen = !fullscreen"
 | 
			
		||||
        >
 | 
			
		||||
          <IconifyIconOffline
 | 
			
		||||
            class="pure-dialog-svg"
 | 
			
		||||
            :icon="
 | 
			
		||||
              options?.fullscreen
 | 
			
		||||
                ? ExitFullscreen
 | 
			
		||||
                : fullscreen
 | 
			
		||||
                ? ExitFullscreen
 | 
			
		||||
                : Fullscreen
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
        </i>
 | 
			
		||||
      </div>
 | 
			
		||||
      <component
 | 
			
		||||
        v-else
 | 
			
		||||
        :is="options?.headerRenderer({ close, titleId, titleClass })"
 | 
			
		||||
      />
 | 
			
		||||
    </template>
 | 
			
		||||
    <!-- default -->
 | 
			
		||||
    <component
 | 
			
		||||
      v-bind="options?.props"
 | 
			
		||||
      :is="options.contentRenderer({ options, index })"
 | 
			
		||||
 | 
			
		||||
@ -15,8 +15,10 @@ type DialogProps = {
 | 
			
		||||
  title?: string;
 | 
			
		||||
  /** `Dialog` 的宽度,默认 `50%` */
 | 
			
		||||
  width?: string | number;
 | 
			
		||||
  /** 是否为全屏 `Dialog`,默认 `false` */
 | 
			
		||||
  /** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
 | 
			
		||||
  fullscreen?: boolean;
 | 
			
		||||
  /** 是否显示全屏操作图标,默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
 | 
			
		||||
  fullscreenIcon?: boolean;
 | 
			
		||||
  /** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
 | 
			
		||||
  top?: string;
 | 
			
		||||
  /** 是否需要遮罩层,默认 `true` */
 | 
			
		||||
 | 
			
		||||
@ -525,7 +525,7 @@ onMounted(() => {
 | 
			
		||||
          :class="[
 | 
			
		||||
            'scroll-item is-closable',
 | 
			
		||||
            linkIsActive(item),
 | 
			
		||||
            $route.path === item.path && showModel === 'card'
 | 
			
		||||
            route.path === item.path && showModel === 'card'
 | 
			
		||||
              ? 'card-active'
 | 
			
		||||
              : ''
 | 
			
		||||
          ]"
 | 
			
		||||
 | 
			
		||||
@ -47,13 +47,13 @@ Object.keys(modules).forEach(key => {
 | 
			
		||||
 | 
			
		||||
/** 导出处理后的静态路由(三级及以上的路由全部拍成二级) */
 | 
			
		||||
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
 | 
			
		||||
  formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
 | 
			
		||||
  formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/** 用于渲染菜单,保持原始层级 */
 | 
			
		||||
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
 | 
			
		||||
  ...remainingRouter
 | 
			
		||||
);
 | 
			
		||||
export const constantMenus: Array<RouteComponent> = ascending(
 | 
			
		||||
  routes.flat(Infinity)
 | 
			
		||||
).concat(...remainingRouter);
 | 
			
		||||
 | 
			
		||||
/** 不参与菜单的路由 */
 | 
			
		||||
export const remainingPaths = Object.keys(remainingRouter).map(v => {
 | 
			
		||||
@ -87,7 +87,9 @@ export function resetRouter() {
 | 
			
		||||
    if (name && router.hasRoute(name) && meta?.backstage) {
 | 
			
		||||
      router.removeRoute(name);
 | 
			
		||||
      router.options.routes = formatTwoStageRoutes(
 | 
			
		||||
        formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
 | 
			
		||||
        formatFlatteningRoutes(
 | 
			
		||||
          buildHierarchyTree(ascending(routes.flat(Infinity)))
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,15 @@ export default {
 | 
			
		||||
        title: $t("menus.hsmessage")
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: "/components/waterfall",
 | 
			
		||||
      name: "Waterfall",
 | 
			
		||||
      component: () => import("@/views/components/waterfall/index.vue"),
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: $t("menus.hswaterfall"),
 | 
			
		||||
        extraIcon: "IF-pure-iconfont-new svg"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: "/components/video",
 | 
			
		||||
      name: "Video",
 | 
			
		||||
 | 
			
		||||
@ -110,6 +110,10 @@ html.dark {
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color: rgb(255 255 255 / 85%) !important;
 | 
			
		||||
        background-color: rgb(255 255 255 / 12%);
 | 
			
		||||
 | 
			
		||||
        .pure-dialog-svg {
 | 
			
		||||
          color: rgb(255 255 255 / 85%) !important;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -69,6 +69,19 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pure-dialog {
 | 
			
		||||
  .pure-dialog-svg {
 | 
			
		||||
    color: var(--el-color-info);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-dialog__headerbtn {
 | 
			
		||||
    top: 20px;
 | 
			
		||||
    right: 14px;
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式,表现更鲜明 */
 | 
			
		||||
.el-dialog__headerbtn,
 | 
			
		||||
.el-message-box__headerbtn {
 | 
			
		||||
@ -94,6 +107,10 @@
 | 
			
		||||
      color: rgb(0 0 0 / 88%) !important;
 | 
			
		||||
      text-decoration: none;
 | 
			
		||||
      background-color: rgb(0 0 0 / 6%);
 | 
			
		||||
 | 
			
		||||
      .pure-dialog-svg {
 | 
			
		||||
        color: rgb(0 0 0 / 88%) !important;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,7 @@ const activities = [
 | 
			
		||||
          placement="bottom"
 | 
			
		||||
        >
 | 
			
		||||
          <div class="message">
 | 
			
		||||
            vue-pure-admin是基于Vue3.0+TypeScript+Vite2.0+Element-Plus编写的一套后台管理系统
 | 
			
		||||
            vue-pure-admin是基于Vue3.0+TypeScript+Vite+Element-Plus编写的一套后台管理系统
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-timeline-item>
 | 
			
		||||
      </el-timeline>
 | 
			
		||||
 | 
			
		||||
@ -28,19 +28,19 @@ Object.keys(devDependencies).forEach(key => {
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <el-card class="box-card mb-4" shadow="never">
 | 
			
		||||
    <el-card class="mb-4 box-card" shadow="never">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <span class="font-medium">关于</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <span style="font-size: 15px">
 | 
			
		||||
        Pure-Admin 是一个基于Vue3、Vite2、TypeScript、Element-Plus
 | 
			
		||||
        Pure-Admin 是一个基于Vue3、Vite、TypeScript、Element-Plus
 | 
			
		||||
        的中后台解决方案,它可以帮助您快速搭建企业级中后台,提供现成的开箱解决方案及丰富的示例。原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
 | 
			
		||||
      </span>
 | 
			
		||||
    </el-card>
 | 
			
		||||
 | 
			
		||||
    <el-card class="box-card m-4" shadow="never">
 | 
			
		||||
    <el-card class="m-4 box-card" shadow="never">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <span class="font-medium">项目信息</span>
 | 
			
		||||
@ -49,7 +49,7 @@ Object.keys(devDependencies).forEach(key => {
 | 
			
		||||
      <PureDescriptions :columns="columns" border :column="3" align="left" />
 | 
			
		||||
    </el-card>
 | 
			
		||||
 | 
			
		||||
    <el-card class="box-card m-4" shadow="never">
 | 
			
		||||
    <el-card class="m-4 box-card" shadow="never">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <span class="font-medium">生产环境依赖</span>
 | 
			
		||||
@ -73,7 +73,7 @@ Object.keys(devDependencies).forEach(key => {
 | 
			
		||||
      </el-descriptions>
 | 
			
		||||
    </el-card>
 | 
			
		||||
 | 
			
		||||
    <el-card class="box-card m-4" shadow="never">
 | 
			
		||||
    <el-card class="m-4 box-card" shadow="never">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <span class="font-medium">开发环境依赖</span>
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
<script setup lang="tsx">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { h, createVNode, ref } from "vue";
 | 
			
		||||
import { message } from "@/utils/message";
 | 
			
		||||
import { cloneDeep } from "@pureadmin/utils";
 | 
			
		||||
@ -9,6 +10,8 @@ defineOptions({
 | 
			
		||||
  name: "Dialog"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
function onBaseClick() {
 | 
			
		||||
  addDialog({
 | 
			
		||||
    title: "基本使用",
 | 
			
		||||
@ -32,6 +35,14 @@ function onFullscreenClick() {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onFullscreenIconClick() {
 | 
			
		||||
  addDialog({
 | 
			
		||||
    title: "全屏按钮",
 | 
			
		||||
    fullscreenIcon: true,
 | 
			
		||||
    contentRenderer: () => <p>弹框内容-全屏按钮</p>
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onModalClick() {
 | 
			
		||||
  addDialog({
 | 
			
		||||
    title: "无背景遮罩层",
 | 
			
		||||
@ -380,7 +391,7 @@ function onBeforeSureClick() {
 | 
			
		||||
          ,采用函数式调用弹框组件(更多操作实例请参考
 | 
			
		||||
          <span
 | 
			
		||||
            class="cursor-pointer text-primary"
 | 
			
		||||
            @click="$router.push({ name: 'Dept' })"
 | 
			
		||||
            @click="router.push({ name: 'Dept' })"
 | 
			
		||||
            >系统管理页面</span
 | 
			
		||||
          >
 | 
			
		||||
          )
 | 
			
		||||
@ -391,6 +402,7 @@ function onBeforeSureClick() {
 | 
			
		||||
      <el-button @click="onBaseClick"> 基本使用 </el-button>
 | 
			
		||||
      <el-button @click="onDraggableClick"> 可拖拽 </el-button>
 | 
			
		||||
      <el-button @click="onFullscreenClick"> 全屏 </el-button>
 | 
			
		||||
      <el-button @click="onFullscreenIconClick"> 全屏按钮 </el-button>
 | 
			
		||||
      <el-button @click="onModalClick"> 无背景遮罩层 </el-button>
 | 
			
		||||
      <el-button @click="onStyleClick"> 自定义弹出位置 </el-button>
 | 
			
		||||
      <el-button @click="onoOpenDelayClick"> 延时2秒打开弹框 </el-button>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								src/views/components/waterfall/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/views/components/waterfall/api.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
export function randomID(length = 6) {
 | 
			
		||||
  return Number(
 | 
			
		||||
    Math.random().toString().substr(3, length) + Date.now()
 | 
			
		||||
  ).toString(36);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const COLORS = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C", "#909399"];
 | 
			
		||||
 | 
			
		||||
function getRandomNum(min: number, max: number) {
 | 
			
		||||
  return Math.floor(Math.random() * (max - min + 1)) + min;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function randomColor() {
 | 
			
		||||
  return COLORS[getRandomNum(0, 4)];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const website = "https://www.getphotoblanket.com";
 | 
			
		||||
 | 
			
		||||
export const getList = ({ page = 1, pageSize = 20 }) => {
 | 
			
		||||
  const url = `${website}/products.json?page=${page}&limit=${pageSize}`;
 | 
			
		||||
  return fetch(url)
 | 
			
		||||
    .then(res => res.json())
 | 
			
		||||
    .then(res => res.products)
 | 
			
		||||
    .then(res => {
 | 
			
		||||
      return res.map((item: any) => {
 | 
			
		||||
        return {
 | 
			
		||||
          id: randomID(),
 | 
			
		||||
          star: false,
 | 
			
		||||
          price: item.variants[0].price,
 | 
			
		||||
          src: {
 | 
			
		||||
            original: item.images[0].src
 | 
			
		||||
          },
 | 
			
		||||
          backgroundColor: randomColor(),
 | 
			
		||||
          name: item.title
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/views/components/waterfall/error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/views/components/waterfall/error.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.5 KiB  | 
							
								
								
									
										151
									
								
								src/views/components/waterfall/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/views/components/waterfall/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,151 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { getList } from "./api";
 | 
			
		||||
import error from "./error.png";
 | 
			
		||||
import loading from "./loading.png";
 | 
			
		||||
import { onMounted, reactive, ref } from "vue";
 | 
			
		||||
import "vue-waterfall-plugin-next/dist/style.css";
 | 
			
		||||
import backTop from "@/assets/svg/back_top.svg?component";
 | 
			
		||||
import { LazyImg, Waterfall } from "vue-waterfall-plugin-next";
 | 
			
		||||
 | 
			
		||||
const options = reactive({
 | 
			
		||||
  // 唯一key值
 | 
			
		||||
  rowKey: "id",
 | 
			
		||||
  // 卡片之间的间隙
 | 
			
		||||
  gutter: 10,
 | 
			
		||||
  // 是否有周围的gutter
 | 
			
		||||
  hasAroundGutter: true,
 | 
			
		||||
  // 卡片在PC上的宽度
 | 
			
		||||
  width: 320,
 | 
			
		||||
  // 自定义行显示个数,主要用于对移动端的适配
 | 
			
		||||
  breakpoints: {
 | 
			
		||||
    1200: {
 | 
			
		||||
      // 当屏幕宽度小于等于1200
 | 
			
		||||
      rowPerView: 4
 | 
			
		||||
    },
 | 
			
		||||
    800: {
 | 
			
		||||
      // 当屏幕宽度小于等于800
 | 
			
		||||
      rowPerView: 3
 | 
			
		||||
    },
 | 
			
		||||
    500: {
 | 
			
		||||
      // 当屏幕宽度小于等于500
 | 
			
		||||
      rowPerView: 2
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  // 动画效果
 | 
			
		||||
  animationEffect: "animate__fadeInUp",
 | 
			
		||||
  // 动画时间
 | 
			
		||||
  animationDuration: 1000,
 | 
			
		||||
  // 动画延迟
 | 
			
		||||
  animationDelay: 300,
 | 
			
		||||
  // 背景色
 | 
			
		||||
  // backgroundColor: "#2C2E3A",
 | 
			
		||||
  // 图片字段选择器,如果层级较深,使用 xxx.xxx.xxx 方式
 | 
			
		||||
  imgSelector: "src.original",
 | 
			
		||||
  // 加载配置
 | 
			
		||||
  loadProps: {
 | 
			
		||||
    loading,
 | 
			
		||||
    error
 | 
			
		||||
  },
 | 
			
		||||
  // 是否懒加载
 | 
			
		||||
  lazyload: true
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const list = ref([]);
 | 
			
		||||
const pageSize = ref();
 | 
			
		||||
 | 
			
		||||
/** 加载更多 */
 | 
			
		||||
function handleLoadMore() {
 | 
			
		||||
  getList({
 | 
			
		||||
    page: page.value,
 | 
			
		||||
    pageSize: pageSize.value
 | 
			
		||||
  }).then(res => {
 | 
			
		||||
    list.value.push(...res);
 | 
			
		||||
    page.value += 1;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleDelete(item, index) {
 | 
			
		||||
  list.value.splice(index, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleClick(item) {
 | 
			
		||||
  console.log(item);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  handleLoadMore();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-card shadow="never" class="!h-[85vh]">
 | 
			
		||||
    <template #header>
 | 
			
		||||
      <div class="card-header">
 | 
			
		||||
        <span class="font-medium">
 | 
			
		||||
          瀑布流组件,采用开源的
 | 
			
		||||
          <el-link
 | 
			
		||||
            href="https://github.com/heikaimu/vue3-waterfall-plugin"
 | 
			
		||||
            target="_blank"
 | 
			
		||||
            style="margin: 0 4px 5px; font-size: 16px"
 | 
			
		||||
          >
 | 
			
		||||
            vue-waterfall-plugin-next
 | 
			
		||||
          </el-link>
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <el-scrollbar height="78vh" class="content">
 | 
			
		||||
      <Waterfall :list="list" v-bind="options">
 | 
			
		||||
        <template #item="{ item, url, index }">
 | 
			
		||||
          <div
 | 
			
		||||
            class="bg-gray-900 rounded-lg shadow-md overflow-hidden transition-all duration-300 ease-linear hover:shadow-lg hover:shadow-gray-600 group"
 | 
			
		||||
            @click="handleClick(item)"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="overflow-hidden">
 | 
			
		||||
              <LazyImg
 | 
			
		||||
                :url="url"
 | 
			
		||||
                class="cursor-pointer transition-all duration-300 ease-linear group-hover:scale-105"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="px-4 pt-2 pb-4 border-t border-t-gray-800">
 | 
			
		||||
              <h4 class="pb-4 text-gray-50 group-hover:text-yellow-300">
 | 
			
		||||
                {{ item.name }}
 | 
			
		||||
              </h4>
 | 
			
		||||
              <div
 | 
			
		||||
                class="pt-3 flex justify-between items-center border-t border-t-gray-600 border-opacity-50"
 | 
			
		||||
              >
 | 
			
		||||
                <div class="text-gray-50">$ {{ item.price }}</div>
 | 
			
		||||
                <div>
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="px-3 h-7 rounded-full bg-red-500 text-sm text-white shadow-lg transition-all duration-300 hover:bg-red-600"
 | 
			
		||||
                    @click.stop="handleDelete(item, index)"
 | 
			
		||||
                  >
 | 
			
		||||
                    删除
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
      </Waterfall>
 | 
			
		||||
 | 
			
		||||
      <div class="flex justify-center py-10">
 | 
			
		||||
        <button
 | 
			
		||||
          class="px-5 py-2 rounded-full bg-gray-700 text-md text-white cursor-pointer hover:bg-gray-800 transition-all duration-300"
 | 
			
		||||
          @click="handleLoadMore"
 | 
			
		||||
        >
 | 
			
		||||
          加载更多
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <el-backtop
 | 
			
		||||
        title="回到顶部"
 | 
			
		||||
        :right="35"
 | 
			
		||||
        :visibility-height="400"
 | 
			
		||||
        target=".content .el-scrollbar__wrap"
 | 
			
		||||
      >
 | 
			
		||||
        <backTop />
 | 
			
		||||
      </el-backtop>
 | 
			
		||||
    </el-scrollbar>
 | 
			
		||||
  </el-card>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/views/components/waterfall/loading.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/views/components/waterfall/loading.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.3 KiB  | 
@ -1,16 +1,19 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import back from "@/assets/svg/back.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "Empty"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="w-full h-full text-center">
 | 
			
		||||
    <h1>业务内容模块</h1>
 | 
			
		||||
    <p>使用场景:需要外嵌平台某个页面,不需要展示菜单导航以及额外模块</p>
 | 
			
		||||
    <div class="back" title="返回上一页" @click="$router.go(-1)">
 | 
			
		||||
    <div class="back" title="返回上一页" @click="router.go(-1)">
 | 
			
		||||
      <back class="w-[80px] h-[80px]" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import noAccess from "@/assets/status/403.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "403"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -46,7 +49,7 @@ defineOptions({
 | 
			
		||||
      </p>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        @click="$router.push('/')"
 | 
			
		||||
        @click="router.push('/')"
 | 
			
		||||
        v-motion
 | 
			
		||||
        :initial="{
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import noExist from "@/assets/status/404.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "404"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -46,7 +49,7 @@ defineOptions({
 | 
			
		||||
      </p>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        @click="$router.push('/')"
 | 
			
		||||
        @click="router.push('/')"
 | 
			
		||||
        v-motion
 | 
			
		||||
        :initial="{
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import noServer from "@/assets/status/500.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "500"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -46,7 +49,7 @@ defineOptions({
 | 
			
		||||
      </p>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        @click="$router.push('/')"
 | 
			
		||||
        @click="router.push('/')"
 | 
			
		||||
        v-motion
 | 
			
		||||
        :initial="{
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
 | 
			
		||||
@ -120,6 +120,7 @@ export function useDept() {
 | 
			
		||||
      },
 | 
			
		||||
      width: "40%",
 | 
			
		||||
      draggable: true,
 | 
			
		||||
      fullscreenIcon: true,
 | 
			
		||||
      closeOnClickModal: false,
 | 
			
		||||
      contentRenderer: () => h(editForm, { ref: formRef }),
 | 
			
		||||
      beforeSure: (done, { options }) => {
 | 
			
		||||
 | 
			
		||||
@ -101,14 +101,14 @@ function onCloseTags() {
 | 
			
		||||
    <el-button class="m-2" @click="onCloseTags">关闭标签</el-button>
 | 
			
		||||
 | 
			
		||||
    <el-divider />
 | 
			
		||||
    <el-button @click="$router.push({ name: 'Menu1-2-2' })">
 | 
			
		||||
    <el-button @click="router.push({ name: 'Menu1-2-2' })">
 | 
			
		||||
      跳转页内菜单(传name对象,优先推荐)
 | 
			
		||||
    </el-button>
 | 
			
		||||
    <el-button @click="$router.push('/nested/menu1/menu1-2/menu1-2-2')">
 | 
			
		||||
    <el-button @click="router.push('/nested/menu1/menu1-2/menu1-2-2')">
 | 
			
		||||
      跳转页内菜单(直接传要跳转的路径)
 | 
			
		||||
    </el-button>
 | 
			
		||||
    <el-button
 | 
			
		||||
      @click="$router.push({ path: '/nested/menu1/menu1-2/menu1-2-2' })"
 | 
			
		||||
      @click="router.push({ path: '/nested/menu1/menu1-2/menu1-2-2' })"
 | 
			
		||||
    >
 | 
			
		||||
      跳转页内菜单(传path对象)
 | 
			
		||||
    </el-button>
 | 
			
		||||
@ -116,7 +116,7 @@ function onCloseTags() {
 | 
			
		||||
    <el-divider />
 | 
			
		||||
    <el-button
 | 
			
		||||
      @click="
 | 
			
		||||
        $router.push({
 | 
			
		||||
        router.push({
 | 
			
		||||
          name: 'Menu1-2-2',
 | 
			
		||||
          query: { text: '传name对象,优先推荐' }
 | 
			
		||||
        })
 | 
			
		||||
@ -126,7 +126,7 @@ function onCloseTags() {
 | 
			
		||||
    </el-button>
 | 
			
		||||
    <el-button
 | 
			
		||||
      @click="
 | 
			
		||||
        $router.push({
 | 
			
		||||
        router.push({
 | 
			
		||||
          path: '/nested/menu1/menu1-2/menu1-2-2',
 | 
			
		||||
          query: { text: '传path对象' }
 | 
			
		||||
        })
 | 
			
		||||
@ -143,7 +143,7 @@ function onCloseTags() {
 | 
			
		||||
    </el-link>
 | 
			
		||||
 | 
			
		||||
    <el-divider />
 | 
			
		||||
    <el-button @click="$router.push({ name: 'Empty' })">
 | 
			
		||||
    <el-button @click="router.push({ name: 'Empty' })">
 | 
			
		||||
      跳转无Layout的空白页面
 | 
			
		||||
    </el-button>
 | 
			
		||||
  </el-card>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								types/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								types/index.d.ts
									
									
									
									
										vendored
									
									
								
							@ -41,6 +41,10 @@ type DeepPartial<T> = {
 | 
			
		||||
  [P in keyof T]?: DeepPartial<T[P]>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
 | 
			
		||||
 | 
			
		||||
type Exclusive<T, U> = (Without<T, U> & U) | (Without<U, T> & T);
 | 
			
		||||
 | 
			
		||||
type TimeoutHandle = ReturnType<typeof setTimeout>;
 | 
			
		||||
 | 
			
		||||
type IntervalHandle = ReturnType<typeof setInterval>;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user