相关bug调整
This commit is contained in:
Vendored
+3
@@ -21,10 +21,12 @@ declare module 'vue' {
|
|||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
|
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
|
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
@@ -50,6 +52,7 @@ declare module 'vue' {
|
|||||||
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||||
|
ElLink: typeof import('element-plus/es')['ElLink']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ function reportError(error: PublicApiError, notifyError: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function readErrorMessage(response: Response) {
|
async function readErrorMessage(response: Response) {
|
||||||
|
if (response.status === 413) {
|
||||||
|
return "单个文件不能超过20MB,请压缩后重新上传";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const payload = await response.clone().json();
|
const payload = await response.clone().json();
|
||||||
return payload?.msg || payload?.message || `请求失败(${response.status})`;
|
return payload?.msg || payload?.message || `请求失败(${response.status})`;
|
||||||
|
|||||||
@@ -4,56 +4,62 @@
|
|||||||
* @Date: 2023-12-15 15:40:39
|
* @Date: 2023-12-15 15:40:39
|
||||||
!-->
|
!-->
|
||||||
<template>
|
<template>
|
||||||
<form-container v-model="visible" title="角色选择" form-size="90%" v-bind="$attrs">
|
<form-container v-model="visible" title="角色选择" :form-size="formSize" v-bind="$attrs" @close="onClose">
|
||||||
<div class="-mt-15px min-h-350px">
|
<div class="role-selector-content">
|
||||||
<el-row :gutter="12" justify="space-between">
|
<!-- 组织树 -->
|
||||||
<el-col :span="4">
|
<div class="selector-tree-col">
|
||||||
<el-scrollbar max-height="650px">
|
<TreeFilter label="name" class="filterWidth" title="组织列表" top-name="全局" :default-expand-all="false"
|
||||||
<TreeFilter
|
:request-api="orgTreeApi" @change="changeOrgTreeFilter" />
|
||||||
label="name"
|
</div>
|
||||||
class="filterWidth"
|
|
||||||
title="组织列表"
|
<!-- 待选角色 -->
|
||||||
top-name="全局"
|
<div class="selector-table-col">
|
||||||
:default-expand-all="false"
|
<ProTable ref="userTable" :columns="columns" :tool-button="false" :init-param="initParam"
|
||||||
:request-api="orgTreeApi"
|
:request-api="roleSelectorApi" :search-col="{ xs: 1, sm: 2, md: 2, lg: 2, xl: 2 }" max-height="300px">
|
||||||
@change="changeOrgTreeFilter"
|
|
||||||
/></el-scrollbar>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="10">
|
|
||||||
<ProTable ref="userTable" :columns="columns" :tool-button="false" :init-param="initParam" :request-api="roleSelectorApi">
|
|
||||||
<!-- 表格 header 按钮 -->
|
<!-- 表格 header 按钮 -->
|
||||||
<template #tableHeader="scope">
|
<template #tableHeader="scope">
|
||||||
<el-button type="primary" @click="addRecords(userTable!.tableData)">添加当前</el-button>
|
<el-button type="primary" @click="addRecords(userTable!.tableData)">添加当前</el-button>
|
||||||
<el-button type="primary" plain :disabled="!scope.isSelected" @click="addRecords(scope.selectedList)">添加选中</el-button>
|
<el-button type="primary" plain :disabled="!scope.isSelected"
|
||||||
|
@click="addRecords(scope.selectedList)">添加选中</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #operation="scope">
|
<template #operation="scope">
|
||||||
<el-button type="primary" link :icon="Plus" plain @click="addRecords([scope.row])">添加</el-button>
|
<el-button type="primary" link :icon="Plus" plain @click="addRecords([scope.row])">添加</el-button>
|
||||||
</template>
|
</template>
|
||||||
</ProTable>
|
</ProTable>
|
||||||
</el-col>
|
</div>
|
||||||
<el-col :span="10">
|
|
||||||
<ProTable ref="chooseTable" :columns="columns" :tool-button="true" :data="chooseDataTmp" @search="searchRecords" @reset="resetRecords">
|
<!-- 已选角色 -->
|
||||||
|
<div class="selector-table-col">
|
||||||
|
<div class="card choose-header-bar">
|
||||||
|
<div class="choose-header-left">
|
||||||
|
<span class="choose-title">已选角色清单</span>
|
||||||
|
<span v-if="maxCount" class="max-count-tip">(最多选择 {{ maxCount }} 个)</span>
|
||||||
|
</div>
|
||||||
|
<el-tag type="primary" size="small" effect="plain">已选择 {{ chooseData.length }} 个角色</el-tag>
|
||||||
|
</div>
|
||||||
|
<ProTable ref="chooseTable" :columns="chooseColumns" :tool-button="false" :pagination="false"
|
||||||
|
:data="chooseDataTmp" max-height="300px">
|
||||||
<!-- 表格 header 按钮 -->
|
<!-- 表格 header 按钮 -->
|
||||||
<template #tableHeader="scope">
|
<template #tableHeader="scope">
|
||||||
<el-button type="danger" @click="delRecords(chooseTable!.tableData)">删除当前</el-button>
|
<el-button type="danger" @click="delRecords(chooseTable!.tableData)">删除当前</el-button>
|
||||||
<el-button type="danger" plain :disabled="!scope.isSelected" @click="delRecords(scope.selectedList)">删除选中</el-button>
|
<el-button type="danger" plain :disabled="!scope.isSelected"
|
||||||
</template>
|
@click="delRecords(scope.selectedList)">删除选中</el-button>
|
||||||
<template #toolButton>
|
|
||||||
<span>已选择:{{ chooseData.length }}人</span>
|
|
||||||
<span v-if="maxCount">,最多选择:{{ maxCount }}人</span>
|
|
||||||
</template>
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #operation="scope">
|
<template #operation="scope">
|
||||||
<el-button type="danger" link :icon="Delete" plain @click="delRecords([scope.row])">删除</el-button>
|
<el-button type="danger" link :icon="Delete" plain @click="delRecords([scope.row])">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</ProTable>
|
</ProTable>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部按钮区域 -->
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
<div class="role-selector-footer">
|
||||||
<el-button @click="onClose"> 取消 </el-button>
|
<el-button @click="onClose"> 取消 </el-button>
|
||||||
<el-button type="primary" @click="handleOk"> 确定 </el-button>
|
<el-button type="primary" @click="handleOk"> 确定 </el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</form-container>
|
</form-container>
|
||||||
</template>
|
</template>
|
||||||
@@ -72,27 +78,36 @@ const visible = ref(false); //是否显示
|
|||||||
// 定义组件 props
|
// 定义组件 props
|
||||||
const props = withDefaults(defineProps<RoleSelectProps>(), {
|
const props = withDefaults(defineProps<RoleSelectProps>(), {
|
||||||
permission: false,
|
permission: false,
|
||||||
multiple: false
|
multiple: false,
|
||||||
|
formSize: "1300px"
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
// 如果表格需要初始化请求参数,直接定义传给 ProTable
|
||||||
const initParam = reactive<RoleSelectTableInitParams>({});
|
const initParam = reactive<RoleSelectTableInitParams>({});
|
||||||
// 获取 userTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
// 获取 userTable 元素
|
||||||
const userTable = ref<ProTableInstance>();
|
const userTable = ref<ProTableInstance>();
|
||||||
// 获取 chooseTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
// 获取 chooseTable 元素
|
||||||
const chooseTable = ref<ProTableInstance>();
|
const chooseTable = ref<ProTableInstance>();
|
||||||
// 表格配置项
|
|
||||||
|
// 待选角色表格配置项(包含搜索项)
|
||||||
const columns: ColumnProps<SysRole.SysRoleInfo>[] = [
|
const columns: ColumnProps<SysRole.SysRoleInfo>[] = [
|
||||||
{ type: "selection", fixed: "left", width: 50 },
|
{ type: "selection", fixed: "left", width: 50 },
|
||||||
{ prop: "operation", label: "操作", width: 80, fixed: "left" },
|
{ prop: "operation", label: "操作", width: 80, fixed: "left" },
|
||||||
{ prop: "name", label: "角色名", search: { el: "input", span: 2 } }
|
{ prop: "name", label: "角色名", search: { el: "input" } }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 已选角色表格配置项(不包含搜索,更轻量)
|
||||||
|
const chooseColumns: ColumnProps<SysRole.SysRoleInfo>[] = [
|
||||||
|
{ type: "selection", fixed: "left", width: 50 },
|
||||||
|
{ prop: "operation", label: "操作", width: 80, fixed: "left" },
|
||||||
|
{ prop: "name", label: "角色名" }
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 显示选择器 */
|
/** 显示选择器 */
|
||||||
function showSelector(data: SysRole.SysRoleInfo[] = []) {
|
function showSelector(data: SysRole.SysRoleInfo[] = []) {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
chooseDataTmp.value = data;
|
chooseDataTmp.value = data ? [...data] : [];
|
||||||
chooseData.value = data;
|
chooseData.value = data ? [...data] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭选择器 */
|
/** 关闭选择器 */
|
||||||
@@ -110,9 +125,10 @@ function handleOk() {
|
|||||||
|
|
||||||
/** 部门切换 */
|
/** 部门切换 */
|
||||||
function changeOrgTreeFilter(val: number | string) {
|
function changeOrgTreeFilter(val: number | string) {
|
||||||
userTable.value!.pageable.pageNum = 1;
|
if (userTable.value?.pageable) {
|
||||||
if (val != "") {
|
userTable.value.pageable.pageNum = 1;
|
||||||
// 如果传入的val不为空
|
}
|
||||||
|
if (val !== "") {
|
||||||
initParam.orgId = val;
|
initParam.orgId = val;
|
||||||
} else {
|
} else {
|
||||||
initParam.orgId = null;
|
initParam.orgId = null;
|
||||||
@@ -124,19 +140,19 @@ const chooseDataTmp = ref<SysRole.SysRoleInfo[]>([]); //临时选择的数据
|
|||||||
|
|
||||||
/** 添加记录 */
|
/** 添加记录 */
|
||||||
function addRecords(records: any[]) {
|
function addRecords(records: any[]) {
|
||||||
//如果不是多选,判断是否已经添加了
|
// 如果不是多选,判断是否已经添加了
|
||||||
if (!props.multiple) {
|
if (!props.multiple) {
|
||||||
if (chooseData.value.length > 0 || records.length > 1) {
|
if (chooseData.value.length > 0 || records.length > 1) {
|
||||||
ElMessage.warning("只可选择一条");
|
ElMessage.warning("只可选择一个角色");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chooseData.value = records;
|
chooseData.value = records;
|
||||||
chooseDataTmp.value = chooseData.value;
|
chooseDataTmp.value = chooseData.value;
|
||||||
} else {
|
} else {
|
||||||
//如果是多选,先判断已添加列表是否有重复的id,有则过滤掉,没有则直接添加
|
// 如果是多选,先判断已添加列表是否有重复的id,有则过滤掉,没有则直接添加
|
||||||
records = records.filter(item => !chooseData.value.find(it => it.id == item.id));
|
records = records.filter(item => !chooseData.value.find(it => it.id === item.id));
|
||||||
if (props.maxCount && props.maxCount < records.length + chooseData.value.length) {
|
if (props.maxCount && props.maxCount < records.length + chooseData.value.length) {
|
||||||
ElMessage.warning("最多选择" + props.maxCount + "条");
|
ElMessage.warning(`最多选择 ${props.maxCount} 个角色`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chooseData.value = chooseData.value.concat(records); // 添加到已选中列表
|
chooseData.value = chooseData.value.concat(records); // 添加到已选中列表
|
||||||
@@ -152,32 +168,97 @@ function delRecords(records: any[]) {
|
|||||||
chooseTable.value?.refresh(); // 刷新表格
|
chooseTable.value?.refresh(); // 刷新表格
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索记录 */
|
|
||||||
function searchRecords() {
|
|
||||||
if (chooseTable.value?.searchParam?.name) {
|
|
||||||
//搜索name符合的记录
|
|
||||||
chooseDataTmp.value = chooseDataTmp.value.filter(item => item.name.includes(chooseTable.value?.searchParam.name)); //过滤掉已选中的
|
|
||||||
chooseTable.value?.refresh(); //刷新表格
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置记录 */
|
|
||||||
function resetRecords() {
|
|
||||||
chooseDataTmp.value = chooseData.value;
|
|
||||||
chooseTable.value?.refresh(); //刷新表格
|
|
||||||
}
|
|
||||||
// 暴露方法
|
// 暴露方法
|
||||||
defineExpose({ showSelector });
|
defineExpose({ showSelector });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.filterWidth {
|
.role-selector-content {
|
||||||
width: 100%;
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: stretch;
|
||||||
|
min-height: 460px;
|
||||||
}
|
}
|
||||||
:deep(.el-tabs--border-card > .el-tabs__content) {
|
|
||||||
padding: 5px;
|
.selector-tree-col {
|
||||||
|
width: 220px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 460px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selector-table-col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-header-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 64px;
|
||||||
|
padding: 0 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.choose-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-count-tip {
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-selector-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.filter) {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
padding: 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.card) {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.table-search) {
|
||||||
|
padding: 14px 14px 0 !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.table-main) {
|
:deep(.table-main) {
|
||||||
height: 90%;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.table-header) {
|
||||||
|
|
||||||
|
.header-button-lf,
|
||||||
|
.header-button-ri {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export interface RoleSelectProps {
|
|||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
/** 最大角色数 */
|
/** 最大角色数 */
|
||||||
maxCount?: number;
|
maxCount?: number;
|
||||||
|
/** 弹窗宽度尺寸 */
|
||||||
|
formSize?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 角色选择器表格初始化参数 */
|
/** 角色选择器表格初始化参数 */
|
||||||
|
|||||||
@@ -243,36 +243,54 @@
|
|||||||
:key="record.id"
|
:key="record.id"
|
||||||
:timestamp="formatTime(record.createTime)"
|
:timestamp="formatTime(record.createTime)"
|
||||||
placement="top"
|
placement="top"
|
||||||
type="primary"
|
:type="changeTimelineType(record.changeType)"
|
||||||
>
|
>
|
||||||
<div class="change-record-card">
|
<div class="change-record-card">
|
||||||
|
<!-- 顶部信息:操作类型标签 + 生效日期 + 操作人 -->
|
||||||
<div class="change-record-header">
|
<div class="change-record-header">
|
||||||
<strong>{{ changeTypeText(record.changeType) }}</strong>
|
<div class="change-record-title-wrap">
|
||||||
<span>{{ record.createUser || "系统" }}</span>
|
<el-tag :type="changeTypeTagType(record.changeType)" size="default" effect="light" class="change-type-tag">
|
||||||
|
{{ changeTypeText(record.changeType) }}
|
||||||
|
</el-tag>
|
||||||
|
<span v-if="record.effectiveDate" class="change-effective-badge">
|
||||||
|
<el-icon class="mr-1"><Calendar /></el-icon>生效于 {{ formatDate(record.effectiveDate) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="record.effectiveDate" class="change-record-line">
|
<div class="change-operator">
|
||||||
生效日期:{{ formatDate(record.effectiveDate) }}
|
<el-icon class="operator-icon"><User /></el-icon>
|
||||||
|
<span class="operator-name">{{ record.createUser || "系统" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="record.remark" class="change-record-line">
|
|
||||||
备注:{{ record.remark }}
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasChangeSnapshot(record)" class="change-diff">
|
|
||||||
<div class="change-diff-title">变更明细</div>
|
<!-- 变更明细:轻量 Diff 流向列表 -->
|
||||||
<el-table
|
<div v-if="hasChangeSnapshot(record)" class="change-diff-flow">
|
||||||
:data="changeDiffRows(record)"
|
<div
|
||||||
border
|
v-for="diff in changeDiffRows(record)"
|
||||||
size="small"
|
:key="diff.key"
|
||||||
empty-text="前后值无差异"
|
class="change-diff-item"
|
||||||
class="change-diff-table"
|
|
||||||
>
|
>
|
||||||
<el-table-column prop="label" label="字段" min-width="120" />
|
<span class="diff-field-label">{{ diff.label }}</span>
|
||||||
<el-table-column prop="beforeText" label="修改前" min-width="150" />
|
<div class="diff-values">
|
||||||
<el-table-column prop="afterText" label="修改后" min-width="150" />
|
<span class="diff-val-before">{{ diff.beforeText }}</span>
|
||||||
</el-table>
|
<el-icon class="diff-arrow"><Right /></el-icon>
|
||||||
|
<span class="diff-val-after">{{ diff.afterText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!changeDiffRows(record).length" class="change-diff-none">
|
||||||
|
前后字段值无实质差异
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="change-record-empty">
|
<div v-else class="change-record-empty">
|
||||||
该记录未保存修改前后值
|
该记录未保存修改前后快照
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 真实备注(过滤掉无意义系统默认同义反复文案) -->
|
||||||
|
<div v-if="shouldShowRemark(record)" class="change-remark-box">
|
||||||
|
<el-icon class="remark-icon"><ChatDotRound /></el-icon>
|
||||||
|
<span class="remark-text">{{ record.remark }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 附件列表 -->
|
||||||
<div v-if="record.attachments?.length" class="change-attachments">
|
<div v-if="record.attachments?.length" class="change-attachments">
|
||||||
<el-button
|
<el-button
|
||||||
v-for="file in record.attachments"
|
v-for="file in record.attachments"
|
||||||
@@ -536,6 +554,7 @@
|
|||||||
v-model:file-list="regularizationFiles"
|
v-model:file-list="regularizationFiles"
|
||||||
multiple
|
multiple
|
||||||
:limit="10"
|
:limit="10"
|
||||||
|
:on-exceed="handleRegularizationExceed"
|
||||||
:http-request="uploadRegularizationFile"
|
:http-request="uploadRegularizationFile"
|
||||||
:before-upload="beforeRegularizationUpload"
|
:before-upload="beforeRegularizationUpload"
|
||||||
:on-remove="removeRegularizationFile"
|
:on-remove="removeRegularizationFile"
|
||||||
@@ -560,11 +579,14 @@
|
|||||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||||
import {
|
import {
|
||||||
Briefcase,
|
Briefcase,
|
||||||
|
Calendar,
|
||||||
|
ChatDotRound,
|
||||||
Clock,
|
Clock,
|
||||||
Connection,
|
Connection,
|
||||||
Document,
|
Document,
|
||||||
Download,
|
Download,
|
||||||
FirstAidKit,
|
FirstAidKit,
|
||||||
|
Right,
|
||||||
User,
|
User,
|
||||||
UserFilled,
|
UserFilled,
|
||||||
WarningFilled
|
WarningFilled
|
||||||
@@ -1206,6 +1228,44 @@ function changeTypeText(value?: string) {
|
|||||||
return map[value || ""] || value || "人事变更";
|
return map[value || ""] || value || "人事变更";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeTypeTagType(value?: string): "primary" | "success" | "warning" | "danger" | "info" {
|
||||||
|
switch (value) {
|
||||||
|
case "regularization":
|
||||||
|
return "success";
|
||||||
|
case "regularization_revoke":
|
||||||
|
return "warning";
|
||||||
|
case "hr_info_update":
|
||||||
|
return "primary";
|
||||||
|
default:
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeTimelineType(value?: string): "primary" | "success" | "warning" | "info" {
|
||||||
|
switch (value) {
|
||||||
|
case "regularization":
|
||||||
|
return "success";
|
||||||
|
case "regularization_revoke":
|
||||||
|
return "warning";
|
||||||
|
default:
|
||||||
|
return "primary";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldShowRemark(record: any) {
|
||||||
|
if (!record?.remark || !String(record.remark).trim()) return false;
|
||||||
|
const remark = String(record.remark).trim();
|
||||||
|
const defaultRemarks = [
|
||||||
|
"编辑员工人事信息",
|
||||||
|
"办理员工转正",
|
||||||
|
"撤销员工转正",
|
||||||
|
"编辑人事信息",
|
||||||
|
"办理转正",
|
||||||
|
"撤销转正"
|
||||||
|
];
|
||||||
|
return !defaultRemarks.includes(remark) && remark !== changeTypeText(record?.changeType);
|
||||||
|
}
|
||||||
|
|
||||||
type EmployeeChangeFieldKey =
|
type EmployeeChangeFieldKey =
|
||||||
| "employmentType"
|
| "employmentType"
|
||||||
| "regularizationStatus"
|
| "regularizationStatus"
|
||||||
@@ -1467,7 +1527,15 @@ function openRegularization(row: OnboardingEmployee) {
|
|||||||
regularizationVisible.value = true;
|
regularizationVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRegularizationExceed() {
|
||||||
|
ElMessage.warning("文件总数不超过 10");
|
||||||
|
}
|
||||||
|
|
||||||
function beforeRegularizationUpload(file: File) {
|
function beforeRegularizationUpload(file: File) {
|
||||||
|
if (regularizationFiles.value.length >= 10) {
|
||||||
|
ElMessage.warning("文件总数不超过 10");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const extension = "." + (file.name.split(".").pop() || "").toLowerCase();
|
const extension = "." + (file.name.split(".").pop() || "").toLowerCase();
|
||||||
const allowed = [
|
const allowed = [
|
||||||
".pdf", ".doc", ".docx", ".xls", ".xlsx",
|
".pdf", ".doc", ".docx", ".xls", ".xlsx",
|
||||||
@@ -1506,6 +1574,9 @@ async function submitRegularization() {
|
|||||||
if (!regularizationForm.regularizationDate) {
|
if (!regularizationForm.regularizationDate) {
|
||||||
return ElMessage.warning("请选择转正日期");
|
return ElMessage.warning("请选择转正日期");
|
||||||
}
|
}
|
||||||
|
if (regularizationFiles.value.length > 10) {
|
||||||
|
return ElMessage.warning("文件总数不超过 10");
|
||||||
|
}
|
||||||
if (regularizationFiles.value.some(file => file.status === "uploading")) {
|
if (regularizationFiles.value.some(file => file.status === "uploading")) {
|
||||||
return ElMessage.warning("材料仍在上传,请稍候");
|
return ElMessage.warning("材料仍在上传,请稍候");
|
||||||
}
|
}
|
||||||
@@ -1513,6 +1584,9 @@ async function submitRegularization() {
|
|||||||
const fileIds = regularizationFiles.value
|
const fileIds = regularizationFiles.value
|
||||||
.map(file => Number((file.response as any)?.fileId))
|
.map(file => Number((file.response as any)?.fileId))
|
||||||
.filter(fileId => Number.isFinite(fileId) && fileId > 0);
|
.filter(fileId => Number.isFinite(fileId) && fileId > 0);
|
||||||
|
if (fileIds.length > 10) {
|
||||||
|
return ElMessage.warning("文件总数不超过 10");
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
regularizationFiles.value.some(file => file.status === "fail")
|
regularizationFiles.value.some(file => file.status === "fail")
|
||||||
|| fileIds.length !== regularizationFiles.value.length
|
|| fileIds.length !== regularizationFiles.value.length
|
||||||
@@ -1753,59 +1827,175 @@ onBeforeUnmount(clearMaterialPreviews);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.change-record-card {
|
.change-record-card {
|
||||||
padding: 12px 14px;
|
padding: 14px 16px;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
background: #f8fafc;
|
background: #ffffff;
|
||||||
|
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-record-card:hover {
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-record-header {
|
.change-record-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
flex-wrap: wrap;
|
||||||
color: #1e293b;
|
gap: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px dashed #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-record-header span,
|
.change-record-title-wrap {
|
||||||
.change-record-line {
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-type-tag {
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-effective-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
|
background: #f8fafc;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-operator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
color: #475569;
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-record-line {
|
.operator-icon {
|
||||||
margin-top: 6px;
|
font-size: 13px;
|
||||||
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-diff {
|
.operator-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 变更 Diff 标签流 */
|
||||||
|
.change-diff-flow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-diff-title {
|
.change-diff-item {
|
||||||
margin-bottom: 8px;
|
display: flex;
|
||||||
color: #334155;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
padding: 8px 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-diff-table {
|
.diff-field-label {
|
||||||
width: 100%;
|
min-width: 90px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-values {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-val-before {
|
||||||
|
color: #64748b;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-arrow {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-val-after {
|
||||||
|
color: #2563eb;
|
||||||
|
background: #eff6ff;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-diff-none {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-record-empty {
|
.change-record-empty {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
background: #f1f5f9;
|
background: #f8fafc;
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 真实备注气泡框 */
|
||||||
|
.change-remark-box {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-left: 3px solid #cbd5e1;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-icon {
|
||||||
|
margin-top: 2px;
|
||||||
|
color: #94a3b8;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-text {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.change-attachments {
|
.change-attachments {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 8px;
|
margin-top: 10px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-card {
|
.section-card {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="单文件大小">
|
<el-form-item label="单文件大小">
|
||||||
<el-input-number v-model="materialForm.maxFileSizeMb" :min="1" :max="100" />
|
<el-input-number v-model="materialForm.maxFileSizeMb" :min="1" :max="20" />
|
||||||
<span class="unit-suffix">MB</span>
|
<span class="unit-suffix">MB</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|||||||
@@ -888,21 +888,10 @@
|
|||||||
plain
|
plain
|
||||||
:disabled="item.reviewStatus === 'passed' || !canManage(detail.task)"
|
:disabled="item.reviewStatus === 'passed' || !canManage(detail.task)"
|
||||||
:title="disabledReason(detail.task)"
|
:title="disabledReason(detail.task)"
|
||||||
@click="reviewMaterial(item.id, 'passed')"
|
@click="reviewMaterial(item.id)"
|
||||||
>
|
>
|
||||||
合格
|
合格
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
|
||||||
v-auth="'onboardingTaskMaterialReview'"
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:disabled="item.reviewStatus === 'need_supplement' || !canManage(detail.task)"
|
|
||||||
:title="disabledReason(detail.task)"
|
|
||||||
@click="reviewMaterial(item.id, 'need_supplement')"
|
|
||||||
>
|
|
||||||
需补充
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2869,21 +2858,9 @@ async function download(fileId: number, fallbackFileName?: string) {
|
|||||||
|
|
||||||
onBeforeUnmount(clearMaterialPreviews);
|
onBeforeUnmount(clearMaterialPreviews);
|
||||||
|
|
||||||
async function reviewMaterial(taskMaterialId: number, reviewStatus: string) {
|
async function reviewMaterial(taskMaterialId: number) {
|
||||||
let reviewRemark = "";
|
await onboardingTaskApi.reviewMaterial({ taskMaterialId, reviewStatus: "passed", reviewRemark: "" });
|
||||||
if (reviewStatus === "need_supplement") {
|
ElMessage.success("该材料已标记为合格");
|
||||||
const result = await getMessageBoxPromptResult(
|
|
||||||
ElMessageBox.prompt("请输入该材料退回补充说明:", "材料不合格退回", {
|
|
||||||
inputPattern: /\S+/,
|
|
||||||
inputErrorMessage: "请填写说明"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (!result) return;
|
|
||||||
reviewRemark = result.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
await onboardingTaskApi.reviewMaterial({ taskMaterialId, reviewStatus, reviewRemark });
|
|
||||||
ElMessage.success(reviewStatus === "passed" ? "该材料已标记为合格" : "已标记需补充");
|
|
||||||
|
|
||||||
if (detail.value?.task) {
|
if (detail.value?.task) {
|
||||||
await showDetail(detail.value.task, true);
|
await showDetail(detail.value.task, true);
|
||||||
|
|||||||
@@ -1396,12 +1396,16 @@ function getRemainingFileCount(mat: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PUBLIC_MATERIAL_MAX_FILE_SIZE_MB = 20;
|
||||||
|
const PUBLIC_MATERIAL_DEFAULT_EXTENSIONS = ["png", "jpg", "jpeg", "pdf"];
|
||||||
|
|
||||||
function getAllowedExtensions(mat: any) {
|
function getAllowedExtensions(mat: any) {
|
||||||
return String(mat.allowedExtensions || "")
|
const configured = String(mat.allowedExtensions || "")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((item: string) => item.trim().replace(/^\./, ""))
|
.map((item: string) => item.trim().replace(/^\./, ""))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
return configured.length ? configured : [...PUBLIC_MATERIAL_DEFAULT_EXTENSIONS];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUploadAccept(mat: any) {
|
function getUploadAccept(mat: any) {
|
||||||
@@ -1415,9 +1419,11 @@ function getMaterialLimitText(mat: any) {
|
|||||||
const formatText = allowedExtensions.length
|
const formatText = allowedExtensions.length
|
||||||
? allowedExtensions.join("、").toUpperCase()
|
? allowedExtensions.join("、").toUpperCase()
|
||||||
: "不限";
|
: "不限";
|
||||||
const sizeText = mat.maxFileSizeMb
|
const configuredSize = Number(mat.maxFileSizeMb);
|
||||||
? ` · 单个不超过 ${mat.maxFileSizeMb} MB`
|
const maxFileSizeMb = configuredSize > 0
|
||||||
: "";
|
? Math.min(configuredSize, PUBLIC_MATERIAL_MAX_FILE_SIZE_MB)
|
||||||
|
: PUBLIC_MATERIAL_MAX_FILE_SIZE_MB;
|
||||||
|
const sizeText = ` · 单个不超过 ${maxFileSizeMb} MB`;
|
||||||
return `最多 ${getMaxFileCount(mat)} 份 · 格式:${formatText}${sizeText}`;
|
return `最多 ${getMaxFileCount(mat)} 份 · 格式:${formatText}${sizeText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1702,8 +1708,12 @@ function beforeUpload(file: File, mat: any) {
|
|||||||
ElMessage.error(`仅支持格式:${mat.allowedExtensions}`);
|
ElMessage.error(`仅支持格式:${mat.allowedExtensions}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (mat.maxFileSizeMb && file.size > Number(mat.maxFileSizeMb) * 1024 * 1024) {
|
const configuredSize = Number(mat.maxFileSizeMb);
|
||||||
ElMessage.error(`单个文件不能超过 ${mat.maxFileSizeMb} MB`);
|
const maxFileSizeMb = configuredSize > 0
|
||||||
|
? Math.min(configuredSize, PUBLIC_MATERIAL_MAX_FILE_SIZE_MB)
|
||||||
|
: PUBLIC_MATERIAL_MAX_FILE_SIZE_MB;
|
||||||
|
if (file.size > maxFileSizeMb * 1024 * 1024) {
|
||||||
|
ElMessage.error(`单个文件不能超过 ${maxFileSizeMb} MB,请压缩后重新上传`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
setMaterialUploading(mat.materialTypeId, true);
|
setMaterialUploading(mat.materialTypeId, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user