| | |
| | | }) |
| | | } |
| | | |
| | | |
| | | export function findAllQualifiedStockRecordTypeOptions() { |
| | | // åæ ¼å
¥åºæ¥æºç±»å |
| | | export function findAllQualifiedStockInRecordTypeOptions() { |
| | | return request({ |
| | | url: '/basic/enum/StockQualifiedRecordTypeEnum', |
| | | url: '/basic/enum/StockInQualifiedRecordTypeEnum', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | export function findAllUnqualifiedStockRecordTypeOptions() { |
| | | // ä¸åæ ¼å
¥åºæ¥æºç±»å |
| | | export function findAllUnQualifiedStockInRecordTypeOptions() { |
| | | return request({ |
| | | url: '/basic/enum/StockUnQualifiedRecordTypeEnum', |
| | | url: '/basic/enum/StockInUnQualifiedRecordTypeEnum', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // åæ ¼åºåºæ¥æºç±»å |
| | | export function findAllQualifiedStockOutRecordTypeOptions() { |
| | | return request({ |
| | | url: '/basic/enum/StockOutQualifiedRecordTypeEnum', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // ä¸åæ ¼åºåºæ¥æºç±»å |
| | | export function findAllUnQualifiedStockOutRecordTypeOptions() { |
| | | return request({ |
| | | url: '/basic/enum/StockOutUnQualifiedRecordTypeEnum', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | // å®åå¤ç-éä»¶å表 |
| | | export function afterSalesServiceFileListPage(query) { |
| | | return request({ |
| | | url: '/afterSalesService/file/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | // å®åå¤ç-éä»¶æ°å¢ |
| | | export function afterSalesServiceFileAdd(data) { |
| | | return request({ |
| | | url: '/afterSalesService/file/add', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | // å®åå¤ç-éä»¶å é¤ |
| | | export function afterSalesServiceFileDel(ids) { |
| | | return request({ |
| | | url: '/afterSalesService/file/del', |
| | | method: 'delete', |
| | | data: ids, |
| | | }) |
| | | } |
| | | |
| | | // å®åå¤ç-维修记å½å表 |
| | | export function afterSalesServiceRepairListPage(query) { |
| | | return request({ |
| | | url: '/afterSalesService/repair/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // 临æå®å管ç-å页æ¥è¯¢ |
| | | export function expiryAfterSalesListPage(query) { |
| | | return request({ |
| | |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-æ°å¢ |
| | | export function addProductOrder(data) { |
| | | return request({ |
| | | url: "/productOrder/addProductOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function delProductOrder(ids) { |
| | | return request({ |
| | | url: `/productOrder/${ids}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-æ¥è¯¢äº§åç»æå表 |
| | | export function listProcessBom(query) { |
| | | return request({ |
| | |
| | | } |
| | | |
| | | // è·åçç¹æ£æµææ ç»è®¡ |
| | | export function getTopParameters(inspectType) { |
| | | export function getTopParameters(modelType) { |
| | | return request({ |
| | | url: '/qualityReport/getTopParameters', |
| | | method: 'get', |
| | | params: { inspectType } |
| | | params: { modelType } |
| | | }) |
| | | } |
| | |
| | | // é¦é¡µæ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å·¥åºæ°æ®ç产ç»è®¡æç» |
| | | export const processDataProductionStatistics = (params) => { |
| | | return request({ |
| | | url: "/home/processDataProductionStatistics", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // è´¨éç»è®¡ |
| | | export const qualityInspectionStatistics = (params) => { |
| | | return request({ |
| | | url: "/home/qualityInspectionStatistics", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // åæææ£æµ |
| | | export const rawMaterialDetection = (query) => { |
| | | return request({ |
| | | url: "/home/rawMaterialDetection", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | }; |
| | | |
| | | // è¿ç¨æ£æµ |
| | | export const processDetection = (query) => { |
| | | return request({ |
| | | url: "/home/processDetection", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | }; |
| | | |
| | | // æååºåæ£æµ |
| | | export const factoryDetection = (query) => { |
| | | return request({ |
| | | url: "/home/factoryDetection", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | }; |
| | | |
| | | // æ£éªæ°é |
| | | export const qualityInspectionCount = () => { |
| | | return request({ |
| | | url: "/home/qualityInspectionCount", |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // ä¸åæ ¼é¢è¦ |
| | | export const nonComplianceWarning = () => { |
| | | return request({ |
| | | url: "/home/nonComplianceWarning", |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // 宿æ£éªæ° |
| | | export const completedInspectionCount = () => { |
| | | return request({ |
| | | url: "/home/completedInspectionCount", |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // ä¸åæ ¼äº§åæå |
| | | export const unqualifiedProductRanking = () => { |
| | | return request({ |
| | | url: "/home/unqualifiedProductRanking", |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // ä¸åæ ¼æ£åå¤çåæ |
| | | export const unqualifiedProductProcessingAnalysis = () => { |
| | | return request({ |
| | | url: "/home/unqualifiedProductProcessingAnalysis", |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // éå®-éè´-åºåæ°æ® |
| | | export const getBusiness = () => { |
| | | return request({ |
| | |
| | | method: "get", |
| | | }); |
| | | }; |
| | | // è´¨æ£åæ |
| | | export const qualityStatistics = () => { |
| | | // è´¨æ£åæï¼å¯ä¼ dateType: 1å¨ 2æ 3å£åº¦ï¼ |
| | | export const qualityStatistics = (params) => { |
| | | return request({ |
| | | url: "/home/qualityStatistics", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å·¥åæ§è¡æçåæï¼dateType: 1å¨ 2æ 3å£åº¦ï¼ |
| | | export const workOrderEfficiencyAnalysis = (params) => { |
| | | return request({ |
| | | url: "/home/workOrderEfficiencyAnalysis", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // çäº§æ ¸ç®åæ |
| | | export const productionAccountingAnalysis = (query) => { |
| | | return request({ |
| | | url: "/home/productionAccountingAnalysis", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | }; |
| | | // åºæ¶åºä»ç»è®¡ |
| | |
| | | export const getProgressStatistics = () => { |
| | | return request({ |
| | | url: "/home/progressStatistics", |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // è®¢åæ°éç»è®¡ï¼çäº§è®¢åæ°ãå·²å®æè®¢åæ°ãå¾
çäº§è®¢åæ°ï¼ |
| | | export const orderCount = () => { |
| | | return request({ |
| | | url: "/home/orderCount", |
| | | method: "get", |
| | | }); |
| | | }; |
| | |
| | | }); |
| | | }; |
| | | |
| | | // å·¥åºäº§åºåæï¼dateType: 1å¨ 2æ 3å£åº¦ï¼ |
| | | export const processOutputAnalysis = (params) => { |
| | | return request({ |
| | | url: "/home/processOutputAnalysis", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // åææéè´éé¢å æ¯ |
| | | export const rawMaterialPurchaseAmountRatio = () => { |
| | | return request({ |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æå
¥äº§åºåæ |
| | | export const inputOutputAnalysis = (params) => { |
| | | return request({ |
| | | url: "/home/inputOutputAnalysis", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // 产åå¨è½¬å¤©æ° |
| | | export const productTurnoverDays = () => { |
| | | 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> |
| | | |
| | |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | const emit = defineEmits(['finished']) |
| | | const emit = defineEmits(['finished', 'click']) |
| | | |
| | | // Props |
| | | const props = defineProps({ |
| | |
| | | }, |
| | | dataset: { |
| | | type: Object, |
| | | default: () => {} |
| | | default: () => { } |
| | | }, |
| | | xAxis: { |
| | | type: Array, |
| | |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | option: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | option: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | }) |
| | | |
| | | import { watch } from 'vue' |
| | |
| | | chartInstance = echarts.init(chartRef.value) |
| | | finishedHandler = () => emit('finished') |
| | | chartInstance.on('finished', finishedHandler) |
| | | chartInstance.on('click', (params) => { |
| | | emit('click', params) |
| | | }) |
| | | renderChart() |
| | | // setOption åè¡¥ä¸æ¬¡ resizeï¼ç¡®ä¿é¦å±å°ºå¯¸æ£ç¡® |
| | | nextTick(() => { |
| | |
| | | |
| | | // Methods |
| | | function generateChart(option) { |
| | | const copiedOption = option |
| | | |
| | | const copiedOption = option |
| | | |
| | | if (copiedOption.series && copiedOption.series.length > 0) { |
| | | copiedOption.series.forEach((s, index) => { |
| | | if (s.type === 'line' && props.lineColors.length) { |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | |
| | | chartInstance.setOption(copiedOption) |
| | | } |
| | | |
| | |
| | | tooltip: props.tooltip, |
| | | visualMap: Object.keys(props.visualMap).length ? props.visualMap : undefined, |
| | | } |
| | | |
| | | |
| | | chartInstance.clear() |
| | | generateChart(option) |
| | | } |
| | |
| | | |
| | | // Watch all reactive props that affect the chart |
| | | watch( |
| | | () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap], |
| | | () => { |
| | | // 妿é¦å±è¿æ²¡åå§åæåï¼çå¾
å®¹å¨ ready å忏²æ |
| | | if (!chartInstance) { |
| | | initChartWhenReady() |
| | | return |
| | | } |
| | | renderChart() |
| | | // æ°æ®åååè¡¥ä¸æ¬¡ resizeï¼é¿å
å¸å±åå导è´çåç§» |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | }, |
| | | { deep: true, immediate: true } |
| | | () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap], |
| | | () => { |
| | | // 妿é¦å±è¿æ²¡åå§åæåï¼çå¾
å®¹å¨ ready å忏²æ |
| | | if (!chartInstance) { |
| | | initChartWhenReady() |
| | | return |
| | | } |
| | | renderChart() |
| | | // æ°æ®åååè¡¥ä¸æ¬¡ resizeï¼é¿å
å¸å±åå导è´çåç§» |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ) |
| | | </script> |
| | |
| | | :props="{ children: 'children', label: 'label' }" |
| | | highlight-current |
| | | node-key="id" |
| | | style=" |
| | | height: calc(100vh - 190px); |
| | | overflow-y: scroll; |
| | | scrollbar-width: none; |
| | | " |
| | | class="product-tree-scroll" |
| | | style="height: calc(100vh - 190px); overflow-y: auto" |
| | | > |
| | | <template #default="{ node, data }"> |
| | | <div class="custom-tree-node"> |
| | |
| | | <component :is="data.children && data.children.length > 0 |
| | | ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" /> |
| | | </el-icon> |
| | | {{ data.label }} |
| | | <span class="tree-node-label">{{ data.label }}</span> |
| | | </span> |
| | | <div> |
| | | <el-button |
| | |
| | | <el-input |
| | | v-model="form.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | maxlength="20" |
| | | show-word-limit |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | |
| | | productName: "", |
| | | }, |
| | | rules: { |
| | | productName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | productName: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | { max: 20, message: "产ååç§°ä¸è½è¶
è¿20个å符", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | modelForm: { |
| | | model: "", |
| | |
| | | display: flex; |
| | | } |
| | | .left { |
| | | width: 380px; |
| | | width: 450px; |
| | | min-width: 450px; |
| | | padding: 16px; |
| | | background: #ffffff; |
| | | } |
| | | .right { |
| | | width: calc(100% - 380px); |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding: 16px; |
| | | margin-left: 20px; |
| | | background: #ffffff; |
| | | } |
| | | .custom-tree-node { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | |
| | | padding-right: 8px; |
| | | } |
| | | .tree-node-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; /* åç´å±
ä¸ */ |
| | | align-items: center; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | .tree-node-content .orange-icon { |
| | | flex-shrink: 0; |
| | | } |
| | | .tree-node-label { |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .orange-icon { |
| | | color: orange; |
| | | font-size: 18px; |
| | | margin-right: 8px; /* 徿 䏿åä¹é´å ç¹é´è· */ |
| | | } |
| | | .product-tree-scroll { |
| | | scrollbar-width: thin; |
| | | scrollbar-color: #c0c4cc #f5f7fa; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar { |
| | | width: 8px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-track { |
| | | background: #f5f7fa; |
| | | border-radius: 4px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-thumb { |
| | | background: #c0c4cc; |
| | | border-radius: 4px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-thumb:hover { |
| | | background: #909399; |
| | | } |
| | | </style> |
| | |
| | | listKnowledgeBase({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.value.total = res.data.total; |
| | | // 妿å½å页æ°è¶
è¿æ»é¡µæ°ï¼éç½®å°ç¬¬1页并鿰æ¥è¯¢ |
| | | const maxPage = Math.ceil(res.data.total / page.value.size) || 1; |
| | | if (page.value.current > maxPage && maxPage > 0) { |
| | | page.value.current = 1; |
| | | // éæ°æ¥è¯¢ç¬¬1é¡µæ°æ® |
| | | return getList(); |
| | | } |
| | | tableData.value = res.data.records; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | const oldSize = page.value.size; |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | // 妿 size æ¹åäºï¼éç½®å°ç¬¬1页ï¼é¿å
å½å页è¶
åºèå´ |
| | | if (oldSize !== obj.limit) { |
| | | page.value.current = 1; |
| | | } |
| | | getList(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-table :data="regulations" |
| | | border |
| | | v-loading="tableLoading" |
| | | style="width: 100%"> |
| | | <el-table-column prop="regulationNum" |
| | | label="å¶åº¦ç¼å·" |
| | | width="120" /> |
| | | <el-table-column prop="title" |
| | | label="å¶åº¦æ é¢" |
| | | min-width="150" /> |
| | | <el-table-column prop="category" |
| | | label="åç±»" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="version" |
| | | label="çæ¬" |
| | | width="120" /> |
| | | <el-table-column prop="createUserName" |
| | | label="åå¸äºº" |
| | | width="120" /> |
| | | <el-table-column prop="createTime" |
| | | label="å叿¶é´" |
| | | width="180" /> |
| | | <el-table-column prop="status" |
| | | label="ç¶æ" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'çæä¸' : 'å·²åºæ¢' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="readCount" |
| | | label="已读人æ°" |
| | | width="100" /> |
| | | <el-table-column label="æä½" |
| | | width="320" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | @click="viewRegulation(scope.row)">æ¥ç</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | @click="repealEdit(scope.row)">åºå¼</el-button> |
| | | <el-button link |
| | | type="success" |
| | | @click="viewVersionHistory(scope.row)">çæ¬åå²</el-button> |
| | | <!-- <el-button link type="warning" @click="viewReadStatus(scope.row)">é
è¯»ç¶æ</el-button> --> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openFileDialog(scope.row)">éä»¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="regulationTableColumn" |
| | | :tableData="regulations" |
| | | :tableLoading="tableLoading" |
| | | :page="page" |
| | | :isShowPagination="true" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | <!-- ç¨å°ç³è¯·å¯¹è¯æ¡ï¼å·²ç§»é¤ï¼ --> |
| | |
| | | :delete-method="handleAttachmentDelete" |
| | | :rules-regulations-management-id="currentFileRuleId" |
| | | :name-column-label="'éä»¶åç§°'" |
| | | @upload="handleAttachmentUpload" /> |
| | | @upload="handleAttachmentUpload"/> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | delRuleFile, |
| | | addRuleFile, |
| | | } from "@/api/collaborativeApproval/rulesRegulationsManagementFile.js"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const operationType = ref("add"); |
| | |
| | | const currentFileRuleId = ref(null); |
| | | const filePage = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | size: 1000, |
| | | total: 0, |
| | | }); |
| | | // è§ç« å¶åº¦ç¸å
³ |
| | |
| | | }); |
| | | |
| | | const regulations = ref([]); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const regulationTableColumn = ref([ |
| | | { label: "å¶åº¦ç¼å·", prop: "regulationNum"}, |
| | | { label: "å¶åº¦æ é¢", prop: "title" }, |
| | | { |
| | | label: "åç±»", |
| | | prop: "category", |
| | | dataType: "tag", |
| | | formatData: (v) => getCategoryText(v), |
| | | formatType: () => "info", |
| | | }, |
| | | { label: "çæ¬", prop: "version", width: 120 }, |
| | | { label: "åå¸äºº", prop: "createUserName", width: 120 }, |
| | | { label: "å叿¶é´", prop: "createTime", width: 180 }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | width: 100, |
| | | dataType: "tag", |
| | | formatData: (v) => (v === "active" ? "çæä¸" : "å·²åºæ¢"), |
| | | formatType: (v) => (v === "active" ? "success" : "info"), |
| | | }, |
| | | { label: "已读人æ°", prop: "readCount", width: 100 }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | width: 320, |
| | | fixed: "right", |
| | | align: "center", |
| | | operation: [ |
| | | { name: "æ¥ç", clickFun: (row) => viewRegulation(row) }, |
| | | { name: "ç¼è¾", clickFun: (row) => handleEdit(row) }, |
| | | { name: "åºå¼", clickFun: (row) => repealEdit(row) }, |
| | | { name: "çæ¬åå²", clickFun: (row) => viewVersionHistory(row) }, |
| | | { name: "éä»¶", clickFun: (row) => openFileDialog(row) }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const versionHistory = ref([]); |
| | | |
| | |
| | | regulations.value = res.data.records; |
| | | // è¿æ»¤æå·²åºå¼çå¶åº¦ |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.value.total = res.data.total; |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // å页ååå¤ç |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getRegulationList(); |
| | | }; |
| | | onMounted(() => { |
| | | // åå§å |
| | | getRegulationList(); |
| | |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-table :data="sealApplications" border v-loading="tableLoading" style="width: 100%"> |
| | | <el-table-column prop="applicationNum" label="ç³è¯·ç¼å·" width="120" /> |
| | | <el-table-column prop="title" label="ç³è¯·æ é¢" min-width="200" /> |
| | | <el-table-column prop="createUserName" label="ç³è¯·äºº" width="120" /> |
| | | <el-table-column prop="department" label="æå±é¨é¨" width="150" /> |
| | | <el-table-column prop="sealType" label="ç¨å°ç±»å" width="120"> |
| | | <template #default="scope"> |
| | | {{ getSealTypeText(scope.row.sealType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="ç³è¯·æ¶é´" width="180" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link @click="viewSealDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button |
| | | v-if="scope.row.status === 'pending'" |
| | | link |
| | | type="primary" |
| | | @click="approveSeal(scope.row)" |
| | | > |
| | | å®¡æ¹ |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.status === 'pending'" |
| | | link |
| | | type="danger" |
| | | @click="rejectSeal(scope.row)" |
| | | > |
| | | æç» |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="sealTableColumn" |
| | | :tableData="sealApplications" |
| | | :tableLoading="tableLoading" |
| | | :page="page" |
| | | :isShowPagination="true" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | |
| | | </el-form> |
| | | </FormDialog> |
| | | |
| | | <!-- è§ç« å¶åº¦åå¸å¯¹è¯æ¡ --> |
| | | <!-- <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? 'åå¸å¶åº¦' : 'ç¼è¾å¶åº¦'" width="800px"> |
| | | <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px"> |
| | | <el-form-item label="å¶åº¦ç¼å·" prop="regulationNum"> |
| | | <el-input v-model="regulationForm.regulationNum" placeholder="请è¾å
¥å¶åº¦ç¼å·" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¶åº¦æ é¢" prop="title"> |
| | | <el-input v-model="regulationForm.title" placeholder="请è¾å
¥å¶åº¦æ é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¶åº¦åç±»" prop="category"> |
| | | <el-select v-model="regulationForm.category" placeholder="è¯·éæ©å¶åº¦åç±»" style="width: 100%"> |
| | | <el-option label="人äºå¶åº¦" value="hr" /> |
| | | <el-option label="è´¢å¡å¶åº¦" value="finance" /> |
| | | <el-option label="å®å
¨å¶åº¦" value="safety" /> |
| | | <el-option label="ææ¯å¶åº¦" value="tech" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å¶åº¦å
容" prop="content"> |
| | | <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="请è¾å
¥å¶åº¦è¯¦ç»å
容" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¶åº¦çæ¬" prop="version"> |
| | | <el-input v-model="regulationForm.version" placeholder="请è¾å
¥å¶åº¦çæ¬" /> |
| | | </el-form-item> |
| | | <el-form-item label="çææ¶é´" prop="effectiveTime"> |
| | | <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" placeholder="éæ©çææ¶é´" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="éç¨èå´" prop="scope"> |
| | | <el-checkbox-group v-model="regulationForm.scope"> |
| | | <el-checkbox label="all">å
¨ä½åå·¥</el-checkbox> |
| | | <el-checkbox label="manager">管çå±</el-checkbox> |
| | | <el-checkbox label="hr">人äºé¨é¨</el-checkbox> |
| | | <el-checkbox label="finance">è´¢å¡é¨é¨</el-checkbox> |
| | | <el-checkbox label="tech">ææ¯é¨é¨</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯å¦éè¦ç¡®è®¤" prop="requireConfirm"> |
| | | <el-switch v-model="regulationForm.requireConfirm" /> |
| | | <span class="ml-10">å¼å¯ååå·¥éè¦é
读确认</span> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="showRegulationDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitRegulation">åå¸å¶åº¦</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> --> |
| | | |
| | | <!-- ç¨å°è¯¦æ
å¯¹è¯æ¡ --> |
| | | <FormDialog |
| | | v-model="showSealDetailDialog" |
| | |
| | | </div> |
| | | </FormDialog> |
| | | |
| | | <!-- è§ç« å¶åº¦è¯¦æ
å¯¹è¯æ¡ --> |
| | | <FormDialog |
| | | v-model="showRegulationDetailDialog" |
| | | title="è§ç« å¶åº¦è¯¦æ
" |
| | | :width="'800px'" |
| | | @close="closeRegulationDetailDialog" |
| | | @confirm="handleRegulationDetailConfirm" |
| | | @cancel="closeRegulationDetailDialog" |
| | | > |
| | | <div v-if="currentRegulationDetail"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="å¶åº¦ç¼å·">{{ currentRegulationDetail.id }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¶åº¦æ é¢">{{ currentRegulationDetail.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="åç±»">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item> |
| | | <el-descriptions-item label="çæ¬">{{ currentRegulationDetail.version }}</el-descriptions-item> |
| | | <el-descriptions-item label="åå¸äºº">{{ currentRegulationDetail.createUserName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å叿¶é´">{{ currentRegulationDetail.createTime }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="mt-20"> |
| | | <h4>å¶åº¦å
容</h4> |
| | | <div class="regulation-content">{{ currentRegulationDetail.content }}</div> |
| | | </div> |
| | | <!-- 妿tableData>0 æ¾ç¤º --> |
| | | <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" > |
| | | <el-button type="success" @click="resetForm(currentRegulationDetail)">确认æ¥ç</el-button> |
| | | </div> |
| | | </div> |
| | | </FormDialog> |
| | | |
| | | <!-- çæ¬åå²å¯¹è¯æ¡ --> |
| | | <FormDialog |
| | | v-model="showVersionHistoryDialog" |
| | | title="çæ¬åå²" |
| | | :width="'800px'" |
| | | @close="closeVersionHistoryDialog" |
| | | @confirm="closeVersionHistoryDialog" |
| | | @cancel="closeVersionHistoryDialog" |
| | | > |
| | | <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="version" label="çæ¬å·" width="100" /> |
| | | <el-table-column prop="updateTime" label="æ´æ°æ¶é´" width="180" /> |
| | | <el-table-column prop="createUserName" label="æ´æ°äºº" width="120" /> |
| | | <el-table-column prop="changeLog" label="åæ´è¯´æ"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'çæä¸' : 'å·²åºæ¢' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </FormDialog> |
| | | |
| | | <!-- é
è¯»ç¶æå¯¹è¯æ¡ --> |
| | | <FormDialog |
| | | v-model="showReadStatusDialog" |
| | | title="é
è¯»ç¶æ" |
| | | :width="'800px'" |
| | | @close="closeReadStatusDialog" |
| | | @confirm="closeReadStatusDialog" |
| | | @cancel="closeReadStatusDialog" |
| | | > |
| | | <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="employee" label="åå·¥å§å" width="120" /> |
| | | <el-table-column prop="department" label="æå±é¨é¨" width="150" /> |
| | | <el-table-column prop="createTime" label="é
读æ¶é´" width="180" /> |
| | | <el-table-column prop="confirmTime" label="确认æ¶é´" width="180" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'confirmed' ? '已确认' : 'æªç¡®è®¤' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ref, reactive, onMounted, getCurrentInstance, watch } from 'vue' |
| | | import { useRoute } from 'vue-router' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus } from '@element-plus/icons-vue' |
| | | import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus } from '@/api/collaborativeApproval/sealManagement.js' |
| | | import { el } from 'element-plus/es/locales.mjs' |
| | | import { getUserProfile, userListNoPageByTenantId } from '@/api/system/user.js' |
| | | import { listSealApplication, addSealApplication, updateSealApplication } from '@/api/collaborativeApproval/sealManagement.js' |
| | | import { userListNoPageByTenantId } from '@/api/system/user.js' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { userLoginFacotryList } from "@/api/system/user.js" |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js" |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const currentUser = ref(null) |
| | | const activeTab = ref('seal') |
| | | const operationType = ref('add') |
| | | const tableData = ref([]) |
| | | // ç¨å°ç³è¯·ç¸å
³ |
| | | const userStore = useUserStore() |
| | | const route = useRoute() |
| | |
| | | // å页忰 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | size: 10, |
| | | total: 0 |
| | | }) |
| | | // è§ç« å¶åº¦ç¸å
³ |
| | | const showRegulationDialog = ref(false) |
| | | const showRegulationDetailDialog = ref(false) |
| | | const showVersionHistoryDialog = ref(false) |
| | | const showReadStatusDialog = ref(false) |
| | | const currentRegulationDetail = ref(null) |
| | | const regulationFormRef = ref() |
| | | const regulationForm = reactive({ |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: false |
| | | }) |
| | | |
| | | const readStatus = ref({ |
| | | id: '', |
| | | ruleId: '', |
| | | employee: '', |
| | | department: '', |
| | | createTime: '', |
| | | confirmTime: '', |
| | | status: 'unconfirmed' |
| | | }) |
| | | |
| | | const regulationRules = { |
| | | title: [{ required: true, message: '请è¾å
¥å¶åº¦æ é¢', trigger: 'blur' }], |
| | | category: [{ required: true, message: 'è¯·éæ©å¶åº¦åç±»', trigger: 'change' }], |
| | | content: [{ required: true, message: '请è¾å
¥å¶åº¦å
容', trigger: 'blur' }], |
| | | effectiveTime: [{ required: true, message: 'è¯·éæ©çææ¶é´', trigger: 'change' }], |
| | | scope: [{ required: true, message: 'è¯·éæ©éç¨èå´', trigger: 'change' }] |
| | | } |
| | | |
| | | const regulationSearchForm = reactive({ |
| | | title: '', |
| | | category: '' |
| | | }) |
| | | |
| | | // åæ°æ® |
| | | const sealApplications = ref([]) |
| | | |
| | | const regulations = ref([]) |
| | | |
| | | const versionHistory = ref([]) |
| | | |
| | | const readStatusList = ref([]) |
| | | // { employee: 'éå¿å¼º', department: 'éå®é¨', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' }, |
| | | // { employee: 'åé
å©·', department: 'ææ¯é¨', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' }, |
| | | // { employee: 'ç建å½', department: 'è´¢å¡é¨', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' } |
| | | |
| | | // ç¨å°ç³è¯·ç¶æ |
| | | const getStatusType = (status) => { |
| | |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | // å¶åº¦ç¶æ |
| | | // ç¨å°ç³è¯·ç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | pending: 'å¾
审æ¹', |
| | |
| | | official: 'å
¬ç« ', |
| | | contract: 'ååä¸ç¨ç« ', |
| | | finance: 'è´¢å¡ä¸ç¨ç« ', |
| | | legal: 'æ³äººç« ', |
| | | tegal: 'ææ¯ä¸ç¨ç« ' |
| | | } |
| | | return sealTypeMap[sealType] || 'æªç¥' |
| | | } |
| | | // å¶åº¦åç±» |
| | | const getCategoryText = (category) => { |
| | | const categoryMap = { |
| | | hr: '人äºå¶åº¦', |
| | | finance: 'è´¢å¡å¶åº¦', |
| | | safety: 'å®å
¨å¶åº¦', |
| | | tech: 'ææ¯å¶åº¦' |
| | | |
| | | // ç¨å°ç³è¯·è¡¨æ ¼åé
ç½®ï¼éå¨ getStatusText/getSealTypeText çä¹åå®ä¹ï¼ |
| | | const sealTableColumn = ref([ |
| | | { label: 'ç³è¯·ç¼å·', prop: 'applicationNum',}, |
| | | { label: 'ç³è¯·æ é¢', prop: 'title', showOverflowTooltip: true }, |
| | | { label: 'ç³è¯·äºº', prop: 'createUserName', }, |
| | | { label: 'æå±é¨é¨', prop: 'department', width: 150 }, |
| | | { |
| | | label: 'ç¨å°ç±»å', |
| | | prop: 'sealType', |
| | | dataType: 'tag', |
| | | formatData: (v) => getSealTypeText(v), |
| | | formatType: () => 'info' |
| | | }, |
| | | { label: 'ç³è¯·æ¶é´', prop: 'createTime', width: 180 }, |
| | | { |
| | | label: 'ç¶æ', |
| | | prop: 'status', |
| | | width: 100, |
| | | dataType: 'tag', |
| | | formatData: (v) => getStatusText(v), |
| | | formatType: (v) => getStatusType(v) |
| | | }, |
| | | { |
| | | dataType: 'action', |
| | | label: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | align: 'center', |
| | | operation: [ |
| | | { name: 'æ¥ç', clickFun: (row) => viewSealDetail(row) }, |
| | | { |
| | | name: '审æ¹', |
| | | clickFun: (row) => approveSeal(row), |
| | | showHide: (row) => row.status === 'pending' |
| | | }, |
| | | { |
| | | name: 'æç»', |
| | | clickFun: (row) => rejectSeal(row), |
| | | showHide: (row) => row.status === 'pending' |
| | | } |
| | | ] |
| | | } |
| | | return categoryMap[category] || 'æªç¥' |
| | | } |
| | | ]) |
| | | |
| | | // æç´¢å°ç« ç³è¯· |
| | | const searchSealApplications = () => { |
| | | page.current=1 |
| | |
| | | sealSearchForm.status = '' |
| | | sealSearchForm.applicationNum = '' |
| | | searchSealApplications() |
| | | } |
| | | // æç´¢å¶åº¦ |
| | | const searchRegulations = () => { |
| | | page.current=1 |
| | | getRegulationList() |
| | | } |
| | | // éç½®å¶åº¦æç´¢ |
| | | const resetRegulationSearch = () => { |
| | | regulationSearchForm.title = '' |
| | | regulationSearchForm.category = '' |
| | | searchRegulations() |
| | | } |
| | | // æäº¤ç¨å°ç³è¯· |
| | | const submitSealApplication = async () => { |
| | |
| | | const closeSealDetailDialog = () => { |
| | | showSealDetailDialog.value = false |
| | | } |
| | | // å
³éè§ç« å¶åº¦è¯¦æ
å¯¹è¯æ¡ |
| | | const closeRegulationDetailDialog = () => { |
| | | showRegulationDetailDialog.value = false |
| | | } |
| | | // å¤çè§ç« å¶åº¦è¯¦æ
确认 |
| | | const handleRegulationDetailConfirm = () => { |
| | | // 妿tableData>0ï¼æ§è¡ç¡®è®¤æ¥çæä½ |
| | | if (currentRegulationDetail.value && tableData.value && tableData.value.length > 0) { |
| | | resetForm(currentRegulationDetail.value) |
| | | } |
| | | closeRegulationDetailDialog() |
| | | } |
| | | // å
³éçæ¬åå²å¯¹è¯æ¡ |
| | | const closeVersionHistoryDialog = () => { |
| | | showVersionHistoryDialog.value = false |
| | | } |
| | | // å
³éé
è¯»ç¶æå¯¹è¯æ¡ |
| | | const closeReadStatusDialog = () => { |
| | | showReadStatusDialog.value = false |
| | | } |
| | | // æ°å¢ |
| | | const handleAdd = () => { |
| | | operationType.value = 'add' |
| | | resetRegulationForm() |
| | | showRegulationDialog.value = true |
| | | } |
| | | |
| | | // ç¼è¾ |
| | | const handleEdit = (row) => { |
| | | operationType.value = 'edit' |
| | | Object.assign(regulationForm, row) |
| | | showRegulationDialog.value = true |
| | | } |
| | | // åºå¼ |
| | | const repealEdit = (row) => { |
| | | operationType.value = 'edit' |
| | | Object.assign(regulationForm, row) |
| | | regulationForm.status = 'repealed' |
| | | ElMessageBox.confirm('确认åºå¼è¯¥å¶åº¦ï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å¶åº¦åºå¼æå') |
| | | // showRegulationDialog.value = false |
| | | getRegulationList() |
| | | resetRegulationForm() |
| | | } |
| | | }) |
| | | }).catch(() => { |
| | | ElMessage({ |
| | | type: 'info', |
| | | message: '已忶åºå¼' |
| | | }) |
| | | }) |
| | | } |
| | | // åå¸å¶åº¦ |
| | | const submitRegulation = async () => { |
| | | try { |
| | | await regulationFormRef.value.validate() |
| | | if(operationType.value == 'add'){ |
| | | addRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å¶åº¦å叿å') |
| | | showRegulationDialog.value = false |
| | | getRegulationList() |
| | | resetRegulationForm() |
| | | } |
| | | }) |
| | | }else{ |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å¶åº¦ç¼è¾æå') |
| | | showRegulationDialog.value = false |
| | | resetRegulationForm() |
| | | getRegulationList() |
| | | }})} |
| | | }catch(err){ |
| | | ElMessage.error(err.msg) |
| | | } |
| | | } |
| | | //éç½®å¶åº¦è¡¨å |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: false |
| | | }) |
| | | } |
| | | |
| | | |
| | | // æ¥çç¨å°ç³è¯·è¯¦æ
|
| | | const viewSealDetail = (row) => { |
| | |
| | | } |
| | | // 审æ¹ç¨å°ç³è¯· |
| | | const approveSeal = (row) => { |
| | | console.log(row) |
| | | ElMessageBox.confirm('确认éè¿è¯¥ç¨å°ç³è¯·ï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | |
| | | updateSealApplication(row).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('审æ¹éè¿') |
| | | getSealApplicationList() |
| | | } |
| | | }) |
| | | }) |
| | |
| | | row.status = 'rejected' |
| | | updateSealApplication(row).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å®¡æ¹æç»') |
| | | ElMessage.success('å·²æç»ç³è¯·') |
| | | getSealApplicationList() |
| | | } |
| | | }) |
| | | ElMessage.success('å·²æç»ç³è¯·') |
| | | }) |
| | | } |
| | | // è·åå¨èåå·¥å表 |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | //è·åå½åç»å½ç¨æ·ä¿¡æ¯ |
| | | getUserProfile().then(res => { |
| | | if(res.code == 200){ |
| | | console.log(res.data.userName) |
| | | currentUser.value = res.data.userName |
| | | } |
| | | }) |
| | | staffOnJobListPage({staffState: 1, ...page}).then(res => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records |
| | | // //çéåºåcurrentUserååç人å |
| | | tableData.value = res.data.records.filter(item => item.staffName === currentUser.value) |
| | | page.total = res.data.total; |
| | | |
| | | if(tableData.value.length == 0){ |
| | | ElMessage.error('å½åç¨æ·æªå å
¥ä»»ä½é¨é¨') |
| | | } |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | |
| | | |
| | | }; |
| | | |
| | | // æ¥çå¶åº¦çæ¬åå² |
| | | const viewVersionHistory = (row) => { |
| | | showVersionHistoryDialog.value = true |
| | | const params = { |
| | | |
| | | category: row.category |
| | | } |
| | | listRuleManagement(page,params).then(res => { |
| | | if(res.code == 200){ |
| | | versionHistory.value = res.data.records |
| | | } |
| | | }) |
| | | } |
| | | // æ¥çå¶åº¦è¯¦æ
|
| | | const viewRegulation = (row) => { |
| | | getList() |
| | | currentRegulationDetail.value = row |
| | | showRegulationDetailDialog.value = true |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if(res.code == 200){ |
| | | readStatusList.value = res.data |
| | | if(readStatusList.value.length==0 && tableData.value.length>0){ |
| | | const params = { |
| | | ruleId: row.id, |
| | | employee: tableData.value[0].staffName, |
| | | department: tableData.value[0].postJob, |
| | | status: 'unconfirmed' |
| | | } |
| | | addReadingStatus(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å¶åº¦é
读æå') |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | } |
| | | // æ¥çå¶åº¦é
è¯»ç¶æ |
| | | const viewReadStatus = (row) => { |
| | | showReadStatusDialog.value = true |
| | | //æ¥çé
è¯»ç¶æå表 |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if(res.code == 200){ |
| | | readStatusList.value = res.data |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //确认æ¥ç |
| | | const resetForm = (row) => { |
| | | console.log("row",row) |
| | | row.readCount = row.readCount + 1 |
| | | |
| | | updateRuleManagement(row).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('æ¥çæ°éä¿®æ¹æå') |
| | | //ä¿®æ¹é
è¯»ç¶æ |
| | | //æ ¹æ®å¶åº¦idåå½åç»å½çåå·¥å¾å°é
è¯»ç¶æ |
| | | // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName ) |
| | | // if(item.length>0){ |
| | | // item[0].status = 'confirmed', |
| | | // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | // } |
| | | // çéå½åå工对åºè¯¥å¶åº¦çé
è¯»ç¶æè®°å½ |
| | | let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id); |
| | | |
| | | if (statusItem) { |
| | | // 妿æ¾å°è®°å½ï¼æ´æ°ç¶æå确认æ¶é´ |
| | | statusItem.status = 'confirmed'; |
| | | // æ ¼å¼åæ¶é´ä¸º"YYYY-MM-DD HH:mm:ss"æ ¼å¼ |
| | | const now = new Date(); |
| | | statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; |
| | | // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | |
| | | updateReadingStatus(statusItem).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å¶åº¦é
è¯»ç¶æä¿®æ¹æå') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | } |
| | | }) |
| | | } |
| | | |
| | |
| | | // è·åå°ç« ç³è¯·åè¡¨æ°æ® |
| | | const getSealApplicationList = async () => { |
| | | tableLoading.value = true |
| | | listSealApplication(page,sealSearchForm) |
| | | listSealApplication(page, sealSearchForm) |
| | | .then(res => { |
| | | //è·åå½åç»å½çé¨é¨ä¿¡æ¯ |
| | | // è·åå½åç»å½çé¨é¨ä¿¡æ¯å¹¶è¿æ»¤æ°æ® |
| | | const currentFactoryName = userStore.currentFactoryName |
| | | if (currentFactoryName) { |
| | | // æ ¹æ®currentFactoryNameè¿æ»¤åºdepartmentç¸åçæ°æ® |
| | | sealApplications.value = res.data.records.filter(item => item.department === currentFactoryName) |
| | | // æ´æ°è¿æ»¤åçæ»æ° |
| | | page.total = sealApplications.value.length |
| | | } else { |
| | | // å¦ææ²¡æcurrentFactoryNameï¼åæ¾ç¤ºæææ°æ® |
| | | sealApplications.value = res.data.records |
| | | page.total = res.data.total |
| | | } |
| | | // sealApplications.value = res.data.records |
| | | // page.value.total = res.data.total; |
| | | tableLoading.value = false; |
| | | |
| | | sealApplications.value = res.data.records |
| | | page.total = res.data.total |
| | | tableLoading.value = false |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | // è·åè§ç« å¶åº¦åè¡¨æ°æ® |
| | | const getRegulationList = async () => { |
| | | tableLoading.value = true |
| | | listRuleManagement(page,regulationSearchForm) |
| | | .then(res => { |
| | | |
| | | regulations.value = res.data.records |
| | | // è¿æ»¤æå·²åºå¼çå¶åº¦ |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | |
| | | // å页ååå¤ç |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | |
| | | } else { |
| | | getSealApplicationList() |
| | | } |
| | | getRegulationList() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .regulation-content { |
| | | background-color: #f5f5f5; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | height: 200px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <FileListDialog |
| | | ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | title="å®åéä»¶" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :upload-method="handleFileUpload" |
| | | :delete-method="handleFileDelete" |
| | | /> |
| | | <el-dialog |
| | | v-model="repairDialogVisible" |
| | | title="维修记å½" |
| | | width="700px" |
| | | destroy-on-close |
| | | @close="repairRecordList = []" |
| | | > |
| | | <el-table |
| | | :data="repairRecordList" |
| | | border |
| | | v-loading="repairRecordLoading" |
| | | max-height="400" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="55" align="center" /> |
| | | <el-table-column label="ç»´ä¿®æ¥æ" prop="maintenanceTime" min-width="120" show-overflow-tooltip> |
| | | <template #default="{ row }"> |
| | | {{ row.maintenanceTime || row.repairTime || '-' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="维修人" prop="maintenanceName" min-width="100" show-overflow-tooltip> |
| | | <template #default="{ row }"> |
| | | {{ row.maintenanceName || row.repairName || '-' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç»´ä¿®ç»æ" prop="maintenanceResult" min-width="180" show-overflow-tooltip /> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button @click="repairDialogVisible = false">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, getCurrentInstance, nextTick} from "vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {afterSalesServiceDelete, afterSalesServiceListPage} from "@/api/customerService/index.js"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import request from "@/utils/request"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { |
| | | afterSalesServiceDelete, |
| | | afterSalesServiceListPage, |
| | | afterSalesServiceFileListPage, |
| | | afterSalesServiceFileAdd, |
| | | afterSalesServiceFileDel, |
| | | afterSalesServiceRepairListPage, |
| | | } from "@/api/customerService/index.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore() |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | width: 240, |
| | | operation: [ |
| | | { |
| | | name: "å¤ç", |
| | |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("view", row); |
| | | }, |
| | | }, |
| | | // TODO ä¸ºåæ¥åæ·»å ç |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | // TODO ä¸ºåæ¥åæ·»å ç |
| | | { |
| | | name: "维修记å½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openRepairDialog(row); |
| | | }, |
| | | }, |
| | | ], |
| | |
| | | selectedRows.value = selection; |
| | | }; |
| | | const formDia = ref() |
| | | const fileListRef = ref(null) |
| | | const fileListDialogVisible = ref(false) |
| | | const currentFileRow = ref(null) |
| | | const repairDialogVisible = ref(false) |
| | | const repairRecordList = ref([]) |
| | | const repairRecordLoading = ref(false) |
| | | |
| | | // æå¼ç»´ä¿®è®°å½å¼¹æ¡ |
| | | const openRepairDialog = async (row) => { |
| | | repairDialogVisible.value = true |
| | | repairRecordLoading.value = true |
| | | repairRecordList.value = [] |
| | | try { |
| | | const res = await afterSalesServiceRepairListPage({ |
| | | afterSalesServiceId: row.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (res.code === 200 && res.data?.records) { |
| | | repairRecordList.value = res.data.records |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("è·å维修记å½å¤±è´¥") |
| | | } finally { |
| | | repairRecordLoading.value = false |
| | | } |
| | | } |
| | | |
| | | // æå¼éä»¶å¼¹æ¡----- TODOï¼æ¥å£æ¯æ²¡æå¯¹æ¥çï¼éè¦æ°å¢æ¥å£ï¼ä¸ºåæ¥åæ·»å ç |
| | | const openFilesFormDia = async (row) => { |
| | | currentFileRow.value = row |
| | | try { |
| | | const res = await afterSalesServiceFileListPage({ |
| | | afterSalesServiceId: row.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (res.code === 200 && fileListRef.value) { |
| | | const fileList = (res.data?.records || []).map((item) => ({ |
| | | name: item.name || item.fileName, |
| | | url: item.url || item.fileUrl, |
| | | id: item.id, |
| | | ...item, |
| | | })) |
| | | fileListRef.value.open(fileList) |
| | | fileListDialogVisible.value = true |
| | | } else { |
| | | fileListRef.value?.open([]) |
| | | fileListDialogVisible.value = true |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("è·åéä»¶å表失败") |
| | | fileListRef.value?.open([]) |
| | | fileListDialogVisible.value = true |
| | | } |
| | | } |
| | | |
| | | // ä¸ä¼ éä»¶ |
| | | const handleFileUpload = async () => { |
| | | if (!currentFileRow.value) { |
| | | proxy.$modal.msgWarning("请å
éæ©æ°æ®") |
| | | return |
| | | } |
| | | return new Promise((resolve) => { |
| | | const input = document.createElement("input") |
| | | input.type = "file" |
| | | input.style.display = "none" |
| | | input.onchange = async (e) => { |
| | | const file = e.target.files[0] |
| | | if (!file) { |
| | | resolve(null) |
| | | return |
| | | } |
| | | try { |
| | | const formData = new FormData() |
| | | formData.append("file", file) |
| | | const uploadRes = await request({ |
| | | url: "/file/upload", |
| | | method: "post", |
| | | data: formData, |
| | | headers: { |
| | | "Content-Type": "multipart/form-data", |
| | | Authorization: `Bearer ${getToken()}`, |
| | | }, |
| | | }) |
| | | if (uploadRes.code === 200) { |
| | | const fileData = { |
| | | afterSalesServiceId: currentFileRow.value.id, |
| | | name: uploadRes.data?.originalName || file.name, |
| | | url: uploadRes.data?.tempPath || uploadRes.data?.url, |
| | | } |
| | | const saveRes = await afterSalesServiceFileAdd(fileData) |
| | | if (saveRes.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå") |
| | | const listRes = await afterSalesServiceFileListPage({ |
| | | afterSalesServiceId: currentFileRow.value.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map((item) => ({ |
| | | name: item.name || item.fileName, |
| | | url: item.url || item.fileUrl, |
| | | id: item.id, |
| | | ...item, |
| | | })) |
| | | fileListRef.value.setList(fileList) |
| | | } |
| | | resolve({ name: fileData.name, url: fileData.url, id: saveRes.data?.id }) |
| | | } else { |
| | | proxy.$modal.msgError(saveRes.msg || "æä»¶ä¿å失败") |
| | | resolve(null) |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(uploadRes.msg || "æä»¶ä¸ä¼ 失败") |
| | | resolve(null) |
| | | } |
| | | } catch (err) { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败") |
| | | resolve(null) |
| | | } finally { |
| | | document.body.removeChild(input) |
| | | } |
| | | } |
| | | document.body.appendChild(input) |
| | | input.click() |
| | | }) |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async (row) => { |
| | | try { |
| | | const res = await afterSalesServiceFileDel([row.id]) |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å 餿å") |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await afterSalesServiceFileListPage({ |
| | | afterSalesServiceId: currentFileRow.value.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map((item) => ({ |
| | | name: item.name || item.fileName, |
| | | url: item.url || item.fileUrl, |
| | | id: item.id, |
| | | ...item, |
| | | })) |
| | | fileListRef.value.setList(fileList) |
| | | } |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥") |
| | | return false |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥") |
| | | return false |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æå¡è¯ä»·æ¦è§ï¼æ¨¡æåå·¥ä¸ç»©è¯å --> |
| | | <el-row :gutter="16" class="mb16"> |
| | | <el-col :span="8"> |
| | | <el-card shadow="never"> |
| | | <div class="kpi-title">æ¬æå¹³åè¯å</div> |
| | | <div class="kpi-value"> |
| | | {{ overallAvgScore.toFixed(1) }} |
| | | <span class="kpi-unit">å</span> |
| | | </div> |
| | | <el-rate v-model="overallAvgScore" disabled show-score score-template="{value} / 5" /> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card shadow="never"> |
| | | <div class="kpi-title">å·²è¯ä»·ç»´ä¿®å·¥å</div> |
| | | <div class="kpi-value"> |
| | | {{ ratedCount }} |
| | | <span class="kpi-unit">å</span> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card shadow="never"> |
| | | <div class="kpi-title">å¾
è¯ä»·ç»´ä¿®å·¥å</div> |
| | | <div class="kpi-value kpi-warning"> |
| | | {{ pendingCount }} |
| | | <span class="kpi-unit">å</span> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ï¼ç®¡çåæå·¥ç¨å¸ / å®¢æ· / æ¶é´è¿½æº¯è¯ä»· --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">维修工ç¨å¸ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.engineerName" |
| | | placeholder="请è¾å
¥å·¥ç¨å¸å§å" |
| | | style="width: 180px" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">客æ·åç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | style="width: 180px" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">宿æ¶é´ï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | |
| | | <span class="search_title ml10">è¯ä»·ç¶æï¼</span> |
| | | <el-select |
| | | v-model="searchForm.status" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 140px" |
| | | clearable |
| | | > |
| | | <el-option label="å¾
è¯ä»·" value="pending" /> |
| | | <el-option label="å·²è¯ä»·" value="rated" /> |
| | | </el-select> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button icon="Download" @click="handleExport"> |
| | | 导åºè¯ä»·ç»è®¡ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»´ä¿®è¯ä»·åè¡¨ï¼æ¨¡æâç»´ä¿®å®æå触åè¯ä»·âåºæ¯ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 24em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="orderNo" label="维修工åå·" width="160" show-overflow-tooltip /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" width="160" show-overflow-tooltip /> |
| | | <el-table-column prop="customerName" label="客æ·åç§°" width="180" show-overflow-tooltip /> |
| | | <el-table-column prop="engineerName" label="维修工ç¨å¸" width="120" /> |
| | | <el-table-column prop="completeTime" label="ç»´ä¿®å®ææ¶é´" width="180" /> |
| | | <el-table-column prop="score" label="æçº§è¯å" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-rate v-if="scope.row.score" v-model="scope.row.score" disabled /> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="è¯ä»·ç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag |
| | | :type="scope.row.status === 'rated' ? 'success' : 'warning'" |
| | | size="small" |
| | | > |
| | | {{ scope.row.status === 'rated' ? 'å·²è¯ä»·' : 'å¾
è¯ä»·' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="feedback" label="客æ·åé¦" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" width="160" align="center" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | v-if="scope.row.status === 'pending'" |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEvaluate(scope.row)" |
| | | > |
| | | å»è¯ä»· |
| | | </el-button> |
| | | <el-button |
| | | v-else |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEvaluate(scope.row)" |
| | | > |
| | | æ¥ç / ä¿®æ¹è¯ä»· |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- è¯ä»·å¼¹æ¡ï¼æ¨¡æâç»´ä¿®å®æåå¼¹åºå®¢æ·ç«¯è¯ä»·â --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="520px" |
| | | destroy-on-close |
| | | > |
| | | <div class="dialog-order-info" v-if="currentOrder"> |
| | | <div>维修工åï¼{{ currentOrder.orderNo }}</div> |
| | | <div>设å¤åç§°ï¼{{ currentOrder.deviceName }}</div> |
| | | <div>维修工ç¨å¸ï¼{{ currentOrder.engineerName }}</div> |
| | | </div> |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-form-item label="æçº§è¯åï¼" prop="score"> |
| | | <el-rate v-model="form.score" :max="5" /> |
| | | </el-form-item> |
| | | <el-form-item label="æååé¦ï¼" prop="feedback"> |
| | | <el-input |
| | | v-model="form.feedback" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请填åå¯¹æ¬æ¬¡ç»´ä¿®æå¡çè¯ä»·ï¼å¦ååºé度ãä¸ä¸ç¨åº¦ãæ²éä½éªç" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">æ 交 è¯ ä»·</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | // 模æç»´ä¿®å·¥å + 客æ·è¯ä»·æ°æ® |
| | | const rawOrders = ref([ |
| | | { |
| | | id: 1, |
| | | orderNo: "WX-2024-1201-001", |
| | | deviceName: "ç©ºåæº A1 å·", |
| | | customerName: "ååçµåç§ææéå
¬å¸", |
| | | engineerName: "çå¸å
", |
| | | completeTime: "2024-12-01 10:30:00", |
| | | completeDate: "2024-12-01", |
| | | status: "rated", |
| | | score: 5, |
| | | feedback: "ç»´ä¿®é常ä¸ä¸ï¼ååºé度快ï¼ç°åºè§£éä¹å¾æ¸
æ°ï¼æ»¡æã", |
| | | }, |
| | | { |
| | | id: 2, |
| | | orderNo: "WX-2024-1201-002", |
| | | deviceName: "æ³¨å¡æº B3 å·", |
| | | customerName: "åä¸ç²¾å¯å¶é æéå
¬å¸", |
| | | engineerName: "æå¸å
", |
| | | completeTime: "2024-12-01 15:20:00", |
| | | completeDate: "2024-12-01", |
| | | status: "rated", |
| | | score: 4, |
| | | feedback: "æ´ä½è¿ä¸éï¼å°±æ¯å°åºæ¶é´ç¨å¾®é¿äºä¸ç¹ï¼å¸æåé¢è½åå¿«ä¸äºã", |
| | | }, |
| | | { |
| | | id: 3, |
| | | orderNo: "WX-2024-1202-003", |
| | | deviceName: "çæ¥æºå¨äºº C2 å·", |
| | | customerName: "è¥¿åæ°è½æºç§æè¡ä»½", |
| | | engineerName: "å¼ å¸å
", |
| | | completeTime: "2024-12-02 11:05:00", |
| | | completeDate: "2024-12-02", |
| | | status: "pending", |
| | | score: null, |
| | | feedback: "", |
| | | }, |
| | | { |
| | | id: 4, |
| | | orderNo: "WX-2024-1203-005", |
| | | deviceName: "æµè¯å° D1 å·", |
| | | customerName: "åæ¹æ±½è½¦é¶é¨ä»¶æéå
¬å¸", |
| | | engineerName: "çå¸å
", |
| | | completeTime: "2024-12-03 09:50:00", |
| | | completeDate: "2024-12-03", |
| | | status: "pending", |
| | | score: null, |
| | | feedback: "", |
| | | }, |
| | | ]); |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | engineerName: "", |
| | | customerName: "", |
| | | dateRange: [], |
| | | status: "", |
| | | }); |
| | | |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([...rawOrders.value]); |
| | | |
| | | // ç»è®¡ï¼æ´ä½è¯åãå·²è¯ä»· / å¾
è¯ä»·æ°é |
| | | const ratedOrders = computed(() => |
| | | rawOrders.value.filter((o) => o.status === "rated" && o.score) |
| | | ); |
| | | |
| | | const overallAvgScore = computed(() => { |
| | | if (!ratedOrders.value.length) return 0; |
| | | const sum = ratedOrders.value.reduce((acc, cur) => acc + (cur.score || 0), 0); |
| | | return sum / ratedOrders.value.length; |
| | | }); |
| | | |
| | | const ratedCount = computed(() => ratedOrders.value.length); |
| | | const pendingCount = computed( |
| | | () => rawOrders.value.filter((o) => o.status === "pending").length |
| | | ); |
| | | |
| | | // æ¥è¯¢ / éç½® |
| | | const recomputeTable = () => { |
| | | const list = rawOrders.value.filter((item) => { |
| | | if ( |
| | | searchForm.engineerName && |
| | | !item.engineerName.includes(searchForm.engineerName.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if ( |
| | | searchForm.customerName && |
| | | !item.customerName.includes(searchForm.customerName.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (searchForm.status && item.status !== searchForm.status) { |
| | | return false; |
| | | } |
| | | if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) { |
| | | const [start, end] = searchForm.dateRange; |
| | | if (item.completeDate < start || item.completeDate > end) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }); |
| | | tableData.value = list; |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.engineerName = ""; |
| | | searchForm.customerName = ""; |
| | | searchForm.dateRange = []; |
| | | searchForm.status = ""; |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | // 导åºï¼æ¼ç¤ºï¼ |
| | | const handleExport = () => { |
| | | ElMessage.success("å½å为æ¼ç¤ºé¡µé¢ï¼è¯ä»·å¯¼åºåè½æªå¯¹æ¥å®é
æ¥å£"); |
| | | }; |
| | | |
| | | // è¯ä»·å¼¹æ¡ |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("ç»´ä¿®æå¡è¯ä»·"); |
| | | const currentOrder = ref(null); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | score: 0, |
| | | feedback: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | score: [{ required: true, message: "è¯·éæ©æçº§è¯å", trigger: "change" }], |
| | | feedback: [{ required: true, message: "请填åæååé¦", trigger: "blur" }], |
| | | }; |
| | | |
| | | // æå¼è¯ä»·ï¼æ¨¡æâç»´ä¿®å®æç¡®è®¤åå¼¹åºè¯ä»·å¼¹æ¡â |
| | | const openEvaluate = (row) => { |
| | | currentOrder.value = row; |
| | | dialogTitle.value = |
| | | row.status === "pending" ? "ç»´ä¿®æå¡è¯ä»·" : "æ¥ç / ä¿®æ¹è¯ä»·"; |
| | | form.score = row.score || 0; |
| | | form.feedback = row.feedback || ""; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æäº¤è¯ä»·ï¼åæ¥å°æ¬å°âåå·¥ä¸ç»©ç»è®¡â |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid || !currentOrder.value) return; |
| | | |
| | | const target = rawOrders.value.find((o) => o.id === currentOrder.value.id); |
| | | if (target) { |
| | | target.score = form.score; |
| | | target.feedback = form.feedback; |
| | | target.status = "rated"; |
| | | } |
| | | |
| | | ElMessage.success("è¯ä»·æäº¤æåï¼å·²åæ¥è³åå·¥ä¸ç»©ç»è®¡"); |
| | | dialogVisible.value = false; |
| | | recomputeTable(); |
| | | }); |
| | | }; |
| | | |
| | | // åå§åå表 |
| | | recomputeTable(); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .mb16 { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .kpi-title { |
| | | font-size: 13px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .kpi-value { |
| | | margin-top: 6px; |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .kpi-unit { |
| | | font-size: 12px; |
| | | margin-left: 4px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .kpi-warning { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .dialog-order-info { |
| | | margin-bottom: 12px; |
| | | font-size: 13px; |
| | | color: #606266; |
| | | line-height: 1.8; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | 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> |
| | |
| | | :on-remove="handleRemove" |
| | | :file-list="fileList" |
| | | multiple |
| | | :limit="10" |
| | | :show-file-list="false" |
| | | :data="{documentId: currentDocumentId}" |
| | | accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.xml,.jpg,.jpeg,.png,.gif,.bmp,.rar,.zip,.7z" |
| | |
| | | |
| | | <!-- éä»¶å表 --> |
| | | <div class="attachment-list"> |
| | | <el-table :data="fileList" border height="400px" v-loading="loading"> |
| | | <el-table :data="fileList" border max-height="400px" v-loading="loading"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column label="éä»¶åç§°" prop="name" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column label="æä»¶å¤§å°" prop="size" width="100" align="center"> |
| | |
| | | <template> |
| | | <div class="dashboard"> |
| | | <!-- 顶鍿¨ªå两æ --> |
| | | <div class="dashboard-top"> |
| | | <!-- å·¦ï¼ä¼ä¸ä¿¡æ¯+ä¸å¤§æ°æ®å¡çï¼ä¸ä¸æåï¼ --> |
| | | <div class="top-left"> |
| | | <div class="company-info"> |
| | | <div class="section-title">ç»éä¿¡æ¯</div> |
| | | <div style="display: flex;align-items: center;gap: 20px"> |
| | | <img :src="userStore.avatar" class="avatar" alt=""/> |
| | | <div class="company-card"> |
| | | <div class="company-name">{{userStore.name}}</div> |
| | | <div class="company-meta">{{userStore.roleName}}</div> |
| | | </div> |
| | | <div style="display: flex;align-items: center;gap: 8px"> |
| | | <el-icon color="#5053B5" size="22"><Clock /></el-icon> |
| | | <span>ç»éæ¥æï¼{{userStore.currentLoginTime}}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-cards"> |
| | | <div class="data-card sales"> |
| | | <div class="data-title">é宿°æ®</div> |
| | | <div class="data-num"> |
| | | <div> |
| | | <div class="data-desc">æ¬æéå®é¢/å
</div> |
| | | <div class="data-value">{{businessInfo.monthSaleMoney}}</div> |
| | | </div> |
| | | <div> |
| | | <div class="data-desc">æªå¼ç¥¨éé¢/å
</div> |
| | | <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | <div class="data-card purchase"> |
| | | <div class="data-title">éè´æ°æ®</div> |
| | | <div class="data-num"> |
| | | <div> |
| | | <div class="data-desc">æ¬æéè´é¢/å
</div> |
| | | <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div> |
| | | </div> |
| | | <div> |
| | | <div class="data-desc">å¾
仿¬¾éé¢/å
</div> |
| | | <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-card inventory"> |
| | | <div class="data-title">åºåæ°æ®</div> |
| | | <div class="data-num"> |
| | | <div> |
| | | <div class="data-desc">å½ååºåæ»é/ä»¶</div> |
| | | <div class="data-value">{{businessInfo.inventoryNum}}</div> |
| | | </div> |
| | | <div> |
| | | <div class="data-desc">仿¥å
¥åº/ä»¶</div> |
| | | <div class="data-value">{{businessInfo.todayInventoryNum}}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- å³ï¼å¾
åäºé¡¹ --> |
| | | <div class="todo-panel"> |
| | | <div class="section-title">å¾
åäºé¡¹</div> |
| | | <ul class="todo-list" v-if="todoList.length > 0"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | | <div class="todo-title">å¾
åç¼å·ï¼{{item.approveId}}</div> |
| | | <div class="todo-division">é¨é¨ï¼{{item.approveDeptName}}</div> |
| | | <div class="todo-time">{{item.approveTime}}</div> |
| | | </div> |
| | | <div class="todo-division">å¾
åäºç±ï¼{{item.approveReason}}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center"> |
| | | ææ æ°æ® |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä¸é¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div class="section-title">客æ·ååéé¢åæ</div> |
| | | <div class="contract-summary"> |
| | | <div class="contract-info"> |
| | | <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/> |
| | | <div class="contract-card"> |
| | | <div class="contract-name">æ»ååéé¢(å
)</div> |
| | | <div class="contract-meta"> |
| | | <div class="main-amount">{{sum}}</div> |
| | | <div>å¨åæ¯: <span class="up">{{yny}}% </span> æ¥ç¯æ¯: <span class="up">{{chain}}% </span></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px"> |
| | | <div> |
| | | <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" |
| | | :series="materialPieSeries" |
| | | :tooltip="pieTooltip"></Echarts> |
| | | </div> |
| | | <ul class="contract-list"> |
| | | <li v-for="item in materialPieSeries[0].data" :key="item.name"> |
| | | <div style="display: flex;align-items: center;justify-content: space-between;width: 100%"> |
| | | <div class="line" :style="{color: item.itemStyle.color}">â{{item.name}}</div> |
| | | <div style="width: 70px">{{item.rate}}%</div> |
| | | <div>ï¿¥{{item.value}}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </div> |
| | | <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> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :series="barSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | style="height: 260px"></Echarts> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºé¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <!-- <div class="main-panel">--> |
| | | <!-- <div class="section-title">è´¨éç»è®¡</div>--> |
| | | <!-- <div class="quality-cards">--> |
| | | <!-- <div class="quality-card one">åææå·²æ£æµæ° <span>{{qualityStatisticsObject.supplierNum}}ä»¶</span></div>--> |
| | | <!-- <div class="quality-card two">è¿ç¨æ£éªæ°é <span>{{qualityStatisticsObject.processNum}}ä»¶</span></div>--> |
| | | <!-- <div class="quality-card three">åºåå·²æ£æ°é <span>{{qualityStatisticsObject.factoryNum}}ä»¶</span></div>--> |
| | | <!-- </div>--> |
| | | <!-- <Echarts ref="chart"--> |
| | | <!-- :chartStyle="chartStyle"--> |
| | | <!-- :grid="grid"--> |
| | | <!-- :legend="barLegend"--> |
| | | <!-- :series="barSeries1"--> |
| | | <!-- :tooltip="tooltip"--> |
| | | <!-- :xAxis="xAxis1"--> |
| | | <!-- :yAxis="yAxis1"--> |
| | | <!-- style="height: 260px"></Echarts>--> |
| | | <!-- </div>--> |
| | | <div class="main-panel"> |
| | | <div class="section-title">忬¾ä¸å¼ç¥¨åæ</div> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | | :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="dashboard"> |
| | | <!-- 顶鍿¨ªå两æ --> |
| | | <div class="dashboard-top"> |
| | | <!-- å·¦ï¼ä¼ä¸ä¿¡æ¯+ä¸å¤§æ°æ®å¡çï¼ä¸ä¸æåï¼ --> |
| | | <div class="top-left"> |
| | | <div class="company-info"> |
| | | <!-- é¡¶é¨é®åæ¡ --> |
| | | <div class="welcome-banner"> |
| | | <div class="welcome-title"> |
| | | <span class="welcome-user">{{ userStore.roleName || 'ç³»ç»ç®¡çå' }}</span> |
| | | <span> æ¨å¥½ï¼ç¥æ¨å¼å¿æ¯ä¸å¤©</span> |
| | | </div> |
| | | <div class="welcome-time">ç»å½äº: {{ userStore.currentLoginTime }}</div> |
| | | </div> |
| | | |
| | | <!-- ç¨æ·ä¿¡æ¯å¡ç --> |
| | | <div class="user-card"> |
| | | <img :src="userStore.avatar" class="avatar" alt="" /> |
| | | <div class="user-card-main"> |
| | | <div class="user-name">{{ userStore.name }}</div> |
| | | <div class="user-role">{{ userStore.roleName }}</div> |
| | | <div class="user-meta"> |
| | | <span>{{ userStore.phoneNumber || '123456789' }}</span> |
| | | <span class="sep">|</span> |
| | | <span>{{ userStore.deptName || 'ç»ç»æ¶æ' }}</span> |
| | | <span class="sep">|</span> |
| | | <span>{{ userStore.postName || 'å²ä½å' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-cards"> |
| | | <div class="data-card sales"> |
| | | <div class="data-title">é宿°æ®</div> |
| | | <div class="data-num"> |
| | | <div> |
| | | <div class="data-desc">æ¬æéå®é¢/å
</div> |
| | | <div class="data-value">{{ businessInfo.monthSaleMoney }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="data-desc">æªå¼ç¥¨éé¢/å
</div> |
| | | <div class="data-value">{{ businessInfo.monthSaleHaveMoney }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | <div class="data-card purchase"> |
| | | <div class="data-title">éè´æ°æ®</div> |
| | | <div class="data-num"> |
| | | <div> |
| | | <div class="data-desc">æ¬æéè´é¢/å
</div> |
| | | <div class="data-value">{{ businessInfo.monthPurchaseMoney }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="data-desc">å¾
仿¬¾éé¢/å
</div> |
| | | <div class="data-value">{{ businessInfo.monthPurchaseHaveMoney }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-card inventory"> |
| | | <div class="data-title">åºåæ°æ®</div> |
| | | <div class="data-num"> |
| | | <div> |
| | | <div class="data-desc">å½ååºåæ»é/ä»¶</div> |
| | | <div class="data-value">{{ businessInfo.inventoryNum }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="data-desc">仿¥å
¥åº/ä»¶</div> |
| | | <div class="data-value">{{ businessInfo.todayInventoryNum }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- å³ï¼å¾
åäºé¡¹ --> |
| | | <div class="todo-panel"> |
| | | <div class="section-title">å¾
åäºé¡¹</div> |
| | | <ul class="todo-list" v-if="todoList.length > 0"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | | <div class="todo-title">å¾
åç¼å·ï¼{{ item.approveId }}</div> |
| | | <div class="todo-division">é¨é¨ï¼{{ item.approveDeptName }}</div> |
| | | <div class="todo-time">{{ item.approveTime }}</div> |
| | | </div> |
| | | <div class="todo-division">å¾
åäºç±ï¼{{ item.approveReason }}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center"> |
| | | ææ æ°æ® |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel process-panel"> |
| | | <div class="process-panel__header"> |
| | | <div class="section-title">å·¥åºæ°æ®ç产ç»è®¡æç»</div> |
| | | <div style="display: flex; gap: 10px; align-items: center;"> |
| | | <el-button type="primary" size="small" plain icon="Filter" @click="openProcessDialog">鿩工åº</el-button> |
| | | <el-button type="info" size="small" plain icon="Refresh" @click="resetProcessFilter">éç½®</el-button> |
| | | <el-radio-group v-model="processRange" size="small" @change="refreshProcessStats"> |
| | | <el-radio-button :value="1">æ¥</el-radio-button> |
| | | <el-radio-button :value="2">å¨</el-radio-button> |
| | | <el-radio-button :value="3">æ</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="process-panel__body"> |
| | | <div class="process-panel__chart"> |
| | | <Echarts :chartStyle="{ width: '100%', height: '100%' }" :grid="processGrid" :series="processSeries" |
| | | :tooltip="processTooltip" :xAxis="processXAxis" :yAxis="processYAxis" style="height: 100%" |
| | | @click="handleChartClick" /> |
| | | </div> |
| | | |
| | | <div class="process-panel__aside"> |
| | | <div class="process-legend"> |
| | | <div class="process-legend__item"> |
| | | <span class="dot dot-blue"></span><span>æå
¥é</span> |
| | | </div> |
| | | <div class="process-legend__item"> |
| | | <span class="dot dot-yellow"></span><span>æ¥åºé</span> |
| | | </div> |
| | | <div class="process-legend__item"> |
| | | <span class="dot dot-teal"></span><span>产åºé</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="process-card process-card--name">{{ processAside.processName }}</div> |
| | | |
| | | <div class="process-card"> |
| | | <div class="process-card__label">ç´¯è®¡æ»æå
¥</div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalInput) }}<span class="unit">å
</span> |
| | | </div> |
| | | </div> |
| | | <div class="process-card"> |
| | | <div class="process-card__label">ç´¯è®¡æ»æ¥åº</div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalScrap) }}<span class="unit">å
</span> |
| | | </div> |
| | | </div> |
| | | <div class="process-card"> |
| | | <div class="process-card__label">累计æ»äº§åº</div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalOutput) }}<span class="unit">å
</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å·¥åºéæ©å¼¹çª --> |
| | | <el-dialog v-model="processDialogVisible" title="鿩工åº" width="500px" append-to-body> |
| | | <div class="process-selection-wrapper"> |
| | | <el-checkbox-group v-model="tempProcessIds"> |
| | | <div class="process-grid"> |
| | | <el-checkbox v-for="item in processOptions" :key="item.id" :label="item.id" border> |
| | | {{ item.name }} |
| | | </el-checkbox> |
| | | </div> |
| | | </el-checkbox-group> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="processDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleProcessDialogConfirm">确认</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- ä¸é¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div class="section-title">客æ·ååéé¢åæ</div> |
| | | <div class="contract-summary"> |
| | | <div class="contract-info"> |
| | | <img src="../assets/images/khtitle.png" alt="" style="width: 42px" /> |
| | | <div class="contract-card"> |
| | | <div class="contract-name">æ»ååéé¢(å
)</div> |
| | | <div class="contract-meta"> |
| | | <div class="main-amount">{{ sum }}</div> |
| | | <div>å¨åæ¯: <span class="up">{{ yny }}% </span> æ¥ç¯æ¯: <span class="up">{{ chain }}% </span></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div |
| | | style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px"> |
| | | <div> |
| | | <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" :series="materialPieSeries" |
| | | :tooltip="pieTooltip"></Echarts> |
| | | </div> |
| | | <ul class="contract-list"> |
| | | <li v-for="item in materialPieSeries[0].data" :key="item.name"> |
| | | <div style="display: flex;align-items: center;justify-content: space-between;width: 100%"> |
| | | <div class="line" :style="{ color: item.itemStyle.color }">â{{ item.name }}</div> |
| | | <div style="width: 70px">{{ item.rate }}%</div> |
| | | <div>ï¿¥{{ item.value }}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </div> |
| | | <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>--> |
| | | </div> |
| | | <Echarts ref="chart" :color="barColors2" :chartStyle="chartStyle" :grid="grid" :series="barSeries" |
| | | :tooltip="tooltip" :xAxis="xAxis" :yAxis="yAxis" style="height: 260px"></Echarts> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºé¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;"> |
| | | <div class="section-title" style="margin-bottom: 0;">è´¨éç»è®¡</div> |
| | | <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo"> |
| | | <el-radio-button :value="1">å¨</el-radio-button> |
| | | <el-radio-button :value="2">æ</el-radio-button> |
| | | <el-radio-button :value="3">å£åº¦</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <div class="quality-cards"> |
| | | <div class="quality-card one">åææå·²æ£æµæ° <span>{{ qualityStatisticsObject.supplierNum }}ä»¶</span></div> |
| | | <div class="quality-card two">è¿ç¨æ£éªæ°é <span>{{ qualityStatisticsObject.processNum }}ä»¶</span></div> |
| | | <div class="quality-card three">åºåå·²æ£æ°é <span>{{ qualityStatisticsObject.factoryNum }}ä»¶</span></div> |
| | | </div> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="barLegend" :series="barSeries1" |
| | | :tooltip="tooltip" :xAxis="xAxis1" :yAxis="yAxis1" style="height: 260px"></Echarts> |
| | | </div> |
| | | |
| | | <div class="main-panel"> |
| | | <div class="section-title">忬¾ä¸å¼ç¥¨åæ</div> |
| | | <Echarts ref="invoiceChart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | | :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, computed, reactive } from 'vue' |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import * as echarts from 'echarts'; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import { |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | getBusiness, |
| | | homeTodos, |
| | | qualityStatistics, |
| | | statisticsReceivablePayable |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | getBusiness, |
| | | homeTodos, |
| | | processDataProductionStatistics, |
| | | statisticsReceivablePayable, |
| | | qualityInspectionStatistics |
| | | } from "@/api/viewIndex.js"; |
| | | import { list } from '@/api/productionManagement/productionProcess'; |
| | | |
| | | |
| | | const userStore = useUserStore() |
| | | |
| | | const processOptions = ref([]) |
| | | const selectedProcessIds = ref([]) |
| | | const tempProcessIds = ref([]) |
| | | const processDialogVisible = ref(false) |
| | | const activeProcessIndex = ref(0) |
| | | |
| | | const businessInfo = ref({ |
| | | inventoryNum: 0, |
| | | monthPurchaseHaveMoney: 0, |
| | | monthPurchaseMoney: 0, |
| | | monthSaleHaveMoney: 0, |
| | | monthSaleMoney: 0, |
| | | todayInventoryNum: 0, |
| | | inventoryNum: 0, |
| | | monthPurchaseHaveMoney: 0, |
| | | monthPurchaseMoney: 0, |
| | | monthSaleHaveMoney: 0, |
| | | monthSaleMoney: 0, |
| | | todayInventoryNum: 0, |
| | | }) |
| | | const qualityStatisticsObject = ref({ |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }) |
| | | const sum = ref(0) |
| | | const yny = ref(0) |
| | | const chain = ref(0) |
| | | |
| | | const pieLegend = reactive({ |
| | | show: false, |
| | | show: false, |
| | | }) |
| | | const barSeries = ref([ |
| | | { |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | } |
| | | }, |
| | | { |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | } |
| | | }, |
| | | ]) |
| | | |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'åææä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'è¿ç¨ä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'åºåä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'åææä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'è¿ç¨ä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'åºåä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | ]) |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | width: '100%', |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const chartStylePie = { |
| | | width: '140%', |
| | | height: '140%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | width: '140%', |
| | | height: '140%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | } |
| | | const barLegend = { |
| | | show: true, |
| | | data: ['åææä¸åæ ¼æ°', 'è¿ç¨ä¸åæ ¼æ°', 'åºåä¸åæ ¼æ°'] |
| | | show: true, |
| | | data: ['åææä¸åæ ¼æ°', 'è¿ç¨ä¸åæ ¼æ°', 'åºåä¸åæ ¼æ°'] |
| | | } |
| | | const barLegend1 = { |
| | | show: true, |
| | | data: ['é¢ä»è´¦æ¬¾', 'åºä»è´¦æ¬¾', '颿¶è´¦æ¬¾', 'åºæ¶è´¦æ¬¾'] |
| | | show: true, |
| | | data: ['é¢ä»è´¦æ¬¾', 'åºä»è´¦æ¬¾', '颿¶è´¦æ¬¾', 'åºæ¶è´¦æ¬¾'] |
| | | } |
| | | const lineLegend = { |
| | | show: true, |
| | | data: ['å¼ç¥¨', '忬¾'] |
| | | show: true, |
| | | data: ['å¼ç¥¨', '忬¾'] |
| | | } |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | } |
| | | const xAxis = [{ |
| | | type: 'value', |
| | | type: 'value', |
| | | }] |
| | | const xAxis1 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | data: [] |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | data: [] |
| | | }]) |
| | | const yAxis = [{ |
| | | type: 'category', |
| | | data: [ 'åºä»è´¦æ¬¾', 'åºæ¶è´¦æ¬¾',] |
| | | type: 'category', |
| | | data: ['åºä»è´¦æ¬¾', 'åºæ¶è´¦æ¬¾',] |
| | | }] |
| | | const yAxis1 = [{ |
| | | type: 'value' |
| | | type: 'value' |
| | | }] |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: function (params) { |
| | | // å¨æçææç¤ºä¿¡æ¯ï¼åºäºæ°æ®é¡¹ç name 屿§ |
| | | const description = params.name === 'æ¬æåæ¬¾éé¢' ? 'æ¬æåæ¬¾éé¢' : 'åºæ¶æ¬¾éé¢'; |
| | | return `${description} ${formatNumber(params.value)}å
${params.percent}%`; |
| | | }, |
| | | position: 'right' |
| | | trigger: 'item', |
| | | formatter: function (params) { |
| | | // å¨æçææç¤ºä¿¡æ¯ï¼åºäºæ°æ®é¡¹ç name 屿§ |
| | | const description = params.name === 'æ¬æåæ¬¾éé¢' ? 'æ¬æåæ¬¾éé¢' : 'åºæ¶æ¬¾éé¢'; |
| | | return `${description} ${formatNumber(params.value)}å
${params.percent}%`; |
| | | }, |
| | | position: 'right' |
| | | }) |
| | | const materialPieSeries = ref([ |
| | | { |
| | | type: 'pie', |
| | | radius: ['66%', '90%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: [] |
| | | } |
| | | { |
| | | type: 'pie', |
| | | radius: ['66%', '90%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | const lineSeries = ref([ |
| | | { |
| | | type: 'line', |
| | | data: [], |
| | | label: { |
| | | show: true |
| | | }, |
| | | { |
| | | type: 'line', |
| | | data: [], |
| | | label: { |
| | | show: true |
| | | }, |
| | | showSymbol: true, // æ¾ç¤ºåç¹ |
| | | }, |
| | | }, |
| | | ]) |
| | | const tooltipLine = { |
| | | trigger: 'axis', |
| | | trigger: 'axis', |
| | | } |
| | | const yAxis2 = ref([ |
| | | { |
| | | type: 'value', |
| | | } |
| | | { |
| | | type: 'value', |
| | | } |
| | | ]) |
| | | const xAxis2 = ref([ |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisLabel: { |
| | | interval: 0, |
| | | formatter: function(value) { |
| | | return value.replace(/~/g, '\n'); |
| | | }, |
| | | } |
| | | } |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisLabel: { |
| | | interval: 0, |
| | | formatter: function (value) { |
| | | return value.replace(/~/g, '\n'); |
| | | }, |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | // å¾
åäºé¡¹ |
| | | const todoList = ref([]) |
| | | const radio1 = ref(1) |
| | | const qualityRange = ref(1) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const barChart = ref(null) |
| | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getBusinessData() |
| | | analysisCustomer() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | qualityStatisticsInfo() |
| | | getAmountHalfYearNum() |
| | | getBusinessData() |
| | | analysisCustomer() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | qualityStatisticsInfo() |
| | | getAmountHalfYearNum() |
| | | getProcessList() |
| | | }) |
| | | // æ°æ®ç»è®¡ |
| | | const getBusinessData = () => { |
| | | getBusiness().then((res) => { |
| | | businessInfo.value = {...res.data} |
| | | }) |
| | | getBusiness().then((res) => { |
| | | businessInfo.value = { ...res.data } |
| | | }) |
| | | } |
| | | // ååéé¢ |
| | | const analysisCustomer = () => { |
| | | analysisCustomerContractAmounts().then((res) => { |
| | | sum.value = res.data.sum |
| | | yny.value = res.data.yny |
| | | chain.value = res.data.chain |
| | | analysisCustomerContractAmounts().then((res) => { |
| | | sum.value = res.data.sum |
| | | yny.value = res.data.yny |
| | | chain.value = res.data.chain |
| | | // 为æ¯ä¸ªæ°æ®é¡¹åé
éæºé¢è² |
| | | materialPieSeries.value[0].data = res.data.item.map(item => ({ |
| | | ...item, |
| | | itemStyle: { color: getRandomColor() } |
| | | })) |
| | | }) |
| | | }) |
| | | } |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | }) |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | }) |
| | | } |
| | | // è·åå·¥åºå表 |
| | | const getProcessList = () => { |
| | | list().then(res => { |
| | | processOptions.value = res.data |
| | | }) |
| | | } |
| | | |
| | | const openProcessDialog = () => { |
| | | tempProcessIds.value = [...selectedProcessIds.value] |
| | | processDialogVisible.value = true |
| | | } |
| | | |
| | | const handleProcessDialogConfirm = () => { |
| | | selectedProcessIds.value = [...tempProcessIds.value] |
| | | processDialogVisible.value = false |
| | | refreshProcessStats() |
| | | } |
| | | |
| | | const resetProcessFilter = () => { |
| | | selectedProcessIds.value = [] |
| | | tempProcessIds.value = [] |
| | | refreshProcessStats() |
| | | } |
| | | |
| | | const handleChartClick = (params) => { |
| | | if (params && params.dataIndex !== undefined) { |
| | | activeProcessIndex.value = params.dataIndex |
| | | } |
| | | } |
| | | // åºä»åºæ¶ç»è®¡ |
| | | const statisticsReceivable = (type) => { |
| | | statisticsReceivablePayable({type: radio1.value}).then((res) => { |
| | | barSeries.value[0].data = [ |
| | | // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } }, |
| | | { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } }, |
| | | // { value: res.data.advanceMoney, itemStyle: { color: barColors2[2] } }, |
| | | { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } } |
| | | ] |
| | | }) |
| | | const statisticsReceivable = () => { |
| | | statisticsReceivablePayable({ type: radio1.value }).then((res) => { |
| | | barSeries.value[0].data = [ |
| | | // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } }, |
| | | { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } }, |
| | | // { value: res.data.advanceMoney, itemStyle: { color: barColors2[2] } }, |
| | | { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } } |
| | | ] |
| | | }) |
| | | } |
| | | // è´¨æ£ç»è®¡ |
| | | const qualityStatisticsInfo = () => { |
| | | qualityStatistics().then((res) => { |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | | barSeries1.value[1].data.push(item.processNum) |
| | | barSeries1.value[2].data.push(item.factoryNum) |
| | | }) |
| | | qualityStatisticsObject.value.supplierNum = res.data.supplierNum |
| | | qualityStatisticsObject.value.processNum = res.data.processNum |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | qualityInspectionStatistics({ type: qualityRange.value }).then((res) => { |
| | | xAxis1.value[0].data = [] |
| | | barSeries1.value[0].data = [] |
| | | barSeries1.value[1].data = [] |
| | | barSeries1.value[2].data = [] |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | | barSeries1.value[1].data.push(item.processNum) |
| | | barSeries1.value[2].data.push(item.factoryNum) |
| | | }) |
| | | qualityStatisticsObject.value.supplierNum = res.data.supplierNum |
| | | qualityStatisticsObject.value.processNum = res.data.processNum |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | } |
| | | const getAmountHalfYearNum = async () => { |
| | | const res = await getAmountHalfYear() |
| | | console.log(res) |
| | | const monthName = [] |
| | | const receiptAmount = [] |
| | | const invoiceAmount = [] |
| | | res.data.forEach(item => { |
| | | monthName.push(item.month) |
| | | receiptAmount.push(item.receiptAmount) |
| | | invoiceAmount.push(item.invoiceAmount) |
| | | }) |
| | | // æ£ç¡®ååºå¼èµå¼ï¼å建æ°ç xAxis å series 对象 |
| | | xAxis2.value[0].data = monthName |
| | | xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~')); |
| | | lineSeries.value = [ |
| | | { |
| | | name: 'å¼ç¥¨', |
| | | type: 'line', |
| | | data: receiptAmount, |
| | | stack: 'Total', |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(131, 207, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(186, 228, 255, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | itemStyle: { |
| | | color: '#2D99FF', |
| | | borderColor: '#2D99FF' |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | showSymbol: true, |
| | | }, |
| | | { |
| | | name: '忬¾', |
| | | type: 'line', |
| | | data: invoiceAmount, |
| | | stack: 'Total', |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | itemStyle: { |
| | | color: '#83CFFF', |
| | | borderColor: '#83CFFF' |
| | | }, |
| | | showSymbol: true, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(54, 153, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(89, 169, 254, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | } |
| | | ] |
| | | const res = await getAmountHalfYear() |
| | | console.log(res) |
| | | const monthName = [] |
| | | const receiptAmount = [] |
| | | const invoiceAmount = [] |
| | | res.data.forEach(item => { |
| | | monthName.push(item.month) |
| | | receiptAmount.push(item.receiptAmount) |
| | | invoiceAmount.push(item.invoiceAmount) |
| | | }) |
| | | // æ£ç¡®ååºå¼èµå¼ï¼å建æ°ç xAxis å series 对象 |
| | | xAxis2.value[0].data = monthName |
| | | xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~')); |
| | | lineSeries.value = [ |
| | | { |
| | | name: 'å¼ç¥¨', |
| | | type: 'line', |
| | | data: receiptAmount, |
| | | stack: 'Total', |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(131, 207, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(186, 228, 255, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | itemStyle: { |
| | | color: '#2D99FF', |
| | | borderColor: '#2D99FF' |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | showSymbol: true, |
| | | }, |
| | | { |
| | | name: '忬¾', |
| | | type: 'line', |
| | | data: invoiceAmount, |
| | | stack: 'Total', |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | itemStyle: { |
| | | color: '#83CFFF', |
| | | borderColor: '#83CFFF' |
| | | }, |
| | | showSymbol: true, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(54, 153, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(89, 169, 254, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | } |
| | | ] |
| | | } |
| | | |
| | | // å·¥åºæ°æ®ç产ç»è®¡æç»ï¼åæ°æ® + å¾è¡¨ï¼ |
| | | const processRange = ref(1) |
| | | const processChartData = ref([]) |
| | | |
| | | const processXAxis = ref([ |
| | | { |
| | | nameTextStyle: { color: 'rgba(0,0,0,0.35)', fontSize: 12 }, |
| | | axisLabel: { color: 'rgba(0,0,0,0.35)' }, |
| | | splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)', type: 'dashed' } }, |
| | | }, |
| | | ]) |
| | | |
| | | const processYAxis = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: 'rgba(0,0,0,0.45)' }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const processGrid = reactive({ left: 0, right: 100, top: 30, bottom: 20, containLabel: true }) |
| | | |
| | | const processTooltip = reactive({ |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter: (params) => { |
| | | const name = params?.[0]?.name ?? '' |
| | | const list = Array.isArray(params) ? params : [] |
| | | const lines = list |
| | | .map((p) => { |
| | | const colorBox = `<span style="display:inline-block;margin-right:6px;border-radius:2px;width:10px;height:10px;background:${p.color}"></span>` |
| | | return `${colorBox}${p.seriesName} <b style="float:right;">${Number(p.value || 0).toFixed(2)}</b>` |
| | | }) |
| | | .join('<br/>') |
| | | return `<div style="min-width:140px;"><div style="font-weight:700;margin-bottom:6px;">${name}</div>${lines}</div>` |
| | | }, |
| | | }) |
| | | |
| | | const processSeries = computed(() => { |
| | | const input = processChartData.value.map((i) => i.input) |
| | | const scrap = processChartData.value.map((i) => i.scrap) |
| | | const output = processChartData.value.map((i) => i.output) |
| | | |
| | | return [ |
| | | { |
| | | name: 'æå
¥é', |
| | | type: 'bar', |
| | | stack: 'total', |
| | | barWidth: 22, |
| | | itemStyle: { color: '#1E5BFF', borderRadius: [6, 0, 0, 6] }, |
| | | data: input, |
| | | }, |
| | | { |
| | | name: 'æ¥åºé', |
| | | type: 'bar', |
| | | stack: 'total', |
| | | barWidth: 22, |
| | | itemStyle: { color: '#F7B500' }, |
| | | data: scrap, |
| | | }, |
| | | { |
| | | name: '产åºé', |
| | | type: 'bar', |
| | | stack: 'total', |
| | | barWidth: 22, |
| | | itemStyle: { color: '#19C6C6', borderRadius: [0, 6, 6, 0] }, |
| | | data: output, |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | const processAside = computed(() => { |
| | | const list = processChartData.value |
| | | const item = list[activeProcessIndex.value] || {} |
| | | return { |
| | | processName: item.name || 'ææ æ°æ®', |
| | | totalInput: item.input || 0, |
| | | totalScrap: item.scrap || 0, |
| | | totalOutput: item.output || 0, |
| | | } |
| | | }) |
| | | |
| | | const formatAmount = (n) => { |
| | | const num = Number(n || 0) |
| | | return num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) |
| | | } |
| | | |
| | | const refreshProcessStats = () => { |
| | | processDataProductionStatistics({ |
| | | type: processRange.value, |
| | | processIds: selectedProcessIds.value.length > 0 ? selectedProcessIds.value.join(',') : null |
| | | }).then(res => { |
| | | processChartData.value = res.data.map(item => ({ |
| | | name: item.processName, |
| | | input: item.totalInput, |
| | | scrap: item.totalScrap, |
| | | output: item.totalOutput |
| | | })) |
| | | processYAxis.value[0].data = processChartData.value.map((i) => i.name) |
| | | activeProcessIndex.value = 0 |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getBusinessData() |
| | | analysisCustomer() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | qualityStatisticsInfo() |
| | | getAmountHalfYearNum() |
| | | refreshProcessStats() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .dashboard { |
| | | background: #f5f7fa; |
| | | min-height: 100vh; |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | } |
| | | .dashboard-top { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | .company-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | padding: 20px; |
| | | min-width: 0; |
| | | background-color: #EFF2FB; /* ä½¿ç¨æå®çèæ¯é¢è² */ |
| | | background-image: url("../assets/images/denglu.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | border-radius: 12px; |
| | | height: 138px; |
| | | } |
| | | .avatar { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | object-fit: contain; |
| | | background: #fff; |
| | | border: 1px solid #eee; |
| | | } |
| | | .company-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | position: relative; |
| | | padding-right: 15px; |
| | | background: #f5f7fa; |
| | | min-height: 100vh; |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .company-card::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 0; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | .dashboard-top { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | align-items: flex-start; |
| | | justify-content: space-evenly; |
| | | } |
| | | .company-name { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #161A9A; |
| | | |
| | | .company-info { |
| | | padding: 0; |
| | | overflow: hidden; |
| | | border-radius: 12px; |
| | | background: #fff; |
| | | height: 100%; |
| | | } |
| | | .company-meta { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #818185; |
| | | |
| | | .welcome-banner { |
| | | padding: 10px 10px; |
| | | background: linear-gradient(135deg, rgba(229, 240, 255, 0.9), rgba(214, 232, 255, 0.7), rgba(207, 236, 255, 0.9)); |
| | | } |
| | | |
| | | .welcome-title { |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | color: #222; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | .welcome-user { |
| | | margin-right: 6px; |
| | | } |
| | | |
| | | .welcome-time { |
| | | margin-top: 10px; |
| | | font-size: 16px; |
| | | color: rgba(0, 0, 0, 0.55); |
| | | } |
| | | |
| | | .user-card { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 18px 22px; |
| | | } |
| | | |
| | | .user-card-main { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 5px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .user-name { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #111; |
| | | letter-spacing: 1px; |
| | | } |
| | | |
| | | .user-role { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 20px; |
| | | padding: 5px 10px; |
| | | background: rgba(245, 246, 248, 1); |
| | | color: #333; |
| | | width: fit-content; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .user-meta { |
| | | font-size: 12px; |
| | | color: rgba(0, 0, 0, 0.55); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .user-meta .sep { |
| | | margin: 0 10px; |
| | | color: rgba(0, 0, 0, 0.25); |
| | | } |
| | | |
| | | .avatar { |
| | | width: 90px; |
| | | height: 90px; |
| | | border-radius: 50%; |
| | | object-fit: cover; |
| | | flex: 0 0 auto; |
| | | } |
| | | |
| | | .data-cards { |
| | | display: flex; |
| | | gap: 16px; |
| | | justify-content: flex-start; |
| | | background: #ffffff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | width: 50%; |
| | | display: flex; |
| | | gap: 16px; |
| | | justify-content: flex-start; |
| | | background: #ffffff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .data-title { |
| | | font-weight: 700; |
| | | font-size: 26px; |
| | | color: #FFFFFF; |
| | | font-weight: 700; |
| | | font-size: 26px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .data-num { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .data-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 14px 10px 10px 10px; |
| | | min-width: 160px; |
| | | box-shadow: 0 2px 8px #eee; |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 32%; |
| | | height: 140px; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 14px 10px 10px 10px; |
| | | min-width: 160px; |
| | | box-shadow: 0 2px 8px #eee; |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 32%; |
| | | height: 140px; |
| | | } |
| | | |
| | | .data-card.sales { |
| | | background-image: url("../assets/images/xioashoushuju.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background-image: url("../assets/images/xioashoushuju.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.purchase { |
| | | background-image: url("../assets/images/caigou.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background-image: url("../assets/images/caigou.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.inventory { |
| | | background-image: url("../assets/images/kucun.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | background-image: url("../assets/images/kucun.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-desc { |
| | | font-weight: 500; |
| | | font-size: 13px; |
| | | color: #FFFFFF; |
| | | font-weight: 500; |
| | | font-size: 13px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | margin: 10px 0; |
| | | color: #FFFFFF; |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | margin: 10px 0; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .top-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | width: 50%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | height: 180px; |
| | | width: 20%; |
| | | } |
| | | |
| | | .todo-panel { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | width: 50%; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | height: 180px; |
| | | width: 30%; |
| | | } |
| | | |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | font-size: 15px; |
| | | overflow-y: auto; |
| | | height: 260px; |
| | | height: 100px; |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | font-size: 15px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 8px 20px; |
| | | height: 74px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | background: rgba(225,227,250,0.62); |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 8px 20px; |
| | | height: 74px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | background: rgba(225, 227, 250, 0.62); |
| | | } |
| | | |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | position: relative; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | position: relative; |
| | | } |
| | | |
| | | .todo-title::before { |
| | | content: ''; /* å¿
éï¼è¡¨ç¤ºè¿éæä¸ä¸ªå
容 */ |
| | | position: absolute; |
| | | left: -10px; /* å®ä½å°å·¦ä¾§ */ |
| | | top: 50%; /* åç´å±
ä¸ */ |
| | | transform: translateY(-50%); /* å¾®è°åç´å±
ä¸ */ |
| | | width: 6px; /* åçç´å¾ */ |
| | | height: 6px; /* åçç´å¾ */ |
| | | background: #498CEB; |
| | | border-radius: 50%; /* 让å
¶åæåå½¢ */ |
| | | content: ''; |
| | | /* å¿
éï¼è¡¨ç¤ºè¿éæä¸ä¸ªå
容 */ |
| | | position: absolute; |
| | | left: -10px; |
| | | /* å®ä½å°å·¦ä¾§ */ |
| | | top: 50%; |
| | | /* åç´å±
ä¸ */ |
| | | transform: translateY(-50%); |
| | | /* å¾®è°åç´å±
ä¸ */ |
| | | width: 6px; |
| | | /* åçç´å¾ */ |
| | | height: 6px; |
| | | /* åçç´å¾ */ |
| | | background: #498CEB; |
| | | border-radius: 50%; |
| | | /* 让å
¶åæåå½¢ */ |
| | | } |
| | | |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | } |
| | | |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | } |
| | | |
| | | .todo-meta { |
| | | color: #888; |
| | | font-size: 13px; |
| | | color: #888; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .dashboard-row { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .main-panel { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .section-title { |
| | | position: relative; |
| | | font-size: 18px; |
| | | color: #333; |
| | | padding-left: 10px; |
| | | margin-bottom: 10px; |
| | | font-weight: 700; |
| | | position: relative; |
| | | font-size: 18px; |
| | | color: #333; |
| | | padding-left: 10px; |
| | | margin-bottom: 10px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .section-title::before { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 4px; |
| | | content: ''; |
| | | width: 4px; |
| | | height: 18px; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 4px; |
| | | content: ''; |
| | | width: 4px; |
| | | height: 18px; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .contract-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20px; |
| | | height: 90px; |
| | | background: rgba(245,245,245,0.59); |
| | | width: 100%; |
| | | border-radius: 10px; |
| | | padding: 10px 30px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20px; |
| | | height: 90px; |
| | | background: rgba(245, 245, 245, 0.59); |
| | | width: 100%; |
| | | border-radius: 10px; |
| | | padding: 10px 30px; |
| | | } |
| | | |
| | | .contract-summary { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 30px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .contract-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .contract-name { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #050505; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #050505; |
| | | } |
| | | |
| | | .contract-meta { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | gap: 80px; |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | gap: 80px; |
| | | } |
| | | |
| | | .main-amount { |
| | | font-size: 24px; |
| | | color: rgba(51,50,50,0.85); |
| | | font-size: 24px; |
| | | color: rgba(51, 50, 50, 0.85); |
| | | } |
| | | .up { color: #e57373; } |
| | | |
| | | .up { |
| | | color: #e57373; |
| | | } |
| | | |
| | | .contract-list { |
| | | margin-top: 16px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | list-style: none; |
| | | padding: 0; |
| | | height: 190px; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | margin-top: 16px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | list-style: none; |
| | | padding: 0; |
| | | height: 190px; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | } |
| | | |
| | | .line { |
| | | position: relative; |
| | | width: 230px; |
| | | position: relative; |
| | | width: 230px; |
| | | } |
| | | |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 2px; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | content: ''; |
| | | position: absolute; |
| | | right: 2px; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .contract-list li { |
| | | margin-top: 10px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .quality-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .quality-card { |
| | | border-radius: 8px; |
| | | padding: 15px 10px 10px 50px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(0,0,0,0.67); |
| | | width: 236px; |
| | | height: 49px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | border-radius: 8px; |
| | | padding: 15px 10px 10px 50px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(0, 0, 0, 0.67); |
| | | width: 236px; |
| | | height: 49px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .quality-card.one { |
| | | background-image: url("../assets/images/yuancailiao.png"); |
| | | background-image: url("../assets/images/yuancailiao.png"); |
| | | } |
| | | |
| | | .quality-card.two { |
| | | background-image: url("../assets/images/guocheng.png"); |
| | | background-image: url("../assets/images/guocheng.png"); |
| | | } |
| | | |
| | | .quality-card.three { |
| | | background-image: url("../assets/images/chuchang.png"); |
| | | |
| | | background-image: url("../assets/images/chuchang.png"); |
| | | |
| | | } |
| | | |
| | | .quality-card span { |
| | | color: #4fc3f7; |
| | | font-weight: bold; |
| | | margin-left: 6px; |
| | | color: #4fc3f7; |
| | | font-weight: bold; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 220px; |
| | | margin-top: 10px; |
| | | width: 100%; |
| | | height: 220px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .process-panel { |
| | | padding-bottom: 10px; |
| | | } |
| | | |
| | | .process-panel__header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .process-panel__body { |
| | | display: flex; |
| | | gap: 24px; |
| | | align-items: stretch; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .process-panel__chart { |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding: 6px 0; |
| | | } |
| | | |
| | | .process-panel__aside { |
| | | width: 260px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .process-legend { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | align-items: flex-start; |
| | | padding: 8px 6px; |
| | | } |
| | | |
| | | .process-legend__item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | font-size: 13px; |
| | | color: rgba(0, 0, 0, 0.55); |
| | | } |
| | | |
| | | .dot { |
| | | width: 10px; |
| | | height: 10px; |
| | | border-radius: 2px; |
| | | display: inline-block; |
| | | } |
| | | |
| | | .dot-blue { |
| | | background: #1E5BFF; |
| | | } |
| | | |
| | | .dot-yellow { |
| | | background: #F7B500; |
| | | } |
| | | |
| | | .dot-teal { |
| | | background: #19C6C6; |
| | | } |
| | | |
| | | .process-card { |
| | | background: rgba(245, 247, 250, 0.9); |
| | | border-radius: 10px; |
| | | padding: 16px 16px; |
| | | } |
| | | |
| | | .process-card--name { |
| | | background: rgba(235, 242, 255, 1); |
| | | color: #1E5BFF; |
| | | font-weight: 800; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .process-card__label { |
| | | font-size: 13px; |
| | | color: rgba(0, 0, 0, 0.55); |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .process-card__value { |
| | | font-size: 24px; |
| | | font-weight: 800; |
| | | color: rgba(0, 0, 0, 0.8); |
| | | } |
| | | |
| | | .process-card__value .unit { |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | color: rgba(0, 0, 0, 0.45); |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | | .process-panel__body { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .process-panel__aside { |
| | | width: 100%; |
| | | flex-direction: row; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .process-card { |
| | | flex: 1; |
| | | min-width: 220px; |
| | | } |
| | | } |
| | | |
| | | .process-selection-wrapper { |
| | | max-height: 400px; |
| | | overflow-y: auto; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .process-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | :deep(.el-checkbox.is-bordered) { |
| | | margin-left: 0 !important; |
| | | width: 100%; |
| | | } |
| | | </style> |
| | |
| | | delStockOut, |
| | | } from "@/api/inventoryManagement/stockOut.js"; |
| | | import { |
| | | findAllQualifiedStockRecordTypeOptions, |
| | | findAllStockRecordTypeOptions, |
| | | findAllUnqualifiedStockRecordTypeOptions |
| | | findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | if (props.type === '0') { |
| | | findAllQualifiedStockRecordTypeOptions() |
| | | findAllQualifiedStockOutRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | | return |
| | | } |
| | | findAllUnqualifiedStockRecordTypeOptions() |
| | | findAllUnQualifiedStockOutRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | |
| | | batchDeleteStockInRecords, |
| | | } from "@/api/inventoryManagement/stockInRecord.js"; |
| | | import { |
| | | findAllQualifiedStockRecordTypeOptions, |
| | | findAllUnqualifiedStockRecordTypeOptions |
| | | findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | const {proxy} = getCurrentInstance(); |
| | |
| | | const stockRecordTypeOptions = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | size: 10, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | |
| | | getStockInRecordListPage(params) |
| | | .then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total || 0; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }) |
| | |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | if (props.type === '0') { |
| | | findAllQualifiedStockRecordTypeOptions() |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | | return |
| | | } |
| | | findAllUnqualifiedStockRecordTypeOptions() |
| | | findAllUnQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | |
| | | style="width: 240px;" |
| | | /> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | <el-button type="primary" @click="onSearch" style="margin-left: 10px"> |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | |
| | | show-overflow-tooltip |
| | | /> |
| | | </el-table> |
| | | <pagination |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { |
| | | getStockMonthlyReport, |
| | | getStockInOutReport, |
| | | } from '@/api/inventoryManagement/stockReport' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { |
| | | getStockInventoryInAndOutReportList, |
| | | getStockInventoryReportList |
| | | } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import {findAllQualifiedStockRecordTypeOptions} from "@/api/basicData/enum.js"; |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions,findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | |
| | | tableData: [] |
| | | }) |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }) |
| | | |
| | | const total = ref(0) |
| | | |
| | | const stockRecordTypeOptions = ref([]) |
| | | |
| | | const getRecordType = (recordType) => { |
| | |
| | | |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | findAllQualifiedStockRecordTypeOptions() |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | findAllUnQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = [...stockRecordTypeOptions.value,...res.data]; |
| | | }) |
| | | }) |
| | | } |
| | | |
| | |
| | | |
| | | // æ¥è¡¨ç±»åæ¹å |
| | | const handleReportTypeChange = () => { |
| | | page.current = 1 |
| | | reportData.value = { |
| | | summary: null, |
| | | chartData: null, |
| | |
| | | |
| | | tableLoading.value = true |
| | | try { |
| | | const params = getQueryParams() |
| | | const baseParams = getQueryParams() |
| | | const params = { |
| | | ...baseParams, |
| | | current: page.current, |
| | | size: page.size, |
| | | } |
| | | let response |
| | | |
| | | if (searchForm.reportType === 'inout') { |
| | |
| | | response = await getStockInventoryReportList(params) |
| | | } |
| | | if (response.code === 200) { |
| | | reportData.value.tableData = response.data.records |
| | | reportData.value.tableData = response.data.records || [] |
| | | total.value = response.data.total || 0 |
| | | // reportData.value.summary = response.data.summary |
| | | // reportData.value.chartData = response.data.chartData |
| | | // nextTick(() => { |
| | |
| | | } finally { |
| | | tableLoading.value = false |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢æé®ï¼éç½®å°ç¬¬ä¸é¡µå¹¶æ¥è¯¢ |
| | | const onSearch = () => { |
| | | page.current = 1 |
| | | handleQuery() |
| | | } |
| | | |
| | | // å页åå |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page |
| | | page.size = obj.limit |
| | | handleQuery() |
| | | } |
| | | // // çæåæ°æ® |
| | | // const generateMockData = () => { |
| | |
| | | ] |
| | | |
| | | fetchStockRecordTypeOptions() |
| | | // åå§åå è½½ä¸æ¬¡æ°æ® |
| | | handleQuery() |
| | | }) |
| | | </script> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- ç»è®¡æ¦è§ --> |
| | | <el-row :gutter="16" style="margin-bottom: 16px"> |
| | | <el-col :span="6"> |
| | | <el-card shadow="never"> |
| | | <div>æ»ä»»å¡æ°</div> |
| | | <div style="font-size: 22px; font-weight: 600; margin-top: 4px"> |
| | | {{ totalTasks }} |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card shadow="never"> |
| | | <div>è¿è¡ä¸ä»»å¡</div> |
| | | <div style="font-size: 22px; font-weight: 600; margin-top: 4px"> |
| | | {{ runningTasks }} |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card shadow="never"> |
| | | <div>已宿任å¡</div> |
| | | <div style="font-size: 22px; font-weight: 600; margin-top: 4px"> |
| | | {{ finishedTasks }} |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card shadow="never"> |
| | | <div>宿ç</div> |
| | | <div style="font-size: 22px; font-weight: 600; margin-top: 4px"> |
| | | {{ completionRate }}% |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ä»»å¡ç¼å·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.taskNo" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥ä»»å¡ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">车è¾ç¼å·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.vehicleCode" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">任塿¥æï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | @change="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">ç¶æï¼</span> |
| | | <el-select |
| | | v-model="searchForm.status" |
| | | style="width: 140px" |
| | | placeholder="è¯·éæ©ä»»å¡ç¶æ" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="openAdd"> |
| | | æ°å»ºè¿è¾ä»»å¡ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 22em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="taskNo" |
| | | label="ä»»å¡ç¼å·" |
| | | width="150" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="outboundOrderNo" |
| | | label="åºåºè®¢åå·" |
| | | width="180" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="vehicleCode" |
| | | label="车è¾ç¼å·" |
| | | width="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="plateNumber" |
| | | label="车çå·ç " |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="driverName" |
| | | label="叿º" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="loadAddress" |
| | | label="è£
è´§å°ç¹" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="deliveryAddress" |
| | | label="éè´§å°ç¹" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="loadTime" |
| | | label="è£
è´§æ¶é´" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="deliveryTime" |
| | | label="éè´§æ¶é´" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="signTime" |
| | | label="ç¾æ¶æ¶é´" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="ç¶æ" width="110" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="statusTagType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è¿åº¦" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-progress |
| | | :percentage="scope.row.progress" |
| | | :status="scope.row.status === '已宿' ? 'success' : undefined" |
| | | :stroke-width="12" |
| | | :show-text="false" |
| | | /> |
| | | <div style="font-size: 12px; margin-top: 4px"> |
| | | {{ scope.row.progress }}% |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="160" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEdit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeRow(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="780px" |
| | | destroy-on-close |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110px" |
| | | label-position="right" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»»å¡ç¼å·ï¼" prop="taskNo"> |
| | | <el-input |
| | | v-model="form.taskNo" |
| | | placeholder="请è¾å
¥ä»»å¡ç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåºè®¢åå·ï¼" prop="outboundOrderNo"> |
| | | <el-input |
| | | v-model="form.outboundOrderNo" |
| | | placeholder="请è¾å
¥å
³èåºåºè®¢åå·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç¼å·ï¼" prop="vehicleCode"> |
| | | <el-input |
| | | v-model="form.vehicleCode" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车çå·ç ï¼" prop="plateNumber"> |
| | | <el-input |
| | | v-model="form.plateNumber" |
| | | placeholder="请è¾å
¥è½¦çå·ç " |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="叿ºï¼" prop="driverName"> |
| | | <el-input |
| | | v-model="form.driverName" |
| | | placeholder="请è¾å
¥å¸æºå§å" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="叿ºçµè¯ï¼" prop="driverPhone"> |
| | | <el-input |
| | | v-model="form.driverPhone" |
| | | placeholder="请è¾å
¥å¸æºèç³»çµè¯" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è£
è´§å°ç¹ï¼" prop="loadAddress"> |
| | | <el-input |
| | | v-model="form.loadAddress" |
| | | placeholder="请è¾å
¥è£
è´§å°ç¹" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´§å°ç¹ï¼" prop="deliveryAddress"> |
| | | <el-input |
| | | v-model="form.deliveryAddress" |
| | | placeholder="请è¾å
¥éè´§å°ç¹" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="è£
è´§æ¶é´ï¼" prop="loadTime"> |
| | | <el-date-picker |
| | | v-model="form.loadTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm" |
| | | placeholder="è¯·éæ©è£
è´§æ¶é´" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éè´§æ¶é´ï¼" prop="deliveryTime"> |
| | | <el-date-picker |
| | | v-model="form.deliveryTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm" |
| | | placeholder="è¯·éæ©éè´§æ¶é´" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¾æ¶æ¶é´ï¼" prop="signTime"> |
| | | <el-date-picker |
| | | v-model="form.signTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm" |
| | | placeholder="è¯·éæ©ç¾æ¶æ¶é´" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ä»»å¡ç¶æ"> |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è®¡åæ¥æï¼" prop="planDate"> |
| | | <el-date-picker |
| | | v-model="form.planDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©è®¡åæ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleCancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | // 模æè¿è¾ä»»å¡æ°æ® |
| | | const rawTasks = ref([ |
| | | { |
| | | id: 1, |
| | | taskNo: "T2024-1201-001", |
| | | outboundOrderNo: "OUT-2024-1201-1001", |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | driverName: "å¼ å¸å
", |
| | | driverPhone: "13800000001", |
| | | loadAddress: "æ·±å³ä»åºAåº", |
| | | deliveryAddress: "广å·å®¢æ·ä¸é¨", |
| | | planDate: "2024-12-01", |
| | | loadTime: "2024-12-01 09:00:00", |
| | | deliveryTime: "2024-12-01 14:30:00", |
| | | signTime: "2024-12-01 15:00:00", |
| | | status: "已宿", |
| | | }, |
| | | { |
| | | id: 2, |
| | | taskNo: "T2024-1201-002", |
| | | outboundOrderNo: "OUT-2024-1201-1002", |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | driverName: "æå¸å
", |
| | | driverPhone: "13800000002", |
| | | loadAddress: "æ·±å³ä»åºBåº", |
| | | deliveryAddress: "ä¸è客æ·äºé¨", |
| | | planDate: "2024-12-01", |
| | | loadTime: "2024-12-01 10:00:00", |
| | | deliveryTime: "2024-12-01 13:00:00", |
| | | signTime: "", |
| | | status: "è¿è¾ä¸", |
| | | }, |
| | | { |
| | | id: 3, |
| | | taskNo: "T2024-1202-001", |
| | | outboundOrderNo: "OUT-2024-1202-1003", |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | driverName: "å¼ å¸å
", |
| | | driverPhone: "13800000001", |
| | | loadAddress: "æ·±å³ä»åºAåº", |
| | | deliveryAddress: "ä½å±±å®¢æ·ä¸é¨", |
| | | planDate: "2024-12-02", |
| | | loadTime: "2024-12-02 08:30:00", |
| | | deliveryTime: "", |
| | | signTime: "", |
| | | status: "å¾
å车", |
| | | }, |
| | | { |
| | | id: 4, |
| | | taskNo: "T2024-1203-001", |
| | | outboundOrderNo: "OUT-2024-1203-1004", |
| | | vehicleCode: "CL-202403", |
| | | plateNumber: "粤C11223", |
| | | driverName: "çå¸å
", |
| | | driverPhone: "13800000003", |
| | | loadAddress: "æ·±å³ä»åºCåº", |
| | | deliveryAddress: "æ å·å®¢æ·åé¨", |
| | | planDate: "2024-12-03", |
| | | loadTime: "", |
| | | deliveryTime: "", |
| | | signTime: "", |
| | | status: "æªå¼å§", |
| | | }, |
| | | ]); |
| | | |
| | | // ç¶ææä¸¾ |
| | | const statusOptions = [ |
| | | { label: "æªå¼å§", value: "æªå¼å§" }, |
| | | { label: "å¾
å车", value: "å¾
å车" }, |
| | | { label: "è¿è¾ä¸", value: "è¿è¾ä¸" }, |
| | | { label: "å¾
ç¾æ¶", value: "å¾
ç¾æ¶" }, |
| | | { label: "已宿", value: "已宿" }, |
| | | ]; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | taskNo: "", |
| | | vehicleCode: "", |
| | | dateRange: [], |
| | | status: "", |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ®ï¼å¸¦è¿åº¦ç计ç®åæ®µï¼ |
| | | const tableData = ref([]); |
| | | |
| | | // ç»è®¡ |
| | | const totalTasks = computed(() => rawTasks.value.length); |
| | | const finishedTasks = computed( |
| | | () => rawTasks.value.filter((t) => t.status === "已宿").length |
| | | ); |
| | | const runningTasks = computed( |
| | | () => |
| | | rawTasks.value.filter((t) => |
| | | ["å¾
å车", "è¿è¾ä¸", "å¾
ç¾æ¶"].includes(t.status) |
| | | ).length |
| | | ); |
| | | const completionRate = computed(() => { |
| | | if (!totalTasks.value) return 0; |
| | | return Math.round((finishedTasks.value / totalTasks.value) * 100); |
| | | }); |
| | | |
| | | // 计ç®åæ¡ä»»å¡è¿åº¦ |
| | | const computeProgress = (task) => { |
| | | if (task.status === "已宿" || task.signTime) return 100; |
| | | if (task.status === "å¾
ç¾æ¶" || task.deliveryTime) return 80; |
| | | if (task.status === "è¿è¾ä¸") return 60; |
| | | if (task.status === "å¾
å车" || task.loadTime) return 30; |
| | | if (task.status === "æªå¼å§") return 0; |
| | | return 0; |
| | | }; |
| | | |
| | | // ç¶æ tag æ ·å¼ |
| | | const statusTagType = (status) => { |
| | | if (status === "已宿") return "success"; |
| | | if (status === "è¿è¾ä¸") return "warning"; |
| | | if (status === "å¾
ç¾æ¶" || status === "å¾
å车") return "info"; |
| | | return "default"; |
| | | }; |
| | | |
| | | // éç®è¡¨æ ¼æ°æ® |
| | | const recomputeTable = () => { |
| | | const filtered = rawTasks.value |
| | | .filter((t) => { |
| | | if (searchForm.taskNo && !t.taskNo.includes(searchForm.taskNo.trim())) { |
| | | return false; |
| | | } |
| | | if ( |
| | | searchForm.vehicleCode && |
| | | !t.vehicleCode.includes(searchForm.vehicleCode.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (searchForm.status && t.status !== searchForm.status) { |
| | | return false; |
| | | } |
| | | if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) { |
| | | const [start, end] = searchForm.dateRange; |
| | | if (!t.planDate || t.planDate < start || t.planDate > end) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }) |
| | | .map((t) => ({ |
| | | ...t, |
| | | progress: computeProgress(t), |
| | | })); |
| | | |
| | | tableData.value = filtered; |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.taskNo = ""; |
| | | searchForm.vehicleCode = ""; |
| | | searchForm.dateRange = []; |
| | | searchForm.status = ""; |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | // è¡æ ·å¼ |
| | | const tableRowClassName = ({ row }) => { |
| | | if (row.status === "已宿") { |
| | | return "row-finished"; |
| | | } |
| | | if (row.status === "è¿è¾ä¸") { |
| | | return "row-running"; |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // å¼¹çª & 表å |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("æ°å»ºè¿è¾ä»»å¡"); |
| | | const isEdit = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: null, |
| | | taskNo: "", |
| | | outboundOrderNo: "", |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | driverName: "", |
| | | driverPhone: "", |
| | | loadAddress: "", |
| | | deliveryAddress: "", |
| | | planDate: "", |
| | | loadTime: "", |
| | | deliveryTime: "", |
| | | signTime: "", |
| | | status: "æªå¼å§", |
| | | }); |
| | | |
| | | const rules = { |
| | | taskNo: [{ required: true, message: "请è¾å
¥ä»»å¡ç¼å·", trigger: "blur" }], |
| | | outboundOrderNo: [ |
| | | { required: true, message: "请è¾å
¥åºåºè®¢åå·", trigger: "blur" }, |
| | | ], |
| | | vehicleCode: [{ required: true, message: "请è¾å
¥è½¦è¾ç¼å·", trigger: "blur" }], |
| | | plateNumber: [{ required: true, message: "请è¾å
¥è½¦çå·ç ", trigger: "blur" }], |
| | | driverName: [{ required: true, message: "请è¾å
¥å¸æºå§å", trigger: "blur" }], |
| | | loadAddress: [{ required: true, message: "请è¾å
¥è£
è´§å°ç¹", trigger: "blur" }], |
| | | deliveryAddress: [ |
| | | { required: true, message: "请è¾å
¥éè´§å°ç¹", trigger: "blur" }, |
| | | ], |
| | | planDate: [{ required: true, message: "è¯·éæ©è®¡åæ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | // æ°å»º |
| | | const openAdd = () => { |
| | | dialogTitle.value = "æ°å»ºè¿è¾ä»»å¡"; |
| | | isEdit.value = false; |
| | | Object.assign(form, { |
| | | id: null, |
| | | taskNo: "", |
| | | outboundOrderNo: "", |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | driverName: "", |
| | | driverPhone: "", |
| | | loadAddress: "", |
| | | deliveryAddress: "", |
| | | planDate: "", |
| | | loadTime: "", |
| | | deliveryTime: "", |
| | | signTime: "", |
| | | status: "æªå¼å§", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const openEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾è¿è¾ä»»å¡"; |
| | | isEdit.value = true; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¿å |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | |
| | | if (isEdit.value) { |
| | | const index = rawTasks.value.findIndex((t) => t.id === form.id); |
| | | if (index !== -1) { |
| | | rawTasks.value[index] = { ...form }; |
| | | } |
| | | ElMessage.success("è¿è¾ä»»å¡å·²æ´æ°"); |
| | | } else { |
| | | const newId = rawTasks.value.length |
| | | ? Math.max(...rawTasks.value.map((t) => t.id)) + 1 |
| | | : 1; |
| | | rawTasks.value.push({ ...form, id: newId }); |
| | | ElMessage.success("è¿è¾ä»»å¡å·²æ°å¢"); |
| | | } |
| | | dialogVisible.value = false; |
| | | recomputeTable(); |
| | | }); |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const removeRow = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å é¤è¯¥è¿è¾ä»»å¡ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | rawTasks.value = rawTasks.value.filter((t) => t.id !== row.id); |
| | | recomputeTable(); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | recomputeTable(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | ::v-deep(.row-finished) { |
| | | background-color: #f6ffed; |
| | | } |
| | | |
| | | ::v-deep(.row-running) { |
| | | background-color: #fffbe6; |
| | | } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">车è¾ç¼å·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.vehicleCode" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">å æ²¹æ¶é´ï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | @change="handleQuery" |
| | | /> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="openAdd"> |
| | | æ°å¢å æ²¹è®°å½ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="vehicleCode" |
| | | label="车è¾ç¼å·" |
| | | width="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="plateNumber" |
| | | label="车çå·ç " |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="fuelDate" |
| | | label="å æ²¹æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="gunNo" |
| | | label="æ²¹æªå·" |
| | | width="90" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | prop="amount" |
| | | label="éé¢(å
)" |
| | | width="100" |
| | | align="right" |
| | | > |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.amount?.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="liters" |
| | | label="åæ°(L)" |
| | | width="90" |
| | | align="right" |
| | | > |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.liters?.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="startMileage" |
| | | label="èµ·å§éç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="endMileage" |
| | | label="ç»æéç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="distance" |
| | | label="è¡é©¶éç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="fuelConsumption" |
| | | label="æ²¹è(L/100km)" |
| | | width="130" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <span |
| | | :style="scope.row.isAbnormal ? 'color:#F56C6C;font-weight:600;' : ''" |
| | | > |
| | | {{ scope.row.fuelConsumption != null ? scope.row.fuelConsumption.toFixed(2) : '-' }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="avgConsumption" |
| | | label="车è¾å¹³åæ²¹è" |
| | | width="130" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <span> |
| | | {{ scope.row.avgConsumption != null ? scope.row.avgConsumption.toFixed(2) : '-' }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å¼å¸¸é¢è¦" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.isAbnormal" type="danger" size="small"> |
| | | å¼å¸¸ |
| | | </el-tag> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEdit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeRow(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="640px" |
| | | destroy-on-close |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110px" |
| | | label-position="right" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç¼å·ï¼" prop="vehicleCode"> |
| | | <el-input |
| | | v-model="form.vehicleCode" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车çå·ç ï¼" prop="plateNumber"> |
| | | <el-input |
| | | v-model="form.plateNumber" |
| | | placeholder="请è¾å
¥è½¦çå·ç " |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å æ²¹æ¥æï¼" prop="fuelDate"> |
| | | <el-date-picker |
| | | v-model="form.fuelDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©å æ²¹æ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ²¹æªå·ï¼" prop="gunNo"> |
| | | <el-input |
| | | v-model="form.gunNo" |
| | | placeholder="请è¾å
¥æ²¹æªå·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éé¢(å
)ï¼" prop="amount"> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | placeholder="请è¾å
¥éé¢" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ°(L)ï¼" prop="liters"> |
| | | <el-input-number |
| | | v-model="form.liters" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | placeholder="请è¾å
¥åæ°" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµ·å§éç¨(km)ï¼" prop="startMileage"> |
| | | <el-input-number |
| | | v-model="form.startMileage" |
| | | :min="0" |
| | | :step="1" |
| | | placeholder="请è¾å
¥èµ·å§éç¨" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»æéç¨(km)ï¼" prop="endMileage"> |
| | | <el-input-number |
| | | v-model="form.endMileage" |
| | | :min="0" |
| | | :step="1" |
| | | placeholder="请è¾å
¥ç»æéç¨" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleCancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | // 模æå æ²¹è®°å½æ°æ® |
| | | const rawRecords = ref([ |
| | | { |
| | | id: 1, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2024-12-01", |
| | | gunNo: "01", |
| | | amount: 500, |
| | | liters: 70, |
| | | startMileage: 12000, |
| | | endMileage: 12600, |
| | | }, |
| | | { |
| | | id: 2, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2024-12-15", |
| | | gunNo: "02", |
| | | amount: 520, |
| | | liters: 72, |
| | | startMileage: 12600, |
| | | endMileage: 13250, |
| | | }, |
| | | { |
| | | id: 3, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | fuelDate: "2024-12-05", |
| | | gunNo: "03", |
| | | amount: 430, |
| | | liters: 60, |
| | | startMileage: 8000, |
| | | endMileage: 8520, |
| | | }, |
| | | { |
| | | id: 4, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | fuelDate: "2024-12-20", |
| | | gunNo: "01", |
| | | amount: 450, |
| | | liters: 63, |
| | | startMileage: 8520, |
| | | endMileage: 9000, |
| | | }, |
| | | { |
| | | id: 5, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2025-01-05", |
| | | gunNo: "01", |
| | | amount: 700, |
| | | liters: 90, |
| | | startMileage: 13250, |
| | | endMileage: 13600, // ææ¾å¼å¸¸æ²¹è |
| | | }, |
| | | ]); |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | vehicleCode: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ®ï¼å
å«è®¡ç®åæ®µï¼ |
| | | const tableData = ref([]); |
| | | |
| | | // å¼¹çª & 表å |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("æ°å¢å 油记å½"); |
| | | const isEdit = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | fuelDate: "", |
| | | gunNo: "", |
| | | amount: null, |
| | | liters: null, |
| | | startMileage: null, |
| | | endMileage: null, |
| | | }); |
| | | |
| | | const rules = { |
| | | vehicleCode: [{ required: true, message: "请è¾å
¥è½¦è¾ç¼å·", trigger: "blur" }], |
| | | plateNumber: [{ required: true, message: "请è¾å
¥è½¦çå·ç ", trigger: "blur" }], |
| | | fuelDate: [{ required: true, message: "è¯·éæ©å æ²¹æ¥æ", trigger: "change" }], |
| | | gunNo: [{ required: true, message: "请è¾å
¥æ²¹æªå·", trigger: "blur" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | liters: [{ required: true, message: "请è¾å
¥åæ°", trigger: "blur" }], |
| | | startMileage: [{ required: true, message: "请è¾å
¥èµ·å§éç¨", trigger: "blur" }], |
| | | endMileage: [{ required: true, message: "请è¾å
¥ç»æéç¨", trigger: "blur" }], |
| | | }; |
| | | |
| | | // éæ°è®¡ç®æ²¹èã平忲¹èåå¼å¸¸é¢è¦ |
| | | const recomputeTable = () => { |
| | | const records = rawRecords.value; |
| | | |
| | | // 1. å
æè½¦è¾ç»è®¡å¹³åæ²¹è |
| | | const stats = {}; |
| | | records.forEach((r) => { |
| | | const distance = r.endMileage - r.startMileage; |
| | | if (distance <= 0 || !r.liters) return; |
| | | const cons = (r.liters / distance) * 100; // L/100km |
| | | if (!stats[r.vehicleCode]) { |
| | | stats[r.vehicleCode] = { totalCons: 0, count: 0 }; |
| | | } |
| | | stats[r.vehicleCode].totalCons += cons; |
| | | stats[r.vehicleCode].count += 1; |
| | | }); |
| | | |
| | | const avgMap = {}; |
| | | Object.keys(stats).forEach((key) => { |
| | | avgMap[key] = stats[key].totalCons / stats[key].count; |
| | | }); |
| | | |
| | | // 2. æçéæ¡ä»¶è¿æ»¤å¹¶è¡¥å
计ç®å段 |
| | | const filtered = records |
| | | .filter((r) => { |
| | | if ( |
| | | searchForm.vehicleCode && |
| | | !r.vehicleCode.includes(searchForm.vehicleCode.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) { |
| | | const [start, end] = searchForm.dateRange; |
| | | if (r.fuelDate < start || r.fuelDate > end) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }) |
| | | .map((r) => { |
| | | const distance = r.endMileage - r.startMileage; |
| | | const fuelConsumption = |
| | | distance > 0 && r.liters |
| | | ? (r.liters / distance) * 100 |
| | | : null; |
| | | const avgConsumption = |
| | | avgMap[r.vehicleCode] != null ? avgMap[r.vehicleCode] : null; |
| | | const isAbnormal = |
| | | avgConsumption != null && |
| | | fuelConsumption != null && |
| | | fuelConsumption > avgConsumption * 1.2; |
| | | |
| | | return { |
| | | ...r, |
| | | distance, |
| | | fuelConsumption, |
| | | avgConsumption, |
| | | isAbnormal, |
| | | }; |
| | | }); |
| | | |
| | | tableData.value = filtered; |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.vehicleCode = ""; |
| | | searchForm.dateRange = []; |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | // è¡æ ·å¼ï¼å¼å¸¸é«äº®ï¼ |
| | | const tableRowClassName = ({ row }) => { |
| | | if (row.isAbnormal) { |
| | | return "row-abnormal"; |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | const openAdd = () => { |
| | | dialogTitle.value = "æ°å¢å 油记å½"; |
| | | isEdit.value = false; |
| | | Object.assign(form, { |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | fuelDate: "", |
| | | gunNo: "", |
| | | amount: null, |
| | | liters: null, |
| | | startMileage: null, |
| | | endMileage: null, |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const openEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾å 油记å½"; |
| | | isEdit.value = true; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¿å |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | |
| | | if (form.endMileage <= form.startMileage) { |
| | | ElMessage.warning("ç»æéç¨å¿
须大äºèµ·å§éç¨"); |
| | | return; |
| | | } |
| | | |
| | | if (isEdit.value) { |
| | | const index = rawRecords.value.findIndex((r) => r.id === form.id); |
| | | if (index !== -1) { |
| | | rawRecords.value[index] = { ...form }; |
| | | } |
| | | ElMessage.success("å æ²¹è®°å½å·²æ´æ°"); |
| | | } else { |
| | | const newId = rawRecords.value.length |
| | | ? Math.max(...rawRecords.value.map((r) => r.id)) + 1 |
| | | : 1; |
| | | rawRecords.value.push({ ...form, id: newId }); |
| | | ElMessage.success("å æ²¹è®°å½å·²æ°å¢"); |
| | | } |
| | | dialogVisible.value = false; |
| | | recomputeTable(); |
| | | }); |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const removeRow = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å é¤è¯¥å 油记å½ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | rawRecords.value = rawRecords.value.filter((r) => r.id !== row.id); |
| | | recomputeTable(); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | recomputeTable(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | ::v-deep(.row-abnormal) { |
| | | background-color: #fff5f5; |
| | | } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">车çå·ç ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.plateNumber" |
| | | style="width: 180px" |
| | | placeholder="请è¾å
¥è½¦çå·ç " |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">车è¾ç±»åï¼</span> |
| | | <el-select |
| | | v-model="searchForm.vehicleType" |
| | | style="width: 160px" |
| | | placeholder="è¯·éæ©è½¦è¾ç±»å" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in vehicleTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">æå±é¨é¨ï¼</span> |
| | | <el-select |
| | | v-model="searchForm.department" |
| | | style="width: 160px" |
| | | placeholder="è¯·éæ©æå±é¨é¨" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in departmentOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">ç¶æï¼</span> |
| | | <el-select |
| | | v-model="searchForm.status" |
| | | style="width: 140px" |
| | | placeholder="è¯·éæ©ç¶æ" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">彿¡£ç¶æï¼</span> |
| | | <el-select |
| | | v-model="searchForm.archived" |
| | | style="width: 140px" |
| | | placeholder="è¯·éæ©å½æ¡£ç¶æ" |
| | | clearable |
| | | > |
| | | <el-option label="æªå½æ¡£" value="false" /> |
| | | <el-option label="已彿¡£" value="true" /> |
| | | </el-select> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="openAdd">æ°å¢è½¦è¾</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="vehicleCode" |
| | | label="车è¾ç¼å·" |
| | | width="140" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="plateNumber" |
| | | label="车çå·ç " |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="vehicleType" |
| | | label="车è¾ç±»å" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="department" |
| | | label="æå±é¨é¨" |
| | | width="140" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="purchaseDate" |
| | | label="è´ç½®æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="licenseNumber" |
| | | label="è¡é©¶è¯ç¼å·" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="licenseIssueDate" |
| | | label="åè¯æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="licenseExpireDate" |
| | | label="å°ææ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="ç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="statusTagType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="彿¡£ç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.archived ? 'info' : 'success'"> |
| | | {{ scope.row.archived ? '已彿¡£' : 'æªå½æ¡£' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="220" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEdit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="warning" |
| | | link |
| | | size="small" |
| | | :disabled="scope.row.archived" |
| | | @click="archiveRow(scope.row)" |
| | | > |
| | | 彿¡£ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeRow(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="600px" |
| | | destroy-on-close |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | label-position="right" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç¼å·ï¼" prop="vehicleCode"> |
| | | <el-input v-model="form.vehicleCode" placeholder="请è¾å
¥è½¦è¾ç¼å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车çå·ç ï¼" prop="plateNumber"> |
| | | <el-input v-model="form.plateNumber" placeholder="请è¾å
¥è½¦çå·ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç±»åï¼" prop="vehicleType"> |
| | | <el-select |
| | | v-model="form.vehicleType" |
| | | placeholder="è¯·éæ©è½¦è¾ç±»å" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in vehicleTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æå±é¨é¨ï¼" prop="department"> |
| | | <el-select |
| | | v-model="form.department" |
| | | placeholder="è¯·éæ©æå±é¨é¨" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in departmentOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´ç½®æ¥æï¼" prop="purchaseDate"> |
| | | <el-date-picker |
| | | v-model="form.purchaseDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©è´ç½®æ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¡é©¶è¯ç¼å·ï¼" prop="licenseNumber"> |
| | | <el-input |
| | | v-model="form.licenseNumber" |
| | | placeholder="请è¾å
¥è¡é©¶è¯ç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åè¯æ¥æï¼" prop="licenseIssueDate"> |
| | | <el-date-picker |
| | | v-model="form.licenseIssueDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©åè¯æ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å°ææ¥æï¼" prop="licenseExpireDate"> |
| | | <el-date-picker |
| | | v-model="form.licenseExpireDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©å°ææ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleCancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | // 模æè½¦è¾åºç¡æ°æ® |
| | | const allVehicles = ref([ |
| | | { |
| | | id: 1, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | vehicleType: "å¢å¼è´§è½¦", |
| | | department: "ç©æµä¸é¨", |
| | | purchaseDate: "2022-03-15", |
| | | licenseNumber: "4401-2022-0001", |
| | | licenseIssueDate: "2022-03-10", |
| | | licenseExpireDate: "2026-03-10", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | }, |
| | | { |
| | | id: 2, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | vehicleType: "å·è车", |
| | | department: "ç©æµäºé¨", |
| | | purchaseDate: "2021-08-01", |
| | | licenseNumber: "4401-2021-0123", |
| | | licenseIssueDate: "2021-07-28", |
| | | licenseExpireDate: "2025-07-28", |
| | | status: "ç»´ä¿®", |
| | | archived: false, |
| | | }, |
| | | { |
| | | id: 3, |
| | | vehicleCode: "CL-202403", |
| | | plateNumber: "粤C11223", |
| | | vehicleType: "çµå¼è½¦", |
| | | department: "项ç®è¿è¾é¨", |
| | | purchaseDate: "2020-05-20", |
| | | licenseNumber: "4401-2020-0456", |
| | | licenseIssueDate: "2020-05-18", |
| | | licenseExpireDate: "2024-05-18", |
| | | status: "é²ç½®", |
| | | archived: false, |
| | | }, |
| | | { |
| | | id: 4, |
| | | vehicleCode: "CL-202404", |
| | | plateNumber: "粤D33445", |
| | | vehicleType: "å¢å¼è´§è½¦", |
| | | department: "èµäº§ç®¡çé¨", |
| | | purchaseDate: "2019-11-11", |
| | | licenseNumber: "4401-2019-0789", |
| | | licenseIssueDate: "2019-11-08", |
| | | licenseExpireDate: "2023-11-08", |
| | | status: "å¨ç¨", |
| | | archived: true, |
| | | }, |
| | | ]); |
| | | |
| | | // 䏿æä¸¾ |
| | | const vehicleTypeOptions = [ |
| | | { label: "å¢å¼è´§è½¦", value: "å¢å¼è´§è½¦" }, |
| | | { label: "å·è车", value: "å·è车" }, |
| | | { label: "çµå¼è½¦", value: "çµå¼è½¦" }, |
| | | { label: "å
¶ä»", value: "å
¶ä»" }, |
| | | ]; |
| | | |
| | | const departmentOptions = [ |
| | | { label: "ç©æµä¸é¨", value: "ç©æµä¸é¨" }, |
| | | { label: "ç©æµäºé¨", value: "ç©æµäºé¨" }, |
| | | { label: "项ç®è¿è¾é¨", value: "项ç®è¿è¾é¨" }, |
| | | { label: "èµäº§ç®¡çé¨", value: "èµäº§ç®¡çé¨" }, |
| | | ]; |
| | | |
| | | const statusOptions = [ |
| | | { label: "å¨ç¨", value: "å¨ç¨" }, |
| | | { label: "é²ç½®", value: "é²ç½®" }, |
| | | { label: "ç»´ä¿®", value: "ç»´ä¿®" }, |
| | | ]; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | department: "", |
| | | status: "", |
| | | archived: "", |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([...allVehicles.value]); |
| | | |
| | | // å¼¹çª & 表å |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("æ°å¢è½¦è¾"); |
| | | const isEdit = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | department: "", |
| | | purchaseDate: "", |
| | | licenseNumber: "", |
| | | licenseIssueDate: "", |
| | | licenseExpireDate: "", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | }); |
| | | |
| | | const rules = { |
| | | vehicleCode: [{ required: true, message: "请è¾å
¥è½¦è¾ç¼å·", trigger: "blur" }], |
| | | plateNumber: [{ required: true, message: "请è¾å
¥è½¦çå·ç ", trigger: "blur" }], |
| | | vehicleType: [{ required: true, message: "è¯·éæ©è½¦è¾ç±»å", trigger: "change" }], |
| | | department: [{ required: true, message: "è¯·éæ©æå±é¨é¨", trigger: "change" }], |
| | | purchaseDate: [{ required: true, message: "è¯·éæ©è´ç½®æ¥æ", trigger: "change" }], |
| | | status: [{ required: true, message: "è¯·éæ©ç¶æ", trigger: "change" }], |
| | | licenseNumber: [{ required: true, message: "请è¾å
¥è¡é©¶è¯ç¼å·", trigger: "blur" }], |
| | | licenseIssueDate: [{ required: true, message: "è¯·éæ©åè¯æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | tableData.value = allVehicles.value.filter((item) => { |
| | | if ( |
| | | searchForm.plateNumber && |
| | | !item.plateNumber.includes(searchForm.plateNumber.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (searchForm.vehicleType && item.vehicleType !== searchForm.vehicleType) { |
| | | return false; |
| | | } |
| | | if (searchForm.department && item.department !== searchForm.department) { |
| | | return false; |
| | | } |
| | | if (searchForm.status && item.status !== searchForm.status) { |
| | | return false; |
| | | } |
| | | if (searchForm.archived !== "") { |
| | | const targetArchived = searchForm.archived === "true"; |
| | | if (item.archived !== targetArchived) return false; |
| | | } |
| | | return true; |
| | | }); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.plateNumber = ""; |
| | | searchForm.vehicleType = ""; |
| | | searchForm.department = ""; |
| | | searchForm.status = ""; |
| | | searchForm.archived = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | const openAdd = () => { |
| | | dialogTitle.value = "æ°å¢è½¦è¾"; |
| | | isEdit.value = false; |
| | | Object.assign(form, { |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | department: "", |
| | | purchaseDate: "", |
| | | licenseInfo: "", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const openEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾è½¦è¾"; |
| | | isEdit.value = true; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¿å |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | if (isEdit.value) { |
| | | const index = allVehicles.value.findIndex((v) => v.id === form.id); |
| | | if (index !== -1) { |
| | | allVehicles.value[index] = { ...form }; |
| | | } |
| | | ElMessage.success("车è¾ä¿¡æ¯å·²æ´æ°"); |
| | | } else { |
| | | const newId = allVehicles.value.length |
| | | ? Math.max(...allVehicles.value.map((v) => v.id)) + 1 |
| | | : 1; |
| | | allVehicles.value.push({ ...form, id: newId }); |
| | | ElMessage.success("车è¾ä¿¡æ¯å·²æ°å¢"); |
| | | } |
| | | dialogVisible.value = false; |
| | | handleQuery(); |
| | | }); |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // 彿¡£ |
| | | const archiveRow = (row) => { |
| | | if (row.archived) return; |
| | | ElMessageBox.confirm( |
| | | "æ¯å¦ç¡®è®¤å°è¯¥è½¦è¾å½æ¡£ï¼å½æ¡£åä»
ä¿çæ¥è¯¢ï¼ä¸ååä¸è¿è¾ä»»å¡åé
ã", |
| | | "彿¡£æç¤º", |
| | | { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(() => { |
| | | row.archived = true; |
| | | if (row.status === "å¨ç¨") { |
| | | row.status = "é²ç½®"; |
| | | } |
| | | ElMessage.success("车è¾å·²å½æ¡£"); |
| | | handleQuery(); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const removeRow = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å é¤è¯¥è½¦è¾åºç¡ä¿¡æ¯ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | allVehicles.value = allVehicles.value.filter((v) => v.id !== row.id); |
| | | handleQuery(); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // ç¶ææ ·å¼ |
| | | const statusTagType = (status) => { |
| | | if (status === "å¨ç¨") return "success"; |
| | | if (status === "é²ç½®") return "info"; |
| | | if (status === "ç»´ä¿®") return "warning"; |
| | | return "default"; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- åå·¥æå¡åº --> |
| | | <el-card shadow="never" class="mb16"> |
| | | <div class="attendance-header"> |
| | | <div> |
| | | <div class="title">æå¡ç¾å°</div> |
| | | <div class="sub-title">æ¯æä¸é®æå¡ï¼èªå¨è®°å½ä¸ä¸çæ¶é´</div> |
| | | </div> |
| | | <div class="attendance-actions"> |
| | | <div class="time-block"> |
| | | <div class="label">å½åæ¶é´</div> |
| | | <div class="value">{{ nowTime }}</div> |
| | | </div> |
| | | <el-button type="primary" size="large" @click="handleCheckInOut"> |
| | | {{ checkInOutText }} |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <el-descriptions border :column="4" class="mt10"> |
| | | <el-descriptions-item label="åå·¥å§å"> |
| | | {{ currentUser.name }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å·¥å·"> |
| | | {{ currentUser.no }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æå±é¨é¨"> |
| | | {{ currentUser.dept }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="仿¥ç¶æ"> |
| | | <el-tag :type="todayStatusTag" size="small"> |
| | | {{ todayStatusText }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¸çæ¶é´"> |
| | | {{ todayRecord?.checkInTime || '-' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¸çæ¶é´"> |
| | | {{ todayRecord?.checkOutTime || '-' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å·¥æ¶(å°æ¶)"> |
| | | {{ todayRecord?.workHours ?? '-' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å¼å¸¸æ è®°"> |
| | | <span v-if="todayRecord?.status === 'normal'">-</span> |
| | | <el-tag v-else type="danger" size="small"> |
| | | {{ todayRecord?.statusText }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ï¼ç®¡çåè夿¥æ¥ï¼ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">é¨é¨ï¼</span> |
| | | <el-select |
| | | v-model="searchForm.dept" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | style="width: 180px" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in deptOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">æ¥æï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.date" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | clearable |
| | | /> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button icon="Download" @click="handleExport"> |
| | | 导åºè夿¥æ¥ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è夿¥æ¥è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 24em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :row-class-name="rowClassName" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="date" |
| | | label="æ¥æ" |
| | | width="120" |
| | | /> |
| | | <el-table-column |
| | | prop="dept" |
| | | label="é¨é¨" |
| | | width="140" |
| | | /> |
| | | <el-table-column |
| | | prop="name" |
| | | label="å§å" |
| | | width="120" |
| | | /> |
| | | <el-table-column |
| | | prop="no" |
| | | label="å·¥å·" |
| | | width="120" |
| | | /> |
| | | <el-table-column |
| | | prop="checkInTime" |
| | | label="ä¸çæ¶é´" |
| | | width="140" |
| | | /> |
| | | <el-table-column |
| | | prop="checkOutTime" |
| | | label="ä¸çæ¶é´" |
| | | width="140" |
| | | /> |
| | | <el-table-column |
| | | prop="workHours" |
| | | label="å·¥æ¶(å°æ¶)" |
| | | width="110" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | prop="statusText" |
| | | label="èå¤ç¶æ" |
| | | width="120" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-tag |
| | | v-if="scope.row.status === 'normal'" |
| | | type="success" |
| | | size="small" |
| | | > |
| | | æ£å¸¸ |
| | | </el-tag> |
| | | <el-tag |
| | | v-else |
| | | type="danger" |
| | | size="small" |
| | | > |
| | | {{ scope.row.statusText }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="remark" |
| | | label="夿³¨" |
| | | show-overflow-tooltip |
| | | /> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | // 模æå½åç»å½åå·¥ |
| | | const currentUser = reactive({ |
| | | id: 1, |
| | | name: "å¼ ä¸", |
| | | no: "E10001", |
| | | dept: "ç产ä¸é¨", |
| | | }); |
| | | |
| | | // é¨é¨é项 |
| | | const deptOptions = [ |
| | | { label: "ç产ä¸é¨", value: "ç产ä¸é¨" }, |
| | | { label: "ç产äºé¨", value: "ç产äºé¨" }, |
| | | { label: "设å¤ç»´æ¤é¨", value: "设å¤ç»´æ¤é¨" }, |
| | | { label: "è´¨æ£é¨", value: "è´¨æ£é¨" }, |
| | | ]; |
| | | |
| | | // 模æèå¤åå§æ°æ® |
| | | const rawAttendance = ref([ |
| | | { |
| | | id: 1, |
| | | date: "2024-12-01", |
| | | userId: 1, |
| | | name: "å¼ ä¸", |
| | | no: "E10001", |
| | | dept: "ç产ä¸é¨", |
| | | checkInTime: "08:58", |
| | | checkOutTime: "18:10", |
| | | workHours: 9.2, |
| | | status: "normal", |
| | | statusText: "æ£å¸¸", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 2, |
| | | date: "2024-12-01", |
| | | userId: 2, |
| | | name: "æå", |
| | | no: "E10002", |
| | | dept: "ç产ä¸é¨", |
| | | checkInTime: "09:15", |
| | | checkOutTime: "18:05", |
| | | workHours: 8.8, |
| | | status: "late", |
| | | statusText: "è¿å°", |
| | | remark: "å äº¤éæ¥å µè¿å°", |
| | | }, |
| | | { |
| | | id: 3, |
| | | date: "2024-12-01", |
| | | userId: 3, |
| | | name: "çäº", |
| | | no: "E20001", |
| | | dept: "设å¤ç»´æ¤é¨", |
| | | checkInTime: "08:50", |
| | | checkOutTime: "17:20", |
| | | workHours: 8.5, |
| | | status: "early", |
| | | statusText: "æ©é", |
| | | remark: "å¤åºå¤çç´§æ¥æ
é", |
| | | }, |
| | | { |
| | | id: 4, |
| | | date: "2024-12-02", |
| | | userId: 1, |
| | | name: "å¼ ä¸", |
| | | no: "E10001", |
| | | dept: "ç产ä¸é¨", |
| | | checkInTime: "08:45", |
| | | checkOutTime: "18:30", |
| | | workHours: 9.7, |
| | | status: "normal", |
| | | statusText: "æ£å¸¸", |
| | | remark: "å ç0.5å°æ¶", |
| | | }, |
| | | ]); |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | dept: "", |
| | | date: "", |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | // å½åæ¶é´å±ç¤º |
| | | const nowTime = ref(""); |
| | | let timer = null; |
| | | |
| | | const updateNowTime = () => { |
| | | const now = new Date(); |
| | | const Y = now.getFullYear(); |
| | | const M = String(now.getMonth() + 1).padStart(2, "0"); |
| | | const D = String(now.getDate()).padStart(2, "0"); |
| | | const h = String(now.getHours()).padStart(2, "0"); |
| | | const m = String(now.getMinutes()).padStart(2, "0"); |
| | | const s = String(now.getSeconds()).padStart(2, "0"); |
| | | nowTime.value = `${Y}-${M}-${D} ${h}:${m}:${s}`; |
| | | }; |
| | | |
| | | // 仿¥æ¥æ |
| | | const todayStr = computed(() => nowTime.value.slice(0, 10)); |
| | | |
| | | // 彿¥å½ååå·¥èå¤è®°å½ |
| | | const todayRecord = computed(() => |
| | | rawAttendance.value.find( |
| | | (item) => |
| | | item.userId === currentUser.id && item.date === todayStr.value |
| | | ) |
| | | ); |
| | | |
| | | // æå¡æé®ææ¬ |
| | | const checkInOutText = computed(() => { |
| | | if (!todayRecord.value || !todayRecord.value.checkInTime) { |
| | | return "ä¸çæå¡"; |
| | | } |
| | | if (!todayRecord.value.checkOutTime) { |
| | | return "ä¸çæå¡"; |
| | | } |
| | | return "仿¥å·²æå¡å®æ"; |
| | | }); |
| | | |
| | | // 仿¥ç¶æå±ç¤º |
| | | const todayStatusTag = computed(() => { |
| | | if (!todayRecord.value) return "info"; |
| | | if (todayRecord.value.status === "normal") return "success"; |
| | | return "danger"; |
| | | }); |
| | | |
| | | const todayStatusText = computed(() => { |
| | | if (!todayRecord.value) return "æªæå¡"; |
| | | return todayRecord.value.statusText || "æ£å¸¸"; |
| | | }); |
| | | |
| | | // è¡æ ·å¼ï¼å¼å¸¸é«äº® |
| | | const rowClassName = ({ row }) => { |
| | | if (row.status === "late" || row.status === "early") { |
| | | return "row-abnormal"; |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const recomputeTable = () => { |
| | | const list = rawAttendance.value.filter((item) => { |
| | | if (searchForm.dept && item.dept !== searchForm.dept) { |
| | | return false; |
| | | } |
| | | if (searchForm.date && item.date !== searchForm.date) { |
| | | return false; |
| | | } |
| | | return true; |
| | | }); |
| | | tableData.value = list; |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.dept = ""; |
| | | searchForm.date = ""; |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | // 导åºï¼æ¼ç¤ºï¼ |
| | | const handleExport = () => { |
| | | ElMessage.success("å½å为æ¼ç¤ºé¡µé¢ï¼å¯¼åºåè½æªå¯¹æ¥å®é
æ¥å£"); |
| | | }; |
| | | |
| | | // æå¡é»è¾ï¼ä»
å端模æï¼ |
| | | const handleCheckInOut = () => { |
| | | const [dateStr, timeStr] = nowTime.value.split(" "); |
| | | if (!dateStr || !timeStr) return; |
| | | |
| | | // ä¸çæå¡ |
| | | if (!todayRecord.value) { |
| | | const newId = rawAttendance.value.length |
| | | ? Math.max(...rawAttendance.value.map((i) => i.id)) + 1 |
| | | : 1; |
| | | const status = |
| | | timeStr > "09:00:00" ? "late" : "normal"; |
| | | const statusText = status === "late" ? "è¿å°" : "æ£å¸¸"; |
| | | rawAttendance.value.push({ |
| | | id: newId, |
| | | date: dateStr, |
| | | userId: currentUser.id, |
| | | name: currentUser.name, |
| | | no: currentUser.no, |
| | | dept: currentUser.dept, |
| | | checkInTime: timeStr.slice(0, 5), |
| | | checkOutTime: "", |
| | | workHours: null, |
| | | status, |
| | | statusText, |
| | | remark: "", |
| | | }); |
| | | ElMessage.success("ä¸çæå¡æå"); |
| | | } else if (!todayRecord.value.checkOutTime) { |
| | | // ä¸çæå¡ |
| | | todayRecord.value.checkOutTime = timeStr.slice(0, 5); |
| | | // ç®åæ 9:00-18:00 计ç®å·¥æ¶ |
| | | const start = todayRecord.value.checkInTime || "09:00"; |
| | | const [sh, sm] = start.split(":").map((v) => parseInt(v, 10)); |
| | | const [eh, em] = todayRecord.value.checkOutTime |
| | | .split(":") |
| | | .map((v) => parseInt(v, 10)); |
| | | const diff = (eh * 60 + em - (sh * 60 + sm)) / 60; |
| | | todayRecord.value.workHours = Number(Math.max(diff, 0).toFixed(1)); |
| | | |
| | | // æ©é夿ï¼18:00 å离å¼è§ä¸ºæ©éï¼åªç¤ºæï¼ |
| | | if (timeStr < "18:00:00") { |
| | | todayRecord.value.status = "early"; |
| | | todayRecord.value.statusText = "æ©é"; |
| | | } else if (todayRecord.value.status === "normal") { |
| | | todayRecord.value.statusText = "æ£å¸¸"; |
| | | } |
| | | ElMessage.success("ä¸çæå¡æå"); |
| | | } else { |
| | | ElMessage.info("仿¥å·²å®æä¸ä¸çæå¡"); |
| | | } |
| | | |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | updateNowTime(); |
| | | timer = setInterval(updateNowTime, 1000); |
| | | // é»è®¤å±ç¤ºå½å¤©æ°æ® |
| | | const today = new Date(); |
| | | const Y = today.getFullYear(); |
| | | const M = String(today.getMonth() + 1).padStart(2, "0"); |
| | | const D = String(today.getDate()).padStart(2, "0"); |
| | | searchForm.date = `${Y}-${M}-${D}`; |
| | | recomputeTable(); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (timer) { |
| | | clearInterval(timer); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .mb16 { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .attendance-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .attendance-header .title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .attendance-header .sub-title { |
| | | font-size: 13px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .attendance-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .time-block { |
| | | text-align: right; |
| | | } |
| | | |
| | | .time-block .label { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .time-block .value { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | ::v-deep(.row-abnormal) { |
| | | background-color: #fff5f5; |
| | | } |
| | | </style> |
| | | |
| | |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | :page="page" |
| | | @selection-change="handleSelectionChange" |
| | | height="500" |
| | | @pagination="paginationSearch" |
| | |
| | | const getList = () => { |
| | | fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | page.total = res.data.total; |
| | | }) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | |
| | | prop="entryDate" |
| | | width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" |
| | | prop="remarks" |
| | | width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | width="120" |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨Â·ï¼" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | prop="remarks"> |
| | | <el-input v-model="form.remarks" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | type="textarea" |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" |
| | | prop="remark"> |
| | | prop="purchaseLedgerFiles"> |
| | | <el-upload v-model:file-list="fileList" |
| | | :action="upload.url" |
| | | multiple |
| | |
| | | delProduct(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | closeProductDia(); |
| | | getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then( |
| | | getPurchaseById({ id: currentId.value, type: 2 }).then( |
| | | res => { |
| | | productData.value = res.productData; |
| | | } |
| | |
| | | <el-table-column label="产ååç§°" prop="productName" min-width="160" /> |
| | | <el-table-column label="è§æ ¼åç§°" prop="model" min-width="140" /> |
| | | <el-table-column label="åä½" prop="unit" width="100" /> |
| | | <el-table-column label="æ¯å¦è´¨æ£" prop="isQuality" width="100"> |
| | | <template #default="scope"> |
| | | {{scope.row.isQuality ? "æ¯" : "å¦"}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" fixed="right" width="150"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link size="small" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | <el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(scope.row)" :disabled="scope.row.isComplete">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | {{ item.model }} |
| | | <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> --> |
| | | </div> |
| | | <el-tag type="primary" class="product-tag" v-if="item.isQuality">è´¨æ£</el-tag> |
| | | </div> |
| | | <div v-else class="product-info empty">ææ äº§åä¿¡æ¯</div> |
| | | </div> |
| | | |
| | | <!-- æä½æé® --> |
| | | <div class="card-footer"> |
| | | <el-button type="primary" link size="small" @click="handleEdit(item)">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(item)">å é¤</el-button> |
| | | <el-button type="primary" link size="small" @click="handleEdit(item)" :disabled="item.isComplete">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(item)" :disabled="item.isComplete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | clearable |
| | | :disabled="true" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ¯å¦è´¨æ£" prop="isQuality"> |
| | | <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | |
| | | productName: "", |
| | | model: "", |
| | | unit: "", |
| | | isQuality: false, |
| | | }); |
| | | |
| | | const rules = { |
| | |
| | | productName: row.productName || "", |
| | | model: row.model || "", |
| | | unit: row.unit || "", |
| | | isQuality: row.isQuality, |
| | | }; |
| | | dialogVisible.value = true; |
| | | }; |
| | |
| | | productRouteId: routeId.value, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | isQuality: form.value.isQuality, |
| | | dragSort, |
| | | }) |
| | | : addOrUpdateProcessRouteItem({ |
| | | routeId: routeId.value, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | isQuality: form.value.isQuality, |
| | | dragSort, |
| | | }); |
| | | |
| | |
| | | id: form.value.id, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | isQuality: form.value.isQuality, |
| | | }) |
| | | : addOrUpdateProcessRouteItem({ |
| | | routeId: routeId.value, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | id: form.value.id, |
| | | isQuality: form.value.isQuality, |
| | | }); |
| | | |
| | | updatePromise |
| | |
| | | color: #409eff; |
| | | } |
| | | |
| | | .product-tag { |
| | | margin: 10px 0; |
| | | } |
| | | |
| | | .card-footer { |
| | | display: flex; |
| | | justify-content: space-around; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="isShow" |
| | | title="æ°å¢ç产订å" |
| | | width="800" |
| | | @close="closeModal" |
| | | > |
| | | <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> |
| | | <el-form-item |
| | | label="产ååç§°" |
| | | prop="productModelId" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: 'è¯·éæ©äº§å', |
| | | trigger: 'change', |
| | | } |
| | | ]" |
| | | > |
| | | <el-button type="primary" @click="showProductSelectDialog = true"> |
| | | {{ formState.productName ? formState.productName : 'éæ©äº§å' }} |
| | | </el-button> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="è§æ ¼" |
| | | prop="productModelName" |
| | | > |
| | | <el-input v-model="formState.productModelName" disabled /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="åä½" |
| | | prop="unit" |
| | | > |
| | | <el-input v-model="formState.unit" disabled /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å·¥èºè·¯çº¿"> |
| | | <el-select v-model="formState.routeId" |
| | | placeholder="è¯·éæ©å·¥èºè·¯çº¿" |
| | | style="width: 100%;" |
| | | :loading="bindRouteLoading"> |
| | | <el-option v-for="item in routeOptions" |
| | | :key="item.id" |
| | | :label="`${item.processRouteCode || ''}`" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="éæ±æ°é" |
| | | prop="quantity" |
| | | > |
| | | <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- 产åéæ©å¼¹çª --> |
| | | <ProductSelectDialog |
| | | v-model="showProductSelectDialog" |
| | | @confirm="handleProductSelect" |
| | | single |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="handleSubmit">确认</el-button> |
| | | <el-button @click="closeModal">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, computed, getCurrentInstance} from "vue"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | required: true, |
| | | }, |
| | | |
| | | type: { |
| | | type: String, |
| | | required: true, |
| | | default: 'qualified', |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'completed']); |
| | | |
| | | // ååºå¼æ°æ®ï¼æ¿ä»£é项å¼ç dataï¼ |
| | | const formState = ref({ |
| | | productId: undefined, |
| | | productModelId: undefined, |
| | | routeId: undefined, |
| | | productName: "", |
| | | productModelName: "", |
| | | unit: "", |
| | | quantity: 0, |
| | | }); |
| | | |
| | | const isShow = computed({ |
| | | get() { |
| | | return props.visible; |
| | | }, |
| | | set(val) { |
| | | emit('update:visible', val); |
| | | }, |
| | | }); |
| | | |
| | | const showProductSelectDialog = ref(false); |
| | | |
| | | let { proxy } = getCurrentInstance() |
| | | |
| | | const closeModal = () => { |
| | | // éç½®è¡¨åæ°æ® |
| | | formState.value = { |
| | | productId: undefined, |
| | | productModelId: undefined, |
| | | routeId: undefined, |
| | | productName: "", |
| | | productModelName: "", |
| | | quantity: '', |
| | | }; |
| | | isShow.value = false; |
| | | }; |
| | | |
| | | // 产åéæ©å¤ç |
| | | const handleProductSelect = async (products) => { |
| | | if (products && products.length > 0) { |
| | | const product = products[0]; |
| | | formState.value.productId = product.productId; |
| | | formState.value.productName = product.productName; |
| | | formState.value.productModelName = product.model; |
| | | formState.value.productModelId = product.id; |
| | | formState.value.unit = product.unit; |
| | | showProductSelectDialog.value = false; |
| | | fetchRouteOptions( product.id); |
| | | // 触å表åéªè¯æ´æ° |
| | | proxy.$refs["formRef"]?.validateField('productModelId'); |
| | | } |
| | | }; |
| | | |
| | | const routeOptions = ref([]); |
| | | const bindRouteLoading = ref(false); |
| | | const fetchRouteOptions = (productModelId) => { |
| | | formState.value.routeId = undefined; |
| | | routeOptions.value = [] |
| | | bindRouteLoading.value = true; |
| | | listProcessRoute({ productModelId: productModelId }).then(res => { |
| | | routeOptions.value = res.data || []; |
| | | }).finally(() => { |
| | | bindRouteLoading.value = false; |
| | | }) |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | // éªè¯æ¯å¦éæ©äºäº§ååè§æ ¼ |
| | | if (!formState.value.productModelId) { |
| | | proxy.$modal.msgError("è¯·éæ©äº§å"); |
| | | return; |
| | | } |
| | | if (!formState.value.productModelId) { |
| | | proxy.$modal.msgError("è¯·éæ©è§æ ¼"); |
| | | return; |
| | | } |
| | | |
| | | addProductOrder(formState.value).then(res => { |
| | | // å
³éæ¨¡ææ¡ |
| | | isShow.value = false; |
| | | // åç¥ç¶ç»ä»¶å·²å®æ |
| | | emit('completed'); |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | }) |
| | | } |
| | | }) |
| | | }; |
| | | |
| | | |
| | | defineExpose({ |
| | | closeModal, |
| | | handleSubmit, |
| | | isShow, |
| | | }); |
| | | </script> |
| | |
| | | </el-form-item> |
| | | </el-form> |
| | | <div> |
| | | <el-button type="primary" @click="isShowNewModal = true">æ°å¢</el-button> |
| | | <el-button type="danger" @click="handleDelete">å é¤</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | :page="page" |
| | | :tableLoading="tableLoading" |
| | | :row-class-name="tableRowClassName" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="pagination"> |
| | | <template #completionStatus="{ row }"> |
| | | <el-progress |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <new-product-order v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | @completed="handleQuery" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | productOrderListPage, |
| | | listProcessRoute, |
| | | bindingRoute, |
| | | listProcessBom, |
| | | listProcessBom, delProductOrder, |
| | | } from "@/api/productionManagement/productionOrder.js"; |
| | | import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js"; |
| | | import {fileDel} from "@/api/financialManagement/revenueManagement.js"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue")); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const router = useRouter(); |
| | | const isShowNewModal = ref(false); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const selectedRows = ref([]); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | |
| | | // æ·»å 表è¡ç±»åæ¹æ³ |
| | | const tableRowClassName = ({ row }) => { |
| | | const diff = row.deliveryDaysDiff; |
| | | if (!row.deliveryDate) return ''; |
| | | if (row.isFh) return ''; |
| | | |
| | | const diff = row.deliveryDaysDiff; |
| | | if (diff === 15) { |
| | | return 'yellow'; |
| | | } else if (diff === 10) { |
| | |
| | | }); |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | delProductOrder(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | |
| | | <el-form-item label="å·¥èµå®é¢" prop="salaryQuota"> |
| | | <el-input v-model="formState.salaryQuota" type="number" :step="0.001" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯å¦è´¨æ£" prop="isQuality"> |
| | | <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="formState.remark" type="textarea" /> |
| | | </el-form-item> |
| | |
| | | no: props.record.no, |
| | | remark: props.record.remark, |
| | | salaryQuota: props.record.salaryQuota, |
| | | isQuality: props.record.isQuality, |
| | | }); |
| | | |
| | | const isShow = computed({ |
| | |
| | | no: newRecord.no || '', |
| | | remark: newRecord.remark || '', |
| | | salaryQuota: newRecord.salaryQuota || '', |
| | | isQuality: props.record.isQuality, |
| | | }; |
| | | } |
| | | }, { immediate: true, deep: true }); |
| | |
| | | no: props.record.no || '', |
| | | remark: props.record.remark || '', |
| | | salaryQuota: props.record.salaryQuota || '', |
| | | isQuality: props.record.isQuality, |
| | | }; |
| | | } |
| | | }); |
| | |
| | | <el-form-item label="å·¥èµå®é¢" prop="salaryQuota"> |
| | | <el-input v-model="formState.salaryQuota" type="number" :step="0.001" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯å¦è´¨æ£" prop="isQuality"> |
| | | <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="formState.remark" type="textarea" /> |
| | | </el-form-item> |
| | |
| | | name: '', |
| | | remark: '', |
| | | salaryQuota: '', |
| | | isQuality: false, |
| | | }); |
| | | |
| | | const isShow = computed({ |
| | |
| | | label: "å·¥åºåç§°", |
| | | prop: "name", |
| | | }, |
| | | |
| | | { |
| | | label: "å·¥èµå®é¢", |
| | | prop: "salaryQuota", |
| | | }, |
| | | { |
| | | label: "æ¯å¦è´¨æ£", |
| | | prop: "isQuality", |
| | | formatData: (params) => { |
| | | return params ? "æ¯" : "å¦"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | prop: "remark", |
| | | }, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <el-form |
| | | :model="queryParams" |
| | | ref="queryForm" |
| | | :inline="true" |
| | | v-show="showSearch" |
| | | label-width="90px" |
| | | > |
| | | <el-form-item label="产ååå·" prop="productModel"> |
| | | <el-input |
| | | v-model="queryParams.productModel" |
| | | placeholder="请è¾å
¥äº§ååå·" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°" prop="customerName"> |
| | | <el-input |
| | | v-model="queryParams.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å馿¶é´" prop="feedbackRange"> |
| | | <el-date-picker |
| | | v-model="queryParams.feedbackRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤çç¶æ" prop="status"> |
| | | <el-select |
| | | v-model="queryParams.status" |
| | | placeholder="è¯·éæ©å¤çç¶æ" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" icon="Search" @click="handleQuery"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button icon="Refresh" @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- æä½åº --> |
| | | <el-row :gutter="10" class="mb8"> |
| | | <el-col :span="3"> |
| | | <el-button |
| | | type="primary" |
| | | plain |
| | | icon="Plus" |
| | | @click="handleAdd" |
| | | > |
| | | æ°å¢å®åè´¨éè®°å½ |
| | | </el-button> |
| | | </el-col> |
| | | <el-col :span="3"> |
| | | <el-button |
| | | type="success" |
| | | plain |
| | | icon="Edit" |
| | | :disabled="single" |
| | | @click="handleUpdate" |
| | | > |
| | | ä¿®æ¹ |
| | | </el-button> |
| | | </el-col> |
| | | <el-col :span="3"> |
| | | <el-button |
| | | type="danger" |
| | | plain |
| | | icon="Delete" |
| | | :disabled="multiple" |
| | | @click="handleDelete" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </el-col> |
| | | <el-col :span="3"> |
| | | <el-button |
| | | type="warning" |
| | | plain |
| | | icon="Download" |
| | | @click="handleExport" |
| | | > |
| | | å¯¼åº |
| | | </el-button> |
| | | </el-col> |
| | | <right-toolbar |
| | | v-model:showSearch="showSearch" |
| | | @queryTable="getList" |
| | | /> |
| | | </el-row> |
| | | |
| | | <!-- æ°æ®è¡¨ --> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="afterSalesList" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="åºå·" type="index" width="55" align="center" /> |
| | | <el-table-column label="éå®ååå·" prop="contractNo" width="160" /> |
| | | <el-table-column label="产åç¼å·" prop="productCode" width="140" /> |
| | | <el-table-column label="产ååå·" prop="productModel" width="140" /> |
| | | <el-table-column label="客æ·åç§°" prop="customerName" width="160" /> |
| | | <el-table-column label="èç³»æ¹å¼" prop="contact" width="140" /> |
| | | <el-table-column label="å馿¶é´" prop="feedbackTime" width="160"> |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.feedbackTime }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="é®é¢æè¿°" prop="problemDesc" show-overflow-tooltip /> |
| | | <el-table-column label="ç»´ä¿®æ
åµ" prop="repairInfo" show-overflow-tooltip /> |
| | | <el-table-column label="å¤çç»æ" prop="result" show-overflow-tooltip /> |
| | | <el-table-column label="å¤çç¶æ" prop="status" width="120" align="center"> |
| | | <template #default="scope"> |
| | | <dict-tag |
| | | :options="statusOptions" |
| | | :value="scope.row.status" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" class-name="small-padding fixed-width" width="160" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button size="small" type="text" icon="Edit" @click="handleUpdate(scope.row)"> |
| | | ä¿®æ¹ |
| | | </el-button> |
| | | <el-button size="small" type="text" icon="Delete" @click="handleDelete(scope.row)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.pageNum" |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="getList" |
| | | /> |
| | | |
| | | <!-- æ°å¢/ä¿®æ¹å¼¹çª --> |
| | | <el-dialog |
| | | :title="title" |
| | | v-model="open" |
| | | width="900px" |
| | | append-to-body |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110px" |
| | | > |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éå®ååå·" prop="contractNo"> |
| | | <el-input |
| | | v-model="form.contractNo" |
| | | placeholder="è¯·éæ©å
³èéå®ååå·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产åç¼å·" prop="productCode"> |
| | | <el-input |
| | | v-model="form.productCode" |
| | | placeholder="è¯·éæ©å
³è产åç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产ååå·" prop="productModel"> |
| | | <el-input |
| | | v-model="form.productModel" |
| | | placeholder="请è¾å
¥äº§ååå·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customerName"> |
| | | <el-input |
| | | v-model="form.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»æ¹å¼" prop="contact"> |
| | | <el-input |
| | | v-model="form.contact" |
| | | placeholder="请è¾å
¥å®¢æ·èç³»æ¹å¼" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å馿¶é´" prop="feedbackTime"> |
| | | <el-date-picker |
| | | v-model="form.feedbackTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm" |
| | | placeholder="è¯·éæ©å馿¶é´" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="客æ·åé¦é®é¢" prop="problemDesc"> |
| | | <el-input |
| | | v-model="form.problemDesc" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请详ç»è®°å½å®¢æ·åé¦é®é¢ãç°è±¡æè¿°çä¿¡æ¯" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´ä¿®æ
åµ" prop="repairInfo"> |
| | | <el-input |
| | | v-model="form.repairInfo" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="è®°å½ç»´ä¿®è¿ç¨ã使ç¨å¤ä»¶ãè¿ä¿®æ¬¡æ°ç" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¤çç»æ" prop="result"> |
| | | <el-input |
| | | v-model="form.result" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="è®°å½æç»å¤çç»æï¼å¦æ´æ¢ãéè´§ãå级ç" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¤çç¶æ" prop="status"> |
| | | <el-select |
| | | v-model="form.status" |
| | | placeholder="è¯·éæ©å¤çç¶æ" |
| | | > |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="å¯è®°å½åç»è·è¸ªæè§ãå¤çç»è®ºç" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup name="AfterSalesTraceability"> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // ç¶æåå
¸ |
| | | const statusOptions = ref([ |
| | | { label: "å¾
å¤ç", value: "0" }, |
| | | { label: "å¤çä¸", value: "1" }, |
| | | { label: "已宿", value: "2" }, |
| | | { label: "å·²å
³é", value: "3" }, |
| | | ]); |
| | | |
| | | // 模æå®åè´¨éæ°æ® |
| | | const afterSalesList = ref([ |
| | | { |
| | | id: 1, |
| | | contractNo: "SC-2024-001", |
| | | productCode: "P-10001", |
| | | productModel: "XG-500A", |
| | | customerName: "ååçµåç§ææéå
¬å¸", |
| | | contact: "å¼ å·¥ / 13800000001", |
| | | feedbackTime: "2024-12-01 10:23:00", |
| | | problemDesc: "使ç¨ä¸ä¸ªæååºç°é´ææ§æçµï¼å½±å产线稳å®è¿è¡ã", |
| | | repairInfo: "宿工ç¨å¸ä¸é¨æ£ä¿®ï¼æ´æ¢çµæºæ¨¡åå¹¶å åºæ¥çº¿ç«¯åã", |
| | | result: "æ´æ¢çµæºæ»æï¼æ¢å¤æ£å¸¸ä½¿ç¨ï¼å»ºè®®å®¢æ·å¢å UPSä¿æ¤ã", |
| | | status: "2", |
| | | remark: "åå
¥éç¹è·è¸ªå®¢æ·ï¼åç»è§å¯ä¸ä¸ªå£åº¦ã", |
| | | }, |
| | | { |
| | | id: 2, |
| | | contractNo: "SC-2024-015", |
| | | productCode: "P-10045", |
| | | productModel: "XG-500B", |
| | | customerName: "åä¸ç²¾å¯å¶é æéå
¬å¸", |
| | | contact: "æå·¥ / 13800000002", |
| | | feedbackTime: "2024-12-05 15:40:00", |
| | | problemDesc: "é¨åæ¹æ¬¡åºç°å¤å£³å®è±ï¼å®¢æ·æè¯å¤è§è´¨éä¸è¾¾æ ã", |
| | | repairInfo: "ä¸ç产ç°åºæ ¸æ¥ï¼ç¡®è®¤æ¥ææ¬è¿åå
è£
ç¯èåå¨ç£ç¢°é£é©ã", |
| | | result: "对é®é¢æ¹æ¬¡éæ°è¿å·¥ï¼è¡¥åè¯åï¼å¹¶ä¼åå
è£
鲿¤æ¹æ¡ã", |
| | | status: "1", |
| | | remark: "éè·è¸ªåç»æ¹æ¬¡æè¯çååã", |
| | | }, |
| | | { |
| | | id: 3, |
| | | contractNo: "SC-2024-032", |
| | | productCode: "P-10110", |
| | | productModel: "XG-600C", |
| | | customerName: "è¥¿åæ°è½æºç§æè¡ä»½", |
| | | contact: "çå·¥ / 13800000003", |
| | | feedbackTime: "2024-11-28 09:15:00", |
| | | problemDesc: "ç°åºè°è¯æ¶åç°æ¥å£ä¸å
¼å®¹ï¼éè¦éé
å®¢æ·æ§çç³»ç»ã", |
| | | repairInfo: "è¿ç¨ææ¯æ¯æ+ç°åºå·¥ç¨å¸èåææ¥ï¼æä¾è¿æ¸¡éé
æ¹æ¡ã", |
| | | result: "éè¿æ´æ¢æ¥æä»¶å¹¶å级åºä»¶çæ¬è§£å³ã", |
| | | status: "0", |
| | | remark: "å»ºè®®ä¸æ¬¡åååç½®æ²éæ¥å£è§æ ¼ã", |
| | | }, |
| | | ]); |
| | | |
| | | const open = ref(false); |
| | | const loading = ref(false); |
| | | const showSearch = ref(true); |
| | | const ids = ref([]); |
| | | const single = ref(true); |
| | | const multiple = ref(true); |
| | | const total = ref(3); |
| | | const title = ref("æ°å¢å®åè´¨éè®°å½"); |
| | | |
| | | const data = reactive({ |
| | | form: {}, |
| | | queryParams: { |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | productModel: null, |
| | | customerName: null, |
| | | feedbackRange: [], |
| | | status: null, |
| | | }, |
| | | rules: { |
| | | contractNo: [ |
| | | { required: true, message: "éå®ååå·ä¸è½ä¸ºç©º", trigger: "blur" }, |
| | | ], |
| | | productCode: [ |
| | | { required: true, message: "产åç¼å·ä¸è½ä¸ºç©º", trigger: "blur" }, |
| | | ], |
| | | productModel: [ |
| | | { required: true, message: "产ååå·ä¸è½ä¸ºç©º", trigger: "blur" }, |
| | | ], |
| | | customerName: [ |
| | | { required: true, message: "客æ·åç§°ä¸è½ä¸ºç©º", trigger: "blur" }, |
| | | ], |
| | | contact: [ |
| | | { required: true, message: "èç³»æ¹å¼ä¸è½ä¸ºç©º", trigger: "blur" }, |
| | | ], |
| | | feedbackTime: [ |
| | | { required: true, message: "å馿¶é´ä¸è½ä¸ºç©º", trigger: "change" }, |
| | | ], |
| | | problemDesc: [ |
| | | { required: true, message: "客æ·åé¦é®é¢ä¸è½ä¸ºç©º", trigger: "blur" }, |
| | | ], |
| | | status: [ |
| | | { required: true, message: "å¤çç¶æä¸è½ä¸ºç©º", trigger: "change" }, |
| | | ], |
| | | }, |
| | | }); |
| | | |
| | | const { queryParams, form, rules } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表ï¼ä»
å端çéï¼ä¸è°æ¥å£ï¼ |
| | | function getList() { |
| | | loading.value = true; |
| | | const list = afterSalesList.value.filter((item) => { |
| | | if ( |
| | | queryParams.value.productModel && |
| | | !item.productModel |
| | | ?.toLowerCase() |
| | | .includes(queryParams.value.productModel.toLowerCase()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if ( |
| | | queryParams.value.customerName && |
| | | !item.customerName |
| | | ?.toLowerCase() |
| | | .includes(queryParams.value.customerName.toLowerCase()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (queryParams.value.status && item.status !== queryParams.value.status) { |
| | | return false; |
| | | } |
| | | if ( |
| | | Array.isArray(queryParams.value.feedbackRange) && |
| | | queryParams.value.feedbackRange.length === 2 |
| | | ) { |
| | | const [start, end] = queryParams.value.feedbackRange; |
| | | const dateStr = item.feedbackTime?.slice(0, 10); |
| | | if (dateStr < start || dateStr > end) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }); |
| | | total.value = list.length; |
| | | // æ¤å¤æªåå页ï¼ä»
模æå
¨éå±ç¤º |
| | | afterSalesList.value = list; |
| | | loading.value = false; |
| | | } |
| | | |
| | | // åæ¶ |
| | | function cancel() { |
| | | open.value = false; |
| | | reset(); |
| | | } |
| | | |
| | | // 表åéç½® |
| | | function reset() { |
| | | form.value = { |
| | | id: null, |
| | | contractNo: null, |
| | | productCode: null, |
| | | productModel: null, |
| | | customerName: null, |
| | | contact: null, |
| | | feedbackTime: null, |
| | | problemDesc: null, |
| | | repairInfo: null, |
| | | result: null, |
| | | status: "0", |
| | | remark: null, |
| | | }; |
| | | proxy.resetForm("formRef"); |
| | | } |
| | | |
| | | // æç´¢ |
| | | function handleQuery() { |
| | | queryParams.value.pageNum = 1; |
| | | getList(); |
| | | } |
| | | |
| | | // éç½® |
| | | function resetQuery() { |
| | | proxy.resetForm("queryForm"); |
| | | queryParams.value.feedbackRange = []; |
| | | handleQuery(); |
| | | } |
| | | |
| | | // å¤é |
| | | function handleSelectionChange(selection) { |
| | | ids.value = selection.map((item) => item.id); |
| | | single.value = selection.length !== 1; |
| | | multiple.value = !selection.length; |
| | | } |
| | | |
| | | // æ°å¢ |
| | | function handleAdd() { |
| | | reset(); |
| | | open.value = true; |
| | | title.value = "æ°å¢å®åè´¨éè®°å½"; |
| | | } |
| | | |
| | | // ä¿®æ¹ |
| | | function handleUpdate(row) { |
| | | reset(); |
| | | const current = row || afterSalesList.value.find((item) => item.id === ids.value[0]); |
| | | if (current) { |
| | | form.value = { ...current }; |
| | | open.value = true; |
| | | title.value = "ä¿®æ¹å®åè´¨éè®°å½"; |
| | | } |
| | | } |
| | | |
| | | // æäº¤ |
| | | function submitForm() { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (form.value.id != null) { |
| | | // ä¿®æ¹ï¼æ¿æ¢æ¬å°æ¨¡ææ°æ® |
| | | const index = afterSalesList.value.findIndex( |
| | | (item) => item.id === form.value.id |
| | | ); |
| | | if (index !== -1) { |
| | | afterSalesList.value.splice(index, 1, { ...form.value }); |
| | | } |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | } else { |
| | | // æ°å¢ï¼æå
¥æ¬å°æ¨¡ææ°æ® |
| | | const newId = |
| | | afterSalesList.value.length > 0 |
| | | ? Math.max(...afterSalesList.value.map((i) => i.id)) + 1 |
| | | : 1; |
| | | afterSalesList.value.push({ ...form.value, id: newId }); |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | } |
| | | open.value = false; |
| | | getList(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // å é¤ |
| | | function handleDelete(row) { |
| | | const deleteIds = row?.id ? [row.id] : ids.value; |
| | | if (!deleteIds || deleteIds.length === 0) { |
| | | proxy.$modal.msgWarning("请å
éæ©è¦å é¤çè®°å½"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm( |
| | | 'æ¯å¦ç¡®è®¤å é¤éä¸çå®åè´¨éè®°å½ï¼', |
| | | "è¦å", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(() => { |
| | | // å端å 餿¬å°æ¨¡ææ°æ® |
| | | afterSalesList.value = afterSalesList.value.filter( |
| | | (item) => !deleteIds.includes(item.id) |
| | | ); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | } |
| | | |
| | | // å¯¼åº |
| | | function handleExport() { |
| | | proxy.$modal.msgSuccess("导åºåè½ä¸ºæ¼ç¤ºåè½ï¼å½åæªå¯¹æ¥å®é
å¯¼åºæ¥å£"); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .mb8 { |
| | | margin-bottom: 8px; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |
| | | |
| | |
| | | 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 = {} |
| | | // å
æ¸
空表åéªè¯ç¶æï¼é¿å
éªç |
| | | await nextTick(); |
| | | proxy.$refs.formRef?.clearValidate(); |
| | | |
| | | // å¹¶è¡å è½½åºç¡æ°æ® |
| | | const [userListsRes] = await Promise.all([ |
| | | userListNoPage(), |
| | | getProductOptions(), |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }) |
| | | ]); |
| | | userList.value = userListsRes.data; |
| | | |
| | | form.value = {} |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | getProductOptions(); |
| | | |
| | | if (operationType.value === 'edit') { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | // å
è®¾ç½®è¡¨åæ°æ®ï¼ä½ææ¶æ¸
空 testStandardIdï¼çé项å è½½å®æåå设置 |
| | | form.value = {...row, testStandardId: ''} |
| | | currentProductId.value = row.productId || 0 |
| | | // ç¼è¾æ¨¡å¼ä¸ï¼å
å è½½ææ é项ï¼ç¶åå è½½åæ°å表 |
| | | if (currentProductId.value) { |
| | | // å
å è½½ææ é项 |
| | | let params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 2 |
| | | } |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | testStandardOptions.value = res.data || []; |
| | | // ä½¿ç¨ nextTick å setTimeout ç¡®ä¿éé¡¹å·²ç»æ¸²æå° DOM |
| | | nextTick(() => { |
| | | setTimeout(() => { |
| | | // 妿ç¼è¾æ°æ®ä¸æ testStandardIdï¼å设置并å 载对åºçåæ° |
| | | if (savedTestStandardId) { |
| | | // ç¡®ä¿ç±»åå¹é
ï¼item.id å¯è½æ¯æ°åæåç¬¦ä¸²ï¼ |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) |
| | | ); |
| | | if (matchedOption) { |
| | | // ç¡®ä¿ä½¿ç¨å¹é
项ç idï¼ä¿æç±»åä¸è´ï¼ |
| | | form.value.testStandardId = matchedOption.id; |
| | | // ç¼è¾åºæ¯ä¿çå·²ææ£éªå¼ï¼ç´æ¥æåååæ°æ°æ® |
| | | getQualityInspectParamList(row.id); |
| | | } else { |
| | | // 妿æ¾ä¸å°å¹é
项ï¼å°è¯ç´æ¥ä½¿ç¨åå¼ |
| | | console.warn('æªæ¾å°å¹é
çææ é项ï¼testStandardId:', savedTestStandardId, 'å¯ç¨é项:', testStandardOptions.value); |
| | | form.value.testStandardId = savedTestStandardId; |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } else { |
| | | // å¦åä½¿ç¨æ§çé»è¾ |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | }, 100); |
| | | }); |
| | | }); |
| | | } else { |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | currentProductId.value = row.productId || 0 |
| | | // æ¸
空éªè¯ç¶æï¼é¿å
æ°æ®å è½½è¿ç¨ä¸çæ ¡éªéªç |
| | | nextTick(() => { |
| | | proxy.$refs.formRef?.clearValidate(); |
| | | }); |
| | | |
| | | // ç¼è¾æ¨¡å¼ä¸ï¼å¹¶è¡å è½½è§æ ¼åå·åææ é项 |
| | | if (currentProductId.value) { |
| | | // 设置产ååç§° |
| | | form.value.productName = findNodeById(productOptions.value, currentProductId.value); |
| | | |
| | | // å¹¶è¡å è½½è§æ ¼åå·åææ é项 |
| | | const params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 2 |
| | | }; |
| | | |
| | | Promise.all([ |
| | | modelList({ id: currentProductId.value }), |
| | | qualityInspectDetailByProductId(params) |
| | | ]).then(([modelRes, testStandardRes]) => { |
| | | // è®¾ç½®è§æ ¼åå·é项 |
| | | modelOptions.value = modelRes || []; |
| | | // å¦æè¡¨åä¸å·²æ productModelIdï¼è®¾ç½®å¯¹åºç model å unit |
| | | if (form.value.productModelId && modelOptions.value.length > 0) { |
| | | const selectedModel = modelOptions.value.find(item => item.id == form.value.productModelId); |
| | | if (selectedModel) { |
| | | form.value.model = selectedModel.model || ''; |
| | | form.value.unit = selectedModel.unit || ''; |
| | | } |
| | | } |
| | | |
| | | // è®¾ç½®ææ é项 |
| | | testStandardOptions.value = testStandardRes.data || []; |
| | | |
| | | // 设置 testStandardId å¹¶å è½½åæ°å表 |
| | | nextTick(() => { |
| | | if (savedTestStandardId) { |
| | | // ç¡®ä¿ç±»åå¹é
ï¼item.id å¯è½æ¯æ°åæåç¬¦ä¸²ï¼ |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) |
| | | ); |
| | | if (matchedOption) { |
| | | // ç¡®ä¿ä½¿ç¨å¹é
项ç idï¼ä¿æç±»åä¸è´ï¼ |
| | | form.value.testStandardId = matchedOption.id; |
| | | } else { |
| | | // 妿æ¾ä¸å°å¹é
项ï¼å°è¯ç´æ¥ä½¿ç¨åå¼ |
| | | console.warn('æªæ¾å°å¹é
çææ é项ï¼testStandardId:', savedTestStandardId, 'å¯ç¨é项:', testStandardOptions.value); |
| | | form.value.testStandardId = savedTestStandardId; |
| | | } |
| | | } |
| | | // ç¼è¾åºæ¯ä¿çå·²ææ£éªå¼ï¼ç´æ¥æåååæ°æ°æ® |
| | | getQualityInspectParamList(row.id); |
| | | }); |
| | | }); |
| | | } else { |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } |
| | | } |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç产工åå·", |
| | | prop: "workOrderNo", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "æ£éªå", |
| | | prop: "checkName", |
| | | }, |
| | |
| | | // æå¼å¼¹æ¡ |
| | | 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) => { |
| | |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç产工åå·", |
| | | prop: "workOrderNo", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "å·¥åº", |
| | | prop: "process", |
| | | width: 230 |
| | |
| | | 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) => { |
| | |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "éè´è®¢åå·", |
| | | prop: "purchaseContractNo", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ä¾åºå", |
| | | prop: "supplier", |
| | | width: 230 |
| | |
| | | import { ref, onMounted } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { productInOutAnalysis } from '@/api/viewIndex.js' |
| | | import { inputOutputAnalysis } from '@/api/viewIndex.js' |
| | | |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { |
| | |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | productInOutAnalysis({ type: 1 }) |
| | | inputOutputAnalysis() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const list = res.data |
| | | xAxis1.value[0].data = list.map((d) => d.date) |
| | | lineSeries.value[0].data = list.map((d) => Number(d.outCount) || 0) |
| | | lineSeries.value[1].data = list.map((d) => Number(d.inCount) || 0) |
| | | lineSeries.value[0].data = list.map((d) => Number(d.outputSum) || 0) |
| | | lineSeries.value[1].data = list.map((d) => Number(d.inputSum) || 0) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | import { orderCount } from '@/api/viewIndex.js' |
| | | |
| | | const statItems = ref([]) |
| | | |
| | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | |
| | | const fetchData = () => { |
| | | salesPurchaseStorageProductCount() |
| | | orderCount() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | statItems.value = res.data.map((item) => ({ |
| | |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åéå®/éè´/å¨åäº§åæ°å¤±è´¥:', err) |
| | | console.error('è·åè®¢åæ°éç»è®¡å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { productSalesAnalysis } from '@/api/viewIndex.js' |
| | | import { processOutputAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import DateTypeSwitch from '@/views/reportAnalysis/financialAnalysis/components/DateTypeSwitch.vue' |
| | |
| | | formatter: function (name) { |
| | | const item = pieObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å
}{percent|${item.rate}}{unit|%}` |
| | | return `{title|${name}}{value|${item.value}}{unit|ä»¶}{percent|${item.rate}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}å
({d}%)', |
| | | formatter: '{a} <br/>{b} : {c}ä»¶ ({d}%)', |
| | | } |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: '产åéå®éé¢åæ', |
| | | name: 'å·¥åºäº§åºåæ', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | center: ['25%', '50%'], |
| | |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | productSalesAnalysis() |
| | | processOutputAnalysis({ dateType: dateType.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const items = res.data |
| | |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产åéå®éé¢åæå¤±è´¥:', err) |
| | | console.error('è·åå·¥åºäº§åºåæå¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <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" |
| | | /> |
| | | <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: 320px" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <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 dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | const dateType = ref(1) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '140%', |
| | | } |
| | | |
| | | const grid = { left: '10%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['产é', 'å·¥èµ', 'åæ ¼ç'], |
| | | data: ['宿æ°é', 'å·¥èµéé¢', 'åæ ¼ç'], |
| | | top: '0%' |
| | | } |
| | | |
| | | // æ±ç¶å¾ï¼äº§éãå·¥èµï¼æçº¿å¾ï¼åæ ¼çï¼ç»¿è²ï¼ |
| | | // åå§å series ç»æ |
| | | const chartSeries = ref([ |
| | | { |
| | | name: '产é', |
| | | name: '宿æ°é', |
| | | type: 'bar', |
| | | barWidth: 20, |
| | | barGap: '40%', |
| | | yAxisIndex: 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: 'rgba(78, 228, 255, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | barWidth: 15, |
| | | itemStyle: { color: '#4EE4FF' }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'å·¥èµ', |
| | | name: 'å·¥èµéé¢', |
| | | type: 'bar', |
| | | barGap: '40%', |
| | | barWidth: 20, |
| | | yAxisIndex: 1, |
| | | 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: [], |
| | | barWidth: 15, |
| | | itemStyle: { color: '#00A4ED' }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'åæ ¼ç', |
| | | type: 'line', |
| | | yAxisIndex: 2, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(90, 216, 166, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(90, 216, 166, 1)' }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | yAxisIndex: 1, |
| | | smooth: true, |
| | | itemStyle: { color: '#FFD339' }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] } |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { type: 'value', name: 'æ°é/éé¢', axisLabel: { color: '#B8C8E0' }, splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } } }, |
| | | { type: 'value', name: 'åæ ¼ç(%)', max: 100, axisLabel: { formatter: '{value}%', color: '#B8C8E0' }, splitLine: { show: false } } |
| | | ] |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | let unit = 'ä»¶' |
| | | if (item.seriesName === 'åæ ¼ç') unit = '%' |
| | | else if (item.seriesName === 'å·¥èµ') unit = 'å
' |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}${unit}</div>` |
| | | let res = params[0].axisValueLabel + '<br/>' |
| | | params.forEach(item => { |
| | | const unit = item.seriesName === 'åæ ¼ç' ? '%' : (item.seriesName === 'å·¥èµéé¢' ? ' å
' : ' 个') |
| | | res += `${item.marker} ${item.seriesName}: ${item.value}${unit}<br/>` |
| | | }) |
| | | return result |
| | | }, |
| | | return res |
| | | } |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | ]) |
| | | const yAxis1 = [ |
| | | { type: 'value', name: '产é(ä»¶)', position: 'left', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { type: 'value', name: 'å·¥èµ(å
)', position: 'left', offset: 50, axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { |
| | | type: 'value', |
| | | name: 'åæ ¼ç(%)', |
| | | position: 'right', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | }, |
| | | ] |
| | | |
| | | const handleDateTypeChange = () => { |
| | | fetchData() |
| | | } |
| | | |
| | | 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)) |
| | | }) |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const items = res.data |
| | | |
| | | xAxis1.value[0].data = items.map(item => item.dateStr) |
| | | chartSeries.value[0].data = items.map(item => Number(item.numberOfCompleted) || 0) |
| | | chartSeries.value[1].data = items.map(item => Number(item.amount) || 0) |
| | | chartSeries.value[2].data = items.map(item => Number(item.passRate) || 0) |
| | | |
| | | console.log('æ´æ°åçæ°æ®:', chartSeries.value) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产éãå·¥èµä¸åæ ¼çæ°æ®å¤±è´¥:', err) |
| | | console.error('æ°æ®å 载失败', err) |
| | | }) |
| | | } |
| | | |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | box-sizing: border-box; |
| | | } |
| | | </style> |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | <div> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { qualityStatistics } from '@/api/viewIndex.js' |
| | | import { workOrderEfficiencyAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '160%', |
| | | height: '140%', |
| | | } |
| | | |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | |
| | | data: ['å¼å·¥', '宿', 'è¯åç'], |
| | | } |
| | | |
| | | // æ±ç¶å¾ï¼å¼å·¥ãå®æï¼æçº¿å¾ï¼è¯åçï¼é¢è² rgba(90, 216, 166, 1)ï¼ |
| | | const chartSeries = ref([ |
| | | { |
| | | name: 'å¼å·¥', |
| | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { type: 'value', name: 'ä»¶', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { |
| | |
| | | }, |
| | | ] |
| | | |
| | | const handleDateTypeChange = () => { |
| | | fetchData() |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | qualityStatistics() |
| | | workOrderEfficiencyAnalysis({ dateType: dateType.value }) |
| | | .then((res) => { |
| | | if (!res?.data?.item || !Array.isArray(res.data.item)) return |
| | | const items = res.data.item |
| | | if (res.code !== 200 || !Array.isArray(res.data)) return |
| | | const items = res.data |
| | | 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)) |
| | | }) |
| | | // å¼å·¥ |
| | | chartSeries.value[0].data = items.map((d) => Number(d.startQuantity) || 0) |
| | | // 宿 |
| | | chartSeries.value[1].data = items.map((d) => Number(d.finishQuantity) || 0) |
| | | // è¯åç |
| | | chartSeries.value[2].data = items.map((d) => Math.min(100, parseFloat(d.yieldRate) || 0)) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åå¼å·¥ä¸è¯åçæ°æ®å¤±è´¥:', err) |
| | | console.error('è·åå·¥åæ§è¡æçåæå¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | |
| | | gap: 20px; |
| | | } |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | | <div class="chart-header"> |
| | | <PanelHeader title="宿æ£éªæ°" /> |
| | | <div class="warn-range" @click="handleRangeClick">è¿7天</div> |
| | | </div> |
| | | <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 { completedInspectionCount } 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: '8%', right: '8%', bottom: '8%', top: '15%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | top: '5%', |
| | | left: 'center', |
| | | textStyle: { color: '#B8C8E0', fontSize: 14 }, |
| | | itemGap: 30, |
| | | data: ['åæ ¼', 'ä¸åæ ¼', 'åæ ¼ç'], |
| | | } |
| | | |
| | | // æ±ç¶å¾ï¼åæ ¼ï¼é»è²ï¼ãä¸åæ ¼ï¼ç´«è²ï¼ï¼æçº¿å¾ï¼åæ ¼çï¼èè²ï¼ |
| | | const chartSeries = ref([ |
| | | { |
| | | name: 'åæ ¼', |
| | | type: 'bar', |
| | | barWidth: 20, |
| | | barGap: '20%', |
| | | yAxisIndex: 0, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(255, 215, 0, 1)' }, // éé»è²é¡¶é¨ |
| | | { offset: 1, color: 'rgba(255, 215, 0, 0.5)' }, // åéæåºé¨ |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: 'ä¸åæ ¼', |
| | | type: 'bar', |
| | | barGap: '20%', |
| | | barWidth: 20, |
| | | yAxisIndex: 0, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, // ç´«è²é¡¶é¨ |
| | | { offset: 1, color: 'rgba(144, 97, 248, 0.6)' }, // åéæåºé¨ |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: 'åæ ¼ç', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { |
| | | color: 'rgba(78, 228, 255, 1)', // éè² |
| | | width: 2, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(78, 228, 255, 1)', |
| | | borderWidth: 2, |
| | | borderColor: '#fff', |
| | | }, |
| | | emphasis: { |
| | | focus: 'series', |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowColor: 'rgba(78, 228, 255, 0.8)', |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | backgroundColor: 'rgba(0, 0, 0, 0.8)', |
| | | borderColor: 'rgba(78, 228, 255, 0.5)', |
| | | borderWidth: 1, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | let unit = '' |
| | | if (item.seriesName === 'åæ ¼ç') { |
| | | unit = '%' |
| | | } else { |
| | | unit = 'ä»¶' |
| | | } |
| | | result += `<div style="margin: 4px 0;">${item.marker} ${item.seriesName}: ${item.value}${unit}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | axisLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { |
| | | type: 'value', |
| | | name: 'åä½: ä»¶', |
| | | nameLocation: 'start', |
| | | nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 10] }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | axisLine: { show: false }, |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { color: 'rgba(184, 200, 224, 0.2)', type: 'dashed' }, |
| | | }, |
| | | }, |
| | | { |
| | | type: 'value', |
| | | name: 'åä½: %', |
| | | nameLocation: 'end', |
| | | nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 10] }, |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12, formatter: '{value}' }, |
| | | axisLine: { show: false }, |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { color: 'rgba(184, 200, 224, 0.2)', type: 'dashed' }, |
| | | }, |
| | | }, |
| | | ] |
| | | |
| | | const fetchData = () => { |
| | | completedInspectionCount() |
| | | .then((res) => { |
| | | if (res?.code === 200 && Array.isArray(res?.data)) { |
| | | const items = res.data |
| | | // æ´æ°Xè½´æ¥ææ°æ® |
| | | xAxis1.value[0].data = items.map((d) => d.dateStr || '') |
| | | // æ´æ°åæ ¼æ°ï¼é»è²æ±ç¶å¾ï¼ |
| | | chartSeries.value[0].data = items.map((d) => Number(d.qualifiedCount) || 0) |
| | | // æ´æ°ä¸åæ ¼æ°ï¼ç´«è²æ±ç¶å¾ï¼ |
| | | chartSeries.value[1].data = items.map((d) => Number(d.unqualifiedCount) || 0) |
| | | // æ´æ°åæ ¼çï¼èè²æçº¿å¾ï¼ |
| | | chartSeries.value[2].data = items.map((d) => Number(d.passRate) || 0) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å宿æ£éªæ°æ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | const handleRangeClick = () => { |
| | | // å
ææªå¾åéæ"è¿7天"ï¼åç»æçå®çé鿱忥å
¥ |
| | | fetchData() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .chart-header { |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .warn-range { |
| | | position: absolute; |
| | | right: 0; |
| | | top: 0; |
| | | height: 32px; |
| | | padding: 0 14px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 4px; |
| | | color: #ffffff; |
| | | font-weight: 600; |
| | | font-size: 14px; |
| | | 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; |
| | | z-index: 10; |
| | | } |
| | | |
| | | .warn-range:hover { |
| | | background: linear-gradient(180deg, rgba(51, 140, 255, 1) 0%, rgba(0, 184, 237, 1) 100%); |
| | | } |
| | | |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | position: relative; |
| | | background: radial-gradient(circle at 50% 50%, rgba(78, 228, 255, 0.05) 0%, rgba(0, 0, 0, 0) 70%); |
| | | } |
| | | </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.parentProductTitle }}-{{ item.productTitle }} |
| | | </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 { nonComplianceWarning } from '@/api/viewIndex.js' |
| | | |
| | | const { proxy } = getCurrentInstance() || {} |
| | | |
| | | const warnings = ref([]) |
| | | |
| | | // å æ¯æ°æ® |
| | | const ratios = ref({ |
| | | rawMaterialRatio: 0, |
| | | semiFinishedProductRatio: 0, |
| | | finishedProductRatio: 0, |
| | | }) |
| | | |
| | | 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' |
| | | } |
| | | |
| | | // æ ¹æ®productTitleæ å°ç±»å |
| | | const mapProductTitleToType = (productTitle) => { |
| | | if (productTitle === 'åææ') return 'raw' |
| | | if (productTitle === 'åæå') return 'semi' |
| | | if (productTitle === 'æå') return 'final' |
| | | return 'raw' // é»è®¤å¼ |
| | | } |
| | | |
| | | 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(() => { |
| | | return [ |
| | | { name: 'åææ', value: ratios.value.rawMaterialRatio, itemStyle: { color: TAG_COLORS.raw } }, |
| | | { name: 'åæå', value: ratios.value.semiFinishedProductRatio, itemStyle: { color: TAG_COLORS.semi } }, |
| | | { name: 'æå', value: ratios.value.finishedProductRatio, 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 nonComplianceWarning() |
| | | if (res?.code === 200 && res?.data) { |
| | | const data = res.data |
| | | |
| | | // æ´æ°å æ¯æ°æ® |
| | | ratios.value = { |
| | | rawMaterialRatio: data.rawMaterialRatio ?? 0, |
| | | semiFinishedProductRatio: data.semiFinishedProductRatio ?? 0, |
| | | finishedProductRatio: data.finishedProductRatio ?? 0, |
| | | } |
| | | |
| | | // æ´æ°è¦åå表 |
| | | const children = data.children || [] |
| | | warnings.value = children.map((item, idx) => { |
| | | const type = mapProductTitleToType(item.parentProductTitle) |
| | | const date = item.date ? item.date.replace(/-/g, '.') : '' |
| | | return { |
| | | id: item.id ?? `warning-${idx}`, |
| | | type, |
| | | parentProductTitle: item.parentProductTitle || 'åææ', |
| | | productTitle: item.productTitle || 'åææ', |
| | | title: item.description || 'ä¸åæ ¼é¢è¦', |
| | | date, |
| | | } |
| | | }) |
| | | } |
| | | } catch (e) { |
| | | // æ¥å£å¤±è´¥åä¿æç©ºæ°æ® |
| | | console.error('è·åä¸åæ ¼é¢è¦å¤±è´¥:', e) |
| | | } |
| | | } |
| | | |
| | | const openWarning = (item) => { |
| | | const title = `ã${item.parentProductTitle}-${item.productTitle}ã${item.title}` |
| | | if (proxy?.$modal?.alert) { |
| | | proxy.$modal.alert(title) |
| | | 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: 130px 1fr auto 110px; |
| | | align-items: center; |
| | | gap: 12px; |
| | | color: #b8c8e0; |
| | | font-size: 14px; |
| | | line-height: 1.2; |
| | | padding: 8px 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 { qualityInspectionCount } 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 = () => { |
| | | qualityInspectionCount() |
| | | .then((res) => { |
| | | if (res.code === 200 && res.data) { |
| | | const data = res.data |
| | | |
| | | statItems.value = [ |
| | | { |
| | | name: 'æ»æ£éªæ°', |
| | | value: data.totalCount ?? 0, |
| | | rate: data.totalCountGrowthRate ?? 0, |
| | | }, |
| | | { |
| | | name: '仿¥å¾
宿æ°', |
| | | value: data.todayPendingCount ?? 0, |
| | | rate: data.todayPendingCountGrowthRate ?? 0, |
| | | }, |
| | | { |
| | | name: '仿¥å·²å®ææ°', |
| | | value: data.todayCompletedCount ?? 0, |
| | | rate: data.todayCompletedCountGrowthRate ?? 0, |
| | | }, |
| | | ] |
| | | } |
| | | }) |
| | | .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: 16px; |
| | | 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, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import { rawMaterialDetection, processDetection, factoryDetection } from '@/api/viewIndex.js' |
| | | |
| | | const QUALIFIED_COLOR = '#4EE4FF' |
| | | const UNQUALIFIED_COLOR = '#3378FF' |
| | | const TRACK_COLOR = 'rgba(78, 228, 255, 0.12)' |
| | | |
| | | const apiMap = { |
| | | raw: rawMaterialDetection, |
| | | process: processDetection, |
| | | final: factoryDetection, |
| | | } |
| | | |
| | | |
| | | const fetchSectionData = async (section) => { |
| | | const api = apiMap[section.key] |
| | | if (!api) return |
| | | |
| | | try { |
| | | const res = await api({ |
| | | type: section.dateType, |
| | | }) |
| | | |
| | | if (res?.code === 200 && res?.data) { |
| | | const data = res.data |
| | | section.qualifiedCount = Number(data.qualifiedCount || 0) |
| | | section.unqualifiedCount = Number(data.unqualifiedCount || 0) |
| | | section.qualifiedRate = Number(data.qualifiedRate || 0) |
| | | section.unqualifiedRate = Number(data.unqualifiedRate || 0) |
| | | } |
| | | } catch (err) { |
| | | console.error(`${section.key} æ¥å£è¯·æ±å¤±è´¥`, err) |
| | | } |
| | | } |
| | | |
| | | |
| | | const sections = reactive([ |
| | | { |
| | | key: 'raw', |
| | | title: 'åæææ£æµ', |
| | | dateType: 1, |
| | | qualifiedCount: 0, |
| | | unqualifiedCount: 0, |
| | | qualifiedRate: 0, |
| | | unqualifiedRate: 0, |
| | | }, |
| | | { |
| | | key: 'process', |
| | | title: 'è¿ç¨æ£æµ', |
| | | dateType: 1, |
| | | qualifiedCount: 0, |
| | | unqualifiedCount: 0, |
| | | qualifiedRate: 0, |
| | | unqualifiedRate: 0, |
| | | }, |
| | | { |
| | | key: 'final', |
| | | title: 'æååºåæ£æµ', |
| | | dateType: 1, |
| | | qualifiedCount: 0, |
| | | unqualifiedCount: 0, |
| | | qualifiedRate: 0, |
| | | unqualifiedRate: 0, |
| | | }, |
| | | ]) |
| | | |
| | | 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 |
| | | // åæ¢æ¥æç±»åæ¶éæ°è·åæ°æ® |
| | | fetchSectionData(section) |
| | | } |
| | | |
| | | // ç»ä»¶æè½½æ¶è·åææsectionçæ°æ® |
| | | onMounted(() => { |
| | | sections.forEach((section) => { |
| | | fetchSectionData(section) |
| | | }) |
| | | }) |
| | | </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="computedSeries" |
| | | :tooltip="landTooltip" :color="landColors" :options="pieOptions" style="height: 100%" class="land-chart" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onBeforeUnmount } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import { unqualifiedProductProcessingAnalysis } 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 å¯ææ¬æ ·å¼ |
| | | const dotRich = landColors.reduce((acc, color, idx) => { |
| | | acc[`dot${idx}`] = { |
| | | width: 8, |
| | | height: 8, |
| | | borderRadius: 8, |
| | | backgroundColor: color, |
| | | align: 'center', |
| | | } |
| | | return acc |
| | | }, {}) |
| | | |
| | | // å¾ä¾é
ç½® |
| | | const landLegend = ref({ |
| | | show: false, |
| | | icon: 'circle', |
| | | data: [], |
| | | right: '8%', |
| | | top: '40%', |
| | | orient: 'vertical', |
| | | textStyle: { |
| | | color: '#fff', |
| | | rich: { |
| | | unit: { color: '#fff', fontSize: 12, padding: [0, 10, 0, 0] }, |
| | | text: { width: 60, color: '#fff', fontSize: 12 }, |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // æç¤ºæ¡é
ç½® |
| | | const landTooltip = { |
| | | trigger: 'item', |
| | | alwaysShowContent: false, |
| | | position: function (pt) { |
| | | return [pt[0], 130] |
| | | }, |
| | | formatter: function (params) { |
| | | // ç¡®ä¿ params.data åå¨ |
| | | if (!params.data) return '' |
| | | const { name, value, rate } = params.data |
| | | return `${name}<br/>æ°éï¼${value}个<br/>å æ¯ï¼${rate}%` |
| | | }, |
| | | } |
| | | |
| | | // 使ç¨è®¡ç®å±æ§å¤ç Series |
| | | const computedSeries = computed(() => { |
| | | return [ |
| | | { |
| | | name: 'ä¸åæ ¼æ£åå¤çåæ', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | | label: { |
| | | show: true, |
| | | position: 'outside', |
| | | color: '#fff', |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20 }, |
| | | child: { fontSize: 12, color: '#fff', lineHeight: 18 }, |
| | | }, |
| | | formatter: function (params) { |
| | | if (!params.data) return '' |
| | | const dotKey = `dot${params.dataIndex % landColors.length}` |
| | | return `{${dotKey}|} {parent|${params.data.name} (${params.data.value}个)}` |
| | | }, |
| | | }, |
| | | labelLine: { |
| | | show: true, |
| | | length: 20, |
| | | lineStyle: { color: '#B8C8E0' }, |
| | | }, |
| | | data: dataList.value, |
| | | }, |
| | | { |
| | | // å
åè£
饰 |
| | | type: 'pie', |
| | | radius: ['35%', '40%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | itemStyle: { color: 'rgba(0, 127, 255, 0.25)' }, |
| | | data: [1], |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | const chartStyle = { width: '100%', height: '126%' } |
| | | const pieOptions = { backgroundColor: 'transparent' } |
| | | |
| | | // èæ¯å¤çé©å |
| | | const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | offsetX: '-51.5%', |
| | | offsetY: '-39%', |
| | | watchData: dataList |
| | | }) |
| | | |
| | | const loadData = async () => { |
| | | try { |
| | | const res = await unqualifiedProductProcessingAnalysis() |
| | | if (res && res.code === 200) { |
| | | dataList.value = (res.data || []).map((it) => ({ |
| | | name: it.name, |
| | | value: Number(it.value || 0), |
| | | rate: it.rate, |
| | | })) |
| | | landLegend.value.data = dataList.value.map((d) => d.name) |
| | | |
| | | // æ°æ®æ´æ°åå¾®è°èæ¯ |
| | | setTimeout(() => { |
| | | adjustBackgroundPosition() |
| | | }, 100) |
| | | } |
| | | } catch (e) { |
| | | console.error('è·åæ°æ®å¤±è´¥:', e) |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | .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; |
| | | 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">{{ item.rank }}</div> --> |
| | | <div style="flex: 1" class="main-panel-box-left">{{ item.productName }}</div> |
| | | <div style="flex: 3" class="main-panel-box-right"> |
| | | <!-- <div class="main-panel-box-right-title">{{ item.productName }}</div> --> |
| | | <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 { unqualifiedProductRanking } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | |
| | | const panelList = ref([]) |
| | | |
| | | const format = (percentage) => { |
| | | return `${percentage}%` |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | unqualifiedProductRanking() |
| | | .then((res) => { |
| | | if (res?.code === 200 && Array.isArray(res?.data)) { |
| | | const data = res.data |
| | | panelList.value = data.map((item, index) => { |
| | | const total = Number(item.totalCount) || 0 |
| | | const finished = Number(item.completedCount) || 0 |
| | | const passRate = Number(item.passRate) || 0 |
| | | |
| | | return { |
| | | rank: `Top${index + 1}`, |
| | | productName: item.productName || `产å${index + 1}`, |
| | | total: total.toFixed(2), |
| | | finished: finished.toFixed(2), |
| | | qualifiedRate: passRate.toFixed(2), |
| | | percentage: Math.min(100, Math.max(0, passRate)), // ç¡®ä¿ç¾åæ¯å¨0-100ä¹é´ |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åå·¥åæ§è¡æçåææ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | 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; |
| | | flex: 1; |
| | | |
| | | .main-panel-box-right-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #ffffff; |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | .main-panel-box-right-text { |
| | | font-size: 12px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding-right: 60px; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .main-panel-box-right-progress { |
| | | :deep(.el-progress__text) { |
| | | color: white !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .main-panel-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | height: 100%; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | overflow: hidden; |
| | | } |
| | | </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-table-column> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="250" |
| | | min-width="150" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | <!-- <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="openForm('edit', scope.row)">ç¼è¾</el-button> |
| | | @click="openForm('edit', scope.row)">ç¼è¾</el-button> --> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="scope.row.isRectify || scope.row.rectifyActualTime" |
| | | @click="openForm('edit2', scope.row)">æ´æ¹</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="!scope.row.rectifyActualTime" |
| | | :disabled="!scope.row.rectifyActualTime || scope.row.verifyTime" |
| | | @click="openForm('edit3', scope.row)">éªæ¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | 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> |
| | |
| | | { |
| | | name: "ç»ææç»", |
| | | type: "text", |
| | | // disabled: row => row.state !== 2, |
| | | disabled: row => row.state == 0, |
| | | clickFun: row => { |
| | | viewResultDetail(row); |
| | | }, |
| | |
| | | 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(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | |
| | | const getStatusTagType = (statusName = '') => { |
| | | const normalized = statusName.trim(); |
| | | if (!normalized) return 'info'; |
| | | return normalized === 'æªå®æå款' ? 'danger' : 'success'; |
| | | return normalized === 'æªå®æä»æ¬¾' ? 'danger' : 'success'; |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | |
| | | <el-table-column label="å½å
¥æ¥æ" prop="entryDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="ç¾è®¢æ¥æ" prop="executionDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="äº¤ä»æ¥æ" prop="deliveryDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remarks" width="200" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" min-width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">ç¼è¾</el-button> |
| | |
| | | </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> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨Â·ï¼" prop="remark"> |
| | | <el-input v-model="form.remark" placeholder="请è¾å
¥" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> |
| | | <el-form-item label="夿³¨ï¼" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请è¾å
¥" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="remark"> |
| | | <el-form-item label="éä»¶ææï¼" prop="salesLedgerFiles"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | |
| | | |
| | | // æ·»å 表è¡ç±»åæ¹æ³ |
| | | const tableRowClassName = ({ row }) => { |
| | | const diff = row.deliveryDaysDiff; |
| | | if (!row.deliveryDate) return ''; |
| | | if (row.isFh) return ''; |
| | | |
| | | const diff = row.deliveryDaysDiff; |
| | | if (diff === 15) { |
| | | return 'yellow'; |
| | | } else if (diff === 10) { |
| | |
| | | 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; |
| | |
| | | <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0"> |
| | | <el-table-column prop="product" label="产ååç§°" width="200"> |
| | | <template #default="scope"> |
| | | <el-tree-select |
| | | v-model="scope.row.productId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels($event, scope.row)" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item"> |
| | | <el-tree-select |
| | | v-model="scope.row.productId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels($event, scope.row)" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" width="150"> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-model="scope.row.specificationId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)" |
| | | > |
| | | <el-option |
| | | v-for="item in scope.row.modelOptions || []" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item"> |
| | | <el-select |
| | | v-model="scope.row.specificationId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in scope.row.modelOptions || []" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="åä½"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.unit" placeholder="åä½" /> |
| | | <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item"> |
| | | <el-input v-model="scope.row.unit" placeholder="åä½" clearable/> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> |
| | | <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="80" align="center"> |
| | |
| | | totalAmount: 0 |
| | | }) |
| | | |
| | | const rules = { |
| | | const baseRules = { |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | quotationDate: [{ required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change' }], |
| | | validDate: [{ required: true, message: 'è¯·éæ©æææ', trigger: 'change' }], |
| | | paymentMethod: [{ required: true, message: '请è¾å
¥ä»æ¬¾æ¹å¼', trigger: 'blur' }] |
| | | } |
| | | |
| | | const productRowRules = { |
| | | productId: [{ required: true, message: 'è¯·éæ©äº§ååç§°', trigger: 'change' }], |
| | | specificationId: [{ required: true, message: 'è¯·éæ©è§æ ¼åå·', trigger: 'change' }], |
| | | unit: [{ required: true, message: '请填ååä½', trigger: 'blur' }], |
| | | unitPrice: [{ required: true, message: '请填ååä»·', trigger: 'change' }] |
| | | } |
| | | const rules = computed(() => { |
| | | const r = { ...baseRules } |
| | | ;(form.products || []).forEach((_, i) => { |
| | | r[`products.${i}.productId`] = productRowRules.productId |
| | | r[`products.${i}.specificationId`] = productRowRules.specificationId |
| | | r[`products.${i}.unit`] = productRowRules.unit |
| | | r[`products.${i}.unitPrice`] = productRowRules.unitPrice |
| | | }) |
| | | return r |
| | | }) |
| | | const userList = ref([]); |
| | | const customerOption = ref([]); |
| | | |
| | |
| | | ElMessage.warning('请è³å°æ·»å ä¸ä¸ªäº§å') |
| | | return |
| | | } |
| | | |
| | | |
| | | // 审æ¹äººå¿
å¡«æ ¡éª |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | |
| | | padding: 8px 0; |
| | | } |
| | | |
| | | .product-table-form-item { |
| | | margin-bottom: 0; |
| | | :deep(.el-form-item__content) { |
| | | margin-left: 0 !important; |
| | | } |
| | | :deep(.el-form-item__label) { |
| | | width: auto; |
| | | min-width: auto; |
| | | } |
| | | } |
| | | |
| | | .approver-nodes-container { |
| | | display: flex; |
| | | flex-wrap: wrap; |