实现审批台接收到采购申请信息

This commit is contained in:
Mr.j 2025-08-08 15:44:56 +08:00
parent 0d515ab87f
commit 5a13911694
5 changed files with 416 additions and 78 deletions

View File

@ -7,66 +7,7 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
Avatar: typeof import('./../components/Avatar/index.vue')['default']
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
CellCopy: typeof import('./../components/CellCopy/index.vue')['default']
Chart: typeof import('./../components/Chart/index.vue')['default']
ColumnSetting: typeof import('./../components/GiTable/src/components/ColumnSetting.vue')['default']
CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default']
CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default']
DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default']
DayForm: typeof import('./../components/GenCron/CronForm/component/day-form.vue')['default']
FilePreview: typeof import('./../components/FilePreview/index.vue')['default']
GiCellAvatar: typeof import('./../components/GiCell/GiCellAvatar.vue')['default']
GiCellGender: typeof import('./../components/GiCell/GiCellGender.vue')['default']
GiCellStatus: typeof import('./../components/GiCell/GiCellStatus.vue')['default']
GiCellTag: typeof import('./../components/GiCell/GiCellTag.vue')['default']
GiCellTags: typeof import('./../components/GiCell/GiCellTags.vue')['default']
GiCodeView: typeof import('./../components/GiCodeView/index.vue')['default']
GiDot: typeof import('./../components/GiDot/index.tsx')['default']
GiEditTable: typeof import('./../components/GiEditTable/GiEditTable.vue')['default']
GiFooter: typeof import('./../components/GiFooter/index.vue')['default']
GiForm: typeof import('./../components/GiForm/src/GiForm.vue')['default']
GiIconBox: typeof import('./../components/GiIconBox/index.vue')['default']
GiIconSelector: typeof import('./../components/GiIconSelector/index.vue')['default']
GiIframe: typeof import('./../components/GiIframe/index.vue')['default']
GiOption: typeof import('./../components/GiOption/index.vue')['default']
GiOptionItem: typeof import('./../components/GiOptionItem/index.vue')['default']
GiPageLayout: typeof import('./../components/GiPageLayout/index.vue')['default']
GiSpace: typeof import('./../components/GiSpace/index.vue')['default']
GiSplitButton: typeof import('./../components/GiSplitButton/index.vue')['default']
GiSplitPane: typeof import('./../components/GiSplitPane/index.vue')['default']
GiSplitPaneFlexibleBox: typeof import('./../components/GiSplitPane/components/GiSplitPaneFlexibleBox.vue')['default']
GiSvgIcon: typeof import('./../components/GiSvgIcon/index.vue')['default']
GiTable: typeof import('./../components/GiTable/src/GiTable.vue')['default']
GiTag: typeof import('./../components/GiTag/index.tsx')['default']
GiThemeBtn: typeof import('./../components/GiThemeBtn/index.vue')['default']
HourForm: typeof import('./../components/GenCron/CronForm/component/hour-form.vue')['default']
Icon403: typeof import('./../components/icons/Icon403.vue')['default']
Icon404: typeof import('./../components/icons/Icon404.vue')['default']
Icon500: typeof import('./../components/icons/Icon500.vue')['default']
IconBorders: typeof import('./../components/icons/IconBorders.vue')['default']
IconTableSize: typeof import('./../components/icons/IconTableSize.vue')['default']
IconTreeAdd: typeof import('./../components/icons/IconTreeAdd.vue')['default']
IconTreeReduce: typeof import('./../components/icons/IconTreeReduce.vue')['default']
ImageImport: typeof import('./../components/ImageImport/index.vue')['default']
ImageImportWizard: typeof import('./../components/ImageImportWizard/index.vue')['default']
IndustrialImageList: typeof import('./../components/IndustrialImageList/index.vue')['default']
JsonPretty: typeof import('./../components/JsonPretty/index.vue')['default']
MinuteForm: typeof import('./../components/GenCron/CronForm/component/minute-form.vue')['default']
MonthForm: typeof import('./../components/GenCron/CronForm/component/month-form.vue')['default']
ParentView: typeof import('./../components/ParentView/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default']
SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default']
TextCopy: typeof import('./../components/TextCopy/index.vue')['default']
TurbineGrid: typeof import('./../components/TurbineGrid/index.vue')['default']
UserSelect: typeof import('./../components/UserSelect/index.vue')['default']
Verify: typeof import('./../components/Verify/index.vue')['default']
VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default']
VerifySlide: typeof import('./../components/Verify/Verify/VerifySlide.vue')['default']
WeekForm: typeof import('./../components/GenCron/CronForm/component/week-form.vue')['default']
YearForm: typeof import('./../components/GenCron/CronForm/component/year-form.vue')['default']
} }
} }

View File

@ -129,29 +129,18 @@ const debouncedSearch = debounce(() => {
const search = () => { const search = () => {
console.log('🔍 ApprovalSearch - 搜索按钮被点击') console.log('🔍 ApprovalSearch - 搜索按钮被点击')
console.log('🔍 ApprovalSearch - 搜索表单数据:', searchForm) console.log('🔍 ApprovalSearch - 搜索表单数据:', searchForm)
console.log('🔍 ApprovalSearch - 搜索表单数据类型:', typeof searchForm)
console.log('🔍 ApprovalSearch - 搜索表单的键值对:')
Object.entries(searchForm).forEach(([key, value]) => {
console.log(` ${key}: ${value} (${typeof value})`)
})
// // -
const searchParams: EquipmentApprovalListReq = { const searchParams: EquipmentApprovalListReq = {
equipmentName: searchForm.equipmentName || undefined, equipmentName: searchForm.equipmentName || undefined,
applicantName: searchForm.applicantName || undefined, applicantName: searchForm.applicantName || undefined,
businessType: searchForm.businessType, businessType: searchForm.businessType || undefined,
approvalStatus: searchForm.approvalStatus, approvalStatus: searchForm.approvalStatus || undefined,
applyTimeStart: searchForm.applyTimeStart, applyTimeStart: searchForm.applyTimeStart || undefined,
applyTimeEnd: searchForm.applyTimeEnd applyTimeEnd: searchForm.applyTimeEnd || undefined,
} }
console.log('🔍 ApprovalSearch - 发送的搜索参数:', searchParams) console.log('🔍 ApprovalSearch - 发送的搜索参数:', searchParams)
console.log('🔍 ApprovalSearch - 发送参数的类型:', typeof searchParams)
console.log('🔍 ApprovalSearch - 发送参数的键值对:')
Object.entries(searchParams).forEach(([key, value]) => {
console.log(` ${key}: ${value} (${typeof value})`)
})
emit('search', searchParams) emit('search', searchParams)
} }

View File

@ -458,10 +458,10 @@ const loadData = async (searchParams?: EquipmentApprovalListReq) => {
loading.value = true loading.value = true
try { try {
// -
const params: EquipmentApprovalListReq = { const params: EquipmentApprovalListReq = {
pageSize: pagination.pageSize || 10, pageSize: pagination.pageSize,
page: pagination.current || 1, page: pagination.current,
pageNum: pagination.current || 1, // pageNum
...(searchParams || {}), ...(searchParams || {}),
} }

View File

@ -0,0 +1,340 @@
<template>
<a-modal
:visible="visible"
:title="modalTitle"
width="800px"
@ok="handleSubmit"
@cancel="handleCancel"
@update:visible="(value) => emit('update:visible', value)"
:confirm-loading="submitLoading"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="vertical"
class="procurement-application-form"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="设备名称" field="equipmentName">
<a-input
v-model="formData.equipmentName"
placeholder="请输入设备名称"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="设备类型" field="equipmentType">
<a-select
v-model="formData.equipmentType"
placeholder="请选择设备类型"
allow-clear
>
<a-option value="服务器">服务器</a-option>
<a-option value="网络设备">网络设备</a-option>
<a-option value="办公设备">办公设备</a-option>
<a-option value="计算机">计算机</a-option>
<a-option value="安防设备">安防设备</a-option>
<a-option value="显示设备">显示设备</a-option>
<a-option value="测试设备">测试设备</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="设备型号" field="equipmentModel">
<a-input
v-model="formData.equipmentModel"
placeholder="请输入设备型号"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="品牌" field="brand">
<a-input
v-model="formData.brand"
placeholder="请输入品牌"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="供应商名称" field="supplierName">
<a-input
v-model="formData.supplierName"
placeholder="请输入供应商名称"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="数量" field="quantity">
<a-input-number
v-model="formData.quantity"
placeholder="请输入数量"
:min="1"
:precision="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="预算金额" field="budgetAmount">
<a-input-number
v-model="formData.budgetAmount"
placeholder="请输入预算金额"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="紧急程度" field="urgencyLevel">
<a-select
v-model="formData.urgencyLevel"
placeholder="请选择紧急程度"
allow-clear
>
<a-option value="LOW"></a-option>
<a-option value="NORMAL">普通</a-option>
<a-option value="HIGH"></a-option>
<a-option value="URGENT">紧急</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="采购类型" field="procurementType">
<a-select
v-model="formData.procurementType"
placeholder="请选择采购类型"
allow-clear
>
<a-option value="NEW_PURCHASE">新采购</a-option>
<a-option value="REPLACEMENT">更换</a-option>
<a-option value="UPGRADE">升级</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="预期交付日期" field="expectedDeliveryDate">
<a-date-picker
v-model="formData.expectedDeliveryDate"
placeholder="请选择预期交付日期"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="申请原因" field="applyReason">
<a-textarea
v-model="formData.applyReason"
placeholder="请详细说明采购原因"
:rows="4"
allow-clear
/>
</a-form-item>
<a-form-item label="技术要求" field="technicalRequirements">
<a-textarea
v-model="formData.technicalRequirements"
placeholder="请描述设备的技术要求(可选)"
:rows="3"
allow-clear
/>
</a-form-item>
<a-form-item label="业务合理性说明" field="businessJustification">
<a-textarea
v-model="formData.businessJustification"
placeholder="请说明采购的业务合理性(可选)"
:rows="3"
allow-clear
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import { equipmentApprovalApi } from '@/apis/equipment/approval'
interface Props {
visible: boolean
equipmentData?: any
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const modalTitle = ref('申请采购')
const submitLoading = ref(false)
const formRef = ref()
//
const formData = reactive({
equipmentId: '', // ID
equipmentName: '',
equipmentType: '',
equipmentModel: '',
brand: '',
supplierName: '',
quantity: 1,
budgetAmount: undefined,
urgencyLevel: 'NORMAL',
procurementType: 'NEW_PURCHASE',
expectedDeliveryDate: null,
applyReason: '',
technicalRequirements: '',
businessJustification: ''
})
//
const rules = {
equipmentId: [{ required: true, message: '设备ID不能为空' }],
equipmentName: [{ required: true, message: '请输入设备名称' }],
equipmentType: [{ required: true, message: '请选择设备类型' }],
quantity: [{ required: true, message: '请输入数量' }],
budgetAmount: [{ required: true, message: '请输入预算金额' }],
urgencyLevel: [{ required: true, message: '请选择紧急程度' }],
procurementType: [{ required: true, message: '请选择采购类型' }],
applyReason: [
{ required: true, message: '请输入申请原因' },
{
validator: (value: string) => {
if (!value || value.trim() === '') {
return new Error('申请原因不能为空')
}
return true
}
}
]
}
//
watch(() => props.equipmentData, (newData) => {
if (newData) {
//
Object.assign(formData, {
equipmentId: newData.equipmentId || '', // ID
equipmentName: newData.equipmentName || '',
equipmentType: newData.equipmentType || '',
equipmentModel: newData.equipmentModel || '',
brand: newData.brand || '',
supplierName: newData.supplierName || '',
quantity: newData.quantity || 1,
budgetAmount: newData.unitPrice || undefined,
urgencyLevel: 'NORMAL',
procurementType: 'NEW_PURCHASE',
expectedDeliveryDate: null,
applyReason: '', //
technicalRequirements: '',
businessJustification: ''
})
}
}, { immediate: true })
//
watch(() => props.visible, (visible) => {
if (visible) {
//
if (props.equipmentData) {
// watch
} else {
//
formRef.value?.resetFields()
}
}
})
//
const handleSubmit = async () => {
try {
// applyReason
if (!formData.applyReason || formData.applyReason.trim() === '') {
Message.error('请输入申请原因')
return
}
await formRef.value.validate()
submitLoading.value = true
const submitData = {
...formData,
applyReason: formData.applyReason.trim(), //
expectedDeliveryDate: formData.expectedDeliveryDate ?
(formData.expectedDeliveryDate as any).format('YYYY-MM-DD') : null
}
//
console.log('提交的表单数据:', submitData)
console.log('applyReason 值:', submitData.applyReason)
console.log('applyReason 类型:', typeof submitData.applyReason)
console.log('applyReason 长度:', submitData.applyReason?.length)
await equipmentApprovalApi.submitProcurementApplication(submitData)
Message.success('采购申请提交成功')
emit('success')
handleCancel()
} catch (error: any) {
console.error('提交采购申请失败:', error)
Message.error(error?.message || '提交失败')
} finally {
submitLoading.value = false
}
}
//
const handleCancel = () => {
emit('update:visible', false)
//
formData.equipmentId = ''
formData.applyReason = ''
formData.technicalRequirements = ''
formData.businessJustification = ''
formData.expectedDeliveryDate = null
formRef.value?.resetFields()
}
</script>
<style scoped lang="scss">
.procurement-application-form {
.arco-form-item {
margin-bottom: 16px;
}
.arco-input,
.arco-select,
.arco-input-number,
.arco-date-picker {
border-radius: 6px;
}
.arco-textarea {
border-radius: 6px;
}
}
</style>

View File

@ -175,6 +175,19 @@
<a-button type="text" size="small" @click="handleEdit(record)"> <a-button type="text" size="small" @click="handleEdit(record)">
编辑 编辑
</a-button> </a-button>
<!-- 申请采购按钮 -->
<a-button
v-if="canApplyProcurement(record)"
type="primary"
size="small"
@click="handleApplyProcurement(record)"
>
申请采购
</a-button>
<!-- 显示审批状态 -->
<a-tag v-if="record.approvalStatus" :color="getApprovalStatusColor(record.approvalStatus)">
{{ getApprovalStatusText(record.approvalStatus) }}
</a-tag>
<a-popconfirm <a-popconfirm
content="确定要删除这条采购记录吗?" content="确定要删除这条采购记录吗?"
@ok="handleDelete(record)" @ok="handleDelete(record)"
@ -195,6 +208,13 @@
:mode="modalMode" :mode="modalMode"
@success="handleModalSuccess" @success="handleModalSuccess"
/> />
<!-- 采购申请弹窗 -->
<ProcurementApplicationModal
v-model:visible="applicationModalVisible"
:equipment-data="currentApplicationData"
@success="handleApplicationSuccess"
/>
</div> </div>
</template> </template>
@ -213,7 +233,9 @@ import {
import message from '@arco-design/web-vue/es/message' import message from '@arco-design/web-vue/es/message'
import ProcurementModal from './components/ProcurementModal.vue' import ProcurementModal from './components/ProcurementModal.vue'
import ProcurementSearch from './components/ProcurementSearch.vue' import ProcurementSearch from './components/ProcurementSearch.vue'
import ProcurementApplicationModal from './components/ProcurementApplicationModal.vue'
import { equipmentProcurementApi } from '@/apis/equipment/procurement' import { equipmentProcurementApi } from '@/apis/equipment/procurement'
import { equipmentApprovalApi } from '@/apis/equipment/approval'
import type { EquipmentListReq, EquipmentResp } from '@/apis/equipment/type' import type { EquipmentListReq, EquipmentResp } from '@/apis/equipment/type'
defineOptions({ name: 'EquipmentProcurement' }) defineOptions({ name: 'EquipmentProcurement' })
@ -243,6 +265,10 @@ const modalVisible = ref(false)
const currentProcurement = ref<EquipmentResp | null>(null) const currentProcurement = ref<EquipmentResp | null>(null)
const modalMode = ref<'add' | 'edit' | 'view'>('add') const modalMode = ref<'add' | 'edit' | 'view'>('add')
//
const applicationModalVisible = ref(false)
const currentApplicationData = ref<EquipmentResp | null>(null)
// //
const selectedRowKeys = ref<string[]>([]) const selectedRowKeys = ref<string[]>([])
const rowSelection = reactive({ const rowSelection = reactive({
@ -611,6 +637,12 @@ const handleEdit = (record: EquipmentResp) => {
modalVisible.value = true modalVisible.value = true
} }
//
const handleApplyProcurement = (record: EquipmentResp) => {
currentApplicationData.value = { ...record }
applicationModalVisible.value = true
}
// //
const handleDelete = async (record: EquipmentResp) => { const handleDelete = async (record: EquipmentResp) => {
try { try {
@ -629,6 +661,12 @@ const handleModalSuccess = () => {
loadData(currentSearchParams.value) loadData(currentSearchParams.value)
} }
//
const handleApplicationSuccess = () => {
applicationModalVisible.value = false
loadData(currentSearchParams.value)
}
// //
const refreshData = () => { const refreshData = () => {
loadData(currentSearchParams.value) loadData(currentSearchParams.value)
@ -674,6 +712,36 @@ const getTotalAmount = () => {
return formatPrice(total) return formatPrice(total)
} }
//
const canApplyProcurement = (record: EquipmentResp) => {
//
return !record.approvalStatus || record.approvalStatus === 'PENDING_APPLICATION'
}
//
const getApprovalStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
'PENDING_APPLICATION': 'blue',
'PENDING': 'orange',
'APPROVED': 'green',
'REJECTED': 'red',
'WITHDRAWN': 'gray'
}
return colorMap[status] || 'blue'
}
//
const getApprovalStatusText = (status: string) => {
const textMap: Record<string, string> = {
'PENDING_APPLICATION': '待申请',
'PENDING': '待审批',
'APPROVED': '已通过',
'REJECTED': '已拒绝',
'WITHDRAWN': '已撤回'
}
return textMap[status] || '未知'
}
onMounted(() => { onMounted(() => {
loadData() loadData()
}) })