zhangwencui
6 天以前 10253baf7dd53c077bdd41f960230e8887c65149
feat(app): 同步中亿装配人力资源改动
已修改6个文件
53 ■■■■■ 文件已修改
src/api/hrm.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/company.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/hrm/attendance-rule/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/hrm/salary-calculation/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/hrm/social-security-scheme/index.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/hrm/transfer/form.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm.ts
@@ -5,7 +5,7 @@
export interface SimpleOption { id: number; name: string; code?: string; parentId?: number; status?: number }
export interface HrmAttachment { id: number; name?: string; url?: string; path?: string; size?: number }
export interface UserOption { id: number; nickname?: string; username?: string; deptId?: number; deptName?: string }
export interface EmployeeSimple { id: number; employeeNo: string; name: string; deptName?: string; employeeStatus?: number }
export interface EmployeeSimple { id: number; employeeNo: string; name: string; deptId?: number; deptName?: string; postId?: number; postIds?: number[]; postName?: string; employeeStatus?: number }
export interface Employee {
  id?: number; deptId?: number; deptName?: string; postId?: number; postName?: string;
  roleIds?: number[]; roleNames?: string; employeeNo?: string; name?: string; gender?: number; birthday?: string; phone?: string;
@@ -53,7 +53,7 @@
  id?: number; name?: string; deptId?: number; deptName?: string; workStartTime?: string; workEndTime?: string;
  lateAllowMinutes?: number; earlyAllowMinutes?: number; overtimeThreshold?: number; status?: number; remark?: string;
  locationName?: string; locationAddress?: string; longitude?: number; latitude?: number; radiusMeters?: number;
  locationRequired?: boolean; coordinateSystem?: "WGS84";
  locationRequired?: boolean; coordinateSystem?: "WGS84"; effectiveStartDate?: string; effectiveEndDate?: string;
}
export interface AttendanceRecord {
  id?: number; userId?: number; userName?: string; deptId?: number; deptName?: string; phone?: string; date?: string;
@@ -191,11 +191,11 @@
export interface AttendanceException extends HrmRow { userId?: number; userName?: string; deptName?: string; date?: string; exceptionType?: number; exceptionTypeName?: string; reason?: string; blobIds?: number[]; attachmentList?: HrmAttachment[]; status?: number; processInstanceId?: string; remark?: string; createTime?: string }
export interface EmployeeSchedule extends HrmRow { employeeId?: number; employeeName?: string; employeeNo?: string; date?: string; shiftId?: number; shiftName?: string; shiftColor?: string; remark?: string }
export interface SalaryStructure extends HrmRow { name?: string; overtimePayRatio?: number; restDayOvertimePayRatio?: number; legalHolidayOvertimePayRatio?: number; status?: number; remark?: string }
export interface SocialSecurityScheme extends HrmRow { name?: string; effectiveDate?: string; pensionCompanyRatio?: number; pensionPersonalRatio?: number; medicalCompanyRatio?: number; medicalPersonalRatio?: number; unemploymentCompanyRatio?: number; unemploymentPersonalRatio?: number; workInjuryRatio?: number; maternityRatio?: number; housingFundCompanyRatio?: number; housingFundPersonalRatio?: number; status?: number; remark?: string }
export interface SocialSecurityScheme extends HrmRow { name?: string; effectiveDate?: string; pensionCompanyRatio?: number; pensionPersonalRatio?: number; medicalCompanyRatio?: number; medicalPersonalRatio?: number; unemploymentCompanyRatio?: number; unemploymentPersonalRatio?: number; workInjuryRatio?: number; maternityRatio?: number; seriousIllnessCompanyRatio?: number; seriousIllnessPersonalRatio?: number; longTermCareCompanyRatio?: number; longTermCarePersonalRatio?: number; housingFundCompanyRatio?: number; housingFundPersonalRatio?: number; status?: number; remark?: string }
export interface TaxRateConfig extends HrmRow { name?: string; effectiveDate?: string; level?: number; minAmount?: number; maxAmount?: number; taxRate?: number; quickDeduction?: number }
export interface LeaveTypeConfig extends HrmRow { leaveType?: number; name?: string; deductionRatio?: number; remark?: string; status?: number }
export interface EmployeeSocialSecurity extends HrmRow { userId?: number; userName?: string; deptName?: string; socialSecuritySchemeId?: number; schemeName?: string; socialSecurityBase?: number; housingFundBase?: number; effectiveDate?: string; status?: number; remark?: string }
export interface SalaryCalculation extends HrmRow { no?: string; period?: string; userId?: number; userName?: string; deptId?: number; deptName?: string; baseSalary?: number; performanceSalary?: number; overtimePay?: number; mealAllowance?: number; transportAllowance?: number; otherAllowance?: number; socialSecurityDeduction?: number; housingFundDeduction?: number; taxDeduction?: number; otherDeduction?: number; leaveDeduction?: number; actualSalary?: number; workDays?: number; overtimeHours?: number; totalWorkHours?: number; status?: number; statusName?: string; remark?: string; createTime?: string }
export interface SalaryCalculation extends HrmRow { no?: string; period?: string; userId?: number; userName?: string; deptId?: number; deptName?: string; baseSalary?: number; postSalary?: number; senioritySalary?: number; performanceSalary?: number; fullAttendanceBonus?: number; overtimePay?: number; mealAllowance?: number; transportAllowance?: number; otherAllowance?: number; socialSecurityDeduction?: number; housingFundDeduction?: number; taxDeduction?: number; otherDeduction?: number; leaveDeduction?: number; actualSalary?: number; workDays?: number; actualDays?: number; overtimeHours?: number; totalWorkHours?: number; status?: number; statusName?: string; remark?: string; createTime?: string }
export interface SalaryPayment extends HrmRow { no?: string; period?: string; totalAmount?: number; totalCount?: number; paymentMethod?: number; paymentTime?: string; status?: number; blobIds?: number[]; attachmentList?: HrmAttachment[]; remark?: string; createTime?: string }
export interface SalaryPaymentDetail extends SalaryCalculation { paymentId?: number; calculationId?: number; paymentTime?: string }
@@ -250,6 +250,7 @@
export function deleteEmployeeSocialSecurity(token: string, id: number) { return request<boolean>(`/hrm/salary/employee-social-security/delete${query({ id })}`, { method: "DELETE", header: header(token) }); }
export function getSalaryCalculationPage(token: string, params: Record<string, unknown>) { return request<PageResult<SalaryCalculation>>(`/hrm/salary/calculation/page${query(params as Record<string, string | number | boolean | undefined>)}`, { header: header(token) }); }
export function calculateSalary(token: string, params: { period: string; deptId?: number }) { return request<boolean>(`/hrm/salary/calculation/calculate${query(params)}`, { method: "POST", header: header(token) }); }
export function updateSalaryCalculation(token: string, data: SalaryCalculation) { return request<boolean>("/hrm/salary/calculation/update", { method: "PUT", data: data as Record<string, unknown>, header: header(token) }); }
export function confirmSalary(token: string, ids: number[]) { return request<boolean>("/hrm/salary/calculation/confirm", { method: "POST", data: { ids }, header: header(token) }); }
export function revokeSalary(token: string, ids: number[]) { return request<boolean>("/hrm/salary/calculation/revoke", { method: "POST", data: { ids }, header: header(token) }); }
export function deleteSalaryCalculation(token: string, id: number) { return request<boolean>(`/hrm/salary/calculation/delete${query({ id })}`, { method: "DELETE", header: header(token) }); }
src/config/company.ts
@@ -83,6 +83,6 @@
};
/** ★ 打包开关:只改这一行 */
export const ACTIVE_COMPANY: CompanyCode = "changhui";
export const ACTIVE_COMPANY: CompanyCode = "zhongyi";
export const APP_COMPANY: AppCompanyConfig = COMPANY_PROFILES[ACTIVE_COMPANY];
src/pages/hrm/attendance-rule/index.vue
@@ -1,5 +1,5 @@
<script setup lang="ts">
import HrmCrudPage, { type CrudConfig } from "../components/HrmCrudPage.vue";
import { createAttendanceRule, deleteAttendanceRule, getAttendanceRulePage, updateAttendanceRule } from "../../../api/hrm";
const config: CrudConfig = { title:"考勤规则", permission:"hrm:attendance-rule", fields:[{key:"name",label:"规则名称",required:true},{key:"deptId",label:"部门ID",type:"number"},{key:"workStartTime",label:"上班时间",required:true},{key:"workEndTime",label:"下班时间",required:true},{key:"lateAllowMinutes",label:"迟到宽限(分钟)",type:"number"},{key:"earlyAllowMinutes",label:"早退宽限(分钟)",type:"number"},{key:"overtimeThreshold",label:"加班起算(分钟)",type:"number"},{key:"locationName",label:"打卡地点"},{key:"locationAddress",label:"地点地址"},{key:"longitude",label:"经度",type:"number"},{key:"latitude",label:"纬度",type:"number"},{key:"radiusMeters",label:"有效范围(米)",type:"number"},{key:"locationRequired",label:"必须在范围内",type:"switch"},{key:"remark",label:"备注"}], cardFields:[{key:"name",label:"名称"},{key:"deptName",label:"部门"},{key:"workStartTime",label:"上班"},{key:"workEndTime",label:"下班"},{key:"locationName",label:"打卡地点"},{key:"radiusMeters",label:"范围(米)"}], page:true, load:(t,k,p,s)=>getAttendanceRulePage(t,{pageNo:p,pageSize:s,name:k||undefined}), create:createAttendanceRule, update:updateAttendanceRule, remove:deleteAttendanceRule };
const config: CrudConfig = { title:"考勤规则", permission:"hrm:attendance-rule", fields:[{key:"name",label:"规则名称",required:true},{key:"deptId",label:"部门ID",type:"number"},{key:"workStartTime",label:"上班时间",required:true},{key:"workEndTime",label:"下班时间",required:true},{key:"lateAllowMinutes",label:"迟到宽限(分钟)",type:"number"},{key:"earlyAllowMinutes",label:"早退宽限(分钟)",type:"number"},{key:"overtimeThreshold",label:"加班起算(分钟)",type:"number"},{key:"effectiveStartDate",label:"生效开始日期"},{key:"effectiveEndDate",label:"生效结束日期"},{key:"locationName",label:"打卡地点"},{key:"locationAddress",label:"地点地址"},{key:"longitude",label:"经度",type:"number"},{key:"latitude",label:"纬度",type:"number"},{key:"radiusMeters",label:"有效范围(米)",type:"number"},{key:"locationRequired",label:"必须在范围内",type:"switch"},{key:"remark",label:"备注"}], cardFields:[{key:"name",label:"名称"},{key:"deptName",label:"部门"},{key:"workStartTime",label:"上班"},{key:"workEndTime",label:"下班"},{key:"effectiveStartDate",label:"生效开始"},{key:"effectiveEndDate",label:"生效结束"},{key:"locationName",label:"打卡地点"},{key:"radiusMeters",label:"范围(米)"}], page:true, load:(t,k,p,s)=>getAttendanceRulePage(t,{pageNo:p,pageSize:s,name:k||undefined}), create:createAttendanceRule, update:updateAttendanceRule, remove:deleteAttendanceRule };
</script><template><HrmCrudPage :config="config" /></template>
src/pages/hrm/salary-calculation/index.vue
@@ -1,11 +1,11 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { onPullDownRefresh, onReachBottom, onShow } from "@dcloudio/uni-app";
import { calculateSalary, confirmSalary, deleteSalaryCalculation, getSalaryCalculationPage, revokeSalary, type SalaryCalculation } from "../../../api/hrm";
import { calculateSalary, confirmSalary, deleteSalaryCalculation, getSalaryCalculationPage, revokeSalary, updateSalaryCalculation, type SalaryCalculation } from "../../../api/hrm";
import { useAuth } from "../../../stores/auth";
import { confirm, money, toast } from "../shared";
const { hasPermission, state } = useAuth();
const rows = ref<SalaryCalculation[]>([]), period = ref(new Date().toISOString().slice(0, 7)), loading = ref(false), pageNo = ref(1), total = ref(0);
const rows = ref<SalaryCalculation[]>([]), period = ref(new Date().toISOString().slice(0, 7)), loading = ref(false), pageNo = ref(1), total = ref(0), editing = ref<SalaryCalculation>();
const pageSize = 15;
const canQuery = computed(() => hasPermission("hrm:salary-calculation:query"));
const hasMore = computed(() => rows.value.length < total.value);
@@ -26,6 +26,10 @@
  try { await calculateSalary(state.accessToken, { period: period.value }); toast("核算完成"); await load(true); }
  catch (error) { toast(error instanceof Error ? error.message : "核算失败"); }
}
const editFields: Array<[keyof SalaryCalculation, string]> = [["baseSalary","基本工资"],["postSalary","岗位工资"],["senioritySalary","工龄工资"],["performanceSalary","绩效工资"],["fullAttendanceBonus","全勤奖"],["overtimePay","加班工资"],["mealAllowance","餐补"],["transportAllowance","交通补贴"],["otherAllowance","其他补贴"],["socialSecurityDeduction","社保扣款"],["housingFundDeduction","公积金扣款"],["taxDeduction","个税扣款"],["otherDeduction","其他扣款"],["leaveDeduction","请假扣款"]];
function openEdit(row: SalaryCalculation) { editing.value = { ...row }; }
function recalculate() { if (!editing.value) return; const income = [editing.value.baseSalary,editing.value.postSalary,editing.value.senioritySalary,editing.value.performanceSalary,editing.value.fullAttendanceBonus,editing.value.overtimePay,editing.value.mealAllowance,editing.value.transportAllowance,editing.value.otherAllowance].reduce((sum,value)=>sum+Number(value||0),0); const deduction = [editing.value.socialSecurityDeduction,editing.value.housingFundDeduction,editing.value.taxDeduction,editing.value.otherDeduction,editing.value.leaveDeduction].reduce((sum,value)=>sum+Number(value||0),0); editing.value.actualSalary = Number((income-deduction).toFixed(2)); }
async function saveEdit() { if (!state.accessToken || !editing.value) return; recalculate(); try { await updateSalaryCalculation(state.accessToken, editing.value); toast("保存成功"); editing.value = undefined; await load(true); } catch (error) { toast(error instanceof Error ? error.message : "保存失败"); } }
async function action(row: SalaryCalculation, type: "confirm" | "revoke" | "delete") {
  const label = type === "confirm" ? "确认" : type === "revoke" ? "撤销确认" : "删除";
  if (!state.accessToken || !row.id || !await confirm("确认" + label + "该薪酬记录?")) return;
@@ -39,4 +43,4 @@
onShow(() => load(true));
onPullDownRefresh(() => load(true));
onReachBottom(() => { if (hasMore.value) load(false); });
</script><template><view class="page"><template v-if="canQuery"><view class="search"><input v-model="period" placeholder="薪酬周期 YYYY-MM" @confirm="load"/><text @click="load">查询</text></view><view v-if="hasPermission('hrm:salary-calculation:calculate')" class="top-actions"><button @click="calc">执行薪酬核算</button></view><view v-for="row in rows" :key="row.id" class="card"><view class="head"><view class="head-main"><text class="name">{{row.userName||'--'}}</text><text class="sub">{{row.no||'--'}} · {{row.deptName||'--'}}</text></view><text :class="['tag',row.status===1?'tag-green':'tag-blue']">{{row.statusName|| (row.status===1?'已确认':'待确认')}}</text></view><view class="grid"><text>基本工资:{{money(row.baseSalary)}}</text><text>绩效工资:{{money(row.performanceSalary)}}</text><text>加班工资:{{money(row.overtimePay)}}</text><text>社保扣款:{{money(row.socialSecurityDeduction)}}</text><text>公积金扣款:{{money(row.housingFundDeduction)}}</text><text>个税:{{money(row.taxDeduction)}}</text><text>实发工资:{{money(row.actualSalary)}}</text><text>出勤:{{row.workDays??0}} 天</text></view><view class="actions"><button v-if="row.status===0&&hasPermission('hrm:salary-calculation:confirm')" @click="action(row,'confirm')">确认</button><button v-if="row.status===1&&hasPermission('hrm:salary-calculation:confirm')" @click="action(row,'revoke')">撤销确认</button><button v-if="row.status===0&&hasPermission('hrm:salary-calculation:delete')" class="danger" @click="action(row,'delete')">删除</button></view></view><view v-if="!rows.length" class="state">{{loading?'加载中...':'暂无薪酬核算记录'}}</view><view v-else class="state more">{{loading ? "加载中..." : hasMore ? "上拉加载更多" : "没有更多了"}}</view></template><view v-else class="state">暂无薪酬核算查看权限</view></view></template><style scoped lang="scss">@import "../list.scss";.top-actions{display:flex;justify-content:flex-end;margin-bottom:18rpx}.top-actions button{margin:0;color:var(--color-primary, #1769d8);font-size:24rpx;background:#fff}</style>
</script><template><view class="page"><template v-if="canQuery"><view class="search"><input v-model="period" placeholder="薪酬周期 YYYY-MM" @confirm="load"/><text @click="load">查询</text></view><view v-if="hasPermission('hrm:salary-calculation:calculate')" class="top-actions"><button @click="calc">执行薪酬核算</button></view><view v-for="row in rows" :key="row.id" class="card"><view class="head"><view class="head-main"><text class="name">{{row.userName||'--'}}</text><text class="sub">{{row.no||'--'}} · {{row.deptName||'--'}}</text></view><text :class="['tag',row.status===1?'tag-green':'tag-blue']">{{row.statusName|| (row.status===1?'已确认':'待确认')}}</text></view><view class="grid"><text>基本工资:{{money(row.baseSalary)}}</text><text>岗位工资:{{money(row.postSalary)}}</text><text>工龄工资:{{money(row.senioritySalary)}}</text><text>绩效工资:{{money(row.performanceSalary)}}</text><text>全勤奖:{{money(row.fullAttendanceBonus)}}</text><text>加班工资:{{money(row.overtimePay)}}</text><text>社保扣款:{{money(row.socialSecurityDeduction)}}</text><text>公积金扣款:{{money(row.housingFundDeduction)}}</text><text>个税:{{money(row.taxDeduction)}}</text><text>实发工资:{{money(row.actualSalary)}}</text><text>应出:{{row.workDays??0}} 天</text><text>实出:{{row.actualDays??0}} 天</text></view><view class="actions"><button v-if="row.status===0&&hasPermission('hrm:salary-calculation:confirm')" @click="action(row,'confirm')">确认</button><button v-if="row.status===1&&hasPermission('hrm:salary-calculation:confirm')" @click="action(row,'revoke')">撤销确认</button><button v-if="row.status===0&&hasPermission('hrm:salary-calculation:delete')" class="danger" @click="action(row,'delete')">删除</button></view></view><view v-if="!rows.length" class="state">{{loading?'加载中...':'暂无薪酬核算记录'}}</view><view v-else class="state more">{{loading ? "加载中..." : hasMore ? "上拉加载更多" : "没有更多了"}}</view></template><view v-else class="state">暂无薪酬核算查看权限</view></view><view v-if="editing" class="panel"><view class="form-card"><view class="section-title">修改薪酬核算</view><view v-for="field in editFields" :key="field[0]" class="field"><text>{{field[1]}}</text><input :value="editing[field[0]] ?? 0" type="digit" @input="editing[field[0]]=Number($event.detail.value);recalculate()" /></view><view class="field"><text>应出天数</text><input v-model.number="editing.workDays" type="number" /></view><view class="field"><text>实出天数</text><input v-model.number="editing.actualDays" type="number" /></view><view class="field"><text>加班时长</text><input v-model.number="editing.overtimeHours" type="digit" /></view><view class="field"><text>实发工资</text><text>{{money(editing.actualSalary)}}</text></view><view class="toolbar"><button @click="editing=undefined">取消</button><button @click="saveEdit">保存</button></view></view></view></template><style scoped lang="scss">@import "../list.scss";.top-actions{display:flex;justify-content:flex-end;margin-bottom:18rpx}.top-actions button{margin:0;color:var(--color-primary, #1769d8);font-size:24rpx;background:#fff}.panel{position:fixed;z-index:20;inset:0;padding:10vh 30rpx;background:rgba(20,30,45,.48)}.panel .form-card{max-height:78vh;overflow:auto}.field{display:flex;align-items:center;min-height:88rpx;padding:0 24rpx;border-bottom:1rpx solid #eef1f5}.field text:first-child{width:220rpx}.field input{flex:1;text-align:right}.toolbar{display:flex;gap:16rpx;padding:20rpx}.toolbar button{flex:1}</style>
src/pages/hrm/social-security-scheme/index.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import HrmCrudPage, { type CrudConfig } from "../components/HrmCrudPage.vue";
import { createSocialSecurityScheme, deleteSocialSecurityScheme, getSocialSecuritySchemeList, updateSocialSecurityScheme } from "../../../api/hrm";
const ratios=["pensionCompanyRatio","pensionPersonalRatio","medicalCompanyRatio","medicalPersonalRatio","unemploymentCompanyRatio","unemploymentPersonalRatio","workInjuryRatio","maternityRatio","housingFundCompanyRatio","housingFundPersonalRatio"];
const labels=["养老单位比例","养老个人比例","医疗单位比例","医疗个人比例","失业单位比例","失业个人比例","工伤比例","生育比例","公积金单位比例","公积金个人比例"];
const ratios=["pensionCompanyRatio","pensionPersonalRatio","medicalCompanyRatio","medicalPersonalRatio","unemploymentCompanyRatio","unemploymentPersonalRatio","workInjuryRatio","maternityRatio","seriousIllnessCompanyRatio","seriousIllnessPersonalRatio","longTermCareCompanyRatio","longTermCarePersonalRatio","housingFundCompanyRatio","housingFundPersonalRatio"];
const labels=["养老单位比例","养老个人比例","医疗单位比例","医疗个人比例","失业单位比例","失业个人比例","工伤比例","生育比例","大病医疗单位比例","大病医疗个人比例","长期护理单位比例","长期护理个人比例","公积金单位比例","公积金个人比例"];
const config: CrudConfig = { title:"社保公积金方案", permission:"hrm:social-security-scheme", fields:[{key:"name",label:"方案名称",required:true},{key:"effectiveDate",label:"生效日期",required:true},...ratios.map((key,i)=>({key,label:labels[i],type:"number" as const,required:true})),{key:"remark",label:"备注"}], cardFields:[{key:"name",label:"名称"},{key:"effectiveDate",label:"生效日期"},{key:"pensionCompanyRatio",label:"养老单位比例"},{key:"housingFundCompanyRatio",label:"公积金单位比例"}], load:(t,k)=>getSocialSecuritySchemeList(t,{name:k||undefined}), create:createSocialSecurityScheme, update:updateSocialSecurityScheme, remove:deleteSocialSecurityScheme };
</script><template><HrmCrudPage :config="config" /></template>
src/pages/hrm/transfer/form.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { createTransfer, getHrmDeptList, getHrmDicts, getHrmPostList, getHrmUserList, getTransfer, updateTransfer, type HrmAttachment, type SimpleOption, type Transfer, type UserOption } from "../../../api/hrm";
import { createTransfer, getHrmDeptList, getHrmDicts, getHrmPostList, getEmployeeSimpleList, getTransfer, updateTransfer, type EmployeeSimple, type HrmAttachment, type SimpleOption, type Transfer } from "../../../api/hrm";
import type { DeptSimple, DictData } from "../../../api/crm";
import { useAuth } from "../../../stores/auth";
import { HRM_DICT, label, options } from "../shared";
@@ -9,25 +9,27 @@
const { hasPermission, state } = useAuth();
const id = ref<number>(); const mode = ref("edit"); const saving = ref(false);
const dicts = ref<DictData[]>([]); const users = ref<UserOption[]>([]); const depts = ref<DeptSimple[]>([]); const posts = ref<SimpleOption[]>([]);
const dicts = ref<DictData[]>([]); const employees = ref<EmployeeSimple[]>([]); const depts = ref<DeptSimple[]>([]); const posts = ref<SimpleOption[]>([]);
const attachments = ref<HrmAttachment[]>([]);
const form = reactive<Transfer>({ userId: undefined, currentDeptId: undefined, currentPostId: undefined, targetDeptId: undefined, targetPostId: undefined, transferType: undefined, effectiveDate: "", reason: "", blobIds: [] });
const readonly = computed(() => mode.value === "detail");
const canSave = computed(() => !readonly.value && (id.value ? hasPermission("hrm:transfer:update") : hasPermission("hrm:transfer:create")));
const typeOptions = computed(() => options(dicts.value, HRM_DICT.transferType));
const userText = computed(() => form.userName || state.user?.nickname || "当前登录用户");
const userText = computed(() => form.userName || employees.value.find(item => item.id === form.userId)?.name || "请选择调岗人");
const currentDeptText = computed(() => depts.value.find(item => item.id === form.currentDeptId)?.name || form.currentDeptName || "请选择原部门");
const currentPostText = computed(() => posts.value.find(item => item.id === form.currentPostId)?.name || form.currentPostName || "请选择原岗位");
const targetDeptText = computed(() => depts.value.find(item => item.id === form.targetDeptId)?.name || form.targetDeptName || "请选择目标部门");
const targetPostText = computed(() => posts.value.find(item => item.id === form.targetPostId)?.name || form.targetPostName || "请选择目标岗位");
function selectUser(employeeId: number) { const employee = employees.value.find(item => item.id === employeeId); form.userId = employee?.id; form.userName = employee?.name; form.currentDeptId = employee?.deptId; form.currentDeptName = employee?.deptName; form.currentPostId = employee?.postId || employee?.postIds?.[0]; form.currentPostName = employee?.postName; }
async function save() {
  if (!state.user?.id || !form.currentDeptId || !form.currentPostId || !form.targetDeptId || !form.targetPostId || form.transferType === undefined || !form.effectiveDate || !form.reason?.trim()) return uni.showToast({ title: "请完整填写必填项", icon: "none" });
  if (!form.userId || !form.currentDeptId || !form.currentPostId || !form.targetDeptId || !form.targetPostId || form.transferType === undefined || !form.effectiveDate || !form.reason?.trim()) return uni.showToast({ title: "请完整填写必填项", icon: "none" });
  if (form.currentDeptId === form.targetDeptId) return uni.showToast({ title: "目标部门不能与原部门相同", icon: "none" });
  if (form.currentPostId === form.targetPostId) return uni.showToast({ title: "目标岗位不能与原岗位相同", icon: "none" });
  if (!state.accessToken || saving.value) return; saving.value = true;
  try {
    const data = { ...form, id: id.value, userId: state.user.id };
    const data = { ...form, id: id.value };
    if (id.value) await updateTransfer(state.accessToken, data); else id.value = await createTransfer(state.accessToken, data);
    uni.showToast({ title: "保存成功", icon: "success" }); setTimeout(() => uni.navigateBack(), 500);
  } catch (error) { uni.showToast({ title: error instanceof Error ? error.message : "保存失败", icon: "none" }); }
@@ -37,8 +39,8 @@
  id.value = query.id ? Number(query.id) : undefined; mode.value = query.mode === "detail" ? "detail" : "edit";
  if (!state.accessToken) return;
  try {
    const [dictData, userData, deptData, postData] = await Promise.all([getHrmDicts(state.accessToken), getHrmUserList(state.accessToken), getHrmDeptList(state.accessToken), getHrmPostList(state.accessToken)]);
    dicts.value = dictData; users.value = userData; depts.value = deptData; posts.value = postData;
    const [dictData, userData, deptData, postData] = await Promise.all([getHrmDicts(state.accessToken), getEmployeeSimpleList(state.accessToken), getHrmDeptList(state.accessToken), getHrmPostList(state.accessToken)]);
    dicts.value = dictData; employees.value = userData; depts.value = deptData; posts.value = postData;
    if (id.value) {
      const detail = await getTransfer(state.accessToken, id.value);
      if (Number(detail.userId) !== Number(state.user?.id)) {
@@ -49,9 +51,7 @@
      attachments.value = detail.attachmentList || [];
    }
    else {
      const current = userData.find(item => item.id === state.user?.id);
      form.userId = state.user?.id; form.userName = state.user?.nickname;
      form.currentDeptId = current?.deptId; form.currentDeptName = current?.deptName || state.user?.dept?.name;
      Object.assign(form, { userId: undefined, userName: undefined, currentDeptId: undefined, currentDeptName: undefined, currentPostId: undefined, currentPostName: undefined });
    }
  } catch (error) { uni.showToast({ title: error instanceof Error ? error.message : "数据加载失败", icon: "none" }); }
});
@@ -59,9 +59,9 @@
<template>
  <view class="page"><view class="form-card">
    <view class="field"><text>申请人</text><text class="value">{{ userText }}</text></view>
    <view class="field"><text>原部门 <text class="required">*</text></text><picker v-if="!readonly" :range="depts" range-key="name" @change="form.currentDeptId=depts[$event.detail.value].id"><view class="picker"><text>{{ currentDeptText }}</text><text>›</text></view></picker><text v-else class="value">{{ currentDeptText }}</text></view>
    <view class="field"><text>原岗位 <text class="required">*</text></text><picker v-if="!readonly" :range="posts" range-key="name" @change="form.currentPostId=posts[$event.detail.value].id"><view class="picker"><text>{{ currentPostText }}</text><text>›</text></view></picker><text v-else class="value">{{ currentPostText }}</text></view>
    <view class="field"><text>调岗人 <text class="required">*</text></text><picker v-if="!readonly" :range="employees" range-key="name" @change="selectUser(employees[$event.detail.value].id)"><view class="picker"><text>{{ userText }}</text><text>›</text></view></picker><text v-else class="value">{{ userText }}</text></view>
    <view class="field"><text>原部门 <text class="required">*</text></text><text class="value">{{ currentDeptText }}</text></view>
    <view class="field"><text>原岗位 <text class="required">*</text></text><text class="value">{{ currentPostText }}</text></view>
    <view class="field"><text>目标部门 <text class="required">*</text></text><picker v-if="!readonly" :range="depts" range-key="name" @change="form.targetDeptId=depts[$event.detail.value].id"><view class="picker"><text>{{ targetDeptText }}</text><text>›</text></view></picker><text v-else class="value">{{ targetDeptText }}</text></view>
    <view class="field"><text>目标岗位 <text class="required">*</text></text><picker v-if="!readonly" :range="posts" range-key="name" @change="form.targetPostId=posts[$event.detail.value].id"><view class="picker"><text>{{ targetPostText }}</text><text>›</text></view></picker><text v-else class="value">{{ targetPostText }}</text></view>
    <view class="field"><text>调岗类型 <text class="required">*</text></text><picker v-if="!readonly" :range="typeOptions" range-key="label" @change="form.transferType=Number(typeOptions[$event.detail.value].value)"><view class="picker"><text>{{ label(dicts,HRM_DICT.transferType,form.transferType,"请选择调岗类型") }}</text><text>›</text></view></picker><text v-else class="value">{{ label(dicts,HRM_DICT.transferType,form.transferType) }}</text></view>