Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // éç¨ä¸ä¼ æ¥å£ï¼æ¯æ FormData æ¹éä¼ æä»¶ |
| | | export function uploadFile(data) { |
| | | return request({ |
| | | url: '/common/upload', |
| | | method: 'post', |
| | | data, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | }, |
| | | }) |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | // å¤å¶ |
| | | export function copy(data) { |
| | | return request({ |
| | | url: "/technologyBom/copy", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | // ä¿®æ¹ |
| | | export function update(data) { |
| | | return request({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | const props = defineProps({ |
| | | list: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | thumbSize: { |
| | | type: Number, |
| | | default: 72, |
| | | }, |
| | | gap: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | }) |
| | | |
| | | const normalizedList = computed(() => { |
| | | return (props.list || []) |
| | | .filter((item) => item && item.previewURL) |
| | | .map((item, index) => ({ |
| | | id: item.id ?? index, |
| | | name: item.originalFilename || `image-${index + 1}`, |
| | | url: item.previewURL, |
| | | })) |
| | | }) |
| | | const previewUrls = computed(() => normalizedList.value.map((item) => item.url)) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="attachment-image-preview"> |
| | | <div v-if="!normalizedList.length" class="empty">ææ å¾ç</div> |
| | | |
| | | <div v-else class="thumbs" :style="{ gap: `${gap}px` }"> |
| | | <el-image |
| | | v-for="(item, index) in normalizedList" |
| | | :key="item.id" |
| | | class="thumb" |
| | | :style="{ width: `${thumbSize}px`, height: `${thumbSize}px` }" |
| | | :src="item.url" |
| | | :preview-src-list="previewUrls" |
| | | :initial-index="index" |
| | | fit="cover" |
| | | preview-teleported |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .attachment-image-preview { |
| | | width: 100%; |
| | | } |
| | | |
| | | .empty { |
| | | height: 120px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--el-text-color-secondary); |
| | | border: 1px dashed var(--el-border-color); |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .thumbs { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .thumb { |
| | | border: 1px solid var(--el-border-color); |
| | | border-radius: 6px; |
| | | overflow: hidden; |
| | | cursor: pointer; |
| | | background: #fff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | import { UploadFilled } from '@element-plus/icons-vue' |
| | | import { uploadFile } from '@/api/basicData/common' |
| | | |
| | | const props = defineProps({ |
| | | fileList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | index: { |
| | | type: Number, |
| | | default: -1, |
| | | }, |
| | | childrenKey: { |
| | | type: String, |
| | | default: 'files', |
| | | }, |
| | | limit: { |
| | | type: Number, |
| | | default: 20, |
| | | }, |
| | | fileSize: { |
| | | type: Number, |
| | | default: 50, |
| | | }, |
| | | fileType: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | buttonText: { |
| | | type: String, |
| | | default: 'åå»éæ©æä»¶', |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | uploadFieldName: { |
| | | type: String, |
| | | default: 'files', |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:fileList', 'change']) |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const uploadRef = ref() |
| | | const uploadQueueTimer = ref(null) |
| | | const uploading = ref(false) |
| | | const queuedUidSet = ref(new Set()) |
| | | const innerList = ref([]) |
| | | |
| | | function readListFromProps() { |
| | | if (props.index > -1) { |
| | | const row = props.fileList?.[props.index] |
| | | return Array.isArray(row?.[props.childrenKey]) ? row[props.childrenKey] : [] |
| | | } |
| | | return Array.isArray(props.fileList) ? props.fileList : [] |
| | | } |
| | | |
| | | watch( |
| | | () => props.fileList, |
| | | () => { |
| | | innerList.value = [...readListFromProps()] |
| | | }, |
| | | { deep: true, immediate: true }, |
| | | ) |
| | | |
| | | const currentList = computed({ |
| | | get() { |
| | | return innerList.value |
| | | }, |
| | | set(value) { |
| | | const nextList = Array.isArray(value) ? value : [] |
| | | innerList.value = nextList |
| | | |
| | | if (props.index > -1) { |
| | | const nextModelValue = Array.isArray(props.fileList) ? [...props.fileList] : [] |
| | | const currentRow = nextModelValue[props.index] || {} |
| | | nextModelValue[props.index] = { |
| | | ...currentRow, |
| | | [props.childrenKey]: nextList, |
| | | } |
| | | emit('update:fileList', nextModelValue) |
| | | emit('change', nextList, nextModelValue) |
| | | return |
| | | } |
| | | |
| | | emit('update:fileList', nextList) |
| | | emit('change', nextList, nextList) |
| | | }, |
| | | }) |
| | | |
| | | const displayFileList = computed(() => { |
| | | return currentList.value.map((item, index) => ({ |
| | | uid: getItemUid(item, index), |
| | | name: getItemName(item, index), |
| | | url: getItemUrl(item), |
| | | status: 'success', |
| | | rawData: item, |
| | | })) |
| | | }) |
| | | |
| | | const uploadTip = computed(() => { |
| | | if (!props.fileType.length) return `å个æä»¶ä¸è¶
è¿ ${props.fileSize}MB` |
| | | return `æ¯æ ${props.fileType.join('/')}ï¼å个æä»¶ä¸è¶
è¿ ${props.fileSize}MB` |
| | | }) |
| | | |
| | | function getItemUid(item, index) { |
| | | if (item?.id !== undefined && item?.id !== null) return `${item.id}` |
| | | return `${getItemName(item, index)}-${getItemUrl(item) || index}` |
| | | } |
| | | |
| | | function getItemUrl(item) { |
| | | if (!item) return '' |
| | | if (typeof item === 'string') return item |
| | | return item.url || item.downloadURL || item.previewURL || item.previewUrl || '' |
| | | } |
| | | |
| | | function getItemName(item, index = 0) { |
| | | if (!item) return `file-${index + 1}` |
| | | if (typeof item === 'string') return `file-${index + 1}` |
| | | return item.name || item.originalFilename || item.fileName || item.uidFilename || `file-${index + 1}` |
| | | } |
| | | |
| | | function normalizeResponseItem(item, index) { |
| | | if (typeof item === 'string') { |
| | | return { |
| | | name: `file-${currentList.value.length + index + 1}`, |
| | | url: item, |
| | | } |
| | | } |
| | | return Object.assign({}, item, { |
| | | url: item.url || item.downloadURL || item.previewURL || item.previewUrl || '', |
| | | name: item.name || item.originalFilename || item.fileName || item.uidFilename || `file-${currentList.value.length + index + 1}`, |
| | | }) |
| | | } |
| | | |
| | | function extractResponseArray(response) { |
| | | if (Array.isArray(response)) return response |
| | | if (Array.isArray(response?.data)) return response.data |
| | | if (Array.isArray(response?.data?.data)) return response.data.data |
| | | if (Array.isArray(response?.payload)) return response.payload |
| | | if (Array.isArray(response?.payload?.data)) return response.payload.data |
| | | if (Array.isArray(response?.rows)) return response.rows |
| | | if (Array.isArray(response?.result)) return response.result |
| | | return [] |
| | | } |
| | | |
| | | function validateFile(rawFile) { |
| | | const extension = rawFile.name.includes('.') |
| | | ? rawFile.name.slice(rawFile.name.lastIndexOf('.') + 1).toLowerCase() |
| | | : '' |
| | | |
| | | if (props.fileType.length) { |
| | | const isValidType = props.fileType.some((type) => { |
| | | const normalizedType = String(type).toLowerCase() |
| | | return rawFile.type.toLowerCase().includes(normalizedType) || extension === normalizedType |
| | | }) |
| | | if (!isValidType) { |
| | | proxy.$modal.msgError(`请ä¸ä¼ ${props.fileType.join('/')} æ ¼å¼çæä»¶`) |
| | | return false |
| | | } |
| | | } |
| | | |
| | | const isWithinSize = rawFile.size / 1024 / 1024 <= props.fileSize |
| | | if (!isWithinSize) { |
| | | proxy.$modal.msgError(`æä»¶å¤§å°ä¸è½è¶
è¿ ${props.fileSize}MB`) |
| | | return false |
| | | } |
| | | |
| | | return true |
| | | } |
| | | |
| | | function scheduleUpload(uploadFiles) { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | uploadQueueTimer.value = setTimeout(() => { |
| | | const readyFiles = uploadFiles.filter((file) => file.raw && !queuedUidSet.value.has(file.uid)) |
| | | if (!readyFiles.length) return |
| | | |
| | | const remainCount = props.limit - currentList.value.length |
| | | if (remainCount <= 0) { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} 个æä»¶`) |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const selectedFiles = readyFiles.slice(0, remainCount) |
| | | if (selectedFiles.length < readyFiles.length) { |
| | | proxy.$modal.msgWarning(`æå¤ä¸ä¼ ${props.limit} 个æä»¶ï¼è¶
åºé¨å已忽ç¥`) |
| | | } |
| | | |
| | | selectedFiles.forEach((file) => queuedUidSet.value.add(file.uid)) |
| | | uploadSelectedFiles(selectedFiles) |
| | | }, 0) |
| | | } |
| | | |
| | | async function uploadSelectedFiles(files) { |
| | | const validFiles = files.filter((file) => validateFile(file.raw)) |
| | | const invalidFiles = files.filter((file) => !validFiles.includes(file)) |
| | | |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | |
| | | if (!validFiles.length) { |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const formData = new FormData() |
| | | validFiles.forEach((file) => { |
| | | formData.append(props.uploadFieldName, file.raw) |
| | | }) |
| | | |
| | | uploading.value = true |
| | | proxy.$modal.loading('æä»¶ä¸ä¼ ä¸ï¼è¯·ç¨å...') |
| | | |
| | | try { |
| | | const response = await uploadFile(formData) |
| | | const responseList = extractResponseArray(response).map((item, index) => normalizeResponseItem(item, index)) |
| | | |
| | | if (!responseList.length) { |
| | | proxy.$modal.msgError('ä¸ä¼ æ¥å£æªè¿åæ°ç»æ°æ®') |
| | | return |
| | | } |
| | | |
| | | currentList.value = [...currentList.value, ...responseList] |
| | | proxy.$modal.msgSuccess('ä¸ä¼ æå') |
| | | } catch (error) { |
| | | proxy.$modal.msgError(error?.message || 'ä¸ä¼ 失败') |
| | | } finally { |
| | | validFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | uploadRef.value?.clearFiles() |
| | | uploading.value = false |
| | | proxy.$modal.closeLoading() |
| | | } |
| | | } |
| | | |
| | | function handleChange(file, uploadFiles) { |
| | | if (props.disabled || uploading.value) return |
| | | scheduleUpload(uploadFiles) |
| | | } |
| | | |
| | | function handleRemove(file) { |
| | | const targetUrl = file.url || getItemUrl(file.rawData) |
| | | const nextList = currentList.value.filter((item, index) => { |
| | | const itemUrl = getItemUrl(item) |
| | | const itemName = getItemName(item, index) |
| | | return !(itemUrl === targetUrl && itemName === file.name) |
| | | }) |
| | | currentList.value = nextList |
| | | } |
| | | |
| | | function handleExceed() { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} 个æä»¶`) |
| | | } |
| | | |
| | | function openFile(file) { |
| | | const fileUrl = file.url || getItemUrl(file.rawData) |
| | | if (!fileUrl) return |
| | | window.open(fileUrl, '_blank') |
| | | } |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="attachment-upload-file"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | drag |
| | | :auto-upload="false" |
| | | :multiple="true" |
| | | :show-file-list="true" |
| | | :file-list="displayFileList" |
| | | :disabled="disabled || uploading" |
| | | :limit="limit" |
| | | :on-change="handleChange" |
| | | :on-remove="handleRemove" |
| | | :on-exceed="handleExceed" |
| | | :on-preview="openFile" |
| | | > |
| | | <el-icon class="upload-drag-icon"><UploadFilled /></el-icon> |
| | | <div class="el-upload__text"> |
| | | å°æä»¶æå°æ¤å¤ï¼æ <em>{{ buttonText }}</em> |
| | | </div> |
| | | <div class="upload-tip">{{ uploadTip }}</div> |
| | | </el-upload> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .attachment-upload-file { |
| | | width: 100%; |
| | | } |
| | | |
| | | .upload-drag-icon { |
| | | font-size: 40px; |
| | | color: var(--el-text-color-secondary); |
| | | } |
| | | |
| | | .upload-tip { |
| | | margin-top: 8px; |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 12px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | import {Plus} from '@element-plus/icons-vue' |
| | | import {uploadFile} from '@/api/basicData/common' |
| | | |
| | | const props = defineProps({ |
| | | fileList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | index: { |
| | | type: Number, |
| | | default: -1, |
| | | }, |
| | | childrenKey: { |
| | | type: String, |
| | | default: 'images', |
| | | }, |
| | | limit: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | fileSize: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | fileType: { |
| | | type: Array, |
| | | default: () => ['png', 'jpg', 'jpeg', 'webp'], |
| | | }, |
| | | buttonText: { |
| | | type: String, |
| | | default: 'ä¸ä¼ å¾ç', |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | uploadFieldName: { |
| | | type: String, |
| | | default: 'files', |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:fileList', 'change']) |
| | | const {proxy} = getCurrentInstance() |
| | | |
| | | const uploadRef = ref() |
| | | const previewVisible = ref(false) |
| | | const previewUrl = ref('') |
| | | const uploadQueueTimer = ref(null) |
| | | const uploading = ref(false) |
| | | const queuedUidSet = ref(new Set()) |
| | | |
| | | const currentList = computed({ |
| | | get() { |
| | | if (props.index > -1) { |
| | | const row = props.fileList?.[props.index] |
| | | return Array.isArray(row?.[props.childrenKey]) ? row[props.childrenKey] : [] |
| | | } |
| | | return Array.isArray(props.fileList) ? props.fileList : [] |
| | | }, |
| | | set(value) { |
| | | const nextList = Array.isArray(value) ? value : [] |
| | | if (props.index > -1) { |
| | | const nextModelValue = Array.isArray(props.fileList) ? [...props.fileList] : [] |
| | | const currentRow = nextModelValue[props.index] || {} |
| | | nextModelValue[props.index] = { |
| | | ...currentRow, |
| | | [props.childrenKey]: nextList, |
| | | } |
| | | emit('update:fileList', nextModelValue) |
| | | emit('change', nextList, nextModelValue) |
| | | return |
| | | } |
| | | emit('update:fileList', nextList) |
| | | emit('change', nextList, nextList) |
| | | }, |
| | | }) |
| | | |
| | | const displayFileList = computed(() => { |
| | | return currentList.value.map((item, index) => ({ |
| | | uid: getItemUid(item, index), |
| | | name: getItemName(item, index), |
| | | url: getItemUrl(item), |
| | | status: 'success', |
| | | rawData: item, |
| | | })) |
| | | }) |
| | | |
| | | const uploadTip = computed(() => { |
| | | return `æ¯æ ${props.fileType.join('/')}ï¼åå¼ ä¸è¶
è¿ ${props.fileSize}MB` |
| | | }) |
| | | |
| | | function getItemUid(item, index) { |
| | | if (item?.id !== undefined && item?.id !== null) return `${item.id}` |
| | | return `${getItemName(item, index)}-${getItemUrl(item) || index}` |
| | | } |
| | | |
| | | function getItemUrl(item) { |
| | | if (!item) return '' |
| | | if (typeof item === 'string') return item |
| | | return item.url || item.previewURL || '' |
| | | } |
| | | |
| | | function getItemName(item, index = 0) { |
| | | if (!item) return `image-${index + 1}` |
| | | if (typeof item === 'string') return `image-${index + 1}` |
| | | return item.name || item.fileName || item.originalFilename || `image-${index + 1}` |
| | | } |
| | | |
| | | function normalizeResponseItem(item, index) { |
| | | if (typeof item === 'string') { |
| | | return { |
| | | name: `image-${currentList.value.length + index + 1}`, |
| | | url: item, |
| | | } |
| | | } |
| | | return Object.assign({}, item, { |
| | | url: item.url || item.previewURL || item.previewUrl || '', |
| | | name: item.name || item.originalFilename || item.fileName || `image-${currentList.value.length + index + 1}`, |
| | | }) |
| | | } |
| | | |
| | | function extractResponseArray(response) { |
| | | if (Array.isArray(response)) return response |
| | | if (Array.isArray(response?.data)) return response.data |
| | | if (Array.isArray(response?.data?.data)) return response.data.data |
| | | if (Array.isArray(response?.payload)) return response.payload |
| | | if (Array.isArray(response?.payload?.data)) return response.payload.data |
| | | if (Array.isArray(response?.rows)) return response.rows |
| | | if (Array.isArray(response?.result)) return response.result |
| | | return [] |
| | | } |
| | | |
| | | function validateFile(rawFile) { |
| | | let isValidType = false |
| | | const extension = rawFile.name.includes('.') |
| | | ? rawFile.name.slice(rawFile.name.lastIndexOf('.') + 1).toLowerCase() |
| | | : '' |
| | | |
| | | if (props.fileType.length) { |
| | | isValidType = props.fileType.some((type) => { |
| | | const normalizedType = String(type).toLowerCase() |
| | | return rawFile.type.toLowerCase().includes(normalizedType) || extension === normalizedType |
| | | }) |
| | | } else { |
| | | isValidType = rawFile.type.includes('image') |
| | | } |
| | | |
| | | if (!isValidType) { |
| | | proxy.$modal.msgError(`请ä¸ä¼ ${props.fileType.join('/')} æ ¼å¼çå¾ç`) |
| | | return false |
| | | } |
| | | |
| | | const isWithinSize = rawFile.size / 1024 / 1024 <= props.fileSize |
| | | if (!isWithinSize) { |
| | | proxy.$modal.msgError(`å¾ç大å°ä¸è½è¶
è¿ ${props.fileSize}MB`) |
| | | return false |
| | | } |
| | | |
| | | return true |
| | | } |
| | | |
| | | function scheduleUpload(uploadFiles) { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | uploadQueueTimer.value = setTimeout(() => { |
| | | const readyFiles = uploadFiles.filter((file) => file.raw && !queuedUidSet.value.has(file.uid)) |
| | | if (!readyFiles.length) return |
| | | |
| | | const remainCount = props.limit - currentList.value.length |
| | | if (remainCount <= 0) { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} å¼ å¾ç`) |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const selectedFiles = readyFiles.slice(0, remainCount) |
| | | if (selectedFiles.length < readyFiles.length) { |
| | | proxy.$modal.msgWarning(`æå¤ä¸ä¼ ${props.limit} å¼ å¾çï¼è¶
åºé¨å已忽ç¥`) |
| | | } |
| | | |
| | | selectedFiles.forEach((file) => queuedUidSet.value.add(file.uid)) |
| | | uploadSelectedFiles(selectedFiles) |
| | | }, 0) |
| | | } |
| | | |
| | | async function uploadSelectedFiles(files) { |
| | | const validFiles = files.filter((file) => validateFile(file.raw)) |
| | | const invalidFiles = files.filter((file) => !validFiles.includes(file)) |
| | | |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | |
| | | if (!validFiles.length) { |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const formData = new FormData() |
| | | validFiles.forEach((file) => { |
| | | formData.append(props.uploadFieldName, file.raw) |
| | | }) |
| | | |
| | | uploading.value = true |
| | | proxy.$modal.loading('å¾çä¸ä¼ ä¸ï¼è¯·ç¨å...') |
| | | |
| | | try { |
| | | const response = await uploadFile(formData) |
| | | const responseList = extractResponseArray(response).map((item, index) => normalizeResponseItem(item, index)) |
| | | |
| | | if (!responseList.length) { |
| | | proxy.$modal.msgError('ä¸ä¼ æ¥å£æªè¿åæ°ç»æ°æ®') |
| | | return |
| | | } |
| | | console.log('responseList', responseList) |
| | | |
| | | currentList.value = [...currentList.value, ...responseList] |
| | | console.log('currentList.value', currentList.value) |
| | | proxy.$modal.msgSuccess('ä¸ä¼ æå') |
| | | } catch (error) { |
| | | proxy.$modal.msgError(error?.message || 'ä¸ä¼ 失败') |
| | | } finally { |
| | | validFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | uploadRef.value?.clearFiles() |
| | | uploading.value = false |
| | | proxy.$modal.closeLoading() |
| | | } |
| | | } |
| | | |
| | | function handleChange(file, uploadFiles) { |
| | | if (props.disabled || uploading.value) return |
| | | scheduleUpload(uploadFiles) |
| | | } |
| | | |
| | | function handleRemove(file) { |
| | | const targetUrl = file.url || getItemUrl(file.rawData) |
| | | const nextList = currentList.value.filter((item, index) => { |
| | | const itemUrl = getItemUrl(item) |
| | | const itemName = getItemName(item, index) |
| | | return !(itemUrl === targetUrl && itemName === file.name) |
| | | }) |
| | | currentList.value = nextList |
| | | } |
| | | |
| | | function handlePreview(file) { |
| | | previewUrl.value = file.url || getItemUrl(file.rawData) |
| | | previewVisible.value = true |
| | | } |
| | | |
| | | function handleExceed() { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} å¼ å¾ç`) |
| | | } |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="attachment-upload-image"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :auto-upload="false" |
| | | :multiple="true" |
| | | :show-file-list="true" |
| | | :file-list="displayFileList" |
| | | list-type="picture-card" |
| | | accept="image/*" |
| | | :disabled="disabled || uploading" |
| | | :limit="limit" |
| | | :on-change="handleChange" |
| | | :on-remove="handleRemove" |
| | | :on-preview="handlePreview" |
| | | :on-exceed="handleExceed" |
| | | > |
| | | <div class="upload-trigger"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | <span>{{ buttonText }}</span> |
| | | </div> |
| | | </el-upload> |
| | | |
| | | <div class="upload-tip"> |
| | | {{ uploadTip }} |
| | | </div> |
| | | |
| | | <el-dialog v-model="previewVisible" title="å¾çé¢è§" width="720px" append-to-body> |
| | | <img class="preview-image" :src="previewUrl" alt="preview"/> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .attachment-upload-image { |
| | | width: 100%; |
| | | } |
| | | |
| | | .upload-trigger { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 6px; |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .upload-tip { |
| | | margin-top: 8px; |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .preview-image { |
| | | display: block; |
| | | max-width: 100%; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | :deep(.el-upload-list--picture-card) { |
| | | margin: 0; |
| | | } |
| | | |
| | | :deep(.el-upload--picture-card) { |
| | | width: 132px; |
| | | height: 132px; |
| | | } |
| | | |
| | | :deep(.el-upload-list--picture-card .el-upload-list__item) { |
| | | width: 132px; |
| | | height: 132px; |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container"> |
| | | <div class="table_list"> |
| | | <div style="text-align: right; margin-bottom: 10px;"> |
| | | <el-button type="primary" @click="handleAdd">æ°å¢</el-button> |
| | | <el-button type="info" plain icon="Upload" @click="handleImport" |
| | | <el-button type="primary" |
| | | @click="handleAdd">æ°å¢</el-button> |
| | | <el-button type="info" |
| | | plain |
| | | icon="Upload" |
| | | @click="handleImport" |
| | | v-hasPermi="['product:bom:import']">导å
¥</el-button> |
| | | <el-button type="warning" plain icon="Download" @click="handleExport" :disabled="selectedRows.length !== 1" |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | @click="handleExport" |
| | | :disabled="selectedRows.length !== 1" |
| | | v-hasPermi="['product:bom:export']">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">å é¤</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleBatchDelete" |
| | | :disabled="selectedRows.length === 0">å é¤</el-button> |
| | | </div> |
| | | <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" |
| | | @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"> |
| | | <template #detail="{ row }"> |
| | | <el-button type="primary" text @click="showDetail(row)">{{ row.bomNo }} |
| | | <el-button type="primary" |
| | | text |
| | | @click="showDetail(row)">{{ row.bomNo }} |
| | | </el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow" /> |
| | | |
| | | <StructureEdit v-if="showEdit" |
| | | v-model:show-model="showEdit" |
| | | :record="currentRow" /> |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? 'æ°å¢BOM' : 'ç¼è¾BOM'" width="600px" |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢BOM' : 'ç¼è¾BOM'" |
| | | width="600px" |
| | | @close="closeDialog"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-form-item label="产ååç§°" prop="productModelId"> |
| | | <el-button type="primary" @click="showProductSelectDialog = true"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | <el-form-item label="产ååç§°" |
| | | prop="productModelId"> |
| | | <el-button type="primary" |
| | | @click="showProductSelectDialog = true"> |
| | | {{ form.productName || 'éæ©äº§å' }} |
| | | </el-button> |
| | | </el-form-item> |
| | | <el-form-item label="çæ¬å·" prop="version"> |
| | | <el-input v-model="form.version" placeholder="请è¾å
¥çæ¬å·" clearable /> |
| | | <el-form-item label="çæ¬å·" |
| | | prop="version"> |
| | | <el-input v-model="form.version" |
| | | placeholder="请è¾å
¥çæ¬å·" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" clearable /> |
| | | <el-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | <el-button type="primary" |
| | | @click="handleSubmit">ç¡®å®</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 产åéæ©å¼¹çª --> |
| | | <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> |
| | | |
| | | <ProductSelectDialog v-model="showProductSelectDialog" |
| | | @confirm="handleProductSelect" |
| | | single /> |
| | | <!-- BOM导å
¥å¯¹è¯æ¡ --> |
| | | <ImportDialog ref="uploadRef" v-model="upload.open" :title="upload.title" :action="upload.url" |
| | | :headers="upload.headers" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" :show-download-template="true" @confirm="submitFileForm" |
| | | @download-template="handleDownloadTemplate" @close="handleImportClose" /> |
| | | <ImportDialog ref="uploadRef" |
| | | v-model="upload.open" |
| | | :title="upload.title" |
| | | :action="upload.url" |
| | | :headers="upload.headers" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :show-download-template="true" |
| | | @confirm="submitFileForm" |
| | | @download-template="handleDownloadTemplate" |
| | | @close="handleImportClose" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue"; |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | onMounted, |
| | | getCurrentInstance, |
| | | defineAsyncComponent, |
| | | } from "vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { listPage, add, update, batchDelete, exportBom, downloadTemplate } from "@/api/productionManagement/productBom.js"; |
| | | import { useRouter } from 'vue-router' |
| | | import { ElMessageBox } from 'element-plus' |
| | | import { |
| | | listPage, |
| | | add, |
| | | copy, |
| | | update, |
| | | batchDelete, |
| | | exportBom, |
| | | downloadTemplate, |
| | | } from "@/api/productionManagement/productBom.js"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import ImportDialog from "@/components/Dialog/ImportDialog.vue"; |
| | | |
| | | const router = useRouter() |
| | | const { proxy } = getCurrentInstance() |
| | | const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue')) |
| | | const router = useRouter(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const StructureEdit = defineAsyncComponent(() => |
| | | import("@/views/productionManagement/productStructure/StructureEdit.vue") |
| | | ); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "BOMç¼å·", |
| | | prop: "bomNo", |
| | | dataType: 'slot', |
| | | dataType: "slot", |
| | | slot: "detail", |
| | | minWidth: 140 |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | |
| | | minWidth: 160 |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "productModelName", |
| | | minWidth: 140 |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "çæ¬å·", |
| | | prop: "version", |
| | | width: 100 |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | prop: "remark", |
| | | minWidth: 160 |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 150, |
| | | width: 250, |
| | | operation: [ |
| | | { |
| | | name: "å¤å¶", |
| | | type: "text", |
| | | clickFun: row => { |
| | | handleCopy(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleEdit(row) |
| | | } |
| | | clickFun: row => { |
| | | handleEdit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | type: "danger", |
| | | link: true, |
| | | clickFun: (row) => { |
| | | handleDelete(row) |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | clickFun: row => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | |
| | | const selectedRows = ref([]); |
| | | const currentRow = ref({}); |
| | | const dialogVisible = ref(false); |
| | | const operationType = ref('add'); // add | edit |
| | | const operationType = ref("add"); // add | edit |
| | | const formRef = ref(null); |
| | | const showProductSelectDialog = ref(false); |
| | | |
| | |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/technologyBom/uploadBom" |
| | | url: import.meta.env.VITE_APP_BASE_API + "/technologyBom/uploadBom", |
| | | }); |
| | | |
| | | const page = reactive({ |
| | |
| | | productModelName: "", |
| | | productModelId: "", |
| | | remark: "", |
| | | version: "" |
| | | version: "", |
| | | }, |
| | | rules: { |
| | | productModelId: [{ required: true, message: "è¯·éæ©äº§å", trigger: "change" }], |
| | | version: [{ required: true, message: "请è¾å
¥çæ¬å·", trigger: "blur" }] |
| | | } |
| | | productModelId: [ |
| | | { required: true, message: "è¯·éæ©äº§å", trigger: "change" }, |
| | | ], |
| | | version: [{ required: true, message: "请è¾å
¥çæ¬å·", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å页 |
| | | const pagination = (obj) => { |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | |
| | | current: page.current, |
| | | size: page.size, |
| | | }) |
| | | .then((res) => { |
| | | .then(res => { |
| | | const records = res?.data?.records || []; |
| | | tableData.value = records; |
| | | page.total = res?.data?.total || 0; |
| | | }) |
| | | .catch((err) => { |
| | | .catch(err => { |
| | | console.error("è·åå表失败ï¼", err); |
| | | }) |
| | | .finally(() => { |
| | |
| | | |
| | | // æ°å¢ |
| | | const handleAdd = () => { |
| | | operationType.value = 'add'; |
| | | operationType.value = "add"; |
| | | Object.assign(form.value, { |
| | | id: undefined, |
| | | productName: "", |
| | | productModelName: "", |
| | | productModelId: "", |
| | | remark: "", |
| | | version: "" |
| | | version: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | const handleCopy = row => { |
| | | // handleAdd(row); |
| | | ElMessageBox.confirm("确认å¤å¶è¯¥BOMï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | copy({ |
| | | id: row.id, |
| | | }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å¤å¶æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("å¤å¶å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const handleEdit = (row) => { |
| | | operationType.value = 'edit'; |
| | | const handleEdit = row => { |
| | | operationType.value = "edit"; |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | productName: row.productName || "", |
| | | productModelName: row.productModelName || "", |
| | | productModelId: row.productModelId || "", |
| | | remark: row.remark || "", |
| | | version: row.version || "" |
| | | version: row.version || "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // å é¤ï¼åæ¡ï¼ |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥BOMï¼', 'æç¤º', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm("确认å é¤è¯¥BOMï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchDelete([row.id]) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess('å 餿å'); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError('å é¤å¤±è´¥'); |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { }); |
| | |
| | | // æ¹éå é¤ |
| | | const handleBatchDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning('è¯·éæ©æ°æ®'); |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm('éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼', 'å é¤æç¤º', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchDelete(ids) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess('å 餿å'); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError('å é¤å¤±è´¥'); |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { }); |
| | | }; |
| | | |
| | | // 产åéæ© |
| | | const handleProductSelect = (products) => { |
| | | const handleProductSelect = products => { |
| | | if (products && products.length > 0) { |
| | | const product = products[0]; |
| | | form.value.productModelId = product.id; |
| | |
| | | |
| | | // æäº¤è¡¨å |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | const payload = { ...form.value }; |
| | | if (operationType.value === 'add') { |
| | | if (operationType.value === "add") { |
| | | add(payload) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess('æ°å¢æå'); |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | closeDialog(); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError('æ°å¢å¤±è´¥'); |
| | | proxy.$modal.msgError("æ°å¢å¤±è´¥"); |
| | | }); |
| | | } else { |
| | | update(payload) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess('ä¿®æ¹æå'); |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeDialog(); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError('ä¿®æ¹å¤±è´¥'); |
| | | proxy.$modal.msgError("ä¿®æ¹å¤±è´¥"); |
| | | }); |
| | | } |
| | | } |
| | |
| | | proxy.$modal.msgSuccess(response.msg || "导å
¥æå"); |
| | | getList(); |
| | | } else { |
| | | proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导å
¥ç»æ", { dangerouslyUseHTMLString: true }); |
| | | proxy.$alert( |
| | | "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + |
| | | response.msg + |
| | | "</div>", |
| | | "导å
¥ç»æ", |
| | | { dangerouslyUseHTMLString: true } |
| | | ); |
| | | } |
| | | }; |
| | | |
| | |
| | | const bomId = selectedRows.value[0].id; |
| | | const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`; |
| | | |
| | | exportBom(bomId).then(res => { |
| | | exportBom(bomId) |
| | | .then(res => { |
| | | // è¿åçæ°æ®æ¯å¦ä¸ºç©º |
| | | if (!res) { |
| | | proxy.$modal.msgError("导åºå¤±è´¥ï¼è¿åæ°æ®ä¸ºç©º"); |
| | | return; |
| | | } |
| | | |
| | | const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); |
| | | const downloadElement = document.createElement('a'); |
| | | const blob = new Blob([res], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | const downloadElement = document.createElement("a"); |
| | | const href = window.URL.createObjectURL(blob); |
| | | |
| | | downloadElement.style.display = 'none'; |
| | | downloadElement.style.display = "none"; |
| | | downloadElement.href = href; |
| | | downloadElement.download = fileName; |
| | | |
| | |
| | | window.URL.revokeObjectURL(href); |
| | | |
| | | proxy.$modal.msgSuccess("å¯¼åºæå"); |
| | | }).catch(err => { |
| | | }) |
| | | .catch(err => { |
| | | console.error("导åºå¼å¸¸ï¼", err); |
| | | proxy.$modal.msgError("ç³»ç»å¼å¸¸ï¼å¯¼åºå¤±è´¥"); |
| | | }); |
| | |
| | | return; |
| | | } |
| | | |
| | | const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); |
| | | const downloadElement = document.createElement('a'); |
| | | const blob = new Blob([res], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | const downloadElement = document.createElement("a"); |
| | | const href = window.URL.createObjectURL(blob); |
| | | |
| | | downloadElement.href = href; |
| | |
| | | }; |
| | | |
| | | // æ¥ç详æ
|
| | | const showDetail = (row) => { |
| | | const showDetail = row => { |
| | | router.push({ |
| | | path: '/productionManagement/productStructureDetail', |
| | | path: "/productionManagement/productStructureDetail", |
| | | query: { |
| | | id: row.id, |
| | | bomNo: row.bomNo || '', |
| | | productName: row.productName || '', |
| | | productModelName: row.productModelName || '' |
| | | } |
| | | bomNo: row.bomNo || "", |
| | | productName: row.productName || "", |
| | | productModelName: row.productModelName || "", |
| | | }, |
| | | }); |
| | | }; |
| | | |