Commit 30a96ce8 by yubin
parents a91a89c2 0bef0175
......@@ -71,3 +71,12 @@ export const getInboundById = (param) => {
data: param
})
}
// 归还详情
export const getPendingReturnById = (param) => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmInbound/getPendingReturnById',
method: 'post',
data: param
})
}
......@@ -18,6 +18,15 @@ export const getPendingBorrowList = (param) => {
})
}
// 获取历史列表
export const getHistoryBorrowList = (param) => {
return axios.request({
url: '/api/keyDmBorrow/selectHistoryList',
method: 'post',
data: param
})
}
// 保存申请(含明细)
export const saveBorrowApplication = (param) => {
return axios.request({
......
......@@ -47,8 +47,9 @@ export const dmUserOffice = (param) => {
export const getUserTypeList = (param) => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/selectList'
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/selectList',
method: 'post',
data: param
})
}
// 获取人员多选器列表(所有在职人员)
......
......@@ -167,3 +167,77 @@ export const getWorkloadDetails = (param) => {
data: param
})
}
// ===== 图表分析 =====
// 汇总统计数据
export const getSummaryStats = () => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/getSummaryStats',
method: 'post'
})
}
// 请假类型统计
export const getLeaveTypeStats = () => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/getLeaveTypeStats',
method: 'post'
})
}
// 人员请假统计
export const getLeaveUserStats = () => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/getLeaveUserStats',
method: 'post'
})
}
// 用品领用统计
export const getMaterialUsageStats = () => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/getMaterialUsageStats',
method: 'post'
})
}
// 库存预警列表
export const getStockWarningList = () => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/getStockWarningList',
method: 'post'
})
}
// ===== 部门管理 =====
export const getOrgList = (param) => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/selectOrgList',
method: 'post',
data: param
})
}
export const saveOrg = (param) => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/saveOrg',
method: 'post',
data: param
})
}
export const deleteOrg = (param) => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/deleteOrg',
method: 'post',
data: param
})
}
export const assignUsersToOrg = (param) => {
return axios.request({
url: '/api/ac/jilinsscgsdp/keyDmUserCategory/assignUsersToOrg',
method: 'post',
data: param
})
}
......@@ -254,5 +254,13 @@ export default [
title: '日常管理统计'
},
component: () => import('@/view/key-person/key_dm_inventory/stats')
},
{
path: '/key-person/dmChartAnalysis',
name: 'dmChartAnalysis',
meta: {
title: '日常数据统计分析'
},
component: () => import('@/view/key-person/key_dm_conf/chartAnalysis')
}
]
<template>
<div class="chart-analysis-wrapper">
<!-- 第一行:统计标签 -->
<div class="header-container">
<div class="title-wrapper">
<div class="title-bar"></div>
<h3 class="title-text">数据概览</h3>
</div>
</div>
<Row class="statistics-bar">
<Col span="8">
<div class="statistics-item leave-count">
<div class="stat-icon">
<Icon type="ios-calendar-outline" size="32" />
</div>
<div class="stat-content">
<div class="stat-title">请假申请次数</div>
<div class="stat-number">{{ summaryData.leaveCount }}</div>
<div class="stat-unit"></div>
</div>
</div>
</Col>
<Col span="8">
<div class="statistics-item borrow-count">
<div class="stat-icon">
<Icon type="ios-paper-outline" size="32" />
</div>
<div class="stat-content">
<div class="stat-title">办公用品申领次数</div>
<div class="stat-number">{{ summaryData.borrowCount }}</div>
<div class="stat-unit"></div>
</div>
</div>
</Col>
<Col span="8">
<div class="statistics-item inbound-count">
<div class="stat-icon">
<Icon type="ios-archive-outline" size="32" />
</div>
<div class="stat-content">
<div class="stat-title">办公用品入库次数</div>
<div class="stat-number">{{ summaryData.inboundCount }}</div>
<div class="stat-unit"></div>
</div>
</div>
</Col>
</Row>
<!-- 第二行:请假相关图表 -->
<div class="header-container">
<div class="title-wrapper">
<div class="title-bar"></div>
<h3 class="title-text">请假数据分析</h3>
</div>
</div>
<Row :gutter="16">
<Col span="10">
<Card>
<div class="chart-title">请假类型占比</div>
<div ref="leaveTypeChart" class="chart-container"></div>
</Card>
</Col>
<Col span="14">
<Card>
<div class="chart-title">月度各人员请假次数</div>
<div ref="leaveUserChart" class="chart-container"></div>
</Card>
</Col>
</Row>
<!-- 第三行:办公用品相关图表和表格 -->
<div class="header-container">
<div class="title-wrapper">
<div class="title-bar"></div>
<h3 class="title-text">办公用品数据分析</h3>
</div>
</div>
<Row :gutter="16">
<Col span="10">
<Card>
<div class="chart-title">月度各用品领用量</div>
<div ref="materialUsageChart" class="chart-container"></div>
</Card>
</Col>
<Col span="14">
<Card class="stock-warning-card">
<div class="chart-title">库存预警信息</div>
<div class="table-container">
<Table :data="stockWarningData" :columns="stockWarningColumns" size="small" border :height="chartHeight">
<template slot="threshold" slot-scope="{ row }">
<span>{{ row.min_stock }}-{{ row.max_stock }}</span>
</template>
<template slot="status" slot-scope="{ row }">
<Tag :color="row.status === 'low' ? 'red' : 'orange'">
{{ row.status === 'low' ? '库存不足' : '库存过高' }}
</Tag>
</template>
</Table>
</div>
</Card>
</Col>
</Row>
</div>
</template>
<script>
import * as echarts from 'echarts'
import {
getSummaryStats,
getLeaveTypeStats,
getLeaveUserStats,
getMaterialUsageStats,
getStockWarningList
} from '@/api/key-dm'
export default {
name: 'chart-analysis',
data () {
return {
summaryData: {
leaveCount: 0,
borrowCount: 0,
inboundCount: 0
},
leaveTypeChart: null,
leaveUserChart: null,
materialUsageChart: null,
chartHeight: 240,
stockWarningData: [],
stockWarningColumns: [
{ title: '用品名称', key: 'material_name', align: 'center', minWidth: 50 },
{ title: '现有数量', key: 'available_quantity', align: 'center', minWidth: 50 },
{ title: '阙值范围', slot: 'threshold', align: 'center', minWidth: 50 },
{ title: '状态', slot: 'status', align: 'center', minWidth: 50 }
]
}
},
mounted () {
this.loadData()
},
beforeDestroy () {
if (this.leaveTypeChart) {
this.leaveTypeChart.dispose()
}
if (this.leaveUserChart) {
this.leaveUserChart.dispose()
}
if (this.materialUsageChart) {
this.materialUsageChart.dispose()
}
},
methods: {
loadData () {
this.loadSummaryStats()
this.loadLeaveTypeStats()
this.loadLeaveUserStats()
this.loadMaterialUsageStats()
this.loadStockWarningList()
},
loadSummaryStats () {
getSummaryStats().then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.summaryData = ret.data.data || this.summaryData
}
}).catch(() => {
this.$Message.error('获取统计数据失败')
})
},
loadLeaveTypeStats () {
getLeaveTypeStats().then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.initLeaveTypeChart(ret.data.data || [])
}
}).catch(() => {
this.$Message.error('获取请假类型统计失败')
})
},
loadLeaveUserStats () {
getLeaveUserStats().then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.initLeaveUserChart(ret.data.data || [])
}
}).catch(() => {
this.$Message.error('获取人员请假统计失败')
})
},
loadMaterialUsageStats () {
getMaterialUsageStats().then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.initMaterialUsageChart(ret.data.data || [])
}
}).catch(() => {
this.$Message.error('获取用品领用统计失败')
})
},
loadStockWarningList () {
getStockWarningList().then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.stockWarningData = ret.data.data || []
}
}).catch(() => {
this.$Message.error('获取库存预警数据失败')
})
},
initLeaveTypeChart (data) {
this.$nextTick(() => {
const chartDom = this.$refs.leaveTypeChart
if (!chartDom) return
this.leaveTypeChart = echarts.init(chartDom)
const colors = ['#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#91cc75', '#fac858', '#ee6666']
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
textStyle: {
fontSize: 12
}
},
color: colors,
series: [
{
name: '请假类型',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'outside',
formatter: '{b}: {c}',
fontSize: 12
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
labelLine: {
show: true,
length: 10,
length2: 10
},
data: data.map((item, index) => ({
value: item.count,
name: item.type_name,
itemStyle: {
color: colors[index % colors.length]
}
}))
}
]
}
this.leaveTypeChart.setOption(option)
})
},
initLeaveUserChart (data) {
this.$nextTick(() => {
const chartDom = this.$refs.leaveUserChart
if (!chartDom) return
this.leaveUserChart = echarts.init(chartDom)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.map(item => item.user_name),
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '请假次数',
type: 'bar',
data: data.map(item => item.leave_count),
itemStyle: {
color: '#409EFF'
}
}
]
}
this.leaveUserChart.setOption(option)
})
},
initMaterialUsageChart (data) {
this.$nextTick(() => {
const chartDom = this.$refs.materialUsageChart
if (!chartDom) return
this.materialUsageChart = echarts.init(chartDom)
const colors = ['#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#91cc75', '#fac858', '#ee6666']
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
textStyle: {
fontSize: 12
}
},
color: colors,
series: [
{
name: '领用量',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'outside',
formatter: '{b}: {c}',
fontSize: 12
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
labelLine: {
show: true,
length: 10,
length2: 10
},
data: data.map((item, index) => ({
value: item.total_quantity,
name: item.material_name,
itemStyle: {
color: colors[index % colors.length]
}
}))
}
]
}
this.materialUsageChart.setOption(option)
})
}
}
}
</script>
<style scoped>
.chart-analysis-wrapper {
padding: 20px;
}
.header-container {
margin: 20px 0 10px 0;
}
.title-wrapper {
display: flex;
align-items: center;
}
.title-bar {
width: 4px;
height: 20px;
background-color: #409EFF;
margin-right: 10px;
}
.title-text {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.statistics-bar {
margin-bottom: 20px;
}
.statistics-item {
display: flex;
align-items: center;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
margin: 0 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.statistics-item.leave-count {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.statistics-item.borrow-count {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.statistics-item.inbound-count {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-icon {
color: white;
margin-right: 20px;
}
.stat-content {
flex: 1;
}
.stat-title {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 8px;
}
.stat-number {
font-size: 36px;
font-weight: bold;
color: white;
margin-bottom: 4px;
display: inline;
}
.stat-unit {
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
display: inline;
}
.chart-container {
width: 100%;
height: 240px;
}
.chart-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
text-align: center;
}
.stock-warning-card {
height: 315px;
display: flex;
flex-direction: column;
}
.table-container {
flex: 1;
overflow: hidden;
}
</style>
......@@ -9,17 +9,51 @@
</template>
</Table>
</TabPane>
<!-- 部门管理 -->
<TabPane label="部门管理" name="org">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="12">
<span>部门名称:</span>
<Input v-model="filters.org.org_name" placeholder="请输入" style="width: 200px" />
</Col>
<Col span="12" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('org')">搜索</Button>
<Button type="primary" class="mr10" @click="handleReset('org')">重置</Button>
<Button type="primary" @click="openAddModal('org')">新增</Button>
</Col>
</Row>
</div>
<Table border :loading="loading.org" :columns="orgColumns" :data="tables.org">
<template slot="action" slot-scope="{ row }">
<Button size="small" type="primary" class="mr5" @click="openEditModal('org', row)">修改</Button>
<Button size="small" type="info" class="mr5" @click="openUserAssignModal(row)">人员划分</Button>
<Poptip confirm title="确认删除?" transfer @on-ok="handleDelete('org', row)">
<Button size="small" type="error">删除</Button>
</Poptip>
</template>
</Table>
<Page
class="page_style"
:total="pagers.org.totalRecord"
:current="pagers.org.pageNo"
:page-size="pagers.org.pageSize"
show-total
show-sizer
@on-change="pageChange('org', $event)"
@on-page-size-change="sizeChange('org', $event)"
/>
</TabPane>
<!-- 假种管理 -->
<TabPane label="假种管理" name="leaveType">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="6">
<span>类型名称:</span>
<Input v-model="filters.leaveType.type_name" placeholder="请输入" style="width: 70%" />
</Col>
<Col span="6">
<Col span="12">
<span>类型编码:</span>
<Input v-model="filters.leaveType.type_code" placeholder="请输入" style="width: 70%" />
<Input v-model="filters.leaveType.type_code" placeholder="请输入" style="width: 200px" class="mr10" />
<span>类型名称:</span>
<Input v-model="filters.leaveType.type_name" placeholder="请输入" style="width: 200px" />
</Col>
<Col span="12" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('leaveType')">搜索</Button>
......@@ -52,13 +86,11 @@
<TabPane label="办公用品分类管理" name="materialCategory">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="6">
<span>分类名称:</span>
<Input v-model="filters.materialCategory.category_name" placeholder="请输入" style="width: 70%" />
</Col>
<Col span="6">
<Col span="12">
<span>分类编码:</span>
<Input v-model="filters.materialCategory.category_code" placeholder="请输入" style="width: 70%" />
<Input v-model="filters.materialCategory.category_code" placeholder="请输入" style="width: 200px" class="mr10" />
<span>分类名称:</span>
<Input v-model="filters.materialCategory.category_name" placeholder="请输入" style="width: 200px" />
</Col>
<Col span="12" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('materialCategory')">搜索</Button>
......@@ -91,19 +123,15 @@
<TabPane label="办公用品管理" name="material">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="6">
<span>名称:</span>
<Input v-model="filters.material.material_name" placeholder="请输入" style="width: 70%" />
</Col>
<Col span="6">
<span>编码:</span>
<Input v-model="filters.material.material_code" placeholder="请输入" style="width: 70%" />
</Col>
<Col span="6">
<Col span="18">
<span>分类:</span>
<Select v-model="filters.material.category_id" clearable style="width: 70%">
<Select v-model="filters.material.category_id" clearable style="width: 200px" class="mr10">
<Option v-for="cat in materialCategoryOptions" :key="cat.id" :value="cat.id">{{ cat.category_name }}</Option>
</Select>
<span>编码:</span>
<Input v-model="filters.material.material_code" placeholder="请输入" style="width: 200px" class="mr10" />
<span>名称:</span>
<Input v-model="filters.material.material_name" placeholder="请输入" style="width: 200px" />
</Col>
<Col span="6" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('material')">搜索</Button>
......@@ -180,27 +208,12 @@
<Input type="textarea" :rows="3" v-model="modals.leaveType.form.remark" placeholder="请输入类型描述" />
</FormItem>
<Row :gutter="16">
<Col span="12">
<FormItem label="基准天数" prop="base_days">
<InputNumber v-model="modals.leaveType.form.base_days" :min="0" :precision="2" style="width: 100%" />
</FormItem>
</Col>
<Col span="12">
<!-- <Col span="12">
<FormItem label="年度上限" prop="max_days_per_year">
<InputNumber v-model="modals.leaveType.form.max_days_per_year" :min="0" :precision="2" style="width: 100%" />
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<FormItem label="需要审批">
<Select v-model="modals.leaveType.form.need_approval" style="width: 100%">
<Option :value="1">需要</Option>
<Option :value="0">不需要</Option>
</Select>
</FormItem>
</Col>
<Col span="12">
</Col> -->
<Col span="24">
<FormItem label="排序号" prop="order_no">
<InputNumber v-model="modals.leaveType.form.order_no" :min="0" style="width: 100%" />
</FormItem>
......@@ -356,9 +369,55 @@
</div>
</Modal>
<!-- 部门管理弹窗 -->
<Modal v-model="modals.org.visible" :title="modals.org.isEdit ? '修改部门' : '新增部门'" width="600" :mask-closable="false">
<Form :label-width="120" :model="modals.org.form" :rules="modals.org.rules" ref="orgForm">
<!-- <FormItem label="部门编码" prop="org_code">
<Input v-model="modals.org.form.org_code" placeholder="请输入部门编码" />
</FormItem> -->
<FormItem label="部门名称" prop="org_name">
<Input v-model="modals.org.form.org_name" placeholder="请输入部门名称" />
</FormItem>
<!-- <FormItem label="所属机构编码" prop="source_org">
<Input v-model="modals.org.form.source_org" placeholder="请输入所属机构编码" />
</FormItem> -->
<FormItem label="部门描述" prop="remark">
<Input type="textarea" :rows="3" v-model="modals.org.form.remark" placeholder="请输入部门描述" />
</FormItem>
<FormItem label="排序号" prop="order_no">
<InputNumber v-model="modals.org.form.order_no" :min="0" style="width: 100%" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="modals.org.visible = false">取消</Button>
<Button type="primary" :loading="modals.org.saving" @click="handleSave('org')">保存</Button>
</div>
</Modal>
<!-- 人员划分弹窗 -->
<Modal v-model="userAssignModal.visible" title="人员划分" width="800" :mask-closable="false">
<div style="margin-bottom: 16px;">
<span style="font-weight: bold;">部门:</span>{{ userAssignModal.orgName }}
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold;">已划分人员:</span>
<div style="margin-top: 8px;">
<Tag v-for="user in userAssignModal.selectedUsers" :key="user.user_id" closable @on-close="removeAssignedUser(user)">
{{ user.user_name }}
</Tag>
</div>
</div>
<div slot="footer">
<Button @click="userAssignModal.visible = false">取消</Button>
<Button type="primary" @click="addUsersToOrg">添加人员</Button>
<Button type="primary" :loading="userAssignModal.saving" @click="saveUserAssignment">保存</Button>
</div>
</Modal>
<!-- 人员选择器 -->
<UserMultiSelector
v-model="userSelectorVisible"
:round="userRound"
@on-ok="handleUserSelectorOk"
@cancel="userSelectorVisible = false" />
......@@ -379,7 +438,11 @@ import {
importMaterial,
getPermissionConfigList,
savePermissionUsers,
getUserDmPermissionList
getUserDmPermissionList,
getOrgList,
saveOrg,
deleteOrg,
assignUsersToOrg
} from '@/api/key-dm'
import axios from 'axios'
import UserMultiSelector from '@/view/key-person/key_dm_user/userMultiSelector.vue'
......@@ -393,25 +456,29 @@ export default {
filters: {
leaveType: { type_name: '', type_code: '' },
materialCategory: { category_name: '', category_code: '' },
material: { material_name: '', material_code: '', category_id: '' }
material: { material_name: '', material_code: '', category_id: '' },
org: { org_name: '', org_code: '' }
},
tables: {
permissionConfig: [],
leaveType: [],
materialCategory: [],
material: []
material: [],
org: []
},
loading: {
permissionConfig: false,
leaveType: false,
materialCategory: false,
material: false
material: false,
org: false
},
pagers: {
permissionConfig: { pageNo: 1, pageSize: 10, totalRecord: 0 },
leaveType: { pageNo: 1, pageSize: 10, totalRecord: 0 },
materialCategory: { pageNo: 1, pageSize: 10, totalRecord: 0 },
material: { pageNo: 1, pageSize: 10, totalRecord: 0 }
material: { pageNo: 1, pageSize: 10, totalRecord: 0 },
org: { pageNo: 1, pageSize: 10, totalRecord: 0 }
},
modals: {
leaveType: {
......@@ -473,6 +540,23 @@ export default {
category_id: [{ required: true, message: '请选择分类', trigger: 'change' }],
material_name: [{ required: true, message: '请输入物料名称', trigger: 'blur' }]
}
},
org: {
visible: false,
isEdit: false,
saving: false,
form: {
id: '',
source_org: '',
org_code: '',
org_name: '',
remark: '',
order_no: 0
},
rules: {
// org_code: [{ required: true, message: '请输入部门编码', trigger: 'blur' }],
org_name: [{ required: true, message: '请输入部门名称', trigger: 'blur' }]
}
}
},
userConfigModal: {
......@@ -482,6 +566,13 @@ export default {
permissionName: '',
selectedUsers: []
},
userAssignModal: {
visible: false,
saving: false,
orgId: '',
orgName: '',
selectedUsers: []
},
userSelectorVisible: false,
importShow: false,
impBtnDisabled: false,
......@@ -490,8 +581,8 @@ export default {
{ type: 'index', title: '序号', width: 60, align: 'center' },
{ title: '类型编码', key: 'type_code', align: 'center' },
{ title: '类型名称', key: 'type_name', align: 'center' },
{ title: '基准天数', key: 'base_days', align: 'center', width: 100 },
{ title: '需要审批', key: 'need_approval', align: 'center', width: 100, render: (h, { row }) => h('span', row.need_approval === 1 ? '需要' : '不需要') },
// { title: '基准天数', key: 'base_days', align: 'center', width: 100 },
// { title: '需要审批', key: 'need_approval', align: 'center', width: 100, render: (h, { row }) => h('span', row.need_approval === 1 ? '需要' : '不需要') },
{ title: '排序号', key: 'order_no', align: 'center', width: 100 },
{ title: '操作', slot: 'action', align: 'center', width: 180, fixed: 'right' }
],
......@@ -528,12 +619,23 @@ export default {
{ title: '配置人员', key: 'user_names', align: 'center', minWidth: 300 },
{ title: '操作', slot: 'action', align: 'center', width: 120, fixed: 'right' }
],
orgColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
// { title: '部门编码', key: 'org_code', align: 'center' },
{ title: '部门名称', key: 'org_name', align: 'center' },
// { title: '所属机构编码', key: 'source_org', align: 'center' },
{ title: '部门人员', key: 'user_names', align: 'center' },
{ title: '描述', key: 'remark', align: 'center' },
{ title: '排序号', key: 'order_no', align: 'center', width: 100 },
{ title: '操作', slot: 'action', align: 'center', width: 200, fixed: 'right' }
],
power: {
leave_approval: false, // 请假审核
leave_view: false, // 请假查询(统计)
supply_approval: false, // 用品申领审核
supply_view: false // 用品查询(统计)
}
},
userRound: '1'
}
},
created () {
......@@ -572,7 +674,8 @@ export default {
permissionConfig: () => Promise.resolve({ data: { errcode: 0, data: [] } }),
leaveType: getLeaveTypeList,
materialCategory: getMaterialCategoryList,
material: getMaterialList
material: getMaterialList,
org: getOrgList
}
const api = apiMap[tab]
if (!api) return
......@@ -597,7 +700,8 @@ export default {
permissionConfig: () => ({}),
leaveType: () => ({ type_name: '', type_code: '' }),
materialCategory: () => ({ category_name: '', category_code: '' }),
material: () => ({ material_name: '', material_code: '', category_id: '' })
material: () => ({ material_name: '', material_code: '', category_id: '' }),
org: () => ({ org_name: '', org_code: '' })
}
this.filters[tab] = resetMap[tab]()
this.pagers[tab].pageNo = 1
......@@ -662,6 +766,14 @@ export default {
can_borrow: 1,
material_desc: '',
order_no: 0
},
org: {
id: '',
source_org: '',
org_code: '',
org_name: '',
remark: '',
order_no: 0
}
}
return JSON.parse(JSON.stringify(defaults[tab]))
......@@ -676,7 +788,8 @@ export default {
const saveApiMap = {
leaveType: saveLeaveType,
materialCategory: saveMaterialCategory,
material: saveMaterial
material: saveMaterial,
org: saveOrg
}
const api = saveApiMap[tab]
api(modal.form).then(ret => {
......@@ -698,7 +811,8 @@ export default {
const deleteApiMap = {
leaveType: deleteLeaveType,
materialCategory: deleteMaterialCategory,
material: deleteMaterial
material: deleteMaterial,
org: deleteOrg
}
const api = deleteApiMap[tab]
api({ id: row.id }).then(ret => {
......@@ -748,17 +862,29 @@ export default {
console.log('this.userConfigModal', this.userConfigModal)
},
addUsers () {
this.userRound = '1'
this.userSelectorVisible = true
},
handleUserSelectorOk (selectedUsers) {
// 将新选择的用户添加到已选用户列表中,避免重复
const existingIds = this.userConfigModal.selectedUsers.map(u => u.user_id)
const newUsers = selectedUsers.filter(u => !existingIds.includes(u.id)).map(u => ({
user_id: u.id,
user_name: u.name,
gh: u.gh
}))
this.userConfigModal.selectedUsers = [...this.userConfigModal.selectedUsers, ...newUsers]
if (this.userConfigModal.visible) {
// 权限配置弹窗
const existingIds = this.userConfigModal.selectedUsers.map(u => u.user_id)
const newUsers = selectedUsers.filter(u => !existingIds.includes(u.id)).map(u => ({
user_id: u.id,
user_name: u.name,
gh: u.gh
}))
this.userConfigModal.selectedUsers = [...this.userConfigModal.selectedUsers, ...newUsers]
} else if (this.userAssignModal.visible) {
// 人员划分弹窗
const existingIds = this.userAssignModal.selectedUsers.map(u => u.user_id)
const newUsers = selectedUsers.filter(u => !existingIds.includes(u.id)).map(u => ({
user_id: u.id,
user_name: u.name,
gh: u.gh
}))
this.userAssignModal.selectedUsers = [...this.userAssignModal.selectedUsers, ...newUsers]
}
this.userSelectorVisible = false
},
removeUser (user) {
......@@ -876,6 +1002,46 @@ export default {
const areaId = info.area_id || ''
if (areaId && areaId.startsWith('2201')) return true
return false
},
openUserAssignModal (row) {
this.userAssignModal.visible = true
this.userAssignModal.orgId = row.id
this.userAssignModal.orgName = row.org_name
this.userAssignModal.selectedUsers = row.user_names ? (JSON.parse(row.users) || []) : []
// 加载该部门已分配的人员
// this.loadOrgAssignedUsers(row.id)
},
// loadOrgAssignedUsers (orgId) {
// getOrgList({ pageNo: 1, pageSize: 1000, params: { id: orgId } }).then(ret => {
// if (ret.data && ret.data.errcode === 0) {
// const data = ret.data.data || {}
// // 这里可以根据实际需求调整如何获取已分配的人员
// // 暂时设置为空,后续根据实际业务逻辑调整
// }
// })
// },
addUsersToOrg () {
this.userRound = '2'
this.userSelectorVisible = true
},
removeAssignedUser (user) {
this.userAssignModal.selectedUsers = this.userAssignModal.selectedUsers.filter(u => u.user_id !== user.user_id)
},
saveUserAssignment () {
this.userAssignModal.saving = true
const userIds = this.userAssignModal.selectedUsers.map(u => u.user_id)
assignUsersToOrg({
org_id: this.userAssignModal.orgId,
user_ids: userIds
}).then(ret => {
if (ret.data && ret.data.errcode === 0) {
this.fetchList("org")
this.$Message.success('保存成功')
this.userAssignModal.visible = false
} else {
this.$Notice.error({ title: '保存失败', desc: ret.data && ret.data.errmsg })
}
}).finally(() => { })
}
}
}
......
......@@ -4,26 +4,77 @@
<TabPane label="办公用品入库" name="inbound">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="8">
<Input v-model="filters.inbound.inbound_no" placeholder="单号/批次号" style="width:60%" />
<Button type="primary" class="mr10" @click="handleSearch('inbound')">搜索</Button>
<Button @click="handleReset('inbound')">重置</Button>
<Col span="20">
<div class="search-form">
<Form :inline="true" :model="filters.return" class="form-inline">
<FormItem label="入库单号:" class="form-item">
<Input
v-model="filters.inbound.inbound_no"
placeholder="请输入入库单号:"
class="form-input"
/>
</FormItem>
<FormItem label="批次号:" class="form-item">
<Input
v-model="filters.inbound.batch_no"
placeholder="请输入批次号:"
class="form-input"
/>
</FormItem>
<FormItem label="入库日期" class="form-item">
<DatePicker
v-model="inboundDateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
clearable
/>
</FormItem>
<FormItem label="入库类型:" class="form-item">
<Select
v-model="filters.inbound.inbound_type"
placeholder="请选择入库类型"
class="form-input"
>
<Option :value='1'>手工入库</Option>
<Option :value='2'>归还入库</Option>
</Select>
</FormItem>
<FormItem label="入库类型:" class="form-item">
<Select
v-model="filters.inbound.inbound_status"
placeholder="请选择入库类型"
class="form-input"
>
<Option :value='0'>待入库</Option>
<Option :value='1'>已入库</Option>
</Select>
</FormItem>
<FormItem class="form-item-actions">
<Button type="primary" @click="handleSearch('inbound')">搜索</Button>
<Button @click="handleReset('inbound')" style="margin-left: 8px;">重置</Button>
</FormItem>
</Form>
</div>
</Col>
<Col span="16" class="text-right">
<Col span="4" class="text-right">
<Button type="success" @click="openInboundModal">新增入库</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="row.inbound_status === 0">
<Button size="small" type="success">入库</Button>
</Poptip>
<Button size="small" @click="openDetail(row)">详细</Button>
<Button size="small" type="primary" @click="openEdit(row)" v-if="canEdit(row)">修改</Button>
<Poptip confirm title="确认删除?" transfer @on-ok="deleteInbound(row)">
<Button size="small" type="error">删除</Button>
</Poptip>
<Poptip confirm title="确认执行入库?" transfer @on-ok="doInbound(row)" v-if="row.inbound_status===0">
<Button size="small" type="success">入库</Button>
</Poptip>
</template>
</Table>
<Page class="page_style" :total="pagers.inbound.totalRecord" :current="pagers.inbound.pageNo" :page-size="pagers.inbound.pageSize"
......@@ -34,9 +85,28 @@
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="10">
<Input v-model="filters.return.key" placeholder="申请单号/申请人" style="width:60%" />
<Button type="primary" class="mr10" @click="handleSearch('return')">搜索</Button>
<Button @click="handleReset('return')">重置</Button>
<div class="search-form">
<Form :inline="true" :model="filters.return" class="form-inline">
<FormItem label="申请单号:" class="form-item">
<Input
v-model="filters.return.application_no"
placeholder="请输入申请单号"
class="form-input"
/>
</FormItem>
<FormItem label="申请人:" class="form-item">
<Input
v-model="filters.return.applicant_name"
placeholder="请输入申请人"
class="form-input"
/>
</FormItem>
<FormItem class="form-item-actions">
<Button type="primary" @click="handleSearch('return')">搜索</Button>
<Button @click="handleReset('return')" style="margin-left: 8px;">重置</Button>
</FormItem>
</Form>
</div>
</Col>
</Row>
</div>
......@@ -73,7 +143,13 @@
<Input v-model="inboundModal.form.batch_no" />
</FormItem>
<FormItem label="入库日期">
<DatePicker v-model="inboundModal.form.inbound_date" type="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" />
......@@ -136,17 +212,42 @@ import {
getPendingReturnList,
processReturn,
getInventoryList,
getInboundById
getInboundById,
getPendingReturnById
} from '@/api/key-dm-inbound'
import MaterialSelector from '@/view/key-person/key_dm_conf/materialSelector.vue'
import { normalizeVisitTimeValue } from '@/view/key-person/key_dm_conf/dates.js'
export default {
name: 'key-dm-inbound-index',
components: { MaterialSelector },
data () {
return {
// 时间范围数组
inboundDateRange: null,
// 入库状态映射字典
inboundTypeMap: { '1': '手工', '2': '归还' },
inboundStatusMap: { '0': '待入库', '1': '已入库' },
opTypeMap: { '1': '入库', '2': '出库' },
issueStatusType: { '0': '待发放', '1': '已发放' },
activeTab: 'inbound',
filters: { inbound: { inbound_no: '' }, return: { key: '' }, inventory: { material_name: '' } },
filters: {
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: ''
}
},
tables: { inbound: [], return: [], inventory: [] },
pagers: { inbound: { pageNo: 1, pageSize: 10, totalRecord: 0 }, return: { pageNo: 1, pageSize: 10, totalRecord: 0 }, inventory: { pageNo: 1, pageSize: 10, totalRecord: 0 } },
loading: { inbound: false, return: false, inventory: false },
......@@ -154,9 +255,30 @@ export default {
{ 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' },
{ title: '类型', key: 'inbound_type', align: 'center' },
{ title: '状态', key: 'inbound_status', 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: 360, align: 'center' }
],
returnColumns: [
......@@ -164,7 +286,14 @@ export default {
{ title: '申请单号', key: 'application_no', align: 'center' },
{ title: '申请人', key: 'applicant_name', align: 'center' },
{ title: '部门', key: 'department_name', align: 'center' },
{ title: '发放状态', key: 'issue_status', align: 'center' },
{
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: [
......@@ -172,8 +301,16 @@ export default {
{ title: '物料编码', key: 'material_code', align: 'center' },
{ title: '物料名称', key: 'material_name', align: 'center' },
{ title: '总量', key: 'total_quantity', align: 'center' },
{ title: '可用', key: 'available_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)
}
},
{ title: '借出', key: 'borrowed_quantity', align: 'center' },
{ title: '损坏', key: 'damaged_quantity', align: 'center' }
],
// 模态窗口内表格列定义
inboundDetailColumns: [
......@@ -192,13 +329,18 @@ export default {
},
style: { width: '100px' },
on: {
input: (val) => {
params.row.inbound_quantity = val || 0
params.row.total_amount = ((params.row.inbound_quantity || 0) * (params.row.unit_price || 0)).toFixed(2)
'on-change': (val) => {
// 使用Vue.set确保响应式更新
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,
......@@ -211,20 +353,30 @@ export default {
},
style: { width: '100px' },
on: {
input: (val) => {
params.row.unit_price = val || 0
params.row.total_amount = ((params.row.inbound_quantity || 0) * (params.row.unit_price || 0)).toFixed(2)
'on-change': (val) => {
// 使用Vue.set确保响应式更新
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: '总金额',
}
},
{
title: '总金额',
key: 'total_amount',
minWidth: 120,
render: (h, params) => {
const amount = ((params.row.inbound_quantity || 0) * (params.row.unit_price || 0)).toFixed(2)
return h('span', amount)
} }
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' },
......@@ -238,9 +390,32 @@ export default {
{ title: '单价', key: 'unit_price' }
],
detailLogsColumns: [
{ title: '操作类型', key: 'op_type' },
{
title: '物料名称',
key: 'material_name'
},
{
title: '操作类型',
key: 'op_type',
render: (h, { row }) => {
return h('span', this.opTypeMap[row.op_type] || row.op_type || '-')
}
},
{ title: '数量', key: 'quantity' },
{ title: '时间', key: 'create_time' }
{
title: '操作人',
key: 'userName',
render: (h, { row }) => {
return h('span', row.userName || row.create_by || '-')
}
},
{
title: '时间',
key: 'create_time',
render: (h, { row }) => {
return h('span', this.formatDateTime(row.create_time) || row.create_time || '-')
}
}
],
// 选择器控制与已选明细
showMaterialSelector: false,
......@@ -250,8 +425,27 @@ export default {
detailModal: { visible: false, loading: false, data: {}, details: [], logs: [] }
}
},
watch: {
inboundDateRange (newVal) {
if (newVal && Array.isArray(newVal)) {
this.filters.inbound.startDate = normalizeVisitTimeValue(newVal[0])
this.filters.inbound.endDate = normalizeVisitTimeValue(newVal[1])
} else {
this.filters.inbound.startDate = null
this.filters.inbound.endDate = null
}
}
},
created () { this.fetchList('inbound') },
methods: {
// 获取当天日期,格式为 yyyy-MM-dd
getTodayDate () {
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
handleTabChange (name) {
this.activeTab = name
if (name === 'return') this.fetchList('return')
......@@ -270,14 +464,17 @@ export default {
} else this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.loading[tab] = false })
},
handleSearch (tab) { (this.pagers[tab] || {}).pageNo = 1; this.fetchList(tab) },
handleReset (tab) { this.filters[tab] = {}; (this.pagers[tab] || {}).pageNo = 1; this.fetchList(tab) },
handleSearch (tab) {
(this.pagers[tab] || {}).pageNo = 1
this.fetchList(tab)
},
handleReset (tab) { this.inboundDateRange = null; 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) },
openInboundModal () {
this.inboundModal.isEdit = false
this.inboundModal.form = { inbound_no: '', batch_no: '', inbound_date: '', inbound_type: 1, storage_location: '', remark: '' }
this.inboundModal.form = { inbound_no: '', batch_no: '', inbound_date: this.getTodayDate(), inbound_type: 1, storage_location: '', remark: '' }
this.inboundModal.details = []
this.inboundModal.visible = true
},
......@@ -285,6 +482,7 @@ export default {
this.inboundSelectedDetails = list || []
},
handleInboundMaterialSelectorOk (selectedRows) {
console.log('选择的物料信息' + selectedRows)
if (!Array.isArray(selectedRows) || selectedRows.length === 0) {
this.$Message.warning('未选择物料')
return
......@@ -296,6 +494,7 @@ export default {
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,
......@@ -306,6 +505,32 @@ export default {
})
this.showMaterialSelector = false
},
// 转换时间戳为yyyy-MM-dd
formatDate (timestamp) {
if (!timestamp || isNaN(Number(timestamp))) return ''
const d = new Date(Number(timestamp))
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
},
// 转换时间戳为yyyy-MM-dd hh-mm-ss
formatDateTime (timestamp) {
// 空值/非数字处理:返回空字符串
if (!timestamp || isNaN(Number(timestamp))) return ''
const d = new Date(Number(timestamp))
// 年
const year = d.getFullYear()
// 月(补0)
const month = String(d.getMonth() + 1).padStart(2, '0')
// 日(补0)
const day = String(d.getDate()).padStart(2, '0')
// 时(24小时制,补0)
const hour = String(d.getHours()).padStart(2, '0')
// 分(补0)
const minute = String(d.getMinutes()).padStart(2, '0')
// 秒(补0)
const second = String(d.getSeconds()).padStart(2, '0')
// 拼接为 yyyy-MM-dd HH:mm:ss
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
},
deleteSelectedInboundDetails () {
if (!this.inboundSelectedDetails || this.inboundSelectedDetails.length === 0) {
this.$Message.warning('请先选择要删除的明细行')
......@@ -320,25 +545,87 @@ export default {
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 () {
// 1. 验证明细数据
this.inboundModal.saving = true
saveInbound(Object.assign({}, this.inboundModal.form, { details: this.inboundModal.details })).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 })
}).finally(() => { this.inboundModal.saving = false })
// 1. 验证主表必填项
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
}
// 2. 验证明细数据
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
}
// 2. 转换数据类型
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
}
})
// 3. 发送请求
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
})
},
canEdit (row) { return row.inbound_status === 0 },
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 }) }) },
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 = []
getInboundById({ id: row.id }).then(ret => {
getPendingReturnById({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) this.returnModal.details = ret.data.data.details || []
})
this.returnModal.visible = true
......@@ -355,6 +642,7 @@ export default {
this.detailModal.loading = true
getInboundById({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
console.log(ret.data)
this.detailModal.data = ret.data.data || {}
this.detailModal.details = ret.data.data.details || []
this.detailModal.logs = ret.data.data.logs || []
......@@ -372,4 +660,67 @@ export default {
.text-right { text-align: right; }
.page_style { margin-top: 12px; text-align: right; }
.mt8 { margin-top: 8px; }
/* 搜索区域外层盒:统一间距+背景 */
.search-container {
padding: 16px;
background: #fff;
border-radius: 4px;
margin-bottom: 16px;
}
/* 行容器:垂直居中+间距 */
.search-row {
margin: 0 !important;
}
/* 内联表单:消除默认间距 */
.form-inline {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0;
}
/* 表单项:统一间距+对齐 */
.form-item {
margin: 0 8px 0 0 !important;
display: flex;
align-items: center;
}
/* 输入框/下拉框:统一宽度 */
.form-input {
width: 180px !important;
}
/* 按钮组:单独样式 */
.form-item-actions {
margin-left: 16px !important;
}
/* 右侧操作列:居右+内边距 */
.action-col {
text-align: right;
padding-right: 16px;
}
/* 响应式适配:小屏幕自动换行 */
@media (max-width: 768px) {
.form-inline {
flex-direction: column;
align-items: flex-start;
}
.form-item {
margin: 8px 0 !important;
width: 100%;
}
.form-input {
width: 100% !important;
}
.action-col {
text-align: left;
padding: 16px 0 0 0;
}
}
</style>
......@@ -4,12 +4,44 @@
<TabPane label="办公用品申领" name="apply">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="10">
<Input v-model="filters.apply.applicant_name" placeholder="姓名/工号" style="width:60%" />
<Button type="primary" class="mr10" @click="handleSearch('apply')">搜索</Button>
<Button @click="handleReset('apply')">重置</Button>
<Col span="20">
<div class="search-form">
<Form :inline="true" :model="filters.apply" class="form-inline">
<FormItem label="申请单号:" class="form-item">
<Input
v-model="filters.apply.application_no"
placeholder="请输入申请单号"
class="form-input"
/>
</FormItem>
<FormItem label="申请人:" class="form-item">
<Input
v-model="filters.apply.applicant_name"
placeholder="请输入申请人"
class="form-input"
/>
</FormItem>
<FormItem label="审批状态:" class="form-item">
<Select
v-model="filters.apply.approval_status"
placeholder="请选择审批状态"
class="form-input"
>
<Option :value='0'>待提交</Option>
<Option :value='1'>审核中</Option>
<Option :value='9'>审核通过</Option>
<Option :value='-1'>驳回</Option>
</Select>
</FormItem>
<FormItem class="form-item-actions">
<Button type="primary" @click="handleSearch('apply')">搜索</Button>
<Button @click="handleReset('apply')" style="margin-left: 8px;">重置</Button>
</FormItem>
</Form>
</div>
</Col>
<Col span="14" class="text-right">
<!-- 右侧操作按钮区:固定居右 -->
<Col span="4" class="action-col">
<Button type="success" @click="openApplyModal">申请</Button>
</Col>
</Row>
......@@ -40,6 +72,47 @@
</TabPane>
<TabPane label="办公用品审核历史查询" name="history">
<div class="search-div">
<Row type="flex" :gutter="16">
<Col span="24">
<div class="search-form">
<Form :inline="true" :model="filters.history" class="form-inline">
<FormItem label="申请单号:" class="form-item">
<Input
v-model="filters.history.application_no"
placeholder="请输入申请单号"
class="form-input"
/>
</FormItem>
<FormItem label="申请人:" class="form-item">
<Input
v-model="filters.history.applicant_name"
placeholder="请输入申请人"
class="form-input"
/>
</FormItem>
<FormItem label="审批状态:" class="form-item">
<Select
v-model="filters.history.approval_status"
placeholder="请选择审批状态"
class="form-input"
>
<Option :value= "0">待提交</Option>
<Option :value= "1">审核中</Option>
<Option :value= "9">审核通过</Option>
<Option :value= "-1">驳回</Option>
</Select>
</FormItem>
<FormItem class="form-item-actions">
<Button type="primary" @click="handleSearch('history')">搜索</Button>
<Button @click="handleReset('history')" style="margin-left: 8px;">重置</Button>
</FormItem>
</Form>
</div>
</Col>
</Row>
</div>
<Table :data="tables.history" :loading="loading.history" :columns="historyColumns" border>
<template slot="action" slot-scope="{ row }">
<Button size="small" @click="openDetail(row)">详细</Button>
......@@ -63,7 +136,13 @@
<Input v-model="applyModal.form.borrow_purpose" />
</FormItem>
<FormItem label="预计归还日期">
<DatePicker v-model="applyModal.form.expected_return_date" type="date" />
<DatePicker
v-model="applyModal.form.expected_return_date"
type="date"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
placeholder="请选择预计归还日期"
/>
</FormItem>
<h4>申请明细</h4>
......@@ -107,6 +186,7 @@
<div v-else>
<Row :gutter="16"><Col span="12"><p><strong>申请人:</strong>{{ detailModal.data.applicant_name }}</p></Col><Col span="12"><p><strong>部门:</strong>{{ detailModal.data.department_name }}</p></Col></Row>
<Row class="mt8"><Col span="24"><p><strong>领用用途:</strong>{{ detailModal.data.borrow_purpose }}</p></Col></Row>
<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"><h4>审批记录</h4></Col></Row>
......@@ -135,8 +215,17 @@ export default {
components: { MaterialSelector },
data () {
return {
// 申请弹窗中的可编辑行状态
editingRowIndex: -1,
editingCellField: '',
// 审批状态映射字典
approvalStatusMap: { '0': '待提交', '1': '审核中', '9': '审核通过', '-1': '驳回' },
opTypeMap: { '1': '入库', '2': '出库' },
activeTab: 'apply',
filters: { apply: { applicant_name: '' } },
filters: {
apply: { application_no: '', applicant_name: '', approval_status: null },
history: { application_no: '', applicant_name: '', approval_status: null }
},
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 },
......@@ -145,10 +234,41 @@ export default {
{ title: '申请单号', key: 'application_no', align: 'center' },
{ title: '申请人', key: 'applicant_name', align: 'center' },
{ title: '部门', key: 'department_name', align: 'center' },
{ title: '请假类型', key: 'borrow_purpose', align: 'center' },
{ title: '起止时间', key: 'start_time', align: 'center' },
{ title: '时长', key: 'duration', align: 'center' },
{ title: '状态', key: 'approval_status', align: 'center' },
{ title: '审评用途', key: 'borrow_purpose', align: 'center' },
{
title: '起止时间',
key: 'start_time',
align: 'center',
width: '300px',
render: (h, { row }) => {
return h('span', (this.formatDateTime(row.create_time) || row.create_time || '-') + '--' + (this.formatDateTime(row.approval_time) || row.approval_time || '-'))
}
},
{
title: '时长',
key: 'duration',
align: 'center',
render: (h, { row }) => {
const createTime = Number(row.create_time) || 0
const approvalTime = Number(row.approval_time) || 0
let duration = null
if (createTime !== 0 && approvalTime !== 0) {
const diffMs = Math.abs(approvalTime - createTime)
duration = this.formatDuration(diffMs)
} else {
duration = this.formatDuration('')
}
return h('span', duration)
}
},
{
title: '状态',
key: 'approval_status',
align: 'center',
render: (h, { row }) => {
return h('span', this.approvalStatusMap[row.approval_status + ''] || row.approval_status || '-')
}
},
{ title: '操作', slot: 'action', align: 'center', width: 320 }
],
pendingColumns: [
......@@ -156,15 +276,36 @@ export default {
{ title: '申请单号', key: 'application_no', align: 'center' },
{ title: '申请人', key: 'applicant_name', align: 'center' },
{ title: '部门', key: 'department_name', align: 'center' },
{ title: '提交时间', key: 'submit_time', align: 'center' },
{
title: '提交时间',
key: 'submit_time',
align: 'center',
render: (h, { row }) => {
return h('span', this.formatDate(row.submit_time) || row.submit_time || '-')
}
},
{ title: '操作', slot: 'action', width: 120, align: 'center' }
],
historyColumns: [
{ type: 'index', title: '序号', width: 60, align: 'center' },
{ title: '申请单号', key: 'application_no', align: 'center' },
{ title: '申请人', key: 'applicant_name', align: 'center' },
{ title: '状态', key: 'approval_status', align: 'center' },
{ title: '审批完成时间', key: 'approval_complete_time', align: 'center' },
{
title: '状态',
key: 'approval_status',
align: 'center',
render: (h, { row }) => {
return h('span', this.approvalStatusMap[row.approval_status + ''] || row.approval_status || '-')
}
},
{
title: '审批完成时间',
key: 'approval_time',
align: 'center',
render: (h, { row }) => {
return h('span', this.formatDateTime(row.approval_time) || row.approval_time || '-')
}
},
{ title: '操作', slot: 'action', width: 100, align: 'center' }
],
// 模态窗口内表格列定义
......@@ -172,7 +313,77 @@ export default {
{ type: 'selection', width: 60 },
{ title: '物料编码', key: 'material_code' },
{ title: '物料名称', key: 'material_name' },
{ title: '申请数量', key: 'apply_quantity' },
{
title: '申请数量',
key: 'apply_quantity',
render: (h, { row, column, index }) => {
// 如果当前行是编辑状态,显示输入框
if (this.editingRowIndex === index && this.editingCellField === 'apply_quantity') {
return h('InputNumber', {
props: {
value: row.apply_quantity,
min: 1,
max: row.available_quantity || 9999,
precision: 0
},
style: { width: '100%' },
on: {
input: (value) => {
// 更新数据
this.applyModal.details[index].apply_quantity = value
},
'on-blur': () => {
// 失去焦点时退出编辑模式
this.editingRowIndex = -1
this.editingCellField = ''
},
'on-keyup': (event) => {
// 按回车键保存并退出编辑
if (event.keyCode === 13) {
this.editingRowIndex = -1
this.editingCellField = ''
}
}
}
})
} else {
// 非编辑状态,显示文本和编辑图标
return h('div', {
style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
padding: '0 8px'
},
on: {
click: () => {
// 点击进入编辑模式
this.editingRowIndex = index
this.editingCellField = 'apply_quantity'
// 下一个tick聚焦到输入框
this.$nextTick(() => {
const input = document.querySelector(`.ivu-table-cell-with-input input`)
if (input) input.focus()
})
}
}
}, [
h('span', row.apply_quantity || 0),
h('Icon', {
props: {
type: 'md-create',
size: 14
},
style: {
color: '#2d8cf0',
marginLeft: '8px'
}
})
])
}
}
},
{ title: '单位', key: 'unit' },
{ title: '备注', key: 'issue_remark' }
],
......@@ -190,21 +401,108 @@ export default {
{ title: '已归还', key: 'returned_quantity' }
],
detailLogsColumns: [
{ title: '操作类型', key: 'op_type' },
{ title: '物料名称', key: 'material_name' },
{
title: '操作类型',
key: 'op_type',
render: (h, { row }) => {
return h('span', this.opTypeMap[row.op_type] || row.op_type || '-')
}
},
{ title: '数量', key: 'quantity' },
{ title: '时间', key: 'create_time' },
{
title: '时间',
key: 'create_time',
width: '150px',
render: (h, { row }) => {
return h('span', this.formatDateTime(row.create_time) || row.create_time || '-')
}
},
{ title: '操作人', key: 'create_by' }
],
// 选择器控制与已选明细
showMaterialSelector: false,
applySelectedDetails: [],
applyModal: { visible: false, isEdit: false, saving: false, form: {}, details: [] },
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: [] }
}
},
created () { this.fetchList('apply') },
methods: {
// 获取当天日期,格式为 yyyy-MM-dd
getTodayDate () {
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
// 格式化毫秒差值为易读的时长
formatDuration (diffMs) {
// 无有效差值时返回兜底值
if (diffMs <= 0) return '--'
// 时间单位换算(毫秒)
const oneSecond = 1000
const oneMinute = oneSecond * 60
const oneHour = oneMinute * 60
const oneDay = oneHour * 24
// 计算各单位的数值
const days = Math.floor(diffMs / oneDay)
const hours = Math.floor((diffMs % oneDay) / oneHour)
const minutes = Math.floor((diffMs % oneHour) / oneMinute)
const seconds = Math.floor((diffMs % oneMinute) / oneSecond)
// 拼接时长字符串(按需显示非零单位,避免冗余)
let duration = ''
if (days > 0) duration += `${days}天`
if (hours > 0 || duration) duration += `${hours}时`
if (minutes > 0 || duration) duration += `${minutes}分`
duration += `${seconds}秒`
return duration
},
// 处理单元格点击编辑
handleCellEdit (row, column, index, field) {
if (field === 'apply_quantity') {
this.editingRowIndex = index
this.editingCellField = field
// 下一个tick聚焦到输入框
this.$nextTick(() => {
const input = document.querySelector(`.ivu-table-cell-with-input input`)
if (input) input.focus()
})
}
},
// 添加方法:退出编辑模式
exitEditMode () {
this.editingRowIndex = -1
this.editingCellField = ''
},
// 转换时间戳为yyyy-MM-dd
formatDate (timestamp) {
if (!timestamp || isNaN(Number(timestamp))) return ''
const d = new Date(Number(timestamp))
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
},
// 转换时间戳为yyyy-MM-dd hh-mm-ss
formatDateTime (timestamp) {
// 空值/非数字处理:返回空字符串
if (!timestamp || isNaN(Number(timestamp))) return ''
const d = new Date(Number(timestamp))
// 年
const year = d.getFullYear()
// 月(补0)
const month = String(d.getMonth() + 1).padStart(2, '0')
// 日(补0)
const day = String(d.getDate()).padStart(2, '0')
// 时(24小时制,补0)
const hour = String(d.getHours()).padStart(2, '0')
// 分(补0)
const minute = String(d.getMinutes()).padStart(2, '0')
// 秒(补0)
const second = String(d.getSeconds()).padStart(2, '0')
// 拼接为 yyyy-MM-dd HH:mm:ss
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
},
handleTabChange (name) {
this.activeTab = name
if (name === 'pending') this.fetchList('pending')
......@@ -214,7 +512,19 @@ export default {
const apiMap = { apply: getBorrowList, pending: getPendingBorrowList, history: getBorrowList }
const api = apiMap[tab]; if (!api) return
this.loading[tab] = true
const payload = Object.assign({}, this.pagers[tab] || {}, { params: this.filters[tab] || {} })
// 清理空值,防止参数传递问题
const cleanParams = (obj) => {
const cleaned = {}
for (let key in obj) {
const value = obj[key]
if (value !== null && value !== undefined && value !== '') {
cleaned[key] = value
}
}
return cleaned
}
const payload = Object.assign({}, this.pagers[tab] || {}, { params: cleanParams(this.filters[tab] || {}) })
console.log(payload)
api(payload).then(ret => {
if (ret.data && ret.data.errcode === 0) {
const data = ret.data.data || {}
......@@ -223,20 +533,32 @@ export default {
} else this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
}).finally(() => { this.loading[tab] = false })
},
handleSearch (tab) { (this.pagers[tab] || {}).pageNo = 1; this.fetchList(tab) },
handleSearch (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) },
openApplyModal () {
this.applyModal.isEdit = false
this.applyModal.form = { applicant_id: '', applicant_name: '', department_id: '', department_name: '', borrow_purpose: '', expected_return_date: '', approval_status: 0 }
this.applyModal.form = { applicant_id: '', applicant_name: '', department_id: '', department_name: '', borrow_purpose: '', expected_return_date: this.getTodayDate(), approval_status: 0 }
this.applyModal.details = []
this.applyModal.visible = true
this.exitEditMode()
},
openEdit (row) {
console.log(row)
this.applyModal.isEdit = true
this.applyModal.form = Object.assign({}, row)
// 关键:时间戳转 yyyy-MM-dd 字符串
if (this.applyModal.form.expected_return_date) {
this.applyModal.form.expected_return_date = this.formatDate(this.applyModal.form.expected_return_date)
} else {
// 空值兜底
this.applyModal.form.expected_return_date = this.getTodayDate()
}
// load details from backend when open (simplified)
getBorrowById({ id: row.id }).then(ret => {
if (ret.data && ret.data.errcode === 0) {
......@@ -244,6 +566,7 @@ export default {
}
})
this.applyModal.visible = true
this.exitEditMode()
},
onApplyDetailSelectionChange (list) {
this.applySelectedDetails = list || []
......@@ -260,6 +583,7 @@ export default {
exist.apply_quantity = Number(exist.apply_quantity || 0) + 1
} else {
this.applyModal.details.push({
material_id: sel.id,
material_code: sel.material_code,
material_name: sel.material_name,
apply_quantity: 1,
......@@ -281,6 +605,7 @@ export default {
this.applySelectedDetails = []
},
saveApplication () {
this.exitEditMode()
this.applyModal.saving = true
const payload = Object.assign({}, this.applyModal.form, { details: this.applyModal.details })
saveBorrowApplication(payload).then(ret => {
......@@ -346,4 +671,79 @@ export default {
.text-right { text-align: right; }
.page_style { margin-top: 12px; text-align: right; }
.mt8 { margin-top: 8px; }
/* 搜索区域外层盒:统一间距+背景 */
.search-container {
padding: 16px;
background: #fff;
border-radius: 4px;
margin-bottom: 16px;
}
/* 行容器:垂直居中+间距 */
.search-row {
margin: 0 !important;
}
/* 内联表单:消除默认间距 */
.form-inline {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0;
}
/* 表单项:统一间距+对齐 */
.form-item {
margin: 0 8px 0 0 !important;
display: flex;
align-items: center;
}
/* 输入框/下拉框:统一宽度 */
.form-input {
width: 180px !important;
}
/* 按钮组:单独样式 */
.form-item-actions {
margin-left: 16px !important;
}
/* 右侧操作列:居右+内边距 */
.action-col {
text-align: right;
padding-right: 16px;
}
/* 响应式适配:小屏幕自动换行 */
@media (max-width: 768px) {
.form-inline {
flex-direction: column;
align-items: flex-start;
}
.form-item {
margin: 8px 0 !important;
width: 100%;
}
.form-input {
width: 100% !important;
}
.action-col {
text-align: left;
padding: 16px 0 0 0;
}
}
/* 添加一些样式来美化编辑状态 */
.ivu-table-cell {
position: relative;
}
.edit-icon {
opacity: 0;
transition: opacity 0.2s;
}
.ivu-table-row:hover .edit-icon {
opacity: 1;
}
</style>
......@@ -152,84 +152,128 @@ export default {
const et = this.workEnd ? normalizeVisitTimeValue(this.workEnd) : null
if (type === 'leave') {
this.detailModal.columns = [
{ title: '人员', key: 'user_name', align: 'center' },
{ title: '开始', key: 'start_time', align: 'center', render: (h, params) => {
const time = params.row.start_time
if (time) {
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return h('span', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)
{
title: '人员',
key: 'user_name',
align: 'center'
},
{
title: '开始',
key: 'start_time',
align: 'center',
render: (h, params) => {
const time = params.row.start_time
if (time) {
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return h('span', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)
}
return h('span', '-')
}
return h('span', '-')
}},
{ title: '结束', key: 'end_time', align: 'center', render: (h, params) => {
const time = params.row.end_time
if (time) {
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return h('span', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)
},
{
title: '结束',
key: 'end_time',
align: 'center',
render: (h, params) => {
const time = params.row.end_time
if (time) {
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return h('span', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)
}
return h('span', '-')
}
return h('span', '-')
}},
{ title: '时长', key: 'duration', align: 'center' },
{ title: '状态', key: 'status', align: 'center', render: (h, params) => {
const status = params.row.status
let statusText = ''
switch (status) {
case 0: statusText = '未提交'; break
case 1: statusText = '审核中'; break
case 9: statusText = '审核通过'; break
case -1: statusText = '驳回'; break
default: statusText = status
},
{
title: '时长',
key: 'duration',
align: 'center'
},
{
title: '状态',
key: 'status',
align: 'center',
render: (h, params) => {
const status = params.row.status
let statusText = ''
switch (status) {
case 0: statusText = '未提交'; break
case 1: statusText = '审核中'; break
case 9: statusText = '审核通过'; break
case -1: statusText = '驳回'; break
default: statusText = status
}
return h('span', statusText)
}
return h('span', statusText)
}}
}
]
getWorkloadDetails({ user_id: row.user_id, start: st, end: et, type: 'leave' }).then(ret => {
if (ret.data && ret.data.errcode === 0) { this.detailModal.rows = ret.data.data || []; this.detailModal.visible = true } else this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
})
} else {
this.detailModal.columns = [
{ title: '申请时间', key: 'submit_time', align: 'center', render: (h, params) => {
const time = params.row.submit_time
if (time) {
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return h('span', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)
{
title: '申请时间',
key: 'submit_time',
align: 'center',
render: (h, params) => {
const time = params.row.submit_time
if (time) {
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return h('span', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)
}
return h('span', '-')
}
return h('span', '-')
}},
{ title: '物料', key: 'material_name', align: 'center' },
{ title: '申请数量', key: 'apply_quantity', align: 'center' },
{ title: '归还数量', key: 'returned_quantity', align: 'center' },
{ title: '状态', key: 'approval_status', align: 'center', render: (h, params) => {
const status = params.row.approval_status
let statusText = ''
switch (status) {
case 0: statusText = '待提交'; break
case 1: statusText = '审核中'; break
case 9: statusText = '审核通过'; break
case -1: statusText = '驳回'; break
default: statusText = status
},
{
title: '物料',
key: 'material_name',
align: 'center'
},
{
title: '申请数量',
key: 'apply_quantity',
align: 'center'
},
{
title: '归还数量',
key: 'returned_quantity',
align: 'center'
},
{
title: '状态',
key: 'approval_status',
align: 'center',
render: (h, params) => {
const status = params.row.approval_status
let statusText = ''
switch (status) {
case 0: statusText = '待提交'; break
case 1: statusText = '审核中'; break
case 9: statusText = '审核通过'; break
case -1: statusText = '驳回'; break
default: statusText = status
}
return h('span', statusText)
}
return h('span', statusText)
}}
}
]
getWorkloadDetails({ user_id: row.user_id, start: st, end: et, type: 'inventory' }).then(ret => {
if (ret.data && ret.data.errcode === 0) { this.detailModal.rows = ret.data.data || []; this.detailModal.visible = true } else this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
......
......@@ -4,22 +4,20 @@
<TabPane label="请假申请" name="apply">
<div class="search-div">
<Row type="flex" :gutter="16" class="mt8">
<Col span="12">
<Col span="20">
<span>开始:</span>
<DatePicker v-model="filters.apply.start_time" type="date" placeholder="开始日期" style="min-width:110px;margin-right:20px" />
<span>结束:</span>
<DatePicker v-model="filters.apply.end_time" type="date" placeholder="结束日期" style="min-width:110px;margin-right:20px" />
</Col>
<Col span="8">
<span>状态:</span>
<Select v-model="filters.apply.status" style="width: 60%">
<Select v-model="filters.apply.status" style="width: 150px">
<Option v-for="opt in statusOptions" :key="opt.id" :value="opt.id">{{ opt.name }}</Option>
</Select>
</Col>
<Col span="4" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('apply')">搜索</Button>
<Button class="mr10" @click="handleReset('apply')">重置</Button>
<Button type="success" @click="openApplyModal">申请</Button>
<Button type="primary" class="mr10" @click="handleReset('apply')">重置</Button>
<Button type="primary" @click="openApplyModal">申请</Button>
</Col>
</Row>
</div>
......@@ -58,21 +56,19 @@
<TabPane label="审核历史查询" name="history">
<div class="search-div">
<Row type="flex" :gutter="16" align="middle">
<Col span="12">
<Col span="20">
<span>开始:</span>
<DatePicker v-model="filters.history.start_time" type="date" placeholder="开始日期" style="min-width:110px;margin-right:20px" />
<span>结束:</span>
<DatePicker v-model="filters.history.end_time" type="date" placeholder="结束日期" style="min-width:110px;margin-right:20px" />
</Col>
<Col span="8">
<span>状态:</span>
<Select v-model="filters.history.status" style="width: 60%">
<Select v-model="filters.history.status" style="width: 150px" class="mr10">
<Option v-for="opt in statusOptions" :key="opt.id" :value="opt.id">{{ opt.name }}</Option>
</Select>
</Col>
<Col span="4" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('history')">搜索</Button>
<Button @click="handleReset('history')">重置</Button>
<Button type="primary" @click="handleReset('history')">重置</Button>
</Col>
</Row>
</div>
......@@ -88,21 +84,19 @@
<TabPane label="请假申请查询" name="query">
<div class="search-div">
<Row type="flex" :gutter="16" align="middle">
<Col span="8">
<Col span="16">
<span>开始:</span>
<DatePicker v-model="filters.query.start_time" type="date" placeholder="开始日期" style="min-width:110px;margin-right:20px" />
<span>结束:</span>
<DatePicker v-model="filters.query.end_time" type="date" placeholder="结束日期" style="min-width:110px;margin-right:20px" />
</Col>
<Col span="8">
<span>状态:</span>
<Select v-model="filters.query.status" style="width: 60%">
<Select v-model="filters.query.status" style="width: 150px">
<Option v-for="opt in statusOptions" :key="opt.id" :value="opt.id">{{ opt.name }}</Option>
</Select>
</Col>
<Col span="8" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch('query')">搜索</Button>
<Button @click="handleReset('query')">重置</Button>
<Button type="primary" @click="handleReset('query')">重置</Button>
</Col>
</Row>
</div>
......@@ -124,11 +118,11 @@
<span>结束:</span>
<DatePicker v-model="statsEnd" type="date" placeholder="结束日期" style="min-width:110px;margin-right:20px" />
<span>部门:</span>
<Input v-model="statsDept" placeholder="请输入部门名称" style="width:65%" />
<Input v-model="statsDept" placeholder="请输入部门名称" style="width: 200px" />
</Col>
<Col span="8" class="text-right">
<Button type="primary" class="mr10" @click="loadStats">统计</Button>
<Button @click="resetStats">重置</Button>
<Button type="primary" @click="resetStats">重置</Button>
</Col>
</Row>
</div>
......@@ -425,6 +419,7 @@ export default {
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: getPendingList, history: getApprovalHistory, query: getLeaveList }
......
......@@ -10,11 +10,11 @@
</Col> -->
<Col :span="12" class="text-left">
<span>姓名:</span>
<Input v-model="filters.name" placeholder="请输入姓名" style="width: 40%" />
<Input v-model="filters.name" placeholder="请输入姓名" style="width: 200px" />
</Col>
<Col :span="12" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch">搜索</Button>
<Button @click="handleReset" class="mr10">重置</Button>
<Button type="primary" @click="handleReset" class="mr10">重置</Button>
<Button type="primary" class="mr10" @click="handleSync">同步</Button>
</Col>
</Row>
......@@ -56,10 +56,10 @@
<!-- 角色已移除 -->
<!-- <FormItem label="归属部门">
<SelectDisplay v-model="editModal.form.office_id" placeholder="请输入机构" @open="openOfficeSelect(editModal.form)" />
</FormItem>
<FormItem label="直属领导">
<SelectDisplay v-model="editModal.form.leader" placeholder="只能选择本级或父机构人员" @open="openSelectLeader(editModal.form)" />
</FormItem> -->
<FormItem label="直属领导">
<Input v-model="editModal.form.leader_name" readonly placeholder="点击选择直属领导" @click.native="selectLeader" style="cursor: pointer" />
</FormItem>
<FormItem label="是否离职">
<Select v-model="editModal.form.is_leave" style="width: 160px">
<Option :value="0">未离职</Option>
......@@ -72,16 +72,25 @@
<Button type="primary" :loading="editModal.saving" @click="saveEdit">保存</Button>
</div>
</Modal>
<!-- 人员选择器 -->
<UserSelector
v-model="userSelectorVisible"
permission=""
@on-ok="handleLeaderSelected"
@cancel="userSelectorVisible = false" />
<!-- 领导与归属部门选择已移除 -->
</div>
</template>
<script>
import { getDmUserList, syncDmUsersByOffice, saveDmUser } from '@/api/key-dm-user'
import UserSelector from './userSelector.vue'
export default {
name: 'key-dm-user-index',
components: {},
components: { UserSelector },
data () {
return {
officeCode: '',
......@@ -103,7 +112,8 @@ export default {
visible: false,
saving: false,
form: {}
}
},
userSelectorVisible: false
// leader / office state 已移除
}
},
......@@ -188,6 +198,14 @@ export default {
this.editModal.form = copy
this.editModal.visible = true
},
selectLeader () {
this.userSelectorVisible = true
},
handleLeaderSelected (user) {
this.editModal.form.leader = user.id
this.editModal.form.leader_name = user.name
this.userSelectorVisible = false
},
/* leader / office 相关方法已移除 */
saveEdit () {
this.$refs.editForm.validate(valid => {
......
......@@ -4,7 +4,7 @@
<Row type="flex" :gutter="16" align="middle">
<Col :span="12">
<span>姓名:</span>
<Input v-model="filters.name" placeholder="请输入姓名" @on-enter="handleSearch" />
<Input v-model="filters.name" placeholder="请输入姓名" style="width: 200px" />
</Col>
<Col :span="12" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch">搜索</Button>
......@@ -37,7 +37,9 @@ export default {
name: 'user-multi-selector',
props: {
// v-model: value 控制显示
value: { type: Boolean, default: false }
value: { type: Boolean, default: false },
// 范围参数1父,本级,下级2本级
round: { type: String, default: '1' }
},
data () {
return {
......@@ -50,12 +52,12 @@ export default {
confirming: false,
columns: [
{ type: 'selection', width: 60, align: 'center' },
{ title: '归属部门', key: 'office_name', align: 'center', minWidth: 150 },
{ title: '姓名', key: 'name', align: 'center', minWidth: 100 },
{ title: '工号', key: 'gh', align: 'center', minWidth: 120 },
{ title: '邮箱', key: 'email', align: 'center', minWidth: 150 },
// { title: '邮箱', key: 'email', align: 'center', minWidth: 150 },
{ title: '电话', key: 'phone', align: 'center', minWidth: 120 },
{ title: '手机', key: 'mobile', align: 'center', minWidth: 120 },
{ title: '归属部门', key: 'office_name', align: 'center', minWidth: 150 }
{ title: '手机', key: 'mobile', align: 'center', minWidth: 120 }
]
}
},
......@@ -78,11 +80,7 @@ export default {
methods: {
fetchList () {
this.loading = true
const payload = {
pageNo: this.pager.pageNo,
pageSize: this.pager.pageSize,
params: this.filters
}
const payload = Object.assign({}, { pageNo: this.pager.pageNo, pageSize: this.pager.pageSize, round: this.round }, this.filters)
getUserMultiSelectorList(payload).then(ret => {
if (ret.data && ret.data.errcode === 0) {
const data = ret.data.data || {}
......
<template>
<Modal v-model="visible" title="选择人员" width="900" :mask-closable="false">
<div class="search-div">
<Row type="flex" :gutter="16" align="middle">
<Col :span="12">
<span>姓名:</span>
<Input v-model="filters.name" placeholder="请输入姓名" style="width: 200px" />
</Col>
<Col :span="12" class="text-right">
<Button type="primary" class="mr10" @click="handleSearch">搜索</Button>
<Button @click="handleReset">重置</Button>
</Col>
</Row>
</div>
<Table
:data="rows"
:loading="loading"
:columns="columns"
border
highlight-row
@on-current-change="onCurrentRowChange"
@on-row-dblclick="onRowDblclick" />
<Page class="page_style" :total="pager.totalRecord" :current="pager.pageNo" :page-size="pager.pageSize" show-total show-sizer
@on-change="pageChange" @on-page-size-change="sizeChange" />
<div slot="footer">
<Button @click="handleCancel">取消</Button>
<Button type="primary" :loading="confirming" @click="handleConfirm" :disabled="!selectedRow">确定</Button>
</div>
</Modal>
</template>
<script>
import { getUserSelectorList } from '@/api/key-dm-user'
export default {
name: 'user-selector',
props: {
// v-model: value 控制显示
value: { type: Boolean, default: false },
// 权限参数
permission: { type: String, default: '' }
},
data () {
return {
visibleInternal: false,
filters: { name: '' },
rows: [],
loading: false,
pager: { pageNo: 1, pageSize: 10, totalRecord: 0 },
selectedRow: null,
confirming: false,
columns: [
{ title: '姓名', key: 'name', align: 'center', minWidth: 100 },
{ title: '工号', key: 'gh', align: 'center', minWidth: 120 },
{ title: '邮箱', key: 'email', align: 'center', minWidth: 150 },
{ title: '电话', key: 'phone', align: 'center', minWidth: 120 },
{ title: '手机', key: 'mobile', align: 'center', minWidth: 120 },
{ title: '归属部门', key: 'office_name', align: 'center', minWidth: 150 },
{ title: '人员分类', key: 'category_names', align: 'center', minWidth: 200 }
]
}
},
computed: {
visible: {
get () { return this.value },
set (v) { this.$emit('input', v) }
}
},
watch: {
visible (v) {
if (v) {
this.pager.pageNo = 1
this.selectedRow = null
this.filters.name = ''
this.fetchList()
}
}
},
methods: {
fetchList () {
this.loading = true
const payload = Object.assign({},
{ pageNo: this.pager.pageNo, pageSize: this.pager.pageSize, office_code: this.officeCode, permission: this.permission },
this.filters)
getUserSelectorList(payload).then(ret => {
if (ret.data && ret.data.errcode === 0) {
const data = ret.data.data || {}
this.rows = data.results || []
this.pager.totalRecord = data.totalRecord || 0
} else {
this.$Notice.error({ title: '查询失败', desc: ret.data && ret.data.errmsg })
}
}).finally(() => { this.loading = false })
},
handleSearch () {
this.pager.pageNo = 1
this.fetchList()
},
handleReset () {
this.filters = { name: '' }
this.pager.pageNo = 1
this.fetchList()
},
pageChange (pageNo) {
this.pager.pageNo = pageNo
this.fetchList()
},
sizeChange (size) {
this.pager.pageSize = size
this.pager.pageNo = 1
this.fetchList()
},
onCurrentRowChange (currentRow, oldCurrentRow) {
this.selectedRow = currentRow
},
onRowDblclick (row, index) {
this.selectedRow = row
this.handleConfirm()
},
handleCancel () {
this.$emit('cancel')
this.$emit('input', false)
},
handleConfirm () {
if (!this.selectedRow) {
this.$Message.warning('请先选择人员')
return
}
this.confirming = true
// emit selected user to parent
this.$emit('on-ok', this.selectedRow)
this.$emit('input', false)
this.confirming = false
}
}
}
</script>
<style scoped>
.search-div { border: 1px solid #dce1e7; padding: 12px; margin-bottom: 12px; background-color: #f8fbff; }
.mr10 { margin-right: 10px; }
.page_style { margin-top: 12px; text-align: right; }
.text-right { text-align: right; }
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论