feat: 添加权限管理
This commit is contained in:
7
src/components/ReAnimateSelector/index.ts
Normal file
7
src/components/ReAnimateSelector/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import reAnimateSelector from "./src/index.vue";
|
||||
|
||||
/** [animate.css](https://animate.style/) 选择器组件 */
|
||||
export const ReAnimateSelector = withInstall(reAnimateSelector);
|
||||
|
||||
export default ReAnimateSelector;
|
||||
114
src/components/ReAnimateSelector/src/animate.ts
Normal file
114
src/components/ReAnimateSelector/src/animate.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
export const animates = [
|
||||
/* Attention seekers */
|
||||
"bounce",
|
||||
"flash",
|
||||
"pulse",
|
||||
"rubberBand",
|
||||
"shakeX",
|
||||
"headShake",
|
||||
"swing",
|
||||
"tada",
|
||||
"wobble",
|
||||
"jello",
|
||||
"heartBeat",
|
||||
/* Back entrances */
|
||||
"backInDown",
|
||||
"backInLeft",
|
||||
"backInRight",
|
||||
"backInUp",
|
||||
/* Back exits */
|
||||
"backOutDown",
|
||||
"backOutLeft",
|
||||
"backOutRight",
|
||||
"backOutUp",
|
||||
/* Bouncing entrances */
|
||||
"bounceIn",
|
||||
"bounceInDown",
|
||||
"bounceInLeft",
|
||||
"bounceInRight",
|
||||
"bounceInUp",
|
||||
/* Bouncing exits */
|
||||
"bounceOut",
|
||||
"bounceOutDown",
|
||||
"bounceOutLeft",
|
||||
"bounceOutRight",
|
||||
"bounceOutUp",
|
||||
/* Fading entrances */
|
||||
"fadeIn",
|
||||
"fadeInDown",
|
||||
"fadeInDownBig",
|
||||
"fadeInLeft",
|
||||
"fadeInLeftBig",
|
||||
"fadeInRight",
|
||||
"fadeInRightBig",
|
||||
"fadeInUp",
|
||||
"fadeInUpBig",
|
||||
"fadeInTopLeft",
|
||||
"fadeInTopRight",
|
||||
"fadeInBottomLeft",
|
||||
"fadeInBottomRight",
|
||||
/* Fading exits */
|
||||
"fadeOut",
|
||||
"fadeOutDown",
|
||||
"fadeOutDownBig",
|
||||
"fadeOutLeft",
|
||||
"fadeOutLeftBig",
|
||||
"fadeOutRight",
|
||||
"fadeOutRightBig",
|
||||
"fadeOutUp",
|
||||
"fadeOutUpBig",
|
||||
"fadeOutTopLeft",
|
||||
"fadeOutTopRight",
|
||||
"fadeOutBottomRight",
|
||||
"fadeOutBottomLeft",
|
||||
/* Flippers */
|
||||
"flip",
|
||||
"flipInX",
|
||||
"flipInY",
|
||||
"flipOutX",
|
||||
"flipOutY",
|
||||
/* Lightspeed */
|
||||
"lightSpeedInRight",
|
||||
"lightSpeedInLeft",
|
||||
"lightSpeedOutRight",
|
||||
"lightSpeedOutLeft",
|
||||
/* Rotating entrances */
|
||||
"rotateIn",
|
||||
"rotateInDownLeft",
|
||||
"rotateInDownRight",
|
||||
"rotateInUpLeft",
|
||||
"rotateInUpRight",
|
||||
/* Rotating exits */
|
||||
"rotateOut",
|
||||
"rotateOutDownLeft",
|
||||
"rotateOutDownRight",
|
||||
"rotateOutUpLeft",
|
||||
"rotateOutUpRight",
|
||||
/* Specials */
|
||||
"hinge",
|
||||
"jackInTheBox",
|
||||
"rollIn",
|
||||
"rollOut",
|
||||
/* Zooming entrances */
|
||||
"zoomIn",
|
||||
"zoomInDown",
|
||||
"zoomInLeft",
|
||||
"zoomInRight",
|
||||
"zoomInUp",
|
||||
/* Zooming exits */
|
||||
"zoomOut",
|
||||
"zoomOutDown",
|
||||
"zoomOutLeft",
|
||||
"zoomOutRight",
|
||||
"zoomOutUp",
|
||||
/* Sliding entrances */
|
||||
"slideInDown",
|
||||
"slideInLeft",
|
||||
"slideInRight",
|
||||
"slideInUp",
|
||||
/* Sliding exits */
|
||||
"slideOutDown",
|
||||
"slideOutLeft",
|
||||
"slideOutRight",
|
||||
"slideOutUp"
|
||||
];
|
||||
136
src/components/ReAnimateSelector/src/index.vue
Normal file
136
src/components/ReAnimateSelector/src/index.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { animates } from "./animate";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "ReAnimateSelector"
|
||||
});
|
||||
|
||||
defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择动画"
|
||||
}
|
||||
});
|
||||
|
||||
const inputValue = defineModel({ type: String });
|
||||
|
||||
const searchVal = ref();
|
||||
const animatesList = ref(animates);
|
||||
const copyAnimatesList = cloneDeep(animatesList);
|
||||
|
||||
const animateClass = computed(() => {
|
||||
return [
|
||||
"mt-1",
|
||||
"flex",
|
||||
"border",
|
||||
"w-[130px]",
|
||||
"h-[100px]",
|
||||
"items-center",
|
||||
"cursor-pointer",
|
||||
"transition-all",
|
||||
"justify-center",
|
||||
"border-[#e5e7eb]",
|
||||
"hover:text-primary",
|
||||
"hover:duration-[700ms]"
|
||||
];
|
||||
});
|
||||
|
||||
const animateStyle = computed(
|
||||
() => (i: string) =>
|
||||
inputValue.value === i
|
||||
? {
|
||||
borderColor: "var(--el-color-primary)",
|
||||
color: "var(--el-color-primary)"
|
||||
}
|
||||
: ""
|
||||
);
|
||||
|
||||
function onChangeIcon(animate: string) {
|
||||
inputValue.value = animate;
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
inputValue.value = "";
|
||||
}
|
||||
|
||||
function filterMethod(value: any) {
|
||||
searchVal.value = value;
|
||||
animatesList.value = copyAnimatesList.value.filter((i: string | any[]) =>
|
||||
i.includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const animateMap = ref({});
|
||||
function onMouseEnter(index: string | number) {
|
||||
animateMap.value[index] = animateMap.value[index]?.loading
|
||||
? Object.assign({}, animateMap.value[index], {
|
||||
loading: false
|
||||
})
|
||||
: Object.assign({}, animateMap.value[index], {
|
||||
loading: true
|
||||
});
|
||||
}
|
||||
function onMouseleave() {
|
||||
animateMap.value = {};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-select
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="placeholder"
|
||||
popper-class="pure-animate-popper"
|
||||
:model-value="inputValue"
|
||||
:filter-method="filterMethod"
|
||||
@clear="onClear"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="w-[280px]">
|
||||
<el-scrollbar
|
||||
noresize
|
||||
height="212px"
|
||||
:view-style="{ overflow: 'hidden' }"
|
||||
class="border-t border-[#e5e7eb]"
|
||||
>
|
||||
<ul class="flex flex-wrap justify-around mb-1">
|
||||
<li
|
||||
v-for="(animate, index) in animatesList"
|
||||
:key="index"
|
||||
:class="animateClass"
|
||||
:style="animateStyle(animate)"
|
||||
@mouseenter.prevent="onMouseEnter(index)"
|
||||
@mouseleave.prevent="onMouseleave"
|
||||
@click="onChangeIcon(animate)"
|
||||
>
|
||||
<h4
|
||||
:class="[
|
||||
`animate__animated animate__${
|
||||
animateMap[index]?.loading
|
||||
? animate + ' animate__infinite'
|
||||
: ''
|
||||
} `
|
||||
]"
|
||||
>
|
||||
{{ animate }}
|
||||
</h4>
|
||||
</li>
|
||||
</ul>
|
||||
<el-empty
|
||||
v-show="animatesList.length === 0"
|
||||
:description="`${searchVal} 动画不存在`"
|
||||
:image-size="60"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.pure-animate-popper {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
</style>
|
||||
3869
src/components/ReIcon/data.ts
Normal file
3869
src/components/ReIcon/data.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,15 @@
|
||||
import iconifyIconOffline from "./src/iconifyIconOffline";
|
||||
import iconifyIconOnline from "./src/iconifyIconOnline";
|
||||
import iconSelect from "./src/Select.vue";
|
||||
import fontIcon from "./src/iconfont";
|
||||
|
||||
/** 本地图标组件 */
|
||||
const IconifyIconOffline = iconifyIconOffline;
|
||||
/** 在线图标组件 */
|
||||
const IconifyIconOnline = iconifyIconOnline;
|
||||
/** `IconSelect`图标选择器组件 */
|
||||
const IconSelect = iconSelect;
|
||||
/** `iconfont`组件 */
|
||||
const FontIcon = fontIcon;
|
||||
|
||||
export { IconifyIconOffline, IconifyIconOnline, FontIcon };
|
||||
export { IconifyIconOffline, IconifyIconOnline, IconSelect, FontIcon };
|
||||
|
||||
268
src/components/ReIcon/src/Select.vue
Normal file
268
src/components/ReIcon/src/Select.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<script setup lang="ts">
|
||||
import { IconJson } from "@/components/ReIcon/data";
|
||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
|
||||
import { ref, computed, CSSProperties, watch } from "vue";
|
||||
import Search from "@iconify-icons/ri/search-eye-line";
|
||||
|
||||
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
|
||||
|
||||
defineOptions({
|
||||
name: "IconSelect"
|
||||
});
|
||||
|
||||
const inputValue = defineModel({ type: String });
|
||||
|
||||
const iconList = ref(IconJson);
|
||||
const icon = ref();
|
||||
const currentActiveType = ref("ep:");
|
||||
// 深拷贝图标数据,前端做搜索
|
||||
const copyIconList = cloneDeep(iconList.value);
|
||||
const totalPage = ref(0);
|
||||
// 每页显示35个图标
|
||||
const pageSize = ref(35);
|
||||
const currentPage = ref(1);
|
||||
|
||||
// 搜索条件
|
||||
const filterValue = ref("");
|
||||
|
||||
const tabsList = [
|
||||
{
|
||||
label: "Element Plus",
|
||||
name: "ep:"
|
||||
},
|
||||
{
|
||||
label: "Remix Icon",
|
||||
name: "ri:"
|
||||
},
|
||||
{
|
||||
label: "Font Awesome 5 Solid",
|
||||
name: "fa-solid:"
|
||||
}
|
||||
];
|
||||
|
||||
const pageList = computed(() =>
|
||||
copyIconList[currentActiveType.value]
|
||||
.filter(i => i.includes(filterValue.value))
|
||||
.slice(
|
||||
(currentPage.value - 1) * pageSize.value,
|
||||
currentPage.value * pageSize.value
|
||||
)
|
||||
);
|
||||
|
||||
const iconItemStyle = computed((): ParameterCSSProperties => {
|
||||
return item => {
|
||||
if (inputValue.value === currentActiveType.value + item) {
|
||||
return {
|
||||
borderColor: "var(--el-color-primary)",
|
||||
color: "var(--el-color-primary)"
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function setVal() {
|
||||
currentActiveType.value = inputValue.value.substring(
|
||||
0,
|
||||
inputValue.value.indexOf(":") + 1
|
||||
);
|
||||
icon.value = inputValue.value.substring(inputValue.value.indexOf(":") + 1);
|
||||
}
|
||||
|
||||
function onBeforeEnter() {
|
||||
if (isAllEmpty(icon.value)) return;
|
||||
setVal();
|
||||
// 寻找当前图标在第几页
|
||||
const curIconIndex = copyIconList[currentActiveType.value].findIndex(
|
||||
i => i === icon.value
|
||||
);
|
||||
currentPage.value = Math.ceil((curIconIndex + 1) / pageSize.value);
|
||||
}
|
||||
|
||||
function onAfterLeave() {
|
||||
filterValue.value = "";
|
||||
}
|
||||
|
||||
function handleClick({ props }) {
|
||||
currentPage.value = 1;
|
||||
currentActiveType.value = props.name;
|
||||
}
|
||||
|
||||
function onChangeIcon(item) {
|
||||
icon.value = item;
|
||||
inputValue.value = currentActiveType.value + item;
|
||||
}
|
||||
|
||||
function onCurrentChange(page) {
|
||||
currentPage.value = page;
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
icon.value = "";
|
||||
inputValue.value = "";
|
||||
}
|
||||
|
||||
watch(
|
||||
() => pageList.value,
|
||||
() =>
|
||||
(totalPage.value = copyIconList[currentActiveType.value].filter(i =>
|
||||
i.includes(filterValue.value)
|
||||
).length),
|
||||
{ immediate: true }
|
||||
);
|
||||
watch(
|
||||
() => inputValue.value,
|
||||
val => val && setVal(),
|
||||
{ immediate: true }
|
||||
);
|
||||
watch(
|
||||
() => filterValue.value,
|
||||
() => (currentPage.value = 1)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="selector">
|
||||
<el-input v-model="inputValue" disabled>
|
||||
<template #append>
|
||||
<el-popover
|
||||
:width="350"
|
||||
trigger="click"
|
||||
popper-class="pure-popper"
|
||||
:popper-options="{
|
||||
placement: 'auto'
|
||||
}"
|
||||
@before-enter="onBeforeEnter"
|
||||
@after-leave="onAfterLeave"
|
||||
>
|
||||
<template #reference>
|
||||
<div
|
||||
class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center"
|
||||
>
|
||||
<IconifyIconOffline v-if="!icon" :icon="Search" />
|
||||
<IconifyIconOnline v-else :icon="inputValue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-input
|
||||
v-model="filterValue"
|
||||
class="px-2 pt-2"
|
||||
placeholder="搜索图标"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<el-tabs v-model="currentActiveType" @tab-click="handleClick">
|
||||
<el-tab-pane
|
||||
v-for="(pane, index) in tabsList"
|
||||
:key="index"
|
||||
:label="pane.label"
|
||||
:name="pane.name"
|
||||
>
|
||||
<el-scrollbar height="220px">
|
||||
<ul class="flex flex-wrap px-2 ml-2">
|
||||
<li
|
||||
v-for="(item, key) in pageList"
|
||||
:key="key"
|
||||
:title="item"
|
||||
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
|
||||
:style="iconItemStyle(item)"
|
||||
@click="onChangeIcon(item)"
|
||||
>
|
||||
<IconifyIconOnline
|
||||
:icon="currentActiveType + item"
|
||||
width="20px"
|
||||
height="20px"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<el-empty
|
||||
v-show="pageList.length === 0"
|
||||
:description="`${filterValue} 图标不存在`"
|
||||
:image-size="60"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div
|
||||
class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]"
|
||||
>
|
||||
<el-pagination
|
||||
class="flex-auto ml-2"
|
||||
:total="totalPage"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:pager-count="5"
|
||||
layout="pager"
|
||||
background
|
||||
size="small"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
<el-button
|
||||
class="justify-end mr-2 ml-2"
|
||||
type="danger"
|
||||
size="small"
|
||||
text
|
||||
bg
|
||||
@click="onClear"
|
||||
>
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-item {
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
transition: all 0.4s;
|
||||
transform: scaleX(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-next) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: -5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-prev) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: 5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-input-group__append) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header),
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
position: static;
|
||||
margin: 0;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -27,8 +27,7 @@ export default defineComponent({
|
||||
return h(
|
||||
"svg",
|
||||
{
|
||||
class: "icon-svg",
|
||||
"aria-hidden": true
|
||||
class: "icon-svg"
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
|
||||
@@ -17,6 +17,7 @@ export default defineComponent({
|
||||
IconifyIcon,
|
||||
{
|
||||
icon: this.icon,
|
||||
"aria-hidden": false,
|
||||
style: attrs?.style
|
||||
? Object.assign(attrs.style, { outline: "none" })
|
||||
: { outline: "none" },
|
||||
|
||||
@@ -17,6 +17,7 @@ export default defineComponent({
|
||||
IconifyIcon,
|
||||
{
|
||||
icon: `${this.icon}`,
|
||||
"aria-hidden": false,
|
||||
style: attrs?.style
|
||||
? Object.assign(attrs.style, { outline: "none" })
|
||||
: { outline: "none" },
|
||||
|
||||
@@ -3,12 +3,68 @@ import { addIcon } from "@iconify/vue/dist/offline";
|
||||
|
||||
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
|
||||
// @iconify-icons/ep
|
||||
import Menu from "@iconify-icons/ep/menu";
|
||||
import Edit from "@iconify-icons/ep/edit";
|
||||
import SetUp from "@iconify-icons/ep/set-up";
|
||||
import Guide from "@iconify-icons/ep/guide";
|
||||
import Monitor from "@iconify-icons/ep/monitor";
|
||||
import Lollipop from "@iconify-icons/ep/lollipop";
|
||||
import Histogram from "@iconify-icons/ep/histogram";
|
||||
import HomeFilled from "@iconify-icons/ep/home-filled";
|
||||
addIcon("ep:menu", Menu);
|
||||
addIcon("ep:edit", Edit);
|
||||
addIcon("ep:set-up", SetUp);
|
||||
addIcon("ep:guide", Guide);
|
||||
addIcon("ep:monitor", Monitor);
|
||||
addIcon("ep:lollipop", Lollipop);
|
||||
addIcon("ep:histogram", Histogram);
|
||||
addIcon("ep:home-filled", HomeFilled);
|
||||
// @iconify-icons/ri
|
||||
import Tag from "@iconify-icons/ri/bookmark-2-line";
|
||||
import Ppt from "@iconify-icons/ri/file-ppt-2-line";
|
||||
import Card from "@iconify-icons/ri/bank-card-line";
|
||||
import Role from "@iconify-icons/ri/admin-fill";
|
||||
import Info from "@iconify-icons/ri/file-info-line";
|
||||
import Dept from "@iconify-icons/ri/git-branch-line";
|
||||
import Table from "@iconify-icons/ri/table-line";
|
||||
import Links from "@iconify-icons/ri/links-fill";
|
||||
import Search from "@iconify-icons/ri/search-line";
|
||||
import FlUser from "@iconify-icons/ri/admin-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
import MindMap from "@iconify-icons/ri/mind-map";
|
||||
import BarChart from "@iconify-icons/ri/bar-chart-horizontal-line";
|
||||
import LoginLog from "@iconify-icons/ri/window-line";
|
||||
import Artboard from "@iconify-icons/ri/artboard-line";
|
||||
import SystemLog from "@iconify-icons/ri/file-search-line";
|
||||
import ListCheck from "@iconify-icons/ri/list-check";
|
||||
import UbuntuFill from "@iconify-icons/ri/ubuntu-fill";
|
||||
import OnlineUser from "@iconify-icons/ri/user-voice-line";
|
||||
import EditBoxLine from "@iconify-icons/ri/edit-box-line";
|
||||
import OperationLog from "@iconify-icons/ri/history-fill";
|
||||
import InformationLine from "@iconify-icons/ri/information-line";
|
||||
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
|
||||
import CheckboxCircleLine from "@iconify-icons/ri/checkbox-circle-line";
|
||||
addIcon("ri:bookmark-2-line", Tag);
|
||||
addIcon("ri:file-ppt-2-line", Ppt);
|
||||
addIcon("ri:bank-card-line", Card);
|
||||
addIcon("ri:admin-fill", Role);
|
||||
addIcon("ri:file-info-line", Info);
|
||||
addIcon("ri:git-branch-line", Dept);
|
||||
addIcon("ri:links-fill", Links);
|
||||
addIcon("ri:table-line", Table);
|
||||
addIcon("ri:search-line", Search);
|
||||
addIcon("ri:admin-line", FlUser);
|
||||
addIcon("ri:settings-3-line", Setting);
|
||||
addIcon("ri:mind-map", MindMap);
|
||||
addIcon("ri:bar-chart-horizontal-line", BarChart);
|
||||
addIcon("ri:window-line", LoginLog);
|
||||
addIcon("ri:file-search-line", SystemLog);
|
||||
addIcon("ri:artboard-line", Artboard);
|
||||
addIcon("ri:list-check", ListCheck);
|
||||
addIcon("ri:ubuntu-fill", UbuntuFill);
|
||||
addIcon("ri:user-voice-line", OnlineUser);
|
||||
addIcon("ri:edit-box-line", EditBoxLine);
|
||||
addIcon("ri:history-fill", OperationLog);
|
||||
addIcon("ri:information-line", InformationLine);
|
||||
addIcon("ri:terminal-window-line", TerminalWindowLine);
|
||||
addIcon("ri:checkbox-circle-line", CheckboxCircleLine);
|
||||
|
||||
7
src/components/ReSelector/index.ts
Normal file
7
src/components/ReSelector/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import reSelector from "./src";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 选择器组件 */
|
||||
export const ReSelector = withInstall(reSelector);
|
||||
|
||||
export default ReSelector;
|
||||
28
src/components/ReSelector/src/index.css
Normal file
28
src/components/ReSelector/src/index.css
Normal file
@@ -0,0 +1,28 @@
|
||||
.hs-rate__icon {
|
||||
font-size: 18px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.hs-item {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
box-sizing: border-box;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.hs-on {
|
||||
background-color: #409eff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.hs-range {
|
||||
background-color: #f2f6fc;
|
||||
}
|
||||
|
||||
.both-left-sides {
|
||||
border-radius: 50% 0 0 50%;
|
||||
}
|
||||
|
||||
.both-right-sides {
|
||||
border-radius: 0 50% 50% 0;
|
||||
}
|
||||
327
src/components/ReSelector/src/index.tsx
Normal file
327
src/components/ReSelector/src/index.tsx
Normal file
@@ -0,0 +1,327 @@
|
||||
import "./index.css";
|
||||
import {
|
||||
unref,
|
||||
computed,
|
||||
nextTick,
|
||||
onBeforeMount,
|
||||
defineComponent,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { addClass, removeClass, toggleClass } from "@pureadmin/utils";
|
||||
|
||||
const stayClass = "stay"; //鼠标点击
|
||||
const activeClass = "hs-on"; //鼠标移动上去
|
||||
const voidClass = "hs-off"; //鼠标移开
|
||||
const inRange = "hs-range"; //当前选中的两个元素之间的背景
|
||||
const bothLeftSides = "both-left-sides";
|
||||
const bothRightSides = "both-right-sides";
|
||||
let selectedDirection = "right"; //默认从左往右,索引变大
|
||||
|
||||
let overList = [];
|
||||
// 存放第一个选中的元素和最后一个选中元素,只能存放这两个元素
|
||||
let selectedList = [];
|
||||
|
||||
const props = {
|
||||
HsKey: {
|
||||
type: Number || String,
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
max: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
}
|
||||
},
|
||||
// 回显数据的索引,长度必须是2
|
||||
echo: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReSelector",
|
||||
props,
|
||||
emits: ["selectedVal"],
|
||||
setup(props, { emit }) {
|
||||
const instance = getCurrentInstance();
|
||||
const currentValue = props.value;
|
||||
|
||||
const rateDisabled = computed(() => {
|
||||
return props.disabled;
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
const result = [];
|
||||
let i = 0;
|
||||
let threshold = currentValue;
|
||||
if (currentValue !== Math.floor(currentValue)) {
|
||||
threshold--;
|
||||
}
|
||||
for (; i < threshold; i++) {
|
||||
result.push(activeClass);
|
||||
}
|
||||
for (; i < props.max.length; i++) {
|
||||
result.push(voidClass);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
// 鼠标移入
|
||||
const setCurrentValue = index => {
|
||||
if (props.disabled) return;
|
||||
// 当选中一个元素后,开始添加背景色
|
||||
if (selectedList.length === 1) {
|
||||
if (overList.length < 1) overList.push({ index });
|
||||
|
||||
let firstIndex = overList[0].index;
|
||||
|
||||
// 往右走,索引变大
|
||||
if (index > firstIndex) {
|
||||
selectedDirection = "right";
|
||||
toggleClass(
|
||||
false,
|
||||
bothRightSides,
|
||||
document.querySelector(".hs-select__item" + selectedList[0].index)
|
||||
);
|
||||
|
||||
while (index >= firstIndex) {
|
||||
addClass(
|
||||
document.querySelector(".hs-select__item" + firstIndex),
|
||||
inRange
|
||||
);
|
||||
firstIndex++;
|
||||
}
|
||||
} else {
|
||||
selectedDirection = "left";
|
||||
toggleClass(
|
||||
true,
|
||||
bothRightSides,
|
||||
document.querySelector(".hs-select__item" + selectedList[0].index)
|
||||
);
|
||||
|
||||
while (index <= firstIndex) {
|
||||
addClass(
|
||||
document.querySelector(".hs-select__item" + firstIndex),
|
||||
inRange
|
||||
);
|
||||
firstIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addClass(document.querySelector("." + voidClass + index), activeClass);
|
||||
};
|
||||
|
||||
// 鼠标离开
|
||||
const resetCurrentValue = index => {
|
||||
if (props.disabled) return;
|
||||
// 移除先检查是否选中 选中则返回false 不移除
|
||||
const currentHsDom = document.querySelector("." + voidClass + index);
|
||||
if (currentHsDom.className.includes(stayClass)) {
|
||||
return false;
|
||||
} else {
|
||||
removeClass(currentHsDom, activeClass);
|
||||
}
|
||||
|
||||
// 当选中一个元素后,开始移除背景色
|
||||
if (selectedList.length === 1) {
|
||||
const firstIndex = overList[0].index;
|
||||
if (index >= firstIndex) {
|
||||
for (let i = 0; i <= index; i++) {
|
||||
removeClass(
|
||||
document.querySelector(".hs-select__item" + i),
|
||||
inRange
|
||||
);
|
||||
}
|
||||
} else {
|
||||
while (index <= firstIndex) {
|
||||
removeClass(
|
||||
document.querySelector(".hs-select__item" + index),
|
||||
inRange
|
||||
);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 鼠标点击
|
||||
const selectValue = (index, item) => {
|
||||
if (props.disabled) return;
|
||||
const len = selectedList.length;
|
||||
|
||||
if (len < 2) {
|
||||
selectedList.push({ item, index });
|
||||
addClass(document.querySelector("." + voidClass + index), stayClass);
|
||||
|
||||
addClass(
|
||||
document.querySelector(".hs-select__item" + selectedList[0].index),
|
||||
bothLeftSides
|
||||
);
|
||||
|
||||
if (selectedList[1]) {
|
||||
if (selectedDirection === "right") {
|
||||
addClass(
|
||||
document.querySelector(
|
||||
".hs-select__item" + selectedList[1].index
|
||||
),
|
||||
bothRightSides
|
||||
);
|
||||
} else {
|
||||
addClass(
|
||||
document.querySelector(
|
||||
".hs-select__item" + selectedList[1].index
|
||||
),
|
||||
bothLeftSides
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (len === 1) {
|
||||
// 顺时针排序
|
||||
if (selectedDirection === "right") {
|
||||
emit("selectedVal", {
|
||||
left: selectedList[0].item,
|
||||
right: selectedList[1].item,
|
||||
whole: selectedList
|
||||
});
|
||||
} else {
|
||||
emit("selectedVal", {
|
||||
left: selectedList[1].item,
|
||||
right: selectedList[0].item,
|
||||
whole: selectedList
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nextTick(() => {
|
||||
selectedList.forEach(v => {
|
||||
removeClass(
|
||||
document.querySelector("." + voidClass + v.index),
|
||||
activeClass,
|
||||
stayClass
|
||||
);
|
||||
|
||||
removeClass(
|
||||
document.querySelector(".hs-select__item" + v.index),
|
||||
bothLeftSides,
|
||||
bothRightSides
|
||||
);
|
||||
});
|
||||
|
||||
selectedList = [];
|
||||
overList = [];
|
||||
for (let i = 0; i <= props.max.length; i++) {
|
||||
const currentDom = document.querySelector(".hs-select__item" + i);
|
||||
if (currentDom) {
|
||||
removeClass(currentDom, inRange);
|
||||
}
|
||||
}
|
||||
|
||||
selectedList.push({ item, index });
|
||||
addClass(document.querySelector("." + voidClass + index), stayClass);
|
||||
|
||||
addClass(
|
||||
document.querySelector(".hs-select__item" + selectedList[0].index),
|
||||
bothLeftSides
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 回显数据
|
||||
const echoView = item => {
|
||||
if (item.length === 0) return;
|
||||
|
||||
if (item.length > 2 || item.length === 1) {
|
||||
throw "传入的数组长度必须是2";
|
||||
}
|
||||
|
||||
item.sort((a, b) => {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
addClass(
|
||||
instance.refs["hsdiv" + props.HsKey + item[0]] as Element,
|
||||
activeClass,
|
||||
stayClass
|
||||
);
|
||||
|
||||
addClass(
|
||||
instance.refs["hstd" + props.HsKey + item[0]] as Element,
|
||||
bothLeftSides
|
||||
);
|
||||
|
||||
addClass(
|
||||
instance.refs["hsdiv" + props.HsKey + item[1]] as Element,
|
||||
activeClass,
|
||||
stayClass
|
||||
);
|
||||
|
||||
addClass(
|
||||
instance.refs["hstd" + props.HsKey + item[1]] as Element,
|
||||
bothRightSides
|
||||
);
|
||||
|
||||
while (item[1] >= item[0]) {
|
||||
addClass(
|
||||
instance.refs["hstd" + props.HsKey + item[0]] as Element,
|
||||
inRange
|
||||
);
|
||||
item[0]++;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
nextTick(() => {
|
||||
echoView(props.echo);
|
||||
});
|
||||
});
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
{props.max.map((item, key) => {
|
||||
return (
|
||||
<td
|
||||
data-index={props.HsKey}
|
||||
ref={`hstd${props.HsKey}${key}`}
|
||||
class={`hs-select__item${key}`}
|
||||
onMousemove={() => setCurrentValue(key)}
|
||||
onMouseleave={() => resetCurrentValue(key)}
|
||||
onClick={() => selectValue(key, item)}
|
||||
style={{
|
||||
cursor: unref(rateDisabled) ? "auto" : "pointer",
|
||||
textAlign: "center"
|
||||
}}
|
||||
key={key}
|
||||
>
|
||||
<div
|
||||
ref={`hsdiv${props.HsKey}${key}`}
|
||||
class={`hs-item ${[unref(classes)[key] + key]}`}
|
||||
>
|
||||
<span>{item}</span>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user