Commit 34ea55c1 by wangchunyang

文字调整

parent 471defb7
......@@ -127,17 +127,17 @@
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="18">
<span>物料编码:</span>
<span>用品编码:</span>
<Input
v-model="filters.inventory.material_code"
placeholder="请输入物料编码"
placeholder="请输入用品编码"
class="mr10"
style="width: 200px"
/>
<span>物料名称:</span>
<span>用品名称:</span>
<Input
v-model="filters.inventory.material_name"
placeholder="请输入物料名称"
placeholder="请输入用品名称"
class="mr10"
style="width: 200px"
/>
......@@ -177,7 +177,7 @@
<Input type="textarea" v-model="inboundModal.form.remark" :rows="2" />
</FormItem>
<h4>明细(从物料库选择)
<h4>明细(从用品库选择)
<div style="float: right;">
<Button size="small" class="mr5" type="primary" @click="showMaterialSelector = true">添加</Button>
<Button size="small" type="error" @click="deleteSelectedInboundDetails">删除</Button>
......@@ -219,7 +219,7 @@
</div>
<div slot="footer"><Button type="primary" @click="detailModal.visible=false">关闭</Button></div>
</Modal>
<!-- 导入物料弹窗 -->
<!-- 导入用品弹窗 -->
<Modal v-model="importShow" title="入库导入" width="360">
<Row type="flex" justify="center" align="middle">
<Col span="10">
......@@ -364,8 +364,8 @@ export default {
],
inventoryColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
{ title: '物料编码', key: 'material_code', align: 'center' },
{ title: '物料名称', key: 'material_name', align: 'center' },
{ title: '用品编码', key: 'material_code', align: 'center' },
{ title: '用品名称', key: 'material_name', align: 'center' },
{ title: '总量', key: 'total_quantity', align: 'center' },
{ title: '借出', key: 'borrowed_quantity', align: 'center' },
{
......@@ -380,8 +380,8 @@ export default {
// 模态窗口内表格列定义
inboundDetailColumns: [
{ type: 'selection', width: 60 },
{ title: '物料编码', key: 'material_code', minWidth: 120 },
{ title: '物料名称', key: 'material_name', minWidth: 150 },
{ title: '用品编码', key: 'material_code', minWidth: 120 },
{ title: '用品名称', key: 'material_name', minWidth: 150 },
{ title: '入库数量',
key: 'inbound_quantity',
minWidth: 120,
......@@ -444,19 +444,19 @@ export default {
}
],
returnDetailColumns: [
{ title: '物料名称', key: 'material_name' },
{ title: '用品名称', key: 'material_name' },
{ title: '申请数量', key: 'apply_quantity' },
{ title: '归还数量', key: 'returned_quantity' }
],
detailDetailColumns: [
{ title: '物料编码', key: 'material_code' },
{ title: '物料名称', key: 'material_name' },
{ title: '用品编码', key: 'material_code' },
{ title: '用品名称', key: 'material_name' },
{ title: '数量', key: 'inbound_quantity' },
{ title: '单价', key: 'unit_price' }
],
detailLogsColumns: [
{
title: '物料名称',
title: '用品名称',
key: 'material_name'
},
{
......@@ -645,9 +645,9 @@ export default {
this.inboundSelectedDetails = list || []
},
handleInboundMaterialSelectorOk (selectedRows) {
console.log('选择的物料信息' + selectedRows)
console.log('选择的用品信息' + selectedRows)
if (!Array.isArray(selectedRows) || selectedRows.length === 0) {
this.$Message.warning('未选择物料')
this.$Message.warning('未选择用品')
return
}
selectedRows.forEach(sel => {
......
......@@ -49,8 +49,8 @@
style="width: 200px"
>
<Option :value='0'>待提交</Option>
<Option :value='1'></Option>
<Option :value='9'>通过</Option>
<Option :value='1'></Option>
<Option :value='9'>通过</Option>
<Option :value='-1'>驳回</Option>
</Select>
</div>
......@@ -80,7 +80,7 @@
show-total show-sizer @on-change="pageChange('apply', $event)" @on-page-size-change="sizeChange('apply', $event)" />
</TabPane>
<TabPane label="办公用品待审核" name="pending" v-if="power.supply_approval">
<TabPane label="办公用品待审批" name="pending">
<Table :data="tables.pending" :loading="loading.pending" :columns="pendingColumns" border>
<template slot="action" slot-scope="{ row }">
<Button size="small" type="primary" @click="openApproveModal(row)">处理</Button>
......@@ -90,7 +90,7 @@
show-total show-sizer @on-change="pageChange('pending', $event)" @on-page-size-change="sizeChange('pending', $event)" />
</TabPane>
<TabPane label="办公用品审核历史查询" name="history" v-if="power.supply_view">
<TabPane label="办公用品审批历史查询" name="history">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="18">
......@@ -137,8 +137,8 @@
style="width: 200px"
>
<Option :value= "0">待提交</Option>
<Option :value= "1"></Option>
<Option :value= "9">通过</Option>
<Option :value= "1"></Option>
<Option :value= "9">通过</Option>
<Option :value= "-1">驳回</Option>
</Select>
</Col>
......@@ -157,156 +157,6 @@
<Page class="page_style" :total="pagers.history.totalRecord" :current="pagers.history.pageNo" :page-size="pagers.history.pageSize"
show-total show-sizer @on-change="pageChange('history', $event)" @on-page-size-change="sizeChange('history', $event)" />
</TabPane>
<TabPane label="办公用品入库" name="inbound" v-if="power.inventory_manager">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="18">
<span>入库单号:</span>
<Input
v-model="filters.inbound.inbound_no"
placeholder="请输入入库单号:"
class="mr10"
style="width: 200px"
/>
<span>批次号:</span>
<Input
v-model="filters.inbound.batch_no"
placeholder="请输入批次号:"
class="mr10"
style="width: 200px"
/>
<span>开始时间:</span>
<DatePicker
v-model="filters.inbound.startDate"
type="date"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
placeholder="请选择开始时间"
class="mr10"
style="width: 150px"
/>
<span>结束时间:</span>
<DatePicker
v-model="filters.inbound.endDate"
type="date"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
placeholder="请选择结束时间"
class="mr10"
style="width: 150px"
/>
<br>
<div class="mt8">
<span>入库类型:</span>
<Select
v-model="filters.inbound.inbound_type"
placeholder="请选择入库类型"
class="mr10"
style="width: 200px"
clearable
>
<Option :value='1'>手工入库</Option>
<Option :value='2'>归还入库</Option>
</Select>
<span>入库状态:</span>
<Select
v-model="filters.inbound.inbound_status"
placeholder="请选择入库状态"
class="mr10"
style="width: 200px"
clearable
>
<Option :value='0'>待入库</Option>
<Option :value='1'>已入库</Option>
</Select>
</div>
</Col>
<Col span="6" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('inbound')">搜索</Button>
<Button type="primary" class="mr10" @click="handleReset('inbound')">重置</Button>
<Button type="primary" class="mr10" @click="openInboundModal">新增入库</Button>
<Button type="primary" class="mr10" v-if="showImport()" @click="importShow = true">导入</Button>
</Col>
</Row>
</div>
<Table :data="tables.inbound" :loading="loading.inbound" :columns="inboundColumns" border>
<template slot="action" slot-scope="{ row }">
<Poptip confirm title="确认执行入库?" transfer @on-ok="doInbound(row)" v-if="canEditInbound(row)">
<Button size="small" type="success" class="mr5">入库</Button>
</Poptip>
<Button size="small" @click="openInboundDetail(row)" class="mr5">详细</Button>
<Button size="small" type="primary" @click="openEditInbound(row)" v-if="canEditInbound(row)" class="mr5">修改</Button>
<Poptip confirm title="确认删除?" transfer @on-ok="deleteInbound(row)" v-if="canEditInbound(row)">
<Button size="small" type="error">删除</Button>
</Poptip>
</template>
</Table>
<Page class="page_style" :total="pagers.inbound.totalRecord" :current="pagers.inbound.pageNo" :page-size="pagers.inbound.pageSize"
show-total show-sizer @on-change="pageChange('inbound', $event)" @on-page-size-change="sizeChange('inbound', $event)" />
</TabPane>
<TabPane label="办公用品归还" name="return" v-if="power.inventory_manager">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="18">
<span>部门:</span>
<Input
v-model="filters.return.department_name"
placeholder="请输入部门"
class="mr10"
style="width: 200px"
/>
<span>申领人:</span>
<Input
v-model="filters.return.applicant_name"
placeholder="请输入申请人"
class="mr10"
style="width: 200px"
/>
</Col>
<Col span="6" class="action-col">
<Button type="primary" class="mr10" @click="handleSearch('return')">搜索</Button>
<Button type="primary" class="mr10" @click="handleReset('return')" >重置</Button>
</Col>
</Row>
</div>
<Table :data="tables.return" :loading="loading.return" :columns="returnColumns" border>
<template slot="action" slot-scope="{ row }">
<Button size="small" type="primary" @click="openReturnModal(row)">归还</Button>
</template>
</Table>
<Page class="page_style" :total="pagers.return.totalRecord" :current="pagers.return.pageNo" :page-size="pagers.return.pageSize"
show-total show-sizer @on-change="pageChange('return', $event)" @on-page-size-change="sizeChange('return', $event)" />
</TabPane>
<TabPane label="库存查询" name="inventory" v-if="power.inventory_manager">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="18">
<span>物料编码:</span>
<Input
v-model="filters.inventory.material_code"
placeholder="请输入物料编码"
class="mr10"
style="width: 200px"
/>
<span>物料名称:</span>
<Input
v-model="filters.inventory.material_name"
placeholder="请输入物料名称"
class="mr10"
style="width: 200px"
/>
</Col>
<Col span="6" class="action-col">
<Button type="primary" class="mr10" @click="handleSearch('inventory')">搜索</Button>
<Button type="primary" class="mr10" @click="handleReset('inventory')">重置</Button>
</Col>
</Row>
</div>
<Table :data="tables.inventory" :loading="loading.inventory" :columns="inventoryColumns" border />
</TabPane>
</Tabs>
<!-- 申请弹窗 -->
......@@ -363,7 +213,7 @@
</div>
</Modal>
<!-- 审弹窗 -->
<!-- 审弹窗 -->
<Modal v-model="approveModal.visible" title="申请处理" width="700">
<Form :model="approveModal.record" :label-width="120">
<FormItem label="申请单号"><span>{{ approveModal.record.application_no }}</span></FormItem>
......@@ -391,106 +241,11 @@
<Row class="mt8"><Col span="24"><p><strong>预计归还日期:</strong>{{ formatDate(detailModal.data.expected_return_date) }}</p></Col></Row>
<Row class="mt8"><Col span="24"><h4>明细</h4></Col></Row>
<Table :data="detailModal.details" :columns="detailDetailColumns" size="small" border />
<Row class="mt8"><Col span="24"><h3><strong>审批详情</strong></h3></Col></Row><br>
<!-- <Table :data="detailModal.logs" :columns="detailLogsColumns" size="small" border /> -->
<Row :gutter="16"><Col span="12"><p><strong>审批人:</strong>{{ detailModal.data.approver_name }}</p></Col><Col span="12"><p><strong>审批结果:</strong>{{ approvalStatusMap[detailModal.data.approval_status] }}</p></Col></Row>
<Row class="mt8"><Col span="24"><p><strong>审批意见:</strong>{{ detailModal.data.approval_opinion }}</p></Col></Row>
<Row class="mt8"><Col span="24"><h4>审批记录</h4></Col></Row>
<Table :data="detailModal.logs" :columns="detailLogsColumns" size="small" border />
</div>
<div slot="footer"><Button type="primary" @click="detailModal.visible=false">关闭</Button></div>
</Modal>
<!-- 入库弹窗 -->
<Modal v-model="inboundModal.visible" :title="inboundModal.isEdit ? '修改入库' : '新增入库'" width="800">
<Form :model="inboundModal.form" :rules="inboundRules" :label-width="120" ref="inboundForm">
<FormItem label="入库单号" prop="inbound_no">
<Input v-model="inboundModal.form.inbound_no" />
</FormItem>
<FormItem label="批次号" prop="batch_no">
<Input v-model="inboundModal.form.batch_no" />
</FormItem>
<FormItem label="入库日期" prop="inbound_date">
<DatePicker
v-model="inboundModal.form.inbound_date"
type="date"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
placeholder="请选择入库日期"
/>
</FormItem>
<FormItem label="存放位置">
<Input v-model="inboundModal.form.storage_location" />
</FormItem>
<FormItem label="备注">
<Input type="textarea" v-model="inboundModal.form.remark" :rows="2" />
</FormItem>
<h4>明细(从物料库选择)
<div style="float: right;">
<Button size="small" class="mr5" type="primary" @click="showInboundMaterialSelector = true">添加</Button>
<Button size="small" type="error" @click="deleteSelectedInboundDetails">删除</Button>
</div>
</h4>
<Table :data="inboundModal.details" :columns="inboundDetailColumns" size="small" border @on-selection-change="onInboundDetailSelectionChange" style="width: 100%" />
<MaterialSelector v-model="showInboundMaterialSelector" :selected="[]" @on-ok="handleInboundMaterialSelectorOk" @cancel="showInboundMaterialSelector = false" />
</Form>
<div slot="footer">
<Button @click="inboundModal.visible=false">取消</Button>
<Button type="primary" :loading="inboundModal.saving" @click="saveInbound">保存</Button>
</div>
</Modal>
<!-- 归还弹窗 -->
<Modal v-model="returnModal.visible" title="归还物品" width="700">
<Form :model="returnModal.record" :label-width="120">
<FormItem label="申请单号"><span>{{ returnModal.record.application_no }}</span></FormItem>
<FormItem label="申请人"><span>{{ returnModal.record.applicant_name }}</span></FormItem>
<FormItem label="归还明细">
<Table :data="returnModal.details" :columns="returnDetailColumns" size="small" border />
</FormItem>
</Form>
<div slot="footer">
<Button @click="returnModal.visible=false">取消</Button>
<Button type="primary" :loading="returnModal.saving" @click="confirmReturn">归还</Button>
</div>
</Modal>
<!-- 入库详情弹窗 -->
<Modal v-model="inboundDetailModal.visible" title="入库/归还详情" width="720">
<Spin fix v-if="inboundDetailModal.loading"></Spin>
<div v-else>
<Row :gutter="16"><Col span="12"><p><strong>单号:</strong>{{ inboundDetailModal.data.inbound_no || inboundDetailModal.data.application_no }}</p></Col></Row>
<Row class="mt8"><Col span="24"><h4>明细</h4></Col></Row>
<Table :data="inboundDetailModal.details" :columns="inboundDetailDetailColumns" size="small" border />
</div>
<div slot="footer"><Button type="primary" @click="inboundDetailModal.visible=false">关闭</Button></div>
</Modal>
<!-- 导入物料弹窗 -->
<Modal v-model="importShow" title="入库导入" width="360">
<Row type="flex" justify="center" align="middle">
<Col span="10">
<div style="text-align: center;">
<Upload
action="#"
:show-upload-list="false"
:before-upload="handleBeforeUpload"
:format="['xls','xlsx']"
accept=".xls,.xlsx"
:max-size="4096">
<Button icon="ios-cloud-upload-outline" type="primary" :loading="impBtnDisabled">导入</Button>
</Upload>
</div>
</Col>
<Col span="12">
<div style="text-align: center;">
<Button type="primary" @click="tplDownload" style="margin-right: 10px;">下载模板</Button>
</div>
</Col>
</Row>
<div slot="footer">
<Button size="large" type="text" @click="importShow = false">关闭</Button>
</div>
</Modal>
<!-- <Modal v-model="approverModal.visible" title="选择审批人" width="500">
<Form :model="approverModal.form" :label-width="80">
<FormItem label="审批人:" required>
......@@ -536,22 +291,10 @@ import {
approveBorrow,
rejectBorrow,
getBorrowById,
getSupplyApproval,
getInboundList,
saveInbound,
deleteInbound,
doInbound,
getPendingReturnList,
processReturn,
getInventoryList,
getInboundById,
getPendingReturnById,
importInbound,
getUserDmPermissionList
getSupplyApproval
} from '@/api/key-dm'
import MaterialSelector from '@/view/key-person/key_dm_conf/materialSelector.vue'
import { normalizeVisitTimeValue } from '@/view/key-person/key_dm_conf/dates.js'
import axios from 'axios'
export default {
name: 'key-dm-inventory-index',
......@@ -567,17 +310,6 @@ export default {
{ required: true, message: '请选择审批人', trigger: 'change' }
]
},
inboundRules: {
inbound_no: [
{ required: true, message: '请填写入库单号', trigger: 'blur' }
],
batch_no: [
{ required: true, message: '请选择批次号', trigger: 'blur' }
],
inbound_date: [
{ required: true, message: '请选择入库日期' }
]
},
approverModal: {
visible: false,
submitting: false,
......@@ -589,30 +321,16 @@ export default {
editingRowIndex: -1,
editingCellField: '',
// 审批状态映射字典
approvalStatusMap: { '0': '待提交', '1': '审核中', '9': '审核通过', '-1': '驳回' },
approvalStatusMap: { '0': '待提交', '1': '审批中', '9': '审批通过', '-1': '驳回' },
opTypeMap: { '1': '入库', '2': '出库' },
activeTab: 'apply',
filters: {
apply: { application_no: '', applicant_name: '', approval_status: null, startDate: null, endDate: null },
history: { application_no: '', applicant_name: '', approval_status: null, startDate: null, endDate: null },
inbound: {
inbound_no: null,
batch_no: null,
startDate: null,
endDate: null,
inbound_type: null,
inbound_status: null
},
return: {
application_no: '',
applicant_name: ''
},
inventory: {
material_name: ''
}
history: { application_no: '', applicant_name: '', approval_status: null, startDate: null, endDate: null }
},
tables: { apply: [], pending: [], history: [], inbound: [], return: [], inventory: [] },
pagers: { apply: { pageNo: 1, pageSize: 10, totalRecord: 0 }, pending: { pageNo: 1, pageSize: 10, totalRecord: 0 }, history: { pageNo: 1, pageSize: 10, totalRecord: 0 }, inbound: { pageNo: 1, pageSize: 10, totalRecord: 0 }, return: { pageNo: 1, pageSize: 10, totalRecord: 0 }, inventory: { pageNo: 1, pageSize: 10, totalRecord: 0 } },
loading: { apply: false, pending: false, history: false, inbound: false, return: false, inventory: false },
tables: { apply: [], pending: [], history: [] },
pagers: { apply: { pageNo: 1, pageSize: 10, totalRecord: 0 }, pending: { pageNo: 1, pageSize: 10, totalRecord: 0 }, history: { pageNo: 1, pageSize: 10, totalRecord: 0 } },
loading: { apply: false, pending: false, history: false },
applyColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
// { title: '申请单号', key: 'application_no', align: 'center' },
......@@ -715,8 +433,8 @@ export default {
// 模态窗口内表格列定义
applyDetailColumns: [
{ type: 'selection', width: 60 },
{ title: '物料编码', key: 'material_code' },
{ title: '物料名称', key: 'material_name' },
{ title: '用品编码', key: 'material_code' },
{ title: '用品名称', key: 'material_name' },
{
title: '申请数量',
key: 'apply_quantity',
......@@ -788,29 +506,24 @@ export default {
}
}
},
{ title: '单位', key: 'unit' }
{ title: '单位', key: 'unit' },
{ title: '备注', key: 'issue_remark' }
],
approveDetailColumns: [
{ title: '物料名称', key: 'material_name' },
{ title: '用品名称', key: 'material_name' },
{ title: '申请数量', key: 'apply_quantity' },
{ title: '单位', key: 'unit' },
{
title: '可用库存',
key: 'available_quantity',
render: (h, { row }) => {
return h('span', row.available_quantity || 0)
}
}
{ title: '可用库存', key: 'available_quantity' }
],
detailDetailColumns: [
{ title: '物料编码', key: 'material_code' },
{ title: '物料名称', key: 'material_name' },
{ title: '用品编码', key: 'material_code' },
{ title: '用品名称', key: 'material_name' },
{ title: '申请数量', key: 'apply_quantity' },
{ title: '单位', key: 'unit' },
{ title: '已归还', key: 'returned_quantity' }
],
detailLogsColumns: [
{ title: '物料名称', key: 'material_name' },
{ title: '用品名称', key: 'material_name' },
{
title: '操作类型',
key: 'op_type',
......@@ -827,207 +540,21 @@ export default {
return h('span', this.formatDateTime(row.create_time) || row.create_time || '-')
}
},
{ title: '操作人', key: 'name' }
],
// 入库相关表格列定义
inboundColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
{ title: '入库单号', key: 'inbound_no', align: 'center' },
{ title: '批次号', key: 'batch_no', align: 'center' },
{
title: '入库日期',
key: 'inbound_date',
align: 'center',
render: (h, { row }) => {
return h('span', this.formatDate(row.inbound_date) || row.inbound_date || '')
}
},
{
title: '类型',
key: 'inbound_type',
align: 'center',
render: (h, { row }) => {
return h('span', this.inboundTypeMap[row.inbound_type] || row.inbound_type || '-')
}
},
{
title: '状态',
key: 'inbound_status',
align: 'center',
render: (h, { row }) => {
return h('span', this.inboundStatusMap[row.inbound_status] || row.inbound_status || '-')
}
},
{ title: '操作', slot: 'action', width: 260, align: 'center' }
],
returnColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
{ title: '部门', key: 'department_name', align: 'center' },
{ title: '申领人', key: 'applicant_name', align: 'center' },
{ title: '申领用途', key: 'borrow_purpose', align: 'center' },
{
title: '提交审批时间',
key: 'submit_time',
align: 'center',
render: (h, { row }) => {
return h('span', this.formatDateTime(row.submit_time) || '-')
}
},
{
title: '审批通过时间',
key: 'approval_time',
align: 'center',
render: (h, { row }) => {
return h('span', this.formatDateTime(row.approval_time) || '-')
}
},
{
title: '发放状态',
key: 'issue_status',
align: 'center',
render: (h, { row }) => {
return h('span', this.issueStatusType[row.issue_status] || row.issue_status || '-')
}
},
{ title: '操作', slot: 'action', width: 140, align: 'center' }
],
inventoryColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
{ title: '物料编码', key: 'material_code', align: 'center' },
{ title: '物料名称', key: 'material_name', align: 'center' },
{ title: '总量', key: 'total_quantity', align: 'center' },
{ title: '借出', key: 'borrowed_quantity', align: 'center' },
{
title: '可用',
key: 'available_quantity',
align: 'center',
render: (h, { row }) => {
return h('span', (row.total_quantity - row.borrowed_quantity - row.damaged_quantity) || 0)
}
}
],
// 入库相关模态窗口内表格列定义
inboundDetailColumns: [
{ type: 'selection', width: 60 },
{ title: '物料编码', key: 'material_code', minWidth: 120 },
{ title: '物料名称', key: 'material_name', minWidth: 150 },
{ title: '入库数量',
key: 'inbound_quantity',
minWidth: 120,
render: (h, params) => {
return h('InputNumber', {
props: {
value: params.row.inbound_quantity || 0,
min: 0,
precision: 0
},
style: { width: '100px' },
on: {
'on-change': (val) => {
this.$set(this.inboundModal.details[params.index], 'inbound_quantity', Number(val) || 0)
const quantity = Number(val) || 0
const unitPrice = this.inboundModal.details[params.index].unit_price || 0
this.$set(this.inboundModal.details[params.index], 'total_amount', (quantity * unitPrice).toFixed(2))
}
}
})
}
},
{ title: '单价',
key: 'unit_price',
minWidth: 120,
render: (h, params) => {
return h('InputNumber', {
props: {
value: params.row.unit_price || 0,
min: 0,
precision: 2
},
style: { width: '100px' },
on: {
'on-change': (val) => {
this.$set(this.inboundModal.details[params.index], 'unit_price', Number(val) || 0)
const quantity = this.inboundModal.details[params.index].inbound_quantity || 0
const unitPrice = Number(val) || 0
this.$set(this.inboundModal.details[params.index], 'total_amount', (quantity * unitPrice).toFixed(2))
}
}
})
}
},
{
title: '总金额',
key: 'total_amount',
minWidth: 120,
render: (h, params) => {
const quantity = Number(params.row.inbound_quantity) || 0
const unitPrice = Number(params.row.unit_price) || 0
const total = (quantity * unitPrice).toFixed(2)
this.$set(params.row, 'total_amount', total)
return h('span', total)
}
}
],
returnDetailColumns: [
{ title: '物料名称', key: 'material_name' },
{ title: '申请数量', key: 'apply_quantity' },
{ title: '归还数量', key: 'returned_quantity' }
],
inboundDetailDetailColumns: [
{ title: '物料编码', key: 'material_code' },
{ title: '物料名称', key: 'material_name' },
{ title: '数量', key: 'inbound_quantity' },
{ title: '单价', key: 'unit_price' }
{ title: '操作人', key: 'create_by' }
],
// 选择器控制与已选明细
showMaterialSelector: false,
showInboundMaterialSelector: false,
applySelectedDetails: [],
applyModal: { visible: false, isEdit: false, saving: false, form: {}, details: [], isEditing: false },
approveModal: { visible: false, record: {}, details: [], opinion: '', submitting: false },
detailModal: { visible: false, loading: false, data: {}, details: [], logs: [] },
// 入库相关数据
importShow: false,
impBtnDisabled: false,
inboundTypeMap: { '1': '手工', '2': '归还' },
inboundStatusMap: { '0': '待入库', '1': '已入库' },
opTypeMap: { '1': '入库', '2': '出库' },
issueStatusType: { '0': '待发放', '1': '已发放' },
inboundSelectedDetails: [],
inboundModal: { visible: false, isEdit: false, saving: false, form: {}, details: [] },
returnModal: { visible: false, record: {}, details: [], saving: false },
inboundDetailModal: { visible: false, loading: false, data: {}, details: [], logs: [] },
power: {
leave_approval: false, // 请假审核
leave_view: false, // 请假查询(统计)
supply_approval: false, // 用品申领审核
supply_view: false, // 用品查询(统计)
inventory_manager: false // 库存管理
}
detailModal: { visible: false, loading: false, data: {}, details: [], logs: [] }
}
},
created () {
this.fetchList('apply')
this.fetchApproverList()
this.getUserDmPermission()
},
methods: {
getUserDmPermission () {
getUserDmPermissionList({}).then(ret => {
console.log('ret', ret)
if (ret.data && ret.data.errcode === 0) {
const data = ret.data.data || []
for (let i = 0; i < data.length; i++) {
var ele = data[i]
if (this.power.hasOwnProperty(ele.code)) {
this.power[ele.code] = true
}
}
} else {
this.$Notice.error({ title: '查询失败', desc: ret.data.errmsg || '请稍后重试' })
}
})
},
// 新增:获取审批人列表
async fetchApproverList () {
await getSupplyApproval().then(ret => {
......@@ -1123,12 +650,9 @@ export default {
this.activeTab = name
if (name === 'pending') this.fetchList('pending')
if (name === 'history') this.fetchList('history')
if (name === 'inbound') this.fetchList('inbound')
if (name === 'return') this.fetchList('return')
if (name === 'inventory') this.fetchList('inventory')
},
fetchList (tab) {
const apiMap = { apply: getBorrowList, pending: getPendingBorrowList, history: getHistoryBorrowList, inbound: getInboundList, return: getPendingReturnList, inventory: getInventoryList }
const apiMap = { apply: getBorrowList, pending: getPendingBorrowList, history: getHistoryBorrowList }
const api = apiMap[tab]; if (!api) return
this.loading[tab] = true
// 清理空值,防止参数传递问题
......@@ -1158,21 +682,7 @@ export default {
(this.pagers[tab] || {}).pageNo = 1
this.fetchList(tab)
},
handleReset (tab) {
if (tab === 'inbound') {
this.filters[tab] = {
inbound_no: null,
batch_no: null,
startDate: null,
endDate: null,
inbound_type: null,
inbound_status: null
}
} else {
this.filters[tab] = {}
}
; (this.pagers[tab] || {}).pageNo = 1; this.fetchList(tab)
},
handleReset (tab) { this.filters[tab] = {}; (this.pagers[tab] || {}).pageNo = 1; this.fetchList(tab) },
pageChange (tab, pageNo) { (this.pagers[tab] || {}).pageNo = pageNo; this.fetchList(tab) },
sizeChange (tab, size) { (this.pagers[tab] || {}).pageSize = size; (this.pagers[tab] || {}).pageNo = 1; this.fetchList(tab) },
......@@ -1230,11 +740,10 @@ export default {
},
handleMaterialSelectorOk (selectedRows) {
if (!Array.isArray(selectedRows) || selectedRows.length === 0) {
this.$Message.warning('未选择物料')
this.$Message.warning('未选择用品')
return
}
selectedRows.forEach(sel => {
console.log(sel)
const code = sel.material_code
const exist = this.applyModal.details.find(d => d.material_code === code)
if (exist) {
......@@ -1295,7 +804,6 @@ export default {
this.$Message.success('保存成功')
this.applyModal.visible = false
this.fetchList('apply')
console.log(this.applyModal.saving)
} else {
this.$Notice.error({ title: '保存失败', desc: ret.data && ret.data.errmsg })
}
......@@ -1317,12 +825,10 @@ export default {
return (row.approval_status === 1 && row.applicant_id === userId)
},
submit (row) {
console.log(row)
// 暂存当前申请ID
this.approverModal.currentBorrowId = row.id
// 清空上次选择的审批人
this.approverModal.form.approver_id = ''
this.approverModal.form.approver_id = row.approver_id
// 获取审批人列表
this.fetchApproverList()
// 打开审批人选择弹窗
......@@ -1332,6 +838,7 @@ export default {
// 新增:确认选择审批人后提交
confirmSubmitWithApprover () {
const approverId = this.approverModal.form.approver_id
this.approverModal.submitting = true
// 调用提交接口,携带审批人ID(需后端接口支持)
submitBorrow({
......@@ -1365,30 +872,15 @@ export default {
},
confirmApprove () {
this.approveModal.submitting = true
approveBorrow({ id: this.approveModal.record.id, comment: this.approveModal.opinion }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.$Message.success('已通过,已生成出库并更新库存')
this.approveModal.visible = false; this.fetchList('pending')
this.fetchList('apply')
} else {
this.$Notice.error({ title: '操作失败', desc: ret.data && ret.data.errmsg })
}
approveBorrow({ id: this.approveModal.record.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) { this.$Message.success('已通过,已生成出库并更新库存'); this.approveModal.visible = false; this.fetchList('pending'); this.fetchList('apply') } else this.$Notice.error({ title: '操作失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.approveModal.submitting = false })
},
confirmReject () {
if (!this.approveModal.opinion || !this.approveModal.opinion.trim()) {
this.$Message.warning('请输入驳回原因')
return
}
if (!this.approveModal.opinion || !this.approveModal.opinion.trim()) { this.$Message.warning('请输入驳回原因'); return }
this.approveModal.submitting = true
rejectBorrow({ id: this.approveModal.record.id, comment: this.approveModal.opinion }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.$Message.success('已驳回')
this.approveModal.visible = false
this.fetchList('pending')
} else {
this.$Notice.error({ title: '驳回失败', desc: ret.data && ret.data.errmsg })
}
if (ret.data && ret.data.errcode === 0) { this.$Message.success('已驳回'); this.approveModal.visible = false; this.fetchList('pending') } else this.$Notice.error({ title: '驳回失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.approveModal.submitting = false })
},
......@@ -1402,243 +894,6 @@ export default {
this.detailModal.logs = ret.data.data.logs || []
} else this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.detailModal.loading = false })
},
// 入库相关方法
showImport () {
return true
},
async tplDownload () {
try {
const response = await axios.post(
'/api/ac/jilinsscgsdp/keyDmInbound/templateDownload',
{},
{
responseType: 'blob'
}
)
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.href = url
link.download = '入库导入模板.xlsx'
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
} catch (error) {
console.error('下载失败:', error)
}
},
handleBeforeUpload (file) {
const fileExt = file.name.split('.').pop().toLocaleLowerCase()
if (fileExt === 'xlsx' || fileExt === 'xls') {
this.readFile(file)
} else {
this.$Notice.warning({
title: '文件类型错误',
desc: '文件:' + file.name + '不是EXCEL文件,请选择后缀为.xlsx或者.xls的EXCEL文件。'
})
}
return false
},
async readFile (file) {
this.impBtnDisabled = true
const formdata = new FormData()
formdata.append('file', file)
formdata.append('fileName', file.name)
const ret = await importInbound(formdata)
if (ret && ret.data) {
this.importShow = false
this.impBtnDisabled = false
const mobj = ret.data.data || {}
let msg = '添加' + (mobj.insert || 0) + '条, 更新' + (mobj.update || 0) + '条'
if (mobj.error) {
msg += ', 缺少关键信息未能导入' + mobj.error + '条'
}
if (mobj.errInfo && mobj.errInfo !== '') {
msg += ', 以下记录导入失败:' + mobj.errInfo
}
if (mobj.errmsg && mobj.errmsg !== 'success') {
msg += mobj.errmsg
}
if ((mobj.insert || 0) + (mobj.update || 0) > 0) {
this.fetchList('inbound')
}
this.$Notice.success({
title: '导入完成!',
desc: msg
})
} else {
this.impBtnDisabled = false
this.$Notice.warning({
title: '未导入数据!',
desc: ret && ret.data && ret.data.errmsg
})
}
},
openInboundModal () {
this.inboundModal.isEdit = false
this.inboundModal.form = { inbound_no: '', batch_no: '', inbound_date: this.getTodayDate(), inbound_type: 1, storage_location: '', remark: '' }
this.inboundModal.details = []
this.inboundModal.visible = true
},
onInboundDetailSelectionChange (list) {
this.inboundSelectedDetails = list || []
},
handleInboundMaterialSelectorOk (selectedRows) {
console.log('选择的物料信息' + selectedRows)
if (!Array.isArray(selectedRows) || selectedRows.length === 0) {
this.$Message.warning('未选择物料')
return
}
selectedRows.forEach(sel => {
const code = sel.material_code
const exist = this.inboundModal.details.find(d => d.material_code === code)
if (exist) {
exist.inbound_quantity = Number(exist.inbound_quantity || 0) + 1
} else {
this.inboundModal.details.push({
material_id: sel.id,
material_code: sel.material_code,
material_name: sel.material_name,
inbound_quantity: 0,
unit_price: 0,
total_amount: 0
})
}
})
this.showInboundMaterialSelector = false
},
deleteSelectedInboundDetails () {
if (!this.inboundSelectedDetails || this.inboundSelectedDetails.length === 0) {
this.$Message.warning('请先选择要删除的明细行')
return
}
const toRemoveCodes = this.inboundSelectedDetails.map(r => r.material_code)
this.inboundModal.details = this.inboundModal.details.filter(d => !toRemoveCodes.includes(d.material_code))
this.inboundSelectedDetails = []
},
openEditInbound (row) {
this.inboundModal.isEdit = true
getInboundById({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.inboundModal.form = ret.data.data || {}
if (this.inboundModal.form.inbound_date) {
this.inboundModal.form.inbound_date = this.formatDate(this.inboundModal.form.inbound_date) || ''
} else {
this.inboundModal.form.inbound_date = this.getTodayDate()
}
this.inboundModal.details = ret.data.data.details || []
}
})
this.inboundModal.visible = true
},
saveInbound () {
this.inboundModal.saving = true
const { inbound_no, batch_no, inbound_date } = this.inboundModal.form
if (!inbound_no || !batch_no || !inbound_date) {
this.$Message.warning('入库单号、批次号、入库日期为必填项')
this.inboundModal.saving = false
return
}
if (this.inboundModal.details === null || this.inboundModal.details.length < 1) {
this.$Message.warning('请至少入库一条用品')
this.inboundModal.saving = false
return
}
const invalidDetails = this.inboundModal.details.filter(detail => {
return !detail.inbound_quantity || detail.inbound_quantity <= 0
})
if (invalidDetails.length > 0) {
this.$Message.warning('请填写有效的入库数量(必须大于0)')
this.inboundModal.saving = false
return
}
const details = this.inboundModal.details.map(detail => {
return {
material_id: detail.material_id,
material_code: detail.material_code,
material_name: detail.material_name,
inbound_quantity: Number(detail.inbound_quantity) || 0,
unit_price: Number(detail.unit_price) || 0,
total_amount: Number(detail.total_amount) || 0
}
})
const payload = {
...this.inboundModal.form,
details: details
}
console.log('发送的数据:', payload)
saveInbound(payload).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.$Message.success('保存成功')
this.inboundModal.visible = false
this.fetchList('inbound')
} else {
this.$Notice.error({
title: '保存失败',
desc: ret.data & ret.data.errmsg || '未知错误'
})
}
}).catch(error => {
console.error('保存出错:', error)
this.$Notice.error({ title: '保存出错', desc: error.message })
}).finally(() => {
this.inboundModal.saving = false
})
},
canEditInbound (row) {
const userId = this.$store.state.user.userId
return row.inbound_status === 0 && row.create_by === userId
},
deleteInbound (row) {
deleteInbound({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.$Message.success('删除成功'); this.fetchList('inbound')
} else {
this.$Notice.error({
title: '删除失败', desc: ret.data && ret.data.errmsg
})
}
})
},
doInbound (row) {
doInbound({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.$Message.success('入库成功')
this.fetchList('inbound')
this.fetchList('inventory')
} else {
this.$Notice.error({ title: '入库失败', desc: ret.data && ret.data.errmsg })
}
})
},
openReturnModal (row) {
this.returnModal.record = Object.assign({}, row)
this.returnModal.details = []
getPendingReturnById({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) this.returnModal.details = ret.data.data.details || []
})
this.returnModal.visible = true
},
confirmReturn () {
this.returnModal.saving = true
processReturn({ id: this.returnModal.record.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) { this.$Message.success('归还已入库并更新库存'); this.returnModal.visible = false; this.fetchList('return'); this.fetchList('inventory') } else this.$Notice.error({ title: '归还失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.returnModal.saving = false })
},
openInboundDetail (row) {
this.inboundDetailModal.visible = true
this.inboundDetailModal.loading = true
getInboundById({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
console.log(ret.data)
this.inboundDetailModal.data = ret.data.data || {}
this.inboundDetailModal.details = ret.data.data.details || []
this.inboundDetailModal.logs = ret.data.data.logs || []
} else this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.inboundDetailModal.loading = false })
}
}
}
......
......@@ -9,8 +9,8 @@
<DatePicker v-model="usageStart" type="date" style="min-width:110px;margin-right:20px" />
<span>结束:</span>
<DatePicker v-model="usageEnd" type="date" style="min-width:110px;margin-right:20px" />
<span>物料</span>
<Input v-model="usageMaterial" placeholder="物料名称或编码" style="width: 150px" />
<span>用品</span>
<Input v-model="usageMaterial" placeholder="用品名称或编码" style="width: 150px" />
</Col>
<Col :span="4" class="text-right">
<Button type="primary" class="mr10" @click="loadUsage">统计</Button>
......@@ -125,7 +125,7 @@ export default {
this.detailModal.columns = [
{ title: '部门', key: 'department_name', align: 'center' },
{ title: '人员', key: 'user_name', align: 'center' },
{ title: '物料', key: 'material_name', align: 'center' },
{ title: '用品', key: 'material_name', align: 'center' },
{ title: '申请数量', key: 'apply_quantity', align: 'center' },
{ title: '已归还数量', key: 'returned_quantity', align: 'center' }
]
......@@ -210,8 +210,8 @@ export default {
let statusText = ''
switch (status) {
case 0: statusText = '未提交'; break
case 1: statusText = '审中'; break
case 9: statusText = '审通过'; break
case 1: statusText = '审中'; break
case 9: statusText = '审通过'; break
case -1: statusText = '驳回'; break
default: statusText = status
}
......@@ -244,7 +244,7 @@ export default {
}
},
{
title: '物料',
title: '用品',
key: 'material_name',
align: 'center'
},
......@@ -267,8 +267,8 @@ export default {
let statusText = ''
switch (status) {
case 0: statusText = '待提交'; break
case 1: statusText = '审中'; break
case 9: statusText = '审通过'; break
case 1: statusText = '审中'; break
case 9: statusText = '审通过'; break
case -1: statusText = '驳回'; break
default: statusText = status
}
......
......@@ -28,9 +28,7 @@
<template slot="action" slot-scope="{ row }">
<Button size="small" class="mr10" @click="openDetail(row)">详细</Button>
<Button size="small" type="primary" class="mr10" @click="openEdit(row)" v-if="canEdit(row)">修改</Button>
<Poptip confirm title="确认提交?" transfer @on-ok="submit(row)" v-if="canSubmit(row)">
<Button size="small" confirm type="success" class="mr10">提交</Button>
</Poptip>
<Button size="small" type="success" class="mr10" @click="submit(row)" v-if="canSubmit(row)">提交</Button>
<Poptip confirm title="确认删除?" transfer @on-ok="deleteApply(row)" v-if="canDelete(row)">
<Button size="small" type="error" class="mr10">删除</Button>
</Poptip>
......@@ -44,7 +42,7 @@
show-total show-sizer @on-change="pageChange('apply', $event)" @on-page-size-change="sizeChange('apply', $event)" />
</TabPane>
<TabPane :label="'请假待审核'" name="pending" v-if="power.leave_approval">
<TabPane :label="'请假待审批'" name="pending">
<Table border :loading="loading.pending" :columns="pendingColumns" :data="tables.pending">
<template slot="action" slot-scope="{ row }">
<div class="action-buttons">
......@@ -56,7 +54,7 @@
show-total show-sizer @on-change="pageChange('pending', $event)" @on-page-size-change="sizeChange('pending', $event)" />
</TabPane>
<TabPane label="审核历史查询" name="history" v-if="power.leave_view">
<TabPane label="审批历史查询" name="history">
<div class="search-div">
<Row type="flex" :gutter="16" align="middle">
<Col span="18">
......@@ -64,8 +62,10 @@
<DatePicker v-model="filters.history.start_time" type="date" placeholder="开始日期" style="width: 200px" class="mr10" />
<span>结束:</span>
<DatePicker v-model="filters.history.end_time" type="date" placeholder="结束日期" style="width: 200px" class="mr10" />
<span>审批人:</span>
<Input v-model="filters.history.approver_name" placeholder="请输入审批人姓名" style="width: 200px" />
<span>状态:</span>
<Select v-model="filters.history.status" style="width: 200px">
<Option v-for="opt in statusOptionsFor('history')" :key="opt.id" :value="opt.id">{{ opt.name }}</Option>
</Select>
</Col>
<Col span="6" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('history')">搜索</Button>
......@@ -82,26 +82,22 @@
show-total show-sizer @on-change="pageChange('history', $event)" @on-page-size-change="sizeChange('history', $event)" />
</TabPane>
<TabPane label="请假申请查询" name="query" v-if="power.leave_view">
<TabPane label="请假申请查询" name="query">
<div class="search-div">
<Row type="flex" :gutter="16" align="middle">
<Col span="24">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<Col span="18">
<span>开始:</span>
<DatePicker v-model="filters.query.start_time" type="date" placeholder="开始日期" style="width: 200px" class="mr10" />
<span>结束:</span>
<DatePicker v-model="filters.query.end_time" type="date" placeholder="结束日期" style="width: 200px" class="mr10" />
<span>状态:</span>
<Select v-model="filters.query.status" style="width: 200px">
<Option v-for="opt in statusOptionsFor('history')" :key="opt.id" :value="opt.id">{{ opt.name }}</Option>
<Option v-for="opt in statusOptionsFor('query')" :key="opt.id" :value="opt.id">{{ opt.name }}</Option>
</Select>
</div>
<div>
</Col>
<Col span="6" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('query')">搜索</Button>
<Button type="primary" class="mr10" @click="handleReset('query')">重置</Button>
</div>
</div>
</Col>
</Row>
</div>
......@@ -114,7 +110,7 @@
show-total show-sizer @on-change="pageChange('query', $event)" @on-page-size-change="sizeChange('query', $event)" />
</TabPane>
<TabPane label="请假统计" name="stats" v-if="power.leave_view">
<TabPane label="请假统计" name="stats">
<div class="search-div">
<Row type="flex" :gutter="16" align="middle">
<Col span="18">
......@@ -148,7 +144,7 @@
<DatePicker v-model="applyModal.form.end_time" type="datetime" placeholder="请选择结束时间" />
</FormItem>
<FormItem label="请假时长(天)" prop="duration">
<Input v-model="applyModal.form.duration" placeholder="请输入请假时长" />
<Input v-model="applyModal.form.duration" placeholder="可手动录入或自动计算" />
</FormItem>
<FormItem label="请假事由" prop="reason">
<Input type="textarea" v-model="applyModal.form.reason" :rows="3" />
......@@ -172,7 +168,7 @@
</div>
</Modal>
<!-- 审弹窗 -->
<!-- 审弹窗 -->
<Modal v-model="approveModal.visible" title="请假审批" width="700">
<Form :model="approveModal.record" :label-width="120">
<FormItem label="申请人">
......@@ -193,18 +189,18 @@
<div class="approval-flow-section">
<div v-for="(flow, index) in approveModal.approvalFlow" :key="index" class="flow-item">
<Row class="flow-row">
<Col span="12" class="flow-col"><span class="label">人:</span><span class="value no-ellipsis" :title="flow.approver_name">{{ flow.approver_name || '-' }}</span></Col>
<Col span="12" class="flow-col"><span class="label">人:</span><span class="value no-ellipsis" :title="flow.approver_name">{{ flow.approver_name || '-' }}</span></Col>
<Col span="12" class="flow-col"><span class="label">时间:</span><span class="value no-ellipsis" :title="flow.create_time">{{ flow.create_time || '-' }}</span></Col>
</Row>
<Row class="flow-row">
<Col span="24" class="flow-col"><span class="label">转审意见:</span><span class="value" :title="flow.transfer_reason">{{ flow.transfer_reason || '-' }}</span></Col>
<Col span="24" class="flow-col"><span class="label">转审意见:</span><span class="value" :title="flow.transfer_reason">{{ flow.transfer_reason || '-' }}</span></Col>
</Row>
</div>
</div>
</FormItem>
<FormItem>
<Checkbox v-model="approveModal.transferEnabled">转审</Checkbox>
<Checkbox v-model="approveModal.transferEnabled">转审</Checkbox>
</FormItem>
<FormItem v-if="approveModal.transferEnabled" label="选择审批人" prop="transferApprover">
<Select v-if="leaveApprovalOptions.length > 0" v-model="approveModal.transferApprover" style="width: 60%" placeholder="请选择审批人">
......@@ -229,7 +225,7 @@
</div>
</Modal>
<!-- 详情弹窗(申请 + 审记录) -->
<!-- 详情弹窗(申请 + 审记录) -->
<Modal v-model="detailModal.visible" title="请假详情" width="720">
<Spin fix v-if="detailModal.loading"></Spin>
<div v-else>
......@@ -239,7 +235,6 @@
</Row>
<Row class="mt8"><Col span="24"><p><strong>请假类型:</strong>{{ detailModal.data.leave_type_name || '-' }}</p></Col></Row>
<Row class="mt8"><Col span="24"><p><strong>起止时间:</strong>{{ formatDatetime(detailModal.data.start_time) }} - {{ formatDatetime(detailModal.data.end_time) }}</p></Col></Row>
<Row class="mt8"><Col span="24"><p><strong>请假时长(天):</strong>{{ detailModal.data.duration || '-' }}</p></Col></Row>
<Row class="mt8"><Col span="24"><p><strong>请假事由:</strong>{{ detailModal.data.reason || '-' }}</p></Col></Row>
<Row class="mt8"><Col span="24"><p><strong>状态:</strong>{{ mapStatusText(detailModal.data.status) }}</p></Col></Row>
<Row v-if="detailModal.data.status === -1 || String(detailModal.data.status) === '-1'" class="mt8"><Col span="24"><p><strong>驳回理由:</strong>{{ detailModal.data.back_reason || detailModal.data.backReason || '-' }}</p></Col></Row>
......@@ -252,8 +247,8 @@
<div slot="footer"><Button type="primary" @click="detailModal.visible=false">关闭</Button></div>
</Modal>
<!-- 转审弹窗 -->
<Modal v-model="transferModal.visible" title="转审" width="520">
<!-- 转审弹窗 -->
<Modal v-model="transferModal.visible" title="转审" width="520">
<Form :model="transferModal" :label-width="120">
<FormItem label="选择审批人" prop="approver_id">
<Select v-model="transferModal.selectedApprover" style="width: 60%">
......@@ -289,7 +284,6 @@ import {
deleteLeaveById,
selectTransferApprovalList,
transferLeaveApproval,
getUserDmPermissionList,
getLeaveApprovalPermission
} from '@/api/key-dm'
// 注意:不单独调用后台的 isPending 接口,改为复用 getPendingLeaveList 获取 totalRecord 判定是否存在待办
......@@ -299,10 +293,10 @@ export default {
data () {
return {
activeTab: 'apply',
filters: { apply: { start_time: null, end_time: null, status: '' }, history: { start_time: null, end_time: null, status: '' }, query: { start_time: null, end_time: null, status: '', approver_name: '' } },
// 下拉:请假审权限可选人员
filters: { apply: { start_time: null, end_time: null, status: '' }, history: { start_time: null, end_time: null, status: '' }, query: { start_time: null, end_time: null, status: '' } },
// 下拉:请假审权限可选人员
leaveApprovalOptions: [],
// 下拉:转审审批人列表
// 下拉:转审审批人列表
transferApprovalOptions: [],
selectedApprovalUser: '',
tables: { apply: [], pending: [], history: [], query: [], stats: [] },
......@@ -317,7 +311,7 @@ export default {
{ id: '0', name: '未提交' },
{ id: '1', name: '待审批' },
{ id: '2', name: '审批中' },
{ id: '9', name: '审通过' },
{ id: '9', name: '审通过' },
{ id: '-1', name: '已驳回' }
],
applyColumns: [
......@@ -335,7 +329,6 @@ export default {
{ title: '申请人', key: 'user_name', align: 'center' },
{ title: '请假类型', key: 'leave_type_name', align: 'center' },
{ title: '起止时间', key: 'start_time', align: 'center' },
{ title: '请假时长(天)', key: 'duration', align: 'center' },
{ title: '提交时间', key: 'submit_time', align: 'center' },
{ title: '操作', slot: 'action', align: 'center', width: 120 }
],
......@@ -345,27 +338,6 @@ export default {
{ title: '请假类型', key: 'leave_type_name', align: 'center' },
{ title: '起止时间', key: 'start_time', align: 'center', render: (h, { row }) => h('span', `${row.start_time || '-'} ~ ${row.end_time || '-'}`) },
{ title: '审批完成时间', key: 'approval_complete_time', align: 'center' },
{ title: '结果', key: 'op_result', align: 'center', render: (h, { row }) => {
let statusText = '';
let statusClass = '';
switch (row.op_result) {
case 0:
statusText = '未通过';
statusClass = 'text-orange';
break;
case 1:
statusText = '通过';
statusClass = 'text-green';
break;
case -1:
statusText = '驳回';
statusClass = 'text-red';
break;
default:
statusText = '-';
}
return h('span', { class: statusClass }, statusText);
} },
{ title: '操作', slot: 'action', align: 'center', width: 100 }
],
queryColumns: [
......@@ -406,47 +378,88 @@ export default {
}
],
applyModal: { visible: false, isEdit: false, saving: false, form: {} },
// 控制是否在赋值时触发自动计算(true = 允许计算;false = 赋值时不计算)
suppressDurationCalc: false,
approveModal: { visible: false, record: {}, comment: '', submitting: false, transferEnabled: false, transferApprover: '', approvalFlow: [] },
transferModal: { visible: false, selectedApprover: '', comment: '', processing: false, record: {} },
detailModal: { visible: false, loading: false, data: {}, approvals: [] },
statsStart: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
statsEnd: new Date(),
statsDept: '',
power: {
leave_approval: false, // 请假审核
leave_view: false, // 请假查询(统计)
supply_approval: false, // 用品申领审核
supply_view: false, // 用品查询(统计)
inventory_manager: false // 库存管理
},
// 表单验证规则
applyRules: {
leave_type_id: [
{ required: true, message: '请选择请假类型', trigger: 'input' }
{ required: true, message: '请选择请假类型', trigger: 'change' }
],
start_time: [
{ required: true, message: '请选择开始时间', trigger: 'input', type: 'date' }
{ required: true, message: '请选择开始时间', trigger: 'change', type: 'date' }
],
end_time: [
{ required: true, message: '请选择结束时间', trigger: 'input', type: 'date' }
{ required: true, message: '请选择结束时间', trigger: 'change', type: 'date' }
],
duration: [
{ required: true, message: '请填写请假时长', trigger: 'input' }
{
validator: function (rule, value, callback) {
const form = (this && this.applyModal && this.applyModal.form) ? this.applyModal.form : {}
const parseToDateLocal = (v) => {
if (v === null || v === undefined || v === '') return null
if (typeof v === 'number') return new Date(v)
if (typeof v === 'string') {
if (/^\d+$/.test(v)) return new Date(Number(v))
const s = v.includes('T') ? v : v.replace(' ', 'T')
const parsed = new Date(s)
return isNaN(parsed.getTime()) ? null : parsed
}
if (v instanceof Date) return isNaN(v.getTime()) ? null : v
const parsed = new Date(v)
return isNaN(parsed.getTime()) ? null : parsed
}
// 解析起止时间的整天差
const sd = parseToDateLocal(form.start_time)
const ed = parseToDateLocal(form.end_time)
let fullDays = 0
if (sd && ed) {
const diff = ed.getTime() - sd.getTime()
fullDays = Math.floor(diff / (1000 * 60 * 60 * 24))
}
// 如果不足整天,不校验(允许用户填写任意时长)
if (fullDays < 1) { callback(); return }
// 整天或以上时,必须填写且整数部分需与计算天数一致
if (value === null || value === undefined || String(value).trim() === '') {
callback(new Error('请填写请假时长')); return
}
const num = Number(value)
if (isNaN(num) || num <= 0) { callback(new Error('请假天数必须为大于 0 的数字')); return }
const integerPart = Math.floor(num)
if (integerPart !== fullDays) {
callback(new Error(`请假整数天应为 ${fullDays} 天,请确认`)); return
}
callback()
},
trigger: 'blur'
}
],
approver_id: [
{ required: true, message: '请选择审批人', trigger: 'input' }
{ required: true, message: '请选择审批人', trigger: 'change' }
],
reason: [
{ required: true, message: '请填写请假事由', trigger: 'input' }
{ required: true, message: '请填写请假事由', trigger: 'change' }
],
emergency_phone: [
{ message: '请输入紧急电话', trigger: 'input' },
{ message: '请输入紧急电话', trigger: 'blur' },
{
pattern: /^(1[3-9]\d{9})|((0\d{2,3}-?)?\d{7,8})$/,
message: '请输入有效的电话号码',
trigger: 'input'
trigger: 'blur'
}
]
],
// (必填性与具体数值由上面的 validator 决定)
}
}
},
......@@ -456,10 +469,23 @@ export default {
this.loadLeaveApprovalOptions()
// 加载待审批计数,用于页面顶部/选项卡徽章显示
this.loadPendingCount()
this.getUserDmPermission()
// 将 duration 的 validator 绑定到组件实例的方法,确保内部能访问 this.applyModal.form
this.applyRules.duration = [{ validator: this.validateDuration, trigger: 'blur' }]
},
watch: {
// 监听开始时间和结束时间变化,自动计算天数
'applyModal.form.start_time' (newVal) {
if (this.suppressDurationCalc) return
// 用户修改起始时间时,先清空已有时长(避免与新时间不符),再进行自动计算
if (this.applyModal && this.applyModal.form) this.applyModal.form.duration = ''
this.calculateDuration()
},
'applyModal.form.end_time' (newVal) {
if (this.suppressDurationCalc) return
// 用户修改结束时间时,先清空已有时长(避免与新时间不符),再进行自动计算
if (this.applyModal && this.applyModal.form) this.applyModal.form.duration = ''
this.calculateDuration()
},
// 当弹窗关闭时清理选择的审批人,确保下次打开时下拉框能正确刷新显示
'applyModal.visible' (val) {
if (!val && this.applyModal && this.applyModal.form) {
......@@ -477,7 +503,7 @@ export default {
},
'approveModal.visible' (val) {
if (!val && this.approveModal) {
// 关闭审批弹窗时清空审批流程和转审相关字段,避免残留数据影响下次打开
// 关闭审批弹窗时清空审批流程和转审相关字段,避免残留数据影响下次打开
this.approveModal.approvalFlow = []
this.approveModal.transferEnabled = false
this.approveModal.transferApprover = ''
......@@ -487,21 +513,47 @@ export default {
}
},
methods: {
getUserDmPermission () {
getUserDmPermissionList({}).then(ret => {
console.log('ret', ret)
if (ret.data && ret.data.errcode === 0) {
const data = ret.data.data || []
for (let i = 0; i < data.length; i++) {
var ele = data[i]
if (this.power.hasOwnProperty(ele.code)) {
this.power[ele.code] = true
// validator 方法:确保能通过 this 访问组件数据
validateDuration (rule, value, callback) {
const form = this.applyModal && this.applyModal.form ? this.applyModal.form : {}
const parseToDateLocal = (v) => {
if (v === null || v === undefined || v === '') return null
if (typeof v === 'number') return new Date(v)
if (typeof v === 'string') {
if (/^\d+$/.test(v)) return new Date(Number(v))
const s = v.includes('T') ? v : v.replace(' ', 'T')
const parsed = new Date(s)
return isNaN(parsed.getTime()) ? null : parsed
}
if (v instanceof Date) return isNaN(v.getTime()) ? null : v
const parsed = new Date(v)
return isNaN(parsed.getTime()) ? null : parsed
}
} else {
this.$Notice.error({ title: '查询失败', desc: ret.data.errmsg || '请稍后重试' })
const sd = parseToDateLocal(form.start_time)
const ed = parseToDateLocal(form.end_time)
let fullDays = 0
if (sd && ed) {
const diff = ed.getTime() - sd.getTime()
fullDays = Math.floor(diff / (1000 * 60 * 60 * 24))
}
})
// 如果不足整天,不校验
if (fullDays < 1) { callback(); return }
if (value === null || value === undefined || String(value).trim() === '') {
callback(new Error('请填写请假时长')); return
}
const num = Number(value)
if (isNaN(num) || num <= 0) { callback(new Error('请假天数必须为大于 0 的数字')); return }
const integerPart = Math.floor(num)
if (integerPart !== fullDays) {
callback(new Error(`请假整数天应为 ${fullDays} 天,请确认`)); return
}
callback()
},
// 返回给定 tab 的状态下拉选项:只有 apply tab 显示“未提交(0)”,其他 tab 隐藏该选项
statusOptionsFor (tab) {
......@@ -509,12 +561,36 @@ export default {
// 过滤掉 id 为 '0' 或 0 的未提交选项
return (this.statusOptions || []).filter(opt => String(opt.id) !== '0')
},
// 计算请假天数(只计算整天,忽略零点几部分)
calculateDuration () {
const form = this.applyModal.form
if (!form || !form.start_time || !form.end_time) return
const startDate = new Date(form.start_time)
const endDate = new Date(form.end_time)
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) return
if (endDate.getTime() <= startDate.getTime()) return
// 计算整天数(向下取整),只要整天 >=1 则自动填充为整数天
const diffTime = endDate.getTime() - startDate.getTime()
const fullDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
const existing = form.duration !== null && form.duration !== undefined && String(form.duration).trim() !== ''
if (fullDays >= 1) {
// 只有当表单中尚未填写时才自动填充,避免覆盖后端/用户已有值
if (!existing) form.duration = String(fullDays)
} else {
// 少于整天时不自动计算,交由用户手动填写(例如半天、几小时)
// 不覆盖已有用户填写的值
if (!existing) form.duration = ''
}
},
handleTabChange (name) {
this.activeTab = name
if (name === 'pending') this.fetchList('pending')
if (name === 'history') this.fetchList('history')
if (name === 'query') this.fetchList('query')
if (name === 'stats') this.loadStats()
},
fetchList (tab) {
const apiMap = { apply: getLeaveListByUserId, pending: getPendingLeaveList, history: getLeaveApprovalHistory, query: getLeaveList }
......@@ -661,14 +737,14 @@ export default {
* 0 => 未提交
* 1 => 待审批
* 2 => 审批中
* 9 => 审通过
* 9 => 审通过
* other => 显示原始值或通用占位
*/
mapStatusText (status) {
if (status === 0 || String(status) === '0') return '未提交'
if (status === 1 || String(status) === '1') return '待审批'
if (status === 2 || String(status) === '2') return '审批中'
if (status === 9 || String(status) === '9') return '审通过'
if (status === 9 || String(status) === '9') return '审通过'
if (status === -1 || String(status) === '-1') return '已驳回'
if (status === null || status === undefined || status === '') return '-'
return String(status)
......@@ -780,22 +856,24 @@ export default {
// 默认开始时间为当天(时分秒置为 0),精确到天
const today = new Date()
today.setHours(0, 0, 0, 0)
// 在赋值期间禁止自动计算,直到下一个 tick 再恢复,避免渲染期间 watcher 覆盖表单值
this.suppressDurationCalc = true
this.applyModal.form = { user_id: '', user_name: '', leave_type_id: '', start_time: today, end_time: '', duration_unit: 2, duration: '', reason: '', emergency_contact: '', emergency_phone: '', approver_id: this.selectedApprovalUser || '' }
this.$nextTick(() => { this.suppressDurationCalc = false })
this.applyModal.visible = true
},
openEdit (row) {
this.applyModal.isEdit = true
// 在赋值期间禁止自动计算,直到渲染稳定后再恢复,避免覆盖已有 duration
this.suppressDurationCalc = true
this.applyModal.form = Object.assign({}, row)
this.$nextTick(() => { this.suppressDurationCalc = false })
// ensure approver_id exists on form when editing
if (!this.applyModal.form.approver_id) this.applyModal.form.approver_id = row.approver_id || row.approverId || row.transfer_to_id || ''
// 保证与 leaveApprovalOptions 中 id 的类型一致(字符串)
if (this.applyModal.form.approver_id !== null && this.applyModal.form.approver_id !== undefined) {
this.applyModal.form.approver_id = String(this.applyModal.form.approver_id)
}
// 确保duration字段为字符串类型,避免数字0被验证为无效值
if (this.applyModal.form.duration !== null && this.applyModal.form.duration !== undefined) {
this.applyModal.form.duration = String(this.applyModal.form.duration)
}
// 编辑时需要设置leave_id为主表id,如果没有则使用id作为回退
this.applyModal.form.leave_id = row.leave_id || row.id
this.applyModal.visible = true
......@@ -826,6 +904,7 @@ export default {
const endDate = parseToDate(form.end_time)
if (startDate && endDate) {
if (endDate.getTime() <= startDate.getTime()) {
this.applyModal.saving = false
this.$Message.warning('结束时间必须大于开始时间')
return
}
......@@ -867,9 +946,13 @@ export default {
return status === 0
},
submit (row) {
this.$Modal.confirm({ title: '确认提交',
content: '确认提交此请假申请进入审批流程?',
onOk: () => {
submitLeaveApplication({ id: row.id, leave_id: row.leave_id }).then(ret => {
if (ret.data && ret.data.errcode === 0) { this.$Message.success('提交成功'); this.fetchList('apply') } else this.$Notice.error({ title: '提交失败', desc: ret.data && ret.data.errmsg })
})
} })
},
revoke (row) {
revokeLeaveApplication({ id: row.id, leave_id: row.leave_id }).then(ret => {
......@@ -882,11 +965,11 @@ export default {
this.approveModal.transferEnabled = false
this.approveModal.transferApprover = ''
// 仅使用 selectTransferApprovalList 接口来获取审批流程与转审选项(移除对 getLeaveById 的调用)
// 仅使用 selectTransferApprovalList 接口来获取审批流程与转审选项(移除对 getLeaveById 的调用)
selectTransferApprovalList({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
const data = ret.data.data || []
// 填充转审下拉选项
// 填充转审下拉选项
this.transferApprovalOptions = (data || []).map(it => {
const rawId = it.user_id || it.id || it.userId || ''
return {
......@@ -894,7 +977,7 @@ export default {
name: it.user_name || it.name || it.userName || it.nick || ''
}
})
// 直接使用该接口返回的数据作为审批流程展示(包含转审记录)
// 直接使用该接口返回的数据作为审批流程展示(包含转审记录)
this.approveModal.approvalFlow = (data || []).map(item => ({
// 仅显示后端返回的 approver_name(或 approverName),不再使用 transfer_to_name 回退
approver_name: item.approver_name || item.approverName || '',
......@@ -907,7 +990,7 @@ export default {
// 接口返回错误,清空数据并记录日志
this.transferApprovalOptions = []
this.approveModal.approvalFlow = []
console.error('获取转审审批人列表失败', ret.data && ret.data.errmsg)
console.error('获取转审审批人列表失败', ret.data && ret.data.errmsg)
}
}).catch(err => {
// 出错时提供默认占位并清空展示
......@@ -915,7 +998,7 @@ export default {
this.approveModal.approvalFlow = []
console.error('selectTransferApprovalList error', err)
}).finally(() => {
// 如果转审已启用且未选择任何审批人,尝试选择第一个有效 id(非空字符串)
// 如果转审已启用且未选择任何审批人,尝试选择第一个有效 id(非空字符串)
if (this.approveModal.transferEnabled) {
const firstValid = (this.transferApprovalOptions || []).find(opt => opt && opt.id !== null && opt.id !== undefined && String(opt.id).trim() !== '')
if (!this.approveModal.transferApprover) {
......@@ -1007,17 +1090,19 @@ export default {
refillApply () {
// 将批准记录数据反显到申请表单,清除 id 和 leave_id
this.applyModal.isEdit = false
// 在赋值期间禁止自动计算,直到渲染稳定后再恢复
this.suppressDurationCalc = true
this.applyModal.form = {
leave_type_id: this.approveModal.record.leave_type_id || '',
start_time: this.approveModal.record.start_time || '',
end_time: this.approveModal.record.end_time || '',
duration: this.approveModal.record.duration !== null && this.approveModal.record.duration !== undefined ? String(this.approveModal.record.duration) : '',
reason: this.approveModal.record.reason || '',
emergency_contact: this.approveModal.record.emergency_contact || '',
emergency_phone: this.approveModal.record.emergency_phone || '',
approver_id: this.approveModal.record.approver_id || this.selectedApprovalUser || ''
}
// 不保留 id 和 leave_id,保持为空
this.$nextTick(() => { this.suppressDurationCalc = false })
this.applyModal.visible = true
// 关闭审批弹窗
this.approveModal.visible = false
......@@ -1025,17 +1110,19 @@ export default {
refillApplyDirect (row) {
// 直接从列表行数据进行重新填报,清除 id 和 leave_id
this.applyModal.isEdit = false
// 在赋值期间禁止自动计算,直到渲染稳定后再恢复
this.suppressDurationCalc = true
this.applyModal.form = {
leave_type_id: row.leave_type_id || '',
start_time: row.start_time || '',
end_time: row.end_time || '',
duration: row.duration || '',
reason: row.reason || '',
emergency_contact: row.emergency_contact || '',
emergency_phone: row.emergency_phone || '',
approver_id: row.approver_id || this.selectedApprovalUser || ''
}
// 不保留 id 和 leave_id,保持为空
this.$nextTick(() => { this.suppressDurationCalc = false })
this.applyModal.visible = true
},
openDetail (row) {
......@@ -1078,32 +1165,15 @@ export default {
},
openHistoryDetail (row) { this.openDetail(row) },
renderApproveResult (h, { row }) { return row.approver_result === 1 ? '通过' : (row.approver_result === -1 ? '驳回' : '未处理') },
formatDatetime (val) {
if (!val) return '-';
try {
// 处理ISO格式时间字符串,如 "2023-12-31T10:30:00.000Z"
if (typeof val === 'string' && val.includes('T')) {
// 移除时区信息和毫秒,转换为本地时间格式
const date = new Date(val);
if (!isNaN(date.getTime())) {
const pad = (n) => (n < 10 ? '0' + n : String(n));
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' +
pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
}
}
// 如果不是ISO格式或解析失败,返回原值的T替换版本
return String(val).replace('T', ' ');
} catch (e) {
return String(val).replace('T', ' ');
}
},
formatDatetime (val) { if (!val) return '-'; return String(val).replace('T', ' ') },
loadStats () {
this.loading.stats = true
getLeaveStats({
const params = {
start: this.statsStart,
end: this.statsEnd,
department: this.statsDept
}).then(ret => {
}
getLeaveStats({ params }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
console.log('ret.data.data', ret.data.data)
this.tables.stats = ret.data.data || []
......@@ -1113,10 +1183,10 @@ export default {
}).finally(() => { this.loading.stats = false })
},
resetStats () {
// 重置日期条件为默认值并直接查询数据
this.statsStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1)
this.statsEnd = new Date()
this.loadStats()
this.statsStart = null
this.statsEnd = null
this.statsDept = ''
this.tables.stats = []
},
deleteApply (row) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论