Files
yunzerwebsiteallinone/backend/src/views/apps/organization/components/OrgEditDialog.vue
T
2026-08-27 21:25:39 +08:00

242 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>