feat: 页面结构

This commit is contained in:
wangdan-fit2cloud 2023-10-16 18:58:51 +08:00
parent 439400d023
commit 46e291ec5c
28 changed files with 537 additions and 122 deletions

View File

@ -5,65 +5,5 @@
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>

View File

@ -6,7 +6,7 @@
? iconMap[iconName].iconReader()
: iconMap['404'].iconReader()
"
class="app-icon"
class="el-icon app-icon"
>
</component>
<el-icon v-else-if="iconName">

View File

@ -94,5 +94,25 @@ export const iconMap: any = {
)
])
}
},
'app-team': {
iconReader: () => {
return h('i', [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M 824.2 699.9 c -25.4 -25.4 -54.7 -45.7 -86.4 -60.4 C 783.1 602.8 812 546.8 812 484 c 0 -110.8 -92.4 -201.7 -203.2 -200 c -109.1 1.7 -197 90.6 -197 200 c 0 62.8 29 118.8 74.2 155.5 c -31.7 14.7 -60.9 34.9 -86.4 60.4 C 345 754.6 314 826.8 312 903.8 c -0.1 4.5 3.5 8.2 8 8.2 h 56 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 C 493.8 707.7 551.1 684 612 684 c 60.9 0 118.2 23.7 161.3 66.8 C 814.5 792 838 846.3 840 904.3 c 0.1 4.3 3.7 7.7 8 7.7 h 56 c 4.5 0 8.1 -3.7 8 -8.2 c -2 -77 -33 -149.2 -87.8 -203.9 Z M 612 612 c -34.2 0 -66.4 -13.3 -90.5 -37.5 c -24.5 -24.5 -37.9 -57.1 -37.5 -91.8 c 0.3 -32.8 13.4 -64.5 36.3 -88 c 24 -24.6 56.1 -38.3 90.4 -38.7 c 33.9 -0.3 66.8 12.9 91 36.6 c 24.8 24.3 38.4 56.8 38.4 91.4 c 0 34.2 -13.3 66.3 -37.5 90.5 c -24.2 24.2 -56.4 37.5 -90.6 37.5 Z M 361.5 510.4 c -0.9 -8.7 -1.4 -17.5 -1.4 -26.4 c 0 -15.9 1.5 -31.4 4.3 -46.5 c 0.7 -3.6 -1.2 -7.3 -4.5 -8.8 c -13.6 -6.1 -26.1 -14.5 -36.9 -25.1 c -25.8 -25.2 -39.7 -59.3 -38.7 -95.4 c 0.9 -32.1 13.8 -62.6 36.3 -85.6 c 24.7 -25.3 57.9 -39.1 93.2 -38.7 c 31.9 0.3 62.7 12.6 86 34.4 c 7.9 7.4 14.7 15.6 20.4 24.4 c 2 3.1 5.9 4.4 9.3 3.2 c 17.6 -6.1 36.2 -10.4 55.3 -12.4 c 5.6 -0.6 8.8 -6.6 6.3 -11.6 c -32.5 -64.3 -98.9 -108.7 -175.7 -109.9 c -110.9 -1.7 -203.3 89.2 -203.3 199.9 c 0 62.8 28.9 118.8 74.2 155.5 c -31.8 14.7 -61.1 35 -86.5 60.4 c -54.8 54.7 -85.8 126.9 -87.8 204 c -0.1 4.5 3.5 8.2 8 8.2 h 56.1 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 c 29.4 -29.4 65.4 -49.8 104.7 -59.7 c 3.9 -1 6.5 -4.7 6 -8.7 Z',
fill: 'currentColor'
})
]
)
])
}
}
}

View File

@ -1,7 +1,7 @@
import { type App } from 'vue'
import AppIcon from './icons/AppIcon.vue'
import LoginLayout from './layout/login-layout/index.vue'
import LoginContainer from './layout/login-container/index.vue'
import LoginLayout from './login-layout/index.vue'
import LoginContainer from './login-container/index.vue'
export default {
install(app: App) {

View File

@ -1,39 +0,0 @@
<script setup lang="ts">
import { ref, onBeforeUpdate } from 'vue'
import { useRoute } from 'vue-router'
import TopBar from '@/components/layout/top-bar/index.vue'
const route = useRoute()
const cachedViews: any = ref([])
onBeforeUpdate(() => {
let isCached = route.meta?.cache
let name = route.name
if (isCached && name && !cachedViews.value.includes(name)) {
cachedViews.value.push(name)
}
})
</script>
<template>
<div class="app-layout">
<div class="app-header">
<TopBar></TopBar>
</div>
<div class="app-main">
<router-view v-slot="{ Component }">
<transition appear name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</div>
</div>
</template>
<style lang="scss" scoped>
.app-main {
height: calc(100vh - var(--app-header-height));
padding: 0 !important;
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<el-scrollbar>
<div class="content-container">
<div class="content-container__header" v-if="slots.header || header">
<slot name="header">
<back-button :path="backPath" :name="backName" :to="backTo" v-if="showBack"></back-button>
<span>{{ header }}</span>
</slot>
</div>
<slot></slot>
</div>
</el-scrollbar>
</template>
<script setup>
import { computed, useSlots } from 'vue';
import BackButton from '@/components/back-button/index.vue';
defineOptions({ name: 'LayoutContent' });
const slots = useSlots();
const prop = defineProps({
header: String,
backPath: String,
backName: String,
backTo: [Object, String],
});
const showBack = computed(() => {
const { backPath, backName, backTo } = prop;
return backPath || backName || backTo;
});
</script>
<style lang="scss">
@use '@/styles/common/mixins.scss' as *;
@use '@/styles/common/variables.scss' as *;
.content-container {
transition: 0.3s;
color: var(--el-text-color-primary);
padding: $view-padding;
.content-container__header {
font-weight: 500;
padding-bottom: $view-padding;
font-size: 20px;
box-sizing: border-box;
span {
vertical-align: middle;
}
}
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<div class="content-container__main">
<div class="content-container__toolbar" v-if="slots.toolbar">
<slot name="toolbar"></slot>
</div>
<slot></slot>
</div>
</template>
<script setup>
import { useSlots } from 'vue';
defineOptions({ name: 'LayoutContentMain' });
const slots = useSlots();
</script>
<style lang="scss">
@use '@/styles/common/mixins.scss' as *;
@use '@/styles/common/variables.scss' as *;
.content-container__main {
background-color: #ffffff;
padding: $view-padding;
border-radius: 4px;
box-sizing: border-box;
min-height: calc(100vh - 210px);
.content-container__toolbar {
@include flex-row(space-between, center);
margin-bottom: 10px;
}
}
</style>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { TopBar, AppMain } from '../components'
</script>
<template>
<div class="app-layout">
<div class="app-header">
<TopBar />
</div>
<div class="app-main">
<AppMain />
</div>
</div>
</template>
<style lang="scss" scoped>
.app-layout {
background-color: var(--app-layout-bg-color);
}
.app-main {
height: calc(100vh - var(--app-header-height));
padding: 0 !important;
}
.app-header {
background-color: var(--app-header-bg-color);
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<router-view v-slot="{ Component }">
<transition appear name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</template>
<script setup lang="ts">
import { ref, onBeforeUpdate } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const cachedViews: any = ref([])
onBeforeUpdate(() => {
let isCached = route.meta?.cache
let name = route.name
if (isCached && name && !cachedViews.value.includes(name)) {
cachedViews.value.push(name)
}
})
</script>

View File

@ -0,0 +1,3 @@
export { default as Sidebar } from './sidebar/index.vue'
export { default as AppMain } from './app-main/index.vue'
export { default as TopBar } from './top-bar/index.vue'

View File

@ -0,0 +1,20 @@
<template>
<div v-if="!menu.meta || !menu.meta.hidden">
<el-menu-item ref="subMenu" :index="menu.path" popper-class="sidebar-popper">
<template #title>
<AppIcon v-if="menu.meta && menu.meta.icon" :iconName="menu.meta.icon" />
<span v-if="menu.meta && menu.meta.title">{{ menu.meta.title }}</span>
</template>
</el-menu-item>
</div>
</template>
<script setup lang="ts">
import { RouteRecordRaw } from 'vue-router'
defineProps<{
menu: RouteRecordRaw
}>()
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,44 @@
<template>
<div class="sidebar">
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu :default-active="activeMenu">
<sidebar-item
:menu="menu"
v-hasPermission="menu.meta?.permission"
v-for="(menu, index) in subMenuList"
:key="index"
>
</sidebar-item>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { getChildRouteListByPathAndName } from '@/router/index'
import SidebarItem from './SidebarItem.vue'
const route = useRoute()
const subMenuList = computed(() => {
return getChildRouteListByPathAndName(route.path, route.name)
})
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
</script>
<style lang="scss">
.sidebar {
.el-menu {
height: 100%;
border: none;
}
}
</style>

View File

@ -18,7 +18,7 @@
import { computed, ref } from 'vue'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
import ResetPassword from '@/components/layout/top-bar/components/avatar/ResetPasssword.vue'
import ResetPassword from './ResetPasssword.vue'
const userStore = useUserStore()
const router = useRouter()
const firstUserName = computed(() => {

View File

@ -14,13 +14,13 @@
</div>
</template>
<script setup lang="ts">
import TopMenu from '@/components/layout/top-bar/components/top-menu/index.vue'
import Avatar from '@/components/layout/top-bar/components/avatar/index.vue'
import TopMenu from './top-menu/index.vue'
import Avatar from './avatar/index.vue'
const defaultTitle = import.meta.env.VITE_APP_TITLE
</script>
<style lang="scss">
.top-bar-container {
border-bottom: 1px solid rgba(229, 229, 229, 1);
border-bottom: 1px solid var(--el-border-color);
height: var(--app-header-height);
box-sizing: border-box;
padding: var(--app-header-padding);

View File

@ -12,7 +12,8 @@
<script setup lang="ts">
import { computed } from 'vue'
import { getChildRouteListByPathAndName } from '@/router/index'
import MenuItem from '@/components/layout/top-bar/components/top-menu/MenuItem.vue'
import MenuItem from './MenuItem.vue'
const topMenuList = computed(() => {
return getChildRouteListByPathAndName('/', 'home')
})

View File

@ -0,0 +1,20 @@
<template>
<div class="main-layout h-full flex">
<div class="sidebar-container"><Sidebar /></div>
<div class="view-container">
<AppMain />
</div>
</div>
</template>
<script setup lang="ts">
import { Sidebar, AppMain } from '../components'
</script>
<style lang="scss">
.sidebar-container {
transition: width 0.28s;
width: var(--sidebar-width);
background-color: var(--sidebar-bg-color);
border-right: 1px solid var(--el-border-color);
}
</style>

View File

@ -1,8 +1,32 @@
import Layout from '@/layout/main-layout/index.vue'
const datasetRouter = {
path: '/dataset',
name: 'dataset',
meta: { icon: 'app-dataset', title: '数据集', permission: 'DATASET:READ' },
component: () => import('@/views/dataset/index.vue')
redirect: '/dataset',
children: [
{
path: '/dataset',
name: 'dataset',
component: () => import('@/views/dataset/index.vue')
},
{
path: '/dataset/doc',
name: 'DatasetDoc',
meta: { icon: 'House', title: '文档', activeMenu: '/dataset' },
component: Layout,
hidden: true,
redirect: '/dataset/doc',
children: [
{
path: '/dataset/doc',
name: 'DatasetDoc',
meta: { icon: 'House', title: '文档' },
component: () => import('@/views/dataset/DatasetDoc.vue')
}
]
}
]
}
export default datasetRouter

View File

@ -1,8 +1,18 @@
import Layout from '@/layout/main-layout/index.vue'
const settingRouter = {
path: '/setting',
name: 'setting',
meta: { icon: 'Setting', title: '系统设置', permission: 'SETTING:READ' },
component: () => import('@/views/setting/index.vue')
redirect: '/setting',
component: Layout,
children: [
{
path: '/setting',
name: 'setting',
meta: { icon: 'app-team', title: '团队管理' },
component: () => import('@/views/setting/index.vue')
}
]
}
export default settingRouter

View File

@ -8,7 +8,7 @@ export const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: () => import('@/components/layout/app-layout/index.vue'),
component: () => import('@/layout/app-layout/index.vue'),
children: [
{
path: '/first',
@ -19,6 +19,7 @@ export const routes: Array<RouteRecordRaw> = [
...rolesRoutes
]
},
{
path: '/login',
name: 'login',

View File

@ -17,7 +17,7 @@
padding: 0;
margin: 0 24px;
height: 56px;
border-bottom: 1px solid #d5d6d8;
border-bottom: 1px solid var(--el-border-color);
.el-drawer__title {
color: #1f2329;
font-weight: 500;

View File

@ -1,5 +1,6 @@
:root {
--el-color-primary: rgba(51, 112, 255, 1);
--app-layout-bg-color: #f3f5f6;
--app-base-text-color: rgba(31, 35, 41, 1);
--app-base-text-font-size: 14px;
--app-base-text-hover-color: rgba(51, 112, 255, 1);
@ -8,7 +9,8 @@
/** header 组件 */
--app-header-height: 56px;
--app-header-padding: 0 20px;
--app-header-bg-color: #252b3c;
/** top-bar 组件 */
--app-header-bg-color: #ffffff;
/** sidebar 组件 */
--sidebar-bg-color: #ffffff;
--sidebar-width: 200px;
}

View File

@ -0,0 +1,7 @@
<template>
<div>dataset 文档</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped></style>

View File

@ -1,4 +1,4 @@
<template >
<template>
<div>
dataset
</div>

View File

@ -1,9 +1,235 @@
<template >
<div>
setting
</div>
</template>
<script lang="ts" setup>
<template>
<div>团队管理</div>
<!-- <div class="sales-department__tree">
<support-tree
ref="salesDepartmentTree"
v-model:data="dataSource"
:title="`飞致云 ${regionName}`"
buttonName="新建部门"
:beforeAppend="appendDepartment"
:beforeRemove="removedDepartment"
:beforeEdit="editDepartment"
:filterable="true"
:highlight-current="true"
node-key="id"
:props="defaultProps"
@node-click="treeClick"
/>
</div>
<div class="sales-department__table">
<complex-table v-if="currentDepartment" :data="memberTable" border>
<template #toolbar>
<div class="table-header">
<h3>{{ currentDepartment?.name }}</h3>
<div>
<el-button type="primary" @click="addMember">设置人员</el-button>
</div>
</div>
</template>
<el-table-column label="账户ID" prop="username" show-overflow-tooltip />
<el-table-column label="姓名">
<template #default="{ row }">
<icon
icon="iconfont icon-vip"
v-if="currentDepartment?.leaderId === row.userId"
style="color: #f8bc2e"
></icon>
{{ row.name }}
</template>
</el-table-column>
<el-table-column label="手机号" prop="phone" />
<el-table-column label="邮箱" prop="email" show-overflow-tooltip />
<el-table-column label="角色">
<template #default="{ row }">
{{ currentDepartment?.leaderId === row.userId ? '销售主管' : '销售' }}
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="{ row }">
<el-button
type="primary"
size="small"
v-if="currentDepartment?.leaderId !== row.userId"
@click="setLeader(row)"
>升级</el-button
>
</template>
</el-table-column>
</complex-table>
<el-empty :image-size="200" v-else />
</div> -->
<!-- 设置部门
<department-dialog ref="departmentDialogRef" :title="dialogTitle" @refresh="refresh" />
<member-dialog ref="memberDialogRef" @refresh="refresh" /> -->
</template>
<script lang="ts" setup>
import { onMounted, ref, watch, nextTick } from 'vue'
// import DepartmentDialog from './components/DepartmentDialog.vue'
// import MemberDialog from './components/MemberDialog.vue'
// import { getSalesTeams, deleteSalesTeams, putSetLeader } from '@/api/sales-team-controller'
// import { searchiInTree } from '@/utils/utils'
// import { $error, $confirm, $success } from '@/utils/message'
// import useStore from '@/store'
// const { user } = useStore()
// const defaultProps = {
// children: 'subTeams'
// }
// const salesDepartmentTree = ref()
// const memberDialogRef = ref()
// const departmentDialogRef = ref()
// const loading = ref(false)
// const regionName = ref('')
// const dialogTitle = ref('')
// const dataSource = ref([])
// const salesOptions = ref([])
// const currentDepartment = ref(null)
// const memberTable = ref([])
// watch([salesOptions, currentDepartment], (val) => {
// if (val[0]?.length && val[1]) {
// const leader = val[0].filter((item) => item.userId === val[1]?.leaderId)
// const member = val[1]?.teamMember
// ? val[0].filter((item) => val[1]?.teamMember.includes(item.userId))
// : []
// memberTable.value = [...leader, ...member]
// }
// })
// function addMember() {
// memberDialogRef.value.open(currentDepartment.value)
// }
// const appendDepartment = (data) => {
// dialogTitle.value = ''
// departmentDialogRef.value.open('create', data)
// return false
// }
// const removedDepartment = (node, data) => {
// $confirm('?', {
// confirmButtonText: '',
// cancelButtonText: '',
// type: 'warning'
// })
// .then(() => {
// loading.value = true
// deleteSalesTeams(data.id)
// .then(() => {
// $success('')
// getTeams()
// })
// .catch(() => {
// loading.value = false
// })
// })
// .catch(() => {})
// return false
// }
// const editDepartment = (node) => {
// dialogTitle.value = ''
// const parent = node.isLeaf ? node.parent.data : null
// departmentDialogRef.value.open('edit', node.data, parent)
// return false
// }
// function setLeader(row) {
// loading.value = true
// putSetLeader(currentDepartment.value.id, row.userId)
// .then((data) => {
// getTeams()
// })
// .catch(() => {
// loading.value = false
// })
// }
// function treeClick(node) {
// currentDepartment.value = node
// }
// function getTeams() {
// loading.value = true
// getSalesTeams({ includeUsers: true })
// .then((data) => {
// dataSource.value = data
// currentDepartment.value = currentDepartment.value
// ? searchiInTree(dataSource.value, currentDepartment.value, 'subTeams')
// : dataSource.value?.length && dataSource.value[0]
// nextTick(() => {
// salesDepartmentTree.value.setCurrentKey(currentDepartment.value?.id)
// })
// loading.value = false
// })
// .catch(() => {
// loading.value = false
// })
// }
// function getSalesList() {
// loading.value = true
// user
// .asyncGetInternalUsers({ group: ['sales', 'sales_leader', 'region_sales_admin'] })
// .then((res) => {
// salesOptions.value = res
// loading.value = false
// })
// .catch(() => {
// loading.value = false
// })
// }
// function getInfo() {
// loading.value = true
// user
// .asyncGetUserInfo()
// .then((data) => {
// regionName.value = data?.region?.name ? `- ${data?.region?.name}` : ''
// loading.value = false
// })
// .catch(() => {
// loading.value = false
// })
// }
// function refresh() {
// getTeams()
// }
onMounted(() => {
// getSalesList()
// getInfo()
// getTeams()
})
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
$department-left-width: 300px;
.sales-department {
&__tree {
width: $department-left-width;
min-width: $department-left-width;
padding-right: 20px;
box-sizing: content-box;
}
&__table {
border-left: 1px solid var(--el-border-color);
width: calc(100% - $department-left-width - 40px);
padding-left: 20px;
.table-header {
h3 {
font-size: 18px;
}
}
}
}
</style>