mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-09 13:53:38 +08:00
chore:更换到主分支
This commit is contained in:
95
src/components/breadCrumb/index.vue
Normal file
95
src/components/breadCrumb/index.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group appear name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
|
||||
<span
|
||||
v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
|
||||
class="no-redirect"
|
||||
>{{ $t(item.meta.title) }}</span
|
||||
>
|
||||
<a v-else @click.prevent="handleLink(item)">{{
|
||||
$t(item.meta.title)
|
||||
}}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import * as pathToRegexp from "path-to-regexp";
|
||||
import { ref, defineComponent, watch, Ref } from "vue";
|
||||
import { useRoute, useRouter, RouteLocationMatched } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "breadCrumb",
|
||||
setup() {
|
||||
const levelList: Ref<RouteLocationMatched[]> = ref([]);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const isDashboard = (route: RouteLocationMatched): Boolean | string => {
|
||||
const name = route && (route.name as string);
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return name.trim().toLocaleLowerCase() === "welcome".toLocaleLowerCase();
|
||||
};
|
||||
|
||||
const getBreadcrumb = (): void => {
|
||||
let matched = route.matched.filter(
|
||||
(item) => item.meta && item.meta.title
|
||||
);
|
||||
const first = matched[0];
|
||||
if (!isDashboard(first)) {
|
||||
matched = [
|
||||
({
|
||||
path: "/welcome",
|
||||
meta: { title: "home" },
|
||||
} as unknown) as RouteLocationMatched,
|
||||
].concat(matched);
|
||||
}
|
||||
levelList.value = matched.filter(
|
||||
(item) => item.meta && item.meta.title && item.meta.breadcrumb !== false
|
||||
);
|
||||
};
|
||||
|
||||
getBreadcrumb();
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => getBreadcrumb()
|
||||
);
|
||||
|
||||
const pathCompile = (path: string): string | Object => {
|
||||
const { params } = route;
|
||||
var toPath = pathToRegexp.compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
|
||||
const handleLink = (item: RouteLocationMatched): any => {
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect.toString());
|
||||
return;
|
||||
}
|
||||
router.push(pathCompile(path));
|
||||
};
|
||||
|
||||
return { levelList, handleLink };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped >
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
50
src/components/hamBurger/index.vue
Normal file
50
src/components/hamBurger/index.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div style="padding: 0 15px" @click="toggleClick">
|
||||
<svg
|
||||
:class="{ 'is-active': isActive }"
|
||||
class="hamburger"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
<path
|
||||
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
export default defineComponent({
|
||||
name: "hamBurger",
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["toggleClick"],
|
||||
setup(props, ctx) {
|
||||
const toggleClick = () => {
|
||||
ctx.emit("toggleClick");
|
||||
};
|
||||
|
||||
return { toggleClick };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
207
src/components/info/index.vue
Normal file
207
src/components/info/index.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="info">
|
||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" class="rule-form">
|
||||
<el-form-item prop="userName">
|
||||
<el-input
|
||||
clearable
|
||||
v-model="ruleForm.userName"
|
||||
placeholder="请输入用户名"
|
||||
prefix-icon="el-icon-user"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="passWord">
|
||||
<el-input
|
||||
clearable
|
||||
type="password"
|
||||
show-password
|
||||
v-model="ruleForm.passWord"
|
||||
placeholder="请输入密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="verify">
|
||||
<el-input
|
||||
maxlength="2"
|
||||
onkeyup="this.value=this.value.replace(/[^\d.]/g,'');"
|
||||
v-model.number="ruleForm.verify"
|
||||
placeholder="请输入验证码"
|
||||
></el-input>
|
||||
<span
|
||||
class="verify"
|
||||
title="刷新"
|
||||
v-html="ruleForm.svg"
|
||||
@click.prevent="refreshVerify"
|
||||
></span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click.prevent="onBehavior">
|
||||
{{ tipsFalse }}
|
||||
</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<span class="tips" @click="changPage">{{ tips }}</span>
|
||||
</el-form-item>
|
||||
<span title="测试用户 直接登录" class="secret" @click="noSecret"
|
||||
>免密登录</span
|
||||
>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import {
|
||||
ref,
|
||||
defineComponent,
|
||||
PropType,
|
||||
onBeforeMount,
|
||||
getCurrentInstance,
|
||||
watch,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import { storageSession } from "../../utils/storage";
|
||||
|
||||
export interface ContextProps {
|
||||
userName: string;
|
||||
passWord: string;
|
||||
verify: number | null;
|
||||
svg: any;
|
||||
telephone?: number;
|
||||
dynamicText?: string;
|
||||
}
|
||||
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
ruleForm: {
|
||||
type: Object as PropType<ContextProps>,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
emits: ["onBehavior", "refreshVerify"],
|
||||
setup(props, ctx) {
|
||||
let vm: any;
|
||||
|
||||
let tips = ref("注册");
|
||||
let tipsFalse = ref("登录");
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
watch(
|
||||
route,
|
||||
async ({ path }, prevRoute: unknown): Promise<void> => {
|
||||
await nextTick();
|
||||
path.includes("register")
|
||||
? (tips.value = "登录") && (tipsFalse.value = "注册")
|
||||
: (tips.value = "注册") && (tipsFalse.value = "登录");
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const rules: Object = ref({
|
||||
userName: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||
passWord: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ min: 6, message: "密码长度必须不小于6位", trigger: "blur" },
|
||||
],
|
||||
verify: [
|
||||
{ required: true, message: "请输入验证码", trigger: "blur" },
|
||||
{ type: "number", message: "验证码必须是数字类型", trigger: "blur" },
|
||||
],
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
vm = getCurrentInstance(); //获取组件实例
|
||||
});
|
||||
|
||||
// 点击登录或注册
|
||||
const onBehavior = (evt: Object): void => {
|
||||
vm.refs.ruleForm.validate((valid: Boolean) => {
|
||||
if (valid) {
|
||||
ctx.emit("onBehavior", evt);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 刷新验证码
|
||||
const refreshVerify = (): void => {
|
||||
ctx.emit("refreshVerify");
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const resetForm = (): void => {
|
||||
vm.refs.ruleForm.resetFields();
|
||||
};
|
||||
|
||||
// 登录、注册页面切换
|
||||
const changPage = (): void => {
|
||||
tips.value === "注册" ? router.push("/register") : router.push("/login");
|
||||
};
|
||||
|
||||
const noSecret = (): void => {
|
||||
storageSession.setItem("info", {
|
||||
username: "测试用户",
|
||||
accessToken: "eyJhbGciOiJIUzUxMiJ9.test",
|
||||
});
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
return {
|
||||
rules,
|
||||
tips,
|
||||
tipsFalse,
|
||||
resetForm,
|
||||
onBehavior,
|
||||
refreshVerify,
|
||||
changPage,
|
||||
noSecret,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info {
|
||||
width: 30vw;
|
||||
height: 48vh;
|
||||
background: url("../../assets/login.png") no-repeat center;
|
||||
background-size: cover;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
right: 100px;
|
||||
top: 30vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@media screen and (max-width: 750px){
|
||||
width: 88vw;
|
||||
right: 25px;
|
||||
top: 22vh;
|
||||
}
|
||||
.rule-form {
|
||||
width: 80%;
|
||||
.verify {
|
||||
position: absolute;
|
||||
margin: -10px 0 0 -120px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
color: #409eff;
|
||||
float: right;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.secret {
|
||||
color: #409eff;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
216
src/components/splitPane/index.vue
Normal file
216
src/components/splitPane/index.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div
|
||||
:style="{ cursor, userSelect }"
|
||||
class="vue-splitter-container clearfix"
|
||||
@mouseup="onMouseUp"
|
||||
@mousemove="onMouseMove"
|
||||
>
|
||||
<div
|
||||
:class="leftClass"
|
||||
:split="splitSet.split"
|
||||
:style="{ [type]: percent + '%' }"
|
||||
>
|
||||
<slot name="paneL"></slot>
|
||||
</div>
|
||||
|
||||
<resizer
|
||||
:style="{ [resizeType]: percent + '%' }"
|
||||
:split="splitSet.split"
|
||||
@mousedown.prevent="onMouseDown"
|
||||
@click.prevent="onClick"
|
||||
></resizer>
|
||||
|
||||
<div
|
||||
:class="rightClass"
|
||||
:split="splitSet.split"
|
||||
:style="{ [type]: 100 - percent + '%' }"
|
||||
>
|
||||
<slot name="paneR"></slot>
|
||||
</div>
|
||||
|
||||
<div v-if="active" class="vue-splitter-container-mask"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
getCurrentInstance,
|
||||
computed,
|
||||
watch,
|
||||
PropType,
|
||||
onBeforeMount,
|
||||
} from "vue";
|
||||
import resizer from "./resizer.vue";
|
||||
|
||||
export interface ContextProps {
|
||||
minPercent: number;
|
||||
defaultPercent: number;
|
||||
split: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "splitPane",
|
||||
components: { resizer },
|
||||
props: {
|
||||
splitSet: {
|
||||
type: Object as PropType<ContextProps>,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
emits: ["resize"],
|
||||
setup(props, ctx) {
|
||||
let active = ref(false);
|
||||
let hasMoved = ref(false);
|
||||
let height = ref(null);
|
||||
let percent = ref(props.splitSet?.defaultPercent);
|
||||
let type = props.splitSet?.split === "vertical" ? "width" : "height";
|
||||
let resizeType = props.splitSet?.split === "vertical" ? "left" : "top";
|
||||
|
||||
let leftClass = ref([
|
||||
"splitter-pane splitter-paneL",
|
||||
props.splitSet?.split,
|
||||
]);
|
||||
|
||||
let rightClass = ref([
|
||||
"splitter-pane splitter-paneR",
|
||||
props.splitSet?.split,
|
||||
]);
|
||||
|
||||
const userSelect = computed(() => {
|
||||
return active.value ? "none" : "";
|
||||
});
|
||||
|
||||
const cursor = computed(() => {
|
||||
return active.value
|
||||
? props.splitSet?.split === "vertical"
|
||||
? "col-resize"
|
||||
: "row-resize"
|
||||
: "";
|
||||
});
|
||||
|
||||
const onClick = (): void => {
|
||||
if (!hasMoved.value) {
|
||||
percent.value = 50;
|
||||
ctx.emit("resize", percent.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseDown = (): void => {
|
||||
active.value = true;
|
||||
hasMoved.value = false;
|
||||
};
|
||||
|
||||
const onMouseUp = (): void => {
|
||||
active.value = false;
|
||||
};
|
||||
|
||||
const onMouseMove = (e: any): void => {
|
||||
if (e.buttons === 0 || e.which === 0) {
|
||||
active.value = false;
|
||||
}
|
||||
|
||||
if (active.value) {
|
||||
let offset = 0;
|
||||
let target = e.currentTarget;
|
||||
if (props.splitSet?.split === "vertical") {
|
||||
while (target) {
|
||||
offset += target.offsetLeft;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
} else {
|
||||
while (target) {
|
||||
offset += target.offsetTop;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
}
|
||||
|
||||
const currentPage =
|
||||
props.splitSet?.split === "vertical" ? e.pageX : e.pageY;
|
||||
const targetOffset =
|
||||
props.splitSet?.split === "vertical"
|
||||
? e.currentTarget.offsetWidth
|
||||
: e.currentTarget.offsetHeight;
|
||||
const percents =
|
||||
Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100;
|
||||
|
||||
if (
|
||||
percents > props.splitSet?.minPercent &&
|
||||
percents < 100 - props.splitSet?.minPercent
|
||||
) {
|
||||
percent.value = percents;
|
||||
}
|
||||
|
||||
ctx.emit("resize", percent.value);
|
||||
|
||||
hasMoved.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
userSelect,
|
||||
cursor,
|
||||
active,
|
||||
hasMoved,
|
||||
height,
|
||||
percent,
|
||||
type,
|
||||
resizeType,
|
||||
onClick,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
onMouseMove,
|
||||
leftClass: leftClass.value.join(" "),
|
||||
rightClass: rightClass.value.join(" "),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
.vue-splitter-container {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.vue-splitter-container-mask {
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.splitter-pane.vertical.splitter-paneL {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
padding-right: 3px;
|
||||
}
|
||||
.splitter-pane.vertical.splitter-paneR {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
height: 100%;
|
||||
padding-left: 3px;
|
||||
}
|
||||
.splitter-pane.horizontal.splitter-paneL {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
.splitter-pane.horizontal.splitter-paneR {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
padding-top: 3px;
|
||||
}
|
||||
</style>
|
||||
59
src/components/splitPane/resizer.vue
Normal file
59
src/components/splitPane/resizer.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div :class="classes"></div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import { computed, defineComponent } from "vue";
|
||||
export default defineComponent({
|
||||
name: "resizer",
|
||||
props: {
|
||||
split: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
setup(props, ctx) {
|
||||
let classes = computed(() => {
|
||||
return ["splitter-pane-resizer", props.split, props.className].join(" ");
|
||||
});
|
||||
return {
|
||||
classes,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.splitter-pane-resizer {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: #000;
|
||||
position: absolute;
|
||||
opacity: 0.2;
|
||||
z-index: 1;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.splitter-pane-resizer.horizontal {
|
||||
height: 11px;
|
||||
margin: -5px 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
.splitter-pane-resizer.vertical {
|
||||
width: 11px;
|
||||
height: 100%;
|
||||
margin-left: -5px;
|
||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user