Browse Source

feat(partspare): 新增备品备件采购申请功能

- 在maintenanceSkillCard组件中完善技能掌握状态变更逻辑,掌握时默认满分和当天日期,未掌握时分数置零
- 新增partsparePurchase API模块,提供采购申请的增删改查和采购接口
- 创建备品备件采购申请页面,支持新建、查询、修改、删除和批量采购功能
- 实现备品备件选择弹窗和采购申请修改弹窗功能
- 添加采购权限控制和表格响应式布局支持
master
qiankanghui 4 weeks ago
parent
commit
ebb1e13fc4
  1. 21
      src/api/partspare/partsparePurchase.js
  2. 665
      src/views/modules/partspare/partsparePurchase.vue
  3. 11
      src/views/modules/repairSkill/maintenanceSkillCard.vue

21
src/api/partspare/partsparePurchase.js

@ -0,0 +1,21 @@
import { createAPI } from "@/utils/httpRequest.js";
// 待采购申请列表(分页)
export const queryPurchaseApplyPage = data =>
createAPI(`/pms/partspare/purchase/apply/queryPage`, "post", data);
// 保存采购申请(写入中间表)
export const savePurchaseApply = data =>
createAPI(`/pms/partspare/purchase/apply/save`, "post", data);
// 修改采购申请
export const updatePurchaseApply = data =>
createAPI(`/pms/partspare/purchase/apply/update`, "post", data);
// 删除采购申请
export const deletePurchaseApply = data =>
createAPI(`/pms/partspare/purchase/apply/delete`, "post", data);
// 采购(落库到 purchase_order_head + purchase_order)
export const doPurchase = data =>
createAPI(`/pms/partspare/purchase/apply/purchase`, "post", data);

665
src/views/modules/partspare/partsparePurchase.vue

@ -0,0 +1,665 @@
<template>
<div class="mod-config">
<!-- 新建采购申请 -->
<el-card shadow="never" class="apply-card">
<div slot="header" class="card-header">
<span class="card-title">新建采购申请</span>
<span class="card-tip">申请单号自动生成</span>
</div>
<div class="card-sub-tip">带 <span class="required-star">*</span> 的字段为必填项</div>
<el-form ref="applyForm" :inline="true" label-position="top" :model="formData" :rules="formRules">
<el-form-item label="BU" prop="buKey">
<el-select v-model="formData.buKey" placeholder="请选择所属 BU" clearable style="width: 240px">
<el-option
v-for="item in userBuList"
:key="item.site + '_' + item.buNo"
:label="item.buDesc"
:value="composeBuKey(item.site, item.buNo)">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="备品备件编码">
<el-input v-model="formData.partNo" placeholder="输入编码自动带出信息" readonly style="width: 240px"
@click.native="openPartDialog">
<i slot="suffix" class="el-input__icon el-icon-search" @click="openPartDialog"></i>
</el-input>
</el-form-item>
<el-form-item label="备品备件名称" prop="partDescription">
<el-input v-model="formData.partDescription" placeholder="请输入物品名称" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="规格型号">
<el-input v-model="formData.spec" placeholder="请输入规格型号" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="采购数量" prop="qty">
<el-input-number v-model="formData.qty" :min="0" :controls="false" placeholder="0" style="width: 200px"></el-input-number>
<span class="unit-suffix">件</span>
</el-form-item>
<el-form-item label="供应商">
<el-input v-model="formData.supplierName" placeholder="根据备件自动带出" readonly style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="物品链接">
<el-input v-model="formData.itemUrl" placeholder="粘贴采购物品链接(选填)" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="请购原因" prop="purchaseReason">
<el-input v-model="formData.purchaseReason" placeholder="请填写采购原因" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label=" ">
<el-button @click="resetForm">清空</el-button>
<el-button type="primary" :loading="saveLoading" @click="saveApply">保存申请</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 待采购申请 -->
<el-card shadow="never" class="apply-card" style="margin-top: 12px">
<div slot="header" class="card-header">
<span class="card-title">待采购申请</span>
<div v-if="canPurchase">
<span class="card-tip">已选择 {{ selectionDataList.length }} 条</span>
<el-button type="primary" size="small" icon="el-icon-shopping-cart-full" :loading="purchaseLoading"
style="margin-left: 12px" @click="handlePurchase">采购</el-button>
</div>
</div>
<el-table
:data="dataList"
border
ref="applyTable"
:height="height"
v-loading="dataListLoading"
@selection-change="handleSelectionChange"
style="width: 100%;">
<el-table-column v-if="canPurchase" type="selection" width="50" align="center" header-align="center"></el-table-column>
<el-table-column prop="buDesc" label="BU" min-width="90" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="partNo" label="备品备件编码" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="partDescription" label="备品备件名称" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column prop="spec" label="规格型号" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="qty" label="采购数量" min-width="90" align="right" header-align="center"></el-table-column>
<el-table-column prop="supplierName" label="供应商" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column label="物品链接" min-width="100" align="center" header-align="center">
<template slot-scope="scope">
<el-link v-if="scope.row.itemUrl" type="primary" :href="scope.row.itemUrl" target="_blank">查看链接</el-link>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="purchaseReason" label="请购原因" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="applyBy" label="申请人" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column
v-if="canPurchase"
fixed="right"
header-align="center"
align="center"
width="120"
label="操作">
<template slot-scope="scope">
<el-link style="cursor: pointer" @click="handleEdit(scope.row)">修改</el-link>
<el-link style="cursor: pointer" @click="handleDelete(scope.row)">| 删除</el-link>
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin-top: 8px"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[20, 50, 100, 500]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
</el-card>
<!-- 备品备件选择弹窗 -->
<el-dialog title="选择备品备件" :close-on-click-modal="false" v-drag :visible.sync="partDialogVisible" width="900px">
<el-form :inline="true" label-position="top" :model="partQuery">
<el-form-item label="备品备件编码">
<el-input v-model="partQuery.partNo" clearable style="width: 140px"></el-input>
</el-form-item>
<el-form-item label="备品备件名称">
<el-input v-model="partQuery.partDescription" clearable style="width: 180px"></el-input>
</el-form-item>
<el-form-item label=" ">
<el-button type="primary" @click="getPartList">查询</el-button>
</el-form-item>
</el-form>
<el-table
:data="partList"
border
height="400px"
v-loading="partLoading"
@row-click="choosePart"
highlight-current-row
style="width: 100%;">
<el-table-column prop="partNo" label="备品备件编码" min-width="120"></el-table-column>
<el-table-column prop="partDescription" label="备品备件名称" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="spec" label="规格型号" min-width="140" show-overflow-tooltip></el-table-column>
<el-table-column prop="umid" label="单位" min-width="70" align="center" header-align="center"></el-table-column>
<el-table-column prop="supplierId" label="供应商编码" min-width="120" show-overflow-tooltip></el-table-column>
</el-table>
<el-pagination
style="margin-top: 8px"
@size-change="partSizeChange"
@current-change="partCurrentChange"
:current-page="partQuery.page"
:page-sizes="[20, 50, 100]"
:page-size="partQuery.limit"
:total="partTotal"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<span slot="footer">
<el-button type="primary" @click="partDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<!-- 修改采购申请弹窗 -->
<el-dialog title="修改采购申请" :close-on-click-modal="false" v-drag :visible.sync="editDialogVisible" width="640px">
<el-form ref="editForm" label-position="top" :model="editData" :rules="formRules" class="edit-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="BU" prop="buKey">
<el-select v-model="editData.buKey" placeholder="请选择所属 BU" disabled style="width: 100%">
<el-option
v-for="item in userBuList"
:key="'e_' + item.site + '_' + item.buNo"
:label="item.buDesc"
:value="composeBuKey(item.site, item.buNo)">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备品备件编码">
<el-input v-model="editData.partNo" placeholder="点击选择备品备件" readonly style="width: 100%"
@click.native="openPartDialog('edit')">
<i slot="suffix" class="el-input__icon el-icon-search" @click="openPartDialog('edit')"></i>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备品备件名称" prop="partDescription">
<el-input v-model="editData.partDescription" placeholder="请输入物品名称" style="width: 100%"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规格型号">
<el-input v-model="editData.spec" style="width: 100%"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购数量" prop="qty">
<div class="qty-field">
<el-input-number v-model="editData.qty" :min="0" :controls="false" class="qty-input"></el-input-number>
<span class="unit-suffix">件</span>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商">
<el-input v-model="editData.supplierName" readonly style="width: 100%"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="物品链接">
<el-input v-model="editData.itemUrl" style="width: 100%"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="请购原因" prop="purchaseReason">
<el-input v-model="editData.purchaseReason" style="width: 100%"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer">
<el-button type="primary" :loading="editLoading" @click="submitEdit">确定</el-button>
<el-button @click="editDialogVisible = false">取消</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getAllBuList } from '@/api/factory/site.js'
import { getPartSpareList } from '@/api/partspare/partspare.js'
import {
queryPurchaseApplyPage,
savePurchaseApply,
updatePurchaseApply,
deletePurchaseApply,
doPurchase
} from '@/api/partspare/partsparePurchase.js'
export default {
data () {
return {
userBuList: [],
defaultBuKey: '',
height: 300,
// 新建表单
formData: this.getDefaultFormData(),
formRules: {
buKey: [{ required: true, message: '请选择BU', trigger: ['blur', 'change'] }],
partDescription: [{ required: true, message: '请输入备品备件名称', trigger: 'blur' }],
qty: [{ required: true, message: '请输入采购数量', trigger: ['blur', 'change'] }],
purchaseReason: [{ required: true, message: '请输入请购原因', trigger: 'blur' }]
},
saveLoading: false,
dataList: [],
selectionDataList: [],
pageIndex: 1,
pageSize: 20,
totalPage: 0,
dataListLoading: false,
purchaseLoading: false,
// 备件选择弹窗
partDialogVisible: false,
partDialogTarget: 'form',
partQuery: {
partNo: '',
partDescription: '',
page: 1,
limit: 20
},
partList: [],
partTotal: 0,
partLoading: false,
// 修改弹窗
editDialogVisible: false,
editLoading: false,
editData: this.getDefaultFormData()
}
},
computed: {
// 采购/编辑权限:拥有 partSpare:purchase:edit 才能采购、修改、删除;否则仅可保存申请
canPurchase () {
return this.isAuth('partSpare:purchase:edit')
}
},
created () {
this.getBu()
},
mounted () {
this.$nextTick(() => {
this.calcTableHeight()
window.addEventListener('resize', this.calcTableHeight)
})
},
beforeDestroy () {
window.removeEventListener('resize', this.calcTableHeight)
},
methods: {
getDefaultFormData () {
return {
id: null,
buKey: '',
partNo: '',
partDescription: '',
spec: '',
umid: '',
qty: 0,
supplierId: '',
supplierName: '',
itemUrl: '',
purchaseReason: ''
}
},
// 计算表格高度,使其向下铺满至分页上方
calcTableHeight () {
this.$nextTick(() => {
const table = this.$refs.applyTable
if (table && table.$el) {
const top = table.$el.getBoundingClientRect().top
// 预留分页高度及上下间距
this.height = Math.max(window.innerHeight - top - 70, 200)
} else {
this.height = window.innerHeight - 380
}
})
},
// 获取 BU 列表(新建采购申请默认选中 RFID)
getBu () {
getAllBuList({ username: this.$store.state.user.name }).then(({ data }) => {
if (data && data.code === 0) {
this.userBuList = data.rows || []
this.defaultBuKey = this.getDefaultBuKey()
this.formData.buKey = this.defaultBuKey
}
this.handleQuery()
}).catch(() => {
this.handleQuery()
})
},
getDefaultBuKey () {
const target = this.userBuList.find(item => {
const pure = this.normalizeBuNo(item.site, item.buNo)
return pure && pure.toUpperCase().indexOf('RFID') >= 0
})
return target ? this.composeBuKey(target.site, target.buNo) : ''
},
composeBuKey (site, buNo) {
return site + '_' + this.normalizeBuNo(site, buNo)
},
normalizeBuNo (site, buNo) {
if (buNo === null || buNo === undefined) {
return ''
}
const buText = String(buNo).trim()
const siteText = (site === null || site === undefined) ? '' : String(site).trim()
const prefix = siteText ? siteText + '_' : ''
if (prefix && buText.indexOf(prefix) === 0) {
return buText.substring(prefix.length)
}
return buText
},
parseBuKey (buKey) {
if (!buKey || buKey.indexOf('_') < 0) {
return { site: '', buNo: '' }
}
const idx = buKey.indexOf('_')
const site = buKey.substring(0, idx)
const rawBuNo = buKey.substring(idx + 1)
return { site: site, buNo: this.normalizeBuNo(site, rawBuNo) }
},
getBuDescByKey (buKey) {
const item = this.userBuList.find(i => this.composeBuKey(i.site, i.buNo) === buKey)
return item ? item.buDesc : ''
},
// 保存申请
saveApply () {
this.$refs.applyForm.validate((valid) => {
if (!valid) {
return
}
const buData = this.parseBuKey(this.formData.buKey)
if (!buData.site || !buData.buNo) {
this.$message.warning('请选择BU')
return
}
const params = { ...this.formData, ...buData }
delete params.buKey
this.saveLoading = true
savePurchaseApply(params).then(({ data }) => {
this.saveLoading = false
if (data && data.code === 0) {
this.$message.success(data.msg || '保存成功')
this.resetForm()
this.handleQuery()
} else {
this.$message.error(data.msg || '保存失败')
}
}).catch(() => {
this.saveLoading = false
})
})
},
resetForm () {
this.formData = this.getDefaultFormData()
this.formData.buKey = this.defaultBuKey
if (this.$refs.applyForm) {
this.$refs.applyForm.clearValidate()
}
},
// 打开备件选择弹窗
openPartDialog (target) {
this.partDialogTarget = target || 'form'
const buKey = this.partDialogTarget === 'edit' ? this.editData.buKey : this.formData.buKey
const buData = this.parseBuKey(buKey)
if (!buData.site || !buData.buNo) {
this.$message.warning('请先选择BU')
return
}
this.partQuery = { partNo: '', partDescription: '', page: 1, limit: 20, buDesc: this.getBuDescByKey(buKey) }
this.partDialogVisible = true
this.getPartList()
},
getPartList () {
this.partLoading = true
const query = {
buDesc: this.partQuery.buDesc,
partNo: this.partQuery.partNo,
partDescription: this.partQuery.partDescription,
page: this.partQuery.page,
limit: this.partQuery.limit
}
getPartSpareList(query).then(({ data }) => {
this.partLoading = false
if (data && data.code === 0) {
this.partList = data.page.list
this.partTotal = data.page.totalCount
} else {
this.partList = []
this.partTotal = 0
}
}).catch(() => {
this.partLoading = false
})
},
partSizeChange (val) {
this.partQuery.limit = val
this.partQuery.page = 1
this.getPartList()
},
partCurrentChange (val) {
this.partQuery.page = val
this.getPartList()
},
// 选中备件回填
choosePart (row) {
const target = this.partDialogTarget === 'edit' ? this.editData : this.formData
target.partNo = row.partNo
target.partDescription = row.partDescription
target.spec = row.spec
target.umid = row.umid
target.supplierId = row.supplierId
target.supplierName = row.supplierName || row.supplierId
this.partDialogVisible = false
},
// 待采购列表查询(无查询条件,展示全部待采购数据)
handleQuery () {
this.dataListLoading = true
const params = {
page: this.pageIndex,
limit: this.pageSize
}
queryPurchaseApplyPage(params).then(({ data }) => {
this.dataListLoading = false
if (data && data.code === 0) {
this.dataList = data.page.list
this.pageIndex = data.page.currPage
this.pageSize = data.page.pageSize
this.totalPage = data.page.totalCount
} else {
this.dataList = []
this.totalPage = 0
}
}).catch(() => {
this.dataListLoading = false
})
},
sizeChangeHandle (val) {
this.pageSize = val
this.pageIndex = 1
this.handleQuery()
},
currentChangeHandle (val) {
this.pageIndex = val
this.handleQuery()
},
handleSelectionChange (val) {
this.selectionDataList = val
},
// 采购
handlePurchase () {
if (this.selectionDataList.length === 0) {
this.$message.warning('请选择需要采购的数据')
return
}
this.$confirm(`确定对选中的 ${this.selectionDataList.length} 条数据执行采购操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const ids = this.selectionDataList.map(item => String(item.id))
this.purchaseLoading = true
doPurchase(ids).then(({ data }) => {
this.purchaseLoading = false
if (data && data.code === 0) {
this.$message.success(data.msg || '采购成功')
if (this.$refs.applyTable) {
this.$refs.applyTable.clearSelection()
}
this.handleQuery()
} else {
this.$message.error(data.msg || '采购失败')
}
}).catch(() => {
this.purchaseLoading = false
})
}).catch(() => {})
},
// 修改
handleEdit (row) {
if (!row) {
return
}
this.editData = {
id: row.id,
buKey: this.composeBuKey(row.site, row.buNo),
partNo: row.partNo,
partDescription: row.partDescription,
spec: row.spec,
umid: row.umid,
qty: row.qty,
supplierId: row.supplierId,
supplierName: row.supplierName,
itemUrl: row.itemUrl,
purchaseReason: row.purchaseReason
}
this.editDialogVisible = true
this.$nextTick(() => {
if (this.$refs.editForm) {
this.$refs.editForm.clearValidate()
}
})
},
submitEdit () {
this.$refs.editForm.validate((valid) => {
if (!valid) {
return
}
const buData = this.parseBuKey(this.editData.buKey)
if (!buData.site || !buData.buNo) {
this.$message.warning('请选择BU')
return
}
const params = { ...this.editData, ...buData }
delete params.buKey
params.id = String(params.id)
this.editLoading = true
updatePurchaseApply(params).then(({ data }) => {
this.editLoading = false
if (data && data.code === 0) {
this.$message.success(data.msg || '修改成功')
this.editDialogVisible = false
this.handleQuery()
} else {
this.$message.error(data.msg || '修改失败')
}
}).catch(() => {
this.editLoading = false
})
})
},
// 删除
handleDelete (row) {
if (!row) {
return
}
this.$confirm(`确定删除该条采购申请?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const ids = [String(row.id)]
deletePurchaseApply(ids).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success(data.msg || '删除成功')
if (this.$refs.applyTable) {
this.$refs.applyTable.clearSelection()
}
this.handleQuery()
} else {
this.$message.error(data.msg || '删除失败')
}
})
}).catch(() => {})
}
}
}
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-size: 16px;
font-weight: bold;
}
.card-tip {
font-size: 12px;
color: #909399;
margin-right: 8px;
}
.card-sub-tip {
font-size: 12px;
color: #909399;
margin-bottom: 8px;
}
.required-star {
color: #f56c6c;
}
.unit-suffix {
margin-left: 6px;
color: #909399;
font-size: 12px;
}
.apply-card >>> .el-card__header {
padding: 12px 20px;
}
.edit-form >>> .el-form-item {
margin-bottom: 12px;
}
.qty-field {
display: flex;
align-items: center;
}
.qty-field .qty-input {
flex: 1;
}
</style>

11
src/views/modules/repairSkill/maintenanceSkillCard.vue

@ -298,9 +298,14 @@ export default {
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' 00:00:00'
},
onMasteryChange (row) {
// 选择“是”时,若未填掌握时间则默认今天
if (row.masteryStatus === 'Y' && !row.masterDate) {
row.masterDate = this.todayStr()
// 掌握情况为“是”:分数默认取模板满分,未填掌握时间则默认今天;为“否”:分数置 0
if (row.masteryStatus === 'Y') {
row.score = Number(row.maxScore) || 0
if (!row.masterDate) {
row.masterDate = this.todayStr()
}
} else if (row.masteryStatus === 'N') {
row.score = 0
}
},
getDataList (flag) {

Loading…
Cancel
Save