✨ 完善组织人员增量同步与用户管理交互

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-10 13:56:29 +08:00
parent eaece0209d
commit ea797356ce
9 changed files with 322 additions and 178 deletions
+94 -21
View File
@@ -35,8 +35,16 @@
plain
:loading="oaSyncLoading"
v-if="isAdministrator"
@click="handleOaOrgSync"
>自动同步组织
@click="handleOaOrgFullSync"
>全量同步
</el-button>
<el-button
type="primary"
plain
:loading="oaSyncLoading"
v-if="isAdministrator"
@click="handleOaOrgIncrementalSync"
>增量同步
</el-button>
</template>
<template #menu="scope">
@@ -191,10 +199,12 @@ const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const ORGANIZATION_TYPE_DICT_CODE = 'organization_type';
/**
* 组织类型名称 → 字典数字键(兼容 dictKey 配成中文的情况)
* 与业务字典 organization_type 及历史别名对齐;优先仍以数字 dictKey 为准
*/
const ORGANIZATION_TYPE_CODE_BY_LABEL = {
集团: 1,
一级集团: 1,
公司: 2,
事业部: 2,
二级事业部: 2,
部门: 3,
@@ -234,6 +244,7 @@ export default {
oaSyncCancelled: false,
oaSyncStatus: 'running',
oaSyncStage: 'company',
oaSyncMode: 'full',
oaSyncClearNonTop: false,
oaSyncController: null,
oaSyncProgress: {
@@ -278,6 +289,7 @@ export default {
minWidth: 160,
order: 80,
search: true,
searchOrder: 2,
rules: [
{
required: true,
@@ -389,6 +401,8 @@ export default {
prop: 'shortName',
minWidth: 140,
order: 60,
search: true,
searchOrder: 1,
},
{
label: '助记码',
@@ -462,15 +476,33 @@ export default {
{
label: '排序',
prop: 'sort',
type: 'number',
type: 'input',
align: 'right',
width: 80,
order: 30,
change: ({ value }) => {
if (value === undefined || value === null || value === '') {
return;
}
const digits = String(value).replace(/\D/g, '');
if (String(value) !== digits) {
this.form.sort = digits;
}
},
rules: [
{
required: true,
message: '请输入排序',
trigger: 'blur',
validator: (rule, value, callback) => {
if (value === '' || value === undefined || value === null) {
callback();
return;
}
if (!/^\d+$/.test(String(value).trim())) {
callback(new Error('排序只能输入数字'));
return;
}
callback();
},
trigger: ['blur', 'change'],
},
],
},
@@ -509,12 +541,10 @@ export default {
value: 'tenantId',
},
hide: true,
search: website.tenantMode,
},
{
label: '机构全称',
prop: 'fullName',
search: true,
hide: true,
addDisplay: false,
editDisplay: false,
@@ -568,16 +598,17 @@ export default {
return ids.join(',');
},
oaSyncDialogTitle() {
const modeLabel = this.oaSyncMode === 'incremental' ? '增量同步' : '全量同步';
if (this.oaSyncStatus === 'done') {
return '同步完成';
return `${modeLabel}完成`;
}
if (this.oaSyncStatus === 'cancelled') {
return '已取消同步';
return `已取消${modeLabel}`;
}
if (this.oaSyncStatus === 'error') {
return '同步失败';
return `${modeLabel}失败`;
}
return '自动同步组织';
return modeLabel;
},
oaSyncStageLabel() {
if (this.oaSyncStage === 'clear') {
@@ -617,10 +648,10 @@ export default {
},
},
methods: {
handleOaOrgSync() {
handleOaOrgFullSync() {
this.$confirm(
'是否清除非顶级组织?选择“是”将先删除顶级以外的组织后再同步;选择“否”则直接同步。',
'提示',
'全量同步将拉取 OA 全部公司/部门;已存在且 OA 更新时间一致的记录会跳过。是否清除非顶级组织?选择“是”将先删除顶级以外的组织后再同步;选择“否”则直接全量同步。',
'全量同步',
{
confirmButtonText: '是',
cancelButtonText: '否',
@@ -630,20 +661,37 @@ export default {
}
)
.then(() => {
this.startOaOrgSync(true);
this.startOaOrgSync({ clearNonTop: true, incremental: false });
})
.catch(action => {
if (action === 'cancel') {
this.startOaOrgSync(false);
this.startOaOrgSync({ clearNonTop: false, incremental: false });
}
});
},
startOaOrgSync(clearNonTop) {
handleOaOrgIncrementalSync() {
this.$confirm(
'增量同步仅拉取本地最大 OA 更新时间之后有变更的组织;不存在的新增,已存在且更新时间不一致的修改,一致的跳过。是否继续?',
'增量同步',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
type: 'info',
}
)
.then(() => {
this.startOaOrgSync({ clearNonTop: false, incremental: true });
})
.catch(() => {});
},
startOaOrgSync({ clearNonTop = false, incremental = false } = {}) {
this.oaSyncLoading = true;
this.oaSyncVisible = true;
this.oaSyncRunning = true;
this.oaSyncCancelled = false;
this.oaSyncStatus = 'running';
this.oaSyncMode = incremental ? 'incremental' : 'full';
this.oaSyncClearNonTop = !!clearNonTop;
this.oaSyncStage = clearNonTop ? 'clear' : 'company';
this.oaSyncController = new AbortController();
@@ -666,10 +714,21 @@ export default {
},
async runOaOrgSyncStage(syncApi) {
const size = 20;
const incremental = this.oaSyncMode === 'incremental';
let current = 1;
let modifiedSince = '';
while (!this.oaSyncCancelled) {
const res = await syncApi(current, size, this.oaSyncController?.signal);
const res = await syncApi(
current,
size,
this.oaSyncController?.signal,
incremental,
modifiedSince
);
const page = res?.data?.data || {};
if (!modifiedSince && page.modifiedSince) {
modifiedSince = page.modifiedSince;
}
this.oaSyncProgress.current = page.current || current;
this.oaSyncProgress.size = page.size || size;
this.oaSyncProgress.total = page.total || 0;
@@ -806,7 +865,7 @@ export default {
},
/**
* 业务字典项转下拉选项。后端 deptCategory 为 Integer,value 必须始终为数字。
* 优先用数字 dictKey;若 dictKey 配成中文,则按名称映射已知编码。
* 优先用数字 dictKey;其次按名称映射;再回退 sort,避免字典项被静默丢弃。
*/
normalizeOrganizationTypeOption(item) {
const label = item?.dictValue ?? item?.label;
@@ -833,10 +892,19 @@ export default {
value: codeByLabel,
};
}
// 未命中名称映射时,用排序号兜底,保证与业务字典项一致展示
const sortNumber = Number(item?.sort);
if (!Number.isNaN(sortNumber) && sortNumber >= 1) {
return {
label: labelText,
value: sortNumber,
};
}
return null;
},
/**
* 提交前将组织类型规范为 Integer;空状态字段去掉,避免 "" 反序列化失败。
* 提交前将组织类型规范为 Integer;空状态/排序字段去掉,避免 "" 反序列化失败。
* 排序为空时由后端默认填充 1。
*/
prepareDeptSubmitRow(row) {
const category = this.resolveDeptCategoryValue(row.deptCategory);
@@ -849,6 +917,11 @@ export default {
} else {
row.status = Number(row.status);
}
if (row.sort === '' || row.sort === null || row.sort === undefined) {
delete row.sort;
} else {
row.sort = Number(String(row.sort).trim());
}
return true;
},
resolveDeptCategoryValue(value) {