feat: 团队管理

This commit is contained in:
wangdan-fit2cloud 2023-10-18 19:06:22 +08:00
parent 46e291ec5c
commit 23991a6ab2
19 changed files with 187 additions and 309 deletions

4
ui/package-lock.json generated
View File

@ -2028,7 +2028,7 @@
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-10.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"node_modules/escape-string-regexp": {
@ -7283,7 +7283,7 @@
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-10.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"escape-string-regexp": {

View File

@ -0,0 +1,41 @@
<template>
<el-scrollbar>
<div class="content-container">
<div class="content-container__header mb-10" v-if="slots.header || header">
<slot name="header">
<span>{{ header }}</span>
</slot>
</div>
<div class="content-container__main">
<slot></slot>
</div>
</div>
</el-scrollbar>
</template>
<script setup lang="ts">
import { useSlots } from 'vue'
defineOptions({ name: 'LayoutContent' })
const slots = useSlots()
defineProps({
header: String
})
</script>
<style lang="scss" scope>
.content-container {
transition: 0.3s;
.content-container__header {
font-weight: 600;
font-size: 18px;
box-sizing: border-box;
}
.content-container__main {
background-color: var(--app-view-bg-color);
border-radius: 6px;
box-sizing: border-box;
min-height: calc(100vh - var(--app-header-height) - 69px);
}
}
</style>

View File

@ -30,12 +30,4 @@ const props = withDefaults(
const isIconfont = computed(() => props.iconName?.includes('app-'))
</script>
<style lang="scss" scoped>
.app-icon {
line-height: 1em;
svg {
height: 1em;
width: 1em;
}
}
</style>
<style lang="scss" scoped></style>

View File

@ -114,5 +114,25 @@ export const iconMap: any = {
)
])
}
},
'app-add-users': {
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 892 772 h -80 v -80 c 0 -4.4 -3.6 -8 -8 -8 h -48 c -4.4 0 -8 3.6 -8 8 v 80 h -80 c -4.4 0 -8 3.6 -8 8 v 48 c 0 4.4 3.6 8 8 8 h 80 v 80 c 0 4.4 3.6 8 8 8 h 48 c 4.4 0 8 -3.6 8 -8 v -80 h 80 c 4.4 0 8 -3.6 8 -8 v -48 c 0 -4.4 -3.6 -8 -8 -8 Z M 373.5 498.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.8 -1.7 -203.2 89.2 -203.2 200 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.8 -1.1 6.4 -4.8 5.9 -8.8 Z M 824 472 c 0 -109.4 -87.9 -198.3 -196.9 -200 C 516.3 270.3 424 361.2 424 472 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 357 742.6 326 814.8 324 891.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 505.8 695.7 563 672 624 672 c 110.4 0 200 -89.5 200 -200 Z m -109.5 90.5 C 690.3 586.7 658.2 600 624 600 s -66.3 -13.3 -90.5 -37.5 C 509 538 495.7 505.4 496 470.7 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.1 34.2 -13.4 66.3 -37.6 90.5 Z',
fill: 'currentColor'
})
]
)
])
}
}
}

View File

@ -2,11 +2,13 @@ import { type App } from 'vue'
import AppIcon from './icons/AppIcon.vue'
import LoginLayout from './login-layout/index.vue'
import LoginContainer from './login-container/index.vue'
import LayoutContent from './content-container/LayoutContent.vue'
export default {
install(app: App) {
app.component(AppIcon.name, AppIcon)
app.component(LoginLayout.name, LoginLayout)
app.component(LoginContainer.name, LoginContainer)
app.component(LayoutContent.name, LayoutContent)
}
}

View File

@ -1,52 +0,0 @@
<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

@ -1,32 +0,0 @@
<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

@ -1,5 +1,5 @@
<template>
<div v-if="!menu.meta || !menu.meta.hidden">
<div v-if="!menu.meta || !menu.meta.hidden" class="sidebar-item">
<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" />
@ -10,11 +10,23 @@
</template>
<script setup lang="ts">
import { RouteRecordRaw } from 'vue-router'
import { type RouteRecordRaw } from 'vue-router'
defineProps<{
menu: RouteRecordRaw
}>()
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.sidebar-item {
.el-menu-item {
padding-left: 30px !important;
font-weight: 500;
}
.el-menu-item.is-active {
color: var(--el-menu-active-color);
background: var(--el-color-primary-light-9);
}
}
</style>

View File

@ -60,7 +60,7 @@
</el-input>
<el-button
size="large"
class="send-email-button ml-1"
class="send-email-button ml-10"
@click="sendEmail"
:loading="loading"
>获取验证码</el-button

View File

@ -1,9 +1,9 @@
<template>
<div class="top-bar-container flex-between">
<div class="top-bar-container flex-between border-b">
<div class="flex-center h-full">
<div class="app-title-container flex-center">
<div class="app-title-icon"></div>
<div class="app-title-text ml-1">{{ defaultTitle }}</div>
<div class="app-title-text ml-10">{{ defaultTitle }}</div>
</div>
<el-divider direction="vertical" class="line" />
<TopMenu></TopMenu>
@ -20,13 +20,12 @@ const defaultTitle = import.meta.env.VITE_APP_TITLE
</script>
<style lang="scss">
.top-bar-container {
border-bottom: 1px solid var(--el-border-color);
height: var(--app-header-height);
box-sizing: border-box;
padding: var(--app-header-padding);
.app-title-container {
margin-right: 30px;
margin-right: 20px;
.app-title-icon {
background-image: url('@/assets/logo.png');
background-size: 100% 100%;

View File

@ -1,6 +1,6 @@
<template>
<div class="main-layout h-full flex">
<div class="sidebar-container"><Sidebar /></div>
<div class="sidebar-container border-r"><Sidebar /></div>
<div class="view-container">
<AppMain />
</div>
@ -15,6 +15,9 @@ import { Sidebar, AppMain } from '../components'
transition: width 0.28s;
width: var(--sidebar-width);
background-color: var(--sidebar-bg-color);
border-right: 1px solid var(--el-border-color);
}
.view-container {
width: 100%;
padding: var(--app-view-padding);
}
</style>

View File

@ -4,7 +4,8 @@ import {
createWebHistory,
type NavigationGuardNext,
type RouteLocationNormalized,
type RouteRecordRaw
type RouteRecordRaw,
type RouteRecordName
} from 'vue-router'
import { useUserStore } from '@/stores/user'
import { store } from '@/stores'
@ -51,14 +52,14 @@ router.beforeEach(
}
)
export const getChildRouteListByPathAndName = (path: string, name: string) => {
export const getChildRouteListByPathAndName = (path: string, name?: RouteRecordName | null | undefined) => {
return getChildRouteList(routes, path, name)
}
export const getChildRouteList: (
routeList: Array<RouteRecordRaw>,
path: string,
name: string
name?: RouteRecordName | null | undefined
) => Array<RouteRecordRaw> = (routeList, path, name) => {
for (let index = 0; index < routeList.length; index++) {
const route = routeList[index]

View File

@ -74,23 +74,27 @@ ul {
height: 100%;
}
.mt-1 {
.mt-10 {
margin-top: 10px;
}
.ml-1 {
.ml-10 {
margin-left: 10px;
}
.mr-1 {
.mr-10 {
margin-right: 10px;
}
.mb-1 {
.mb-10 {
margin-bottom: 10px;
}
.mb-2 {
.mb-20 {
margin-bottom: 20px;
}
.p-15 {
padding: 15px;
}
.flex {
display: flex;
}
@ -107,173 +111,24 @@ ul {
align-items: center;
}
// 创建表单
.create-catalog-container {
height: 100%;
margin-top: -20px;
.padding-top-30 {
padding-top: 30px;
}
.padding-top-40 {
padding-top: 40px;
}
// 表单外套
.form-div {
text-align: center;
margin: 0 auto;
width: 80%;
min-width: 300px;
form {
.el-form-item {
margin-bottom: 28px;
}
label {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #1f2329;
flex: none;
order: 0;
flex-grow: 0;
}
}
}
// 删除按钮样式
.delete-button-class {
cursor: pointer;
color: #646a73;
}
// 添加按钮样式
.add-button-class {
cursor: pointer;
border: 0 solid;
//width: 105px;
height: 22px;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 22px;
display: flex;
align-items: center;
letter-spacing: -0.1px;
color: #3370ff;
.span-class {
vertical-align: 2px;
color: #3370ff;
padding-left: 5px;
}
}
button {
height: 32px;
min-width: 80px;
}
.save-btn {
background-color: #3370ff;
}
.cancel-btn {
}
// 下方操作按钮区域
.footer {
border-top: 1px solid var(--el-border-color);
padding: 24px 0px 0px 0px;
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
margin: 0px -50px 0px;
.footer-form {
min-width: 400px;
}
.footer-center {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.footer-btn {
margin: 0px 80px 0px;
text-align: right;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-end;
}
}
.description {
padding-left: 15px;
font-size: smaller;
color: #606266;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
// 自定义弹出框样式
.custom-dialog {
//标题样式
.el-dialog__header {
padding: 24px !important;
}
//关闭按钮样式
.el-dialog__headerbtn .el-dialog__close {
height: auto !important;
color: #646a73 !important;
font-size: x-large !important;
}
.el-dialog__headerbtn .el-dialog__close:hover {
background: rgba(31, 35, 41, 0.1) !important;
border-radius: 4px !important;
}
//内容间距
.el-dialog__body {
padding: 0px 24px 0px 24px;
}
.el-dialog__footer {
padding-bottom: 29px !important;
}
//下方按钮
.footer-btn {
button {
height: 32px;
min-width: 80px;
}
.save-btn {
background-color: #3370ff;
}
.cancel-btn {
}
}
.border-b {
border-bottom: 1px solid var(--el-border-color);
}
.border-r {
border-right: 1px solid var(--el-border-color);
}
.custom-radio-group.el-radio-group {
border: 1px solid #bbbfc4;
border-radius: 5px;
height: 30px;
label {
border: 0px solid;
padding: 2px 10px 2px 4px;
}
.el-radio-button__inner {
padding: 4px;
border: 0px;
border-radius: 5px;
}
.el-radio-button {
height: auto;
}
.el-radio-button is-active {
height: auto;
}
.el-radio-button__original-radio:checked + .el-radio-button__inner {
color: #3370ff;
border: 0;
box-shadow: 0 0 0 0;
background: rgba(51, 112, 255, 0.1);
}
.border-b-light {
border-bottom: 1px solid var(--el-border-color-lighter);
}
.main-calc-height {
height: calc(100vh - 125px);
}

View File

@ -1,3 +1,6 @@
:root {
--el-menu-item-height: 45px;
}
.el-avatar {
--el-avatar-bg-color: var(--el-color-primary);
--el-avatar-size-small: 33px;

View File

@ -2,15 +2,14 @@
--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);
--app-base-text-hover-bg-color: rgba(51, 112, 255, 0.1);
--app-base-action-text-color: var(--app-base-text-hover-color);
--app-view-padding: 15px;
--app-view-bg-color: #ffffff;
--hover-bg-color: #fafafa;
/** header 组件 */
--app-header-height: 56px;
--app-header-padding: 0 20px;
--app-header-bg-color: #ffffff;
/** sidebar 组件 */
--sidebar-bg-color: #ffffff;
--sidebar-width: 200px;
--sidebar-width: 220px;
}

View File

@ -1,7 +1,7 @@
<template>
<login-layout>
<LoginContainer>
<h3 class="mb-2">忘记密码</h3>
<h3 class="mb-20">忘记密码</h3>
<el-form
class="register-form"
ref="resetPasswordFormRef"
@ -34,7 +34,7 @@
</el-input>
<el-button
size="large"
class="send-email-button ml-1"
class="send-email-button ml-10"
@click="sendEmail"
:loading="loading"
>获取验证码</el-button
@ -45,7 +45,7 @@
<el-button type="primary" class="login-submit-button w-full" @click="checkCode"
>立即验证</el-button
>
<div class="operate-container mt-1">
<div class="operate-container mt-10">
<el-button
class="register"
@click="router.push('/login')"

View File

@ -1,7 +1,7 @@
<template>
<login-layout>
<LoginContainer>
<h3 class="mb-2">注册</h3>
<h3 class="mb-20">注册</h3>
<el-form class="register-form" :model="registerForm" :rules="rules" ref="registerFormRef">
<el-form-item prop="username">
<el-input
@ -69,7 +69,7 @@
</el-input>
<el-button
size="large"
class="send-email-button ml-1"
class="send-email-button ml-10"
@click="sendEmail"
:loading="sendEmailLoading"
>获取验证码</el-button
@ -80,7 +80,7 @@
<el-button type="primary" class="login-submit-button w-full" @click="register"
>注册</el-button
>
<div class="operate-container mt-1">
<div class="operate-container mt-10">
<el-button
class="register"
@click="router.push('/login')"

View File

@ -1,7 +1,7 @@
<template>
<login-layout>
<LoginContainer>
<h3 class="mb-2">修改密码</h3>
<h3 class="mb-20">修改密码</h3>
<el-form
class="reset-password-form"
ref="resetPasswordFormRef"
@ -40,7 +40,7 @@
<el-button type="primary" class="login-submit-button w-full" @click="resetPassword"
>确认修改</el-button
>
<div class="operate-container mt-1">
<div class="operate-container mt-10">
<el-button
class="register"
@click="router.push('/login')"

View File

@ -1,7 +1,43 @@
<template>
<div>团队管理</div>
<LayoutContent header="团队管理">
<div class="team-manage flex main-calc-height">
<div class="team-member p-15 border-r">
<h3>团队成员</h3>
<div class="align-right">
<el-button type="primary" link
><AppIcon iconName="app-add-users" class="add-user-icon" />添加成员</el-button
>
</div>
<div class="mt-10">
<el-input v-model="filterText" placeholder="请输入用户名搜索" suffix-icon="Search" />
</div>
<div class="member-list mt-10">
<ul>
<li class="active border-b-light flex-between p-15">
<div>
<span>baixin</span>
<el-tag class="ml-10" effect="dark">所有者</el-tag>
</div>
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<el-icon><MoreFilled /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>移除</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</li>
</ul>
</div>
</div>
<div class="permission-setting p-15">
<h3>权限设置</h3>
</div>
</div>
<!-- <div class="sales-department__tree">
<!-- <div class="sales-department__tree">
<support-tree
ref="salesDepartmentTree"
v-model:data="dataSource"
@ -59,13 +95,16 @@
</complex-table>
<el-empty :image-size="200" v-else />
</div> -->
<!-- 设置部门
<!-- 设置部门
<department-dialog ref="departmentDialogRef" :title="dialogTitle" @refresh="refresh" />
<member-dialog ref="memberDialogRef" @refresh="refresh" /> -->
</LayoutContent>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch, nextTick } from 'vue'
const filterText = ref('')
// import DepartmentDialog from './components/DepartmentDialog.vue'
// import MemberDialog from './components/MemberDialog.vue'
// import { getSalesTeams, deleteSalesTeams, putSetLeader } from '@/api/sales-team-controller'
@ -212,22 +251,18 @@ onMounted(() => {
</script>
<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;
.team-manage {
.add-user-icon {
margin-right: 5px;
font-size: 20px;
}
&__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;
}
.team-member {
width: 250px;
.member-list {
li {
&.active {
background: var(--el-color-primary-light-9);
}
}
}