refactor ts

This commit is contained in:
LiuHao
2023-04-02 01:01:56 +08:00
parent 5c87f1cb2c
commit 251d2411f2
264 changed files with 15432 additions and 13015 deletions

View File

@ -1,57 +1,55 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group 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">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script setup lang="ts">
import { RouteLocationMatched } from 'vue-router'
<script setup>
const route = useRoute();
const router = useRouter();
const levelList = ref([])
const levelList = ref<RouteLocationMatched[]>([])
function getBreadcrumb() {
const getBreadcrumb = () => {
// only show routes with meta.title
let matched = route.matched.filter(item => item.meta && item.meta.title);
const first = matched[0]
// 判断是否为首页
if (!isDashboard(first)) {
matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched)
matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched)
}
levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
function isDashboard(route) {
const name = route && route.name
const isDashboard = (route: RouteLocationMatched) => {
const name = route && route.name as string
if (!name) {
return false
}
return name.trim() === 'Index'
}
function handleLink(item) {
const handleLink = (item: RouteLocationMatched) => {
const { redirect, path } = item
if (redirect) {
router.push(redirect)
return
}
router.push(path)
redirect ? router.push(redirect as string) : router.push(path)
}
watchEffect(() => {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
if (route.path.startsWith('/redirect/')) return
getBreadcrumb()
})
getBreadcrumb();
onMounted(() => {
getBreadcrumb();
})
</script>
<style lang='scss' scoped>
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group 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">{{
item.meta?.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
@ -63,4 +61,4 @@ getBreadcrumb();
cursor: text;
}
}
</style>
</style>

View File

@ -1,31 +1,10 @@
<template>
<div>
<template v-for="(item, index) in options">
<template v-if="values.includes(item.value)">
<span
v-if="item.elTagType == 'default' || item.elTagType == ''"
:key="item.value"
:index="index"
:class="item.elTagClass"
>{{ item.label }}</span>
<el-tag
v-else
:disable-transitions="true"
:key="item.value + ''"
:index="index"
:type="item.elTagType === 'primary' ? '' : item.elTagType"
:class="item.elTagClass"
>{{ item.label }}</el-tag>
</template>
</template>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
<script setup>
const props = defineProps({
// 数据
options: {
type: Array,
type: Array as PropType<DictDataOption[]>,
default: null,
},
// 当前的值
@ -33,17 +12,41 @@ const props = defineProps({
})
const values = computed(() => {
if (props.value !== null && typeof props.value !== 'undefined') {
return Array.isArray(props.value) ? props.value : [String(props.value)];
} else {
return [];
}
if (props.value !== null && typeof props.value !== 'undefined') {
return Array.isArray(props.value) ? props.value : [String(props.value)];
} else {
return [];
}
})
</script>
<template>
<div>
<template v-for="(item, index) in options">
<template v-if="values.includes(item.value)">
<span
v-if="item.elTagType == 'default' || item.elTagType == ''"
:key="item.value"
:index="index"
:class="item.elTagClass"
>{{ item.label }}</span
>
<el-tag
v-else
:disable-transitions="true"
:key="item.value + ''"
:index="index"
:type="item.elTagType === 'primary' ? '' : item.elTagType"
:class="item.elTagClass"
>{{ item.label }}</el-tag
>
</template>
</template>
</div>
</template>
<style scoped>
.el-tag + .el-tag {
margin-left: 10px;
}
</style>
</style>

View File

@ -1,36 +1,8 @@
<template>
<div>
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
class="editor-img-uploader"
name="file"
:show-file-list="false"
:headers="headers"
style="display: none"
ref="uploadRef"
v-if="type == 'url'"
>
</el-upload>
<div class="editor">
<quill-editor
ref="myQuillEditor"
v-model:content="content"
contentType="html"
@textChange="(e) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
/>
</div>
</div>
</template>
<script setup>
<script setup lang="ts">
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { getToken } from "@/utils/auth";
import { ComponentInternalInstance } from "vue";
const props = defineProps({
/* 编辑器的内容 */
@ -64,10 +36,12 @@ const props = defineProps({
}
});
const { proxy } = getCurrentInstance();
// 上传的图片服务器地址
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/system/oss/upload");
const headers = ref({ Authorization: "Bearer " + getToken() });
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const upload = reactive<UploadOption>({
headers: { Authorization: "Bearer " + getToken() },
url: import.meta.env.VITE_APP_BASE_API + '/system/oss/upload'
})
const myQuillEditor = ref();
const options = ref({
@ -90,10 +64,10 @@ const options = ref({
["link", "image", "video"] // 链接、图片、视频
],
handlers: {
image: function (value) {
image: function (value: any) {
if (value) {
// 调用element图片上传
document.querySelector(".editor-img-uploader>.el-upload").click();
(document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
} else {
Quill.format("image", true);
}
@ -106,7 +80,7 @@ const options = ref({
});
const styles = computed(() => {
let style = {};
let style: any = {};
if (props.minHeight) {
style.minHeight = `${props.minHeight}px`;
}
@ -118,51 +92,79 @@ const styles = computed(() => {
const content = ref("");
watch(() => props.modelValue, (v) => {
if (v !== content) {
if (v !== content.value) {
content.value = v === undefined ? "<p></p>" : v;
}
}, { immediate: true });
// 图片上传成功返回图片地址
function handleUploadSuccess(res, file) {
const handleUploadSuccess = (res: any) => {
// 获取富文本实例
let quill = toRaw(myQuillEditor.value).getQuill();
// 如果上传成功
if (res.code == 200) {
if (res.code === 200) {
// 获取光标位置
let length = quill.selection.savedRange.index;
// 插入图片res为服务器返回的图片链接地址
quill.insertEmbed(length, "image", res.data.url);
// 调整光标到最后
quill.setSelection(length + 1);
proxy.$modal.closeLoading();
proxy?.$modal.closeLoading();
} else {
proxy.$modal.loading(res.msg);
proxy.$modal.closeLoading();
proxy?.$modal.loading(res.msg);
proxy?.$modal.closeLoading();
}
}
// 图片上传前拦截
function handleBeforeUpload(file) {
const handleBeforeUpload = (file: any) => {
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传文件,请稍候...");
proxy?.$modal.loading('正在上传文件,请稍候...');
return true;
}
// 图片失败拦截
function handleUploadError(err) {
proxy.$modal.msgError("上传文件失败");
const handleUploadError = (err: any) => {
console.error(err);
proxy?.$modal.msgError('上传文件失败');
}
</script>
<template>
<div>
<el-upload
:action="upload.url"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
class="editor-img-uploader"
name="file"
:show-file-list="false"
:headers="upload.headers"
style="display: none"
v-if="type === 'url'"
>
</el-upload>
<div class="editor">
<quill-editor
ref="myQuillEditor"
v-model:content="content"
contentType="html"
@textChange="(e: any) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
/>
</div>
</div>
</template>
<style>
.editor, .ql-toolbar {
white-space: pre-wrap !important;
@ -175,9 +177,9 @@ function handleUploadError(err) {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
border-right: 0;
content: "保存";
padding-right: 0px;
padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {

View File

@ -1,46 +1,8 @@
<template>
<div class="upload-file">
<el-upload
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
class="upload-file-uploader"
ref="fileUpload"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
的文件
</div>
<!-- 文件列表 -->
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<el-link :href="`${file.url}`" :underline="false" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
</div>
</li>
</transition-group>
</div>
</template>
<script setup>
<script setup lang="ts">
import { getToken } from "@/utils/auth";
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance } from "vue";
import { ElUpload, UploadFile } from "element-plus";
const props = defineProps({
modelValue: [String, Object, Array],
@ -66,32 +28,35 @@ const props = defineProps({
}
});
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emit = defineEmits(['update:modelValue']);
const number = ref(0);
const uploadList = ref([]);
const uploadList = ref<any[]>([]);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = ref(baseUrl + "/system/oss/upload"); // 上传文件服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const fileList = ref<any[]>([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
const fileUploadRef = ref(ElUpload);
watch(() => props.modelValue, async val => {
if (val) {
let temp = 1;
// 首先将值转为数组
let list;
let list = [];
if (Array.isArray(val)) {
list = val;
} else {
await listByIds(val).then(res => {
list = res.data.map(oss => {
oss = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
return oss;
const res = await listByIds(val as string)
list = res.data.map((oss) => {
const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
return data;
});
})
}
// 然后将数组转为对象数组
fileList.value = list.map(item => {
@ -106,14 +71,14 @@ watch(() => props.modelValue, async val => {
},{ deep: true, immediate: true });
// 上传前校检格式和大小
function handleBeforeUpload(file) {
const handleBeforeUpload = (file: any) => {
// 校检文件类型
if (props.fileType.length) {
const fileName = file.name.split('.');
const fileExt = fileName[fileName.length - 1];
const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
return false;
}
}
@ -121,41 +86,41 @@ function handleBeforeUpload(file) {
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传文件,请稍候...");
proxy?.$modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
const handleExceed = () => {
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传失败
function handleUploadError(err) {
proxy.$modal.msgError("上传文件失败");
const handleUploadError = () => {
proxy?.$modal.msgError("上传文件失败");
}
// 上传成功回调
function handleUploadSuccess(res, file) {
const handleUploadSuccess = (res:any, file: UploadFile) => {
if (res.code === 200) {
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
uploadedSuccessfully();
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg);
proxy.$refs.fileUpload.handleRemove(file);
proxy?.$modal.closeLoading();
proxy?.$modal.msgError(res.msg);
fileUploadRef.value.handleRemove(file);
uploadedSuccessfully();
}
}
// 删除文件
function handleDelete(index) {
const handleDelete = (index: number) => {
let ossId = fileList.value[index].ossId;
delOss(ossId);
fileList.value.splice(index, 1);
@ -163,18 +128,18 @@ function handleDelete(index) {
}
// 上传结束处理
function uploadedSuccessfully() {
const uploadedSuccessfully =() => {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
proxy?.$modal.closeLoading();
}
}
// 获取文件名称
function getFileName(name) {
const getFileName = (name: string) => {
// 如果是url那么取最后的名字 如果不是直接返回
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
@ -184,18 +149,62 @@ function getFileName(name) {
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
const listToString = (list: any[], separator?: string) => {
let strs = "";
separator = separator || ",";
for (let i in list) {
if(list[i].ossId) {
strs += list[i].ossId + separator;
list.forEach(item => {
if (item.ossId) {
strs += item.ossId + separator;
}
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
})
return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
</script>
<template>
<div class="upload-file">
<el-upload
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
class="upload-file-uploader"
ref="fileUploadRef"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
<!-- 文件列表 -->
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<el-link :href="`${file.url}`" :underline="false" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
</div>
</li>
</transition-group>
</div>
</template>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;

View File

@ -1,19 +1,4 @@
<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 setup>
<script setup lang="ts">
defineProps({
isActive: {
type: Boolean,
@ -21,12 +6,22 @@ defineProps({
}
})
const emit = defineEmits()
const emit = defineEmits(['toggleClick'])
const toggleClick = () => {
emit('toggleClick');
}
</script>
<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>
<style scoped>
.hamburger {
display: inline-block;

View File

@ -1,49 +1,36 @@
<template>
<div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelectRef"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select>
</div>
</template>
<script setup>
<script setup lang="ts">
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate'
import usePermissionStore from '@/store/modules/permission'
import { RouteOption } from 'vue-router'
type Router = Array<{
path: string;
title: string[];
}>
const search = ref('');
const options = ref([]);
const searchPool = ref([]);
const options = ref<any>([]);
const searchPool = ref<Router>([]);
const show = ref(false);
const fuse = ref(undefined);
const headerSearchSelectRef = ref(null);
const fuse = ref();
const headerSearchSelectRef = ref(ElSelect);
const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
function click() {
const click = () => {
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
}
};
function close() {
const close = () => {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
options.value = []
show.value = false
}
function change(val) {
const change = (val: any) => {
const path = val.path;
if (isHttp(path)) {
// http(s):// 路径新窗口打开
@ -52,14 +39,13 @@ function change(val) {
} else {
router.push(path)
}
search.value = ''
options.value = []
nextTick(() => {
show.value = false
})
}
function initFuse(list) {
const initFuse = (list: Router) => {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
@ -77,39 +63,36 @@ function initFuse(list) {
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
function generateRoutes(routes, basePath = '', prefixTitle = []) {
let res = []
for (const r of routes) {
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
let res: Router = []
routes.forEach(r => {
// skip hidden router
if (r.hidden) { continue }
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle]
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title]
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
if (!r.hidden) {
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle]
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
})
return res;
}
function querySearch(query) {
const querySearch = (query: string) => {
if (query !== '') {
options.value = fuse.value.search(query)
} else {
@ -138,7 +121,26 @@ watch(searchPool, (list) => {
})
</script>
<style lang='scss' scoped>
<template>
<div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelectRef"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select>
</div>
</template>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
@ -176,4 +178,4 @@ watch(searchPool, (list) => {
}
}
}
</style>
</style>

View File

@ -1,74 +1,105 @@
<template>
<div class="icon-body">
<el-input
v-model="iconName"
style="position: relative;"
clearable
placeholder="请输入图标名称"
@clear="filterIcons"
@input="filterIcons"
>
<template #suffix><i class="el-icon-search el-input__icon" /></template>
</el-input>
<div class="icon-list">
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
<svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
<span>{{ item }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import icons from '@/components/IconSelect/requireIcons';
<script setup>
import icons from './requireIcons'
const props = defineProps({
modelValue: {
type: String,
require: true
},
width: {
type: String,
require: false,
default: '400px'
}
});
const iconName = ref('');
const iconList = ref(icons);
const emit = defineEmits(['selected']);
const emit = defineEmits(['update:modelValue']);
const visible = ref(false);
const { modelValue, width } = toRefs(props);
const iconNames = ref<string[]>(icons);
function filterIcons() {
iconList.value = icons
if (iconName.value) {
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
const filterValue = ref('');
/**
* 筛选图标
*/
const filterIcons = () => {
if (filterValue.value) {
iconNames.value = icons.filter(iconName =>
iconName.includes(filterValue.value)
);
} else {
iconNames.value = icons;
}
}
function selectedIcon(name) {
emit('selected', name)
document.body.click()
/**
* 选择图标
* @param iconName 选择的图标名称
*/
const selectedIcon = (iconName: string) => {
emit('update:modelValue', iconName);
visible.value = false;
}
function reset() {
iconName.value = ''
iconList.value = icons
}
defineExpose({
reset
})
</script>
<style lang='scss' scoped>
.icon-body {
width: 100%;
padding: 10px;
.icon-list {
height: 200px;
overflow-y: scroll;
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
<template>
<div class="relative" :style="{ width: width }">
<el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
<template #prepend>
<svg-icon :icon-class="modelValue as string"></svg-icon>
</template>
</el-input>
<el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" :width="450">
<template #reference>
<div @click="visible = !visible" class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]">
<i-ep-caret-top v-show="visible"></i-ep-caret-top>
<i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom>
</div>
</template>
<el-input class="p-2" v-model="filterValue" placeholder="搜索图标" clearable @input="filterIcons" />
<el-scrollbar height="w-[200px]">
<ul class="icon-list">
<el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light">
<li class="icon-item" @click="selectedIcon(iconName)">
<svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" />
</li>
</el-tooltip>
</ul>
</el-scrollbar>
</el-popover>
</div>
</template>
<style scoped lang="scss">
.el-divider--horizontal {
margin: 10px auto !important;
}
.icon-list {
display: flex;
flex-wrap: wrap;
padding-left: 10px;
margin-top: 10px;
.icon-item {
cursor: pointer;
width: 10%;
margin: 0 10px 10px 0;
padding: 5px;
display: flex;
flex-direction: column;
justify-items: center;
align-items: center;
border: 1px solid #ccc;
&:hover {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
transition: all 0.2s;
transform: scaleX(1.1);
}
}
}
</style>
</style>

View File

@ -1,8 +0,0 @@
let icons = []
const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
for (const path in modules) {
const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
icons.push(p);
}
export default icons

View File

@ -0,0 +1,7 @@
const icons: string[] = [];
const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
for (const path in modules) {
const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
icons.push(p);
}
export default icons;

View File

@ -1,21 +1,4 @@
<template>
<el-image
:src="`${realSrc}`"
fit="cover"
:style="`width:${realWidth};height:${realHeight};`"
:preview-src-list="realSrcList"
preview-teleported
>
<template #error>
<div class="image-slot">
<el-icon><picture-filled /></el-icon>
</div>
</template>
</el-image>
</template>
<script setup>
<script setup lang="ts">
const props = defineProps({
src: {
type: String,
@ -44,7 +27,7 @@ const realSrcList = computed(() => {
return;
}
let real_src_list = props.src.split(",");
let srcList = [];
let srcList:string[] = [];
real_src_list.forEach(item => {
return srcList.push(item);
});
@ -60,6 +43,16 @@ const realHeight = computed(() =>
);
</script>
<template>
<el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported>
<template #error>
<div class="image-slot">
<el-icon><picture-filled /></el-icon>
</div>
</template>
</el-image>
</template>
<style lang="scss" scoped>
.el-image {
border-radius: 5px;

View File

@ -1,53 +1,9 @@
<template>
<div class="component-upload-image">
<el-upload
multiple
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
ref="imageUpload"
:before-remove="handleDelete"
:show-file-list="true"
:headers="headers"
:file-list="fileList"
:on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= limit }"
>
<el-icon class="avatar-uploader-icon"><plus /></el-icon>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
<el-dialog
v-model="dialogVisible"
title="预览"
width="800px"
append-to-body
>
<img
:src="dialogImageUrl"
style="display: block; max-width: 100%; margin: 0 auto"
/>
</el-dialog>
</div>
</template>
<script setup>
<script setup lang="ts">
import { getToken } from "@/utils/auth";
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance, PropType } from "vue";
import { OssVO } from "@/api/system/oss/types";
import { ElUpload, UploadFile } from "element-plus";
const props = defineProps({
modelValue: [String, Object, Array],
@ -63,7 +19,7 @@ const props = defineProps({
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
type: Array as PropType<string[]>,
default: () => ["png", "jpg", "jpeg"],
},
// 是否显示提示
@ -73,41 +29,45 @@ const props = defineProps({
},
});
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emit = defineEmits(['update:modelValue']);
const number = ref(0);
const uploadList = ref([]);
const uploadList = ref<any[]>([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(baseUrl + "/system/oss/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const fileList = ref<any[]>([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
const imageUploadRef = ref(ElUpload);
watch(() => props.modelValue, async val => {
if (val) {
// 首先将值转为数组
let list;
let list:OssVO[] = [];
if (Array.isArray(val)) {
list = val;
list = val as OssVO[];
} else {
await listByIds(val).then(res => {
list = res.data;
})
const res = await listByIds(val as string)
list = res.data
}
// 然后将数组转为对象数组
fileList.value = list.map(item => {
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
let itemData;
if (typeof item === "string") {
item = { name: item, url: item };
itemData = { name: item, url: item };
} else {
// 此处name使用ossId 防止删除出现重名
item = { name: item.ossId, url: item.url, ossId: item.ossId };
itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
}
return item;
return itemData;
});
} else {
fileList.value = [];
@ -115,8 +75,8 @@ watch(() => props.modelValue, async val => {
}
},{ deep: true, immediate: true });
// 上传前loading加载
function handleBeforeUpload(file) {
/** 上传前loading加载 */
const handleBeforeUpload = (file: any) => {
let isImg = false;
if (props.fileType.length) {
let fileExtension = "";
@ -132,7 +92,7 @@ function handleBeforeUpload(file) {
isImg = file.type.indexOf("image") > -1;
}
if (!isImg) {
proxy.$modal.msgError(
proxy?.$modal.msgError(
`文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
);
return false;
@ -140,35 +100,35 @@ function handleBeforeUpload(file) {
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传图片,请稍候...");
proxy?.$modal.loading("正在上传图片,请稍候...");
number.value++;
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
const handleExceed = () => {
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传成功回调
function handleUploadSuccess(res, file) {
const handleUploadSuccess = (res: any, file: UploadFile) => {
if (res.code === 200) {
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
uploadedSuccessfully();
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg);
proxy.$refs.imageUpload.handleRemove(file);
proxy?.$modal.closeLoading();
proxy?.$modal.msgError(res.msg);
imageUploadRef.value.handleRemove(file);
uploadedSuccessfully();
}
}
// 删除图片
function handleDelete(file) {
const handleDelete = (file: UploadFile): boolean => {
const findex = fileList.value.map(f => f.name).indexOf(file.name);
if (findex > -1 && uploadList.value.length === number.value) {
let ossId = fileList.value[findex].ossId;
@ -177,33 +137,34 @@ function handleDelete(file) {
emit("update:modelValue", listToString(fileList.value));
return false;
}
return true;
}
// 上传结束处理
function uploadedSuccessfully() {
const uploadedSuccessfully = () => {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
proxy?.$modal.closeLoading();
}
}
// 上传失败
function handleUploadError(res) {
proxy.$modal.msgError("上传图片失败");
proxy.$modal.closeLoading();
const handleUploadError = () => {
proxy?.$modal.msgError("上传图片失败");
proxy?.$modal.closeLoading();
}
// 预览
function handlePictureCardPreview(file) {
const handlePictureCardPreview = (file: any) => {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
const listToString = (list: any[], separator?: string) => {
let strs = "";
separator = separator || ",";
for (let i in list) {
@ -211,13 +172,52 @@ function listToString(list, separator) {
strs += list[i].ossId + separator;
}
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
</script>
<template>
<div class="component-upload-image">
<el-upload
multiple
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
ref="imageUpload"
:before-remove="handleDelete"
:show-file-list="true"
:headers="headers"
:file-list="fileList"
:on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= limit }"
>
<el-icon class="avatar-uploader-icon"><plus /></el-icon>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
</el-dialog>
</div>
</template>
<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {
display: none;
}
</style>
</style>

View File

@ -1,21 +1,12 @@
<template>
<div :class="{ 'hidden': hidden }" class="pagination-container">
<el-pagination
:background="background"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script lang="ts">
export default {
name: 'Pagination'
}
</script>
<script setup>
<script setup lang="ts">
import { scrollTo } from '@/utils/scroll-to'
import { PropType } from "vue";
const props = defineProps({
total: {
@ -31,7 +22,7 @@ const props = defineProps({
default: 20
},
pageSizes: {
type: Array,
type: Array as PropType<number[]>,
default() {
return [10, 20, 30, 50]
}
@ -56,10 +47,14 @@ const props = defineProps({
hidden: {
type: Boolean,
default: false
},
float: {
type: String,
default: 'right'
}
})
const emit = defineEmits();
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
const currentPage = computed({
get() {
return props.page
@ -76,7 +71,7 @@ const pageSize = computed({
emit('update:limit', val)
}
})
function handleSizeChange(val) {
function handleSizeChange(val: number) {
if (currentPage.value * val > props.total) {
currentPage.value = 1
}
@ -85,21 +80,39 @@ function handleSizeChange(val) {
scrollTo(0, 800)
}
}
function handleCurrentChange(val) {
function handleCurrentChange(val: number) {
emit('pagination', { page: val, limit: pageSize.value })
if (props.autoScroll) {
scrollTo(0, 800)
}
}
</script>
<style scoped>
<template>
<div :class="{ 'hidden': hidden }" class="pagination-container">
<el-pagination
:background="background"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<style lang="scss" scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
.el-pagination{
float: v-bind(float);
}
}
.pagination-container.hidden {
display: none;
}
</style>
</style>

View File

@ -1,3 +1,3 @@
<template >
<router-view />
<template>
<router-view />
</template>

View File

@ -1,35 +1,14 @@
<template>
<div class="top-right-btn" :style="style">
<el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" />
</el-tooltip>
</el-row>
<el-dialog :title="title" v-model="open" append-to-body>
<el-transfer
:titles="['显示', '隐藏']"
v-model="value"
:data="columns"
@change="dataChange"
></el-transfer>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { TransferKey } from "element-plus";
import { PropType } from "vue";
<script setup>
const props = defineProps({
showSearch: {
type: Boolean,
default: true,
},
columns: {
type: Array,
type: Array as PropType<FieldOption[]>,
},
search: {
type: Boolean,
@ -44,14 +23,14 @@ const props = defineProps({
const emits = defineEmits(['update:showSearch', 'queryTable']);
// 显隐数据
const value = ref([]);
const value = ref<Array<number>>([]);
// 弹出层标题
const title = ref("显示/隐藏");
// 是否显示弹出层
const open = ref(false);
const style = computed(() => {
const ret = {};
const ret: any = {};
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
}
@ -69,27 +48,47 @@ function refresh() {
}
// 右侧列表元素变化
function dataChange(data) {
for (let item in props.columns) {
const key = props.columns[item].key;
props.columns[item].visible = !data.includes(key);
}
function dataChange(data: TransferKey[]) {
props.columns?.forEach((item) => {
item.visible = !data.includes(item.key);
})
}
// 打开显隐列dialog
function showColumn() {
const showColumn = () => {
open.value = true;
}
// 显隐列初始默认隐藏列
for (let item in props.columns) {
if (props.columns[item].visible === false) {
value.value.push(parseInt(item));
}
}
onMounted(() => {
props.columns?.forEach((item) => {
if (!item.visible) {
value.value.push(item.key);
}
})
})
</script>
<style lang='scss' scoped>
<template>
<div class="top-right-btn" :style="style">
<el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" />
</el-tooltip>
</el-row>
<el-dialog :title="title" v-model="open" append-to-body>
<el-transfer :titles="['显示', '隐藏']" v-model="value" :data="columns" @change="dataChange"></el-transfer>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;
@ -102,4 +101,4 @@ for (let item in props.columns) {
.my-el-transfer {
text-align: center;
}
</style>
</style>

View File

@ -1,7 +1,7 @@
<template>
<div>
<svg-icon icon-class="question" @click="goto" />
</div>
<div>
<svg-icon icon-class="question" @click="goto" />
</div>
</template>
<script setup>
@ -10,4 +10,4 @@ const url = ref('https://javalionli.gitee.io/plus-doc');
function goto() {
window.open(url.value)
}
</script>
</script>

View File

@ -1,7 +1,7 @@
<template>
<div>
<svg-icon icon-class="github" @click="goto" />
</div>
<div>
<svg-icon icon-class="github" @click="goto" />
</div>
</template>
<script setup>

View File

@ -1,16 +1,14 @@
<template>
<div>
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
</div>
<div>
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
</div>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen();
<script setup lang="ts">
const { isFullscreen, toggle } = useFullscreen();
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
@ -19,4 +17,4 @@ const { isFullscreen, enter, exit, toggle } = useFullscreen();
height: 20px;
vertical-align: 10px;
}
</style>
</style>

View File

@ -1,45 +1,44 @@
<template>
<div>
<el-dropdown trigger="click" @command="handleSetSize">
<div class="size-icon--style">
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value">
{{ item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup>
<script setup lang="ts">
import useAppStore from "@/store/modules/app";
import { ComponentInternalInstance } from "vue";
const appStore = useAppStore();
const size = computed(() => appStore.size);
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const sizeOptions = ref([
{ label: "较大", value: "large" },
{ label: "默认", value: "default" },
{ label: "稍小", value: "small" },
]);
function handleSetSize(size) {
proxy.$modal.loading("正在设置布局大小,请稍候...");
const handleSetSize = (size: string) => {
proxy?.$modal.loading("正在设置布局大小,请稍候...");
appStore.setSize(size);
setTimeout("window.location.reload()", 1000);
}
</script>
<style lang='scss' scoped>
<template>
<div>
<el-dropdown trigger="click" @command="handleSetSize">
<div class="size-icon--style">
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value">
{{ item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<style lang="scss" scoped>
.size-icon--style {
font-size: 18px;
line-height: 50px;
padding-right: 7px;
}
</style>
</style>

View File

@ -1,39 +1,33 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName" :fill="color" />
</svg>
</template>
<script>
export default defineComponent({
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
},
color: {
type: String,
default: ''
},
<script setup lang="ts">
const props = defineProps({
iconClass: {
type: String,
required: true
},
setup(props) {
return {
iconName: computed(() => `#icon-${props.iconClass}`),
svgClass: computed(() => {
if (props.className) {
return `svg-icon ${props.className}`
}
return 'svg-icon'
})
}
className: {
type: String,
default: ''
},
color: {
type: String,
default: ''
},
})
const iconName = computed(() => `#icon-${props.iconClass}`);
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`
}
return 'svg-icon'
})
</script>
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName" :fill="color" />
</svg>
</template>
<style scope lang="scss">
.sub-el-icon,
.nav-icon {

View File

@ -1,10 +0,0 @@
import * as components from '@element-plus/icons-vue'
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};

View File

@ -1,44 +1,15 @@
<template>
<el-menu
:default-active="activeMenu"
mode="horizontal"
@select="handleSelect"
:ellipsis="false"
>
<template v-for="(item, index) in topMenus">
<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
><svg-icon :icon-class="item.meta.icon" />
{{ item.meta.title }}</el-menu-item
>
</template>
<!-- 顶部菜单超出数量折叠 -->
<el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
<template #title>更多菜单</template>
<template v-for="(item, index) in topMenus">
<el-menu-item
:index="item.path"
:key="index"
v-if="index >= visibleNumber"
><svg-icon :icon-class="item.meta.icon" />
{{ item.meta.title }}</el-menu-item
>
</template>
</el-sub-menu>
</el-menu>
</template>
<script setup>
import { constantRoutes } from "@/router"
import { isHttp } from '@/utils/validate'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
<script setup lang="ts">
import { constantRoutes } from '@/router';
import { isHttp } from '@/utils/validate';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
// 顶部栏初始数
const visibleNumber = ref(null);
const visibleNumber = ref<number>(-1);
// 当前激活菜单的 index
const currentIndex = ref(null);
const currentIndex = ref<string>();
// 隐藏侧边栏路由
const hideList = ['/index', '/user/profile'];
@ -55,12 +26,12 @@ const routers = computed(() => permissionStore.topbarRouters);
// 顶部显示菜单
const topMenus = computed(() => {
let topMenus = [];
let topMenus:RouteOption[] = [];
routers.value.map((menu) => {
if (menu.hidden !== true) {
// 兼容顶部栏一级菜单内部跳转
if (menu.path === "/") {
topMenus.push(menu.children[0]);
topMenus.push(menu.children? menu.children[0] : menu);
} else {
topMenus.push(menu);
}
@ -71,21 +42,21 @@ const topMenus = computed(() => {
// 设置子路由
const childrenMenus = computed(() => {
let childrenMenus = [];
let childrenMenus:RouteOption[] = [];
routers.value.map((router) => {
for (let item in router.children) {
if (router.children[item].parentPath === undefined) {
router.children?.forEach((item) => {
if (item.parentPath === undefined) {
if(router.path === "/") {
router.children[item].path = "/" + router.children[item].path;
item.path = "/" + item.path;
} else {
if(!isHttp(router.children[item].path)) {
router.children[item].path = router.path + "/" + router.children[item].path;
if(!isHttp(item.path)) {
item.path = router.path + "/" + item.path;
}
}
router.children[item].parentPath = router.path;
item.parentPath = router.path;
}
childrenMenus.push(router.children[item]);
}
childrenMenus.push(item);
})
})
return constantRoutes.concat(childrenMenus);
})
@ -108,12 +79,12 @@ const activeMenu = computed(() => {
return activePath;
})
function setVisibleNumber() {
const setVisibleNumber = () => {
const width = document.body.getBoundingClientRect().width / 3;
visibleNumber.value = parseInt(width / 85);
visibleNumber.value = parseInt(String(width / 85));
}
function handleSelect(key, keyPath) {
const handleSelect = (key: string, keyPath: string[]) => {
currentIndex.value = key;
const route = routers.value.find(item => item.path === key);
if (isHttp(key)) {
@ -121,7 +92,7 @@ function handleSelect(key, keyPath) {
window.open(key, "_blank");
} else if (!route || !route.children) {
// 没有子路由路径内部打开
router.push({ path: key });
router.push({ path: key, fullPath: '' });
appStore.toggleSideBarHide(true);
} else {
// 显示左侧联动菜单
@ -130,8 +101,8 @@ function handleSelect(key, keyPath) {
}
}
function activeRoutes(key) {
let routes = [];
const activeRoutes = (key: string) => {
let routes:RouteOption[] = [];
if (childrenMenus.value && childrenMenus.value.length > 0) {
childrenMenus.value.map((item) => {
if (key == item.parentPath || (key == "index" && "" == item.path)) {
@ -159,6 +130,26 @@ onMounted(() => {
})
</script>
<template>
<el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
<template v-for="(item, index) in topMenus">
<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
>
</template>
<!-- 顶部菜单超出数量折叠 -->
<el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
<template #title>更多菜单</template>
<template v-for="(item, index) in topMenus">
<el-menu-item :index="item.path" :key="index" v-if="index >= visibleNumber"
><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
>
</template>
</el-sub-menu>
</el-menu>
</template>
<style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item {
float: left;

View File

@ -1,36 +1,5 @@
<template>
<div class="el-tree-select">
<el-select
style="width: 100%"
v-model="valueId"
ref="treeSelect"
:filterable="true"
:clearable="true"
@clear="clearHandle"
:filter-method="selectFilterData"
:placeholder="placeholder"
>
<el-option :value="valueId" :label="valueTitle">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="objMap"
:node-key="objMap.value"
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKey"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>
<script setup>
const { proxy } = getCurrentInstance();
<script setup lang="ts">
import { ElTreeSelect } from 'element-plus'
const props = defineProps({
/* 配置项 */
@ -68,6 +37,9 @@ const props = defineProps({
}
})
const selectTree = ref(ElTreeSelect);
const emit = defineEmits(['update:value']);
const valueId = computed({
@ -77,16 +49,16 @@ const valueId = computed({
}
});
const valueTitle = ref('');
const defaultExpandedKey = ref([]);
const defaultExpandedKey = ref<any[]>([]);
function initHandle() {
nextTick(() => {
const selectedValue = valueId.value;
if(selectedValue !== null && typeof (selectedValue) !== 'undefined') {
const node = proxy.$refs.selectTree.getNode(selectedValue)
const node = selectTree.value.getNode(selectedValue)
if (node) {
valueTitle.value = node.data[props.objMap.label]
proxy.$refs.selectTree.setCurrentKey(selectedValue) // 设置默认选中
selectTree.value.setCurrentKey(selectedValue) // 设置默认选中
defaultExpandedKey.value = [selectedValue] // 设置默认展开
}
} else {
@ -94,17 +66,17 @@ function initHandle() {
}
})
}
function handleNodeClick(node) {
function handleNodeClick(node: any) {
valueTitle.value = node[props.objMap.label]
valueId.value = node[props.objMap.value];
defaultExpandedKey.value = [];
proxy.$refs.treeSelect.blur()
selectTree.value.blur()
selectFilterData('')
}
function selectFilterData(val) {
proxy.$refs.selectTree.filter(val)
function selectFilterData(val: any) {
selectTree.value.filter(val)
}
function filterNode(value, data) {
function filterNode(value: any, data: any) {
if (!value) return true
return data[props.objMap['label']].indexOf(value) !== -1
}
@ -128,7 +100,7 @@ watch(valueId, () => {
})
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
@import "@/assets/styles/variables.module.scss";
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
padding: 0;
@ -153,4 +125,34 @@ ul li .el-tree .el-tree-node__content {
background-color: mix(#fff, $--color-primary, 90%);
color: $--color-primary;
}
</style>
</style>
<template>
<div class="el-tree-select">
<el-select
style="width: 100%"
v-model="valueId"
ref="treeSelect"
:filterable="true"
:clearable="true"
@clear="clearHandle"
:filter-method="selectFilterData"
:placeholder="placeholder"
>
<el-option :value="valueId" :label="valueTitle">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="objMap"
:node-key="objMap.value"
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKey"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>

View File

@ -1,14 +1,4 @@
<template>
<div v-loading="loading" :style="'height:' + height">
<iframe
:src="url"
frameborder="no"
style="width: 100%; height: 100%"
scrolling="auto" />
</div>
</template>
<script setup>
<script setup lang="ts">
const props = defineProps({
src: {
type: String,
@ -29,3 +19,9 @@ onMounted(() => {
};
})
</script>
<template>
<div v-loading="loading" :style="'height:' + height">
<iframe :src="url" frameborder="no" style="width: 100%; height: 100%" scrolling="auto" />
</div>
</template>