This commit is contained in:
2022-12-09 16:41:41 +08:00
parent c1cce5a7c2
commit ff7aa8774f
2003 changed files with 156639 additions and 140 deletions

View File

@@ -0,0 +1,81 @@
<script lang="ts">
export default {
name: "TablePage",
};
</script>
<script lang="ts" setup>
import { LayIcon } from "@layui/icons-vue";
import LayPage from "../page/index.vue";
import { computed, WritableComputedRef } from "vue";
export interface TablePageProps {
showPage?: boolean;
showSkip?: boolean;
showLimit?: boolean;
showCount?: boolean;
showRefresh?: boolean;
current: number;
limits?: number[];
pages?: number;
total: number;
limit: number;
theme?: string;
count?:number;
}
const props = withDefaults(defineProps<TablePageProps>(), {
showPage: true,
showLimit: true,
showSkip: true,
});
const emit = defineEmits(["update:current", "update:limit", "change"]);
const current: WritableComputedRef<number> = computed({
get() {
return props.current;
},
set(val) {
emit("update:current", val);
},
});
const limit: WritableComputedRef<number> = computed({
get() {
return props.limit;
},
set(val) {
emit("update:limit", val);
},
});
const change = (pageData: any) => {
emit("change", pageData);
};
</script>
<template>
<lay-page
:total="total"
:show-page="showPage"
:show-skip="showSkip"
:show-limit="showLimit"
:show-count="showCount"
:show-refresh="showRefresh"
:limits="limits"
:theme="theme"
:pages="pages"
:count="count"
v-model="current"
v-model:limit="limit"
@change="change"
>
<template #prev>
<lay-icon type="layui-icon-left" />
</template>
<template #next>
<lay-icon type="layui-icon-right" />
</template>
</lay-page>
</template>

View File

@@ -0,0 +1,270 @@
<template>
<aside :class="classAside">
<div class="lay-aside-top">
<lay-button
type="primary"
size="xs"
:class="classAsideBtn"
@click="handlerBtnClick()"
>
<lay-icon :type="iconType" size="40"> </lay-icon>
</lay-button>
</div>
<lay-scroll :thumbWidth="0">
<ul>
<li
v-for="(anchor, index) in anchorList"
:key="index"
class="lay-aside-list"
:class="{ active: index === activeIndex }"
@click.prevent="handlerListItemClick(index, anchor)"
>
<a
:href="`#${anchor}`"
class="lay-aside-link"
:class="{ active: index === activeIndex }"
>{{ anchor }}</a
>
</li>
</ul>
</lay-scroll>
</aside>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, shallowRef, watch } from "vue";
const props = defineProps<{
anchors?: Array<string> | string;
currIndex: number;
show: boolean;
}>();
let activeIndex = ref<number>(0);
const show = ref<boolean>(props.show);
const iconType = ref<string>("layui-icon-right");
const anchors: string | string[] | undefined = props.anchors;
const scrollTop = ref<number>(0);
const scrollRefEl = shallowRef<HTMLElement | undefined>(undefined);
let enableAnimation = false;
const anchorList = computed(() => {
return typeof anchors === "string" ? anchors?.split(",") : anchors;
});
const classAside = computed(() => [
"lay-aside",
{ "lay-aside-animation": enableAnimation },
{ "lay-aside-collapse": !show.value },
]);
const classAsideBtn = computed(() => {
let classBtn = [];
if (enableAnimation) {
classBtn = [
"lay-aside-collapse-btn",
"lay-aside-animation",
{ "lay-aside-collapse-btn-collapse": !show.value },
];
} else {
classBtn = [
"lay-aside-collapse-btn",
{ "lay-aside-collapse-btn-collapse": !show.value },
];
enableAnimation = true;
}
return classBtn;
});
const handlerBtnClick = () => {
show.value = !show.value;
};
const handlerListItemClick = (index: number, id: string) => {
activeIndex.value = index;
scrollToTitle(id);
};
/**锚点标签跟随滚动高亮 */
const handlerScroll = () => {
// 距离顶部 90 改变 activeIndex
scrollTop.value = getScrollTop(scrollRefEl.value) + 90;
anchorList.value?.forEach((item, index) => {
const elOffsetTop = document.getElementById(item)?.offsetTop;
if (elOffsetTop) {
if (index === 0 && scrollTop.value < elOffsetTop) {
activeIndex.value = 0;
} else if (scrollTop.value >= elOffsetTop) {
activeIndex.value = index;
}
}
});
};
const handlerCollapse = () => {
iconType.value = show.value ? "layui-icon-right" : "layui-icon-left";
// @ts-ignore
scrollRefEl.value!.firstElementChild!.style.marginRight = show.value
? "180px"
: "0px";
};
watch(show, () => {
handlerCollapse();
});
onMounted(() => {
// @ts-ignore TODO 封装 hooks
scrollRefEl.value = document.querySelector(".layui-body");
if (!scrollRefEl.value) {
throw new Error(`scroll element is not existed: ".layui-body"`);
}
scrollRefEl.value.scrollTop = 0;
scrollRefEl.value?.addEventListener("scroll", throttle(handlerScroll, 500));
// 如果已折叠,关闭组件初始渲染时的动画,然后自动开启
// @ts-ignore
show.value =
// @ts-ignore
scrollRefEl.value!.firstElementChild!.style.marginRight !== "0px";
enableAnimation = show.value;
});
/**获取滚动高度 */
const getScrollTop = (el: HTMLElement | undefined): number => {
return el
? el.scrollTop
: window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop ||
0;
};
/**平滑滚动 */
const scrollToTitle = (id: string): void => {
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});
};
const throttle = (func: Function, wait: number) => {
var timer: any = null;
return (...args: any) => {
if (!timer) {
timer = setTimeout(() => {
timer = null;
func.apply(this, args);
}, wait);
}
};
};
</script>
<style lang="less" scoped>
.lay-aside {
position: fixed;
top: 65px;
right: 17px;
box-sizing: border-box;
width: 180px;
padding: 0 25px;
border-left: 1px solid rgb(229 230 235);
transition: none;
-webkit-transition: none;
height: calc(100% - 60px);
}
.lay-aside-collapse {
right: -180px;
opacity: 0.7;
}
.lay-aside-top {
height: 29px;
}
.lay-aside-link {
display: inline-block;
padding: 1px 4px;
color: grey;
font-size: 13px;
line-height: 2;
max-width: 140px;
min-width: 68px;
text-decoration: none;
background-color: transparent;
border-radius: 2px;
cursor: pointer;
transition: all 0.1s cubic-bezier(0, 0, 1, 1);
}
.lay-aside-list {
position: relative;
margin: 5px 0px 0px 4px;
padding-left: 2px;
max-width: 140px;
border-radius: 2px;
list-style: none;
&:hover {
background-color: #f6f6f6 !important;
color: var(--global-checked-color);
}
&:active {
background-color: #f6f6f6 !important;
color: var(--global-checked-color);
}
&.active {
background-color: #f6f6f6 !important;
* {
color: var(--global-checked-color);
}
}
}
.lay-aside-collapse-btn {
position: fixed;
right: 197px;
top: calc(50% - 20px);
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 40px;
background-color: #f6f6f6;
border-radius: 0px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border: rgb(229 230 235) 1px solid;
border-right: none;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
transition: none;
-webkit-transition: none;
color: rgba(0, 0, 0, 0.8);
&:hover {
background-color: #e2e2e2;
}
}
.lay-aside-collapse-btn-collapse {
right: 0px;
}
.lay-aside-animation {
transition: right 200ms;
-webkit-transition: right 200ms;
}
@media screen and (max-width: 768px) {
.lay-aside {
width: 100px !important;
}
.lay-aside-collapse-btn {
right: 98px;
}
.lay-aside-collapse-btn-collapse {
right: 15px;
}
.lay-aside-list {
max-width: 68px;
}
}
</style>

View File

@@ -0,0 +1,146 @@
<script lang="ts">
export default {
name: "LayRate",
};
</script>
<script setup lang="ts">
import { computed, ref, watch, withDefaults } from "vue";
import "./index.less";
export interface RateProps {
theme?: string;
length?: number;
modelValue: number;
readonly?: boolean | string;
half?: boolean;
text?: boolean;
isBlock?: boolean;
allowClear?: boolean;
clearIcon?: string;
icons?: string[];
}
const props = withDefaults(defineProps<RateProps>(), {
length: 5,
modelValue: 0,
readonly: false,
half: false,
text: false,
isBlock: false,
allowClear: false,
clearIcon: "layui-icon-close-fill",
icons: () => [
"layui-icon-rate",
"layui-icon-rate-half",
"layui-icon-rate-solid",
],
});
const emit = defineEmits(["update:modelValue", "select", "clear"]);
const currentValue = ref<number>(props.modelValue);
// 临时存储值
const tempValue = ref(currentValue.value);
// 是否存在半颗星
const isHalf = computed(
() => props.half && Math.round(currentValue.value) !== currentValue.value
);
watch(
() => props.modelValue,
() => {
currentValue.value = props.modelValue;
tempValue.value = props.modelValue;
}
);
// 计算评分星值
const getValue = function (index: number, event: any): number {
if (!props.half) {
return index;
}
return index - (event.offsetX <= event.target.offsetWidth / 2 ? 0.5 : 0);
};
// 在评分星移动事件
const mousemove = function (index: number, event: any) {
if (props.readonly) {
return false;
}
currentValue.value = getValue(index, event);
};
// 离开评分星事件
const mouseleave = function () {
if (props.readonly) {
return false;
}
currentValue.value = tempValue.value;
};
// 选择评分星 --> 单击事件
const action = function (index: number, event: any) {
if (props.readonly) {
return false;
}
currentValue.value = getValue(index, event);
tempValue.value = currentValue.value;
emit("update:modelValue", currentValue.value);
emit("select", currentValue.value);
};
// 清除评分图标
const showClearIcon = computed(() => !props.readonly && props.allowClear);
const clearRate = function () {
tempValue.value = 0;
currentValue.value = 0;
emit("clear", currentValue.value);
};
</script>
<template>
<div :class="isBlock ? 'layui-block' : 'layui-inline'">
<ul class="layui-rate" @mouseleave="mouseleave">
<li
v-for="index of length"
:key="index"
class="layui-inline"
@mousemove="mousemove(index, $event)"
@click="action(index, $event)"
>
<i
v-if="index <= Math.ceil(currentValue)"
:class="[
'layui-icon',
`${
icons[
icons.length -
(isHalf && index === Math.ceil(currentValue) ? 2 : 1)
]
}`,
]"
:style="{ color: theme }"
></i>
<i
v-else
:class="['layui-icon'].concat(icons[0])"
:style="{ color: theme }"
></i>
</li>
</ul>
<template v-if="text">
<span class="layui-inline">
<slot :value="currentValue">
{{ currentValue + "星" }}
</slot>
</span>
</template>
<template v-if="showClearIcon">
<i
:class="['layui-icon', 'layui-rate-clear-icon', clearIcon]"
@click="clearRate"
title="清除评分"
></i>
</template>
</div>
</template>