Browse Source

feat(inspection): 添加检验排程概览页面

- 新增 inspectionScheduleOverview.vue 页面,包含 QC 排程情况和验货进度列表两个标签页
- 实现所有 QC 人员排程可视化展示功能,支持按日期查询排程任务
- 添加验货进度列表功能,支持多条件筛选和分页查询
- 集成日历视图展示 QC 检验任务分布情况
- 实现任务卡片显示 QC 名称、供应商信息和城市简称
- 添加申请变更中任务的特殊样式标识

feat(part): 优化产品信息管理功能

- 在新增产品时实现 SKU 联想自动补全功能
- 添加基准料号失焦时查询已维护 SKU 的逻辑
- 集成供应商产品价格表校验机制
- 实现新品审核流程的自动处理功能
- 添加 SKU 自动建议列表和前缀匹配搜索

feat(supplier): 增强价格表报告权限控制

- 添加特殊权限 SRM0003 控制价格表导出功能
- 实现导出权限动态校验和界面按钮显示控制
- 集成权限验证 API 接口调用

feat(supplier): 扩展供应商零件价格管理

- 添加删除供应商零件阶梯价功能
- 实现供应商零件按 Item 和 SKU 校验存在性接口
- 集成新品审核流程启动功能
master
qiankanghui 2 weeks ago
parent
commit
21753c5a88
  1. 2
      src/api/part/partInfo.js
  2. 2
      src/api/supplier/supplierPartMoqPrice.js
  3. 732
      src/views/modules/inspection/inspectionScheduleOverview.vue
  4. 140
      src/views/modules/srmPart/partInfoList.vue
  5. 23
      src/views/modules/supplier/priceSheetReport.vue
  6. 39
      src/views/modules/supplier/supplierPartMoqPrice.vue

2
src/api/part/partInfo.js

@ -7,6 +7,8 @@ export const updatePart = data => createAPI('/part/save', 'POST', data)
export const copyPartByBase = data => createAPI('/part/copyByBasePartNo', 'POST', data) export const copyPartByBase = data => createAPI('/part/copyByBasePartNo', 'POST', data)
export const startPartFormalApprove = data => createAPI('/part/startFormalApprove', 'POST', data) export const startPartFormalApprove = data => createAPI('/part/startFormalApprove', 'POST', data)
export const changePartDefaultSupplier = data => createAPI('/part/changeDefaultSupplier', 'POST', data) export const changePartDefaultSupplier = data => createAPI('/part/changeDefaultSupplier', 'POST', data)
// 查询基准料号下已维护过的SKU(去重),用于新增时SKU联想
export const listSkuByBasePartNo = data => createAPI('/part/listSkuByBasePartNo', 'POST', data)
export const searchPartInfo = data => createAPI('part/getPart','POST', data) export const searchPartInfo = data => createAPI('part/getPart','POST', data)
export const deletePart = data => createAPI('/part/delete', 'POST', data) export const deletePart = data => createAPI('/part/delete', 'POST', data)
//产品审批 //产品审批

2
src/api/supplier/supplierPartMoqPrice.js

@ -5,6 +5,8 @@ export const listSupplierPartMoq = data => createAPI('/supplier/supplierPartMoqP
export const listSupplierPartMoqPage = data => createAPI('/supplier/supplierPartMoqPrice/listSupplierPartPage', 'post', data) export const listSupplierPartMoqPage = data => createAPI('/supplier/supplierPartMoqPrice/listSupplierPartPage', 'post', data)
export const saveSupplierPartMoq = data => createAPI('/supplier/supplierPartMoqPrice/saveSupplierPart', 'post', data) export const saveSupplierPartMoq = data => createAPI('/supplier/supplierPartMoqPrice/saveSupplierPart', 'post', data)
export const addSupplierPartMoq = data => createAPI('/supplier/supplierPartMoqPrice/addSupplierPart', 'post', data) export const addSupplierPartMoq = data => createAPI('/supplier/supplierPartMoqPrice/addSupplierPart', 'post', data)
export const deleteSupplierPartMoq = data => createAPI('/supplier/supplierPartMoqPrice/deleteSupplierPart', 'post', data)
export const checkSupplierPartByItemSku = data => createAPI('/supplier/supplierPartMoqPrice/checkSupplierPartByItemSku', 'post', data)
// 阶梯价接口 - rqrq // 阶梯价接口 - rqrq
export const listCurrentTierPrice = data => createAPI('/supplier/supplierPartMoqPrice/listCurrentTierPrice', 'post', data) export const listCurrentTierPrice = data => createAPI('/supplier/supplierPartMoqPrice/listCurrentTierPrice', 'post', data)

732
src/views/modules/inspection/inspectionScheduleOverview.vue

@ -0,0 +1,732 @@
<template>
<div class="customer-css inspection-schedule-overview" ref="pageRoot">
<el-tabs v-model="activeTab" type="border-card" class="customer-tab" @tab-click="handleTabClick">
<!-- 页签一:所有QC人员排程情况 -->
<el-tab-pane label="QC排程情况" name="schedule">
<el-form :inline="true" size="small" class="all-qc-query-form">
<el-form-item label="起始日期">
<el-date-picker
v-model="allQcQuery.startDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择起始日期"
style="width: 150px;" />
</el-form-item>
<el-form-item label=" ">
<el-button type="primary" size="small" @click="queryAllQcSchedule">查询</el-button>
</el-form-item>
</el-form>
<div class="all-qc-schedule-container" v-loading="allQcLoading">
<div class="schedule-days-grid">
<div v-for="day in allScheduleDays" :key="day.date" class="schedule-day-card">
<div class="day-header">
<span class="date-label">{{ day.dateLabel }}</span>
<span class="date-weekday">{{ day.weekday }}</span>
</div>
<div class="day-body">
<div
v-for="qcTask in day.qcTasks"
:key="qcTask.userName"
class="qc-task-item">
<div class="task-list">
<div
v-for="task in qcTask.tasks"
:key="task.requestNo"
:class="['mini-task', { 'mini-task--change-applying': isChangeApplyingTask(task) }]">
<span class="task-line-text" :title="buildTaskLineText(task, qcTask.qcName)">{{ buildTaskLineText(task, qcTask.qcName) }}</span>
</div>
</div>
</div>
<div v-if="day.qcTasks.length === 0" class="empty-day">
<span class="empty-text">暂无排程</span>
</div>
</div>
</div>
</div>
</div>
</el-tab-pane>
<!-- 页签二:已排程/验货中/申请变更中 列表 -->
<el-tab-pane label="验货进度列表" name="progress">
<el-form :inline="true" label-position="top" class="search-form-inline">
<div class="search-row">
<el-form-item class="search-item">
<span style="cursor: pointer" slot="label" @click="getBaseList(520)"><a href="#">供应商编码</a></span>
<el-input v-model="searchData.supplierNo" style="width: 120px" @keyup.enter.native="getMainData"/>
</el-form-item>
<el-form-item label="供应商名称" class="search-item">
<el-input v-model="searchData.supplierName" style="width: 220px" @keyup.enter.native="getMainData"/>
</el-form-item>
<el-form-item class="search-item">
<span style="cursor: pointer" slot="label" @click="getBaseList(2016, 'qcOperator')"><a href="#">QC人员</a></span>
<el-input v-model="searchData.qcOperator" clearable style="width: 120px" @keyup.enter.native="getMainData"/>
</el-form-item>
<el-form-item class="search-item">
<span style="cursor: pointer" slot="label" @click="getBaseList(2016, 'createBy')"><a href="#">申请人员</a></span>
<el-input v-model="searchData.createBy" clearable style="width: 120px" @keyup.enter.native="getMainData"/>
</el-form-item>
<el-form-item label="申请单号" class="search-item">
<el-input v-model="searchData.requestNo" style="width: 120px" @keyup.enter.native="getMainData"/>
</el-form-item>
</div>
<div class="search-row">
<el-form-item label="状态" class="search-item">
<el-select v-model="searchData.statusFilter" clearable placeholder="全部" style="width: 140px">
<el-option label="已排程" value="Scheduled" />
<el-option label="验货中" value="Inspecting" />
<el-option label="申请变更中" value="ChangeApplying" />
</el-select>
</el-form-item>
<el-form-item label="建议验货日期" class="search-item">
<div class="date-range">
<el-date-picker v-model="searchData.needInspectDateStart" type="date" value-format="yyyy-MM-dd" placeholder="开始" style="width: 150px"/>
<span class="split">-</span>
<el-date-picker v-model="searchData.needInspectDateEnd" type="date" value-format="yyyy-MM-dd" placeholder="结束" style="width: 150px"/>
</div>
</el-form-item>
<el-form-item label="计划验货日期" class="search-item">
<div class="date-range">
<el-date-picker v-model="searchData.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="开始" style="width: 150px"/>
<span class="split">-</span>
<el-date-picker v-model="searchData.planEndDate" type="date" value-format="yyyy-MM-dd" placeholder="结束" style="width: 150px"/>
</div>
</el-form-item>
<el-form-item label=" " class="search-item search-btn-item">
<el-button type="primary" @click="getMainData">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</div>
</el-form>
<el-table
:height="tableHeight"
:data="mainDataList"
border
ref="mainTable"
highlight-current-row
v-loading="dataListLoading"
style="margin-top: 0px; width: 100%;">
<el-table-column
v-for="(item,index) in columnArray1" :key="index"
:sortable="item.columnSortable"
:prop="item.columnProp"
:header-align="item.headerAlign"
:show-overflow-tooltip="item.showOverflowTooltip"
:align="item.align"
:fixed="item.fixed === '' ? false : item.fixed"
:min-width="item.columnWidth"
:label="item.columnLabel">
<template slot-scope="scope">
<span v-if="!item.columnHidden">{{ scope.row[item.columnProp] }}</span>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top: 0px"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[20, 50, 100, 200, 500]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
</el-tab-pane>
</el-tabs>
<Chooselist ref="baseList" @getBaseData="getBaseData"></Chooselist>
</div>
</template>
<script>
import {
getQcPersonList,
getScheduleView,
searchInspectionRequestHeaderList
} from '@/api/inspection/inspectionRequestHeader.js'
import Chooselist from '@/views/modules/common/Chooselist_eam'
// 页签二固定查询的状态分组:已排程、验货中、申请变更中
const PROGRESS_GROUP = 'PROGRESS_GROUP'
// 构建页签二表格列配置(模块级函数,避免依赖组件初始化顺序)
const buildColumn = (prop, label, width, align) => ({
columnProp: prop,
columnLabel: label,
columnWidth: width,
headerAlign: 'center',
align: align || 'left',
showOverflowTooltip: true,
columnSortable: false,
columnHidden: false,
columnImage: false,
fixed: false
})
export default {
name: 'InspectionScheduleOverview',
components: {
Chooselist
},
data () {
return {
activeTab: 'schedule',
functionId: (this.$route && this.$route.meta) ? this.$route.meta.menuId : '',
// ===== 页签一:所有QC排程 =====
qcList: [],
qcLoaded: false,
allQcLoading: false,
allQcScheduleLoaded: false,
allScheduleDays: [],
allQcQuery: {
startDate: ''
},
// ===== 页签二:验货进度列表 =====
tableHeight: 400,
tagNo: '',
searchType: '',
dataListLoading: false,
mainDataList: [],
progressLoaded: false,
pageIndex: 1,
pageSize: 50,
totalPage: 0,
searchData: {
site: this.$store.state.user.site,
requestNo: '',
supplierNo: '',
supplierName: '',
status: PROGRESS_GROUP,
statusFilter: '',
qcOperator: '',
createBy: '',
needInspectDateStart: '',
needInspectDateEnd: '',
planStartDate: '',
planEndDate: '',
planStartDateStr: '',
planEndDateStr: '',
page: 1,
limit: 50
},
columnArray1: [
buildColumn('qcOperator', 'QC人员', '100', 'left'),
buildColumn('status', '状态', '100', 'center'),
buildColumn('requestNo', '申请单号', '120', 'left'),
buildColumn('requestDate', '申请日期', '120', 'center'),
buildColumn('supplierNo', '供应商编码', '120', 'left'),
buildColumn('supplierName', '供应商名称', '150', 'left'),
buildColumn('createBy', '申请人员', '100', 'left'),
buildColumn('needInspectDate', '建议验货日期', '120', 'center'),
buildColumn('planStartDate', '计划验货日期', '120', 'center'),
buildColumn('inspectAddress', '验货地址', '200', 'left'),
buildColumn('inspectCityShort', '城市简称', '120', 'left'),
buildColumn('inspectContract', '联系人', '150', 'left'),
buildColumn('remark', '备注', '150', 'left')
]
}
},
mounted () {
this.allQcQuery.startDate = this.getTodayDateString()
this.$nextTick(() => {
this.updateTableHeight()
// 默认加载页签一
this.loadQcList().then(() => {
this.loadAllQcSchedule()
this.allQcScheduleLoaded = true
})
})
window.addEventListener('resize', this.updateTableHeight)
},
beforeDestroy () {
window.removeEventListener('resize', this.updateTableHeight)
},
methods: {
updateTableHeight () {
// 页签二仅有主表,预留页签头、查询区与分页高度
this.tableHeight = Math.max(280, window.innerHeight - 320)
},
handleTabClick (tab) {
if (tab.name === 'progress') {
this.$nextTick(this.updateTableHeight)
if (!this.progressLoaded) {
this.getMainData()
this.progressLoaded = true
}
} else if (tab.name === 'schedule') {
if (!this.allQcScheduleLoaded) {
this.loadQcList().then(() => {
this.loadAllQcSchedule()
this.allQcScheduleLoaded = true
})
}
}
},
// ==================== 页签一:所有QC排程 ====================
loadQcList () {
const params = {
page: 1,
limit: 100,
site: this.$store.state.user.site
}
return getQcPersonList(params).then(({ data }) => {
if (data.code === 0 && data.page && data.page.list) {
this.qcList = data.page.list.map(item => ({
id: item.id || item.userId,
qcName: item.userDisplay || item.userName,
userName: item.userName,
region: ''
}))
this.qcLoaded = true
} else {
this.qcList = []
}
}).catch(() => {
this.qcList = []
})
},
queryAllQcSchedule () {
if (!this.allQcQuery.startDate) {
this.allQcQuery.startDate = this.getTodayDateString()
}
this.loadAllQcSchedule()
},
loadAllQcSchedule () {
if (!this.qcList || this.qcList.length === 0) {
this.allScheduleDays = this.generateNextDays(10, this.allQcQuery.startDate)
this.$message.warning('暂无QC人员数据')
return
}
const startDate = this.allQcQuery.startDate || this.getTodayDateString()
this.allQcQuery.startDate = startDate
this.allQcLoading = true
const promises = this.qcList.map(qc => {
return getScheduleView(qc.userName, startDate, this.$store.state.user.site).then(({ data }) => {
if (data.code === 0) {
const days = this.generateNextDays(10, startDate)
const tasksByDate = {}
;(data.list || []).forEach(task => {
const taskStartDate = new Date(task.planStartDate)
const taskEndDate = new Date(task.planEndDate || task.planStartDate)
let currentDate = new Date(taskStartDate)
while (currentDate <= taskEndDate) {
const dateStr = this.formatDate(currentDate)
if (!tasksByDate[dateStr]) tasksByDate[dateStr] = []
const exists = tasksByDate[dateStr].some(t => t.requestNo === task.requestNo)
if (!exists) {
tasksByDate[dateStr].push({
requestNo: task.requestNo,
poNo: task.poNo,
supplierName: task.supplierName,
supplierShortOffice: task.supplierShortOffice || '',
inspectAddress: task.inspectAddress,
inspectCityShort: task.inspectCityShort || '',
planStartDate: task.planStartDate,
planEndDate: task.planEndDate,
statusDb: task.statusDb || ''
})
}
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1)
}
})
const scheduleDays = days.map(day => ({
...day,
tasks: tasksByDate[day.date] || []
}))
return { userName: qc.userName, qcName: qc.qcName, scheduleDays }
}
return { userName: qc.userName, qcName: qc.qcName, scheduleDays: this.generateNextDays(10, startDate) }
}).catch(() => {
return { userName: qc.userName, qcName: qc.qcName, scheduleDays: this.generateNextDays(10, startDate) }
})
})
Promise.all(promises).then(results => {
const daysMap = {}
const baseDays = this.generateNextDays(10, startDate)
baseDays.forEach(day => {
daysMap[day.date] = {
date: day.date,
dateLabel: day.dateLabel,
weekday: day.weekday,
qcTasks: []
}
})
results.forEach(result => {
result.scheduleDays.forEach(day => {
if (day.tasks && day.tasks.length > 0 && daysMap[day.date]) {
daysMap[day.date].qcTasks.push({
userName: result.userName,
qcName: result.qcName,
tasks: day.tasks
})
}
})
})
this.allScheduleDays = Object.values(daysMap).sort((a, b) => a.date.localeCompare(b.date))
this.allQcLoading = false
}).catch(() => {
this.allQcLoading = false
})
},
generateNextDays (days, startDate) {
const result = []
const baseDate = this.parseDateString(startDate) || new Date()
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
for (let i = 0; i < days; i++) {
const date = new Date(baseDate)
date.setDate(baseDate.getDate() + i)
result.push({
date: this.formatDate(date),
dateLabel: `${date.getMonth() + 1}/${date.getDate()}`,
weekday: weekdays[date.getDay()],
tasks: []
})
}
return result
},
getTodayDateString () {
return this.formatDate(new Date())
},
parseDateString (dateStr) {
if (!dateStr) {
return null
}
const parts = String(dateStr).split('-')
if (parts.length !== 3) {
return null
}
const year = Number(parts[0])
const month = Number(parts[1]) - 1
const day = Number(parts[2])
const parsedDate = new Date(year, month, day)
if (Number.isNaN(parsedDate.getTime())) {
return null
}
return parsedDate
},
formatDate (date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
getShortOfficeDisplayName (shortOffice, supplierName) {
const value = shortOffice || supplierName || ''
return value.length > 30 ? value.substring(0, 30) : value
},
buildTaskLineText (task, qcDisplayName) {
const displayName = qcDisplayName || task.qcDisplay || ''
const supplierShortOffice = this.getShortOfficeDisplayName(task.supplierShortOffice, task.supplierName)
const inspectCityShort = task.inspectCityShort || ''
return [displayName, supplierShortOffice, inspectCityShort].filter(item => item).join(' | ')
},
isChangeApplyingTask (task) {
return !!(task && task.statusDb === 'ChangeApplying')
},
// ==================== 页签二:验货进度列表 ====================
resolveUserCode (val, tagNo) {
if (!val) return ''
if (tagNo === 2016) {
return val.username || ''
}
if (tagNo === 528 || tagNo === 529 || tagNo === 530) {
return val.UserName || ''
}
return ''
},
getBaseList (val, type) {
this.tagNo = val
this.searchType = type || ''
this.$nextTick(() => {
let strVal = ''
let conSql = ''
if (val === 520) {
strVal = this.searchData.supplierNo || ''
conSql = " and site = '" + this.$store.state.user.site + "'"
}
if (val === 2016) {
strVal = type === 'createBy'
? (this.searchData.createBy || '')
: (type === 'qcOperator' ? (this.searchData.qcOperator || '') : '')
conSql = " and username <> '' "
}
this.$refs.baseList.init(val, strVal, conSql)
})
},
getBaseData (val) {
if (this.tagNo === 520) {
this.searchData.supplierNo = val.supplier_no || ''
}
if (this.tagNo === 2016) {
const userCode = this.resolveUserCode(val, 2016)
if (this.searchType === 'createBy') {
this.searchData.createBy = userCode
} else if (this.searchType === 'qcOperator') {
this.searchData.qcOperator = userCode
}
}
},
getMainData () {
this.searchData.limit = this.pageSize
this.searchData.page = this.pageIndex
this.searchData.planStartDateStr = this.searchData.planStartDate || ''
this.searchData.planEndDateStr = this.searchData.planEndDate || ''
// 选中具体状态时按该状态精确查询,未选时按已排程/验货中/申请变更中分组查询
this.searchData.status = this.searchData.statusFilter || PROGRESS_GROUP
this.dataListLoading = true
searchInspectionRequestHeaderList(this.searchData).then(({ data }) => {
if (data.code === 0) {
this.mainDataList = data.page.list
this.pageIndex = data.page.currPage
this.pageSize = data.page.pageSize
this.totalPage = data.page.totalCount
} else {
this.mainDataList = []
this.totalPage = 0
this.$message.error((data && data.msg) || '查询失败')
}
this.dataListLoading = false
}).catch(() => {
this.mainDataList = []
this.totalPage = 0
this.dataListLoading = false
})
},
resetSearch () {
this.searchData = {
site: this.$store.state.user.site,
requestNo: '',
supplierNo: '',
supplierName: '',
status: PROGRESS_GROUP,
statusFilter: '',
qcOperator: '',
createBy: '',
needInspectDateStart: '',
needInspectDateEnd: '',
planStartDate: '',
planEndDate: '',
planStartDateStr: '',
planEndDateStr: '',
page: 1,
limit: 50
}
this.pageIndex = 1
this.getMainData()
},
sizeChangeHandle (val) {
this.pageSize = val
this.pageIndex = 1
this.getMainData()
},
currentChangeHandle (val) {
this.pageIndex = val
this.getMainData()
}
}
}
</script>
<style scoped lang="scss">
.inspection-schedule-overview {
padding: 0;
}
/* ===== 页签一:所有QC排程 ===== */
.all-qc-query-form {
padding: 0 4px;
margin-bottom: 6px;
}
.all-qc-schedule-container {
height: calc(100vh - 230px);
min-height: 320px;
overflow-y: auto;
padding: 10px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
&:hover {
background: #c0c4cc;
}
}
}
.schedule-days-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.schedule-day-card {
border: 2px solid #dcdfe6;
border-radius: 4px;
background: #fff;
display: flex;
flex-direction: column;
height: 340px;
width: 100%;
.day-header {
padding: 8px 10px;
background: #f5f7fa;
border-bottom: 2px solid #dcdfe6;
display: flex;
justify-content: space-between;
align-items: center;
height: 38px;
flex-shrink: 0;
.date-label {
font-size: 14px;
font-weight: bold;
color: #303133;
}
.date-weekday {
font-size: 11px;
color: #909399;
}
}
.day-body {
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
height: calc(100% - 38px);
overflow-y: auto;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 2px;
&:hover {
background: #c0c4cc;
}
}
}
}
.qc-task-item {
padding: 6px 8px;
background: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 3px;
transition: all 0.3s;
&:hover {
background: #ecf5ff;
border-color: #409eff;
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.12);
}
.task-list {
display: flex;
flex-direction: column;
gap: 3px;
}
.mini-task {
padding: 3px 5px;
background: #fff;
border-radius: 2px;
border: 1px solid transparent;
}
.mini-task--change-applying {
border-color: #f56c6c;
box-shadow: 0 0 6px rgba(245, 108, 108, 0.35);
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
}
}
.task-line-text {
font-size: 12px;
color: #303133;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.empty-day {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
.empty-text {
color: #c0c4cc;
font-size: 13px;
}
}
/* ===== 页签二:验货进度列表 ===== */
.search-form-inline {
background: #fff;
padding: 0;
margin-bottom: 0;
}
.search-row {
display: flex;
align-items: flex-end;
flex-wrap: wrap;
gap: 0;
margin-bottom: 0;
}
.search-item {
flex: none;
margin-bottom: 0;
margin-right: 12px;
}
.search-item /deep/ .el-form-item__label {
padding-bottom: 5px;
line-height: 1;
height: auto;
}
.search-btn-item {
flex: none;
margin-bottom: 0;
margin-right: 0;
}
.search-item /deep/ .el-form-item__content {
line-height: normal;
}
.date-range {
display: flex;
align-items: center;
}
.split {
padding: 0 6px;
color: #606266;
font-size: 13px;
}
/deep/ .customer-tab .el-tabs__content {
padding: 5px !important;
}
</style>

140
src/views/modules/srmPart/partInfoList.vue

@ -146,7 +146,7 @@
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="基准料号" prop="basePartNo" :required="isAdd"> <el-form-item label="基准料号" prop="basePartNo" :required="isAdd">
<el-input v-model="currentpart.basePartNo" :disabled="!isAdd" @input="syncGeneratedPartNo"></el-input>
<el-input v-model="currentpart.basePartNo" :disabled="!isAdd" @input="syncGeneratedPartNo" @blur="onBasePartNoBlur"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
@ -186,7 +186,13 @@
<el-col :span="6"> <el-col :span="6">
<el-form-item> <el-form-item>
<span style="cursor: pointer" slot="label" @click="getBaseList(508)"><a href="#">SKU</a></span> <span style="cursor: pointer" slot="label" @click="getBaseList(508)"><a href="#">SKU</a></span>
<el-input v-model="currentpart.sku" ></el-input>
<el-autocomplete
v-model="currentpart.sku"
:fetch-suggestions="querySkuSuggestions"
clearable
style="width: 100%"
placeholder="选择或输入SKU">
</el-autocomplete>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -387,8 +393,11 @@ import {
copyPartByBase, copyPartByBase,
deletePart, deletePart,
startPartFormalApprove, startPartFormalApprove,
changePartDefaultSupplier
changePartDefaultSupplier,
listSkuByBasePartNo
} from '@/api/part/partInfo.js' } from '@/api/part/partInfo.js'
import { checkSupplierPartByItemSku } from '@/api/supplier/supplierPartMoqPrice'
import { startNewProductAudit } from '@/api/supplier/supplierPartNewProductAudit'
import excel from '@/utils/excel-util.js' import excel from '@/utils/excel-util.js'
import Chooselist from '@/views/modules/common/Chooselist_eam' import Chooselist from '@/views/modules/common/Chooselist_eam'
import basicInfo from './com_part_BasicInfo' import basicInfo from './com_part_BasicInfo'
@ -887,6 +896,7 @@ export default {
// 产品对话框相关 // 产品对话框相关
partDialogVisible: false, partDialogVisible: false,
isAdd: true, isAdd: true,
skuOptions: [],
currentpart: { currentpart: {
partNo: '', partNo: '',
basePartNo: '', basePartNo: '',
@ -1087,6 +1097,37 @@ export default {
if (!this.isAdd) return if (!this.isAdd) return
this.currentpart.partNo = this.generatePartNo(this.currentpart.basePartNo, this.currentpart.partSuffix) this.currentpart.partNo = this.generatePartNo(this.currentpart.basePartNo, this.currentpart.partSuffix)
}, },
// 基准料号失焦:查询该基准料号下已维护过的SKU,作为SKU联想建议(仍允许输入不存在的SKU)
onBasePartNoBlur () {
if (!this.isAdd) return
const basePartNo = (this.currentpart.basePartNo || '').trim().toUpperCase()
if (!basePartNo) {
this.skuOptions = []
return
}
const site = (this.$store.state.user.site || '').trim().toUpperCase()
// 当前SKU为空、或是上一次自动带出/联想列表中的值时,才允许被新的第一个覆盖,避免冲掉用户手动录入的自定义SKU
const prevOptions = this.skuOptions || []
const curSku = this.currentpart.sku
const canOverwrite = !curSku || prevOptions.indexOf(curSku) !== -1
listSkuByBasePartNo({ site, basePartNo }).then(({ data }) => {
const rows = (data && data.code === 0 && data.rows) ? data.rows : []
this.skuOptions = rows
// 默认选中第一个自动赋值给SKU输入框
if (rows.length && canOverwrite) {
this.currentpart.sku = rows[0]
}
}).catch(() => {
this.skuOptions = []
})
},
// SKU 联想:从已维护的 skuOptions 中前缀/包含匹配,无匹配时也允许直接录入
querySkuSuggestions (queryString, cb) {
const list = this.skuOptions || []
const kw = (queryString || '').trim().toUpperCase()
const results = kw ? list.filter(item => (item || '').toUpperCase().indexOf(kw) !== -1) : list
cb(results.map(item => ({ value: item })))
},
extractBasePartNo (partNo) { extractBasePartNo (partNo) {
const value = (partNo || '').trim().toUpperCase() const value = (partNo || '').trim().toUpperCase()
if (!value) return '' if (!value) return ''
@ -1529,6 +1570,7 @@ export default {
// 新增产品 // 新增产品
addPart() { addPart() {
this.isAdd = true this.isAdd = true
this.skuOptions = []
this.currentpart = { this.currentpart = {
partNo: '', partNo: '',
basePartNo: '', basePartNo: '',
@ -1564,6 +1606,7 @@ export default {
return return
} }
this.isAdd = false this.isAdd = false
this.skuOptions = []
this.currentpart = {...row} this.currentpart = {...row}
// 编辑时基准料号以原值为准,空值保持为空 - rqrq // 编辑时基准料号以原值为准,空值保持为空 - rqrq
this.currentpart.partSuffix = this.extractPartSuffix(this.currentpart.partNo) this.currentpart.partSuffix = this.extractPartSuffix(this.currentpart.partNo)
@ -1620,9 +1663,70 @@ export default {
this.currentpart.partNo = this.generatePartNo(basePartNo, partSuffix) this.currentpart.partNo = this.generatePartNo(basePartNo, partSuffix)
} }
this.$refs.partForm.validate((valid) => { this.$refs.partForm.validate((valid) => {
if (valid) {
const apiCall = this.isAdd ? createPart(this.currentpart) : updatePart(this.currentpart)
if (!valid) {
this.$message.warning('请填写必填字段')
return
}
const supplierCode = (this.currentpart.defaultSupplierNo || '').trim().toUpperCase()
const item = (this.currentpart.item || '').trim().toUpperCase()
const sku = (this.currentpart.sku || '').trim().toUpperCase()
// 供应商编码、Item、SKU 三者都有值时,校验价格表是否已存在该组合
if (supplierCode && item && sku) {
checkSupplierPartByItemSku({
site: this.$store.state.user.site,
supplierCode,
item,
sku
}).then(({ data }) => {
if (data && data.code === 0 && data.exists) {
// 价格表已存在该组合,正常保存
this.doSavePart(false)
} else {
// 价格表不存在该组合,弹窗让用户选择是否执行审核
this.promptNewProductAuditChoice()
}
}).catch(() => {
// 校验接口异常时不阻塞保存
this.doSavePart(false)
})
} else {
this.doSavePart(false)
}
})
},
/**
* 弹窗询问是否跳过新品审核流程:
* 选择“跳过”→保存并自动执行开始审核;选择“不跳过”→仅保存不执行审核
*/
promptNewProductAuditChoice () {
this.$confirm(
'供应商产品价格表中未找到记录,是否跳过新品审核流程?',
'新品审核',
{
confirmButtonText: '跳过',
cancelButtonText: '不跳过',
distinguishCancelAndClose: true,
type: 'warning'
}
).then(() => {
// 用户选择“跳过”,保存后自动执行开始审核
this.doSavePart(true)
}).catch(action => {
if (action === 'cancel') {
// 用户选择“不跳过”,仅保存不执行审核
this.doSavePart(false)
}
// action === 'close' 时不做任何操作
})
},
/**
* 执行实际保存逻辑
* @param {boolean} needStartAudit 保存成功后是否自动发起新品审核
*/
doSavePart (needStartAudit) {
const apiCall = this.isAdd ? createPart(this.currentpart) : updatePart(this.currentpart)
apiCall.then(({data}) => { apiCall.then(({data}) => {
if (data && data.code === 0) { if (data && data.code === 0) {
const finishSave = (refreshOk) => { const finishSave = (refreshOk) => {
@ -1632,6 +1736,10 @@ export default {
this.$message.success(successMsg) this.$message.success(successMsg)
this.partDialogVisible = false this.partDialogVisible = false
this.getMainData() this.getMainData()
// 保存成功后,根据用户选择决定是否发起新品审核
if (needStartAudit) {
this.executeStartNewProductAudit()
}
} }
// 新增后自动执行“刷新属性模板”按钮同等逻辑 - rqrq // 新增后自动执行“刷新属性模板”按钮同等逻辑 - rqrq
if (this.isAdd) { if (this.isAdd) {
@ -1647,9 +1755,29 @@ export default {
}).catch(error => { }).catch(error => {
this.$message.error('操作失败: ' + error.message) this.$message.error('操作失败: ' + error.message)
}) })
},
/**
* 调用开始审核接口,生成新品审核记录
*/
executeStartNewProductAudit () {
const supplierCode = (this.currentpart.defaultSupplierNo || '').trim().toUpperCase()
const partNo = (this.currentpart.partNo || '').trim().toUpperCase()
if (!supplierCode || !partNo) {
return
}
startNewProductAudit({
site: this.$store.state.user.site,
supplierCode,
partNo
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success('已创建新品审核记录')
} else { } else {
this.$message.warning('请填写必填字段')
this.$message.warning((data && data.msg) || '开始审核失败')
} }
}).catch(() => {
this.$message.warning('开始审核失败')
}) })
}, },

23
src/views/modules/supplier/priceSheetReport.vue

@ -50,6 +50,7 @@
重置 重置
</el-button> </el-button>
<el-button <el-button
v-if="hasExportPermission"
type="success" type="success"
icon="el-icon-download" icon="el-icon-download"
class="customer-bun-min" class="customer-bun-min"
@ -144,6 +145,7 @@ import {
getPaymentTermsPage, getPaymentTermsPage,
getPriceListPage getPriceListPage
} from '@/api/supplier/priceSheetReport' } from '@/api/supplier/priceSheetReport'
import { getPermissionValue } from '@/api/sys/specialSecurity.js'
const ReportPagination = { const ReportPagination = {
name: 'ReportPagination', name: 'ReportPagination',
@ -191,6 +193,7 @@ export default {
}, },
activeTab: 'priceList', activeTab: 'priceList',
exportLoading: false, exportLoading: false,
hasExportPermission: false,
tableHeight: 520, tableHeight: 520,
tabState: { tabState: {
priceList: createTabState(), priceList: createTabState(),
@ -208,6 +211,7 @@ export default {
this.$nextTick(this.updateTableHeight) this.$nextTick(this.updateTableHeight)
window.addEventListener('resize', this.updateTableHeight) window.addEventListener('resize', this.updateTableHeight)
this.loadTab('priceList') this.loadTab('priceList')
this.checkExportPermission()
}, },
activated () { activated () {
this.$nextTick(this.updateTableHeight) this.$nextTick(this.updateTableHeight)
@ -216,6 +220,25 @@ export default {
window.removeEventListener('resize', this.updateTableHeight) window.removeEventListener('resize', this.updateTableHeight)
}, },
methods: { methods: {
// 校验当前用户是否有导出价格表的特殊权限(SRM0003)
checkExportPermission () {
const username = this.$store.state.user.name || ''
if (!username) {
this.hasExportPermission = false
return
}
getPermissionValue(username, 'SRM0003').then(({ data }) => {
if (data && data.code === 0) {
// value 为 'Y' 表示有权限
this.hasExportPermission = String(data.value).trim() === 'Y'
} else {
this.hasExportPermission = false
}
}).catch(error => {
console.error('查询导出权限失败:', error)
this.hasExportPermission = false
})
},
updateTableHeight () { updateTableHeight () {
this.$nextTick(() => { this.$nextTick(() => {
const table = this.$refs[`${this.activeTab}Table`] const table = this.$refs[`${this.activeTab}Table`]

39
src/views/modules/supplier/supplierPartMoqPrice.vue

@ -94,6 +94,12 @@
<span>{{ scope.row.active === 'Y' ? '是' : '否' }}</span> <span>{{ scope.row.active === 'Y' ? '是' : '否' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="90" header-align="center" align="center" fixed="right">
<template slot-scope="scope">
<el-link style="cursor: pointer" @click.stop="handleTopRowClick(scope.row); openTierDialog()">变更</el-link>
<el-link style="cursor: pointer" @click.stop="deleteSupplierPart(scope.row)">删除</el-link>
</template>
</el-table-column>
</el-table> </el-table>
<el-pagination <el-pagination
class="main-pagination" class="main-pagination"
@ -112,9 +118,6 @@
当前选择: 当前选择:
{{ selectedSupplierPart ? `${selectedSupplierPart.supplierCode} / ${selectedSupplierPart.partNo}` : '未选择供应商物料' }} {{ selectedSupplierPart ? `${selectedSupplierPart.supplierCode} / ${selectedSupplierPart.partNo}` : '未选择供应商物料' }}
</span> </span>
<div>
<el-button type="primary" class="customer-bun-min" :disabled="!selectedSupplierPart" @click="openTierDialog">变更</el-button>
</div>
</div> </div>
<el-table <el-table
@ -453,6 +456,7 @@ import {
listSupplierPartMoqPage, listSupplierPartMoqPage,
saveSupplierPartMoq, saveSupplierPartMoq,
addSupplierPartMoq, addSupplierPartMoq,
deleteSupplierPartMoq,
listCurrentTierPrice, listCurrentTierPrice,
saveTierPrice, saveTierPrice,
listCopySeriesPart, listCopySeriesPart,
@ -782,6 +786,35 @@ export default {
this.addSaveLoading = false this.addSaveLoading = false
}) })
}, },
deleteSupplierPart (row) {
if (!row || !row.supplierCode || !row.partNo) {
this.$message.warning('该行数据不完整,无法删除')
return
}
this.$confirm(`确认删除供应商 ${row.supplierCode} 的物料 ${row.partNo} 吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const inData = {
site: this.$store.state.user.site,
supplierCode: row.supplierCode,
partNo: row.partNo
}
deleteSupplierPartMoq(inData).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success('删除成功')
this.selectedSupplierPart = null
this.currentTierPriceList = []
this.searchSupplierPartList(false)
} else {
this.$alert((data && data.msg) || '删除失败', '错误', { confirmButtonText: '确定' })
}
}).catch(() => {
this.$message.error('删除失败')
})
}).catch(() => {})
},
openCopyDialog () { openCopyDialog () {
if (!this.selectedSupplierPart || !this.selectedSupplierPart.partNo || !this.selectedSupplierPart.supplierCode) { if (!this.selectedSupplierPart || !this.selectedSupplierPart.partNo || !this.selectedSupplierPart.supplierCode) {
this.$message.warning('请先选择供应商物料') this.$message.warning('请先选择供应商物料')

Loading…
Cancel
Save