@@ -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> | |||