数据权限

master
zzy 2026-04-21 14:23:39 +08:00
parent 435c2f7d2b
commit 66a7790dc7
7 changed files with 104 additions and 94 deletions

View File

@ -73,5 +73,6 @@ export const exportTenant = (params: TenantExportReqVO) => {
} }
export const getSimpleTenantList = () => { export const getSimpleTenantList = () => {
return request.get({ url: '/system/tenant/simple-list' }) return request.get({ url: '/system/tenant/simple-total-list' })
} }

View File

@ -39,6 +39,7 @@
<el-col :span="24" class="px-10px"> <el-col :span="24" class="px-10px">
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantId"> <el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantId">
<el-select <el-select
ref="tenantSelectRef"
v-model="loginData.loginForm.tenantId" v-model="loginData.loginForm.tenantId"
:placeholder="t('login.tenantNamePlaceholder')" :placeholder="t('login.tenantNamePlaceholder')"
:prefix-icon="iconHouse" :prefix-icon="iconHouse"
@ -126,6 +127,7 @@ const loginLoading = ref(false)
const verify = ref() const verify = ref()
const captchaType = ref('blockPuzzle') // blockPuzzle clickWord pictureWord const captchaType = ref('blockPuzzle') // blockPuzzle clickWord pictureWord
const tenantList = ref<LoginApi.TenantInfo[]>([]) const tenantList = ref<LoginApi.TenantInfo[]>([])
const tenantSelectRef = ref()
const isFetchingTenants = ref(false) const isFetchingTenants = ref(false)
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN) const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
@ -197,9 +199,12 @@ const fetchTenantList = async () => {
tenantList.value = res.tenants tenantList.value = res.tenants
// //
if (res.tenants.length > 0) { if (res.tenants.length === 1) {
loginData.loginForm.tenantId = res.tenants[0].tenantId loginData.loginForm.tenantId = res.tenants[0].tenantId
} else {
//
loginData.loginForm.tenantId = undefined
} }
} catch (error) { } catch (error) {
console.error('获取租户列表失败:', error) console.error('获取租户列表失败:', error)
@ -234,12 +239,13 @@ const getCode = async () => {
if (tenantList.value.length === 1) { if (tenantList.value.length === 1) {
loginData.loginForm.tenantId = tenantList.value[0].tenantId loginData.loginForm.tenantId = tenantList.value[0].tenantId
// //
continueLogin()
} else { } else {
// //
if (!loginData.loginForm.tenantId) { if (tenantSelectRef.value) {
message.error('请选择租户') tenantSelectRef.value.visible = true
return
} }
message.info('请选择要登录的租户')
} }
} else if (loginData.tenantEnable === 'true' && tenantList.value.length > 0) { } else if (loginData.tenantEnable === 'true' && tenantList.value.length > 0) {
// //
@ -247,17 +253,24 @@ const getCode = async () => {
message.error('请选择租户') message.error('请选择租户')
return return
} }
}
//
continueLogin()
}
}
//
const continueLogin = () => {
// //
if (loginData.captchaEnable === 'false') { if (loginData.captchaEnable === 'false') {
await handleLogin({}) handleLogin({})
} else { } else {
// //
// //
verify.value.show() verify.value.show()
} }
} }
// ID // ID
const setTenantId = () => { const setTenantId = () => {
if (loginData.loginForm.tenantId) { if (loginData.loginForm.tenantId) {

View File

@ -36,17 +36,6 @@
<el-input v-model="formData.propertyCompany" placeholder="请输入物业名称" /> <el-input v-model="formData.propertyCompany" placeholder="请输入物业名称" />
</el-form-item> </el-form-item>
<!-- 用户名和密码仅新增时显示 -->
<div v-if="formType === 'create'" class="inline-row">
<el-form-item label="用户名" prop="username">
<el-input v-model="formData.username" placeholder="请输入用户名4-30位数字和字母" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" type="password" placeholder="请输入密码4-16位" show-password />
</el-form-item>
</div>
<div class="inline-row"> <div class="inline-row">
<el-form-item label="来访审核" prop="visitAudit"> <el-form-item label="来访审核" prop="visitAudit">
<el-radio-group v-model="formData.visitAudit"> <el-radio-group v-model="formData.visitAudit">
@ -106,8 +95,6 @@ const formData = ref({
longitude: undefined, longitude: undefined,
latitude: undefined, latitude: undefined,
propertyCompany: undefined, propertyCompany: undefined,
username: undefined,
password: undefined,
visitAudit: undefined, visitAudit: undefined,
gateAudit: undefined gateAudit: undefined
}) })
@ -117,14 +104,6 @@ const formRules = reactive({
streetName: [{ required: true, message: '街道名称不能为空', trigger: 'blur' }], streetName: [{ required: true, message: '街道名称不能为空', trigger: 'blur' }],
districtName: [{ required: true, message: '社区名称不能为空', trigger: 'blur' }], districtName: [{ required: true, message: '社区名称不能为空', trigger: 'blur' }],
communityAddress: [{ required: true, message: '小区地址不能为空', trigger: 'blur' }], communityAddress: [{ required: true, message: '小区地址不能为空', trigger: 'blur' }],
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9]{4,30}$/, message: '用户名由 4-30 位数字和字母组成', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{ min: 4, max: 16, message: '密码长度为 4-16 位', trigger: 'blur' }
],
visitAudit: [{ required: true, message: '来访审核不能为空', trigger: 'change' }], visitAudit: [{ required: true, message: '来访审核不能为空', trigger: 'change' }],
gateAudit: [{ required: true, message: '道闸申请不能为空', trigger: 'change' }] gateAudit: [{ required: true, message: '道闸申请不能为空', trigger: 'change' }]
}) })
@ -186,8 +165,6 @@ const resetForm = () => {
longitude: undefined, longitude: undefined,
latitude: undefined, latitude: undefined,
propertyCompany: undefined, propertyCompany: undefined,
username: undefined,
password: undefined,
visitAudit: undefined, visitAudit: undefined,
gateAudit: undefined gateAudit: undefined
} }

View File

@ -28,7 +28,7 @@
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="手机号码" prop="mobile"> <el-form-item label="手机号码" prop="mobile" required>
<el-input v-model="formData.mobile" maxlength="11" placeholder="请输入手机号码" /> <el-input v-model="formData.mobile" maxlength="11" placeholder="请输入手机号码" />
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -139,6 +139,7 @@ const formRules = reactive<FormRules>({
} }
], ],
mobile: [ mobile: [
{ required: true, message: '手机号码不能为空', trigger: 'blur' },
{ {
pattern: /^1[3-9]\d{9}$/, pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码', message: '请输入正确的手机号码',

View File

@ -7,7 +7,7 @@
label-width="100px" label-width="100px"
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile" required>
<el-input v-model="formData.mobile" placeholder="请输入手机号" /> <el-input v-model="formData.mobile" placeholder="请输入手机号" />
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
@ -96,7 +96,14 @@ const formData = ref({
groupId: undefined groupId: undefined
}) })
const formRules = reactive({ const formRules = reactive({
mobile: [{ required: true, message: '手机号不能为空', trigger: 'blur' }], mobile: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
}
],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // Ref const formRef = ref() // Ref

View File

@ -150,9 +150,9 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180" width="180"
/> />
<el-table-column label="操作" align="center" width="240" fixed="right"> <el-table-column label="操作" align="center" width="320" fixed="right">
<template #default="scope"> <template #default="scope">
<div class="flex items-center justify-center"> <div class="action-buttons">
<el-button <el-button
type="primary" type="primary"
link link
@ -368,6 +368,15 @@ onMounted(() => {
} }
} }
.action-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 4px;
padding: 4px 0;
}
:deep(.user-table) { :deep(.user-table) {
border: 1px solid #ebeef5; border: 1px solid #ebeef5;

View File

@ -22,31 +22,30 @@
<el-checkbox-group v-model="checkedTenantIds" class="tenant-list"> <el-checkbox-group v-model="checkedTenantIds" class="tenant-list">
<el-checkbox <el-checkbox
v-for="tenant in filteredAvailableTenants" v-for="tenant in filteredAvailableTenants"
:key="tenant.tenantId" :key="tenant.id"
:label="tenant.tenantId" :label="tenant.id"
:value="tenant.tenantId" :value="tenant.id"
> >
{{ tenant.tenantName }} {{ tenant.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
<div class="middle-panel"> <div class="middle-panel">
<el-button type="primary" :icon="Right" @click="handleAssign" :disabled="checkedTenantIds.length === 0" /> <el-button type="primary" :icon="Right" @click="handleAssign" :disabled="checkedTenantIds.length === 0" />
<el-button type="info" :icon="Right" @click="handleRemove" :disabled="selectedAssignedIds.length === 0" /> <el-button type="info" :icon="Back" @click="handleRemove" :disabled="selectedAssignedIds.length === 0" />
</div> </div>
<div class="right-panel"> <div class="right-panel">
<div class="panel-header">已分配的小区</div> <div class="panel-header">已分配的小区</div>
<el-checkbox-group v-model="selectedAssignedIds" class="tenant-list"> <el-checkbox-group v-model="selectedAssignedIds" class="tenant-list">
<el-checkbox <el-checkbox
v-for="rel in assignedTenants" v-for="tenant in assignedTenantList"
:key="rel.tenantId" :key="tenant.id"
:label="rel.tenantId" :label="tenant.id"
:value="rel.tenantId" :value="tenant.id"
> >
{{ rel.tenantName }} {{ tenant.name }}
<el-tag v-if="rel.isDefault" size="small" type="success" class="ml-5px"></el-tag>
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
@ -60,40 +59,52 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Right } from '@element-plus/icons-vue' import { Right, Back } from '@element-plus/icons-vue'
import * as UserTenantApi from '@/api/system/userTenant' import * as UserTenantApi from '@/api/system/userTenant'
import * as TenantApi from '@/api/system/tenant' import * as TenantApi from '@/api/system/tenant'
import * as UserApi from '@/api/system/user'
import type { UserVO } from '@/api/system/user' import type { UserVO } from '@/api/system/user'
defineOptions({ name: 'UserTenantAssignForm' }) defineOptions({ name: 'UserTenantAssignForm' })
const message = useMessage() const message = useMessage()
const emit = defineEmits(['success'])
const visible = ref(false) const visible = ref(false)
const formRef = ref() const formRef = ref()
const currentUserInfo = ref<UserVO>({} as UserVO) const currentUserInfo = ref<UserVO>({} as UserVO)
const allTenants = ref<any[]>([]) const allTenants = ref<any[]>([])
const assignedTenants = ref<any[]>([]) const assignedTenantList = ref<any[]>([])
const checkedTenantIds = ref<number[]>([]) const checkedTenantIds = ref<number[]>([])
const selectedAssignedIds = ref<number[]>([]) const selectedAssignedIds = ref<number[]>([])
const availableTenantSearch = ref('') const availableTenantSearch = ref('')
const filteredAvailableTenants = computed(() => { const filteredAvailableTenants = computed(() => {
if (!availableTenantSearch.value) { const assignedIds = new Set(assignedTenantList.value.map(t => t.id))
return allTenants.value let available = allTenants.value.filter(t => !assignedIds.has(t.id))
if (availableTenantSearch.value) {
available = available.filter(
(t) => t.name?.toLowerCase().includes(availableTenantSearch.value.toLowerCase())
)
} }
return allTenants.value.filter( return available
(t) => t.tenantName?.toLowerCase().includes(availableTenantSearch.value.toLowerCase())
)
}) })
const open = (user: UserVO) => { const open = async (user: UserVO) => {
visible.value = true visible.value = true
currentUserInfo.value = user
checkedTenantIds.value = [] checkedTenantIds.value = []
selectedAssignedIds.value = [] selectedAssignedIds.value = []
loadTenants()
loadAssignedTenants(user.id) await loadTenants()
if (user.id) {
const userData = await UserApi.getUser(user.id)
currentUserInfo.value = userData
const assignedIds = userData.tenantList || []
assignedTenantList.value = allTenants.value.filter(t => assignedIds.includes(t.id))
}
} }
const loadTenants = async () => { const loadTenants = async () => {
@ -102,58 +113,49 @@ const loadTenants = async () => {
allTenants.value = data || [] allTenants.value = data || []
} catch (error) { } catch (error) {
console.error('加载小区列表失败', error) console.error('加载小区列表失败', error)
message.error('加载小区列表失败')
} }
} }
const loadAssignedTenants = async (userId: number) => { const handleAssign = () => {
try {
const data = await UserTenantApi.getUserTenants(userId)
assignedTenants.value = data || []
} catch (error) {
console.error('加载已分配小区失败', error)
}
}
const handleAssign = async () => {
if (checkedTenantIds.value.length === 0) { if (checkedTenantIds.value.length === 0) {
message.warning('请选择要分配的小区') message.warning('请选择要分配的小区')
return return
} }
try { const selectedTenants = allTenants.value.filter(t => checkedTenantIds.value.includes(t.id))
await UserTenantApi.batchAssignUserToTenants({ assignedTenantList.value.push(...selectedTenants)
userId: currentUserInfo.value.id, checkedTenantIds.value = []
tenantIds: checkedTenantIds.value
})
message.success('分配成功')
checkedTenantIds.value = []
loadAssignedTenants(currentUserInfo.value.id)
} catch (error) {
console.error('分配失败', error)
}
} }
const handleRemove = async () => { const handleRemove = () => {
if (selectedAssignedIds.value.length === 0) { if (selectedAssignedIds.value.length === 0) {
message.warning('请选择要移除的小区') message.warning('请选择要移除的小区')
return return
} }
try { assignedTenantList.value = assignedTenantList.value.filter(
for (const tenantId of selectedAssignedIds.value) { t => !selectedAssignedIds.value.includes(t.id)
await UserTenantApi.removeUserFromTenant(currentUserInfo.value.id, tenantId) )
} selectedAssignedIds.value = []
message.success('移除成功')
selectedAssignedIds.value = []
loadAssignedTenants(currentUserInfo.value.id)
} catch (error) {
console.error('移除失败', error)
}
} }
const handleSubmit = () => { const handleSubmit = async () => {
visible.value = false try {
emit('success') const tenantIds = assignedTenantList.value.map(t => t.id)
await UserTenantApi.batchAssignUserToTenants({
userId: currentUserInfo.value.id,
tenantIds: tenantIds
})
message.success('分配成功')
visible.value = false
emit('success')
} catch (error) {
console.error('保存失败', error)
message.error('保存失败')
}
} }
defineExpose({ open }) defineExpose({ open })