修复快捷工具无法显示

This commit is contained in:
hero920103 committed 2026-10-30 17:29:28 +08:00
1 parent 9898962836
commit 2e2b837a20
23 files changed
+1941 -38

No files matched your search

@@ -1,5 +1,13 @@
<template>
<el-drawer v-model="visible" :title="isEdit ? `编辑${labels.employeeLabel}` : `新增${labels.employeeLabel}`" size="520px">
<el-drawer v-model="visible" :title="isEdit ? `编辑${labels.employeeLabel}` : isCopy ? `复制${labels.employeeLabel}` : `新增${labels.employeeLabel}`" size="520px">
<el-alert
v-if="isCopy"
:title="`已按原${labels.employeeLabel}预填,账号与密码需重新填写,保存后会生成一条新记录`"
type="info"
:closable="false"
show-icon
style="margin-bottom: 16px"
/>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<div class="form-title">账号信息</div>
@@ -123,7 +131,7 @@
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">保存</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">{{ isEdit ? '保存' : '创建' }}</el-button>
</template>
</el-drawer>
</template>
@@ -132,6 +140,7 @@
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { buildOrgTree, moduleLabels, unwrap, useOrganizationApi } from '../composables';
import { copyName } from '@/utils/copyFields';
const props = defineProps({
/** 所属模块:erp(进销存)或 oa(办公自动化) */
@@ -146,6 +155,11 @@ const labels = computed(() => moduleLabels(props.module));
const visible = ref(false);
const loading = ref(false);
const isEdit = ref(false);
/**
* 复制模式:预填原记录但走「创建」流程,保存后生成一条新记录。
* 复制时不置 isEdit —— 账号因此保持可编辑,密码也仍然必填。
*/
const isCopy = ref(false);
const editingId = ref(0);
const formRef = ref();
/** 编辑回填期间置 true,级联 watcher 跳过清空,避免回填值被覆盖 */
@@ -293,16 +307,23 @@ const loadOptions = async () => {
}
};
const open = async (row, defaults = {}) => {
/**
* 打开抽屉。
* @param {object|null} row 编辑的记录;传 null 表示新增
* @param {object} options { copy: true } 复制模式;
* { department, affiliate_unit } 新增时预选组织
*/
const open = async (row, options = {}) => {
Object.assign(form, emptyForm());
isEdit.value = Boolean(row?.id);
isCopy.value = Boolean(options.copy);
isEdit.value = Boolean(row?.id) && !isCopy.value;
editingId.value = row?.id || 0;
visible.value = true;
formRef.value?.clearValidate();
await loadOptions();
if (isEdit.value) {
if (row?.id) {
try {
const detail = unwrap(await api.value.getEmployeeDetail(row.id)) || {};
backfilling.value = true;
@@ -320,6 +341,13 @@ const open = async (row, defaults = {}) => {
home_address: detail.home_address || '',
account_status: Number(detail.account_status ?? detail.status ?? 1),
});
if (isCopy.value) {
// 复制:账号是登录名且租户内唯一,必须换一个新的;密码重新设置
form.account = '';
form.password = '';
form.confirmPassword = '';
form.name = copyName(detail.name);
}
// watcher 为 pre-flush 异步执行,需等其执行完毕再复位标志,
// 否则复位后 watcher 才触发,仍会清空 department / position。
await nextTick();
@@ -331,11 +359,11 @@ const open = async (row, defaults = {}) => {
return;
}
if (defaults.department) {
form.department = String(defaults.department);
if (options.department) {
form.department = String(options.department);
}
if (defaults.affiliate_unit) {
form.affiliate_unit = String(defaults.affiliate_unit);
if (options.affiliate_unit) {
form.affiliate_unit = String(options.affiliate_unit);
}
};
@@ -85,10 +85,11 @@
<el-tag :type="employeeStatusTagType(row)" size="small">{{ employeeStatusText(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="280" fixed="right" align="center">
<el-table-column label="操作" width="330" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="viewDialogRef?.open(row)">查看</el-button>
<el-button link type="primary" size="small" @click="editDialogRef?.open(row)">编辑</el-button>
<el-button link type="warning" size="small" @click="editDialogRef?.open(row, { copy: true })">复制</el-button>
<el-button link type="primary" size="small" @click="moveDialogRef?.open([row])">调岗</el-button>
<el-button link type="warning" size="small" @click="handleResetPassword(row)">重置密码</el-button>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
@@ -1,11 +1,19 @@
<template>
<el-dialog
v-model="visible"
:title="isEdit ? '编辑组织' : '新建组织'"
:title="isEdit ? '编辑组织' : isCopy ? '复制组织' : '新建组织'"
width="600px"
:close-on-click-modal="false"
@closed="handleClosed"
>
<el-alert
v-if="isCopy"
title="已按原组织预填,修改后保存会在同级下生成一条新的组织记录"
type="info"
:closable="false"
show-icon
style="margin-bottom: 16px"
/>
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="上级组织" prop="parent_id">
<el-tree-select
@@ -77,6 +85,7 @@
import { computed, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { buildOrgTree, collectSubtreeIds, unwrap, useOrganizationApi } from '../composables';
import { copyCode, copyName } from '@/utils/copyFields';
const props = defineProps({
/** 所属模块:erp(进销存)或 oa(办公自动化) */
@@ -90,6 +99,8 @@ const api = computed(() => useOrganizationApi(props.module));
const visible = ref(false);
const loading = ref(false);
const isEdit = ref(false);
/** 复制模式:预填原记录但走「创建」流程,保存后生成一条新记录 */
const isCopy = ref(false);
const formRef = ref();
const orgTree = ref([]);
const employeeOptions = ref([]);
@@ -159,9 +170,16 @@ const loadOptions = async () => {
}
};
const open = (row, parentId) => {
/**
* 打开弹窗。
* @param {object|null} row 编辑的记录;传 null 表示新建
* @param {object} options { copy: true } 复制模式;{ parentId } 新建时预选上级组织
*/
const open = (row, options = {}) => {
Object.assign(form, emptyForm());
isEdit.value = Boolean(row);
isCopy.value = Boolean(options.copy);
// 复制时不能置 isEdit,否则提交会走更新、覆盖掉原记录
isEdit.value = Boolean(row) && !isCopy.value;
editingId.value = row?.id || 0;
if (row) {
@@ -175,8 +193,13 @@ const open = (row, parentId) => {
status: Number(row.status ?? 1),
remark: row.remark || '',
});
} else if (parentId) {
form.parent_id = Number(parentId);
if (isCopy.value) {
// 复制:编码在租户内唯一,必须换一个新的;名称加副本后缀便于区分
form.org_code = copyCode(row.org_code, 20);
form.org_name = copyName(row.org_name);
}
} else if (options.parentId) {
form.parent_id = Number(options.parentId);
form.is_company = 0;
} else {
form.is_company = 1;
@@ -11,7 +11,7 @@
导入导出
</el-button>
<el-button :icon="Setting" @click="settingsRef?.open()">组织设置</el-button>
<el-button type="primary" :icon="Plus" @click="orgDialogRef?.open(null, null)">新建组织</el-button>
<el-button type="primary" :icon="Plus" @click="orgDialogRef?.open(null)">新建组织</el-button>
</div>
</div>
@@ -75,10 +75,11 @@
</el-tag>
</span>
<span class="node-actions">
<el-button link type="primary" size="small" @click.stop="orgDialogRef?.open(null, data.id)">
<el-button link type="primary" size="small" @click.stop="orgDialogRef?.open(null, { parentId: data.id })">
加子级
</el-button>
<el-button link type="primary" size="small" @click.stop="orgDialogRef?.open(data)">编辑</el-button>
<el-button link type="warning" size="small" @click.stop="orgDialogRef?.open(data, { copy: true })">复制</el-button>
<el-button link type="danger" size="small" @click.stop="handleDeleteOrg(data)">删除</el-button>
</span>
</div>
@@ -168,9 +169,10 @@
<el-tag :type="employeeStatusTagType(row)" size="small">{{ employeeStatusText(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="170" fixed="right">
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="employeeDialogRef?.open(row)">编辑</el-button>
<el-button link type="warning" size="small" @click="employeeDialogRef?.open(row, { copy: true })">复制</el-button>
<el-button link type="primary" size="small" @click="moveDialogRef?.open([row])">调岗</el-button>
<el-button link type="danger" size="small" @click="handleDeleteEmployee(row)">删除</el-button>
</template>
@@ -1,11 +1,19 @@
<template>
<el-dialog
v-model="visible"
:title="isEdit ? '编辑职位' : '新增职位'"
:title="isEdit ? '编辑职位' : isCopy ? '复制职位' : '新增职位'"
width="560px"
:close-on-click-modal="false"
@closed="handleClosed"
>
<el-alert
v-if="isCopy"
title="已按原职位预填,修改后保存会生成一条新的职位记录"
type="info"
:closable="false"
show-icon
style="margin-bottom: 16px"
/>
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="所属部门" prop="department_id">
<el-tree-select
@@ -35,6 +43,26 @@
</el-radio-group>
</el-form-item>
<el-form-item label="职级" prop="rank_id">
<el-select
v-model="form.rank_id"
clearable
placeholder="选择职级"
style="width: 100%"
:loading="rankLoading"
>
<el-option
v-for="item in rankOptions"
:key="item.id"
:label="rankLabel(item)"
:value="item.id"
/>
</el-select>
<div class="form-tip">
职级决定该职位的等级,供薪酬定薪与权限分级使用;可选项随所选部门变化(始终包含全公司通用职级)
</div>
</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%" />
</el-form-item>
@@ -61,9 +89,11 @@
</template>
<script setup>
import { computed, reactive, ref } from 'vue';
import { computed, reactive, ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { buildOrgTree, unwrap, useOrganizationApi } from '../composables';
import { getJobRankList } from '@/api/jobRank';
import { copyCode, copyName } from '@/utils/copyFields';
const props = defineProps({
/** 所属模块:erp(进销存)或 oa(办公自动化) */
@@ -77,9 +107,13 @@ const api = computed(() => useOrganizationApi(props.module));
const visible = ref(false);
const loading = ref(false);
const isEdit = ref(false);
/** 复制模式:预填原记录但走「创建」流程,保存后生成一条新记录 */
const isCopy = ref(false);
const editingId = ref(0);
const formRef = ref();
const departmentTree = ref([]);
const rankOptions = ref([]);
const rankLoading = ref(false);
const treeSelectProps = { value: 'id', label: 'org_name', children: 'children' };
@@ -88,6 +122,7 @@ const emptyForm = () => ({
position_name: '',
position_code: '',
position_type: 0,
rank_id: null,
sort: 0,
status: 1,
remark: '',
@@ -102,6 +137,34 @@ const rules = {
],
};
/** 职级下拉文案:编码 + 名称,全公司通用的带上标识 */
const rankLabel = (item) => {
const scope = item.department_id === 0 ? '(全公司)' : '';
return `${item.rank_code} ${item.rank_name}${scope}`;
};
/** 加载某部门可用的职级(本部门职级 + 全公司通用职级) */
const loadRanks = async (departmentId) => {
rankLoading.value = true;
try {
const params = { scope: 'assign' };
if (departmentId) params.department_id = departmentId;
rankOptions.value = unwrap(await getJobRankList(params)) || [];
} catch {
rankOptions.value = [];
} finally {
rankLoading.value = false;
}
};
// 切换部门后职级可选范围随之变化,重新拉取
watch(
() => form.department_id,
(value) => {
loadRanks(value);
}
);
const loadDepartments = async () => {
try {
departmentTree.value = buildOrgTree(unwrap(await api.value.getOrganizationList()));
@@ -110,9 +173,16 @@ const loadDepartments = async () => {
}
};
const open = (row, departmentId) => {
/**
* 打开弹窗。
* @param {object|null} row 编辑的记录;传 null 表示新增
* @param {object} options { copy: true } 复制模式;{ departmentId } 新增时预选部门
*/
const open = (row, options = {}) => {
Object.assign(form, emptyForm());
isEdit.value = Boolean(row?.id);
isCopy.value = Boolean(options.copy);
// 复制时不能置 isEdit,否则提交会走更新、覆盖掉原记录
isEdit.value = Boolean(row?.id) && !isCopy.value;
editingId.value = row?.id || 0;
if (row) {
@@ -121,16 +191,23 @@ const open = (row, departmentId) => {
position_name: row.position_name || '',
position_code: row.position_code || '',
position_type: Number(row.position_type || 0),
rank_id: row.rank_id ? Number(row.rank_id) : null,
sort: Number(row.sort || 0),
status: Number(row.status ?? 1),
remark: row.remark || '',
});
} else if (departmentId) {
form.department_id = Number(departmentId);
if (isCopy.value) {
// 复制:编码在租户内唯一,必须换一个新的;名称加副本后缀便于区分
form.position_code = copyCode(row.position_code, 50);
form.position_name = copyName(row.position_name);
}
} else if (options.departmentId) {
form.department_id = Number(options.departmentId);
}
visible.value = true;
loadDepartments();
loadRanks(form.department_id);
};
const handleSubmit = async () => {
@@ -146,6 +223,7 @@ const handleSubmit = async () => {
position_name: form.position_name.trim(),
position_code: form.position_code.trim(),
position_type: form.position_type,
rank_id: form.rank_id || 0,
sort: form.sort,
status: form.status,
remark: form.remark || '',
@@ -176,3 +254,12 @@ const handleClosed = () => {
defineExpose({ open });
</script>
<style scoped>
.form-tip {
font-size: 12px;
color: #909399;
line-height: 1.6;
margin-top: 2px;
}
</style>
@@ -7,7 +7,7 @@
</div>
<div class="header-actions">
<el-button :icon="Refresh" @click="loadData">刷新</el-button>
<el-button type="primary" :icon="Plus" @click="editDialogRef?.open(null, filters.department_id)">
<el-button type="primary" :icon="Plus" @click="editDialogRef?.open(null, { departmentId: filters.department_id })">
新增职位
</el-button>
</div>
@@ -59,6 +59,12 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="职级" min-width="140">
<template #default="{ row }">
<el-tag v-if="row.rank_id" size="small" type="primary">{{ row.rank_name || '已设置' }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="80" align="center" />
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
@@ -70,9 +76,10 @@
<el-table-column label="备注" min-width="140">
<template #default="{ row }">{{ row.remark || '-' }}</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right" align="center">
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="editDialogRef?.open(row)">编辑</el-button>
<el-button link type="warning" size="small" @click="editDialogRef?.open(row, { copy: true })">复制</el-button>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>