hcm前端2版
This commit is contained in:
368
klp-ui/src/views/hrm/flow/task.vue
Normal file
368
klp-ui/src/views/hrm/flow/task.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<section class="panel-grid">
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<div class="header-left">
|
||||
<span>流程任务</span>
|
||||
<el-radio-group v-model="mode" size="small" @change="fetchList">
|
||||
<el-radio-button label="todo">我的待办</el-radio-button>
|
||||
<el-radio-button label="mine">我发起的</el-radio-button>
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-select
|
||||
v-model="query.status"
|
||||
placeholder="状态"
|
||||
clearable
|
||||
size="mini"
|
||||
style="width: 140px"
|
||||
@change="fetchList"
|
||||
>
|
||||
<el-option label="待办" value="pending" />
|
||||
<el-option label="完成" value="done" />
|
||||
<el-option label="拒绝" value="rejected" />
|
||||
<el-option label="撤回" value="withdrawn" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="actions-inline">
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="fetchList">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" height="680" stripe @row-click="openDetail">
|
||||
<el-table-column label="任务ID" prop="taskId" width="100" />
|
||||
<el-table-column label="实例" prop="instId" width="100" />
|
||||
<el-table-column label="业务类型" prop="bizType" min-width="100" />
|
||||
<el-table-column label="节点" prop="nodeId" min-width="90" />
|
||||
<el-table-column label="办理人" prop="assigneeUserId" min-width="120" />
|
||||
<el-table-column label="状态" prop="status" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusType(scope.row.status)">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="到期" prop="expireTime" min-width="150">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.expireTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click.stop="openDetail(scope.row)">详情</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'pending'"
|
||||
size="mini"
|
||||
type="text"
|
||||
@click.stop="openAction(scope.row, 'approve')"
|
||||
>通过</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'pending'"
|
||||
size="mini"
|
||||
type="text"
|
||||
@click.stop="openAction(scope.row, 'reject')"
|
||||
>驳回</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
@click.stop="openAction(scope.row, 'withdraw')"
|
||||
>撤回</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<el-drawer
|
||||
title="任务详情"
|
||||
:visible.sync="detailVisible"
|
||||
size="60%"
|
||||
append-to-body
|
||||
>
|
||||
<div v-if="detailTask" class="detail-wrap">
|
||||
<el-descriptions :column="3" size="small" border class="mb12">
|
||||
<el-descriptions-item label="任务ID">{{ detailTask.taskId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实例">{{ detailTask.instId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="业务类型">{{ detailTask.bizType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节点">{{ detailTask.nodeId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理人">{{ detailTask.assigneeUserId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusType(detailTask.status)">{{ detailTask.status }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="到期">{{ formatDate(detailTask.expireTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ detailTask.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-tabs value="form">
|
||||
<el-tab-pane label="表单数据" name="form">
|
||||
<el-table :data="formData" v-loading="formLoading" size="mini">
|
||||
<el-table-column label="字段" prop="fieldName" min-width="140" />
|
||||
<el-table-column label="值" prop="fieldValue" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column label="展示标签" prop="fieldLabel" min-width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="流转历史" name="history">
|
||||
<el-table :data="actionList" v-loading="actionLoading" size="mini">
|
||||
<el-table-column label="动作" prop="action" min-width="120" />
|
||||
<el-table-column label="办理人" prop="actionUserId" min-width="120" />
|
||||
<el-table-column label="时间" prop="createTime" min-width="160">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="200" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div v-else class="placeholder">请选择任务查看详情</div>
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog
|
||||
:title="actionDialogTitle"
|
||||
:visible.sync="actionDialogVisible"
|
||||
width="520px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form :model="actionForm" label-width="100px" size="small">
|
||||
<el-form-item label="意见">
|
||||
<el-input v-model="actionForm.remark" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
<template v-if="detailTask && detailTask.bizType === 'seal' && actionType === 'approve'">
|
||||
<el-divider>盖章(选填,仅用印业务)</el-divider>
|
||||
<el-form-item label="待盖章文件">
|
||||
<el-input v-model="actionForm.stampBo.targetFileUrl" placeholder="OSS URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="章图片">
|
||||
<el-input v-model="actionForm.stampBo.stampImageUrl" placeholder="OSS URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="页码">
|
||||
<el-input-number v-model="actionForm.stampBo.pageNo" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="坐标 (x,y)">
|
||||
<div class="coord-row">
|
||||
<el-input-number v-model="actionForm.stampBo.xPx" :min="0" />
|
||||
<el-input-number v-model="actionForm.stampBo.yPx" :min="0" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="尺寸 (可选)">
|
||||
<div class="coord-row">
|
||||
<el-input-number v-model="actionForm.stampBo.widthPx" :min="1" />
|
||||
<el-input-number v-model="actionForm.stampBo.heightPx" :min="1" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="actionDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="actionSubmitting" @click="submitAction">提交</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listFlowTask,
|
||||
listTodoFlowTask,
|
||||
approveFlowTask,
|
||||
rejectFlowTask,
|
||||
withdrawFlowTask,
|
||||
listFlowAction,
|
||||
listFlowFormData
|
||||
} from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmFlowTask',
|
||||
data() {
|
||||
return {
|
||||
mode: 'todo',
|
||||
query: { status: undefined, pageNum: 1, pageSize: 50 },
|
||||
list: [],
|
||||
loading: false,
|
||||
detailVisible: false,
|
||||
detailTask: null,
|
||||
actionList: [],
|
||||
actionLoading: false,
|
||||
formData: [],
|
||||
formLoading: false,
|
||||
actionDialogVisible: false,
|
||||
actionDialogTitle: '',
|
||||
actionType: '',
|
||||
actionSubmitting: false,
|
||||
actionForm: {
|
||||
remark: '',
|
||||
stampBo: {
|
||||
targetFileUrl: '',
|
||||
stampImageUrl: '',
|
||||
pageNo: 1,
|
||||
xPx: 0,
|
||||
yPx: 0,
|
||||
widthPx: undefined,
|
||||
heightPx: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
statusType(status) {
|
||||
if (!status) return 'info'
|
||||
const map = { pending: 'warning', done: 'success', rejected: 'danger', withdrawn: 'info' }
|
||||
return map[status] || 'info'
|
||||
},
|
||||
formatDate(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())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||
},
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const userId = this.$store?.state?.user?.userId
|
||||
const params = { ...this.query }
|
||||
let req
|
||||
if (this.mode === 'todo' && userId) {
|
||||
req = listTodoFlowTask(userId)
|
||||
} else if (this.mode === 'mine' && userId) {
|
||||
req = listFlowTask({ ...params, startUserId: userId })
|
||||
} else {
|
||||
req = listFlowTask(params)
|
||||
}
|
||||
req
|
||||
.then(res => {
|
||||
this.list = res.rows || res || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailTask = row
|
||||
this.detailVisible = true
|
||||
this.loadActions(row)
|
||||
this.loadFormData(row)
|
||||
},
|
||||
loadActions(row) {
|
||||
if (!row) return
|
||||
if (row.instId && !/^\d+$/.test(row.instId)) {
|
||||
this.$message.warning('实例ID需为数字,已跳过加载动作')
|
||||
return
|
||||
}
|
||||
this.actionLoading = true
|
||||
listFlowAction({ instId: row.instId, pageNum: 1, pageSize: 100 })
|
||||
.then(res => {
|
||||
this.actionList = res.rows || res || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.actionLoading = false
|
||||
})
|
||||
},
|
||||
loadFormData(row) {
|
||||
if (!row) return
|
||||
if (row.instId && !/^\d+$/.test(row.instId)) {
|
||||
this.$message.warning('实例ID需为数字,已跳过加载表单数据')
|
||||
return
|
||||
}
|
||||
this.formLoading = true
|
||||
listFlowFormData({ instId: row.instId, pageNum: 1, pageSize: 100 })
|
||||
.then(res => {
|
||||
this.formData = res.rows || res || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
openAction(row, type) {
|
||||
this.detailTask = row
|
||||
this.actionType = type
|
||||
const titleMap = { approve: '审批通过', reject: '驳回', withdraw: '撤回' }
|
||||
this.actionDialogTitle = titleMap[type] || '操作'
|
||||
this.actionForm = {
|
||||
remark: '',
|
||||
stampBo: {
|
||||
targetFileUrl: '',
|
||||
stampImageUrl: '',
|
||||
pageNo: 1,
|
||||
xPx: 0,
|
||||
yPx: 0,
|
||||
widthPx: undefined,
|
||||
heightPx: undefined
|
||||
}
|
||||
}
|
||||
this.actionDialogVisible = true
|
||||
},
|
||||
submitAction() {
|
||||
if (!this.detailTask || !this.actionType) return
|
||||
this.actionSubmitting = true
|
||||
const payload = { remark: this.actionForm.remark }
|
||||
if (this.actionType === 'approve' && this.detailTask.bizType === 'seal') {
|
||||
// 仅当用户填了必要字段时传 stampBo
|
||||
const sb = this.actionForm.stampBo
|
||||
if (sb.targetFileUrl && sb.stampImageUrl && sb.pageNo && sb.xPx != null && sb.yPx != null) {
|
||||
payload.stampBo = { ...sb }
|
||||
}
|
||||
}
|
||||
const apiMap = {
|
||||
approve: approveFlowTask,
|
||||
reject: rejectFlowTask,
|
||||
withdraw: withdrawFlowTask
|
||||
}
|
||||
apiMap[this.actionType](this.detailTask.taskId, payload)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功')
|
||||
this.actionDialogVisible = false
|
||||
this.fetchList()
|
||||
this.loadActions(this.detailTask)
|
||||
})
|
||||
.finally(() => {
|
||||
this.actionSubmitting = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hrm-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.metal-panel {
|
||||
border: 1px solid #d7d9df;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.actions-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.detail-wrap {
|
||||
padding-right: 4px;
|
||||
}
|
||||
.coord-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
.placeholder {
|
||||
color: #a0a3ad;
|
||||
padding: 12px;
|
||||
}
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
237
klp-ui/src/views/hrm/leaveBalance/index.vue
Normal file
237
klp-ui/src/views/hrm/leaveBalance/index.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<div class="header-left">
|
||||
<span>请假余额维护</span>
|
||||
<div class="filters">
|
||||
<el-select
|
||||
v-model="query.empId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择员工"
|
||||
size="mini"
|
||||
style="width: 200px"
|
||||
@change="fetchList"
|
||||
>
|
||||
<el-option
|
||||
v-for="emp in employees"
|
||||
:key="emp.empId"
|
||||
:label="`${emp.empName || emp.empNo || emp.empId}`"
|
||||
:value="emp.empId"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="query.leaveType"
|
||||
clearable
|
||||
placeholder="假期类型"
|
||||
size="mini"
|
||||
style="width: 160px"
|
||||
@change="fetchList"
|
||||
>
|
||||
<el-option label="年假" value="annual" />
|
||||
<el-option label="事假" value="personal" />
|
||||
<el-option label="病假" value="sick" />
|
||||
<el-option label="调休" value="lieu" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
<el-button size="mini" type="primary" icon="el-icon-search" @click="fetchList">查询</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions-inline">
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="openDialog()">新增/调整</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="list" v-loading="loading" stripe height="620">
|
||||
<el-table-column label="员工" prop="empId" min-width="140">
|
||||
<template slot-scope="scope">
|
||||
{{ renderEmp(scope.row.empId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="假期类型" prop="leaveType" min-width="120" />
|
||||
<el-table-column label="余额(小时)" prop="balanceHours" min-width="120" />
|
||||
<el-table-column label="失效日期" prop="expireDate" min-width="140">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.expireDate, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" 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
|
||||
title="调整请假余额"
|
||||
:visible.sync="dialogVisible"
|
||||
width="480px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form :model="form" label-width="100px" size="small">
|
||||
<el-form-item label="员工" required>
|
||||
<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="假期类型" required>
|
||||
<el-select v-model="form.leaveType" placeholder="假期类型" style="width: 100%">
|
||||
<el-option label="年假" value="annual" />
|
||||
<el-option label="事假" value="personal" />
|
||||
<el-option label="病假" value="sick" />
|
||||
<el-option label="调休" value="lieu" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="余额(小时)" required>
|
||||
<el-input-number v-model="form.balanceHours" :min="0" :step="0.5" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="失效日期">
|
||||
<el-date-picker v-model="form.expireDate" type="date" placeholder="可选" style="width: 100%" />
|
||||
</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" class="dialog-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 { listLeaveBalance, addLeaveBalance, updateLeaveBalance, delLeaveBalance, allEmployee } from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmLeaveBalance',
|
||||
data() {
|
||||
return {
|
||||
query: { empId: undefined, leaveType: undefined, pageNum: 1, pageSize: 50 },
|
||||
list: [],
|
||||
loading: false,
|
||||
dialogVisible: false,
|
||||
submitting: false,
|
||||
form: {},
|
||||
employees: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadEmployees()
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
formatDate(val, mode = 'datetime') {
|
||||
if (!val) return ''
|
||||
const d = new Date(val)
|
||||
const p = n => (n < 10 ? `0${n}` : n)
|
||||
if (mode === 'date') return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||
},
|
||||
renderEmp(empId) {
|
||||
const emp = this.employees.find(e => e.empId === empId)
|
||||
return emp ? `${emp.empName || emp.empNo || emp.empId}` : empId
|
||||
},
|
||||
loadEmployees() {
|
||||
allEmployee().then(res => {
|
||||
this.employees = res || []
|
||||
})
|
||||
},
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
listLeaveBalance(this.query)
|
||||
.then(res => {
|
||||
this.list = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
resetQuery() {
|
||||
this.query = { empId: undefined, leaveType: undefined, pageNum: 1, pageSize: 50 }
|
||||
this.fetchList()
|
||||
},
|
||||
openDialog(row) {
|
||||
this.dialogVisible = true
|
||||
this.form = row ? { ...row } : { balanceHours: 0 }
|
||||
},
|
||||
submit() {
|
||||
if (!this.form.empId || !this.form.leaveType) {
|
||||
this.$message.error('请选择员工和假期类型')
|
||||
return
|
||||
}
|
||||
if (this.form.balanceHours == null) {
|
||||
this.$message.error('请填写余额')
|
||||
return
|
||||
}
|
||||
this.submitting = true
|
||||
const api = this.form.balId ? updateLeaveBalance : addLeaveBalance
|
||||
api(this.form)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.dialogVisible = false
|
||||
this.fetchList()
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
},
|
||||
delRow(row) {
|
||||
this.$confirm('确认删除该假期余额记录吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delLeaveBalance(row.balId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.fetchList()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hrm-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.metal-panel {
|
||||
border: 1px solid #d7d9df;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.actions-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
258
klp-ui/src/views/hrm/position/index.vue
Normal file
258
klp-ui/src/views/hrm/position/index.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>岗位与职级维护</span>
|
||||
</div>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="岗位" name="position">
|
||||
<div class="actions-inline mb8">
|
||||
<el-input v-model="posQuery.positionName" size="mini" placeholder="岗位名称" clearable style="width: 200px" @keyup.enter.native="loadPosition" />
|
||||
<el-button size="mini" type="primary" icon="el-icon-search" @click="loadPosition">查询</el-button>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openPosDialog()">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="positionList" v-loading="posLoading" height="480" stripe>
|
||||
<el-table-column label="岗位名称" prop="positionName" min-width="160" />
|
||||
<el-table-column label="岗位编码" prop="positionCode" min-width="140" />
|
||||
<el-table-column label="职级" prop="gradeId" min-width="140">
|
||||
<template slot-scope="scope">{{ renderGrade(scope.row.gradeId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openPosDialog(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click="delPos(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="职级" name="grade">
|
||||
<div class="actions-inline mb8">
|
||||
<el-input v-model="gradeQuery.gradeName" size="mini" placeholder="职级名称" clearable style="width: 200px" @keyup.enter.native="loadGrade" />
|
||||
<el-button size="mini" type="primary" icon="el-icon-search" @click="loadGrade">查询</el-button>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openGradeDialog()">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="gradeList" v-loading="gradeLoading" height="480" stripe>
|
||||
<el-table-column label="职级名称" prop="gradeName" min-width="160" />
|
||||
<el-table-column label="职级编码" prop="gradeCode" min-width="140" />
|
||||
<el-table-column label="等级" prop="gradeLevel" min-width="100" />
|
||||
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openGradeDialog(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click="delGradeRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 岗位弹窗 -->
|
||||
<el-dialog :visible.sync="posDialogVisible" title="岗位" width="420px" append-to-body>
|
||||
<el-form ref="posFormRef" :model="posForm" :rules="posRules" label-width="90px" size="small">
|
||||
<el-form-item label="岗位名称" prop="positionName">
|
||||
<el-input v-model="posForm.positionName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位编码" prop="positionCode">
|
||||
<el-input v-model="posForm.positionCode" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职级" prop="gradeId">
|
||||
<el-select v-model="posForm.gradeId" placeholder="选择职级" filterable style="width: 100%">
|
||||
<el-option v-for="g in gradeList" :key="g.gradeId" :label="g.gradeName || g.gradeId" :value="g.gradeId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="posForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="posDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="posSubmitting" @click="submitPos">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 职级弹窗 -->
|
||||
<el-dialog :visible.sync="gradeDialogVisible" title="职级" width="420px" append-to-body>
|
||||
<el-form ref="gradeFormRef" :model="gradeForm" :rules="gradeRules" label-width="90px" size="small">
|
||||
<el-form-item label="职级名称" prop="gradeName">
|
||||
<el-input v-model="gradeForm.gradeName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职级编码" prop="gradeCode">
|
||||
<el-input v-model="gradeForm.gradeCode" />
|
||||
</el-form-item>
|
||||
<el-form-item label="等级" prop="gradeLevel">
|
||||
<el-input-number v-model="gradeForm.gradeLevel" :min="1" :max="20" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="gradeForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="gradeDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="gradeSubmitting" @click="submitGrade">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listPosition,
|
||||
addPosition,
|
||||
updatePosition,
|
||||
delPosition,
|
||||
listGrade,
|
||||
addGrade,
|
||||
updateGrade,
|
||||
delGrade
|
||||
} from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmPosition',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'position',
|
||||
positionList: [],
|
||||
posLoading: false,
|
||||
posQuery: { positionName: '' },
|
||||
posDialogVisible: false,
|
||||
posSubmitting: false,
|
||||
posForm: {},
|
||||
posRules: {
|
||||
positionName: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }],
|
||||
positionCode: [{ required: true, message: '请输入岗位编码', trigger: 'blur' }]
|
||||
},
|
||||
gradeList: [],
|
||||
gradeLoading: false,
|
||||
gradeQuery: { gradeName: '' },
|
||||
gradeDialogVisible: false,
|
||||
gradeSubmitting: false,
|
||||
gradeForm: {},
|
||||
gradeRules: {
|
||||
gradeName: [{ required: true, message: '请输入职级名称', trigger: 'blur' }],
|
||||
gradeCode: [{ required: true, message: '请输入职级编码', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadGrade()
|
||||
this.loadPosition()
|
||||
},
|
||||
methods: {
|
||||
renderGrade(id) {
|
||||
const g = this.gradeList.find(i => i.gradeId === id)
|
||||
return g ? `${g.gradeName || ''}${g.gradeCode ? ` (${g.gradeCode})` : ''}` : id
|
||||
},
|
||||
// 职位
|
||||
loadPosition() {
|
||||
this.posLoading = true
|
||||
listPosition({ ...this.posQuery, pageNum: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
this.positionList = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.posLoading = false
|
||||
})
|
||||
},
|
||||
openPosDialog(row) {
|
||||
this.posForm = row ? { ...row } : { positionName: '', positionCode: '', gradeId: undefined, remark: '' }
|
||||
this.posDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.posFormRef && this.$refs.posFormRef.clearValidate())
|
||||
},
|
||||
submitPos() {
|
||||
this.$refs.posFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.posSubmitting = true
|
||||
const api = this.posForm.positionId ? updatePosition : addPosition
|
||||
api(this.posForm)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.posDialogVisible = false
|
||||
this.loadPosition()
|
||||
})
|
||||
.finally(() => {
|
||||
this.posSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delPos(row) {
|
||||
this.$confirm('确认删除该岗位吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delPosition(row.positionId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadPosition()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 职级
|
||||
loadGrade() {
|
||||
this.gradeLoading = true
|
||||
listGrade({ ...this.gradeQuery, pageNum: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
this.gradeList = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.gradeLoading = false
|
||||
})
|
||||
},
|
||||
openGradeDialog(row) {
|
||||
this.gradeForm = row ? { ...row } : { gradeName: '', gradeCode: '', gradeLevel: 1, remark: '' }
|
||||
this.gradeDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.gradeFormRef && this.$refs.gradeFormRef.clearValidate())
|
||||
},
|
||||
submitGrade() {
|
||||
this.$refs.gradeFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.gradeSubmitting = true
|
||||
const api = this.gradeForm.gradeId ? updateGrade : addGrade
|
||||
api(this.gradeForm)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.gradeDialogVisible = false
|
||||
this.loadGrade()
|
||||
this.loadPosition() // 更新岗位职级展示
|
||||
})
|
||||
.finally(() => {
|
||||
this.gradeSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delGradeRow(row) {
|
||||
this.$confirm('确认删除该职级吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delGrade(row.gradeId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadGrade()
|
||||
this.loadPosition()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hrm-page {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.metal-panel {
|
||||
border: 1px solid #d7d9df;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user