init
This commit is contained in:
parent
b8eebd1d17
commit
30b524a25d
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<span class="headerAvatar">
|
||||
<template v-if="picType === 'avatar'">
|
||||
<el-avatar
|
||||
v-if="userStore.userInfo.headerImg"
|
||||
:size="30"
|
||||
:src="avatar"
|
||||
/>
|
||||
<el-avatar
|
||||
v-else
|
||||
:size="30"
|
||||
:src="noAvatar"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="picType === 'img'">
|
||||
<img
|
||||
v-if="userStore.userInfo.headerImg"
|
||||
:src="avatar"
|
||||
class="avatar"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="noAvatar"
|
||||
class="avatar"
|
||||
>
|
||||
</template>
|
||||
<template v-if="picType === 'file'">
|
||||
<el-image
|
||||
:src="file"
|
||||
class="file"
|
||||
:preview-src-list="previewSrcList"
|
||||
:preview-teleported="true"
|
||||
/>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import noAvatarPng from '@/assets/noBody.png'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'CustomPic'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
picType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'avatar'
|
||||
},
|
||||
picSrc: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
preview: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API + '/')
|
||||
const noAvatar = ref(noAvatarPng)
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const avatar = computed(() => {
|
||||
if (props.picSrc === '') {
|
||||
if (userStore.userInfo.headerImg !== '' && userStore.userInfo.headerImg.slice(0, 4) === 'http') {
|
||||
return userStore.userInfo.headerImg
|
||||
}
|
||||
return path.value + userStore.userInfo.headerImg
|
||||
} else {
|
||||
if (props.picSrc !== '' && props.picSrc.slice(0, 4) === 'http') {
|
||||
return props.picSrc
|
||||
}
|
||||
return path.value + props.picSrc
|
||||
}
|
||||
})
|
||||
const file = computed(() => {
|
||||
if (props.picSrc && props.picSrc.slice(0, 4) !== 'http') {
|
||||
return path.value + props.picSrc
|
||||
}
|
||||
return props.picSrc
|
||||
})
|
||||
const previewSrcList = computed(() => props.preview ? [file.value] : [])
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.headerAvatar{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.file{
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="download"
|
||||
@click="exportExcelFunc"
|
||||
>导出</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
templateId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
condition: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
order: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const exportExcelFunc = async() => {
|
||||
if (props.templateId === '') {
|
||||
ElMessage.error('组件未设置模板ID')
|
||||
return
|
||||
}
|
||||
const baseUrl = import.meta.env.VITE_BASE_API
|
||||
const paramsCopy = JSON.parse(JSON.stringify(props.condition))
|
||||
if (props.limit) {
|
||||
paramsCopy.limit = props.limit
|
||||
}
|
||||
if (props.offset) {
|
||||
paramsCopy.offset = props.offset
|
||||
}
|
||||
if (props.order) {
|
||||
paramsCopy.order = props.order
|
||||
}
|
||||
const params = Object.entries(paramsCopy)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&')
|
||||
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}`
|
||||
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="download"
|
||||
@click="exportTemplate"
|
||||
>下载模板</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
templateId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const exportTemplate = async() => {
|
||||
if (props.templateId === '') {
|
||||
ElMessage.error('组件未设置模板ID')
|
||||
return
|
||||
}
|
||||
const baseUrl = import.meta.env.VITE_BASE_API
|
||||
const url = `${baseUrl}/sysExportTemplate/exportTemplate?templateID=${props.templateId}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<el-upload
|
||||
:action="url"
|
||||
:show-file-list="false"
|
||||
:on-success="handleSuccess"
|
||||
:multiple="false"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="upload"
|
||||
>导入</el-button>
|
||||
</el-upload>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const baseUrl = import.meta.env.VITE_BASE_API
|
||||
|
||||
const props = defineProps({
|
||||
templateId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['on-success'])
|
||||
|
||||
const url = `${baseUrl}/sysExportTemplate/importExcel?templateID=${props.templateId}`
|
||||
|
||||
const handleSuccess = (res) => {
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('导入成功')
|
||||
emit('on-success')
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<vue-office-docx
|
||||
:src="docx"
|
||||
@rendered="rendered"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Docx'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
// 引入VueOfficeDocx组件
|
||||
import VueOfficeDocx from '@vue-office/docx'
|
||||
// 引入相关样式
|
||||
import '@vue-office/docx/lib/index.css'
|
||||
|
||||
const model = defineModel({
|
||||
type: String,
|
||||
})
|
||||
|
||||
const docx = ref(null)
|
||||
watch(
|
||||
() => model,
|
||||
value => { docx.value = value },
|
||||
{ immediate: true }
|
||||
)
|
||||
const rendered = () => {
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<VueOfficeExcel :src="excel" @rendered="renderedHandler" @error="errorHandler" style="height: 100vh;width: 100vh"/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Excel'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
//引入VueOfficeExcel组件
|
||||
import VueOfficeExcel from '@vue-office/excel'
|
||||
//引入相关样式
|
||||
import '@vue-office/excel/lib/index.css'
|
||||
import {ref, watch} from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: () => ""
|
||||
}
|
||||
})
|
||||
const excel = ref('')
|
||||
watch(() => props.modelValue, val => excel.value = val, {immediate: true})
|
||||
const renderedHandler = () => {
|
||||
|
||||
}
|
||||
const errorHandler = () => {
|
||||
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<div class="border border-solid border-gray-100 h-full w-full">
|
||||
<el-row>
|
||||
<div v-if="ext==='docx'">
|
||||
<Docx v-model="fullFileURL" />
|
||||
</div>
|
||||
<div v-else-if="ext==='pdf'">
|
||||
<Pdf v-model="fullFileURL" />
|
||||
</div>
|
||||
<div v-else-if="ext==='xlsx'">
|
||||
<Excel v-model="fullFileURL" />
|
||||
</div>
|
||||
<div v-else-if="ext==='image'">
|
||||
<el-image
|
||||
:src="fullFileURL"
|
||||
lazy
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Office'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import Docx from '@/components/office/docx.vue'
|
||||
import Pdf from '@/components/office/pdf.vue'
|
||||
import Excel from '@/components/office/excel.vue'
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const model = defineModel({ type: String})
|
||||
|
||||
const fileUrl = ref('')
|
||||
const ext = ref('')
|
||||
watch(
|
||||
() => model,
|
||||
val => {
|
||||
fileUrl.value = val
|
||||
const fileExt = val.split('.')[1] || ''
|
||||
const image = ['png', 'jpg', 'jpeg', 'gif']
|
||||
ext.value = image.includes(fileExt) ? 'image' : fileExt
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
const fullFileURL = computed(() => {
|
||||
return path.value + '/' + fileUrl.value
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<vue-office-pdf
|
||||
|
||||
:src="pdf"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "Pdf"
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import {ref, watch} from "vue"
|
||||
|
||||
//引入VueOfficeDocx组件
|
||||
import VueOfficePdf from "@vue-office/pdf";
|
||||
//引入相关样式
|
||||
import '@vue-office/docx/lib/index.css'
|
||||
console.log("pdf===>")
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: () => ""
|
||||
}
|
||||
})
|
||||
const pdf = ref(null)
|
||||
watch(() => props.modelValue, val => pdf.value = val, {immediate: true})
|
||||
const renderedHandler = () => {
|
||||
console.log("pdf 加载成功")
|
||||
}
|
||||
const errorHandler = () => {
|
||||
console.log("pdf 错误")
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div class="border border-solid border-gray-100 h-full">
|
||||
<Toolbar
|
||||
:editor="editorRef"
|
||||
:default-config="toolbarConfig"
|
||||
mode="default"
|
||||
/>
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
class="overflow-y-hidden mt-0.5"
|
||||
style="height: 18rem;"
|
||||
:default-config="editorConfig"
|
||||
mode="default"
|
||||
@onCreated="handleCreated"
|
||||
@onChange="change"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||
|
||||
const basePath = import.meta.env.VITE_BASE_API
|
||||
|
||||
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getUrl } from '@/utils/image'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const emits = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
const change = (editor) => {
|
||||
emits('change', editor)
|
||||
emits('update:modelValue', valueHtml.value)
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const editorRef = shallowRef()
|
||||
const valueHtml = ref('')
|
||||
|
||||
const toolbarConfig = {}
|
||||
const editorConfig = {
|
||||
placeholder: '请输入内容...',
|
||||
MENU_CONF: {}
|
||||
}
|
||||
editorConfig.MENU_CONF['uploadImage'] = {
|
||||
fieldName: 'file',
|
||||
server: basePath + '/fileUploadAndDownload/upload?noSave=1',
|
||||
customInsert(res, insertFn) {
|
||||
if (res.code === 0) {
|
||||
const urlPath = getUrl(res.data.file.url)
|
||||
insertFn(urlPath, res.data.file.name)
|
||||
return
|
||||
}
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor
|
||||
valueHtml.value = props.modelValue
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
valueHtml.value = props.modelValue
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div class="border border-solid border-gray-100 h-full">
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
class="overflow-y-hidden mt-0.5"
|
||||
:default-config="editorConfig"
|
||||
mode="default"
|
||||
@onCreated="handleCreated"
|
||||
@onChange="change"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||
|
||||
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
|
||||
import { Editor } from '@wangeditor/editor-for-vue'
|
||||
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const emits = defineEmits(['change', 'update:modelValue'])
|
||||
const editorConfig = ref({
|
||||
readOnly: true
|
||||
})
|
||||
const change = (editor) => {
|
||||
emits('change', editor)
|
||||
emits('update:modelValue', valueHtml.value)
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const editorRef = shallowRef()
|
||||
const valueHtml = ref('')
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor
|
||||
valueHtml.value = props.modelValue
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
valueHtml.value = props.modelValue
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
multiple
|
||||
:action="`${path}/fileUploadAndDownload/upload?noSave=1`"
|
||||
:on-error="uploadError"
|
||||
:on-success="uploadSuccess"
|
||||
:show-file-list="true"
|
||||
:file-list="fileList"
|
||||
:limit="limit"
|
||||
:accept="accept"
|
||||
class="upload-btn"
|
||||
>
|
||||
<el-button type="primary">上传文件</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'UploadCommon',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const fullscreenLoading = ref(false)
|
||||
|
||||
const fileList = ref(props.modelValue)
|
||||
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
|
||||
watch(fileList.value, (val) => {
|
||||
console.log(val)
|
||||
emits('update:modelValue', val)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
value => {
|
||||
fileList.value = value
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
const uploadSuccess = (res) => {
|
||||
const { data } = res
|
||||
if (data.file) {
|
||||
fileList.value.push({
|
||||
name: data.file.name,
|
||||
url: data.file.url
|
||||
})
|
||||
fullscreenLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const uploadError = () => {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '上传失败'
|
||||
})
|
||||
fullscreenLoading.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,485 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="!multiple"
|
||||
class="update-image"
|
||||
:style="{
|
||||
'background-image': `url(${getUrl(model)})`,
|
||||
'position': 'relative',
|
||||
}"
|
||||
>
|
||||
<el-icon
|
||||
v-if="isVideoExt(model || '')"
|
||||
:size="32"
|
||||
class="video video-icon"
|
||||
style=""
|
||||
>
|
||||
<VideoPlay />
|
||||
</el-icon>
|
||||
<video
|
||||
v-if="isVideoExt(model || '')"
|
||||
class="avatar video-avatar video"
|
||||
muted
|
||||
preload="metadata"
|
||||
style=""
|
||||
@click="openChooseImg"
|
||||
>
|
||||
<source :src="getUrl(model) + '#t=1'">
|
||||
</video>
|
||||
<span
|
||||
v-if="model"
|
||||
class="update"
|
||||
style="position: absolute;"
|
||||
@click="openChooseImg"
|
||||
>
|
||||
<el-icon>
|
||||
<delete />
|
||||
</el-icon>
|
||||
删除</span>
|
||||
<span
|
||||
v-else
|
||||
class="update text-gray-600"
|
||||
@click="openChooseImg"
|
||||
>
|
||||
<el-icon>
|
||||
<plus />
|
||||
</el-icon>
|
||||
上传</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="multiple-img"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in multipleValue"
|
||||
:key="index"
|
||||
class="update-image"
|
||||
:style="{
|
||||
'background-image': `url(${getUrl(item)})`,
|
||||
'position': 'relative',
|
||||
}"
|
||||
>
|
||||
<el-icon
|
||||
v-if="isVideoExt(item || '')"
|
||||
:size="32"
|
||||
class="video video-icon"
|
||||
>
|
||||
<VideoPlay />
|
||||
</el-icon>
|
||||
<video
|
||||
v-if="isVideoExt(item || '')"
|
||||
class="avatar video-avatar video"
|
||||
muted
|
||||
preload="metadata"
|
||||
@click="deleteImg(index)"
|
||||
>
|
||||
<source :src="getUrl(item) + '#t=1'">
|
||||
</video>
|
||||
<span
|
||||
class="update"
|
||||
style="position: absolute;"
|
||||
@click="deleteImg(index)"
|
||||
>
|
||||
<el-icon>
|
||||
<delete />
|
||||
</el-icon>
|
||||
删除</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!maxUpdateCount || maxUpdateCount>multipleValue.length"
|
||||
class="add-image"
|
||||
>
|
||||
<span
|
||||
class="update text-gray-600"
|
||||
@click="openChooseImg"
|
||||
>
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
上传</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="drawer"
|
||||
title="媒体库"
|
||||
size="650px"
|
||||
>
|
||||
<warning-bar
|
||||
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
|
||||
/>
|
||||
<div class="gva-btn-list">
|
||||
<upload-common
|
||||
:image-common="imageCommon"
|
||||
class="upload-btn-media-library"
|
||||
@on-success="getImageList"
|
||||
/>
|
||||
<upload-image
|
||||
:image-url="imageUrl"
|
||||
:file-size="512"
|
||||
:max-w-h="1080"
|
||||
class="upload-btn-media-library"
|
||||
@on-success="getImageList"
|
||||
/>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
:inline="true"
|
||||
:model="search"
|
||||
>
|
||||
<el-form-item label="">
|
||||
<el-input
|
||||
v-model="search.keyword"
|
||||
class="keyword"
|
||||
placeholder="请输入文件名或备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="getImageList"
|
||||
>查询
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="media">
|
||||
<div
|
||||
v-for="(item,key) in picList"
|
||||
:key="key"
|
||||
class="media-box"
|
||||
>
|
||||
<div class="header-img-box-list">
|
||||
<el-image
|
||||
:key="key"
|
||||
:src="getUrl(item.url)"
|
||||
fit="cover"
|
||||
style="width: 100%;height: 100%;"
|
||||
@click="chooseImg(item.url)"
|
||||
>
|
||||
<template #error>
|
||||
<el-icon
|
||||
v-if="isVideoExt(item.url || '')"
|
||||
:size="32"
|
||||
class="video video-icon"
|
||||
>
|
||||
<VideoPlay />
|
||||
</el-icon>
|
||||
<video
|
||||
v-if="isVideoExt(item.url || '')"
|
||||
class="avatar video-avatar video"
|
||||
muted
|
||||
preload="metadata"
|
||||
@click="chooseImg(item.url)"
|
||||
>
|
||||
<source :src="getUrl(item.url) + '#t=1'">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
<div
|
||||
v-else
|
||||
class="header-img-box-list"
|
||||
>
|
||||
<el-icon class="lost-image">
|
||||
<icon-picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<div
|
||||
class="img-title"
|
||||
@click="editFileNameFunc(item)"
|
||||
>{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:style="{'justify-content':'center'}"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { getUrl, isVideoExt } from '@/utils/image'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getFileList, editFileName } from '@/api/fileUploadAndDownload'
|
||||
import UploadImage from '@/components/upload/image.vue'
|
||||
import UploadCommon from '@/components/upload/common.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Delete, Plus, Picture as IconPicture } from '@element-plus/icons-vue'
|
||||
|
||||
const imageUrl = ref('')
|
||||
const imageCommon = ref('')
|
||||
|
||||
const search = ref({})
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(20)
|
||||
|
||||
const model = defineModel({ type: [String, Array] })
|
||||
|
||||
const props = defineProps({
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxUpdateCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const multipleValue = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.multiple) {
|
||||
multipleValue.value = model.value
|
||||
}
|
||||
})
|
||||
const deleteImg = (index) => {
|
||||
multipleValue.value.splice(index, 1)
|
||||
model.value = multipleValue.value
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getImageList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getImageList()
|
||||
}
|
||||
const editFileNameFunc = async(row) => {
|
||||
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '不能为空',
|
||||
inputValue: row.name
|
||||
}).then(async({ value }) => {
|
||||
row.name = value
|
||||
// console.log(row)
|
||||
const res = await editFileName(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '编辑成功!',
|
||||
})
|
||||
getImageList()
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消修改'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const drawer = ref(false)
|
||||
const picList = ref([])
|
||||
|
||||
const imageTypeList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp']
|
||||
const videoTyteList = ['mp4', 'avi', 'rmvb', 'rm', 'asf', 'divx', 'mpg', 'mpeg', 'mpe', 'wmv', 'mkv', 'vob']
|
||||
|
||||
const listObj = {
|
||||
image: imageTypeList,
|
||||
video: videoTyteList
|
||||
}
|
||||
|
||||
const chooseImg = (url) => {
|
||||
console.log(url)
|
||||
if (props.fileType) {
|
||||
const typeSuccess = listObj[props.fileType].some(item => {
|
||||
if (url.includes(item)) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (!typeSuccess) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '当前类型不支持使用'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if (props.multiple) {
|
||||
multipleValue.value.push(url)
|
||||
model.value = multipleValue.value
|
||||
} else {
|
||||
model.value = url
|
||||
}
|
||||
drawer.value = false
|
||||
}
|
||||
const openChooseImg = async() => {
|
||||
if (model.value && !props.multiple) {
|
||||
model.value = ''
|
||||
return
|
||||
}
|
||||
await getImageList()
|
||||
drawer.value = true
|
||||
}
|
||||
|
||||
const getImageList = async() => {
|
||||
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
|
||||
if (res.code === 0) {
|
||||
picList.value = res.data.list
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.multiple-img {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.add-image {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
line-height: 120px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 20px;
|
||||
border: 1px dashed #ccc;
|
||||
background-size: cover;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.update-image {
|
||||
cursor: pointer;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
line-height: 120px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 20px;
|
||||
border: 1px dashed #ccc;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(255, 255, 255, 0.15) 0%,
|
||||
rgba(0, 0, 0, 0.15) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
at top center,
|
||||
rgba(255, 255, 255, 0.4) 0%,
|
||||
rgba(0, 0, 0, 0.4) 120%
|
||||
) #989898;
|
||||
background-blend-mode: multiply, multiply;
|
||||
background-size: cover;
|
||||
|
||||
.update {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.video {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.video-icon {
|
||||
position: absolute;
|
||||
left: calc(50% - 16px);
|
||||
top: calc(50% - 16px);
|
||||
}
|
||||
|
||||
video {
|
||||
object-fit: cover;
|
||||
max-width: 100%;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.update {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn-media-library {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.media {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.media-box {
|
||||
width: 120px;
|
||||
margin-left: 20px;
|
||||
|
||||
.img-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-img-box-list {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
line-height: 120px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
.el-image__inner {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
vertical-align: middle;
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.el-image {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-icon {
|
||||
position: absolute;
|
||||
left: calc(50% - 16px);
|
||||
top: calc(50% - 16px);
|
||||
}
|
||||
|
||||
video {
|
||||
object-fit: cover;
|
||||
max-width: 100%;
|
||||
min-height: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<svg
|
||||
:class="svgClass"
|
||||
v-bind="$attrs"
|
||||
:color="color"
|
||||
>
|
||||
<use
|
||||
:xlink:href="'#'+name"
|
||||
rel="external nofollow"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'currentColor'
|
||||
}
|
||||
})
|
||||
|
||||
const svgClass = computed(() => {
|
||||
if (props.name) {
|
||||
return `svg-icon ${props.name}`
|
||||
}
|
||||
return 'svg-icon'
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
@apply w-4 h-4;
|
||||
fill: currentColor;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
:action="`${path}/fileUploadAndDownload/upload`"
|
||||
:before-upload="checkFile"
|
||||
:on-error="uploadError"
|
||||
:on-success="uploadSuccess"
|
||||
:show-file-list="false"
|
||||
class="upload-btn"
|
||||
>
|
||||
<el-button type="primary">普通上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { isVideoMime, isImageMime } from '@/utils/image'
|
||||
|
||||
defineOptions({
|
||||
name: 'UploadCommon',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['on-success'])
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const fullscreenLoading = ref(false)
|
||||
|
||||
const checkFile = (file) => {
|
||||
fullscreenLoading.value = true
|
||||
const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo 应支持在项目中设置
|
||||
const isLt5M = file.size / 1024 / 1024 < 5 // 5MB, @todo 应支持项目中设置
|
||||
const isVideo = isVideoMime(file.type)
|
||||
const isImage = isImageMime(file.type)
|
||||
let pass = true
|
||||
if (!isVideo && !isImage) {
|
||||
ElMessage.error('上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!')
|
||||
fullscreenLoading.value = false
|
||||
pass = false
|
||||
}
|
||||
if (!isLt5M && isVideo) {
|
||||
ElMessage.error('上传视频大小不能超过 5MB')
|
||||
fullscreenLoading.value = false
|
||||
pass = false
|
||||
}
|
||||
if (!isLt500K && isImage) {
|
||||
ElMessage.error('未压缩的上传图片大小不能超过 500KB,请使用压缩上传')
|
||||
fullscreenLoading.value = false
|
||||
pass = false
|
||||
}
|
||||
|
||||
console.log('upload file check result: ', pass)
|
||||
|
||||
return pass
|
||||
}
|
||||
|
||||
const uploadSuccess = (res) => {
|
||||
const { data } = res
|
||||
if (data.file) {
|
||||
emit('on-success', data.file.url)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadError = () => {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '上传失败'
|
||||
})
|
||||
fullscreenLoading.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
:action="`${path}/fileUploadAndDownload/upload`"
|
||||
:show-file-list="false"
|
||||
:on-success="handleImageSuccess"
|
||||
:before-upload="beforeImageUpload"
|
||||
:multiple="false"
|
||||
>
|
||||
<el-button type="primary">压缩上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ImageCompress from '@/utils/image'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'UploadImage',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['on-success'])
|
||||
const props = defineProps({
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 2048 // 2M 超出后执行压缩
|
||||
},
|
||||
maxWH: {
|
||||
type: Number,
|
||||
default: 1920 // 图片长宽上限
|
||||
}
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const beforeImageUpload = (file) => {
|
||||
const isJPG = file.type === 'image/jpeg'
|
||||
const isPng = file.type === 'image/png'
|
||||
if (!isJPG && !isPng) {
|
||||
ElMessage.error('上传头像图片只能是 jpg或png 格式!')
|
||||
return false
|
||||
}
|
||||
|
||||
const isRightSize = file.size / 1024 < props.fileSize
|
||||
if (!isRightSize) {
|
||||
// 压缩
|
||||
const compress = new ImageCompress(file, props.fileSize, props.maxWH)
|
||||
return compress.compress()
|
||||
}
|
||||
return isRightSize
|
||||
}
|
||||
|
||||
const handleImageSuccess = (res) => {
|
||||
const { data } = res
|
||||
if (data.file) {
|
||||
emit('on-success', data.file.url)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-uploader {
|
||||
border: 1px dashed #d9d9d9;
|
||||
width: 180px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.image-uploader {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.image-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
line-height: 178px;
|
||||
text-align: center;
|
||||
}
|
||||
.image {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div
|
||||
class="px-1.5 py-2 flex items-center bg-amber-50 rounded gap-2 mb-3 text-amber-500"
|
||||
:class="href&&'cursor-pointer'"
|
||||
@click="open"
|
||||
>
|
||||
<el-icon class="text-xl">
|
||||
<warning-filled />
|
||||
</el-icon>
|
||||
<span>
|
||||
{{ title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { WarningFilled } from '@element-plus/icons-vue'
|
||||
const prop = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
if (prop.href) {
|
||||
window.open(prop.href)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* web框架组
|
||||
*
|
||||
* */
|
||||
// 加载网站配置文件夹
|
||||
import { register } from './global'
|
||||
|
||||
export default {
|
||||
install: (app) => {
|
||||
register(app)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* 网站配置文件
|
||||
*/
|
||||
|
||||
const config = {
|
||||
appName: '利农天下',
|
||||
appLogo: '',
|
||||
showViteLogo: true,
|
||||
logs: [],
|
||||
}
|
||||
|
||||
export const viteLogo = (env) => {
|
||||
console.log('哈哈哈')
|
||||
}
|
||||
|
||||
export default config
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import config from './config'
|
||||
import { h } from 'vue'
|
||||
|
||||
// 统一导入el-icon图标
|
||||
import * as ElIconModules from '@element-plus/icons-vue'
|
||||
import svgIcon from '@/components/svgIcon/svgIcon.vue'
|
||||
// 导入转换图标名称的函数
|
||||
|
||||
const createIconComponent = (name) => ({
|
||||
name: 'SvgIcon',
|
||||
render() {
|
||||
return h(svgIcon, {
|
||||
name: name,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const registerIcons = async (app) => {
|
||||
const iconModules = import.meta.glob('@/assets/icons/**/*.svg')
|
||||
for (const path in iconModules) {
|
||||
const iconName = path.split('/').pop().replace(/\.svg$/, '')
|
||||
// 如果iconName带空格则不加入到图标库中并且提示名称不合法
|
||||
if (iconName.indexOf(' ') !== -1) {
|
||||
console.error(`icon ${iconName}.svg includes whitespace`)
|
||||
continue
|
||||
}
|
||||
const iconComponent = createIconComponent(iconName)
|
||||
config.logs.push({
|
||||
'key': iconName,
|
||||
'label': iconName,
|
||||
})
|
||||
app.component(iconName, iconComponent)
|
||||
}
|
||||
}
|
||||
|
||||
export const register = (app) => {
|
||||
// 统一注册el-icon图标
|
||||
for (const iconName in ElIconModules) {
|
||||
app.component(iconName, ElIconModules[iconName])
|
||||
}
|
||||
app.component('SvgIcon', svgIcon)
|
||||
registerIcons(app)
|
||||
app.config.globalProperties.$GIN_VUE_ADMIN = config
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 权限按钮展示指令
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
export default {
|
||||
install: (app) => {
|
||||
const userStore = useUserStore()
|
||||
app.directive('auth', {
|
||||
// 当被绑定的元素插入到 DOM 中时……
|
||||
mounted: function(el, binding) {
|
||||
const userInfo = userStore.userInfo
|
||||
let type = ''
|
||||
switch (Object.prototype.toString.call(binding.value)) {
|
||||
case '[object Array]':
|
||||
type = 'Array'
|
||||
break
|
||||
case '[object String]':
|
||||
type = 'String'
|
||||
break
|
||||
case '[object Number]':
|
||||
type = 'Number'
|
||||
break
|
||||
default:
|
||||
type = ''
|
||||
break
|
||||
}
|
||||
if (type === '') {
|
||||
el.parentNode.removeChild(el)
|
||||
return
|
||||
}
|
||||
const waitUse = binding.value.toString().split(',')
|
||||
let flag = waitUse.some(item => Number(item) === userInfo.authorityId)
|
||||
if (binding.modifiers.not) {
|
||||
flag = !flag
|
||||
}
|
||||
if (!flag) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// 监听 window 的 resize 事件,返回当前窗口的宽高
|
||||
import { shallowRef } from 'vue'
|
||||
import { tryOnMounted, useEventListener } from '@vueuse/core'
|
||||
|
||||
const width = shallowRef(0)
|
||||
const height = shallowRef(0)
|
||||
|
||||
export const useWindowResize = (cb) => {
|
||||
const onResize = () => {
|
||||
width.value = window.innerWidth
|
||||
height.value = window.innerHeight
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb(width.value, height.value)
|
||||
}
|
||||
}
|
||||
|
||||
tryOnMounted(onResize)
|
||||
useEventListener('resize', onResize, { passive: true })
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { createPinia } from 'pinia'
|
||||
|
||||
const store = createPinia()
|
||||
|
||||
export {
|
||||
store
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { findSysDictionary } from '@/api/sysDictionary'
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useDictionaryStore = defineStore('dictionary', () => {
|
||||
const dictionaryMap = ref({})
|
||||
|
||||
const setDictionaryMap = (dictionaryRes) => {
|
||||
dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes }
|
||||
}
|
||||
|
||||
const getDictionary = async(type) => {
|
||||
if (dictionaryMap.value[type] && dictionaryMap.value[type].length) {
|
||||
return dictionaryMap.value[type]
|
||||
} else {
|
||||
const res = await findSysDictionary({ type })
|
||||
if (res.code === 0) {
|
||||
const dictionaryRes = {}
|
||||
const dict = []
|
||||
res.data.resysDictionary.sysDictionaryDetails && res.data.resysDictionary.sysDictionaryDetails.forEach(item => {
|
||||
dict.push({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
extend: item.extend
|
||||
})
|
||||
})
|
||||
dictionaryRes[res.data.resysDictionary.type] = dict
|
||||
setDictionaryMap(dictionaryRes)
|
||||
return dictionaryMap.value[type]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dictionaryMap,
|
||||
setDictionaryMap,
|
||||
getDictionary
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { asyncRouterHandle } from '@/utils/asyncRouter'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { asyncMenu } from '@/api/menu'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const notLayoutRouterArr = []
|
||||
const keepAliveRoutersArr = []
|
||||
const nameMap = {}
|
||||
|
||||
const formatRouter = (routes, routeMap, parent) => {
|
||||
routes && routes.forEach(item => {
|
||||
item.parent = parent
|
||||
item.meta.btns = item.btns
|
||||
item.meta.hidden = item.hidden
|
||||
if (item.meta.defaultMenu === true) {
|
||||
if (!parent) {
|
||||
item = { ...item, path: `/${item.path}` }
|
||||
notLayoutRouterArr.push(item)
|
||||
}
|
||||
}
|
||||
routeMap[item.name] = item
|
||||
if (item.children && item.children.length > 0) {
|
||||
formatRouter(item.children, routeMap, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const KeepAliveFilter = (routes) => {
|
||||
routes && routes.forEach(item => {
|
||||
// 子菜单中有 keep-alive 的,父菜单也必须 keep-alive,否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。
|
||||
if ((item.children && item.children.some(ch => ch.meta.keepAlive) || item.meta.keepAlive)) {
|
||||
item.component && item.component().then(val => {
|
||||
keepAliveRoutersArr.push(val.default.name)
|
||||
nameMap[item.name] = val.default.name
|
||||
})
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
KeepAliveFilter(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const useRouterStore = defineStore('router', () => {
|
||||
const keepAliveRouters = ref([])
|
||||
const asyncRouterFlag = ref(0)
|
||||
const setKeepAliveRouters = (history) => {
|
||||
const keepArrTemp = []
|
||||
history.forEach(item => {
|
||||
if (nameMap[item.name]) {
|
||||
keepArrTemp.push(nameMap[item.name])
|
||||
}
|
||||
})
|
||||
keepAliveRouters.value = Array.from(new Set(keepArrTemp))
|
||||
}
|
||||
emitter.on('setKeepAlive', setKeepAliveRouters)
|
||||
|
||||
const asyncRouters = ref([])
|
||||
const routeMap = ({})
|
||||
// 从后台获取动态路由
|
||||
const SetAsyncRouter = async() => {
|
||||
asyncRouterFlag.value++
|
||||
const baseRouter = [{
|
||||
path: '/layout',
|
||||
name: 'layout',
|
||||
component: 'view/layout/index.vue',
|
||||
meta: {
|
||||
title: '底层layout'
|
||||
},
|
||||
children: []
|
||||
}]
|
||||
const asyncRouterRes = await asyncMenu()
|
||||
const asyncRouter = asyncRouterRes.data.menus
|
||||
asyncRouter && asyncRouter.push({
|
||||
path: 'reload',
|
||||
name: 'Reload',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: '',
|
||||
closeTab: true,
|
||||
},
|
||||
component: 'view/error/reload.vue'
|
||||
})
|
||||
formatRouter(asyncRouter, routeMap)
|
||||
baseRouter[0].children = asyncRouter
|
||||
if (notLayoutRouterArr.length !== 0) {
|
||||
baseRouter.push(...notLayoutRouterArr)
|
||||
}
|
||||
asyncRouterHandle(baseRouter)
|
||||
KeepAliveFilter(asyncRouter)
|
||||
asyncRouters.value = baseRouter
|
||||
return true
|
||||
}
|
||||
|
||||
return {
|
||||
asyncRouters,
|
||||
keepAliveRouters,
|
||||
asyncRouterFlag,
|
||||
SetAsyncRouter,
|
||||
routeMap
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
import { login, getUserInfo, setSelfInfo } from '@/api/user'
|
||||
import { jsonInBlacklist } from '@/api/jwt'
|
||||
import router from '@/router/index'
|
||||
import { ElLoading, ElMessage } from 'element-plus'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRouterStore } from './router'
|
||||
import cookie from 'js-cookie'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const loadingInstance = ref(null)
|
||||
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {},
|
||||
sideMode: 'dark',
|
||||
activeColor: 'var(--el-color-primary)',
|
||||
baseColor: '#fff'
|
||||
})
|
||||
const token = ref(window.localStorage.getItem('token') || cookie.get('x-token') || '')
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const NeedInit = () => {
|
||||
token.value = ''
|
||||
window.localStorage.removeItem('token')
|
||||
router.push({ name: 'Init', replace: true })
|
||||
}
|
||||
|
||||
const ResetUserInfo = (value = {}) => {
|
||||
userInfo.value = {
|
||||
...userInfo.value,
|
||||
...value
|
||||
}
|
||||
}
|
||||
/* 获取用户信息*/
|
||||
const GetUserInfo = async() => {
|
||||
const res = await getUserInfo()
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.userInfo)
|
||||
}
|
||||
return res
|
||||
}
|
||||
/* 登录*/
|
||||
const LoginIn = async(loginInfo) => {
|
||||
loadingInstance.value = ElLoading.service({
|
||||
fullscreen: true,
|
||||
text: '登录中,请稍候...',
|
||||
})
|
||||
try {
|
||||
const res = await login(loginInfo)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
const routerStore = useRouterStore()
|
||||
await routerStore.SetAsyncRouter()
|
||||
const asyncRouters = routerStore.asyncRouters
|
||||
asyncRouters.forEach(asyncRouter => {
|
||||
router.addRoute(asyncRouter)
|
||||
})
|
||||
|
||||
if (!router.hasRoute(userInfo.value.authority.defaultRouter)) {
|
||||
ElMessage.error('请联系管理员进行授权')
|
||||
} else {
|
||||
await router.replace({ name: userInfo.value.authority.defaultRouter })
|
||||
}
|
||||
|
||||
loadingInstance.value.close()
|
||||
|
||||
const isWin = ref(/windows/i.test(navigator.userAgent))
|
||||
if (isWin.value) {
|
||||
window.localStorage.setItem('osType', 'WIN')
|
||||
} else {
|
||||
window.localStorage.setItem('osType', 'MAC')
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (e) {
|
||||
loadingInstance.value.close()
|
||||
}
|
||||
loadingInstance.value.close()
|
||||
}
|
||||
/* 登出*/
|
||||
const LoginOut = async() => {
|
||||
const res = await jsonInBlacklist()
|
||||
if (res.code === 0) {
|
||||
await ClearStorage()
|
||||
router.push({ name: 'Login', replace: true })
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
/* 清理数据 */
|
||||
const ClearStorage = async() => {
|
||||
token.value = ''
|
||||
sessionStorage.clear()
|
||||
window.localStorage.removeItem('token')
|
||||
cookie.remove('x-token')
|
||||
}
|
||||
/* 设置侧边栏模式*/
|
||||
const changeSideMode = async(data) => {
|
||||
const res = await setSelfInfo({ sideMode: data })
|
||||
if (res.code === 0) {
|
||||
userInfo.value.sideMode = data
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '设置成功'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mode = computed(() => userInfo.value.sideMode)
|
||||
const sideMode = computed(() => {
|
||||
if (userInfo.value.sideMode === 'dark') {
|
||||
return '#191a23'
|
||||
} else if (userInfo.value.sideMode === 'light') {
|
||||
return '#fff'
|
||||
} else {
|
||||
return userInfo.value.sideMode
|
||||
}
|
||||
})
|
||||
const baseColor = computed(() => {
|
||||
if (userInfo.value.sideMode === 'dark') {
|
||||
return '#fff'
|
||||
} else if (userInfo.value.sideMode === 'light') {
|
||||
return '#191a23'
|
||||
} else {
|
||||
return userInfo.value.baseColor
|
||||
}
|
||||
})
|
||||
const activeColor = computed(() => {
|
||||
return 'var(--el-color-primary)'
|
||||
})
|
||||
|
||||
watch(() => token.value, () => {
|
||||
window.localStorage.setItem('token', token.value)
|
||||
})
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
NeedInit,
|
||||
ResetUserInfo,
|
||||
GetUserInfo,
|
||||
LoginIn,
|
||||
LoginOut,
|
||||
changeSideMode,
|
||||
mode,
|
||||
sideMode,
|
||||
setToken,
|
||||
baseColor,
|
||||
activeColor,
|
||||
loadingInstance,
|
||||
ClearStorage
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import service from '@/utils/request'
|
||||
// @Tags System
|
||||
// @Summary 发送测试邮件
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
||||
// @Router /email/emailTest [post]
|
||||
export const emailTest = (data) => {
|
||||
return service({
|
||||
url: '/email/emailTest',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags System
|
||||
// @Summary 发送邮件
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param data body email_response.Email true "发送邮件必须的参数"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
||||
// @Router /email/sendEmail [post]
|
||||
export const sendEmail = (data) => {
|
||||
return service({
|
||||
url: '/email/sendEmail',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar title="需要提前配置email配置文件,为防止不必要的垃圾邮件,在线体验功能不开放此功能体验。" />
|
||||
<div class="gva-form-box">
|
||||
<el-form
|
||||
ref="emailForm"
|
||||
label-position="right"
|
||||
label-width="80px"
|
||||
:model="form"
|
||||
>
|
||||
<el-form-item label="目标邮箱">
|
||||
<el-input v-model="form.to" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮件">
|
||||
<el-input v-model="form.subject" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮件内容">
|
||||
<el-input
|
||||
v-model="form.body"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="sendTestEmail">发送测试邮件</el-button>
|
||||
<el-button @click="sendEmail">发送邮件</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { emailTest } from '@/plugin/email/api/email.js'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Email',
|
||||
})
|
||||
|
||||
const emailForm = ref(null)
|
||||
const form = reactive({
|
||||
to: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
})
|
||||
const sendTestEmail = async() => {
|
||||
const res = await emailTest()
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('发送成功')
|
||||
}
|
||||
}
|
||||
|
||||
const sendEmail = async() => {
|
||||
const res = await emailTest()
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('发送成功,请查收')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
const routes = [{
|
||||
path: '/',
|
||||
redirect: '/login'
|
||||
},
|
||||
{
|
||||
path: '/init',
|
||||
name: 'Init',
|
||||
component: () => import('@/view/init/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('@/view/login/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
meta: {
|
||||
closeTab: true,
|
||||
},
|
||||
component: () => import('@/view/error/index.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': #ffffff,
|
||||
'black': #000000,
|
||||
'primary': (
|
||||
'base': #4d70ff,
|
||||
),
|
||||
'success': (
|
||||
'base': #67c23a,
|
||||
),
|
||||
'warning': (
|
||||
'base': #e6a23c,
|
||||
),
|
||||
'danger': (
|
||||
'base': #f56c6c,
|
||||
),
|
||||
'error': (
|
||||
'base': #f56c6c,
|
||||
),
|
||||
'info': (
|
||||
'base': #909399,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
@import '@/style/main.scss';
|
||||
|
||||
.el-button {
|
||||
font-weight: 400;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
|
||||
.gva-search-box {
|
||||
@apply p-6 pb-0.5 bg-white rounded mb-3;
|
||||
}
|
||||
|
||||
.gva-form-box {
|
||||
@apply p-6 bg-white rounded;
|
||||
}
|
||||
|
||||
.gva-pagination {
|
||||
@apply flex justify-end;
|
||||
.el-pagination__editor {
|
||||
.el-input__inner {
|
||||
@apply h-8;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
@apply rounded text-white;
|
||||
background: var(--el-color-primary);
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-drawer__header{
|
||||
margin-bottom: 0 !important;
|
||||
padding-top: 16px !important;
|
||||
padding-bottom: 16px !important;
|
||||
@apply border-0 border-b border-solid border-gray-200;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@font-face {
|
||||
font-family: 'gvaIcon';
|
||||
src: url('data:font/ttf;charset=utf-8;base64,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AAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
.gvaIcon {
|
||||
font-family: "gvaIcon" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.gvaIcon-arrow-double-left:before {
|
||||
content: "\e665";
|
||||
}
|
||||
|
||||
.gvaIcon-arrow-double-right:before {
|
||||
content: "\e666";
|
||||
}
|
||||
|
||||
.gvaIcon-fullscreen-shrink:before {
|
||||
content: "\e676";
|
||||
}
|
||||
.gvaIcon-customer-service:before {
|
||||
content: "\e66a";
|
||||
}
|
||||
|
||||
.gvaIcon-fullscreen-expand:before {
|
||||
content: "\e675";
|
||||
}
|
||||
|
||||
.gvaIcon-prompt:before {
|
||||
content: "\e67b";
|
||||
}
|
||||
|
||||
.gvaIcon-refresh:before {
|
||||
content: "\e67c";
|
||||
}
|
||||
|
||||
.gvaIcon-search:before {
|
||||
content: "\e67d";
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,701 @@
|
|||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
@import '@/style/iconfont.css';
|
||||
html {
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
/* 1 */
|
||||
height: 0;
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
/* 1 */
|
||||
text-decoration: underline;
|
||||
/* 2 */
|
||||
text-decoration: underline dotted;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
display: table;
|
||||
/* 1 */
|
||||
max-width: 100%;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
white-space: normal;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
HTML,
|
||||
body,
|
||||
div,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
li,
|
||||
dt,
|
||||
dd,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
form,
|
||||
fieldset,
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: none;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
address,
|
||||
caption,
|
||||
cite,
|
||||
code,
|
||||
th,
|
||||
var {
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input::-webkit-input-placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input::-ms-input-placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input::-moz-placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input[type=submit],
|
||||
input[type=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol,
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
// 导航
|
||||
#app {
|
||||
.el-container {
|
||||
@apply relative h-full w-full;
|
||||
}
|
||||
.el-container.mobile.openside {
|
||||
@apply fixed top-0;
|
||||
}
|
||||
.gva-aside {
|
||||
@apply fixed top-0 left-0 z-[1001] overflow-hidden;
|
||||
.el-menu {
|
||||
@apply border-r-0;
|
||||
}
|
||||
}
|
||||
.aside {
|
||||
.el-menu--collapse {
|
||||
>.el-menu-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.el-sub-menu {
|
||||
.el-menu {
|
||||
.is-active {
|
||||
// 关闭三级菜单二级菜单样式
|
||||
ul {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
// 关闭三级菜单二级菜单样式
|
||||
.is-active.is-opened {
|
||||
ul {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hideside {
|
||||
.aside {
|
||||
@apply w-[54px]
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.gva-aside {
|
||||
@apply w-[54px];
|
||||
}
|
||||
}
|
||||
|
||||
.hideside {
|
||||
.main-cont.el-main {
|
||||
@apply ml-[54px];
|
||||
}
|
||||
}
|
||||
.mobile {
|
||||
.main-cont.el-main {
|
||||
@apply ml-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// layout
|
||||
|
||||
.admin-box {
|
||||
@apply min-h-[calc(100vh-200px)] px-3 py-4 mt-28 mb-4 mx-1;
|
||||
.el-table {
|
||||
th {
|
||||
@apply px-0 py-2;
|
||||
.cell {
|
||||
@apply leading-[40px] text-gray-700;
|
||||
}
|
||||
}
|
||||
td {
|
||||
@apply px-0 py-2;
|
||||
.cell {
|
||||
@apply leading-[40px] text-gray-600;
|
||||
}
|
||||
}
|
||||
.is-leaf {
|
||||
@apply border-b border-t-0 border-l-0 border-solid border-gray-50;
|
||||
border-right:var(--el-table-border);
|
||||
background: #F7FBFF !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// table
|
||||
.el-pagination {
|
||||
@apply mt-8;
|
||||
.btn-prev,
|
||||
.btn-next {
|
||||
@apply border border-solid border-gray-300 rounded;
|
||||
}
|
||||
.el-pager {
|
||||
li {
|
||||
@apply border border-solid border-gray-300 rounded text-gray-600 text-sm mx-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-container.layout-cont {
|
||||
.header-cont {
|
||||
@apply px-4 h-16 bg-white;
|
||||
}
|
||||
|
||||
|
||||
.main-cont {
|
||||
@apply h-screen overflow-visible;
|
||||
&.el-main {
|
||||
@apply min-h-full ml-[220px] bg-main p-0 overflow-auto;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@apply h-16 flex items-center p-0 ml-12 text-lg;
|
||||
.el-breadcrumb__item {
|
||||
.el-breadcrumb__inner {
|
||||
@apply text-gray-600;
|
||||
}
|
||||
}
|
||||
.el-breadcrumb__item:nth-last-child(1) {
|
||||
.el-breadcrumb__inner {
|
||||
@apply text-gray-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.router-history {
|
||||
@apply bg-white p-0 border-t border-l-0 border-r-0 border-b-0 border-solid border-gray-100;
|
||||
.el-tabs__header {
|
||||
@apply m-0;
|
||||
.el-tabs__item{
|
||||
@apply border-solid border-r border-t-0 border-gray-100 border-b-0 border-l-0;
|
||||
}
|
||||
.el-tabs__item.is-active {
|
||||
@apply bg-blue-500 bg-opacity-5;
|
||||
}
|
||||
.el-tabs__nav {
|
||||
@apply border-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aside {
|
||||
@apply overflow-auto;
|
||||
}
|
||||
.el-menu-vertical {
|
||||
@apply h-[calc(100vh-60px)];
|
||||
&:not(.el-menu--collapse) {
|
||||
@apply w-[220px];
|
||||
}
|
||||
}
|
||||
.el-menu--collapse {
|
||||
@apply w-[54px];
|
||||
li {
|
||||
.el-tooltip,
|
||||
.el-sub-menu__title {
|
||||
@apply px-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
@apply overflow-hidden
|
||||
}
|
||||
|
||||
.gva-table-box {
|
||||
@apply p-6 bg-white rounded;
|
||||
}
|
||||
|
||||
.gva-btn-list {
|
||||
@apply mb-3 flex gap-3 items-center;
|
||||
}
|
||||
|
||||
|
||||
#nprogress .bar {
|
||||
background: #29d !important;
|
||||
}
|
||||
.gva-customer-icon{
|
||||
@apply w-4 h-4;
|
||||
}
|
||||
|
||||
.el-form--inline {
|
||||
.el-form-item {
|
||||
& > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete {
|
||||
@apply w-52;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
const viewModules = import.meta.glob('../view/**/*.vue')
|
||||
const pluginModules = import.meta.glob('../plugin/**/*.vue')
|
||||
|
||||
export const asyncRouterHandle = (asyncRouter) => {
|
||||
asyncRouter.forEach(item => {
|
||||
if (item.component && typeof item.component === 'string') {
|
||||
if (item.component.split('/')[0] === 'view') {
|
||||
item.component = dynamicImport(viewModules, item.component)
|
||||
} else if (item.component.split('/')[0] === 'plugin') {
|
||||
item.component = dynamicImport(pluginModules, item.component)
|
||||
}
|
||||
}
|
||||
if (item.children) {
|
||||
asyncRouterHandle(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function dynamicImport(
|
||||
dynamicViewsModules,
|
||||
component
|
||||
) {
|
||||
const keys = Object.keys(dynamicViewsModules)
|
||||
const matchKeys = keys.filter((key) => {
|
||||
const k = key.replace('../', '')
|
||||
return k === component
|
||||
})
|
||||
const matchKey = matchKeys[0]
|
||||
|
||||
return dynamicViewsModules[matchKey]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { useRoute } from 'vue-router'
|
||||
import { reactive } from 'vue'
|
||||
export const useBtnAuth = () => {
|
||||
const route = useRoute()
|
||||
return route.meta.btns || reactive({})
|
||||
}
|
||||
|
|
@ -24,12 +24,6 @@ export const formatOnlyDate = (time) => {
|
|||
return ''
|
||||
}
|
||||
}
|
||||
export const formatPriceType = (priceType) => {
|
||||
switch (priceType) {
|
||||
case 1: return '出厂价'
|
||||
default: '未定义'
|
||||
}
|
||||
}
|
||||
|
||||
export const filterDict = (value, options) => {
|
||||
const rowLabel = options && options.filter(item => item.value === value)
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
<div class="gva-top-card-left-title">早安,请开始一天的工作吧</div>
|
||||
<div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
|
||||
<div>
|
||||
<!-- <div class="gva-top-card-left-item">
|
||||
<div class="gva-top-card-left-item">
|
||||
插件仓库:
|
||||
<a
|
||||
style="color:#409EFF"
|
||||
target="view_window"
|
||||
href="https://plugin.gin-vue-admin.com/#/layout/home"
|
||||
>https://plugin.gin-vue-admin.com</a>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import axios from 'axios'
|
|||
import { ref } from 'vue'
|
||||
|
||||
const weatherInfo = ref('今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。')
|
||||
const amapKey = '1f0c8b27920dc41d204800793d629d8e'
|
||||
const amapKey = '8e8baa8a7317586c29ec694895de6e0a'
|
||||
|
||||
export const useWeatherInfo = () => {
|
||||
ip()
|
||||
|
|
@ -16,20 +16,16 @@ export const ip = async () => {
|
|||
return false
|
||||
}
|
||||
const res = await axios.get('https://restapi.amap.com/v3/ip?key=' + amapKey)
|
||||
var cityCode
|
||||
if (res.data.status !== '0') {
|
||||
cityCode = res.data.adcode
|
||||
} else {
|
||||
cityCode = '100000'
|
||||
if (res.data.adcode) {
|
||||
getWeather(res.data.adcode)
|
||||
}
|
||||
getWeather(cityCode)
|
||||
}
|
||||
|
||||
const getWeather = async(code) => {
|
||||
const url = 'https://restapi.amap.com/v3/weather/weatherInfo?key=' + amapKey + '&extensions=base&city=' + code
|
||||
const response = await axios.get(url)
|
||||
const response = await axios.get('https://restapi.amap.com/v3/weather/weatherInfo?key=' + amapKey + '&extensions=base&city=' + code)
|
||||
if (response.data.status === '1') {
|
||||
const s = response.data.lives[0]
|
||||
weatherInfo.value = s.city + ' 天气:' + s.weather + ' 温度:' + s.temperature + '摄氏度 风向:' + s.winddirection + ' 风力:' + s.windpower + '级 空气湿度:' + s.humidity
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue