Merge branch 'master' of https://git.evo-techina.com/sgc/mes-vue
This commit is contained in:
commit
ab120a1256
82
mes-ui/src/api/concessionApply.ts
Normal file
82
mes-ui/src/api/concessionApply.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
// 让步接收申请表 类型定义
|
||||||
|
export interface ConcessionApply {
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
qcId?: number
|
||||||
|
|
||||||
|
concessionQty?: number
|
||||||
|
|
||||||
|
applyRemark?: string
|
||||||
|
|
||||||
|
applyContent?: string
|
||||||
|
|
||||||
|
applyFile?: string
|
||||||
|
|
||||||
|
applyUserId?: number
|
||||||
|
|
||||||
|
deptId?: number
|
||||||
|
|
||||||
|
instanceId?: string
|
||||||
|
|
||||||
|
approveStatus?: number
|
||||||
|
|
||||||
|
createTime?: string
|
||||||
|
|
||||||
|
updateTime?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 让步接收申请表 API
|
||||||
|
export const concessionApplyApi = {
|
||||||
|
// 分页查询
|
||||||
|
page(params: { page: number; pageSize: number; id?: number }) {
|
||||||
|
return request({ url: '/biz/concessionApply/page', method: 'get', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取详情
|
||||||
|
detail(id: string) {
|
||||||
|
return request({ url: `/biz/concessionApply/${id}`, method: 'get' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
create(data: ConcessionApply) {
|
||||||
|
return request({ url: '/biz/concessionApply', method: 'post', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
update(data: ConcessionApply) {
|
||||||
|
return request({ url: '/biz/concessionApply', method: 'put', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
delete(ids: string[]) {
|
||||||
|
return request({ url: `/biz/concessionApply/${ids.join(',')}`, method: 'delete' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
export(params?: { ids?: string[]; id?: number }) {
|
||||||
|
const p: Record<string, any> = {}
|
||||||
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
|
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
||||||
|
return request({ url: `/biz/concessionApply/export`, method: 'get', params: p, responseType: 'blob' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导入
|
||||||
|
importData(file: File) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request<{ success: number; fail: number; errors: string[] }>({
|
||||||
|
url: `/biz/concessionApply/import`,
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
downloadTemplate() {
|
||||||
|
return request({ url: `/biz/concessionApply/template`, method: 'get', responseType: 'blob' })
|
||||||
|
}
|
||||||
|
}
|
||||||
427
mes-ui/src/views/biz/concessionApply/index.vue
Normal file
427
mes-ui/src/views/biz/concessionApply/index.vue
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<n-card>
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<div class="search-form">
|
||||||
|
<n-form inline :model="searchForm" label-placement="left">
|
||||||
|
<n-form-item label="主键id">
|
||||||
|
<n-input v-model:value="searchForm.id" placeholder="请输入主键id" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon><n-icon><SearchOutline /></n-icon></template>
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleReset">
|
||||||
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
|
重置
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleAdd">
|
||||||
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
|
新增
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="importModalVisible = true">
|
||||||
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleExport">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
||||||
|
</n-button>
|
||||||
|
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||||
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
:scroll-x="1200"
|
||||||
|
@update:page="handlePageChange"
|
||||||
|
@update:page-size="handlePageSizeChange"
|
||||||
|
@update:checked-row-keys="handleCheck"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
||||||
|
<n-form-item label="质检编号" path="qcId">
|
||||||
|
<n-input v-model:value="formData.qcId" placeholder="请输入质检编号" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="让步接收数量" path="concessionQty">
|
||||||
|
<n-input v-model:value="formData.concessionQty" placeholder="请输入让步接收数量" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="申请原因" path="applyRemark">
|
||||||
|
<n-input v-model:value="formData.applyRemark" type="textarea" placeholder="请输入申请原因" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="申请内容" path="applyContent">
|
||||||
|
<n-input v-model:value="formData.applyContent" type="textarea" placeholder="请输入申请内容" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="申请人id(发去人id)" path="applyUserId">
|
||||||
|
<n-input v-model:value="formData.applyUserId" placeholder="请输入申请人id(发去人id)" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="部门id" path="deptId">
|
||||||
|
<n-input v-model:value="formData.deptId" placeholder="请输入部门id" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="钉钉单据id" path="instanceId">
|
||||||
|
<n-input v-model:value="formData.instanceId" placeholder="请输入钉钉单据id" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="审批状态 1待审批 2审批通过 3 审批驳回" path="approveStatus">
|
||||||
|
<n-select v-model:value="formData.approveStatus" placeholder="请选择审批状态 1待审批 2审批通过 3 审批驳回" :options="approveStatusOptions" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="modalVisible = false">取消</n-button>
|
||||||
|
<n-button type="primary" @click="handleSubmit">确定</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 导入弹窗 -->
|
||||||
|
<n-modal v-model:show="importModalVisible" preset="card" title="导入让步接收申请表" style="width: 500px">
|
||||||
|
<n-space vertical>
|
||||||
|
<n-alert type="info">
|
||||||
|
<template #header>导入说明</template>
|
||||||
|
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
|
||||||
|
<li>请先下载导入模板,按模板格式填写数据</li>
|
||||||
|
<li>支持 .xlsx 或 .xls 格式</li>
|
||||||
|
</ul>
|
||||||
|
</n-alert>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleDownloadTemplate">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
下载模板
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
|
||||||
|
<n-upload-dragger>
|
||||||
|
<div style="margin-bottom: 12px">
|
||||||
|
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
|
||||||
|
</div>
|
||||||
|
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
|
||||||
|
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx 或 .xls 格式</n-p>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</n-space>
|
||||||
|
<template #footer>
|
||||||
|
<n-button @click="importModalVisible = false">关闭</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
|
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||||
|
import { concessionApplyApi, type ConcessionApply } from '@/api/concessionApply'
|
||||||
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
id: null as number | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<ConcessionApply[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const selectedIds = ref<number[]>([])
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
itemCount: 0,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const modalTitle = ref('')
|
||||||
|
const importModalVisible = ref(false)
|
||||||
|
const formRef = ref()
|
||||||
|
const defaultFormData: ConcessionApply = {
|
||||||
|
qcId: undefined,
|
||||||
|
concessionQty: undefined,
|
||||||
|
applyRemark: '',
|
||||||
|
applyContent: '',
|
||||||
|
applyFile: '',
|
||||||
|
applyUserId: undefined,
|
||||||
|
deptId: undefined,
|
||||||
|
instanceId: '',
|
||||||
|
approveStatus: undefined,
|
||||||
|
}
|
||||||
|
const formData = reactive<ConcessionApply>({ ...defaultFormData })
|
||||||
|
|
||||||
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
|
const approveStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns: DataTableColumns<ConcessionApply> = [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ title: '主键id', key: 'id' },
|
||||||
|
{ title: '质检编号', key: 'qcId' },
|
||||||
|
{ title: '让步接收数量', key: 'concessionQty' },
|
||||||
|
{ title: '申请原因', key: 'applyRemark' },
|
||||||
|
{ title: '申请内容', key: 'applyContent' },
|
||||||
|
{ title: '申请附件', key: 'applyFile' },
|
||||||
|
{ title: '申请人id(发去人id)', key: 'applyUserId' },
|
||||||
|
{ title: '部门id', key: 'deptId' },
|
||||||
|
{ title: '钉钉单据id', key: 'instanceId' },
|
||||||
|
{ title: '审批状态 1待审批 2审批通过 3 审批驳回', key: 'approveStatus',
|
||||||
|
render(row) {
|
||||||
|
const val = row.approveStatus
|
||||||
|
const opt = approveStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
|
return opt ? opt.label : (val ?? '-')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||||
|
{ title: '更新时间', key: 'updateTime', width: 180 },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
||||||
|
h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
||||||
|
}),
|
||||||
|
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await concessionApplyApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
id: searchForm.id || undefined,
|
||||||
|
})
|
||||||
|
tableData.value = res.list
|
||||||
|
pagination.itemCount = res.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
function handleReset() {
|
||||||
|
searchForm.id = null
|
||||||
|
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择
|
||||||
|
function handleCheck(keys: Array<string | number>) {
|
||||||
|
selectedIds.value = keys as number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
function handleAdd() {
|
||||||
|
modalTitle.value = '新增让步接收申请表'
|
||||||
|
Object.assign(formData, defaultFormData)
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
function handleEdit(row: ConcessionApply) {
|
||||||
|
modalTitle.value = '编辑让步接收申请表'
|
||||||
|
Object.assign(formData, row)
|
||||||
|
if (formData.createTime && typeof formData.createTime === 'string') {
|
||||||
|
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.updateTime && typeof formData.updateTime === 'string') {
|
||||||
|
formData.updateTime = new Date(formData.updateTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
try {
|
||||||
|
const submitData = { ...formData } as ConcessionApply
|
||||||
|
if (typeof submitData.createTime === 'number') {
|
||||||
|
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.updateTime === 'number') {
|
||||||
|
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (submitData.id) {
|
||||||
|
await concessionApplyApi.update(submitData)
|
||||||
|
message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await concessionApplyApi.create(submitData)
|
||||||
|
message.success('新增成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function handleDelete(row: ConcessionApply) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除该记录吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await concessionApplyApi.delete([row.id!])
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
function handleBatchDelete() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await concessionApplyApi.delete(selectedIds.value)
|
||||||
|
message.success('删除成功')
|
||||||
|
selectedIds.value = []
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
async function handleExport() {
|
||||||
|
try {
|
||||||
|
const params: Record<string, any> = {}
|
||||||
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
|
if (searchForm.id != null) params.id = searchForm.id
|
||||||
|
const blob = await concessionApplyApi.export(params)
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '让步接收申请表数据.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
async function handleDownloadTemplate() {
|
||||||
|
try {
|
||||||
|
const blob = await concessionApplyApi.downloadTemplate()
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '让步接收申请表导入模板.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入上传
|
||||||
|
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||||
|
if (!file.file) return
|
||||||
|
try {
|
||||||
|
const result = await concessionApplyApi.importData(file.file)
|
||||||
|
if (result.fail > 0) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '导入结果',
|
||||||
|
content: `成功: ${result.success} 条,失败: ${result.fail} 条\n错误信息: ${(result.errors || []).join('\n') || '无'}`,
|
||||||
|
positiveText: '确定'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.success(`导入成功,共 ${result.success} 条数据`)
|
||||||
|
importModalVisible.value = false
|
||||||
|
}
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载字典选项
|
||||||
|
async function loadDictOptions() {
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('sys_status')
|
||||||
|
approveStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
loadDictOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
98
src/api/ConcessionApply.ts
Normal file
98
src/api/ConcessionApply.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
// 让步接收申请表 类型定义
|
||||||
|
export interface ConcessionApply {
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
qcId?: number
|
||||||
|
|
||||||
|
concessionQty?: number
|
||||||
|
|
||||||
|
applyRemark?: string
|
||||||
|
|
||||||
|
applyContent?: string
|
||||||
|
|
||||||
|
applyFile?: string
|
||||||
|
|
||||||
|
applyUserId?: number
|
||||||
|
|
||||||
|
deptId?: number
|
||||||
|
|
||||||
|
instanceId?: string
|
||||||
|
|
||||||
|
approveStatus?: number
|
||||||
|
|
||||||
|
createTime?: string
|
||||||
|
|
||||||
|
updateTime?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface DetailModel {
|
||||||
|
|
||||||
|
businessId:string,
|
||||||
|
title:string,
|
||||||
|
deptName:string,
|
||||||
|
approveResult:string,
|
||||||
|
approveRecord:[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 让步接收申请表 API
|
||||||
|
export const concessionApplyApi = {
|
||||||
|
// 分页查询
|
||||||
|
page(params: { page: number; pageSize: number; id?: number }) {
|
||||||
|
return request({ url: '/biz/concessionApply/page', method: 'get', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取详情
|
||||||
|
detail(id: string) {
|
||||||
|
return request({ url: `/biz/concessionApply/${id}`, method: 'get' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
create(data: ConcessionApply) {
|
||||||
|
return request({ url: '/biz/concessionApply', method: 'post', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
update(data: ConcessionApply) {
|
||||||
|
return request({ url: '/biz/concessionApply', method: 'put', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
delete(ids: string[]) {
|
||||||
|
return request({ url: `/biz/concessionApply/${ids.join(',')}`, method: 'delete' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
export(params?: { ids?: string[]; id?: number }) {
|
||||||
|
const p: Record<string, any> = {}
|
||||||
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
|
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
||||||
|
return request({ url: `/biz/concessionApply/export`, method: 'get', params: p, responseType: 'blob' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导入
|
||||||
|
importData(file: File) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request<{ success: number; fail: number; errors: string[] }>({
|
||||||
|
url: `/biz/concessionApply/import`,
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
downloadTemplate() {
|
||||||
|
return request({ url: `/biz/concessionApply/template`, method: 'get', responseType: 'blob' })
|
||||||
|
},
|
||||||
|
|
||||||
|
//撤回审批申请
|
||||||
|
approveCancel(params?:{id?:number}){
|
||||||
|
return request({url:`/biz/concessionApply/approveCancel`,method:'get',params})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -45,8 +45,55 @@ export interface OrderItem {
|
|||||||
|
|
||||||
starter?: number
|
starter?: number
|
||||||
|
|
||||||
|
orderCode?: string
|
||||||
|
|
||||||
|
routeCode?: string
|
||||||
|
|
||||||
|
proWorkshop?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface KingdeePrdMo {
|
||||||
|
productionOrderNo: string
|
||||||
|
billNo: string
|
||||||
|
workShopName: string
|
||||||
|
materialCode: string
|
||||||
|
quantity: number | null
|
||||||
|
specModel: string | null
|
||||||
|
unit: string | null
|
||||||
|
materialName: string | null
|
||||||
|
routingNo: string | null
|
||||||
|
planStartTime: string | null
|
||||||
|
planFinishTime: string | null
|
||||||
|
pickMtrlStatus: string | null
|
||||||
|
children: KingdeeProcessRoute[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 金蝶工艺路线工序(树子节点) */
|
||||||
|
export interface KingdeeProcessRoute {
|
||||||
|
/** 工序号 FOperNumber */
|
||||||
|
operNumber: number | null
|
||||||
|
/** 工作中心 FWorkCenterId.FName */
|
||||||
|
workCenterName: string | null
|
||||||
|
/** 生产车间 FDepartmentId.FName */
|
||||||
|
departmentName: string | null
|
||||||
|
/** 工序名称 FProcessProperty */
|
||||||
|
processName: string | null
|
||||||
|
/** 工序说明 FOperDescription */
|
||||||
|
operDescription: string | null
|
||||||
|
/** 活动单位 FActivity1UnitID.FName */
|
||||||
|
activityUnit: string | null
|
||||||
|
/** 工序控制码 FOptCtrlCodeId.FName */
|
||||||
|
optCtrlCodeName: string | null
|
||||||
|
/** 活动量 FActivity1Qty */
|
||||||
|
activityQty: number | null
|
||||||
|
/** 计划开始时间 yyyy-MM-dd HH:mm:ss */
|
||||||
|
planStartTime: string | null
|
||||||
|
/** 计划结束时间 yyyy-MM-dd HH:mm:ss */
|
||||||
|
planFinishTime: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 生产订单 API
|
// 生产订单 API
|
||||||
export const orderItemApi = {
|
export const orderItemApi = {
|
||||||
// 分页查询
|
// 分页查询
|
||||||
@ -81,6 +128,10 @@ export const orderItemApi = {
|
|||||||
preCreation(data: any) {
|
preCreation(data: any) {
|
||||||
return request({ url: '/biz/orderItem/preCreation', method: 'post', data })
|
return request({ url: '/biz/orderItem/preCreation', method: 'post', data })
|
||||||
},
|
},
|
||||||
|
//根据生产编号查询预生产订单
|
||||||
|
preCreationInfo(id: number, params:any) {
|
||||||
|
return request({ url: `/biz/orderItem/preCreation/${id}`, method: 'get', params})
|
||||||
|
},
|
||||||
|
|
||||||
//查询项目名称
|
//查询项目名称
|
||||||
xmoptions(params: any) {
|
xmoptions(params: any) {
|
||||||
@ -92,12 +143,31 @@ export const orderItemApi = {
|
|||||||
return request({ url: '/biz/orderItem/options', method: 'get', params })
|
return request({ url: '/biz/orderItem/options', method: 'get', params })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
holdTemporarilySave(id: number, data: KingdeePrdMo[]){
|
||||||
|
return request({ url: `/biz/orderItem/kingdee/temporarily/save/${id}`, method: 'post', data })
|
||||||
|
},
|
||||||
|
// 同步订单和计划表
|
||||||
|
synchronizationSave(id: number, data: KingdeePrdMo[]) {
|
||||||
|
return request({ url: `/biz/orderItem/kingdee/order/create/${id}`, method: 'post', data })
|
||||||
|
},
|
||||||
// 导出
|
// 导出
|
||||||
export(params?: { ids?: string[]; code?: string; projectId?: number; beginTime?: string; endTime?: string }) {
|
export(params?: {
|
||||||
|
ids?: string[]
|
||||||
|
code?: string
|
||||||
|
projectName?: string
|
||||||
|
orderCode?: string
|
||||||
|
routeCode?: string
|
||||||
|
proWorkshop?: string
|
||||||
|
beginTime?: string
|
||||||
|
endTime?: string
|
||||||
|
}) {
|
||||||
const p: Record<string, any> = {}
|
const p: Record<string, any> = {}
|
||||||
if (params?.ids?.length) p.ids = params.ids.join(',')
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
if (params?.code !== undefined && params?.code !== null) p.code = params.code
|
if (params?.code !== undefined && params?.code !== null) p.code = params.code
|
||||||
if (params?.projectId !== undefined && params?.projectId !== null) p.projectId = params.projectId
|
if (params?.projectName !== undefined && params?.projectName !== null) p.projectName = params.projectName
|
||||||
|
if (params?.orderCode !== undefined && params?.orderCode !== null) p.orderCode = params.orderCode
|
||||||
|
if (params?.routeCode !== undefined && params?.routeCode !== null) p.routeCode = params.routeCode
|
||||||
|
if (params?.proWorkshop !== undefined && params?.proWorkshop !== null) p.proWorkshop = params.proWorkshop
|
||||||
if (params?.beginTime !== undefined && params?.beginTime !== null) p.beginTime = params.beginTime
|
if (params?.beginTime !== undefined && params?.beginTime !== null) p.beginTime = params.beginTime
|
||||||
if (params?.endTime !== undefined && params?.endTime !== null) p.endTime = params.endTime
|
if (params?.endTime !== undefined && params?.endTime !== null) p.endTime = params.endTime
|
||||||
return request({ url: `/biz/orderItem/export`, method: 'get', params: p, responseType: 'blob' })
|
return request({ url: `/biz/orderItem/export`, method: 'get', params: p, responseType: 'blob' })
|
||||||
|
|||||||
@ -69,18 +69,57 @@ export interface KingdeePrdMo {
|
|||||||
|
|
||||||
/** 金蝶工艺路线工序(树子节点) */
|
/** 金蝶工艺路线工序(树子节点) */
|
||||||
export interface KingdeeProcessRoute {
|
export interface KingdeeProcessRoute {
|
||||||
|
/** 工序号 FOperNumber */
|
||||||
operNumber: number | null
|
operNumber: number | null
|
||||||
|
/** 工作中心 FWorkCenterId.FName */
|
||||||
workCenterName: string | null
|
workCenterName: string | null
|
||||||
|
/** 生产车间 FDepartmentId.FName */
|
||||||
departmentName: string | null
|
departmentName: string | null
|
||||||
processProperty: string | null
|
/** 工序名称 FProcessProperty */
|
||||||
|
processName: string | null
|
||||||
|
/** 工序说明 FOperDescription */
|
||||||
operDescription: string | null
|
operDescription: string | null
|
||||||
|
/** 活动单位 FActivity1UnitID.FName */
|
||||||
activityUnit: string | null
|
activityUnit: string | null
|
||||||
|
/** 工序控制码 FOptCtrlCodeId.FName */
|
||||||
optCtrlCodeName: string | null
|
optCtrlCodeName: string | null
|
||||||
|
/** 活动量 FActivity1Qty */
|
||||||
activityQty: number | null
|
activityQty: number | null
|
||||||
|
/** 计划开始时间 yyyy-MM-dd HH:mm:ss */
|
||||||
planStartTime: string | null
|
planStartTime: string | null
|
||||||
|
/** 计划结束时间 yyyy-MM-dd HH:mm:ss */
|
||||||
planFinishTime: string | null
|
planFinishTime: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解析工序名称,兼容旧字段及后端误映射 */
|
||||||
|
export function resolveProcessName(route: Partial<KingdeeProcessRoute> & { processProperty?: string | null }) {
|
||||||
|
const legacyName = typeof route.processProperty === 'string' ? route.processProperty.trim() : ''
|
||||||
|
const processName = route.processName?.trim() || legacyName || ''
|
||||||
|
const operDescription = route.operDescription?.trim() || ''
|
||||||
|
|
||||||
|
if (processName) return processName
|
||||||
|
// 兼容:FProcessProperty 被写入 operDescription 而 processName 为空
|
||||||
|
if (operDescription) return operDescription
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 归一化金蝶工序字段,保证 processName / operDescription 各归其位 */
|
||||||
|
export function normalizeKingdeeProcessRoute(
|
||||||
|
route: KingdeeProcessRoute & { processProperty?: string | null }
|
||||||
|
): KingdeeProcessRoute {
|
||||||
|
const legacyName = typeof route.processProperty === 'string' ? route.processProperty.trim() : ''
|
||||||
|
const processName = route.processName?.trim() || legacyName || ''
|
||||||
|
const operDescription = route.operDescription?.trim() || ''
|
||||||
|
|
||||||
|
if (processName) {
|
||||||
|
return { ...route, processName, operDescription: operDescription || null }
|
||||||
|
}
|
||||||
|
if (operDescription) {
|
||||||
|
return { ...route, processName: operDescription, operDescription: null }
|
||||||
|
}
|
||||||
|
return { ...route, processName: null, operDescription: null }
|
||||||
|
}
|
||||||
|
|
||||||
// 项目表 API
|
// 项目表 API
|
||||||
export const orderProjectApi = {
|
export const orderProjectApi = {
|
||||||
// 分页查询
|
// 分页查询
|
||||||
@ -136,8 +175,8 @@ export const orderProjectApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 查询金蝶生产订单 (支持优先读取本地暂存)
|
// 查询金蝶生产订单 (支持优先读取本地暂存)
|
||||||
getKingdeeOrder(id: number) {
|
getKingdeeOrder(id: number, params:any) {
|
||||||
return request<KingdeePrdMo[]>({ url: `/biz/orderProject/kingdee/order/${id}`, method: 'get' })
|
return request<KingdeePrdMo[]>({ url: `/biz/orderProject/kingdee/order/${id}`, method: 'get', params })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 暂存金蝶生产订单数据(草稿)
|
// 暂存金蝶生产订单数据(草稿)
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export function workload(params?:any) {
|
|||||||
export function inspectio(data:any) {
|
export function inspectio(data:any) {
|
||||||
return request({
|
return request({
|
||||||
url: `/mes/dispatch/qc`,
|
url: `/mes/dispatch/qc`,
|
||||||
method: 'put',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -24,6 +24,12 @@ export interface QcResultDetail {
|
|||||||
|
|
||||||
updateTime?: string
|
updateTime?: string
|
||||||
|
|
||||||
|
files?:any[],
|
||||||
|
|
||||||
|
title?:string,
|
||||||
|
images?:any[],
|
||||||
|
attachments?:any[]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 质检结果明细表 API
|
// 质检结果明细表 API
|
||||||
|
|||||||
@ -68,6 +68,10 @@ export const qualityTestingApi = {
|
|||||||
submit(data: QcResultDetail[]) {
|
submit(data: QcResultDetail[]) {
|
||||||
return request({ url: '/mes/qc/judge', method: 'put', data })
|
return request({ url: '/mes/qc/judge', method: 'put', data })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
submitBF(data: QcResultDetail[]) {
|
||||||
|
return request({ url: '/mes/qc/partial', method: 'post', data })
|
||||||
|
},
|
||||||
//质检撤回
|
//质检撤回
|
||||||
cancel(id: number) {
|
cancel(id: number) {
|
||||||
return request({ url: `/mes/dispatch/revoke/${id}`, method: 'put' })
|
return request({ url: `/mes/dispatch/revoke/${id}`, method: 'put' })
|
||||||
|
|||||||
@ -71,5 +71,11 @@ export const submitLogApi = {
|
|||||||
//汇报统计
|
//汇报统计
|
||||||
statistics(params?: {time?:string; type?:string}){
|
statistics(params?: {time?:string; type?:string}){
|
||||||
return request({url:'/mes/report/statistics',method:'get',params})
|
return request({url:'/mes/report/statistics',method:'get',params})
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
//按派工单id查询汇报
|
||||||
|
getByDispatch(dispatchId:number){
|
||||||
|
return request({url:`/mes/report/bydispatch/${dispatchId}`,method:"get"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,6 +126,14 @@ export const userApi = {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
//查询全部用户
|
||||||
|
getPathList() {
|
||||||
|
return request({
|
||||||
|
url:"sys/user/list",
|
||||||
|
method:'get'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 })
|
||||||
@ -402,8 +431,53 @@ export const fileApi = {
|
|||||||
return request({
|
return request({
|
||||||
url: '/sys/file/upload',
|
url: '/sys/file/upload',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: formData,
|
data: formData
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
// headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
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>{
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
if (path) {
|
||||||
|
formData.append('path', path)
|
||||||
|
}
|
||||||
|
if (groupId !== undefined && groupId !== null && groupId > 0) {
|
||||||
|
formData.append('groupId', groupId.toString())
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: '/sys/file/uploadAndDingding',
|
||||||
|
method: 'post',
|
||||||
|
data: formData
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getApprovalSpaceId() {
|
||||||
|
return request({
|
||||||
|
url:'sys/file/getApprovalSpaceId',
|
||||||
|
method:'get',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -418,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}`
|
||||||
},
|
},
|
||||||
|
|||||||
36
src/components/SiteLogoMark.vue
Normal file
36
src/components/SiteLogoMark.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!--
|
||||||
|
站点 Logo 展示:优先显示图片,无 URL 或加载失败时回退为站点名称首字母。
|
||||||
|
用于 layout、login、register 等页面;src 不传则使用 siteStore.siteLogo。
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
v-if="showSiteLogoImg"
|
||||||
|
:src="siteLogo"
|
||||||
|
:class="imgClass"
|
||||||
|
alt="Logo"
|
||||||
|
@error="handleLogoError"
|
||||||
|
/>
|
||||||
|
<div v-else :class="iconClass" :style="iconStyle">{{ fallbackLetter }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useSiteLogo } from '@/composables/useSiteLogo'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
/** 自定义 Logo 地址,默认取 siteStore.siteLogo */
|
||||||
|
src?: string
|
||||||
|
/** 图片模式下的 class */
|
||||||
|
imgClass?: string
|
||||||
|
/** 首字母 fallback 模式下的 class */
|
||||||
|
iconClass?: string
|
||||||
|
/** 首字母 fallback 的内联样式 */
|
||||||
|
iconStyle?: Record<string, string>
|
||||||
|
}>(), {
|
||||||
|
imgClass: 'logo-img',
|
||||||
|
iconClass: 'logo-icon',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { siteLogo, showSiteLogoImg, fallbackLetter, handleLogoError } = useSiteLogo(
|
||||||
|
() => props.src
|
||||||
|
)
|
||||||
|
</script>
|
||||||
30
src/composables/useSiteLogo.ts
Normal file
30
src/composables/useSiteLogo.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { useSiteStore } from '@/stores/site'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点 Logo:图片加载失败时回退为站点名称首字母
|
||||||
|
*/
|
||||||
|
export function useSiteLogo(src?: () => string | undefined) {
|
||||||
|
const siteStore = useSiteStore()
|
||||||
|
const siteName = computed(() => siteStore.siteName || 'MES系统')
|
||||||
|
const siteLogo = computed(() => src?.() ?? siteStore.siteLogo)
|
||||||
|
const logoLoadFailed = ref(false)
|
||||||
|
const showSiteLogoImg = computed(() => !!siteLogo.value && !logoLoadFailed.value)
|
||||||
|
const fallbackLetter = computed(() => siteName.value.charAt(0) || 'M')
|
||||||
|
|
||||||
|
watch(siteLogo, () => {
|
||||||
|
logoLoadFailed.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleLogoError() {
|
||||||
|
logoLoadFailed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
siteName,
|
||||||
|
siteLogo,
|
||||||
|
showSiteLogoImg,
|
||||||
|
fallbackLetter,
|
||||||
|
handleLogoError,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,8 +17,11 @@
|
|||||||
>
|
>
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="logo" :class="{ 'logo-collapsed': collapsed, 'logo-primary': themeStore.headerUsePrimaryColor }" :style="themeStore.headerUsePrimaryColor ? { background: themeStore.primaryColor, borderBottomColor: themeStore.primaryColor } : {}">
|
<div class="logo" :class="{ 'logo-collapsed': collapsed, 'logo-primary': themeStore.headerUsePrimaryColor }" :style="themeStore.headerUsePrimaryColor ? { background: themeStore.primaryColor, borderBottomColor: themeStore.primaryColor } : {}">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark
|
||||||
<div v-else class="logo-icon" :style="{ background: themeStore.headerUsePrimaryColor ? '#fff' : themeStore.primaryColor, color: themeStore.headerUsePrimaryColor ? themeStore.primaryColor : '#fff' }">{{ siteName.charAt(0) }}</div>
|
img-class="logo-img"
|
||||||
|
icon-class="logo-icon"
|
||||||
|
:icon-style="logoIconStyle"
|
||||||
|
/>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<span v-if="!collapsed" class="logo-text">{{ siteName }}</span>
|
<span v-if="!collapsed" class="logo-text">{{ siteName }}</span>
|
||||||
<!-- <span v-if="!collapsed" class="logo-text">伊特智造MES</span> -->
|
<!-- <span v-if="!collapsed" class="logo-text">伊特智造MES</span> -->
|
||||||
@ -43,8 +46,11 @@
|
|||||||
<n-layout-header bordered class="layout-header" :class="[`theme-${layoutConfig.theme}`, { 'header-primary': themeStore.headerUsePrimaryColor }]" :style="headerStyle">
|
<n-layout-header bordered class="layout-header" :class="[`theme-${layoutConfig.theme}`, { 'header-primary': themeStore.headerUsePrimaryColor }]" :style="headerStyle">
|
||||||
<!-- 顶部菜单模式下的Logo -->
|
<!-- 顶部菜单模式下的Logo -->
|
||||||
<div v-if="layoutConfig.siderPosition === 'top'" class="header-logo">
|
<div v-if="layoutConfig.siderPosition === 'top'" class="header-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark
|
||||||
<div v-else class="logo-icon" :style="{ background: themeStore.headerUsePrimaryColor ? '#fff' : themeStore.primaryColor, color: themeStore.headerUsePrimaryColor ? themeStore.primaryColor : '#fff' }">{{ siteName.charAt(0) }}</div>
|
img-class="logo-img"
|
||||||
|
icon-class="logo-icon"
|
||||||
|
:icon-style="logoIconStyle"
|
||||||
|
/>
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -373,6 +379,7 @@ import ProfileModal from '@/components/ProfileModal.vue'
|
|||||||
import PasswordModal from '@/components/PasswordModal.vue'
|
import PasswordModal from '@/components/PasswordModal.vue'
|
||||||
import MessageNotification from '@/components/MessageNotification.vue'
|
import MessageNotification from '@/components/MessageNotification.vue'
|
||||||
import TabBar from '@/components/TabBar.vue'
|
import TabBar from '@/components/TabBar.vue'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
import { noticeApi, chatApi, type SysNotice, type ChatMessage } from '@/api/message'
|
import { noticeApi, chatApi, type SysNotice, type ChatMessage } from '@/api/message'
|
||||||
import { iconMap as externalIconMap } from '@/utils/icons'
|
import { iconMap as externalIconMap } from '@/utils/icons'
|
||||||
|
|
||||||
@ -387,7 +394,10 @@ const themeStore = useThemeStore()
|
|||||||
|
|
||||||
// 站点配置
|
// 站点配置
|
||||||
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
||||||
const siteLogo = computed(() => siteStore.siteLogo)
|
const logoIconStyle = computed(() => ({
|
||||||
|
background: themeStore.headerUsePrimaryColor ? '#fff' : themeStore.primaryColor,
|
||||||
|
color: themeStore.headerUsePrimaryColor ? themeStore.primaryColor : '#fff',
|
||||||
|
}))
|
||||||
|
|
||||||
// 注册全局message
|
// 注册全局message
|
||||||
window.$message = message
|
window.$message = message
|
||||||
|
|||||||
@ -154,6 +154,18 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/biz/order/index.vue'),
|
component: () => import('@/views/biz/order/index.vue'),
|
||||||
meta: { title: '订单管理', icon: 'ListOutline' }
|
meta: { title: '订单管理', icon: 'ListOutline' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/orderProject',
|
||||||
|
name: 'orderProject',
|
||||||
|
component: () => import('@/views/biz/orderProject/index.vue'),
|
||||||
|
meta: { title: '项目管理', icon: 'ListOutline' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/orderProject/gantt',
|
||||||
|
name: 'OrderProjectGantt',
|
||||||
|
component: () => import('@/views/biz/orderProject/components/GanttSchedule.vue'),
|
||||||
|
meta: { title: '甘特图排产', icon: 'CalendarOutline', activeMenu: '/biz/orderProject' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'biz/code',
|
path: 'biz/code',
|
||||||
name: 'code',
|
name: 'code',
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { configGroupApi } from '@/api/org'
|
|||||||
*/
|
*/
|
||||||
export const useSiteStore = defineStore('site', () => {
|
export const useSiteStore = defineStore('site', () => {
|
||||||
// 站点名称
|
// 站点名称
|
||||||
const siteName = ref('CSY Admin')
|
const siteName = ref('MES系统')
|
||||||
// 站点描述
|
// 站点描述
|
||||||
const siteDescription = ref('伊特智造MES')
|
const siteDescription = ref('伊特智造MES')
|
||||||
// 站点 Logo
|
// 站点 Logo
|
||||||
@ -33,7 +33,7 @@ export const useSiteStore = defineStore('site', () => {
|
|||||||
try {
|
try {
|
||||||
const config = await configGroupApi.getPublicConfig()
|
const config = await configGroupApi.getPublicConfig()
|
||||||
if (config.system) {
|
if (config.system) {
|
||||||
siteName.value = config.system.siteName || 'CSY Admin'
|
siteName.value = config.system.siteName || 'MES系统'
|
||||||
siteDescription.value = config.system.siteDescription || '伊特智造MES'
|
siteDescription.value = config.system.siteDescription || '伊特智造MES'
|
||||||
siteLogo.value = config.system.siteLogo || ''
|
siteLogo.value = config.system.siteLogo || ''
|
||||||
copyright.value = config.system.copyright || ''
|
copyright.value = config.system.copyright || ''
|
||||||
|
|||||||
@ -125,6 +125,12 @@ service.interceptors.request.use(
|
|||||||
if (userStore.token) {
|
if (userStore.token) {
|
||||||
config.headers['Authorization'] = userStore.token
|
config.headers['Authorization'] = userStore.token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.data instanceof FormData) {
|
||||||
|
// 文件上传:不设置 JSON 请求头、不序列化,交给浏览器自动处理 multipart
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
config.headers["Content-Type"] = 'application/json;charset=utf-8'
|
config.headers["Content-Type"] = 'application/json;charset=utf-8'
|
||||||
config.data = config.data instanceof Object ? JSON.stringify(config.data) : config.data
|
config.data = config.data instanceof Object ? JSON.stringify(config.data) : config.data
|
||||||
return config
|
return config
|
||||||
|
|||||||
904
src/views/biz/concessionApply/index.vue
Normal file
904
src/views/biz/concessionApply/index.vue
Normal file
@ -0,0 +1,904 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<n-card>
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<div class="search-form">
|
||||||
|
<n-form inline :model="searchForm" label-placement="left">
|
||||||
|
<n-form-item label="主键id">
|
||||||
|
<n-input v-model:value="searchForm.id" placeholder="请输入主键id" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon><n-icon><SearchOutline /></n-icon></template>
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleReset">
|
||||||
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
|
重置
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleAdd">
|
||||||
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
|
新增
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="importModalVisible = true">
|
||||||
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleExport">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
||||||
|
</n-button>
|
||||||
|
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||||
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
size="small"
|
||||||
|
remote
|
||||||
|
:border="false"
|
||||||
|
:single-line="false"
|
||||||
|
striped
|
||||||
|
/>
|
||||||
|
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
|
||||||
|
<n-pagination
|
||||||
|
v-model:page="pagination.page"
|
||||||
|
v-model:page-size="pagination.pageSize"
|
||||||
|
:item-count="pagination.itemCount"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
show-size-picker
|
||||||
|
show-quick-jumper
|
||||||
|
@update:page="handlePageChange"
|
||||||
|
@update:page-size="handlePageSizeChange"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
共 {{ pagination.itemCount }} 条
|
||||||
|
</template>
|
||||||
|
</n-pagination>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
||||||
|
<n-form-item label="质检编号" path="qcId">
|
||||||
|
<n-input v-model:value="formData.qcId" placeholder="请输入质检编号" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="让步接收数量" path="concessionQty">
|
||||||
|
<n-input v-model:value="formData.concessionQty" placeholder="请输入让步接收数量" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="申请原因" path="applyRemark">
|
||||||
|
<n-input v-model:value="formData.applyRemark" type="textarea" placeholder="请输入申请原因" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="申请内容" path="applyContent">
|
||||||
|
<n-input v-model:value="formData.applyContent" type="textarea" placeholder="请输入申请内容" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="申请人id(发去人id)" path="applyUserId">
|
||||||
|
<n-input v-model:value="formData.applyUserId" placeholder="请输入申请人id(发去人id)" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="部门id" path="deptId">
|
||||||
|
<n-input v-model:value="formData.deptId" placeholder="请输入部门id" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="钉钉单据id" path="instanceId">
|
||||||
|
<n-input v-model:value="formData.instanceId" placeholder="请输入钉钉单据id" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="审批状态 1待审批 2审批通过 3 审批驳回" path="approveStatus">
|
||||||
|
<n-select v-model:value="formData.approveStatus" placeholder="请选择审批状态 1待审批 2审批通过 3 审批驳回" :options="approveStatusOptions" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="modalVisible = false">取消</n-button>
|
||||||
|
<n-button type="primary" @click="handleSubmit">确定</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 导入弹窗 -->
|
||||||
|
<n-modal v-model:show="importModalVisible" preset="card" title="导入让步接收申请表" style="width: 500px">
|
||||||
|
<n-space vertical>
|
||||||
|
<n-alert type="info">
|
||||||
|
<template #header>导入说明</template>
|
||||||
|
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
|
||||||
|
<li>请先下载导入模板,按模板格式填写数据</li>
|
||||||
|
<li>支持 .xlsx 或 .xls 格式</li>
|
||||||
|
</ul>
|
||||||
|
</n-alert>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleDownloadTemplate">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
下载模板
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
|
||||||
|
<n-upload-dragger>
|
||||||
|
<div style="margin-bottom: 12px">
|
||||||
|
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
|
||||||
|
</div>
|
||||||
|
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
|
||||||
|
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx 或 .xls 格式</n-p>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</n-space>
|
||||||
|
<template #footer>
|
||||||
|
<n-button @click="importModalVisible = false">关闭</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 审批记录弹窗 -->
|
||||||
|
<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;">
|
||||||
|
<div class="header-bar" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
||||||
|
<span style="font-size:20sp;color:#666">审批编号: {{ detailModel?.businessId }}</span>
|
||||||
|
<div>
|
||||||
|
<n-icon style="font-size:22px;margin:0 6px;cursor:pointer" @click="downloadOutline()" ><DownloadOutline /></n-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding:10px 0;">
|
||||||
|
<div style="font-size:30px;font-weight:500;margin-bottom:12px;">{{ detailModel?.title }}</div>
|
||||||
|
<div style="font-size:36sp;color:#777;margin-bottom:12px;">{{ detailModel?.deptName }}</div>
|
||||||
|
<div :style="{fontSize:'30sp', color:getApproveResultColor(detailModel?.approveResult)}">{{ detailModel?.approveResult }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="seal-wrap" style="position:absolute;right:40px;top:50%;transform:translateY(-50%);">
|
||||||
|
<div v-if="detailModel?.approveResult==='已通过'" class="seal" :class="{'pass-seal':detailModel?.approveResult==='已通过'}">已通过</div>
|
||||||
|
<div v-if="detailModel?.approveResult==='已驳回'" class="seal" :class="{'pass-seal_reject':detailModel?.approveResult==='已驳回'}">已驳回</div>
|
||||||
|
<div v-if="detailModel?.approveResult==='已撤销'" class="seal" :class="{'pass-seal_cancel':detailModel?.approveResult==='已撤销'}">已撤销</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
<n-card bordered style="width:100%;max-width:800px;padding:24px;margin-top: 10px;position:relative;">
|
||||||
|
<div style="padding:10px;">
|
||||||
|
<!-- 循环审批记录 -->
|
||||||
|
<div
|
||||||
|
v-for="(item,index) in detailModel?.approveRecord"
|
||||||
|
:key="index"
|
||||||
|
style="display:flex;gap:12px;position:relative;padding-bottom:24px;"
|
||||||
|
>
|
||||||
|
<!-- 左侧:圆点 + 竖连接线 -->
|
||||||
|
<div style="display:flex;flex-direction:column;align-items:center;">
|
||||||
|
<!-- 圆点图标 -->
|
||||||
|
<div style="width:20px;height:20px;border-radius:50%;background:#36d399;display:flex;align-items:center;justify-content:center;">
|
||||||
|
<!-- 可替换CashIcon图标 -->
|
||||||
|
<span style="color:#fff;font-size:12px;">$</span>
|
||||||
|
</div>
|
||||||
|
<!-- 中间竖线:最后一条去掉竖线 -->
|
||||||
|
<div v-if="index !== detailModel.approveRecord.length -1" style="width:2px;flex:1;background:#d0d5dd;margin-top:4px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧:标题 + 时间 + 备注 -->
|
||||||
|
<div style="flex:1;">
|
||||||
|
<!-- 用户名+操作结果 -->
|
||||||
|
<div style="font-size:18px;margin-bottom:4px;">
|
||||||
|
{{ item.username ?? '未知人员' }}
|
||||||
|
<span :style="{
|
||||||
|
color: item.operationResult === '已同意' ? '#00b42a' : item.operationResult === '已拒绝' ? '#f53f3f' : item.operationResult === '已撤销' ? '#ff7d00' : '#666',
|
||||||
|
marginLeft:'8px'
|
||||||
|
}">
|
||||||
|
{{ item.operationResult }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- 时间 -->
|
||||||
|
<div style="color:#888;font-size:14px;margin-bottom:6px;">{{ item.date }}</div>
|
||||||
|
<!-- 审批备注 -->
|
||||||
|
<div v-if="item.remark" style="padding:6px 10px;background:#f7f8fa;border-radius:6px;color:#333;">
|
||||||
|
{{ item.remark }}
|
||||||
|
</div>
|
||||||
|
<div v-if="item.attachments && item.attachments.length" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px;">
|
||||||
|
<!-- 图片类附件:png/jpg/jpeg/gif -->
|
||||||
|
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px;">
|
||||||
|
<div
|
||||||
|
v-for="file in item.attachments.filter(f=>['png','jpg','jpeg','gif'].includes(f.file_type.toLowerCase()))"
|
||||||
|
:key="file.file_id"
|
||||||
|
@click="previewImg(file)"
|
||||||
|
style="width:80px;height:80px;border-radius:6px;overflow:hidden;border:1px solid #eee;cursor:pointer;"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
</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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
|
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,
|
||||||
|
CashOutline as CashIcon,
|
||||||
|
EllipsisHorizontalCircle
|
||||||
|
} from '@vicons/ionicons5'
|
||||||
|
import { concessionApplyApi, DetailModel, type ConcessionApply } from '@/api/concessionApply'
|
||||||
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
|
import html2canvas from 'html2canvas'
|
||||||
|
import { jsPDF } from 'jspdf'
|
||||||
|
import * as dd from 'dingtalk-jsapi';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
id: null as number | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<ConcessionApply[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const selectedIds = ref<number[]>([])
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
itemCount: 0,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const modalTitle = ref('')
|
||||||
|
const importModalVisible = ref(false)
|
||||||
|
const formRef = ref()
|
||||||
|
const defaultFormData: ConcessionApply = {
|
||||||
|
qcId: undefined,
|
||||||
|
concessionQty: undefined,
|
||||||
|
applyRemark: '',
|
||||||
|
applyContent: '',
|
||||||
|
applyFile: '',
|
||||||
|
applyUserId: undefined,
|
||||||
|
deptId: undefined,
|
||||||
|
instanceId: '',
|
||||||
|
approveStatus: undefined,
|
||||||
|
}
|
||||||
|
const formData = reactive<ConcessionApply>({ ...defaultFormData })
|
||||||
|
|
||||||
|
//审批记录弹窗
|
||||||
|
const recordModalVisible = ref(false)
|
||||||
|
|
||||||
|
const detailModel = ref<DetailModel>()
|
||||||
|
|
||||||
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
|
const approveStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
|
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = {
|
||||||
|
}
|
||||||
|
|
||||||
|
const showRef = ref(false)
|
||||||
|
const currentIndex = ref(0)
|
||||||
|
const previewSrcList = ref([])
|
||||||
|
|
||||||
|
const fileModalShow = ref(false)
|
||||||
|
const fileList = ref([])
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns: DataTableColumns<ConcessionApply> = [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ title: '物料编号', key: 'materialCode',align:"center",minWidth:"150"},
|
||||||
|
{ title: '物料名称', key: 'materialName',align:"center",minWidth:"150"},
|
||||||
|
{ title: '让步接收数量', key: 'concessionQty',align:"center",minWidth:"150" },
|
||||||
|
{ title: '申请原因', key: 'applyRemark',align:"center",minWidth:"150" },
|
||||||
|
{ title: '申请内容', key: 'applyContent',align:"center",minWidth:"200" },
|
||||||
|
{
|
||||||
|
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: 'deptName',align:"center",minWidth:"150" },
|
||||||
|
// { title: '钉钉单据编号', key: 'instanceId',align:"center",minWidth:"150" },
|
||||||
|
{ title: '审批完成时间', key: 'approveTime',align:"center",minWidth:"250" },
|
||||||
|
{ title: '审批状态', key: 'approveStatus',align:"center",minWidth:"150" },
|
||||||
|
{ title: '审批结果', key: 'approveResult',align:"center",minWidth:"150",
|
||||||
|
render(row:any){
|
||||||
|
console.log(row.approveResult);
|
||||||
|
|
||||||
|
if(row.approveResult == '待审批') return h(NTag, { type: 'default', size: 'small' }, { default: () => row.approveResult })
|
||||||
|
if(row.approveResult == '已驳回') return h(NTag, { type: 'error', size: 'small' }, { default: () => row.approveResult })
|
||||||
|
if(row.approveResult == '已通过') return h(NTag, { type: 'success', size: 'small' }, { default: () => row.approveResult })
|
||||||
|
return row.approveResult
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '审批人', key: 'approveUserName',align:"center",minWidth:"150" },
|
||||||
|
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||||
|
{ title: '更新时间', key: 'updateTime', width: 180 },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
||||||
|
h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
||||||
|
}),
|
||||||
|
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
|
||||||
|
}),
|
||||||
|
h(NDropdown,{
|
||||||
|
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)})]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await concessionApplyApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
id: searchForm.id || undefined,
|
||||||
|
})
|
||||||
|
tableData.value = res.list
|
||||||
|
pagination.itemCount = res.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
function handleReset() {
|
||||||
|
searchForm.id = null
|
||||||
|
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择
|
||||||
|
function handleCheck(keys: Array<string | number>) {
|
||||||
|
selectedIds.value = keys as number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
function handleAdd() {
|
||||||
|
modalTitle.value = '新增让步接收申请表'
|
||||||
|
Object.assign(formData, defaultFormData)
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
function handleEdit(row: ConcessionApply) {
|
||||||
|
modalTitle.value = '编辑让步接收申请表'
|
||||||
|
Object.assign(formData, row)
|
||||||
|
if (formData.createTime && typeof formData.createTime === 'string') {
|
||||||
|
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.updateTime && typeof formData.updateTime === 'string') {
|
||||||
|
formData.updateTime = new Date(formData.updateTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
try {
|
||||||
|
const submitData = { ...formData } as ConcessionApply
|
||||||
|
if (typeof submitData.createTime === 'number') {
|
||||||
|
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.updateTime === 'number') {
|
||||||
|
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (submitData.id) {
|
||||||
|
await concessionApplyApi.update(submitData)
|
||||||
|
message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await concessionApplyApi.create(submitData)
|
||||||
|
message.success('新增成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function handleDelete(row: ConcessionApply) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除该记录吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await concessionApplyApi.delete([row.id!])
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//查看审批记录
|
||||||
|
function handleViewApproveRecord(row:any) {
|
||||||
|
|
||||||
|
recordModalVisible.value = true
|
||||||
|
|
||||||
|
detailModel.value = {...row}
|
||||||
|
console.log(detailModel.value);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
function handleBatchDelete() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await concessionApplyApi.delete(selectedIds.value)
|
||||||
|
message.success('删除成功')
|
||||||
|
selectedIds.value = []
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
async function handleExport() {
|
||||||
|
try {
|
||||||
|
const params: Record<string, any> = {}
|
||||||
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
|
if (searchForm.id != null) params.id = searchForm.id
|
||||||
|
const blob = await concessionApplyApi.export(params)
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '让步接收申请表数据.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
async function handleDownloadTemplate() {
|
||||||
|
try {
|
||||||
|
const blob = await concessionApplyApi.downloadTemplate()
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '让步接收申请表导入模板.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入上传
|
||||||
|
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||||
|
if (!file.file) return
|
||||||
|
try {
|
||||||
|
const result = await concessionApplyApi.importData(file.file)
|
||||||
|
if (result.fail > 0) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '导入结果',
|
||||||
|
content: `成功: ${result.success} 条,失败: ${result.fail} 条\n错误信息: ${(result.errors || []).join('\n') || '无'}`,
|
||||||
|
positiveText: '确定'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.success(`导入成功,共 ${result.success} 条数据`)
|
||||||
|
importModalVisible.value = false
|
||||||
|
}
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载字典选项
|
||||||
|
async function loadDictOptions() {
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('assets_state')
|
||||||
|
approveStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTimelineType = (type) => {
|
||||||
|
console.log(type);
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
'START_PROCESS_INSTANCE': 'info', //发起-蓝色
|
||||||
|
'AGREE': 'success',//通过-绿色
|
||||||
|
'REFUSE': 'error',//驳回-红色
|
||||||
|
'TERMINATE_PROCESS_INSTANCE':'warning',//撤回-橙黄
|
||||||
|
'grey':'default', //兼容grey
|
||||||
|
'info':'default'
|
||||||
|
}
|
||||||
|
return map[type] || 'default'
|
||||||
|
}
|
||||||
|
const getTitleAttribute = (item) =>{
|
||||||
|
return item.username +" "+item.operationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getApproveResultColor(status) {
|
||||||
|
switch(status){
|
||||||
|
case '已通过':
|
||||||
|
return "green"
|
||||||
|
case '已驳回':
|
||||||
|
return "red"
|
||||||
|
case '已撤销':
|
||||||
|
return "gray"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRemark=(item) =>{
|
||||||
|
console.log(item.remark);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadOutline() {
|
||||||
|
const model = detailModel.value
|
||||||
|
if (!model) {
|
||||||
|
alert("暂无审批数据")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 动态拼接审批记录行 HTML(彻底修复表格结构)
|
||||||
|
let recordHtml = ""
|
||||||
|
const recordList = model.approveRecord || []
|
||||||
|
recordList.forEach((item, index) => {
|
||||||
|
const remark = item.remark ?? ""
|
||||||
|
// 第一条记录需要带左侧的「审批人」单元格,后续记录只显示右侧内容
|
||||||
|
if (index === 0) {
|
||||||
|
recordHtml += `
|
||||||
|
<tr>
|
||||||
|
<td style="background: #f5f5f5;" rowspan="${recordList.length}">审批人</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||||
|
<div>
|
||||||
|
${remark ? remark + "<br>" : ""}
|
||||||
|
${item.username ?? "未知人员"} ${item.operationResult ?? ""}
|
||||||
|
</div>
|
||||||
|
<span style="color: #888; white-space: nowrap;">${item.date ?? ""}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
// 后续记录只需要右侧的内容单元格,左侧被 rowspan 合并
|
||||||
|
recordHtml += `
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||||
|
<div>
|
||||||
|
${remark ? remark + "<br>" : ""}
|
||||||
|
${item.username ?? "未知人员"} ${item.operationResult ?? ""}
|
||||||
|
</div>
|
||||||
|
<span style="color: #888; white-space: nowrap;">${item.date ?? ""}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. 动态印章
|
||||||
|
let sealText = ""
|
||||||
|
let sealBorder = "#00b42a"
|
||||||
|
let sealColor = "#00b42a"
|
||||||
|
if (model.approveResult === "已通过") {
|
||||||
|
sealText = "已通过"
|
||||||
|
} else if (model.approveResult === "已驳回") {
|
||||||
|
sealText = "已驳回"
|
||||||
|
sealBorder = "#f53f3f"
|
||||||
|
sealColor = "#f53f3f"
|
||||||
|
} else if (model.approveResult === "已撤销") {
|
||||||
|
sealText = "已撤销"
|
||||||
|
sealBorder = "#ff7d00"
|
||||||
|
sealColor = "#ff7d00"
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUser = recordList[0]?.username ?? ""
|
||||||
|
const nowTime = new Date().toLocaleString()
|
||||||
|
|
||||||
|
// 3. 完整 HTML(修复了表格结构)
|
||||||
|
const html = `
|
||||||
|
<div style="width: 750px; margin: 0 auto; font-family: SimSun, serif; position: relative; background: #fff;">
|
||||||
|
<div style="text-align: center; font-size: 22px; font-weight: bold; margin-bottom: 20px;">通用审批</div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-size: 14px; margin-bottom: 20px; align-items: center;">
|
||||||
|
<span>${model.title ?? ""}</span>
|
||||||
|
<span>创建时间:${model.createTime ?? ""}</span>
|
||||||
|
</div>
|
||||||
|
<table border="1" cellpadding="8" cellspacing="0" style="width: 100%; border-collapse: collapse; font-size: 14px;">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 120px; background: #f5f5f5;">审批编号</td>
|
||||||
|
<td colspan="3">${model.businessId ?? ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="background: #f5f5f5;">创建人</td>
|
||||||
|
<td>${createUser}</td>
|
||||||
|
<td style="width: 120px; background: #f5f5f5;">创建人部门</td>
|
||||||
|
<td>${model.deptName ?? ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="background: #f5f5f5;">申请内容</td>
|
||||||
|
<td colspan="3">${model.content ?? ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="background: #f5f5f5;">审批详情</td>
|
||||||
|
<td colspan="3">${model.approveResult ?? ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="background: #f5f5f5;">附件</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
</tr>
|
||||||
|
${recordHtml}
|
||||||
|
</table>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-top: 20px;">
|
||||||
|
<span>打印时间:${nowTime}</span>
|
||||||
|
<span>打印人:${createUser}</span>
|
||||||
|
</div>
|
||||||
|
${sealText ? `
|
||||||
|
<div style="position: absolute; top: 100px; right: 20px; width: 100px; height: 100px; border: 3px solid ${sealBorder}; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: ${sealColor}; font-size: 20px; font-weight: bold; transform: rotate(15deg);">
|
||||||
|
${sealText}
|
||||||
|
</div>
|
||||||
|
` : ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
// ========== 导出PDF逻辑 ==========
|
||||||
|
const tempDiv = document.createElement('div')
|
||||||
|
tempDiv.innerHTML = html
|
||||||
|
tempDiv.style.position = 'absolute'
|
||||||
|
tempDiv.style.left = '-9999px'
|
||||||
|
tempDiv.style.top = '0'
|
||||||
|
document.body.appendChild(tempDiv)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const canvas = await html2canvas(tempDiv, {
|
||||||
|
scale: 2,
|
||||||
|
useCORS: true,
|
||||||
|
logging: false,
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
allowTaint: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const imgData = canvas.toDataURL('image/jpeg', 1.0)
|
||||||
|
const pdf = new jsPDF('p', 'mm', 'a4')
|
||||||
|
const a4W = 210
|
||||||
|
const imgH = (canvas.height * a4W) / canvas.width
|
||||||
|
|
||||||
|
pdf.addImage(imgData, 'JPEG', 0, 0, a4W, imgH)
|
||||||
|
pdf.save(`通用审批_${model.businessId || "审批记录"}.pdf`)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("导出失败:", err)
|
||||||
|
alert("导出PDF失败")
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(tempDiv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
loadData()
|
||||||
|
loadDictOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.pass-seal{
|
||||||
|
width:150px;
|
||||||
|
height:150px;
|
||||||
|
border:4px solid #36c972;
|
||||||
|
border-radius:50%;
|
||||||
|
color:#36c972;
|
||||||
|
font-size:40px;
|
||||||
|
font-weight:bold;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
transform:rotate(25deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pass-seal_reject{
|
||||||
|
width:150px;
|
||||||
|
height:150px;
|
||||||
|
border:4px solid #da0d0d;
|
||||||
|
border-radius:50%;
|
||||||
|
color: #da0d0d;
|
||||||
|
font-size:40px;
|
||||||
|
font-weight:bold;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
transform:rotate(25deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pass-seal_cancel{
|
||||||
|
width:150px;
|
||||||
|
height:150px;
|
||||||
|
border:4px solid #3a3a3a;
|
||||||
|
border-radius:50%;
|
||||||
|
color:#3a3a3a;
|
||||||
|
font-size:40px;
|
||||||
|
font-weight:bold;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
transform:rotate(25deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
462
src/views/biz/kingdee/index.vue
Normal file
462
src/views/biz/kingdee/index.vue
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
<template>
|
||||||
|
<div class="kingdee-toolbar">
|
||||||
|
<n-space>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
:disabled="kingdeeLoading || !kingdeeTableData.length"
|
||||||
|
@click="kingdeeToggleExpandAll"
|
||||||
|
>
|
||||||
|
{{ kingdeeAllExpanded ? '全部收起' : '全部展开' }}
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
:disabled="kingdeeLoading || !kingdeeTableData.length"
|
||||||
|
@click="handleGanttSchedule"
|
||||||
|
>
|
||||||
|
<template #icon><n-icon><CalendarOutline /></n-icon></template>
|
||||||
|
甘特图排产
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
<n-data-table
|
||||||
|
v-model:expanded-row-keys="kingdeeExpandedKeys"
|
||||||
|
:columns="kingdeeMoColumns"
|
||||||
|
:data="kingdeeTableData"
|
||||||
|
:loading="kingdeeLoading"
|
||||||
|
:row-key="kingdeeMoRowKey"
|
||||||
|
:scroll-x="kingdeeMoScrollX"
|
||||||
|
:max-height="kingdeeTableMaxHeight"
|
||||||
|
:row-class-name="kingdeeMoRowClassName"
|
||||||
|
size="small"
|
||||||
|
style="margin-bottom:15px"
|
||||||
|
/>
|
||||||
|
<div style="margin-bottom: 1px;">
|
||||||
|
<!-- <template #footer> -->
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button type="error" :disabled="kingdeeLoading" @click="reCrawlReorderForm">
|
||||||
|
重新读取数据
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary" :disabled="kingdeeLoading" @click="handleSaveDraft">
|
||||||
|
保存草稿
|
||||||
|
</n-button>
|
||||||
|
<n-button type="info" :disabled="kingdeeLoading" @click="handleSyncOrderAndPlan">
|
||||||
|
同步订单计划
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="$emit('close-modal')">关闭</n-button>
|
||||||
|
</n-space>
|
||||||
|
<!-- </template> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, h, computed, defineProps, onMounted, watch, defineEmits} from 'vue'
|
||||||
|
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, useMessage, useDialog, type DataTableColumns } from 'naive-ui'
|
||||||
|
import { type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { CalendarOutline } from '@vicons/ionicons5'
|
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits(['close-modal','holdTemporarilyCallback', "synchronizationCallback","reCrawlCallback"])
|
||||||
|
const dialog = useDialog()
|
||||||
|
const message = useMessage()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
kingdeeTableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
selectedId:{
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(()=>props.kingdeeTableData,()=>{
|
||||||
|
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[]
|
||||||
|
},{ deep: true } )
|
||||||
|
|
||||||
|
|
||||||
|
const kingdeeModalVisible = ref(false)
|
||||||
|
const kingdeeLoading = ref(false)
|
||||||
|
/** 主表行:工序仅存 processRoutes,避免 children 触发树形重复行 */
|
||||||
|
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
|
||||||
|
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
||||||
|
const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
||||||
|
// const kingdeeCurrentProjectId = ref<number | null>(null) // 记录当前正在查询的订单ID
|
||||||
|
const kingdeeTableMaxHeight = 'min(78vh, 720px)'
|
||||||
|
const kingdeeMoScrollX = 1820
|
||||||
|
const kingdeeProcessScrollX = 1360
|
||||||
|
|
||||||
|
const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
||||||
|
{
|
||||||
|
type: 'expand',
|
||||||
|
expandable: () => true,
|
||||||
|
renderExpand: (row) => renderKingdeeExpand(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型', key: 'rowType', width: 96, align: 'center',
|
||||||
|
render() {
|
||||||
|
return h(NTag, { size: 'small', type: 'info', bordered: false }, { default: () => '生产订单' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '单据编号', key: 'billNo', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '生产令号', key: 'productionOrderNo', width: 130, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '工艺路线', key: 'routingNo', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '物料编码', key: 'materialCode', width: 180, ellipsis: { tooltip: true } },
|
||||||
|
{ title: '物料名称', key: 'materialName', width: 160, ellipsis: { tooltip: true } },
|
||||||
|
{
|
||||||
|
title: '数量', key: 'quantity', width: 90, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return formatKingdeeQty(row.quantity)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '生产车间', key: 'workShopName', width: 100, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '规格型号', key: 'specModel', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '单位', key: 'unit', width: 60, align: 'center' },
|
||||||
|
{
|
||||||
|
title: '计划开工', key: 'planStartTime', width: 180, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return renderKingdeePlanPickerMo(row, 'planStartTime')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '计划完工', key: 'planFinishTime', width: 180, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return renderKingdeePlanPickerMo(row, 'planFinishTime')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '领料状态', key: 'pickMtrlStatus', width: 90, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
const val = row.pickMtrlStatus
|
||||||
|
return val != null ? (pickMtrlStatusMap[String(val)] ?? val) : '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
||||||
|
{
|
||||||
|
title: '类型', key: 'rowType', width: 72, align: 'center',
|
||||||
|
render() {
|
||||||
|
return h(NTag, { size: 'small', type: 'warning', bordered: false }, { default: () => '工序' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '工序号', key: 'operNumber', width: 80, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return row.operNumber != null ? String(row.operNumber) : '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '工序名称', key: 'processProperty', width: 100, align: 'center' },
|
||||||
|
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
|
||||||
|
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{
|
||||||
|
title: '计划开始时间', key: 'planStartTime', width: 180, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return renderKingdeePlanPicker(row, 'planStartTime')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '计划结束时间', key: 'planFinishTime', width: 180, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return renderKingdeePlanPicker(row, 'planFinishTime')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '活动量', key: 'activityQty', width: 90, align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return formatKingdeeQty(row.activityQty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
|
||||||
|
{ title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const kingdeeAllExpanded = computed(() => {
|
||||||
|
const total = kingdeeTableData.value.length
|
||||||
|
if (!total) return false
|
||||||
|
return kingdeeExpandedKeys.value.length >= total
|
||||||
|
})
|
||||||
|
|
||||||
|
const pickMtrlStatusMap: Record<string, string> = {
|
||||||
|
'1': '未领料',
|
||||||
|
'2': '待定',
|
||||||
|
'3': '已领料',
|
||||||
|
}
|
||||||
|
|
||||||
|
function kingdeeMoRowClassName() {
|
||||||
|
return 'kingdee-mo-row'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatDateTime(ts: number) {
|
||||||
|
const d = new Date(ts)
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseKingdeePlanTime(val: string | number | null | undefined): number | null {
|
||||||
|
if (val == null || val === '') return null
|
||||||
|
if (typeof val === 'number') return val
|
||||||
|
const ts = new Date(val.replace(' ', 'T')).getTime()
|
||||||
|
return Number.isNaN(ts) ? null : ts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function renderKingdeePlanPicker(row: KingdeeProcessRoute, field: 'planStartTime' | 'planFinishTime') {
|
||||||
|
return h(NDatePicker, {
|
||||||
|
value: parseKingdeePlanTime(row[field]),
|
||||||
|
type: 'datetime',
|
||||||
|
clearable: true,
|
||||||
|
size: 'small',
|
||||||
|
style: { width: '100%' },
|
||||||
|
onUpdateValue: (val: number | null) => {
|
||||||
|
row[field] = val != null ? formatDateTime(val) : null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function renderKingdeePlanPickerMo(row: KingdeeMoTableRow, field: 'planStartTime' | 'planFinishTime') {
|
||||||
|
return h(NDatePicker, {
|
||||||
|
value: parseKingdeePlanTime(row[field]),
|
||||||
|
type: 'datetime',
|
||||||
|
clearable: true,
|
||||||
|
size: 'small',
|
||||||
|
style: { width: '100%' },
|
||||||
|
onUpdateValue: (val: number | null) => {
|
||||||
|
row[field] = val != null ? formatDateTime(val) : null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKingdeeQty(val: number | null | undefined) {
|
||||||
|
return val != null ? Number(val).toFixed(3) : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function kingdeeMoRowKey(row: KingdeeMoTableRow) {
|
||||||
|
return `mo-${row.productionOrderNo}-${row.billNo}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function kingdeeToggleExpandAll() {
|
||||||
|
if (kingdeeAllExpanded.value) {
|
||||||
|
kingdeeExpandedKeys.value = []
|
||||||
|
} else {
|
||||||
|
kingdeeExpandedKeys.value = kingdeeTableData.value.map(kingdeeMoRowKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function renderKingdeeExpand(row: KingdeeMoTableRow) {
|
||||||
|
const routes = getKingdeeRoutes(row)
|
||||||
|
if (!routes.length) {
|
||||||
|
return h('div', { class: 'kingdee-expand-empty' }, '暂无工艺路线工序')
|
||||||
|
}
|
||||||
|
return h('div', { class: 'kingdee-expand-wrap' }, [
|
||||||
|
h(NDataTable, {
|
||||||
|
class: 'kingdee-process-table',
|
||||||
|
columns: kingdeeProcessColumns,
|
||||||
|
data: routes,
|
||||||
|
rowKey: kingdeeProcessRowKey,
|
||||||
|
size: 'small',
|
||||||
|
bordered: true,
|
||||||
|
striped: true,
|
||||||
|
scrollX: kingdeeProcessScrollX,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKingdeeRoutes(mo: KingdeeMoTableRow) {
|
||||||
|
return mo.processRoutes
|
||||||
|
}
|
||||||
|
function kingdeeProcessRowKey(row: KingdeeProcessRoute) {
|
||||||
|
return `route-${row.operNumber}-${row.workCenterName}-${row.operDescription}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 甘特图排产
|
||||||
|
function handleGanttSchedule() {
|
||||||
|
// 将待排产数据和当前项目ID存入 sessionStorage,供甘特图页面使用
|
||||||
|
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(kingdeeTableData.value))
|
||||||
|
if (props.selectedId) {
|
||||||
|
sessionStorage.setItem('gantt_project_id', String(props.selectedId))
|
||||||
|
}
|
||||||
|
// 跳转到甘特图排产独立页面
|
||||||
|
router.push('/biz/orderProject/gantt')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步订单和计划表
|
||||||
|
async function handleSyncOrderAndPlan() {
|
||||||
|
if (!props.selectedId) return
|
||||||
|
|
||||||
|
kingdeeLoading.value = true
|
||||||
|
try {
|
||||||
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
|
const draftData = kingdeeTableData.value.map(mo => {
|
||||||
|
const { processRoutes, ...rest } = mo
|
||||||
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
|
})
|
||||||
|
|
||||||
|
emit('synchronizationCallback', props.selectedId, draftData, (res) => {
|
||||||
|
if("success" === res){
|
||||||
|
message.success('同步订单计划成功')
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
kingdeeModalVisible.value = false
|
||||||
|
} catch (error) {
|
||||||
|
// 错误拦截器已处理
|
||||||
|
} finally {
|
||||||
|
kingdeeLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const reCrawlReorderForm = () => {
|
||||||
|
// // 暂存金蝶生产订单数据(草稿)
|
||||||
|
if (!props.selectedId) return
|
||||||
|
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '重启读取会导致当前页面数据全部重置, 确定需要重新读取吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
kingdeeLoading.value = true
|
||||||
|
try {
|
||||||
|
emit('reCrawlCallback', props.selectedId, true)
|
||||||
|
message.success('重启读取成功')
|
||||||
|
} catch (error) {
|
||||||
|
// 错误拦截器已处理
|
||||||
|
} finally {
|
||||||
|
kingdeeLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveDraft = () => {
|
||||||
|
// // 暂存金蝶生产订单数据(草稿)
|
||||||
|
if (!props.selectedId) return
|
||||||
|
|
||||||
|
kingdeeLoading.value = true
|
||||||
|
try {
|
||||||
|
const draftData = kingdeeTableData.value.map(mo => {
|
||||||
|
const { processRoutes, ...rest } = mo
|
||||||
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
|
})
|
||||||
|
emit('holdTemporarilyCallback', props.selectedId, draftData)
|
||||||
|
message.success('草稿保存成功')
|
||||||
|
} catch (error) {
|
||||||
|
// 错误拦截器已处理
|
||||||
|
} finally {
|
||||||
|
kingdeeLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// // 暂存金蝶生产订单数据(草稿)
|
||||||
|
// async function handleSaveDraft() {
|
||||||
|
// if (!kingdeeCurrentProjectId) return
|
||||||
|
|
||||||
|
// kingdeeLoading.value = true
|
||||||
|
// try {
|
||||||
|
// // 恢复数据结构,将 processRoutes 转换为 children
|
||||||
|
// const draftData = kingdeeTableData.value.map(mo => {
|
||||||
|
// const { processRoutes, ...rest } = mo
|
||||||
|
// return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
|
// })
|
||||||
|
|
||||||
|
// emit('holdTemporarilyCallback', { id:kingdeeCurrentProjectId, data:draftData })
|
||||||
|
// // await orderProjectApi.saveKingdeeOrderDraft(kingdeeCurrentProjectId.value, draftData)
|
||||||
|
// // message.success('草稿保存成功')
|
||||||
|
// } catch (error) {
|
||||||
|
// // 错误拦截器已处理
|
||||||
|
// } finally {
|
||||||
|
// kingdeeLoading.value = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kingdee-toolbar {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-modal .n-card) {
|
||||||
|
max-height: 94vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-modal .n-card__content) {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-mo-row td) {
|
||||||
|
background-color: rgba(32, 128, 240, 0.06) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-mo-row:hover td) {
|
||||||
|
background-color: rgba(32, 128, 240, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kingdee-expand-wrap {
|
||||||
|
margin: 4px 8px 8px 36px;
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
border: 1px solid var(--n-border-color);
|
||||||
|
border-left: 4px solid #18a058;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--n-action-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-process-table .n-data-table-th) {
|
||||||
|
background: rgba(24, 160, 88, 0.12) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-process-table .n-data-table-td) {
|
||||||
|
background-color: var(--n-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.kingdee-process-table .n-data-table-tr--striped .n-data-table-td) {
|
||||||
|
background-color: var(--n-action-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kingdee-expand-empty {
|
||||||
|
margin: 4px 8px 8px 36px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--n-text-color-3);
|
||||||
|
border: 1px dashed var(--n-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--n-action-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<!-- 工具栏 -->
|
<!-- 工具栏 -->
|
||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button
|
<!--<n-button
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleAdd"
|
@click="handleAdd"
|
||||||
@ -42,15 +42,15 @@
|
|||||||
<template #icon><n-icon><AddOutline /></n-icon></template>
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
新增/补单
|
新增/补单
|
||||||
</n-button>
|
</n-button>
|
||||||
<!-- <n-button @click="importModalVisible = true">
|
<n-button @click="importModalVisible = true">
|
||||||
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
导入
|
导入
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button @click="handleExport">
|
<n-button @click="handleExport">
|
||||||
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
||||||
</n-button> -->
|
</n-button>
|
||||||
<!-- <n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||||
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
删除
|
删除
|
||||||
</n-button> -->
|
</n-button> -->
|
||||||
@ -124,16 +124,11 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi> -->
|
</n-gi> -->
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="项目名称" path="projectCode">
|
<n-form-item label="项目名称" path="project">
|
||||||
<!-- <n-input v-if="formData.id" v-model:value="formData.projectName" disabled placeholder="请选择项目名称" />
|
|
||||||
-->
|
|
||||||
|
|
||||||
<n-input v-if="formData.id" v-model:value="formData.projectName" disabled placeholder="请选择项目" />
|
<n-input v-if="formData.id" v-model:value="formData.projectName" disabled placeholder="请选择项目" />
|
||||||
|
|
||||||
<!--@search="xmhand"-->
|
|
||||||
<n-select
|
<n-select
|
||||||
v-else
|
v-else
|
||||||
v-model:value="formData.projectCode"
|
v-model:value="formData.projectId"
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择项目"
|
placeholder="请选择项目"
|
||||||
:options="xmlist"
|
:options="xmlist"
|
||||||
@ -182,6 +177,21 @@
|
|||||||
<n-input v-else v-model:value="formData.quantity" placeholder="请输入生产数量" disabled />
|
<n-input v-else v-model:value="formData.quantity" placeholder="请输入生产数量" disabled />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="订单编号" path="orderCode">
|
||||||
|
<n-input v-model:value="formData.orderCode" placeholder="订单编号" :disabled="!formData.id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="工艺路线编码" path="routeCode">
|
||||||
|
<n-input v-model:value="formData.routeCode" placeholder="工艺路线编码" :disabled="!formData.id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产车间" path="proWorkshop">
|
||||||
|
<n-input v-model:value="formData.proWorkshop" placeholder="生产车间" :disabled="!formData.id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="是否半成品" path="sfProduct">
|
<n-form-item label="是否半成品" path="sfProduct">
|
||||||
@ -341,77 +351,39 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
<!-- 导入弹窗 -->
|
<n-modal
|
||||||
<!-- <n-modal v-model:show="importModalVisible" preset="card" title="导入生产订单" style="width: 500px">
|
v-model:show="kingdeeModalVisible"
|
||||||
<n-space vertical>
|
preset="card"
|
||||||
<n-alert type="info">
|
:title="kingdeeModalTitle"
|
||||||
<template #header>导入说明</template>
|
class="kingdee-modal"
|
||||||
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
|
:style="{ width: '96vw', maxWidth: '1920px' }"
|
||||||
<li>请先下载导入模板,按模板格式填写数据</li>
|
:content-style="{ padding: '16px 24px', minHeight: '72vh' }"
|
||||||
<li>支持 .xlsx 或 .xls 格式</li>
|
:mask-closable="false"
|
||||||
</ul>
|
:closable="true"
|
||||||
</n-alert>
|
:close-on-esc="false"
|
||||||
<n-space>
|
>
|
||||||
<n-button type="primary" @click="handleDownloadTemplate">
|
<KingdeeVue
|
||||||
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
:kingdeeTableData="kingdeeTableData"
|
||||||
下载模板
|
:selectedId="kingdeeOrderOtemId"
|
||||||
</n-button>
|
@close-modal="kingdeeModalVisible = false"
|
||||||
</n-space>
|
@holdTemporarilyCallback="holdTemporarilySave"
|
||||||
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
|
@synchronizationCallback="synchronizationSave"
|
||||||
<n-upload-dragger>
|
@reCrawlCallback="reCrawlReorderForm"/>
|
||||||
<div style="margin-bottom: 12px">
|
</n-modal>
|
||||||
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
|
|
||||||
</div>
|
|
||||||
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
|
|
||||||
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx 或 .xls 格式</n-p>
|
|
||||||
</n-upload-dragger>
|
|
||||||
</n-upload>
|
|
||||||
</n-space>
|
|
||||||
<template #footer>
|
|
||||||
<n-button @click="importModalVisible = false">关闭</n-button>
|
|
||||||
</template>
|
|
||||||
</n-modal> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
ref,
|
import { NButton, NSpace, NIcon, useMessage, useDialog, type UploadCustomRequestOptions} from 'naive-ui'
|
||||||
reactive,
|
import { orderItemApi, type OrderItem } from '@/api/orderItem'
|
||||||
h,
|
|
||||||
onMounted
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
NButton,
|
|
||||||
NSpace,
|
|
||||||
NIcon,
|
|
||||||
//NUpload,
|
|
||||||
useMessage,
|
|
||||||
useDialog,
|
|
||||||
//type DataTableColumns,
|
|
||||||
type UploadCustomRequestOptions
|
|
||||||
} from 'naive-ui'
|
|
||||||
import {
|
|
||||||
SearchOutline,
|
|
||||||
RefreshOutline,
|
|
||||||
AddOutline,
|
|
||||||
//TrashOutline,
|
|
||||||
//CreateOutline,
|
|
||||||
// CloudUploadOutline,
|
|
||||||
// DownloadOutline
|
|
||||||
} from '@vicons/ionicons5'
|
|
||||||
import {
|
|
||||||
orderItemApi,
|
|
||||||
type OrderItem
|
|
||||||
} from '@/api/orderItem'
|
|
||||||
|
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
import { VueDraggable } from 'vue-draggable-plus'
|
import { VueDraggable } from 'vue-draggable-plus'
|
||||||
|
|
||||||
// import Gxitem from './gxitem.vue'
|
import KingdeeVue from '@/views/biz/kingdee/index.vue'
|
||||||
|
|
||||||
|
|
||||||
|
import { type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||||
import { userApi } from '@/api/system'
|
import { userApi } from '@/api/system'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@ -431,6 +403,9 @@ const hasPermission = (permission: string) => userStore.hasPermission(permission
|
|||||||
const searchForm = reactive<any>({
|
const searchForm = reactive<any>({
|
||||||
code: '',
|
code: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
|
orderCode: '',
|
||||||
|
routeCode: '',
|
||||||
|
proWorkshop: '',
|
||||||
beginTime: null as number | null,
|
beginTime: null as number | null,
|
||||||
endTime: null as number | null,
|
endTime: null as number | null,
|
||||||
})
|
})
|
||||||
@ -477,6 +452,9 @@ const defaultFormData = {
|
|||||||
assingWorkOperationId: '',
|
assingWorkOperationId: '',
|
||||||
assingWorkOperationTime: null,
|
assingWorkOperationTime: null,
|
||||||
starter: '',
|
starter: '',
|
||||||
|
orderCode: '',
|
||||||
|
routeCode: '',
|
||||||
|
proWorkshop: '',
|
||||||
}
|
}
|
||||||
const formData = reactive<any>({ ...defaultFormData })
|
const formData = reactive<any>({ ...defaultFormData })
|
||||||
|
|
||||||
@ -512,6 +490,24 @@ const columns = [
|
|||||||
title: '物料编码',
|
title: '物料编码',
|
||||||
key: 'code'
|
key: 'code'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
minWidth: 150,
|
||||||
|
title: '订单编号',
|
||||||
|
key: 'orderCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
minWidth: 150,
|
||||||
|
title: '工艺路线编码',
|
||||||
|
key: 'routeCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
minWidth: 150,
|
||||||
|
title: '生产车间',
|
||||||
|
key: 'proWorkshop',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
@ -602,7 +598,7 @@ const columns = [
|
|||||||
align:'center',
|
align:'center',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 140,
|
width: 190,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
|
|
||||||
@ -614,7 +610,6 @@ const columns = [
|
|||||||
type:'primary',
|
type:'primary',
|
||||||
ghost:true,
|
ghost:true,
|
||||||
onClick: () => { handleEdit(row) } },
|
onClick: () => { handleEdit(row) } },
|
||||||
//{ default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑' ]}
|
|
||||||
{ default: () => '编辑'}
|
{ default: () => '编辑'}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -627,6 +622,15 @@ const columns = [
|
|||||||
{ default: () => '派工'}
|
{ default: () => '派工'}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
if(hasPermission('biz:orderItem:reorderForm')){
|
||||||
|
buttons.push(h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type:'info',
|
||||||
|
ghost:true,
|
||||||
|
onClick: () => handleReorderForm(row, false) }, //, disabled: row.starter !==1
|
||||||
|
{ default: () => '补单'}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
||||||
|
|
||||||
@ -664,7 +668,10 @@ async function loadData() {
|
|||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
code: searchForm.code,
|
code: searchForm.code,
|
||||||
projectId: searchForm.projectId,
|
projectName: searchForm.projectName,
|
||||||
|
orderCode: searchForm.orderCode,
|
||||||
|
routeCode: searchForm.routeCode,
|
||||||
|
proWorkshop: searchForm.proWorkshop,
|
||||||
beginTime: searchForm.beginTime,
|
beginTime: searchForm.beginTime,
|
||||||
endTime: searchForm.endTime
|
endTime: searchForm.endTime
|
||||||
})
|
})
|
||||||
@ -684,13 +691,12 @@ function handleSearch() {
|
|||||||
// 重置
|
// 重置
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.code = ''
|
searchForm.code = ''
|
||||||
|
searchForm.projectName = ''
|
||||||
searchForm.projectId = ''
|
searchForm.orderCode = ''
|
||||||
|
searchForm.routeCode = ''
|
||||||
|
searchForm.proWorkshop = ''
|
||||||
searchForm.beginTime = null
|
searchForm.beginTime = null
|
||||||
|
|
||||||
searchForm.endTime = null
|
searchForm.endTime = null
|
||||||
|
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,14 +724,20 @@ let planList = ref<any>([])
|
|||||||
if(formData.code){
|
if(formData.code){
|
||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
orderItemApi.preCreation({
|
orderItemApi.preCreation({
|
||||||
//productionCode:formData.productionCode
|
|
||||||
code:formData.code,
|
code:formData.code,
|
||||||
projectCode:formData.projectCode
|
projectCode:formData.projectCode
|
||||||
}).then((rps:any) => {
|
}).then((rps:any) => {
|
||||||
planList.value = rps.planList ? rps.planList : []
|
planList.value = rps.planList ? rps.planList : []
|
||||||
|
formData.orderCode = rps.orderCode ?? ''
|
||||||
|
formData.routeCode = rps.routeCode ?? ''
|
||||||
|
formData.proWorkshop = rps.proWorkshop ?? ''
|
||||||
|
if (rps.quantity != null) formData.quantity = rps.quantity
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
|
formData.orderCode = ''
|
||||||
|
formData.routeCode = ''
|
||||||
|
formData.proWorkshop = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,35 +766,22 @@ let xmlist = ref<any>([])
|
|||||||
let xmloading = ref(false)
|
let xmloading = ref(false)
|
||||||
|
|
||||||
|
|
||||||
function xmhand() {
|
async function xmhand() {
|
||||||
xmloading.value = true
|
xmloading.value = true
|
||||||
orderItemApi.xmoptions({
|
xmlist.value = await orderItemApi.xmoptions({});
|
||||||
// page: 1,
|
|
||||||
// pageSize: 100,
|
|
||||||
// username: v,
|
|
||||||
}).then((rps:any) =>{
|
|
||||||
if(rps && rps.length > 0){
|
|
||||||
xmlist.value = rps.map((n:any) => {
|
|
||||||
return {
|
|
||||||
label:n.label,
|
|
||||||
value:n.value+''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
xmloading.value = false
|
xmloading.value = false
|
||||||
}).catch(() => {
|
|
||||||
xmloading.value = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let wllist = ref<any>([])
|
let wllist = ref<any>([])
|
||||||
function xmselupdate(v:any,opt:any) {
|
function xmselupdate(v:any,opt:any) {
|
||||||
// console.log(v,'sle')
|
|
||||||
// console.log(opt,'opt')
|
|
||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
wllist.value.splice(0)
|
wllist.value.splice(0)
|
||||||
formData.code = ''
|
formData.code = ''
|
||||||
|
formData.orderCode = ''
|
||||||
|
formData.routeCode = ''
|
||||||
|
formData.proWorkshop = ''
|
||||||
formData.projectName = opt.label
|
formData.projectName = opt.label
|
||||||
|
formData.projectCode = opt?.additionParams?.projectCode || ''
|
||||||
if(v){
|
if(v){
|
||||||
wlhand(opt.value)
|
wlhand(opt.value)
|
||||||
}
|
}
|
||||||
@ -791,29 +790,91 @@ function xmselupdate(v:any,opt:any) {
|
|||||||
//根据项目查询物料
|
//根据项目查询物料
|
||||||
|
|
||||||
let wlloading = ref(false)
|
let wlloading = ref(false)
|
||||||
function wlhand(v:any) {
|
async function wlhand(v:any) {
|
||||||
wlloading.value = true
|
wlloading.value = true
|
||||||
orderItemApi.wloptions({
|
wllist.value = await orderItemApi.wloptions({projectId:v });
|
||||||
projectId:v
|
|
||||||
// page: 1,
|
|
||||||
// pageSize: 100,
|
|
||||||
// username: v
|
|
||||||
}).then((rps:any) =>{
|
|
||||||
if(rps && rps.length > 0){
|
|
||||||
wllist.value = rps.map((n:any) => {
|
|
||||||
return {
|
|
||||||
label:n.label,
|
|
||||||
value:n.value+''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log(yglist.value,'12')
|
|
||||||
wlloading.value = false
|
wlloading.value = false
|
||||||
}).catch(() => {
|
|
||||||
wlloading.value = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kingdeeOrderOtemId = ref<number | null>(null) // 记录当前正在查询的订单ID
|
||||||
|
const kingdeeModalVisible = ref(false)
|
||||||
|
const kingdeeModalTitle = ref('金蝶生产订单')
|
||||||
|
const kingdeeLoading = ref(false)
|
||||||
|
|
||||||
|
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
|
||||||
|
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
||||||
|
// const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
||||||
|
|
||||||
|
async function reCrawlReorderForm(id:number,reCrawl:Boolean){
|
||||||
|
try {
|
||||||
|
const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl})
|
||||||
|
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
||||||
|
const { children, ...rest } = mo
|
||||||
|
const routes = [...(children ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0)).map((r) => ({
|
||||||
|
...r,
|
||||||
|
planStartTime: r.planStartTime ?? null,
|
||||||
|
planFinishTime: r.planFinishTime ?? null,
|
||||||
|
}))
|
||||||
|
return { ...rest, processRoutes: routes }
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
kingdeeLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleReorderForm(row:OrderItem, reCrawl:Boolean){
|
||||||
|
if (!row.id) {
|
||||||
|
message.warning('缺少物料订单ID,无法查询')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kingdeeOrderOtemId.value = row.id
|
||||||
|
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
||||||
|
kingdeeModalVisible.value = true
|
||||||
|
kingdeeLoading.value = true
|
||||||
|
reCrawlReorderForm(row.id, false)
|
||||||
|
kingdeeLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂存金蝶生产订单数据(草稿)
|
||||||
|
async function holdTemporarilySave(id: number, data: KingdeePrdMo[]) {
|
||||||
|
try {
|
||||||
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
|
await orderItemApi.holdTemporarilySave(id, data)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误拦截器已处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const synchronizationSave = async (id: number, data: KingdeePrdMo[], callback:Function) => {
|
||||||
|
// try {
|
||||||
|
// await orderItemApi.synchronizationSave(id, data) .then((rps:any) =>{
|
||||||
|
|
||||||
|
// }).catch((res) => {
|
||||||
|
// callback(res)
|
||||||
|
// })
|
||||||
|
// // 接口拿到数据,调用子的回调,回传结果
|
||||||
|
// } catch (err) {
|
||||||
|
// callback(err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 同步订单和计划表
|
||||||
|
async function synchronizationSave(id: number, data: KingdeePrdMo[], callback:Function) {
|
||||||
|
try {
|
||||||
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
|
await orderItemApi.synchronizationSave(id, data)
|
||||||
|
.then((rps:any) =>{
|
||||||
|
callback(rps)
|
||||||
|
}).catch((res) => {
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
// 错误拦截器已处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
function handleEdit(row: OrderItem) {
|
function handleEdit(row: OrderItem) {
|
||||||
modalTitle.value = '编辑生产订单'
|
modalTitle.value = '编辑生产订单'
|
||||||
@ -1022,7 +1083,10 @@ async function handleExport() {
|
|||||||
const params: Record<string, any> = {}
|
const params: Record<string, any> = {}
|
||||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
if (searchForm.code) params.code = searchForm.code
|
if (searchForm.code) params.code = searchForm.code
|
||||||
if (searchForm.projectId != null) params.projectId = searchForm.projectId
|
if (searchForm.projectName) params.projectName = searchForm.projectName
|
||||||
|
if (searchForm.orderCode) params.orderCode = searchForm.orderCode
|
||||||
|
if (searchForm.routeCode) params.routeCode = searchForm.routeCode
|
||||||
|
if (searchForm.proWorkshop) params.proWorkshop = searchForm.proWorkshop
|
||||||
if (searchForm.beginTime != null) params.beginTime = searchForm.beginTime
|
if (searchForm.beginTime != null) params.beginTime = searchForm.beginTime
|
||||||
if (searchForm.endTime != null) params.endTime = searchForm.endTime
|
if (searchForm.endTime != null) params.endTime = searchForm.endTime
|
||||||
const blob = await orderItemApi.export(params)
|
const blob = await orderItemApi.export(params)
|
||||||
|
|||||||
@ -1,25 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="gantt-schedule-page">
|
<div class="gantt-schedule-page">
|
||||||
<div class="gantt-header">
|
<div class="gantt-header">
|
||||||
<n-space justify="space-between" align="center">
|
<div class="gantt-header-left">
|
||||||
<n-h2 style="margin: 0">甘特图排产</n-h2>
|
<h1 class="page-title">甘特图排产</h1>
|
||||||
|
<p class="page-desc">拖拽工序条块调整计划时间,完成后请保存排产草稿</p>
|
||||||
|
</div>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button type="primary" :loading="saving" @click="handleSaveDraft">保存排产草稿</n-button>
|
<n-button type="primary" :loading="saving" @click="handleSaveDraft">保存排产草稿</n-button>
|
||||||
<n-button @click="handleBack">返回</n-button>
|
<n-button @click="handleBack">返回</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-space>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="gantt-container" ref="ganttContainer"></div>
|
|
||||||
|
<div class="gantt-meta">
|
||||||
|
<span>生产订单 {{ scheduleStats.moCount }} 条</span>
|
||||||
|
<span class="meta-divider">|</span>
|
||||||
|
<span>工序 {{ scheduleStats.routeCount }} 道</span>
|
||||||
|
<span class="meta-divider">|</span>
|
||||||
|
<span>今日 {{ todayLabel }}</span>
|
||||||
|
<span class="meta-divider">|</span>
|
||||||
|
<span class="meta-legend"><i class="legend-today-line" />今日时间轴</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gantt-panel">
|
||||||
|
<div class="gantt-panel-head">
|
||||||
|
<span class="panel-title">排产明细</span>
|
||||||
|
<span class="panel-tip">主产品行展示物料编码与名称,子行展示工序号与工序名称</span>
|
||||||
|
</div>
|
||||||
|
<div class="gantt-container" ref="ganttContainer" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, toRaw } from 'vue'
|
import { ref, onMounted, onUnmounted, toRaw, computed } from 'vue'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { gantt } from 'dhtmlx-gantt'
|
import { gantt } from 'dhtmlx-gantt'
|
||||||
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
||||||
import { orderProjectApi, type KingdeePrdMo } from '@/api/orderProject'
|
import { orderProjectApi, normalizeKingdeeProcessRoute, resolveProcessName, type KingdeePrdMo } from '@/api/orderProject'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const ganttContainer = ref<HTMLElement | null>(null)
|
const ganttContainer = ref<HTMLElement | null>(null)
|
||||||
@ -30,6 +48,42 @@ const tableData = ref<any[]>([])
|
|||||||
const projectId = ref<number | null>(null)
|
const projectId = ref<number | null>(null)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
|
||||||
|
const PROCESS_COLORS = [
|
||||||
|
'#409eff', '#67c23a', '#e6a23c', '#909399', '#f56c6c',
|
||||||
|
'#36cfc9', '#597ef7', '#9254de', '#73d13d',
|
||||||
|
]
|
||||||
|
|
||||||
|
const WEEKDAY_LABELS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||||
|
|
||||||
|
function getRouteColorIndex(index: number) {
|
||||||
|
return index % PROCESS_COLORS.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRouteColor(index: number) {
|
||||||
|
return PROCESS_COLORS[getRouteColorIndex(index)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDayScale(date: Date) {
|
||||||
|
return `${date.getDate()}日 ${WEEKDAY_LABELS[date.getDay()]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleStats = computed(() => {
|
||||||
|
let routeCount = 0
|
||||||
|
tableData.value.forEach((mo) => {
|
||||||
|
routeCount += mo.processRoutes?.length ?? 0
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
moCount: tableData.value.length,
|
||||||
|
routeCount,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const todayLabel = computed(() => {
|
||||||
|
const d = new Date()
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||||
|
})
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
@ -42,7 +96,6 @@ async function handleSaveDraft() {
|
|||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
// 恢复数据结构,将 processRoutes 转换为 children
|
|
||||||
const draftData = tableData.value.map(mo => {
|
const draftData = tableData.value.map(mo => {
|
||||||
const { processRoutes, ...rest } = mo
|
const { processRoutes, ...rest } = mo
|
||||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
@ -50,11 +103,7 @@ async function handleSaveDraft() {
|
|||||||
|
|
||||||
await orderProjectApi.saveKingdeeOrderDraft(projectId.value, draftData)
|
await orderProjectApi.saveKingdeeOrderDraft(projectId.value, draftData)
|
||||||
message.success('排产草稿保存成功')
|
message.success('排产草稿保存成功')
|
||||||
|
|
||||||
// 更新 session 里的暂存,以防返回页面时不一致
|
|
||||||
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(tableData.value))
|
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(tableData.value))
|
||||||
} catch (error) {
|
|
||||||
// 错误拦截器已处理
|
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@ -62,10 +111,8 @@ async function handleSaveDraft() {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
initGantt()
|
initGantt()
|
||||||
|
|
||||||
|
|
||||||
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
||||||
const idStr = sessionStorage.getItem('gantt_project_id')
|
const idStr = sessionStorage.getItem('gantt_project_id')
|
||||||
|
|
||||||
@ -75,12 +122,15 @@ onMounted(async () => {
|
|||||||
|
|
||||||
if (stateStr) {
|
if (stateStr) {
|
||||||
const parsedData = JSON.parse(stateStr)
|
const parsedData = JSON.parse(stateStr)
|
||||||
tableData.value = parsedData
|
tableData.value = (Array.isArray(parsedData) ? parsedData : []).map((mo: any) => ({
|
||||||
|
...mo,
|
||||||
|
processRoutes: (mo.processRoutes ?? []).map(normalizeKingdeeProcessRoute),
|
||||||
|
}))
|
||||||
renderGanttData()
|
renderGanttData()
|
||||||
} else {
|
} else {
|
||||||
message.warning('未获取到排产数据')
|
message.warning('未获取到排产数据')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error('加载排产数据失败')
|
message.error('加载排产数据失败')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -90,122 +140,197 @@ function formatGanttDate(date: Date) {
|
|||||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatGridDate(value: Date | string | undefined) {
|
||||||
|
if (!value) return ''
|
||||||
|
const date = value instanceof Date ? value : new Date(String(value).replace(' ', 'T'))
|
||||||
|
if (isNaN(date.getTime())) return ''
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoMaterialLabel(mo: any) {
|
||||||
|
const code = mo.materialCode?.trim() || ''
|
||||||
|
const name = mo.materialName?.trim() || ''
|
||||||
|
if (code && name) return `${code} ${name}`
|
||||||
|
return code || name
|
||||||
|
}
|
||||||
|
|
||||||
function initGantt() {
|
function initGantt() {
|
||||||
if (!ganttContainer.value) return
|
if (!ganttContainer.value) return
|
||||||
|
|
||||||
// 清理之前的事件绑定
|
|
||||||
ganttEventIds.forEach(id => gantt.detachEvent(id))
|
ganttEventIds.forEach(id => gantt.detachEvent(id))
|
||||||
ganttEventIds = []
|
ganttEventIds = []
|
||||||
|
|
||||||
gantt.clearAll()
|
gantt.clearAll()
|
||||||
|
|
||||||
// 配置甘特图
|
gantt.config.date_format = '%Y-%m-%d %H:%i:%s'
|
||||||
gantt.config.date_format = "%Y-%m-%d %H:%i:%s"
|
gantt.config.scale_height = 60
|
||||||
gantt.config.scale_height = 50
|
gantt.config.row_height = 40
|
||||||
|
gantt.config.bar_height = 26
|
||||||
gantt.config.scales = [
|
gantt.config.scales = [
|
||||||
{ unit: "month", step: 1, format: "%Y年 %m月" },
|
{ unit: 'month', step: 1, format: '%Y年 %m月' },
|
||||||
{ unit: "day", step: 1, format: "%d日" }
|
{ unit: 'day', step: 1, format: formatDayScale },
|
||||||
]
|
]
|
||||||
gantt.config.columns = [
|
gantt.config.columns = [
|
||||||
{ name: "text", label: "单号 / 序号", tree: true, width: 200, resize: true },
|
{
|
||||||
{ name: "materialCode", label: "物料编码", align: "center", width: 140, resize: true },
|
name: 'text',
|
||||||
{ name: "name", label: "名称", align: "center", width: 160, resize: true },
|
label: '工序号 / 物料编码',
|
||||||
{ name: "start_date", label: "计划开始", align: "center", width: 130 },
|
tree: true,
|
||||||
{ name: "end_date", label: "计划结束", align: "center", width: 130 },
|
width: 200,
|
||||||
|
resize: true,
|
||||||
|
template(task: any) {
|
||||||
|
if (task._type === 'route') return task.operNumber ?? ''
|
||||||
|
return task.materialCode ?? ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'routeProcessName',
|
||||||
|
label: '工序名称 / 物料名称',
|
||||||
|
align: 'center',
|
||||||
|
width: 168,
|
||||||
|
resize: true,
|
||||||
|
template(task: any) {
|
||||||
|
if (task._type === 'route') {
|
||||||
|
return task.routeProcessName ?? resolveProcessName(task._raw ?? {}) ?? ''
|
||||||
|
}
|
||||||
|
return task.materialName ?? ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'start_date',
|
||||||
|
label: '计划开始',
|
||||||
|
align: 'center',
|
||||||
|
width: 118,
|
||||||
|
template(task: any) {
|
||||||
|
return formatGridDate(task.start_date)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'end_date',
|
||||||
|
label: '计划结束',
|
||||||
|
align: 'center',
|
||||||
|
width: 118,
|
||||||
|
template(task: any) {
|
||||||
|
return formatGridDate(task.end_date)
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
//??
|
|
||||||
gantt.config.layout = {
|
gantt.config.layout = {
|
||||||
css: "gantt_container",
|
css: 'gantt_container',
|
||||||
cols: [
|
cols: [
|
||||||
{
|
{
|
||||||
width: 300,
|
width: 620,
|
||||||
minWidth: 200,
|
minWidth: 420,
|
||||||
maxWidth: 600,
|
maxWidth: 680,
|
||||||
rows: [
|
rows: [
|
||||||
{ view: "grid", scrollX: "gridScroll", scrollable: true,
|
{ view: 'grid', scrollX: 'gridScroll', scrollable: true, scrollY: 'scrollVer' },
|
||||||
scrollY: "scrollVer"
|
{ view: 'scrollbar', id: 'gridScroll', group: 'horizontal' },
|
||||||
},
|
],
|
||||||
{ view: "scrollbar", id: "gridScroll", group: "horizontal" } /*!*/
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{ resizer: true, width: 1 },
|
{ resizer: true, width: 1 },
|
||||||
{
|
{
|
||||||
rows: [
|
rows: [
|
||||||
{ view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
|
{ view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
|
||||||
{ view: "scrollbar", id: "scrollHor", group: "horizontal" } /*!*/
|
{ view: 'scrollbar', id: 'scrollHor', group: 'horizontal' },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{ view: "scrollbar", id: "scrollVer" }
|
{ view: 'scrollbar', id: 'scrollVer' },
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
//??
|
|
||||||
|
|
||||||
// 行为配置
|
gantt.config.drag_progress = false
|
||||||
gantt.config.drag_progress = false // 禁用进度拖拽
|
gantt.config.drag_links = false
|
||||||
gantt.config.drag_links = false // 禁用连线拖拽(视需求而定)
|
// 使用自然日历(含周六日),不按工作日历自动顺延
|
||||||
gantt.config.work_time = true // 启用工作时间计算
|
gantt.config.work_time = false
|
||||||
// 注意:gantt.config.auto_scheduling 是商业版功能,开源版不能开启,否则会报错
|
gantt.config.correct_work_time = false
|
||||||
|
gantt.config.skip_off_time = false
|
||||||
|
gantt.config.duration_unit = 'day'
|
||||||
|
gantt.config.smart_rendering = true
|
||||||
|
|
||||||
gantt.i18n.setLocale("cn")
|
gantt.i18n.setLocale('cn')
|
||||||
|
gantt.plugins({ marker: true })
|
||||||
|
|
||||||
// 启用 marker 插件(今日时间轴)
|
gantt.templates.task_text = function(_start, _end, task) {
|
||||||
gantt.plugins({
|
|
||||||
marker: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// 图表条块文字自定义
|
|
||||||
gantt.templates.task_text = function(start, end, task) {
|
|
||||||
if (task._type === 'route') {
|
if (task._type === 'route') {
|
||||||
return task.name; // 工序在图上显示工序名称
|
return task.routeProcessName || resolveProcessName(task._raw ?? {}) || ''
|
||||||
|
}
|
||||||
|
return task.materialName || task.materialLabel || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
gantt.templates.task_class = function(_start, _end, task) {
|
||||||
|
if (task._type === 'mo') return 'gantt-task-mo'
|
||||||
|
if (task._type === 'route') {
|
||||||
|
return `gantt-task-route gantt-route-c-${task.colorIndex ?? 0}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
gantt.templates.grid_row_class = function(_start, _end, task) {
|
||||||
|
if (task._type === 'mo') return 'gantt-row-mo'
|
||||||
|
if (task._type === 'route') {
|
||||||
|
return `gantt-row-route gantt-route-c-${task.colorIndex ?? 0}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
gantt.templates.timeline_cell_class = function(_task, date) {
|
||||||
|
const day = date.getDay()
|
||||||
|
if (day === 0 || day === 6) return 'gantt-weekend'
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
return task.text;
|
|
||||||
};
|
|
||||||
|
|
||||||
gantt.init(ganttContainer.value)
|
gantt.init(ganttContainer.value)
|
||||||
|
}
|
||||||
|
|
||||||
// 添加今日时间轴标记
|
/** 今日时间轴标记(数据刷新后需重新添加) */
|
||||||
const today = new Date()
|
function addTodayMarker() {
|
||||||
|
const now = new Date()
|
||||||
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 12, 0, 0)
|
||||||
gantt.addMarker({
|
gantt.addMarker({
|
||||||
start_date: today,
|
start_date: today,
|
||||||
css: "today-marker",
|
css: 'today-marker',
|
||||||
text: "今日",
|
text: '今日',
|
||||||
title: "今日: " + formatGanttDate(today)
|
title: `今日: ${formatGanttDate(now)}`,
|
||||||
})
|
})
|
||||||
|
gantt.showDate(today)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRouteProcessName(route: any) {
|
||||||
|
return resolveProcessName(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderGanttData() {
|
function renderGanttData() {
|
||||||
gantt.clearAll()
|
gantt.clearAll()
|
||||||
// 转换数据格式
|
|
||||||
const tasks: any[] = []
|
const tasks: any[] = []
|
||||||
|
|
||||||
|
|
||||||
const rawData = toRaw(tableData.value) || []
|
const rawData = toRaw(tableData.value) || []
|
||||||
const processColors = ['#3498db', '#1abc9c', '#9b59b6', '#e67e22', '#e74c3c', '#2ecc71', '#f1c40f', '#34495e', '#7f8c8d']
|
|
||||||
|
|
||||||
rawData.forEach((mo: any, moIndex: number) => {
|
rawData.forEach((mo: any, moIndex: number) => {
|
||||||
const moId = `mo_${moIndex}_${mo.productionOrderNo}_${mo.billNo}`
|
const moId = `mo_${moIndex}_${mo.productionOrderNo}_${mo.billNo}`
|
||||||
// 寻找子工序中最早和最晚的时间作为项目时间(或者使用默认时间)
|
|
||||||
let minDate: Date | null = mo.planStartTime ? new Date(mo.planStartTime.replace(' ', 'T')) : null
|
let minDate: Date | null = mo.planStartTime ? new Date(mo.planStartTime.replace(' ', 'T')) : null
|
||||||
let maxDate: Date | null = mo.planFinishTime ? new Date(mo.planFinishTime.replace(' ', 'T')) : null
|
let maxDate: Date | null = mo.planFinishTime ? new Date(mo.planFinishTime.replace(' ', 'T')) : null
|
||||||
if (!minDate || isNaN(minDate.getTime())) minDate = new Date()
|
if (!minDate || isNaN(minDate.getTime())) minDate = new Date()
|
||||||
if (!maxDate || isNaN(maxDate.getTime())) maxDate = new Date(minDate.getTime() + 86400000)
|
if (!maxDate || isNaN(maxDate.getTime())) maxDate = new Date(minDate.getTime() + 86400000)
|
||||||
if (maxDate <= minDate) maxDate = new Date(minDate.getTime() + 86400000)
|
if (maxDate <= minDate) maxDate = new Date(minDate.getTime() + 86400000)
|
||||||
|
|
||||||
|
const materialLabel = formatMoMaterialLabel(mo)
|
||||||
tasks.push({
|
tasks.push({
|
||||||
id: moId,
|
id: moId,
|
||||||
text: mo.materialCode || '', //mo.productionOrderNo || mo.billNo || '未知单号', ??
|
text: materialLabel,
|
||||||
|
materialLabel,
|
||||||
materialCode: mo.materialCode || '',
|
materialCode: mo.materialCode || '',
|
||||||
name: mo.materialName || '产品',
|
materialName: mo.materialName || '',
|
||||||
|
operNumber: '',
|
||||||
|
processName: '',
|
||||||
start_date: formatGanttDate(minDate),
|
start_date: formatGanttDate(minDate),
|
||||||
end_date: formatGanttDate(maxDate),
|
end_date: formatGanttDate(maxDate),
|
||||||
open: false, // 树形默认关闭
|
open: true,
|
||||||
type: gantt.config.types.project,
|
type: gantt.config.types.project,
|
||||||
_type: 'mo',
|
_type: 'mo',
|
||||||
progress:0.6, //??
|
progress: 0,
|
||||||
_raw: mo
|
_raw: mo,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (mo.processRoutes && mo.processRoutes.length > 0) {
|
if (mo.processRoutes?.length > 0) {
|
||||||
mo.processRoutes.forEach((route: any, index: number) => {
|
mo.processRoutes.forEach((route: any, index: number) => {
|
||||||
const routeId = `route_${moId}_${index}`
|
const routeId = `route_${moId}_${index}`
|
||||||
let rStart = route.planStartTime ? new Date(route.planStartTime.replace(' ', 'T')) : null
|
let rStart = route.planStartTime ? new Date(route.planStartTime.replace(' ', 'T')) : null
|
||||||
@ -213,45 +338,47 @@ function renderGanttData() {
|
|||||||
|
|
||||||
if (!rStart || isNaN(rStart.getTime())) rStart = minDate
|
if (!rStart || isNaN(rStart.getTime())) rStart = minDate
|
||||||
if (!rEnd || isNaN(rEnd.getTime())) rEnd = maxDate
|
if (!rEnd || isNaN(rEnd.getTime())) rEnd = maxDate
|
||||||
if (rEnd <= rStart) rEnd = new Date(rStart!.getTime() + 3600000) // 默认工序时长加1小时
|
if (rEnd <= rStart) rEnd = new Date(rStart!.getTime() + 3600000)
|
||||||
|
|
||||||
|
const operNumber = route.operNumber != null ? String(route.operNumber) : String(index + 1)
|
||||||
|
const routeProcessName = getRouteProcessName(route)
|
||||||
|
const colorIndex = getRouteColorIndex(index)
|
||||||
|
const routeColor = getRouteColor(index)
|
||||||
|
|
||||||
tasks.push({
|
tasks.push({
|
||||||
id: routeId,
|
id: routeId,
|
||||||
text:mo.materialCode || '', //route.operNumber != null ? String(route.operNumber) : String(index + 1), ??
|
text: operNumber,
|
||||||
materialCode: '',
|
operNumber,
|
||||||
name: route.processProperty || '工序',
|
routeProcessName,
|
||||||
color: processColors[index % processColors.length],
|
colorIndex,
|
||||||
|
routeColor,
|
||||||
|
color: routeColor,
|
||||||
start_date: formatGanttDate(rStart!),
|
start_date: formatGanttDate(rStart!),
|
||||||
end_date: formatGanttDate(rEnd),
|
end_date: formatGanttDate(rEnd),
|
||||||
parent: moId,
|
parent: moId,
|
||||||
_type: 'route',
|
_type: 'route',
|
||||||
progress:0.6, //??
|
progress: 0,
|
||||||
_raw: route
|
_raw: route,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
gantt.parse({ data: tasks, links: [] })
|
gantt.parse({ data: tasks, links: [] })
|
||||||
|
addTodayMarker()
|
||||||
|
|
||||||
// 限制只能拖拽工序,不能直接拖拽主工单(项目)
|
const evDragId = gantt.attachEvent('onBeforeTaskDrag', (id) => {
|
||||||
const evDragId = gantt.attachEvent("onBeforeTaskDrag", (id, mode, e) => {
|
|
||||||
const task = gantt.getTask(id)
|
const task = gantt.getTask(id)
|
||||||
if (task.type === gantt.config.types.project) {
|
return task.type !== gantt.config.types.project
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
ganttEventIds.push(evDragId)
|
ganttEventIds.push(evDragId)
|
||||||
|
|
||||||
// 绑定拖拽结束事件
|
const evId = gantt.attachEvent('onAfterTaskDrag', (id, mode) => {
|
||||||
const evId = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
|
|
||||||
const task = gantt.getTask(id)
|
const task = gantt.getTask(id)
|
||||||
if (mode === 'move' || mode === 'resize') {
|
if (mode === 'move' || mode === 'resize') {
|
||||||
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
||||||
const end = gantt.date.date_to_str(gantt.config.date_format)(task.end_date)
|
const end = gantt.date.date_to_str(gantt.config.date_format)(task.end_date)
|
||||||
|
|
||||||
// 更新原数据
|
|
||||||
if (task._type === 'mo') {
|
if (task._type === 'mo') {
|
||||||
task._raw.planStartTime = start
|
task._raw.planStartTime = start
|
||||||
task._raw.planFinishTime = end
|
task._raw.planFinishTime = end
|
||||||
@ -259,33 +386,35 @@ function renderGanttData() {
|
|||||||
task._raw.planStartTime = start
|
task._raw.planStartTime = start
|
||||||
task._raw.planFinishTime = end
|
task._raw.planFinishTime = end
|
||||||
|
|
||||||
// 自动汇总更新父级工单的时间
|
|
||||||
const parentId = task.parent
|
const parentId = task.parent
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
const parentTask = gantt.getTask(parentId)
|
const parentTask = gantt.getTask(parentId)
|
||||||
if (parentTask) {
|
|
||||||
const children = gantt.getChildren(parentId)
|
const children = gantt.getChildren(parentId)
|
||||||
let minDate: Date | null = null
|
let minD: Date | null = null
|
||||||
let maxDate: Date | null = null
|
let maxD: Date | null = null
|
||||||
|
|
||||||
children.forEach((childId: string | number) => {
|
children.forEach((childId: string | number) => {
|
||||||
const childTask = gantt.getTask(childId)
|
const childTask = gantt.getTask(childId)
|
||||||
if (!minDate || childTask.start_date < minDate) minDate = childTask.start_date
|
const cs = childTask.start_date
|
||||||
if (!maxDate || childTask.end_date > maxDate) maxDate = childTask.end_date
|
const ce = childTask.end_date
|
||||||
|
if (cs && (!minD || cs < minD)) minD = cs
|
||||||
|
if (ce && (!maxD || ce > maxD)) maxD = ce
|
||||||
})
|
})
|
||||||
|
|
||||||
if (minDate && maxDate) {
|
if (minD && maxD) {
|
||||||
parentTask.start_date = new Date(minDate)
|
parentTask.start_date = new Date(minD)
|
||||||
parentTask.end_date = new Date(maxDate)
|
parentTask.end_date = new Date(maxD)
|
||||||
parentTask._raw.planStartTime = gantt.date.date_to_str(gantt.config.date_format)(minDate)
|
parentTask._raw.planStartTime = gantt.date.date_to_str(gantt.config.date_format)(minD)
|
||||||
parentTask._raw.planFinishTime = gantt.date.date_to_str(gantt.config.date_format)(maxDate)
|
parentTask._raw.planFinishTime = gantt.date.date_to_str(gantt.config.date_format)(maxD)
|
||||||
gantt.updateTask(parentId)
|
gantt.updateTask(parentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
message.success(`任务 [${task.text}] 计划时间已更新`)
|
const label = task._type === 'route'
|
||||||
|
? (task.routeProcessName || task.operNumber)
|
||||||
|
: (task.materialLabel || task.materialName)
|
||||||
|
message.success(`任务 [${label}] 计划时间已更新`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ganttEventIds.push(evId)
|
ganttEventIds.push(evId)
|
||||||
@ -299,36 +428,249 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.gantt-schedule-page {
|
.gantt-schedule-page {
|
||||||
|
--gs-font: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', sans-serif;
|
||||||
|
--gs-title: #333333;
|
||||||
|
--gs-text: #606266;
|
||||||
|
--gs-border: #e4e7ed;
|
||||||
|
--gs-bg: #f5f7fa;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100vh - 120px);
|
height: calc(100vh - 120px);
|
||||||
background-color: var(--n-color);
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 8px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-family: var(--gs-font);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
background: var(--gs-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gantt-header {
|
.gantt-header {
|
||||||
margin-bottom: 16px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--gs-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-header-left {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gs-title);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-meta {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-divider {
|
||||||
|
margin: 0 10px;
|
||||||
|
color: #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-legend {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-today-line {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
background: #f56c6c;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--gs-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-bottom: 1px solid var(--gs-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gs-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gantt-container {
|
.gantt-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0; /* 必须设置min-height以允许flex子元素内部滚动 */
|
min-height: 0;
|
||||||
border: 1px solid var(--n-border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.today-marker) {
|
/* dhtmlx-gantt:仅表头标题加粗,其余正常字重 */
|
||||||
background-color: #ff5252;
|
.gantt-container :deep(.gantt_container) {
|
||||||
|
font-family: var(--gs-font);
|
||||||
|
font-weight: 400;
|
||||||
|
border: none !important;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
:deep(.today-marker .gantt_marker_content) {
|
|
||||||
background-color: #ff5252;
|
.gantt-container :deep(.gantt_grid_scale),
|
||||||
color: white;
|
.gantt-container :deep(.gantt_task_scale) {
|
||||||
|
background: #fafafa;
|
||||||
|
border-bottom: 1px solid var(--gs-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_scale .gantt_scale_cell) {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
border-color: var(--gs-border) !important;
|
||||||
|
line-height: 1.3;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_grid_scale .gantt_grid_head_cell) {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gs-title);
|
||||||
|
border-color: var(--gs-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工序号文字与进度条同色(无圆点) */
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-0 .gantt_cell:first-child) { color: #409eff; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-1 .gantt_cell:first-child) { color: #67c23a; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-2 .gantt_cell:first-child) { color: #e6a23c; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-3 .gantt_cell:first-child) { color: #909399; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-4 .gantt_cell:first-child) { color: #f56c6c; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-5 .gantt_cell:first-child) { color: #36cfc9; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-6 .gantt_cell:first-child) { color: #597ef7; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-7 .gantt_cell:first-child) { color: #9254de; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-8 .gantt_cell:first-child) { color: #73d13d; }
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-0) { background-color: #409eff !important; border-color: #409eff !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-1) { background-color: #67c23a !important; border-color: #67c23a !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-2) { background-color: #e6a23c !important; border-color: #e6a23c !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-3) { background-color: #909399 !important; border-color: #909399 !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-4) { background-color: #f56c6c !important; border-color: #f56c6c !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-5) { background-color: #36cfc9 !important; border-color: #36cfc9 !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-6) { background-color: #597ef7 !important; border-color: #597ef7 !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-7) { background-color: #9254de !important; border-color: #9254de !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-8) { background-color: #73d13d !important; border-color: #73d13d !important; }
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-task-route .gantt_task_progress) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.18) !important;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-task-route) {
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_grid_data .gantt_cell) {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
border-color: #f0f2f5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-mo) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-mo .gantt_cell) {
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_row:hover) {
|
||||||
|
background: #f5f7fa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_row.gantt_selected),
|
||||||
|
.gantt-container :deep(.gantt_row.gantt_selected) {
|
||||||
|
background: #ecf5ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-task-route .gantt_task_content) {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt_project) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_weekend) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_marker.today-marker) {
|
||||||
|
background: #f56c6c;
|
||||||
|
width: 2px;
|
||||||
|
z-index: 4;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.today-marker .gantt_marker_content) {
|
||||||
|
background: #f56c6c;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 0 4px;
|
padding: 2px 8px;
|
||||||
|
box-shadow: 0 1px 4px rgba(245, 108, 108, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_layout_content) {
|
||||||
|
border-color: var(--gs-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_hor_scroll),
|
||||||
|
.gantt-container :deep(.gantt_ver_scroll) {
|
||||||
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -148,48 +148,13 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
:close-on-esc="false"
|
:close-on-esc="false"
|
||||||
>
|
>
|
||||||
<div class="kingdee-toolbar">
|
<KingdeeVue
|
||||||
<n-space>
|
:kingdeeTableData="kingdeeTableData"
|
||||||
<n-button
|
:selectedId="kingdeeCurrentProjectId"
|
||||||
size="small"
|
@close-modal="kingdeeModalVisible = false"
|
||||||
:disabled="kingdeeLoading || !kingdeeTableData.length"
|
@holdTemporarilyCallback="handleSaveDraft"
|
||||||
@click="kingdeeToggleExpandAll"
|
@synchronizationCallback="handleSyncOrderAndPlan"
|
||||||
>
|
@reCrawlCallback="reCrawlReorderForm"/>
|
||||||
{{ kingdeeAllExpanded ? '全部收起' : '全部展开' }}
|
|
||||||
</n-button>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
:disabled="kingdeeLoading || !kingdeeTableData.length"
|
|
||||||
@click="handleGanttSchedule"
|
|
||||||
>
|
|
||||||
<template #icon><n-icon><CalendarOutline /></n-icon></template>
|
|
||||||
甘特图排产
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</div>
|
|
||||||
<n-data-table
|
|
||||||
v-model:expanded-row-keys="kingdeeExpandedKeys"
|
|
||||||
:columns="kingdeeMoColumns"
|
|
||||||
:data="kingdeeTableData"
|
|
||||||
:loading="kingdeeLoading"
|
|
||||||
:row-key="kingdeeMoRowKey"
|
|
||||||
:scroll-x="kingdeeMoScrollX"
|
|
||||||
:max-height="kingdeeTableMaxHeight"
|
|
||||||
:row-class-name="kingdeeMoRowClassName"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<template #footer>
|
|
||||||
<n-space justify="end">
|
|
||||||
<n-button type="info" :disabled="kingdeeLoading" @click="handleSyncOrderAndPlan">
|
|
||||||
同步订单计划
|
|
||||||
</n-button>
|
|
||||||
<n-button type="primary" :disabled="kingdeeLoading" @click="handleSaveDraft">
|
|
||||||
保存草稿
|
|
||||||
</n-button>
|
|
||||||
<n-button @click="kingdeeModalVisible = false">关闭</n-button>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
<!-- 导入弹窗 -->
|
<!-- 导入弹窗 -->
|
||||||
@ -229,12 +194,15 @@
|
|||||||
import { ref, reactive, h, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, h, computed, onMounted, watch } from 'vue'
|
||||||
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
||||||
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute, normalizeKingdeeProcessRoute } from '@/api/orderProject'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import KingdeeVue from '@/views/biz/kingdee/index.vue'
|
||||||
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@ -451,7 +419,7 @@ const columns: DataTableColumns<OrderProject> = [
|
|||||||
const buttons = []
|
const buttons = []
|
||||||
if (hasPermission('biz:orderProject:getKingdeeOrder')) {
|
if (hasPermission('biz:orderProject:getKingdeeOrder')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(NButton, { size: 'small', quaternary: true, type: 'info', onClick: () => handleKingdeeQuery(row) }, {
|
h(NButton, { size: 'small', quaternary: true, type: 'info', onClick: () => handleKingdeeQuery(row, false) }, {
|
||||||
default: () => [h(NIcon, null, { default: () => h(EyeOutline) }), ' 预生产查询']
|
default: () => [h(NIcon, null, { default: () => h(EyeOutline) }), ' 预生产查询']
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -534,7 +502,7 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
|||||||
return row.operNumber != null ? String(row.operNumber) : '-'
|
return row.operNumber != null ? String(row.operNumber) : '-'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '工序名称', key: 'processProperty', width: 100, align: 'center' },
|
{ title: '工序名称', key: 'processName', width: 100, align: 'center' },
|
||||||
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
|
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
|
||||||
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
@ -557,7 +525,7 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
|
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
|
||||||
{ title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '工序控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
@ -691,23 +659,14 @@ function handleGanttSchedule() {
|
|||||||
router.push('/biz/orderProject/gantt')
|
router.push('/biz/orderProject/gantt')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 金蝶预生产查询
|
async function reCrawlReorderForm(id:number,reCrawl:Boolean){
|
||||||
async function handleKingdeeQuery(row: OrderProject) {
|
|
||||||
if (!row.id) {
|
|
||||||
message.warning('缺少项目ID,无法查询')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kingdeeCurrentProjectId.value = row.id
|
|
||||||
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
|
||||||
kingdeeModalVisible.value = true
|
|
||||||
kingdeeLoading.value = true
|
|
||||||
kingdeeTableData.value = []
|
|
||||||
kingdeeExpandedKeys.value = []
|
|
||||||
try {
|
try {
|
||||||
const data = await orderProjectApi.getKingdeeOrder(row.id)
|
const data = await orderProjectApi.getKingdeeOrder(id,{reCrawl:reCrawl})
|
||||||
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
||||||
const { children, ...rest } = mo
|
const { children, ...rest } = mo
|
||||||
const routes = [...(children ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0)).map((r) => ({
|
const routes = [...(children ?? [])]
|
||||||
|
.sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||||
|
.map((r) => normalizeKingdeeProcessRoute({
|
||||||
...r,
|
...r,
|
||||||
planStartTime: r.planStartTime ?? null,
|
planStartTime: r.planStartTime ?? null,
|
||||||
planFinishTime: r.planFinishTime ?? null,
|
planFinishTime: r.planFinishTime ?? null,
|
||||||
@ -721,50 +680,115 @@ async function handleKingdeeQuery(row: OrderProject) {
|
|||||||
kingdeeLoading.value = false
|
kingdeeLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 金蝶预生产查询
|
||||||
|
async function handleKingdeeQuery(row: OrderProject,reCrawl:Boolean) {
|
||||||
|
if (!row.id) {
|
||||||
|
message.warning('缺少项目ID,无法查询')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kingdeeCurrentProjectId.value = row.id
|
||||||
|
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
||||||
|
kingdeeModalVisible.value = true
|
||||||
|
kingdeeLoading.value = true
|
||||||
|
kingdeeTableData.value = []
|
||||||
|
kingdeeExpandedKeys.value = []
|
||||||
|
reCrawlReorderForm(row.id,reCrawl)
|
||||||
|
// try {
|
||||||
|
// const data = await orderProjectApi.getKingdeeOrder(row.id)
|
||||||
|
// kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
||||||
|
// const { children, ...rest } = mo
|
||||||
|
// const routes = [...(children ?? [])]
|
||||||
|
// .sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||||
|
// .map((r) => normalizeKingdeeProcessRoute({
|
||||||
|
// ...r,
|
||||||
|
// planStartTime: r.planStartTime ?? null,
|
||||||
|
// planFinishTime: r.planFinishTime ?? null,
|
||||||
|
// }))
|
||||||
|
// return { ...rest, processRoutes: routes }
|
||||||
|
// })
|
||||||
|
// if (kingdeeTableData.value.length === 0) {
|
||||||
|
// message.info('未查询到金蝶生产订单数据')
|
||||||
|
// }
|
||||||
|
// } finally {
|
||||||
|
// kingdeeLoading.value = false
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 暂存金蝶生产订单数据(草稿)
|
// 暂存金蝶生产订单数据(草稿)
|
||||||
async function handleSaveDraft() {
|
// async function holdTemporarilyCallback(id: number, data: KingdeePrdMo[]) {
|
||||||
if (!kingdeeCurrentProjectId.value) return
|
// await orderProjectApi.saveKingdeeOrderDraft(id, data)
|
||||||
|
// }
|
||||||
|
|
||||||
kingdeeLoading.value = true
|
// 暂存金蝶生产订单数据(草稿)
|
||||||
|
async function handleSaveDraft(id: number, data: KingdeePrdMo[]) {
|
||||||
try {
|
try {
|
||||||
// 恢复数据结构,将 processRoutes 转换为 children
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
const draftData = kingdeeTableData.value.map(mo => {
|
await orderProjectApi.saveKingdeeOrderDraft(id, data)
|
||||||
const { processRoutes, ...rest } = mo
|
|
||||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
|
||||||
})
|
|
||||||
|
|
||||||
await orderProjectApi.saveKingdeeOrderDraft(kingdeeCurrentProjectId.value, draftData)
|
|
||||||
message.success('草稿保存成功')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 错误拦截器已处理
|
// 错误拦截器已处理
|
||||||
} finally {
|
|
||||||
kingdeeLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同步订单和计划表
|
// 同步订单和计划表
|
||||||
async function handleSyncOrderAndPlan() {
|
async function handleSyncOrderAndPlan(id: number, data: KingdeePrdMo[], callback:Function) {
|
||||||
if (!kingdeeCurrentProjectId.value) return
|
|
||||||
|
|
||||||
kingdeeLoading.value = true
|
|
||||||
try {
|
try {
|
||||||
// 恢复数据结构,将 processRoutes 转换为 children
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
const draftData = kingdeeTableData.value.map(mo => {
|
await orderProjectApi.saveKingdeeOrderAndPlan(id, data)
|
||||||
const { processRoutes, ...rest } = mo
|
.then((rps:any) =>{
|
||||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
callback(rps)
|
||||||
|
}).catch((res) => {
|
||||||
|
callback(res)
|
||||||
})
|
})
|
||||||
|
|
||||||
await orderProjectApi.saveKingdeeOrderAndPlan(kingdeeCurrentProjectId.value, draftData)
|
|
||||||
message.success('同步订单计划成功')
|
|
||||||
kingdeeModalVisible.value = false
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 错误拦截器已处理
|
// 错误拦截器已处理
|
||||||
} finally {
|
|
||||||
kingdeeLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // 暂存金蝶生产订单数据(草稿)
|
||||||
|
// async function handleSaveDraft() {
|
||||||
|
// if (!kingdeeCurrentProjectId.value) return
|
||||||
|
|
||||||
|
// kingdeeLoading.value = true
|
||||||
|
// try {
|
||||||
|
// // 恢复数据结构,将 processRoutes 转换为 children
|
||||||
|
// const draftData = kingdeeTableData.value.map(mo => {
|
||||||
|
// const { processRoutes, ...rest } = mo
|
||||||
|
// return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
|
// })
|
||||||
|
|
||||||
|
// await orderProjectApi.saveKingdeeOrderDraft(kingdeeCurrentProjectId.value, draftData)
|
||||||
|
// message.success('草稿保存成功')
|
||||||
|
// } catch (error) {
|
||||||
|
// // 错误拦截器已处理
|
||||||
|
// } finally {
|
||||||
|
// kingdeeLoading.value = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 同步订单和计划表
|
||||||
|
// async function handleSyncOrderAndPlan() {
|
||||||
|
// if (!kingdeeCurrentProjectId.value) return
|
||||||
|
|
||||||
|
// kingdeeLoading.value = true
|
||||||
|
// try {
|
||||||
|
// // 恢复数据结构,将 processRoutes 转换为 children
|
||||||
|
// const draftData = kingdeeTableData.value.map(mo => {
|
||||||
|
// const { processRoutes, ...rest } = mo
|
||||||
|
// return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
|
// })
|
||||||
|
|
||||||
|
// await orderProjectApi.saveKingdeeOrderAndPlan(kingdeeCurrentProjectId.value, draftData)
|
||||||
|
// message.success('同步订单计划成功')
|
||||||
|
// kingdeeModalVisible.value = false
|
||||||
|
// } catch (error) {
|
||||||
|
// // 错误拦截器已处理
|
||||||
|
// } finally {
|
||||||
|
// kingdeeLoading.value = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
function handleDelete(row: OrderProject) {
|
function handleDelete(row: OrderProject) {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
|
|||||||
@ -180,8 +180,16 @@
|
|||||||
|
|
||||||
<!--质检单详情面板-->
|
<!--质检单详情面板-->
|
||||||
<n-modal v-model:show="detailVisible" preset="card" :title="detailTitle" style="width: 800px">
|
<n-modal v-model:show="detailVisible" preset="card" :title="detailTitle" style="width: 800px">
|
||||||
<n-card title="主表信息" hoverable style="margin-bottom: 12px" >
|
<n-card hoverable style="margin-bottom: 12px" >
|
||||||
<n-descriptions :column="2" bordered>
|
<template #header>
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;width:100%">
|
||||||
|
<span>主表信息</span>
|
||||||
|
<n-button text @click="infoIsCollapse = !infoIsCollapse">
|
||||||
|
<n-icon :component="infoIsCollapse ? ArrowDownOutline : ArrowUpOutline" />
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<n-descriptions :column="3" bordered v-show="!infoIsCollapse">
|
||||||
<n-descriptions-item
|
<n-descriptions-item
|
||||||
v-for="item in mappedInfoList"
|
v-for="item in mappedInfoList"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
@ -191,13 +199,24 @@
|
|||||||
</n-descriptions-item>
|
</n-descriptions-item>
|
||||||
</n-descriptions>
|
</n-descriptions>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card title="明细列表" hoverable style="margin-bottom: 12px">
|
<n-card hoverable style="margin-bottom: 12px">
|
||||||
|
<template #header>
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;width:100%">
|
||||||
|
<span>明细列表</span>
|
||||||
|
<n-button text @click="detailIsCollapse = !detailIsCollapse">
|
||||||
|
<n-icon :component="detailIsCollapse ? ArrowDownOutline : ArrowUpOutline" />
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<n-data-table
|
<n-data-table
|
||||||
|
v-show="!detailIsCollapse"
|
||||||
:columns="detailColumns"
|
:columns="detailColumns"
|
||||||
:data="detailTableData"
|
:data="detailTableData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="(row) => row.id"
|
:row-key="(row) => row.id"
|
||||||
:scroll-x="1200"
|
:scroll-x="700"
|
||||||
|
max-height="320"
|
||||||
|
:pagination="{ pageSize:4 }"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
<!-- <n-card title="追溯码" hoverable>-->
|
<!-- <n-card title="追溯码" hoverable>-->
|
||||||
@ -205,110 +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>
|
|
||||||
</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>
|
||||||
@ -317,12 +233,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
import {
|
import {
|
||||||
NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions,
|
NButton, NSpace, NIcon, NUpload,NTag, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions,
|
||||||
NDropdown
|
NDropdown
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline,
|
SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline,
|
||||||
EllipsisHorizontalOutline
|
EllipsisHorizontalOutline, ArrowDownOutline, ArrowUpOutline,ArchiveOutline as ArchiveIcon
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
||||||
import {assingReworkApi} from '@/api/assingRework'
|
import {assingReworkApi} from '@/api/assingRework'
|
||||||
@ -330,9 +247,13 @@ import { dictDataApi } from '@/api/org'
|
|||||||
|
|
||||||
import {Language} from "@xterm/xterm/src/vs/base/common/platform.ts";
|
import {Language} from "@xterm/xterm/src/vs/base/common/platform.ts";
|
||||||
|
|
||||||
|
import {fileApi} from '@/api/system1'
|
||||||
|
|
||||||
import value = Language.value;
|
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 { env } from 'echarts'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@ -365,24 +286,53 @@ const importModalVisible = ref(false)
|
|||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const detailTitle = ref('')
|
const detailTitle = ref('')
|
||||||
|
|
||||||
|
const infoIsCollapse = ref(false)
|
||||||
|
const detailIsCollapse = ref(true)
|
||||||
|
|
||||||
//提交判定明细
|
//提交判定明细
|
||||||
const submitDetailModalVisible = ref(false)
|
const submitDetailModalVisible = ref(false)
|
||||||
const traceCodeShow = ref(false)
|
const traceCodeShow = ref(false)
|
||||||
|
//提交明细/部分提交明细标识
|
||||||
|
const submitFlag = ref<Boolean>()
|
||||||
|
|
||||||
|
|
||||||
|
const customOrderKeys = [
|
||||||
|
'materialCode',
|
||||||
|
'materialName',
|
||||||
|
'materialQty',
|
||||||
|
'processCode',
|
||||||
|
'processName',
|
||||||
|
'processQty',
|
||||||
|
'assingCode',
|
||||||
|
'workerName',
|
||||||
|
'assingQty',
|
||||||
|
'qcNo',
|
||||||
|
'sourceType',
|
||||||
|
'totalQty',
|
||||||
|
'status',
|
||||||
|
'inspector',
|
||||||
|
'inspectTime',
|
||||||
|
'traceType'
|
||||||
|
]
|
||||||
|
|
||||||
const infoDataMapping = {
|
const infoDataMapping = {
|
||||||
id: '质检单编号',
|
|
||||||
|
materialCode: '物料编码',
|
||||||
|
materialName: '物料名称',
|
||||||
|
materialQty: '物料数量',
|
||||||
|
processCode:'工序编号',
|
||||||
|
processName:'工序名称',
|
||||||
|
processQty:'生产总数',
|
||||||
|
assingCode:'派工编号',
|
||||||
|
workerName:'派工人',
|
||||||
|
assingQty:'派工数量',
|
||||||
qcNo: '质检单编号',
|
qcNo: '质检单编号',
|
||||||
sourceType: '来源类型',
|
sourceType: '来源类型',
|
||||||
sourceId: '来源编号',
|
|
||||||
orderId: '订单编号',
|
|
||||||
processId: '工序编号',
|
|
||||||
itemId: '物料编号',
|
|
||||||
totalQty: '报检总数',
|
totalQty: '报检总数',
|
||||||
status: '质检状态',
|
status: '质检状态',
|
||||||
inspectorId: '质检员编号',
|
inspector: '质检员',
|
||||||
inspectTime: '质检时间',
|
inspectTime: '质检时间',
|
||||||
traceType: '追溯类型',
|
traceType: '追溯类型'
|
||||||
}
|
}
|
||||||
const infoDataMap = new Map(Object.entries(infoDataMapping))
|
const infoDataMap = new Map(Object.entries(infoDataMapping))
|
||||||
const mappedInfoList = ref<Array<{ label: string; key: string; value: any }>>([])
|
const mappedInfoList = ref<Array<{ label: string; key: string; value: any }>>([])
|
||||||
@ -391,22 +341,27 @@ const detailColumns: DataTableColumns<Object> = [
|
|||||||
{
|
{
|
||||||
title: '判定类型',
|
title: '判定类型',
|
||||||
key: 'resultType',
|
key: 'resultType',
|
||||||
|
align: 'center',
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.resultType
|
const val = row.resultType
|
||||||
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '判定数量',
|
title: '判定数量',
|
||||||
key: 'qty',
|
key: 'qty',
|
||||||
|
align: 'center',
|
||||||
width: 80
|
width: 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '批次号',
|
title: '批次号',
|
||||||
key: 'traceCode',
|
key: 'traceCode',
|
||||||
width: 80,
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.traceCode
|
const val = row.traceCode
|
||||||
return val ?? '-'
|
return val ?? '-'
|
||||||
@ -416,6 +371,7 @@ const detailColumns: DataTableColumns<Object> = [
|
|||||||
title: '不良代码',
|
title: '不良代码',
|
||||||
key: 'defectCode',
|
key: 'defectCode',
|
||||||
width: 80,
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.defectCode
|
const val = row.defectCode
|
||||||
return val ?? '-'
|
return val ?? '-'
|
||||||
@ -425,16 +381,19 @@ const detailColumns: DataTableColumns<Object> = [
|
|||||||
title: '后续动作',
|
title: '后续动作',
|
||||||
key: 'handleAction',
|
key: 'handleAction',
|
||||||
width: 80,
|
width: 80,
|
||||||
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '动作执行状态',
|
title: '动作执行状态',
|
||||||
key: 'actionStatus',
|
key: 'actionStatus',
|
||||||
width: 80,
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.actionStatus
|
const val = row.actionStatus
|
||||||
const opt = qcActionStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcActionStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -467,6 +426,8 @@ const defaultFormDetailData:QcResultDetail = {
|
|||||||
handleAction: undefined,
|
handleAction: undefined,
|
||||||
actionStatus: undefined
|
actionStatus: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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 })
|
||||||
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||||
@ -497,37 +458,43 @@ const formRules = {
|
|||||||
// 表格列
|
// 表格列
|
||||||
const columns: DataTableColumns<QualityTesting> = [
|
const columns: DataTableColumns<QualityTesting> = [
|
||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ title: '质检编号', key: 'qcNo' },
|
{ title: '质检编号', key: 'qcNo',align: 'center' },
|
||||||
{ title: '来源类型', key: 'sourceType',
|
{ title: '来源类型', key: 'sourceType',align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
const val = row.sourceType
|
const val = row.sourceType
|
||||||
const opt = qcSourceTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcSourceTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// { title: '来源业务ID(汇报单/派工单等)', key: 'sourceId' },
|
// { title: '来源业务ID(汇报单/派工单等)', key: 'sourceId' },
|
||||||
// { title: '关联生产订单ID', key: 'orderId' },
|
// { title: '关联生产订单ID', key: 'orderId' },
|
||||||
// { title: '关联工序计划ID', key: 'processId' },
|
// { title: '关联工序计划ID', key: 'processId' },
|
||||||
// { title: '物料ID', key: 'itemId' },
|
// { title: '物料ID', key: 'itemId' },
|
||||||
{ title: '报检总数', key: 'totalQty' },
|
{ title: '报检总数', key: 'totalQty',align: 'center' },
|
||||||
{ title: '质检状态', key: 'status',
|
{ title: '已检验数量', key: 'inspectQty',align: 'center' },
|
||||||
|
{ title: '质检状态', key: 'status',align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
const val = row.status
|
const val = row.status
|
||||||
const opt = qcStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '检验员', key: 'inspector' },
|
{ title: '检验员', key: 'inspector',align: 'center' },
|
||||||
{ title: '检验完成时间', key: 'inspectTime' },
|
{ title: '检验完成时间', key: 'inspectTime',align: 'center' },
|
||||||
{ title: '追溯类型', key: 'traceType',
|
{ title: '追溯类型', key: 'traceType',align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
const val = row.traceType
|
const val = row.traceType
|
||||||
const opt = qcTraceTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcTraceTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '创建时间', key: 'createTime', width: 180 },
|
{ title: '创建时间', key: 'createTime',align: 'center', width: 180 },
|
||||||
{ title: '更新时间', key: 'updateTime', width: 180 },
|
{ title: '更新时间', key: 'updateTime',align: 'center', width: 180 },
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
@ -546,8 +513,10 @@ const columns: DataTableColumns<QualityTesting> = [
|
|||||||
options: [
|
options: [
|
||||||
{ label: '查看详情', key: 'view' },
|
{ label: '查看详情', key: 'view' },
|
||||||
{ type: 'divider' }, // 分割线
|
{ type: 'divider' }, // 分割线
|
||||||
{ label: '提交判定明细', key: 'submitResultDetail' },
|
{ label: '提交判定明细', key: 'submitResultDetail',show:row.status == 1 },
|
||||||
{ type: 'divider' }, // 分割线
|
{ type: 'divider',show:row.status == 1 }, // 分割线
|
||||||
|
{ label: '提交部分判定明细', key: 'submitBFResultDetail',show:(row.status == 1 || row.status == 2) },
|
||||||
|
{ type: 'divider',show:(row.status == 1 || row.status == 2) }, // 分割线
|
||||||
{ label: '查询判定明细', key: 'queryResultDetail' },
|
{ label: '查询判定明细', key: 'queryResultDetail' },
|
||||||
{ type: 'divider' }, // 分割线
|
{ type: 'divider' }, // 分割线
|
||||||
{ label: '质检撤回', key: 'cancelQuality' },
|
{ label: '质检撤回', key: 'cancelQuality' },
|
||||||
@ -561,6 +530,9 @@ const columns: DataTableColumns<QualityTesting> = [
|
|||||||
case 'submitResultDetail':
|
case 'submitResultDetail':
|
||||||
submitResultDetail(row)
|
submitResultDetail(row)
|
||||||
break
|
break
|
||||||
|
case 'submitBFResultDetail':
|
||||||
|
submitBFResultDetail(row)
|
||||||
|
break
|
||||||
case 'queryResultDetail':
|
case 'queryResultDetail':
|
||||||
queryResultDetail(row)
|
queryResultDetail(row)
|
||||||
break
|
break
|
||||||
@ -781,28 +753,28 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
|||||||
async function loadDictOptions() {
|
async function loadDictOptions() {
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('sys_status')
|
const data = await dictDataApi.listByType('sys_status')
|
||||||
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_status')
|
const data = await dictDataApi.listByType('qc_status')
|
||||||
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('source_type')
|
const data = await dictDataApi.listByType('source_type')
|
||||||
qcSourceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcSourceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_trace_type')
|
const data = await dictDataApi.listByType('qc_trace_type')
|
||||||
qcTraceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcTraceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_result_type')
|
const data = await dictDataApi.listByType('qc_result_type')
|
||||||
qcResultTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcResultTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_action_status')
|
const data = await dictDataApi.listByType('qc_action_status')
|
||||||
qcActionStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcActionStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,18 +784,28 @@ async function handleDetail(row: QualityTesting) {
|
|||||||
detailVisible.value = true
|
detailVisible.value = true
|
||||||
const data = await qualityTestingApi.detail(row.id!)
|
const data = await qualityTestingApi.detail(row.id!)
|
||||||
|
|
||||||
// 映射后的数据
|
// // 映射后的数据
|
||||||
mappedInfoList.value = Object.entries(data.info)
|
// mappedInfoList.value = Object.entries(data.info)
|
||||||
.filter(([key]) => infoDataMap.has(key)) // 只保留配置里有的字段
|
// .filter(([key]) => infoDataMap.has(key)) // 只保留配置里有的字段
|
||||||
.map(([key, value]) => ({
|
// .map(([key, value]) => ({
|
||||||
label: infoDataMap.get(key) || key, // 中文标签
|
// label: infoDataMap.get(key) || key, // 中文标签
|
||||||
key, // 原字段名
|
// key, // 原字段名
|
||||||
value // 原始值
|
// value // 原始值
|
||||||
}))
|
// }))
|
||||||
|
setMappedInfoList(data.info)
|
||||||
|
|
||||||
detailTableData.value = data.detail
|
detailTableData.value = data.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 组装列表时按上面自定义顺序循环
|
||||||
|
function setMappedInfoList(rawData: Record<string, any>) {
|
||||||
|
mappedInfoList.value = customOrderKeys.map(key => ({
|
||||||
|
key,
|
||||||
|
label: infoDataMapping[key as keyof typeof infoDataMapping],
|
||||||
|
value: rawData[key]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function formatDisplayValue(key: string, value: any): string {
|
function formatDisplayValue(key: string, value: any): string {
|
||||||
if (value === null || value === undefined) return '-'
|
if (value === null || value === undefined) return '-'
|
||||||
|
|
||||||
@ -860,34 +842,56 @@ async function loadAssignWorkOrder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//提交判定明细
|
//提交判定明细
|
||||||
async 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
|
||||||
}
|
}
|
||||||
submitDetailModalVisible.value = true
|
submitFlag.value = true
|
||||||
formDetailOKData.qcId = row.id
|
|
||||||
formDetailScrapData.qcId = row.id
|
|
||||||
formDetailReworkData.qcId = row.id
|
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
||||||
formDetailConcessionData.qcId = row.id
|
router.push(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function handleSubmitResultDetail() {
|
async function handleSubmitResultDetail() {
|
||||||
try {
|
|
||||||
const submitParam = [formDetailOKData,formDetailScrapData,formDetailReworkData,formDetailConcessionData]
|
formDetailConcessionData.files = uploadFiles.value
|
||||||
await qualityTestingApi.submit(submitParam)
|
|
||||||
|
const submitParam = [
|
||||||
|
formDetailOKData,
|
||||||
|
formDetailScrapData,
|
||||||
|
formDetailReworkData,
|
||||||
|
formDetailConcessionData]
|
||||||
|
console.log(submitParam);
|
||||||
|
|
||||||
|
submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam)
|
||||||
|
|
||||||
message.success('提交成功')
|
message.success('提交成功')
|
||||||
}catch ( error) {
|
|
||||||
message.error('提交失败'+error)
|
|
||||||
}
|
|
||||||
submitDetailModalVisible.value = false
|
submitDetailModalVisible.value = false
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//提交部分判定明细
|
||||||
|
function submitBFResultDetail(row) {
|
||||||
|
|
||||||
|
|
||||||
|
if (row.traceType != 1) {
|
||||||
|
traceCodeShow.value = true
|
||||||
|
}
|
||||||
|
submitFlag.value = false
|
||||||
|
|
||||||
|
|
||||||
|
//version-2
|
||||||
|
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
||||||
|
router.push(path)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//查询判定明细
|
//查询判定明细
|
||||||
function queryResultDetail(row) {
|
function queryResultDetail(row) {
|
||||||
let path = `detail/${row.id}`;
|
let path = `detail/${row.id}`;
|
||||||
@ -913,6 +917,9 @@ function handleCancelQuality(row) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData()
|
loadData()
|
||||||
loadDictOptions()
|
loadDictOptions()
|
||||||
|
|||||||
378
src/views/biz/qualityTesting/submitDetail.vue
Normal file
378
src/views/biz/qualityTesting/submitDetail.vue
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<n-card :title="title" style="margin-bottom: 16px">
|
||||||
|
<n-tabs type="card" animated>
|
||||||
|
<n-tab-pane name="合格" tab="合格">
|
||||||
|
<n-form ref="formRef" :model="formDetailOKData" 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-grid x-gap="8" :cols="3">
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="备注" >
|
||||||
|
<n-input
|
||||||
|
v-model:value="formDetailOKData.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入备注原因"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="报废" tab="报废">
|
||||||
|
<n-form ref="formRef" :model="formDetailScrapData" 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-grid x-gap="8" :cols="3">
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="备注" >
|
||||||
|
<n-input
|
||||||
|
v-model:value="formDetailScrapData.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入备注原因"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="返工" tab="返工">
|
||||||
|
<n-form ref="formRef" :model="formDetailReworkData" 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-grid x-gap="8" :cols="3">
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="备注" >
|
||||||
|
<n-input
|
||||||
|
v-model:value="formDetailReworkData.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入备注原因"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="让步接收" tab="让步接收">
|
||||||
|
<n-form ref="formRef" :model="formDetailConcessionData" 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-grid x-gap="8" :cols="3">
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="备注" >
|
||||||
|
<n-input
|
||||||
|
v-model:value="formDetailConcessionData.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入备注原因"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
</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-gi>
|
||||||
|
<n-form-item label="附件">
|
||||||
|
<n-upload
|
||||||
|
multiple
|
||||||
|
directory-dnd
|
||||||
|
:max="5"
|
||||||
|
:action="null"
|
||||||
|
:custom-request="()=>{}"
|
||||||
|
@change="handleUploadChange"
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
accept=".pdf,.doc,.docx,.xls,.xlsx"
|
||||||
|
: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">
|
||||||
|
仅支持 PDF、DOC、DOCX、XLS、XLSX、JPG、PNG、GIF,单文件最大10MB
|
||||||
|
</n-p>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
|
||||||
|
|
||||||
|
<n-space justify="center" style="width: 100%; margin-top: 16px;">
|
||||||
|
<n-button size="small" @click="handleReset">重置</n-button>
|
||||||
|
<n-button type="primary" size="small" @click="handleSubmitResultDetail">提交</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
|
import {
|
||||||
|
NButton, NSpace, NIcon, NUpload,NTag, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions,
|
||||||
|
NDropdown
|
||||||
|
} from 'naive-ui'
|
||||||
|
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
|
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
|
import {fileApi} from '@/api/system1'
|
||||||
|
import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
||||||
|
import router from '@/router';
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const route = useRoute()
|
||||||
|
const qcId =ref( Number(route.params.id))
|
||||||
|
|
||||||
|
const traceCodeShow = ref(route.params.traceCodeShow === '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 uploadFiles = ref<File[]>([])
|
||||||
|
const fileExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx']
|
||||||
|
|
||||||
|
|
||||||
|
const title = ref(`质检编号: ${route.params.qcNo}`)
|
||||||
|
|
||||||
|
|
||||||
|
const defaultFormDetailData:QcResultDetail = {
|
||||||
|
qcId: undefined,
|
||||||
|
resultType: undefined,
|
||||||
|
qty: 0,
|
||||||
|
traceCode: undefined,
|
||||||
|
defectCode: undefined,
|
||||||
|
handleAction: undefined,
|
||||||
|
actionStatus: undefined,
|
||||||
|
remark: '',
|
||||||
|
title:undefined,
|
||||||
|
images:[],
|
||||||
|
attachments:[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
||||||
|
const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||||
|
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||||
|
const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||||
|
|
||||||
|
|
||||||
|
async function handleSubmitResultDetail() {
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
formDetailOKData,
|
||||||
|
formDetailScrapData,
|
||||||
|
formDetailReworkData,
|
||||||
|
formDetailConcessionData]
|
||||||
|
|
||||||
|
console.log(submitParam);
|
||||||
|
|
||||||
|
|
||||||
|
submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam)
|
||||||
|
|
||||||
|
message.success('提交成功')
|
||||||
|
router.go(-1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function handleUploadChange ({ 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 (!fileExts.includes(ext)) {
|
||||||
|
invalidNames.push(f.name)
|
||||||
|
item.status = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidNames.length) {
|
||||||
|
message.error(`格式不支持:${invalidNames.join('、')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("表单已重置")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@ -184,7 +184,7 @@
|
|||||||
|
|
||||||
<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, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace, NIcon,NTag, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, Options } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, Options } from '@vicons/ionicons5'
|
||||||
import { submitLogApi, type SubmitLog } from '@/api/submitLog'
|
import { submitLogApi, type SubmitLog } from '@/api/submitLog'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
@ -243,18 +243,35 @@ const formRules = {
|
|||||||
// 表格列
|
// 表格列
|
||||||
const columns: DataTableColumns<SubmitLog> = [
|
const columns: DataTableColumns<SubmitLog> = [
|
||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ title: '工序名称', key: 'processName' },
|
{ title: '物料名称', key: 'materialName',align: 'center',
|
||||||
{ title: '工序编号', key: 'processCode' },
|
render(rowData, rowIndex) {
|
||||||
{ title: '派工编号', key: 'assingCode' },
|
return rowData.materialName? rowData.materialName: '-'
|
||||||
{ title: '派工人', key: 'userName' },
|
},
|
||||||
{ title: '派工状态', key: 'assingStatus',
|
},
|
||||||
|
{ title: '物料编码', key: 'materialCode',align: 'center',
|
||||||
|
render(rowData, rowIndex) {
|
||||||
|
return rowData.materialCode? rowData.materialCode: '-'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '物料数量', key: 'materialQty',align: 'center',
|
||||||
|
render(rowData, rowIndex) {
|
||||||
|
return rowData.materialQty? rowData.materialQty: '-'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '工序名称', key: 'processName',align: 'center' },
|
||||||
|
{ title: '工序编号', key: 'processCode',align: 'center' },
|
||||||
|
{ title: '派工编号', key: 'assingCode',align: 'center' },
|
||||||
|
{ title: '派工人', key: 'userName',align: 'center' },
|
||||||
|
{ title: '派工状态', key: 'assingStatus',align: 'center',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.assingStatus
|
const val = row.assingStatus
|
||||||
const opt = qcAssingStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcAssingStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '派工完成时间', key: 'completedTime'},
|
{ title: '派工完成时间', key: 'completedTime',align: 'center'},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
@ -286,7 +303,9 @@ const submitColumns = ref([
|
|||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.submitStatus
|
const val = row.submitStatus
|
||||||
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -474,19 +493,19 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
|||||||
async function loadDictOptions() {
|
async function loadDictOptions() {
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_result_type')
|
const data = await dictDataApi.listByType('qc_result_type')
|
||||||
qcResultTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcResultTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('source_type')
|
const data = await dictDataApi.listByType('source_type')
|
||||||
qcSourceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcSourceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_status')
|
const data = await dictDataApi.listByType('qc_status')
|
||||||
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('assing_status')
|
const data = await dictDataApi.listByType('assing_status')
|
||||||
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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">
|
||||||
@ -73,7 +85,8 @@
|
|||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import { submitLogApi } from '@/api/submitLog'
|
import { submitLogApi } from '@/api/submitLog'
|
||||||
|
import { NButton, NSpace, NIcon,NTag, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, Options } from '@vicons/ionicons5'
|
||||||
//字典数据
|
//字典数据
|
||||||
const statisticTypeOptions = ref<{ label: string; value: any }[]>([])
|
const statisticTypeOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
@ -114,6 +127,9 @@ async function loadStatistics() {
|
|||||||
|
|
||||||
Object.assign(stats,res)
|
Object.assign(stats,res)
|
||||||
|
|
||||||
|
|
||||||
|
console.log(stats);
|
||||||
|
|
||||||
updateCharts()
|
updateCharts()
|
||||||
}catch{}
|
}catch{}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,7 @@
|
|||||||
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark img-class="logo-img" icon-class="logo-icon" />
|
||||||
<div v-else class="logo-icon">{{ siteName.charAt(0) }}</div>
|
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
||||||
@ -111,8 +110,7 @@
|
|||||||
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark img-class="logo-img" icon-class="logo-icon" />
|
||||||
<div v-else class="logo-icon">{{ siteName.charAt(0) }}</div>
|
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
||||||
@ -200,8 +198,11 @@
|
|||||||
<div class="login-banner" style="background: transparent;">
|
<div class="login-banner" style="background: transparent;">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark
|
||||||
<div v-else class="logo-icon" style="background: rgba(255, 255, 255, 0.15); color: #fff; border: 1px solid rgba(255, 255, 255, 0.2);">{{ siteName.charAt(0) }}</div>
|
img-class="logo-img"
|
||||||
|
icon-class="logo-icon"
|
||||||
|
:icon-style="glassLogoIconStyle"
|
||||||
|
/>
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title" style="color: #fff;">{{ siteDescription || '后台管理系统' }}</h1>
|
<h1 class="banner-title" style="color: #fff;">{{ siteDescription || '后台管理系统' }}</h1>
|
||||||
@ -350,6 +351,7 @@ import { useSiteStore } from '@/stores/site'
|
|||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import { authApi } from '@/api/auth'
|
import { authApi } from '@/api/auth'
|
||||||
import { configGroupApi } from '@/api/org'
|
import { configGroupApi } from '@/api/org'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -359,10 +361,14 @@ const siteStore = useSiteStore()
|
|||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
|
|
||||||
// 站点配置(带默认值)
|
// 站点配置(带默认值)
|
||||||
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
const siteName = computed(() => siteStore.siteName || 'MES系统')
|
||||||
const siteDescription = computed(() => siteStore.siteDescription || '伊特智造MES')
|
const siteDescription = computed(() => siteStore.siteDescription || '伊特智造MES')
|
||||||
const siteLogo = computed(() => siteStore.siteLogo)
|
|
||||||
const copyright = computed(() => siteStore.copyright || '版权所有 © 河北伊特')
|
const copyright = computed(() => siteStore.copyright || '版权所有 © 河北伊特')
|
||||||
|
const glassLogoIconStyle = {
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||||
|
}
|
||||||
|
|
||||||
// 登录配置
|
// 登录配置
|
||||||
const captchaEnabled = ref(false)
|
const captchaEnabled = ref(false)
|
||||||
|
|||||||
@ -9,14 +9,14 @@
|
|||||||
label-placement="left"
|
label-placement="left"
|
||||||
>
|
>
|
||||||
<n-grid :cols="24" :x-gap="24">
|
<n-grid :cols="24" :x-gap="24">
|
||||||
<n-form-item-gi :span="6" label="工序编码">
|
<!-- <n-form-item-gi :span="6" label="工序编码">
|
||||||
<n-input v-model:value="searchForm.processId" placeholder="请输入工序编号" clearable />
|
<n-input v-model:value="searchForm.processId" placeholder="请输入工序编号" clearable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi> -->
|
||||||
<n-form-item-gi :span="6" label="工序名称">
|
<n-form-item-gi :span="6" label="工序名称">
|
||||||
<n-input v-model:value="searchForm.processName" placeholder="请输入工序名称" clearable />
|
<n-input v-model:value="searchForm.processName" placeholder="请输入工序名称" clearable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="6" label="工人名称">
|
<n-form-item-gi :span="6" label="工人名称">
|
||||||
<n-input v-model:value="searchForm.stringUserName" placeholder="请输入工人名称" clearable />
|
<n-input v-model:value="searchForm.userName" placeholder="请输入工人名称" clearable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<!-- <n-form-item-gi :span="6" label="数里">
|
<!-- <n-form-item-gi :span="6" label="数里">
|
||||||
<n-input v-model:value="searchForm.processId" placeholder="请输入数里" clearable />
|
<n-input v-model:value="searchForm.processId" placeholder="请输入数里" clearable />
|
||||||
@ -99,7 +99,7 @@
|
|||||||
v-model:page="pagination.page"
|
v-model:page="pagination.page"
|
||||||
v-model:page-size="pagination.pageSize"
|
v-model:page-size="pagination.pageSize"
|
||||||
:item-count="pagination.itemCount"
|
:item-count="pagination.itemCount"
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
:page-sizes="[2,10, 20, 50, 100]"
|
||||||
show-size-picker
|
show-size-picker
|
||||||
show-quick-jumper
|
show-quick-jumper
|
||||||
@update:page="handlePageChange"
|
@update:page="handlePageChange"
|
||||||
@ -140,8 +140,14 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="工人名称" path="stringUserName">
|
<n-form-item label="改派工人" path="">
|
||||||
<n-input v-model:value="formData.stringUserName" placeholder="请选择工人" />
|
<n-select
|
||||||
|
v-model:value="reassform.userId"
|
||||||
|
placeholder="请指定改派的员工"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
:options="reassignEmployees"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
@ -202,8 +208,15 @@
|
|||||||
<n-input v-model:value="gxobg.name" disabled />
|
<n-input v-model:value="gxobg.name" disabled />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item label="工人名称" path="">
|
<n-form-item label="改派工人" path="">
|
||||||
<n-input v-model:value="reassform.userid" placeholder="请选择员工" />
|
<n-select
|
||||||
|
v-model:value="reassform.userId"
|
||||||
|
placeholder="请指定改派的员工"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
:options="reassignEmployees"
|
||||||
|
/>
|
||||||
|
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="改派原因" path="reason">
|
<n-form-item label="改派原因" path="reason">
|
||||||
<n-input type="textarea" v-model:value="reassform.reason" placeholder="请输入改派原因" />
|
<n-input type="textarea" v-model:value="reassform.reason" placeholder="请输入改派原因" />
|
||||||
@ -259,6 +272,76 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
|
||||||
|
<!--查询汇报详情-->
|
||||||
|
|
||||||
|
<n-modal v-model:show="reportDetailVisible" preset="card" :title="reportDetailTitle" style="width: 1000px">
|
||||||
|
<n-card hoverable style="margin-bottom: 12px" >
|
||||||
|
<template #header>
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;width:100%">
|
||||||
|
<span>主表信息</span>
|
||||||
|
<n-button text @click="infoIsCollapse = !infoIsCollapse">
|
||||||
|
<n-icon :component="infoIsCollapse ? ArrowDownOutline : ArrowUpOutline" />
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<n-descriptions :column="3" bordered v-show="!infoIsCollapse">
|
||||||
|
<n-descriptions-item
|
||||||
|
v-for="item in mappedInfoList"
|
||||||
|
:key="item.key"
|
||||||
|
:label="item.label"
|
||||||
|
>
|
||||||
|
{{ formatDisplayValue(item.key, item.value) }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</n-card>
|
||||||
|
<n-card title="汇报信息" hoverable style="margin-bottom: 12px" >
|
||||||
|
<div v-if="qualityTestings.length === 0" style="height:520px;display:flex;align-items:center;justify-content:center;color:#999">
|
||||||
|
暂无质检单据数据
|
||||||
|
</div>
|
||||||
|
<n-tabs
|
||||||
|
type="line"
|
||||||
|
animated
|
||||||
|
placement="left"
|
||||||
|
style="height: 520px"
|
||||||
|
v-else>
|
||||||
|
<n-tab-pane
|
||||||
|
v-for="item in qualityTestings"
|
||||||
|
:name="item.qcNo"
|
||||||
|
:tab="item.qcNo">
|
||||||
|
<n-card title="质检基础信息" style="margin-bottom:12px">
|
||||||
|
<n-descriptions :column="3">
|
||||||
|
<n-descriptions-item label="资源类型">{{formatDisplayValue("resourceType",item.resourceType) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="质检状态">{{formatDisplayValue("status",item.qcStatus) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="总数量">{{ item.totalQty }}</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!--汇报表格-->
|
||||||
|
<n-card title="汇报列表">
|
||||||
|
<n-data-table
|
||||||
|
:columns="submitColumns"
|
||||||
|
:data="item.submitLogs"
|
||||||
|
:scroll-x="700"
|
||||||
|
max-height="320"
|
||||||
|
:pagination="{ pageSize:3 }"
|
||||||
|
>
|
||||||
|
<!--空数据提示插槽-->
|
||||||
|
<template #empty>
|
||||||
|
<div style="padding:40px;text-align:center;color:#999">
|
||||||
|
当前没有数据
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-data-table>
|
||||||
|
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
</n-tabs>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -268,11 +351,13 @@ import {
|
|||||||
reactive,
|
reactive,
|
||||||
h,
|
h,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
render,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
|
NDropdown,
|
||||||
NIcon,
|
NIcon,
|
||||||
NSpace,
|
NSpace,
|
||||||
NPagination,
|
NPagination,
|
||||||
@ -283,14 +368,12 @@ import {
|
|||||||
//useDialog,
|
//useDialog,
|
||||||
type FormInst,
|
type FormInst,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SearchOutline,
|
SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline,
|
||||||
RefreshOutline
|
EllipsisHorizontalOutline, ArrowDownOutline, ArrowUpOutline
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
|
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { dictDataApi } from '@/api/org'
|
||||||
import {
|
import {
|
||||||
disworlist,
|
disworlist,
|
||||||
editdiswor,
|
editdiswor,
|
||||||
@ -299,6 +382,10 @@ import {
|
|||||||
//workload
|
//workload
|
||||||
} from '@/api/production'
|
} from '@/api/production'
|
||||||
|
|
||||||
|
import { submitLogApi } from '@/api/submitLog'
|
||||||
|
|
||||||
|
import { SysUser, userApi } from '@/api/system'
|
||||||
|
|
||||||
//const dialog = useDialog()
|
//const dialog = useDialog()
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@ -308,14 +395,51 @@ const userStore = useUserStore()
|
|||||||
// 权限检查
|
// 权限检查
|
||||||
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
||||||
|
|
||||||
|
//判定状态字典
|
||||||
|
const qcResultTypeOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
//业务来源字典
|
||||||
|
const qcSourceTypeOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
//质检状态字典
|
||||||
|
const qcStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
//派工状态字典
|
||||||
|
const qcAssingStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
|
//汇报详情
|
||||||
|
const infoIsCollapse = ref(false)
|
||||||
|
|
||||||
|
const reportDetailVisible = ref(false)
|
||||||
|
const reportDetailTitle = ref<string>('')
|
||||||
|
|
||||||
|
const mappedInfoList = ref<Array<{ label: string; key: string; value: any }>>([])
|
||||||
|
const qualityTestings = ref<any[]>([])
|
||||||
|
|
||||||
|
const submitColumns = ref([
|
||||||
|
{ title: "提交人", key: "sumbitUser" },
|
||||||
|
{ title: "提交时间", key: "submitCreateTime" },
|
||||||
|
{ title: "提交数量", key: "quantity" },
|
||||||
|
{ title: "单据状态", key: "submitStatus",
|
||||||
|
render: (row) => {
|
||||||
|
const val = row.submitStatus
|
||||||
|
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
//改派员工列
|
||||||
|
const reassignEmployees = ref<Array<{value: number, username: string}>>([])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==================== 搜索表单 ====================
|
// ==================== 搜索表单 ====================
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
processId:'',
|
processId:null ,
|
||||||
processName:'',
|
processName:null,
|
||||||
stringUserName:''
|
userName:null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -331,15 +455,39 @@ const pagination = reactive({
|
|||||||
itemCount: 0
|
itemCount: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
// {
|
// {
|
||||||
// type: 'selection',
|
// type: 'selection',
|
||||||
// minWidth:60
|
// minWidth:60
|
||||||
// },
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
align:'center',
|
align:"center",
|
||||||
title: '工序编号',
|
title:"物料名称",
|
||||||
key: 'processId',
|
key:"materialName",
|
||||||
|
minWidth:150,
|
||||||
|
render(row:any) {
|
||||||
|
var opt = row.materialName
|
||||||
|
if(opt) return opt
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:"center",
|
||||||
|
title:"物料编码",
|
||||||
|
key:"materialCode",
|
||||||
|
minWidth:150,
|
||||||
|
render(row:any) {
|
||||||
|
var opt = row.materialCode
|
||||||
|
if(opt) return opt
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:"center",
|
||||||
|
title:"派工编号",
|
||||||
|
key:"assingCode",
|
||||||
minWidth:150
|
minWidth:150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -354,27 +502,19 @@ const columns = [
|
|||||||
key: 'assingStatus',
|
key: 'assingStatus',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
let text = ''
|
const val = row.assingStatus
|
||||||
if(row.assingStatus == 0){
|
const opt = qcAssingStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
text = '待执行'
|
if (!opt) return val ?? '-'
|
||||||
}else if(row.assingStatus == 1){
|
|
||||||
text = '执行中'
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}else if(row.assingStatus == 2){
|
|
||||||
text = '质检中'
|
|
||||||
}else if(row.assingStatus == 3){
|
|
||||||
text = '已汇报'
|
|
||||||
}else if(row.assingStatus == 4){
|
|
||||||
text = '已关闭'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
//派工状态0待执行 1执行中 2质检中+3已汇报一4已关闭
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
title: '工人名称',
|
title: '工人名称',
|
||||||
key: 'stringUserName',
|
key: 'userName',
|
||||||
minWidth: 200
|
minWidth: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -389,7 +529,7 @@ const columns = [
|
|||||||
key: 'rework',
|
key: 'rework',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
const quit = row.rework === 3
|
const quit = row.rework === 1
|
||||||
return h(NTag, {type: quit ? 'error' : 'success', size: 'small'}, {default: () => (quit ? '是' : '否')})
|
return h(NTag, {type: quit ? 'error' : 'success', size: 'small'}, {default: () => (quit ? '是' : '否')})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -429,9 +569,14 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
title: '质检打回数里',
|
title: '质检打回数量',
|
||||||
key: 'qualityReturnNum',
|
key: 'qualityReturnNum',
|
||||||
minWidth: 150
|
minWidth: 150,
|
||||||
|
render(row:any) {
|
||||||
|
var count = row.qualityReturnNum
|
||||||
|
if(count>0) return count
|
||||||
|
return 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
@ -461,7 +606,7 @@ const columns = [
|
|||||||
}
|
}
|
||||||
if(row.status == 2){
|
if(row.status == 2){
|
||||||
return h(NTag, {
|
return h(NTag, {
|
||||||
type:'warning',
|
type:'error',
|
||||||
size: 'small'
|
size: 'small'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -491,52 +636,79 @@ const columns = [
|
|||||||
width: 200,
|
width: 200,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
const buttons:any = [
|
return h('div',{ style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } },[
|
||||||
|
h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
||||||
|
}),
|
||||||
|
h(NDropdown, {
|
||||||
|
trigger:'click',
|
||||||
|
options:[
|
||||||
|
{label:"改派",key:'transfer'},
|
||||||
|
{type: 'divider' }, // 分割线
|
||||||
|
{label: '启用/暂停',key:'pauseResume'},
|
||||||
|
{type: 'divider' }, // 分割线
|
||||||
|
{label:'查看汇报',key:'view'},
|
||||||
|
],
|
||||||
|
onSelect:(key:string)=>{
|
||||||
|
switch(key) {
|
||||||
|
case "transfer":
|
||||||
|
handlreass(row)
|
||||||
|
break
|
||||||
|
case "pauseResume":
|
||||||
|
handlpause(row)
|
||||||
|
break
|
||||||
|
case "view":
|
||||||
|
handleViewReport(row.id)
|
||||||
|
break
|
||||||
|
|
||||||
// h(NButton, {
|
}
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
// 下拉触发按钮:三点图标 / 更多文字
|
||||||
|
default: () => h(NButton, { size: 'small', quaternary: true }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(EllipsisHorizontalOutline) })]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
// const buttons:any = [
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// if(hasPermission('biz:assingWork:edit')){
|
||||||
|
// buttons.push(h(NButton, {
|
||||||
|
// size: 'small',
|
||||||
|
// type:'primary',
|
||||||
|
// ghost:true,
|
||||||
|
// onClick: () => { handleEdit(row) } },
|
||||||
|
// //{ default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑' ]}
|
||||||
|
// { default: () => '编辑'}
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
// if(hasPermission('biz:assingWork:transfer')){
|
||||||
|
// buttons.push(h(NButton, {
|
||||||
|
// size: 'small',
|
||||||
|
// type:'success',
|
||||||
|
// ghost:true,
|
||||||
|
// onClick: () => { handlreass(row) } },
|
||||||
|
// { default: () => '改派'}
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
// if(hasPermission('biz:assingWork:pauseResume')){
|
||||||
|
// buttons.push(h(NButton, {
|
||||||
// size: 'small',
|
// size: 'small',
|
||||||
// type:'warning',
|
// type:'warning',
|
||||||
// ghost:true,
|
// ghost:true,
|
||||||
// onClick: () => handleEdit(row) },
|
// onClick:()=>{ handlpause(row) }
|
||||||
// //{ default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 暂停' ]}
|
// },
|
||||||
// { default: () => '暂停'}
|
// { default: () => '暂停'}
|
||||||
// )
|
// ))
|
||||||
]
|
// }
|
||||||
|
|
||||||
if(hasPermission('biz:assingWork:edit')){
|
|
||||||
buttons.push(h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type:'primary',
|
|
||||||
ghost:true,
|
|
||||||
onClick: () => { handleEdit(row) } },
|
|
||||||
//{ default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑' ]}
|
|
||||||
{ default: () => '编辑'}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
if(hasPermission('biz:assingWork:transfer')){
|
|
||||||
buttons.push(h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type:'success',
|
|
||||||
ghost:true,
|
|
||||||
onClick: () => { handlreass(row) } },
|
|
||||||
{ default: () => '改派'}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
if(hasPermission('biz:assingWork:pauseResume')){
|
|
||||||
buttons.push(h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type:'warning',
|
|
||||||
ghost:true,
|
|
||||||
onClick:()=>{ handlpause(row) }
|
|
||||||
},
|
|
||||||
{ default: () => '暂停'}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
// buttons.push(
|
// buttons.push(
|
||||||
|
|
||||||
// )
|
// )
|
||||||
|
|
||||||
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
// return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -572,9 +744,11 @@ function search() {
|
|||||||
|
|
||||||
//重置
|
//重置
|
||||||
function reset() {
|
function reset() {
|
||||||
searchForm.processId =''
|
searchForm.processId =null
|
||||||
searchForm.processName = ''
|
searchForm.processName = null
|
||||||
searchForm.stringUserName = ''
|
searchForm.userName = null
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取列表数据
|
//获取列表数据
|
||||||
@ -651,9 +825,9 @@ let reassform = reactive<any>({
|
|||||||
// userId:'', //工人id
|
// userId:'', //工人id
|
||||||
// stringUserName:'', //工人名称
|
// stringUserName:'', //工人名称
|
||||||
|
|
||||||
id:'', //派工id
|
id:null, //派工id
|
||||||
reason:'', //改派原因
|
reason:null, //改派原因
|
||||||
userid:456 //改派人Id
|
userId:null //改派人Id
|
||||||
})
|
})
|
||||||
const reassrules = {
|
const reassrules = {
|
||||||
userid: [{ required: true, message: '请选择员工', trigger: 'blur' }]
|
userid: [{ required: true, message: '请选择员工', trigger: 'blur' }]
|
||||||
@ -722,8 +896,7 @@ function handlpause(n:any) {
|
|||||||
pauseformRef.value?.restoreValidation()
|
pauseformRef.value?.restoreValidation()
|
||||||
pauseform.id = n.id
|
pauseform.id = n.id
|
||||||
pauseform.reason = ''
|
pauseform.reason = ''
|
||||||
pauseform.status = n.staus
|
pauseform.status = n.status === '0'?'1':'0'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseSubmit() {
|
function pauseSubmit() {
|
||||||
@ -733,12 +906,10 @@ function pauseSubmit() {
|
|||||||
pauseisedbtn.value = true
|
pauseisedbtn.value = true
|
||||||
pauseresume(pauseform).then(() => {
|
pauseresume(pauseform).then(() => {
|
||||||
message.success('操作成功!')
|
message.success('操作成功!')
|
||||||
setTimeout(()=> {
|
|
||||||
pausemodal.value = false
|
pausemodal.value = false
|
||||||
pauseLoading.value = false
|
pauseLoading.value = false
|
||||||
pauseisedbtn.value = false
|
pauseisedbtn.value = false
|
||||||
getlist()
|
getlist()
|
||||||
},1000)
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
pauseLoading.value = false
|
pauseLoading.value = false
|
||||||
pauseisedbtn.value = false
|
pauseisedbtn.value = false
|
||||||
@ -747,23 +918,94 @@ function pauseSubmit() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// function zantqy(row:any) {
|
async function handleViewReport(id:number) {
|
||||||
// dialog.warning({
|
reportDetailTitle.value='汇报详情'
|
||||||
// title: '温馨提示',
|
reportDetailVisible.value = true
|
||||||
// content: `确定要暂停"${row.processName}"吗?`,
|
|
||||||
// positiveText: '确定',
|
const res = await submitLogApi.getByDispatch(id);
|
||||||
// negativeText: '取消',
|
|
||||||
// onPositiveClick: () => {
|
mappedInfoList.value = [
|
||||||
// pauseresume({}).then(() => {
|
{key:"materialCode",label:"物料编码",value:res.materialCode},
|
||||||
// message.success('操作成功!')
|
{key:"materialName",label:"物料名称",value:res.materialName},
|
||||||
// getlist()
|
{key:"materialQty",label:"物料数量",value:res.materialQty},
|
||||||
// })
|
{key:"processCode",label:"工序编号",value:res.processCode},
|
||||||
// }
|
{key:"processName",label:"工序名称",value:res.processName},
|
||||||
// })
|
{key:"processQty",label:"生产总数",value:res.processQty},
|
||||||
// }
|
{key:"assingCode",label:"派工编号",value:res.assingCode},
|
||||||
|
{key:"workerName",label:"派工人",value:res.workerName},
|
||||||
|
{key:"assingQty",label:"派工数量",value:res.assingQty}
|
||||||
|
]
|
||||||
|
qualityTestings.value = res.qualityTestings
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatDisplayValue(key: string, value: any): string {
|
||||||
|
if (value === null || value === undefined) return '-'
|
||||||
|
|
||||||
|
// 根据字段类型格式化
|
||||||
|
switch (key) {
|
||||||
|
case 'resourceType': {
|
||||||
|
const sourceOpt = qcSourceTypeOptions.value.find(o => o.value === value || String(o.value) === String(value))
|
||||||
|
return sourceOpt ? sourceOpt.label : String(value)
|
||||||
|
}
|
||||||
|
case 'status': {
|
||||||
|
const statusOpt = qcStatusOptions.value.find(o => o.value === value || String(o.value) === String(value))
|
||||||
|
return statusOpt ? statusOpt.label : String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'qcNo':
|
||||||
|
return value ? new Date(value).toLocaleString('zh-CN') : '-'
|
||||||
|
case 'inspectTime':
|
||||||
|
case 'createTime':
|
||||||
|
case 'updateTime':
|
||||||
|
return value ? new Date(value).toLocaleString('zh-CN') : '-'
|
||||||
|
default:
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 加载字典选项
|
||||||
|
async function loadDictOptions() {
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('qc_result_type')
|
||||||
|
qcResultTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('source_type')
|
||||||
|
qcSourceTypeOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('qc_status')
|
||||||
|
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('assing_status')
|
||||||
|
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//获取员工列
|
||||||
|
async function getUserList() {
|
||||||
|
const res = await userApi.getPathList();
|
||||||
|
|
||||||
|
reassignEmployees.value = res.map((user:SysUser)=>(
|
||||||
|
{
|
||||||
|
label: user.username,
|
||||||
|
value: user.id
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
getlist()
|
getlist()
|
||||||
|
getUserList()
|
||||||
|
loadDictOptions()
|
||||||
//workload()
|
//workload()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,9 +9,9 @@
|
|||||||
label-placement="left"
|
label-placement="left"
|
||||||
>
|
>
|
||||||
<n-grid :cols="24" :x-gap="24">
|
<n-grid :cols="24" :x-gap="24">
|
||||||
<n-form-item-gi :span="6" label="工序编码">
|
<!-- <n-form-item-gi :span="6" label="工序编码">
|
||||||
<n-input v-model:value="searchForm.processId" placeholder="请输入工序编号" clearable />
|
<n-input v-model:value="searchForm.processId" placeholder="请输入工序编号" clearable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi> -->
|
||||||
<n-form-item-gi :span="6" label="工序名称">
|
<n-form-item-gi :span="6" label="工序名称">
|
||||||
<n-input v-model:value="searchForm.processName" placeholder="请输入工序名称" clearable />
|
<n-input v-model:value="searchForm.processName" placeholder="请输入工序名称" clearable />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
@ -182,7 +182,7 @@ import {
|
|||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
|
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { dictDataApi } from '@/api/org'
|
||||||
import {
|
import {
|
||||||
mytasks,
|
mytasks,
|
||||||
inspectio
|
inspectio
|
||||||
@ -198,6 +198,9 @@ const userStore = useUserStore()
|
|||||||
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
||||||
|
|
||||||
|
|
||||||
|
//派工状态字典
|
||||||
|
const qcAssingStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
// ==================== 搜索表单 ====================
|
// ==================== 搜索表单 ====================
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
@ -225,9 +228,31 @@ const columns = [
|
|||||||
// minWidth:60
|
// minWidth:60
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
align:'center',
|
align:"center",
|
||||||
title: '工序编号',
|
title:"物料名称",
|
||||||
key: 'processId',
|
key:"materialName",
|
||||||
|
minWidth:150,
|
||||||
|
render(row:any) {
|
||||||
|
var opt = row.materialName
|
||||||
|
if(opt) return opt
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:"center",
|
||||||
|
title:"物料编码",
|
||||||
|
key:"materialCode",
|
||||||
|
minWidth:150,
|
||||||
|
render(row:any) {
|
||||||
|
var opt = row.materialCode
|
||||||
|
if(opt) return opt
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:"center",
|
||||||
|
title:"派工编号",
|
||||||
|
key:"assingCode",
|
||||||
minWidth:150
|
minWidth:150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -242,27 +267,19 @@ const columns = [
|
|||||||
key: 'assingStatus',
|
key: 'assingStatus',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
let text = ''
|
const val = row.assingStatus
|
||||||
if(row.assingStatus == 0){
|
const opt = qcAssingStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
text = '待执行'
|
if (!opt) return val ?? '-'
|
||||||
}else if(row.assingStatus == 1){
|
|
||||||
text = '执行中'
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}else if(row.assingStatus == 2){
|
|
||||||
text = '质检中'
|
|
||||||
}else if(row.assingStatus == 3){
|
|
||||||
text = '已汇报'
|
|
||||||
}else if(row.assingStatus == 4){
|
|
||||||
text = '已关闭'
|
|
||||||
}
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
}
|
||||||
//派工状态0待执行 1执行中 2质检中+3已汇报一4已关闭
|
//派工状态0待执行 1执行中 2质检中+3已汇报一4已关闭
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
title: '工人名称',
|
title: '工人名称',
|
||||||
key: 'stringUserName',
|
key: 'userName',
|
||||||
minWidth: 200
|
minWidth: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -277,7 +294,7 @@ const columns = [
|
|||||||
key: 'rework',
|
key: 'rework',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
const quit = row.rework === 3
|
const quit = row.rework === 1
|
||||||
return h(NTag, {type: quit ? 'error' : 'success', size: 'small'}, {default: () => (quit ? '是' : '否')})
|
return h(NTag, {type: quit ? 'error' : 'success', size: 'small'}, {default: () => (quit ? '是' : '否')})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -418,7 +435,7 @@ function reset() {
|
|||||||
//获取列表数据
|
//获取列表数据
|
||||||
function getlist() {
|
function getlist() {
|
||||||
mytasks(searchForm).then((rps:any) => {
|
mytasks(searchForm).then((rps:any) => {
|
||||||
datalist.value = rps.list
|
datalist.value = rps.records
|
||||||
pagination.itemCount = rps.total
|
pagination.itemCount = rps.total
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -506,6 +523,14 @@ function pauseSubmit() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载字典选项
|
||||||
|
async function loadDictOptions() {
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType('assing_status')
|
||||||
|
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
|
}
|
||||||
|
|
||||||
// function zantqy(row:any) {
|
// function zantqy(row:any) {
|
||||||
// dialog.warning({
|
// dialog.warning({
|
||||||
// title: '温馨提示',
|
// title: '温馨提示',
|
||||||
@ -524,6 +549,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
|
|
||||||
getlist()
|
getlist()
|
||||||
|
loadDictOptions()
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -5,8 +5,7 @@
|
|||||||
<div class="register-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
<div class="register-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark img-class="logo-img" icon-class="logo-icon" />
|
||||||
<div v-else class="logo-icon">{{ siteName.charAt(0) }}</div>
|
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title">加入我们</h1>
|
<h1 class="banner-title">加入我们</h1>
|
||||||
@ -116,6 +115,7 @@ import { useSiteStore } from '@/stores/site'
|
|||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import { authApi } from '@/api/auth'
|
import { authApi } from '@/api/auth'
|
||||||
import { configGroupApi } from '@/api/org'
|
import { configGroupApi } from '@/api/org'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@ -124,7 +124,6 @@ const themeStore = useThemeStore()
|
|||||||
|
|
||||||
// 站点配置
|
// 站点配置
|
||||||
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
||||||
const siteLogo = computed(() => siteStore.siteLogo)
|
|
||||||
const copyright = computed(() => siteStore.copyright || '版权所有 © 成都火星网络科技有限公司 2025-2030')
|
const copyright = computed(() => siteStore.copyright || '版权所有 © 成都火星网络科技有限公司 2025-2030')
|
||||||
|
|
||||||
// 注册配置
|
// 注册配置
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
<n-form-item label="站点Logo">
|
<n-form-item label="站点Logo">
|
||||||
<div class="logo-upload">
|
<div class="logo-upload">
|
||||||
<div class="logo-preview" v-if="configs.system.siteLogo">
|
<div class="logo-preview" v-if="configs.system.siteLogo">
|
||||||
<img :src="configs.system.siteLogo" alt="Logo" />
|
<SiteLogoMark :src="configs.system.siteLogo" />
|
||||||
<n-button size="small" quaternary type="error" class="logo-delete" @click="configs.system.siteLogo = ''">
|
<n-button size="small" quaternary type="error" class="logo-delete" @click="configs.system.siteLogo = ''">
|
||||||
<template #icon><n-icon><CloseOutline /></n-icon></template>
|
<template #icon><n-icon><CloseOutline /></n-icon></template>
|
||||||
</n-button>
|
</n-button>
|
||||||
@ -938,6 +938,7 @@ import { configGroupApi, type SysConfigGroup, type SmsLog } from '@/api/org'
|
|||||||
import { fileApi } from '@/api/system'
|
import { fileApi } from '@/api/system'
|
||||||
import { wechatApi } from '@/api/wechat'
|
import { wechatApi } from '@/api/wechat'
|
||||||
import { useSiteStore } from '@/stores/site'
|
import { useSiteStore } from '@/stores/site'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const siteStore = useSiteStore()
|
const siteStore = useSiteStore()
|
||||||
@ -1812,6 +1813,7 @@ async function loadWechatMpMenu() {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
|
:deep(.logo-img),
|
||||||
img {
|
img {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
max-height: 80px;
|
max-height: 80px;
|
||||||
@ -1822,6 +1824,20 @@ async function loadWechatMpMenu() {
|
|||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.logo-icon) {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f9fafb;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-delete {
|
.logo-delete {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -8px;
|
top: -8px;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user