初始化项目
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="page-shell">
|
||||
<section class="page-card table-fill">
|
||||
<div class="page-toolbar">
|
||||
<span class="text-sm font-semibold text-slate-700">组织架构</span>
|
||||
<n-button type="primary" @click="openCreate()">新增组织</n-button>
|
||||
</div>
|
||||
<div class="card-body card-body-fill tree-card-body">
|
||||
<n-tree
|
||||
block-line
|
||||
expand-on-click
|
||||
:data="treeData"
|
||||
key-field="id"
|
||||
label-field="name"
|
||||
children-field="children"
|
||||
:render-switcher-icon="renderSwitcherIcon"
|
||||
:render-label="renderLabel"
|
||||
:expanded-keys="expandedKeys"
|
||||
@update:expanded-keys="onExpanded"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<n-modal
|
||||
v-model:show="modal.visible"
|
||||
preset="card"
|
||||
:title="modal.mode === 'create' ? '新增组织' : '编辑组织'"
|
||||
class="w-[520px]"
|
||||
>
|
||||
<n-form ref="formRef" :model="form" :rules="rules" label-placement="left" label-width="90">
|
||||
<n-form-item label="上级组织">
|
||||
<n-tree-select
|
||||
v-model:value="form.parentId"
|
||||
clearable
|
||||
:options="treeSelectOptions"
|
||||
placeholder="可选"
|
||||
key-field="id"
|
||||
label-field="name"
|
||||
children-field="children"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="组织名称" path="name"
|
||||
><n-input v-model:value="form.name"
|
||||
/></n-form-item>
|
||||
<n-form-item label="组织编码" path="code"
|
||||
><n-input v-model:value="form.code" :disabled="modal.mode === 'edit'"
|
||||
/></n-form-item>
|
||||
<n-form-item label="排序"
|
||||
><n-input-number v-model:value="form.sort" :min="0"
|
||||
/></n-form-item>
|
||||
<n-form-item label="状态">
|
||||
<n-radio-group v-model:value="form.status">
|
||||
<n-radio-button value="ENABLED">启用</n-radio-button>
|
||||
<n-radio-button value="DISABLED">禁用</n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<n-button @click="modal.visible = false">取消</n-button>
|
||||
<n-button type="primary" :loading="saveMutation.isPending.value" @click="save"
|
||||
>保存</n-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, reactive, ref } from 'vue'
|
||||
import type { FormInst, FormRules, TreeOption } from 'naive-ui'
|
||||
import { NButton, NPopconfirm, NSpace, NTag, useMessage } from 'naive-ui'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { useMutation, useQuery } from '@tanstack/vue-query'
|
||||
import { createOrgApi, deleteOrgApi, listOrgsApi, updateOrgApi } from '@/api/system/org'
|
||||
import type { OrgTreeNode } from '@/types/system/org'
|
||||
import { statusLabel, statusTagType } from '@/utils/display'
|
||||
|
||||
const message = useMessage()
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
const expandedKeys = ref<string[]>([])
|
||||
|
||||
const modal = reactive({ visible: false, mode: 'create' as 'create' | 'edit', id: '' })
|
||||
const form = reactive({
|
||||
parentId: null as string | null,
|
||||
name: '',
|
||||
code: '',
|
||||
sort: 0,
|
||||
status: 'ENABLED'
|
||||
})
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '请输入组织名称', trigger: ['blur', 'input'] }],
|
||||
code: [{ required: true, message: '请输入组织编码', trigger: ['blur', 'input'] }]
|
||||
}
|
||||
|
||||
const orgQuery = useQuery({
|
||||
queryKey: ['system', 'orgs'],
|
||||
queryFn: listOrgsApi
|
||||
})
|
||||
|
||||
const treeData = computed(() => orgQuery.data.value ?? [])
|
||||
const treeSelectOptions = computed(() => treeData.value as unknown as TreeOption[])
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const payload = {
|
||||
parentId: form.parentId ?? undefined,
|
||||
name: form.name.trim(),
|
||||
code: form.code.trim(),
|
||||
sort: form.sort,
|
||||
status: form.status
|
||||
}
|
||||
if (modal.mode === 'create') return createOrgApi(payload)
|
||||
return updateOrgApi(modal.id, payload)
|
||||
},
|
||||
onSuccess: async () => {
|
||||
message.success('保存成功')
|
||||
modal.visible = false
|
||||
await orgQuery.refetch()
|
||||
expandedKeys.value = collectAllKeys(treeData.value)
|
||||
}
|
||||
})
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: deleteOrgApi,
|
||||
onSuccess: async () => {
|
||||
message.success('删除成功')
|
||||
await orgQuery.refetch()
|
||||
}
|
||||
})
|
||||
|
||||
function openCreate(parentId?: string) {
|
||||
modal.mode = 'create'
|
||||
modal.id = ''
|
||||
form.parentId = parentId ?? null
|
||||
form.name = ''
|
||||
form.code = ''
|
||||
form.sort = 0
|
||||
form.status = 'ENABLED'
|
||||
modal.visible = true
|
||||
}
|
||||
|
||||
function openEdit(node: OrgTreeNode) {
|
||||
modal.mode = 'edit'
|
||||
modal.id = node.id
|
||||
form.parentId = node.parentId ?? null
|
||||
form.name = node.name
|
||||
form.code = node.code
|
||||
form.sort = node.sort
|
||||
form.status = node.status
|
||||
modal.visible = true
|
||||
}
|
||||
|
||||
async function save() {
|
||||
await formRef.value?.validate()
|
||||
await saveMutation.mutateAsync()
|
||||
}
|
||||
|
||||
function renderLabel(payload: { option: unknown }) {
|
||||
const node = payload.option as OrgTreeNode
|
||||
return h('div', { class: 'flex w-full items-center justify-between py-2' }, [
|
||||
h('div', { class: 'flex items-center gap-2' }, [
|
||||
h('span', node.name),
|
||||
h(
|
||||
NTag,
|
||||
{ size: 'small', type: statusTagType(node.status) },
|
||||
{ default: () => statusLabel(node.status) }
|
||||
)
|
||||
]),
|
||||
h(NSpace, { size: 6 }, () => [
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', tertiary: true, onClick: () => openCreate(node.id) },
|
||||
{ default: () => '新增子级' }
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', tertiary: true, type: 'primary', onClick: () => openEdit(node) },
|
||||
{ default: () => '编辑' }
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{ onPositiveClick: () => deleteMutation.mutate(node.id) },
|
||||
{
|
||||
trigger: () =>
|
||||
h(NButton, { size: 'small', tertiary: true, type: 'error' }, { default: () => '删除' }),
|
||||
default: () => '确认删除该组织吗?'
|
||||
}
|
||||
)
|
||||
])
|
||||
])
|
||||
}
|
||||
function onExpanded(keys: string[]) {
|
||||
expandedKeys.value = keys
|
||||
}
|
||||
|
||||
function collectAllKeys(nodes: OrgTreeNode[]): string[] {
|
||||
return nodes.flatMap((node) => [node.id, ...collectAllKeys(node.children ?? [])])
|
||||
}
|
||||
|
||||
function renderSwitcherIcon() {
|
||||
return h(ChevronRight, { size: 16, strokeWidth: 2.25, class: 'tree-switcher-icon' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-card-body {
|
||||
padding: 8px 10px 12px;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-switcher) {
|
||||
width: 28px;
|
||||
height: 42px;
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
:deep(.tree-switcher-icon) {
|
||||
display: block;
|
||||
color: #0f8f86;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-content) {
|
||||
min-height: 42px;
|
||||
align-items: center;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-content__text) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user