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 { get, post } from '@/request/index'
import type { import type {
LoginRequest, LoginRequest,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,16 +7,13 @@ import {
type RouteRecordRaw, type RouteRecordRaw,
type RouteRecordName type RouteRecordName
} from 'vue-router' } from 'vue-router'
import { useUserStore } from '@/stores/user' import useStore from '@/stores';
import { store } from '@/stores'
import { routes } from '@/router/routes' import { routes } from '@/router/routes'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: routes routes: routes
}) })
// 解决刷新获取用户信息问题
let userStore: any = null
// 路由前置拦截器 // 路由前置拦截器
router.beforeEach( router.beforeEach(
@ -25,21 +22,19 @@ router.beforeEach(
next() next()
return return
} }
if (userStore === null) { const { user } = useStore();
userStore = useUserStore(store)
}
const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password'] const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password']
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) { if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
const token = userStore.getToken() const token = user.getToken()
if (!token) { if (!token) {
next({ next({
path: '/login' path: '/login'
}) })
return return
} }
if (!userStore.userInfo) { if (!user.userInfo) {
await userStore.profile() 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'
import { createPinia } from "pinia"; const store = createPinia()
const store = createPinia(); export { store }
import useUserStore from './modules/user'
export function setupStore(app: App<Element>) { const useStore = () => ({
app.use(store); 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-r {
border-right: 1px solid var(--el-border-color); border-right: 1px solid var(--el-border-color);
} }
.border-t {
border-top: 1px solid var(--el-border-color);
}
.border-b-light { .border-b-light {
border-bottom: 1px solid var(--el-border-color-lighter); border-bottom: 1px solid var(--el-border-color-lighter);
} }
.cursor{
cursor: pointer;
}
.main-calc-height { .main-calc-height {
height: calc(100vh - 125px); height: calc(100vh - 125px);
} }

View File

@ -11,5 +11,7 @@
--app-header-bg-color: #ffffff; --app-header-bg-color: #ffffff;
/** sidebar 组件 */ /** sidebar 组件 */
--sidebar-bg-color: #ffffff; --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 useStore from '@/stores';
import { useUserStore } from '@/stores/user'
import { Role, Permission, ComplexPermission } from '@/utils/permission/type' import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
/** /**
* *
@ -7,9 +6,9 @@ import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
* @returns True false * @returns True false
*/ */
const hasPermissionChild = (permission: Role | string | Permission | ComplexPermission) => { const hasPermissionChild = (permission: Role | string | Permission | ComplexPermission) => {
const userStore = useUserStore(store) const { user } = useStore();
const permissions = userStore.getPermissions() const permissions = user.getPermissions()
const role = userStore.getRole() const role = user.getRole()
if (!permission) { if (!permission) {
return true return true
} }

View File

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

View File

@ -19,7 +19,7 @@
<el-tag class="ml-10" effect="dark">所有者</el-tag> <el-tag class="ml-10" effect="dark">所有者</el-tag>
</div> </div>
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link"> <span class="cursor">
<el-icon><MoreFilled /></el-icon> <el-icon><MoreFilled /></el-icon>
</span> </span>
<template #dropdown> <template #dropdown>
@ -32,72 +32,40 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="permission-setting p-15"> <div class="permission-setting flex">
<div class="team-manage__table p-15">
<h3>权限设置</h3> <h3>权限设置</h3>
</div> <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>
<!-- <div class="sales-department__tree"> <div class="team-manage__footer border-t p-15 flex">
<support-tree <el-button type="primary">保存</el-button>
ref="salesDepartmentTree" </div>
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>
</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> </LayoutContent>
</template> </template>
@ -105,145 +73,56 @@
import { onMounted, ref, watch, nextTick } from 'vue' import { onMounted, ref, watch, nextTick } from 'vue'
const filterText = ref('') const filterText = ref('')
// import DepartmentDialog from './components/DepartmentDialog.vue' const activeName = ref('dataset')
// import MemberDialog from './components/MemberDialog.vue' const allChecked = ref(false)
// import { getSalesTeams, deleteSalesTeams, putSetLeader } from '@/api/sales-team-controller' const tableHeight = ref(0)
// import { searchiInTree } from '@/utils/utils' function handleClick() {}
// import { $error, $confirm, $success } from '@/utils/message'
// import useStore from '@/store' const tableData = [
// const { user } = useStore() {
date: '2016-05-03',
// const defaultProps = { name: 'Tom',
// children: 'subTeams' address: 'No. 189, Grove St, Los Angeles'
// } },
{
// const salesDepartmentTree = ref() date: '2016-05-02',
// const memberDialogRef = ref() name: 'Tom',
// const departmentDialogRef = ref() address: 'No. 189, Grove St, Los Angeles'
// const loading = ref(false) },
{
// const regionName = ref('') date: '2016-05-04',
// const dialogTitle = ref('') name: 'Tom',
// const dataSource = ref([]) address: 'No. 189, Grove St, Los Angeles'
// const salesOptions = ref([]) },
// const currentDepartment = ref(null) {
// const memberTable = ref([]) date: '2016-05-01',
name: 'Tom',
// watch([salesOptions, currentDepartment], (val) => { address: 'No. 189, Grove St, Los Angeles'
// if (val[0]?.length && val[1]) { },
// const leader = val[0].filter((item) => item.userId === val[1]?.leaderId) {
// const member = val[1]?.teamMember date: '2016-05-08',
// ? val[0].filter((item) => val[1]?.teamMember.includes(item.userId)) name: 'Tom',
// : [] address: 'No. 189, Grove St, Los Angeles'
// memberTable.value = [...leader, ...member] },
// } {
// }) date: '2016-05-06',
name: 'Tom',
// function addMember() { address: 'No. 189, Grove St, Los Angeles'
// memberDialogRef.value.open(currentDepartment.value) },
// } {
date: '2016-05-07',
// const appendDepartment = (data) => { name: 'Tom',
// dialogTitle.value = '' address: 'No. 189, Grove St, Los Angeles'
// 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(() => { onMounted(() => {
tableHeight.value = window.innerHeight - 300
window.onresize = () => {
return (() => {
tableHeight.value = window.innerHeight - 300
})()
}
// getSalesList() // getSalesList()
// getInfo() // getInfo()
// getTeams() // getTeams()
@ -256,9 +135,10 @@ onMounted(() => {
margin-right: 5px; margin-right: 5px;
font-size: 20px; font-size: 20px;
} }
} .team-member {
.team-member { box-sizing: border-box;
width: 250px; width: var(--team-manage-left-width);
min-width: var(--team-manage-left-width);
.member-list { .member-list {
li { li {
&.active { &.active {
@ -266,5 +146,18 @@ onMounted(() => {
} }
} }
} }
}
.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> </style>