hrm前端3版

This commit is contained in:
2025-12-23 10:37:00 +08:00
parent 3c6919013d
commit 56c15ac621
7 changed files with 875 additions and 215 deletions

View File

@@ -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) {

View 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: 岗位IDleader/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>

View 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>

View File

@@ -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
}
}
}

View 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>

View 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>

View 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>