mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-07 00:47:19 +08:00
Merge branch 'main' into gitee
This commit is contained in:
commit
87a2af7181
@ -90,6 +90,7 @@ menus:
|
||||
hsMenuTree: Menu Tree
|
||||
hsVideoFrame: Video Frame Capture
|
||||
hsWavesurfer: Audio Visualization
|
||||
hsRipple: Ripple
|
||||
hsOptimize: Debounce、Throttle、Copy、Longpress Directives
|
||||
hsWatermark: Water Mark
|
||||
hsPrint: Print
|
||||
|
@ -90,6 +90,7 @@ menus:
|
||||
hsMenuTree: 菜单树结构
|
||||
hsVideoFrame: 视频帧截取-wasm版
|
||||
hsWavesurfer: 音频可视化
|
||||
hsRipple: 波纹(Ripple)
|
||||
hsOptimize: 防抖、截流、复制、长按指令
|
||||
hsWatermark: 水印
|
||||
hsPrint: 打印
|
||||
|
16
package.json
16
package.json
@ -49,7 +49,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@howdyjs/mouse-menu": "2.0.9",
|
||||
"@howdyjs/mouse-menu": "^2.1.3",
|
||||
"@logicflow/core": "^1.2.22",
|
||||
"@logicflow/extension": "^1.2.22",
|
||||
"@pureadmin/descriptions": "^1.2.0",
|
||||
@ -78,7 +78,7 @@
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.19.6",
|
||||
"plus-pro-components": "^0.0.1",
|
||||
"plus-pro-components": "^0.0.2",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"responsive-storage": "^2.2.0",
|
||||
@ -91,7 +91,7 @@
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.10.1",
|
||||
"vue-json-pretty": "^2.3.0",
|
||||
"vue-pdf-embed": "1.2.1",
|
||||
"vue-pdf-embed": "^2.0.2",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue-tippy": "^6.4.1",
|
||||
"vue-types": "^5.1.1",
|
||||
@ -122,11 +122,11 @@
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/qs": "^6.9.12",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"boxen": "^7.1.1",
|
||||
"cloc": "^2.11.0",
|
||||
"cssnano": "^6.0.5",
|
||||
@ -147,14 +147,14 @@
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.71.1",
|
||||
"stylelint": "^16.2.1",
|
||||
"stylelint-config-recess-order": "^4.6.0",
|
||||
"stylelint-config-recess-order": "^5.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-config-standard-scss": "^13.0.0",
|
||||
"stylelint-prettier": "^5.0.0",
|
||||
"svgo": "^3.2.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.4",
|
||||
"vite": "^5.1.5",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-fake-server": "^2.1.1",
|
||||
|
501
pnpm-lock.yaml
generated
501
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -2,3 +2,4 @@ export * from "./auth";
|
||||
export * from "./copy";
|
||||
export * from "./longpress";
|
||||
export * from "./optimize";
|
||||
export * from "./ripple";
|
||||
|
48
src/directives/ripple/index.scss
Normal file
48
src/directives/ripple/index.scss
Normal file
@ -0,0 +1,48 @@
|
||||
/* stylelint-disable-next-line scss/dollar-variable-colon-space-after */
|
||||
$ripple-animation-transition-in:
|
||||
transform 0.4s cubic-bezier(0, 0, 0.2, 1),
|
||||
opacity 0.2s cubic-bezier(0, 0, 0.2, 1) !default;
|
||||
$ripple-animation-transition-out: opacity 0.5s cubic-bezier(0, 0, 0.2, 1) !default;
|
||||
$ripple-animation-visible-opacity: 0.25 !default;
|
||||
|
||||
.v-ripple {
|
||||
&__container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
&__animation {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
background: currentcolor;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
will-change: transform, opacity;
|
||||
|
||||
&--enter {
|
||||
opacity: 0;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&--in {
|
||||
opacity: $ripple-animation-visible-opacity;
|
||||
transition: $ripple-animation-transition-in;
|
||||
}
|
||||
|
||||
&--out {
|
||||
opacity: 0;
|
||||
transition: $ripple-animation-transition-out;
|
||||
}
|
||||
}
|
||||
}
|
234
src/directives/ripple/index.ts
Normal file
234
src/directives/ripple/index.ts
Normal file
@ -0,0 +1,234 @@
|
||||
import "./index.scss";
|
||||
import { isObject } from "@pureadmin/utils";
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
interface RippleOptions {
|
||||
class?: string;
|
||||
center?: boolean;
|
||||
circle?: boolean;
|
||||
}
|
||||
|
||||
export interface RippleDirectiveBinding
|
||||
extends Omit<DirectiveBinding, "modifiers" | "value"> {
|
||||
value?: boolean | { class: string };
|
||||
modifiers: {
|
||||
center?: boolean;
|
||||
circle?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
function transform(el: HTMLElement, value: string) {
|
||||
el.style.transform = value;
|
||||
el.style.webkitTransform = value;
|
||||
}
|
||||
|
||||
const calculate = (
|
||||
e: PointerEvent,
|
||||
el: HTMLElement,
|
||||
value: RippleOptions = {}
|
||||
) => {
|
||||
const offset = el.getBoundingClientRect();
|
||||
|
||||
// 获取点击位置距离 el 的垂直和水平距离
|
||||
let localX = e.clientX - offset.left;
|
||||
let localY = e.clientY - offset.top;
|
||||
|
||||
let radius = 0;
|
||||
let scale = 0.3;
|
||||
// 计算点击位置到 el 顶点最远距离,即为圆的最大半径(勾股定理)
|
||||
if (el._ripple?.circle) {
|
||||
scale = 0.15;
|
||||
radius = el.clientWidth / 2;
|
||||
radius = value.center
|
||||
? radius
|
||||
: radius + Math.sqrt((localX - radius) ** 2 + (localY - radius) ** 2) / 4;
|
||||
} else {
|
||||
radius = Math.sqrt(el.clientWidth ** 2 + el.clientHeight ** 2) / 2;
|
||||
}
|
||||
|
||||
// 中心点坐标
|
||||
const centerX = `${(el.clientWidth - radius * 2) / 2}px`;
|
||||
const centerY = `${(el.clientHeight - radius * 2) / 2}px`;
|
||||
|
||||
// 点击位置坐标
|
||||
const x = value.center ? centerX : `${localX - radius}px`;
|
||||
const y = value.center ? centerY : `${localY - radius}px`;
|
||||
|
||||
return { radius, scale, x, y, centerX, centerY };
|
||||
};
|
||||
|
||||
const ripples = {
|
||||
show(e: PointerEvent, el: HTMLElement, value: RippleOptions = {}) {
|
||||
if (!el?._ripple?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建 ripple 元素和 ripple 父元素
|
||||
const container = document.createElement("span");
|
||||
const animation = document.createElement("span");
|
||||
|
||||
container.appendChild(animation);
|
||||
container.className = "v-ripple__container";
|
||||
|
||||
if (value.class) {
|
||||
container.className += ` ${value.class}`;
|
||||
}
|
||||
|
||||
const { radius, scale, x, y, centerX, centerY } = calculate(e, el, value);
|
||||
|
||||
// ripple 圆大小
|
||||
const size = `${radius * 2}px`;
|
||||
|
||||
animation.className = "v-ripple__animation";
|
||||
animation.style.width = size;
|
||||
animation.style.height = size;
|
||||
|
||||
el.appendChild(container);
|
||||
|
||||
// 获取目标元素样式表
|
||||
const computed = window.getComputedStyle(el);
|
||||
// 防止 position 被覆盖导致 ripple 位置有问题
|
||||
if (computed && computed.position === "static") {
|
||||
el.style.position = "relative";
|
||||
el.dataset.previousPosition = "static";
|
||||
}
|
||||
|
||||
animation.classList.add("v-ripple__animation--enter");
|
||||
animation.classList.add("v-ripple__animation--visible");
|
||||
transform(
|
||||
animation,
|
||||
`translate(${x}, ${y}) scale3d(${scale},${scale},${scale})`
|
||||
);
|
||||
animation.dataset.activated = String(performance.now());
|
||||
|
||||
setTimeout(() => {
|
||||
animation.classList.remove("v-ripple__animation--enter");
|
||||
animation.classList.add("v-ripple__animation--in");
|
||||
transform(animation, `translate(${centerX}, ${centerY}) scale3d(1,1,1)`);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
hide(el: HTMLElement | null) {
|
||||
if (!el?._ripple?.enabled) return;
|
||||
|
||||
const ripples = el.getElementsByClassName("v-ripple__animation");
|
||||
|
||||
if (ripples.length === 0) return;
|
||||
const animation = ripples[ripples.length - 1] as HTMLElement;
|
||||
|
||||
if (animation.dataset.isHiding) return;
|
||||
else animation.dataset.isHiding = "true";
|
||||
|
||||
const diff = performance.now() - Number(animation.dataset.activated);
|
||||
const delay = Math.max(250 - diff, 0);
|
||||
|
||||
setTimeout(() => {
|
||||
animation.classList.remove("v-ripple__animation--in");
|
||||
animation.classList.add("v-ripple__animation--out");
|
||||
|
||||
setTimeout(() => {
|
||||
const ripples = el.getElementsByClassName("v-ripple__animation");
|
||||
if (ripples.length === 1 && el.dataset.previousPosition) {
|
||||
el.style.position = el.dataset.previousPosition;
|
||||
delete el.dataset.previousPosition;
|
||||
}
|
||||
|
||||
if (animation.parentNode?.parentNode === el)
|
||||
el.removeChild(animation.parentNode);
|
||||
}, 300);
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
function isRippleEnabled(value: any): value is true {
|
||||
return typeof value === "undefined" || !!value;
|
||||
}
|
||||
|
||||
function rippleShow(e: PointerEvent) {
|
||||
const value: RippleOptions = {};
|
||||
const element = e.currentTarget as HTMLElement | undefined;
|
||||
|
||||
if (!element?._ripple || element._ripple.touched) return;
|
||||
|
||||
value.center = element._ripple.centered;
|
||||
if (element._ripple.class) {
|
||||
value.class = element._ripple.class;
|
||||
}
|
||||
|
||||
ripples.show(e, element, value);
|
||||
}
|
||||
|
||||
function rippleHide(e: Event) {
|
||||
const element = e.currentTarget as HTMLElement | null;
|
||||
if (!element?._ripple) return;
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (element._ripple) {
|
||||
element._ripple.touched = false;
|
||||
}
|
||||
});
|
||||
ripples.hide(element);
|
||||
}
|
||||
|
||||
function updateRipple(
|
||||
el: HTMLElement,
|
||||
binding: RippleDirectiveBinding,
|
||||
wasEnabled: boolean
|
||||
) {
|
||||
const { value, modifiers } = binding;
|
||||
const enabled = isRippleEnabled(value);
|
||||
if (!enabled) {
|
||||
ripples.hide(el);
|
||||
}
|
||||
|
||||
el._ripple = el._ripple ?? {};
|
||||
el._ripple.enabled = enabled;
|
||||
el._ripple.centered = modifiers.center;
|
||||
el._ripple.circle = modifiers.circle;
|
||||
if (isObject(value) && value.class) {
|
||||
el._ripple.class = value.class;
|
||||
}
|
||||
|
||||
if (enabled && !wasEnabled) {
|
||||
el.addEventListener("pointerdown", rippleShow);
|
||||
el.addEventListener("pointerup", rippleHide);
|
||||
} else if (!enabled && wasEnabled) {
|
||||
removeListeners(el);
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners(el: HTMLElement) {
|
||||
el.removeEventListener("pointerdown", rippleShow);
|
||||
el.removeEventListener("pointerup", rippleHide);
|
||||
}
|
||||
|
||||
function mounted(el: HTMLElement, binding: RippleDirectiveBinding) {
|
||||
updateRipple(el, binding, false);
|
||||
}
|
||||
|
||||
function unmounted(el: HTMLElement) {
|
||||
delete el._ripple;
|
||||
removeListeners(el);
|
||||
}
|
||||
|
||||
function updated(el: HTMLElement, binding: RippleDirectiveBinding) {
|
||||
if (binding.value === binding.oldValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wasEnabled = isRippleEnabled(binding.oldValue);
|
||||
updateRipple(el, binding, wasEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 指令 v-ripple
|
||||
* @use 用法如下
|
||||
* 1. v-ripple 代表启用基本的 ripple 功能
|
||||
* 2. v-ripple="{ class: 'text-red' }" 代表自定义 ripple 颜色,支持 tailwindcss,生效样式是 color
|
||||
* 3. v-ripple.center 代表从中心扩散
|
||||
*/
|
||||
export const Ripple: Directive = {
|
||||
mounted,
|
||||
unmounted,
|
||||
updated
|
||||
};
|
@ -42,6 +42,15 @@ export default {
|
||||
title: $t("menus.hsExcel")
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/ripple",
|
||||
name: "Ripple",
|
||||
component: () => import("@/views/able/ripple.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsRipple"),
|
||||
extraIcon: "IF-pure-iconfont-new svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/debounce",
|
||||
name: "Debounce",
|
||||
|
@ -64,7 +64,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标和el-upload上传文件列表右侧关闭图标的样式,表现更鲜明 */
|
||||
/* 全局覆盖element-plus的el-tour、el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标和el-upload上传文件列表右侧关闭图标的样式,表现更鲜明 */
|
||||
.el-dialog__headerbtn,
|
||||
.el-message-box__headerbtn {
|
||||
&:hover {
|
||||
@ -75,6 +75,7 @@
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
&.el-tour__close,
|
||||
&.el-dialog__close,
|
||||
&.el-drawer__close,
|
||||
&.el-message-box__close,
|
||||
|
@ -21,7 +21,7 @@ const source =
|
||||
|
||||
const handleDocumentRender = () => {
|
||||
loading.value = false;
|
||||
pageCount.value = pdfRef.value.pageCount;
|
||||
pageCount.value = pdfRef.value.doc.numPages;
|
||||
};
|
||||
|
||||
const showAllPagesChange = () => {
|
||||
@ -29,6 +29,7 @@ const showAllPagesChange = () => {
|
||||
};
|
||||
|
||||
const onPrint = () => {
|
||||
// 如果在打印时,打印页面是本身的两倍,在打印配置 页面 设置 仅限页码为奇数的页面 即可
|
||||
pdfRef.value.print();
|
||||
};
|
||||
</script>
|
||||
|
71
src/views/able/ripple.vue
Normal file
71
src/views/able/ripple.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "Ripple"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="font-medium">波纹(Ripple)</div>
|
||||
</template>
|
||||
<div class="mb-5">组件中的波纹</div>
|
||||
<el-alert
|
||||
title="v-ripple在某些组件中使用波纹特效会异常,这是因为v-ripple指令只能作用于当前元素,某些组件有多层元素嵌套,且目标元素没在顶层,所以会导致特效异常"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
/>
|
||||
<el-space wrap class="my-5">
|
||||
<el-button v-ripple>Default</el-button>
|
||||
<el-button v-ripple type="primary">Primary</el-button>
|
||||
<el-button v-ripple type="success">Success</el-button>
|
||||
<el-button v-ripple type="info">Info</el-button>
|
||||
<el-button v-ripple type="warning">Warning</el-button>
|
||||
<el-button v-ripple type="danger">Danger</el-button>
|
||||
</el-space>
|
||||
<el-card v-ripple class="mb-5 w-[510px] select-none" shadow="hover">
|
||||
卡片
|
||||
</el-card>
|
||||
|
||||
<div class="mb-5">
|
||||
只要在组件或HTML元素上使用v-ripple指令,就可以启用基本的ripple功能
|
||||
</div>
|
||||
<div
|
||||
v-ripple
|
||||
class="mb-5 text-center shadow-md rounded-md p-8 text-lg select-none"
|
||||
>
|
||||
HTML元素
|
||||
</div>
|
||||
<span
|
||||
v-ripple
|
||||
class="inline-block shadow-md rounded-md p-8 text-lg select-none"
|
||||
>
|
||||
行内元素需要添加display: block或display: inline-block才能生效
|
||||
</span>
|
||||
|
||||
<div class="my-5">
|
||||
当使用v-ripple.center时,将始终从目标的中心处产生波纹
|
||||
</div>
|
||||
<div
|
||||
v-ripple.center
|
||||
class="mb-5 text-center shadow-md rounded-md p-8 text-lg select-none"
|
||||
>
|
||||
始终从中心触发波纹
|
||||
</div>
|
||||
|
||||
<div class="mb-5">
|
||||
使用v-ripple="{ class: '' }"添加类来自定义波纹颜色,支持tailwindcss
|
||||
</div>
|
||||
<el-alert
|
||||
title="自定义样式生效为文字颜色,例如:color: 'red';"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
/>
|
||||
<div
|
||||
v-ripple="{ class: 'text-red-500' }"
|
||||
class="my-5 text-center shadow-md rounded-md p-4 text-lg select-none"
|
||||
>
|
||||
自定义波纹颜色
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
@ -75,7 +75,9 @@ Object.keys(devDependencies).forEach(key => {
|
||||
<span class="font-medium">平台信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<PureDescriptions border :columns="columns" :column="4" />
|
||||
<el-scrollbar>
|
||||
<PureDescriptions border :columns="columns" :column="4" />
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
|
||||
<el-card class="m-4 box-card" shadow="never">
|
||||
@ -84,28 +86,30 @@ Object.keys(devDependencies).forEach(key => {
|
||||
<span class="font-medium">生产环境依赖</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions border size="small" :column="6">
|
||||
<el-descriptions-item
|
||||
v-for="(item, index) in schema"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:label-class-name="getMainLabel(item.label)"
|
||||
class-name="pure-version"
|
||||
label-align="right"
|
||||
>
|
||||
<a
|
||||
:href="'https://www.npmjs.com/package/' + item.label"
|
||||
target="_blank"
|
||||
<el-scrollbar>
|
||||
<el-descriptions border size="small" :column="6">
|
||||
<el-descriptions-item
|
||||
v-for="(item, index) in schema"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:label-class-name="getMainLabel(item.label)"
|
||||
class-name="pure-version"
|
||||
label-align="right"
|
||||
>
|
||||
<span
|
||||
:class="getMainLabel(item.label)"
|
||||
style="color: var(--el-color-primary)"
|
||||
<a
|
||||
:href="'https://www.npmjs.com/package/' + item.label"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item.field }}
|
||||
</span>
|
||||
</a>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<span
|
||||
:class="getMainLabel(item.label)"
|
||||
style="color: var(--el-color-primary)"
|
||||
>
|
||||
{{ item.field }}
|
||||
</span>
|
||||
</a>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
|
||||
<el-card class="m-4 box-card" shadow="never">
|
||||
@ -114,28 +118,30 @@ Object.keys(devDependencies).forEach(key => {
|
||||
<span class="font-medium">开发环境依赖</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions border size="small" :column="5">
|
||||
<el-descriptions-item
|
||||
v-for="(item, index) in devSchema"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:label-class-name="getMainLabel(item.label)"
|
||||
class-name="pure-version"
|
||||
label-align="right"
|
||||
>
|
||||
<a
|
||||
:href="'https://www.npmjs.com/package/' + item.label"
|
||||
target="_blank"
|
||||
<el-scrollbar>
|
||||
<el-descriptions border size="small" :column="5">
|
||||
<el-descriptions-item
|
||||
v-for="(item, index) in devSchema"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:label-class-name="getMainLabel(item.label)"
|
||||
class-name="pure-version"
|
||||
label-align="right"
|
||||
>
|
||||
<span
|
||||
:class="getMainLabel(item.label)"
|
||||
style="color: var(--el-color-primary)"
|
||||
<a
|
||||
:href="'https://www.npmjs.com/package/' + item.label"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item.field }}
|
||||
</span>
|
||||
</a>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<span
|
||||
:class="getMainLabel(item.label)"
|
||||
style="color: var(--el-color-primary)"
|
||||
>
|
||||
{{ item.field }}
|
||||
</span>
|
||||
</a>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,63 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import intro from "intro.js";
|
||||
import "intro.js/minified/introjs.min.css";
|
||||
|
||||
type GuideStep = {
|
||||
element: string | HTMLElement;
|
||||
title: string;
|
||||
intro: string;
|
||||
position: "left" | "right" | "top" | "bottom";
|
||||
};
|
||||
|
||||
defineOptions({
|
||||
name: "Guide"
|
||||
});
|
||||
|
||||
const GUIDE_STEPS = [
|
||||
{
|
||||
element: document.querySelector(".sidebar-logo-container") as
|
||||
| string
|
||||
| HTMLElement,
|
||||
title: "项目名称和Logo",
|
||||
intro: "您可以在这里设置项目名称和Logo",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector("#header-search") as string | HTMLElement,
|
||||
title: "搜索菜单",
|
||||
intro: "您可以在这里搜索想要查看的菜单",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector("#header-notice") as string | HTMLElement,
|
||||
title: "消息通知",
|
||||
intro: "您可以在这里查看管理员发送的消息",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector("#header-translation") as
|
||||
| string
|
||||
| HTMLElement,
|
||||
title: "国际化",
|
||||
intro: "您可以在这里进行语言切换",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector(".set-icon") as string | HTMLElement,
|
||||
title: "项目配置",
|
||||
intro: "您可以在这里查看项目配置",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector(".tags-view") as string | HTMLElement,
|
||||
title: "多标签页",
|
||||
intro: "这里是您访问过的页面的历史",
|
||||
position: "bottom"
|
||||
}
|
||||
] as Partial<GuideStep>[];
|
||||
|
||||
const tourOpen = ref(false);
|
||||
|
||||
const onGuide = () => {
|
||||
intro()
|
||||
.setOptions({
|
||||
steps: [
|
||||
{
|
||||
element: document.querySelector(".sidebar-logo-container") as
|
||||
| string
|
||||
| HTMLElement,
|
||||
title: "项目名称和Logo",
|
||||
intro: "您可以在这里设置项目名称和Logo",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector("#header-search") as
|
||||
| string
|
||||
| HTMLElement,
|
||||
title: "搜索菜单",
|
||||
intro: "您可以在这里搜索想要查看的菜单",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector("#header-notice") as
|
||||
| string
|
||||
| HTMLElement,
|
||||
title: "消息通知",
|
||||
intro: "您可以在这里查看管理员发送的消息",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector("#header-translation") as
|
||||
| string
|
||||
| HTMLElement,
|
||||
title: "国际化",
|
||||
intro: "您可以在这里进行语言切换",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector(".set-icon") as string | HTMLElement,
|
||||
title: "项目配置",
|
||||
intro: "您可以在这里查看项目配置",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
element: document.querySelector(".tags-view") as string | HTMLElement,
|
||||
title: "多标签页",
|
||||
intro: "这里是您访问过的页面的历史",
|
||||
position: "bottom"
|
||||
}
|
||||
]
|
||||
steps: GUIDE_STEPS
|
||||
})
|
||||
.start();
|
||||
};
|
||||
|
||||
const onTour = () => {
|
||||
tourOpen.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -69,6 +81,18 @@ const onGuide = () => {
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-button @click="onGuide"> 打开引导页 </el-button>
|
||||
<el-button @click="onGuide"> 打开引导页 (intro.js) </el-button>
|
||||
<el-button @click="onTour"> 打开引导页 (el-tour) </el-button>
|
||||
|
||||
<el-tour v-model="tourOpen">
|
||||
<el-tour-step
|
||||
v-for="step in GUIDE_STEPS"
|
||||
:key="step.title"
|
||||
:target="() => step.element"
|
||||
:title="step.title"
|
||||
:description="step.intro"
|
||||
:placement="step.position"
|
||||
/>
|
||||
</el-tour>
|
||||
</el-card>
|
||||
</template>
|
||||
|
14
types/global.d.ts
vendored
14
types/global.d.ts
vendored
@ -180,4 +180,18 @@ declare global {
|
||||
$storage: ResponsiveStorage;
|
||||
$config: PlatformConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展 `Elemet`
|
||||
*/
|
||||
interface Element {
|
||||
// v-ripple 作用于 src/directives/ripple/index.ts 文件
|
||||
_ripple?: {
|
||||
enabled?: boolean;
|
||||
centered?: boolean;
|
||||
class?: string;
|
||||
circle?: boolean;
|
||||
touched?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user