Commit 1f356ec5ab50c3355d2cd01b1b7dee433635562d

Authored by chenhang4442024
1 parent 9d971593

feat: 增加人事考勤工资和盖章功能

Too many changes to show.

To preserve performance only 12 of 39 files are displayed.

src/api/project/account.ts
... ... @@ -2,19 +2,77 @@ import { defHttp } from '/@/utils/http/axios';
2 2  
3 3 enum Api {
4 4 ROLE_LIST = '/order/erp/roles/all',
  5 + ROLE_ADD = '/order/erp/roles/add',
  6 + ROLE_EDIT = '/order/erp/roles/edit',
  7 + ROLE_DELETE = '/order/erp/roles/delete',
  8 +
5 9 USER_LIST = '/order/erp/users/list_by_page',
6 10 USER_ADD = '/order/erp/users/add',
7 11 USER_EDIT = '/order/erp/users/edit',
8 12 USER_DELETE = '/order/erp/users/delete',
9 13 USER_OPT = '/order/erp/users/opt',
10 14 USER_RESET_PASSWORD = '/order/erp/users/reset', // 管理员重置密码
  15 +
  16 +
  17 + DEPT_LIST = '/order/erp/depts/all',
  18 + DEPT_ADD = '/order/erp/depts/add',
  19 + DEPT_EDIT = '/order/erp/depts/edit',
  20 + DEPT_DELETE = '/order/erp/depts/delete',
  21 +
  22 + COMPANY_LIST = '/order/erp/jobs/all',
  23 + COMPANY_ADD = '/order/erp/jobs/add',
  24 + COMPANY_EDIT = '/order/erp/jobs/edit',
  25 + COMPANY_DELETE = '/order/erp/jobs/delete',
  26 +
  27 + // 人事配置相关API
  28 + PERSONNEL_CONFIG_LIST = '/order/erp/system_setting/query_list',
  29 + PERSONNEL_CONFIG_ADD = '/order/erp/system_setting/add',
  30 + PERSONNEL_CONFIG_EDIT = '/order/erp/system_setting/edit',
  31 + PERSONNEL_CONFIG_DELETE = '/order/erp/system_setting/delete_by_id',
11 32 }
12 33  
13 34 export const getRoleList = async (params: any) => {
14   - return defHttp.post<any>({
  35 + const res = await defHttp.post<any>({
15 36 url: Api.ROLE_LIST,
16 37 params,
17 38 });
  39 +console.log('==== 角色列表API原始返回数据 ====', res);
  40 +
  41 + // 在API层直接过滤掉生产科角色
  42 + if (Array.isArray(res)) {
  43 + const filtered = res.filter(item => item.id !== 6);
  44 + console.log('==== API层过滤后的角色列表 ====', filtered);
  45 + return filtered;
  46 + }
  47 +
  48 + return res;
  49 +};
  50 +// 添加角色
  51 +export const addRole = async (params: any) => {
  52 + return defHttp.post<any>({
  53 + url: Api.ROLE_ADD,
  54 + params,
  55 + }, {
  56 + successMsg: '添加角色成功',
  57 + });
  58 +};
  59 +// 编辑角色
  60 +export const editRole = async (params: any) => {
  61 + return defHttp.post<any>({
  62 + url: Api.ROLE_EDIT,
  63 + params,
  64 + }, {
  65 + successMsg: '编辑角色成功',
  66 + });
  67 +};
  68 +// 删除角色
  69 +export const deleteRole = async (params: any) => {
  70 + return defHttp.post<any>({
  71 + url: Api.ROLE_DELETE,
  72 + params,
  73 + }, {
  74 + successMsg: '删除角色成功',
  75 + });
18 76 };
19 77  
20 78 export const getUserList = async (params: any) => {
... ... @@ -38,23 +96,21 @@ export const getUserList = async (params: any) =&gt; {
38 96 // };
39 97  
40 98 export const userAdd = async (params: any) => {
41   - return defHttp.post<any>(
42   - {
  99 + return defHttp.post<any>({
43 100 url: Api.USER_ADD,
44 101 params,
45   - },
46   - { message: '保存成功' },
47   - );
  102 + }, {
  103 + successMsg: '保存成功',
  104 + });
48 105 };
49 106  
50 107 export const userEdit = async (params: any) => {
51   - return defHttp.post<any>(
52   - {
  108 + return defHttp.post<any>({
53 109 url: Api.USER_EDIT,
54 110 params,
55   - },
56   - { message: '保存成功' },
57   - );
  111 + }, {
  112 + successMsg: '保存成功',
  113 + });
58 114 };
59 115  
60 116 export const userDelete = async (params: any) => {
... ... @@ -65,21 +121,186 @@ export const userDelete = async (params: any) =&gt; {
65 121 };
66 122  
67 123 export const userOpt = async (params: any) => {
68   - return defHttp.post<any>(
69   - {
  124 + return defHttp.post<any>({
70 125 url: Api.USER_OPT,
71 126 params,
72   - },
73   - { message: '操作成功' },
74   - );
  127 + }, {
  128 + successMsg: '操作成功',
  129 + });
75 130 };
76 131  
77 132 export const userResetPassword = async (params: any) => {
78   - return defHttp.post<any>(
79   - {
  133 + return defHttp.post<any>({
80 134 url: Api.USER_RESET_PASSWORD,
81 135 params,
82   - },
83   - { message: '重置密码成功' },
84   - );
  136 + }, {
  137 + successMsg: '重置密码成功',
  138 + });
  139 +};
  140 +export const getDeptList = async (params: any) => {
  141 + return defHttp.post<any>({
  142 + url: Api.DEPT_LIST,
  143 + params,
  144 + });
  145 +};
  146 +// 添加部门
  147 +export const addDept = async (params: any) => {
  148 + return defHttp.post<any>({
  149 + url: Api.DEPT_ADD,
  150 + params,
  151 + }, {
  152 + successMsg: '添加部门成功',
  153 + });
  154 +};
  155 +// 编辑部门
  156 +export const editDept = async (params: any) => {
  157 + return defHttp.post<any>({
  158 + url: Api.DEPT_EDIT,
  159 + params,
  160 + }, {
  161 + successMsg: '编辑部门成功',
  162 + });
  163 +};
  164 +// 删除部门
  165 +export const deleteDept = async (params: any) => {
  166 + return defHttp.post<any>({
  167 + url: Api.DEPT_DELETE,
  168 + params,
  169 + }, {
  170 + successMsg: '删除部门成功',
  171 + });
85 172 };
  173 +// 获取公司列表
  174 +export const getCompanyList = async (params: any) => {
  175 + return defHttp.post<any>({
  176 + url: Api.COMPANY_LIST,
  177 + params,
  178 + });
  179 +};
  180 +// 添加公司
  181 +export const addCompany = async (params: any) => {
  182 + return defHttp.post<any>({
  183 + url: Api.COMPANY_ADD,
  184 + params,
  185 + }, {
  186 + successMsg: '添加公司成功',
  187 + });
  188 +};
  189 +// 编辑公司
  190 +export const editCompany = async (params: any) => {
  191 + return defHttp.post<any>({
  192 + url: Api.COMPANY_EDIT,
  193 + params,
  194 + }, {
  195 + successMsg: '编辑公司成功',
  196 + });
  197 +};
  198 +// 删除公司
  199 +export const deleteCompany = async (params: any) => {
  200 + return defHttp.post<any>({
  201 + url: Api.COMPANY_DELETE,
  202 + params,
  203 + }, {
  204 + successMsg: '删除公司成功',
  205 + });
  206 +};
  207 +// 人事配置相关API
  208 +export const getPersonnelConfigList = async (params: any) => {
  209 + // 合并请求参数,确保必要的参数存在
  210 + const requestParams = {
  211 + settingType: 60,
  212 + ...params, // 保留其他可能的过滤参数
  213 + };
  214 +
  215 + const res = await defHttp.post<any>({
  216 + url: Api.PERSONNEL_CONFIG_LIST,
  217 + params: requestParams,
  218 + });
  219 +
  220 + // 处理后端返回的数据,转换为表格所需格式
  221 + let items: any[] = [];
  222 +
  223 + if (res && Array.isArray(res)) {
  224 + items = res.map((item: any) => {
  225 + // 解析relationValue JSON字符串
  226 + let relationData = { allDay: 0, weekDay: 0, payDay: 0, appointedDay: 0, monthlyWorkingDay: 0, settingType: 0 };
  227 + try {
  228 + if (item.relationValue) {
  229 +
  230 + // 检查relationValue是否已经是对象
  231 + if (typeof item.relationValue === 'string') {
  232 + const parsedData = JSON.parse(item.relationValue);
  233 + if (typeof parsedData === 'object' && parsedData !== null) {
  234 + relationData = { ...relationData, ...parsedData };
  235 + console.log('解析后的relationValue数据:', relationData);
  236 + }
  237 + } else {
  238 + // 尝试解析JSON字符串
  239 + const parsedData = JSON.parse(item.relationValue);
  240 + if (Array.isArray(parsedData) && parsedData.length > 0) {
  241 + relationData = { ...relationData, ...parsedData[0] };
  242 + }
  243 + }
  244 + }
  245 + } catch (e) {
  246 + console.error('解析relationValue失败:', e, 'relationValue:', item.relationValue);
  247 + }
  248 +
  249 + // 处理考勤配置数据 (settingType 61-64)
  250 + if(item.settingType == 61 || item.settingType == 62 || item.settingType == 63 || item.settingType == 64){
  251 + // 修正:确保monthlyWorkingDay映射到workDays,payDay映射到salaryDays
  252 + relationData.weekDay = relationData.monthlyWorkingDay;
  253 + // 确保payDay也被正确记录
  254 + relationData.payDay = relationData.payDay || 0;
  255 + relationData.settingType = item.settingType;
  256 + console.log('处理后的考勤数据:', relationData);
  257 + }
  258 +
  259 + return {
  260 + id: item.id,
  261 + year: item.settingValue, // 年份 - settingValue
  262 + month: item.relationName, // 月份 - relationName
  263 + calendarDays: relationData.allDay, // 日历天数 - relationValue中的allDay
  264 + workDays: relationData.weekDay || relationData.monthlyWorkingDay, // 工作日 - 优先使用weekDay,兼容monthlyWorkingDay
  265 + salaryDays: relationData.payDay, // 计薪日 - relationValue中的payDay
  266 + legalHolidays: relationData.appointedDay, // 法定日 - relationValue中的appointedDay
  267 + settingType: item.settingType
  268 + };
  269 + });
  270 + }
  271 +
  272 + // 添加最终处理后的数据日志
  273 + console.log('最终处理后的数据:', items);
  274 +
  275 + return {
  276 + items: items,
  277 + total: items.length
  278 + };
  279 +};
  280 +// 添加人事配置
  281 +export const addPersonnelConfig = async (params: any) => {
  282 + return defHttp.post<any>({
  283 + url: Api.PERSONNEL_CONFIG_ADD,
  284 + params,
  285 + }, {
  286 + successMsg: '添加人事配置成功',
  287 + });
  288 +};
  289 +// 编辑人事配置
  290 +export const editPersonnelConfig = async (params: any) => {
  291 + return defHttp.post<any>({
  292 + url: Api.PERSONNEL_CONFIG_EDIT,
  293 + params,
  294 + }, {
  295 + successMsg: '编辑人事配置成功',
  296 + });
  297 +};
  298 +// 删除人事配置
  299 +export const deletePersonnelConfig = async (params: any) => {
  300 + return defHttp.post<any>({
  301 + url: Api.PERSONNEL_CONFIG_DELETE,
  302 + params,
  303 + }, {
  304 + successMsg: '删除人事配置成功',
  305 + });
  306 +};
86 307 \ No newline at end of file
... ...
src/api/project/seal.ts 0 → 100644
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +
  3 +enum Api {
  4 + SEAL_LIST = '/order/erp/seal/list_by_page', // 印章申请列表
  5 + SEAL_CREATE = '/order/erp/seal/add', // 创建印章申请
  6 + SEAL_UPDATE = '/order/erp/seal/update', // 更新印章申请
  7 + SEAL_DELETE = '/order/erp/seal/delete', // 删除印章申请
  8 + SEAL_AUDIT = '/order/erp/seal/audit', // 审核印章申请
  9 + SYSTEM_SETTING = '/order/erp/system_setting/query_list', // 获取系统设置
  10 + COMPANY_LIST = '/order/erp/jobs/all', // 获取公司列表
  11 + DEPT_LIST = '/order/erp/depts/all', // 获取部门列表
  12 + SEAL_SET_STATUS = '/order/erp/seal/setStatus',
  13 +}
  14 +
  15 +// 获取印章申请列表
  16 +export const getSealList = async (params: any) => {
  17 + const res = await defHttp.post<any>({
  18 + url: Api.SEAL_LIST,
  19 + params,
  20 + });
  21 +
  22 + // 添加调试信息
  23 + console.log('[getSealList] 完整响应:', res);
  24 +
  25 + // 根据实际响应结构处理数据
  26 + if (res && res.records) {
  27 + console.log('[getSealList] records数据:', res.records);
  28 +
  29 + // 如果records是数组
  30 + if (Array.isArray(res.records)) {
  31 + if (res.records.length > 0) {
  32 + console.log('[getSealList] 第一条记录的字段:', Object.keys(res.records[0]));
  33 + console.log('[getSealList] 字段详情:', res.records[0]);
  34 + }
  35 +
  36 + return {
  37 + items: res.records,
  38 + total: res.total || res.records.length
  39 + };
  40 + }
  41 +
  42 + // 如果records是对象,可能包含实际的数据数组
  43 + if (res.records.records && Array.isArray(res.records.records)) {
  44 + console.log('[getSealList] 嵌套records数据:', res.records.records);
  45 + if (res.records.records.length > 0) {
  46 + console.log('[getSealList] 第一条记录的字段:', Object.keys(res.records.records[0]));
  47 + console.log('[getSealList] 字段详情:', res.records.records[0]);
  48 + }
  49 +
  50 + return {
  51 + items: res.records.records,
  52 + total: res.records.total || res.records.records.length
  53 + };
  54 + }
  55 + }
  56 +
  57 + // 如果数据结构不符合预期,返回空数据
  58 + console.log('[getSealList] 数据结构不符合预期,返回空数据');
  59 + return {
  60 + items: [],
  61 + total: 0
  62 + };
  63 +};
  64 +
  65 +// 获取文件分类选项
  66 +export const getFileTypeOptions = async () => {
  67 + const res = await defHttp.post<any>({
  68 + url: Api.SYSTEM_SETTING,
  69 + params: {
  70 + settingCode: 'fileType'
  71 + },
  72 + });
  73 +
  74 + console.log('[getFileTypeOptions] 响应:', res);
  75 +
  76 + if (res && res.records && Array.isArray(res.records)) {
  77 + return res.records.map(item => ({
  78 + label: item.relationValue,
  79 + value: item.id
  80 + }));
  81 + }
  82 +
  83 + if (res && Array.isArray(res)) {
  84 + return res.map(item => ({
  85 + label: item.relationValue,
  86 + value: item.id
  87 + }));
  88 + }
  89 +
  90 + return [];
  91 +};
  92 +
  93 +// 获取印章类型选项
  94 +export const getSealTypeOptions = async () => {
  95 + const res = await defHttp.post<any>({
  96 + url: Api.SYSTEM_SETTING,
  97 + params: {
  98 + settingType: [80]
  99 + },
  100 + });
  101 +
  102 + console.log('[getSealTypeOptions] 响应:', res);
  103 +
  104 + if (res && res.records && Array.isArray(res.records)) {
  105 + return res.records.map(item => ({
  106 + label: item.relationName,
  107 + value: item.id
  108 + }));
  109 + }
  110 +
  111 + if (res && Array.isArray(res)) {
  112 + return res.map(item => ({
  113 + label: item.relationName,
  114 + value: item.id
  115 + }));
  116 + }
  117 +
  118 + return [];
  119 +};
  120 +
  121 +// 获取部门选项
  122 +export const getDeptOptions = async () => {
  123 + const res = await defHttp.post<any>({
  124 + url: Api.DEPT_LIST,
  125 + });
  126 +
  127 + console.log('[getDeptOptions] 响应:', res);
  128 +
  129 + if (res && res.records && Array.isArray(res.records)) {
  130 + return res.records.map(item => ({
  131 + label: item.name,
  132 + value: item.id
  133 + }));
  134 + }
  135 +
  136 + if (res && Array.isArray(res)) {
  137 + return res.map(item => ({
  138 + label: item.name,
  139 + value: item.id
  140 + }));
  141 + }
  142 +
  143 + return [];
  144 +};
  145 +
  146 +// 获取公司列表选项
  147 +export const getCompanyOptions = async () => {
  148 + const res = await defHttp.post<any>({
  149 + url: Api.COMPANY_LIST,
  150 + });
  151 +
  152 + console.log('[getCompanyOptions] 响应:', res);
  153 + console.log('[getCompanyOptions] 响应类型:', typeof res);
  154 + console.log('[getCompanyOptions] 是否为数组:', Array.isArray(res));
  155 +
  156 + // 处理不同的响应格式
  157 + let dataArray = res;
  158 +
  159 + // 如果响应包含 data 字段
  160 + if (res && res.data) {
  161 + console.log('[getCompanyOptions] 找到 data 字段:', res.data);
  162 + dataArray = res.data;
  163 + }
  164 +
  165 + // 如果响应包含 records 字段
  166 + if (res && res.records) {
  167 + console.log('[getCompanyOptions] 找到 records 字段:', res.records);
  168 + dataArray = res.records;
  169 + }
  170 +
  171 + console.log('[getCompanyOptions] 最终数据数组:', dataArray);
  172 + console.log('[getCompanyOptions] 最终数据是否为数组:', Array.isArray(dataArray));
  173 +
  174 + if (Array.isArray(dataArray)) {
  175 + console.log('[getCompanyOptions] 数组长度:', dataArray.length);
  176 + if (dataArray.length > 0) {
  177 + console.log('[getCompanyOptions] 第一个元素:', dataArray[0]);
  178 + console.log('[getCompanyOptions] 第一个元素的字段:', Object.keys(dataArray[0]));
  179 + }
  180 +
  181 + const options = dataArray.map(item => ({
  182 + label: item.name,
  183 + value: item.id
  184 + }));
  185 +
  186 + console.log('[getCompanyOptions] 转换后的选项:', options);
  187 + return options;
  188 + }
  189 +
  190 + console.log('[getCompanyOptions] 数据不是数组格式:', dataArray);
  191 + return [];
  192 +};
  193 +
  194 +// 创建印章申请
  195 +export const createSealApplication = async (params: {
  196 + fileName: string;
  197 + fileTypeId: number;
  198 + signType: string;
  199 + count: number;
  200 + sealTypesId: number[];
  201 + companyId: number;
  202 + deptId: number;
  203 + files: string[];
  204 + note: string;
  205 +}) => {
  206 + return await defHttp.post<any>({
  207 + url: Api.SEAL_CREATE,
  208 + params,
  209 + });
  210 +};
  211 +
  212 +// 编辑印章申请
  213 +export const updateSealApplication = async (params: {
  214 + id: string | number;
  215 + fileName: string;
  216 + fileTypeId: number;
  217 + signType: string;
  218 + count: number;
  219 + sealTypesId: number[];
  220 + companyId: number;
  221 + deptId: number;
  222 + files: string[];
  223 + note: string;
  224 +}) => {
  225 + return await defHttp.post<any>({
  226 + url: '/order/erp/seal/edit',
  227 + params,
  228 + });
  229 +};
  230 +
  231 +// 更新印章申请
  232 +export const updateSeal = async (params: any) => {
  233 + return await defHttp.post<any>({
  234 + url: Api.SEAL_UPDATE,
  235 + params,
  236 + });
  237 +};
  238 +
  239 +// 删除印章申请
  240 +export const deleteSeal = async (id: string | number) => {
  241 + return await defHttp.post<any>({
  242 + url: Api.SEAL_DELETE,
  243 + params: {
  244 + id: id
  245 + },
  246 + });
  247 +};
  248 +
  249 +// 审核印章申请
  250 +export const auditSeal = async (params: any) => {
  251 + return await defHttp.post<any>({
  252 + url: Api.SEAL_AUDIT,
  253 + params,
  254 + });
  255 +};
  256 +
  257 +/**
  258 + * 设置印章申请审核状态
  259 + */
  260 +export const setSealStatus = (params: { id: string | number; managerStatus?: number; financeStatus?: number }) =>
  261 + defHttp.post<any>({ url: Api.SEAL_SET_STATUS, params });
0 262 \ No newline at end of file
... ...
src/api/system/attendance.ts 0 → 100644
  1 +// @ts-nocheck
  2 +import { defHttp } from '/@/utils/http/axios';
  3 +
  4 +// 导出一些基本值确保模块初始化
  5 +export const API_PREFIX = '/order/erp/users/oldAttendance';
  6 +
  7 +/**
  8 + * 考勤数据分页查询参数
  9 + */
  10 +export interface AttendanceParams {
  11 + page: number;
  12 + pageSize: number;
  13 + dateTime: string; // 格式为 yyyy-MM
  14 +}
  15 +
  16 +/**
  17 + * 考勤数据接口
  18 + */
  19 +export interface AttendanceData {
  20 + userId: number; // 用户ID
  21 + nickName: string; // 昵称
  22 + chineseName: string; // 姓名
  23 + roleId: number; // 角色ID
  24 + roleName: string; // 角色名称
  25 + deptName: string; // 部门名称
  26 + companyName: string; // 公司名称
  27 + monthlyAttendanceId: string; // 考勤组ID
  28 + monthlyAttendanceName: string; // 考勤组
  29 + payDay: number; // 个计薪日
  30 + monthlyWorkingDay: number; // 工作日
  31 + attendanceDays: number; // 实际出勤小时
  32 + leaveDays: number; // 请假
  33 + compensatoryLeaveDays: number; // 本月调休
  34 + storageLeaveDays: number; // 本月存休
  35 + workHours: number; // 全部上班时间
  36 + lastMonthLeaveDays: number; // 上月存休
  37 + totalStorageLeaveDays: number; // 累计存休
  38 + attendanceStatus: number; // 审核状态: 0:待审核,10:已审核,20:已驳回
  39 +}
  40 +
  41 +/**
  42 + * MyBatis-Plus分页格式
  43 + */
  44 +export interface PageResult<T> {
  45 + records: T[];
  46 + total: number;
  47 + size: number;
  48 + current: number;
  49 + orders: any[];
  50 + optimizeCountSql: boolean;
  51 + searchCount: boolean;
  52 + pages: number;
  53 + hitCount: boolean;
  54 +}
  55 +
  56 +/**
  57 + * 获取考勤数据分页列表
  58 + */
  59 +export function getAttendanceList(params: AttendanceParams) {
  60 +
  61 +
  62 + // 使用post方式请求数据,确保请求体中包含所有必需参数
  63 + return defHttp.post<PageResult<AttendanceData>>({
  64 + url: `${API_PREFIX}/list_by_page`,
  65 + data: params,
  66 + });
  67 +}
  68 +
  69 +/**
  70 + * 获取考勤审核状态文本
  71 + */
  72 +export function getAttendanceStatusText(status: number): string {
  73 + switch (status) {
  74 + case 0:
  75 + return '待审核';
  76 + case 10:
  77 + return '已审核';
  78 + case 20:
  79 + return '已驳回';
  80 + default:
  81 + return '未创建';
  82 + }
  83 +}
0 84 \ No newline at end of file
... ...
src/api/system/salary.ts 0 → 100644
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
  3 +
  4 +enum Api {
  5 + WAGES_LIST_BY_PAGE = '/order/erp/users/oldWages/list_by_page',
  6 + SAVE_WAGES = '/order/erp/users/oldWages/save',
  7 + DELETE_WAGES = '/order/erp/users/oldWages/delete',
  8 + APPROVE_WAGES = '/order/erp/users/oldWages/approve',
  9 + REJECT_WAGES = '/order/erp/users/oldWages/reject',
  10 + DISTRIBUTE_WAGES = '/order/erp/users/oldWages/distribute',
  11 +}
  12 +
  13 +export interface WagesParams extends BasicPageParams {
  14 + chineseName?: string;
  15 + nickName?: string;
  16 + companyName?: string;
  17 + dateTime: string; // 必须参数,工资月份
  18 + wagesStatus?: string;
  19 + distributionStatus?: string;
  20 +}
  21 +
  22 +export interface WagesItem {
  23 + userId: number;
  24 + nickName: string;
  25 + chineseName: string;
  26 + roleId: number;
  27 + roleName: string;
  28 + deptName: string;
  29 + companyName: string;
  30 + wagesId: number;
  31 + wagesName: string;
  32 + tax: number;
  33 + basicWages: number;
  34 + postAllowance: number;
  35 + managementAllowance: number;
  36 + phoneAllowance: number;
  37 + mealAllowance: number;
  38 + transportationAllowance: number;
  39 + attendanceAllowance: number;
  40 + performanceAllowance: number;
  41 + commissionPrice: number;
  42 + bonusPrice: number;
  43 + deductibleSalaryPrice: number;
  44 + otherDeductionsPrice: number;
  45 + salaryPrice: number;
  46 + ActualSalaryPrice: number;
  47 + totalCost: number;
  48 + individualTotal: number;
  49 + companyTotal: number;
  50 + personalPensionInsurance: number;
  51 + personalPensionMedicalInsurance: number;
  52 + personalPensionUnemploymentInsurance: number;
  53 + personalPensionLargeScaleMedicalInsurance: number;
  54 + personalPensionProvidentFundInsurance: number;
  55 + companyInsurance: number;
  56 + companyMedicalInsurance: number;
  57 + companyUnemploymentInsurance: number;
  58 + companyEmploymentInjuryInsurance: number;
  59 + companyMaternityInsurance: number;
  60 + companyLargeScaleMedicalInsurance: number;
  61 + companyProvidentFundInsurance: number;
  62 + dateTime: string;
  63 + wagesStatus: string; // 审核状态:0:待审核,10:已审核,20:已驳回
  64 + distributionStatus: string; // 发放状态:0:待审核,10:已发放
  65 +}
  66 +
  67 +// 定义可能的响应格式
  68 +export interface WagesResponse {
  69 + records?: WagesItem[];
  70 + total?: number;
  71 + page?: number;
  72 + pageSize?: number;
  73 + current?: number;
  74 + pages?: number;
  75 +}
  76 +
  77 +/**
  78 + * 分页获取工资列表数据
  79 + */
  80 +export function getWagesListByPage(params: WagesParams) {
  81 + return defHttp.post<WagesResponse>({
  82 + url: Api.WAGES_LIST_BY_PAGE,
  83 + params,
  84 + });
  85 +}
  86 +
  87 +/**
  88 + * 保存工资数据
  89 + */
  90 +export function saveWages(params: WagesItem) {
  91 + return defHttp.post<WagesItem>({
  92 + url: Api.SAVE_WAGES,
  93 + params,
  94 + });
  95 +}
  96 +
  97 +/**
  98 + * 删除工资数据
  99 + */
  100 +export function deleteWages(wagesId: string) {
  101 + return defHttp.post({
  102 + url: Api.DELETE_WAGES,
  103 + params: { wagesId },
  104 + });
  105 +}
  106 +
  107 +/**
  108 + * 审核工资数据
  109 + */
  110 +export function approveWages(wagesId: string) {
  111 + return defHttp.post({
  112 + url: Api.APPROVE_WAGES,
  113 + params: { wagesId },
  114 + });
  115 +}
  116 +
  117 +/**
  118 + * 驳回工资数据
  119 + */
  120 +export function rejectWages(wagesId: string) {
  121 + return defHttp.post({
  122 + url: Api.REJECT_WAGES,
  123 + params: { wagesId },
  124 + });
  125 +}
  126 +
  127 +/**
  128 + * 发放工资
  129 + */
  130 +export function distributeWages(wagesId: string) {
  131 + return defHttp.post({
  132 + url: Api.DISTRIBUTE_WAGES,
  133 + params: { wagesId },
  134 + });
  135 +}
0 136 \ No newline at end of file
... ...
src/router/routes/modules/project/system.ts
... ... @@ -8,7 +8,7 @@ const system: AppRouteModule = {
8 8 path: '/system',
9 9 name: 'System',
10 10 component: LAYOUT,
11   - redirect: '/system/account',
  11 + redirect: '/system/personnel',
12 12 meta: {
13 13 orderNo: 4,
14 14 icon: 'ion:settings-outline',
... ... @@ -16,6 +16,17 @@ const system: AppRouteModule = {
16 16 },
17 17 children: [
18 18 {
  19 + path: 'personnel',
  20 + name: 'PersonnelManagement',
  21 + component: LAYOUT,
  22 + redirect: '/system/personnel/account',
  23 + meta: {
  24 + title: '人事管理',
  25 + roles: [RoleEnum.ADMIN],
  26 + ignoreKeepAlive: false,
  27 + },
  28 + children: [
  29 + {
19 30 path: 'account',
20 31 name: 'AccountManagement',
21 32 meta: {
... ... @@ -25,6 +36,51 @@ const system: AppRouteModule = {
25 36 },
26 37 component: () => import('/@/views/project/account/index.vue'),
27 38 },
  39 + {
  40 + path: 'salary',
  41 + name: 'SalaryManagement',
  42 + component: LAYOUT,
  43 + redirect: '/system/personnel/salary/attendance',
  44 + meta: {
  45 + title: '工资管理',
  46 + roles: [RoleEnum.ADMIN],
  47 + ignoreKeepAlive: false,
  48 + },
  49 + children: [
  50 + {
  51 + path: 'attendance',
  52 + name: 'AttendanceStatistics',
  53 + meta: {
  54 + title: '出勤统计表',
  55 + roles: [RoleEnum.ADMIN],
  56 + ignoreKeepAlive: false,
  57 + },
  58 + component: () => import('/@/views/project/personnel/salary/attendance/index.vue'),
  59 + },
  60 + {
  61 + path: 'payroll',
  62 + name: 'PayrollList',
  63 + meta: {
  64 + title: '工资表',
  65 + roles: [RoleEnum.ADMIN],
  66 + ignoreKeepAlive: false,
  67 + },
  68 + component: () => import('/@/views/project/personnel/salary/payroll/index.vue'),
  69 + },
  70 + ],
  71 + },
  72 + {
  73 + path: 'personnel-config',
  74 + name: 'PersonnelConfig',
  75 + meta: {
  76 + title: '人事配置',
  77 + roles: [RoleEnum.ADMIN],
  78 + ignoreKeepAlive: false,
  79 + },
  80 + component: () => import('/@/views/project/account/personnel-config/index.vue'),
  81 + },
  82 + ],
  83 + },
28 84 // {
29 85 // path: 'account_detail/:id',
30 86 // name: 'AccountDetail',
... ...
src/views/project/account/AccountModal.vue
1 1 <template>
2   - <BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
3   - <BasicForm @register="registerForm" />
  2 + <BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" width="1000px" @cancel="handleCancel" :confirmLoading="submitLoading">
  3 + <template v-if="accountType === 'production'">
  4 + <BasicForm @register="productionFormRegister" />
  5 + </template>
  6 + <template v-else>
  7 + <!-- 员工档案表单 -->
  8 + <div class="employee-file-form">
  9 + <!-- 使用标签页组件 -->
  10 + <a-tabs v-model:activeKey="activeTabKey" class="tab-container" @change="handleTabChange">
  11 + <a-tab-pane key="basic" tab="基本信息">
  12 + <a-form ref="employeeFormRef" :model="formState" layout="inline" class="form-section" :rules="rules">
  13 + <a-row :gutter="[16, 16]" style="width: 100%">
  14 + <!-- 第一行:姓名、昵称、性别、入职时间 -->
  15 + <a-col :span="6">
  16 + <a-form-item label="姓名" name="chineseName">
  17 + <a-input v-model:value="formState.chineseName" placeholder="输入" />
  18 + </a-form-item>
  19 + </a-col>
  20 + <a-col :span="6">
  21 + <a-form-item label="昵称(英文名)" name="nickName">
  22 + <a-input v-model:value="formState.nickName" placeholder="输入" />
  23 + </a-form-item>
  24 + </a-col>
  25 + <a-col :span="6">
  26 + <a-form-item label="性别" name="gender">
  27 + <a-select v-model:value="formState.gender" placeholder="选择">
  28 + <a-select-option value="男">男</a-select-option>
  29 + <a-select-option value="女">女</a-select-option>
  30 + </a-select>
  31 + </a-form-item>
  32 + </a-col>
  33 + <a-col :span="6">
  34 + <a-form-item label="入职时间">
  35 + <a-date-picker v-model:value="formState.userBasicDetailVO.entryTime" style="width: 100%" />
  36 + </a-form-item>
  37 + </a-col>
  38 +
  39 + <!-- 第二行:公司、部门、岗位 -->
  40 + <a-col :span="8">
  41 + <a-form-item label="公司" name="companyId">
  42 + <a-select v-model:value="formState.companyId" placeholder="选择">
  43 + <a-select-option v-for="company in companyOptions" :key="company.id" :value="company.id">
  44 + {{ company.name }}
  45 + </a-select-option>
  46 + </a-select>
  47 + </a-form-item>
  48 + </a-col>
  49 + <a-col :span="8">
  50 + <a-form-item label="部门" name="deptId">
  51 + <a-select v-model:value="formState.deptId" placeholder="选择">
  52 + <a-select-option v-for="dept in deptOptions" :key="dept.id" :value="dept.id">
  53 + {{ dept.name }}
  54 + </a-select-option>
  55 + </a-select>
  56 + </a-form-item>
  57 + </a-col>
  58 + <a-col :span="8">
  59 + <a-form-item label="岗位" name="roleId">
  60 + <a-select
  61 + v-model:value="formState.roleId"
  62 + placeholder="选择"
  63 + :disabled="isUpdate && isFieldLocked('roleId')"
  64 + >
  65 + <a-select-option v-for="role in roleOptions" :key="role.id" :value="role.id">
  66 + {{ role.description }}
  67 + </a-select-option>
  68 + </a-select>
  69 + </a-form-item>
  70 + </a-col>
  71 +
  72 + <!-- 第三行:身份证号、年龄、考勤组 -->
  73 + <a-col :span="8">
  74 + <a-form-item label="身份证号" name="idCard" :rules="idCardRules">
  75 + <a-input
  76 + v-model:value="formState.userBasicDetailVO.idCard"
  77 + placeholder="输入身份证号"
  78 + @blur="handleIdCardChange"
  79 + @input="handleIdCardInput"
  80 + />
  81 + </a-form-item>
  82 + </a-col>
  83 + <a-col :span="8">
  84 + <a-form-item label="年龄">
  85 + <a-input
  86 + v-model:value="formState.userBasicDetailVO.age"
  87 + placeholder="自动计算"
  88 +
  89 + style="background-color: #f5f5f5;"
  90 + />
  91 + </a-form-item>
  92 + </a-col>
  93 + <a-col :span="8">
  94 + <a-form-item label="考勤组">
  95 + <a-select
  96 + v-model:value="formState.userBasicDetailVO.selectedAttendanceType"
  97 + placeholder="选择考勤组"
  98 + @change="handleAttendanceTypeChange"
  99 + :style="{ width: '100%' }"
  100 + :disabled="isUpdate && isFieldLocked('monthlyAttendanceName')"
  101 + >
  102 + <!-- 如果有monthlyAttendanceName但没有匹配的selectedAttendanceType,添加一个特殊选项 -->
  103 + <a-select-option v-if="formState.userBasicDetailVO.monthlyAttendanceName && !hasMatchingAttendanceOption"
  104 + :value="'custom'" key="custom">
  105 + {{ formState.userBasicDetailVO.monthlyAttendanceName }}
  106 + </a-select-option>
  107 +
  108 + <template v-if="attendanceOptions.length === 0">
  109 + <a-select-option value="" disabled>没有配置</a-select-option>
  110 + </template>
  111 + <template v-else>
  112 + <a-select-opt-group v-for="yearGroup in yearGroups" :key="yearGroup.year" :label="`${yearGroup.year}年`">
  113 + <a-select-option v-for="option in yearGroup.options" :key="option.value" :value="option.value">
  114 + {{ option.label }}
  115 + </a-select-option>
  116 + </a-select-opt-group>
  117 + </template>
  118 + </a-select>
  119 + <!-- 调试信息 -->
  120 + <div v-if="isDebug" style="color: #999; font-size: 12px;">
  121 + 选项数量: {{ attendanceOptions.length }}
  122 + </div>
  123 + </a-form-item>
  124 + </a-col>
  125 +
  126 + <!-- 第四行:出生年月、民族、户籍地址 -->
  127 + <a-col :span="8">
  128 + <a-form-item label="出生年月">
  129 + <a-date-picker
  130 + v-model:value="formState.userBasicDetailVO.dateBirth"
  131 + style="width: 100%; background-color: #f5f5f5;"
  132 +
  133 + placeholder="自动计算"
  134 + />
  135 + </a-form-item>
  136 + </a-col>
  137 + <a-col :span="6">
  138 + <a-form-item label="民族">
  139 + <a-input v-model:value="formState.userBasicDetailVO.nationality" placeholder="输入" />
  140 + </a-form-item>
  141 + </a-col>
  142 + <a-col :span="10">
  143 + <a-form-item label="户籍地址">
  144 + <a-input v-model:value="formState.userBasicDetailVO.registeredAddress" placeholder="输入" />
  145 + </a-form-item>
  146 + </a-col>
  147 +
  148 + <!-- 第五行:婚姻状况、政治面貌、现居地址 -->
  149 + <a-col :span="6">
  150 + <a-form-item label="婚姻状况">
  151 + <a-select v-model:value="formState.userBasicDetailVO.maritalStatus" placeholder="选择">
  152 + <a-select-option value="未婚">未婚</a-select-option>
  153 + <a-select-option value="已婚">已婚</a-select-option>
  154 + <a-select-option value="离婚">离婚</a-select-option>
  155 + <a-select-option value="丧偶">丧偶</a-select-option>
  156 + </a-select>
  157 + </a-form-item>
  158 + </a-col>
  159 + <a-col :span="6">
  160 + <a-form-item label="政治面貌">
  161 + <a-select v-model:value="formState.userBasicDetailVO.politicalStatus" placeholder="选择">
  162 + <a-select-option value="党员">党员</a-select-option>
  163 + <a-select-option value="团员">团员</a-select-option>
  164 + <a-select-option value="群众">群众</a-select-option>
  165 + <a-select-option value="其他">其他</a-select-option>
  166 + </a-select>
  167 + </a-form-item>
  168 + </a-col>
  169 + <a-col :span="12">
  170 + <a-form-item label="现居地址">
  171 + <a-input v-model:value="formState.userBasicDetailVO.address" placeholder="输入" />
  172 + </a-form-item>
  173 + </a-col>
  174 +
  175 + <!-- 第六行:手机号码、微信、电子邮箱 -->
  176 + <a-col :span="8">
  177 + <a-form-item label="手机号码" name="phone">
  178 + <a-input v-model:value="formState.phone" placeholder="输入" />
  179 + </a-form-item>
  180 + </a-col>
  181 + <a-col :span="8">
  182 + <a-form-item label="微信">
  183 + <a-input v-model:value="formState.userBasicDetailVO.wechat" placeholder="输入" />
  184 + </a-form-item>
  185 + </a-col>
  186 + <a-col :span="8">
  187 + <a-form-item label="电子邮箱" name="email">
  188 + <a-input v-model:value="formState.email" placeholder="输入" />
  189 + </a-form-item>
  190 + </a-col>
  191 +
  192 + <!-- 第七行:紧急联系人、关系、紧急联系人手机号码 -->
  193 + <a-col :span="8">
  194 + <a-form-item label="紧急联系人">
  195 + <a-input v-model:value="formState.userBasicDetailVO.emergencyContact" placeholder="输入" />
  196 + </a-form-item>
  197 + </a-col>
  198 + <a-col :span="8">
  199 + <a-form-item label="关系">
  200 + <a-input v-model:value="formState.userBasicDetailVO.relationShip" placeholder="输入" />
  201 + </a-form-item>
  202 + </a-col>
  203 + <a-col :span="8">
  204 + <a-form-item label="紧急联系人电话">
  205 + <a-input v-model:value="formState.userBasicDetailVO.emergencyContactPhone" placeholder="输入" />
  206 + </a-form-item>
  207 + </a-col>
  208 +
  209 + <!-- 第八行:银行卡号、开户行、社保关系 -->
  210 + <a-col :span="8">
  211 + <a-form-item label="银行卡号">
  212 + <a-input v-model:value="formState.userBasicDetailVO.card" placeholder="输入" />
  213 + </a-form-item>
  214 + </a-col>
  215 + <a-col :span="8">
  216 + <a-form-item label="开户行">
  217 + <a-input v-model:value="formState.userBasicDetailVO.openingBank" placeholder="输入" />
  218 + </a-form-item>
  219 + </a-col>
  220 + <a-col :span="8">
  221 + <a-form-item label="社保关系">
  222 + <a-input v-model:value="formState.userBasicDetailVO.socialSecurity" placeholder="输入" />
  223 + </a-form-item>
  224 + </a-col>
  225 +
  226 + <!-- 第九行:试用期开始时间、试用期结束时间、员工状态 -->
  227 + <a-col :span="8">
  228 + <a-form-item label="试用期开始时间">
  229 + <a-date-picker
  230 + v-model:value="formState.userBasicDetailVO.periodStartTime"
  231 + style="width: 100%"
  232 + :disabled="isUpdate && isFieldLocked('periodStartTime')"
  233 + />
  234 + </a-form-item>
  235 + </a-col>
  236 + <a-col :span="8">
  237 + <a-form-item label="试用期结束时间">
  238 + <a-date-picker
  239 + v-model:value="formState.userBasicDetailVO.periodEndTime"
  240 + style="width: 100%"
  241 + :disabled="isUpdate && isFieldLocked('periodEndTime')"
  242 + />
  243 + </a-form-item>
  244 + </a-col>
  245 + <a-col :span="8">
  246 + <a-form-item label="员工状态">
  247 + <a-select v-model:value="formState.status" disabled placeholder="选择">
  248 + <a-select-option :value="10">在职</a-select-option>
  249 + <a-select-option :value="20">离职</a-select-option>
  250 + </a-select>
  251 + </a-form-item>
  252 + </a-col>
  253 +
  254 + <!-- 第十行:劳务合同签署时间、劳务合同结束时间、合同期限 -->
  255 + <a-col :span="8">
  256 + <a-form-item label="合同签署时间">
  257 + <a-date-picker
  258 + v-model:value="formState.userBasicDetailVO.contractStartTime"
  259 + style="width: 100%"
  260 + :disabled="isUpdate && isFieldLocked('contractStartTime')"
  261 + />
  262 + </a-form-item>
  263 + </a-col>
  264 + <a-col :span="8">
  265 + <a-form-item label="合同结束时间">
  266 + <a-date-picker
  267 + v-model:value="formState.userBasicDetailVO.contractEndTime"
  268 + style="width: 100%"
  269 + :disabled="isUpdate && isFieldLocked('contractEndTime')"
  270 + />
  271 + </a-form-item>
  272 + </a-col>
  273 + <a-col :span="8">
  274 + <a-form-item label="合同期限">
  275 + <a-input :value="calculateContractDuration" disabled style="width: 100%" />
  276 + </a-form-item>
  277 + </a-col>
  278 + </a-row>
  279 + </a-form>
  280 +
  281 + <!-- 备注信息 -->
  282 + <div class="section-header">备注信息</div>
  283 + <a-form layout="inline" class="form-section" :model="formState">
  284 + <a-row :gutter="[16, 16]" style="width: 100%">
  285 + <a-col :span="24">
  286 + <a-form-item label="备注">
  287 + <a-textarea v-model:value="formState.remark" placeholder="输入备注信息" :rows="3" />
  288 + </a-form-item>
  289 + </a-col>
  290 + </a-row>
  291 + </a-form>
  292 + </a-tab-pane>
  293 +
  294 + <a-tab-pane key="salary" tab="工资配置">
  295 + <div class="tab-content-container">
  296 + <a-form layout="inline" class="form-section" :model="formState">
  297 + <a-row :gutter="[16, 16]" style="width: 100%">
  298 + <a-col :span="8">
  299 + <a-form-item label="基本工资">
  300 + <a-input-number
  301 + v-model:value="formState.userWagesVO.basicWages"
  302 + placeholder="输入"
  303 + style="width: 100%"
  304 + :precision="2"
  305 + :min="0"
  306 + :disabled="isUpdate && isFieldLocked('basicWages')"
  307 + />
  308 + </a-form-item>
  309 + </a-col>
  310 + <a-col :span="8">
  311 + <a-form-item label="岗位津贴">
  312 + <a-input-number
  313 + v-model:value="formState.userWagesVO.postAllowance"
  314 + placeholder="输入"
  315 + style="width: 100%"
  316 + :precision="2"
  317 + :min="0"
  318 + :disabled="isUpdate && isFieldLocked('postAllowance')"
  319 + />
  320 + </a-form-item>
  321 + </a-col>
  322 + <a-col :span="8">
  323 + <a-form-item label="管理岗位津贴">
  324 + <a-input-number
  325 + v-model:value="formState.userWagesVO.managementAllowance"
  326 + placeholder="输入"
  327 + style="width: 100%"
  328 + :precision="2"
  329 + :min="0"
  330 + :disabled="isUpdate && isFieldLocked('managementAllowance')"
  331 + />
  332 + </a-form-item>
  333 + </a-col>
  334 +
  335 + <a-col :span="8">
  336 + <a-form-item label="话费补贴">
  337 + <a-input-number
  338 + v-model:value="formState.userWagesVO.phoneAllowance"
  339 + placeholder="输入"
  340 + style="width: 100%"
  341 + :precision="2"
  342 + :min="0"
  343 + :disabled="isUpdate && isFieldLocked('phoneAllowance')"
  344 + />
  345 + </a-form-item>
  346 + </a-col>
  347 + <a-col :span="8">
  348 + <a-form-item label="餐补">
  349 + <a-input-number
  350 + v-model:value="formState.userWagesVO.mealAllowance"
  351 + placeholder="输入"
  352 + style="width: 100%"
  353 + :precision="2"
  354 + :min="0"
  355 + :disabled="isUpdate && isFieldLocked('mealAllowance')"
  356 + />
  357 + </a-form-item>
  358 + </a-col>
  359 + <a-col :span="8">
  360 + <a-form-item label="交通补贴">
  361 + <a-input-number
  362 + v-model:value="formState.userWagesVO.transportationAllowance"
  363 + placeholder="输入"
  364 + style="width: 100%"
  365 + :precision="2"
  366 + :min="0"
  367 + :disabled="isUpdate && isFieldLocked('transportationAllowance')"
  368 + />
  369 + </a-form-item>
  370 + </a-col>
  371 +
  372 + <a-col :span="8">
  373 + <a-form-item label="全勤奖">
  374 + <a-input-number
  375 + v-model:value="formState.userWagesVO.attendanceAllowance"
  376 + placeholder="输入"
  377 + style="width: 100%"
  378 + :precision="2"
  379 + :min="0"
  380 + :disabled="isUpdate && isFieldLocked('attendanceAllowance')"
  381 + />
  382 + </a-form-item>
  383 + </a-col>
  384 + <a-col :span="8">
  385 + <a-form-item label="绩效部分">
  386 + <a-input-number
  387 + v-model:value="formState.userWagesVO.performanceAllowance"
  388 + placeholder="输入"
  389 + style="width: 100%"
  390 + :precision="2"
  391 + :min="0"
  392 + :disabled="isUpdate && isFieldLocked('performanceAllowance')"
  393 + />
  394 + </a-form-item>
  395 + </a-col>
  396 + <a-col :span="8">
  397 + <a-form-item label="社保待遇">
  398 + <a-select
  399 + v-model:value="formState.userWagesVO.socialSetting"
  400 + placeholder="选择"
  401 + @change="handleSocialSettingChange"
  402 + :disabled="isUpdate && isFieldLocked('socialSetting')"
  403 + >
  404 + <template v-if="socialSettingOptions.length === 0">
  405 + <a-select-option value="" disabled>没有配置</a-select-option>
  406 + </template>
  407 + <template v-else>
  408 + <a-select-opt-group v-for="yearGroup in socialSettingYearGroups" :key="yearGroup.year" :label="`${yearGroup.year}年`">
  409 + <a-select-option v-for="option in yearGroup.options" :key="option.value" :value="option.value">
  410 + {{ option.label }}
  411 + </a-select-option>
  412 + </a-select-opt-group>
  413 + </template>
  414 + </a-select>
  415 + <!-- 调试信息 -->
  416 + <div v-if="isDebug" style="color: #999; font-size: 12px;">
  417 + 选项数量: {{ socialSettingOptions.length }}
  418 + <button @click.prevent="parseTestData()" style="margin-left: 8px;">测试解析</button>
  419 + </div>
  420 + </a-form-item>
  421 + </a-col>
  422 +
  423 + <!-- 社保信息部分 - 个人部分 -->
  424 + <a-col :span="24">
  425 + <div class="section-header">社保信息 - 个人部分</div>
  426 + </a-col>
  427 +
  428 + <a-col :span="8">
  429 + <a-form-item label="养老保险(个人)">
  430 + <a-input-number
  431 + v-model:value="formState.userWagesVO.personalPensionInsurance"
  432 + placeholder="系统生成"
  433 + style="width: 100%"
  434 + :precision="2"
  435 + :min="0"
  436 + :disabled="true" />
  437 + </a-form-item>
  438 + </a-col>
  439 +
  440 + <a-col :span="8">
  441 + <a-form-item label="医疗保险(个人)">
  442 + <a-input-number
  443 + v-model:value="formState.userWagesVO.personalPensionMedicalInsurance"
  444 + placeholder="系统生成"
  445 + style="width: 100%"
  446 + :precision="2"
  447 + :min="0"
  448 + :disabled="true" />
  449 + </a-form-item>
  450 + </a-col>
  451 +
  452 + <a-col :span="8">
  453 + <a-form-item label="失业保险(个人)">
  454 + <a-input-number
  455 + v-model:value="formState.userWagesVO.personalPensionUnemploymentInsurance"
  456 + placeholder="系统生成"
  457 + style="width: 100%"
  458 + :precision="2"
  459 + :min="0"
  460 + :disabled="true" />
  461 + </a-form-item>
  462 + </a-col>
  463 +
  464 + <a-col :span="8">
  465 + <a-form-item label="大额医疗(个人)">
  466 + <a-input-number
  467 + v-model:value="formState.userWagesVO.personalPensionLargeScaleMedicalInsurance"
  468 + placeholder="系统生成"
  469 + style="width: 100%"
  470 + :precision="2"
  471 + :min="0"
  472 + :disabled="true" />
  473 + </a-form-item>
  474 + </a-col>
  475 +
  476 + <a-col :span="8">
  477 + <a-form-item label="住房公积金(个人)">
  478 + <a-input-number
  479 + v-model:value="formState.userWagesVO.personalPensionProvidentFundInsurance"
  480 + placeholder="系统生成"
  481 + style="width: 100%"
  482 + :precision="2"
  483 + :min="0"
  484 + :disabled="true" />
  485 + </a-form-item>
  486 + </a-col>
  487 +
  488 + <!-- 社保信息部分 - 企业部分 -->
  489 + <a-col :span="24">
  490 + <div class="section-header">社保信息 - 企业部分</div>
  491 + </a-col>
  492 +
  493 + <a-col :span="8">
  494 + <a-form-item label="养老保险(企业)">
  495 + <a-input-number
  496 + v-model:value="formState.userWagesVO.companyInsurance"
  497 + placeholder="系统生成"
  498 + style="width: 100%"
  499 + :precision="2"
  500 + :min="0"
  501 + :disabled="true" />
  502 + </a-form-item>
  503 + </a-col>
  504 +
  505 + <a-col :span="8">
  506 + <a-form-item label="医疗保险(企业)">
  507 + <a-input-number
  508 + v-model:value="formState.userWagesVO.companyMedicalInsurance"
  509 + placeholder="系统生成"
  510 + style="width: 100%"
  511 + :precision="2"
  512 + :min="0"
  513 + :disabled="true" />
  514 + </a-form-item>
  515 + </a-col>
  516 +
  517 + <a-col :span="8">
  518 + <a-form-item label="失业保险(企业)">
  519 + <a-input-number
  520 + v-model:value="formState.userWagesVO.companyUnemploymentInsurance"
  521 + placeholder="系统生成"
  522 + style="width: 100%"
  523 + :precision="2"
  524 + :min="0"
  525 + :disabled="true" />
  526 + </a-form-item>
  527 + </a-col>
  528 +
  529 + <a-col :span="8">
  530 + <a-form-item label="工伤保险(企业)">
  531 + <a-input-number
  532 + v-model:value="formState.userWagesVO.companyEmploymentInjuryInsurance"
  533 + placeholder="系统生成"
  534 + style="width: 100%"
  535 + :precision="2"
  536 + :min="0"
  537 + :disabled="true" />
  538 + </a-form-item>
  539 + </a-col>
  540 +
  541 + <a-col :span="8">
  542 + <a-form-item label="生育保险(企业)">
  543 + <a-input-number
  544 + v-model:value="formState.userWagesVO.companyMaternityInsurance"
  545 + placeholder="系统生成"
  546 + style="width: 100%"
  547 + :precision="2"
  548 + :min="0"
  549 + :disabled="true" />
  550 + </a-form-item>
  551 + </a-col>
  552 +
  553 + <a-col :span="8">
  554 + <a-form-item label="大额医疗(企业)">
  555 + <a-input-number
  556 + v-model:value="formState.userWagesVO.companyLargeScaleMedicalInsurance"
  557 + placeholder="系统生成"
  558 + style="width: 100%"
  559 + :precision="2"
  560 + :min="0"
  561 + :disabled="true" />
  562 + </a-form-item>
  563 + </a-col>
  564 +
  565 + <a-col :span="8">
  566 + <a-form-item label="住房公积金(企业)">
  567 + <a-input-number
  568 + v-model:value="formState.userWagesVO.companyProvidentFundInsurance"
  569 + placeholder="系统生成"
  570 + style="width: 100%"
  571 + :precision="2"
  572 + :min="0"
  573 + :disabled="true" />
  574 + </a-form-item>
  575 + </a-col>
  576 +
  577 + <!-- 社保信息部分 - 个人部分 -->
  578 + <a-col :span="24">
  579 + <div style="height: 20px;"></div>
  580 + </a-col>
  581 + </a-row>
  582 + </a-form>
  583 + </div>
  584 + </a-tab-pane>
  585 +
  586 +
  587 + </a-tabs>
  588 + </div>
  589 + </template>
4 590 </BasicModal>
5 591 </template>
6 592 <script lang="ts">
7   - import { defineComponent, ref, computed, unref, toRaw } from 'vue';
  593 + import { defineComponent, ref, computed, unref, toRaw, reactive, onMounted, nextTick } from 'vue';
8 594 import { BasicModal, useModalInner } from '/@/components/Modal';
9 595 import { BasicForm, useForm } from '/@/components/Form/index';
  596 + // 分别从不同文件导入
10 597 import { accountFormSchema } from './account.data';
11   -
  598 + import { productionAccountFormSchema } from './productionForm.data';
  599 + import { getCompanyList, getDeptList, getRoleList } from '/@/api/project/account';
  600 + import { defHttp } from '/@/utils/http/axios';
  601 + import type { FormInstance } from 'ant-design-vue';
  602 + import dayjs from 'dayjs'; // 引入dayjs用于日期处理
12 603 export default defineComponent({
13 604 name: 'AccountModal',
14 605 components: { BasicModal, BasicForm },
... ... @@ -16,49 +607,1616 @@
16 607 setup(_, { emit }) {
17 608 const isUpdate = ref(true);
18 609 const rowId = ref('');
  610 + const accountType = ref('employee'); // 默认为员工账号
  611 + const activeTabKey = ref('basic'); // 默认显示基本信息标签
  612 + const employeeFormRef = ref<FormInstance | null>(null); // 添加表单ref类型
  613 + const isDebug = ref(true); // 调试模式
  614 + const submitLoading = ref(false); // 添加提交状态变量
  615 +
  616 + // 添加字段锁定状态管理
  617 + const fieldLockStatus = ref<Record<string, boolean>>({
  618 + // 基本信息字段
  619 + roleId: false, // 岗位
  620 + monthlyAttendanceName: false, // 考勤组
  621 + periodStartTime: false, // 试用期开始时间
  622 + periodEndTime: false, // 试用期结束时间
  623 + contractStartTime: false, // 合同签署时间
  624 + contractEndTime: false, // 合同结束时间
  625 + // 工资配置字段
  626 + basicWages: false, // 基本工资
  627 + postAllowance: false, // 岗位津贴
  628 + managementAllowance: false, // 管理岗位津贴
  629 + phoneAllowance: false, // 话费补贴
  630 + mealAllowance: false, // 餐补
  631 + transportationAllowance: false, // 交通补贴
  632 + attendanceAllowance: false, // 全勤奖
  633 + performanceAllowance: false, // 绩效部分
  634 + socialSetting: false // 社保待遇
  635 + });
  636 +
  637 + // 检查字段是否被锁定的函数
  638 + const isFieldLocked = (fieldName: string): boolean => {
  639 + return fieldLockStatus.value[fieldName] || false;
  640 + };
  641 +
  642 + // 处理字段锁定状态的函数
  643 + const processFieldLockStatus = (userWagesFieldLockApplyVO: any) => {
  644 + if (!userWagesFieldLockApplyVO) {
  645 +
  646 + return;
  647 + }
  648 +
  649 +
  650 +
  651 + // 字段映射关系
  652 + const fieldMapping = {
  653 + // userWagesFieldLockApplyVO字段名 -> 本地字段名
  654 + 'roleName': 'roleId', // 岗位
  655 + 'monthlyAttendanceName': 'monthlyAttendanceName', // 考勤组
  656 + 'periodStartTime': 'periodStartTime', // 试用期开始时间
  657 + 'periodEndTime': 'periodEndTime', // 试用期结束时间
  658 + 'contractStartTime': 'contractStartTime', // 合同签署时间
  659 + 'contractEndTime': 'contractEndTime', // 合同结束时间
  660 + 'basicWages': 'basicWages', // 基本工资
  661 + 'postAllowance': 'postAllowance', // 岗位津贴
  662 + 'managementAllowance': 'managementAllowance', // 管理岗位津贴
  663 + 'phoneAllowance': 'phoneAllowance', // 话费补贴
  664 + 'mealAllowance': 'mealAllowance', // 餐补
  665 + 'transportationAllowance': 'transportationAllowance', // 交通补贴
  666 + 'attendanceAllowance': 'attendanceAllowance', // 全勤奖
  667 + 'performanceAllowance': 'performanceAllowance', // 绩效部分
  668 + 'socialSettingName': 'socialSetting' // 社保待遇
  669 + };
  670 +
  671 + // 重置所有字段为可编辑状态
  672 + Object.keys(fieldLockStatus.value).forEach(key => {
  673 + fieldLockStatus.value[key] = false;
  674 + });
  675 +
  676 +
  677 + // 根据返回的锁定状态设置字段
  678 + Object.entries(fieldMapping).forEach(([apiField, localField]) => {
  679 + const apiValue = userWagesFieldLockApplyVO[apiField];
  680 +
  681 +
  682 + if (apiValue !== undefined && apiValue !== null) {
  683 + const isLocked = apiValue === 'LOCKED';
  684 + fieldLockStatus.value[localField] = isLocked;
  685 +
  686 + } else {
  687 + console.log(`字段 ${apiField} 在API数据中不存在或为空`);
  688 + }
  689 + });
  690 +
  691 + console.log('最终字段锁定状态:', fieldLockStatus.value);
  692 + };
  693 +
  694 + // 定义表单验证规则
  695 + const rules = {
  696 + chineseName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
  697 + nickName: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
  698 + gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
  699 + companyId: [{ required: true, message: '请选择公司', trigger: 'change' }],
  700 + deptId: [{ required: true, message: '请选择部门', trigger: 'change' }],
  701 + roleId: [{ required: true, message: '请选择岗位', trigger: 'change' }],
  702 + phone: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
  703 + email: [{ required: true, message: '请输入电子邮箱', trigger: 'blur' }]
  704 + // 工资字段不设置必填规则,用户没有填写就不发送字段属性
  705 + };
  706 +
  707 + // 身份证号校验规则
  708 + const idCardRules = [
  709 + {
  710 + validator: (rule: any, value: string) => {
  711 + if (!value) {
  712 + return Promise.resolve(); // 非必填,允许为空
  713 + }
  714 +
  715 + // 身份证号格式校验
  716 + if (!validateIdCard(value)) {
  717 + return Promise.reject(new Error('请输入正确的身份证号码'));
  718 + }
  719 +
  720 + return Promise.resolve();
  721 + },
  722 + trigger: 'blur'
  723 + }
  724 + ];
  725 +
  726 + // 身份证号校验函数
  727 + function validateIdCard(idCard: string): boolean {
  728 + if (!idCard) return false;
  729 +
  730 + // 去除空格
  731 + idCard = idCard.replace(/\s/g, '');
  732 +
  733 + // 18位身份证号正则表达式
  734 + const reg18 = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
  735 + // 15位身份证号正则表达式
  736 + const reg15 = /^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/;
  737 +
  738 + if (idCard.length === 18) {
  739 + if (!reg18.test(idCard)) return false;
  740 +
  741 + // 校验18位身份证号的校验码
  742 + const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  743 + const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
  744 +
  745 + let sum = 0;
  746 + for (let i = 0; i < 17; i++) {
  747 + sum += parseInt(idCard[i]) * weights[i];
  748 + }
  749 +
  750 + const checkCode = checkCodes[sum % 11];
  751 + return idCard[17].toUpperCase() === checkCode;
  752 + } else if (idCard.length === 15) {
  753 + return reg15.test(idCard);
  754 + }
  755 +
  756 + return false;
  757 + }
  758 +
  759 + // 从身份证号提取出生日期
  760 + function extractBirthFromIdCard(idCard: string): dayjs.Dayjs | null {
  761 + if (!idCard || !validateIdCard(idCard)) return null;
  762 +
  763 + idCard = idCard.replace(/\s/g, '');
  764 +
  765 + let birthStr = '';
  766 + if (idCard.length === 18) {
  767 + birthStr = idCard.substring(6, 14); // YYYYMMDD
  768 + } else if (idCard.length === 15) {
  769 + birthStr = '19' + idCard.substring(6, 12); // YYMMDD -> 19YYMMDD
  770 + }
  771 +
  772 + if (birthStr.length === 8) {
  773 + const year = parseInt(birthStr.substring(0, 4));
  774 + const month = parseInt(birthStr.substring(4, 6));
  775 + const day = parseInt(birthStr.substring(6, 8));
  776 +
  777 + // 验证日期有效性
  778 + const birthDate = dayjs(`${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`);
  779 + if (birthDate.isValid()) {
  780 + return birthDate;
  781 + }
  782 + }
  783 +
  784 + return null;
  785 + }
  786 +
  787 + // 计算年龄
  788 + function calculateAge(birthDate: dayjs.Dayjs): number {
  789 + const today = dayjs();
  790 + let age = today.year() - birthDate.year();
  791 +
  792 + // 如果今年的生日还没到,年龄减1
  793 + if (today.month() < birthDate.month() ||
  794 + (today.month() === birthDate.month() && today.date() < birthDate.date())) {
  795 + age--;
  796 + }
  797 +
  798 + return age;
  799 + }
  800 +
  801 + // 身份证号输入处理
  802 + function handleIdCardInput(e: Event) {
  803 + const target = e.target as HTMLInputElement;
  804 + let value = target.value;
  805 +
  806 + // 限制输入长度和字符
  807 + value = value.replace(/[^\dXx]/g, ''); // 只允许数字和X
  808 + if (value.length > 18) {
  809 + value = value.substring(0, 18);
  810 + }
  811 +
  812 + formState.userBasicDetailVO.idCard = value;
  813 + }
  814 +
  815 + // 身份证号失焦处理
  816 + function handleIdCardChange() {
  817 + const idCard = formState.userBasicDetailVO.idCard;
  818 +
  819 + if (!idCard) {
  820 + // 清空相关字段
  821 + formState.userBasicDetailVO.age = undefined;
  822 + formState.userBasicDetailVO.dateBirth = null;
  823 + return;
  824 + }
  825 +
  826 + if (validateIdCard(idCard)) {
  827 + // 提取出生日期
  828 + const birthDate = extractBirthFromIdCard(idCard);
  829 + if (birthDate) {
  830 + formState.userBasicDetailVO.dateBirth = birthDate;
  831 + formState.userBasicDetailVO.age = calculateAge(birthDate).toString();
  832 + console.log('身份证号校验通过,自动填充出生日期和年龄');
  833 + }
  834 + } else {
  835 + // 身份证号格式错误,清空相关字段
  836 + formState.userBasicDetailVO.age = undefined;
  837 + formState.userBasicDetailVO.dateBirth = null;
  838 + }
  839 + }
  840 +
  841 + // 用于表单数据的响应式对象
  842 + const formState = reactive({
  843 + id: '',
  844 + chineseName: '',
  845 + nickName: '',
  846 + gender: '',
  847 + phone: '',
  848 + companyId: undefined,
  849 + roleId: undefined,
  850 + deptId: undefined,
  851 + email: '',
  852 + remark: undefined,
  853 + status: undefined,
  854 + baseSalary: undefined,
  855 + positionAllowance: undefined,
  856 + managementAllowance: undefined,
  857 + pensionInsurance: 'basic',
  858 + medicalInsurance: 'basic',
  859 + unemploymentInsurance: 'basic',
  860 + // 用户基本详情VO对象,包含更多员工详细信息
  861 + userBasicDetailVO: {
  862 + idCard: undefined,
  863 + entryTime: null,
  864 + age: undefined,
  865 + monthlyAttendanceIds: [] as number[], // 修改为数组,存储同一类型的多个ID,并指定类型
  866 + selectedAttendanceType: undefined as string | undefined, // 新增字段,存储用户选择的考勤组选项值,格式为"type:value"
  867 + monthlyAttendanceName: undefined as string | undefined, // 新增字段,存储用户选择的考勤组名称
  868 + dateBirth: null,
  869 + nationality: undefined,
  870 + registeredAddress: undefined,
  871 + maritalStatus: undefined,
  872 + politicalStatus: undefined,
  873 + address: undefined,
  874 + wechat: undefined,
  875 + emergencyContact: undefined,
  876 + relationShip: undefined,
  877 + emergencyContactPhone: undefined,
  878 + card: undefined,
  879 + openingBank: undefined,
  880 + socialSecurity: undefined,
  881 + periodStartTime: null,
  882 + periodEndTime: null,
  883 + contractStartTime: null,
  884 + contractEndTime: null
  885 + },
  886 + // 用户工资配置VO对象,包含工资相关字段
  887 + userWagesVO: {
  888 + basicWages: undefined, // 基本工资
  889 + postAllowance: undefined, // 岗位津贴
  890 + managementAllowance: undefined, // 管理岗位津贴
  891 + phoneAllowance: undefined, // 话费补贴
  892 + mealAllowance: undefined, // 餐补
  893 + transportationAllowance: undefined, // 交通补贴
  894 + attendanceAllowance: undefined, // 全勤奖
  895 + performanceAllowance: undefined, // 绩效部分
  896 + socialSetting: undefined, // 社保待遇
  897 + socialSettingName: undefined, // 新增字段,存储用户选择的社保待遇名称
  898 + socialSettingId: undefined, // 新增字段,存储用户选择的社保待遇ID
  899 + // 新增社保相关字段
  900 + personalPensionInsurance: undefined, // 养老保险(个人)
  901 + personalPensionMedicalInsurance: undefined, // 医疗保险(个人)
  902 + personalPensionUnemploymentInsurance: undefined, // 失业保险(个人)
  903 + personalPensionLargeScaleMedicalInsurance: undefined, // 大额医疗(个人)
  904 + personalPensionProvidentFundInsurance: undefined, // 住房公积金(个人)
  905 + companyInsurance: undefined, // 养老保险(企业)
  906 + companyMedicalInsurance: undefined, // 医疗保险(企业)
  907 + companyUnemploymentInsurance: undefined, // 失业保险(企业)
  908 + companyEmploymentInjuryInsurance: undefined, // 工伤保险(企业)
  909 + companyMaternityInsurance: undefined, // 生育保险(企业)
  910 + companyLargeScaleMedicalInsurance: undefined, // 大额医疗(企业)
  911 + companyProvidentFundInsurance: undefined // 住房公积金(企业)
  912 + }
  913 + });
  914 +
  915 + // 下拉选项数据
  916 + const companyOptions = ref<any[]>([]);
  917 + const deptOptions = ref<any[]>([]);
  918 + const roleOptions = ref<any[]>([]);
  919 +
  920 + // 考勤组数据,按类型和年份双重分组存储
  921 + const attendanceGroups = ref<Record<number, Record<string, any[]>>>({
  922 + 61: {},
  923 + 62: {},
  924 + 63: {},
  925 + 64: {}
  926 + });
  927 +
  928 + // 考勤组类型+年份组合的选项
  929 + const attendanceOptions = ref<{value: string, label: string, ids: number[]}[]>([]);
  930 +
  931 + // 社保待遇选项
  932 + const socialSettingOptions = ref<{value: string, label: string, id: number}[]>([]);
  933 +
  934 + // 按年份分组的社保待遇选项
  935 + const socialSettingYearGroups = computed(() => {
  936 + // 按年份归类
  937 + const groupedByYear: Record<string, Record<number, any>> = {};
  938 +
  939 + // 遍历所有选项,提取年份信息并按年份分组
  940 + socialSettingOptions.value.forEach(option => {
  941 + // 获取年份信息 (假设格式是 "year:type" - 与考勤组不同)
  942 + const valueArr = option.value.split(':');
  943 + if (valueArr.length === 2) {
  944 + const year = valueArr[0];
  945 + if (!groupedByYear[year]) {
  946 + groupedByYear[year] = {};
  947 + }
  948 +
  949 + // 由于标签可能已经包含年份信息,这里不需要移除
  950 + groupedByYear[year][option.id] = option;
  951 + } else {
  952 + // 如果没有年份信息,放入"其他"分组
  953 + if (!groupedByYear['其他']) {
  954 + groupedByYear['其他'] = {};
  955 + }
  956 + groupedByYear['其他'][option.id] = option;
  957 + }
  958 + });
  959 +
  960 + // 将分组转换为数组并按年份降序排序(新的年份在前)
  961 + return Object.entries(groupedByYear)
  962 + .map(([year, typeItems]) => ({
  963 + year,
  964 + options: Object.values(typeItems).sort((a, b) => {
  965 + // 按社保类型排序
  966 + const typeA = parseInt(a.value.split(':')[1]);
  967 + const typeB = parseInt(b.value.split(':')[1]);
  968 + return typeA - typeB;
  969 + })
  970 + }))
  971 + .sort((a, b) => b.year.localeCompare(a.year));
  972 + });
  973 +
  974 + // 社保待遇变更处理
  975 + function handleSocialSettingChange(value: string) {
  976 +
  977 + if (value) {
  978 + const selectedOption = socialSettingOptions.value.find(option => option.value === value);
  979 + if (selectedOption) {
  980 + // 设置社保待遇显示值、ID和名称
  981 + const displayValue = value; // 保留显示值(year:type)格式,用于UI显示
  982 + formState.userWagesVO.socialSetting = displayValue;
  983 + // 保存社保待遇ID和名称
  984 + formState.userWagesVO.socialSettingId = selectedOption.id;
  985 + formState.userWagesVO.socialSettingName = selectedOption.label;
  986 +
  987 +
  988 + // 直接使用query_list API获取系统设置数据
  989 + defHttp.post<any>({
  990 + url: '/order/erp/system_setting/query_list',
  991 + params: {
  992 + setting_type: [70, 71, 72,73]
  993 + }
  994 + }).then(settingList => {
  995 + if (Array.isArray(settingList)) {
  996 + const targetSetting = settingList.find(item => item.id === selectedOption.id);
  997 + if (targetSetting && targetSetting.relationValue) {
  998 +
  999 + tryParseAndFillInsurance(targetSetting.relationValue);
  1000 + } else {
  1001 +
  1002 + parseTestData(selectedOption.id);
  1003 + }
  1004 + }
  1005 + }).catch(error => {
  1006 + console.error('获取社保配置数据失败,使用测试数据:', error);
  1007 + parseTestData(selectedOption.id);
  1008 + });
  1009 + } else {
  1010 + console.warn(`未找到选项 ${value} 对应的配置`);
  1011 + }
  1012 + } else {
  1013 +
  1014 + formState.userWagesVO.socialSetting = undefined;
  1015 + formState.userWagesVO.socialSettingName = undefined;
  1016 + formState.userWagesVO.socialSettingId = undefined;
  1017 + // 清空保险字段值
  1018 + formState.userWagesVO.personalPensionInsurance = undefined;
  1019 + formState.userWagesVO.personalPensionMedicalInsurance = undefined;
  1020 + formState.userWagesVO.personalPensionUnemploymentInsurance = undefined;
  1021 + formState.userWagesVO.personalPensionLargeScaleMedicalInsurance = undefined;
  1022 + formState.userWagesVO.personalPensionProvidentFundInsurance = undefined;
  1023 + formState.userWagesVO.companyInsurance = undefined;
  1024 + formState.userWagesVO.companyMedicalInsurance = undefined;
  1025 + formState.userWagesVO.companyUnemploymentInsurance = undefined;
  1026 + formState.userWagesVO.companyEmploymentInjuryInsurance = undefined;
  1027 + formState.userWagesVO.companyMaternityInsurance = undefined;
  1028 + formState.userWagesVO.companyLargeScaleMedicalInsurance = undefined;
  1029 + formState.userWagesVO.companyProvidentFundInsurance = undefined;
  1030 + }
  1031 + }
  1032 +
  1033 + // 尝试解析测试数据,用于调试
  1034 + function parseTestData(id?: number) {
  1035 + // 使用用户提供的示例数据
  1036 + const testRelationValue = "{\"companyEmploymentInjuryInsurance\":1.0,\"companyInsurance\":800.0,\"companyLargeScaleMedicalInsurance\":1100.0,\"companyMaternityInsurance\":1.0,\"companyMedicalInsurance\":900.0,\"companyProvidentFundInsurance\":1200.0,\"companyUnemploymentInsurance\":1000.0,\"personalPensionInsurance\":10.0,\"personalPensionLargeScaleMedicalInsurance\":10.0,\"personalPensionMedicalInsurance\":10.0,\"personalPensionProvidentFundInsurance\":10.0}";
  1037 +
  1038 + tryParseAndFillInsurance(testRelationValue);
  1039 + }
  1040 +
  1041 + // 尝试解析并填充保险数据
  1042 + function tryParseAndFillInsurance(relationValue: any) {
  1043 + try {
  1044 + let insuranceValues;
  1045 + if (typeof relationValue === 'string') {
  1046 + insuranceValues = JSON.parse(relationValue);
  1047 + } else {
  1048 + insuranceValues = relationValue;
  1049 + }
  1050 +
  1051 + // 填充保险字段值
  1052 + formState.userWagesVO.personalPensionInsurance = insuranceValues.personalPensionInsurance;
  1053 + formState.userWagesVO.personalPensionMedicalInsurance = insuranceValues.personalPensionMedicalInsurance;
  1054 + formState.userWagesVO.personalPensionUnemploymentInsurance = insuranceValues.personalPensionUnemploymentInsurance;
  1055 + formState.userWagesVO.personalPensionLargeScaleMedicalInsurance = insuranceValues.personalPensionLargeScaleMedicalInsurance;
  1056 + formState.userWagesVO.personalPensionProvidentFundInsurance = insuranceValues.personalPensionProvidentFundInsurance;
  1057 + formState.userWagesVO.companyInsurance = insuranceValues.companyInsurance;
  1058 + formState.userWagesVO.companyMedicalInsurance = insuranceValues.companyMedicalInsurance;
  1059 + formState.userWagesVO.companyUnemploymentInsurance = insuranceValues.companyUnemploymentInsurance;
  1060 + formState.userWagesVO.companyEmploymentInjuryInsurance = insuranceValues.companyEmploymentInjuryInsurance;
  1061 + formState.userWagesVO.companyMaternityInsurance = insuranceValues.companyMaternityInsurance;
  1062 + formState.userWagesVO.companyLargeScaleMedicalInsurance = insuranceValues.companyLargeScaleMedicalInsurance;
  1063 + formState.userWagesVO.companyProvidentFundInsurance = insuranceValues.companyProvidentFundInsurance;
  1064 +
  1065 +
  1066 + } catch (error) {
  1067 + console.error('解析或填充数据失败:', error);
  1068 + }
  1069 + }
  1070 +
  1071 + // 考勤组类型变更处理
  1072 + function handleAttendanceTypeChange(value: string) {
  1073 + // 特殊处理自定义选项
  1074 + if (value === 'custom') {
  1075 + // 保留monthlyAttendanceName不变,但不设置IDs
  1076 +
  1077 + return;
  1078 + }
19 1079  
20   - const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
  1080 + // 根据选择的类型,获取对应的ID数组
  1081 + const selectedOption = attendanceOptions.value.find(opt => opt.value === value);
  1082 + if (selectedOption) {
  1083 + formState.userBasicDetailVO.monthlyAttendanceIds = selectedOption.ids;
  1084 + formState.userBasicDetailVO.monthlyAttendanceName = selectedOption.label;
  1085 +
  1086 + } else {
  1087 + formState.userBasicDetailVO.monthlyAttendanceIds = [];
  1088 + formState.userBasicDetailVO.monthlyAttendanceName = undefined;
  1089 +
  1090 + }
  1091 + }
  1092 +
  1093 + // 获取考勤组标签
  1094 + function getAttendanceLabel(type: number) {
  1095 + switch (type) {
  1096 + case 61:
  1097 + return '9小时双休';
  1098 + case 62:
  1099 + return '9小时大小周';
  1100 + case 63:
  1101 + return '8小时双休';
  1102 + case 64:
  1103 + return '8.5小时大小周';
  1104 + default:
  1105 + return '未知考勤组';
  1106 + }
  1107 + }
  1108 +
  1109 + // 获取下拉选项数据
  1110 + const fetchOptions = async () => {
  1111 + try {
  1112 + const [companies, depts, roles] = await Promise.all([
  1113 + getCompanyList({}),
  1114 + getDeptList({}),
  1115 + getRoleList({})
  1116 + ]);
  1117 +
  1118 + companyOptions.value = companies || [];
  1119 + deptOptions.value = depts || [];
  1120 + // 过滤掉ID为6的生产科角色
  1121 + roleOptions.value = (roles || []).filter(role => role.id !== 6);
  1122 + } catch (error) {
  1123 + console.error('获取选项数据失败:', error);
  1124 + }
  1125 + };
  1126 +
  1127 + // 获取考勤组数据
  1128 + async function getAttendanceOptions() {
  1129 + try {
  1130 +
  1131 + const res = await defHttp.post<any>({
  1132 + url: '/order/erp/system_setting/query_list',
  1133 + params: {
  1134 + setting_type: [61, 62, 63, 64]
  1135 + }
  1136 + });
  1137 +
  1138 + if (Array.isArray(res)) {
  1139 +
  1140 + // 重置分组数据
  1141 + attendanceGroups.value = {
  1142 + 61: {},
  1143 + 62: {},
  1144 + 63: {},
  1145 + 64: {}
  1146 + };
  1147 +
  1148 + // 清空选项
  1149 + attendanceOptions.value = [];
  1150 +
  1151 + // 按settingType和settingValue双重分组
  1152 + res.forEach(item => {
  1153 + const type = item.settingType;
  1154 + const value = item.settingValue;
  1155 +
  1156 + if (type >= 61 && type <= 64 && value) {
  1157 + if (!attendanceGroups.value[type][value]) {
  1158 + attendanceGroups.value[type][value] = [];
  1159 + }
  1160 + attendanceGroups.value[type][value].push(item);
  1161 + }
  1162 + });
  1163 +
  1164 + // 为每个分组创建选项
  1165 + Object.entries(attendanceGroups.value).forEach(([typeStr, valueGroups]) => {
  1166 + const type = parseInt(typeStr);
  1167 + const baseLabel = getAttendanceLabel(type);
  1168 +
  1169 + Object.entries(valueGroups).forEach(([value, items]) => {
  1170 + if (items.length > 0) {
  1171 + const ids = items.map(item => item.id);
  1172 + // 创建类型+年份的选项,值格式为 "type:value"
  1173 + attendanceOptions.value.push({
  1174 + value: `${type}:${value}`,
  1175 + label: `${baseLabel}(${value})`,
  1176 + ids: ids
  1177 + });
  1178 + }
  1179 + });
  1180 + });
  1181 +
  1182 + // 按考勤类型排序,相同类型的按年份降序排序
  1183 + attendanceOptions.value.sort((a, b) => {
  1184 + const typeA = parseInt(a.value.split(':')[0]);
  1185 + const typeB = parseInt(b.value.split(':')[0]);
  1186 +
  1187 + if (typeA !== typeB) {
  1188 + return typeA - typeB;
  1189 + }
  1190 +
  1191 + // 相同类型,按年份降序
  1192 + const yearA = a.value.split(':')[1];
  1193 + const yearB = b.value.split(':')[1];
  1194 + return yearB.localeCompare(yearA);
  1195 + });
  1196 +
  1197 +
  1198 + }
  1199 + } catch (error) {
  1200 + console.error('获取考勤组数据失败:', error);
  1201 + }
  1202 + }
  1203 +
  1204 + // 获取社保待遇数据
  1205 + async function getSocialSettingOptions() {
  1206 + try {
  1207 +
  1208 + const res = await defHttp.post<any>({
  1209 + url: '/order/erp/system_setting/query_list',
  1210 + params: {
  1211 + setting_type: [70, 71, 72,73]
  1212 + }
  1213 + });
  1214 +
  1215 + if (Array.isArray(res)) {
  1216 +
  1217 +
  1218 + // 清空选项
  1219 + socialSettingOptions.value = [];
  1220 +
  1221 + // 按settingValue(年份)和settingType(配置类型)分组
  1222 + const groupedData: Record<string, Record<number, any>> = {};
  1223 +
  1224 + res.forEach(item => {
  1225 + const year = item.settingValue;
  1226 + const type = item.settingType;
  1227 +
  1228 + if (year && type) {
  1229 + if (!groupedData[year]) {
  1230 + groupedData[year] = {};
  1231 + }
  1232 + groupedData[year][type] = item;
  1233 + }
  1234 + });
  1235 +
  1236 + // 为每个分组创建选项
  1237 + Object.entries(groupedData).forEach(([year, typeItems]) => {
  1238 + Object.entries(typeItems).forEach(([typeStr, item]) => {
  1239 + const type = parseInt(typeStr);
  1240 + let label = '';
  1241 +
  1242 + // 根据类型设置具体的标签名称
  1243 + switch (type) {
  1244 + case 70:
  1245 + label = '五险一金-特殊';
  1246 + break;
  1247 + case 71:
  1248 + label = '五险一金-普通';
  1249 + break;
  1250 + case 72:
  1251 + label = '五险一金-无社保';
  1252 + break;
  1253 + case 73:
  1254 + label = '五险一金-个体';
  1255 + break;
  1256 + default:
  1257 + label = `社保配置${type}`;
  1258 + }
  1259 +
  1260 + // 创建社保选项,值格式为 "year:type"
  1261 + socialSettingOptions.value.push({
  1262 + value: `${year}:${type}`,
  1263 + label: `${label}(${year})`,
  1264 + id: item.id
  1265 + });
  1266 + });
  1267 + });
  1268 +
  1269 + // 按年份降序排序,相同年份按类型升序
  1270 + socialSettingOptions.value.sort((a, b) => {
  1271 + const yearA = a.value.split(':')[0];
  1272 + const yearB = b.value.split(':')[0];
  1273 +
  1274 + if (yearA !== yearB) {
  1275 + return yearB.localeCompare(yearA);
  1276 + }
  1277 +
  1278 + // 相同年份,按类型升序
  1279 + const typeA = parseInt(a.value.split(':')[1]);
  1280 + const typeB = parseInt(b.value.split(':')[1]);
  1281 + return typeA - typeB;
  1282 + });
  1283 +
  1284 +
  1285 + }
  1286 + } catch (error) {
  1287 + console.error('获取社保待遇数据失败:', error);
  1288 + }
  1289 + }
  1290 +
  1291 + onMounted(() => {
  1292 + fetchOptions();
  1293 + });
  1294 + // 员工账号表单
  1295 + const [employeeFormRegister, { resetFields: resetEmployeeForm, setFieldsValue: setEmployeeFormValues }] = useForm({
21 1296 labelWidth: 100,
22 1297 baseColProps: { span: 24 },
23 1298 schemas: accountFormSchema,
24 1299 showActionButtonGroup: false,
25   - actionColOptions: {
26   - span: 23,
27   - },
  1300 + actionColOptions: { span: 23 },
  1301 + });
  1302 + // 生产科账号表单
  1303 + const [productionFormRegister, { resetFields: resetProductionForm, validate: validateProductionForm, setFieldsValue: setProductionFormValues }] = useForm({
  1304 + labelWidth: 100,
  1305 + baseColProps: { span: 24 },
  1306 + schemas: productionAccountFormSchema,
  1307 + showActionButtonGroup: false,
  1308 + actionColOptions: { span: 23 },
28 1309 });
29   -
30 1310 const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
31   - resetFields();
  1311 + // 重置表单
  1312 + resetEmployeeForm();
  1313 + resetProductionForm();
  1314 +
  1315 + // 重置提交状态
  1316 + submitLoading.value = false;
32 1317 setModalProps({ confirmLoading: false });
33   - isUpdate.value = !!data?.isUpdate;
34   - if (unref(isUpdate)) {
  1318 +
  1319 + // 每次打开模态框时都重新获取最新的选项数据
  1320 + await Promise.all([
  1321 + fetchOptions(), // 重新获取公司、部门、岗位数据
  1322 + getAttendanceOptions(), //获取考勤组数据社
  1323 + getSocialSettingOptions() //获取保待遇数据
  1324 + ]);
  1325 +
  1326 + // 重置表单数据
  1327 + Object.keys(formState).forEach(key => {
  1328 + if (key !== 'userBasicDetailVO' && key !== 'userWagesVO') {
  1329 + // 必填字段重置为空字符串,非必填字段重置为undefined
  1330 + const requiredFields = ['chineseName', 'nickName', 'gender', 'phone', 'email'];
  1331 + if (requiredFields.includes(key)) {
  1332 + formState[key] = '';
  1333 + } else {
  1334 + formState[key] = undefined;
  1335 + }
  1336 + }
  1337 + });
  1338 +
  1339 + // companyId, deptId, roleId特殊处理为undefined
  1340 + formState.companyId = undefined;
  1341 + formState.deptId = undefined;
  1342 + formState.roleId = undefined;
  1343 +
  1344 + // 重置用户基本详情VO
  1345 + Object.keys(formState.userBasicDetailVO).forEach(key => {
  1346 + // 日期类型字段设为null,其他字段设为undefined
  1347 + if (['entryTime', 'dateBirth', 'periodStartTime', 'periodEndTime', 'contractStartTime', 'contractEndTime'].includes(key)) {
  1348 + formState.userBasicDetailVO[key] = null;
  1349 + } else if (key === 'monthlyAttendanceIds') {
  1350 + formState.userBasicDetailVO[key] = []; // 数组类型重置为空数组
  1351 + } else if (key === 'monthlyAttendanceName') {
  1352 + formState.userBasicDetailVO[key] = undefined; // 名称字段重置为undefined
  1353 + } else {
  1354 + formState.userBasicDetailVO[key] = undefined;
  1355 + }
  1356 + });
  1357 +
  1358 + // 重置用户工资配置VO
  1359 + Object.keys(formState.userWagesVO).forEach(key => {
  1360 + formState.userWagesVO[key] = undefined;
  1361 + });
  1362 +
  1363 + // 设置默认值
  1364 + formState.pensionInsurance = 'basic';
  1365 + formState.medicalInsurance = 'basic';
  1366 + formState.unemploymentInsurance = 'basic';
  1367 + // 明确设置isUpdate的值
  1368 + if (data?.isUpdate === true) {
  1369 + isUpdate.value = true;
  1370 + } else {
  1371 + isUpdate.value = false;
  1372 + }
  1373 +
  1374 + // 设置账号类型
  1375 + if (data?.type) {
  1376 + accountType.value = data.type;
  1377 + } else if (isUpdate.value && data?.record?.isAdmin) {
  1378 + // 如果是编辑,根据record中的isAdmin判断账号类型
  1379 + accountType.value = data.record.isAdmin === '1' ? 'employee' : 'production';
  1380 + }
  1381 +
  1382 +
  1383 +
  1384 + if (isUpdate.value) {
35 1385 rowId.value = data.record.id;
36 1386  
37   - setFieldsValue({
  1387 +
  1388 + // 使用nextTick确保表单已渲染
  1389 + await nextTick();
  1390 +
  1391 +
  1392 + try {
  1393 + // 根据不同账号类型设置表单值
  1394 + if (accountType.value === 'production') {
  1395 + // 确保record中包含公司ID
  1396 + const recordWithCompanyId = {
38 1397 ...toRaw(data.record),
39   - });
  1398 + // 如果没有companyId但有companyId字段,使用它
  1399 + companyId: data.record.companyId || data.record.company_id,
  1400 + };
  1401 +
  1402 +
  1403 +
  1404 + // 设置表单值
  1405 + setProductionFormValues(recordWithCompanyId);
  1406 + } else {
  1407 + // 确保record中包含公司ID
  1408 + const recordWithCompanyId = {
  1409 + ...toRaw(data.record),
  1410 + // 如果没有companyId但有companyId字段,使用它
  1411 + companyId: data.record.companyId || data.record.company_id,
  1412 + };
  1413 +
  1414 + // 处理字段锁定状态
  1415 + processFieldLockStatus(recordWithCompanyId.userWagesFieldLockApplyVO);
  1416 +
  1417 + // 设置自定义表单数据
  1418 + Object.entries(formState).forEach(([key, value]) => {
  1419 + if (key !== 'userBasicDetailVO' && key !== 'userWagesVO' && recordWithCompanyId[key] !== undefined) {
  1420 + formState[key] = recordWithCompanyId[key];
  1421 + }
  1422 + });
  1423 +
  1424 + // 处理userBasicDetailVO数据
  1425 + if (recordWithCompanyId.userBasicDetailResultVO) {
  1426 +
  1427 +
  1428 + // 直接从返回的数据中提取字段并映射到formState
  1429 + const basicDetail = recordWithCompanyId.userBasicDetailResultVO;
  1430 +
  1431 + // 按照字段名称一一映射
  1432 + // 日期字段需要特殊处理
  1433 + const dateFields = ['entryTime', 'dateBirth', 'periodStartTime', 'periodEndTime', 'contractStartTime', 'contractEndTime'];
  1434 +
  1435 + // 遍历所有字段进行处理
  1436 + Object.keys(basicDetail).forEach(key => {
  1437 + // 如果返回的字段在表单中存在
  1438 + if (formState.userBasicDetailVO.hasOwnProperty(key)) {
  1439 + // 日期字段需要特殊处理
  1440 + if (dateFields.includes(key) && basicDetail[key]) {
  1441 + try {
  1442 + // 使用dayjs处理日期,而不是原生Date对象
  1443 + // 如果日期字段有值且是有效日期,使用dayjs转换
  1444 + const dateValue = basicDetail[key];
  1445 + if (dateValue && dayjs(dateValue).isValid()) {
  1446 + formState.userBasicDetailVO[key] = dayjs(dateValue);
  1447 +
  1448 + } else {
  1449 + // 如果日期无效,设置为null
  1450 + formState.userBasicDetailVO[key] = null;
  1451 + console.warn(`日期字段 ${key} 值无效: ${dateValue},已设置为null`);
  1452 + }
  1453 + } catch (error) {
  1454 + console.error(`处理日期字段 ${key} 出错:`, error);
  1455 + formState.userBasicDetailVO[key] = null;
  1456 + }
  1457 + } else {
  1458 + // 非日期字段直接赋值
  1459 + formState.userBasicDetailVO[key] = basicDetail[key];
  1460 + }
  1461 + }
  1462 + });
  1463 +
  1464 + // 处理考勤组信息 - 优先使用后端返回的monthlyAttendanceName
  1465 + if (basicDetail.monthlyAttendanceName) {
  1466 + // 直接使用后端返回的考勤组名称
  1467 + formState.userBasicDetailVO.monthlyAttendanceName = basicDetail.monthlyAttendanceName;
  1468 +
  1469 + // 尝试查找匹配的选项
  1470 + let foundMatch = false;
  1471 + if (Array.isArray(basicDetail.monthlyAttendanceIds) && basicDetail.monthlyAttendanceIds.length > 0) {
  1472 + const firstId = basicDetail.monthlyAttendanceIds[0];
  1473 + for (const option of attendanceOptions.value) {
  1474 + if (option.ids.includes(firstId)) {
  1475 + formState.userBasicDetailVO.selectedAttendanceType = option.value;
  1476 + foundMatch = true;
  1477 + break;
  1478 + }
  1479 + }
  1480 + } else {
  1481 + // 如果没有ID,尝试通过名称匹配
  1482 + for (const option of attendanceOptions.value) {
  1483 + if (option.label === basicDetail.monthlyAttendanceName) {
  1484 + formState.userBasicDetailVO.selectedAttendanceType = option.value;
  1485 + foundMatch = true;
  1486 + break;
  1487 + }
  1488 + }
  1489 + }
  1490 +
  1491 + // 如果没有匹配项,使用自定义选项
  1492 + if (!foundMatch) {
  1493 + formState.userBasicDetailVO.selectedAttendanceType = 'custom';
  1494 + }
  1495 +
  1496 +
  1497 + }
  1498 +
  1499 + // 处理考勤组ID数组
  1500 + if (Array.isArray(basicDetail.monthlyAttendanceIds) && basicDetail.monthlyAttendanceIds.length > 0) {
  1501 + formState.userBasicDetailVO.monthlyAttendanceIds = basicDetail.monthlyAttendanceIds;
  1502 +
  1503 + // 如果没有monthlyAttendanceName但有ID,尝试根据ID查找考勤组选项
  1504 + if (!basicDetail.monthlyAttendanceName) {
  1505 + const firstId = basicDetail.monthlyAttendanceIds[0];
  1506 + let foundMatch = false;
  1507 +
  1508 + // 遍历所有考勤选项
  1509 + for (const option of attendanceOptions.value) {
  1510 + if (option.ids.includes(firstId)) {
  1511 + formState.userBasicDetailVO.selectedAttendanceType = option.value;
  1512 + formState.userBasicDetailVO.monthlyAttendanceName = option.label;
  1513 + foundMatch = true;
  1514 + break;
  1515 + }
  1516 + }
  1517 +
  1518 + // 如果没找到匹配项但有考勤选项,设置第一个考勤选项
  1519 + if (!foundMatch && attendanceOptions.value.length > 0) {
  1520 + formState.userBasicDetailVO.selectedAttendanceType = attendanceOptions.value[0].value;
  1521 + formState.userBasicDetailVO.monthlyAttendanceName = attendanceOptions.value[0].label;
  1522 + }
  1523 + }
  1524 + }
  1525 +
  1526 +
  1527 + }
  1528 + // 修改原有的备选逻辑,添加对monthlyAttendanceName的处理
  1529 + else if (recordWithCompanyId.userBasicDetailVO) {
  1530 + // 创建一个新对象来处理转换后的数据
  1531 + const processedBasicDetail = { ...recordWithCompanyId.userBasicDetailVO };
  1532 +
  1533 + // 处理考勤组数据
  1534 + // 如果有userID11字段,根据其值查找对应的考勤组类型
  1535 + if (processedBasicDetail.userID11 !== undefined) {
  1536 + // 查找userID11对应的考勤组类型和值
  1537 + let foundMatch = false;
  1538 +
  1539 + // 遍历所有考勤选项
  1540 + for (const option of attendanceOptions.value) {
  1541 + if (option.ids.includes(parseInt(processedBasicDetail.userID11))) {
  1542 + // 设置选择的考勤组选项
  1543 + formState.userBasicDetailVO.selectedAttendanceType = option.value;
  1544 + // 设置ID数组
  1545 + formState.userBasicDetailVO.monthlyAttendanceIds = option.ids;
  1546 + // 设置考勤组名称
  1547 + formState.userBasicDetailVO.monthlyAttendanceName = option.label;
  1548 + foundMatch = true;
  1549 + break;
  1550 + }
  1551 + }
  1552 +
  1553 + // 如果没找到匹配项,但有已有的考勤组名称,使用自定义选项
  1554 + if (!foundMatch) {
  1555 + if (processedBasicDetail.monthlyAttendanceName) {
  1556 + formState.userBasicDetailVO.monthlyAttendanceName = processedBasicDetail.monthlyAttendanceName;
  1557 + formState.userBasicDetailVO.selectedAttendanceType = 'custom';
  1558 + } else if (attendanceOptions.value.length > 0) {
  1559 + // 如果没有考勤组名称但有考勤选项,设置第一个考勤选项
  1560 + const firstOption = attendanceOptions.value[0];
  1561 + formState.userBasicDetailVO.selectedAttendanceType = firstOption.value;
  1562 + formState.userBasicDetailVO.monthlyAttendanceIds = firstOption.ids;
  1563 + formState.userBasicDetailVO.monthlyAttendanceName = firstOption.label;
  1564 + }
  1565 + }
  1566 +
  1567 + delete processedBasicDetail.userID11; // 删除原字段
  1568 + }
  1569 + // 如果没有userID11但有monthlyAttendanceIds,且为数组格式
  1570 + else if (Array.isArray(processedBasicDetail.monthlyAttendanceIds) && processedBasicDetail.monthlyAttendanceIds.length > 0) {
  1571 + formState.userBasicDetailVO.monthlyAttendanceIds = processedBasicDetail.monthlyAttendanceIds;
  1572 +
  1573 + // 根据第一个ID查找对应的选项
  1574 + const firstId = processedBasicDetail.monthlyAttendanceIds[0];
  1575 + let foundMatch = false;
  1576 +
  1577 + // 遍历所有考勤选项
  1578 + for (const option of attendanceOptions.value) {
  1579 + if (option.ids.includes(firstId)) {
  1580 + formState.userBasicDetailVO.selectedAttendanceType = option.value;
  1581 + formState.userBasicDetailVO.monthlyAttendanceName = option.label;
  1582 + foundMatch = true;
  1583 + break;
  1584 + }
  1585 + }
  1586 +
  1587 + // 如果没找到匹配项
  1588 + if (!foundMatch) {
  1589 + // 如果有考勤组名称,使用自定义选项
  1590 + if (processedBasicDetail.monthlyAttendanceName) {
  1591 + formState.userBasicDetailVO.monthlyAttendanceName = processedBasicDetail.monthlyAttendanceName;
  1592 + formState.userBasicDetailVO.selectedAttendanceType = 'custom';
  1593 + } else if (attendanceOptions.value.length > 0) {
  1594 + // 如果没有考勤组名称但有考勤选项,设置第一个考勤选项
  1595 + formState.userBasicDetailVO.selectedAttendanceType = attendanceOptions.value[0].value;
  1596 + formState.userBasicDetailVO.monthlyAttendanceName = attendanceOptions.value[0].label;
  1597 + }
  1598 + }
  1599 +
  1600 + delete processedBasicDetail.monthlyAttendanceIds;
  1601 + }
  1602 + // 如果只有monthlyAttendanceName但没有ID
  1603 + else if (processedBasicDetail.monthlyAttendanceName) {
  1604 + formState.userBasicDetailVO.monthlyAttendanceName = processedBasicDetail.monthlyAttendanceName;
  1605 +
  1606 + // 尝试通过名称匹配现有选项
  1607 + let foundMatch = false;
  1608 + for (const option of attendanceOptions.value) {
  1609 + if (option.label === processedBasicDetail.monthlyAttendanceName) {
  1610 + formState.userBasicDetailVO.selectedAttendanceType = option.value;
  1611 + formState.userBasicDetailVO.monthlyAttendanceIds = option.ids;
  1612 + foundMatch = true;
  1613 + break;
  1614 + }
  1615 + }
  1616 +
  1617 + // 如果没找到匹配项,使用自定义选项
  1618 + if (!foundMatch) {
  1619 + formState.userBasicDetailVO.selectedAttendanceType = 'custom';
  1620 + }
  1621 +
  1622 + delete processedBasicDetail.monthlyAttendanceName;
  1623 + }
  1624 +
  1625 + // 遍历处理后的对象进行赋值
  1626 + Object.entries(processedBasicDetail).forEach(([key, value]) => {
  1627 + if (value !== undefined && formState.userBasicDetailVO.hasOwnProperty(key) &&
  1628 + key !== 'monthlyAttendanceIds' && key !== 'selectedAttendanceType' && key !== 'monthlyAttendanceName') {
  1629 + formState.userBasicDetailVO[key] = value;
  1630 + }
  1631 + });
  1632 + }
  1633 +
  1634 + // 处理userWagesVO数据
  1635 + if (recordWithCompanyId.userWagesVO) {
  1636 +
  1637 +
  1638 + // 先处理普通字段
  1639 + Object.entries(recordWithCompanyId.userWagesVO).forEach(([key, value]) => {
  1640 + if (value !== undefined && formState.userWagesVO.hasOwnProperty(key) && key !== 'socialSetting') {
  1641 + formState.userWagesVO[key] = value;
  1642 + }
  1643 + });
  1644 +
  1645 + // 单独处理社保待遇字段
  1646 + if (recordWithCompanyId.userWagesVO.socialSetting) {
  1647 + const socialSettingValue = recordWithCompanyId.userWagesVO.socialSetting;
  1648 +
  1649 +
  1650 + let selectedSocialSettingId; // 存储找到的选项ID
  1651 +
  1652 + // 判断返回的socialSetting是ID格式还是"year:type"格式
  1653 + if (socialSettingValue.toString().includes(':')) {
  1654 + // 是"year:type"格式,查找对应的选项
  1655 + const option = socialSettingOptions.value.find(opt => opt.value === socialSettingValue);
  1656 + if (option) {
  1657 + selectedSocialSettingId = option.id;
  1658 + formState.userWagesVO.socialSetting = socialSettingValue;
  1659 + formState.userWagesVO.socialSettingId = option.id;
  1660 + formState.userWagesVO.socialSettingName = option.label;
  1661 +
  1662 + }
  1663 + } else {
  1664 + // 是ID格式,直接使用
  1665 + selectedSocialSettingId = parseInt(socialSettingValue);
  1666 + formState.userWagesVO.socialSettingId = selectedSocialSettingId;
  1667 +
  1668 + // 尝试查找对应的选项以获取显示值和名称
  1669 + const option = socialSettingOptions.value.find(opt => opt.id === selectedSocialSettingId);
  1670 + if (option) {
  1671 + formState.userWagesVO.socialSetting = option.value;
  1672 + formState.userWagesVO.socialSettingName = option.label;
  1673 +
  1674 + } else {
  1675 + formState.userWagesVO.socialSetting = socialSettingValue.toString();
  1676 +
  1677 + }
  1678 + }
  1679 +
  1680 + // 根据找到的社保待遇ID加载保险数据
  1681 + if (selectedSocialSettingId) {
  1682 +
  1683 +
  1684 + // 使用query_list API获取系统设置数据
  1685 + defHttp.post<any>({
  1686 + url: '/order/erp/system_setting/query_list',
  1687 + params: {
  1688 + setting_type: [70, 71, 72,73]
  1689 + }
  1690 + }).then(settingList => {
  1691 + if (Array.isArray(settingList)) {
  1692 + const targetSetting = settingList.find(item => item.id === selectedSocialSettingId);
  1693 + if (targetSetting && targetSetting.relationValue) {
  1694 +
  1695 + tryParseAndFillInsurance(targetSetting.relationValue);
  1696 + } else {
  1697 + console.warn(`未找到ID为 ${selectedSocialSettingId} 的社保配置,检查保险字段是否已填充`);
  1698 +
  1699 + // 如果保险字段值已经在返回数据中存在,则不需要额外填充
  1700 + const hasInsuranceValues = Object.keys(formState.userWagesVO).some(key =>
  1701 + key.includes('Insurance') && formState.userWagesVO[key] !== undefined
  1702 + );
  1703 +
  1704 + if (!hasInsuranceValues) {
  1705 + console.warn('未找到保险字段值,使用测试数据填充');
  1706 + parseTestData(selectedSocialSettingId);
  1707 + } else {
  1708 + console.log('保险字段值已存在,不需要额外填充');
  1709 + }
  1710 + }
  1711 + }
  1712 + }).catch(error => {
  1713 + console.error('获取社保配置数据失败:', error);
  1714 +
  1715 + // 如果保险字段值已经在返回数据中存在,则不需要额外填充
  1716 + const hasInsuranceValues = Object.keys(formState.userWagesVO).some(key =>
  1717 + key.includes('Insurance') && formState.userWagesVO[key] !== undefined
  1718 + );
  1719 +
  1720 + if (!hasInsuranceValues) {
  1721 + console.warn('获取失败且未找到保险字段值,使用测试数据填充');
  1722 + parseTestData(selectedSocialSettingId);
  1723 + }
  1724 + });
  1725 + }
  1726 + }
  1727 + }
  1728 +
  1729 + // ID字段特殊处理
  1730 + formState.id = recordWithCompanyId.id;
  1731 +
  1732 +
  1733 +
  1734 + // 设置组件库表单值 (保留原来的功能)
  1735 + await nextTick(); // 再次确保表单已完全更新
  1736 + setEmployeeFormValues(recordWithCompanyId);
  1737 + }
  1738 + } catch (error) {
  1739 + console.error('设置表单值时出错:', error);
  1740 + }
40 1741 }
41 1742 });
42   -
43   - const getTitle = computed(() => (!unref(isUpdate) ? '新增账号' : '编辑账号'));
44   -
  1743 + const getTitle = computed(() => {
  1744 + if (!unref(isUpdate)) {
  1745 + return accountType.value === 'employee' ? '新增员工账号' : '新增生产科账号';
  1746 + }
  1747 + return '编辑账号';
  1748 + });
  1749 + // 计算合同期限
  1750 + const calculateContractDuration = computed(() => {
  1751 + const { contractStartTime, contractEndTime } = formState.userBasicDetailVO;
  1752 + if (contractStartTime && contractEndTime) {
  1753 + const startDate = new Date(contractStartTime);
  1754 + const endDate = new Date(contractEndTime);
  1755 +
  1756 + // 计算年份差异
  1757 + let years = endDate.getFullYear() - startDate.getFullYear();
  1758 +
  1759 + // 检查月份和日期,调整年份差异
  1760 + if (
  1761 + endDate.getMonth() < startDate.getMonth() ||
  1762 + (endDate.getMonth() === startDate.getMonth() && endDate.getDate() < startDate.getDate())
  1763 + ) {
  1764 + years--;
  1765 + }
  1766 +
  1767 + // 计算剩余天数
  1768 + // 先计算完整的日期差值(毫秒)
  1769 + const diffTime = Math.abs(endDate.getTime() - startDate.getTime());
  1770 + // 转换为天数
  1771 + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  1772 + // 减去整年的天数
  1773 + const daysPart = diffDays - (years * 365);
  1774 +
  1775 + // 返回年数和天数
  1776 + return years > 0
  1777 + ? `${years}年(${daysPart}天)`
  1778 + : `${diffDays}天`;
  1779 + }
  1780 + return '';
  1781 + });
  1782 + // 按年份分组的考勤组选项
  1783 + const yearGroups = computed(() => {
  1784 + // 按年份归类
  1785 + const groupedByYear: Record<string, {value: string, label: string, ids: number[]}[]> = {};
  1786 +
  1787 + // 遍历所有选项,提取年份信息并按年份分组
  1788 + attendanceOptions.value.forEach(option => {
  1789 + // 获取年份信息 (假设格式是 "type:year")
  1790 + const valueArr = option.value.split(':');
  1791 + if (valueArr.length === 2) {
  1792 + const year = valueArr[1];
  1793 + if (!groupedByYear[year]) {
  1794 + groupedByYear[year] = [];
  1795 + }
  1796 +
  1797 + // 这里不再移除年份信息,直接使用原始的选项
  1798 + groupedByYear[year].push(option);
  1799 + } else {
  1800 + // 如果没有年份信息,放入"其他"分组
  1801 + if (!groupedByYear['其他']) {
  1802 + groupedByYear['其他'] = [];
  1803 + }
  1804 + groupedByYear['其他'].push(option);
  1805 + }
  1806 + });
  1807 +
  1808 + // 将分组转换为数组并按年份降序排序(新的年份在前)
  1809 + return Object.entries(groupedByYear)
  1810 + .map(([year, options]) => ({
  1811 + year,
  1812 + options: options.sort((a, b) => {
  1813 + // 按考勤类型排序
  1814 + const typeA = parseInt(a.value.split(':')[0]);
  1815 + const typeB = parseInt(b.value.split(':')[0]);
  1816 + return typeA - typeB;
  1817 + })
  1818 + }))
  1819 + .sort((a, b) => b.year.localeCompare(a.year));
  1820 + });
45 1821 async function handleSubmit() {
  1822 + // 如果当前正在提交中,直接返回,防止重复点击
  1823 + if (submitLoading.value) {
  1824 + console.log('正在提交中,请勿重复点击');
  1825 + return;
  1826 + }
  1827 +
46 1828 try {
47   - const values = await validate();
  1829 + // 设置提交状态为true,禁用按钮
  1830 + submitLoading.value = true;
48 1831 setModalProps({ confirmLoading: true });
49   -
  1832 +
  1833 + // 添加最小Loading时间,确保用户感知到按钮状态变化
  1834 + const startTime = Date.now();
  1835 +
  1836 + if (accountType.value === 'production') {
  1837 + // 生产科账号逻辑
  1838 + try {
  1839 + // 执行表单验证
  1840 + const values = await validateProductionForm();
  1841 +
  1842 + // 设置提交数据类型
  1843 + const submitData: Record<string, any> = {};
  1844 +
  1845 + // 遍历values中的每个属性,只保留有值的字段
  1846 + Object.entries(values).forEach(([key, value]) => {
  1847 + // 跳过undefined、null和空字符串
  1848 + if (value !== undefined && value !== null && value !== '') {
  1849 + submitData[key] = value;
  1850 + }
  1851 + });
  1852 + // 始终需要的字段
  1853 + if (rowId.value) {
  1854 + submitData.id = rowId.value;
  1855 + }
  1856 +
  1857 + // 生产科账号固定roleId
  1858 + submitData.roleId = 6;
  1859 + submitData.isAdmin = '0';
  1860 +
  1861 + // 如果有公司ID但没有公司名称,尝试查找公司名称
  1862 + if (submitData.companyId && !submitData.companyName) {
  1863 + const selectedCompany = companyOptions.value.find(company => company.id === submitData.companyId);
  1864 + if (selectedCompany?.name) {
  1865 + submitData.companyName = selectedCompany.name;
  1866 + }
  1867 + }
50 1868 closeModal();
51 1869 emit('success', {
52 1870 isUpdate: unref(isUpdate),
53   - values: { ...values, id: rowId.value || undefined },
54   - });
  1871 + values: submitData,
  1872 + });
  1873 + } catch (error) {
  1874 + // 表单验证失败,重置按钮状态
  1875 + console.error('生产科表单验证失败:', error);
  1876 + submitLoading.value = false;
  1877 + setModalProps({ confirmLoading: false });
  1878 + return;
  1879 + }
  1880 + } else {
  1881 + // 员工账号逻辑
  1882 + try {
  1883 + // 验证表单
  1884 + if (!employeeFormRef.value) {
  1885 + console.error('表单实例不存在');
  1886 + submitLoading.value = false;
  1887 + setModalProps({ confirmLoading: false });
  1888 + return;
  1889 + }
  1890 +
  1891 + // 使用Ant Design表单验证
  1892 + await employeeFormRef.value.validate();
  1893 +
  1894 + // 验证通过后,创建提交数据对象
  1895 + const submitData: Record<string, any> = {};
  1896 +
  1897 + // 处理formState中的主要字段
  1898 + Object.entries(formState).forEach(([key, value]) => {
  1899 + // 跳过userBasicDetailVO和userWagesVO,单独处理
  1900 + if (key === 'userBasicDetailVO' || key === 'userWagesVO') return;
  1901 +
  1902 + // 判断是否为默认值
  1903 + const isDefaultValue =
  1904 + (key === 'pensionInsurance' && value === 'basic') ||
  1905 + (key === 'medicalInsurance' && value === 'basic') ||
  1906 + (key === 'unemploymentInsurance' && value === 'basic');
  1907 +
  1908 + // 只保留非空且非默认值的字段
  1909 + if (value !== undefined && value !== null && value !== '' && !isDefaultValue) {
  1910 + submitData[key] = value;
  1911 + }
  1912 + });
  1913 +
  1914 + // 处理userBasicDetailVO对象中的字段
  1915 + if (formState.userBasicDetailVO) {
  1916 + const filteredUserBasicDetail: Record<string, any> = {};
  1917 + let hasFilledValues = false;
  1918 +
  1919 + // 先检查是否有需要提交的字段
  1920 + const hasValues = Object.entries(formState.userBasicDetailVO).some(([key, value]) => {
  1921 + return value !== undefined &&
  1922 + value !== null &&
  1923 + value !== '' &&
  1924 + key !== 'selectedAttendanceType' &&
  1925 + !(key === 'monthlyAttendanceIds' && Array.isArray(value) && value.length === 0);
  1926 + });
  1927 +
  1928 + if (hasValues) {
  1929 + Object.entries(formState.userBasicDetailVO).forEach(([key, value]) => {
  1930 + // 考勤组ID和名称的特殊处理 - 确保同时提交ID和名称
  1931 + if (key === 'monthlyAttendanceIds' && Array.isArray(value) && value.length > 0) {
  1932 + filteredUserBasicDetail[key] = value;
  1933 + // 如果存在考勤组名称,也添加到提交数据中
  1934 + const attendanceName = formState.userBasicDetailVO.monthlyAttendanceName;
  1935 + if (attendanceName) {
  1936 + filteredUserBasicDetail['monthlyAttendanceName'] = attendanceName;
  1937 + }
  1938 + hasFilledValues = true;
  1939 + }
  1940 + // 跳过selectedAttendanceType(仅用于UI选择,不需要提交)
  1941 + else if (key !== 'selectedAttendanceType' && value !== undefined && value !== null && value !== '') {
  1942 + filteredUserBasicDetail[key] = value;
  1943 + hasFilledValues = true;
  1944 + }
  1945 + });
  1946 +
  1947 + // 只有当有实际填写的字段时,才添加到提交数据
  1948 + if (hasFilledValues && Object.keys(filteredUserBasicDetail).length > 0) {
  1949 + submitData.userBasicDetailVO = filteredUserBasicDetail;
  1950 + }
  1951 + }
  1952 + }
  1953 +
  1954 + // 处理userWagesVO对象中的字段
  1955 + if (formState.userWagesVO) {
  1956 + const filteredUserWagesVO: Record<string, any> = {};
  1957 + let hasFilledValues = false;
  1958 +
  1959 + Object.entries(formState.userWagesVO).forEach(([key, value]) => {
  1960 + // 社保待遇的特殊处理 - 使用ID而不是显示值
  1961 + if (key === 'socialSetting' && value !== undefined && value !== null && value !== '') {
  1962 + // 使用ID而不是显示值
  1963 + const socialSettingId = formState.userWagesVO.socialSettingId;
  1964 + if (socialSettingId !== undefined) {
  1965 + // 使用ID作为socialSetting的值
  1966 + filteredUserWagesVO['socialSetting'] = socialSettingId;
  1967 +
  1968 + // 如果存在社保待遇名称,也添加到提交数据中
  1969 + const socialSettingName = formState.userWagesVO.socialSettingName;
  1970 + if (socialSettingName) {
  1971 + filteredUserWagesVO['socialSettingName'] = socialSettingName;
  1972 + }
  1973 +
  1974 +
  1975 +
  1976 + hasFilledValues = true;
  1977 + }
  1978 + }
  1979 + // 跳过socialSettingName和socialSettingId,因为我们已经处理过了
  1980 + else if (key !== 'socialSettingName' && key !== 'socialSettingId' && value !== undefined && value !== null && value !== '') {
  1981 + filteredUserWagesVO[key] = value;
  1982 + hasFilledValues = true;
  1983 + }
  1984 + });
  1985 +
  1986 + // 只有当有实际填写的字段时,才添加到提交数据
  1987 + if (hasFilledValues && Object.keys(filteredUserWagesVO).length > 0) {
  1988 + submitData.userWagesVO = filteredUserWagesVO;
  1989 + }
  1990 + }
  1991 +
  1992 + // 员工账号类型
  1993 + // if (unref(isUpdate)) {
  1994 + // submitData.isAdmin = '1';
  1995 + // }
  1996 + submitData.isAdmin = '1';
  1997 + // 如果有ID,添加到提交数据
  1998 + if (rowId.value) {
  1999 + submitData.id = rowId.value;
  2000 + }
  2001 +
  2002 + // 如果有公司ID但没有公司名称,尝试查找公司名称
  2003 + if (submitData.companyId && !submitData.companyName) {
  2004 + const selectedCompany = companyOptions.value.find(company => company.id === submitData.companyId);
  2005 + if (selectedCompany?.name) {
  2006 + submitData.companyName = selectedCompany.name;
  2007 + }
  2008 + }
  2009 +
  2010 +
  2011 + closeModal();
  2012 + emit('success', {
  2013 + isUpdate: unref(isUpdate),
  2014 + values: submitData,
  2015 + });
  2016 + } catch (error) {
  2017 + // 表单验证失败,重置按钮状态,不关闭模态框
  2018 + console.error('员工表单验证失败:', error);
  2019 + submitLoading.value = false;
  2020 + setModalProps({ confirmLoading: false });
  2021 + return;
  2022 + }
  2023 + }
  2024 + } catch (error) {
  2025 + // 其他错误,重置按钮状态
  2026 + console.error('提交过程中发生错误:', error);
  2027 + submitLoading.value = false;
  2028 + setModalProps({ confirmLoading: false });
55 2029 } finally {
  2030 + // 只有在成功提交的情况下才执行这里的逻辑
  2031 + // 由于验证失败时会提前return,所以这里只处理成功的情况
  2032 + if (!submitLoading.value) {
  2033 + // 如果submitLoading已经被重置,说明验证失败,不需要执行后续逻辑
  2034 + return;
  2035 + }
  2036 + // 确保最小loading时间为800ms,让用户有感知
  2037 + const loadingTime = Date.now() - startTime;
  2038 + const minLoadingTime = 800; // 最小loading时间,单位毫秒
  2039 +
  2040 + if (loadingTime < minLoadingTime) {
  2041 + await new Promise(resolve => setTimeout(resolve, minLoadingTime - loadingTime));
  2042 + }
  2043 +
  2044 + // 重置按钮状态
56 2045 setModalProps({ confirmLoading: false });
  2046 + submitLoading.value = false;
  2047 +
  2048 + // 添加防抖功能,禁止短时间内再次点击
  2049 + setTimeout(() => {
  2050 + submitLoading.value = false;
  2051 + }, 2000);
57 2052 }
58 2053 }
59   -
60   - return { registerModal, registerForm, getTitle, handleSubmit };
  2054 + function handleTabChange(key: string) {
  2055 + activeTabKey.value = key;
  2056 +
  2057 + }
  2058 + function handleCancel() {
  2059 +
  2060 + submitLoading.value = false; // 重置提交状态
  2061 + setModalProps({ confirmLoading: false }); // 确保模态框按钮状态也被重置
  2062 + closeModal();
  2063 + }
  2064 + // 添加一个计算属性,用于检查是否存在匹配的考勤选项
  2065 + const hasMatchingAttendanceOption = computed(() => {
  2066 + if (!formState.userBasicDetailVO.monthlyAttendanceName || attendanceOptions.value.length === 0) {
  2067 + return false;
  2068 + }
  2069 +
  2070 + // 检查是否有匹配的选项
  2071 + return attendanceOptions.value.some(option =>
  2072 + option.label === formState.userBasicDetailVO.monthlyAttendanceName ||
  2073 + formState.userBasicDetailVO.selectedAttendanceType === option.value
  2074 + );
  2075 + });
  2076 + return {
  2077 + registerModal,
  2078 + employeeFormRegister,
  2079 + productionFormRegister,
  2080 + getTitle,
  2081 + handleSubmit,
  2082 + handleTabChange,
  2083 + handleCancel,
  2084 + accountType,
  2085 + formState,
  2086 + companyOptions,
  2087 + deptOptions,
  2088 + roleOptions,
  2089 + getAttendanceLabel,
  2090 + handleAttendanceTypeChange,
  2091 + handleSocialSettingChange,
  2092 + calculateContractDuration,
  2093 + activeTabKey,
  2094 + employeeFormRef,
  2095 + rules,
  2096 + idCardRules,
  2097 + handleIdCardInput,
  2098 + handleIdCardChange,
  2099 + attendanceOptions,
  2100 + isDebug,
  2101 + yearGroups,
  2102 + socialSettingOptions,
  2103 + socialSettingYearGroups,
  2104 + parseTestData,
  2105 + submitLoading,
  2106 + hasMatchingAttendanceOption,
  2107 + fieldLockStatus,
  2108 + isFieldLocked,
  2109 + isUpdate,
  2110 + processFieldLockStatus,
  2111 + refreshOptions: fetchOptions // 暴露刷新选项数据的方法
  2112 + };
61 2113 },
62 2114 });
63 2115 </script>
64   -./account.data.tsx4
  2116 +<style scoped>
  2117 +.employee-file-form {
  2118 + width: 100%;
  2119 +}
  2120 +.section-header {
  2121 + font-weight: bold;
  2122 + font-size: 16px;
  2123 + padding: 10px;
  2124 + background-color: #f5f5f5;
  2125 + border-left: 4px solid #1890ff;
  2126 + margin-bottom: 16px;
  2127 + margin-top: 16px;
  2128 +}
  2129 +.form-section {
  2130 + padding: 0 10px 16px 10px;
  2131 +}
  2132 +/* 卡片式配置样式 */
  2133 +.config-cards {
  2134 + margin-top: 20px;
  2135 + border: 1px solid #e8e8e8;
  2136 + border-radius: 4px;
  2137 + overflow: hidden;
  2138 +}
  2139 +.card-tabs {
  2140 + display: flex;
  2141 + background-color: #f5f5f5;
  2142 + border-bottom: 1px solid #e8e8e8;
  2143 +}
  2144 +.card-tab {
  2145 + padding: 12px 16px;
  2146 + font-size: 14px;
  2147 + cursor: pointer;
  2148 + transition: all 0.3s;
  2149 + position: relative;
  2150 +}
  2151 +.card-tab.active {
  2152 + color: #1890ff;
  2153 + font-weight: bold;
  2154 +}
  2155 +.card-tab.active::after {
  2156 + content: '';
  2157 + position: absolute;
  2158 + bottom: 0;
  2159 + left: 0;
  2160 + width: 100%;
  2161 + height: 2px;
  2162 + background-color: #1890ff;
  2163 +}
  2164 +.card-tab:hover {
  2165 + color: #40a9ff;
  2166 +}
  2167 +.card-content {
  2168 + padding: 16px;
  2169 + background-color: #fff;
  2170 +}
  2171 +/* 标签页样式 */
  2172 +.tab-container {
  2173 + margin-top: 10px;
  2174 +}
  2175 +.tab-container :deep(.ant-tabs-nav) {
  2176 + margin-bottom: 16px;
  2177 + background-color: #f5f5f5;
  2178 + padding: 0;
  2179 + border-bottom: 1px solid #e8e8e8;
  2180 +}
  2181 +.tab-container :deep(.ant-tabs-nav-list) {
  2182 + width: 100%;
  2183 + display: flex;
  2184 +}
  2185 +.tab-container :deep(.ant-tabs-tab) {
  2186 + padding: 12px 0;
  2187 + margin: 0;
  2188 + flex: 1;
  2189 + text-align: center;
  2190 + transition: all 0.3s;
  2191 +}
  2192 +.tab-container :deep(.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn) {
  2193 + color: #1890ff;
  2194 + font-weight: bold;
  2195 +}
  2196 +.tab-container :deep(.ant-tabs-ink-bar) {
  2197 + background-color: #1890ff;
  2198 +}
  2199 +.tab-content-container {
  2200 + min-height: 500px;
  2201 + padding: 20px 0;
  2202 + background-color: #fff;
  2203 +}
  2204 +/* 确保所有标签页内容区域大小一致 */
  2205 +.ant-tabs-content {
  2206 + height: 100%;
  2207 +}
  2208 +.ant-tabs-tabpane {
  2209 + height: 100%;
  2210 + min-height: 500px;
  2211 +}
  2212 +/* 基本信息标签页可能内容较多,需要滚动 */
  2213 +.ant-tabs-tabpane-basic {
  2214 + overflow-y: auto;
  2215 + max-height: 700px;
  2216 +}
  2217 +/* 空白填充区域 */
  2218 +.spacer {
  2219 + height: 300px;
  2220 + width: 100%;
  2221 +}
  2222 +</style>
... ...
src/views/project/account/AllocationDrawer.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + title="分配比例"
  6 + width="600px"
  7 + height="80vh"
  8 + :showFooter="false"
  9 + >
  10 + <div class="allocation-container">
  11 + <div class="allocation-form">
  12 + <div class="allocation-header">
  13 + <span class="font-bold">客户代码分配比例</span>
  14 + </div>
  15 +
  16 + <div class="allocation-list">
  17 + <div
  18 + v-for="item in allocationList"
  19 + :key="item.customerCode"
  20 + class="allocation-item"
  21 + >
  22 + <a-row :gutter="16" align="middle">
  23 + <a-col :span="8">
  24 + <span class="customer-code">{{ item.customerCode }}</span>
  25 + </a-col>
  26 + <a-col :span="10">
  27 + <div class="value-input-container">
  28 + <a-input-number
  29 + v-if="item.editing"
  30 + v-model:value="item.editValue"
  31 + :min="0"
  32 + :max="100"
  33 + :precision="2"
  34 + placeholder="输入比例"
  35 + style="width: 100%"
  36 + size="small"
  37 + @pressEnter="handleSaveEdit(item)"
  38 + />
  39 + <span v-else class="value-display">{{ item.value }}%</span>
  40 + </div>
  41 + </a-col>
  42 + <a-col :span="6">
  43 + <div class="action-buttons">
  44 + <template v-if="item.editing">
  45 + <a-button
  46 + type="primary"
  47 + size="small"
  48 + @click="handleSaveEdit(item)"
  49 + :loading="item.saving"
  50 + >
  51 + 确定
  52 + </a-button>
  53 + <a-button
  54 + size="small"
  55 + @click="handleCancelEdit(item)"
  56 + style="margin-left: 8px;"
  57 + >
  58 + 取消
  59 + </a-button>
  60 + </template>
  61 + <template v-else>
  62 + <a-button
  63 + v-if="!item.unlocked"
  64 + type="default"
  65 + size="small"
  66 + @click="handleUnlock(item)"
  67 + :icon="h(LockOutlined)"
  68 + >
  69 + 解锁
  70 + </a-button>
  71 + <a-button
  72 + v-else
  73 + type="link"
  74 + size="small"
  75 + @click="handleStartEdit(item)"
  76 + >
  77 + 编辑
  78 + </a-button>
  79 + </template>
  80 + </div>
  81 + </a-col>
  82 + </a-row>
  83 + </div>
  84 + </div>
  85 + </div>
  86 + </div>
  87 + </BasicDrawer>
  88 + </template>
  89 +
  90 + <script lang="ts">
  91 + import { defineComponent, ref, computed, h } from 'vue';
  92 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  93 + import { defHttp } from '/@/utils/http/axios';
  94 + import { useMessage } from '/@/hooks/web/useMessage';
  95 + import { LockOutlined } from '@ant-design/icons-vue';
  96 +
  97 + interface AllocationItem {
  98 + customerCode: string;
  99 + value: number;
  100 + editing?: boolean;
  101 + editValue?: number;
  102 + saving?: boolean;
  103 + unlocked?: boolean;
  104 + }
  105 +
  106 + interface UserInfo {
  107 + id: number;
  108 + chineseName?: string;
  109 + nickName?: string;
  110 + phone?: string;
  111 + }
  112 +
  113 + export default defineComponent({
  114 + name: 'AllocationDrawer',
  115 + components: { BasicDrawer },
  116 + emits: ['success', 'register'],
  117 + setup(_, { emit }) {
  118 + const { createMessage } = useMessage();
  119 + const formRef = ref();
  120 + const confirmLoading = ref(false);
  121 + const userInfo = ref<UserInfo>({} as UserInfo);
  122 + const allocationList = ref<AllocationItem[]>([]);
  123 +
  124 + // 计算总比例
  125 + const totalPercentage = computed(() => {
  126 + return allocationList.value.reduce((sum, item) => sum + (item.value || 0), 0);
  127 + });
  128 +
  129 + const [registerDrawer, { setDrawerProps }] = useDrawerInner(async (data) => {
  130 + console.log('打开分配比例抽屉,接收数据:', data);
  131 +
  132 + if (data?.record) {
  133 + userInfo.value = data.record;
  134 + await loadAllocationData(data.record.id);
  135 + }
  136 + });
  137 +
  138 + // 加载分配比例数据
  139 + async function loadAllocationData(userId: number) {
  140 + try {
  141 + setDrawerProps({ confirmLoading: true });
  142 +
  143 + const response = await defHttp.post({
  144 + url: '/order/erp/userCustomerCode/list',
  145 + data: { userId }
  146 + });
  147 +
  148 + console.log('获取分配比例数据:', response);
  149 +
  150 + if (Array.isArray(response)) {
  151 + allocationList.value = response.map(item => ({
  152 + customerCode: item.customerCode,
  153 + value: item.value || 0,
  154 + editing: false,
  155 + editValue: item.value || 0,
  156 + saving: false,
  157 + unlocked: false
  158 + }));
  159 + } else {
  160 + allocationList.value = [];
  161 + createMessage.warning('未获取到分配比例数据');
  162 + }
  163 + } catch (error) {
  164 + console.error('获取分配比例数据失败:', error);
  165 + createMessage.error('获取分配比例数据失败');
  166 + allocationList.value = [];
  167 + } finally {
  168 + setDrawerProps({ confirmLoading: false });
  169 + }
  170 + }
  171 +
  172 + // 解锁
  173 + function handleUnlock(item: AllocationItem) {
  174 + item.unlocked = true;
  175 + createMessage.success(`${item.customerCode} 已解锁,可以编辑`);
  176 + }
  177 +
  178 + // 开始编辑
  179 + function handleStartEdit(item: AllocationItem) {
  180 + if (!item.unlocked) {
  181 + createMessage.warning('请先解锁后再编辑');
  182 + return;
  183 + }
  184 + item.editing = true;
  185 + item.editValue = item.value;
  186 + }
  187 +
  188 + // 取消编辑
  189 + function handleCancelEdit(item: AllocationItem) {
  190 + item.editing = false;
  191 + item.editValue = item.value;
  192 + // 取消编辑时自动锁定
  193 + item.unlocked = false;
  194 + }
  195 +
  196 + // 保存编辑
  197 + async function handleSaveEdit(item: AllocationItem) {
  198 + try {
  199 + item.saving = true;
  200 +
  201 + const requestData = {
  202 + userId: userInfo.value.id,
  203 + customerCode: item.customerCode,
  204 + value: item.editValue || 0
  205 + };
  206 +
  207 + console.log('保存单个分配比例:', requestData);
  208 +
  209 + await defHttp.post({
  210 + url: '/order/erp/userCustomerCode/edit',
  211 + data: requestData
  212 + });
  213 +
  214 + // 更新本地数据
  215 + item.value = item.editValue || 0;
  216 + item.editing = false;
  217 + // 保存后自动锁定
  218 + item.unlocked = false;
  219 +
  220 + createMessage.success(`${item.customerCode} 分配比例保存成功`);
  221 +
  222 + } catch (error) {
  223 + console.error('保存分配比例失败:', error);
  224 + createMessage.error('保存分配比例失败');
  225 + } finally {
  226 + item.saving = false;
  227 + }
  228 + }
  229 +
  230 + return {
  231 + registerDrawer,
  232 + formRef,
  233 + confirmLoading,
  234 + userInfo,
  235 + allocationList,
  236 + totalPercentage,
  237 + handleUnlock,
  238 + handleStartEdit,
  239 + handleCancelEdit,
  240 + handleSaveEdit,
  241 + h,
  242 + LockOutlined,
  243 + };
  244 + },
  245 + });
  246 + </script>
  247 +
  248 + <style lang="less" scoped>
  249 + .allocation-container {
  250 + padding: 0;
  251 + height: 100%;
  252 + display: flex;
  253 + flex-direction: column;
  254 + }
  255 +
  256 + .allocation-form {
  257 + flex: 1;
  258 + display: flex;
  259 + flex-direction: column;
  260 + }
  261 +
  262 + .allocation-header {
  263 + padding: 12px 16px 8px 16px;
  264 + border-bottom: 1px solid #f0f0f0;
  265 + flex-shrink: 0;
  266 + }
  267 +
  268 + .allocation-list {
  269 + flex: 1;
  270 + padding: 8px 16px 8px 16px;
  271 + overflow-y: auto;
  272 + }
  273 +
  274 + .allocation-item {
  275 + padding: 8px 0;
  276 + border-bottom: 1px solid #f5f5f5;
  277 +
  278 + &:last-child {
  279 + border-bottom: none;
  280 + }
  281 + }
  282 +
  283 + .customer-code {
  284 + font-weight: 500;
  285 + color: #333;
  286 + font-size: 14px;
  287 + }
  288 +
  289 + .value-input-container {
  290 + display: flex;
  291 + align-items: center;
  292 + }
  293 +
  294 + .value-display {
  295 + font-size: 14px;
  296 + color: #333;
  297 + font-weight: 500;
  298 + }
  299 +
  300 + .action-buttons {
  301 + display: flex;
  302 + justify-content: flex-end;
  303 + align-items: center;
  304 + }
  305 + </style>
0 306 \ No newline at end of file
... ...
src/views/project/account/CompanyDrawer.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + :title="'公司管理'"
  6 + width="500px"
  7 + :showFooter="false"
  8 + >
  9 + <!-- 新增公司区域 -->
  10 + <div class="mb-4 flex">
  11 + <a-input v-model:value="companyName" placeholder="请输入公司名称" class="flex-1 mr-2" />
  12 + <a-button type="primary" @click="handleCreate">新增公司</a-button>
  13 + </div>
  14 +
  15 + <!-- 编辑公司区域 -->
  16 + <div v-if="editingRecord" class="mb-4 p-4 border border-blue-300 rounded bg-blue-50">
  17 + <div class="flex items-center justify-between mb-2">
  18 + <h4 class="text-blue-600 font-medium">编辑公司</h4>
  19 + <a-button size="small" @click="cancelEdit">取消</a-button>
  20 + </div>
  21 + <div class="flex">
  22 + <a-input v-model:value="editForm.name" placeholder="请输入公司名称" class="flex-1 mr-2" />
  23 + <a-button type="primary" @click="handleEditSubmit">保存</a-button>
  24 + </div>
  25 + </div>
  26 +
  27 + <BasicTable @register="registerTable">
  28 + <template #bodyCell="{ column, record }">
  29 + <template v-if="column.key === 'action'">
  30 + <TableAction
  31 + :actions="[
  32 + {
  33 + icon: 'clarity:note-edit-line',
  34 + tooltip: '编辑公司',
  35 + onClick: handleEdit.bind(null, record),
  36 + },
  37 + {
  38 + icon: 'ant-design:delete-outlined',
  39 + color: 'error',
  40 + tooltip: '删除公司',
  41 + popConfirm: {
  42 + title: '是否确认删除',
  43 + placement: 'left',
  44 + confirm: handleDelete.bind(null, record),
  45 + },
  46 + },
  47 + ]"
  48 + />
  49 + </template>
  50 + </template>
  51 + </BasicTable>
  52 + </BasicDrawer>
  53 + </template>
  54 + <script lang="ts">
  55 + import { defineComponent, ref } from 'vue';
  56 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  57 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  58 + import { getCompanyList, addCompany, editCompany, deleteCompany } from '/@/api/project/account';
  59 + import { useMessage } from '/@/hooks/web/useMessage';
  60 +
  61 + const columns = [
  62 + {
  63 + title: '公司名称',
  64 + dataIndex: 'name',
  65 + width: 200,
  66 + },
  67 + ];
  68 +
  69 + export default defineComponent({
  70 + name: 'CompanyDrawer',
  71 + components: { BasicDrawer, BasicTable, TableAction },
  72 + setup() {
  73 + const { createMessage } = useMessage();
  74 + const companyName = ref('');
  75 + const editingRecord = ref(null);
  76 + const editForm = ref({
  77 + id: '',
  78 + name: '',
  79 + });
  80 +
  81 + const [registerTable, { reload }] = useTable({
  82 + api: getCompanyList,
  83 + columns,
  84 + pagination: false,
  85 + striped: false,
  86 + useSearchForm: false,
  87 + showTableSetting: false,
  88 + bordered: true,
  89 + showIndexColumn: false,
  90 + canResize: false,
  91 + actionColumn: {
  92 + width: 120,
  93 + title: '操作',
  94 + dataIndex: 'action',
  95 + },
  96 + rowKey: 'id',
  97 + });
  98 +
  99 + const [registerDrawer] = useDrawerInner(async () => {
  100 + reload();
  101 + // 重置编辑状态
  102 + editingRecord.value = null;
  103 + editForm.value = { id: '', name: '' };
  104 + });
  105 +
  106 + function handleEdit(record: Recordable) {
  107 + editingRecord.value = record;
  108 + editForm.value = {
  109 + id: record.id,
  110 + name: record.name,
  111 + };
  112 + }
  113 +
  114 + function cancelEdit() {
  115 + editingRecord.value = null;
  116 + editForm.value = { id: '', name: '' };
  117 + }
  118 +
  119 + async function handleEditSubmit() {
  120 + if (!editForm.value.name) {
  121 + createMessage.warning('公司名称不能为空');
  122 + return;
  123 + }
  124 +
  125 + await editCompany({
  126 + id: editForm.value.id,
  127 + name: editForm.value.name,
  128 + });
  129 +
  130 + createMessage.success('编辑成功');
  131 + cancelEdit();
  132 + reload();
  133 + }
  134 +
  135 + async function handleDelete(record: Recordable) {
  136 + await deleteCompany({ id: record.id });
  137 + createMessage.success('删除成功');
  138 + reload();
  139 + }
  140 +
  141 + async function handleCreate() {
  142 + if (!companyName.value) {
  143 + createMessage.warning('公司名称不能为空');
  144 + return;
  145 + }
  146 +
  147 + await addCompany({ name: companyName.value });
  148 + createMessage.success('添加成功');
  149 + companyName.value = '';
  150 + reload();
  151 + }
  152 +
  153 + return {
  154 + registerDrawer,
  155 + registerTable,
  156 + companyName,
  157 + editingRecord,
  158 + editForm,
  159 + handleCreate,
  160 + handleEdit,
  161 + handleDelete,
  162 + handleEditSubmit,
  163 + cancelEdit,
  164 + };
  165 + },
  166 + });
  167 + </script>
  168 +
0 169 \ No newline at end of file
... ...
src/views/project/account/DeptDrawer.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + :title="'部门管理'"
  6 + width="500px"
  7 + :showFooter="false"
  8 + >
  9 + <!-- 新增部门区域 -->
  10 + <div class="mb-4 flex">
  11 + <a-input v-model:value="deptName" placeholder="请输入部门名称" class="flex-1 mr-2" />
  12 + <a-button type="primary" @click="handleCreate">新增部门</a-button>
  13 + </div>
  14 +
  15 + <!-- 编辑部门区域 -->
  16 + <div v-if="editingRecord" class="mb-4 p-4 border border-blue-300 rounded bg-blue-50">
  17 + <div class="flex items-center justify-between mb-2">
  18 + <h4 class="text-blue-600 font-medium">编辑部门</h4>
  19 + <a-button size="small" @click="cancelEdit">取消</a-button>
  20 + </div>
  21 + <div class="flex">
  22 + <a-input v-model:value="editForm.name" placeholder="请输入部门名称" class="flex-1 mr-2" />
  23 + <a-button type="primary" @click="handleEditSubmit">保存</a-button>
  24 + </div>
  25 + </div>
  26 +
  27 + <BasicTable @register="registerTable">
  28 + <template #bodyCell="{ column, record }">
  29 + <template v-if="column.key === 'action'">
  30 + <TableAction
  31 + :actions="[
  32 + {
  33 + icon: 'clarity:note-edit-line',
  34 + tooltip: '编辑部门',
  35 + onClick: handleEdit.bind(null, record),
  36 + },
  37 + {
  38 + icon: 'ant-design:delete-outlined',
  39 + color: 'error',
  40 + tooltip: '删除部门',
  41 + popConfirm: {
  42 + title: '是否确认删除',
  43 + placement: 'left',
  44 + confirm: handleDelete.bind(null, record),
  45 + },
  46 + },
  47 + ]"
  48 + />
  49 + </template>
  50 + </template>
  51 + </BasicTable>
  52 + </BasicDrawer>
  53 + </template>
  54 + <script lang="ts">
  55 + import { defineComponent, ref } from 'vue';
  56 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  57 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  58 + import { getDeptList, addDept, editDept, deleteDept } from '/@/api/project/account';
  59 + import { useMessage } from '/@/hooks/web/useMessage';
  60 +
  61 + const columns = [
  62 + {
  63 + title: '部门名称',
  64 + dataIndex: 'name',
  65 + width: 200,
  66 + },
  67 + ];
  68 +
  69 + export default defineComponent({
  70 + name: 'DeptDrawer',
  71 + components: { BasicDrawer, BasicTable, TableAction },
  72 + setup() {
  73 + const { createMessage } = useMessage();
  74 + const deptName = ref('');
  75 + const editingRecord = ref(null);
  76 + const editForm = ref({
  77 + id: '',
  78 + name: '',
  79 + });
  80 +
  81 + const [registerTable, { reload }] = useTable({
  82 + api: getDeptList,
  83 + columns,
  84 + pagination: false,
  85 + striped: false,
  86 + useSearchForm: false,
  87 + showTableSetting: false,
  88 + bordered: true,
  89 + showIndexColumn: false,
  90 + canResize: false,
  91 + actionColumn: {
  92 + width: 120,
  93 + title: '操作',
  94 + dataIndex: 'action',
  95 + },
  96 + rowKey: 'id',
  97 + });
  98 +
  99 + const [registerDrawer] = useDrawerInner(async () => {
  100 + reload();
  101 + // 重置编辑状态
  102 + editingRecord.value = null;
  103 + editForm.value = { id: '', name: '' };
  104 + });
  105 +
  106 + function handleEdit(record: Recordable) {
  107 + editingRecord.value = record;
  108 + editForm.value = {
  109 + id: record.id,
  110 + name: record.name,
  111 + };
  112 + }
  113 +
  114 + function cancelEdit() {
  115 + editingRecord.value = null;
  116 + editForm.value = { id: '', name: '' };
  117 + }
  118 +
  119 + async function handleEditSubmit() {
  120 + if (!editForm.value.name) {
  121 + createMessage.warning('部门名称不能为空');
  122 + return;
  123 + }
  124 +
  125 + await editDept({
  126 + id: editForm.value.id,
  127 + name: editForm.value.name,
  128 + });
  129 +
  130 + createMessage.success('编辑成功');
  131 + cancelEdit();
  132 + reload();
  133 + }
  134 +
  135 + async function handleDelete(record: Recordable) {
  136 + await deleteDept({ id: record.id });
  137 + createMessage.success('删除成功');
  138 + reload();
  139 + }
  140 +
  141 + async function handleCreate() {
  142 + if (!deptName.value) {
  143 + createMessage.warning('部门名称不能为空');
  144 + return;
  145 + }
  146 +
  147 + await addDept({ name: deptName.value });
  148 + createMessage.success('添加成功');
  149 + deptName.value = '';
  150 + reload();
  151 + }
  152 +
  153 + return {
  154 + registerDrawer,
  155 + registerTable,
  156 + deptName,
  157 + editingRecord,
  158 + editForm,
  159 + handleCreate,
  160 + handleEdit,
  161 + handleDelete,
  162 + handleEditSubmit,
  163 + cancelEdit,
  164 + };
  165 + },
  166 + });
  167 + </script>
  168 +
0 169 \ No newline at end of file
... ...
src/views/project/account/EmployeeFieldApplyDrawer.vue 0 → 100644
  1 +<template>
  2 + <!-- 申请权限抽屉 -->
  3 + <BasicDrawer
  4 + v-bind="$attrs"
  5 + @register="registerDrawer"
  6 + @ok="handleApplySubmit"
  7 + title="字段编辑权限申请"
  8 + width="60%"
  9 + :destroyOnClose="true"
  10 + :isDetail="true"
  11 + :showFooter="true"
  12 + okText="申请"
  13 + :showDetailBack="false"
  14 + :closable="true"
  15 + class="apply-permission-drawer"
  16 + >
  17 + <div>
  18 + <h3 style="margin-bottom: 16px; color: #1890ff;">请选择需要申请编辑权限的字段:</h3>
  19 + <BasicForm @register="registerApplyForm" />
  20 + </div>
  21 + </BasicDrawer>
  22 +
  23 + <!-- 申请理由组件 -->
  24 + <ApproveReason @register="registerApproveReason" @success="handleApproveSuccess" />
  25 + </template>
  26 +
  27 + <script lang="ts">
  28 + import { defineComponent, ref } from 'vue';
  29 + import { BasicDrawer, useDrawer, useDrawerInner } from '/@/components/Drawer';
  30 + import { BasicForm, useForm } from '/@/components/Form/index';
  31 + import { useModal } from '/@/components/Modal';
  32 + import { message } from 'ant-design-vue';
  33 + import ApproveReason from '../personnel/salary/attendance/ApproveReason.vue';
  34 +
  35 + export default defineComponent({
  36 + name: 'EmployeeFieldApplyDrawer',
  37 + components: {
  38 + BasicDrawer,
  39 + BasicForm,
  40 + ApproveReason,
  41 + },
  42 + setup() {
  43 + const currentApplyRecord = ref<any>(null);
  44 + const selectedApplyFields = ref<any>({});
  45 +
  46 + // 注册申请权限抽屉,使用useDrawerInner接收数据
  47 + const [registerDrawer, { closeDrawer }] = useDrawerInner(async (data) => {
  48 + if (data?.record) {
  49 + currentApplyRecord.value = { ...data.record };
  50 + }
  51 + });
  52 +
  53 + // 注册申请理由模态框
  54 + const [registerApproveReason, { openModal: openApproveReasonModal }] = useModal();
  55 +
  56 + // 定义申请权限表单字段
  57 + const getApplySchema = () => [
  58 + {
  59 + field: 'attendanceAllowance',
  60 + label: '全勤奖',
  61 + component: 'Switch',
  62 + componentProps: {
  63 + checkedValue: 'UN_LOCKED',
  64 + unCheckedValue: 'LOCKED',
  65 + },
  66 + colProps: {
  67 + span: 8,
  68 + },
  69 + },
  70 + {
  71 + field: 'basicWages',
  72 + label: '基本工资',
  73 + component: 'Switch',
  74 + componentProps: {
  75 + checkedValue: 'UN_LOCKED',
  76 + unCheckedValue: 'LOCKED',
  77 + },
  78 + colProps: {
  79 + span: 8,
  80 + },
  81 + },
  82 + {
  83 + field: 'contractEndTime',
  84 + label: '合同结束时间',
  85 + component: 'Switch',
  86 + componentProps: {
  87 + checkedValue: 'UN_LOCKED',
  88 + unCheckedValue: 'LOCKED',
  89 + },
  90 + colProps: {
  91 + span: 8,
  92 + },
  93 + },
  94 + {
  95 + field: 'contractStartTime',
  96 + label: '合同签署时间',
  97 + component: 'Switch',
  98 + componentProps: {
  99 + checkedValue: 'UN_LOCKED',
  100 + unCheckedValue: 'LOCKED',
  101 + },
  102 + colProps: {
  103 + span: 8,
  104 + },
  105 + },
  106 + {
  107 + field: 'managementAllowance',
  108 + label: '管理岗位津贴',
  109 + component: 'Switch',
  110 + componentProps: {
  111 + checkedValue: 'UN_LOCKED',
  112 + unCheckedValue: 'LOCKED',
  113 + },
  114 + colProps: {
  115 + span: 8,
  116 + },
  117 + },
  118 + {
  119 + field: 'mealAllowance',
  120 + label: '餐补',
  121 + component: 'Switch',
  122 + componentProps: {
  123 + checkedValue: 'UN_LOCKED',
  124 + unCheckedValue: 'LOCKED',
  125 + },
  126 + colProps: {
  127 + span: 8,
  128 + },
  129 + },
  130 + {
  131 + field: 'monthlyAttendanceName',
  132 + label: '考勤组',
  133 + component: 'Switch',
  134 + componentProps: {
  135 + checkedValue: 'UN_LOCKED',
  136 + unCheckedValue: 'LOCKED',
  137 + },
  138 + colProps: {
  139 + span: 8,
  140 + },
  141 + },
  142 + {
  143 + field: 'performanceAllowance',
  144 + label: '绩效部分',
  145 + component: 'Switch',
  146 + componentProps: {
  147 + checkedValue: 'UN_LOCKED',
  148 + unCheckedValue: 'LOCKED',
  149 + },
  150 + colProps: {
  151 + span: 8,
  152 + },
  153 + },
  154 + {
  155 + field: 'periodEndTime',
  156 + label: '试用期结束时间',
  157 + component: 'Switch',
  158 + componentProps: {
  159 + checkedValue: 'UN_LOCKED',
  160 + unCheckedValue: 'LOCKED',
  161 + },
  162 + colProps: {
  163 + span: 8,
  164 + },
  165 + },
  166 + {
  167 + field: 'periodStartTime',
  168 + label: '试用期开始时间',
  169 + component: 'Switch',
  170 + componentProps: {
  171 + checkedValue: 'UN_LOCKED',
  172 + unCheckedValue: 'LOCKED',
  173 + },
  174 + colProps: {
  175 + span: 8,
  176 + },
  177 + },
  178 + {
  179 + field: 'phoneAllowance',
  180 + label: '话费补贴',
  181 + component: 'Switch',
  182 + componentProps: {
  183 + checkedValue: 'UN_LOCKED',
  184 + unCheckedValue: 'LOCKED',
  185 + },
  186 + colProps: {
  187 + span: 8,
  188 + },
  189 + },
  190 + {
  191 + field: 'postAllowance',
  192 + label: '岗位津贴',
  193 + component: 'Switch',
  194 + componentProps: {
  195 + checkedValue: 'UN_LOCKED',
  196 + unCheckedValue: 'LOCKED',
  197 + },
  198 + colProps: {
  199 + span: 8,
  200 + },
  201 + },
  202 + {
  203 + field: 'roleName',
  204 + label: '岗位',
  205 + component: 'Switch',
  206 + componentProps: {
  207 + checkedValue: 'UN_LOCKED',
  208 + unCheckedValue: 'LOCKED',
  209 + },
  210 + colProps: {
  211 + span: 8,
  212 + },
  213 + },
  214 + {
  215 + field: 'socialSettingName',
  216 + label: '社保待遇',
  217 + component: 'Switch',
  218 + componentProps: {
  219 + checkedValue: 'UN_LOCKED',
  220 + unCheckedValue: 'LOCKED',
  221 + },
  222 + colProps: {
  223 + span: 8,
  224 + },
  225 + },
  226 + {
  227 + field: 'transportationAllowance',
  228 + label: '交通补贴',
  229 + component: 'Switch',
  230 + componentProps: {
  231 + checkedValue: 'UN_LOCKED',
  232 + unCheckedValue: 'LOCKED',
  233 + },
  234 + colProps: {
  235 + span: 8,
  236 + },
  237 + },
  238 + ];
  239 +
  240 + // 注册申请权限表单
  241 + const [registerApplyForm, { getFieldsValue: getApplyFieldsValue }] = useForm({
  242 + labelWidth: 180,
  243 + schemas: getApplySchema(),
  244 + showActionButtonGroup: false,
  245 + actionColOptions: {
  246 + span: 24,
  247 + },
  248 + });
  249 +
  250 + // 处理申请权限提交
  251 + const handleApplySubmit = async () => {
  252 + try {
  253 + const fieldValues = getApplyFieldsValue();
  254 + selectedApplyFields.value = fieldValues;
  255 + closeDrawer();
  256 +
  257 + // 打开申请理由模态框
  258 + openApproveReasonModal(true, {
  259 + fieldValues: selectedApplyFields.value,
  260 + userId: currentApplyRecord.value?.id,
  261 + apiUrl: '/order/erp/users/fieldApply'
  262 + });
  263 + } catch (error) {
  264 + console.error('获取表单数据失败:', error);
  265 + message.error('获取表单数据失败');
  266 + }
  267 + };
  268 +
  269 + // 处理申请成功回调
  270 + const handleApproveSuccess = () => {
  271 + selectedApplyFields.value = {};
  272 + currentApplyRecord.value = null;
  273 + message.success('申请提交成功');
  274 + };
  275 +
  276 + return {
  277 + registerDrawer,
  278 + registerApplyForm,
  279 + registerApproveReason,
  280 + handleApplySubmit,
  281 + handleApproveSuccess,
  282 + };
  283 + },
  284 + });
  285 + </script>
  286 +
  287 + <style lang="less" scoped>
  288 + // 申请权限抽屉样式,与出勤统计表保持一致
  289 + :deep(.apply-permission-drawer) {
  290 + .ant-drawer-content {
  291 + position: fixed;
  292 + z-index: 10;
  293 + }
  294 + }
  295 + </style>
0 296 \ No newline at end of file
... ...
src/views/project/account/RoleDrawer.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + :title="'角色岗位管理'"
  6 + width="550px"
  7 + :showFooter="false"
  8 + >
  9 + <!-- 新增角色区域 -->
  10 + <div class="mb-4 flex">
  11 + <a-input v-model:value="roleName" placeholder="请输入角色名称" class="mr-2" style="width: 40%" />
  12 + <!-- <a-input v-model:value="roleCode" placeholder="请输入角色编码" class="mr-2" style="width: 40%" /> -->
  13 + <a-button type="primary" @click="handleCreate">新增角色岗位</a-button>
  14 + </div>
  15 +
  16 + <!-- 编辑角色区域 -->
  17 + <div v-if="editingRecord" class="mb-4 p-4 border border-blue-300 rounded bg-blue-50">
  18 + <div class="flex items-center justify-between mb-2">
  19 + <h4 class="text-blue-600 font-medium">编辑角色岗位</h4>
  20 + <a-button size="small" @click="cancelEdit">取消</a-button>
  21 + </div>
  22 + <div class="flex">
  23 + <a-input v-model:value="editForm.name" placeholder="请输入角色名称" class="mr-2" style="width: 40%" />
  24 + <!-- <a-input v-model:value="editForm.code" placeholder="请输入角色编码" class="mr-2" style="width: 40%" /> -->
  25 + <a-button type="primary" @click="handleEditSubmit">保存</a-button>
  26 + </div>
  27 + </div>
  28 +
  29 + <BasicTable @register="registerTable">
  30 + <template #bodyCell="{ column, record }">
  31 + <template v-if="column.key === 'action'">
  32 + <TableAction
  33 + :actions="[
  34 + {
  35 + icon: 'clarity:note-edit-line',
  36 + tooltip: '编辑角色岗位',
  37 + onClick: handleEdit.bind(null, record),
  38 + },
  39 + {
  40 + icon: 'ant-design:delete-outlined',
  41 + color: 'error',
  42 + tooltip: '删除角色岗位',
  43 + popConfirm: {
  44 + title: '是否确认删除',
  45 + placement: 'left',
  46 + confirm: handleDelete.bind(null, record),
  47 + },
  48 + },
  49 + ]"
  50 + />
  51 + </template>
  52 + </template>
  53 + </BasicTable>
  54 + </BasicDrawer>
  55 + </template>
  56 + <script lang="ts">
  57 + import { defineComponent, ref } from 'vue';
  58 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  59 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  60 + import { getRoleList, addRole, editRole, deleteRole } from '/@/api/project/account';
  61 + import { useMessage } from '/@/hooks/web/useMessage';
  62 +
  63 + const columns = [
  64 + {
  65 + title: '角色名称',
  66 + dataIndex: 'name',
  67 + width: 150,
  68 + },
  69 + // {
  70 + // title: '角色编码',
  71 + // dataIndex: 'code',
  72 + // width: 150,
  73 + // },
  74 + ];
  75 +
  76 + export default defineComponent({
  77 + name: 'RoleDrawer',
  78 + components: { BasicDrawer, BasicTable, TableAction },
  79 + setup() {
  80 + const { createMessage } = useMessage();
  81 + const roleName = ref('');
  82 + // const roleCode = ref('');
  83 + const editingRecord = ref(null);
  84 + const editForm = ref({
  85 + id: '',
  86 + name: '',
  87 + // code: '',
  88 + });
  89 +
  90 + // 获取所有角色列表
  91 + const [registerTable, { reload }] = useTable({
  92 + api: getRoleList,
  93 + columns,
  94 + pagination: false,
  95 + striped: false,
  96 + useSearchForm: false,
  97 + showTableSetting: false,
  98 + bordered: true,
  99 + showIndexColumn: false,
  100 + canResize: false,
  101 + actionColumn: {
  102 + width: 120,
  103 + title: '操作',
  104 + dataIndex: 'action',
  105 + },
  106 + rowKey: 'id',
  107 + });
  108 +
  109 + const [registerDrawer] = useDrawerInner(async () => {
  110 + reload();
  111 + // 重置编辑状态
  112 + editingRecord.value = null;
  113 + // editForm.value = { id: '', name: '', code: '' };
  114 + editForm.value = { id: '', name: '' };
  115 + });
  116 +
  117 + function handleEdit(record: Recordable) {
  118 + editingRecord.value = record;
  119 + editForm.value = {
  120 + id: record.id,
  121 + name: record.name,
  122 + // code: record.code || '',
  123 + };
  124 + }
  125 +
  126 + function cancelEdit() {
  127 + editingRecord.value = null;
  128 + // editForm.value = { id: '', name: '', code: '' };
  129 + editForm.value = { id: '', name: ''};
  130 + }
  131 +
  132 + async function handleEditSubmit() {
  133 + if (!editForm.value.name) {
  134 + createMessage.warning('角色名称不能为空');
  135 + return;
  136 + }
  137 +
  138 + // if (!editForm.value.code) {
  139 + // createMessage.warning('角色编码不能为空');
  140 + // return;
  141 + // }
  142 +
  143 + await editRole({
  144 + id: editForm.value.id,
  145 + name: editForm.value.name,
  146 + // code: editForm.value.code,
  147 + });
  148 +
  149 + createMessage.success('编辑成功');
  150 + cancelEdit();
  151 + reload();
  152 + }
  153 +
  154 + async function handleDelete(record: Recordable) {
  155 + await deleteRole({ id: record.id });
  156 + createMessage.success('删除成功');
  157 + reload();
  158 + }
  159 +
  160 + async function handleCreate() {
  161 + if (!roleName.value) {
  162 + createMessage.warning('角色名称不能为空');
  163 + return;
  164 + }
  165 +
  166 + // if (!roleCode.value) {
  167 + // createMessage.warning('角色编码不能为空');
  168 + // return;
  169 + // }
  170 +
  171 + // 添加角色
  172 + await addRole({
  173 + name: roleName.value,
  174 + // code: roleCode.value,
  175 + });
  176 +
  177 + createMessage.success('添加成功');
  178 + roleName.value = '';
  179 + // roleCode.value = '';
  180 + reload();
  181 + }
  182 +
  183 + return {
  184 + registerDrawer,
  185 + registerTable,
  186 + roleName,
  187 + // roleCode,
  188 + editingRecord,
  189 + editForm,
  190 + handleCreate,
  191 + handleEdit,
  192 + handleDelete,
  193 + handleEditSubmit,
  194 + cancelEdit,
  195 + };
  196 + },
  197 + });
  198 + </script>
  199 +
0 200 \ No newline at end of file
... ...
src/views/project/account/account.data.tsx
1   -import { getRoleList } from '/@/api/project/account';
  1 +import { getRoleList, getDeptList, getCompanyList } from '/@/api/project/account';
2 2 import { Tag } from 'ant-design-vue';
3 3 import { BasicColumn, FormSchema } from '/@/components/Table';
4   -
  4 +// 重新包装getRoleList API,强制过滤生产科角色
  5 +const getFilteredRoleList = async (params?: any) => {
  6 + const roles = await getRoleList(params);
  7 + console.log('==== 原始角色列表 ====', roles);
  8 +
  9 + // 确保返回的是数组,并过滤掉ID为6的生产科角色
  10 + if (Array.isArray(roles)) {
  11 + const filtered = roles.filter(item => item.id !== 6);
  12 + console.log('==== 过滤后角色列表 ====', filtered);
  13 + return filtered;
  14 + }
  15 +
  16 + return roles;
  17 +};
5 18 export const columns: BasicColumn[] = [
6 19 {
7   - title: '手机号',
8   - dataIndex: 'phone',
9   - width: 200,
  20 + title: '姓名',
  21 + dataIndex: 'chineseName',
  22 + width: 120,
10 23 },
11 24 {
12 25 title: '昵称',
... ... @@ -14,6 +27,16 @@ export const columns: BasicColumn[] = [
14 27 width: 120,
15 28 },
16 29 {
  30 + title: '性别',
  31 + dataIndex: 'gender',
  32 + width: 80,
  33 + },
  34 + {
  35 + title: '手机号',
  36 + dataIndex: 'phone',
  37 + width: 150,
  38 + },
  39 + {
17 40 title: '邮箱',
18 41 dataIndex: 'email',
19 42 width: 300,
... ... @@ -24,9 +47,19 @@ export const columns: BasicColumn[] = [
24 47 // width: 180,
25 48 // },
26 49 {
27   - title: '角色',
  50 + title: '公司',
  51 + dataIndex: 'companyName',
  52 + width: 150,
  53 + },
  54 + {
  55 + title: '部门',
  56 + dataIndex: 'deptName',
  57 + width: 150,
  58 + },
  59 + {
  60 + title: '角色岗位',
28 61 dataIndex: 'roleName',
29   - width: 200,
  62 + width: 150,
30 63 },
31 64 {
32 65 title: '状态',
... ... @@ -40,26 +73,111 @@ export const columns: BasicColumn[] = [
40 73 {
41 74 title: '备注',
42 75 dataIndex: 'remark',
  76 + width: 400,
43 77 },
44 78 ];
45 79  
46 80 export const searchFormSchema: FormSchema[] = [
47 81 {
48   - field: 'phone',
49   - label: '手机号',
  82 + field: 'chineseName',
  83 + label: '姓名',
50 84 component: 'Input',
51   - colProps: { span: 8 },
  85 + colProps: { span: 3},
52 86 },
53 87 {
54 88 field: 'nickName',
55   - label: '昵称',
  89 + label: '昵称(英文名)',
  90 + component: 'Input',
  91 + colProps: { span: 4 },
  92 + },
  93 + {
  94 + field:'companyId',
  95 + label:'公司',
  96 + component:'ApiSelect',
  97 + componentProps: {
  98 + api: getCompanyList,
  99 + labelField: 'name',
  100 + valueField: 'id',
  101 + immediate: true,
  102 + showSearch: true,
  103 + optionFilterProp: 'label',
  104 + filterOption: (input, option) => {
  105 + return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
  106 + },
  107 + },
  108 + colProps: { span: 5 },
  109 + },
  110 + {
  111 + field: 'roleId',
  112 + label: '角色岗位',
  113 + component: 'ApiSelect',
  114 + componentProps: {
  115 + api: getFilteredRoleList,
  116 + labelField: 'description',
  117 + valueField: 'id',
  118 + },
  119 + colProps: { span: 4 },
  120 + },
  121 + {
  122 + field: 'deptId',
  123 + label: '部门',
  124 + component: 'ApiSelect',
  125 + componentProps: {
  126 + api: getDeptList,
  127 + labelField: 'name',
  128 + valueField: 'id',
  129 + },
  130 + colProps: { span: 4 },
  131 + },
  132 + {
  133 + field: 'phone',
  134 + label: '手机号',
56 135 component: 'Input',
57   - colProps: { span: 8 },
  136 + colProps: { span: 4 },
  137 + },
  138 + {
  139 + field: 'optType',
  140 + label: '状态',
  141 + component: 'Select',
  142 + componentProps: {
  143 + options: [
  144 + { label: '正常', value: 10 },
  145 + { label: '离职', value: 20 },
  146 + ],
  147 + },
  148 + colProps: { span: 4 },
58 149 },
59 150 ];
60   -
  151 +// 员工账号的完整表单
61 152 export const accountFormSchema: FormSchema[] = [
62 153 {
  154 + field: 'chineseName',
  155 + label: '姓名',
  156 + component: 'Input',
  157 + required: true,
  158 + },
  159 + {
  160 + field: 'nickName',
  161 + label: '昵称(英文名)',
  162 + component: 'Input',
  163 + required: true,
  164 + componentProps: {
  165 + disabled: true, // 设置为不可修改
  166 + },
  167 + },
  168 + {
  169 + field: 'gender',
  170 + label: '性别',
  171 + component: 'Select',
  172 + componentProps: {
  173 + options: [
  174 + { label: '男', value: '男' },
  175 + { label: '女', value: '女' },
  176 + ],
  177 + },
  178 + required: true,
  179 + },
  180 + {
63 181 field: 'phone',
64 182 label: '手机号',
65 183 component: 'Input',
... ... @@ -69,58 +187,79 @@ export const accountFormSchema: FormSchema[] = [
69 187 required: true,
70 188 message: '请输入用户名',
71 189 },
72   - // {
73   - // validator(_, value) {
74   - // return new Promise((resolve, reject) => {
75   - // isAccountExist(value)
76   - // .then(() => resolve())
77   - // .catch((err) => {
78   - // reject(err.message || '验证失败');
79   - // });
80   - // });
81   - // },
82   - // },
83 190 ],
84 191 },
85   - // {
86   - // field: 'pwd',
87   - // label: '密码',
88   - // component: 'InputPassword',
89   - // required: true,
90   - // ifShow: false,
91   - // },
92 192 {
93   - label: '角色',
  193 + field:'companyId',
  194 + label:'公司',
  195 + component:'ApiSelect',
  196 + componentProps: {
  197 + api: getCompanyList,
  198 + labelField: 'name',
  199 + valueField: 'id',
  200 + immediate: true,
  201 + showSearch: true,
  202 + optionFilterProp: 'label',
  203 + filterOption: (input, option) => {
  204 + return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
  205 + },
  206 + },
  207 + required: true,
  208 + },
  209 + {
  210 + label: '角色岗位',
94 211 field: 'roleId',
95 212 component: 'ApiSelect',
96 213 componentProps: {
97   - api: getRoleList,
  214 + api: getFilteredRoleList,
  215 + labelField: 'description',
  216 + valueField: 'id',
  217 + },
  218 + required: true,
  219 + },
  220 + {
  221 + label: '角色部门',
  222 + field: 'deptId',
  223 + component: 'ApiSelect',
  224 + componentProps: {
  225 + api: getDeptList,
98 226 labelField: 'name',
99   - valueField: 'level',
  227 + valueField: 'id',
100 228 },
101 229 required: true,
102 230 },
103   - // {
104   - // field: 'dept',
105   - // label: '所属部门',
106   - // component: 'TreeSelect',
107   - // componentProps: {
108   - // fieldNames: {
109   - // label: 'deptName',
110   - // key: 'id',
111   - // value: 'id',
112   - // },
113   - // getPopupContainer: () => document.body,
114   - // },
115   - // required: true,
116   - // },
117 231 {
118   - field: 'nickName',
119   - label: '昵称',
  232 + label: '邮箱',
  233 + field: 'email',
  234 + component: 'InputTextArea',
  235 + required: true,
  236 + },
  237 + {
  238 + label: '备注',
  239 + field: 'remark',
  240 + component: 'InputTextArea',
  241 + },
  242 +];
  243 +// 生产科账号的简化表单(只包含姓名、手机号、邮箱、备注)
  244 +export const productionAccountFormSchema: FormSchema[] = [
  245 + {
  246 + field: 'chineseName',
  247 + label: '姓名',
120 248 component: 'Input',
121 249 required: true,
122 250 },
123   -
  251 + {
  252 + field: 'phone',
  253 + label: '手机号',
  254 + component: 'Input',
  255 + required: true,
  256 + rules: [
  257 + {
  258 + required: true,
  259 + message: '请输入手机号',
  260 + },
  261 + ],
  262 + },
124 263 {
125 264 label: '邮箱',
126 265 field: 'email',
... ...