Files
smartDriveEEFront/src/views/lb-business/department-user/index.vue
2026-05-21 09:08:33 +08:00

1107 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-tabs v-model="activeTab" class="department-user-tabs" @tab-click="handleTabClick">
<el-tab-pane label="部门人员列表" name="list">
<el-card class="filter-container" shadow="never">
<el-form :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="姓名">
<el-input v-model="queryParams.name" clearable placeholder="请输入姓名" style="width: 180px" />
</el-form-item>
<el-form-item label="电话">
<el-input v-model="queryParams.phone" clearable placeholder="请输入电话" style="width: 180px" />
</el-form-item>
<el-form-item label="用户ID">
<el-input v-model="queryParams.userId" clearable placeholder="请输入用户ID" style="width: 180px" />
</el-form-item>
<el-form-item label="父级ID">
<el-input v-model="queryParams.parentId" clearable placeholder="请输入父级ID" style="width: 180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-delete" @click="resetQuery">清空</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="action-container" shadow="never">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-date-picker
v-model="syncDateRange"
type="daterange"
range-separator=""
start-placeholder="同步开始日期"
end-placeholder="同步结束日期"
value-format="yyyy-MM-dd"
style="width: 280px; margin-left: 12px"
/>
<el-button
type="warning"
icon="el-icon-refresh"
:loading="syncLoading"
@click="handleSyncFromDailyUserTrade"
>
自动同步
</el-button>
<el-button
type="success"
icon="el-icon-connection"
:loading="syncParentChainLoading"
style="margin-left: 12px"
@click="handleSyncFromLbUser"
>
构建父链
</el-button>
</el-card>
<el-card class="table-container" shadow="never">
<el-table v-loading="loading" :data="list" style="width: 100%">
<el-table-column label="用户ID" prop="userId" min-width="70" show-overflow-tooltip />
<el-table-column label="姓名" prop="name" min-width="84" show-overflow-tooltip />
<el-table-column label="电话" prop="phone" min-width="84" show-overflow-tooltip />
<el-table-column label="加入日期" prop="joinDate" min-width="120">
<template slot-scope="scope">
{{ scope.row.joinDate || '-' }}
</template>
</el-table-column>
<el-table-column label="组织链" prop="orgChain" min-width="440" class-name="org-chain-column">
<template slot-scope="scope">
<div class="org-chain-cell">{{ scope.row.orgChain || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="更新时间" prop="updateTime" min-width="170">
<template slot-scope="scope">
{{ formatDateTime(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="220">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" @click="handleFindParent(scope.row)">找上级</el-button>
<el-button type="text" style="color:#F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="queryParams.current"
:page-size="queryParams.size"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</el-tab-pane>
<el-tab-pane label="催办招商" name="urge">
<el-card class="filter-container" shadow="never">
<el-form :model="urgeTreeQueryParams" :inline="true" label-width="100px">
<el-form-item label="是否活跃">
<el-select
v-model="urgeTreeQueryParams.isActive"
clearable
placeholder="全部"
style="width: 140px"
>
<el-option label="活跃" :value="1" />
<el-option label="不活跃" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="晚于加入日期">
<el-date-picker
v-model="urgeTreeQueryParams.joinDate"
type="date"
clearable
value-format="yyyy-MM-dd"
placeholder="查询加入日期大于该日期的数据"
style="width: 280px"
/>
</el-form-item>
<el-form-item label="晚于最后成交日期">
<el-date-picker
v-model="urgeTreeQueryParams.lastTradeDate"
type="date"
clearable
value-format="yyyy-MM-dd"
placeholder="查询最后成交日期大于该日期的数据"
style="width: 280px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" :loading="urgeTreeLoading" @click="handleUrgeTreeQuery">
查询
</el-button>
<el-button icon="el-icon-delete" @click="resetUrgeTreeQuery">清空</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="urge-tree-panel">
<div class="urge-tree-toolbar">
<el-button
type="primary"
icon="el-icon-refresh"
:loading="urgeTreeLoading"
@click="fetchUrgeTree"
>
刷新
</el-button>
<el-button
type="success"
icon="el-icon-download"
:loading="urgeTreeExportLoading"
:disabled="!urgeTreeCheckedUserIds.length"
@click="handleUrgeTreeExport"
>
导出
</el-button>
<el-button
icon="el-icon-close"
:disabled="!urgeTreeData.length || !urgeTreeCheckedUserIds.length"
@click="handleUrgeTreeUncheckAll"
>
取消勾选
</el-button>
<span v-if="urgeTreeCheckedUserIds.length" class="urge-tree-selected-tip">
已选 {{ urgeTreeCheckedUserIds.length }}
</span>
</div>
<el-alert
:title="`共 ${urgeTreeTotal} 人`"
type="info"
:closable="false"
show-icon
class="urge-tree-alert"
/>
<el-input
v-model="urgeTreeFilterText"
clearable
placeholder="搜索姓名、用户ID、电话、加入日期、最后成交日期"
prefix-icon="el-icon-search"
class="urge-tree-filter"
/>
<div v-loading="urgeTreeLoading" class="urge-tree-wrap">
<el-tree
v-if="urgeTreeData.length"
ref="urgeTree"
:data="urgeTreeData"
:props="urgeTreeProps"
:filter-node-method="filterUrgeTreeNode"
node-key="userId"
show-checkbox
check-strictly
highlight-current
@check="handleUrgeTreeCheck"
>
<span
slot-scope="{ data }"
class="urge-tree-node"
:class="{
'urge-tree-node--alert': isUrgeTreeLeafOverdue(data),
'urge-tree-node--checked': isUrgeTreeNodeChecked(data.userId)
}"
>
<span class="urge-tree-node-name">{{ data.name || '-' }}</span>
<span class="urge-tree-node-meta">用户ID{{ data.userId || '-' }}</span>
<span class="urge-tree-node-meta">电话{{ data.phone || '-' }}</span>
<span class="urge-tree-node-meta">加入日期{{ data.joinDate || '-' }}</span>
<span class="urge-tree-node-meta">最后成交日期{{ data.lastTradeDate || '-' }}</span>
</span>
</el-tree>
<el-empty v-else description="暂无数据,请点击刷新加载" />
</div>
</el-card>
</el-tab-pane>
</el-tabs>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="760px" @close="resetForm">
<el-form ref="dataForm" :model="form" :rules="formRules" label-width="120px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="租户ID" prop="tenantId">
<el-input v-model="form.tenantId" placeholder="请输入租户ID" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户ID" prop="userId">
<el-input v-model="form.userId" placeholder="请输入用户ID" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="父级ID" prop="parentId">
<el-input v-model="form.parentId" placeholder="请输入父级ID" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="行业" prop="industry">
<el-input v-model="form.industry" placeholder="请输入行业" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="地址" prop="address">
<el-input v-model="form.address" type="textarea" :rows="2" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="加入日期" prop="joinDate">
<el-date-picker
v-model="form.joinDate"
type="date"
clearable
value-format="yyyy-MM-dd"
placeholder="请选择加入日期"
style="width: 100%"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<el-dialog title="找上级 - 人员列表" :visible.sync="parentDialogVisible" width="980px" @close="resetParentDialog">
<el-form :model="parentQueryParams" :inline="true" label-width="70px">
<el-form-item label="姓名">
<el-input v-model="parentQueryParams.name" clearable placeholder="请输入姓名" style="width: 160px" />
</el-form-item>
<el-form-item label="电话">
<el-input v-model="parentQueryParams.phone" clearable placeholder="请输入电话" style="width: 160px" />
</el-form-item>
<el-form-item label="行业">
<el-input v-model="parentQueryParams.industry" clearable placeholder="请输入行业" style="width: 160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleParentQuery">查询</el-button>
<el-button icon="el-icon-delete" @click="resetParentQuery">清空</el-button>
</el-form-item>
</el-form>
<el-alert
v-if="currentParentTarget && currentParentTarget.name"
:title="`当前记录:${currentParentTarget.name}${currentParentTarget.id || '-'}`"
type="info"
:closable="false"
show-icon
class="parent-alert"
/>
<el-table
ref="parentTable"
v-loading="parentLoading"
:data="parentList"
row-key="id"
style="width: 100%"
@selection-change="handleParentSelectionChange"
>
<el-table-column type="selection" width="35" />
<el-table-column label="用户ID" prop="userId" min-width="180" show-overflow-tooltip />
<el-table-column label="姓名" prop="name" min-width="120" />
<el-table-column label="电话" prop="phone" min-width="120" />
</el-table>
<el-pagination
:current-page="parentQueryParams.current"
:page-size="parentQueryParams.size"
:page-sizes="[10, 20, 50, 100]"
:total="parentTotal"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleParentSizeChange"
@current-change="handleParentCurrentChange"
/>
<div slot="footer" class="dialog-footer">
<span class="selected-tip">已选 {{ parentSelectedRows.length }} </span>
<el-button @click="parentDialogVisible = false">关闭</el-button>
<el-button type="primary" :loading="parentBindLoading" @click="confirmParentSelection">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addLbDepartmentUser,
bindLbDepartmentUserParent,
deleteLbDepartmentUser,
exportLbDepartmentUsers,
getLbDepartmentUserList,
getLbDepartmentUserTree,
syncLbDepartmentUserFromDailyUserTrade,
syncLbDepartmentUserFromLbUser,
updateLbDepartmentUser
} from '@/api/lb-department-user'
import { getBusinessHeaders } from '@/utils/business-headers'
function getDefaultUrgeTreeQueryParams() {
return {
isActive: 1,
lastTradeDate: '2026-04-26'
}
}
export default {
name: 'LbDepartmentUser',
data() {
return {
activeTab: 'list',
loading: false,
submitLoading: false,
syncLoading: false,
syncParentChainLoading: false,
urgeTreeLoading: false,
urgeTreeExportLoading: false,
urgeTreeData: [],
urgeTreeTotal: 0,
urgeTreeFilterText: '',
urgeTreeCheckedUserIds: [],
urgeTreeSyncingChecks: false,
urgeTreeQueryParams: getDefaultUrgeTreeQueryParams(),
urgeTreeProps: {
children: 'children',
label: 'name'
},
parentBindLoading: false,
list: [],
total: 0,
dialogVisible: false,
parentDialogVisible: false,
parentLoading: false,
dialogTitle: '',
isEdit: false,
currentParentTarget: null,
parentList: [],
parentTotal: 0,
parentSelectedRows: [],
lastSelectedParentRow: null,
syncDateRange: [],
queryParams: {
current: 1,
size: 10,
userId: '',
parentId: '',
name: '',
phone: ''
},
parentQueryParams: {
current: 1,
size: 10,
userId: '',
parentId: '',
name: '',
phone: '',
industry: ''
},
form: {
id: '',
tenantId: '',
userId: '',
parentId: '',
name: '',
phone: '',
address: '',
industry: '',
joinDate: ''
},
formRules: {
tenantId: [{ required: true, message: '请输入租户ID', trigger: 'blur' }],
userId: [{ required: true, message: '请输入用户ID', trigger: 'blur' }],
parentId: [{ required: true, message: '请输入父级ID', trigger: 'blur' }],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
}
}
},
watch: {
urgeTreeFilterText(val) {
this.$nextTick(() => {
if (this.$refs.urgeTree) {
this.$refs.urgeTree.filter(val)
}
})
}
},
created() {
this.fetchList()
},
methods: {
fetchList() {
this.loading = true
getLbDepartmentUserList(this.buildListParams())
.then((res) => {
if (res && (res.code === 20000 || res.code === 200 || res.success === true)) {
this.list = Array.isArray(res.data) ? res.data : []
this.total = typeof res.total === 'number'
? res.total
: (res.page && typeof res.page.total === 'number' ? res.page.total : this.list.length)
} else {
this.list = []
this.total = 0
}
})
.catch(() => {
this.list = []
this.total = 0
})
.finally(() => {
this.loading = false
})
},
buildListParams() {
const params = {
current: this.queryParams.current,
size: this.queryParams.size,
userId: this.queryParams.userId || undefined,
parentId: this.queryParams.parentId || undefined,
name: this.queryParams.name || undefined,
phone: this.queryParams.phone || undefined
}
Object.keys(params).forEach((key) => {
if (params[key] === undefined || params[key] === '') delete params[key]
})
return params
},
handleQuery() {
this.queryParams.current = 1
this.fetchList()
},
resetQuery() {
this.queryParams = {
current: 1,
size: 10,
userId: '',
parentId: '',
name: '',
phone: ''
}
this.fetchList()
},
handleSizeChange(size) {
this.queryParams.size = size
this.fetchList()
},
handleCurrentChange(current) {
this.queryParams.current = current
this.fetchList()
},
handleAdd() {
this.dialogTitle = '新增部门人员'
this.isEdit = false
this.resetFormData()
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.dataForm && this.$refs.dataForm.clearValidate()
})
},
getTenantIdFromBusinessHeaders() {
const headers = getBusinessHeaders()
const value = headers && headers['X-Tenant-Id']
return value ? String(value).trim() : ''
},
handleSyncFromDailyUserTrade() {
if (!Array.isArray(this.syncDateRange) || this.syncDateRange.length !== 2) {
this.$message.warning('请选择同步开始日期和结束日期')
return
}
const [startDate, endDate] = this.syncDateRange
if (!startDate || !endDate) {
this.$message.warning('请选择同步开始日期和结束日期')
return
}
this.syncLoading = true
syncLbDepartmentUserFromDailyUserTrade({ startDate, endDate })
.then((res) => {
const isSuccess = res && (res.success === true || res.code === 200 || res.code === 20000)
if (!isSuccess) {
this.$message.error((res && res.message) || '自动同步失败')
return
}
this.$message.success((res && res.message) || '自动同步成功')
this.fetchList()
})
.finally(() => {
this.syncLoading = false
})
},
handleSyncFromLbUser() {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
this.syncParentChainLoading = true
syncLbDepartmentUserFromLbUser({ tenantId })
.then((res) => {
const isSuccess = res && (res.success === true || res.code === 200 || res.code === 20000)
if (!isSuccess) {
this.$message.error((res && res.message) || '构建父链失败')
return
}
this.$message.success((res && res.message) || '构建父链成功')
this.fetchList()
})
.finally(() => {
this.syncParentChainLoading = false
})
},
handleTabClick(tab) {
if (!tab || tab.name !== 'urge') return
this.fetchUrgeTree()
},
buildUrgeTreeParams() {
const tenantId = this.getTenantIdFromBusinessHeaders()
const params = { tenantId }
const { isActive, joinDate, lastTradeDate } = this.urgeTreeQueryParams
if (isActive === 0 || isActive === 1) {
params.is_active = isActive
}
if (joinDate) {
params.join_date = joinDate
}
if (lastTradeDate) {
params.last_trade_date = lastTradeDate
}
return params
},
handleUrgeTreeQuery() {
this.fetchUrgeTree()
},
resetUrgeTreeQuery() {
this.urgeTreeQueryParams = getDefaultUrgeTreeQueryParams()
this.fetchUrgeTree()
},
fetchUrgeTree() {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
this.urgeTreeLoading = true
getLbDepartmentUserTree(this.buildUrgeTreeParams())
.then((res) => {
const hasData = res && Array.isArray(res.data)
const isSuccess = res && (res.success === true || res.code === 200 || res.code === 20000 || hasData)
if (!isSuccess) {
this.$message.error((res && res.message) || '加载部门用户树失败')
return
}
this.urgeTreeData = hasData ? res.data : []
this.urgeTreeTotal = typeof res.total === 'number' ? res.total : this.urgeTreeData.length
this.urgeTreeFilterText = ''
this.applyUrgeTreeOverdueChecks()
if (res && res.message) {
this.$message.success(res.message)
}
})
.catch(() => {
this.$message.error('加载部门用户树失败')
})
.finally(() => {
this.urgeTreeLoading = false
})
},
filterUrgeTreeNode(value, data) {
if (!value) return true
const keyword = String(value).trim().toLowerCase()
if (!keyword) return true
const fields = [data.name, data.userId, data.phone, data.joinDate, data.lastTradeDate]
return fields.some((field) => field != null && String(field).toLowerCase().includes(keyword))
},
isUrgeTreeLeafNode(data) {
return !data.children || !data.children.length
},
isJoinDateOverOneMonth(joinDate) {
if (!joinDate) return false
const join = new Date(String(joinDate).replace(/T.*$/, ''))
if (Number.isNaN(join.getTime())) return false
const oneMonthAgo = new Date()
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1)
oneMonthAgo.setHours(0, 0, 0, 0)
join.setHours(0, 0, 0, 0)
return join < oneMonthAgo
},
isUrgeTreeLeafOverdue(data) {
return this.isUrgeTreeLeafNode(data) && this.isJoinDateOverOneMonth(data.joinDate)
},
collectUrgeTreeOverdueLeafUserIds(nodes, result = []) {
if (!nodes || !nodes.length) return result
nodes.forEach((node) => {
if (this.isUrgeTreeLeafOverdue(node) && node.userId) {
result.push(node.userId)
}
if (node.children && node.children.length) {
this.collectUrgeTreeOverdueLeafUserIds(node.children, result)
}
})
return result
},
normalizeUrgeTreeUserId(userId) {
return userId == null || userId === '' ? '' : String(userId)
},
isUrgeTreeKeyInCheckedList(userId, checkedKeys) {
const key = this.normalizeUrgeTreeUserId(userId)
if (!key) return false
return (checkedKeys || []).some((k) => this.normalizeUrgeTreeUserId(k) === key)
},
syncUrgeTreeCheckedFromTree() {
const tree = this.$refs.urgeTree
if (!tree) return
this.urgeTreeCheckedUserIds = tree.getCheckedKeys().map((k) => this.normalizeUrgeTreeUserId(k))
},
applyUrgeTreeOverdueChecks() {
const overdueIds = this.collectUrgeTreeOverdueLeafUserIds(this.urgeTreeData)
this.urgeTreeSyncingChecks = true
this.$nextTick(() => {
const tree = this.$refs.urgeTree
if (tree) {
tree.setCheckedKeys([...new Set(overdueIds)])
tree.filter(this.urgeTreeFilterText || '')
this.syncUrgeTreeCheckedFromTree()
}
this.$nextTick(() => {
this.urgeTreeSyncingChecks = false
})
})
},
isUrgeTreeNodeChecked(userId) {
return this.isUrgeTreeKeyInCheckedList(userId, this.urgeTreeCheckedUserIds)
},
handleUrgeTreeCheck() {
if (this.urgeTreeSyncingChecks) return
this.syncUrgeTreeCheckedFromTree()
},
handleUrgeTreeUncheckAll() {
const tree = this.$refs.urgeTree
if (!tree) return
this.urgeTreeSyncingChecks = true
tree.setCheckedKeys([])
this.urgeTreeCheckedUserIds = []
this.$nextTick(() => {
this.urgeTreeSyncingChecks = false
})
},
handleUrgeTreeExport() {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
this.syncUrgeTreeCheckedFromTree()
const userIds = [...new Set(this.urgeTreeCheckedUserIds.filter((id) => id))]
if (!userIds.length) {
this.$message.warning('请先勾选要导出的用户')
return
}
this.urgeTreeExportLoading = true
exportLbDepartmentUsers(tenantId, userIds)
.then((blob) => {
const safeBlob = blob instanceof Blob ? blob : new Blob([blob], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(safeBlob)
const link = document.createElement('a')
link.href = url
link.download = `lbDepartmentUser_export_${tenantId}_${Date.now()}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
this.$message.success('导出成功')
})
.catch(() => {
this.$message.error('导出失败')
})
.finally(() => {
this.urgeTreeExportLoading = false
})
},
handleEdit(row) {
this.dialogTitle = '编辑部门人员'
this.isEdit = true
this.form = {
id: row.id || '',
tenantId: row.tenantId || '',
userId: row.userId || '',
parentId: row.parentId || '',
name: row.name || '',
phone: row.phone || '',
address: row.address || '',
industry: row.industry || '',
joinDate: row.joinDate || ''
}
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.dataForm && this.$refs.dataForm.clearValidate()
})
},
handleDelete(row) {
this.$confirm('确定删除这条部门人员记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return deleteLbDepartmentUser(row.id)
}).then((res) => {
this.$message.success((res && res.message) || '删除成功')
this.fetchList()
}).catch(() => {})
},
handleFindParent(row) {
this.currentParentTarget = row
this.parentDialogVisible = true
this.parentSelectedRows = []
this.parentQueryParams = {
current: 1,
size: 10,
userId: '',
parentId: '',
name: '',
phone: '',
industry: ''
}
this.fetchParentList()
},
fetchParentList() {
this.parentLoading = true
const params = this.buildParentListParams()
getLbDepartmentUserList(params)
.then((res) => {
if (res && (res.code === 20000 || res.code === 200 || res.success === true)) {
this.parentList = Array.isArray(res.data) ? res.data : []
this.parentTotal = typeof res.total === 'number'
? res.total
: (res.page && typeof res.page.total === 'number' ? res.page.total : this.parentList.length)
} else {
this.parentList = []
this.parentTotal = 0
}
})
.catch(() => {
this.parentList = []
this.parentTotal = 0
})
.finally(() => {
this.parentLoading = false
})
},
buildParentListParams() {
const params = {
current: this.parentQueryParams.current,
size: this.parentQueryParams.size,
userId: this.parentQueryParams.userId || undefined,
parentId: this.parentQueryParams.parentId || undefined,
name: this.parentQueryParams.name || undefined,
phone: this.parentQueryParams.phone || undefined,
industry: this.parentQueryParams.industry || undefined
}
Object.keys(params).forEach((key) => {
if (params[key] === undefined || params[key] === '') delete params[key]
})
return params
},
handleParentSelectionChange(selection) {
if (!selection.length) {
this.parentSelectedRows = []
this.lastSelectedParentRow = null
return
}
const latestSelected = selection[selection.length - 1]
if (selection.length > 1 && this.$refs.parentTable) {
this.$refs.parentTable.clearSelection()
this.$refs.parentTable.toggleRowSelection(latestSelected, true)
}
this.parentSelectedRows = [latestSelected]
this.lastSelectedParentRow = latestSelected
},
handleParentQuery() {
this.parentQueryParams.current = 1
this.fetchParentList()
},
resetParentQuery() {
this.parentQueryParams = {
current: 1,
size: 10,
userId: '',
parentId: '',
name: '',
phone: '',
industry: ''
}
this.fetchParentList()
},
handleParentSizeChange(size) {
this.parentQueryParams.size = size
this.fetchParentList()
},
handleParentCurrentChange(current) {
this.parentQueryParams.current = current
this.fetchParentList()
},
confirmParentSelection() {
if (!this.parentSelectedRows.length) {
this.$message.warning('请先勾选上级人员')
return
}
if (this.parentSelectedRows.length > 1) {
this.$message.warning('一次只能选择 1 位上级人员')
return
}
if (!this.currentParentTarget || !this.currentParentTarget.userId) {
this.$message.error('未获取到当前子用户信息,请重新打开弹框后重试')
return
}
const selectedParent = this.parentSelectedRows[0]
if (!selectedParent || !selectedParent.userId) {
this.$message.error('未获取到上级用户ID请重新选择')
return
}
const tenantId = this.currentParentTarget.tenantId || selectedParent.tenantId || ''
if (!tenantId) {
this.$message.error('未获取到租户ID无法绑定')
return
}
this.parentBindLoading = true
bindLbDepartmentUserParent({
parentUserId: selectedParent.userId,
parentUserName: selectedParent.name || undefined,
childUserId: this.currentParentTarget.userId,
childUserName: this.currentParentTarget.name || undefined,
tenantId
}).then((res) => {
this.$message.success((res && res.message) || '绑定上级成功')
this.parentDialogVisible = false
this.fetchList()
}).finally(() => {
this.parentBindLoading = false
})
},
resetParentDialog() {
this.parentList = []
this.parentTotal = 0
this.parentSelectedRows = []
this.lastSelectedParentRow = null
this.parentBindLoading = false
this.currentParentTarget = null
this.parentQueryParams = {
current: 1,
size: 10,
userId: '',
parentId: '',
name: '',
phone: '',
industry: ''
}
},
handleSubmit() {
this.$refs.dataForm.validate((valid) => {
if (!valid) return
const payload = this.buildPayload()
this.submitLoading = true
const req = this.isEdit ? updateLbDepartmentUser(payload) : addLbDepartmentUser(payload)
req.then((res) => {
this.$message.success((res && res.message) || (this.isEdit ? '更新成功' : '新增成功'))
this.dialogVisible = false
this.fetchList()
}).finally(() => {
this.submitLoading = false
})
})
},
buildPayload() {
const payload = {
tenantId: this.form.tenantId,
userId: this.form.userId,
parentId: this.form.parentId,
name: this.form.name,
phone: this.form.phone || undefined,
address: this.form.address || undefined,
industry: this.form.industry || undefined,
joinDate: this.form.joinDate || undefined
}
if (this.isEdit) payload.id = this.form.id
Object.keys(payload).forEach((key) => {
if (payload[key] === undefined || payload[key] === '') delete payload[key]
})
return payload
},
resetForm() {
this.$refs.dataForm && this.$refs.dataForm.resetFields()
this.resetFormData()
},
resetFormData() {
this.form = {
id: '',
tenantId: '',
userId: '',
parentId: '',
name: '',
phone: '',
address: '',
industry: '',
joinDate: ''
}
},
formatDateTime(dateTime) {
if (!dateTime) return '-'
const date = new Date(String(dateTime).replace('T', ' '))
if (Number.isNaN(date.getTime())) return String(dateTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
.department-user-tabs {
::v-deep .el-tabs__header {
margin-bottom: 16px;
}
}
.urge-tree-panel {
.urge-tree-toolbar {
margin-bottom: 12px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.urge-tree-selected-tip {
color: #606266;
font-size: 14px;
}
}
.filter-container {
margin-bottom: 20px;
}
.action-container {
margin-bottom: 20px;
}
.table-container {
.el-pagination {
margin-top: 20px;
text-align: right;
}
::v-deep .org-chain-column .cell {
padding: 8px 10px;
}
.org-chain-cell {
display: block;
width: 100%;
height: 32px;
line-height: 30px;
padding: 0 12px;
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
color: #606266;
font-size: 14px;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
box-sizing: border-box;
cursor: default;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: #c0c4cc;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background-color: #f5f7fa;
}
}
}
.parent-alert {
margin-bottom: 12px;
}
.urge-tree-alert {
margin-bottom: 12px;
}
.urge-tree-filter {
margin-bottom: 12px;
}
.urge-tree-wrap {
max-height: calc(100vh - 320px);
min-height: 400px;
overflow: auto;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px;
}
.urge-tree-node {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
font-size: 14px;
line-height: 24px;
}
.urge-tree-node-name {
font-weight: 500;
color: #303133;
}
.urge-tree-node-meta {
color: #909399;
font-size: 13px;
}
.urge-tree-node--alert,
.urge-tree-node--checked {
.urge-tree-node-name,
.urge-tree-node-meta {
color: #f56c6c;
}
.urge-tree-node-name {
font-weight: 600;
}
}
.selected-tip {
float: left;
color: #606266;
}
}
</style>