让步接收审批前端代码

修改提交明细页面
This commit is contained in:
shaoleiliu-netizen123 2026-06-10 16:22:12 +08:00
parent d0f1a35d70
commit 9b6d5bb0f1
8 changed files with 383 additions and 285 deletions

View File

@ -90,4 +90,9 @@ export const concessionApplyApi = {
return request({ url: `/biz/concessionApply/template`, method: 'get', responseType: 'blob' }) return request({ url: `/biz/concessionApply/template`, method: 'get', responseType: 'blob' })
}, },
//撤回审批申请
approveCancel(params?:{id?:number}){
return request({url:`/biz/concessionApply/approveCancel`,method:'get',params})
}
} }

View File

@ -24,7 +24,11 @@ export interface QcResultDetail {
updateTime?: string updateTime?: string
files?:any[] files?:any[],
title?:string,
images?:any[],
attachments?:any[]
} }

View File

@ -1,4 +1,5 @@
import { request } from '@/utils/request' import { request } from '@/utils/request'
import { F } from 'vue-router/dist/router-CWoNjPRp.mjs'
// 分页结果 // 分页结果
export interface PageResult<T> { export interface PageResult<T> {
@ -371,6 +372,34 @@ export interface SysFile {
remark?: string remark?: string
} }
export interface DingTalkImage{
id?:number
mainId?:number
fileName?:string
fileSuffix?:string
fileSize?:number
localFilePath?:string
fileUrl?:string
sortNum?:number
createTime?:string
}
export interface DingTalkAttachment{
id?:number
mainId?:number
fileName?:string
fileSuffix?:string
fileSize?:number
localFilePath?:string
ddSpaceId?:string
ddFileId?:string
uploadDdStatus?:string
sortNum?:number
createTime?:string
}
export const fileApi = { export const fileApi = {
page(params: { page: number; pageSize: number; originalName?: string; fileType?: string }): Promise<PageResult<SysFile>> { page(params: { page: number; pageSize: number; originalName?: string; fileType?: string }): Promise<PageResult<SysFile>> {
return request({ url: '/sys/file/page', method: 'get', params }) return request({ url: '/sys/file/page', method: 'get', params })
@ -407,6 +436,28 @@ export const fileApi = {
}) })
}, },
uploadDingImage(file: File):Promise<DingTalkImage> {
const formData = new FormData()
formData.append("file",file)
return request({
url:"/sys/file/uploadDingImage",
method:'post',
data:formData
})
},
uploadDingAttachment(file: File):Promise<DingTalkAttachment> {
const formData = new FormData()
formData.append("file",file)
return request({
url:"/sys/file/uploadDingAttachment",
method:"post",
data:formData
})
},
uploadAndDingding(file: File, path?: string, groupId?: number | null): Promise<SysFile>{ uploadAndDingding(file: File, path?: string, groupId?: number | null): Promise<SysFile>{
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
@ -441,6 +492,8 @@ export const fileApi = {
}) })
}, },
getDownloadUrl(id: number): string { getDownloadUrl(id: number): string {
return `/api/sys/file/download/${id}` return `/api/sys/file/download/${id}`
}, },

View File

@ -140,7 +140,7 @@
</template> </template>
</n-modal> </n-modal>
<!-- 审批记录弹窗 --> <!-- 审批记录弹窗 -->
<n-modal v-model:show="recordModalVisible" ref="exportWrapRef" preset="card" title="审批记录" style="width: 800px"> <n-modal v-model:show="recordModalVisible" ref="exportWrapRef" preset="card" title="审批记录" style="width: 800px">
<n-card bordered style="width:100%;max-width:800px;padding:24px;position:relative;"> <n-card bordered style="width:100%;max-width:800px;padding:24px;position:relative;">
<div class="header-bar" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;"> <div class="header-bar" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
@ -217,14 +217,38 @@
</n-modal> </n-modal>
<n-image-group
v-model:show="showRef"
v-model:current="currentIndex"
:src-list="previewSrcList"
/>
<n-modal v-model:show="fileModalShow" title="附件列表" style="width: 400px;">
<n-space vertical :size="12">
<div v-for="(item, idx) in fileList" :key="idx" style="display:flex;align-items:center;justify-content:space-between;">
<span>{{ item.fileName }}</span>
<n-button
size="small"
type="primary"
@click="window.open(item, '_blank')"
>
下载
</n-button>
</div>
</n-space>
</n-modal>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, h, onMounted } from 'vue' import { ref, reactive, h, onMounted } from 'vue'
import { NButton, NSpace, NIcon, NUpload, useMessage,NTag, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui' import { NButton, NSpace, NIcon, NUpload, useMessage,NTag, useDialog, type DataTableColumns, type UploadCustomRequestOptions, NDropdown, NImage } from 'naive-ui'
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline,DocumentTextOutline, import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline,DocumentTextOutline,
CashOutline as CashIcon CashOutline as CashIcon,
EllipsisHorizontalCircle
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { concessionApplyApi, DetailModel, type ConcessionApply } from '@/api/concessionApply' import { concessionApplyApi, DetailModel, type ConcessionApply } from '@/api/concessionApply'
import { dictDataApi } from '@/api/org' import { dictDataApi } from '@/api/org'
@ -287,6 +311,13 @@ const approveStatusOptions = ref<{ label: string; value: any }[]>([])
const formRules = { const formRules = {
} }
const showRef = ref(false)
const currentIndex = ref(0)
const previewSrcList = ref([])
const fileModalShow = ref(false)
const fileList = ref([])
// //
const columns: DataTableColumns<ConcessionApply> = [ const columns: DataTableColumns<ConcessionApply> = [
{ type: 'selection' }, { type: 'selection' },
@ -295,7 +326,68 @@ const columns: DataTableColumns<ConcessionApply> = [
{ title: '让步接收数量', key: 'concessionQty',align:"center",minWidth:"150" }, { title: '让步接收数量', key: 'concessionQty',align:"center",minWidth:"150" },
{ title: '申请原因', key: 'applyRemark',align:"center",minWidth:"150" }, { title: '申请原因', key: 'applyRemark',align:"center",minWidth:"150" },
{ title: '申请内容', key: 'applyContent',align:"center",minWidth:"200" }, { title: '申请内容', key: 'applyContent',align:"center",minWidth:"200" },
{ title: '申请附件', key: 'applyFile',align:"center",minWidth:"150" }, {
title: '申请图片',
key: 'applyImages',
align: "center",
minWidth: "100",
render(row) {
//
const imageUrls = row.applyImages?.split(",").filter(url => url.trim() !== "") || [];
if (imageUrls.length === 0) return "-";
// 访URL
const fullUrls = imageUrls.map(path => {
if (path.startsWith("http")) return path;
return `${window.location.origin}${path}`;
});
// 3. + +n
return h('div', {
style: 'display: flex; align-items: center; justify-content: center; gap: 6px;'
}, [
//
h('img', {
src: fullUrls[0],
alt: `申请图片`,
style: 'width: 60px; height: 60px; object-fit: cover; border-radius: 4px; cursor: pointer;',
onClick: () => {
//
previewSrcList.value = fullUrls;
currentIndex.value = 0;
showRef.value = true;
}
}),
//
fullUrls.length > 1 ? h('span', {
style: 'font-size: 12px; color: #666; background: #f0f0f0; padding: 2px 6px; border-radius: 10px;'
}, `+${fullUrls.length - 1}`) : null
]);
}
},
{
title: '附件',
key: 'applyAttachments',
align: "center",
minWidth: "180",
render(row) {
const files = row.applyAttachments?.split(",").filter(url => url.trim() !== "") || [];
if (files.length === 0) return "-"
return h('div', {style: 'display:flex;flex-direction:column;gap:4px;'},
files.map((item, idx) =>
h('div', {key: idx, style: 'display:flex;align-items:center;gap:6px;'}, [
h('span', {style: 'font-size:12px;'}, item.fileName),
h('a', {
href: item,
target: '_blank',
style: 'color:#409eff;font-size:12px;cursor:pointer;'
}, '查看')
])
)
)
}
},
{ title: '申请人', key: 'applyUserName',align:"center",minWidth:"150" }, { title: '申请人', key: 'applyUserName',align:"center",minWidth:"150" },
{ title: '部门名称', key: 'deptName',align:"center",minWidth:"150" }, { title: '部门名称', key: 'deptName',align:"center",minWidth:"150" },
// { title: '', key: 'instanceId',align:"center",minWidth:"150" }, // { title: '', key: 'instanceId',align:"center",minWidth:"150" },
@ -310,7 +402,7 @@ const columns: DataTableColumns<ConcessionApply> = [
if(row.approveResult == '已通过') return h(NTag, { type: 'success', size: 'small' }, { default: () => row.approveResult }) if(row.approveResult == '已通过') return h(NTag, { type: 'success', size: 'small' }, { default: () => row.approveResult })
return row.approveResult return row.approveResult
} }
}, },
{ title: '审批人', key: 'approveUserName',align:"center",minWidth:"150" }, { title: '审批人', key: 'approveUserName',align:"center",minWidth:"150" },
{ title: '创建时间', key: 'createTime', width: 180 }, { title: '创建时间', key: 'createTime', width: 180 },
{ title: '更新时间', key: 'updateTime', width: 180 }, { title: '更新时间', key: 'updateTime', width: 180 },
@ -327,9 +419,29 @@ const columns: DataTableColumns<ConcessionApply> = [
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, { h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除'] default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
}), }),
h(NButton, { size: 'small', quaternary: true, type: 'warring', onClick: () => handleViewApproveRecord(row) }, { h(NDropdown,{
default: () => [h(NIcon, null, { default: () => h(DocumentTextOutline) }), ' 审批记录'] trigger: 'click',
}) options:[
{label:'审批记录',key:'approveRecord'},
{type:'divider',show: row.approveStatus === '待审批'},
{label:"撤回审批",key:'approveCancel',show: row.approveStatus === '待审批'}
],
onSelect:(key:string)=>{
switch(key) {
case 'approveRecord':
handleViewApproveRecord(row)
break;
case 'approveCancel':
handleCancel(row)
break
}
}
},{
default:()=>h(NButton,{size:'small',quaternary:true},{
default:()=>[h(NIcon,null,{default:()=>h(EllipsisHorizontalCircle)})]
})
}
)
]) ])
} }
} }
@ -715,6 +827,25 @@ async function downloadOutline() {
} }
async function handleCancel(row) {
dialog.warning({
title: '提示',
content: `确定要撤回ID:${row.id}审批申请吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try{
await concessionApplyApi.approveCancel({id:row.id});
message.success('撤回成功')
loadData()
}catch(error){}
}
})
}
onMounted(() => { onMounted(() => {
loadData() loadData()
loadDictOptions() loadDictOptions()

View File

@ -224,139 +224,7 @@
<!-- </n-card>--> <!-- </n-card>-->
</n-modal> </n-modal>
<!--提交判定明细面板-->
<n-modal v-model:show="submitDetailModalVisible" preset="card" title="提交判定明细" style="width: 800px">
<n-collapse default-expanded-names="['ok', 'scrap', 'rework','concession']">
<n-collapse-item title="合格" name="ok">
<n-form ref="formRef" :model="formDetailOKData" :rules="formRules" label-placement="left" label-width="100px">
<n-grid x-gap="12" :cols="2">
<n-gi>
<n-form-item label="判定数量" path="qty">
<n-input-number v-model:value="formDetailOKData.qty" placeholder="请输入判定数量" :min="0" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
<n-input v-model:value="formDetailOKData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="备注" >
<n-input
v-model:value="formDetailOKData.remark"
type="textarea"
placeholder="输入备注原因"
/>
</n-form-item>
</n-form>
</n-collapse-item>
<n-collapse-item title="报废" name="scrap">
<n-form ref="formRef" :model="formDetailScrapData" :rules="formRules" label-placement="left" label-width="100px">
<n-grid x-gap="12" :cols="2">
<n-gi>
<n-form-item label="判定数量" path="qty">
<n-input-number v-model:value="formDetailScrapData.qty" placeholder="请输入判定数量" :min="0" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
<n-input v-model:value="formDetailScrapData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="备注" >
<n-input
v-model:value="formDetailScrapData.remark"
type="textarea"
placeholder="输入备注原因"
/>
</n-form-item>
</n-form>
</n-collapse-item>
<n-collapse-item title="返工" name="rework">
<n-form ref="formRef" :model="formDetailReworkData" :rules="formRules" label-placement="left" label-width="100px">
<n-grid x-gap="12" :cols="2">
<n-gi>
<n-form-item label="判定数量" path="qty">
<n-input-number v-model:value="formDetailReworkData.qty" placeholder="请输入判定数量" :min="0" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
<n-input v-model:value="formDetailReworkData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="备注" >
<n-input
v-model:value="formDetailReworkData.remark"
type="textarea"
placeholder="输入备注原因"
/>
</n-form-item>
</n-form>
</n-collapse-item>
<n-collapse-item title="让步接收" name="concession">
<n-form ref="formRef" :model="formDetailConcessionData" :rules="formRules" label-placement="left" label-width="100px">
<n-grid x-gap="12" :cols="2">
<n-gi>
<n-form-item label="判定数量" path="qty">
<n-input-number v-model:value="formDetailConcessionData.qty" placeholder="请输入判定数量" :min="0" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
<n-input v-model:value="formDetailConcessionData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="备注" >
<n-input
v-model:value="formDetailConcessionData.remark"
type="textarea"
placeholder="输入备注原因"
/>
</n-form-item>
<n-form-item label="附件">
<n-upload
multiple
directory-dnd
:max="5"
:action="null"
:custom-request="()=>{}"
@change="handleUploadChange"
:file-list="fileList"
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif,.bmp"
:max-size="10*1024*1024"
>
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<ArchiveIcon />
</n-icon>
</div>
<n-text style="font-size: 16px">
点击或者拖动文件到该区域来上传
</n-text>
<n-p depth="3" style="margin: 8px 0 0 0">
仅支持 PDFDOCDOCXXLSXLSXJPGPNGGIF单文件最大10MB
</n-p>
</n-upload-dragger>
</n-upload>
</n-form-item>
</n-form>
</n-collapse-item>
</n-collapse>
<template #footer>
<n-space justify="end">
<n-button @click="submitDetailModalVisible = false">取消</n-button>
<n-button type="primary" @click="handleSubmitResultDetail">确定</n-button>
</n-space>
</template>
</n-modal>
</div> </div>
@ -385,6 +253,7 @@ import value = Language.value;
import {QcResultDetail} from "@/api/qcResultDetail.ts"; import {QcResultDetail} from "@/api/qcResultDetail.ts";
import router from "@/router"; import router from "@/router";
import dd from 'dingtalk-jsapi' import dd from 'dingtalk-jsapi'
import { env } from 'echarts'
const message = useMessage() const message = useMessage()
const dialog = useDialog() const dialog = useDialog()
@ -558,9 +427,6 @@ const defaultFormDetailData:QcResultDetail = {
actionStatus: undefined actionStatus: undefined
} }
//
const fileList = ref<any[]>([])
const uploadFiles = ref<File[]>([])
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 }) const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 }) const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
@ -977,19 +843,15 @@ async function loadAssignWorkOrder() {
// //
function submitResultDetail(row) { function submitResultDetail(row) {
Object.assign(formDetailOKData, reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 }))
Object.assign(formDetailScrapData, reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 }))
Object.assign(formDetailReworkData, reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 }))
Object.assign(formDetailConcessionData, reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 }))
if (row.traceType != 1) { if (row.traceType != 1) {
traceCodeShow.value = true traceCodeShow.value = true
} }
submitFlag.value = true submitFlag.value = true
submitDetailModalVisible.value = true
formDetailOKData.qcId = row.id
formDetailScrapData.qcId = row.id let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
formDetailReworkData.qcId = row.id router.push(path)
formDetailConcessionData.qcId = row.id
} }
@ -1015,20 +877,12 @@ async function handleSubmitResultDetail() {
// //
function submitBFResultDetail(row) { function submitBFResultDetail(row) {
// Object.assign(formDetailOKData, reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'',actionStatus:1 }))
// Object.assign(formDetailScrapData, reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'',actionStatus:1 }))
// Object.assign(formDetailReworkData, reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'',actionStatus:1 }))
// Object.assign(formDetailConcessionData, reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'',actionStatus:1 }))
if (row.traceType != 1) { if (row.traceType != 1) {
traceCodeShow.value = true traceCodeShow.value = true
} }
submitFlag.value = false submitFlag.value = false
// submitDetailModalVisible.value = true
// formDetailOKData.qcId = row.id
// formDetailScrapData.qcId = row.id
// formDetailReworkData.qcId = row.id
// formDetailConcessionData.qcId = row.id
//version-2 //version-2
@ -1064,67 +918,6 @@ function handleCancelQuality(row) {
} }
const isDingTalkEnv = () => {
// dd platform 'notInDingTalk'
return typeof dd !== 'undefined' && dd.env && dd.env.platform !== 'notInDingTalk';
};
//
async function handleUploadChange ({ file, fileList: list }: any) {
//
const allowExt = ['pdf','doc','docx','xls','xlsx','jpg','jpeg','png','gif','bmp']
const invalidFiles:any[] = []
list.forEach(item=>{
const name = item.file.name.toLowerCase()
const ext = name.split('.').pop()
if(!allowExt.includes(ext)){
invalidFiles.push(item.file.name)
item.status = 'error'
}
})
if(invalidFiles.length){
window.$message.error(`${invalidFiles.join('、')}】文件格式不支持`)
}
fileList.value = list
console.log('选中的文件列表', fileList.value)
const validList = list.filter(item =>
item.status === 'pending' && item.file && item.status !== 'error'
)
const successIds = list.filter(x=>x.status==='success' && x.file).map(x=>x.id)
//
uploadFiles.value = uploadFiles.value.filter(item=> successIds.includes(item.uid))
// pending
const pendingList = list.filter(item => item.status === 'pending' && item.file && item.status !== 'error')
if(pendingList.length === 0) return
const uploadPromises = validList.map(async (item: any) => {
try {
const res = await fileApi.uploadAndDingding(item.file, null, null)
uploadFiles.value.push(res)
const targetFile = fileList.value.find(f => f.id === item.id)
if (targetFile) {
targetFile.status = 'success'
targetFile.percentage = 100
}
} catch (error) {
console.error('文件上传失败', error)
const targetFile = fileList.value.find(f => f.id === item.id)
if (targetFile) targetFile.status = 'error'
}
})
await Promise.all(uploadPromises)
}
onMounted(() => { onMounted(() => {

View File

@ -87,17 +87,18 @@
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="让步接收" tab="让步接收"> <n-tab-pane name="让步接收" tab="让步接收">
<n-form ref="formRef" :model="formDetailConcessionData" label-placement="left" label-width="100px"> <n-form ref="formRef" :model="formDetailConcessionData" label-placement="left" label-width="100px">
<n-grid x-gap="12" :cols="2"> <n-grid x-gap="12" :cols="2">
<n-gi> <n-gi>
<n-form-item label="判定数量" path="qty"> <n-form-item label="判定数量" path="qty">
<n-input-number v-model:value="formDetailConcessionData.qty" placeholder="请输入判定数量" :min="0" /> <n-input-number v-model:value="formDetailConcessionData.qty" placeholder="请输入判定数量" :min="0" />
</n-form-item> </n-form-item>
</n-gi> </n-gi>
<n-gi> <n-gi>
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow"> <n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
<n-input v-model:value="formDetailConcessionData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" /> <n-input v-model:value="formDetailConcessionData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
</n-form-item> </n-form-item>
</n-gi> </n-gi>
</n-grid> </n-grid>
<n-grid x-gap="8" :cols="3"> <n-grid x-gap="8" :cols="3">
<n-gi> <n-gi>
@ -110,6 +111,22 @@
</n-form-item> </n-form-item>
</n-gi> </n-gi>
</n-grid> </n-grid>
<n-grid x-gap="8" :cols="3">
<n-gi>
<n-form-item label="图片">
<n-upload
action="null"
:max="5"
:custom-request="()=>{}"
@change="handleUploadImageChange"
v-model:file-list="imageList"
list-type="image-card"
>
点击上传
</n-upload>
</n-form-item>
</n-gi>
</n-grid>
<n-grid x-gap="8" :cols="3"> <n-grid x-gap="8" :cols="3">
<n-gi> <n-gi>
<n-form-item label="附件"> <n-form-item label="附件">
@ -120,8 +137,8 @@
:action="null" :action="null"
:custom-request="()=>{}" :custom-request="()=>{}"
@change="handleUploadChange" @change="handleUploadChange"
:file-list="fileList" v-model:file-list="fileList"
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif,.bmp" accept=".pdf,.doc,.docx,.xls,.xlsx"
:max-size="10*1024*1024" :max-size="10*1024*1024"
> >
<n-upload-dragger> <n-upload-dragger>
@ -167,6 +184,7 @@ import {useRoute} from "vue-router";
import {QcResultDetail} from "@/api/qcResultDetail.ts"; import {QcResultDetail} from "@/api/qcResultDetail.ts";
import {fileApi} from '@/api/system1' import {fileApi} from '@/api/system1'
import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting' import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
import router from '@/router';
const message = useMessage() const message = useMessage()
const route = useRoute() const route = useRoute()
@ -175,9 +193,19 @@ const qcId =ref( Number(route.params.id))
const traceCodeShow = ref(route.params.traceCodeShow === 'true') const traceCodeShow = ref(route.params.traceCodeShow === 'true')
const submitFlag = ref(route.params.submitFlag === 'true') const submitFlag = ref(route.params.submitFlag === 'true')
const MAX_FILE_SIZE = 10 * 1024 * 1024
//
const imageList = ref<any[]>([])
const uploadImages = ref<File[]>([])
const imageExts = ['jpg', 'jpeg', 'png']
// //
const fileList = ref<any[]>([]) const fileList = ref<any[]>([])
const uploadFiles = ref<File[]>([]) const uploadFiles = ref<File[]>([])
const fileExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx']
const title = ref(`质检编号: ${route.params.qcNo}`) const title = ref(`质检编号: ${route.params.qcNo}`)
@ -189,7 +217,11 @@ const defaultFormDetailData:QcResultDetail = {
traceCode: undefined, traceCode: undefined,
defectCode: undefined, defectCode: undefined,
handleAction: undefined, handleAction: undefined,
actionStatus: undefined actionStatus: undefined,
remark: '',
title:undefined,
images:[],
attachments:[]
} }
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 }) const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
@ -200,7 +232,13 @@ const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetail
async function handleSubmitResultDetail() { async function handleSubmitResultDetail() {
formDetailConcessionData.files = uploadFiles.value formDetailOKData.qcId = qcId.value
formDetailScrapData.qcId = qcId.value
formDetailReworkData.qcId = qcId.value
formDetailConcessionData.qcId =qcId.value
formDetailConcessionData.images = uploadImages.value
formDetailConcessionData.attachments = uploadFiles.value
const submitParam = [ const submitParam = [
formDetailOKData, formDetailOKData,
@ -208,68 +246,127 @@ async function handleSubmitResultDetail() {
formDetailReworkData, formDetailReworkData,
formDetailConcessionData] formDetailConcessionData]
console.log(submitParam);
submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam) submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam)
message.success('提交成功') message.success('提交成功')
router.go(-1)
} }
async function handleUploadChange ({ file, fileList: list }: any) { async function handleUploadChange ({ file, fileList }: { file: any, fileList: any[] }) {
const invalidNames: string[] = []
// fileList.forEach(item => {
const allowExt = ['pdf','doc','docx','xls','xlsx','jpg','jpeg','png','gif','bmp'] if (!item.file) return
const invalidFiles:any[] = [] const f = item.file
const ext = f.name.toLowerCase().split('.').pop() || ''
list.forEach(item=>{ if (!fileExts.includes(ext)) {
const name = item.file.name.toLowerCase() invalidNames.push(f.name)
const ext = name.split('.').pop()
if(!allowExt.includes(ext)){
invalidFiles.push(item.file.name)
item.status = 'error' item.status = 'error'
} }
})
if(invalidFiles.length){
window.$message.error(`${invalidFiles.join('、')}】文件格式不支持`)
}
if (invalidNames.length) {
message.error(`格式不支持:${invalidNames.join('、')}`)
fileList.value = list
console.log('选中的文件列表', fileList.value)
const validList = list.filter(item =>
item.status === 'pending' && item.file && item.status !== 'error'
)
const successIds = list.filter(x=>x.status==='success' && x.file).map(x=>x.id)
//
uploadFiles.value = uploadFiles.value.filter(item=> successIds.includes(item.uid))
// pending
const pendingList = list.filter(item => item.status === 'pending' && item.file && item.status !== 'error')
if(pendingList.length === 0) return
const uploadPromises = validList.map(async (item: any) => {
try {
const res = await fileApi.uploadAndDingding(item.file, null, null)
uploadFiles.value.push(res)
const targetFile = fileList.value.find(f => f.id === item.id)
if (targetFile) {
targetFile.status = 'success'
targetFile.percentage = 100
}
} catch (error) {
console.error('文件上传失败', error)
const targetFile = fileList.value.find(f => f.id === item.id)
if (targetFile) targetFile.status = 'error'
} }
})
await Promise.all(uploadPromises) const pendingList = fileList.filter(
item => item.status === 'pending' && item.file && item.status !== 'error'
)
if (pendingList.length === 0) return
const promises = pendingList.map(async (item) => {
try {
const res = await fileApi.uploadDingAttachment(item.file)
uploadFiles.value.push({ uid: item.id, ...res })
item.status = 'success'
item.percentage = 100
message.success("附件上传成功")
} catch (err) {
console.error('附件上传失败', err)
item.status = 'error'
}
})
// await Promise.all(promises)
// const successIds = fileList.filter(x => x.status === 'success').map(x => x.id)
// uploadFiles.value = uploadFiles.value.filter(att => successIds.includes(att.uid))
})
}
async function handleUploadImageChange({ file, fileList }: { file: any, fileList: any[] }) {
const invalidNames: string[] = []
fileList.forEach(item => {
if (!item.file) return
const f = item.file
const ext = f.name.toLowerCase().split('.').pop() || ''
if (!imageExts.includes(ext)) {
invalidNames.push(f.name)
item.status = 'error'
}
if (f.size > MAX_FILE_SIZE) {
message.error(`${f.name} 超出10MB大小限制`)
item.status = 'error'
}
})
if (invalidNames.length) {
message.error(`格式不支持:${invalidNames.join('、')}`)
}
// 2.
const pendingList = fileList.filter(
item => item.status === 'pending' && item.file && item.status !== 'error'
)
if (pendingList.length === 0) return
// 3.
const promises = pendingList.map(async (item) => {
try {
const res = await fileApi.uploadDingImage(item.file)
uploadImages.value.push({ uid: item.id, ...res })
item.status = 'success'
item.percentage = 100
message.success("上传成功")
} catch (err) {
console.error('图片上传失败', err)
item.status = 'error'
}
})
// await Promise.all(promises)
// const successIds = fileList.filter(x => x.status === 'success').map(x => x.id)
// uploadImages.value = uploadImages.value.filter(img => successIds.includes(img.uid))
}
function handleReset() {
Object.assign(formDetailOKData, { ...defaultFormDetailData, resultType: 1, handleAction: '下一道工序', actionStatus: 1 })
Object.assign(formDetailScrapData, { ...defaultFormDetailData, resultType: 2, handleAction: '报废次品', actionStatus: 1 })
Object.assign(formDetailReworkData, { ...defaultFormDetailData, resultType: 3, handleAction: '返工', actionStatus: 1 })
Object.assign(formDetailConcessionData, { ...defaultFormDetailData, resultType: 4, handleAction: '让步接收', actionStatus: 1 })
imageList.value = []
uploadImages.value = []
fileList.value = []
uploadFiles.value = []
message.info("表单已重置")
} }

View File

@ -58,6 +58,18 @@
</div> </div>
</n-card> </n-card>
</div> </div>
<n-grid :cols="2" :x-gap="12">
<n-gi>
<n-card title="产出率" size="small" :bordered="false" content-style="background: transparent">
<n-progress type="circle" status="success" :percentage="stats.outputRate" />
</n-card>
</n-gi>
<n-gi>
<n-card title="报废率" size="small" :bordered="false" content-style="background: transparent">
<n-progress type="circle" status="error" :percentage="stats.scrapRate" />
</n-card>
</n-gi>
</n-grid>
<n-grid :cols="1" :x-gap="24" class="stats-charts"> <n-grid :cols="1" :x-gap="24" class="stats-charts">
<n-gi> <n-gi>
<n-card title="汇报统计图" size="small" :bordered="false" content-style="background: transparent"> <n-card title="汇报统计图" size="small" :bordered="false" content-style="background: transparent">
@ -115,6 +127,9 @@ async function loadStatistics() {
Object.assign(stats,res) Object.assign(stats,res)
console.log(stats);
updateCharts() updateCharts()
}catch{} }catch{}
} }

View File

@ -275,7 +275,7 @@
<!--查询汇报详情--> <!--查询汇报详情-->
<n-modal v-model:show="reportDetailVisible" preset="card" :title="reportDetailTitle" style="width: 800px"> <n-modal v-model:show="reportDetailVisible" preset="card" :title="reportDetailTitle" style="width: 1000px">
<n-card hoverable style="margin-bottom: 12px" > <n-card hoverable style="margin-bottom: 12px" >
<template #header> <template #header>
<div style="display:flex;justify-content:space-between;align-items:center;width:100%"> <div style="display:flex;justify-content:space-between;align-items:center;width:100%">