数据权限
parent
80481f1dcb
commit
356298e599
|
|
@ -71,3 +71,7 @@ export const deleteTenantList = (ids: number[]) => {
|
|||
export const exportTenant = (params: TenantExportReqVO) => {
|
||||
return request.download({ url: '/system/tenant/export-excel', params })
|
||||
}
|
||||
|
||||
export const getSimpleTenantList = () => {
|
||||
return request.get({ url: '/system/tenant/simple-list' })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// src/api/system/userTenant/index.ts
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface TenantInfoVO {
|
||||
tenantId: number
|
||||
tenantName: string
|
||||
isDefault: boolean
|
||||
}
|
||||
|
||||
export interface UserTenantRelVO {
|
||||
id: number
|
||||
userId: number
|
||||
tenantId: number
|
||||
tenantName: string
|
||||
isDefault: boolean
|
||||
createTime: Date
|
||||
}
|
||||
|
||||
export const getUserTenants = (userId: number) => {
|
||||
return request.get({ url: '/system/user-tenant/list-by-user', params: { userId } })
|
||||
}
|
||||
|
||||
export const assignUserToTenant = (data: { userId: number; tenantId: number; isDefault?: boolean }) => {
|
||||
return request.post({ url: '/system/user-tenant/assign', data })
|
||||
}
|
||||
|
||||
export const batchAssignUserToTenants = (data: { userId: number; tenantIds: number[] }) => {
|
||||
return request.post({ url: '/system/user-tenant/batch-assign', data })
|
||||
}
|
||||
|
||||
export const removeUserFromTenant = (userId: number, tenantId: number) => {
|
||||
return request.delete({ url: '/system/user-tenant/remove', params: { userId, tenantId } })
|
||||
}
|
||||
|
||||
export const getSimpleTenantList = () => {
|
||||
return request.get({ url: '/system/tenant/simple-list' })
|
||||
}
|
||||
|
|
@ -86,64 +86,6 @@
|
|||
mode="pop"
|
||||
@success="handleLogin"
|
||||
/>
|
||||
<el-col :span="24" class="px-10px">
|
||||
<el-form-item>
|
||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||
<el-col :span="8">
|
||||
<XButton
|
||||
:title="t('login.btnMobile')"
|
||||
class="w-full"
|
||||
@click="setLoginState(LoginStateEnum.MOBILE)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<XButton
|
||||
:title="t('login.btnQRCode')"
|
||||
class="w-full"
|
||||
@click="setLoginState(LoginStateEnum.QR_CODE)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<XButton
|
||||
:title="t('login.btnRegister')"
|
||||
class="w-full"
|
||||
@click="setLoginState(LoginStateEnum.REGISTER)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
||||
<el-col :span="24" class="px-10px">
|
||||
<el-form-item>
|
||||
<div class="w-full flex justify-between">
|
||||
<Icon
|
||||
v-for="(item, key) in socialList"
|
||||
:key="key"
|
||||
:icon="item.icon"
|
||||
:size="30"
|
||||
class="anticon cursor-pointer"
|
||||
color="#999"
|
||||
@click="doSocialLogin(item.type)"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-divider content-position="center">萌新必读</el-divider>
|
||||
<el-col :span="24" class="px-10px">
|
||||
<el-form-item>
|
||||
<div class="w-full flex justify-between">
|
||||
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
||||
<el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
|
||||
<el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">
|
||||
⚡面试手册
|
||||
</el-link>
|
||||
<el-link href="http://static.yudao.iocoder.cn/mp/Aix9975.jpeg" target="_blank">
|
||||
🤝外包咨询
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="小区名称" prop="communityId">
|
||||
<el-select
|
||||
v-model="formData.communityId"
|
||||
placeholder="请选择小区"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
|
|
@ -24,27 +26,32 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="楼号" prop="buildingNo">
|
||||
<el-input v-model="formData.buildingNo" placeholder="请输入楼号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单元号" prop="unitNo">
|
||||
<el-input v-model="formData.unitNo" placeholder="请输入单元号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门牌号" prop="roomNo">
|
||||
<el-input v-model="formData.roomNo" placeholder="请输入门牌号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产权面积(㎡)" prop="propertyArea">
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产权面积" prop="propertyArea">
|
||||
<el-input v-model="formData.propertyArea" placeholder="请输入产权面积(㎡)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业主ID" prop="memberId">
|
||||
<el-input v-model="formData.memberId" placeholder="请输入业主ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业主姓名" prop="ownerName">
|
||||
<el-input v-model="formData.ownerName" placeholder="请输入业主姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业主手机号" prop="ownerPhone">
|
||||
<el-input v-model="formData.ownerPhone" placeholder="请输入业主手机号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
|
|
@ -73,16 +80,14 @@ const formData = ref({
|
|||
buildingNo: undefined,
|
||||
unitNo: undefined,
|
||||
roomNo: undefined,
|
||||
propertyArea: undefined,
|
||||
memberId: undefined,
|
||||
ownerName: undefined,
|
||||
ownerPhone: undefined
|
||||
propertyArea: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
communityId: [{ required: true, message: '小区不能为空', trigger: 'change' }],
|
||||
buildingNo: [{ required: true, message: '楼号不能为空', trigger: 'blur' }],
|
||||
unitNo: [{ required: true, message: '单元号不能为空', trigger: 'blur' }],
|
||||
roomNo: [{ required: true, message: '门牌号不能为空', trigger: 'blur' }]
|
||||
roomNo: [{ required: true, message: '门牌号不能为空', trigger: 'blur' }],
|
||||
propertyArea: [{ required: true, message: '产权面积不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const communityOptions = ref<CommunitySimpleVO[]>([]) // 小区选项列表
|
||||
|
|
@ -161,10 +166,7 @@ const resetForm = () => {
|
|||
buildingNo: undefined,
|
||||
unitNo: undefined,
|
||||
roomNo: undefined,
|
||||
propertyArea: undefined,
|
||||
memberId: undefined,
|
||||
ownerName: undefined,
|
||||
ownerPhone: undefined
|
||||
propertyArea: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,13 @@
|
|||
prop="deptName"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column
|
||||
label="数据权限"
|
||||
align="center"
|
||||
prop="tenantNames"
|
||||
:show-overflow-tooltip="true"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column label="状态" key="status">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
|
|
@ -170,6 +177,14 @@
|
|||
>
|
||||
分配角色
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleAssignPermission(scope.row)"
|
||||
v-hasPermi="['system:user:assign-tenant']"
|
||||
>
|
||||
分配权限
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
|
|
@ -194,10 +209,9 @@
|
|||
|
||||
<!-- 添加或修改用户对话框 -->
|
||||
<UserForm ref="formRef" @success="getList" />
|
||||
<!-- 用户导入对话框 -->
|
||||
<UserImportForm ref="importFormRef" @success="getList" />
|
||||
<!-- 分配角色 -->
|
||||
<UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
|
||||
<UserTenantAssignForm ref="assignPermissionFormRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -210,6 +224,7 @@ import * as UserApi from '@/api/system/user'
|
|||
import UserForm from '../UserForm.vue'
|
||||
import UserImportForm from '../UserImportForm.vue'
|
||||
import UserAssignRoleForm from '../UserAssignRoleForm.vue'
|
||||
import UserTenantAssignForm from './UserTenantAssignForm.vue'
|
||||
|
||||
defineOptions({ name: 'UserManagement' })
|
||||
|
||||
|
|
@ -325,6 +340,11 @@ const handleRole = (row: UserApi.UserVO) => {
|
|||
assignRoleFormRef.value.open(row)
|
||||
}
|
||||
|
||||
const assignPermissionFormRef = ref()
|
||||
const handleAssignPermission = (row: UserApi.UserVO) => {
|
||||
assignPermissionFormRef.value.open(row)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
<!-- src/views/system/user/components/UserTenantAssignForm.vue -->
|
||||
<template>
|
||||
<Dialog v-model="visible" title="分配小区权限" width="800px">
|
||||
<el-form ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item label="用户名称">
|
||||
<el-input v-model="currentUserInfo.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="currentUserInfo.mobile" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="assign-content">
|
||||
<div class="left-panel">
|
||||
<div class="panel-header">可选择的小区</div>
|
||||
<el-input
|
||||
v-model="availableTenantSearch"
|
||||
placeholder="搜索小区"
|
||||
clearable
|
||||
class="search-input"
|
||||
/>
|
||||
<el-checkbox-group v-model="checkedTenantIds" class="tenant-list">
|
||||
<el-checkbox
|
||||
v-for="tenant in filteredAvailableTenants"
|
||||
:key="tenant.tenantId"
|
||||
:label="tenant.tenantId"
|
||||
:value="tenant.tenantId"
|
||||
>
|
||||
{{ tenant.tenantName }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
|
||||
<div class="middle-panel">
|
||||
<el-button type="primary" :icon="Right" @click="handleAssign" :disabled="checkedTenantIds.length === 0" />
|
||||
<el-button type="info" :icon="Right" @click="handleRemove" :disabled="selectedAssignedIds.length === 0" />
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="panel-header">已分配的小区</div>
|
||||
<el-checkbox-group v-model="selectedAssignedIds" class="tenant-list">
|
||||
<el-checkbox
|
||||
v-for="rel in assignedTenants"
|
||||
:key="rel.tenantId"
|
||||
:label="rel.tenantId"
|
||||
:value="rel.tenantId"
|
||||
>
|
||||
{{ rel.tenantName }}
|
||||
<el-tag v-if="rel.isDefault" size="small" type="success" class="ml-5px">默认</el-tag>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Right } from '@element-plus/icons-vue'
|
||||
import * as UserTenantApi from '@/api/system/userTenant'
|
||||
import * as TenantApi from '@/api/system/tenant'
|
||||
import type { UserVO } from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'UserTenantAssignForm' })
|
||||
|
||||
const message = useMessage()
|
||||
const visible = ref(false)
|
||||
const formRef = ref()
|
||||
const currentUserInfo = ref<UserVO>({} as UserVO)
|
||||
|
||||
const allTenants = ref<any[]>([])
|
||||
const assignedTenants = ref<any[]>([])
|
||||
const checkedTenantIds = ref<number[]>([])
|
||||
const selectedAssignedIds = ref<number[]>([])
|
||||
const availableTenantSearch = ref('')
|
||||
|
||||
const filteredAvailableTenants = computed(() => {
|
||||
if (!availableTenantSearch.value) {
|
||||
return allTenants.value
|
||||
}
|
||||
return allTenants.value.filter(
|
||||
(t) => t.tenantName?.toLowerCase().includes(availableTenantSearch.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const open = (user: UserVO) => {
|
||||
visible.value = true
|
||||
currentUserInfo.value = user
|
||||
checkedTenantIds.value = []
|
||||
selectedAssignedIds.value = []
|
||||
loadTenants()
|
||||
loadAssignedTenants(user.id)
|
||||
}
|
||||
|
||||
const loadTenants = async () => {
|
||||
try {
|
||||
const data = await TenantApi.getSimpleTenantList()
|
||||
allTenants.value = data || []
|
||||
} catch (error) {
|
||||
console.error('加载小区列表失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadAssignedTenants = async (userId: number) => {
|
||||
try {
|
||||
const data = await UserTenantApi.getUserTenants(userId)
|
||||
assignedTenants.value = data || []
|
||||
} catch (error) {
|
||||
console.error('加载已分配小区失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (checkedTenantIds.value.length === 0) {
|
||||
message.warning('请选择要分配的小区')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await UserTenantApi.batchAssignUserToTenants({
|
||||
userId: currentUserInfo.value.id,
|
||||
tenantIds: checkedTenantIds.value
|
||||
})
|
||||
message.success('分配成功')
|
||||
checkedTenantIds.value = []
|
||||
loadAssignedTenants(currentUserInfo.value.id)
|
||||
} catch (error) {
|
||||
console.error('分配失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemove = async () => {
|
||||
if (selectedAssignedIds.value.length === 0) {
|
||||
message.warning('请选择要移除的小区')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
for (const tenantId of selectedAssignedIds.value) {
|
||||
await UserTenantApi.removeUserFromTenant(currentUserInfo.value.id, tenantId)
|
||||
}
|
||||
message.success('移除成功')
|
||||
selectedAssignedIds.value = []
|
||||
loadAssignedTenants(currentUserInfo.value.id)
|
||||
} catch (error) {
|
||||
console.error('移除失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.assign-content {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.middle-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 10px;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.tenant-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue