已添加14个文件
已重命名1个文件
已修改18个文件
已删除87个文件
| | |
| | | "env": { |
| | | "VITE_APP_TITLE": "è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼" |
| | | }, |
| | | "screen": "screen/JZYJView.png", |
| | | "logo": "logo/HYSNLogo.png", |
| | | "favicon": "favicon/HYSNico.ico" |
| | | "screen": "screen/JTWYView.png", |
| | | "logo": "logo/JTWYLogo.png", |
| | | "favicon": "favicon/JTWYico.ico" |
| | | }, |
| | | "TEST": { |
| | | "JTWY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
", |
| | | "VITE_BASE_API": "http://1.15.17.182:9003", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9002" |
| | | "VITE_APP_TITLE": "åæ³°ä¼ä¸ä¿¡æ¯ç®¡ç", |
| | | "VITE_BASE_API": "http://1.15.17.182:9001", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9000" |
| | | }, |
| | | "screen": "screen/HYSNView.png", |
| | | "logo": "logo/ZGLTLogo.png", |
| | | "favicon": "favicon/favicon.ico" |
| | | "screen": "screen/JTWYView.png", |
| | | "logo": "logo/JTWYLogo.png", |
| | | "favicon": "favicon/JTWYico.ico" |
| | | }, |
| | | "screen": "/src/assets/images/login-background.png", |
| | | "logo": "/src/assets/logo/logo.png", |
| | |
| | | } |
| | | |
| | | // è·åçç¹æ£æµææ ç»è®¡ |
| | | export function getTopParameters(inspectType) { |
| | | export function getTopParameters(modelType) { |
| | | return request({ |
| | | url: '/qualityReport/getTopParameters', |
| | | method: 'get', |
| | | params: { inspectType } |
| | | params: { modelType } |
| | | }) |
| | | } |
| | |
| | | method: "get", |
| | | }); |
| | | }; |
| | | // çäº§æ ¸ç®åæ |
| | | export const productionAccountingAnalysis = (query) => { |
| | | return request({ |
| | | url: "/home/productionAccountingAnalysis", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | }; |
| | | // åºæ¶åºä»ç»è®¡ |
| | | export const statisticsReceivablePayable = (query) => { |
| | | return request({ |
| | |
| | | <template> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="title" |
| | | :width="width" |
| | | :before-close="handleClose" |
| | | > |
| | | <div class="file-list-toolbar" v-if="showToolbar"> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="title" |
| | | :width="width" |
| | | :before-close="handleClose"> |
| | | <div class="file-list-toolbar" |
| | | v-if="showToolbar"> |
| | | <template v-if="useBuiltInUpload"> |
| | | <el-upload |
| | | v-model:file-list="uploadFileList" |
| | | class="upload-demo" |
| | | :action="uploadAction" |
| | | :headers="uploadHeaders" |
| | | :show-file-list="false" |
| | | :on-success="handleDefaultUploadSuccess" |
| | | :on-error="handleDefaultUploadError" |
| | | > |
| | | <el-button |
| | | v-if="showUploadButton" |
| | | type="primary" |
| | | size="small" |
| | | > |
| | | <el-upload v-model:file-list="uploadFileList" |
| | | class="upload-demo" |
| | | :action="uploadAction" |
| | | :headers="uploadHeaders" |
| | | :show-file-list="false" |
| | | :on-success="handleDefaultUploadSuccess" |
| | | :on-error="handleDefaultUploadError"> |
| | | <el-button v-if="showUploadButton" |
| | | type="primary" |
| | | size="small"> |
| | | ä¸ä¼ éä»¶ |
| | | </el-button> |
| | | </el-upload> |
| | | </template> |
| | | <template v-else> |
| | | <el-button |
| | | v-if="showUploadButton" |
| | | type="primary" |
| | | size="small" |
| | | @click="handleUpload" |
| | | > |
| | | <el-button v-if="showUploadButton" |
| | | type="primary" |
| | | size="small" |
| | | @click="handleUpload"> |
| | | æ°å¢éä»¶ |
| | | </el-button> |
| | | </template> |
| | | </div> |
| | | <el-table :data="tableData" border :height="tableHeight"> |
| | | <el-table-column |
| | | :label="nameColumnLabel" |
| | | :prop="nameColumnProp" |
| | | :min-width="nameColumnMinWidth" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | v-if="showActions" |
| | | fixed="right" |
| | | label="æä½" |
| | | :width="actionColumnWidth" |
| | | align="center" |
| | | > |
| | | <el-table :data="tableData" |
| | | border |
| | | :height="tableHeight"> |
| | | <el-table-column :label="nameColumnLabel" |
| | | :prop="nameColumnProp" |
| | | :min-width="nameColumnMinWidth" |
| | | show-overflow-tooltip /> |
| | | <el-table-column v-if="showActions" |
| | | fixed="right" |
| | | label="æä½" |
| | | :width="actionColumnWidth" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | v-if="showDownload" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleDownload(scope.row)" |
| | | > |
| | | <el-button v-if="showDownload" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleDownload(scope.row)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | <el-button |
| | | v-if="showPreview" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handlePreview(scope.row)" |
| | | > |
| | | <el-button v-if="showPreview" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handlePreview(scope.row)"> |
| | | é¢è§ |
| | | </el-button> |
| | | <el-button |
| | | v-if="showDeleteButton" |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDelete(scope.row, scope.$index)" |
| | | > |
| | | <el-button v-if="showDeleteButton" |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDelete(scope.row, scope.$index)"> |
| | | å é¤ |
| | | </el-button> |
| | | <slot name="actions" :row="scope.row"></slot> |
| | | <slot name="actions" |
| | | :row="scope.row"></slot> |
| | | </template> |
| | | </el-table-column> |
| | | <slot name="columns"></slot> |
| | | </el-table> |
| | | <pagination v-if="isShowPagination" |
| | | style="margin-bottom: 20px;" |
| | | :total="page.total" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationSearch" |
| | | @change="handleChange" /> |
| | | </el-dialog> |
| | | <filePreview v-if="showPreview" ref="filePreviewRef" /> |
| | | <filePreview v-if="showPreview" |
| | | ref="filePreviewRef" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { getToken } from '@/utils/auth' |
| | | import { ref, computed, getCurrentInstance } from "vue"; |
| | | import pagination from "@/components/Pagination/index.vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import filePreview from "@/components/filePreview/index.vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | title: { |
| | | type: String, |
| | | default: 'éä»¶' |
| | | }, |
| | | width: { |
| | | type: String, |
| | | default: '40%' |
| | | }, |
| | | tableHeight: { |
| | | type: String, |
| | | default: '40vh' |
| | | }, |
| | | nameColumnLabel: { |
| | | type: String, |
| | | default: 'éä»¶åç§°' |
| | | }, |
| | | nameColumnProp: { |
| | | type: String, |
| | | default: 'name' |
| | | }, |
| | | nameColumnMinWidth: { |
| | | type: [String, Number], |
| | | default: 400 |
| | | }, |
| | | actionColumnWidth: { |
| | | type: [String, Number], |
| | | default: 160 |
| | | }, |
| | | showActions: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | showDownload: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | showPreview: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | showUploadButton: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | showDeleteButton: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | urlField: { |
| | | type: String, |
| | | default: 'url' |
| | | }, |
| | | downloadMethod: { |
| | | type: Function, |
| | | default: null |
| | | }, |
| | | previewMethod: { |
| | | type: Function, |
| | | default: null |
| | | }, |
| | | uploadMethod: { |
| | | type: Function, |
| | | default: null |
| | | }, |
| | | deleteMethod: { |
| | | type: Function, |
| | | default: null |
| | | }, |
| | | rulesRegulationsManagementId: { |
| | | type: [String, Number], |
| | | default: '' |
| | | }, |
| | | uploadUrl: { |
| | | type: String, |
| | | default: `${import.meta.env.VITE_APP_BASE_API}/file/upload` |
| | | } |
| | | }) |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | title: { |
| | | type: String, |
| | | default: "éä»¶", |
| | | }, |
| | | width: { |
| | | type: String, |
| | | default: "40%", |
| | | }, |
| | | tableHeight: { |
| | | type: String, |
| | | default: "40vh", |
| | | }, |
| | | nameColumnLabel: { |
| | | type: String, |
| | | default: "éä»¶åç§°", |
| | | }, |
| | | nameColumnProp: { |
| | | type: String, |
| | | default: "name", |
| | | }, |
| | | nameColumnMinWidth: { |
| | | type: [String, Number], |
| | | default: 400, |
| | | }, |
| | | actionColumnWidth: { |
| | | type: [String, Number], |
| | | default: 160, |
| | | }, |
| | | showActions: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | showDownload: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | showPreview: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | showUploadButton: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | showDeleteButton: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | urlField: { |
| | | type: String, |
| | | default: "url", |
| | | }, |
| | | downloadMethod: { |
| | | type: Function, |
| | | default: null, |
| | | }, |
| | | previewMethod: { |
| | | type: Function, |
| | | default: null, |
| | | }, |
| | | uploadMethod: { |
| | | type: Function, |
| | | default: null, |
| | | }, |
| | | deleteMethod: { |
| | | type: Function, |
| | | default: null, |
| | | }, |
| | | rulesRegulationsManagementId: { |
| | | type: [String, Number], |
| | | default: "", |
| | | }, |
| | | uploadUrl: { |
| | | type: String, |
| | | default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`, |
| | | }, |
| | | isShowPagination: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | page: { |
| | | type: Object, |
| | | default: () => ({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }), |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview', 'upload', 'delete']) |
| | | const emit = defineEmits([ |
| | | "update:modelValue", |
| | | "close", |
| | | "download", |
| | | "preview", |
| | | "upload", |
| | | "delete", |
| | | ]); |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const filePreviewRef = ref(null) |
| | | const uploadFileList = ref([]) |
| | | const { proxy } = getCurrentInstance(); |
| | | const filePreviewRef = ref(null); |
| | | const uploadFileList = ref([]); |
| | | |
| | | const dialogVisible = computed({ |
| | | get: () => props.modelValue, |
| | | set: (val) => emit('update:modelValue', val) |
| | | }) |
| | | const dialogVisible = computed({ |
| | | get: () => props.modelValue, |
| | | set: val => emit("update:modelValue", val), |
| | | }); |
| | | |
| | | const tableData = ref([]) |
| | | const showToolbar = computed(() => props.showUploadButton) |
| | | const useBuiltInUpload = computed(() => !props.uploadMethod) |
| | | const uploadAction = computed(() => props.uploadUrl) |
| | | const uploadHeaders = computed(() => ({ |
| | | Authorization: `Bearer ${getToken()}` |
| | | })) |
| | | const tableData = ref([]); |
| | | const showToolbar = computed(() => props.showUploadButton); |
| | | const useBuiltInUpload = computed(() => !props.uploadMethod); |
| | | const uploadAction = computed(() => props.uploadUrl); |
| | | const uploadHeaders = computed(() => ({ |
| | | Authorization: `Bearer ${getToken()}`, |
| | | })); |
| | | |
| | | const handleClose = () => { |
| | | emit('close') |
| | | dialogVisible.value = false |
| | | } |
| | | const handleClose = () => { |
| | | emit("close"); |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | const handleDownload = (row) => { |
| | | if (props.downloadMethod) { |
| | | props.downloadMethod(row) |
| | | } else { |
| | | // é»è®¤ä¸è½½æ¹æ³ |
| | | proxy.$download.name(row[props.urlField]) |
| | | } |
| | | emit('download', row) |
| | | } |
| | | |
| | | const handlePreview = (row) => { |
| | | if (props.previewMethod) { |
| | | props.previewMethod(row) |
| | | } else { |
| | | // é»è®¤é¢è§æ¹æ³ |
| | | if (filePreviewRef.value) { |
| | | filePreviewRef.value.open(row[props.urlField]) |
| | | const handleDownload = row => { |
| | | if (props.downloadMethod) { |
| | | props.downloadMethod(row); |
| | | } else { |
| | | // é»è®¤ä¸è½½æ¹æ³ |
| | | proxy.$download.name(row[props.urlField]); |
| | | } |
| | | } |
| | | emit('preview', row) |
| | | } |
| | | emit("download", row); |
| | | }; |
| | | |
| | | const open = (list) => { |
| | | dialogVisible.value = true |
| | | tableData.value = list || [] |
| | | } |
| | | |
| | | const handleUpload = async () => { |
| | | if (props.uploadMethod) { |
| | | // 妿æä¾äºèªå®ä¹ä¸ä¼ æ¹æ³ï¼ç±ç¶ç»ä»¶è´è´£æ´æ°å表ï¼éè¿ setListï¼ |
| | | // è¿éä¸åèªå¨æ·»å ï¼é¿å
ä¸ç¶ç»ä»¶ç setList éå¤ |
| | | await props.uploadMethod() |
| | | } |
| | | emit('upload') |
| | | } |
| | | |
| | | const handleDelete = async (row, index) => { |
| | | if (props.deleteMethod) { |
| | | const result = await props.deleteMethod(row, index) |
| | | if (result === false) { |
| | | return |
| | | const handlePreview = row => { |
| | | if (props.previewMethod) { |
| | | props.previewMethod(row); |
| | | } else { |
| | | // é»è®¤é¢è§æ¹æ³ |
| | | if (filePreviewRef.value) { |
| | | filePreviewRef.value.open(row[props.urlField]); |
| | | } |
| | | } |
| | | // 妿æä¾äº deleteMethodï¼ç±ç¶ç»ä»¶è´è´£å·æ°å表ï¼ä¸å¨è¿éå é¤ |
| | | } else { |
| | | // å¦ææ²¡ææä¾ deleteMethodï¼æå¨ç»ä»¶å
é¨å é¤ |
| | | removeAttachment(index) |
| | | } |
| | | emit('delete', row) |
| | | } |
| | | emit("preview", row); |
| | | }; |
| | | const paginationSearch = page => { |
| | | props.page.current = page.page; |
| | | props.page.size = page.limit; |
| | | emit("pagination", page.page, page.limit); |
| | | }; |
| | | |
| | | const addAttachment = (item) => { |
| | | tableData.value = [...tableData.value, item] |
| | | } |
| | | const open = list => { |
| | | dialogVisible.value = true; |
| | | tableData.value = list || []; |
| | | }; |
| | | |
| | | const handleDefaultUploadSuccess = async (res, file) => { |
| | | if (res?.code !== 200) { |
| | | ElMessage.error(res?.msg || 'æä»¶ä¸ä¼ 失败') |
| | | return |
| | | } |
| | | if (!props.rulesRegulationsManagementId) { |
| | | ElMessage.error('缺å°è§ç« å¶åº¦IDï¼æ æ³ä¿åéä»¶') |
| | | return |
| | | } |
| | | const fileName = res?.data?.originalName || file?.name |
| | | const fileUrl = res?.data?.tempPath || res?.data?.url |
| | | const payload = { |
| | | fileName, |
| | | fileUrl, |
| | | rulesRegulationsManagementId: props.rulesRegulationsManagementId, |
| | | raw: res?.data || {} |
| | | } |
| | | emit('upload', payload) |
| | | } |
| | | const handleUpload = async () => { |
| | | if (props.uploadMethod) { |
| | | // 妿æä¾äºèªå®ä¹ä¸ä¼ æ¹æ³ï¼ç±ç¶ç»ä»¶è´è´£æ´æ°å表ï¼éè¿ setListï¼ |
| | | // è¿éä¸åèªå¨æ·»å ï¼é¿å
ä¸ç¶ç»ä»¶ç setList éå¤ |
| | | await props.uploadMethod(); |
| | | } |
| | | emit("upload"); |
| | | }; |
| | | |
| | | const handleDefaultUploadError = () => { |
| | | ElMessage.error('æä»¶ä¸ä¼ 失败') |
| | | } |
| | | const handleDelete = async (row, index) => { |
| | | if (props.deleteMethod) { |
| | | const result = await props.deleteMethod(row, index); |
| | | if (result === false) { |
| | | return; |
| | | } |
| | | // 妿æä¾äº deleteMethodï¼ç±ç¶ç»ä»¶è´è´£å·æ°å表ï¼ä¸å¨è¿éå é¤ |
| | | } else { |
| | | // å¦ææ²¡ææä¾ deleteMethodï¼æå¨ç»ä»¶å
é¨å é¤ |
| | | removeAttachment(index); |
| | | } |
| | | emit("delete", row); |
| | | }; |
| | | |
| | | const removeAttachment = (index) => { |
| | | if (index > -1 && index < tableData.value.length) { |
| | | const newList = [...tableData.value] |
| | | newList.splice(index, 1) |
| | | tableData.value = newList |
| | | } |
| | | } |
| | | const addAttachment = item => { |
| | | tableData.value = [...tableData.value, item]; |
| | | }; |
| | | |
| | | const setList = (list) => { |
| | | tableData.value = list || [] |
| | | } |
| | | const handleDefaultUploadSuccess = async (res, file) => { |
| | | if (res?.code !== 200) { |
| | | ElMessage.error(res?.msg || "æä»¶ä¸ä¼ 失败"); |
| | | return; |
| | | } |
| | | if (!props.rulesRegulationsManagementId) { |
| | | ElMessage.error("缺å°è§ç« å¶åº¦IDï¼æ æ³ä¿åéä»¶"); |
| | | return; |
| | | } |
| | | const fileName = res?.data?.originalName || file?.name; |
| | | const fileUrl = res?.data?.tempPath || res?.data?.url; |
| | | const payload = { |
| | | fileName, |
| | | fileUrl, |
| | | rulesRegulationsManagementId: props.rulesRegulationsManagementId, |
| | | raw: res?.data || {}, |
| | | }; |
| | | emit("upload", payload); |
| | | }; |
| | | |
| | | defineExpose({ |
| | | open, |
| | | addAttachment, |
| | | removeAttachment, |
| | | setList, |
| | | handleUpload, |
| | | handleDelete |
| | | }) |
| | | const handleDefaultUploadError = () => { |
| | | ElMessage.error("æä»¶ä¸ä¼ 失败"); |
| | | }; |
| | | |
| | | const removeAttachment = index => { |
| | | if (index > -1 && index < tableData.value.length) { |
| | | const newList = [...tableData.value]; |
| | | newList.splice(index, 1); |
| | | tableData.value = newList; |
| | | } |
| | | }; |
| | | |
| | | const setList = list => { |
| | | tableData.value = list || []; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | open, |
| | | addAttachment, |
| | | removeAttachment, |
| | | setList, |
| | | handleUpload, |
| | | handleDelete, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .file-list-toolbar { |
| | | margin-bottom: 8px; |
| | | text-align: right; |
| | | } |
| | | .file-list-toolbar { |
| | | margin-bottom: 8px; |
| | | text-align: right; |
| | | } |
| | | </style> |
| | | |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :inline="true" :model="queryParams" class="search-form"> |
| | | <el-form :inline="true" |
| | | :model="queryParams" |
| | | class="search-form"> |
| | | <el-form-item label="å·¡æ£ä»»å¡åç§°"> |
| | | <el-input |
| | | v-model="queryParams.taskName" |
| | | placeholder="请è¾å
¥å·¡æ£ä»»å¡åç§°" |
| | | clearable |
| | | style="width: 200px " |
| | | /> |
| | | <el-input v-model="queryParams.taskName" |
| | | placeholder="请è¾å
¥å·¡æ£ä»»å¡åç§°" |
| | | clearable |
| | | style="width: 200px " /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-card> |
| | | <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;"> |
| | | <el-radio-group v-model="activeRadio" @change="radioChange"> |
| | | <el-radio-group v-model="activeRadio" |
| | | @change="radioChange"> |
| | | <el-radio-button v-for="tab in radios" |
| | | :key="tab.name" |
| | | :label="tab.label" |
| | | :value="tab.name"/> |
| | | :value="tab.name" /> |
| | | </el-radio-group> |
| | | <!-- æä½æé®åº --> |
| | | <el-space v-if="activeRadio !== 'task'"> |
| | | <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">æ°å»º</el-button> |
| | | <el-button type="danger" :icon="Delete" @click="handleDelete">å é¤</el-button> |
| | | <el-button type="primary" |
| | | :icon="Plus" |
| | | @click="handleAdd(undefined)">æ°å»º</el-button> |
| | | <el-button type="danger" |
| | | :icon="Delete" |
| | | @click="handleDelete">å é¤</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </el-space> |
| | | <el-space v-else> |
| | |
| | | </div> |
| | | <div> |
| | | <PIMTable :table-loading="tableLoading" |
| | | :table-data="tableData" |
| | | :column="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="handlePagination" |
| | | :is-selection="true" |
| | | :border="true" |
| | | :page="{ |
| | | :table-data="tableData" |
| | | :column="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="handlePagination" |
| | | :is-selection="true" |
| | | :border="true" |
| | | :page="{ |
| | | current: pageNum, |
| | | size: pageSize, |
| | | total: total, |
| | | layout: 'total, sizes, prev, pager, next, jumper' |
| | | }" |
| | | :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }" |
| | | > |
| | | :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"> |
| | | <template #inspector="{ row }"> |
| | | <div class="person-tags"> |
| | | <!-- è°è¯ä¿¡æ¯ï¼ä¸çº¿æ¶å é¤ --> |
| | | <!-- {{ console.log('inspector data:', row.inspector) }} --> |
| | | <template v-if="row.inspector && row.inspector.length > 0"> |
| | | <el-tag |
| | | v-for="(person, index) in row.inspector" |
| | | :key="index" |
| | | size="small" |
| | | type="primary" |
| | | class="person-tag" |
| | | > |
| | | <el-tag v-for="(person, index) in row.inspector" |
| | | :key="index" |
| | | size="small" |
| | | type="primary" |
| | | class="person-tag"> |
| | | {{ person }} |
| | | </el-tag> |
| | | </template> |
| | | <span v-else class="no-data">--</span> |
| | | <span v-else |
| | | class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </el-card> |
| | | <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> |
| | | <form-dia ref="formDia" |
| | | @closeDia="handleQuery"></form-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Delete, Plus } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { Delete, Plus } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | |
| | | // ç»ä»¶å¼å
¥ |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue"; |
| | | import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue"; |
| | | // ç»ä»¶å¼å
¥ |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue"; |
| | | import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue"; |
| | | |
| | | // æ¥å£å¼å
¥ |
| | | import { |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList |
| | | } from "@/api/inspectionManagement/index.js"; |
| | | // æ¥å£å¼å
¥ |
| | | import { |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList, |
| | | } from "@/api/inspectionManagement/index.js"; |
| | | |
| | | // å
¨å±åé |
| | | const { proxy } = getCurrentInstance(); |
| | | const formDia = ref(); |
| | | const viewFiles = ref(); |
| | | // å
¨å±åé |
| | | const { proxy } = getCurrentInstance(); |
| | | const formDia = ref(); |
| | | const viewFiles = ref(); |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | taskName: "", |
| | | }); |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | taskName: "", |
| | | }); |
| | | |
| | | // åéæ¡é
ç½® |
| | | const activeRadio = ref("taskManage"); |
| | | const radios = reactive([ |
| | | { name: "taskManage", label: "宿¶ä»»å¡ç®¡ç" }, |
| | | { name: "task", label: "宿¶ä»»å¡è®°å½" }, |
| | | ]); |
| | | // åéæ¡é
ç½® |
| | | const activeRadio = ref("taskManage"); |
| | | const radios = reactive([ |
| | | { name: "taskManage", label: "宿¶ä»»å¡ç®¡ç" }, |
| | | { name: "task", label: "宿¶ä»»å¡è®°å½" }, |
| | | ]); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const selectedRows = ref([]); |
| | | const tableData = ref([]); |
| | | const operationsArr = ref([]); |
| | | const tableColumns = ref([]); |
| | | const tableLoading = ref(false); |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | // è¡¨æ ¼æ°æ® |
| | | const selectedRows = ref([]); |
| | | const tableData = ref([]); |
| | | const operationsArr = ref([]); |
| | | const tableColumns = ref([]); |
| | | const tableLoading = ref(false); |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | |
| | | // åé
ç½® |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | formatter: (_, __, val) => ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦" |
| | | }[val] || "") |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | formatter: (row, column, cellValue) => { |
| | | // å
夿æ¯å¦æ¯å符串 |
| | | if (typeof cellValue !== 'string') return ''; |
| | | let val = cellValue; |
| | | const replacements = { |
| | | MON: 'å¨ä¸', |
| | | TUE: 'å¨äº', |
| | | WED: 'å¨ä¸', |
| | | THU: 'å¨å', |
| | | FRI: 'å¨äº', |
| | | SAT: 'å¨å
', |
| | | SUN: '卿¥' |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | } |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "createTime", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | ]); |
| | | // åé
ç½® |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | // formatter: (_, __, val) => ({ |
| | | // DAILY: "æ¯æ¥", |
| | | // WEEKLY: "æ¯å¨", |
| | | // MONTHLY: "æ¯æ", |
| | | // QUARTERLY: "å£åº¦" |
| | | // }[val] || "") |
| | | formatData: params => { |
| | | return params === "DAILY" |
| | | ? "æ¯æ¥" |
| | | : params === "WEEKLY" |
| | | ? "æ¯å¨" |
| | | : params === "MONTHLY" |
| | | ? "æ¯æ" |
| | | : params === "QUARTERLY" |
| | | ? "å£åº¦" |
| | | : ""; |
| | | }, |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | formatter: (row, column, cellValue) => { |
| | | // å
夿æ¯å¦æ¯å符串 |
| | | if (typeof cellValue !== "string") return ""; |
| | | let val = cellValue; |
| | | const replacements = { |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace( |
| | | /MON|TUE|WED|THU|FRI|SAT|SUN/g, |
| | | match => replacements[match] |
| | | ); |
| | | }, |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "createTime", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | ]); |
| | | |
| | | // æä½åé
ç½® |
| | | const getOperationColumn = (operations) => { |
| | | if (!operations || operations.length === 0) return null; |
| | | |
| | | const operationConfig = { |
| | | label: "æä½", |
| | | width: 130, |
| | | fixed: "right", |
| | | dataType: "action", |
| | | operation: operations.map(op => { |
| | | switch (op) { |
| | | case 'edit': |
| | | return { |
| | | name: "ç¼è¾", |
| | | clickFun: handleAdd, |
| | | color: "#409EFF" |
| | | }; |
| | | case 'viewFile': |
| | | return { |
| | | name: "æ¥çéä»¶", |
| | | clickFun: viewFile, |
| | | color: "#67C23A" |
| | | }; |
| | | default: |
| | | return null; |
| | | } |
| | | }).filter(Boolean) |
| | | // æä½åé
ç½® |
| | | const getOperationColumn = operations => { |
| | | if (!operations || operations.length === 0) return null; |
| | | |
| | | const operationConfig = { |
| | | label: "æä½", |
| | | width: 130, |
| | | fixed: "right", |
| | | dataType: "action", |
| | | operation: operations |
| | | .map(op => { |
| | | switch (op) { |
| | | case "edit": |
| | | return { |
| | | name: "ç¼è¾", |
| | | clickFun: handleAdd, |
| | | color: "#409EFF", |
| | | }; |
| | | case "viewFile": |
| | | return { |
| | | name: "æ¥çéä»¶", |
| | | clickFun: viewFile, |
| | | color: "#67C23A", |
| | | }; |
| | | default: |
| | | return null; |
| | | } |
| | | }) |
| | | .filter(Boolean), |
| | | }; |
| | | |
| | | return operationConfig; |
| | | }; |
| | | |
| | | return operationConfig; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | radioChange('taskManage'); |
| | | }); |
| | | |
| | | // åéåå |
| | | const radioChange = (value) => { |
| | | if (value === "taskManage") { |
| | | const operationColumn = getOperationColumn(['edit']); |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ['edit']; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(['viewFile']); |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ['viewFile']; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | |
| | | // æ¥è¯¢æä½ |
| | | const handleQuery = () => { |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | // å页å¤ç |
| | | const handlePagination = (val) => { |
| | | pageNum.value = val.page; |
| | | pageSize.value = val.limit; |
| | | getList(); |
| | | }; |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | const params = { ...queryParams, size: pageSize.value, current: pageNum.value }; |
| | | |
| | | let apiCall; |
| | | if (activeRadio.value === "task") { |
| | | apiCall = inspectionTaskList(params); |
| | | } else { |
| | | apiCall = timingTaskList(params); |
| | | } |
| | | |
| | | apiCall.then(res => { |
| | | const rawData = res.data.records || []; |
| | | // å¤ç inspector åæ®µï¼å°å符串转æ¢ä¸ºæ°ç»ï¼éç¨äºæææ
åµï¼ |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | |
| | | // å¤ç inspector åæ®µ |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === 'string') { |
| | | // å符串æéå·åå² |
| | | processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | // éæ°ç»è½¬ä¸ºæ°ç» |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | } else { |
| | | // 空å¼è®¾ä¸ºç©ºæ°ç» |
| | | processedItem.inspector = []; |
| | | } |
| | | |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | onMounted(() => { |
| | | radioChange("taskManage"); |
| | | }); |
| | | }; |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | const resetQuery = () => { |
| | | for (const key in queryParams) { |
| | | if (!["pageNum", "pageSize"].includes(key)) { |
| | | queryParams[key] = ""; |
| | | // åéåå |
| | | const radioChange = value => { |
| | | if (value === "taskManage") { |
| | | const operationColumn = getOperationColumn(["edit"]); |
| | | tableColumns.value = [ |
| | | ...columns.value, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["edit"]; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(["viewFile"]); |
| | | tableColumns.value = [ |
| | | ...columns.value, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["viewFile"]; |
| | | } |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | |
| | | // æ°å¢ / ç¼è¾ |
| | | const handleAdd = (row) => { |
| | | const type = row ? 'edit' : 'add'; |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | // æ¥è¯¢æä½ |
| | | const handleQuery = () => { |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | // å页å¤ç |
| | | const handlePagination = val => { |
| | | pageNum.value = val.page; |
| | | pageSize.value = val.limit; |
| | | getList(); |
| | | }; |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | // æ¥çéä»¶ |
| | | const viewFile = (row) => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | const params = { |
| | | ...queryParams, |
| | | size: pageSize.value, |
| | | current: pageNum.value, |
| | | }; |
| | | |
| | | // å é¤æä½ |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦å é¤çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | |
| | | proxy.$modal.confirm('æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹ï¼').then(() => { |
| | | return delTimingTask(deleteIds); |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | handleQuery(); |
| | | }).catch(() => {}); |
| | | }; |
| | | let apiCall; |
| | | if (activeRadio.value === "task") { |
| | | apiCall = inspectionTaskList(params); |
| | | } else { |
| | | apiCall = timingTaskList(params); |
| | | } |
| | | |
| | | // å¤éåæ´ |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | apiCall |
| | | .then(res => { |
| | | const rawData = res.data.records || []; |
| | | // å¤ç inspector åæ®µï¼å°å符串转æ¢ä¸ºæ°ç»ï¼éç¨äºæææ
åµï¼ |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // æ ¹æ®å½åéä¸çæ ç¾é¡µè°ç¨ä¸åçå¯¼åºæ¥å£ |
| | | if (activeRadio.value === "taskManage") { |
| | | // 宿¶ä»»å¡ç®¡ç |
| | | proxy.download("/timingTask/export", {}, "宿¶ä»»å¡ç®¡ç.xlsx"); |
| | | } else if (activeRadio.value === "task") { |
| | | // 宿¶ä»»å¡è®°å½ |
| | | proxy.download("/inspectionTask/export", {}, "宿¶ä»»å¡è®°å½.xlsx"); |
| | | // å¤ç inspector åæ®µ |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === "string") { |
| | | // å符串æéå·åå² |
| | | processedItem.inspector = processedItem.inspector |
| | | .split(",") |
| | | .map(s => s.trim()) |
| | | .filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | // éæ°ç»è½¬ä¸ºæ°ç» |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | } else { |
| | | // 空å¼è®¾ä¸ºç©ºæ°ç» |
| | | processedItem.inspector = []; |
| | | } |
| | | |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | const resetQuery = () => { |
| | | for (const key in queryParams) { |
| | | if (!["pageNum", "pageSize"].includes(key)) { |
| | | queryParams[key] = ""; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // æ°å¢ / ç¼è¾ |
| | | const handleAdd = row => { |
| | | const type = row ? "edit" : "add"; |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | // æ¥çéä»¶ |
| | | const viewFile = row => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤æä½ |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦å é¤çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | |
| | | proxy.$modal |
| | | .confirm("æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹ï¼") |
| | | .then(() => { |
| | | return delTimingTask(deleteIds); |
| | | }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | handleQuery(); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å¤éåæ´ |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // æ ¹æ®å½åéä¸çæ ç¾é¡µè°ç¨ä¸åçå¯¼åºæ¥å£ |
| | | if (activeRadio.value === "taskManage") { |
| | | // 宿¶ä»»å¡ç®¡ç |
| | | proxy.download("/timingTask/export", {}, "宿¶ä»»å¡ç®¡ç.xlsx"); |
| | | } else if (activeRadio.value === "task") { |
| | | // 宿¶ä»»å¡è®°å½ |
| | | proxy.download("/inspectionTask/export", {}, "宿¶ä»»å¡è®°å½.xlsx"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .person-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | .person-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .person-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | } |
| | | .person-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | .no-data { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | <div class="main-panel"> |
| | | <div style="display: flex;justify-content: space-between;"> |
| | | <div class="section-title">åºæ¶åºä»ç»è®¡</div> |
| | | <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable"> |
| | | <el-radio-button label="æå¨" :value="1" /> |
| | | <el-radio-button label="ææ" :value="2" /> |
| | | <el-radio-button label="æå£åº¦" :value="3" /> |
| | | </el-radio-group> |
| | | <!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">--> |
| | | <!-- <el-radio-button label="æå¨" :value="1" />--> |
| | | <!-- <el-radio-button label="ææ" :value="2" />--> |
| | | <!-- <el-radio-button label="æå£åº¦" :value="3" />--> |
| | | <!-- </el-radio-group>--> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | form.value = {} |
| | | // å
éç½®è¡¨åæ°æ®ï¼ä¿æåæ®µå®æ´ï¼é¿å
å¼¹çªé¦æ¬¡æ¸²ææ¶è§¦åå¿
填红æ¡âéªä¸ä¸âï¼ |
| | | form.value = { |
| | | checkTime: "", |
| | | process: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | } |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | getProductOptions(); |
| | | // å
ç¡®ä¿äº§åæ å·²å è½½ï¼å¦åç¼è¾æ¶äº§å/è§æ ¼åå·æ æ³åæ¾ |
| | | await getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | // å
è®¾ç½®è¡¨åæ°æ®ï¼ä½ææ¶æ¸
空 testStandardIdï¼çé项å è½½å®æåå设置 |
| | | form.value = {...row, testStandardId: ''} |
| | | currentProductId.value = row.productId || 0 |
| | | // å
³é®ï¼ç¼è¾æ¶å è½½è§æ ¼åå·ä¸æéé¡¹ï¼æè½åæ¾ productModelId |
| | | if (currentProductId.value) { |
| | | try { |
| | | const res = await modelList({ id: currentProductId.value }); |
| | | modelOptions.value = res || []; |
| | | // 忥åå¡« model / unitï¼æäºæ¥å£è¿åç row éå¯è½æ²¡å¸¦å
¨ï¼ |
| | | if (form.value.productModelId) { |
| | | handleChangeModel(form.value.productModelId); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½è§æ ¼åå·å¤±è´¥", e); |
| | | modelOptions.value = []; |
| | | } |
| | | } |
| | | // ç¼è¾æ¨¡å¼ä¸ï¼å
å è½½ææ é项ï¼ç¶åå è½½åæ°å表 |
| | | if (currentProductId.value) { |
| | | // å
å è½½ææ é项 |
| | |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } |
| | | // æååæå¼å¼¹çªï¼å¹¶æ¸
çæ ¡éªæï¼é¿å
å¿
å¡«æç¤ºéªç |
| | | dialogFormVisible.value = true; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.(); |
| | | }); |
| | | } |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return productOptions.value; |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | |
| | | const modelOptions = ref([]); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | form.value = {} |
| | | // å
éç½®è¡¨åæ°æ®ï¼ä¿æåæ®µå®æ´ï¼é¿å
å¼¹çªé¦æ¬¡æ¸²ææ¶è§¦åå¿
填红æ¡âéªä¸ä¸âï¼ |
| | | form.value = { |
| | | checkTime: "", |
| | | supplier: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | } |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | getProductOptions(); |
| | | // å
ç¡®ä¿äº§åæ å·²å è½½ï¼å¦åç¼è¾æ¶äº§å/è§æ ¼åå·æ æ³åæ¾ |
| | | await getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | form.value = {...row} |
| | | currentProductId.value = row.productId || 0 |
| | | // å
³é®ï¼ç¼è¾æ¶å è½½è§æ ¼åå·ä¸æéé¡¹ï¼æè½åæ¾ productModelId |
| | | if (currentProductId.value) { |
| | | try { |
| | | const res = await modelList({ id: currentProductId.value }); |
| | | modelOptions.value = res || []; |
| | | // 忥åå¡« model / unitï¼æäºæ¥å£è¿åç row éå¯è½æ²¡å¸¦å
¨ï¼ |
| | | if (form.value.productModelId) { |
| | | handleChangeModel(form.value.productModelId); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½è§æ ¼åå·å¤±è´¥", e); |
| | | modelOptions.value = []; |
| | | } |
| | | } |
| | | // ç¼è¾æ¨¡å¼ä¸ï¼å
å è½½ææ é项ï¼ç¶åå è½½åæ°å表 |
| | | if (currentProductId.value) { |
| | | // å
å è½½ææ é项 |
| | |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } |
| | | // æååæå¼å¼¹çªï¼å¹¶æ¸
çæ ¡éªæï¼é¿å
å¿
å¡«æç¤ºéªç |
| | | dialogFormVisible.value = true; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.(); |
| | | }); |
| | | } |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return productOptions.value; |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { qualityStatistics } from '@/api/viewIndex.js' |
| | | import { productionAccountingAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | qualityStatistics() |
| | | productionAccountingAnalysis({ type: dateType.value }) |
| | | .then((res) => { |
| | | if (!res?.data?.item || !Array.isArray(res.data.item)) return |
| | | const items = res.data.item |
| | | xAxis1.value[0].data = items.map((d) => d.date) |
| | | // 产éï¼åºåæ° |
| | | chartSeries.value[0].data = items.map((d) => Number(d.factoryNum) || 0) |
| | | // å·¥èµï¼ææ åç¬æ¥å£ï¼ç¨ 0 å ä½ï¼åç»å¯æ¥å·¥èµæ¥å£ |
| | | chartSeries.value[1].data = items.map(() => 0) |
| | | // åæ ¼çï¼åºåæ°/è¿ç¨æ°*100ï¼æ åç¬æ¥å£æ¶ç¨æ¤å ä½ï¼ |
| | | chartSeries.value[2].data = items.map((d) => { |
| | | const processNum = Number(d.processNum) || 0 |
| | | const factoryNum = Number(d.factoryNum) || 0 |
| | | if (processNum <= 0) return 0 |
| | | return Math.min(100, Math.round((factoryNum / processNum) * 100)) |
| | | }) |
| | | console.log('res ======> ', res) |
| | | |
| | | if (!Array.isArray(res?.data)) return |
| | | |
| | | const items = res.data |
| | | |
| | | xAxis1.value[0].data = items.map(d => d.dateStr) |
| | | |
| | | // 产é |
| | | chartSeries.value[0].data = items.map(d => Number(d.numberOfCompleted) || 0) |
| | | |
| | | // å·¥èµ |
| | | chartSeries.value[1].data = items.map(d => Number(d.amount) || 0) |
| | | |
| | | // åæ ¼ç |
| | | chartSeries.value[2].data = items.map(d => Number(d.passRate) || 0) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产éãå·¥èµä¸åæ ¼çæ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="carousel-cards"> |
| | | <button |
| | | v-if="canScrollLeft" |
| | | class="nav-button nav-button-left" |
| | | @click="scrollLeftFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å·¦ç®å¤´" /> |
| | | </button> |
| | | <div |
| | | class="cards-container" |
| | | :style="{ '--visible-count': visibleCount }" |
| | | ref="cardsContainerRef" |
| | | > |
| | | <div |
| | | v-for="(item, index) in items" |
| | | :key="index" |
| | | class="card-item" |
| | | > |
| | | <div v-if="item.icon" class="card-icon" :style="{ backgroundImage: `url(${item.icon})` }"></div> |
| | | <div class="card-title"> |
| | | <div class="card-label">{{ item.label }}</div> |
| | | <div class="card-value"> |
| | | <span class="value-number">{{ item.value }}</span> |
| | | <span class="value-unit">{{ item.unit }}</span> |
| | | </div> |
| | | <div v-if="item.rate ?? item.ratio ?? item.percent" class="card-rate"> |
| | | <span class="rate-value">{{ item.rate ?? item.ratio ?? item.percent }}%</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <button |
| | | v-if="canScrollRight" |
| | | class="nav-button nav-button-right" |
| | | @click="scrollRightFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å³ç®å¤´" /> |
| | | </button> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, watch, computed } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | items: { |
| | | type: Array, |
| | | default: () => [], |
| | | validator: (value) => { |
| | | return value.every(item => |
| | | item && typeof item.label !== 'undefined' && |
| | | typeof item.value !== 'undefined' && |
| | | typeof item.unit !== 'undefined' |
| | | ) |
| | | } |
| | | }, |
| | | visibleCount: { |
| | | type: Number, |
| | | default: 3 |
| | | } |
| | | }) |
| | | |
| | | const cardsContainerRef = ref(null) |
| | | const currentScrollLeft = ref(0) |
| | | const maxScrollLeft = ref(0) |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥åå·¦æ»å¨ |
| | | const canScrollLeft = computed(() => { |
| | | return currentScrollLeft.value > 0 |
| | | }) |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥å峿»å¨ |
| | | const canScrollRight = computed(() => { |
| | | return currentScrollLeft.value < maxScrollLeft.value |
| | | }) |
| | | |
| | | // æ´æ°æ»å¨ç¶æ |
| | | const updateScrollState = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | currentScrollLeft.value = container.scrollLeft |
| | | maxScrollLeft.value = container.scrollWidth - container.clientWidth |
| | | } |
| | | |
| | | // åå·¦æ»å¨ |
| | | const scrollLeftFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | container.scrollBy({ |
| | | left: -scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | | }, 300) |
| | | } |
| | | |
| | | // å峿»å¨ |
| | | const scrollRightFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | container.scrollBy({ |
| | | left: scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | | }, 300) |
| | | } |
| | | |
| | | // çå¬ items ååï¼æ´æ°æ»å¨ç¶æ |
| | | watch(() => props.items, () => { |
| | | nextTick(() => { |
| | | updateScrollState() |
| | | }) |
| | | }, { deep: true }) |
| | | |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | updateScrollState() |
| | | // ç嬿»å¨äºä»¶ |
| | | const container = cardsContainerRef.value |
| | | if (container) { |
| | | container.addEventListener('scroll', updateScrollState) |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | // æ¸
çæ»å¨äºä»¶çå¬å¨ |
| | | const container = cardsContainerRef.value |
| | | if (container) { |
| | | container.removeEventListener('scroll', updateScrollState) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .carousel-cards { |
| | | width: 100%; |
| | | overflow: hidden; |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .cards-container { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | overflow-x: auto; |
| | | overflow-y: hidden; |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IE and Edge */ |
| | | padding-bottom: 4px; |
| | | scroll-behavior: smooth; |
| | | } |
| | | |
| | | .cards-container::-webkit-scrollbar { |
| | | display: none; /* Chrome, Safari, Opera */ |
| | | } |
| | | |
| | | .nav-button { |
| | | position: absolute; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | background: rgba(26, 88, 176, 0.6); |
| | | border: 1px solid rgba(26, 88, 176, 0.8); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | z-index: 10; |
| | | transition: all 0.3s ease; |
| | | padding: 0; |
| | | } |
| | | |
| | | .nav-button:hover { |
| | | background: rgba(26, 88, 176, 0.8); |
| | | transform: translateY(-50%) scale(1.1); |
| | | } |
| | | |
| | | .nav-button-left { |
| | | left: -16px; |
| | | } |
| | | |
| | | .nav-button-left img { |
| | | width: 16px; |
| | | height: 16px; |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | .nav-button-right { |
| | | right: -16px; |
| | | } |
| | | |
| | | .nav-button-right img { |
| | | width: 16px; |
| | | height: 16px; |
| | | } |
| | | |
| | | .card-item { |
| | | flex: 0 0 calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%); |
| | | border-radius: 8px 8px 8px 8px; |
| | | padding: 12px 16px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .card-item:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | flex-shrink: 0; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .card-title { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | margin-bottom: 4px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | width: 100%; |
| | | } |
| | | |
| | | .card-value { |
| | | display: flex; |
| | | align-items: baseline; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .card-rate { |
| | | margin-top: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(255, 255, 255, 0.85); |
| | | } |
| | | |
| | | .rate-label { |
| | | opacity: 0.85; |
| | | } |
| | | |
| | | .rate-value { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .value-number { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .value-unit { |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | font-weight: 400; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-radio-group |
| | | v-model="currentValue" |
| | | class="date-type-switch" |
| | | @change="handleChange" |
| | | > |
| | | <el-radio-button :label="1">å¨</el-radio-button> |
| | | <el-radio-button :label="2">æ</el-radio-button> |
| | | <el-radio-button :label="3">å£åº¦</el-radio-button> |
| | | </el-radio-group> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"å¨" |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'change']) |
| | | |
| | | const currentValue = ref(props.modelValue) |
| | | |
| | | // çå¬å¤é¨å¼åå |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | currentValue.value = newVal |
| | | } |
| | | ) |
| | | |
| | | // å¤çå¼åå |
| | | const handleChange = (value) => { |
| | | emit('update:modelValue', value) |
| | | emit('change', value) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .date-type-switch { |
| | | display: inline-flex; |
| | | } |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | } |
| | | |
| | | /* æåä¸ä¸ªæé®å³ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | } |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | title: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-radio-group |
| | | v-model="currentValue" |
| | | class="product-type-switch" |
| | | @change="handleChange" |
| | | > |
| | | <el-radio-button :label="1">åææ</el-radio-button> |
| | | <el-radio-button :label="3">åæå</el-radio-button> |
| | | <el-radio-button :label="2">æå</el-radio-button> |
| | | </el-radio-group> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"åææ" |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'change']) |
| | | |
| | | const currentValue = ref(props.modelValue) |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | currentValue.value = newVal |
| | | } |
| | | ) |
| | | |
| | | const handleChange = (value) => { |
| | | emit('update:modelValue', value) |
| | | emit('change', value) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .product-type-switch { |
| | | display: inline-flex; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="chartSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { qualityStatistics } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '135%', |
| | | } |
| | | |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['å¼å·¥', '宿'], |
| | | } |
| | | |
| | | // æ±ç¶å¾ï¼å¼å·¥ãå®æï¼æçº¿å¾ï¼è¯åçï¼é¢è² rgba(90, 216, 166, 1)ï¼ |
| | | const chartSeries = ref([ |
| | | { |
| | | name: 'å¼å·¥', |
| | | type: 'bar', |
| | | barWidth: 20, |
| | | barGap: '40%', |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0, 164, 237, 0)' }, |
| | | { offset: 0, color: 'rgba(78, 228, 255, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: '宿', |
| | | type: 'bar', |
| | | barGap: '40%', |
| | | barWidth: 20, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(83, 126, 245, 0.19)' }, |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | const unit = item.seriesName === 'è¿7天' |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}${unit}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | ]) |
| | | const yAxis1 = [ |
| | | { type: 'value', name: 'ä»¶', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { |
| | | type: 'value', |
| | | name: 'è¿7天', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | }, |
| | | ] |
| | | |
| | | const fetchData = () => { |
| | | qualityStatistics() |
| | | .then((res) => { |
| | | if (!res?.data?.item || !Array.isArray(res.data.item)) return |
| | | const items = res.data.item |
| | | xAxis1.value[0].data = items.map((d) => d.date) |
| | | // å¼å·¥ï¼è¿ç¨æ£éªæ° |
| | | chartSeries.value[0].data = items.map((d) => Number(d.processNum) || 0) |
| | | // 宿ï¼åºåæ° |
| | | chartSeries.value[1].data = items.map((d) => Number(d.factoryNum) || 0) |
| | | // è¯åçï¼åºåæ°/è¿ç¨æ°*100ï¼æ åç¬æ¥å£æ¶ç¨æ¤å ä½ï¼ |
| | | chartSeries.value[2].data = items.map((d) => { |
| | | const processNum = Number(d.processNum) || 0 |
| | | const factoryNum = Number(d.factoryNum) || 0 |
| | | if (processNum <= 0) return 0 |
| | | return Math.min(100, Math.round((factoryNum / processNum) * 100)) |
| | | }) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åå¼å·¥ä¸è¯åçæ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <div class="warn-panel"> |
| | | <div class="warn-header"> |
| | | <div class="warn-header-left"> |
| | | <div class="warn-badge"></div> |
| | | <span class="warn-title">ä¸åæ ¼é¢è¦</span> |
| | | </div> |
| | | <div class="warn-range" @click="handleRangeClick">è¿7天</div> |
| | | </div> |
| | | |
| | | <div class="warn-body"> |
| | | <div class="warn-list" role="list"> |
| | | <div v-for="item in warnings" :key="item.id" class="warn-item" role="listitem" @click="openWarning(item)"> |
| | | <div class="warn-tag" :class="tagClass(item.type)">{{ item.typeText }}</div> |
| | | <div class="warn-text" :title="item.title">{{ item.title }}</div> |
| | | <div class="warn-action" @click.stop="openWarning(item)">æ¥ç</div> |
| | | <div class="warn-date">{{ item.date }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, getCurrentInstance, ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { qualityUnqualifiedListPage } from '@/api/qualityManagement/nonconformingManagement.js' |
| | | |
| | | const { proxy } = getCurrentInstance() || {} |
| | | |
| | | const warnings = ref([ |
| | | { id: '1', type: 'raw', typeText: 'åææ', title: 'å
³äºä¼ä¸åææè°æ´éç¥', date: '2024.08.24' }, |
| | | { id: '2', type: 'raw', typeText: 'åææ', title: 'å
³äºåæææ¶èæ¹æ¡å»ºè®¾çéç¥', date: '2024.08.24' }, |
| | | { id: '3', type: 'final', typeText: 'æå', title: 'æåå·¥ä½å°ç³»ç»ç»´æ¤è®¡å宿', date: '2024.08.24' }, |
| | | { id: '4', type: 'final', typeText: 'æå', title: 'æåå·¥ä½å°ç³»ç»ç»´æ¤è®¡å宿', date: '2024.08.24' }, |
| | | { id: '5', type: 'semi', typeText: 'åæå', title: 'HRMç³»ç»å®å
¨å级å
¬åï¼å å¼ºè®¿é®æ§å¶â¦', date: '2024.08.24' }, |
| | | { id: '6', type: 'semi', typeText: 'åæå', title: 'HRMç³»ç»å®å
¨å级å
¬åï¼å å¼ºè®¿é®æ§å¶â¦', date: '2024.08.24' }, |
| | | ]) |
| | | |
| | | const TAG_COLORS = { |
| | | raw: '#7C4DFF', |
| | | final: '#F5A000', |
| | | semi: '#FF66CC', |
| | | } |
| | | |
| | | const tagClass = (type) => { |
| | | if (type === 'raw') return 'tag-raw' |
| | | if (type === 'final') return 'tag-final' |
| | | return 'tag-semi' |
| | | } |
| | | |
| | | const pieChartStyle = { width: '100%', height: '100%' } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: (p) => `${p.name}ï¼${p.value}`, |
| | | } |
| | | |
| | | const pieData = computed(() => { |
| | | const counts = { raw: 0, final: 0, semi: 0 } |
| | | warnings.value.forEach((w) => { |
| | | const key = w.type in counts ? w.type : 'raw' |
| | | counts[key] += 1 |
| | | }) |
| | | return [ |
| | | { name: 'åææ', value: counts.raw, itemStyle: { color: TAG_COLORS.raw } }, |
| | | { name: 'åæå', value: counts.semi, itemStyle: { color: TAG_COLORS.semi } }, |
| | | { name: 'æå', value: counts.final, itemStyle: { color: TAG_COLORS.final } }, |
| | | ] |
| | | }) |
| | | |
| | | const pieSeries = computed(() => { |
| | | return [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['0%', '68%'], |
| | | center: ['50%', '50%'], |
| | | startAngle: 90, |
| | | clockwise: true, |
| | | avoidLabelOverlap: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { |
| | | borderColor: '#071a3a', |
| | | borderWidth: 4, |
| | | shadowBlur: 14, |
| | | shadowColor: 'rgba(0, 0, 0, 0.35)', |
| | | }, |
| | | data: pieData.value, |
| | | }, |
| | | { |
| | | // å
åæç¯ï¼å¢å¼ºå±æ¬¡ |
| | | type: 'pie', |
| | | radius: ['70%', '74%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { color: 'rgba(78, 228, 255, 0.12)' }, |
| | | data: [1], |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | const fetchWarnings = async () => { |
| | | try { |
| | | const res = await qualityUnqualifiedListPage({ pageNum: 1, pageSize: 6 }) |
| | | const rows = res?.rows || res?.data?.rows || res?.data || [] |
| | | if (!Array.isArray(rows) || rows.length === 0) return |
| | | |
| | | warnings.value = rows.slice(0, 6).map((r, idx) => { |
| | | const typeCode = r.inspectType ?? r.modelType ?? r.type |
| | | const mappedType = typeCode === 0 || typeCode === '0' ? 'raw' : typeCode === 1 || typeCode === '1' ? 'semi' : 'final' |
| | | const title = r.title || r.unqualifiedTitle || r.remark || r.unqualifiedReason || 'ä¸åæ ¼é¢è¦' |
| | | const date = (r.warningTime || r.createTime || r.updateTime || '').slice(0, 10).replace(/-/g, '.') || '2024.08.24' |
| | | return { |
| | | id: r.id ?? r.unqualifiedId ?? `${idx}`, |
| | | type: mappedType, |
| | | typeText: mappedType === 'raw' ? 'åææ' : mappedType === 'semi' ? 'åæå' : 'æå', |
| | | title, |
| | | date, |
| | | } |
| | | }) |
| | | } catch (e) { |
| | | // æ¥å£å¤±è´¥åä¿æ mock |
| | | console.error('è·åä¸åæ ¼é¢è¦å¤±è´¥:', e) |
| | | } |
| | | } |
| | | |
| | | const openWarning = (item) => { |
| | | const title = `ã${item.typeText}ã${item.title}` |
| | | const content = `${title}æ¶é´ï¼${item.date}` |
| | | if (proxy?.$modal?.alert) { |
| | | proxy.$modal.alert(content) |
| | | return |
| | | } |
| | | // å
åºï¼æ²¡æå
¨å± modal æ¶ç¨ console |
| | | console.log('warning:', { ...item }) |
| | | } |
| | | |
| | | const handleRangeClick = () => { |
| | | // å
ææªå¾åéæâè¿7天âï¼åç»æçå®çé鿱忥å
¥ |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchWarnings() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .warn-panel { |
| | | border: 1px solid #1a58b0; |
| | | padding: 0 18px 18px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | height: 100%; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .warn-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( |
| | | 270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100% |
| | | ) |
| | | 1; |
| | | padding: 10px 0 6px; |
| | | } |
| | | |
| | | .warn-header-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .warn-badge { |
| | | width: 18px; |
| | | height: 18px; |
| | | background: linear-gradient(180deg, #2aa8ff 0%, #4ee4ff 100%); |
| | | clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); |
| | | box-shadow: 0 0 12px rgba(78, 228, 255, 0.25); |
| | | } |
| | | |
| | | .warn-title { |
| | | font-weight: 600; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 24px; |
| | | } |
| | | |
| | | .warn-range { |
| | | height: 32px; |
| | | padding: 0 14px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 4px; |
| | | color: #ffffff; |
| | | font-weight: 600; |
| | | background: linear-gradient(180deg, rgba(51, 120, 255, 1) 0%, rgba(0, 164, 237, 1) 100%); |
| | | border: 1px solid rgba(78, 228, 255, 0.25); |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .warn-body { |
| | | display: grid; |
| | | gap: 18px; |
| | | align-items: stretch; |
| | | min-height: 260px; |
| | | } |
| | | |
| | | .warn-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | padding-top: 6px; |
| | | } |
| | | |
| | | .warn-item { |
| | | display: grid; |
| | | grid-template-columns: 88px 1fr auto 110px; |
| | | align-items: center; |
| | | gap: 12px; |
| | | color: #b8c8e0; |
| | | font-size: 14px; |
| | | line-height: 1; |
| | | padding: 6px 0; |
| | | border-radius: 4px; |
| | | transition: background-color 0.2s, color 0.2s; |
| | | } |
| | | |
| | | .warn-item:hover { |
| | | color: #ff4d4f; |
| | | background-color: rgba(255, 77, 79, 0.06); |
| | | } |
| | | |
| | | .warn-item:hover .warn-text { |
| | | color: #ff4d4f; |
| | | } |
| | | |
| | | .warn-tag { |
| | | height: 28px; |
| | | padding: 0 10px; |
| | | border-radius: 4px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-weight: 700; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | .tag-raw { |
| | | background: #7c4dff; |
| | | } |
| | | |
| | | .tag-final { |
| | | background: #f5a000; |
| | | } |
| | | |
| | | .tag-semi { |
| | | background: #ff66cc; |
| | | } |
| | | |
| | | .warn-text { |
| | | color: #e8f1ff; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .warn-action { |
| | | color: #ff4d4f; |
| | | font-weight: 700; |
| | | white-space: nowrap; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .warn-date { |
| | | color: rgba(184, 200, 224, 0.75); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .warn-chart { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .chart-frame { |
| | | width: 100%; |
| | | height: 260px; |
| | | border: 2px dashed rgba(184, 200, 224, 0.35); |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: radial-gradient(circle at 50% 50%, rgba(78, 228, 255, 0.08) 0%, rgba(0, 0, 0, 0) 65%); |
| | | } |
| | | |
| | | /* å¤åå»åº¦ç¯ */ |
| | | .chart-frame::before { |
| | | content: ''; |
| | | position: absolute; |
| | | width: 220px; |
| | | height: 220px; |
| | | border-radius: 50%; |
| | | background: repeating-conic-gradient(from 0deg, rgba(78, 228, 255, 0.75) 0 1deg, rgba(78, 228, 255, 0) 1deg 9deg); |
| | | -webkit-mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | opacity: 0.5; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | /* ååè¾
å©çº¿ */ |
| | | .chart-frame::after { |
| | | content: ''; |
| | | position: absolute; |
| | | width: 240px; |
| | | height: 240px; |
| | | background: |
| | | linear-gradient(to right, rgba(78, 228, 255, 0) 0%, rgba(78, 228, 255, 0.55) 50%, rgba(78, 228, 255, 0) 100%), |
| | | linear-gradient(to bottom, rgba(78, 228, 255, 0) 0%, rgba(78, 228, 255, 0.55) 50%, rgba(78, 228, 255, 0) 100%); |
| | | background-size: 100% 1px, 1px 100%; |
| | | background-position: center, center; |
| | | background-repeat: no-repeat; |
| | | opacity: 0.35; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | | <div class="card-compare" :class="compareClass(Number(item.rate))"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(item.rate) }}</span> |
| | | <span class="compare-icon">{{ Number(item.rate) >= 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | |
| | | const statItems = ref([]) |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | | return `${num.toFixed(2)}%` |
| | | } |
| | | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | |
| | | const fetchData = () => { |
| | | salesPurchaseStorageProductCount() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | statItems.value = res.data.map((item) => ({ |
| | | name: item.name, |
| | | value: item.value, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åéå®/éè´/å¨åäº§åæ°å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .stats-cards { |
| | | display: flex; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | height: 142px; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | } |
| | | |
| | | .card-compare { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 15px; |
| | | color: #d0e7ff; |
| | | } |
| | | |
| | | .card-compare > span:first-child { |
| | | font-size: 13px; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .compare-value { |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .compare-icon { |
| | | font-size: 14px; |
| | | position: relative; |
| | | top: -1px; /* 轻微ä¸ç§»ï¼è®©ç®å¤´ä¸æååç´å±
ä¸å¯¹é½ */ |
| | | } |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | } |
| | | |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å¨å¶åç»è®¡åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="workInProcessBarLegend" |
| | | :series="workInProcessBarSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="workInProcessXAxis" |
| | | :yAxis="workInProcessYAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 100%" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { getWorkInProcessTurnover } from '@/api/viewIndex.js' |
| | | |
| | | // å¨å¶åå¨è½¬ç»è®¡å¯¹è±¡ |
| | | const workInProcessStatistics = ref({ |
| | | totalQuantity: 0, |
| | | avgTurnoverDays: 0, |
| | | turnoverEfficiency: 0, |
| | | }) |
| | | |
| | | // è½®æå¡çæ°æ®ï¼ç± workInProcessStatistics åæ¥ï¼ |
| | | const cardItems = ref([]) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true, |
| | | } |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | // å¨å¶åå·¥åºæ±ç¶å¾é
ç½® |
| | | const workInProcessXAxis = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | const workInProcessYAxis = [ |
| | | { |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | name: '', |
| | | }, |
| | | ] |
| | | const workInProcessBarLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: [], |
| | | } |
| | | const workInProcessBarSeries = ref([ |
| | | { |
| | | name: 'å¨å¶åæ°é', |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barGap: 0, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 0, color: '#4EE4FF' }, |
| | | ], |
| | | }, |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | color: '#B8C8E0', |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const workInProcessTurnoverInfo = () => { |
| | | getWorkInProcessTurnover() |
| | | .then((res) => { |
| | | if (!res || !res.data) return |
| | | const stats = { |
| | | totalQuantity: res.data.totalOrderCount || 0, |
| | | avgTurnoverDays: res.data.averageTurnoverDays || 0, |
| | | turnoverEfficiency: res.data.turnoverEfficiency || 0, |
| | | } |
| | | workInProcessStatistics.value = stats |
| | | cardItems.value = [ |
| | | { label: 'æ»å¨å¶æ°é', value: stats.totalQuantity, unit: 'ä»¶' }, |
| | | { label: 'å¹³åå¨è½¬å¤©æ°', value: stats.avgTurnoverDays, unit: '天' }, |
| | | { label: 'å¨è½¬æç', value: stats.turnoverEfficiency, unit: '%' }, |
| | | ] |
| | | if (res.data.processDetails && Array.isArray(res.data.processDetails)) { |
| | | workInProcessXAxis.value[0].data = res.data.processDetails |
| | | } else { |
| | | workInProcessXAxis.value[0].data = [] |
| | | } |
| | | if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) { |
| | | workInProcessBarSeries.value[0].data = res.data.processQuantityDetails |
| | | } else { |
| | | workInProcessBarSeries.value[0].data = [] |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åå¨å¶åå¨è½¬ç»è®¡å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | workInProcessTurnoverInfo() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .chart-wrapper { |
| | | height: 70%; |
| | | flex: 1; |
| | | min-height: 200px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="è´¨éææ åæ ¼åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div v-for="section in sections" :key="section.key" class="inspect-block"> |
| | | <div class="filters-row"> |
| | | <div class="filters-row-left"> |
| | | <span></span> |
| | | <p>{{ section.title }}</p> |
| | | </div> |
| | | <DateTypeSwitch v-model="section.dateType" @change="(v) => handleDateTypeChange(section.key, v)" /> |
| | | </div> |
| | | |
| | | <div class="inspect-body"> |
| | | <div class="ring"> |
| | | <Echarts |
| | | :chartStyle="ringChartStyle" |
| | | :series="buildRingSeries(section)" |
| | | :tooltip="ringTooltip" |
| | | :legend="{ show: false }" |
| | | :options="ringOptions" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="stats"> |
| | | <div class="stat-row"> |
| | | <div class="stat-left"> |
| | | <span class="dot dot-qualified"></span> |
| | | <span class="stat-label">åæ ¼æ°</span> |
| | | </div> |
| | | <div class="stat-right"> |
| | | <span class="stat-value">{{ section.qualifiedCount }}</span> |
| | | <span class="stat-percent">{{ formatPercent(section.qualifiedRate) }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="stat-row"> |
| | | <div class="stat-left"> |
| | | <span class="dot dot-unqualified"></span> |
| | | <span class="stat-label">ä¸åæ ¼æ°</span> |
| | | </div> |
| | | <div class="stat-right"> |
| | | <span class="stat-value">{{ section.unqualifiedCount }}</span> |
| | | <span class="stat-percent">{{ formatPercent(section.unqualifiedRate) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | |
| | | const QUALIFIED_COLOR = '#4EE4FF' |
| | | const UNQUALIFIED_COLOR = '#3378FF' |
| | | const TRACK_COLOR = 'rgba(78, 228, 255, 0.12)' |
| | | |
| | | const sections = reactive([ |
| | | { |
| | | key: 'raw', |
| | | title: 'åæææ£æµ', |
| | | dateType: 1, |
| | | qualifiedCount: 199, |
| | | unqualifiedCount: 99, |
| | | qualifiedRate: 90, |
| | | unqualifiedRate: 10, |
| | | }, |
| | | { |
| | | key: 'process', |
| | | title: 'è¿ç¨æ£æµ', |
| | | dateType: 1, |
| | | qualifiedCount: 199, |
| | | unqualifiedCount: 99, |
| | | qualifiedRate: 90, |
| | | unqualifiedRate: 10, |
| | | }, |
| | | { |
| | | key: 'final', |
| | | title: 'æååºåæ£æµ', |
| | | dateType: 1, |
| | | qualifiedCount: 199, |
| | | unqualifiedCount: 99, |
| | | qualifiedRate: 90, |
| | | unqualifiedRate: 10, |
| | | }, |
| | | ]) |
| | | |
| | | const ringChartStyle = { |
| | | width: '110px', |
| | | height: '110px', |
| | | } |
| | | |
| | | const ringOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | const ringTooltip = { |
| | | show: false, |
| | | } |
| | | |
| | | const calcRates = (qualifiedCount, unqualifiedCount) => { |
| | | const total = Number(qualifiedCount || 0) + Number(unqualifiedCount || 0) |
| | | if (total <= 0) return { qualifiedRate: 0, unqualifiedRate: 0 } |
| | | const qualifiedRate = Math.round((Number(qualifiedCount || 0) / total) * 100) |
| | | const unqualifiedRate = Math.max(0, 100 - qualifiedRate) |
| | | return { qualifiedRate, unqualifiedRate } |
| | | } |
| | | |
| | | const formatPercent = (v) => `${Number(v || 0)}%` |
| | | |
| | | const buildRingSeries = (section) => { |
| | | const qualified = Number(section.qualifiedCount || 0) |
| | | const unqualified = Number(section.unqualifiedCount || 0) |
| | | const total = qualified + unqualified |
| | | |
| | | return [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['68%', '82%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { color: TRACK_COLOR }, |
| | | data: [1], |
| | | }, |
| | | { |
| | | name: section.title, |
| | | type: 'pie', |
| | | radius: ['68%', '82%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | startAngle: 90, |
| | | clockwise: true, |
| | | minAngle: total > 0 ? 8 : 0, |
| | | itemStyle: { |
| | | borderColor: 'rgba(10, 28, 58, 0.95)', |
| | | borderWidth: 2, |
| | | }, |
| | | data: [ |
| | | { |
| | | value: qualified, |
| | | name: 'åæ ¼æ°', |
| | | itemStyle: { |
| | | color: QUALIFIED_COLOR, |
| | | shadowBlur: 16, |
| | | shadowColor: 'rgba(78, 228, 255, 0.45)', |
| | | }, |
| | | }, |
| | | { |
| | | value: unqualified, |
| | | name: 'ä¸åæ ¼æ°', |
| | | itemStyle: { |
| | | color: UNQUALIFIED_COLOR, |
| | | shadowBlur: 10, |
| | | shadowColor: 'rgba(51, 120, 255, 0.35)', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | type: 'pie', |
| | | radius: ['52%', '56%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { color: 'rgba(0, 127, 255, 0.22)' }, |
| | | data: [1], |
| | | }, |
| | | ] |
| | | } |
| | | |
| | | const handleDateTypeChange = (key, dateType) => { |
| | | const section = sections.find((s) => s.key === key) |
| | | if (!section) return |
| | | section.dateType = dateType |
| | | const rates = calcRates(section.qualifiedCount, section.unqualifiedCount) |
| | | section.qualifiedRate = rates.qualifiedRate |
| | | section.unqualifiedRate = rates.unqualifiedRate |
| | | } |
| | | |
| | | sections.forEach((s) => { |
| | | const rates = calcRates(s.qualifiedCount, s.unqualifiedCount) |
| | | s.qualifiedRate = rates.qualifiedRate |
| | | s.unqualifiedRate = rates.unqualifiedRate |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | gap: 0; |
| | | } |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | |
| | | .filters-row-left { |
| | | width: 50%; |
| | | color: white; |
| | | /* ç¨flexæ¿ä»£floatï¼è®©åå
ç´ å¯¹é½æ´ç¨³å® */ |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | span { |
| | | /* æ ¸å¿ï¼ç¶çº§ç¸å¯¹å®ä½ï¼ä½ä¸ºä¼ªå
ç´ åºå */ |
| | | position: relative; |
| | | display: inline-block; |
| | | /* ç»ä¼ªå
ç´ åæåçç©ºé´ */ |
| | | padding-left: 22px; |
| | | /* æååç´å±
ä¸ */ |
| | | line-height: 23px; |
| | | margin-right: 8px; |
| | | |
| | | &::after { |
| | | content: ''; |
| | | display: inline-block; |
| | | width: 16px; |
| | | height: 16px; |
| | | clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); |
| | | background: #217AFF; |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 0; |
| | | transform: translateY(-50%); |
| | | /* ç¡®ä¿è±å½¢å¨æ¸åå䏿¹ */ |
| | | z-index: 1; |
| | | } |
| | | |
| | | &::before { |
| | | content: ''; |
| | | display: inline-block; |
| | | width: 18px; |
| | | height: 7px; |
| | | border-radius: 8px; |
| | | background: linear-gradient(360deg, rgba(33,133,255,0.4) 0%, rgba(33,221,255,0) 100%); |
| | | position: absolute; |
| | | top: 50%; |
| | | left: -1px; |
| | | /* ç²¾åè´´å¨è±å½¢æ£ä¸æ¹ */ |
| | | transform: translateY(calc(0% + 8px)); |
| | | z-index: 0; |
| | | } |
| | | } |
| | | |
| | | p { |
| | | width: 100px; |
| | | height: 23px; |
| | | /* æ¸åèµ·å§è²åè±å½¢ç»ä¸ï¼æ´åè° */ |
| | | background: linear-gradient(90deg, #217AFF 0%, rgba(33, 221, 255, 0) 100%); |
| | | /* ç²¾ååç´å±
ä¸ */ |
| | | line-height: 26px; |
| | | text-align: center; |
| | | color: white; |
| | | /* ç¨é«åº¦çä¸åååè§ï¼ç¡®ä¿å·¦è¾¹æ¯å®ç¾åå */ |
| | | border-radius: 12px 0 0 12px; |
| | | /* å¯éï¼å ä¸ç¹å·¦å
è¾¹è·ï¼è®©æåä¸è´´è¾¹ */ |
| | | padding-left: 4px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 14px 18px; |
| | | width: 100%; |
| | | height: 960px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .inspect-block { |
| | | flex: 1 1 0; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 8px 0; |
| | | gap: 6px; |
| | | position: relative; |
| | | } |
| | | |
| | | .inspect-block:not(:last-child)::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, rgba(33, 122, 255, 0) 0%, rgba(33, 122, 255, 0.55) 50%, rgba(33, 122, 255, 0) 100%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .inspect-body { |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | display: flex; |
| | | justify-content:space-around; |
| | | align-items: center; |
| | | gap: 18px; |
| | | } |
| | | |
| | | .ring { |
| | | width: 120px; |
| | | height: 120px; |
| | | flex: 0 0 120px; |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* å¤åå»åº¦ï¼ç¹ç¶ç¯ï¼ */ |
| | | .ring::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: -8px; |
| | | border-radius: 50%; |
| | | background: repeating-conic-gradient( |
| | | from 0deg, |
| | | rgba(78, 228, 255, 0.75) 0 1deg, |
| | | rgba(78, 228, 255, 0) 1deg 9deg |
| | | ); |
| | | -webkit-mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | mask: radial-gradient(circle, transparent 62%, #000 63%); |
| | | opacity: 0.35; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | /* æååå
èæ¯ */ |
| | | .ring::after { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: -20px; |
| | | border-radius: 50%; |
| | | background: radial-gradient(circle, rgba(78, 228, 255, 0.18) 0%, rgba(78, 228, 255, 0.06) 40%, rgba(0, 0, 0, 0) 70%); |
| | | filter: blur(0.2px); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .stats { |
| | | width: 240px; |
| | | flex: 0 0 240px; |
| | | display: grid; |
| | | grid-template-rows: 1fr 1fr; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .stat-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | height: 100%; |
| | | padding: 10px 14px; |
| | | border-radius: 4px; |
| | | border: 1px solid rgba(78, 228, 255, 0.22); |
| | | background: linear-gradient(90deg, rgba(33, 122, 255, 0.28) 0%, rgba(10, 28, 58, 0.35) 55%, rgba(10, 28, 58, 0.2) 100%); |
| | | box-shadow: inset 0 0 18px rgba(16, 45, 95, 0.25); |
| | | } |
| | | |
| | | .stat-left { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | color: #b8c8e0; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .dot { |
| | | width: 10px; |
| | | height: 10px; |
| | | border-radius: 2px; |
| | | display: inline-block; |
| | | box-shadow: 0 0 10px rgba(78, 228, 255, 0.25); |
| | | } |
| | | |
| | | .dot-qualified { |
| | | background: rgba(184, 200, 224, 0.85); |
| | | } |
| | | |
| | | .dot-unqualified { |
| | | background: #4ee4ff; |
| | | } |
| | | |
| | | .stat-right { |
| | | display: inline-flex; |
| | | align-items: baseline; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .stat-value { |
| | | color: #ffffff; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | min-width: 40px; |
| | | text-align: right; |
| | | text-shadow: 0 0 10px rgba(78, 228, 255, 0.15); |
| | | } |
| | | |
| | | .stat-percent { |
| | | color: rgba(184, 200, 224, 0.95); |
| | | font-size: 12px; |
| | | min-width: 40px; |
| | | text-align: right; |
| | | } |
| | | |
| | | /* è®©åæ¢æé®æ´è´´è¿æªå¾ï¼æ´ç´§åï¼ */ |
| | | :deep(.date-type-switch .el-radio-button__inner) { |
| | | padding: 4px 16px; |
| | | font-size: 12px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="产å大类" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :legend="landLegend" |
| | | :series="landSeries" |
| | | :tooltip="landTooltip" |
| | | :color="landColors" |
| | | :options="pieOptions" |
| | | style="height: 100%" |
| | | class="land-chart" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import { productCategoryDistribution } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | const chart = ref(null) |
| | | |
| | | // æ°æ®åè¡¨ï¼æ¥èªæ¥å£ï¼ |
| | | const dataList = ref([]) |
| | | |
| | | // é¢è²å表 |
| | | const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | |
| | | // label 坿æ¬ï¼ä¸ºæ¯ä¸ªé¢è²çæä¸ä¸ªå°åç¹æ ·å¼ï¼ç¡®ä¿å¨ label ä¸å¯è§ï¼ |
| | | const dotRich = landColors.reduce((acc, color, idx) => { |
| | | acc[`dot${idx}`] = { |
| | | width: 8, |
| | | height: 8, |
| | | borderRadius: 8, |
| | | backgroundColor: color, |
| | | align: 'center', |
| | | } |
| | | return acc |
| | | }, {}) |
| | | |
| | | // å¾ä¾é
ç½®ï¼å³ä¾§ç«æï¼ |
| | | const landLegend = { |
| | | show: false, |
| | | icon: 'circle', |
| | | data: [], |
| | | right: '8%', |
| | | top: '40%', |
| | | orient: 'vertical', |
| | | itemGap: 14, |
| | | itemWidth: 6, |
| | | itemHeight: 6, |
| | | textStyle: { |
| | | fontSize: 12, |
| | | rich: { |
| | | unit: { |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | text: { |
| | | width: 60, |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | }, |
| | | }, |
| | | }, |
| | | formatter: function (name) { |
| | | const list = dataList.value || [] |
| | | const item = list.find((d) => d.name === name) |
| | | if (!item) return name |
| | | const val = Number(item.value || 0) |
| | | const totalValue = list.reduce((sum, it) => sum + Number(it.value || 0), 0) |
| | | const percent = totalValue ? ((val / totalValue) * 100).toFixed(2) : '0.00' |
| | | return `{text|${name}}${val}{unit| å
¬é¡·}${percent}{unit|%}` |
| | | }, |
| | | } |
| | | |
| | | // æç¤ºæ¡ |
| | | const landTooltip = { |
| | | // triggerOn: 'hover', |
| | | alwaysShowContent: true, |
| | | position: function (pt) { |
| | | return [pt[0], 130] |
| | | }, |
| | | formatter: function (params) { |
| | | return `${params.name} (${params.value}ç±»)` |
| | | }, |
| | | } |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const landSeries = ref([ |
| | | { |
| | | name: '产å大类', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | | label: { |
| | | show: true, |
| | | position: 'outside', |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | lineHeight: 18, |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20, overflow: 'break' }, |
| | | child: { fontSize: 12, color: '#fff', lineHeight: 18 }, |
| | | }, |
| | | formatter: function (params) { |
| | | const children = params?.data?.children || [] |
| | | const parentName = params?.data?.name || '' |
| | | const rawVal = params?.data?.value |
| | | const parentValue = typeof rawVal === 'number' && !Number.isNaN(rawVal) ? rawVal : (Number(rawVal) || 0) |
| | | const dotKey = `dot${(params?.dataIndex || 0) % landColors.length}` |
| | | const dot = `{${dotKey}|} ` |
| | | const parentLine = `${dot}{parent|${parentName} (${parentValue}ç±»)}` |
| | | if (!children.length) return parentLine |
| | | // ç¶çº§å
¨é¨æ¾ç¤ºï¼å级æå¤ 5 个ï¼è¶
åºæ¾ç¤ºçç¥å· |
| | | const displayed = children.slice(0, 5).map((c) => `{child|${c.name}}`) |
| | | if (children.length > 5) displayed.push('{child|â¦}') |
| | | return [parentLine, ...displayed].join('\n') |
| | | }, |
| | | }, |
| | | labelLine: { |
| | | show: true, |
| | | length: 20, |
| | | length2: 20, |
| | | lineStyle: { |
| | | color: '#B8C8E0', |
| | | }, |
| | | }, |
| | | itemStyle: { |
| | | color: function (params) { |
| | | return landColors[params.dataIndex % landColors.length] |
| | | }, |
| | | }, |
| | | data: dataList.value, |
| | | }, |
| | | { |
| | | // å
å |
| | | type: 'pie', |
| | | radius: ['35%', '40%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { |
| | | show: false, |
| | | }, |
| | | labelLine: { |
| | | show: false, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(0, 127, 255, 0.25)', |
| | | }, |
| | | data: [1], |
| | | }, |
| | | ]) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '126%', |
| | | } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ï¼å¯èªå®ä¹åç§»å¼ |
| | | const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | offsetX: '-51.5%', // X è½´åç§»ï¼å¯å¨æè°æ´ |
| | | offsetY: '-39%', // Y è½´åç§»ï¼å¯å¨æè°æ´ |
| | | watchData: dataList // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const loadData = async () => { |
| | | try { |
| | | const res = await productCategoryDistribution() |
| | | const items = res?.data?.items || [] |
| | | dataList.value = items.map((it) => ({ |
| | | name: it.name, |
| | | value: Number(it.value || 0), |
| | | rate: it.rate, |
| | | children: Array.isArray(it.children) ? it.children : [], |
| | | })) |
| | | landLegend.data = dataList.value.map((d) => d.name) |
| | | landSeries.value[0].data = dataList.value |
| | | // æ°æ®å è½½å®æåè°æ´èæ¯ä½ç½® |
| | | adjustBackgroundPosition() |
| | | } catch (e) { |
| | | console.error('è·å产å大类åå¸å¤±è´¥:', e) |
| | | dataList.value = [] |
| | | landLegend.data = [] |
| | | landSeries.value[0].data = [] |
| | | } |
| | | } |
| | | |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 420px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | width: 360px; |
| | | height: 360px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* é»è®¤å±
ä¸ï¼ä¼å¨ JS ä¸å¨æè°æ´ */ |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="main-panel-container"> |
| | | <div |
| | | style="color: white" |
| | | class="main-panel-box" |
| | | v-for="(item, index) in panelList" |
| | | :key="index" |
| | | > |
| | | <div style="flex: 1" class="main-panel-box-left">Top{{ index + 1 }}</div> |
| | | <div style="flex: 3" class="main-panel-box-right"> |
| | | <div class="main-panel-box-right-text"> |
| | | <span>æ»æ°éï¼{{ item.total }}</span> |
| | | <span>已宿ï¼{{ item.finished }}</span> |
| | | <span>åæ ¼çï¼{{ item.qualifiedRate }}</span> |
| | | </div> |
| | | <div class="main-panel-box-right-progress"> |
| | | <el-progress :percentage="item.percentage" :format="format" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | const panelList = [ |
| | | { total: 100, finished: 100, qualifiedRate: 100, percentage: 100 }, // Top1 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 150, finished: 120, qualifiedRate: 80, percentage: 80 } // Top3 |
| | | ] |
| | | const format = (percentage) => { |
| | | return `${percentage}%`; |
| | | } |
| | | onMounted(() => { |
| | | // fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel-box{ |
| | | display: flex; |
| | | flex-direction: row; |
| | | align-items: center; |
| | | height: 40px; |
| | | .main-panel-box-left{ |
| | | background: red; |
| | | border-radius: 20px; |
| | | text-align: center; |
| | | line-height: 32px; |
| | | margin: 0 20px; |
| | | } |
| | | .main-panel-box-right{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | .main-panel-box-right-text{ |
| | | font-size: 12px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding-right: 60px; |
| | | } |
| | | .main-panel-box-right-progress{ |
| | | :deep(.el-progress__text){ |
| | | color: white !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="scale-container"> |
| | | <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <!-- å
¨å±æé® - ç§»å¨å°å·¦ä¸è§ --> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? 'éåºå
¨å±' : 'å
¨å±æ¾ç¤º'"> |
| | | <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/> |
| | | </svg> |
| | | <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/> |
| | | </svg> |
| | | </button> |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">çäº§æ°æ®åæ</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <LeftTop /> |
| | | </div> |
| | | |
| | | <!-- ä¸é´åºå --> |
| | | <div class="center-panel"> |
| | | <CenterTop /> |
| | | <CenterCenter/> |
| | | <CenterBottom /> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | |
| | | <RightTop /> |
| | | <RightBottom /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterCenter from './components/center-center.vue' |
| | | import RightTop from './components/right-top.vue' |
| | | import RightBottom from './components/right-bottom.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import LeftTop from './components/left-top.vue' |
| | | import CenterTop from './components/center-top.vue' |
| | | import CenterBottom from './components/center-bottom.vue' |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | | |
| | | // ç¼©æ¾æ¯ä¾ |
| | | const scaleRatio = ref(1) |
| | | // 设计尺寸ï¼åºå尺寸ï¼- æ ¹æ®å®é
è®¾è®¡ç¨¿è°æ´ |
| | | const designWidth = 1920 |
| | | const designHeight = 1080 |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | const calculateScale = () => { |
| | | const container = document.querySelector('.scale-container') |
| | | if (!container) return |
| | | |
| | | // è·å容å¨çå®é
尺寸 |
| | | const rect = container.getBoundingClientRect?.() |
| | | const containerWidth = container.clientWidth || rect?.width || window.innerWidth |
| | | const containerHeight = container.clientHeight || rect?.height || window.innerHeight |
| | | |
| | | // 计ç®å®½é«ç¼©æ¾æ¯ä¾ï¼åè¾å°å¼ä»¥ä¿è¯å
容宿´æ¾ç¤ºï¼çæ¯ç¼©æ¾ï¼ |
| | | const scaleX = containerWidth / designWidth |
| | | const scaleY = containerHeight / designHeight |
| | | scaleRatio.value = Math.min(scaleX, scaleY) |
| | | } |
| | | |
| | | // çªå£å¤§å°ååå¤ç |
| | | const handleResize = () => { |
| | | // å»¶è¿æ§è¡ï¼ç¡®ä¿DOMæ´æ°å®æ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 100) |
| | | } |
| | | |
| | | // å
¨å±åè½å®ç° - é对scale-containerå
ç´ |
| | | const toggleFullscreen = () => { |
| | | const element = document.querySelector('.scale-container') |
| | | |
| | | if (!element) return |
| | | |
| | | if (!isFullscreen.value) { |
| | | if (element.requestFullscreen) { |
| | | element.requestFullscreen() |
| | | } else if (element.webkitRequestFullscreen) { |
| | | element.webkitRequestFullscreen() |
| | | } else if (element.msRequestFullscreen) { |
| | | element.msRequestFullscreen() |
| | | } |
| | | } else { |
| | | if (document.exitFullscreen) { |
| | | document.exitFullscreen() |
| | | } else if (document.webkitExitFullscreen) { |
| | | document.webkitExitFullscreen() |
| | | } else if (document.msExitFullscreen) { |
| | | document.msExitFullscreen() |
| | | } |
| | | } |
| | | } |
| | | |
| | | // çå¬å
¨å±ååäºä»¶ |
| | | const handleFullscreenChange = () => { |
| | | const fullscreenElement = document.fullscreenElement || |
| | | document.webkitFullscreenElement || |
| | | document.msFullscreenElement |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container') |
| | | |
| | | // å
¨å±ç¶æååæ¶ï¼å»¶è¿éæ°è®¡ç®ç¼©æ¾æ¯ä¾ï¼ç¡®ä¿DOMæ´æ°å®æï¼ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 200) |
| | | } |
| | | |
| | | // çå½å¨æé©å |
| | | onMounted(() => { |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§å |
| | | nextTick(() => { |
| | | // 计ç®åå§ç¼©æ¾æ¯ä¾ |
| | | calculateScale() |
| | | }) |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | // ç§»é¤æä»¬æ·»å çautofitå¨æè°æ´çå¬å¨ |
| | | if (window._autofitUpdateHandler) { |
| | | window.removeEventListener('resize', window._autofitUpdateHandler) |
| | | delete window._autofitUpdateHandler |
| | | } |
| | | // å
³éautofit |
| | | autofit.off() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* å¤é¨ç¼©æ¾å®¹å¨ - å æ®æ´ä¸ªè§å£ */ |
| | | .scale-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #000; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* å
é¨å
容åºå - åºå®è®¾è®¡å°ºå¯¸ */ |
| | | .data-dashboard { |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | /* å
¨å±ç¶æçæ ·å¼ - ä½ç¨äºscale-container */ |
| | | .scale-container:fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* Webkitæµè§å¨åç¼ */ |
| | | .scale-container:-webkit-full-screen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* MSæµè§å¨åç¼ */ |
| | | .scale-container:-ms-fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | |
| | | .dashboard-header { |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | position: absolute; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | | position: absolute; |
| | | top: 10px; |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | } |
| | | |
| | | .dashboard-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | gap: 30px; |
| | | padding: 0 30px; |
| | | height: calc(100% - 86px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* ç¡®ä¿å颿¿è½å¤æ£ç¡®æ¾ç¤º */ |
| | | .left-panel, .center-panel, .right-panel { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | width: 520px; |
| | | } |
| | | |
| | | .center-panel { |
| | | flex: 1.5; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | }; |
| | | |
| | | const getYearlyStatValue = (type, field) => { |
| | | const stat = yearlyPassRateData.value.find(item => item.inspectType === type); |
| | | const stat = yearlyPassRateData.value.find(item => item.modelType === type); |
| | | return stat ? stat[field] : 0; |
| | | }; |
| | | |
| | | const getInspectStatValue = (type, field) => { |
| | | const stat = inspectStatisticsData.value.find(item => item.inspectType === type); |
| | | const stat = inspectStatisticsData.value.find(item => item.modelType === type); |
| | | return stat ? stat[field] : 0; |
| | | }; |
| | | |
| | | const getPassRateStatValue = (type, field) => { |
| | | const stat = passRateStatisticsData.value.find(item => item.inspectType === type); |
| | | const stat = passRateStatisticsData.value.find(item => item.modelType === type); |
| | | if (stat) { |
| | | if (field === 'completionRate' || field === 'passRate') { |
| | | return stat[field] ? Number(stat[field]).toFixed(0) + '%' : '0%'; |
| | |
| | | |
| | | const fetchTopParametersData = async () => { |
| | | try { |
| | | const typeMap = { raw: 0, semi: 1, final: 2 }; |
| | | const typeMap = { raw: 1, semi: 2, final: 3 }; |
| | | const res = await getTopParameters(typeMap[activeTab.value]); |
| | | if (res.code === 200) { |
| | | topParametersData.value = res.data; |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | page.value.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | getList(); |
| | | }; |
| | | const currentUserId = ref(""); |
| | | const currentUserName = ref(""); |
| | |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="scope.row.isRectify" |
| | | @click="openForm('edit2', scope.row)">æ´æ¹</el-button> |
| | | <el-button link |
| | | type="primary" |
| | |
| | | v-model="fileListDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :is-show-pagination="true" |
| | | :page="filePagination" |
| | | :upload-method="handleUpload" |
| | | :delete-method="handleFileDelete" |
| | | @pagination="paginationSearch" |
| | | title="éä»¶å表" /> |
| | | </div> |
| | | </template> |
| | |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | tableData.value.forEach(item => { |
| | | // console.log(item.rectifyUserId, currentUserId.value, "======="); |
| | | if (Number(item.rectifyUserId) != Number(currentUserId.value)) { |
| | | item.isRectify = true; |
| | | } else { |
| | | item.isRectify = false; |
| | | } |
| | | }); |
| | | return res; |
| | | }) |
| | | .catch(() => { |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const isPeople = rectifyUserId => { |
| | | return Number(rectifyUserId) == Number(currentUserId.value); |
| | | }; |
| | | |
| | | /** |
| | | * 夿æ¯å¦å¯ä»¥åè´§ |
| | |
| | | const statusStr = shippingStatus ? String(shippingStatus).trim() : ""; |
| | | return statusStr === "å¾
åè´§" || statusStr === "å®¡æ ¸æç»"; |
| | | }; |
| | | const filePagination = ref({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | /** |
| | | * ä¸è½½æä»¶ |
| | |
| | | const currentFileRow = ref(null); |
| | | const downLoadFile = row => { |
| | | currentFileRow.value = row; |
| | | fileListPage({ safeHiddenId: row.id }).then(res => { |
| | | fileListPage({ |
| | | safeHiddenId: row.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }).then(res => { |
| | | if (fileListRef.value) { |
| | | fileListRef.value.open(res.data.records); |
| | | fileListRef.value.open(res.data.records || []); |
| | | console.log("res.data", res.data); |
| | | filePagination.value.total = res.data.total || 0; |
| | | } |
| | | }); |
| | | }; |
| | |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCurrentFactoryName(); |
| | | getList(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | getCurrentFactoryName(); |
| | | }); |
| | | // ä¸ä¼ éä»¶ |
| | | const handleUpload = async () => { |
| | |
| | | // éæ°å è½½æä»¶å表 |
| | | const listRes = await fileListPage({ |
| | | safeHiddenId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | // è¿åæ°æä»¶ä¿¡æ¯ |
| | | resolve({ |
| | |
| | | input.click(); |
| | | }); |
| | | }; |
| | | // å页æ¥è¯¢æä»¶å表 |
| | | const paginationSearch = async (page, size) => { |
| | | filePagination.value.current = page; |
| | | filePagination.value.size = size; |
| | | const listRes = await fileListPage({ |
| | | safeHiddenId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | }; |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async row => { |
| | | try { |
| | |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await fileListPage({ |
| | | safeHiddenId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | } |
| | | return true; // è¿å true 表示å 餿åï¼ç»ä»¶ä¼æ´æ°å表 |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | page.value.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | getList(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | getList(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | <div> |
| | | <el-table :data="safeHazardList" |
| | | border |
| | | ref="safeHazardTableRef" |
| | | v-loading="safeHazardLoading" |
| | | style="width: 100%" |
| | | @row-click="handleSafeHazardSelect"> |
| | | :selection="selectedSafeHazardIds" |
| | | @selection-change="handleSafeHazardSelectionChange" |
| | | style="width: 100%"> |
| | | <el-table-column type="selection" |
| | | width="55" |
| | | :selectable="isSelectable" /> |
| | | <el-table-column prop="code" |
| | | label="å±é©æºç¼ç " |
| | | width="180" |
| | |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleSafeHazardSelect">ç¡®å®</el-button> |
| | | <el-button @click="safeHazardSelectVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | |
| | | } |
| | | }; |
| | | const handleApplyQtyChange = () => { |
| | | if (Number(form.value.applyQty) < 0) { |
| | | ElMessage.error("é¢ç¨æ°éä¸è½å°äº0"); |
| | | form.value.applyQty = 0; |
| | | return; |
| | | } |
| | | if (form.value.applyQty > valueItem.value.stockQty) { |
| | | ElMessage.error("é¢ç¨æ°éä¸è½å¤§äºåºåæ°é"); |
| | | form.value.applyQty = ""; |
| | | } |
| | | }; |
| | | const selectedSafeHazardIds = ref([]); |
| | | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | |
| | | const fetchSafeHazardList = () => { |
| | | safeHazardLoading.value = true; |
| | | return safeHazardListPage({ |
| | | page: safeHazardPage.value.current, |
| | | current: safeHazardPage.value.current, |
| | | size: safeHazardPage.value.size, |
| | | }) |
| | | .then(res => { |
| | |
| | | }); |
| | | }; |
| | | |
| | | const handleSafeHazardSelect = item => { |
| | | const isSelectable = row => { |
| | | // åªæåºåæ°é大äº0çè¡æè½è¢«éæ© |
| | | return Number(row.stockQty) > 0; |
| | | }; |
| | | |
| | | const handleSafeHazardSelectionChange = selection => { |
| | | // åªä¿çæåä¸ä¸ªéä¸ç项 |
| | | if (selection.length > 1) { |
| | | const lastSelected = selection[selection.length - 1]; |
| | | selectedSafeHazardIds.value = [lastSelected]; |
| | | proxy.$refs.safeHazardTableRef.clearSelection(); |
| | | proxy.$refs.safeHazardTableRef.toggleRowSelection(lastSelected, true); |
| | | } else if (selection.length === 1) { |
| | | selectedSafeHazardIds.value = [selection[0]]; |
| | | } else { |
| | | selectedSafeHazardIds.value = []; |
| | | } |
| | | }; |
| | | |
| | | const handleSafeHazardSelect = () => { |
| | | if (!selectedSafeHazardIds.value.length) { |
| | | ElMessage.error("è¯·éæ©ä¸ä¸ªå±é©æº"); |
| | | return; |
| | | } |
| | | |
| | | valueItem.value = { |
| | | ...item, |
| | | ...selectedSafeHazardIds.value[0], |
| | | }; |
| | | valueItem.value.type = getTypeLabel(valueItem.value.type); |
| | | form.value.safeHazardId = item.id; |
| | | form.value.safeHazardId = selectedSafeHazardIds.value[0].id; |
| | | safeHazardSelectVisible.value = false; |
| | | }; |
| | | |
| | |
| | | const pagination1 = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | getList(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = selection => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | // ä¸»è¡¨æ ¼ä¹åªä¿çæåä¸ä¸ªéä¸ç项 |
| | | if (selection.length > 1) { |
| | | const lastSelected = selection[selection.length - 1]; |
| | | selectedIds.value = [lastSelected.id]; |
| | | } else if (selection.length === 1) { |
| | | selectedIds.value = [selection[0].id]; |
| | | } else { |
| | | selectedIds.value = []; |
| | | } |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else { |
| | | } else if (dialogType.value === "edit") { |
| | | await formRef1.value.validate(); |
| | | safeHazardRecordUpdate({ ...form.value }) |
| | | .then(res => { |
| | |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (dialogType.value === "view") { |
| | | // æ¥ç模å¼ä¸ä¸æäº¤è¡¨å |
| | | dialogVisible.value = false; |
| | | } |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | |
| | | v-model="fileListDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :is-show-pagination="true" |
| | | :page="filePagination" |
| | | :upload-method="handleUpload" |
| | | :delete-method="handleFileDelete" |
| | | @pagination="paginationSearch" |
| | | title="éä»¶å表" /> |
| | | </div> |
| | | </template> |
| | |
| | | const fileListRef = ref(null); |
| | | const fileListDialogVisible = ref(false); |
| | | const currentFileRow = ref(null); |
| | | const filePagination = ref({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const downLoadFile = row => { |
| | | currentFileRow.value = row; |
| | | fileListPage({ safeCertificationId: row.id }).then(res => { |
| | | fileListPage({ |
| | | safeCertificationId: row.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }).then(res => { |
| | | if (fileListRef.value) { |
| | | fileListRef.value.open(res.data.records); |
| | | } |
| | | filePagination.value.total = res.data.total || 0; |
| | | }); |
| | | }; |
| | | const currentFactoryName = ref(""); |
| | |
| | | // éæ°å è½½æä»¶å表 |
| | | const listRes = await fileListPage({ |
| | | safeCertificationId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | // è¿åæ°æä»¶ä¿¡æ¯ |
| | | resolve({ |
| | |
| | | input.click(); |
| | | }); |
| | | }; |
| | | const paginationSearch = async (page, size) => { |
| | | filePagination.value.current = page; |
| | | filePagination.value.size = size; |
| | | const listRes = await fileListPage({ |
| | | safeCertificationId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item, |
| | | })); |
| | | |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | }; |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async row => { |
| | | try { |
| | |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await fileListPage({ |
| | | safeCertificationId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | } |
| | | return true; // è¿å true 表示å 餿åï¼ç»ä»¶ä¼æ´æ°å表 |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | v-model="fileListDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :is-show-pagination="true" |
| | | :page="filePagination" |
| | | :upload-method="handleUpload" |
| | | :delete-method="handleFileDelete" |
| | | @pagination="paginationSearch" |
| | | title="éä»¶å表" /> |
| | | </div> |
| | | </template> |
| | |
| | | const currentFileRow = ref(null); |
| | | const downLoadFile = row => { |
| | | currentFileRow.value = row; |
| | | safeTrainingFileListPage({ safeTrainingId: row.id }).then(res => { |
| | | safeTrainingFileListPage({ |
| | | safeTrainingId: row.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }).then(res => { |
| | | if (fileListRef.value) { |
| | | fileListRef.value.open(res.data.records); |
| | | filePagination.value.total = res.data?.total || 0; |
| | | } |
| | | }); |
| | | }; |
| | |
| | | // éæ°å è½½æä»¶å表 |
| | | const listRes = await safeTrainingFileListPage({ |
| | | safeTrainingId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | // è¿åæ°æä»¶ä¿¡æ¯ |
| | | resolve({ |
| | |
| | | input.click(); |
| | | }); |
| | | }; |
| | | const filePagination = ref({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const paginationSearch = async (page, size) => { |
| | | filePagination.value.current = page; |
| | | filePagination.value.size = size; |
| | | const listRes = await safeTrainingFileListPage({ |
| | | safeTrainingId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item, |
| | | })); |
| | | |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | }; |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async row => { |
| | | try { |
| | |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await safeTrainingFileListPage({ |
| | | safeTrainingId: currentFileRow.value.id, |
| | | current: filePagination.value.current, |
| | | size: filePagination.value.size, |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | |
| | | ...item, |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | filePagination.value.total = listRes.data?.total || 0; |
| | | } |
| | | } |
| | | return true; // è¿å true 表示å 餿åï¼ç»ä»¶ä¼æ´æ°å表 |
| | |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | getList(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | |
| | | </el-row> |
| | | <el-table :data="productData" border @selection-change="productSelected" show-summary |
| | | :summary-method="summarizeMainTable"> |
| | | <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" /> |
| | | <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" |
| | | :selectable="(row) => !isProductShipped(row)" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column fixed="right" label="æä½" min-width="60" align="center" v-if="operationType !== 'view'"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row,scope.$index)">ç¼è¾</el-button> |
| | | <el-button link type="primary" size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openProductForm('edit', scope.row,scope.$index)">ç¼è¾</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | const productIndex = ref(0); |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProductForm = async (type, row, index) => { |
| | | // ç¼è¾æ¶æ£æ¥äº§åæ¯å¦å·²åè´§æå®¡æ ¸éè¿ |
| | | if (type === "edit" && isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("å·²åè´§æå®¡æ ¸éè¿ç产åä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | |
| | | productOperationType.value = type; |
| | | productForm.value = {}; |
| | | proxy.resetForm("productFormRef"); |
| | |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦æå·²åè´§æå®¡æ ¸éè¿ç产å |
| | | const shippedProducts = productSelectedRows.value.filter(row => isProductShipped(row)); |
| | | if (shippedProducts.length > 0) { |
| | | proxy.$modal.msgWarning("å·²åè´§æå®¡æ ¸éè¿ç产åä¸è½å é¤"); |
| | | return; |
| | | } |
| | | |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | const index = productData.value.findIndex( |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | /** 夿åä¸ªäº§åæ¯å¦å·²åè´§ï¼æ ¹æ®shippingStatus夿ï¼å·²åè´§æå®¡æ ¸éè¿ä¸å¯ç¼è¾åå é¤ï¼ */ |
| | | const isProductShipped = (product) => { |
| | | if (!product) return false; |
| | | const status = String(product.shippingStatus || "").trim(); |
| | | // 妿åè´§ç¶ææ¯"å·²åè´§"æ"å®¡æ ¸éè¿"ï¼åä¸å¯ç¼è¾åå é¤ |
| | | return status === "å·²åè´§" || status === "å®¡æ ¸éè¿"; |
| | | }; |
| | | |
| | | /** 夿éå®è®¢å䏿¯å¦åå¨å·²åè´§/åè´§å®æç产åï¼ä¸å¯å é¤ï¼ */ |
| | | const hasShippedProducts = (products) => { |
| | | if (!products || !products.length) return false; |