242 lines
6.9 KiB
Vue
242 lines
6.9 KiB
Vue
<template>
|
||
<el-dialog
|
||
v-model="visible"
|
||
:title="isEdit ? '编辑组织' : '新建组织'"
|
||
width="600px"
|
||
:close-on-click-modal="false"
|
||
@closed="handleClosed"
|
||
>
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||
<el-form-item label="上级组织" prop="parent_id">
|
||
<el-tree-select
|
||
v-model="form.parent_id"
|
||
:data="parentOptions"
|
||
:props="treeSelectProps"
|
||
check-strictly
|
||
clearable
|
||
placeholder="顶级组织请留空"
|
||
style="width: 100%"
|
||
/>
|
||
<div class="form-tip">留空表示顶级组织(企业单位)</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="组织名称" prop="org_name">
|
||
<el-input v-model="form.org_name" maxlength="50" show-word-limit placeholder="例如:连云港云泽集团" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="组织编码" prop="org_code">
|
||
<el-input v-model="form.org_code" maxlength="20" show-word-limit placeholder="留空时按组织设置自动生成" />
|
||
<div class="form-tip">同一租户内不允许重复,可留空由系统生成</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="组织类型" prop="is_company">
|
||
<el-radio-group v-model="form.is_company">
|
||
<el-radio :value="1">企业单位</el-radio>
|
||
<el-radio :value="0">部门</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="负责人" prop="leader_id">
|
||
<el-select v-model="form.leader_id" filterable clearable placeholder="请选择负责人" style="width: 100%">
|
||
<el-option
|
||
v-for="item in employeeOptions"
|
||
:key="item.id"
|
||
:label="item.name || item.account"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="排序" prop="sort">
|
||
<el-input-number v-model="form.sort" :min="0" :max="9999" controls-position="right" style="width: 100%" />
|
||
<div class="form-tip">数字越小越靠前</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="状态" prop="status">
|
||
<el-radio-group v-model="form.status">
|
||
<el-radio :value="1">启用</el-radio>
|
||
<el-radio :value="0">禁用</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit />
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<template #footer>
|
||
<el-button @click="visible = false">取消</el-button>
|
||
<el-button type="primary" :loading="loading" @click="handleSubmit">
|
||
{{ isEdit ? '保存' : '创建' }}
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, reactive, ref } from 'vue';
|
||
import { ElMessage } from 'element-plus';
|
||
import { buildOrgTree, collectSubtreeIds, unwrap, useOrganizationApi } from '../composables';
|
||
|
||
const props = defineProps({
|
||
/** 所属模块:erp(进销存)或 oa(办公自动化) */
|
||
module: { type: String, default: 'erp' },
|
||
});
|
||
|
||
const emit = defineEmits(['success']);
|
||
|
||
const api = computed(() => useOrganizationApi(props.module));
|
||
|
||
const visible = ref(false);
|
||
const loading = ref(false);
|
||
const isEdit = ref(false);
|
||
const formRef = ref();
|
||
const orgTree = ref([]);
|
||
const employeeOptions = ref([]);
|
||
const editingId = ref(0);
|
||
|
||
const treeSelectProps = { value: 'id', label: 'org_name', children: 'children' };
|
||
|
||
const emptyForm = () => ({
|
||
parent_id: null,
|
||
org_name: '',
|
||
org_code: '',
|
||
leader_id: null,
|
||
sort: 0,
|
||
is_company: 0,
|
||
status: 1,
|
||
remark: '',
|
||
});
|
||
|
||
const form = reactive(emptyForm());
|
||
|
||
const rules = {
|
||
org_name: [
|
||
{ required: true, message: '请输入组织名称', trigger: 'blur' },
|
||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' },
|
||
],
|
||
org_code: [
|
||
{ pattern: /^[a-zA-Z0-9_-]*$/, message: '只能包含英文、数字、下划线和横线', trigger: 'blur' },
|
||
],
|
||
};
|
||
|
||
// 编辑时排除自身及其子树,避免把组织挂到自己的下级从而形成环路
|
||
const parentOptions = computed(() => {
|
||
if (!isEdit.value || !editingId.value) return orgTree.value;
|
||
const excluded = new Set();
|
||
const walk = (nodes) => {
|
||
nodes.forEach((node) => {
|
||
if (Number(node.id) === Number(editingId.value)) {
|
||
collectSubtreeIds(node).forEach((id) => excluded.add(id));
|
||
return;
|
||
}
|
||
walk(node.children || []);
|
||
});
|
||
};
|
||
walk(orgTree.value);
|
||
|
||
const filter = (nodes) =>
|
||
nodes
|
||
.filter((node) => !excluded.has(Number(node.id)))
|
||
.map((node) => ({ ...node, children: filter(node.children || []) }));
|
||
return filter(orgTree.value);
|
||
});
|
||
|
||
const loadOptions = async () => {
|
||
try {
|
||
const [orgList, employees] = await Promise.all([
|
||
api.value.getOrganizationList().then(unwrap),
|
||
api.value.getEmployeeList().then(unwrap),
|
||
]);
|
||
orgTree.value = buildOrgTree(orgList);
|
||
employeeOptions.value = (employees || []).map((item) => ({
|
||
id: item.id,
|
||
name: item.name,
|
||
account: item.account,
|
||
}));
|
||
} catch (error) {
|
||
ElMessage.error(error?.message || '加载选项失败');
|
||
}
|
||
};
|
||
|
||
const open = (row, parentId) => {
|
||
Object.assign(form, emptyForm());
|
||
isEdit.value = Boolean(row);
|
||
editingId.value = row?.id || 0;
|
||
|
||
if (row) {
|
||
Object.assign(form, {
|
||
parent_id: row.parent_id ? Number(row.parent_id) : null,
|
||
org_name: row.org_name || '',
|
||
org_code: row.org_code || '',
|
||
leader_id: row.leader_id ? Number(row.leader_id) : null,
|
||
sort: Number(row.sort || 0),
|
||
is_company: Number(row.is_company || 0),
|
||
status: Number(row.status ?? 1),
|
||
remark: row.remark || '',
|
||
});
|
||
} else if (parentId) {
|
||
form.parent_id = Number(parentId);
|
||
form.is_company = 0;
|
||
} else {
|
||
form.is_company = 1;
|
||
}
|
||
|
||
visible.value = true;
|
||
loadOptions();
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return;
|
||
try {
|
||
await formRef.value.validate();
|
||
} catch {
|
||
return;
|
||
}
|
||
|
||
const payload = {
|
||
parent_id: form.parent_id || 0,
|
||
org_name: form.org_name.trim(),
|
||
org_code: form.org_code.trim(),
|
||
leader_id: form.leader_id || 0,
|
||
sort: form.sort,
|
||
is_company: form.is_company,
|
||
status: form.status,
|
||
remark: form.remark || '',
|
||
};
|
||
|
||
loading.value = true;
|
||
try {
|
||
if (isEdit.value) {
|
||
unwrap(await api.value.updateOrganization(editingId.value, payload));
|
||
ElMessage.success('保存成功');
|
||
} else {
|
||
unwrap(await api.value.createOrganization(payload));
|
||
ElMessage.success('创建成功');
|
||
}
|
||
visible.value = false;
|
||
emit('success');
|
||
} catch (error) {
|
||
ElMessage.error(error?.message || '保存失败');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleClosed = () => {
|
||
formRef.value?.clearValidate();
|
||
Object.assign(form, emptyForm());
|
||
};
|
||
|
||
defineExpose({ open });
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.form-tip {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
line-height: 1.4;
|
||
}
|
||
</style>
|