Merge remote-tracking branch 'origin/dev_New' into dev_New
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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-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> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | | |