feat: 团队管理

This commit is contained in:
wangdan-fit2cloud 2023-10-19 18:18:45 +08:00
parent 23991a6ab2
commit 37164a7b7c
16 changed files with 221 additions and 327 deletions

View File

@ -1,4 +1,4 @@
import { Result } from './../../request/Result'
import { Result } from '@/request/Result'
import { get, post } from '@/request/index'
import type {
LoginRequest,

View File

@ -1,16 +1,16 @@
<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">
<div class="content-container__header mb-10" v-if="slots.header || header">
<slot name="header">
<span>{{ header }}</span>
</slot>
</div>
<el-scrollbar>
<div class="content-container__main">
<slot></slot>
</div>
</div>
</el-scrollbar>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
@ -35,7 +35,8 @@ defineProps({
background-color: var(--app-view-bg-color);
border-radius: 6px;
box-sizing: border-box;
min-height: calc(100vh - var(--app-header-height) - 69px);
// overflow: auto;
// height: 100%;
}
}
</style>

View File

@ -37,7 +37,7 @@
size="large"
class="input-item"
:disabled="true"
v-bind:modelValue="userStore.userInfo?.email"
v-bind:modelValue="user.userInfo?.email"
@change="() => {}"
placeholder="请输入邮箱"
>
@ -81,10 +81,10 @@ import type { ResetCurrentUserPasswordRequest } from '@/api/user/type'
import type { FormInstance, FormRules } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import UserApi from '@/api/user'
import { useUserStore } from '@/stores/user'
import useStore from '@/stores';
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserStore()
const { user } = useStore();
const resetPasswordDialog = ref<boolean>(false)
@ -161,7 +161,7 @@ const resetPassword = () => {
return UserApi.resetCurrentUserPassword(resetPasswordForm.value)
})
.then(() => {
return userStore.logout()
return user.logout()
})
.then(() => {
router.push({ name: 'login' })
@ -173,4 +173,4 @@ const close = () => {
defineExpose({ open, close })
</script>
<style lang="scss" scope></style>
<style lang="scss" scope></style>

View File

@ -16,13 +16,13 @@
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useUserStore } from '@/stores/user'
import useStore from '@/stores';
import { useRouter } from 'vue-router'
import ResetPassword from './ResetPasssword.vue'
const userStore = useUserStore()
const { user } = useStore();
const router = useRouter()
const firstUserName = computed(() => {
return userStore.userInfo?.username?.substring(0, 1)
return user.userInfo?.username?.substring(0, 1)
})
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
@ -31,9 +31,9 @@ const openResetPassword = () => {
}
const logout = () => {
userStore.logout().then(() => {
user.logout().then(() => {
router.push({ name: 'login' })
})
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@ -12,8 +12,10 @@ import { Sidebar, AppMain } from '../components'
</script>
<style lang="scss">
.sidebar-container {
box-sizing: border-box;
transition: width 0.28s;
width: var(--sidebar-width);
min-width: var(--sidebar-width);
background-color: var(--sidebar-bg-color);
}
.view-container {

View File

@ -3,8 +3,7 @@ import { MsgError } from '@/utils/message'
import type { NProgress } from 'nprogress'
import type { Ref } from 'vue'
import type { Result } from '@/request/Result'
import { store } from '@/stores/index'
import { useUserStore } from '@/stores/user'
import useStore from '@/stores'
import router from '@/router'
import { ref, type WritableComputedRef } from 'vue'
@ -24,10 +23,10 @@ instance.interceptors.request.use(
if (config.headers === undefined) {
config.headers = {}
}
const userStore = useUserStore(store)
const token = userStore.getToken()
const { user } = useStore()
const token = user.getToken()
if (token) {
config.headers['AUTHORIZATION'] = token
config.headers['AUTHORIZATION'] = `${token}`
}
return config
},

View File

@ -7,16 +7,13 @@ import {
type RouteRecordRaw,
type RouteRecordName
} from 'vue-router'
import { useUserStore } from '@/stores/user'
import { store } from '@/stores'
import useStore from '@/stores';
import { routes } from '@/router/routes'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes
})
// 解决刷新获取用户信息问题
let userStore: any = null
// 路由前置拦截器
router.beforeEach(
@ -25,21 +22,19 @@ router.beforeEach(
next()
return
}
if (userStore === null) {
userStore = useUserStore(store)
}
const { user } = useStore();
const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password']
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
const token = userStore.getToken()
const token = user.getToken()
if (!token) {
next({
path: '/login'
})
return
}
if (!userStore.userInfo) {
await userStore.profile()
if (!user.userInfo) {
await user.profile()
}
}
// 判断是否有菜单权限

View File

@ -1,12 +0,0 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

View File

@ -1,9 +1,10 @@
import type { App } from "vue";
import { createPinia } from "pinia";
const store = createPinia();
import { createPinia } from 'pinia'
const store = createPinia()
export { store }
import useUserStore from './modules/user'
export function setupStore(app: App<Element>) {
app.use(store);
}
const useStore = () => ({
user: useUserStore()
})
export { store };
export default useStore

View File

@ -0,0 +1,62 @@
import { defineStore } from 'pinia'
import type { User } from '@/api/user/type'
import UserApi from '@/api/user'
export interface appStateTypes {
userInfo: User | null
token: any
}
const useUserStore = defineStore({
id: 'user',
state: (): appStateTypes => ({
userInfo: null,
token: ''
}),
actions: {
getToken(): String | null {
if (this.token) {
return this.token
}
return localStorage.getItem('token')
},
getPermissions() {
if (this.userInfo) {
return this.userInfo?.permissions
} else {
return []
}
},
getRole() {
if (this.userInfo) {
return this.userInfo?.role
} else {
return ''
}
},
async profile() {
return UserApi.profile().then((ok) => {
this.userInfo = ok.data
})
},
async login(username: string, password: string) {
return UserApi.login({ username, password }).then((ok) => {
this.token = ok.data
localStorage.setItem('token', ok.data)
return this.profile()
})
},
async logout() {
return UserApi.logout().then(() => {
localStorage.removeItem('token')
return true
})
}
}
})
export default useUserStore

View File

@ -1,55 +0,0 @@
import { defineStore } from 'pinia'
import type { User } from '@/api/user/type'
import UserApi from '@/api/user'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
const userInfo = ref<User>()
// 用户认证token
const token = ref<string>()
const getToken = () => {
if (token.value) {
return token.value
}
return localStorage.getItem('token')
}
const getPermissions = () => {
if (userInfo.value) {
return userInfo.value.permissions
} else {
return []
}
}
const getRole = () => {
if (userInfo.value) {
return userInfo.value.role
} else {
return ''
}
}
const profile = () => {
return UserApi.profile().then((ok) => {
userInfo.value = ok.data
})
}
const login = (username: string, password: string) => {
return UserApi.login({ username, password }).then((ok) => {
token.value = ok.data
localStorage.setItem('token', ok.data)
return profile()
})
}
const logout = () => {
return UserApi.logout().then(() => {
localStorage.removeItem('token')
return true
})
}
return { token, getToken, userInfo, profile, login, logout, getPermissions, getRole }
})

View File

@ -124,11 +124,18 @@ ul {
.border-r {
border-right: 1px solid var(--el-border-color);
}
.border-t {
border-top: 1px solid var(--el-border-color);
}
.border-b-light {
border-bottom: 1px solid var(--el-border-color-lighter);
}
.cursor{
cursor: pointer;
}
.main-calc-height {
height: calc(100vh - 125px);
}

View File

@ -11,5 +11,7 @@
--app-header-bg-color: #ffffff;
/** sidebar 组件 */
--sidebar-bg-color: #ffffff;
--sidebar-width: 220px;
--sidebar-width: 198px;
--team-manage-left-width : 280px;
}

View File

@ -1,5 +1,4 @@
import { store } from '@/stores'
import { useUserStore } from '@/stores/user'
import useStore from '@/stores';
import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
/**
*
@ -7,9 +6,9 @@ import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
* @returns True false
*/
const hasPermissionChild = (permission: Role | string | Permission | ComplexPermission) => {
const userStore = useUserStore(store)
const permissions = userStore.getPermissions()
const role = userStore.getRole()
const { user } = useStore();
const permissions = user.getPermissions()
const role = user.getRole()
if (!permission) {
return true
}

View File

@ -51,10 +51,10 @@ import { ref } from 'vue'
import type { LoginRequest } from '@/api/user/type'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import { useUserStore } from '@/stores/user'
import useStore from '@/stores'
const loading = ref<boolean>(false)
const userStore = useUserStore()
const { user } = useStore()
const router = useRouter()
const loginForm = ref<LoginRequest>({
username: '',
@ -88,7 +88,7 @@ const loginFormRef = ref<FormInstance>()
const login = () => {
loginFormRef.value?.validate().then(() => {
loading.value = true
userStore
user
.login(loginForm.value.username, loginForm.value.password)
.then(() => {
router.push({ name: 'home' })

View File

@ -19,7 +19,7 @@
<el-tag class="ml-10" effect="dark">所有者</el-tag>
</div>
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<span class="cursor">
<el-icon><MoreFilled /></el-icon>
</span>
<template #dropdown>
@ -32,72 +32,40 @@
</ul>
</div>
</div>
<div class="permission-setting p-15">
<h3>权限设置</h3>
<div class="permission-setting flex">
<div class="team-manage__table p-15">
<h3>权限设置</h3>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="数据集" name="dataset">
<el-table :data="tableData" :max-height="tableHeight">
<el-table-column prop="date" label="数据集名称" />
<el-table-column label="管理" align="center">
<template #header>
<el-checkbox v-model="allChecked" label="管理" />
</template>
<template #default="scope">
<el-checkbox v-model="scope.row.checked" />
</template>
</el-table-column>
<el-table-column label="使用" align="center">
<template #header>
<el-checkbox v-model="allChecked" label="使用" />
</template>
<template #default="scope">
<el-checkbox v-model="scope.row.checked" />
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="应用" name="application">应用</el-tab-pane>
</el-tabs>
</div>
<div class="team-manage__footer border-t p-15 flex">
<el-button type="primary">保存</el-button>
</div>
</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" /> -->
</LayoutContent>
</template>
@ -105,145 +73,56 @@
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'
// import { searchiInTree } from '@/utils/utils'
// import { $error, $confirm, $success } from '@/utils/message'
const activeName = ref('dataset')
const allChecked = ref(false)
const tableHeight = ref(0)
function handleClick() {}
// 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()
// }
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-08',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-06',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-07',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}
]
onMounted(() => {
tableHeight.value = window.innerHeight - 300
window.onresize = () => {
return (() => {
tableHeight.value = window.innerHeight - 300
})()
}
// getSalesList()
// getInfo()
// getTeams()
@ -256,15 +135,29 @@ onMounted(() => {
margin-right: 5px;
font-size: 20px;
}
}
.team-member {
width: 250px;
.member-list {
li {
&.active {
background: var(--el-color-primary-light-9);
.team-member {
box-sizing: border-box;
width: var(--team-manage-left-width);
min-width: var(--team-manage-left-width);
.member-list {
li {
&.active {
background: var(--el-color-primary-light-9);
}
}
}
}
.permission-setting {
box-sizing: border-box;
width: calc(100% - var(--team-manage-left-width) - 5px);
flex-direction: column;
}
.team-manage__table {
flex: 1;
}
.team-manage__footer {
flex: 0 0 auto;
justify-content: right;
}
}
</style>