Merge branch '0.8.X' of https://gitee.com/hdka/klp-oa into 0.8.X
This commit is contained in:
@@ -25,6 +25,10 @@ public class HrmCertificate extends BaseEntity implements Serializable {
|
||||
private Date validFrom;
|
||||
private Date validTo;
|
||||
private String remark;
|
||||
/**
|
||||
* 证书附件 fileIds(逗号分隔的 OSS ID)
|
||||
*/
|
||||
private String fileIds;
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ public class HrmContract extends BaseEntity implements Serializable {
|
||||
private Date endDate;
|
||||
private String status;
|
||||
private String remark;
|
||||
/**
|
||||
* 合同附件 fileIds(逗号分隔的 OSS ID)
|
||||
*/
|
||||
private String fileIds;
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.klp.hrm.domain.bo;
|
||||
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@@ -22,7 +24,15 @@ public class HrmCertificateBo extends BaseEntity {
|
||||
|
||||
private String certNo;
|
||||
private String issuedBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date validFrom;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date validTo;
|
||||
private String remark;
|
||||
/**
|
||||
* 证书附件 fileIds(逗号分隔的 OSS ID)
|
||||
*/
|
||||
private String fileIds;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import lombok.EqualsAndHashCode;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@@ -21,8 +23,16 @@ public class HrmContractBo extends BaseEntity {
|
||||
private String contractNo;
|
||||
|
||||
private String contractType;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date startDate;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endDate;
|
||||
private String status;
|
||||
private String remark;
|
||||
/**
|
||||
* 合同附件 fileIds(逗号分隔的 OSS ID)
|
||||
*/
|
||||
private String fileIds;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
@Data
|
||||
public class HrmCertificateVo implements Serializable {
|
||||
@@ -21,11 +22,17 @@ public class HrmCertificateVo implements Serializable {
|
||||
@Excel(name = "颁发机构")
|
||||
private String issuedBy;
|
||||
@Excel(name = "有效期开始", width = 20, dateFormat = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date validFrom;
|
||||
@Excel(name = "有效期结束", width = 20, dateFormat = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date validTo;
|
||||
@Excel(name = "备注")
|
||||
private String remark;
|
||||
/**
|
||||
* 证书附件 fileIds(逗号分隔的 OSS ID)
|
||||
*/
|
||||
private String fileIds;
|
||||
private String createBy;
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
@Data
|
||||
public class HrmContractVo implements Serializable {
|
||||
@@ -19,13 +20,19 @@ public class HrmContractVo implements Serializable {
|
||||
@Excel(name = "合同类型")
|
||||
private String contractType;
|
||||
@Excel(name = "开始日期", width = 20, dateFormat = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date startDate;
|
||||
@Excel(name = "结束日期", width = 20, dateFormat = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date endDate;
|
||||
@Excel(name = "状态")
|
||||
private String status;
|
||||
@Excel(name = "备注")
|
||||
private String remark;
|
||||
/**
|
||||
* 合同附件 fileIds(逗号分隔的 OSS ID)
|
||||
*/
|
||||
private String fileIds;
|
||||
private String createBy;
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
|
||||
@@ -514,6 +514,74 @@ export function getFlowForm(formId) {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
// 流程模板
|
||||
export function listFlowTemplate(query) {
|
||||
return request({
|
||||
url: '/hrm/flow/template/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
export function getFlowTemplate(tplId) {
|
||||
return request({
|
||||
url: `/hrm/flow/template/${tplId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function addFlowTemplate(data) {
|
||||
return request({
|
||||
url: '/hrm/flow/template',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
export function updateFlowTemplate(data) {
|
||||
return request({
|
||||
url: '/hrm/flow/template',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
export function delFlowTemplate(tplIds) {
|
||||
return request({
|
||||
url: `/hrm/flow/template/${tplIds}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
// 流程节点
|
||||
export function listFlowNode(query) {
|
||||
return request({
|
||||
url: '/hrm/flow/node/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
export function getFlowNode(nodeId) {
|
||||
return request({
|
||||
url: `/hrm/flow/node/${nodeId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function addFlowNode(data) {
|
||||
return request({
|
||||
url: '/hrm/flow/node',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
export function updateFlowNode(data) {
|
||||
return request({
|
||||
url: '/hrm/flow/node',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
export function delFlowNode(nodeIds) {
|
||||
return request({
|
||||
url: `/hrm/flow/node/${nodeIds}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 薪酬
|
||||
export function listPayPlan(query) {
|
||||
@@ -619,7 +687,7 @@ export function delPayslip(slipIds) {
|
||||
// 指标快照
|
||||
export function listStatSnapshot(query) {
|
||||
return request({
|
||||
url: '/hrm/stat/snapshot/list',
|
||||
url: '/hrm/pay/stat/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
|
||||
@@ -48,6 +48,11 @@ export default {
|
||||
props: {
|
||||
// 值
|
||||
value: [String, Object, Array],
|
||||
// 额外的自定义校验函数,返回 false 阻止上传
|
||||
beforeValidate: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
// 数量限制
|
||||
limit: {
|
||||
type: Number,
|
||||
@@ -127,6 +132,15 @@ export default {
|
||||
methods: {
|
||||
// 上传前校检格式和大小
|
||||
handleBeforeUpload(file) {
|
||||
// 额外自定义校验
|
||||
if (this.beforeValidate) {
|
||||
const pass = this.beforeValidate(file);
|
||||
if (pass === false) {
|
||||
// 防止残留文件
|
||||
this.resetUpload();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 校检文件类型
|
||||
if (this.fileType) {
|
||||
const fileName = file.name.split('.');
|
||||
@@ -189,6 +203,17 @@ export default {
|
||||
this.$modal.closeLoading();
|
||||
}
|
||||
},
|
||||
// 重置上传状态(外部可调用)
|
||||
resetUpload() {
|
||||
this.number = 0;
|
||||
this.uploadList = [];
|
||||
this.fileList = [];
|
||||
this.$emit("input", "");
|
||||
if (this.$refs.fileUpload) {
|
||||
this.$refs.fileUpload.clearFiles();
|
||||
}
|
||||
this.$modal.closeLoading();
|
||||
},
|
||||
// 获取文件名称
|
||||
getFileName(name) {
|
||||
// 如果是url那么取最后的名字 如果不是直接返回
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<section class="panel-grid triple">
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>班次</span>
|
||||
<div class="hrm-page attendance-page">
|
||||
<section class="panel-grid">
|
||||
<el-card class="metal-panel flat" shadow="never">
|
||||
<div class="card-toolbar">
|
||||
<div class="actions-inline">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openShiftDialog()">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadShift">刷新</el-button>
|
||||
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="openShiftDialog()">新增</el-button>
|
||||
<el-button size="mini" plain icon="el-icon-refresh" @click="loadShift">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="shiftList" v-loading="shiftLoading" height="320" stripe>
|
||||
@@ -31,9 +30,8 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>排班</span>
|
||||
<el-card class="metal-panel flat" shadow="never">
|
||||
<div class="card-toolbar">
|
||||
<div class="actions-inline">
|
||||
<el-date-picker
|
||||
v-model="scheduleQuery.date"
|
||||
@@ -43,8 +41,8 @@
|
||||
style="width: 140px"
|
||||
@change="loadSchedule"
|
||||
/>
|
||||
<el-button size="mini" type="primary" @click="loadSchedule">查询</el-button>
|
||||
<el-button size="mini" icon="el-icon-plus" @click="openScheduleDialog()">新增</el-button>
|
||||
<el-button size="mini" type="primary" plain @click="loadSchedule">查询</el-button>
|
||||
<el-button size="mini" plain icon="el-icon-plus" @click="openScheduleDialog()">新增</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="scheduleList" v-loading="scheduleLoading" height="320" stripe>
|
||||
@@ -69,9 +67,8 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>打卡与考勤结果</span>
|
||||
<el-card class="metal-panel flat wide" shadow="never">
|
||||
<div class="card-toolbar">
|
||||
<div class="actions-inline">
|
||||
<el-date-picker
|
||||
v-model="punchQuery.range"
|
||||
@@ -448,45 +445,66 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hrm-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
.attendance-page {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 14px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
.metal-panel {
|
||||
border: 1px solid #d7d9df;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.panel-header {
|
||||
.metal-panel :deep(.el-card__body) {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.card-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
justify-content: flex-end;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
.actions-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.flat :deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
.flat {
|
||||
padding: 0;
|
||||
}
|
||||
.wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
.flat :deep(.el-table),
|
||||
.flat :deep(.el-table__body-wrapper),
|
||||
.flat :deep(.el-table__header-wrapper),
|
||||
.flat :deep(.el-table__empty-block) {
|
||||
background: transparent;
|
||||
}
|
||||
.dual-tables {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.table-half {
|
||||
border: 1px dashed #e6e8ed;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.table-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
color: #1f2937;
|
||||
}
|
||||
.exception-tag {
|
||||
color: #409eff;
|
||||
@@ -499,6 +517,9 @@ export default {
|
||||
.panel-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.wide {
|
||||
grid-column: span 1;
|
||||
}
|
||||
.dual-tables {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
179
klp-ui/src/views/hrm/flow/node.vue
Normal file
179
klp-ui/src/views/hrm/flow/node.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>流程节点配置</span>
|
||||
<div class="actions-inline">
|
||||
<el-select v-model="query.tplId" size="mini" placeholder="选择模板" filterable clearable style="width: 200px" @change="loadList">
|
||||
<el-option v-for="tpl in templateOptions" :key="tpl.tplId" :label="`${tpl.tplName || tpl.tplCode} (${tpl.bizType || ''})`" :value="tpl.tplId" />
|
||||
</el-select>
|
||||
<el-input v-model="query.nodeType" size="mini" placeholder="节点类型" clearable style="width: 140px" @keyup.enter.native="loadList" />
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog()">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadList">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" height="520" stripe>
|
||||
<el-table-column label="序号" prop="orderNo" width="80" />
|
||||
<el-table-column label="节点类型" prop="nodeType" min-width="140" />
|
||||
<el-table-column label="审批规则" prop="approverRule" min-width="160" />
|
||||
<el-table-column label="规则值" prop="approverValue" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog :visible.sync="dialogVisible" title="流程节点" width="520px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" size="small">
|
||||
<el-form-item label="模板" prop="tplId">
|
||||
<el-select v-model="form.tplId" placeholder="选择模板" filterable style="width: 100%">
|
||||
<el-option v-for="tpl in templateOptions" :key="tpl.tplId" :label="`${tpl.tplName || tpl.tplCode} (${tpl.bizType || ''})`" :value="tpl.tplId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="顺序" prop="orderNo">
|
||||
<el-input-number v-model="form.orderNo" :min="1" :step="1" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="节点类型" prop="nodeType">
|
||||
<el-input v-model="form.nodeType" placeholder="如 approve/cc/end" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批规则" prop="approverRule">
|
||||
<el-select v-model="form.approverRule" placeholder="选择规则" style="width: 100%">
|
||||
<el-option v-for="opt in approverRuleOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="规则值" prop="approverValue">
|
||||
<el-input v-model="form.approverValue" placeholder="根据规则填写:用户ID/角色编码/岗位ID/字段名等" />
|
||||
<div class="muted">fixed_user: 用户ID逗号分隔;role: 角色编码;position: 岗位ID;leader/initiator 无需填;form_field: 表单字段名</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listFlowNode, addFlowNode, updateFlowNode, delFlowNode, listFlowTemplate } from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmFlowNode',
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
query: { tplId: undefined, nodeType: '' },
|
||||
templateOptions: [],
|
||||
dialogVisible: false,
|
||||
submitting: false,
|
||||
form: {},
|
||||
approverRuleOptions: [
|
||||
{ label: '固定用户', value: 'fixed_user' },
|
||||
{ label: '角色', value: 'role' },
|
||||
{ label: '岗位', value: 'position' },
|
||||
{ label: '直属上级', value: 'leader' },
|
||||
{ label: '发起人', value: 'initiator' },
|
||||
{ label: '表单字段指定', value: 'form_field' }
|
||||
],
|
||||
rules: {
|
||||
tplId: [{ required: true, message: '请选择模板', trigger: 'change' }],
|
||||
orderNo: [{ required: true, message: '请输入顺序', trigger: 'blur' }],
|
||||
nodeType: [{ required: true, message: '请输入节点类型', trigger: 'blur' }],
|
||||
approverRule: [{ required: true, message: '请选择审批规则', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadTemplates()
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
loadTemplates() {
|
||||
listFlowTemplate({ pageNum: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
this.templateOptions = res.rows || []
|
||||
})
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
listFlowNode({ pageNum: 1, pageSize: 500, ...this.query })
|
||||
.then(res => {
|
||||
this.list = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openDialog(row) {
|
||||
this.form = row
|
||||
? { ...row }
|
||||
: {
|
||||
tplId: this.query.tplId || (this.templateOptions[0] && this.templateOptions[0].tplId),
|
||||
orderNo: (this.list[this.list.length - 1]?.orderNo || 0) + 1,
|
||||
nodeType: '',
|
||||
approverRule: '',
|
||||
approverValue: '',
|
||||
remark: ''
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
|
||||
},
|
||||
submit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.nodeId ? updateFlowNode : addFlowNode
|
||||
api(this.form)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.dialogVisible = false
|
||||
this.loadList()
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delRow(row) {
|
||||
this.$confirm('确认删除该节点吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delFlowNode(row.nodeId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadList()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hrm-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.actions-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.muted {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
161
klp-ui/src/views/hrm/flow/template.vue
Normal file
161
klp-ui/src/views/hrm/flow/template.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>流程模板</span>
|
||||
<div class="actions-inline">
|
||||
<el-input v-model="query.tplName" size="mini" placeholder="模板名称" clearable style="width: 160px" @keyup.enter.native="loadList" />
|
||||
<el-select v-model="query.bizType" size="mini" placeholder="业务类型" clearable style="width: 140px" @change="loadList">
|
||||
<el-option label="请假" value="leave" />
|
||||
<el-option label="加班" value="overtime" />
|
||||
<el-option label="出差" value="travel" />
|
||||
<el-option label="用印" value="seal" />
|
||||
<el-option label="薪酬" value="payroll" />
|
||||
</el-select>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog()">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadList">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" height="520" stripe>
|
||||
<el-table-column label="名称" prop="tplName" min-width="160" />
|
||||
<el-table-column label="编码" prop="tplCode" min-width="140" />
|
||||
<el-table-column label="业务类型" prop="bizType" min-width="120" />
|
||||
<el-table-column label="版本" prop="version" width="90" />
|
||||
<el-table-column label="启用" prop="enabled" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.enabled ? 'success' : 'info'" size="mini">{{ scope.row.enabled ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog :visible.sync="dialogVisible" title="流程模板" width="480px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" size="small">
|
||||
<el-form-item label="模板名称" prop="tplName">
|
||||
<el-input v-model="form.tplName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模板编码" prop="tplCode">
|
||||
<el-input v-model="form.tplCode" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型" prop="bizType">
|
||||
<el-select v-model="form.bizType" placeholder="选择业务" style="width: 100%">
|
||||
<el-option label="请假" value="leave" />
|
||||
<el-option label="加班" value="overtime" />
|
||||
<el-option label="出差" value="travel" />
|
||||
<el-option label="用印" value="seal" />
|
||||
<el-option label="薪酬" value="payroll" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="版本" prop="version">
|
||||
<el-input-number v-model="form.version" :min="1" :step="1" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用" prop="enabled">
|
||||
<el-switch v-model="form.enabled" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listFlowTemplate, addFlowTemplate, updateFlowTemplate, delFlowTemplate } from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmFlowTemplate',
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
query: { tplName: '', bizType: undefined },
|
||||
dialogVisible: false,
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: {
|
||||
tplName: [{ required: true, message: '请输入模板名称', trigger: 'blur' }],
|
||||
tplCode: [{ required: true, message: '请输入模板编码', trigger: 'blur' }],
|
||||
bizType: [{ required: true, message: '请选择业务类型', trigger: 'change' }],
|
||||
version: [{ required: true, message: '请输入版本号', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
this.loading = true
|
||||
listFlowTemplate({ pageNum: 1, pageSize: 200, ...this.query })
|
||||
.then(res => {
|
||||
this.list = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openDialog(row) {
|
||||
this.form = row
|
||||
? { ...row }
|
||||
: { tplName: '', tplCode: '', bizType: '', version: 1, enabled: 1, remark: '' }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
|
||||
},
|
||||
submit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.tplId ? updateFlowTemplate : addFlowTemplate
|
||||
api(this.form)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.dialogVisible = false
|
||||
this.loadList()
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delRow(row) {
|
||||
this.$confirm('确认删除该模板吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delFlowTemplate(row.tplId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadList()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hrm-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.actions-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,10 @@
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>组织树</span>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadOrg">刷新</el-button>
|
||||
<div class="actions-inline">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openOrgDialog()">新增组织</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadOrg">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-tree
|
||||
v-loading="orgLoading"
|
||||
@@ -46,6 +49,7 @@
|
||||
<el-option label="离职" value="inactive" />
|
||||
</el-select>
|
||||
<el-button size="mini" type="primary" icon="el-icon-search" @click="loadEmployee">查询</el-button>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openEmpDialog">新增员工</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="employeeList" v-loading="empLoading" height="700" stripe @row-click="openDetail">
|
||||
@@ -184,17 +188,154 @@
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog
|
||||
title="合同"
|
||||
:visible.sync="contractDialogVisible"
|
||||
width="480px"
|
||||
title="新增员工"
|
||||
:visible.sync="empDialogVisible"
|
||||
width="520px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form ref="empFormRef" :model="empForm" :rules="empRules" label-width="100px" size="small">
|
||||
<el-form-item label="工号" prop="empNo">
|
||||
<el-input v-model="empForm.empNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="empName">
|
||||
<el-input v-model="empForm.empName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别">
|
||||
<el-select v-model="empForm.gender" clearable placeholder="选择性别" style="width: 100%">
|
||||
<el-option label="男" value="male" />
|
||||
<el-option label="女" value="female" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机">
|
||||
<el-input v-model="empForm.mobile" />
|
||||
</el-form-item>
|
||||
<el-form-item label="雇佣类型">
|
||||
<el-input v-model="empForm.employmentType" placeholder="如 全职/实习" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="empForm.status" style="width: 100%">
|
||||
<el-option label="在职" value="active" />
|
||||
<el-option label="离职" value="inactive" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="入职日期">
|
||||
<el-date-picker v-model="empForm.hireDate" type="date" placeholder="选择日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织" prop="mainOrgId">
|
||||
<el-select v-model="empForm.mainOrgId" filterable placeholder="选择组织" style="width: 100%">
|
||||
<el-option
|
||||
v-for="org in flatOrgOptions"
|
||||
:key="org.orgId"
|
||||
:label="org.label"
|
||||
:value="org.orgId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="empForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="empDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="empSubmitting" @click="submitEmp">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="新增组织"
|
||||
:visible.sync="orgDialogVisible"
|
||||
width="620px"
|
||||
append-to-body
|
||||
class="org-dialog"
|
||||
>
|
||||
<el-form
|
||||
ref="orgFormRef"
|
||||
:model="orgForm"
|
||||
:rules="orgRules"
|
||||
label-width="110px"
|
||||
size="small"
|
||||
label-position="top"
|
||||
class="org-form"
|
||||
>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="组织名称" prop="orgName">
|
||||
<div class="inline-field">
|
||||
<el-input v-model="orgForm.orgName" placeholder="如:生产一部 / 市场部" />
|
||||
<el-button type="primary" icon="el-icon-magic-stick" plain size="mini" @click="autoFillOrgName">智能生成</el-button>
|
||||
</div>
|
||||
<div class="field-hint">支持快速生成示例名称,可自行修改</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="组织类型" prop="orgType">
|
||||
<el-select v-model="orgForm.orgType" placeholder="选择类型" filterable style="width: 100%">
|
||||
<el-option label="部门" value="department" />
|
||||
<el-option label="事业部" value="division" />
|
||||
<el-option label="公司" value="company" />
|
||||
<el-option label="项目组" value="project" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="上级组织" prop="parentId">
|
||||
<el-cascader
|
||||
v-model="orgForm.parentId"
|
||||
:options="orgTree"
|
||||
:props="{ label: 'orgName', value: 'orgId', children: 'children', emitPath: false, checkStrictly: true }"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="默认取当前选中的组织,可搜索"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div class="field-hint">留空则创建为顶级组织</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="orgForm.remark" type="textarea" :rows="2" placeholder="补充说明(可选)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="orgDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="orgSubmitting" @click="submitOrg">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="劳动合同"
|
||||
:visible.sync="contractDialogVisible"
|
||||
width="520px"
|
||||
append-to-body
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
show-icon
|
||||
class="contract-hint"
|
||||
title="上传后自动用文件名填充合同编号"
|
||||
description="建议文件名格式:合同编号_员工名.pdf"
|
||||
/>
|
||||
<el-form :model="contractForm" label-width="100px" size="small">
|
||||
<el-form-item label="合同编号">
|
||||
<el-input v-model="contractForm.contractNo" />
|
||||
<el-form-item label="合同文件" prop="file">
|
||||
<file-upload
|
||||
ref="contractUploader"
|
||||
v-model="contractForm.fileIds"
|
||||
:limit="1"
|
||||
:file-size="50"
|
||||
:file-type="['pdf','doc','docx','jpg','png']"
|
||||
:before-validate="validateContractBeforeUpload"
|
||||
@success="handleContractUploadSuccess"
|
||||
/>
|
||||
<div class="field-hint">仅限 1 个文件,文件名将自动写入合同编号</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同编号" prop="contractNo">
|
||||
<el-input v-model="contractForm.contractNo" placeholder="上传后自动带出,可手动调整" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-input v-model="contractForm.contractType" />
|
||||
<el-input v-model="contractForm.contractType" placeholder="如:劳动合同/实习协议" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="contractForm.startDate" type="date" placeholder="开始" style="width: 100%" />
|
||||
@@ -203,10 +344,10 @@
|
||||
<el-date-picker v-model="contractForm.endDate" type="date" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-input v-model="contractForm.status" />
|
||||
<el-input v-model="contractForm.status" placeholder="如:生效/签署中" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="contractForm.remark" type="textarea" :rows="2" />
|
||||
<el-input v-model="contractForm.remark" type="textarea" :rows="2" placeholder="补充说明" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -218,27 +359,53 @@
|
||||
<el-dialog
|
||||
title="证书"
|
||||
:visible.sync="certDialogVisible"
|
||||
width="480px"
|
||||
width="720px"
|
||||
append-to-body
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
show-icon
|
||||
class="contract-hint"
|
||||
title="上传证书附件,支持 pdf/doc/jpg/png"
|
||||
description="可登记证书编号、签发机构与有效期起始"
|
||||
/>
|
||||
<el-form :model="certForm" label-width="100px" size="small">
|
||||
<el-form-item label="证书名称">
|
||||
<el-input v-model="certForm.certName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="证书编号">
|
||||
<el-input v-model="certForm.certNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签发机构">
|
||||
<el-input v-model="certForm.issuedBy" />
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期自">
|
||||
<el-date-picker v-model="certForm.validFrom" type="date" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期至">
|
||||
<el-date-picker v-model="certForm.validTo" type="date" placeholder="结束" style="width: 100%" />
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证书名称">
|
||||
<el-input v-model="certForm.certName" placeholder="如:安全生产证书" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证书编号">
|
||||
<el-input v-model="certForm.certNo" placeholder="编号/执照号(可选)" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签发机构">
|
||||
<el-input v-model="certForm.issuedBy" placeholder="颁发单位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="有效期起">
|
||||
<el-date-picker v-model="certForm.validFrom" type="date" placeholder="选择日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="证书附件">
|
||||
<file-upload
|
||||
v-model="certForm.fileIds"
|
||||
:limit="1"
|
||||
:file-size="20"
|
||||
:file-type="['pdf','doc','docx','jpg','png']"
|
||||
:is-show-tip="false"
|
||||
/>
|
||||
<div class="field-hint">仅限 1 个附件,最大 20MB</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="certForm.remark" type="textarea" :rows="2" />
|
||||
<el-input v-model="certForm.remark" type="textarea" :rows="2" placeholder="补充说明(可选)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -287,6 +454,7 @@
|
||||
<el-button type="primary" :loading="relSubmitting" @click="submitRel">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -306,8 +474,12 @@ import {
|
||||
listCertificate,
|
||||
addCertificate,
|
||||
updateCertificate,
|
||||
delCertificate
|
||||
delCertificate,
|
||||
addEmployee,
|
||||
addOrg
|
||||
} from '@/api/hrm'
|
||||
import { delOss } from '@/api/system/oss'
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
|
||||
export default {
|
||||
name: 'HrmOrgEmployee',
|
||||
@@ -319,13 +491,30 @@ export default {
|
||||
employeeList: [],
|
||||
empLoading: false,
|
||||
empQuery: { empName: '', status: undefined, mainOrgId: undefined },
|
||||
empDialogVisible: false,
|
||||
empSubmitting: false,
|
||||
empForm: { empNo: '', empName: '', gender: '', mobile: '', employmentType: '', status: 'active', hireDate: '', remark: '', mainOrgId: undefined },
|
||||
empRules: {
|
||||
empNo: [{ required: true, message: '请输入工号', trigger: 'blur' }],
|
||||
empName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
mainOrgId: [{ required: true, message: '请选择组织', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
},
|
||||
detailVisible: false,
|
||||
detailEmp: null,
|
||||
contractList: [],
|
||||
contractLoading: false,
|
||||
contractDialogVisible: false,
|
||||
contractSubmitting: false,
|
||||
contractForm: {},
|
||||
contractForm: {
|
||||
contractNo: '',
|
||||
contractType: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
status: 'active',
|
||||
remark: '',
|
||||
fileIds: ''
|
||||
},
|
||||
certList: [],
|
||||
certLoading: false,
|
||||
certDialogVisible: false,
|
||||
@@ -343,19 +532,35 @@ export default {
|
||||
},
|
||||
positionOptions: [],
|
||||
positionLoading: false,
|
||||
flatOrgOptions: []
|
||||
flatOrgOptions: [],
|
||||
orgDialogVisible: false,
|
||||
orgSubmitting: false,
|
||||
orgForm: { orgName: '', orgType: '', parentId: undefined, orgCode: '', remark: '' },
|
||||
orgRules: {
|
||||
orgName: [{ required: true, message: '请输入组织名称', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadOrg()
|
||||
this.loadEmployee()
|
||||
this.loadPositions()
|
||||
},
|
||||
components: {
|
||||
FileUpload
|
||||
},
|
||||
methods: {
|
||||
loadTree() {
|
||||
// 兼容旧调用,直接复用 loadOrg
|
||||
this.loadOrg()
|
||||
},
|
||||
renderOrg(orgId) {
|
||||
const found = this.flatOrgOptions.find(o => o.orgId === orgId)
|
||||
return found ? found.label : orgId
|
||||
},
|
||||
renderPosition(positionId) {
|
||||
const found = this.positionOptions.find(p => p.positionId === positionId)
|
||||
return found ? (found.positionName || positionId) : positionId
|
||||
},
|
||||
statusType(status) {
|
||||
if (!status) return 'info'
|
||||
const map = { pending: 'warning', draft: 'info', approved: 'success', rejected: 'danger', active: 'success', inactive: 'info' }
|
||||
@@ -412,6 +617,55 @@ export default {
|
||||
this.empQuery.mainOrgId = node.orgId
|
||||
this.loadEmployee()
|
||||
},
|
||||
openOrgDialog() {
|
||||
this.orgForm = {
|
||||
orgName: '',
|
||||
orgType: '',
|
||||
parentId: this.orgSelected || undefined,
|
||||
orgCode: '',
|
||||
remark: ''
|
||||
}
|
||||
this.orgDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.orgFormRef && this.$refs.orgFormRef.clearValidate())
|
||||
},
|
||||
buildOrgCode(name) {
|
||||
const clean = (name || '').replace(/\s+/g, '').replace(/[^\w\u4e00-\u9fa5]/g, '')
|
||||
if (clean) return clean.slice(0, 20)
|
||||
return `ORG${Date.now()}`
|
||||
},
|
||||
autoFillOrgName() {
|
||||
const typeLabelMap = {
|
||||
department: '部门',
|
||||
division: '事业部',
|
||||
company: '公司',
|
||||
project: '项目组',
|
||||
other: '组织'
|
||||
}
|
||||
const bases = ['运营', '生产', '市场', '研发', '交付', '采购', '质控', '客服', '财务', '人力']
|
||||
const randomBase = bases[Math.floor(Math.random() * bases.length)]
|
||||
const typeLabel = typeLabelMap[this.orgForm.orgType] || '部门'
|
||||
const suffix = new Date().getMilliseconds().toString().padStart(3, '0')
|
||||
this.orgForm.orgName = `${randomBase}${typeLabel}${suffix}`
|
||||
},
|
||||
submitOrg() {
|
||||
this.$refs.orgFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.orgSubmitting = true
|
||||
const payload = { ...this.orgForm }
|
||||
if (!payload.orgCode) {
|
||||
payload.orgCode = this.buildOrgCode(payload.orgName)
|
||||
}
|
||||
addOrg(payload)
|
||||
.then(() => {
|
||||
this.$message.success('新增成功')
|
||||
this.orgDialogVisible = false
|
||||
this.loadOrg()
|
||||
})
|
||||
.finally(() => {
|
||||
this.orgSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
loadEmployee() {
|
||||
if (!this.empQuery.mainOrgId) return
|
||||
this.empLoading = true
|
||||
@@ -423,6 +677,47 @@ export default {
|
||||
this.empLoading = false
|
||||
})
|
||||
},
|
||||
openEmpDialog() {
|
||||
const defaultOrg = this.orgSelected || (this.orgTree[0] && this.orgTree[0].orgId) || undefined
|
||||
this.empForm = {
|
||||
empNo: '',
|
||||
empName: '',
|
||||
gender: '',
|
||||
mobile: '',
|
||||
employmentType: '',
|
||||
status: 'active',
|
||||
hireDate: '',
|
||||
remark: '',
|
||||
mainOrgId: defaultOrg
|
||||
}
|
||||
this.empDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.empFormRef && this.$refs.empFormRef.clearValidate())
|
||||
},
|
||||
formatDateOnly(val) {
|
||||
if (!val) return ''
|
||||
const d = new Date(val)
|
||||
const p = n => (n < 10 ? `0${n}` : n)
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} 00:00:00`
|
||||
},
|
||||
submitEmp() {
|
||||
this.$refs.empFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.empSubmitting = true
|
||||
const payload = { ...this.empForm }
|
||||
if (payload.hireDate) {
|
||||
payload.hireDate = this.formatDateOnly(payload.hireDate)
|
||||
}
|
||||
addEmployee(payload)
|
||||
.then(() => {
|
||||
this.$message.success('新增成功')
|
||||
this.empDialogVisible = false
|
||||
this.loadEmployee()
|
||||
})
|
||||
.finally(() => {
|
||||
this.empSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailEmp = row
|
||||
this.detailVisible = true
|
||||
@@ -440,15 +735,73 @@ export default {
|
||||
this.contractLoading = false
|
||||
})
|
||||
},
|
||||
handleContractUploadSuccess(fileList) {
|
||||
const arr = Array.isArray(fileList) ? fileList : fileList ? [fileList] : []
|
||||
if (!arr.length) return
|
||||
const first = arr[0]
|
||||
const name = (first && first.name) || ''
|
||||
if (!name) return
|
||||
// 二次兜底校验,防止后端已接收但命名不合规
|
||||
if (!this.validateContractBeforeUpload(first)) {
|
||||
this.resetContractUpload(first)
|
||||
return
|
||||
}
|
||||
const base = name.split('/').pop()
|
||||
const withoutExt = base ? base.replace(/\.[^.]+$/, '') : ''
|
||||
const no = withoutExt && withoutExt.indexOf('_') !== -1 ? withoutExt.split('_')[0] : ''
|
||||
if (no && !this.contractForm.contractNo) {
|
||||
this.contractForm.contractNo = no
|
||||
}
|
||||
},
|
||||
validateContractBeforeUpload(file) {
|
||||
const name = file && file.name
|
||||
if (!name) return false
|
||||
const base = name.split('/').pop()
|
||||
const withoutExt = base ? base.replace(/\.[^.]+$/, '') : ''
|
||||
if (!withoutExt || withoutExt.indexOf('_') === -1) {
|
||||
this.$message.error('文件名需包含编号与员工名,并用下划线分隔,例如:合同编号_员工名.pdf')
|
||||
this.resetContractUpload()
|
||||
return false
|
||||
}
|
||||
const no = withoutExt.split('_')[0]
|
||||
const validNo = /^[A-Za-z0-9-]+$/.test(no)
|
||||
if (!validNo) {
|
||||
this.$message.error('合同编号仅支持字母/数字/短横线,请调整文件名')
|
||||
this.resetContractUpload()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
async resetContractUpload(firstFile) {
|
||||
// 清前端状态
|
||||
this.contractForm.fileIds = ''
|
||||
this.contractForm.contractNo = ''
|
||||
const uploader = this.$refs.contractUploader
|
||||
if (uploader && uploader.resetUpload) {
|
||||
uploader.resetUpload()
|
||||
}
|
||||
// 仅在有 ossId 时请求删除
|
||||
const ossId = firstFile && firstFile.ossId
|
||||
if (ossId) {
|
||||
try {
|
||||
await delOss(ossId)
|
||||
} catch (e) {
|
||||
// 忽略删除异常
|
||||
}
|
||||
}
|
||||
},
|
||||
openContractDialog(row) {
|
||||
this.contractForm = row ? { ...row } : { empId: this.detailEmp?.empId, status: 'active' }
|
||||
this.contractForm = row ? { ...row } : { empId: this.detailEmp?.empId, status: 'active', fileIds: '', contractNo: '' }
|
||||
this.contractDialogVisible = true
|
||||
},
|
||||
submitContract() {
|
||||
if (!this.detailEmp) return
|
||||
this.contractSubmitting = true
|
||||
const api = this.contractForm.contractId ? updateContract : addContract
|
||||
api({ ...this.contractForm, empId: this.detailEmp.empId })
|
||||
const payload = { ...this.contractForm, empId: this.detailEmp.empId }
|
||||
if (payload.startDate) payload.startDate = this.formatDateOnly(payload.startDate)
|
||||
if (payload.endDate) payload.endDate = this.formatDateOnly(payload.endDate)
|
||||
api(payload)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.contractDialogVisible = false
|
||||
@@ -519,7 +872,10 @@ export default {
|
||||
if (!valid) return
|
||||
this.relSubmitting = true
|
||||
const api = this.relForm.relId ? updateEmpOrgPosition : addEmpOrgPosition
|
||||
api(this.relForm)
|
||||
const payload = { ...this.relForm }
|
||||
if (payload.startDate) payload.startDate = this.formatDateOnly(payload.startDate)
|
||||
if (payload.endDate) payload.endDate = this.formatDateOnly(payload.endDate)
|
||||
api(payload)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.relDialogVisible = false
|
||||
@@ -539,14 +895,23 @@ export default {
|
||||
})
|
||||
},
|
||||
openCertDialog(row) {
|
||||
this.certForm = row ? { ...row } : { empId: this.detailEmp?.empId }
|
||||
this.certForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
empId: this.detailEmp?.empId,
|
||||
fileIds: '',
|
||||
issuedBy: '',
|
||||
certNo: ''
|
||||
}
|
||||
this.certDialogVisible = true
|
||||
},
|
||||
submitCert() {
|
||||
if (!this.detailEmp) return
|
||||
this.certSubmitting = true
|
||||
const api = this.certForm.certId ? updateCertificate : addCertificate
|
||||
api({ ...this.certForm, empId: this.detailEmp.empId })
|
||||
const payload = { ...this.certForm, empId: this.detailEmp.empId }
|
||||
if (payload.validFrom) payload.validFrom = this.formatDateOnly(payload.validFrom)
|
||||
api(payload)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.certDialogVisible = false
|
||||
@@ -618,7 +983,57 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
.tree-tag {
|
||||
margin-left: 8px;
|
||||
margin: 0;
|
||||
align-self: center;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.org-dialog ::v-deep .el-dialog__body {
|
||||
background: linear-gradient(180deg, #fbfcff 0%, #f7f9fc 100%);
|
||||
padding: 18px 20px 10px;
|
||||
}
|
||||
.org-form .el-form-item {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.org-form .el-input-group__append {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
.org-form .el-input-group__append .el-button {
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
.org-form .el-form-item__label {
|
||||
font-weight: 600;
|
||||
color: #3c4257;
|
||||
}
|
||||
.org-dialog ::v-deep .el-dialog__header {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.inline-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.inline-field .el-input {
|
||||
flex: 1;
|
||||
}
|
||||
.inline-field .el-button {
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.2);
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.field-hint {
|
||||
color: #9aa2b1;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.panel-grid {
|
||||
|
||||
@@ -328,6 +328,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
latestStatDate() {
|
||||
const dates = (this.statList || []).map(i => i.statDate).filter(Boolean)
|
||||
if (!dates.length) return ''
|
||||
return dates.sort().pop()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadPayPlan()
|
||||
this.loadPayRun()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div slot="header" class="panel-header">
|
||||
<span>{{ item.title }}</span>
|
||||
<div class="actions-inline">
|
||||
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="openCreate(item.key)">新增</el-button>
|
||||
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="goCreate(item.key)">新增</el-button>
|
||||
<el-select
|
||||
v-if="item.statusField"
|
||||
v-model="item.query.status"
|
||||
@@ -65,14 +65,21 @@
|
||||
<el-col :span="12">
|
||||
<el-form :model="stampForm" label-width="110px" size="small">
|
||||
<el-form-item label="待盖章文件" prop="targetFileUrl">
|
||||
<el-input v-model="stampForm.targetFileUrl" placeholder="PDF或图片 OSS 完整 URL" />
|
||||
<file-upload
|
||||
v-model="stampForm.targetFileOssId"
|
||||
:limit="1"
|
||||
:file-size="20"
|
||||
:file-type="['pdf', 'png', 'jpg', 'jpeg', 'bmp', 'webp']"
|
||||
:is-show-tip="false"
|
||||
@success="handleTargetUploadSuccess"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="章图片" prop="stampImageUrl">
|
||||
<el-select
|
||||
v-model="stampForm.stampImageUrl"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择章图(字典配置)"
|
||||
placeholder="选择章图"
|
||||
@change="preloadStampImage"
|
||||
style="width: 100%"
|
||||
>
|
||||
@@ -131,120 +138,14 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="新增申请"
|
||||
:visible.sync="createDialogVisible"
|
||||
width="520px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="create-type-switch">
|
||||
<el-radio-group v-model="createType" size="small" @change="() => (createForm = defaultCreateForm(createType))">
|
||||
<el-radio-button label="leave">请假</el-radio-button>
|
||||
<el-radio-button label="overtime">加班</el-radio-button>
|
||||
<el-radio-button label="travel">出差</el-radio-button>
|
||||
<el-radio-button label="seal">用印</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span class="hint-text" style="margin-left:8px">先选类型,再填写必填项</span>
|
||||
</div>
|
||||
<el-form ref="createFormRef" :model="createForm" :rules="currentCreateRules" label-width="110px" size="small">
|
||||
<el-form-item label="申请人">
|
||||
<el-select
|
||||
v-model="createForm.empId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择员工"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="emp in employees"
|
||||
:key="emp.empId"
|
||||
:label="`${emp.empName || emp.empNo || emp.empId}`"
|
||||
:value="emp.empId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="createType === 'leave'">
|
||||
<el-form-item label="请假类型" prop="leaveType">
|
||||
<el-input v-model="createForm.leaveType" placeholder="年假/事假等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="createForm.startTime" type="datetime" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="createForm.endTime" type="datetime" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时长(小时)" prop="hours">
|
||||
<el-input-number v-model="createForm.hours" :min="0.5" :step="0.5" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="createForm.reason" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="createType === 'overtime'">
|
||||
<el-form-item label="加班类型" prop="otType">
|
||||
<el-input v-model="createForm.otType" placeholder="工作日/休息日等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="createForm.startTime" type="datetime" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="createForm.endTime" type="datetime" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时长(小时)" prop="hours">
|
||||
<el-input-number v-model="createForm.hours" :min="0.5" :step="0.5" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="createForm.reason" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="createType === 'travel'">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="createForm.startTime" type="datetime" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="createForm.endTime" type="datetime" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="目的地" prop="destination">
|
||||
<el-input v-model="createForm.destination" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="createForm.reason" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="createType === 'seal'">
|
||||
<el-form-item label="用印类型" prop="sealType">
|
||||
<el-input v-model="createForm.sealType" placeholder="合同章/法人章..." />
|
||||
</el-form-item>
|
||||
<el-form-item label="用途说明" prop="purpose">
|
||||
<el-input v-model="createForm.purpose" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请材料附件" prop="applyFileIds">
|
||||
<file-upload v-model="createForm.applyFileIds" :limit="5" :file-size="50" :file-type="['pdf']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="需要回执">
|
||||
<el-switch v-model="createForm.receiptRequired" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createSubmitting" @click="submitCreate">提交</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listLeaveReq,
|
||||
listOvertimeReq,
|
||||
listTravelReq,
|
||||
listSealReq,
|
||||
listEmployee,
|
||||
addLeaveReq,
|
||||
addOvertimeReq,
|
||||
addTravelReq,
|
||||
addSealReq,
|
||||
approveSealReq,
|
||||
rejectSealReq,
|
||||
cancelSealReq,
|
||||
@@ -264,6 +165,7 @@ export default {
|
||||
stampSubmitting: false,
|
||||
stampForm: {
|
||||
targetFileUrl: '',
|
||||
targetFileOssId: '',
|
||||
stampImageUrl: '',
|
||||
pageNo: 1,
|
||||
xPx: 0,
|
||||
@@ -274,30 +176,11 @@ export default {
|
||||
currentSeal: null,
|
||||
previewNatural: { width: 0, height: 0 },
|
||||
marker: { visible: false, x: 0, y: 0, width: 0, height: 0 },
|
||||
stampImageNatural: { width: 0, height: 0 },
|
||||
employees: [],
|
||||
createDialogVisible: false,
|
||||
createType: '',
|
||||
createSubmitting: false,
|
||||
createForm: {},
|
||||
createRules: {
|
||||
empId: [{ required: true, message: '请选择申请人', trigger: 'change' }],
|
||||
leaveType: [{ required: true, message: '请选择请假类型', trigger: 'blur' }],
|
||||
otType: [{ required: true, message: '请输入加班类型', trigger: 'blur' }],
|
||||
sealType: [{ required: true, message: '请输入用印类型', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
hours: [{ required: true, message: '请输入时长', trigger: 'blur' }],
|
||||
destination: [{ required: true, message: '请输入目的地', trigger: 'blur' }],
|
||||
purpose: [{ required: true, message: '请输入用途说明', trigger: 'blur' }],
|
||||
applyFileIds: [{ required: true, message: '请上传附件', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '请输入事由', trigger: 'blur' }]
|
||||
}
|
||||
stampImageNatural: { width: 0, height: 0 }
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initRequests()
|
||||
this.loadEmployees()
|
||||
},
|
||||
computed: {
|
||||
applicantDisplay() {
|
||||
@@ -305,23 +188,18 @@ export default {
|
||||
const name = user.nickName || user.userName || ''
|
||||
const id = user.userId || user.userId === 0 ? user.userId : ''
|
||||
return name ? `${name}${id ? ` (${id})` : ''}` : id || '当前登录人'
|
||||
},
|
||||
currentCreateRules() {
|
||||
// 动态裁剪规则,避免多余必填影响校验
|
||||
const keys = {
|
||||
leave: ['empId', 'leaveType', 'startTime', 'endTime', 'hours', 'reason'],
|
||||
overtime: ['empId', 'otType', 'startTime', 'endTime', 'hours', 'reason'],
|
||||
travel: ['empId', 'startTime', 'endTime', 'destination', 'reason'],
|
||||
seal: ['empId', 'sealType', 'purpose', 'applyFileIds']
|
||||
}[this.createType] || []
|
||||
const picked = {}
|
||||
keys.forEach(k => {
|
||||
if (this.createRules[k]) picked[k] = this.createRules[k]
|
||||
})
|
||||
return picked
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goCreate(key) {
|
||||
const routeNameMap = {
|
||||
leave: 'HrmLeaveRequest',
|
||||
travel: 'HrmTravelRequest',
|
||||
seal: 'HrmSealRequest'
|
||||
}
|
||||
const name = routeNameMap[key]
|
||||
if (name) this.$router.push({ name })
|
||||
},
|
||||
statusType(status) {
|
||||
if (!status) return 'info'
|
||||
const map = { pending: 'warning', draft: 'info', approved: 'success', rejected: 'danger' }
|
||||
@@ -336,7 +214,6 @@ export default {
|
||||
initRequests() {
|
||||
this.requestBlocks = [
|
||||
{ key: 'leave', title: '请假单', typeField: 'leaveType', statusField: 'status', query: { pageNum: 1, pageSize: 10, status: undefined }, list: [], loading: false, loader: this.loadLeaveReq },
|
||||
{ key: 'overtime', title: '加班单', typeField: 'overtimeType', statusField: 'status', query: { pageNum: 1, pageSize: 10, status: undefined }, list: [], loading: false, loader: this.loadOvertimeReq },
|
||||
{ key: 'travel', title: '出差单', typeField: 'travelType', statusField: 'status', query: { pageNum: 1, pageSize: 10, status: undefined }, list: [], loading: false, loader: this.loadTravelReq },
|
||||
{ key: 'seal', title: '用印申请', typeField: 'sealType', statusField: 'status', query: { pageNum: 1, pageSize: 10, status: undefined }, list: [], loading: false, loader: this.loadSealReq }
|
||||
]
|
||||
@@ -353,17 +230,6 @@ export default {
|
||||
block.loading = false
|
||||
})
|
||||
},
|
||||
loadOvertimeReq() {
|
||||
const block = this.requestBlocks.find(i => i.key === 'overtime')
|
||||
block.loading = true
|
||||
listOvertimeReq(block.query)
|
||||
.then(res => {
|
||||
block.list = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
block.loading = false
|
||||
})
|
||||
},
|
||||
loadTravelReq() {
|
||||
const block = this.requestBlocks.find(i => i.key === 'travel')
|
||||
block.loading = true
|
||||
@@ -411,6 +277,10 @@ export default {
|
||||
},
|
||||
submitStamp() {
|
||||
if (!this.currentSeal) return
|
||||
if (!this.stampForm.targetFileUrl) {
|
||||
this.$message.warning('请先上传待盖章文件')
|
||||
return
|
||||
}
|
||||
this.stampSubmitting = true
|
||||
stampSealJava(this.currentSeal.bizId, this.stampForm)
|
||||
.then(() => {
|
||||
@@ -474,66 +344,11 @@ export default {
|
||||
height: hRatio * displayHeight
|
||||
}
|
||||
},
|
||||
openCreate(type) {
|
||||
this.createType = type
|
||||
this.createDialogVisible = true
|
||||
this.createForm = this.defaultCreateForm(type)
|
||||
this.$nextTick(() => this.$refs.createFormRef && this.$refs.createFormRef.clearValidate())
|
||||
},
|
||||
defaultCreateForm(type) {
|
||||
const common = { empId: '', remark: '' }
|
||||
if (type === 'leave') {
|
||||
return { ...common, leaveType: '', startTime: '', endTime: '', hours: 0, reason: '' }
|
||||
}
|
||||
if (type === 'overtime') {
|
||||
return { ...common, otType: '', startTime: '', endTime: '', hours: 0, reason: '' }
|
||||
}
|
||||
if (type === 'travel') {
|
||||
return { ...common, startTime: '', endTime: '', destination: '', reason: '' }
|
||||
}
|
||||
if (type === 'seal') {
|
||||
return { ...common, sealType: '', purpose: '', applyFileIds: '', receiptRequired: 0 }
|
||||
}
|
||||
return common
|
||||
},
|
||||
loadEmployees() {
|
||||
listEmployee({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.employees = res.rows || res.data || []
|
||||
})
|
||||
},
|
||||
submitCreate() {
|
||||
if (!this.createType) return
|
||||
const apiMap = {
|
||||
leave: addLeaveReq,
|
||||
overtime: addOvertimeReq,
|
||||
travel: addTravelReq,
|
||||
seal: addSealReq
|
||||
}
|
||||
const fn = apiMap[this.createType]
|
||||
if (!fn) return
|
||||
// 默认申请人取当前登录用户
|
||||
const payload = { ...this.createForm }
|
||||
const userId = this.$store?.state?.user?.userId
|
||||
if (userId) {
|
||||
payload.empId = userId
|
||||
} else {
|
||||
this.$message.error('未获取到当前登录用户,无法提交')
|
||||
return
|
||||
}
|
||||
this.$refs.createFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.createSubmitting = true
|
||||
fn(payload)
|
||||
.then(() => {
|
||||
this.$message.success('提交成功')
|
||||
this.createDialogVisible = false
|
||||
const block = this.requestBlocks.find(i => i.key === this.createType)
|
||||
block && block.loader()
|
||||
})
|
||||
.finally(() => {
|
||||
this.createSubmitting = false
|
||||
})
|
||||
})
|
||||
handleTargetUploadSuccess(fileList) {
|
||||
const first = (fileList && fileList[0]) || {}
|
||||
this.stampForm.targetFileUrl = first.url || ''
|
||||
this.stampForm.targetFileOssId = first.ossId || ''
|
||||
this.marker.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
137
klp-ui/src/views/hrm/requests/leave.vue
Normal file
137
klp-ui/src/views/hrm/requests/leave.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="request-page">
|
||||
<el-card class="form-card" shadow="never">
|
||||
<div slot="header" class="card-header">
|
||||
<span>请假申请</span>
|
||||
<div class="actions">
|
||||
<el-button size="mini" icon="el-icon-arrow-left" @click="$router.back()">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" size="small">
|
||||
<el-form-item label="申请人" prop="empId">
|
||||
<el-select
|
||||
v-model="form.empId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择申请人"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="emp in employees"
|
||||
:key="emp.empId"
|
||||
:label="emp.empName || emp.empNo || emp.empId"
|
||||
:value="emp.empId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假类型" prop="leaveType">
|
||||
<el-input v-model="form.leaveType" placeholder="年假/事假/病假等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="form.startTime" type="datetime" placeholder="请选择开始时间" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="form.endTime" type="datetime" placeholder="请选择结束时间" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时长(小时)" prop="hours">
|
||||
<el-input-number v-model="form.hours" :min="0.5" :step="0.5" style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="form.reason" type="textarea" :rows="3" placeholder="请填写请假事由" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<div class="form-actions">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">提交申请</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listEmployee, addLeaveReq } from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmLeaveRequest',
|
||||
data() {
|
||||
return {
|
||||
employees: [],
|
||||
submitting: false,
|
||||
form: {
|
||||
empId: '',
|
||||
leaveType: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
hours: 0,
|
||||
reason: '',
|
||||
remark: ''
|
||||
},
|
||||
rules: {
|
||||
empId: [{ required: true, message: '请选择申请人', trigger: 'change' }],
|
||||
leaveType: [{ required: true, message: '请输入请假类型', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
hours: [{ required: true, message: '请输入时长', trigger: 'blur' }],
|
||||
reason: [{ required: true, message: '请输入事由', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadEmployees()
|
||||
const userId = this.$store?.state?.user?.userId
|
||||
if (userId) this.form.empId = userId
|
||||
},
|
||||
methods: {
|
||||
loadEmployees() {
|
||||
listEmployee({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.employees = res.rows || res.data || []
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const payload = { ...this.form }
|
||||
addLeaveReq(payload)
|
||||
.then(() => {
|
||||
this.$message.success('提交成功')
|
||||
this.$router.push('/hrm/requests')
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.request-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.form-card {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
133
klp-ui/src/views/hrm/requests/seal.vue
Normal file
133
klp-ui/src/views/hrm/requests/seal.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="request-page">
|
||||
<el-card class="form-card" shadow="never">
|
||||
<div slot="header" class="card-header">
|
||||
<span>用印申请</span>
|
||||
<div class="actions">
|
||||
<el-button size="mini" icon="el-icon-arrow-left" @click="$router.back()">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" size="small">
|
||||
<el-form-item label="申请人" prop="empId">
|
||||
<el-select v-model="form.empId" filterable placeholder="选择申请人" style="width: 100%">
|
||||
<el-option
|
||||
v-for="emp in employees"
|
||||
:key="emp.empId"
|
||||
:label="emp.empName || emp.empNo || emp.empId"
|
||||
:value="emp.empId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用印类型" prop="sealType">
|
||||
<el-input v-model="form.sealType" placeholder="合同章/法人章/财务章等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用途说明" prop="purpose">
|
||||
<el-input v-model="form.purpose" type="textarea" :rows="3" placeholder="填写用印用途与背景" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请材料附件" prop="applyFileIds">
|
||||
<file-upload
|
||||
v-model="form.applyFileIds"
|
||||
:limit="5"
|
||||
:file-size="50"
|
||||
:file-type="['pdf']"
|
||||
/>
|
||||
<div class="hint-text">仅支持 PDF,最多 5 个,单个不超过 50MB</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="需要回执">
|
||||
<el-switch v-model="form.receiptRequired" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<div class="form-actions">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">提交申请</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listEmployee, addSealReq } from '@/api/hrm'
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
|
||||
export default {
|
||||
name: 'HrmSealRequest',
|
||||
components: { FileUpload },
|
||||
data() {
|
||||
return {
|
||||
employees: [],
|
||||
submitting: false,
|
||||
form: {
|
||||
empId: '',
|
||||
sealType: '',
|
||||
purpose: '',
|
||||
applyFileIds: '',
|
||||
receiptRequired: 0,
|
||||
remark: ''
|
||||
},
|
||||
rules: {
|
||||
empId: [{ required: true, message: '请选择申请人', trigger: 'change' }],
|
||||
sealType: [{ required: true, message: '请输入用印类型', trigger: 'blur' }],
|
||||
purpose: [{ required: true, message: '请输入用途说明', trigger: 'blur' }],
|
||||
applyFileIds: [{ required: true, message: '请上传 PDF 附件', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadEmployees()
|
||||
const userId = this.$store?.state?.user?.userId
|
||||
if (userId) this.form.empId = userId
|
||||
},
|
||||
methods: {
|
||||
loadEmployees() {
|
||||
listEmployee({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.employees = res.rows || res.data || []
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const payload = { ...this.form }
|
||||
addSealReq(payload)
|
||||
.then(() => {
|
||||
this.$message.success('提交成功')
|
||||
this.$router.push('/hrm/requests')
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.request-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.form-card {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
167
klp-ui/src/views/hrm/requests/travel.vue
Normal file
167
klp-ui/src/views/hrm/requests/travel.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="request-page">
|
||||
<el-card class="form-card" shadow="never">
|
||||
<div slot="header" class="card-header">
|
||||
<span>出差申请</span>
|
||||
<div class="actions">
|
||||
<el-button size="mini" icon="el-icon-arrow-left" @click="$router.back()">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" size="small">
|
||||
<el-form-item label="申请人" prop="empId">
|
||||
<el-select v-model="form.empId" filterable placeholder="选择申请人" style="width: 100%">
|
||||
<el-option
|
||||
v-for="emp in employees"
|
||||
:key="emp.empId"
|
||||
:label="emp.empName || emp.empNo || emp.empId"
|
||||
:value="emp.empId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="form.startTime" type="datetime" placeholder="开始时间" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="form.endTime" type="datetime" placeholder="结束时间" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="目的地" prop="destination">
|
||||
<el-input v-model="form.destination" placeholder="城市/地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="form.reason" type="textarea" :rows="3" placeholder="填写出差事由" />
|
||||
</el-form-item>
|
||||
<el-form-item label="差旅附件" prop="travelFileIds">
|
||||
<file-upload
|
||||
v-model="form.travelFileIds"
|
||||
:limit="8"
|
||||
:file-size="50"
|
||||
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']"
|
||||
multiple
|
||||
/>
|
||||
<div class="hint-text">上传机票、酒店、行程单等(pdf/jpg/png/doc/docx)</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="收款人" prop="payeeName">
|
||||
<el-input v-model="form.payeeName" placeholder="收款人姓名/公司" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行" prop="bankName">
|
||||
<el-input v-model="form.bankName" placeholder="XX银行XX支行" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行账号" prop="bankAccount">
|
||||
<el-input v-model="form.bankAccount" placeholder="汇款账号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<div class="form-actions">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">提交申请</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listEmployee, addTravelReq } from '@/api/hrm'
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
|
||||
export default {
|
||||
name: 'HrmTravelRequest',
|
||||
components: { FileUpload },
|
||||
data() {
|
||||
return {
|
||||
employees: [],
|
||||
submitting: false,
|
||||
form: {
|
||||
empId: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
destination: '',
|
||||
reason: '',
|
||||
travelFileIds: '',
|
||||
payeeName: '',
|
||||
bankName: '',
|
||||
bankAccount: '',
|
||||
remark: ''
|
||||
},
|
||||
rules: {
|
||||
empId: [{ required: true, message: '请选择申请人', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
destination: [{ required: true, message: '请输入目的地', trigger: 'blur' }],
|
||||
reason: [{ required: true, message: '请输入事由', trigger: 'blur' }],
|
||||
travelFileIds: [{ required: true, message: '请上传差旅附件', trigger: 'change' }],
|
||||
payeeName: [{ required: true, message: '请输入收款人', trigger: 'blur' }],
|
||||
bankName: [{ required: true, message: '请输入开户行', trigger: 'blur' }],
|
||||
bankAccount: [{ required: true, message: '请输入银行账号', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadEmployees()
|
||||
const userId = this.$store?.state?.user?.userId
|
||||
if (userId) this.form.empId = userId
|
||||
},
|
||||
methods: {
|
||||
loadEmployees() {
|
||||
listEmployee({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.employees = res.rows || res.data || []
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const payload = { ...this.form }
|
||||
addTravelReq(payload)
|
||||
.then(() => {
|
||||
this.$message.success('提交成功')
|
||||
this.$router.push('/hrm/requests')
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.request-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.form-card {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user