+12
-2
@@ -57,26 +57,36 @@ export const clearNonTopDept = signal => {
|
||||
});
|
||||
};
|
||||
|
||||
export const syncOaCompany = (current = 1, size = 20, signal) => {
|
||||
export const syncOaCompany = (current = 1, size = 20, signal, incremental = false, modifiedSince) => {
|
||||
return request({
|
||||
url: '/blade-system/dept/sync-oa-company',
|
||||
method: 'post',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
incremental: !!incremental,
|
||||
modifiedSince: modifiedSince || undefined,
|
||||
},
|
||||
timeout: 60000,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const syncOaDepartment = (current = 1, size = 20, signal) => {
|
||||
export const syncOaDepartment = (
|
||||
current = 1,
|
||||
size = 20,
|
||||
signal,
|
||||
incremental = false,
|
||||
modifiedSince
|
||||
) => {
|
||||
return request({
|
||||
url: '/blade-system/dept/sync-oa-department',
|
||||
method: 'post',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
incremental: !!incremental,
|
||||
modifiedSince: modifiedSince || undefined,
|
||||
},
|
||||
timeout: 60000,
|
||||
signal,
|
||||
|
||||
+14
-1
@@ -31,13 +31,15 @@ export const add = row => {
|
||||
});
|
||||
};
|
||||
|
||||
export const syncIamAccounts = (current = 1, size = 50, signal) => {
|
||||
export const syncIamAccounts = (current = 1, size = 50, signal, incremental = false, modifiedSince) => {
|
||||
return request({
|
||||
url: '/blade-system/user/sync-iam-accounts',
|
||||
method: 'post',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
incremental: !!incremental,
|
||||
modifiedSince: modifiedSince || undefined,
|
||||
},
|
||||
timeout: 60000,
|
||||
signal,
|
||||
@@ -52,6 +54,17 @@ export const update = row => {
|
||||
});
|
||||
};
|
||||
|
||||
export const changeStatus = (id, status) => {
|
||||
return request({
|
||||
url: '/blade-system/user/change-status',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
status,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const updatePlatform = (userId, userType, userExt) => {
|
||||
return request({
|
||||
url: '/blade-system/user/update-platform',
|
||||
|
||||
@@ -27,7 +27,8 @@ export const userOption = safe => {
|
||||
index: true,
|
||||
selection: true,
|
||||
addBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtn: false,
|
||||
menuWidth: 160,
|
||||
dialogType: 'dialog',
|
||||
dialogWidth: '90%',
|
||||
labelPosition: 'right',
|
||||
@@ -37,6 +38,7 @@ export const userOption = safe => {
|
||||
{
|
||||
label: '账号名',
|
||||
prop: 'account',
|
||||
slot: true,
|
||||
search: true,
|
||||
searchOrder: 2,
|
||||
searchPlaceholder: '支持模糊搜索',
|
||||
@@ -63,7 +65,8 @@ export const userOption = safe => {
|
||||
label: '所属部门',
|
||||
prop: 'deptName',
|
||||
slot: true,
|
||||
overHidden: true,
|
||||
minWidth: 200,
|
||||
overHidden: false,
|
||||
display: false,
|
||||
},
|
||||
{ label: '岗位', prop: 'postName', overHidden: true, display: false },
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="contract-manage-form__attachment-table"
|
||||
@selection-change="selected = $event"
|
||||
>
|
||||
<el-table-column v-if="!readonly" type="selection" width="55" align="center" />
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column v-if="attachmentType" label="附件类型" min-width="160" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
@@ -726,7 +726,15 @@ export default {
|
||||
downloadFileByUrl(url, attachmentName(row));
|
||||
},
|
||||
batchDownload() {
|
||||
(this.selected.length ? this.selected : this.rows || []).forEach(this.download);
|
||||
const sourceRows = this.selected.length ? this.selected : this.rows || [];
|
||||
const downloadableRows = sourceRows.filter(row => attachmentUrl(row));
|
||||
if (!downloadableRows.length) {
|
||||
this.$message.warning(this.selected.length ? '所选附件暂无可下载地址' : '暂无可下载附件');
|
||||
return;
|
||||
}
|
||||
downloadableRows.forEach((row, index) => {
|
||||
window.setTimeout(() => this.download(row), index * 400);
|
||||
});
|
||||
},
|
||||
formatSize(size) {
|
||||
const value = Number(size);
|
||||
|
||||
@@ -1666,6 +1666,7 @@ const downloadTemplate = async () => {
|
||||
ElMessage.error('模板下载失败');
|
||||
}
|
||||
};
|
||||
const isValidMobile = value => /^\d{11}$/.test(String(value || '').trim());
|
||||
const validateImportRows = () => {
|
||||
if (!rows.value.length) {
|
||||
ElMessage.warning('请上传至少一条运单明细');
|
||||
@@ -1677,6 +1678,11 @@ const validateImportRows = () => {
|
||||
ElMessage.warning(`第${index + 1}行${missingField.label}不能为空`);
|
||||
return false;
|
||||
}
|
||||
const driverPhone = String(row.driverPhone ?? '').trim();
|
||||
if (driverPhone && !isValidMobile(driverPhone)) {
|
||||
ElMessage.warning(`第${index + 1}行司机/船长手机号格式不正确,应为11位数字`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1022,7 +1022,7 @@
|
||||
v-model="detailBillingPlanBox"
|
||||
:value="detailBillingPlanForm"
|
||||
:index="detailBillingPlanIndex"
|
||||
:settlement-currency="detail.settlementCurrency || form.settlementCurrency"
|
||||
:settlement-currency="detailRow.settlementCurrency || form.settlementCurrency"
|
||||
readonly
|
||||
/>
|
||||
|
||||
|
||||
+94
-21
@@ -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) {
|
||||
|
||||
+145
-127
@@ -51,72 +51,31 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-operation"
|
||||
:loading="iamSyncLoading"
|
||||
v-if="isAdministrator && !auditMode"
|
||||
@click="handleAudit"
|
||||
>审 核
|
||||
@click="handleOaPersonFullSync"
|
||||
>全量同步
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
:loading="iamSyncLoading"
|
||||
v-if="isAdministrator && !auditMode"
|
||||
@click="handleIamSync"
|
||||
>同步人员
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-check"
|
||||
v-if="isAdministrator && auditMode"
|
||||
@click="handleAuditPass"
|
||||
>通 过
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-close"
|
||||
v-if="isAdministrator && auditMode"
|
||||
@click="handleAuditRefuse"
|
||||
>拒 绝
|
||||
@click="handleOaPersonIncrementalSync"
|
||||
>增量同步
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-refresh-left"
|
||||
v-if="isAdministrator && auditMode"
|
||||
@click="handleAuditBack"
|
||||
>返 回
|
||||
v-if="isAdministrator && !auditMode"
|
||||
@click="handleImport"
|
||||
>导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
v-if="permission.user_role && !auditMode"
|
||||
icon="el-icon-user"
|
||||
@click="handleGrant"
|
||||
>角色配置
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
type="primary"
|
||||
v-if="isAdministrator && !auditMode"
|
||||
@command="handleDataManageCommand"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
<el-button type="primary" plain icon="el-icon-files">
|
||||
数据管理<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="import">
|
||||
<el-icon><upload /></el-icon>
|
||||
导入数据
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="export">
|
||||
<el-icon><download /></el-icon>
|
||||
导出数据
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@click="handleExport"
|
||||
>导出
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu-right>
|
||||
<el-button
|
||||
@@ -132,47 +91,23 @@
|
||||
>锁定管理
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="{ row, index, size }">
|
||||
<template #account="{ row }">
|
||||
<el-link type="primary" @click.stop="openUserDialog('view', row)">{{ row.account }}</el-link>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-link
|
||||
type="primary"
|
||||
v-if="this.permission.user_view"
|
||||
@click.stop="$refs.crud.rowView(row, index)"
|
||||
>查看
|
||||
</el-link>
|
||||
<el-link
|
||||
type="primary"
|
||||
v-if="this.permission.user_edit"
|
||||
v-if="permission.user_edit && !auditMode"
|
||||
@click.stop="openUserDialog('edit', row)"
|
||||
>编辑
|
||||
>修改
|
||||
</el-link>
|
||||
<el-link
|
||||
type="primary"
|
||||
v-if="permission.user_edit && !auditMode"
|
||||
@click.stop="handleStatus(row)"
|
||||
>
|
||||
{{ Number(row.status) === 1 ? '停用' : '启用' }}
|
||||
</el-link>
|
||||
<el-dropdown @command="command => handleDropdownCommand(command, row, index)">
|
||||
<el-link type="primary">更多 </el-link>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="setLeader" v-if="isAdministrator">
|
||||
{{ row.isLeader === 1 ? '取消主管' : '设置主管' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="grantRole" v-if="permission.user_role">
|
||||
角色配置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="authLog" v-if="isAdministrator">
|
||||
认证日志
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="lockUser" v-if="isAdministrator">
|
||||
锁定账号
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="resetPassword" v-if="permission.user_reset">
|
||||
密码重置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="setPassword" v-if="permission.user_reset">
|
||||
修改密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" v-if="this.permission.user_delete">
|
||||
删除用户
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template #roleName="{ row }">
|
||||
<el-tag>{{ row.roleName }}</el-tag>
|
||||
@@ -191,7 +126,7 @@
|
||||
|
||||
<el-dialog
|
||||
v-model="userDialog"
|
||||
:title="userDialogMode === 'add' ? '新增用户' : '编辑用户'"
|
||||
:title="userDialogTitle"
|
||||
width="90%"
|
||||
top="5vh"
|
||||
append-to-body
|
||||
@@ -199,12 +134,12 @@
|
||||
class="user-edit-dialog dialog-form--carded"
|
||||
>
|
||||
<template #header>
|
||||
<span class="dialog-title">{{ userDialogMode === 'add' ? '新增用户' : '编辑用户' }}</span>
|
||||
<span class="dialog-title">{{ userDialogTitle }}</span>
|
||||
</template>
|
||||
<section-card title="用户信息">
|
||||
<el-form ref="userFormRef" :model="form" :rules="userRules" label-position="right" label-width="calc(6em + 24px)">
|
||||
<el-form ref="userFormRef" :model="form" :rules="isUserViewMode ? {} : userRules" label-position="right" label-width="calc(6em + 24px)">
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6"><el-form-item label="账号名" prop="account"><el-input v-model="form.account" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="账号名" prop="account"><el-input v-model="form.account" :disabled="isUserViewMode" /></el-form-item></el-col>
|
||||
<el-col v-if="userDialogMode === 'add'" :span="6">
|
||||
<el-form-item label="登录密码" prop="password">
|
||||
<el-input
|
||||
@@ -216,17 +151,18 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6"><el-form-item label="姓名" prop="realName"><el-input v-model="form.realName" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="手机号" prop="phone"><el-input v-model="form.phone" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="邮箱"><el-input v-model="form.email" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="性别"><el-select v-model="form.sex"><el-option label="男" :value="1" /><el-option label="女" :value="2" /><el-option label="未知" :value="3" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="岗位" prop="postId"><el-tree-select v-model="form.postId" :data="postList" :props="{ label: 'postName', value: 'id' }" check-strictly /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="姓名" prop="realName"><el-input v-model="form.realName" :disabled="isUserViewMode" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="手机号" prop="phone"><el-input v-model="form.phone" :disabled="isUserViewMode" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="邮箱"><el-input v-model="form.email" :disabled="isUserViewMode" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="性别"><el-select v-model="form.sex" :disabled="isUserViewMode" clearable placeholder="请选择"><el-option label="男" :value="1" /><el-option label="女" :value="2" /><el-option label="未知" :value="3" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="岗位" prop="postId"><el-tree-select v-model="form.postId" :data="postList" :props="{ label: 'postName', value: 'id' }" check-strictly :disabled="isUserViewMode" /></el-form-item></el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属组织" prop="deptId">
|
||||
<el-cascader
|
||||
v-model="deptCascaderValue"
|
||||
:options="permissionDeptTree"
|
||||
:props="deptCascaderProps"
|
||||
:disabled="isUserViewMode"
|
||||
clearable
|
||||
filterable
|
||||
collapse-tags
|
||||
@@ -235,10 +171,10 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6"><el-form-item label="工号"><el-input v-model="form.code" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="人员类别"><el-select v-model="form.personCategory"><el-option label="内部员工" :value="1" /><el-option label="承运商" :value="2" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="18"><el-form-item label="数据权限范围"><el-radio-group v-model="form.dataScopeRange"><el-radio :value="1">仅所属组织</el-radio><el-radio :value="2">全部组织</el-radio><el-radio :value="3">自定义</el-radio></el-radio-group></el-form-item></el-col>
|
||||
<el-col :span="18"><el-form-item label="数据层级范围"><el-radio-group v-model="form.dataLevelRange"><el-radio :value="1">包含下级</el-radio><el-radio :value="2">仅本级</el-radio></el-radio-group></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="工号"><el-input v-model="form.code" :disabled="isUserViewMode" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="人员类别"><el-select v-model="form.personCategory" :disabled="isUserViewMode"><el-option label="内部员工" :value="1" /><el-option label="承运商" :value="2" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="18"><el-form-item label="数据权限范围"><el-radio-group v-model="form.dataScopeRange" :disabled="isUserViewMode"><el-radio :value="1">仅所属组织</el-radio><el-radio :value="2">全部组织</el-radio><el-radio :value="3">自定义</el-radio></el-radio-group></el-form-item></el-col>
|
||||
<el-col :span="18"><el-form-item label="数据层级范围"><el-radio-group v-model="form.dataLevelRange" :disabled="isUserViewMode"><el-radio :value="1">包含下级</el-radio><el-radio :value="2">仅本级</el-radio></el-radio-group></el-form-item></el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
@@ -247,6 +183,7 @@
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
:disabled="isUserViewMode"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -260,18 +197,28 @@
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<el-table-column prop="id" label="角色编号" />
|
||||
<el-table-column prop="title" label="角色名称" />
|
||||
<el-table-column label="操作" width="90"><template #default="{ row }"><el-link type="danger" @click="removeFormRole(row.id)">删除</el-link></template></el-table-column>
|
||||
<el-table-column v-if="!isUserViewMode" label="操作" width="90"><template #default="{ row }"><el-link type="danger" @click="removeFormRole(row.id)">删除</el-link></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-if="form.dataScopeRange === 3" label="数据权限">
|
||||
<el-tree :data="permissionDeptTree" show-checkbox node-key="id" :default-checked-keys="form.dataScopeDeptIds || []" :props="{ label: 'title', children: 'children' }" @check="handleDataScopeCheck" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="客商权限">
|
||||
<div class="permission-toolbar customer-toolbar"><el-checkbox v-model="form.includeNewCustomer" :true-value="1" :false-value="0">包含新增的客商</el-checkbox></div>
|
||||
<el-table ref="customerTable" :data="customerList" border @selection-change="handleCustomerSelection"><el-table-column type="selection" width="55" /><el-table-column prop="customerCode" label="客商编码" /><el-table-column prop="fullName" label="客商名称" /></el-table>
|
||||
<div class="permission-toolbar customer-toolbar"><el-checkbox v-model="form.includeNewCustomer" :true-value="1" :false-value="0" :disabled="isUserViewMode">包含新增的客商</el-checkbox></div>
|
||||
<el-table ref="customerTable" :data="customerList" border @selection-change="handleCustomerSelection"><el-table-column type="selection" width="55" :selectable="isCustomerSelectable" /><el-table-column prop="customerCode" label="客商编码" /><el-table-column prop="fullName" label="客商名称" /></el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer><span class="dialog-footer"><el-button type="primary" @click="submitUserForm">保存</el-button><el-button @click="userDialog = false">取消</el-button><el-button v-if="userDialogMode === 'edit'" @click="resetDialogPassword">重置密码</el-button><el-button @click="handleAddRole">添加角色</el-button></span></template>
|
||||
<template #footer>
|
||||
<span v-if="isUserViewMode" class="dialog-footer">
|
||||
<el-button @click="userDialog = false">关闭</el-button>
|
||||
</span>
|
||||
<span v-else class="dialog-footer">
|
||||
<el-button type="primary" @click="submitUserForm">保存</el-button>
|
||||
<el-button @click="userDialog = false">取消</el-button>
|
||||
<el-button v-if="userDialogMode === 'edit'" @click="resetDialogPassword">重置密码</el-button>
|
||||
<el-button @click="handleAddRole">添加角色</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 用户角色配置 -->
|
||||
@@ -442,6 +389,7 @@ import {
|
||||
remove,
|
||||
update,
|
||||
add,
|
||||
changeStatus,
|
||||
syncIamAccounts,
|
||||
grant,
|
||||
resetPassword,
|
||||
@@ -494,6 +442,7 @@ export default {
|
||||
oaSyncRunning: false,
|
||||
oaSyncCancelled: false,
|
||||
oaSyncStatus: 'running',
|
||||
oaSyncMode: 'full',
|
||||
oaSyncController: null,
|
||||
oaSyncProgress: {
|
||||
current: 0,
|
||||
@@ -570,16 +519,17 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'permission', 'isAdministrator']),
|
||||
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;
|
||||
},
|
||||
oaSyncTotalPage() {
|
||||
const total = Number(this.oaSyncProgress.total) || 0;
|
||||
@@ -642,6 +592,18 @@ export default {
|
||||
this.$refs.userFormRef?.validateField('deptId');
|
||||
},
|
||||
},
|
||||
isUserViewMode() {
|
||||
return this.userDialogMode === 'view';
|
||||
},
|
||||
userDialogTitle() {
|
||||
if (this.userDialogMode === 'add') {
|
||||
return '新增用户';
|
||||
}
|
||||
if (this.userDialogMode === 'view') {
|
||||
return '用户详情';
|
||||
}
|
||||
return '编辑用户';
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.user_add, false),
|
||||
@@ -714,28 +676,35 @@ export default {
|
||||
this.showResetPassword(res.data.data);
|
||||
});
|
||||
},
|
||||
handleDataManageCommand(command) {
|
||||
if (command === 'import') {
|
||||
this.handleImport();
|
||||
} else if (command === 'export') {
|
||||
this.handleExport();
|
||||
}
|
||||
},
|
||||
handleIamSync() {
|
||||
this.$confirm('确定从OA同步人员及组织信息?', '提示', {
|
||||
handleOaPersonFullSync() {
|
||||
this.$confirm('全量同步将拉取 OA 全部人员及组织;已存在且 OA 更新时间一致的记录会跳过。是否继续?', '全量同步', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.startOaSync();
|
||||
this.startOaSync(false);
|
||||
});
|
||||
},
|
||||
startOaSync() {
|
||||
handleOaPersonIncrementalSync() {
|
||||
this.$confirm(
|
||||
'增量同步仅拉取本地最大 OA 更新时间之后有变更的人员;不存在的新增,已存在且更新时间不一致的修改,一致的跳过。是否继续?',
|
||||
'增量同步',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
}
|
||||
).then(() => {
|
||||
this.startOaSync(true);
|
||||
});
|
||||
},
|
||||
startOaSync(incremental = false) {
|
||||
this.iamSyncLoading = true;
|
||||
this.oaSyncVisible = true;
|
||||
this.oaSyncRunning = true;
|
||||
this.oaSyncCancelled = false;
|
||||
this.oaSyncStatus = 'running';
|
||||
this.oaSyncMode = incremental ? 'incremental' : 'full';
|
||||
this.oaSyncController = new AbortController();
|
||||
this.oaSyncProgress = {
|
||||
current: 0,
|
||||
@@ -756,11 +725,22 @@ export default {
|
||||
},
|
||||
async runOaSyncPages() {
|
||||
const size = 50;
|
||||
const incremental = this.oaSyncMode === 'incremental';
|
||||
let current = 1;
|
||||
let modifiedSince = '';
|
||||
try {
|
||||
while (!this.oaSyncCancelled) {
|
||||
const res = await syncIamAccounts(current, size, this.oaSyncController?.signal);
|
||||
const res = await syncIamAccounts(
|
||||
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;
|
||||
@@ -892,11 +872,33 @@ export default {
|
||||
.filter(id => String(id) !== String(roleId));
|
||||
},
|
||||
handleDataScopeCheck(_, checked) {
|
||||
if (this.isUserViewMode) {
|
||||
return;
|
||||
}
|
||||
this.form.dataScopeDeptIds = checked.checkedKeys;
|
||||
},
|
||||
handleCustomerSelection(rows) {
|
||||
if (this.isUserViewMode) {
|
||||
return;
|
||||
}
|
||||
this.form.customerIds = rows.map(row => row.id);
|
||||
},
|
||||
isCustomerSelectable() {
|
||||
return !this.isUserViewMode;
|
||||
},
|
||||
normalizeSexDisplay(sex) {
|
||||
if (sex === -1 || sex === '-1' || sex === '' || sex === null || sex === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return Number(sex);
|
||||
},
|
||||
normalizeSexSubmit(sex, clearInvalid = false) {
|
||||
const value = Number(sex);
|
||||
if (value === 1 || value === 2 || value === 3) {
|
||||
return value;
|
||||
}
|
||||
return clearInvalid ? -1 : null;
|
||||
},
|
||||
formatDeptNames(deptName) {
|
||||
if (!deptName) {
|
||||
return [];
|
||||
@@ -906,6 +908,20 @@ export default {
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = Number(row.status) === 1 ? 0 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`确定${actionName}账号【${row.account}】?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
openUserDialog(mode, row = {}) {
|
||||
this.userDialogMode = mode;
|
||||
if (mode === 'add') {
|
||||
@@ -915,7 +931,7 @@ export default {
|
||||
realName: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
sex: 3,
|
||||
sex: undefined,
|
||||
postId: '',
|
||||
deptId: [],
|
||||
code: '',
|
||||
@@ -946,6 +962,7 @@ export default {
|
||||
personCategory: user.personCategory || 1,
|
||||
dataScopeRange: user.dataScopeRange || 1,
|
||||
dataLevelRange: user.dataLevelRange || 1,
|
||||
sex: this.normalizeSexDisplay(user.sex),
|
||||
};
|
||||
this.sensitiveManager.saveInitialData(this.form);
|
||||
this.userDialog = true;
|
||||
@@ -967,6 +984,7 @@ export default {
|
||||
postId: func.join(this.form.postId),
|
||||
roleId: func.join(this.form.roleId),
|
||||
leaderId: func.join(this.form.leaderId),
|
||||
sex: this.normalizeSexSubmit(this.form.sex, this.userDialogMode !== 'add'),
|
||||
};
|
||||
if (this.userDialogMode !== 'add') {
|
||||
delete row.password;
|
||||
@@ -987,6 +1005,7 @@ export default {
|
||||
row.roleId = func.join(row.roleId);
|
||||
row.postId = func.join(row.postId);
|
||||
row.leaderId = func.join(row.leaderId);
|
||||
row.sex = this.normalizeSexSubmit(row.sex);
|
||||
add(row).then(
|
||||
() => {
|
||||
this.initFlag = false;
|
||||
@@ -1008,6 +1027,7 @@ export default {
|
||||
row.roleId = func.join(row.roleId);
|
||||
row.postId = func.join(row.postId);
|
||||
row.leaderId = func.join(row.leaderId);
|
||||
row.sex = this.normalizeSexSubmit(row.sex, true);
|
||||
// 获取需要提交的数据
|
||||
const submitData = this.sensitiveManager.getSubmitData(row);
|
||||
update(submitData).then(
|
||||
@@ -1334,9 +1354,7 @@ export default {
|
||||
if (this.form.hasOwnProperty('leaderId')) {
|
||||
this.form.leaderId = func.split(this.form.leaderId);
|
||||
}
|
||||
if (this.form.sex === -1) {
|
||||
this.form.sex = '';
|
||||
}
|
||||
this.form.sex = this.normalizeSexDisplay(this.form.sex);
|
||||
// 保存初始脱敏数据
|
||||
this.sensitiveManager.saveInitialData(this.form);
|
||||
});
|
||||
|
||||
@@ -339,7 +339,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="经营范围" prop="businessScope" required>
|
||||
<el-form-item label="经营范围" prop="businessScope">
|
||||
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
|
||||
<el-option
|
||||
v-for="item in businessScopeOptions"
|
||||
@@ -460,9 +460,8 @@
|
||||
v-model="deptCascaderValue"
|
||||
:options="deptTree"
|
||||
:props="deptCascaderProps"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择 所属组织"
|
||||
disabled
|
||||
placeholder="桂物物流集团"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -1552,6 +1551,8 @@ import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/m
|
||||
const customerArchiveListRoute = '/vehicle/customer-archive';
|
||||
const customerArchiveFormRoute = '/vehicle/customer-archive/form';
|
||||
const customerArchiveListRefreshKey = 'customer-archive-list-refresh';
|
||||
/** 客商所属组织固定为顶层组织 */
|
||||
const FIXED_OWNER_DEPT_NAME = '桂物物流集团';
|
||||
|
||||
const createTimeRangeMap = {
|
||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||
@@ -1788,15 +1789,6 @@ export default {
|
||||
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||
{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' },
|
||||
],
|
||||
businessScope: [
|
||||
{
|
||||
required: true,
|
||||
type: 'array',
|
||||
min: 1,
|
||||
message: '请选择经营范围',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
remark: [{ max: 500, message: '备注最多500个字符', trigger: 'blur' }],
|
||||
},
|
||||
contactRules: {
|
||||
@@ -3043,17 +3035,34 @@ export default {
|
||||
});
|
||||
return result;
|
||||
},
|
||||
setDefaultTopLevelDept() {
|
||||
const deptIds = Array.isArray(this.archiveForm.deptId)
|
||||
? this.archiveForm.deptId
|
||||
: this.archiveForm.deptId
|
||||
? [this.archiveForm.deptId]
|
||||
: [];
|
||||
const firstTopLevelDept = this.deptTree[0];
|
||||
if (!this.isArchivePage || this.$route.query.id || deptIds.length || !firstTopLevelDept) {
|
||||
findTopLevelDeptByName(name) {
|
||||
const targetName = String(name || '').trim();
|
||||
if (!targetName) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
(this.deptTree || []).find(item => {
|
||||
const label = String(item.label || item.title || item.deptName || item.name || '').trim();
|
||||
return label === targetName;
|
||||
}) || null
|
||||
);
|
||||
},
|
||||
/**
|
||||
* 客商所属组织固定为顶层「桂物物流集团」,表单不可编辑。
|
||||
*/
|
||||
applyFixedOwnerDept() {
|
||||
if (!this.isArchivePage || this.readonly) {
|
||||
return;
|
||||
}
|
||||
this.onDeptChange([firstTopLevelDept.value]);
|
||||
const fixedDept =
|
||||
this.findTopLevelDeptByName(FIXED_OWNER_DEPT_NAME) || (this.deptTree || [])[0] || null;
|
||||
if (!fixedDept) {
|
||||
return;
|
||||
}
|
||||
this.onDeptChange([fixedDept.value]);
|
||||
},
|
||||
setDefaultTopLevelDept() {
|
||||
this.applyFixedOwnerDept();
|
||||
},
|
||||
onDeptChange(value) {
|
||||
// 级联单选:value 为根到选中节点的 ID 路径,取末级作为客商所属组织
|
||||
@@ -4104,6 +4113,7 @@ export default {
|
||||
this.ocrQualificationUploadFiles = [];
|
||||
this.selectedQualificationFiles = [];
|
||||
if (this.isPublicViewPage) this.ensurePublicViewOptions(this.archiveForm);
|
||||
this.applyFixedOwnerDept();
|
||||
this.archiveBox = true;
|
||||
this.loadChangeRecords();
|
||||
if (this.reviewMode) this.openReviewScore();
|
||||
@@ -4120,6 +4130,7 @@ export default {
|
||||
this.qualificationUploadFiles = [];
|
||||
this.ocrQualificationUploadFiles = [];
|
||||
this.selectedQualificationFiles = [];
|
||||
this.applyFixedOwnerDept();
|
||||
this.archiveBox = true;
|
||||
},
|
||||
resetArchive() {
|
||||
@@ -4162,6 +4173,8 @@ export default {
|
||||
return true;
|
||||
},
|
||||
normalizeArchive() {
|
||||
// 提交前再次固定所属组织,防止被绕过
|
||||
this.applyFixedOwnerDept();
|
||||
const archive = JSON.parse(JSON.stringify(this.archiveForm));
|
||||
if (archive.businessTermType === '长期') {
|
||||
archive.businessEndDate = null;
|
||||
|
||||
Reference in New Issue
Block a user