增加erp组织架构
This commit is contained in:
parent
186315d82c
commit
6c904ef3cd
65
src/api/erp.js
Normal file
65
src/api/erp.js
Normal file
@ -0,0 +1,65 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
/*************************************************
|
||||
****************** 组织机构相关接口 ******************
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* 获取组织机构列表
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getOrganizationList() {
|
||||
return request({
|
||||
url: '/admin/erp/organization',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组织机构详情
|
||||
* @param {number} id 组织机构ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getOrganizationDetail(id) {
|
||||
return request({
|
||||
url: `/admin/erp/getOrganizationDetail/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建组织机构数据
|
||||
* @param {Object} data 组织机构数据
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function createOrganization(data) {
|
||||
return request({
|
||||
url: "/admin/erp/organization",
|
||||
method: "post",
|
||||
data: data,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更新组织机构信息
|
||||
export function editOrganization(id, data) {
|
||||
return request({
|
||||
url: `/admin/erp/organization/${id}`,
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除组织机构数据
|
||||
* @param {number} id 组织机构ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function deleteOrganization(id) {
|
||||
return request({
|
||||
url: `/admin/erp/organization/${id}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
@ -7,6 +7,14 @@ export function getAllUsers() {
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
//获取租户用户
|
||||
export function getTenantUsers(tenantId) {
|
||||
return request({
|
||||
url: `/admin/getTenantUsers/${tenantId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getUserInfo(userId) {
|
||||
|
||||
@ -40,6 +40,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
account: userInfo.account || '',
|
||||
name: userInfo.name || '',
|
||||
group_id: userInfo.group_id || '',
|
||||
tenant_id: userInfo.tenant_id || '',
|
||||
avatar: userInfo.avatar || ''
|
||||
}
|
||||
|
||||
|
||||
202
src/views/apps/erp/organization/components/edit.vue
Normal file
202
src/views/apps/erp/organization/components/edit.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="isEdit ? '编辑部门' : '添加部门'"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
|
||||
<el-form-item label="上级部门" prop="parent_id">
|
||||
<el-tree-select
|
||||
v-model="formData.parent_id"
|
||||
:data="treeData"
|
||||
:props="treeProps"
|
||||
check-strictly
|
||||
placeholder="请选择上级部门"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="org_name">
|
||||
<el-input v-model="formData.org_name" placeholder="请输入部门名称,例如:连云港云泽集团" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门编码" prop="org_code">
|
||||
<el-input v-model="formData.org_code" placeholder="请输入部门编码,例如:LYG-YZ-GROUP" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人" prop="leader_id">
|
||||
<el-select v-model="formData.leader_id" placeholder="请选择负责人">
|
||||
<el-option v-for="item in leaderList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="999" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { createOrganization, editOrganization } from '@/api/erp'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { getTenantUsers } from '@/api/user'
|
||||
|
||||
interface TreeNode {
|
||||
id: number
|
||||
org_name: string
|
||||
org_code?: string
|
||||
parent_id?: number
|
||||
parent_name?: string
|
||||
leader_id?: number
|
||||
sort?: number
|
||||
status?: number
|
||||
remark?: string
|
||||
children?: TreeNode[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
treeData: TreeNode[]
|
||||
treeProps: { label: string; children: string; value: string }
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const isEdit = ref(false)
|
||||
const authStore = useAuthStore()
|
||||
const tenantId = (authStore.user as any)?.tenant_id
|
||||
const leaderList = ref([])
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
parent_id: undefined as number | undefined,
|
||||
org_name: '',
|
||||
org_code: '',
|
||||
leader_id: undefined as number | undefined,
|
||||
sort: 0,
|
||||
status: 1,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const formRules: FormRules = {
|
||||
org_name: [{ required: true, message: '请输入部门名称', trigger: 'blur' }],
|
||||
org_code: [{ required: true, message: '请输入部门编码', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLeaderList()
|
||||
})
|
||||
|
||||
const getLeaderList = async () => {
|
||||
const res = await getTenantUsers(tenantId)
|
||||
if (res.code === 200) {
|
||||
leaderList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const open = (data?: TreeNode, parentId?: number) => {
|
||||
if (data) {
|
||||
// 编辑
|
||||
isEdit.value = true
|
||||
Object.assign(formData, data)
|
||||
} else {
|
||||
// 新增
|
||||
isEdit.value = false
|
||||
formData.id = 0
|
||||
formData.parent_id = parentId
|
||||
formData.org_name = ''
|
||||
formData.org_code = ''
|
||||
formData.leader_id = undefined
|
||||
formData.sort = 0
|
||||
formData.status = 1
|
||||
formData.remark = ''
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 提交
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
let res
|
||||
|
||||
if (isEdit.value && formData.id) {
|
||||
// 编辑:使用普通对象提交
|
||||
const submitData: Record<string, any> = {}
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (
|
||||
key !== 'id' &&
|
||||
(formData as any)[key] !== undefined &&
|
||||
(formData as any)[key] !== null
|
||||
) {
|
||||
submitData[key] = (formData as any)[key]
|
||||
}
|
||||
})
|
||||
if (tenantId) {
|
||||
submitData.tenant_id = tenantId
|
||||
}
|
||||
res = await editOrganization(formData.id, submitData)
|
||||
} else {
|
||||
// 新增:使用 FormData 以兼容后端 multipart/form-data
|
||||
const submitData = new FormData()
|
||||
Object.keys(formData).forEach(key => {
|
||||
const value = (formData as any)[key]
|
||||
if (key !== 'id' && value !== undefined && value !== null) {
|
||||
submitData.append(key, String(value))
|
||||
}
|
||||
})
|
||||
if (tenantId) {
|
||||
submitData.append('tenant_id', String(tenantId))
|
||||
}
|
||||
res = await createOrganization(submitData)
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(isEdit.value ? '编辑成功' : '添加成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
} else {
|
||||
ElMessage.error(res.msg || (isEdit.value ? '编辑失败' : '添加失败'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error)
|
||||
ElMessage.error('提交失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLeaderList()
|
||||
})
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
350
src/views/apps/erp/organization/index.vue
Normal file
350
src/views/apps/erp/organization/index.vue
Normal file
@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div class="organization-container">
|
||||
<el-row :gutter="16">
|
||||
<!-- 左侧:组织架构树 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="tree-card" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>组织架构</span>
|
||||
<el-button type="primary" link @click="handleAddRoot">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="treeData"
|
||||
:props="treeProps"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
:expand-on-click-node="false"
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="tree-node">
|
||||
<span class="node-label">{{ node.label }}</span>
|
||||
<div class="node-actions" @click.stop>
|
||||
<el-button link type="primary" size="small" @click="handleAdd(data)">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
<el-button link type="primary" size="small" @click="handleEdit(data)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleDelete(data)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧:详情面板 -->
|
||||
<el-col :span="16">
|
||||
<el-card shadow="hover" class="detail-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ detailTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="selectedNode" class="detail-content">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="部门名称">
|
||||
{{ selectedNode.org_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="部门编码">
|
||||
{{ selectedNode.org_code || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="上级部门">
|
||||
{{ selectedNode.parent_name || '顶级部门' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="负责人">
|
||||
{{ selectedNode.leader_name || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态" :span="2">
|
||||
<el-tag :type="selectedNode.status === 1 ? 'success' : 'danger'">
|
||||
{{ selectedNode.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">
|
||||
{{ selectedNode.remark || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="detail-actions">
|
||||
<el-button type="primary" @click="handleEdit(selectedNode)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleDelete(selectedNode)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="请选择左侧组织节点查看详情" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 添加/编辑对话框 -->
|
||||
<OrganizationEdit
|
||||
ref="editRef"
|
||||
:tree-data="treeData"
|
||||
:tree-props="treeProps"
|
||||
@success="handleSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import OrganizationEdit from './components/edit.vue'
|
||||
import { getOrganizationList, getOrganizationDetail, deleteOrganization } from '@/api/erp'
|
||||
|
||||
// 树形结构数据(与后台字段对应)
|
||||
interface TreeNode {
|
||||
id: number
|
||||
tenant_id?: number
|
||||
org_name: string
|
||||
org_code?: string
|
||||
parent_id?: number
|
||||
parent_name?: string
|
||||
leader_id?: number
|
||||
leader_name?: string
|
||||
sort?: number
|
||||
status?: number
|
||||
remark?: string
|
||||
children?: TreeNode[]
|
||||
}
|
||||
|
||||
// 树形数据
|
||||
const treeData = ref<TreeNode[]>([])
|
||||
|
||||
const treeProps = {
|
||||
label: 'org_name',
|
||||
children: 'children',
|
||||
value: 'id'
|
||||
}
|
||||
|
||||
const treeRef = ref()
|
||||
const editRef = ref()
|
||||
const selectedNode = ref<TreeNode | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
// 将扁平列表构建为树形结构
|
||||
const buildTree = (list: any[]): TreeNode[] => {
|
||||
const nodeMap = new Map<number, TreeNode>()
|
||||
const roots: TreeNode[] = []
|
||||
|
||||
list.forEach(item => {
|
||||
const node: TreeNode = {
|
||||
id: item.id,
|
||||
tenant_id: item.tenant_id,
|
||||
org_name: item.org_name,
|
||||
org_code: item.org_code,
|
||||
parent_id: item.parent_id,
|
||||
parent_name: item.parent_name,
|
||||
leader_id: item.leader_id,
|
||||
leader_name: item.leader_name,
|
||||
sort: item.sort,
|
||||
status: item.status,
|
||||
remark: item.remark,
|
||||
children: []
|
||||
}
|
||||
nodeMap.set(node.id, node)
|
||||
})
|
||||
|
||||
nodeMap.forEach(node => {
|
||||
if (node.parent_id && nodeMap.has(node.parent_id)) {
|
||||
const parent = nodeMap.get(node.parent_id)!
|
||||
if (!parent.children) {
|
||||
parent.children = []
|
||||
}
|
||||
parent.children.push(node)
|
||||
} else {
|
||||
roots.push(node)
|
||||
}
|
||||
})
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
// 获取组织机构详情
|
||||
const loadOrganizationDetail = async (id: number) => {
|
||||
try {
|
||||
const res = await getOrganizationDetail(id)
|
||||
if (res.code === 200) {
|
||||
const data = res.data || null
|
||||
if (data) {
|
||||
selectedNode.value = {
|
||||
id: data.id,
|
||||
tenant_id: data.tenant_id,
|
||||
org_name: data.org_name,
|
||||
org_code: data.org_code,
|
||||
parent_id: data.parent_id,
|
||||
parent_name: data.parent_name,
|
||||
leader_id: data.leader_id,
|
||||
leader_name: data.leader_name,
|
||||
sort: data.sort,
|
||||
status: data.status,
|
||||
remark: data.remark,
|
||||
children: []
|
||||
}
|
||||
} else {
|
||||
selectedNode.value = null
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取组织机构详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取组织机构详情失败:', error)
|
||||
ElMessage.error('获取组织机构详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取组织机构列表
|
||||
const fetchOrganizationList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getOrganizationList()
|
||||
if (res.code === 200) {
|
||||
const list = res.data || []
|
||||
treeData.value = buildTree(list)
|
||||
|
||||
// 如果当前没有选中的节点且有数据,默认加载第一个节点详情
|
||||
if (!selectedNode.value && list.length) {
|
||||
await loadOrganizationDetail(list[0].id)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取组织机构列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取组织机构列表失败:', error)
|
||||
ElMessage.error('获取组织机构列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 详情标题
|
||||
const detailTitle = computed(() => {
|
||||
return selectedNode.value ? `部门详情 - ${selectedNode.value.org_name}` : '部门详情'
|
||||
})
|
||||
|
||||
// 点击节点,加载详情
|
||||
const handleNodeClick = (data: TreeNode) => {
|
||||
loadOrganizationDetail(data.id)
|
||||
}
|
||||
|
||||
// 添加根节点
|
||||
const handleAddRoot = () => {
|
||||
editRef.value?.open(undefined, undefined)
|
||||
}
|
||||
|
||||
// 添加子节点
|
||||
const handleAdd = (data: TreeNode) => {
|
||||
editRef.value?.open(undefined, data.id)
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (data: TreeNode) => {
|
||||
editRef.value?.open(data)
|
||||
}
|
||||
|
||||
// 提交成功回调,重新加载列表
|
||||
const handleSuccess = () => {
|
||||
fetchOrganizationList()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (data: TreeNode) => {
|
||||
ElMessageBox.confirm(`确定要删除部门「${data.org_name}」吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await deleteOrganization(data.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
fetchOrganizationList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchOrganizationList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.organization-container {
|
||||
// padding: 16px;
|
||||
}
|
||||
|
||||
.tree-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
|
||||
.node-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
display: none;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&:hover .node-actions {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
.el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user