!64 版本升级

* Merge branch 'dev' of gitee.com:JavaLionLi/plus-ui into ts
* 升级依赖
* !61 fix: 删除重复环境变量ElUploadInstance
* fix: 删除重复环境变量ElUploadInstance
This commit is contained in:
ahaos
2023-12-13 01:01:52 +00:00
parent 58d7e50de3
commit b06f6a316b
98 changed files with 3191 additions and 3151 deletions

View File

@ -2,8 +2,7 @@
<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>
<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>
@ -11,42 +10,42 @@
</template>
<script setup lang="ts">
import { RouteLocationMatched } from 'vue-router'
import { RouteLocationMatched } from 'vue-router';
const route = useRoute();
const router = useRouter();
const levelList = ref<RouteLocationMatched[]>([])
const levelList = ref<RouteLocationMatched[]>([]);
const getBreadcrumb = () => {
// only show routes with meta.title
let matched = route.matched.filter(item => item.meta && item.meta.title);
const first = matched[0]
let matched = route.matched.filter((item) => item.meta && item.meta.title);
const first = matched[0];
// 判断是否为首页
if (!isDashboard(first)) {
matched = ([{ path: '/index', meta: { title: '首页' } }] as any).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)
}
levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false);
};
const isDashboard = (route: RouteLocationMatched) => {
const name = route && route.name as string
const name = route && (route.name as string);
if (!name) {
return false
return false;
}
return name.trim() === 'Index'
}
return name.trim() === 'Index';
};
const handleLink = (item: RouteLocationMatched) => {
const { redirect, path } = item
redirect ? router.push(redirect as string) : router.push(path)
}
const { redirect, path } = item;
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
getBreadcrumb()
})
if (route.path.startsWith('/redirect/')) return;
getBreadcrumb();
});
onMounted(() => {
getBreadcrumb();
})
});
</script>
<style lang="scss" scoped>

View File

@ -1,53 +1,50 @@
<!-- 代码构建 -->
<script setup lang="ts">
const props = defineProps({
showBtn: {
type: Boolean,
default: false
},
formJson: {
type: Object,
default: undefined
}
})
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const buildRef = ref();
const emits = defineEmits(['reJson', 'saveDesign']);
//获取表单json
const getJson = () => {
const formJson = JSON.stringify(buildRef.value.getFormJson())
const fieldJson = JSON.stringify(buildRef.value.getFieldWidgets())
let data = {
formJson, fieldJson
}
emits("saveDesign", data)
}
onMounted(() => {
if (props.formJson) {
buildRef.value.setFormJson(props.formJson)
}
})
</script>
<template>
<!-- 代码构建 -->
<div>
<v-form-designer
class="build"
ref="buildRef"
class="build"
:designer-config="{ importJsonButton: true, exportJsonButton: true, exportCodeButton: true, generateSFCButton: true, formTemplates: true }"
>
<template #customToolButtons v-if="showBtn">
<template v-if="showBtn" #customToolButtons>
<el-button link type="primary" icon="Select" @click="getJson">保存</el-button>
</template>
</v-form-designer>
</div>
</template>
<script setup lang="ts">
interface Props {
showBtn: boolean;
formJson: any;
}
const props = withDefaults(defineProps<Props>(), {
showBtn: true,
formJson: ''
});
const buildRef = ref();
const emits = defineEmits(['reJson', 'saveDesign']);
//获取表单json
const getJson = () => {
const formJson = JSON.stringify(buildRef.value.getFormJson());
const fieldJson = JSON.stringify(buildRef.value.getFieldWidgets());
let data = {
formJson,
fieldJson
};
emits('saveDesign', data);
};
onMounted(() => {
if (props.formJson) {
buildRef.value.setFormJson(props.formJson);
}
});
</script>
<style lang="scss">
.build {
margin: 0 !important;

View File

@ -1,26 +1,28 @@
<template>
<div class="">
<v-form-render ref="vFormRef" :form-json="formJson" :form-data="formData" />
</div>
</template>
<!-- 动态表单渲染 -->
<script setup name="Render">
<script setup name="Render" lang="ts">
interface Props {
formJson: string | object;
formData: string | object;
isView: boolean;
}
const props = defineProps({
formJson: {
type: [String, Object],
default: ""
},
formData: {
type: [String, Object],
default: ""
},
isView: {
type: Boolean,
default: false
}
})
const props = withDefaults(defineProps<Props>(), {
formJson: '',
formData: '',
isView: false
});
const vFormRef = ref(null)
const vFormRef = ref(null);
// 获取表单数据-异步
const getFormData = () => {
return vFormRef.value.getFormData()
}
return vFormRef.value.getFormData();
};
/**
* 设置表单内容
@ -28,35 +30,28 @@ const getFormData = () => {
* formConfig{ formTemplate表单模板formData表单数据hiddenField需要隐藏的字段字符串集合disabledField需要禁用的自读字符串集合}
*/
const initForm = (formConf) => {
const { formTemplate, formData, hiddenField, disabledField } = toRaw(formConf)
const { formTemplate, formData, hiddenField, disabledField } = toRaw(formConf);
if (formTemplate) {
vFormRef.value.setFormJson(formTemplate)
vFormRef.value.setFormJson(formTemplate);
if (formData) {
vFormRef.value.setFormData(formData)
vFormRef.value.setFormData(formData);
}
if (disabledField && disabledField.length > 0) {
setTimeout(() => {
vFormRef.value.disableWidgets(disabledField)
}, 200)
vFormRef.value.disableWidgets(disabledField);
}, 200);
}
if (hiddenField && hiddenField.length > 0) {
setTimeout(() => {
vFormRef.value.hideWidgets(hiddenField)
}, 200)
vFormRef.value.hideWidgets(hiddenField);
}, 200);
}
if (props.isView) {
console.log(props.isView)
setTimeout(() => {
vFormRef.value.disableForm()
}, 100)
vFormRef.value.disableForm();
}, 100);
}
}
}
defineExpose({ getFormData, initForm })
};
defineExpose({ getFormData, initForm });
</script>
<template>
<div class="">
<v-form-render ref="vFormRef" :form-json="formJson" :form-data="formData" />
</div>
</template>

View File

@ -8,13 +8,13 @@
</span>
<el-tag
v-else
:disable-transitions="true"
:key="item.value + ''"
:disable-transitions="true"
:index="index"
:type="(item.elTagType === 'primary' || item.elTagType === 'default')? '' : item.elTagType"
:class="item.elTagClass"
>
{{ item.label + " " }}
{{ item.label + ' ' }}
</el-tag>
</template>
</template>
@ -25,57 +25,53 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
// 数据
options: {
type: Array as PropType<DictDataOption[]>,
default: null,
},
// 当前的值
value: [Number, String, Array] as PropType<number | string | Array<number | string>>,
// 当未找到匹配的数据时显示value
showValue: propTypes.bool.def(true),
separator: propTypes.string.def(","),
interface Props {
options: Array<DictDataOption>;
value: number | string | Array<number | string>;
showValue: boolean;
separator: string;
}
const props = withDefaults(defineProps<Props>(), {
showValue: true,
separator: ','
});
const values = computed(() => {
if (props.value === '' || props.value === null || typeof props.value === "undefined") return []
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator);
});
const unmatch = computed(() => {
if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === "undefined") return false
if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === 'undefined') return false;
// 传入值为非数组
values.value.forEach(item => {
if (!props.options.some(v => v.value === item)) {
return true // 如果有未匹配项将标志设置为true
values.value.forEach((item) => {
if (!props.options.some((v) => v.value === item)) {
return true; // 如果有未匹配项将标志设置为true
}
})
return false // 返回标志的值
});
return false; // 返回标志的值
});
const unmatchArray = computed(() => {
// 记录未匹配的项
// 记录未匹配的项
const itemUnmatchArray: Array<string | number> = [];
if (props.value !== '' && props.value !== null && typeof props.value !== "undefined") {
values.value.forEach(item => {
if (!props.options.some(v => v.value === item)) {
if (props.value !== '' && props.value !== null && typeof props.value !== 'undefined') {
values.value.forEach((item) => {
if (!props.options.some((v) => v.value === item)) {
itemUnmatchArray.push(item);
}
})
});
}
// 没有value不显示
return handleArray(itemUnmatchArray);
});
const handleArray = (array: Array<string | number>) => {
if (array.length === 0) return "";
if (array.length === 0) return '';
return array.reduce((pre, cur) => {
return pre + " " + cur;
return pre + ' ' + cur;
});
}
};
</script>
<style scoped>

View File

@ -1,6 +1,8 @@
<template>
<div>
<el-upload
v-if="type === 'url'"
ref="uploadRef"
:action="upload.url"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
@ -9,18 +11,16 @@
name="file"
:show-file-list="false"
:headers="upload.headers"
ref="uploadRef"
v-if="type === 'url'"
>
</el-upload>
<div class="editor">
<quill-editor
ref="quillEditorRef"
v-model:content="content"
contentType="html"
@textChange="(e: any) => $emit('update:modelValue', content)"
content-type="html"
:options="options"
:style="styles"
@text-change="(e: any) => $emit('update:modelValue', content)"
/>
</div>
</div>
@ -30,7 +30,7 @@
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from "@/utils/request";
import { globalHeaders } from '@/utils/request';
const props = defineProps({
/* 编辑器的内容 */
@ -52,42 +52,42 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const upload = reactive<UploadOption>({
headers: globalHeaders(),
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
})
});
const quillEditorRef = ref();
const options = ref({
theme: "snow",
theme: 'snow',
bounds: document.body,
debug: "warn",
debug: 'warn',
modules: {
// 工具栏配置
toolbar: {
container: [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用 代码块
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
[{ indent: "-1" }, { indent: "+1" }], // 缩进
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ align: [] }], // 对齐方式
["clean"], // 清除文本格式
["link", "image", "video"] // 链接、图片、视频
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
['blockquote', 'code-block'], // 引用 代码块
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
[{ indent: '-1' }, { indent: '+1' }], // 缩进
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ align: [] }], // 对齐方式
['clean'], // 清除文本格式
['link', 'image', 'video'] // 链接、图片、视频
],
handlers: {
image: function (value: any) {
if (value) {
// 调用element图片上传
(document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
(document.querySelector('.editor-img-uploader>.el-upload') as HTMLDivElement)?.click();
} else {
Quill.format("image", true);
Quill.format('image', true);
}
},
},
}
}
}
},
placeholder: "请输入内容",
readOnly: props.readOnly,
placeholder: '请输入内容',
readOnly: props.readOnly
});
const styles = computed(() => {
@ -99,14 +99,18 @@ const styles = computed(() => {
style.height = `${props.height}px`;
}
return style;
})
});
const content = ref("");
watch(() => props.modelValue, (v) => {
if (v !== content.value) {
content.value = v === undefined ? "<p></p>" : v;
}
}, { immediate: true });
const content = ref('');
watch(
() => props.modelValue,
(v) => {
if (v !== content.value) {
content.value = v === undefined ? '<p></p>' : v;
}
},
{ immediate: true }
);
// 图片上传成功返回图片地址
const handleUploadSuccess = (res: any) => {
@ -117,7 +121,7 @@ const handleUploadSuccess = (res: any) => {
// 获取光标位置
let length = quill.selection.savedRange.index;
// 插入图片res为服务器返回的图片链接地址
quill.insertEmbed(length, "image", res.data.url);
quill.insertEmbed(length, 'image', res.data.url);
// 调整光标到最后
quill.setSelection(length + 1);
proxy?.$modal.closeLoading();
@ -125,11 +129,11 @@ const handleUploadSuccess = (res: any) => {
proxy?.$modal.loading(res.msg);
proxy?.$modal.closeLoading();
}
}
};
// 图片上传前拦截
const handleBeforeUpload = (file: any) => {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
const isJPG = type.includes(file.type);
//检验文件格式
if (!isJPG) {
@ -146,13 +150,13 @@ const handleBeforeUpload = (file: any) => {
}
proxy?.$modal.loading('正在上传文件,请稍候...');
return true;
}
};
// 图片失败拦截
const handleUploadError = (err: any) => {
console.error(err);
proxy?.$modal.msgError('上传文件失败');
}
};
</script>
<style>
@ -167,71 +171,71 @@ const handleUploadError = (err: any) => {
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
.ql-snow .ql-tooltip[data-mode='link']::before {
content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: "保存";
content: '保存';
padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
.ql-snow .ql-tooltip[data-mode='video']::before {
content: '请输入视频地址:';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
content: '等宽字体';
}
</style>

View File

@ -1,6 +1,7 @@
<template>
<div class="upload-file">
<el-upload
ref="fileUploadRef"
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
@ -12,30 +13,29 @@
: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">
<div v-if="showTip" class="el-upload__tip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
格式为 <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">
<li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content">
<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>
<el-link :underline="false" type="danger" @click="handleDelete(index)">删除</el-link>
</div>
</li>
</transition-group>
@ -43,20 +43,20 @@
</template>
<script setup lang="ts">
import { listByIds, delOss } from "@/api/system/oss";
import { listByIds, delOss } from '@/api/system/oss';
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from "@/utils/request";
import { globalHeaders } from '@/utils/request';
const props = defineProps({
modelValue: [String, Object, Array],
// 数量限制
limit: propTypes.number.def(5),
// 大小限制(MB)
fileSize: propTypes.number.def(5),
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(["doc", "xls", "ppt", "txt", "pdf"]),
// 是否显示提示
isShowTip: propTypes.bool.def(true),
modelValue: [String, Object, Array],
// 数量限制
limit: propTypes.number.def(5),
// 大小限制(MB)
fileSize: propTypes.number.def(5),
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']),
// 是否显示提示
isShowTip: propTypes.bool.def(true)
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -65,153 +65,163 @@ const number = ref(0);
const uploadList = ref<any[]>([]);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = ref(baseUrl + "/resource/oss/upload"); // 上传文件服务器地址
const uploadFileUrl = ref(baseUrl + '/resource/oss/upload'); // 上传文件服务器地址
const headers = ref(globalHeaders());
const fileList = ref<any[]>([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
const fileUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
watch(
() => props.modelValue,
async (val) => {
if (val) {
let temp = 1;
// 首先将值转为数组
let list = [];
if (Array.isArray(val)) {
list = val;
} else {
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 => {
item = { name: item.name, url: item.url, ossId: item.ossId };
item.uid = item.uid || new Date().getTime() + temp++;
return item;
let temp = 1;
// 首先将值转为数组
let list = [];
if (Array.isArray(val)) {
list = val;
} else {
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) => {
item = { name: item.name, url: item.url, ossId: item.ossId };
item.uid = item.uid || new Date().getTime() + temp++;
return item;
});
} else {
fileList.value = [];
return [];
fileList.value = [];
return [];
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }
);
// 上传前校检格式和大小
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("/")}格式文件!`);
return false;
}
// 校检文件类型
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('/')}格式文件!`);
return false;
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
proxy?.$modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
}
}
proxy?.$modal.loading('正在上传文件,请稍候...');
number.value++;
return true;
};
// 文件个数超出
const handleExceed = () => {
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
};
// 上传失败
const handleUploadError = () => {
proxy?.$modal.msgError("上传文件失败");
}
proxy?.$modal.msgError('上传文件失败');
};
// 上传成功回调
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);
fileUploadRef.value?.handleRemove(file);
uploadedSuccessfully();
}
}
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);
fileUploadRef.value?.handleRemove(file);
uploadedSuccessfully();
}
};
// 删除文件
const handleDelete = (index: number) => {
let ossId = fileList.value[index].ossId;
delOss(ossId);
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
}
let ossId = fileList.value[index].ossId;
delOss(ossId);
fileList.value.splice(index, 1);
emit('update:modelValue', listToString(fileList.value));
};
// 上传结束处理
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();
}
}
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();
}
};
// 获取文件名称
const getFileName = (name: string) => {
// 如果是url那么取最后的名字 如果不是直接返回
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
} else {
return name;
}
}
// 如果是url那么取最后的名字 如果不是直接返回
if (name.lastIndexOf('/') > -1) {
return name.slice(name.lastIndexOf('/') + 1);
} else {
return name;
}
};
// 对象转成指定字符串分隔
const listToString = (list: any[], separator?: string) => {
let strs = "";
separator = separator || ",";
list.forEach(item => {
if (item.ossId) {
strs += item.ossId + separator;
}
})
return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
let strs = '';
separator = separator || ',';
list.forEach((item) => {
if (item.ossId) {
strs += item.ossId + separator;
}
});
return strs != '' ? strs.substring(0, strs.length - 1) : '';
};
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
margin-right: 10px;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<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"
@ -13,12 +13,12 @@ import { propTypes } from '@/utils/propTypes';
defineProps({
isActive: propTypes.bool.def(false)
})
});
const emit = defineEmits(['toggleClick'])
const emit = defineEmits(['toggleClick']);
const toggleClick = () => {
emit('toggleClick');
}
};
</script>
<style scoped>

View File

@ -1,6 +1,6 @@
<template>
<div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click"/>
<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"
@ -12,23 +12,22 @@
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-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select>
</div>
</template>
<script setup lang="ts" name="HeaderSearch">
import Fuse from 'fuse.js';
import {getNormalPath} from '@/utils/ruoyi';
import {isHttp} from '@/utils/validate';
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import {RouteOption} from 'vue-router';
import { RouteRecordRaw } from 'vue-router';
type Router = Array<{
path: string;
title: string[];
}>
}>;
const search = ref('');
const options = ref<any>([]);
@ -40,36 +39,36 @@ const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const click = () => {
show.value = !show.value
show.value = !show.value;
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
headerSearchSelectRef.value && headerSearchSelectRef.value.focus();
}
};
const close = () => {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
options.value = []
show.value = false
}
headerSearchSelectRef.value && headerSearchSelectRef.value.blur();
options.value = [];
show.value = false;
};
const change = (val: any) => {
const path = val.path;
const query = val.query;
if (isHttp(path)) {
// http(s):// 路径新窗口打开
const pindex = path.indexOf("http");
window.open(path.substr(pindex, path.length), "_blank");
const pindex = path.indexOf('http');
window.open(path.substr(pindex, path.length), '_blank');
} else {
if (query) {
router.push({ path: path, query: JSON.parse(query) });
} else {
router.push(path)
router.push(path);
}
}
search.value = ''
options.value = []
search.value = '';
options.value = [];
nextTick(() => {
show.value = false
})
}
show.value = false;
});
};
const initFuse = (list: Router) => {
fuse.value = new Fuse(list, {
shouldSort: true,
@ -77,20 +76,23 @@ const initFuse = (list: Router) => {
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
}
keys: [
{
name: 'title',
weight: 0.7
},
{
name: 'path',
weight: 0.3
}
]
});
};
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
let res: Router = []
routes.forEach(r => {
const generateRoutes = (routes: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []) => {
let res: Router = [];
routes.forEach((r) => {
// skip hidden router
if (!r.hidden) {
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
@ -98,7 +100,7 @@ const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: strin
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle],
query: ''
}
};
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
@ -109,7 +111,7 @@ const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: strin
}
if (r.query) {
data.query = r.query
data.query = r.query;
}
// recursive child routes
@ -120,20 +122,20 @@ const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: strin
}
}
}
})
});
return res;
}
};
const querySearch = (query: string) => {
if (query !== '') {
options.value = fuse.value.search(query)
options.value = fuse.value.search(query);
} else {
options.value = []
options.value = [];
}
}
};
onMounted(() => {
searchPool.value = generateRoutes(routes.value);
})
});
// watchEffect(() => {
// searchPool.value = generateRoutes(routes.value)
@ -141,15 +143,15 @@ onMounted(() => {
watch(show, (value) => {
if (value) {
document.body.addEventListener('click', close)
document.body.addEventListener('click', close);
} else {
document.body.removeEventListener('click', close)
document.body.removeEventListener('click', close);
}
})
});
watch(searchPool, (list) => {
initFuse(list)
})
initFuse(list);
});
</script>
<style lang="scss" scoped>

View File

@ -1,6 +1,6 @@
<template>
<div class="relative" :style="{ width: width }">
<el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
<el-input v-model="modelValue" readonly placeholder="点击选择图标" @click="visible = !visible">
<template #prepend>
<svg-icon :icon-class="modelValue" />
</template>
@ -8,18 +8,18 @@
<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]">
<div class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]" @click="visible = !visible">
<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-input v-model="filterValue" class="p-2" 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', {active: modelValue == iconName}]" @click="selectedIcon(iconName)">
<li :class="['icon-item', { active: modelValue == iconName }]" @click="selectedIcon(iconName)">
<svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" />
</li>
</el-tooltip>
@ -50,13 +50,11 @@ const filterValue = ref('');
*/
const filterIcons = () => {
if (filterValue.value) {
iconNames.value = icons.filter(iconName =>
iconName.includes(filterValue.value)
);
iconNames.value = icons.filter((iconName) => iconName.includes(filterValue.value));
} else {
iconNames.value = icons;
}
}
};
/**
* 选择图标
* @param iconName 选择的图标名称
@ -64,12 +62,12 @@ const filterIcons = () => {
const selectedIcon = (iconName: string) => {
emit('update:modelValue', iconName);
visible.value = false;
}
};
</script>
<style scoped lang="scss">
.el-scrollbar {
max-height: calc(50vh - 100px)!important;
max-height: calc(50vh - 100px) !important;
overflow-y: auto;
}
.el-divider--horizontal {
@ -99,8 +97,8 @@ const selectedIcon = (iconName: string) => {
}
}
.active {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
}
</style>

View File

@ -15,11 +15,11 @@ const props = defineProps({
src: propTypes.string.def(''),
width: {
type: [Number, String],
default: ""
default: ''
},
height: {
type: [Number, String],
default: ""
default: ''
}
});
@ -27,7 +27,7 @@ const realSrc = computed(() => {
if (!props.src) {
return;
}
let real_src = props.src.split(",")[0];
let real_src = props.src.split(',')[0];
return real_src;
});
@ -35,21 +35,17 @@ const realSrcList = computed(() => {
if (!props.src) {
return;
}
let real_src_list = props.src.split(",");
let real_src_list = props.src.split(',');
let srcList: string[] = [];
real_src_list.forEach(item => {
real_src_list.forEach((item) => {
return srcList.push(item);
});
return srcList;
});
const realWidth = computed(() =>
typeof props.width == "string" ? props.width : `${props.width}px`
);
const realWidth = computed(() => (typeof props.width == 'string' ? props.width : `${props.width}px`));
const realHeight = computed(() =>
typeof props.height == "string" ? props.height : `${props.height}px`
);
const realHeight = computed(() => (typeof props.height == 'string' ? props.height : `${props.height}px`));
</script>
<style lang="scss" scoped>

View File

@ -1,6 +1,7 @@
<template>
<div class="component-upload-image">
<el-upload
ref="imageUpload"
multiple
:action="uploadImgUrl"
list-type="picture-card"
@ -9,7 +10,6 @@
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
ref="imageUpload"
:before-remove="handleDelete"
:show-file-list="true"
:headers="headers"
@ -22,13 +22,13 @@
</el-icon>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
<div v-if="showTip" class="el-upload__tip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</template>
的文件
</div>
@ -40,177 +40,176 @@
</template>
<script setup lang="ts">
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance } from "vue";
import { OssVO } from "@/api/system/oss/types";
import { listByIds, delOss } from '@/api/system/oss';
import { OssVO } from '@/api/system/oss/types';
import { propTypes } from '@/utils/propTypes';
import {globalHeaders} from "@/utils/request";
import { globalHeaders } from '@/utils/request';
const props = defineProps({
modelValue: [String, Object, Array],
// 图片数量限制
limit: propTypes.number.def(5),
// 大小限制(MB)
fileSize: propTypes.number.def(5),
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(["png", "jpg", "jpeg"]),
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
},
modelValue: [String, Object, Array],
// 图片数量限制
limit: propTypes.number.def(5),
// 大小限制(MB)
fileSize: propTypes.number.def(5),
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(['png', 'jpg', 'jpeg']),
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
}
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emit = defineEmits(['update:modelValue']);
const number = ref(0);
const uploadList = ref<any[]>([]);
const dialogImageUrl = ref("");
const dialogImageUrl = ref('');
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(baseUrl + "/resource/oss/upload"); // 上传的图片服务器地址
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址
const headers = ref(globalHeaders());
const fileList = ref<any[]>([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
const imageUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
watch(
() => props.modelValue,
async (val) => {
if (val) {
// 首先将值转为数组
let list: OssVO[] = [];
if (Array.isArray(val)) {
list = val as OssVO[];
// 首先将值转为数组
let list: OssVO[] = [];
if (Array.isArray(val)) {
list = val as OssVO[];
} else {
const res = await listByIds(val as string);
list = res.data;
}
// 然后将数组转为对象数组
fileList.value = list.map((item) => {
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
let itemData;
if (typeof item === 'string') {
itemData = { name: item, url: item };
} else {
const res = await listByIds(val as string)
list = res.data
// 此处name使用ossId 防止删除出现重名
itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
}
// 然后将数组转为对象数组
fileList.value = list.map(item => {
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
let itemData;
if (typeof item === "string") {
itemData = { name: item, url: item };
} else {
// 此处name使用ossId 防止删除出现重名
itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
}
return itemData;
});
return itemData;
});
} else {
fileList.value = [];
return [];
fileList.value = [];
return [];
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }
);
/** 上传前loading加载 */
const handleBeforeUpload = (file: any) => {
let isImg = false;
if (props.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
isImg = props.fileType.some((type: any) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
} else {
isImg = file.type.indexOf("image") > -1;
let isImg = false;
if (props.fileType.length) {
let fileExtension = '';
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1);
}
if (!isImg) {
proxy?.$modal.msgError(
`文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
);
return false;
isImg = props.fileType.some((type: any) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
} else {
isImg = file.type.indexOf('image') > -1;
}
if (!isImg) {
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`);
return false;
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false;
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy?.$modal.loading("正在上传图片,请稍候...");
number.value++;
}
}
proxy?.$modal.loading('正在上传图片,请稍候...');
number.value++;
};
// 文件个数超出
const handleExceed = () => {
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
};
// 上传成功回调
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);
imageUploadRef.value?.handleRemove(file);
uploadedSuccessfully();
}
}
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);
imageUploadRef.value?.handleRemove(file);
uploadedSuccessfully();
}
};
// 删除图片
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;
delOss(ossId);
fileList.value.splice(findex, 1);
emit("update:modelValue", listToString(fileList.value));
return false;
}
return true;
}
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;
delOss(ossId);
fileList.value.splice(findex, 1);
emit('update:modelValue', listToString(fileList.value));
return false;
}
return true;
};
// 上传结束处理
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();
}
}
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();
}
};
// 上传失败
const handleUploadError = () => {
proxy?.$modal.msgError("上传图片失败");
proxy?.$modal.closeLoading();
}
proxy?.$modal.msgError('上传图片失败');
proxy?.$modal.closeLoading();
};
// 预览
const handlePictureCardPreview = (file: any) => {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
}
dialogImageUrl.value = file.url;
dialogVisible.value = true;
};
// 对象转成指定字符串分隔
const listToString = (list: any[], separator?: string) => {
let strs = "";
separator = separator || ",";
for (let i in list) {
if (undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
strs += list[i].ossId + separator;
}
let strs = '';
separator = separator || ',';
for (let i in list) {
if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
strs += list[i].ossId + separator;
}
return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
}
return strs != '' ? strs.substring(0, strs.length - 1) : '';
};
</script>
<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {
display: none;
display: none;
}
</style>

View File

@ -20,16 +20,15 @@ import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();
const { locale } = useI18n();
const message: any = {
zh_CN: '切换语言成功!',
en_US: 'Switch Language Successful!',
}
en_US: 'Switch Language Successful!'
};
const handleLanguageChange = (lang: string) => {
locale.value = lang;
appStore.changeLanguage(lang);
ElMessage.success(message[lang] || '切换语言成功!');
}
};
</script>
<style lang="scss" scoped>

View File

@ -1,9 +1,9 @@
<template>
<div :class="{ 'hidden': hidden }" class="pagination-container">
<div :class="{ hidden: hidden }" class="pagination-container">
<el-pagination
:background="background"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:background="background"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
@ -16,69 +16,69 @@
<script lang="ts">
export default {
name: 'Pagination'
}
name: 'Pagination'
};
</script>
<script setup lang="ts">
import { scrollTo } from '@/utils/scroll-to'
import { propTypes } from "@/utils/propTypes";
import { scrollTo } from '@/utils/scroll-to';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
total: propTypes.number,
page: propTypes.number.def(1),
limit: propTypes.number.def(20),
pageSizes: {
type: Array as PropType<number[]>,
default: () => [10, 20, 30, 50]
},
// 移动端页码按钮的数量端默认值5
pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'),
background: propTypes.bool.def(true),
autoScroll: propTypes.bool.def(true),
hidden: propTypes.bool.def(false),
float: propTypes.string.def('right')
})
total: propTypes.number,
page: propTypes.number.def(1),
limit: propTypes.number.def(20),
pageSizes: {
type: Array,
default: () => [10, 20, 30, 50]
},
// 移动端页码按钮的数量端默认值5
pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'),
background: propTypes.bool.def(true),
autoScroll: propTypes.bool.def(true),
hidden: propTypes.bool.def(false),
float: propTypes.string.def('right')
});
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
const currentPage = computed({
get() {
return props.page
},
set(val) {
emit('update:page', val)
}
})
get() {
return props.page;
},
set(val) {
emit('update:page', val);
}
});
const pageSize = computed({
get() {
return props.limit
},
set(val){
emit('update:limit', val)
}
})
get() {
return props.limit;
},
set(val) {
emit('update:limit', val);
}
});
function handleSizeChange(val: number) {
if (currentPage.value * val > props.total) {
currentPage.value = 1
}
emit('pagination', { page: currentPage.value, limit: val })
if (props.autoScroll) {
scrollTo(0, 800)
}
if (currentPage.value * val > props.total) {
currentPage.value = 1;
}
emit('pagination', { page: currentPage.value, limit: val });
if (props.autoScroll) {
scrollTo(0, 800);
}
}
function handleCurrentChange(val: number) {
emit('pagination', { page: val, limit: pageSize.value })
if (props.autoScroll) {
scrollTo(0, 800)
}
emit('pagination', { page: val, limit: pageSize.value });
if (props.autoScroll) {
scrollTo(0, 800);
}
}
</script>
<style lang="scss" scoped>
.pagination-container {
padding: 32px 16px;
.el-pagination{
.el-pagination {
float: v-bind(float);
}
}

View File

@ -1,13 +1,13 @@
<template>
<div class="top-right-btn" :style="style">
<el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
<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-tooltip v-if="columns" class="item" effect="dark" content="显示/隐藏列" placement="top">
<div class="show-btn">
<el-popover placement="bottom" trigger="click">
<div class="tree-header">显示/隐藏列</div>
@ -15,9 +15,9 @@
ref="columnRef"
:data="columns"
show-checkbox
@check="columnChange"
node-key="key"
:props="{ label: 'label', children: 'children' }"
@check="columnChange"
></el-tree>
<template #reference>
<el-button circle icon="Menu" />
@ -33,51 +33,49 @@
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
showSearch: propTypes.bool.def(true),
columns: {
type: Array as PropType<FieldOption[]>,
},
search: propTypes.bool.def(true),
gutter: propTypes.number.def(10),
})
showSearch: propTypes.bool.def(true),
columns: propTypes.fieldOption,
search: propTypes.bool.def(true),
gutter: propTypes.number.def(10)
});
const columnRef = ref<ElTreeInstance>();
const emits = defineEmits(['update:showSearch', 'queryTable']);
const style = computed(() => {
const ret: any = {};
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
}
return ret;
const ret: any = {};
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
}
return ret;
});
// 搜索
function toggleSearch() {
emits("update:showSearch", !props.showSearch);
emits('update:showSearch', !props.showSearch);
}
// 刷新
function refresh() {
emits("queryTable");
emits('queryTable');
}
// 更改数据列的显示和隐藏
function columnChange(...args: any[]) {
props.columns?.forEach((item) => {
item.visible = args[1].checkedKeys.includes(item.key);
})
});
}
// 显隐列初始默认隐藏列
onMounted(() => {
props.columns?.forEach((item) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
// value.value.push(item.key);
}
})
})
props.columns?.forEach((item) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
// value.value.push(item.key);
}
});
});
</script>
<style lang="scss" scoped>
@ -93,7 +91,7 @@ onMounted(() => {
.my-el-transfer {
text-align: center;
}
.tree-header{
.tree-header {
width: 100%;
line-height: 24px;
text-align: center;

View File

@ -8,6 +8,6 @@
const url = ref('https://plus-doc.dromara.org/');
function goto() {
window.open(url.value)
window.open(url.value);
}
</script>

View File

@ -8,6 +8,6 @@
const url = ref('https://gitee.com/dromara/RuoYi-Vue-Plus');
function goto() {
window.open(url.value)
window.open(url.value);
}
</script>

View File

@ -16,20 +16,20 @@
</template>
<script setup lang="ts">
import useAppStore from "@/store/modules/app";
import useAppStore from '@/store/modules/app';
const appStore = useAppStore();
const size = computed(() => appStore.size);
const sizeOptions = ref([
{ label: "较大", value: "large" },
{ label: "默认", value: "default" },
{ label: "稍小", value: "small" },
{ label: '较大', value: 'large' },
{ label: '默认', value: 'default' },
{ label: '稍小', value: 'small' }
]);
const handleSetSize = (size: string) => {
appStore.setSize(size);
}
appStore.setSize(size);
};
</script>
<style lang="scss" scoped>

View File

@ -8,17 +8,17 @@
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
iconClass: propTypes.string.isRequired,
className: propTypes.string.def(''),
color: propTypes.string.def(''),
})
const iconName = computed(() => `#icon-${props.iconClass}`);
iconClass: propTypes.string.isRequired,
className: propTypes.string.def(''),
color: propTypes.string.def('')
});
const iconName = computed(() => `#icon-${props.iconClass}`);
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`
}
return 'svg-icon'
})
if (props.className) {
return `svg-icon ${props.className}`;
}
return 'svg-icon';
});
</script>
<style scope lang="scss">

View File

@ -1,19 +1,18 @@
<template>
<el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
<el-menu :default-active="activeMenu" mode="horizontal" :ellipsis="false" @select="handleSelect">
<template v-for="(item, index) in topMenus">
<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
><svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
<el-menu-item v-if="index < visibleNumber" :key="index" :style="{ '--theme': theme }" :index="item.path"
><svg-icon v-if="item.meta && item.meta.icon && item.meta.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">
<el-sub-menu v-if="topMenus.length > visibleNumber" :style="{ '--theme': theme }" index="more">
<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
<el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path"
><svg-icon :icon-class="item.meta ? item.meta.icon : ''" /> {{ item.meta?.title }}</el-menu-item
>
</template>
</el-sub-menu>
@ -26,7 +25,7 @@ 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';
import { RouteRecordRaw } from 'vue-router';
// 顶部栏初始数
const visibleNumber = ref<number>(-1);
@ -35,9 +34,9 @@ const currentIndex = ref<string>();
// 隐藏侧边栏路由
const hideList = ['/index', '/user/profile'];
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const appStore = useAppStore();
const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore();
const route = useRoute();
const router = useRouter();
@ -48,73 +47,73 @@ const routers = computed(() => permissionStore.topbarRouters);
// 顶部显示菜单
const topMenus = computed(() => {
let topMenus:RouteOption[] = [];
let topMenus: RouteRecordRaw[] = [];
routers.value.map((menu) => {
if (menu.hidden !== true) {
// 兼容顶部栏一级菜单内部跳转
if (menu.path === "/") {
topMenus.push(menu.children? menu.children[0] : menu);
if (menu.path === '/') {
topMenus.push(menu.children ? menu.children[0] : menu);
} else {
topMenus.push(menu);
topMenus.push(menu);
}
}
})
});
return topMenus;
})
});
// 设置子路由
const childrenMenus = computed(() => {
let childrenMenus:RouteOption[] = [];
let childrenMenus: RouteRecordRaw[] = [];
routers.value.map((router) => {
router.children?.forEach((item) => {
if (item.parentPath === undefined) {
if(router.path === "/") {
item.path = "/" + item.path;
if (router.path === '/') {
item.path = '/' + item.path;
} else {
if(!isHttp(item.path)) {
item.path = router.path + "/" + item.path;
if (!isHttp(item.path)) {
item.path = router.path + '/' + item.path;
}
}
item.parentPath = router.path;
}
childrenMenus.push(item);
})
})
});
});
return constantRoutes.concat(childrenMenus);
})
});
// 默认激活的菜单
const activeMenu = computed(() => {
const path = route.path;
let activePath = path;
if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
if (path !== undefined && path.lastIndexOf('/') > 0 && hideList.indexOf(path) === -1) {
const tmpPath = path.substring(1, path.length);
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
activePath = '/' + tmpPath.substring(0, tmpPath.indexOf('/'));
if (!route.meta.link) {
appStore.toggleSideBarHide(false);
appStore.toggleSideBarHide(false);
}
} else if(!route.children) {
} else if (!route.children) {
activePath = path;
appStore.toggleSideBarHide(true);
}
activeRoutes(activePath);
return activePath;
})
});
const setVisibleNumber = () => {
const width = document.body.getBoundingClientRect().width / 3;
visibleNumber.value = parseInt(String(width / 85));
}
};
const handleSelect = (key: string) => {
currentIndex.value = key;
const route = routers.value.find(item => item.path === key);
const route = routers.value.find((item) => item.path === key);
if (isHttp(key)) {
// http(s):// 路径新窗口打开
window.open(key, "_blank");
window.open(key, '_blank');
} else if (!route || !route.children) {
// 没有子路由路径内部打开
const routeMenu = childrenMenus.value.find(item => item.path === key);
const routeMenu = childrenMenus.value.find((item) => item.path === key);
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query);
router.push({ path: key, query: query });
@ -127,35 +126,35 @@ const handleSelect = (key: string) => {
activeRoutes(key);
appStore.toggleSideBarHide(false);
}
}
};
const activeRoutes = (key: string) => {
let routes:RouteOption[] = [];
let routes: RouteRecordRaw[] = [];
if (childrenMenus.value && childrenMenus.value.length > 0) {
childrenMenus.value.map((item) => {
if (key == item.parentPath || (key == "index" && "" == item.path)) {
if (key == item.parentPath || (key == 'index' && '' == item.path)) {
routes.push(item);
}
});
}
if(routes.length > 0) {
if (routes.length > 0) {
permissionStore.setSidebarRouters(routes);
} else {
appStore.toggleSideBarHide(true);
}
return routes;
}
};
onMounted(() => {
window.addEventListener('resize', setVisibleNumber)
})
window.addEventListener('resize', setVisibleNumber);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', setVisibleNumber)
})
window.removeEventListener('resize', setVisibleNumber);
});
onMounted(() => {
setVisibleNumber()
})
setVisibleNumber();
});
</script>
<style lang="scss">
@ -168,7 +167,8 @@ onMounted(() => {
margin: 0 10px !important;
}
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active,
.el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
border-bottom: 2px solid #{'var(--theme)'} !important;
color: #303133;
}
@ -184,7 +184,9 @@ onMounted(() => {
}
/* 背景色隐藏 */
.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):focus,
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):hover,
.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title:hover {
background-color: #ffffff !important;
}

View File

@ -1,14 +1,14 @@
<template>
<div class="el-tree-select">
<el-select
style="width: 100%"
v-model="valueId"
ref="treeSelect"
v-model="valueId"
style="width: 100%"
:filterable="true"
:clearable="true"
@clear="clearHandle"
:filter-method="selectFilterData"
:placeholder="placeholder"
@clear="clearHandle"
>
<el-option :value="valueId" :label="valueTitle">
<el-tree
@ -29,43 +29,32 @@
</template>
<script setup lang="ts">
interface ObjMap {
value: string;
label: string;
children: string;
}
interface Props {
objMap: ObjMap;
accordion: boolean;
value: string | number;
options: any[];
placeholder: string;
}
const props = defineProps({
/* 配置项 */
objMap: {
type: Object,
default: () => {
return {
value: 'id', // ID字段名
label: 'label', // 显示名称
children: 'children' // 子级字段名
}
}
const props = withDefaults(defineProps<Props>(), {
objMap: () => {
return {
value: 'id',
label: 'label',
children: 'children'
};
},
/* 自动收起 */
accordion: {
type: Boolean,
default: () => {
return false
}
},
/**当前双向数据绑定的值 */
value: {
type: [String, Number],
default: ''
},
/**当前的数据 */
options: {
type: Array,
default: () => []
},
/**输入框内部的文字 */
placeholder: {
type: String,
default: ''
}
})
accordion: false,
value: '',
options: () => [],
placeholder: ''
});
const selectTree = ref<ElTreeSelectInstance>();
@ -74,7 +63,7 @@ const emit = defineEmits(['update:value']);
const valueId = computed({
get: () => props.value,
set: (val) => {
emit('update:value', val)
emit('update:value', val);
}
});
const valueTitle = ref('');
@ -83,54 +72,54 @@ const defaultExpandedKey = ref<any[]>([]);
const initHandle = () => {
nextTick(() => {
const selectedValue = valueId.value;
if (selectedValue !== null && typeof (selectedValue) !== 'undefined') {
const node = selectTree.value?.getNode(selectedValue)
if (selectedValue !== null && typeof selectedValue !== 'undefined') {
const node = selectTree.value?.getNode(selectedValue);
if (node) {
valueTitle.value = node.data[props.objMap.label]
selectTree.value?.setCurrentKey(selectedValue) // 设置默认选中
defaultExpandedKey.value = [selectedValue] // 设置默认展开
valueTitle.value = node.data[props.objMap.label];
selectTree.value?.setCurrentKey(selectedValue); // 设置默认选中
defaultExpandedKey.value = [selectedValue]; // 设置默认展开
}
} else {
clearHandle()
clearHandle();
}
})
}
});
};
const handleNodeClick = (node: any) => {
valueTitle.value = node[props.objMap.label]
valueTitle.value = node[props.objMap.label];
valueId.value = node[props.objMap.value];
defaultExpandedKey.value = [];
selectTree.value?.blur()
selectFilterData('')
}
selectTree.value?.blur();
selectFilterData('');
};
const selectFilterData = (val: any) => {
selectTree.value?.filter(val)
}
selectTree.value?.filter(val);
};
const filterNode = (value: any, data: any) => {
if (!value) return true
return data[props.objMap['label']].indexOf(value) !== -1
}
if (!value) return true;
return data[props.objMap['label']].indexOf(value) !== -1;
};
const clearHandle = () => {
valueTitle.value = ''
valueId.value = ''
valueTitle.value = '';
valueId.value = '';
defaultExpandedKey.value = [];
clearSelected()
}
clearSelected();
};
const clearSelected = () => {
const allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
}
const allNode = document.querySelectorAll('#tree-option .el-tree-node');
allNode.forEach((element) => element.classList.remove('is-current'));
};
onMounted(() => {
initHandle()
})
initHandle();
});
watch(valueId, () => {
initHandle();
})
});
</script>
<style lang="scss" scoped>
@import "@/assets/styles/variables.module.scss";
@import '@/assets/styles/variables.module.scss';
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
padding: 0;

View File

@ -9,18 +9,18 @@ import { propTypes } from '@/utils/propTypes';
const props = defineProps({
src: propTypes.string.isRequired
})
});
const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
const loading = ref(true)
const url = computed(() => props.src)
const height = ref(document.documentElement.clientHeight - 94.5 + 'px;');
const loading = ref(true);
const url = computed(() => props.src);
onMounted(() => {
setTimeout(() => {
loading.value = false;
}, 300);
window.onresize = function temp() {
height.value = document.documentElement.clientHeight - 94.5 + "px;";
height.value = document.documentElement.clientHeight - 94.5 + 'px;';
};
})
});
</script>