749 lines
23 KiB
Vue
749 lines
23 KiB
Vue
<!-- 组织管理 -->
|
|
<!-- eslint-disable vue/no-v-model-argument -->
|
|
<template>
|
|
<div class="page-container">
|
|
<section class="panel">
|
|
<div class="panel-top">
|
|
<h2>组织管理</h2>
|
|
</div>
|
|
|
|
<div class="filter-block">
|
|
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
|
<div class="filter-grid">
|
|
<el-form-item label="关键词">
|
|
<el-input v-model="searchForm.keyword" placeholder="组织名称" clearable />
|
|
</el-form-item>
|
|
</div>
|
|
<div class="filter-actions-row">
|
|
<span class="filter-hint">按组织名称模糊查询。</span>
|
|
<div class="filter-actions-end">
|
|
<el-button class="btn-ghost" @click="handleReset"
|
|
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
|
>
|
|
<el-button class="btn-main" type="primary" @click="handleSearch"
|
|
><el-icon><Search /></el-icon>搜索</el-button
|
|
>
|
|
</div>
|
|
</div>
|
|
</el-form>
|
|
</div>
|
|
|
|
<div class="list-toolbar">
|
|
<div class="list-toolbar-row">
|
|
<div class="list-toolbar-selected">
|
|
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
|
</div>
|
|
<el-button class="btn-main" type="primary" @click="handleAdd">
|
|
<el-icon><Plus /></el-icon>
|
|
新增
|
|
</el-button>
|
|
<el-button class="btn-ghost" @click="toggleExpand">
|
|
{{ isExpandAll ? '全部折叠' : '全部展开' }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="list-body list-wrap">
|
|
<el-table
|
|
ref="tableRef"
|
|
class="list-table"
|
|
:data="tableData"
|
|
v-loading="loading"
|
|
stripe
|
|
row-key="id"
|
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
|
:default-expand-all="isExpandAll"
|
|
@selection-change="handleSelectionChange"
|
|
@row-dblclick="handleView"
|
|
>
|
|
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
|
<el-table-column
|
|
prop="deptName"
|
|
label="组织名称"
|
|
min-width="140"
|
|
align="left"
|
|
header-align="left"
|
|
/>
|
|
<el-table-column
|
|
prop="deptCategoryCode"
|
|
label="组织类别"
|
|
min-width="120"
|
|
align="center"
|
|
header-align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
{{ getDeptCategoryLabel(row.deptCategoryCode) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
prop="status"
|
|
label="状态"
|
|
min-width="80"
|
|
align="center"
|
|
header-align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag :type="dictStore.getTagType('sys_status', row.status)" size="small">
|
|
{{ dictStore.getLabel('sys_status', row.status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
prop="leader"
|
|
label="负责人"
|
|
min-width="160"
|
|
align="center"
|
|
header-align="center"
|
|
/>
|
|
<el-table-column
|
|
prop="leaderPhone"
|
|
label="联系电话"
|
|
min-width="180"
|
|
align="center"
|
|
header-align="center"
|
|
/>
|
|
<el-table-column
|
|
label="操作"
|
|
min-width="160"
|
|
fixed="right"
|
|
align="center"
|
|
header-align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<div style="display: flex; justify-content: center; gap: 8px">
|
|
<el-button class="btn-op-item primary" @click="handleAddChild(row)">
|
|
新增子组织
|
|
</el-button>
|
|
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
|
<el-button class="btn-op-item">
|
|
<el-icon><More /></el-icon>
|
|
</el-button>
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item command="view">
|
|
<el-icon><View /></el-icon>查看
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="edit">
|
|
<el-icon><Edit /></el-icon>编辑
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="delete" divided>
|
|
<el-icon><Delete /></el-icon>删除
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
|
|
<el-dialog
|
|
v-model="dialogVisible"
|
|
:title="dialogTitle"
|
|
width="560px"
|
|
class="dialog-form-layout"
|
|
align-center
|
|
destroy-on-close
|
|
:close-on-click-modal="false"
|
|
@close="handleDialogClose"
|
|
>
|
|
<el-form
|
|
ref="formRef"
|
|
:model="formData"
|
|
:rules="formRules"
|
|
label-position="top"
|
|
class="dept-dialog-form"
|
|
scroll-to-error
|
|
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
|
>
|
|
<div class="form-grid-2">
|
|
<el-form-item label="上级组织" style="grid-column: 1 / -1">
|
|
<el-tree-select
|
|
v-model="parentSelectId"
|
|
:data="deptTreeSelectData"
|
|
node-key="value"
|
|
:props="{ label: 'label', children: 'children' }"
|
|
check-strictly
|
|
filterable
|
|
clearable
|
|
:disabled="isEdit"
|
|
placeholder="请选择上级组织(留空为顶级)"
|
|
class="w-full"
|
|
>
|
|
<template #label="{ value }">
|
|
{{ formatDeptPathLabel(value) }}
|
|
</template>
|
|
</el-tree-select>
|
|
</el-form-item>
|
|
<el-form-item label="组织名称" prop="deptName" style="grid-column: 1 / -1">
|
|
<el-input v-model="formData.deptName" placeholder="请输入组织名称" />
|
|
</el-form-item>
|
|
<el-form-item label="组织类别">
|
|
<el-select v-model="formData.deptCategoryCode" placeholder="请选择组织类别" clearable>
|
|
<el-option
|
|
v-for="opt in deptCategoryOptions"
|
|
:key="opt.value"
|
|
:label="opt.label"
|
|
:value="opt.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="状态">
|
|
<el-select v-model="formData.status" placeholder="请选择状态">
|
|
<el-option
|
|
v-for="opt in statusOptions"
|
|
:key="opt.value"
|
|
:label="opt.label"
|
|
:value="opt.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="负责人">
|
|
<el-select
|
|
v-model="leaderUserId"
|
|
class="w-full"
|
|
filterable
|
|
remote
|
|
clearable
|
|
reserve-keyword
|
|
:remote-method="remoteSearchLeaderUsers"
|
|
:loading="leaderUserLoading"
|
|
placeholder="输入关键词检索用户"
|
|
@change="onLeaderUserChange"
|
|
>
|
|
<el-option
|
|
v-for="u in leaderUserOptions"
|
|
:key="u.id"
|
|
:label="formatLeaderUserLabel(u)"
|
|
:value="u.id"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="联系电话">
|
|
<el-input
|
|
v-model="formData.leaderPhone"
|
|
placeholder="可选,选择负责人后自动填入,可修改"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="备注" style="grid-column: 1 / -1">
|
|
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
|
|
</el-form-item>
|
|
</div>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button class="btn-ghost" @click="dialogVisible = false">
|
|
<el-icon><Close /></el-icon>
|
|
取消
|
|
</el-button>
|
|
<el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
|
|
<el-icon><Check /></el-icon>
|
|
确定
|
|
</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<SystemViewDialog
|
|
v-model:visible="viewVisible"
|
|
:title="viewRow?.deptName || '组织详情'"
|
|
:icon="OfficeBuilding"
|
|
:meta="`${viewRow?.leader ? '负责人:' + viewRow.leader : ''}${viewRow?.leader && viewRow?.createTime ? ' · ' : ''}${viewRow?.createTime || ''}`"
|
|
:sections="viewSections"
|
|
/>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import {
|
|
Plus,
|
|
Close,
|
|
Check,
|
|
View,
|
|
Edit,
|
|
Delete,
|
|
More,
|
|
OfficeBuilding,
|
|
RefreshLeft,
|
|
Search
|
|
} from '@element-plus/icons-vue'
|
|
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
|
import {
|
|
fetchDeptTree,
|
|
fetchCreateDept,
|
|
fetchUpdateDept,
|
|
fetchDeleteDept,
|
|
fetchDictDataOptions,
|
|
fetchUserList
|
|
} from '@/api/admin'
|
|
import { buildTreePathLabelFromFlat } from '@/utils/tree-path'
|
|
import { useDictStore } from '@/store/modules/dict'
|
|
|
|
defineOptions({ name: 'SystemDept' })
|
|
|
|
const dictStore = useDictStore()
|
|
|
|
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
|
|
const tableData = ref<Api.Dept.DeptItem[]>([])
|
|
/** 弹窗上级组织:全量树(不受列表关键词筛选影响) */
|
|
const deptTreeForSelect = ref<Api.Dept.DeptItem[]>([])
|
|
const deptFlatList = ref<Api.Dept.DeptItem[]>([])
|
|
const selectedRows = ref<Api.Dept.DeptItem[]>([])
|
|
const loading = ref(false)
|
|
const isExpandAll = ref(true)
|
|
const tableRef = ref()
|
|
|
|
const dialogVisible = ref(false)
|
|
const dialogTitle = ref('新增组织')
|
|
const isEdit = ref(false)
|
|
const submitLoading = ref(false)
|
|
const formRef = ref()
|
|
const editingId = ref<string | null>(null)
|
|
|
|
const viewVisible = ref(false)
|
|
const viewRow = ref<Api.Dept.DeptItem | null>(null)
|
|
|
|
const formData = reactive({
|
|
parentId: '',
|
|
deptName: '',
|
|
deptCategoryCode: '',
|
|
status: '1',
|
|
leader: '',
|
|
leaderPhone: '',
|
|
orderNum: 0,
|
|
remark: ''
|
|
})
|
|
|
|
const formRules = {
|
|
deptName: [{ required: true, message: '请输入组织名称', trigger: 'blur' }]
|
|
}
|
|
|
|
const deptCategoryOptions = ref<Api.Dict.DictDataOption[]>([])
|
|
const statusOptions = ref<Api.Dict.DictDataOption[]>([])
|
|
|
|
type DeptTreeSelectNode = { value: string; label: string; children?: DeptTreeSelectNode[] }
|
|
|
|
function flattenDeptTree(nodes: Api.Dept.DeptItem[]): Api.Dept.DeptItem[] {
|
|
const out: Api.Dept.DeptItem[] = []
|
|
const walk = (list: Api.Dept.DeptItem[]) => {
|
|
for (const n of list) {
|
|
out.push({ ...n, children: undefined })
|
|
if (n.children?.length) walk(n.children)
|
|
}
|
|
}
|
|
walk(nodes || [])
|
|
return out
|
|
}
|
|
|
|
function buildDeptTreeSelectData(nodes: Api.Dept.DeptItem[]): DeptTreeSelectNode[] {
|
|
return (nodes || []).map((n) => ({
|
|
value: n.id,
|
|
label: n.deptName || '',
|
|
children: n.children?.length ? buildDeptTreeSelectData(n.children) : undefined
|
|
}))
|
|
}
|
|
|
|
const deptTreeSelectData = computed(() => buildDeptTreeSelectData(deptTreeForSelect.value))
|
|
|
|
const parentSelectId = computed({
|
|
get() {
|
|
const pid = (formData.parentId || '').trim()
|
|
return pid && pid !== '0' ? pid : undefined
|
|
},
|
|
set(val: string | undefined) {
|
|
formData.parentId = val ? val : '0'
|
|
}
|
|
})
|
|
|
|
function formatDeptPathLabel(nodeId: string | null | undefined) {
|
|
const id = String(nodeId ?? '').trim()
|
|
if (!id) return ''
|
|
return (
|
|
buildTreePathLabelFromFlat(id, deptFlatList.value, { nameKey: 'deptName' }) ||
|
|
deptFlatList.value.find((d) => d.id === id)?.deptName ||
|
|
''
|
|
)
|
|
}
|
|
|
|
function getParentDeptDisplay(parentId: string | null | undefined) {
|
|
const pid = (parentId || '').trim()
|
|
if (!pid || pid === '0') return '无(顶级组织)'
|
|
return formatDeptPathLabel(pid) || pid
|
|
}
|
|
|
|
/** 负责人:关联 sys_user,提交仍写 leader / leaderPhone 字符串 */
|
|
const leaderUserId = ref<string | null>(null)
|
|
const leaderUserOptions = ref<Api.User.UserItem[]>([])
|
|
const leaderSelectedSnapshot = ref<Api.User.UserItem | null>(null)
|
|
const leaderUserLoading = ref(false)
|
|
|
|
function formatLeaderUserLabel(u: Api.User.UserItem) {
|
|
const name = (u.nickName || '').trim() || '-'
|
|
const no = (u.userNo || '').trim()
|
|
const phone = (u.phoneNo || '').trim()
|
|
const parts = [name]
|
|
if (no) parts.push(no)
|
|
if (phone) parts.push(phone)
|
|
return parts.join(' · ')
|
|
}
|
|
|
|
async function remoteSearchLeaderUsers(query: string) {
|
|
leaderUserLoading.value = true
|
|
try {
|
|
const q = (query || '').trim()
|
|
const res = await fetchUserList({
|
|
page: 1,
|
|
pageSize: 50,
|
|
keyword: q || undefined,
|
|
status: '1'
|
|
})
|
|
let list = [...(res.list || [])]
|
|
const curId = leaderUserId.value
|
|
if (
|
|
curId &&
|
|
leaderSelectedSnapshot.value?.id === curId &&
|
|
!list.some((x) => x.id === curId)
|
|
) {
|
|
list = [leaderSelectedSnapshot.value, ...list]
|
|
}
|
|
leaderUserOptions.value = list
|
|
} catch (e) {
|
|
console.error('检索用户失败:', e)
|
|
leaderUserOptions.value = []
|
|
} finally {
|
|
leaderUserLoading.value = false
|
|
}
|
|
}
|
|
|
|
function onLeaderUserChange(val: string | null | undefined) {
|
|
if (!val) {
|
|
leaderUserId.value = null
|
|
leaderSelectedSnapshot.value = null
|
|
leaderUserOptions.value = []
|
|
formData.leader = ''
|
|
formData.leaderPhone = ''
|
|
return
|
|
}
|
|
const u =
|
|
leaderUserOptions.value.find((x) => x.id === val) ||
|
|
(leaderSelectedSnapshot.value?.id === val ? leaderSelectedSnapshot.value : undefined)
|
|
if (!u) return
|
|
leaderSelectedSnapshot.value = { ...u }
|
|
formData.leader = (u.nickName || '').trim()
|
|
formData.leaderPhone = (u.phoneNo || '').trim()
|
|
}
|
|
|
|
async function resolveLeaderUserForEdit() {
|
|
leaderUserId.value = null
|
|
leaderSelectedSnapshot.value = null
|
|
leaderUserOptions.value = []
|
|
const name = (formData.leader || '').trim()
|
|
const phone = (formData.leaderPhone || '').trim()
|
|
if (!name && !phone) return
|
|
leaderUserLoading.value = true
|
|
try {
|
|
const res = await fetchUserList({
|
|
page: 1,
|
|
pageSize: 50,
|
|
keyword: name || phone || undefined,
|
|
status: '1'
|
|
})
|
|
const list = res.list || []
|
|
const match =
|
|
list.find(
|
|
(u) =>
|
|
(u.nickName || '').trim() === name && (!phone || (u.phoneNo || '').trim() === phone)
|
|
) ||
|
|
(name ? list.find((u) => (u.nickName || '').trim() === name) : undefined) ||
|
|
(name ? list.find((u) => (u.userNo || '').trim() === name) : undefined)
|
|
if (match) {
|
|
leaderUserOptions.value = list
|
|
leaderUserId.value = match.id
|
|
leaderSelectedSnapshot.value = { ...match }
|
|
formData.leader = (match.nickName || '').trim()
|
|
if ((match.phoneNo || '').trim()) {
|
|
formData.leaderPhone = (match.phoneNo || '').trim()
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('匹配负责人用户失败:', e)
|
|
} finally {
|
|
leaderUserLoading.value = false
|
|
}
|
|
}
|
|
|
|
const loadDictOptions = async () => {
|
|
try {
|
|
const [zzlbbm, bmzt] = await Promise.all([
|
|
fetchDictDataOptions('zzlbbm'),
|
|
fetchDictDataOptions('bmzt')
|
|
])
|
|
deptCategoryOptions.value = zzlbbm || []
|
|
statusOptions.value = bmzt || []
|
|
} catch (error) {
|
|
console.error('加载字典选项失败:', error)
|
|
}
|
|
}
|
|
|
|
const getDeptCategoryLabel = (code: string) => {
|
|
if (!code) return '-'
|
|
const opt = deptCategoryOptions.value.find((o) => o.value === code)
|
|
return opt ? opt.label : code
|
|
}
|
|
|
|
const viewSections = computed(() => [
|
|
{
|
|
title: '基本信息',
|
|
fieldLayout: 'table' as const,
|
|
tableMode: 'triple' as const,
|
|
fields: [
|
|
{ label: '组织名称', value: viewRow.value?.deptName, long: false },
|
|
{ label: '上级组织', value: getParentDeptDisplay(viewRow.value?.parentId), long: false },
|
|
{
|
|
label: '组织类别',
|
|
value: getDeptCategoryLabel(viewRow.value?.deptCategoryCode || ''),
|
|
long: false
|
|
},
|
|
{
|
|
label: '状态',
|
|
value: viewRow.value?.status === '1' ? '正常' : '停用',
|
|
tagType: viewRow.value?.status === '1' ? 'success' : 'info',
|
|
long: false
|
|
},
|
|
{ label: '负责人', value: viewRow.value?.leader, long: false },
|
|
{ label: '联系电话', value: viewRow.value?.leaderPhone, long: false },
|
|
{ label: '备注', value: viewRow.value?.remark, long: false }
|
|
]
|
|
}
|
|
])
|
|
|
|
function handleView(row: Api.Dept.DeptItem) {
|
|
viewRow.value = { ...row }
|
|
viewVisible.value = true
|
|
}
|
|
|
|
const handleSelectionChange = (rows: Api.Dept.DeptItem[]) => {
|
|
selectedRows.value = rows
|
|
}
|
|
|
|
/** 树表格的 default-expand-all 只在初次挂载生效,展开态需用表格 API 同步 */
|
|
function applyTreeExpand(expand: boolean) {
|
|
const table = tableRef.value
|
|
if (!table?.toggleRowExpansion) return
|
|
const preorder = (items: Api.Dept.DeptItem[]) => {
|
|
for (const row of items) {
|
|
if (row.children?.length) {
|
|
table.toggleRowExpansion(row, expand)
|
|
preorder(row.children)
|
|
}
|
|
}
|
|
}
|
|
const postorder = (items: Api.Dept.DeptItem[]) => {
|
|
for (const row of items) {
|
|
if (row.children?.length) {
|
|
postorder(row.children)
|
|
table.toggleRowExpansion(row, expand)
|
|
}
|
|
}
|
|
}
|
|
if (expand) preorder(tableData.value)
|
|
else postorder(tableData.value)
|
|
}
|
|
|
|
async function loadDeptTreeForSelect() {
|
|
try {
|
|
const res = await fetchDeptTree()
|
|
deptTreeForSelect.value = res || []
|
|
deptFlatList.value = flattenDeptTree(deptTreeForSelect.value)
|
|
} catch (error) {
|
|
console.error('加载上级组织选项失败:', error)
|
|
deptTreeForSelect.value = []
|
|
deptFlatList.value = []
|
|
}
|
|
}
|
|
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
const res = await fetchDeptTree({
|
|
keyword: searchForm.keyword || undefined
|
|
})
|
|
tableData.value = res || []
|
|
await nextTick()
|
|
applyTreeExpand(isExpandAll.value)
|
|
} catch (error) {
|
|
console.error('加载组织数据失败:', error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function handleSearch() {
|
|
loadData()
|
|
}
|
|
|
|
function handleReset() {
|
|
searchForm.keyword = ''
|
|
loadData()
|
|
}
|
|
|
|
function toggleExpand() {
|
|
isExpandAll.value = !isExpandAll.value
|
|
nextTick(() => {
|
|
applyTreeExpand(isExpandAll.value)
|
|
})
|
|
}
|
|
|
|
function resetForm() {
|
|
formData.parentId = ''
|
|
formData.deptName = ''
|
|
formData.deptCategoryCode = ''
|
|
formData.status = '1'
|
|
formData.leader = ''
|
|
formData.leaderPhone = ''
|
|
formData.orderNum = 0
|
|
formData.remark = ''
|
|
editingId.value = null
|
|
leaderUserId.value = null
|
|
leaderUserOptions.value = []
|
|
leaderSelectedSnapshot.value = null
|
|
}
|
|
|
|
function handleAdd() {
|
|
isEdit.value = false
|
|
dialogTitle.value = '新增组织'
|
|
resetForm()
|
|
formData.parentId = '0'
|
|
dialogVisible.value = true
|
|
formRef.value?.clearValidate?.()
|
|
}
|
|
|
|
function handleAddChild(row: Api.Dept.DeptItem) {
|
|
isEdit.value = false
|
|
dialogTitle.value = '新增子组织'
|
|
resetForm()
|
|
formData.parentId = row.id
|
|
dialogVisible.value = true
|
|
formRef.value?.clearValidate?.()
|
|
}
|
|
|
|
function handleEdit(row: Api.Dept.DeptItem) {
|
|
isEdit.value = true
|
|
dialogTitle.value = '编辑组织'
|
|
editingId.value = row.id
|
|
formData.parentId = row.parentId || ''
|
|
formData.deptName = row.deptName || ''
|
|
formData.deptCategoryCode = row.deptCategoryCode || ''
|
|
formData.status = row.status || '1'
|
|
formData.leader = row.leader || ''
|
|
formData.leaderPhone = row.leaderPhone || ''
|
|
formData.orderNum = row.orderNum ?? 0
|
|
formData.remark = row.remark || ''
|
|
dialogVisible.value = true
|
|
formRef.value?.clearValidate?.()
|
|
nextTick(() => {
|
|
resolveLeaderUserForEdit()
|
|
})
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
await formRef.value?.validate?.()
|
|
submitLoading.value = true
|
|
try {
|
|
const leader = (formData.leader ?? '').trim()
|
|
const leaderPhone = (formData.leaderPhone ?? '').trim()
|
|
const remark = (formData.remark ?? '').trim()
|
|
if (isEdit.value && editingId.value) {
|
|
// 编辑时须显式传空字符串,否则 undefined 不会进入请求体,后端无法清空字段
|
|
await fetchUpdateDept(editingId.value, {
|
|
deptName: formData.deptName.trim(),
|
|
deptCategoryCode: formData.deptCategoryCode || '',
|
|
status: formData.status,
|
|
leader,
|
|
leaderPhone,
|
|
orderNum: formData.orderNum,
|
|
remark
|
|
})
|
|
} else {
|
|
await fetchCreateDept({
|
|
parentId: formData.parentId || undefined,
|
|
deptName: formData.deptName.trim(),
|
|
deptCategoryCode: formData.deptCategoryCode || undefined,
|
|
status: formData.status,
|
|
leader: leader || undefined,
|
|
leaderPhone: leaderPhone || undefined,
|
|
orderNum: formData.orderNum,
|
|
remark: remark || undefined
|
|
})
|
|
}
|
|
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
|
|
dialogVisible.value = false
|
|
await Promise.all([loadData(), loadDeptTreeForSelect()])
|
|
} catch {
|
|
/* 校验或接口错误已由全局处理 */
|
|
} finally {
|
|
submitLoading.value = false
|
|
}
|
|
}
|
|
|
|
function handleDropdownCommand(command: string, row: any) {
|
|
switch (command) {
|
|
case 'view':
|
|
handleView(row)
|
|
break
|
|
case 'edit':
|
|
handleEdit(row)
|
|
break
|
|
case 'delete':
|
|
handleDelete(row)
|
|
break
|
|
}
|
|
}
|
|
|
|
async function handleDelete(row: Api.Dept.DeptItem) {
|
|
try {
|
|
const childCount = row.children?.length || 0
|
|
const confirmText = childCount
|
|
? `该组织下有 ${childCount} 个子组织,删除将一并删除全部子组织。确定继续吗?`
|
|
: '确定要删除该组织吗?'
|
|
|
|
await ElMessageBox.confirm(confirmText, '提示', { type: 'warning' })
|
|
await fetchDeleteDept(row.id)
|
|
ElMessage.success('删除成功')
|
|
await Promise.all([loadData(), loadDeptTreeForSelect()])
|
|
} catch (error: any) {
|
|
if (error !== 'cancel') console.error('删除失败:', error)
|
|
}
|
|
}
|
|
|
|
function handleDialogClose() {
|
|
resetForm()
|
|
formRef.value?.clearValidate()
|
|
}
|
|
// 输入检索条件时自动搜索(防抖 300ms)
|
|
watchDebounced(
|
|
searchForm,
|
|
() => {
|
|
pagination.page = 1
|
|
loadData()
|
|
},
|
|
{ debounce: 300, deep: true }
|
|
)
|
|
|
|
onMounted(() => {
|
|
loadData()
|
|
loadDeptTreeForSelect()
|
|
loadDictOptions()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.dept-dialog-form {
|
|
:deep(.el-form-item) {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
</style>
|