| | |
| | | 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] |
| | | // 判断是否为首页 |
| | | if (!isDashboard(first)) { |
| | | matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched) |
| | | } |
| | | levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false) |
| | | // 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: '首页' } }] as any).concat(matched) |
| | | } |
| | | 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 |
| | | if (!name) { |
| | | return false |
| | | } |
| | | return name.trim() === 'Index' |
| | | const name = route && route.name as string |
| | | if (!name) { |
| | | return false |
| | | } |
| | | 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 you go to the redirect page, do not update the breadcrumbs |
| | | if (route.path.startsWith('/redirect/')) return |
| | | getBreadcrumb() |
| | | }) |
| | | onMounted(() => { |
| | | getBreadcrumb(); |
| | | getBreadcrumb(); |
| | | }) |
| | | </script> |
| | | |
| | |
| | | <!-- 代码构建 --> |
| | | <script setup lang="ts"> |
| | | |
| | | import { ComponentInternalInstance } from "vue"; |
| | | |
| | | const props = defineProps({ |
| | | showBtn: { |
| | | type: Boolean, |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { PropType } from 'vue'; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | |
| | | const props = defineProps({ |
| | |
| | | // 当前的值 |
| | | value: [Number, String, Array] as PropType<number | string | Array<number | string>>, |
| | | // 当未找到匹配的数据时,显示value |
| | | showValue: { |
| | | type: Boolean as PropType<boolean>, |
| | | default: true, |
| | | }, |
| | | showValue: propTypes.bool.def(true), |
| | | }); |
| | | |
| | | const values = computed(() => { |
| | |
| | | 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"; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | /* 编辑器的内容 */ |
| | | modelValue: { |
| | | type: String, |
| | | }, |
| | | /* 高度 */ |
| | | height: { |
| | | type: Number, |
| | | default: null, |
| | | }, |
| | | /* 最小高度 */ |
| | | minHeight: { |
| | | type: Number, |
| | | default: null, |
| | | }, |
| | | /* 只读 */ |
| | | readOnly: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /* 上传文件大小限制(MB) */ |
| | | fileSize: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | /* 类型(base64格式、url格式) */ |
| | | type: { |
| | | type: String, |
| | | default: "url", |
| | | } |
| | | /* 编辑器的内容 */ |
| | | modelValue: propTypes.string, |
| | | /* 高度 */ |
| | | height: propTypes.number.def(400), |
| | | /* 最小高度 */ |
| | | minHeight: propTypes.number.def(400), |
| | | /* 只读 */ |
| | | readOnly: propTypes.bool.def(false), |
| | | /* 上传文件大小限制(MB) */ |
| | | fileSize: propTypes.number.def(5), |
| | | /* 类型(base64格式、url格式) */ |
| | | type: propTypes.string.def('url') |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
| | | |
| | | const upload = reactive<UploadOption>({ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload' |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload' |
| | | }) |
| | | const myQuillEditor = ref(); |
| | | |
| | | const options = ref({ |
| | | theme: "snow", |
| | | bounds: document.body, |
| | | 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"] // 链接、图片、视频 |
| | | ], |
| | | handlers: { |
| | | image: function (value: any) { |
| | | if (value) { |
| | | // 调用element图片上传 |
| | | (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click(); |
| | | } else { |
| | | Quill.format("image", true); |
| | | } |
| | | }, |
| | | }, |
| | | } |
| | | }, |
| | | placeholder: '请输入内容', |
| | | readOnly: props.readOnly, |
| | | theme: "snow", |
| | | bounds: document.body, |
| | | 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"] // 链接、图片、视频 |
| | | ], |
| | | handlers: { |
| | | image: function (value: any) { |
| | | if (value) { |
| | | // 调用element图片上传 |
| | | (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click(); |
| | | } else { |
| | | Quill.format("image", true); |
| | | } |
| | | }, |
| | | }, |
| | | } |
| | | }, |
| | | placeholder: '请输入内容', |
| | | readOnly: props.readOnly, |
| | | }); |
| | | |
| | | const styles = computed(() => { |
| | | let style: any = {}; |
| | | if (props.minHeight) { |
| | | style.minHeight = `${props.minHeight}px`; |
| | | } |
| | | if (props.height) { |
| | | style.height = `${props.height}px`; |
| | | } |
| | | return style; |
| | | let style: any = {}; |
| | | if (props.minHeight) { |
| | | style.minHeight = `${props.minHeight}px`; |
| | | } |
| | | if (props.height) { |
| | | 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; |
| | | } |
| | | if (v !== content.value) { |
| | | content.value = v === undefined ? "<p></p>" : v; |
| | | } |
| | | }, { immediate: true }); |
| | | |
| | | // 图片上传成功返回图片地址 |
| | | const handleUploadSuccess = (res: any) => { |
| | | // 获取富文本实例 |
| | | let quill = toRaw(myQuillEditor.value).getQuill(); |
| | | // 如果上传成功 |
| | | 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(); |
| | | } else { |
| | | proxy?.$modal.loading(res.msg); |
| | | proxy?.$modal.closeLoading(); |
| | | } |
| | | // 获取富文本实例 |
| | | let quill = toRaw(myQuillEditor.value).getQuill(); |
| | | // 如果上传成功 |
| | | 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(); |
| | | } else { |
| | | proxy?.$modal.loading(res.msg); |
| | | proxy?.$modal.closeLoading(); |
| | | } |
| | | } |
| | | |
| | | // 图片上传前拦截 |
| | | const handleBeforeUpload = (file: any) => { |
| | | // 校检文件大小 |
| | | 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('正在上传文件,请稍候...'); |
| | | return true; |
| | | } |
| | | proxy?.$modal.loading('正在上传文件,请稍候...'); |
| | | return true; |
| | | } |
| | | |
| | | // 图片失败拦截 |
| | | const handleUploadError = (err: any) => { |
| | | console.error(err); |
| | | proxy?.$modal.msgError('上传文件失败'); |
| | | console.error(err); |
| | | proxy?.$modal.msgError('上传文件失败'); |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | .editor, .ql-toolbar { |
| | | .editor, |
| | | .ql-toolbar { |
| | | white-space: pre-wrap !important; |
| | | line-height: normal !important; |
| | | } |
| | | |
| | | .quill-img { |
| | | display: none; |
| | | } |
| | | |
| | | .ql-snow .ql-tooltip[data-mode="link"]::before { |
| | | content: "请输入链接地址:"; |
| | | } |
| | | |
| | | .ql-snow .ql-tooltip.ql-editing a.ql-action::after { |
| | | border-right: 0; |
| | | content: "保存"; |
| | |
| | | .ql-snow .ql-picker.ql-size .ql-picker-item::before { |
| | | 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="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-header .ql-picker-item::before { |
| | | 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="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="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="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-item::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: "等宽字体"; |
| | |
| | | <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"; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: [String, Object, Array], |
| | | // 数量限制 |
| | | limit: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | limit: propTypes.number.def(5), |
| | | // 大小限制(MB) |
| | | fileSize: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | fileSize: propTypes.number.def(5), |
| | | // 文件类型, 例如['png', 'jpg', 'jpeg'] |
| | | fileType: { |
| | | type: Array, |
| | | default: () => ["doc", "xls", "ppt", "txt", "pdf"], |
| | | }, |
| | | fileType: propTypes.array.def(["doc", "xls", "ppt", "txt", "pdf"]), |
| | | // 是否显示提示 |
| | | isShowTip: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | isShowTip: propTypes.bool.def(true), |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
| | |
| | | () => props.isShowTip && (props.fileType || props.fileSize) |
| | | ); |
| | | |
| | | const fileUploadRef = ref(ElUpload); |
| | | const fileUploadRef = ref<ElUploadInstance>(); |
| | | |
| | | watch(() => props.modelValue, async val => { |
| | | if (val) { |
| | |
| | | if (Array.isArray(val)) { |
| | | list = val; |
| | | } else { |
| | | const res = await listByIds(val as string) |
| | | 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 = { name: item.name, url: item.url, ossId: item.ossId }; |
| | | item.uid = item.uid || new Date().getTime() + temp++; |
| | | return item; |
| | | }); |
| | |
| | | fileList.value = []; |
| | | return []; |
| | | } |
| | | },{ deep: true, immediate: true }); |
| | | }, { deep: true, immediate: true }); |
| | | |
| | | // 上传前校检格式和大小 |
| | | const handleBeforeUpload = (file: any) => { |
| | |
| | | } |
| | | |
| | | // 上传成功回调 |
| | | const handleUploadSuccess = (res:any, file: UploadFile) => { |
| | | 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(); |
| | |
| | | number.value--; |
| | | proxy?.$modal.closeLoading(); |
| | | proxy?.$modal.msgError(res.msg); |
| | | fileUploadRef.value.handleRemove(file); |
| | | fileUploadRef.value?.handleRemove(file); |
| | | uploadedSuccessfully(); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | // 上传结束处理 |
| | | const 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 = []; |
| | |
| | | |
| | | <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> |
| | |
| | | <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"> |
| | | <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" |
| | | /> |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | defineProps({ |
| | | isActive: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | isActive: propTypes.bool.def(false) |
| | | }) |
| | | |
| | | const emit = defineEmits(['toggleClick']) |
| | | const toggleClick = () => { |
| | | emit('toggleClick'); |
| | | emit('toggleClick'); |
| | | } |
| | | </script> |
| | | |
| | |
| | | import { isHttp } from '@/utils/validate'; |
| | | import usePermissionStore from '@/store/modules/permission'; |
| | | import { RouteOption } from 'vue-router'; |
| | | import { ElSelect } from 'element-plus'; |
| | | |
| | | type Router = Array<{ |
| | | path: string; |
| | |
| | | const searchPool = ref<Router>([]); |
| | | const show = ref(false); |
| | | const fuse = ref(); |
| | | const headerSearchSelectRef = ref(ElSelect); |
| | | const headerSearchSelectRef = ref<ElSelectInstance>(); |
| | | const router = useRouter(); |
| | | const routes = computed(() => usePermissionStore().routes); |
| | | |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | .header-search { |
| | | font-size: 0 !important; |
| | | font-size: 0 !important; |
| | | |
| | | .search-icon { |
| | | cursor: pointer; |
| | | font-size: 18px; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .header-search-select { |
| | | font-size: 18px; |
| | | transition: width 0.2s; |
| | | width: 0; |
| | | overflow: hidden; |
| | | background: transparent; |
| | | border-radius: 0; |
| | | display: inline-block; |
| | | vertical-align: middle; |
| | | |
| | | :deep(.el-input__inner) { |
| | | border-radius: 0; |
| | | border: 0; |
| | | padding-left: 0; |
| | | padding-right: 0; |
| | | box-shadow: none !important; |
| | | border-bottom: 1px solid #d9d9d9; |
| | | vertical-align: middle; |
| | | .search-icon { |
| | | cursor: pointer; |
| | | font-size: 18px; |
| | | vertical-align: middle; |
| | | } |
| | | } |
| | | |
| | | &.show { |
| | | .header-search-select { |
| | | width: 210px; |
| | | margin-left: 10px; |
| | | font-size: 18px; |
| | | transition: width 0.2s; |
| | | width: 0; |
| | | overflow: hidden; |
| | | background: transparent; |
| | | border-radius: 0; |
| | | display: inline-block; |
| | | vertical-align: middle; |
| | | |
| | | :deep(.el-input__inner) { |
| | | border-radius: 0; |
| | | border: 0; |
| | | padding-left: 0; |
| | | padding-right: 0; |
| | | box-shadow: none !important; |
| | | border-bottom: 1px solid #d9d9d9; |
| | | vertical-align: middle; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &.show { |
| | | .header-search-select { |
| | | width: 210px; |
| | | margin-left: 10px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | |
| | | <script setup lang="ts"> |
| | | import icons from '@/components/IconSelect/requireIcons'; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: String, |
| | | require: true |
| | | }, |
| | | width: { |
| | | type: String, |
| | | require: false, |
| | | default: '400px' |
| | | } |
| | | modelValue: propTypes.string.isRequired, |
| | | width: propTypes.string.def('400px') |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:modelValue']); |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | src: { |
| | | type: String, |
| | | default: "" |
| | | }, |
| | | width: { |
| | | type: [Number, String], |
| | | default: "" |
| | | }, |
| | | height: { |
| | | type: [Number, String], |
| | | default: "" |
| | | } |
| | | src: propTypes.string.def(''), |
| | | width: { |
| | | type: [Number, String], |
| | | default: "" |
| | | }, |
| | | height: { |
| | | type: [Number, String], |
| | | default: "" |
| | | } |
| | | }); |
| | | |
| | | const realSrc = computed(() => { |
| | | if (!props.src) { |
| | | return; |
| | | } |
| | | let real_src = props.src.split(",")[0]; |
| | | return real_src; |
| | | if (!props.src) { |
| | | return; |
| | | } |
| | | let real_src = props.src.split(",")[0]; |
| | | return real_src; |
| | | }); |
| | | |
| | | const realSrcList = computed(() => { |
| | | if (!props.src) { |
| | | return; |
| | | } |
| | | let real_src_list = props.src.split(","); |
| | | let srcList:string[] = []; |
| | | real_src_list.forEach(item => { |
| | | return srcList.push(item); |
| | | }); |
| | | return srcList; |
| | | if (!props.src) { |
| | | return; |
| | | } |
| | | let real_src_list = props.src.split(","); |
| | | let srcList: string[] = []; |
| | | real_src_list.forEach(item => { |
| | | return srcList.push(item); |
| | | }); |
| | | return srcList; |
| | | }); |
| | | |
| | | const realWidth = computed(() => |
| | | typeof props.width == "string" ? props.width : `${props.width}px` |
| | | typeof props.width == "string" ? props.width : `${props.width}px` |
| | | ); |
| | | |
| | | const realHeight = computed(() => |
| | | typeof props.height == "string" ? props.height : `${props.height}px` |
| | | typeof props.height == "string" ? props.height : `${props.height}px` |
| | | ); |
| | | </script> |
| | | |
| | |
| | | border-radius: 5px; |
| | | background-color: #ebeef5; |
| | | box-shadow: 0 0 5px 1px #ccc; |
| | | |
| | | :deep(.el-image__inner) { |
| | | transition: all 0.3s; |
| | | cursor: pointer; |
| | | |
| | | &:hover { |
| | | transform: scale(1.2); |
| | | } |
| | | } |
| | | |
| | | :deep(.image-slot) { |
| | | display: flex; |
| | | justify-content: center; |
| | |
| | | :on-preview="handlePictureCardPreview" |
| | | :class="{ hide: fileList.length >= limit }" |
| | | > |
| | | <el-icon class="avatar-uploader-icon"><plus /></el-icon> |
| | | <el-icon class="avatar-uploader-icon"> |
| | | <plus /> |
| | | </el-icon> |
| | | </el-upload> |
| | | <!-- 上传提示 --> |
| | | <div class="el-upload__tip" v-if="showTip"> |
| | |
| | | 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"; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: [String, Object, Array], |
| | | // 图片数量限制 |
| | | limit: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | limit: propTypes.number.def(5), |
| | | // 大小限制(MB) |
| | | fileSize: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | fileSize: propTypes.number.def(5), |
| | | // 文件类型, 例如['png', 'jpg', 'jpeg'] |
| | | fileType: { |
| | | type: Array as PropType<string[]>, |
| | | default: () => ["png", "jpg", "jpeg"], |
| | | }, |
| | | fileType: propTypes.array.def(["png", "jpg", "jpeg"]), |
| | | // 是否显示提示 |
| | | isShowTip: { |
| | | type: Boolean, |
| | |
| | | () => props.isShowTip && (props.fileType || props.fileSize) |
| | | ); |
| | | |
| | | const imageUploadRef = ref(ElUpload); |
| | | const imageUploadRef = ref<ElUploadInstance>(); |
| | | |
| | | watch(() => props.modelValue, async val => { |
| | | if (val) { |
| | | // 首先将值转为数组 |
| | | let list:OssVO[] = []; |
| | | let list: OssVO[] = []; |
| | | if (Array.isArray(val)) { |
| | | list = val as OssVO[]; |
| | | } else { |
| | |
| | | fileList.value = []; |
| | | return []; |
| | | } |
| | | },{ deep: true, immediate: true }); |
| | | }, { deep: true, immediate: true }); |
| | | |
| | | /** 上传前loading加载 */ |
| | | const handleBeforeUpload = (file: any) => { |
| | |
| | | if (file.name.lastIndexOf(".") > -1) { |
| | | fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1); |
| | | } |
| | | isImg = props.fileType.some((type) => { |
| | | isImg = props.fileType.some((type: any) => { |
| | | if (file.type.indexOf(type) > -1) return true; |
| | | if (fileExtension && fileExtension.indexOf(type) > -1) return true; |
| | | return false; |
| | |
| | | number.value--; |
| | | proxy?.$modal.closeLoading(); |
| | | proxy?.$modal.msgError(res.msg); |
| | | imageUploadRef.value.handleRemove(file); |
| | | imageUploadRef.value?.handleRemove(file); |
| | | uploadedSuccessfully(); |
| | | } |
| | | } |
| | |
| | | let strs = ""; |
| | | separator = separator || ","; |
| | | for (let i in list) { |
| | | if(undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) { |
| | | if (undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) { |
| | | strs += list[i].ossId + separator; |
| | | } |
| | | } |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { TransferKey } from "element-plus"; |
| | | import { PropType } from "vue"; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | showSearch: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | showSearch: propTypes.bool.def(true), |
| | | columns: { |
| | | type: Array as PropType<FieldOption[]>, |
| | | }, |
| | | search: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | gutter: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | search: propTypes.bool.def(true), |
| | | gutter: propTypes.number.def(10), |
| | | }) |
| | | |
| | | const emits = defineEmits(['update:showSearch', 'queryTable']); |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | iconClass: { |
| | | type: String, |
| | | required: true |
| | | }, |
| | | className: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | color: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | iconClass: propTypes.string.isRequired, |
| | | className: propTypes.string.def(''), |
| | | color: propTypes.string.def(''), |
| | | }) |
| | | const iconName = computed(() => `#icon-${props.iconClass}`); |
| | | const svgClass = computed(() => { |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ElTreeSelect } from 'element-plus' |
| | | |
| | | const props = defineProps({ |
| | | /* 配置项 */ |
| | | objMap: { |
| | | type: Object, |
| | | default: () => { |
| | | return { |
| | | value: 'id', // ID字段名 |
| | | label: 'label', // 显示名称 |
| | | children: 'children' // 子级字段名 |
| | | type: Object, |
| | | default: () => { |
| | | return { |
| | | value: 'id', // ID字段名 |
| | | label: 'label', // 显示名称 |
| | | children: 'children' // 子级字段名 |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | /* 自动收起 */ |
| | | accordion: { |
| | | type: Boolean, |
| | | default: () => { |
| | | return false |
| | | } |
| | | type: Boolean, |
| | | default: () => { |
| | | return false |
| | | } |
| | | }, |
| | | /**当前双向数据绑定的值 */ |
| | | value: { |
| | | type: [String, Number], |
| | | default: '' |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | /**当前的数据 */ |
| | | options: { |
| | | type: Array, |
| | | default: () => [] |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | /**输入框内部的文字 */ |
| | | placeholder: { |
| | | type: String, |
| | | default: '' |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }) |
| | | |
| | | |
| | | const selectTree = ref(ElTreeSelect); |
| | | const selectTree = ref<ElTreeSelectInstance>(); |
| | | |
| | | const emit = defineEmits(['update:value']); |
| | | |
| | | const valueId = computed({ |
| | | get: () => props.value, |
| | | set: (val) => { |
| | | emit('update:value', val) |
| | | emit('update:value', val) |
| | | } |
| | | }); |
| | | const valueTitle = ref(''); |
| | | const defaultExpandedKey = ref<any[]>([]); |
| | | |
| | | function initHandle() { |
| | | const initHandle = () => { |
| | | nextTick(() => { |
| | | const selectedValue = valueId.value; |
| | | 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] // 设置默认展开 |
| | | const selectedValue = valueId.value; |
| | | 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] // 设置默认展开 |
| | | } |
| | | } else { |
| | | clearHandle() |
| | | } |
| | | } else { |
| | | clearHandle() |
| | | } |
| | | }) |
| | | } |
| | | function handleNodeClick(node: any) { |
| | | const handleNodeClick = (node: any) => { |
| | | valueTitle.value = node[props.objMap.label] |
| | | valueId.value = node[props.objMap.value]; |
| | | defaultExpandedKey.value = []; |
| | | selectTree.value.blur() |
| | | selectTree.value?.blur() |
| | | selectFilterData('') |
| | | } |
| | | function selectFilterData(val: any) { |
| | | selectTree.value.filter(val) |
| | | const selectFilterData = (val: any) => { |
| | | selectTree.value?.filter(val) |
| | | } |
| | | function filterNode(value: any, data: any) { |
| | | const filterNode = (value: any, data: any) => { |
| | | if (!value) return true |
| | | return data[props.objMap['label']].indexOf(value) !== -1 |
| | | } |
| | | function clearHandle() { |
| | | const clearHandle = () => { |
| | | valueTitle.value = '' |
| | | valueId.value = '' |
| | | defaultExpandedKey.value = []; |
| | | clearSelected() |
| | | } |
| | | function clearSelected() { |
| | | const clearSelected = () => { |
| | | const allNode = document.querySelectorAll('#tree-option .el-tree-node') |
| | | allNode.forEach((element) => element.classList.remove('is-current')) |
| | | } |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "@/assets/styles/variables.module.scss"; |
| | | |
| | | .el-scrollbar .el-scrollbar__view .el-select-dropdown__item { |
| | | padding: 0; |
| | | background-color: #fff; |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | const props = defineProps({ |
| | | src: { |
| | | type: String, |
| | | required: true |
| | | } |
| | | src: propTypes.string.isRequired |
| | | }) |
| | | |
| | | const height = ref(document.documentElement.clientHeight - 94.5 + "px;") |