| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- <template>
- <div class="me-enterprise">
- <div class="enterprise-header">
- <h2 class="enterprise-title">我的企业</h2>
- </div>
- <div class="enterprise-content">
- <!-- 加载状态 -->
- <div v-if="loading" class="loading-state">
- <el-icon class="loading-icon">
- <Loading />
- </el-icon>
- <p>加载中...</p>
- </div>
- <!-- 如果没有企业,显示创建企业提示 -->
- <div v-else-if="!hasEnterprise" class="empty-state">
- <div class="empty-icon">
- <el-icon class="empty-icon__icon">
- <OfficeBuilding />
- </el-icon>
- </div>
- <p class="empty-text">您还没有加入任何企业</p>
- <el-button type="primary" @click="openCreateDialog">
- 创建企业
- </el-button>
- </div>
- <!-- 如果有企业,显示企业员工列表 -->
- <div v-else class="enterprise-list">
- <!-- 企业信息展示 -->
- <div class="list-header">
- <div class="enterprise-info">
- <div class="enterprise-name">
- <el-icon class="enterprise-name-icon">
- <OfficeBuilding />
- </el-icon>
- <span class="name-text">{{ currentEnterpriseName }}</span>
- </div>
- <p class="hint-text">注:一个账号只能归属一个企业</p>
- </div>
- <div class="header-actions">
- <el-button type="primary" :icon="Plus" @click="showAddMemberDialog = true">
- 添加企业成员
- </el-button>
- </div>
- </div>
- <div style="margin-bottom: 10px;">
- <el-button type="primary" size="small" plain @click="handleOpenProductSeatDialog">产品分配</el-button>
- </div>
- <!-- 员工列表表格 -->
- <div class="table-section">
- <el-table ref="employeeTable" :data="employeeList" style="width: 100%"
- :header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: '500' }" empty-text="暂无员工数据"
- >
- <el-table-column prop="id" label="编号" width="100" align="center" />
- <el-table-column prop="name" label="姓名" min-width="150" align="center" />
- <el-table-column prop="email" label="邮箱" min-width="200" align="center" />
- <el-table-column prop="role_text" label="角色" width="140" align="center">
- <template #default="{ row }">
- <el-tag :type="row.role === 1 ? 'success' : 'info'" size="small">
- {{ row.role_text }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="120" align="center">
- <template #default="{ row }">
- <el-tag :type="getStatusType(row.status)" size="small">
- {{ row.status_text }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="join_time" label="加入时间" width="160" align="center" />
- <el-table-column label="操作" width="100" align="center" fixed="right">
- <template #default="{ row }">
- <el-button type="danger" size="small" :disabled="!row.can_operate || row.id === currentUserId"
- @click="handleDeleteMember(row)">
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- 分页控件 -->
- <div v-if="employeeList.length > 0 || total > 0" class="pagination-section">
- <el-pagination :current-page="currentPage" :page-size="pageSize" :page-sizes="pageSizes" :total="total"
- layout="total, sizes, prev, pager, next, jumper" background @size-change="handlePageSizeChange"
- @current-change="handleCurrentPageChange" />
- </div>
- </div>
- </div>
- <!-- 创建企业弹框 -->
- <CreateEnterpriseDialog v-model:visible="showCreateDialog" @success="handleCreateSuccess"
- @close="handleDialogClose" />
- <!-- 添加企业成员弹框 -->
- <AddMemberDialog v-model:visible="showAddMemberDialog" :company-id="currentCompanyId" @success="loadEnterpriseList"
- @close="showAddMemberDialog = false" />
- <!-- 产品分配席位弹框 -->
- <ProductSeatDialog v-model:visible="showProductSeatDialog" :member-list="employeeList"
- :selected-member-ids="selectedMemberIdsForDialog" @success="handleProductSeatSuccess"
- @close="handleProductSeatDialogClose" />
- </div>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, ref, watch } from 'vue'
- import { storeToRefs } from 'pinia'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { Loading, OfficeBuilding, Plus } from '@element-plus/icons-vue'
- import { useUserInfo } from '/@/stores/userInfo'
- import CreateEnterpriseDialog from '../CreateEnterpriseDialog.vue'
- import AddMemberDialog from '../AddMemberDialog.vue'
- import ProductSeatDialog from '../ProductSeatDialog.vue'
- import { checkCompany, myCompanies, members, removeMember } from '/@/api/frontend/me'
- defineOptions({ name: 'MeEnterprise' })
- type Employee = {
- id: number
- name: string // 企业名称
- email: string // 电子邮箱
- role: number // 角色:1创建人/管理员,2普通成员
- status: number // 状态:1正常,2禁用
- join_time: string // 加入时间
- create_time: string // 创建时间
- role_text: string // 角色文本
- status_text: string // 状态文本
- join_time_formatted: string // 格式化的加入时间
- create_time_formatted: string // 格式化的创建时间
- can_operate: boolean // 是否可以操作(只有创建人可以操作)
- }
- type ProductSeatResult = {
- mode?: 'assign' | 'revoke'
- memberIds: number[]
- }
- const userStore = useUserInfo()
- const { id: userId } = storeToRefs(userStore)
- const loading = ref(false)
- const employeeList = ref<Employee[]>([
- ])
- const showCreateDialog = ref(false)
- const showAddMemberDialog = ref(false)
- const showProductSeatDialog = ref(false)
- const selectedMemberIdsForDialog = ref<number[]>([])
- const currentPage = ref(1)
- const pageSize = ref(10)
- const pageSizes = [10, 20, 30, 50]
- const total = ref(0)
- // 企业数据占位,后续可通过接口填充
- const companyId = ref<number | string | null>(null)
- const companyName = ref('')
- const hasEnterprise = computed(() => {
- const id = companyId.value
- if (id === null || id === undefined) return false
- if (id === '' || id === 0) return false
- return true
- })
- const currentCompanyId: any = computed(() => companyId.value)
- const currentEnterpriseName = computed(() => {
- if (!hasEnterprise.value) return '我的企业'
- if (companyName.value) return companyName.value
- return `企业 (ID: ${companyId.value})`
- })
- const currentUserId = computed(() => userId.value || null)
- // 请求锁,防止重复请求
- const isLoadingList = ref(false)
- // 获取企业列表及员工
- const loadEnterpriseList = async () => {
- if (!hasEnterprise.value) {
- loading.value = false
- employeeList.value = []
- total.value = 0
- return
- }
- // 如果正在加载,直接返回,避免重复请求
- if (isLoadingList.value) {
- return
- }
- isLoadingList.value = true
- loading.value = true
- try {
- // 获取企业成员列表
- const membersRes = await members({
- company_id: companyId.value,
- page: currentPage.value,
- pageSize: pageSize.value
- })
- employeeList.value = membersRes.data?.list || []
- total.value = membersRes.data?.total || 0
- } catch (error: any) {
- // 如果是取消的请求(axios 自动取消重复请求),不显示错误
- // axios 取消的请求通常会有 message 属性包含 'cancel' 或 'Cancel'
- if (error?.message && (error.message.includes('cancel') || error.message.includes('Cancel'))) {
- return
- }
- ElMessage.error('获取成员列表失败')
- employeeList.value = []
- total.value = 0
- } finally {
- loading.value = false
- isLoadingList.value = false
- }
- }
- const handlePageSizeChange = (val: number) => {
- pageSize.value = val
- currentPage.value = 1
- loadEnterpriseList()
- }
- const handleCurrentPageChange = (val: number) => {
- currentPage.value = val
- loadEnterpriseList()
- }
- const handleCreateSuccess = async (data?: any) => {
- // 创建成功后刷新企业信息
- if (data?.id) {
- currentPage.value = 1
- companyId.value = data.id
- companyName.value = data.name || ''
- // companyId 变化会触发 watch,自动加载列表,不需要手动调用
- } else {
- // 如果没有返回数据,重新检查企业信息
- await handleCheckCompany()
- }
- }
- const handleDialogClose = () => {
- showCreateDialog.value = false
- }
- // 创建企业
- const openCreateDialog = () => {
- console.log('openCreateDialog called, showCreateDialog before:', showCreateDialog.value)
- showCreateDialog.value = true
- console.log('showCreateDialog after:', showCreateDialog.value)
- }
- const handleDeleteMember = async (member: Employee) => {
- if (!member?.id) {
- ElMessage.error('成员信息不完整')
- return
- }
- if (!member.can_operate) {
- ElMessage.warning('您没有权限操作此成员')
- return
- }
- try {
- await ElMessageBox.confirm(`确定要删除成员 "${member.name || member.email}" 吗?`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- })
- await removeMember({
- member_id: member.id,
- company_id: companyId.value
- })
- ElMessage.success('删除成员成功')
- loadEnterpriseList()
- } catch (error: any) {
- ElMessage.error('删除成员失败')
- }
- }
- // 检查是否有企业
- const handleCheckCompany = async () => {
- try {
- const res = await checkCompany()
- if (res.code === 1 && res.data?.has_company) {
- // 如果已有企业,获取企业信息
- const companiesRes = await myCompanies({})
- const company = companiesRes.data
- currentPage.value = 1
- companyId.value = company.id
- companyName.value = company.name || ''
- // companyId 变化会触发 watch,自动加载列表,不需要手动调用
- } else {
- // 没有企业,清空数据
- companyId.value = null
- companyName.value = ''
- }
- } catch (error) {
- console.error('检查企业失败:', error)
- ElMessage.error('检查企业状态失败')
- companyId.value = null
- companyName.value = ''
- }
- }
- // 选择分配员工
- const handleOpenProductSeatDialog = () => {
- // 直接打开分配弹窗,不再依赖表格选中
- selectedMemberIdsForDialog.value = []
- showProductSeatDialog.value = true
- }
- const handleProductSeatSuccess = (data: ProductSeatResult) => {
- loadEnterpriseList()
- const count = data?.memberIds?.length || 0
- ElMessage.success(
- data.mode === 'assign'
- ? `成功为 ${count} 位员工分配产品席位`
- : `成功取消 ${count} 位员工的产品席位`
- )
- }
- const handleProductSeatDialogClose = () => {
- showProductSeatDialog.value = false
- selectedMemberIdsForDialog.value = []
- }
- type TagType = 'success' | 'warning' | 'info' | 'danger' | 'primary'
- const getStatusType = (status: number): TagType => {
- const statusMap: Record<number, TagType> = {
- 1: 'success',
- 2: 'danger'
- }
- return statusMap[status] || 'info'
- }
- watch(
- companyId,
- (newVal, oldVal) => {
- // 只在 companyId 从无到有或发生变化时加载
- // 避免初始化时 immediate 触发(因为初始值为 null)
- if (newVal && newVal !== oldVal) {
- loadEnterpriseList()
- } else if (!newVal && oldVal) {
- // 如果企业被清空,清空列表
- employeeList.value = []
- total.value = 0
- }
- }
- )
- onMounted(() => {
- handleCheckCompany()
- })
- </script>
- <style scoped lang="scss">
- .me-enterprise {
- width: 100%;
- .enterprise-header {
- margin-bottom: 20px;
- padding-bottom: 15px;
- border-bottom: 1px solid #e4e7ed;
- .enterprise-title {
- margin: 0;
- font-size: 20px;
- font-weight: 500;
- color: #303133;
- }
- }
- .enterprise-content {
- padding: 20px 0;
- color: #606266;
- }
- .empty-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60px 20px;
- text-align: center;
- .empty-icon {
- font-size: 64px;
- color: #c0c4cc;
- margin-bottom: 20px;
- .empty-icon__icon {
- font-size: 64px;
- }
- }
- .empty-text {
- margin: 0 0 30px 0;
- font-size: 16px;
- color: #909399;
- }
- }
- .loading-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60px 20px;
- text-align: center;
- .loading-icon {
- font-size: 32px;
- color: #409eff;
- margin-bottom: 16px;
- animation: rotating 2s linear infinite;
- }
- p {
- margin: 0;
- font-size: 14px;
- color: #909399;
- }
- }
- .enterprise-list {
- .list-header {
- margin-bottom: 20px;
- padding: 20px 24px;
- background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
- border-radius: 12px;
- border: 1px solid #e4e7ed;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- gap: 20px;
- .enterprise-info {
- display: flex;
- flex-direction: column;
- gap: 12px;
- flex: 1;
- .enterprise-name {
- display: flex;
- align-items: center;
- gap: 8px;
- .enterprise-name-icon {
- font-size: 20px;
- color: #409eff;
- }
- .name-text {
- font-size: 18px;
- font-weight: 600;
- color: #303133;
- }
- }
- .hint-text {
- margin: 0;
- font-size: 13px;
- color: #909399;
- }
- }
- .header-actions {
- flex-shrink: 0;
- }
- }
- .table-section {
- margin-bottom: 20px;
- background: #fff;
- border-radius: 12px;
- overflow: hidden;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
- ::v-deep .el-table {
- border: none;
- &::before {
- display: none;
- }
- .el-table__header-wrapper {
- .el-table__header {
- th {
- border-bottom: 2px solid #f0f2f5;
- border-right: none;
- }
- }
- }
- .el-table__body-wrapper {
- .el-table__body {
- tr {
- td {
- border-bottom: 1px solid #f5f7fa;
- border-right: none;
- }
- &:hover {
- background-color: #fafbfc;
- }
- &:last-child {
- td {
- border-bottom: none;
- }
- }
- }
- }
- }
- }
- }
- .batch-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 20px;
- background: linear-gradient(135deg, #ecf5ff 0%, #e1f3ff 100%);
- border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
- margin-bottom: 20px;
- border: 1px solid #b3d8ff;
- .batch-info {
- font-size: 14px;
- color: #606266;
- strong {
- color: #409eff;
- font-weight: 600;
- }
- }
- .batch-buttons {
- display: flex;
- gap: 12px;
- }
- }
- .pagination-section {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- padding: 16px 20px;
- background: #fff;
- border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
- margin-top: 20px;
- ::v-deep .el-pagination {
- .el-pagination__total {
- color: #606266;
- font-size: 14px;
- font-weight: 500;
- }
- }
- }
- }
- }
- @keyframes rotating {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
- }
- @media (max-width: 768px) {
- .me-enterprise {
- .enterprise-list {
- .list-header {
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
- .hint-text {
- width: 100%;
- }
- .header-actions {
- width: 100%;
- .el-button {
- width: 100%;
- }
- }
- }
- .table-section {
- overflow-x: auto;
- }
- }
- }
- }
- </style>
|