提交
This commit is contained in:
1 parent
dc31968204
commit
27ac28b8f5
58 files changed
+5960
-4960
No files matched your search
@@ -144,6 +144,24 @@
|
||||
: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>
|
||||
@@ -235,11 +253,15 @@
|
||||
fetchDictDataOptions,
|
||||
fetchUserList
|
||||
} from '@/api/admin'
|
||||
import { buildTreePathLabelFromFlat } from '@/utils/tree-path'
|
||||
|
||||
defineOptions({ name: 'SystemDept' })
|
||||
|
||||
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)
|
||||
@@ -273,6 +295,56 @@
|
||||
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[]>([])
|
||||
@@ -398,6 +470,7 @@
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '组织名称', value: viewRow.value?.deptName },
|
||||
{ label: '上级组织', value: getParentDeptDisplay(viewRow.value?.parentId) },
|
||||
{ label: '组织类别', value: getDeptCategoryLabel(viewRow.value?.deptCategoryCode || '') },
|
||||
{ label: '状态', value: viewRow.value?.status === '1' ? '正常' : '停用', tagType: viewRow.value?.status === '1' ? 'success' : 'info' },
|
||||
{ label: '负责人', value: viewRow.value?.leader },
|
||||
@@ -440,6 +513,18 @@
|
||||
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 {
|
||||
@@ -528,24 +613,35 @@
|
||||
await formRef.value?.validate?.()
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = {
|
||||
parentId: formData.parentId || undefined,
|
||||
deptName: formData.deptName.trim(),
|
||||
deptCategoryCode: formData.deptCategoryCode || undefined,
|
||||
status: formData.status,
|
||||
leader: formData.leader?.trim() || undefined,
|
||||
leaderPhone: formData.leaderPhone?.trim() || undefined,
|
||||
orderNum: formData.orderNum,
|
||||
remark: formData.remark?.trim() || undefined
|
||||
}
|
||||
const leader = (formData.leader ?? '').trim()
|
||||
const leaderPhone = (formData.leaderPhone ?? '').trim()
|
||||
const remark = (formData.remark ?? '').trim()
|
||||
if (isEdit.value && editingId.value) {
|
||||
await fetchUpdateDept(editingId.value, payload)
|
||||
// 编辑时须显式传空字符串,否则 undefined 不会进入请求体,后端无法清空字段
|
||||
await fetchUpdateDept(editingId.value, {
|
||||
deptName: formData.deptName.trim(),
|
||||
deptCategoryCode: formData.deptCategoryCode || '',
|
||||
status: formData.status,
|
||||
leader,
|
||||
leaderPhone,
|
||||
orderNum: formData.orderNum,
|
||||
remark
|
||||
})
|
||||
} else {
|
||||
await fetchCreateDept(payload)
|
||||
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 loadData()
|
||||
await Promise.all([loadData(), loadDeptTreeForSelect()])
|
||||
} catch {
|
||||
/* 校验或接口错误已由全局处理 */
|
||||
} finally {
|
||||
@@ -563,7 +659,7 @@
|
||||
await ElMessageBox.confirm(confirmText, '提示', { type: 'warning' })
|
||||
await fetchDeleteDept(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadData()
|
||||
await Promise.all([loadData(), loadDeptTreeForSelect()])
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') console.error('删除失败:', error)
|
||||
}
|
||||
@@ -576,6 +672,7 @@
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
loadDeptTreeForSelect()
|
||||
loadDictOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -204,95 +204,159 @@
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
:title="viewRow?.dictName || '字典详情'"
|
||||
width="900px"
|
||||
:icon="Collection"
|
||||
:meta="`字典类型编码:${viewRow?.dictType || ''}${viewRow?.createTime ? ' · ' + viewRow.createTime : ''}`"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
>
|
||||
<template #append>
|
||||
<div class="view-section dict-view-data-section">
|
||||
<h3>字典数据</h3>
|
||||
<div class="list-body list-wrap dict-view-table-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="viewDictDataList"
|
||||
v-loading="viewDictDataLoading"
|
||||
stripe
|
||||
row-key="id"
|
||||
empty-text="暂无字典数据"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="dictLabel"
|
||||
label="数据标签"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="dictValue"
|
||||
label="数据值"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
v-model="dictDataDialogVisible"
|
||||
:title="`${currentDictType.dictName} - 字典数据配置`"
|
||||
width="900px"
|
||||
class="dialog-form-layout"
|
||||
class="dialog-form-layout dict-data-config-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div style="margin-bottom: 12px">
|
||||
<el-button class="btn-main" type="primary" @click="handleAddDictData">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增数据
|
||||
</el-button>
|
||||
<div class="dict-data-dialog-head">
|
||||
<span class="muted">字典类型编码:{{ currentDictType.dictType }}</span>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="dictDataTableData"
|
||||
v-loading="dictDataLoading"
|
||||
stripe
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column type="index" label="#" width="50" align="center" />
|
||||
<el-table-column
|
||||
prop="dictLabel"
|
||||
label="数据标签"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="dictValue"
|
||||
label="数据值"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
show-overflow-tooltip
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
<div class="list-toolbar dict-data-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<el-button class="btn-main" type="primary" @click="handleAddDictData">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增数据
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-body list-wrap dict-data-table-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="dictDataTableData"
|
||||
v-loading="dictDataLoading"
|
||||
stripe
|
||||
row-key="id"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
class="btn-op-item primary"
|
||||
@click="handleEditDictData(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteDictData(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar" style="margin-top: 12px">
|
||||
<el-pagination
|
||||
:current-page="dictDataPagination.page"
|
||||
:page-size="dictDataPagination.pageSize"
|
||||
:total="dictDataPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(val) => (
|
||||
(dictDataPagination.page = 1),
|
||||
(dictDataPagination.pageSize = val),
|
||||
loadDictData()
|
||||
)
|
||||
"
|
||||
@current-change="
|
||||
(val) => ((dictDataPagination.page = val), loadDictData())
|
||||
"
|
||||
/>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="
|
||||
(index) =>
|
||||
(dictDataPagination.page - 1) * dictDataPagination.pageSize + index + 1
|
||||
"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="dictLabel"
|
||||
label="数据标签"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="dictValue"
|
||||
label="数据值"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
class="btn-op-item primary"
|
||||
@click="handleEditDictData(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteDictData(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
:current-page="dictDataPagination.page"
|
||||
:page-size="dictDataPagination.pageSize"
|
||||
:total="dictDataPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(val) => (
|
||||
(dictDataPagination.page = 1),
|
||||
(dictDataPagination.pageSize = val),
|
||||
loadDictData()
|
||||
)
|
||||
"
|
||||
@current-change="
|
||||
(val) => ((dictDataPagination.page = val), loadDictData())
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
@@ -385,6 +449,7 @@
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<Api.Dict.DictTypeItem | null>(null)
|
||||
const viewDictDataList = ref<Api.Dict.DictDataItem[]>([])
|
||||
const viewDictDataLoading = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
dictName: '',
|
||||
@@ -436,35 +501,23 @@
|
||||
editingId.value = null
|
||||
}
|
||||
|
||||
const viewSections = computed(() => {
|
||||
const sections: any[] = [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '字典名称', value: viewRow.value?.dictName },
|
||||
{ label: '字典类型编码', value: viewRow.value?.dictType },
|
||||
{ label: '备注', value: viewRow.value?.remark, span: true },
|
||||
{ label: '创建时间', value: viewRow.value?.createTime }
|
||||
]
|
||||
}
|
||||
]
|
||||
if (viewDictDataList.value.length > 0) {
|
||||
sections.push({
|
||||
title: '字典数据',
|
||||
fields: viewDictDataList.value.map((item) => ({
|
||||
label: item.dictLabel || '',
|
||||
value: item.dictValue || '',
|
||||
span: false
|
||||
}))
|
||||
})
|
||||
const viewSections = computed(() => [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '字典名称', value: viewRow.value?.dictName },
|
||||
{ label: '字典类型编码', value: viewRow.value?.dictType },
|
||||
{ label: '备注', value: viewRow.value?.remark, span: true },
|
||||
{ label: '创建时间', value: viewRow.value?.createTime }
|
||||
]
|
||||
}
|
||||
return sections
|
||||
})
|
||||
])
|
||||
|
||||
async function handleView(row: Api.Dict.DictTypeItem) {
|
||||
viewRow.value = { ...row }
|
||||
viewDictDataList.value = []
|
||||
viewVisible.value = true
|
||||
viewDictDataLoading.value = true
|
||||
try {
|
||||
const res = await fetchDictDataList({
|
||||
page: 1,
|
||||
@@ -474,6 +527,8 @@
|
||||
viewDictDataList.value = res.list || []
|
||||
} catch (e) {
|
||||
console.error('加载字典数据失败:', e)
|
||||
} finally {
|
||||
viewDictDataLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +723,41 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dict-view-data-section {
|
||||
padding: 18px 22px;
|
||||
border-bottom: none;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 14px;
|
||||
padding-left: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
border-left: 3px solid #10b981;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dict-view-table-wrap {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dict-data-dialog-head {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.muted {
|
||||
font-size: 13px;
|
||||
color: var(--mint-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.dict-data-toolbar {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dict-data-table-wrap {
|
||||
padding-top: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user