<template>
|
<div class="dept-page art-full-height">
|
<ArtSearchBar
|
v-model="searchForm"
|
:items="searchItems"
|
:show-expand="false"
|
@search="handleSearch"
|
@reset="handleReset"
|
/>
|
|
<ElCard class="art-table-card">
|
<ArtTableHeader
|
:loading="loading"
|
:show-zebra="false"
|
v-model:columns="columnChecks"
|
@refresh="handleRefresh"
|
>
|
<template #left>
|
<ElSpace wrap>
|
<ElButton v-auth="'add'" @click="handleAdd" v-ripple>{{ t('pages.system.dept.buttons.add') }}</ElButton>
|
<ElButton @click="toggleExpand" v-ripple>
|
{{ isExpanded ? t('common.actions.collapse') : t('common.actions.expand') }}
|
</ElButton>
|
</ElSpace>
|
</template>
|
</ArtTableHeader>
|
|
<ArtTable
|
ref="tableRef"
|
rowKey="id"
|
:loading="loading"
|
:columns="columns"
|
:data="tableData"
|
:stripe="false"
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
:default-expand-all="false"
|
/>
|
|
<DeptDialog
|
v-model:visible="dialogVisible"
|
:dept-data="currentDeptData"
|
:dept-tree-options="deptTreeOptions"
|
@submit="handleDialogSubmit"
|
/>
|
</ElCard>
|
</div>
|
</template>
|
|
<script setup>
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { useI18n } from 'vue-i18n'
|
import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
|
import { useTableColumns } from '@/hooks/core/useTableColumns'
|
import {
|
fetchDeleteDept,
|
fetchGetDeptDetail,
|
fetchGetDeptTree,
|
fetchSaveDept,
|
fetchUpdateDept
|
} from '@/api/system-manage'
|
import DeptDialog from './modules/dept-dialog.vue'
|
import { createDeptTableColumns } from './deptTable.columns'
|
import {
|
buildDeptDialogModel,
|
buildDeptSavePayload,
|
buildDeptSearchParams,
|
buildDeptTreeOptions,
|
createDeptSearchState,
|
normalizeDeptTreeRows
|
} from './deptPage.helpers'
|
|
defineOptions({ name: 'Dept' })
|
|
const { t } = useI18n()
|
const loading = ref(false)
|
const isExpanded = ref(false)
|
const tableRef = ref()
|
const dialogVisible = ref(false)
|
const currentDeptData = ref(buildDeptDialogModel())
|
const deptTreeOptions = ref([])
|
const tableData = ref([])
|
|
const initialSearchState = createDeptSearchState()
|
const searchForm = reactive({ ...initialSearchState })
|
|
const searchItems = computed(() => [
|
{
|
label: t('pages.system.dept.table.name'),
|
key: 'condition',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.system.dept.search.conditionPlaceholder')
|
}
|
}
|
])
|
|
const { columnChecks, columns } = useTableColumns(() =>
|
createDeptTableColumns({
|
t,
|
handleEdit: handleEdit,
|
handleDelete: handleDelete
|
})
|
)
|
|
onMounted(() => {
|
loadDeptTree()
|
})
|
|
async function loadDeptTree() {
|
loading.value = true
|
try {
|
const tree = await guardRequestWithMessage(
|
fetchGetDeptTree(buildDeptSearchParams(searchForm)),
|
[],
|
{
|
timeoutMessage: t('pages.system.dept.messages.pageTimeout')
|
}
|
)
|
const normalizedRows = normalizeDeptTreeRows(tree || [])
|
tableData.value = normalizedRows
|
deptTreeOptions.value = buildDeptTreeOptions(normalizedRows)
|
} finally {
|
loading.value = false
|
}
|
}
|
|
function handleSearch() {
|
loadDeptTree()
|
}
|
|
function handleReset() {
|
Object.assign(searchForm, { ...initialSearchState })
|
loadDeptTree()
|
}
|
|
function handleRefresh() {
|
loadDeptTree()
|
}
|
|
function handleAdd() {
|
currentDeptData.value = buildDeptDialogModel()
|
deptTreeOptions.value = buildDeptTreeOptions(tableData.value)
|
dialogVisible.value = true
|
}
|
|
async function handleEdit(row) {
|
try {
|
const detail = await fetchGetDeptDetail(row.id)
|
currentDeptData.value = buildDeptDialogModel(detail || row)
|
deptTreeOptions.value = buildDeptTreeOptions(tableData.value, row.id)
|
dialogVisible.value = true
|
} catch (error) {
|
ElMessage.error(error?.message || t('pages.system.dept.messages.detailFailed'))
|
}
|
}
|
|
async function handleDelete(row) {
|
try {
|
await ElMessageBox.confirm(
|
t('crud.confirm.deleteMessage', {
|
entity: t('pages.system.dept.entity'),
|
label: row.name || row.id
|
}),
|
t('crud.confirm.deleteTitle'),
|
{
|
confirmButtonText: t('common.confirm'),
|
cancelButtonText: t('common.cancel'),
|
type: 'warning'
|
}
|
)
|
await fetchDeleteDept(row.id)
|
ElMessage.success(t('crud.messages.deleteSuccess'))
|
await loadDeptTree()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
|
}
|
}
|
}
|
|
async function handleDialogSubmit(formData) {
|
const payload = buildDeptSavePayload(formData)
|
if (payload.id && payload.id === payload.parentId) {
|
ElMessage.error(t('pages.system.dept.messages.parentSelfInvalid'))
|
return
|
}
|
|
try {
|
if (payload.id) {
|
await fetchUpdateDept(payload)
|
ElMessage.success(t('crud.messages.updateSuccess'))
|
} else {
|
await fetchSaveDept(payload)
|
ElMessage.success(t('crud.messages.createSuccess'))
|
}
|
dialogVisible.value = false
|
currentDeptData.value = buildDeptDialogModel()
|
await loadDeptTree()
|
} catch (error) {
|
ElMessage.error(error?.message || t('crud.messages.submitFailed'))
|
}
|
}
|
|
function toggleExpand() {
|
isExpanded.value = !isExpanded.value
|
nextTick(() => {
|
if (tableRef.value?.elTableRef && tableData.value) {
|
const processRows = (rows) => {
|
rows.forEach((row) => {
|
if (row.children?.length) {
|
tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)
|
processRows(row.children)
|
}
|
})
|
}
|
processRows(tableData.value)
|
}
|
})
|
}
|
</script>
|