@@ -0,0 +1,65 @@ | |||||
<script setup name="accessory"> | |||||
import { downFileById } from '@/http/apis/commonApi' | |||||
import { useRouter } from 'vue-router' | |||||
const props = defineProps({ | |||||
fileName: { | |||||
type: String, | |||||
default: '' | |||||
}, | |||||
fileId: { | |||||
type: Number, | |||||
default: undefined | |||||
}, | |||||
isDownLoad: { | |||||
type: Boolean, | |||||
default: true | |||||
} | |||||
}) | |||||
// 下载附件 | |||||
const downloadFile = async () => { | |||||
const res = await downFileById({ fileId: props.fileId }) | |||||
const url = URL.createObjectURL(res) | |||||
const link = document.createElement('a') | |||||
link.style.display = 'none' | |||||
link.href = url | |||||
link.download = props.fileName | |||||
document.body.appendChild(link) | |||||
link.click() | |||||
document.body.removeChild(link) | |||||
window.URL.revokeObjectURL(url) | |||||
} | |||||
// 预览附件 | |||||
const router = useRouter(), | |||||
view = () => { | |||||
const routeUrl = router.resolve({ | |||||
path: '/fileView', | |||||
query: { id: props.fileId } | |||||
}) | |||||
window.open(routeUrl.href, '_blank') | |||||
} | |||||
</script> | |||||
<template> | |||||
<div v-if="fileName&&fileId" class="accessory text-primary flex items-center flex-wrap"> | |||||
<span class="cursor-pointer" @click="view">{{ fileName||'-' }}</span> | |||||
<p v-if="isDownLoad" class="ml-16 flex items-center text-primary btn cursor-pointer" @click="downloadFile"> | |||||
<svg-icon name="down-icon" class="mr-2 text-14" /> | |||||
<span>下载</span> | |||||
</p> | |||||
</div> | |||||
<span v-else>-</span> | |||||
</template> | |||||
<style lang="less"> | |||||
.accessory { | |||||
.btn { | |||||
line-height: 22px; | |||||
height: 22px; | |||||
background: rgba(9, 103, 253, 0.06); | |||||
border-radius: 4px; | |||||
border: 1px solid #0967FD; | |||||
padding: 0 6px; | |||||
font-size: 14px; | |||||
width: 60px; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,64 @@ | |||||
<script setup name='breadcrumb'> | |||||
import { watch, ref } from 'vue' | |||||
import { useRoute, useRouter } from 'vue-router' | |||||
// import store from '@/store' | |||||
const route = useRoute(), | |||||
router = useRouter(), | |||||
routeName = ref(), | |||||
breadcrumbs = ref([]), | |||||
goBack = () => { | |||||
router.go(-1) | |||||
} | |||||
watch( | |||||
route, | |||||
(val) => { | |||||
routeName.value = route.meta.title | |||||
breadcrumbs.value = route.matched.filter((item, index) => index !== 0) | |||||
}, | |||||
{ immediate: true, deep: true } | |||||
) | |||||
</script> | |||||
<template> | |||||
<div class="ml-20 mt-10"> | |||||
<div class="flex items-center"> | |||||
<div | |||||
class="flex items-center mr-5 backsize" | |||||
@click="goBack" | |||||
> | |||||
<el-icon> | |||||
<ArrowLeft /> | |||||
</el-icon>返回 | |||||
</div> | |||||
<el-breadcrumb separator="/"> | |||||
<!-- <el-breadcrumb-item--> | |||||
<!-- v-for="tag in store.appStore.tagNavList"--> | |||||
<!-- :key="tag.path"--> | |||||
<!-- :to="{ name: tag.path, query:tag.query || {}, params: tag.params || {}}"--> | |||||
<!-- >{{ tag.name }}</el-breadcrumb-item>--> | |||||
<el-breadcrumb-item v-for="item in breadcrumbs" :key="item.name"> | |||||
{{ item.meta.title }} | |||||
</el-breadcrumb-item> | |||||
</el-breadcrumb> | |||||
</div> | |||||
<div class="mt-8 font-semibold routeNameSize"><span>{{ routeName }}</span><img class="rightTopLogo" src="../../assets/images/rightTopLogo.png" alt="" /></div> | |||||
</div> | |||||
</template> | |||||
<style lang='less' scoped> | |||||
.backsize{ | |||||
cursor: pointer; | |||||
font-size: 14px; | |||||
} | |||||
.routeNameSize { | |||||
overflow: hidden; | |||||
font-size: 20px; | |||||
.rightTopLogo{ | |||||
position: absolute; | |||||
top: 0; | |||||
right: 0; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,108 @@ | |||||
<script setup name="elAside"> | |||||
import { ref, watch } from 'vue' | |||||
import sidebar from '@/components/sidebar/index.vue' | |||||
import { useRoute } from 'vue-router' | |||||
import store from '@/store' | |||||
import { storeToRefs } from 'pinia' | |||||
const route = useRoute(), | |||||
isCollapse = ref(false), | |||||
active = ref(route.path.split('/')[route.path.split('/').length - 1]), | |||||
{ menuArr } = storeToRefs(store.menuStore), | |||||
emits = defineEmits(['collapseFn']), | |||||
clickFn = () => { | |||||
isCollapse.value = !isCollapse.value | |||||
emits('collapseFn', isCollapse.value) | |||||
}, | |||||
sidebarMenu = ref([]) | |||||
watch( | |||||
route, | |||||
(val) => { | |||||
sidebarMenu.value = | |||||
(menuArr.value.find((i) => i.name === val.meta.topMenu) && | |||||
menuArr.value.find((i) => i.name === val.meta.topMenu).children) || | |||||
[] | |||||
}, | |||||
{ immediate: true, deep: true } | |||||
) | |||||
</script> | |||||
<template> | |||||
<div class="mainBox"> | |||||
<!-- <el-aside class="aside" :width="isCollapse ? '64px' : '200px'"> --> | |||||
<el-scrollbar> | |||||
<sidebar | |||||
:sidebar-menu="sidebarMenu" | |||||
:is-collapse="isCollapse" | |||||
:active="active" | |||||
/> | |||||
<div class="collapse"> | |||||
<el-icon color="rgba(0, 0, 0, 0.45)"> | |||||
<expand | |||||
v-if="isCollapse" | |||||
@click="clickFn" | |||||
/> | |||||
<fold | |||||
v-else | |||||
@click="clickFn" | |||||
/> | |||||
</el-icon> | |||||
</div> | |||||
</el-scrollbar> | |||||
<!-- </el-aside> --> | |||||
</div> | |||||
</template> | |||||
<style lang="less" scoped> | |||||
.mainBox { | |||||
height: 570px; | |||||
margin: 8px 0 0 16px; | |||||
border-radius: 4px; | |||||
position: relative; | |||||
.el-scrollbar__bar { | |||||
&.is-horizontal { | |||||
display: none; | |||||
} | |||||
} | |||||
:deep(.el-menu) { | |||||
border: none; | |||||
background-color: none; | |||||
.el-sub-menu__title { | |||||
color: #666; | |||||
} | |||||
.el-sub-menu{ | |||||
&.is-active{ | |||||
.el-sub-menu__title{ | |||||
color:var(--el-menu-active-color); | |||||
} | |||||
} | |||||
} | |||||
.el-menu-item { | |||||
color: #666; | |||||
border-right: 2px solid transparent; | |||||
&.is-active { | |||||
color: var(--el-menu-active-color); | |||||
border-right: 2px solid var(--el-menu-active-color); | |||||
background-color: rgba(27, 113, 227, 0.15); | |||||
} | |||||
} | |||||
} | |||||
.el-menu-vertical-demo:not(.el-menu--collapse) { | |||||
width: 200px; | |||||
height: 570px; | |||||
overflow-y: auto; | |||||
} | |||||
.collapse { | |||||
position: absolute; | |||||
bottom: 0; | |||||
display: flex; | |||||
align-items: center; | |||||
height: 40px; | |||||
width: 100%; | |||||
padding: 0 18px; | |||||
border-top: 1px solid rgba(0, 0, 0, 0.09); | |||||
} | |||||
.main { | |||||
padding: 16px; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,92 @@ | |||||
<script setup name="roleManage"> | |||||
import { nextTick, onMounted, ref } from 'vue' | |||||
import { districtList } from '@/http/apis/commonApi' | |||||
import { storeToRefs } from 'pinia' | |||||
import store from '@/store' | |||||
const userInfo = storeToRefs(store.userStore).userInfo | |||||
const props = defineProps({ | |||||
defaultExpandAll: { | |||||
type: Boolean, | |||||
default: true | |||||
}, | |||||
defaultData: { | |||||
type: Object, | |||||
default: undefined | |||||
}, | |||||
params: { | |||||
type: Object, | |||||
default: () => { | |||||
return { | |||||
regionCode: 330500, | |||||
regionLevel: 2 | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
const treeData = ref([]), | |||||
emits = defineEmits(['getTree']), | |||||
customNodeClass = { | |||||
label: 'name' | |||||
}, | |||||
nodeClick = (data) => { | |||||
console.log(data) | |||||
emits('getTree', { ...data }) | |||||
}, | |||||
treeRef = ref() | |||||
onMounted(async () => { | |||||
const res = await districtList(props.params) | |||||
treeData.value = [res.data] | |||||
// 默认选中某个节点 | |||||
if (props.defaultData) { | |||||
nextTick(() => { | |||||
treeRef.value && treeRef.value.setCurrentKey(props.defaultData.regionCode, true) | |||||
emits('getTree', { ...treeRef.value.getCurrentNode() }) | |||||
}) | |||||
} else { | |||||
nextTick(() => { | |||||
treeRef.value && treeRef.value.setCurrentKey(userInfo.value.regionCode, true) | |||||
emits('getTree', { ...treeRef.value.getCurrentNode() }) | |||||
}) | |||||
} | |||||
}) | |||||
</script> | |||||
<template> | |||||
<div class="userManageMenu"> | |||||
<div class="treeTitle flexItem"> | |||||
<svg-icon name="location" svg-class="svgIcon" /> <span | |||||
style="font-weight: 600" | |||||
>区划选择</span> | |||||
</div> | |||||
<el-tree | |||||
ref="treeRef" | |||||
class="tree" | |||||
:data="treeData" | |||||
node-key="regionCode" | |||||
:highlight-current="true" | |||||
:props="customNodeClass" | |||||
:default-expand-all="defaultExpandAll" | |||||
:current-node-key="defaultData&&defaultData.code||undefined" | |||||
:expand-on-click-node="false" | |||||
@node-click="nodeClick" | |||||
/> | |||||
</div> | |||||
</template> | |||||
<style lang="less" scoped> | |||||
.userManageMenu { | |||||
background: #fff; | |||||
padding: 16px 0px 0px; | |||||
box-shadow: 0px 3px 6px 0px rgba(62,99,170,0.06); | |||||
height:auto; | |||||
:deep(.el-menu){ | |||||
border-right: none; | |||||
} | |||||
.tree { | |||||
padding: 16px 16px; | |||||
} | |||||
.treeTitle { | |||||
padding: 0px 16px 16px; | |||||
border-bottom: 1px solid #d8dadf; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,27 @@ | |||||
<script setup name="fileView"> | |||||
// import { renderAsync } from 'docx-preview' | |||||
import { downloadToPdfStreamFileUrl, downloadFileUrl } from '@/utils/uploadAction.js' | |||||
import { onMounted, ref } from 'vue' | |||||
import { useRoute } from 'vue-router' | |||||
import { downFileById } from '@/http/apis/commonApi' | |||||
const route = useRoute(), | |||||
fileRef = ref() | |||||
onMounted(async () => { | |||||
if (import.meta.env.MODE === 'production') { | |||||
window.location.href = await downloadToPdfStreamFileUrl(route.query.id) | |||||
} else { | |||||
const res = await downFileById({ fileId: route.query.id }) | |||||
if ( | |||||
res.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || res.type === 'application/msword' | |||||
) { | |||||
window.location.href = await downloadToPdfStreamFileUrl(route.query.id) | |||||
} else { | |||||
window.location.href = await downloadFileUrl(route.query.id) | |||||
} | |||||
} | |||||
}) | |||||
</script> | |||||
<template> | |||||
<div ref="fileRef"></div> | |||||
</template> |
@@ -0,0 +1,267 @@ | |||||
<script setup name="indexTree"> | |||||
import { nextTick, ref, watch } from 'vue' | |||||
import { tagFuzzyMatch } from '@/http/apis/performanceEvaluation/indicatorTemplate' | |||||
const props = defineProps({ | |||||
visible: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
showCheckbox: { | |||||
type: Boolean, | |||||
default: false | |||||
}, // 是否多选 | |||||
defaultData: Array, // 用于回显数据, | |||||
// 获取数据的参数 | |||||
params: { | |||||
type: Object, | |||||
default: () => {} | |||||
}, | |||||
title: { | |||||
type: String, | |||||
default: '一级指标' | |||||
}, | |||||
// 展开节点 | |||||
defaultExpandedkeys: { | |||||
type: Array, | |||||
default: () => { | |||||
return [] | |||||
} | |||||
}, | |||||
// 数据结构 | |||||
defaultProps: { | |||||
type: Object, | |||||
default: () => { | |||||
return { | |||||
children: 'projectIndexes', | |||||
label: 'name', | |||||
value: 'indexId', | |||||
isLeaf: 'isLeaf' | |||||
} | |||||
} | |||||
} | |||||
}), | |||||
emits = defineEmits(['close', 'getSelectData']), // close 关闭 getSelectIndex:获取数据 | |||||
handleClose = () => { | |||||
emits('close') | |||||
inputText.value = '' | |||||
}, | |||||
submit = () => { | |||||
emits('getSelectData', selectData.value) | |||||
handleClose() | |||||
} | |||||
const filterText = ref('') | |||||
const inputText = ref('') | |||||
const treeRef = ref() | |||||
watch( | |||||
() => props.visible, | |||||
val => { | |||||
if (val) { | |||||
nextTick(async () => { | |||||
if (props.defaultData && props.defaultData.length) { | |||||
selectData.value = [...props.defaultData] | |||||
} else { | |||||
filterText.value = undefined | |||||
selectData.value = [] | |||||
} | |||||
await getTreeData() | |||||
}) | |||||
} | |||||
} | |||||
) | |||||
// 搜索 | |||||
// watch(filterText, async val => { | |||||
// await getTreeData() | |||||
// nextTick(() => { | |||||
// treeRef.value && treeRef.value.filter(val) | |||||
// }) | |||||
// }) | |||||
const search = async () => { | |||||
await getTreeData(filterText.value) | |||||
nextTick(() => { | |||||
treeRef.value && treeRef.value.filter(filterText.value) | |||||
}) | |||||
} | |||||
const expandAll = ref(false), | |||||
filterNode = (value, data) => { | |||||
if (!value) return true | |||||
return true | |||||
} | |||||
// 获取选择的单位 | |||||
const selectData = ref([]), | |||||
checkChange = (data, { checkedKeys, checkedNodes }) => { | |||||
if (props.showCheckbox) { | |||||
if (checkedKeys.includes(data[props.defaultProps.value])) { | |||||
selectData.value.push(data) | |||||
} else { | |||||
selectData.value = selectData.value.filter(i => i[props.defaultProps.value] !== data[props.defaultProps.value]) | |||||
} | |||||
} else { | |||||
selectData.value = [data] | |||||
} | |||||
}, | |||||
delData = (index, key) => { | |||||
selectData.value.splice(index, 1) | |||||
nextTick(() => { | |||||
if (props.showCheckbox) { | |||||
treeRef.value && treeRef.value.setChecked(key, false) | |||||
} else { | |||||
treeRef.value && treeRef.value.setCurrentKey(null) | |||||
} | |||||
}) | |||||
}, | |||||
treeData = ref(), | |||||
getTreeData = async (filterText) => { | |||||
treeData.value = [] | |||||
const res = await tagFuzzyMatch({ ...props.params, name: filterText || undefined }) | |||||
const nodeData = changeData(res.data.filter(i => i.projectIndexes?.length)) | |||||
treeData.value = nodeData | |||||
nextTick(() => { | |||||
if (props.showCheckbox) { | |||||
if (selectData.value && selectData.value.length) { | |||||
treeRef.value && treeRef.value.setCheckedNodes(selectData.value) | |||||
} else { | |||||
treeRef.value && treeRef.value.setCheckedKeys([]) | |||||
} | |||||
} else { | |||||
if (selectData.value && selectData.value.length) { | |||||
treeRef.value && treeRef.value.setCurrentKey(selectData.value[0].indexId) | |||||
} else { | |||||
treeRef.value && treeRef.value.setCurrentKey(null) | |||||
} | |||||
} | |||||
}) | |||||
}, | |||||
changeData = (data) => { | |||||
const arr = [] | |||||
data.forEach(i => { | |||||
arr.push({ | |||||
...i, | |||||
name: i.name || i.indexName, | |||||
disabled: !i.indexLevel, | |||||
indexId: i.id, | |||||
projectIndexes: i.projectIndexes?.length && changeData(i.projectIndexes) || undefined | |||||
}) | |||||
}) | |||||
return arr | |||||
} | |||||
</script> | |||||
<template> | |||||
<el-dialog | |||||
:model-value="props.visible" | |||||
:title="`选择${title}`" | |||||
width="80%" | |||||
destroy-on-close | |||||
:before-close="handleClose" | |||||
> | |||||
<div class="unitBox"> | |||||
<div class="left"> | |||||
<div class="title">列表</div> | |||||
<div class="content"> | |||||
<el-input | |||||
v-model="filterText" | |||||
class="mb-16" | |||||
placeholder="按名称搜索" | |||||
> | |||||
<template #append> | |||||
<el-button icon="Search" @click="search" /> | |||||
</template> | |||||
</el-input> | |||||
<el-scrollbar> | |||||
<el-tree | |||||
ref="treeRef" | |||||
:data="treeData" | |||||
class="filter-tree" | |||||
:node-key="defaultProps.value" | |||||
:default-checked-keys=" | |||||
(props.defaultData && | |||||
props.defaultData.length && | |||||
props.defaultData | |||||
.map(item => item[defaultProps.value])) || | |||||
[] | |||||
" | |||||
:current-node-key=" | |||||
(props.defaultData && | |||||
props.defaultData.length && | |||||
props.defaultData[0][defaultProps.value]) || | |||||
undefined | |||||
" | |||||
:expand-on-click-node="true" | |||||
:props="defaultProps" | |||||
:show-checkbox="showCheckbox" | |||||
:check-on-click-node="!showCheckbox" | |||||
check-strictly | |||||
:highlight-current="!showCheckbox" | |||||
:filter-node-method="filterNode" | |||||
:default-expand-all="expandAll" | |||||
:default-expanded-keys="defaultExpandedkeys" | |||||
@check="checkChange" | |||||
/> | |||||
</el-scrollbar> | |||||
</div> | |||||
</div> | |||||
<div class="right"> | |||||
<div class="title">已选{{ title }}</div> | |||||
<div class="content"> | |||||
<el-tag | |||||
v-for="(item, index) in selectData" | |||||
:key="item.key" | |||||
closable | |||||
@close="delData(index, item.id, item.type)" | |||||
> | |||||
{{ item.name }} | |||||
</el-tag> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<template #footer> | |||||
<span class="dialog-footer"> | |||||
<el-button type="primary" @click="submit">提交</el-button> | |||||
<el-button @click="handleClose">关闭</el-button> | |||||
</span> | |||||
</template> | |||||
</el-dialog> | |||||
</template> | |||||
<style lang="less"> | |||||
.unitBox { | |||||
display: flex; | |||||
height: 500px; | |||||
overflow: hidden; | |||||
.el-tag { | |||||
margin: 0 4px 8px; | |||||
} | |||||
.title { | |||||
padding: 12px; | |||||
border-bottom: 1px solid #ccc; | |||||
margin-bottom: 12px; | |||||
} | |||||
.left, | |||||
.right { | |||||
flex: 1; | |||||
border-bottom: 1px solid #ccc; | |||||
display: flex; | |||||
flex-flow: column; | |||||
overflow: hidden; | |||||
.content { | |||||
flex: 1; | |||||
padding: 0 12px; | |||||
overflow: scroll; | |||||
} | |||||
} | |||||
.left { | |||||
border-top: 1px solid #ccc; | |||||
border-left: 1px solid #ccc; | |||||
border-right: 1px solid #ccc; | |||||
.inputText { | |||||
padding: 12px; | |||||
} | |||||
} | |||||
.right { | |||||
border-top: 1px solid #ccc; | |||||
border-right: 1px solid #ccc; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,9 @@ | |||||
<script setup> | |||||
</script> | |||||
<template> | |||||
<div>11</div> | |||||
</template> | |||||
<style lang='less' scoped> | |||||
</style> |
@@ -0,0 +1,337 @@ | |||||
<script setup name="layout"> | |||||
import { getCurrentInstance, onMounted, ref, nextTick } from 'vue' | |||||
import { infoReceived } from '@/http/apis/home' | |||||
import store from '@/store' | |||||
import { storeToRefs } from 'pinia' | |||||
import { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router' | |||||
import Cookie from 'js-cookie' | |||||
import { logout } from '@/http/apis/auth' | |||||
import moment from 'moment' | |||||
const { proxy } = getCurrentInstance(), | |||||
{ projectCount, infoList } = storeToRefs(store.countStore) || 0, | |||||
userInfo = storeToRefs(store.userStore).userInfo || {}, | |||||
router = useRouter(), | |||||
route = useRoute(), | |||||
{ menuArr } = storeToRefs(store.menuStore), | |||||
sidebarMenu = ref([]), | |||||
toRoute = name => { | |||||
sidebarMenu.value = menuArr.value.find(i => i.name === name).children | |||||
if (name === 'cockpits') { | |||||
const url = router.resolve({ path: '/cockpit' }) | |||||
window.open(url.href, '_blank') | |||||
} else { router.push({ name }) } | |||||
}, | |||||
// 退出登录 | |||||
loginOut = async () => { | |||||
await logout() | |||||
Cookie.remove('token') | |||||
localStorage.clear() | |||||
router.push('/login') | |||||
window.location.reload() | |||||
}, | |||||
// 申请权限 | |||||
apply = () => { | |||||
proxy.$messageBox.alert('请联系管理员申请权限(13588274036)', '申请权限', { | |||||
showConfirmButton: false | |||||
}) | |||||
}, | |||||
toNewsDetail = async ({ id, type, instanceId, projectId, meetingId }) => { | |||||
if (type === 'PROJECT_REVIEW') { | |||||
await infoReceived({ id }) | |||||
await store.countStore.setCountStore() | |||||
router.push({ name: 'handleDuringExamine', query: { instanceId, projectId }}) | |||||
} else if (type === 'PROJECT_REVIEW_PASS' || type === 'PROJECT_REVIEW_REJECT') { | |||||
await infoReceived({ id }) | |||||
await store.countStore.setCountStore() | |||||
router.push({ name: 'projectStore' }) | |||||
} else if (type === 'PROJECT_REVIEW_BACK') { | |||||
await infoReceived({ id }) | |||||
await store.countStore.setCountStore() | |||||
router.push({ name: 'handleAfterGiveBack', query: { instanceId, id: projectId }}) | |||||
} else if (type === 'EXPERT_REVIEW') { | |||||
router.push({ name: 'expertReview' }) | |||||
} else if (type === 'REVIEW_MEETING') { | |||||
router.push({ name: 'meetingDetail', query: { id: meetingId }}) | |||||
} | |||||
}, | |||||
// 菜单栏判断 | |||||
currentMenu = ref(), | |||||
topMenuRef = ref(), | |||||
hidMenuActive = ref(false), | |||||
hidMenus = ref(), | |||||
hidMenuRef = ref(), | |||||
hidMenuHeight = ref(), | |||||
hidNum = ref(), | |||||
widthDifference = ref(0), | |||||
// 判断隐藏菜单 | |||||
setHidMenu = async () => { | |||||
currentMenu.value = JSON.parse(JSON.stringify(menuArr.value.filter((i) => !i.meta.hidden && i.meta.menuType === 'MENU'))) | |||||
currentMenu.value.forEach((i) => { | |||||
i.meta.hidden = false | |||||
}) | |||||
await nextTick() | |||||
widthDifference.value = (currentMenu.value.length * 120) - topMenuRef.value.offsetWidth | |||||
hidNum.value = Math.floor((((currentMenu.value.length * 120) - topMenuRef.value.offsetWidth) / 120) + 2) | |||||
hidMenus.value = currentMenu.value.slice(-hidNum.value) | |||||
for (let i = 1; i <= hidNum.value; i++) { | |||||
currentMenu.value[currentMenu.value.length - i].meta.hidden = true | |||||
} | |||||
await nextTick() | |||||
hidMenuHeight.value = hidMenuRef.value?.offsetHeight | |||||
}, | |||||
moreMenuClick = () => { | |||||
hidMenuActive.value = !hidMenuActive.value | |||||
}, | |||||
openUrl = (url) => { | |||||
window.open(url, '_blank') | |||||
}, | |||||
mode = import.meta.env.MODE | |||||
onBeforeRouteUpdate(() => { | |||||
setHidMenu() | |||||
}) | |||||
onMounted(() => { | |||||
setHidMenu() | |||||
window.addEventListener('resize', () => { | |||||
setHidMenu() | |||||
}) | |||||
document.addEventListener('click', () => { | |||||
hidMenuActive.value = false | |||||
}) | |||||
store.countStore.setCountStore() | |||||
}) | |||||
</script> | |||||
<template> | |||||
<el-container class="h-screen overflow-hidden"> | |||||
<el-header class="header flex items-center justify-between"> | |||||
<div class=" flex items-center flex-1"> | |||||
<p class="title text-black flex-shrink-0">湖州市信息化项目管理系统</p> | |||||
<ul ref="topMenuRef" class="flex -mx-20 header-menu flex-1"> | |||||
<template | |||||
v-for="item in currentMenu" | |||||
:key="item.name" | |||||
> | |||||
<li | |||||
v-if="!item.meta.hidden" | |||||
class="px-12 text-16 cursor-pointer flex items-center" | |||||
@click="route.meta.topMenu !== item.name ? toRoute(item.name) : null" | |||||
> | |||||
<span | |||||
class="flex items-center" | |||||
:class="[ | |||||
route.meta.topMenu === item.name | |||||
? 'active-menu' | |||||
: 'border-transparent' | |||||
]" | |||||
> | |||||
{{ item.meta.title }} | |||||
</span> | |||||
</li> | |||||
</template> | |||||
<li | |||||
v-if="hidNum>0" | |||||
class="px-20 text-16 cursor-pointer whitespace-nowrap relative flex items-center" | |||||
@click.stop="moreMenuClick" | |||||
> | |||||
<span | |||||
class="flex items-center" | |||||
:class="[ | |||||
hidMenus.some((i)=>i.name === route.meta.topMenu) | |||||
? 'active-menu' | |||||
: 'border-transparent' | |||||
]" | |||||
> | |||||
<el-icon class="mr-8" :size="16"><More /></el-icon> | |||||
更多菜单 | |||||
</span> | |||||
<div | |||||
class="absolute hidMenus w-full top-full z-10 text-center overflow-hidden" | |||||
:style="{height:hidMenuActive?`${hidMenuHeight}px`:0,opacity:hidMenuActive?`100%`:0}" | |||||
> | |||||
<div ref="hidMenuRef" class="py-10"> | |||||
<p | |||||
v-for="i in hidMenus" | |||||
:key="i.name" | |||||
class="px-20 hidMenu" | |||||
@click="toRoute(i.name)" | |||||
> | |||||
<span | |||||
class="flex items-center" | |||||
:class="[ | |||||
route.meta.topMenu === i.name | |||||
? 'active-menu' | |||||
: 'border-transparent' | |||||
]" | |||||
> | |||||
{{ i?.meta?.title }} | |||||
</span> | |||||
</p> | |||||
</div> | |||||
</div> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
<div class="flex items-center flex-shrink-0"> | |||||
<span class="text-14 cursor-pointer mr-8 pointTit" @click="openUrl(`https://xmglhejia.dsj.lishui.gov.cn/sys/thirdLogin/zzd/login?employeeCode=${userInfo.employeeCode}&employeeName==${userInfo.realName}`)">核价组件</span> | |||||
<span class="text-14 cursor-pointer mr-8 pointTit" @click="openUrl(mode==='production'?`https://xmglchacho.dsj.lishui.gov.cn:4430/login?code=${userInfo.employeeCode}`:`http://xmcc.ningdatech.com/login?code=${userInfo.employeeCode}`)">查重组件</span> | |||||
<el-tooltip content="操作手册" placement="bottom" effect="light"> | |||||
<svg-icon name="czsc" class="cursor-pointer text-16" /> | |||||
</el-tooltip> | |||||
<el-badge | |||||
:value="projectCount" | |||||
:hidden="!projectCount" | |||||
class="h-16 ml-12 mr-28" | |||||
> | |||||
<el-dropdown trigger="hover"> | |||||
<span class="el-dropdown-link"> | |||||
<svg-icon name="bell" class="cursor-pointer text-16" /> | |||||
</span> | |||||
<template #dropdown> | |||||
<el-dropdown-menu style="width:305px" class="newsDropDownMenu"> | |||||
<p class="text-center font-semibold border-b border-gray-300 text-14 py-9">消息通知</p> | |||||
<el-dropdown-item | |||||
v-for="item in infoList?.sort((a, b) => { | |||||
return moment(b.createTime).valueOf() - moment(a.createTime).valueOf() | |||||
}).filter((item, index) => index < 5)" | |||||
:key="item.id" | |||||
@click="toNewsDetail(item)" | |||||
> | |||||
<div class="w-11/12 items-center text-14"><div class="truncate" style="color: rgba(0, 0, 0, 0.65)">{{ item.content }}</div><div style="color: rgba(0, 0, 0, 0.25)">{{ item.createTime }}</div></div> | |||||
</el-dropdown-item> | |||||
<el-dropdown-item class="justify-center font-semibold border-t border-gray-300" style="color:rgba(0, 87, 255, 1)" @click="router.push({name:'infoCenter'})">查看更多<svg-icon name="forward" svg-class="message-icon mr-8" /></el-dropdown-item> | |||||
</el-dropdown-menu> | |||||
</template> | |||||
</el-dropdown> | |||||
</el-badge> | |||||
<el-popover | |||||
placement="bottom" | |||||
:width="200" | |||||
trigger="click" | |||||
popper-class="loginOutPop" | |||||
> | |||||
<template #reference> | |||||
<a class="flex items-center text-[rgba(0,0,0,0.85)] text-14"> | |||||
{{ userInfo.realName }} | |||||
<el-icon class="el-icon--right"><arrow-down /></el-icon> | |||||
</a> | |||||
</template> | |||||
<div> | |||||
<div class="flex p-8"> | |||||
<span class="flex-shrink-0">当前角色:</span> | |||||
<div> | |||||
<p v-for="(item,index) in userInfo.userRoleInfoList" :key="index">{{ item.name }}</p> | |||||
</div> | |||||
</div> | |||||
<div class="flex p-8"> | |||||
<span class="flex-shrink-0">当前区域:</span> | |||||
<div> | |||||
{{ userInfo.regionName }} | |||||
</div> | |||||
</div> | |||||
<div class="flex p-8"> | |||||
<span class="flex-shrink-0">当前单位:</span> | |||||
<div> | |||||
{{ userInfo.empPosUnitName }} | |||||
</div> | |||||
</div> | |||||
<el-divider style="margin: 8px 0 0 0" /> | |||||
<p v-if="userInfo.regionCode==='330000'&&userInfo.userRoleInfoList?.length<2&&userInfo.userRoleInfoList?.[0].code==='VISITOR'" class="cursor-pointer p-8 login-out" @click="apply"><el-icon><SwitchButton /></el-icon>申请权限</p> | |||||
<p class="cursor-pointer p-8 login-out flex items-center" @click="loginOut"> | |||||
<el-icon class="mr-4"><SwitchButton /></el-icon> | |||||
<span>退出登录</span> | |||||
</p> | |||||
</div> | |||||
</el-popover> | |||||
</div> | |||||
</el-header> | |||||
<router-view v-slot="{ Component }"> | |||||
<component :is="Component" /> | |||||
</router-view> | |||||
</el-container> | |||||
</template> | |||||
<style lang="less" scoped> | |||||
.header { | |||||
&.el-header { | |||||
height: auto; | |||||
background: linear-gradient(180deg, rgba(0, 127, 237, 0.40) 12.79%, rgba(0, 127, 237, 0.13) 64.55%, rgba(0, 127, 237, 0.00) 100%), #FFF; | |||||
padding: 25px 24px 47px; | |||||
border: 1px solid #CBE4FA; | |||||
.title{ | |||||
color: rgba(0, 0, 0, 0.85); | |||||
font-family: PangMenZhengDao; | |||||
font-size: 24px; | |||||
font-style: normal; | |||||
font-weight: 400; | |||||
line-height: 24px; | |||||
margin-right: 24px; | |||||
} | |||||
.message-icon { | |||||
width: 16px; | |||||
height: 16px; | |||||
} | |||||
.title-icon { | |||||
width: 300px; | |||||
height: 32px; | |||||
} | |||||
.right { | |||||
height: 32px; | |||||
background-color: rgba(27, 113, 227, 0.4); | |||||
} | |||||
} | |||||
.header-menu { | |||||
color: rgba(0, 0, 0, 0.85);; | |||||
font-weight: 500; | |||||
padding-top: 4px; | |||||
li:hover { | |||||
//background: linear-gradient(90deg, #20DEEF 0%, #007FED 100%); | |||||
color:#007FED; | |||||
padding: 2px 12px; | |||||
//background-color: rgba(0, 0, 0, 0.1); | |||||
} | |||||
.hidMenus { | |||||
left: 0; | |||||
border-radius: 4px; | |||||
margin-top: 2px; | |||||
color:rgba(0, 0, 0, 0.85); | |||||
background: linear-gradient(180deg, rgba(0, 127, 237, 0.4) 12.79%, rgba(0, 127, 237, 0.13) 64.55%, rgba(0, 127, 237, 0) 100%), #FFF; | |||||
border: 1px solid #CBE4FA; | |||||
//background: linear-gradient(90deg, #20DEEF 0%, #007FED 100%); | |||||
//background: linear-gradient(270deg, #1b71e3 0%, #27b0e9 100%); | |||||
transition: all 0.2s; | |||||
z-index: 999; | |||||
width: auto; | |||||
&.h-0 { | |||||
height: 0; | |||||
} | |||||
&.h-100 { | |||||
height: 200px; | |||||
} | |||||
.hidMenu { | |||||
padding-top: 4px; | |||||
padding-bottom: 4px; | |||||
&:hover { | |||||
color:#007FED; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
#infoDropdown { | |||||
width: 305px; | |||||
} | |||||
.active-menu{ | |||||
border-radius: 13px; | |||||
background: linear-gradient(90deg, #20DEEF 0%, #007FED 100%); | |||||
color:#ffffff; | |||||
padding: 2px 12px; | |||||
} | |||||
} | |||||
.newsDropDownMenu{ | |||||
.el-dropdown-menu__item:not(.is-disabled):focus{ | |||||
background: rgba(0, 87, 255, 0.08); | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,32 @@ | |||||
<script setup name="noData"> | |||||
</script> | |||||
<template> | |||||
<div class="mainBox"> | |||||
<div class="noDataSvg"> | |||||
<svg-icon name="noData" svg-class="noDataClass" /> | |||||
</div> | |||||
<div>暂无数据</div> | |||||
</div> | |||||
</template> | |||||
<style scoped lang="less"> | |||||
div { | |||||
color: #000000; | |||||
opacity: 0.45; | |||||
text-align: center; | |||||
font-size: 14px; | |||||
} | |||||
.noDataClass { | |||||
width: 50px; | |||||
height: 50px; | |||||
} | |||||
.mainBox { | |||||
padding: 40px 0px; | |||||
} | |||||
.noDataSvg { | |||||
display: flex; | |||||
justify-content: center; | |||||
} | |||||
</style> | |||||
@@ -0,0 +1,324 @@ | |||||
<script setup name="orgTree"> | |||||
import { getOrgTree } from '@/http/apis/systemManage/unitManage' | |||||
import { getOrgLine } from '@/http/apis/commonApi' | |||||
import { nextTick, ref, watch } from 'vue' | |||||
const props = defineProps({ | |||||
visible: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
showCheckbox: { | |||||
type: Boolean, | |||||
default: false | |||||
}, // 是否多选 | |||||
defaultData: Array, // 用于回显数据, | |||||
// 获取数据的参数 | |||||
params: { | |||||
type: Object, | |||||
default: () => {} | |||||
}, | |||||
// 是否选择到人 | |||||
isPerson: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
title: { | |||||
type: String, | |||||
default: '单位' | |||||
}, | |||||
// 展开节点 | |||||
defaultExpandedkeys: { | |||||
type: Array, | |||||
default: () => { | |||||
return [] | |||||
} | |||||
}, | |||||
// 数据结构 | |||||
defaultProps: { | |||||
type: Object, | |||||
default: () => { | |||||
return { | |||||
children: 'children', | |||||
label: 'title', | |||||
value: 'key', | |||||
isLeaf: 'isLeaf' | |||||
} | |||||
} | |||||
}, | |||||
type: { | |||||
type: String, | |||||
default: 'UNIT' // LINE:条线 UNIT:单位 | |||||
} | |||||
}), | |||||
emits = defineEmits(['close', 'getSelectUnit']), // close 关闭 getSelectUnit:获取数据 | |||||
handleClose = () => { | |||||
emits('close') | |||||
inputText.value = '' | |||||
}, | |||||
submit = () => { | |||||
emits('getSelectUnit', selectData.value) | |||||
handleClose() | |||||
} | |||||
const filterText = ref('') | |||||
const inputText = ref('') | |||||
const treeRef = ref() | |||||
const loadNode = async (node, resolve) => { | |||||
if (node.level) { | |||||
let res | |||||
if (props.type === 'LINE') { | |||||
res = await getOrgLine({ | |||||
...props.params, | |||||
parentCode: node.data.businessStripCode | |||||
}) | |||||
if (res.data) { | |||||
let nodeData | |||||
if (props.isPerson && props.showCheckbox) { | |||||
nodeData = res.data.map(i => ({ ...i, disabled: i.type === 'ORGANIZATION', isLeaf: i.type === 'MEMBER' })) | |||||
} else { | |||||
nodeData = res.data.map(i => ({ ...i, isLeaf: i.type === 'MEMBER' })) | |||||
} | |||||
resolve(nodeData) | |||||
} else { | |||||
resolve([]) | |||||
} | |||||
} else { | |||||
let nodeData | |||||
if (node?.data?.children?.length) { | |||||
if (props.isPerson && props.showCheckbox) { | |||||
nodeData = node?.data?.children.map(i => ({ ...i, disabled: i.type === 'ORGANIZATION', isLeaf: i.type === 'MEMBER' })) | |||||
} else { | |||||
nodeData = node?.data?.children.map(i => ({ ...i, isLeaf: i.type === 'MEMBER' })) | |||||
} | |||||
resolve(nodeData) | |||||
} else { | |||||
res = await getOrgTree({ | |||||
...props.params, | |||||
organizationCode: node.data.key | |||||
}) | |||||
if (res.data?.[0]?.children) { | |||||
if (props.isPerson && props.showCheckbox) { | |||||
nodeData = res.data?.[0]?.children.map(i => ({ ...i, disabled: i.type === 'ORGANIZATION', isLeaf: i.type === 'MEMBER' })) | |||||
} else { | |||||
nodeData = res.data?.[0]?.children.map(i => ({ ...i, isLeaf: i.type === 'MEMBER' })) | |||||
} | |||||
resolve(nodeData) | |||||
} else { | |||||
resolve([]) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
nextTick(() => { | |||||
if (props.showCheckbox) { | |||||
if (selectData.value && selectData.value.length) { | |||||
treeRef.value && treeRef.value.setCheckedNodes(selectData.value) | |||||
} else { | |||||
treeRef.value && treeRef.value.setCheckedKeys([]) | |||||
} | |||||
} else { | |||||
if (selectData.value && selectData.value.length) { | |||||
treeRef.value && treeRef.value.setCurrentKey(selectData.value[0].key) | |||||
} else { | |||||
treeRef.value && treeRef.value.setCurrentKey(null) | |||||
} | |||||
} | |||||
}) | |||||
} | |||||
watch( | |||||
() => props.visible, | |||||
val => { | |||||
if (val) { | |||||
nextTick(async () => { | |||||
await getTreeData() | |||||
if (props.defaultData && props.defaultData.length) { | |||||
selectData.value = [...props.defaultData] | |||||
} else { | |||||
filterText.value = undefined | |||||
selectData.value = [] | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
) | |||||
// 搜索 | |||||
// watch(filterText, async val => { | |||||
// await getTreeData() | |||||
// nextTick(() => { | |||||
// treeRef.value && treeRef.value.filter(val) | |||||
// }) | |||||
// }) | |||||
const search = async () => { | |||||
await getTreeData(filterText.value) | |||||
nextTick(() => { | |||||
treeRef.value && treeRef.value.filter(filterText.value) | |||||
}) | |||||
} | |||||
const expandAll = ref(false), | |||||
filterNode = (value, data) => { | |||||
if (!value) return true | |||||
return true | |||||
} | |||||
// 获取选择的单位 | |||||
const selectData = ref([]), | |||||
checkChange = (data, { checkedKeys, checkedNodes }) => { | |||||
if (props.showCheckbox) { | |||||
if (checkedKeys.includes(data[props.defaultProps.value])) { | |||||
selectData.value.push(data) | |||||
} else { | |||||
selectData.value = selectData.value.filter(i => i[props.defaultProps.value] !== data[props.defaultProps.value]) | |||||
} | |||||
} else { | |||||
selectData.value = [data] | |||||
} | |||||
}, | |||||
delData = (index, key, type) => { | |||||
selectData.value.splice(index, 1) | |||||
nextTick(() => { | |||||
if (props.showCheckbox) { | |||||
treeRef.value && treeRef.value.setChecked(key, false) | |||||
} else { | |||||
treeRef.value && treeRef.value.setCurrentKey(null) | |||||
} | |||||
}) | |||||
}, | |||||
treeData = ref(), | |||||
getTreeData = async (filterText) => { | |||||
treeData.value = [] | |||||
let res | |||||
if (props.type === 'LINE') { | |||||
res = await getOrgLine({}) | |||||
} else { | |||||
res = await getOrgTree({ ...props.params, organizationName: !props.isPerson && filterText || undefined, employeeName: props.isPerson && filterText || undefined, parentCode: undefined }) | |||||
} | |||||
let nodeData | |||||
if (props.isPerson && props.showCheckbox) { | |||||
nodeData = res.data.map(i => ({ ...i, disabled: true })) | |||||
} else { | |||||
nodeData = res.data.map(i => ({ ...i })) | |||||
} | |||||
treeData.value = nodeData | |||||
} | |||||
</script> | |||||
<template> | |||||
<el-dialog | |||||
:model-value="props.visible" | |||||
:title="`选择${title}`" | |||||
:width="800" | |||||
destroy-on-close | |||||
:before-close="handleClose" | |||||
> | |||||
<div class="unitBox"> | |||||
<div class="left"> | |||||
<div class="title">列表</div> | |||||
<div class="content"> | |||||
<el-input | |||||
v-model="filterText" | |||||
class="mb-16" | |||||
placeholder="按名称搜索" | |||||
> | |||||
<template #append> | |||||
<el-button icon="Search" @click="search" /> | |||||
</template> | |||||
</el-input> | |||||
<el-scrollbar> | |||||
<el-tree | |||||
ref="treeRef" | |||||
:data="treeData" | |||||
class="filter-tree" | |||||
:node-key="defaultProps.value" | |||||
:default-checked-keys=" | |||||
(props.defaultData && | |||||
props.defaultData.length && | |||||
props.defaultData | |||||
.map(item => item[defaultProps.value])) || | |||||
[] | |||||
" | |||||
:current-node-key=" | |||||
(props.defaultData && | |||||
props.defaultData.length && | |||||
props.defaultData[0][defaultProps.value]) || | |||||
undefined | |||||
" | |||||
:load="loadNode" | |||||
lazy | |||||
:props="defaultProps" | |||||
:show-checkbox="showCheckbox" | |||||
:check-on-click-node="!showCheckbox" | |||||
check-strictly | |||||
:highlight-current="!showCheckbox" | |||||
:filter-node-method="filterNode" | |||||
:default-expand-all="expandAll" | |||||
:default-expanded-keys="defaultExpandedkeys" | |||||
@check="checkChange" | |||||
/> | |||||
</el-scrollbar> | |||||
</div> | |||||
</div> | |||||
<div class="right"> | |||||
<div class="title">已选{{ title }}</div> | |||||
<div class="content"> | |||||
<el-tag | |||||
v-for="(item, index) in selectData" | |||||
:key="item.key" | |||||
closable | |||||
@close="delData(index, item.key, item.type)" | |||||
> | |||||
{{ type === 'LINE' ? item.businessStripName:item.title }} | |||||
</el-tag> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<template #footer> | |||||
<span class="dialog-footer"> | |||||
<el-button type="primary" @click="submit">提交</el-button> | |||||
<el-button @click="handleClose">关闭</el-button> | |||||
</span> | |||||
</template> | |||||
</el-dialog> | |||||
</template> | |||||
<style lang="less"> | |||||
.unitBox { | |||||
display: flex; | |||||
height: 500px; | |||||
overflow: hidden; | |||||
.el-tag { | |||||
margin: 0 4px 8px; | |||||
} | |||||
.title { | |||||
padding: 12px; | |||||
border-bottom: 1px solid #ccc; | |||||
margin-bottom: 12px; | |||||
} | |||||
.left, | |||||
.right { | |||||
flex: 1; | |||||
border-bottom: 1px solid #ccc; | |||||
display: flex; | |||||
flex-flow: column; | |||||
overflow: hidden; | |||||
.content { | |||||
flex: 1; | |||||
padding: 0 12px; | |||||
overflow: scroll; | |||||
} | |||||
} | |||||
.left { | |||||
border-top: 1px solid #ccc; | |||||
border-left: 1px solid #ccc; | |||||
border-right: 1px solid #ccc; | |||||
.inputText { | |||||
padding: 12px; | |||||
} | |||||
} | |||||
.right { | |||||
border-top: 1px solid #ccc; | |||||
border-right: 1px solid #ccc; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,43 @@ | |||||
<script setup> | |||||
import { computed } from 'vue' | |||||
import { useRoute } from 'vue-router' | |||||
import sidebarItem from './sidebarItem.vue' | |||||
import { storeToRefs } from 'pinia' | |||||
import store from '@/store' | |||||
const props = defineProps({ | |||||
isCollapse: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
sidebarMenu: { | |||||
type: Array, | |||||
default: () => [] | |||||
} | |||||
}) | |||||
const userInfo = storeToRefs(store.userStore).userInfo | |||||
const active = computed(() => { | |||||
const route = useRoute() | |||||
const { meta, name } = route | |||||
if (meta.activeMenu) { | |||||
return meta.activeMenu | |||||
} | |||||
return name | |||||
}) | |||||
</script> | |||||
<template> | |||||
<el-menu | |||||
:default-active="active" | |||||
:collapse="props.isCollapse" | |||||
class="el-menu-vertical-demo" | |||||
unique-opened | |||||
> | |||||
<sidebar-item | |||||
v-for="item in userInfo.regionCode!=='331123'?props.sidebarMenu.filter(i=>i.name!=='applicationRegist'):props.sidebarMenu" | |||||
:key="item.name" | |||||
:index="item.name" | |||||
:title="item.meta.title" | |||||
:item="item" | |||||
:active="active" | |||||
/> | |||||
</el-menu> | |||||
</template> |
@@ -0,0 +1,79 @@ | |||||
<script setup> | |||||
import { useRouter, useRoute } from 'vue-router' | |||||
import { storeToRefs } from 'pinia' | |||||
import store from '@/store' | |||||
const { totalNum } = storeToRefs(store.countStore) || 0, | |||||
userInfo = storeToRefs(store.userStore).userInfo || {}, | |||||
router = useRouter(), | |||||
route = useRoute(), | |||||
props = defineProps({ | |||||
index: String, | |||||
title: String, | |||||
item: Object, | |||||
active: String | |||||
}), | |||||
toRoute = (val) => { | |||||
router.push({ name: val.index }) | |||||
}, | |||||
customIsShow = (item) => { | |||||
if ((item.name === 'operationProjectRecord') && userInfo.value.regionCode === '330500') { | |||||
return true | |||||
} | |||||
return true | |||||
} | |||||
</script> | |||||
<template> | |||||
<template | |||||
v-if="!(item.children && item.children.length) && !item.meta.hidden&&customIsShow(item)" | |||||
> | |||||
<el-menu-item :index="props.index" @click="toRoute"> | |||||
<svg-icon | |||||
v-if="item.meta.activeIcon&&item.meta.uIcon" | |||||
:name="index == active ? item.meta.activeIcon : item.meta.uIcon" | |||||
svg-class="menuIcon" | |||||
/> | |||||
<template #title> | |||||
<el-badge | |||||
v-if="item.name==='waitMeToHandle'" | |||||
:value="totalNum" | |||||
:max="99" | |||||
class="myBadage" | |||||
:hidden="!totalNum" | |||||
> | |||||
<span>{{ title }}</span> | |||||
</el-badge> | |||||
<span v-else>{{ title }}</span> | |||||
</template> | |||||
</el-menu-item> | |||||
</template> | |||||
<template v-else> | |||||
<el-sub-menu v-if="!item.meta.hidden&&(customIsShow(item))" :index="props.index"> | |||||
<template #title> | |||||
<svg-icon | |||||
v-if="item.meta.activeIcon&&item.meta.uIcon" | |||||
:name="route.path.includes(index)||route.meta.activeMenu?.includes(index) ? item.meta.activeIcon : item.meta.uIcon" | |||||
svg-class="menuIcon" | |||||
/> | |||||
<span>{{ title }}</span> | |||||
</template> | |||||
<sidebar-item | |||||
v-for="i in item.children" | |||||
:key="i.name" | |||||
:index="i.name" | |||||
:title="i.meta.title" | |||||
:item="i" | |||||
/> | |||||
</el-sub-menu> | |||||
</template> | |||||
</template> | |||||
<style scoped lang='less'> | |||||
.menuIcon { | |||||
width: 16px; | |||||
height: 16px; | |||||
margin-right: 4px; | |||||
} | |||||
</style> |
@@ -0,0 +1,27 @@ | |||||
<script setup name="svgIcon"> | |||||
import { computed, ref } from 'vue' | |||||
const props = defineProps({ | |||||
prefix: { type: String, default: 'icon' }, | |||||
name: { type: String, required: true }, | |||||
svgClass: { type: String, default: '' }, | |||||
color: { type: String, default: '' } | |||||
}) | |||||
const symbolId = ref(computed(() => `#${props.prefix}-${props.name}`)) | |||||
</script> | |||||
<template> | |||||
<svg aria-hidden="true" :class="['svg-icon', svgClass]"> | |||||
<use :xlink:href="symbolId" :fill="color" /> | |||||
</svg> | |||||
</template> | |||||
<style scoped> | |||||
.svg-icon { | |||||
width: 1em; | |||||
height: 1em; | |||||
vertical-align: -0.1em; | |||||
overflow: hidden; | |||||
fill: currentColor; | |||||
} | |||||
</style> |
@@ -0,0 +1,16 @@ | |||||
import { defineComponent } from 'vue' | |||||
export default defineComponent({ | |||||
name: 'Expand', | |||||
props: { | |||||
row: Object, | |||||
render: Function, | |||||
index: Number, | |||||
column: { | |||||
type: Object, | |||||
default: null | |||||
} | |||||
}, | |||||
render () { | |||||
return this.render(this.row) | |||||
} | |||||
}) |
@@ -0,0 +1,243 @@ | |||||
<script setup name="tableList"> | |||||
import Eexpand from './E-expand' | |||||
import { ref, reactive, onMounted } from 'vue' | |||||
const props = defineProps({ | |||||
column: { | |||||
type: Object | |||||
}, | |||||
data: { | |||||
type: Array | |||||
}, | |||||
size: String, | |||||
emptyTemp: { | |||||
type: Boolean, | |||||
default: true | |||||
}, | |||||
pagination: { | |||||
type: Boolean, | |||||
default: true | |||||
}, | |||||
height: { | |||||
type: Number, | |||||
default: null | |||||
}, | |||||
rowKey: { | |||||
type: [String, Function] | |||||
}, | |||||
selectable: { | |||||
type: Function, | |||||
default: () => true | |||||
}, | |||||
// 合并单元格 | |||||
spanMethod: { | |||||
type: Function, | |||||
default: () => { | |||||
return { | |||||
rowspan: 1, | |||||
colspan: 1 | |||||
} | |||||
} | |||||
}, | |||||
// 是否带斑马纹 | |||||
stripe: { | |||||
type: Boolean, | |||||
default: true | |||||
}, | |||||
// 是否有边框 | |||||
border: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
// 分页 | |||||
pageSizes: { | |||||
type: Array, | |||||
default: () => [10, 20, 30, 40] | |||||
}, | |||||
total: { | |||||
type: Number, | |||||
default: 0 | |||||
}, | |||||
small: { | |||||
type: Boolean, | |||||
default: false | |||||
} | |||||
}), | |||||
emits = defineEmits(['selectionChange', 'getTableData', 'handleTable', 'radioChange', 'selectionRow', 'selectionAll']), | |||||
tableRef = ref(null), | |||||
// 多选 | |||||
selectionChange = selection => { | |||||
emits('selectionChange', selection) | |||||
}, | |||||
selectRow = (selection, row) => { | |||||
emits('selectionRow', selection, row) | |||||
}, | |||||
selectAll = (selection) => { | |||||
emits('selectionAll', selection) | |||||
}, | |||||
// 单选 | |||||
radio = ref(), | |||||
radioChange = (row) => { | |||||
emits('radioChange', row) | |||||
}, | |||||
setRadio = (data) => { | |||||
radio.value = data | |||||
}, | |||||
// 分页 | |||||
pageParams = reactive({ | |||||
pageNumber: 1, | |||||
pageSize: props.pageSizes[0] | |||||
}), | |||||
handleSizeChange = () => { | |||||
emits('getTableData', pageParams) | |||||
}, | |||||
handleCurrentChange = () => { | |||||
emits('getTableData', pageParams) | |||||
}, | |||||
// 多选表格-默认选中 | |||||
toggleRowSelect = ref((row, selected) => { | |||||
tableRef.value.toggleRowSelection(row, selected) | |||||
}), | |||||
getSelectRows = () => { | |||||
return tableRef.value.getSelectionRows() | |||||
} | |||||
onMounted(() => { | |||||
const tableDom = document.querySelector('#out-table') | |||||
emits('handleTable', tableDom) | |||||
}) | |||||
defineExpose({ tableRef, pageParams, setRadio, toggleRowSelect, getSelectRows }) | |||||
</script> | |||||
<template> | |||||
<div> | |||||
<el-table | |||||
id="out-table" | |||||
ref="tableRef" | |||||
:data="props.data" | |||||
:stripe="props.stripe" | |||||
:height="props.height" | |||||
:row-key="props.rowKey" | |||||
:size="size" | |||||
:span-method="spanMethod" | |||||
:border="props.border" | |||||
@selection-change="selectionChange" | |||||
@select="selectRow" | |||||
@select-all="selectAll" | |||||
> | |||||
<template v-if="props.emptyTemp" #empty><el-empty /></template> | |||||
<template v-for="item in props.column" :key="item.key"> | |||||
<template v-if="item.type"> | |||||
<el-table-column | |||||
v-if="item.type === 'index'" | |||||
type="index" | |||||
:label="item.label" | |||||
:width="item.width" | |||||
/> | |||||
<el-table-column | |||||
v-if="item.type === 'selection'" | |||||
type="selection" | |||||
:width="item.width" | |||||
:reserve-selection="item.reserveSelection" | |||||
:selectable="props.selectable" | |||||
/> | |||||
<el-table-column v-if="item.type === 'radio'" type="radio" :width="item.width"> | |||||
<template #default="scope"> | |||||
<el-radio | |||||
:key="scope.row[item.key]" | |||||
v-model="radio" | |||||
:label="scope.row[item.key]" | |||||
:disabled="scope.row.isRadioDisabled" | |||||
@change="radioChange(scope.row)" | |||||
> </el-radio> | |||||
</template> | |||||
</el-table-column> | |||||
<el-table-column v-if="item.type === 'expand'" type="expand"> | |||||
<template #default="scope"> | |||||
<slot name="expand" :scope="scope"></slot> | |||||
</template> | |||||
</el-table-column> | |||||
</template> | |||||
<template v-else-if="item.children"> | |||||
<el-table-column | |||||
:label="item.label" | |||||
:prop="item.prop" | |||||
:width="item.width" | |||||
:min-width="item.minWidth" | |||||
:show-overflow-tooltip="item.showOverflowTooltip || false" | |||||
:fixed="item.fixed || false" | |||||
:column-key="item.columnKey" | |||||
> | |||||
<el-table-column | |||||
v-for="(child,key) in item.children" | |||||
:key="key" | |||||
:label="child.label" | |||||
:prop="child.prop" | |||||
:width="child.width" | |||||
:min-width="child.minWidth" | |||||
:show-overflow-tooltip="child.showOverflowTooltip || false" | |||||
:fixed="child.fixed || false" | |||||
:column-key="child.columnKey" | |||||
> | |||||
<template #default="scope"> | |||||
<template v-if="child.slot"> | |||||
<slot :name="child.slot" :scope="scope"></slot> | |||||
</template> | |||||
<template v-if="child.render"> | |||||
<Eexpand | |||||
:column="child" | |||||
:row="scope.row" | |||||
:render="child.render" | |||||
:index="scope.$index" | |||||
/> | |||||
</template> | |||||
</template></el-table-column> | |||||
</el-table-column> | |||||
</template> | |||||
<template v-else> | |||||
<el-table-column | |||||
:label="item.label" | |||||
:prop="item.prop" | |||||
:width="item.width" | |||||
:min-width="item.minWidth" | |||||
:show-overflow-tooltip="item.showOverflowTooltip || false" | |||||
:fixed="item.fixed || false" | |||||
:column-key="item.columnKey" | |||||
> | |||||
<template #default="scope"> | |||||
<template v-if="item.slot"> | |||||
<slot :name="item.slot" :scope="scope"></slot> | |||||
</template> | |||||
<template v-if="item.render"> | |||||
<Eexpand | |||||
:column="item" | |||||
:row="scope.row" | |||||
:render="item.render" | |||||
:index="scope.$index" | |||||
/> | |||||
</template> | |||||
</template> | |||||
</el-table-column> | |||||
</template> | |||||
</template> | |||||
</el-table> | |||||
<el-pagination | |||||
v-if="pagination" | |||||
v-model:currentPage="pageParams.pageNumber" | |||||
v-model:page-size="pageParams.pageSize" | |||||
:small="small" | |||||
background | |||||
:page-sizes="props.pageSizes" | |||||
layout="total, sizes, prev, pager, next" | |||||
:total="props.total" | |||||
@size-change="handleSizeChange" | |||||
@current-change="handleCurrentChange" | |||||
/> | |||||
</div> | |||||
</template> | |||||
<style scoped lang="less"> | |||||
.el-pagination { | |||||
padding: 16px 16px 0 16px; | |||||
flex-wrap: wrap; | |||||
justify-content: flex-end; | |||||
} | |||||
</style> |
@@ -0,0 +1,62 @@ | |||||
<script setup name="tagsView"> | |||||
import { watch, ref } from 'vue' | |||||
import { useRoute, useRouter } from 'vue-router' | |||||
import store from '@/store' | |||||
const route = useRoute(), | |||||
router = useRouter(), | |||||
clickFn = ({ props: { name }}) => { | |||||
router.push({ | |||||
name: name, | |||||
query: store.appStore.tagNavList.find(i => i.path === name).query || {}, | |||||
params: store.appStore.tagNavList.find(i => i.path === name).params || {} | |||||
}) | |||||
}, | |||||
removeTab = (path) => { | |||||
store.appStore.removeTag(path) | |||||
}, | |||||
acvtie = ref(route.path.split('/')[route.path.split('/').length - 1]) | |||||
watch( | |||||
route, | |||||
(val) => { | |||||
acvtie.value = route.path.split('/')[route.path.split('/').length - 1] | |||||
}, | |||||
{ immediate: true, deep: true } | |||||
) | |||||
</script> | |||||
<template> | |||||
<div class="scroll-outer"> | |||||
<div class="scroll-body"> | |||||
<el-tabs | |||||
v-model="acvtie" | |||||
type="card" | |||||
class="demo-tabs" | |||||
:closable="store.appStore.tagNavList.length!=1" | |||||
@tab-remove="removeTab" | |||||
@tab-click="clickFn" | |||||
> | |||||
<el-tab-pane | |||||
v-for="tag in store.appStore.tagNavList" | |||||
:key="tag.path" | |||||
:data="JSON.stringify(tag)" | |||||
:label="tag.name" | |||||
:name="tag.path" | |||||
/></el-tabs> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<style scoped lang="less"> | |||||
.scroll-outer { | |||||
padding: 5px 0px 0px 20px; | |||||
width: calc(100vw - 200px); | |||||
.scroll-body { | |||||
overflow: visible; | |||||
white-space: nowrap; | |||||
:deep(.el-tabs__header){ | |||||
margin: 0px; | |||||
} | |||||
} | |||||
} | |||||
</style> | |||||