Merge branch 'main' of github.com:pure-admin/vue-pure-admin into gitee

This commit is contained in:
xiaoxian521 2023-05-16 14:35:01 +08:00
commit 93498b6643
8 changed files with 145 additions and 53 deletions

View File

@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck", "typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck",
"svgo": "svgo -f src/assets/svg -o src/assets/svg", "svgo": "svgo -f src/assets/svg -o src/assets/svg",
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML", "cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install", "clean:cache": "rimraf node_modules && rimraf .eslintcache && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"", "lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",

View File

@ -105,7 +105,8 @@ watch(
props.modelValue.indexOf(":") + 1 props.modelValue.indexOf(":") + 1
); );
} }
} },
{ immediate: true }
); );
watch( watch(
() => { () => {
@ -140,12 +141,11 @@ watch(
</template> </template>
<el-input <el-input
class="p-2" class="px-2 pt-2"
v-model="filterValue" v-model="filterValue"
placeholder="搜索图标" placeholder="搜索图标"
clearable clearable
/> />
<el-divider border-style="dashed" />
<el-tabs v-model="currentActiveType" @tab-click="handleClick"> <el-tabs v-model="currentActiveType" @tab-click="handleClick">
<el-tab-pane <el-tab-pane
@ -154,24 +154,26 @@ watch(
:label="pane.label" :label="pane.label"
:name="pane.name" :name="pane.name"
> >
<el-divider class="tab-divider" border-style="dashed" />
<el-scrollbar height="220px"> <el-scrollbar height="220px">
<ul class="flex flex-wrap px-2 ml-2"> <ul class="flex flex-wrap px-2 ml-2">
<li <li
v-for="(item, key) in pageList" v-for="(item, key) in pageList"
:key="key" :key="key"
:title="item" :title="item"
class="icon-item p-2 w-[1/10] cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid" class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
:style="iconItemStyle(item)" :style="iconItemStyle(item)"
@click="onChangeIcon(item)" @click="onChangeIcon(item)"
> >
<IconifyIconOnline :icon="currentActiveType + item" /> <IconifyIconOnline
:icon="currentActiveType + item"
width="20px"
height="20px"
/>
</li> </li>
</ul> </ul>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<el-divider border-style="dashed" />
<el-pagination <el-pagination
small small
@ -190,14 +192,6 @@ watch(
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-divider--horizontal {
margin: 1px auto !important;
}
.tab-divider.el-divider--horizontal {
margin: 0 !important;
}
.icon-item { .icon-item {
&:hover { &:hover {
color: var(--el-color-primary); color: var(--el-color-primary);
@ -234,5 +228,10 @@ watch(
:deep(.el-tabs__nav-wrap) { :deep(.el-tabs__nav-wrap) {
position: static; position: static;
margin: 0; margin: 0;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.06);
}
:deep(.el-tabs__content) {
margin-top: 4px;
} }
</style> </style>

View File

@ -1,3 +1,17 @@
<script setup lang="ts">
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
import { useNav } from "@/layout/hooks/useNav";
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
const props = withDefaults(defineProps<{ total: number }>(), {
total: 0
});
const { device } = useNav();
</script>
<template> <template>
<div class="search-footer text-[#333] dark:text-white"> <div class="search-footer text-[#333] dark:text-white">
<span class="search-footer-item"> <span class="search-footer-item">
@ -13,16 +27,15 @@
<mdiKeyboardEsc class="icon" /> <mdiKeyboardEsc class="icon" />
关闭 关闭
</span> </span>
<p
v-if="device !== 'mobile' && props.total > 0"
class="search-footer-total"
>
{{ props.total }}
</p>
</div> </div>
</template> </template>
<script setup lang="ts">
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.search-footer { .search-footer {
display: flex; display: flex;
@ -40,5 +53,10 @@ import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff, box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff,
0 1px 2px 1px #1e235a66; 0 1px 2px 1px #1e235a66;
} }
.search-footer-total {
position: absolute;
right: 20px;
}
} }
</style> </style>

View File

@ -8,7 +8,7 @@ import { transformI18n } from "@/plugins/i18n";
import { ref, computed, shallowRef } from "vue"; import { ref, computed, shallowRef } from "vue";
import { useDebounceFn, onKeyStroke } from "@vueuse/core"; import { useDebounceFn, onKeyStroke } from "@vueuse/core";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import Search from "@iconify-icons/ep/search"; import Search from "@iconify-icons/ri/search-line";
interface Props { interface Props {
/** 弹窗显隐 */ /** 弹窗显隐 */
@ -25,6 +25,8 @@ const props = withDefaults(defineProps<Props>(), {});
const router = useRouter(); const router = useRouter();
const keyword = ref(""); const keyword = ref("");
const scrollbarRef = ref();
const resultRef = ref();
const activePath = ref(""); const activePath = ref("");
const inputRef = ref<HTMLInputElement | null>(null); const inputRef = ref<HTMLInputElement | null>(null);
const resultOptions = shallowRef([]); const resultOptions = shallowRef([]);
@ -83,6 +85,11 @@ function handleClose() {
}, 200); }, 200);
} }
function scrollTo(index) {
const scrollTop = resultRef.value.handleScroll(index);
scrollbarRef.value.setScrollTop(scrollTop);
}
/** key up */ /** key up */
function handleUp() { function handleUp() {
const { length } = resultOptions.value; const { length } = resultOptions.value;
@ -92,8 +99,10 @@ function handleUp() {
); );
if (index === 0) { if (index === 0) {
activePath.value = resultOptions.value[length - 1].path; activePath.value = resultOptions.value[length - 1].path;
scrollTo(resultOptions.value.length - 1);
} else { } else {
activePath.value = resultOptions.value[index - 1].path; activePath.value = resultOptions.value[index - 1].path;
scrollTo(index - 1);
} }
} }
@ -109,6 +118,7 @@ function handleDown() {
} else { } else {
activePath.value = resultOptions.value[index + 1].path; activePath.value = resultOptions.value[index + 1].path;
} }
scrollTo(index + 1);
} }
/** key enter */ /** key enter */
@ -127,41 +137,55 @@ onKeyStroke("ArrowDown", handleDown);
<template> <template>
<el-dialog <el-dialog
top="5vh" top="5vh"
class="pure-search-dialog"
v-model="show" v-model="show"
:width="device === 'mobile' ? '80vw' : '50vw'" :show-close="false"
:width="device === 'mobile' ? '80vw' : '40vw'"
:before-close="handleClose" :before-close="handleClose"
:style="{
borderRadius: '6px'
}"
@opened="inputRef.focus()" @opened="inputRef.focus()"
@closed="inputRef.blur()" @closed="inputRef.blur()"
> >
<el-input <el-input
ref="inputRef" ref="inputRef"
size="large"
v-model="keyword" v-model="keyword"
clearable clearable
placeholder="请输入关键词搜索" placeholder="搜索菜单"
@input="handleSearch" @input="handleSearch"
> >
<template #prefix> <template #prefix>
<span class="el-input__icon"> <IconifyIconOffline
<IconifyIconOffline :icon="Search" /> :icon="Search"
</span> class="text-primary w-[24px] h-[24px]"
/>
</template> </template>
</el-input> </el-input>
<div class="search-result-container"> <div class="search-result-container">
<el-empty v-if="resultOptions.length === 0" description="暂无搜索结果" /> <el-scrollbar ref="scrollbarRef" max-height="600px">
<SearchResult <el-empty
v-else v-if="resultOptions.length === 0"
v-model:value="activePath" description="暂无搜索结果"
:options="resultOptions" />
@click="handleEnter" <SearchResult
/> v-else
ref="resultRef"
v-model:value="activePath"
:options="resultOptions"
@click="handleEnter"
/>
</el-scrollbar>
</div> </div>
<template #footer> <template #footer>
<SearchFooter /> <SearchFooter :total="resultOptions.length" />
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.search-result-container { .search-result-container {
margin-top: 20px; margin-top: 12px;
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { computed, getCurrentInstance } from "vue";
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component"; import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
@ -28,6 +28,7 @@ interface Emits {
const props = withDefaults(defineProps<Props>(), {}); const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const instance = getCurrentInstance()!;
const itemStyle = computed(() => { const itemStyle = computed(() => {
return item => { return item => {
@ -57,22 +58,33 @@ async function handleMouse(item) {
function handleTo() { function handleTo() {
emit("enter"); emit("enter");
} }
function handleScroll(index: number) {
const curInstance = instance?.proxy?.$refs[`resultItemRef${index}`];
if (!curInstance) return 0;
const curRef = curInstance[0] as ElRef;
const scrollTop = curRef.offsetTop + 128; // 128 result-item56px+56px=112pxmargin8px+8px=16px
return scrollTop > 600 ? scrollTop - 600 : 0; // 600 el-scrollbar max-height="600px"
}
defineExpose({ handleScroll });
</script> </script>
<template> <template>
<div class="result"> <div class="result">
<template v-for="item in options" :key="item.path"> <div
<div v-for="(item, index) in options"
class="result-item dark:bg-[#1d1d1d]" :key="item.path"
:style="itemStyle(item)" :ref="'resultItemRef' + index"
@click="handleTo" class="result-item dark:bg-[#1d1d1d]"
@mouseenter="handleMouse(item)" :style="itemStyle(item)"
> @click="handleTo"
<component :is="useRenderIcon(item.meta?.icon ?? Bookmark2Line)" /> @mouseenter="handleMouse(item)"
<span class="result-item-title">{{ t(item.meta?.title) }}</span> >
<enterOutlined /> <component :is="useRenderIcon(item.meta?.icon ?? Bookmark2Line)" />
</div> <span class="result-item-title">{{ t(item.meta?.title) }}</span>
</template> <enterOutlined />
</div>
</div> </div>
</template> </template>

View File

@ -139,6 +139,23 @@ html.dark {
} }
} }
/* 自定义菜单搜索样式 */
.pure-search-dialog {
.el-dialog__footer {
box-shadow: 0 -1px 0 0 #555a64, 0 -3px 6px 0 rgb(69 98 155 / 12%);
}
.search-footer {
.search-footer-item {
color: rgb(235 235 235 / 60%);
.icon {
box-shadow: none;
}
}
}
}
/* ReSegmented 组件 */ /* ReSegmented 组件 */
.pure-segmented { .pure-segmented {
color: rgb(255 255 255 / 65%); color: rgb(255 255 255 / 65%);

View File

@ -148,3 +148,24 @@
} }
} }
} }
/* 自定义菜单搜索样式 */
.pure-search-dialog {
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding-top: 12px;
padding-bottom: 0;
}
.el-input__inner {
font-size: 1.2em;
}
.el-dialog__footer {
padding-bottom: 10px;
box-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgb(69 98 155 / 12%);
}
}

View File

@ -17,11 +17,12 @@ export const useVerifyCode = () => {
await formEl.validateField(props, isValid => { await formEl.validateField(props, isValid => {
if (isValid) { if (isValid) {
clearInterval(timer.value); clearInterval(timer.value);
isDisabled.value = true;
text.value = `${time}`;
timer.value = setInterval(() => { timer.value = setInterval(() => {
if (time > 0) { if (time > 0) {
text.value = `${time}`;
isDisabled.value = true;
time -= 1; time -= 1;
text.value = `${time}`;
} else { } else {
text.value = ""; text.value = "";
isDisabled.value = false; isDisabled.value = false;