123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- <template>
- <el-card class="box-card">
- <div class="header-container">
- <div class="left">
- <span class="title">生产概况</span>
- <span class="update-time" v-if="updateTime">{{ updateTime }}</span>
- </div>
- <div class="right">
- <div class="filter-group">
- <slot name="query-buttons"></slot>
- <el-button
- type="text"
- icon="el-icon-download"
- @click="handleExport"
- style="
- padding: 0;
- font-size: 16px;
- color: #909399;
- margin-left: 8px;
- "
- />
- </div>
- </div>
- </div>
- <div class="table-container">
- <el-table
- v-loading="loading"
- :data="localTableData"
- style="width: 100%"
- border
- size="small"
- :height="localTableData.length > 10 ? 450 : null"
- :header-cell-style="{ background: '#f5f7fa' }"
- >
- <el-table-column type="index" label="序号" width="50" align="center" />
- <el-table-column
- prop="goodsName"
- label="货品名称"
- width="150"
- align="center"
- sortable
- />
- <el-table-column
- prop="model"
- label="货品型号"
- width="80"
- align="center"
- sortable
- />
- <el-table-column
- prop="orderStatus"
- label="货品状态"
- width="100"
- align="center"
- sortable
- >
- <template slot-scope="scope">
- <el-tag
- :type="getStatusType(scope.row.orderStatus)"
- size="mini"
- effect="plain"
- >
- {{ scope.row.orderStatus }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column
- prop="progress"
- label="进度"
- align="center"
- width="250"
- sortable
- >
- <template slot-scope="scope">
- <div
- class="progress-wrapper"
- @mouseenter="
- showTooltip(
- $event,
- (
- (scope.row.installedQuantity / scope.row.totalQuantity) *
- 100
- ).toFixed(0)
- )
- "
- @mouseleave="hideTooltip"
- >
- <div class="progress-container">
- <div class="progress-bar">
- <div class="custom-progress">
- <div
- class="progress-inner"
- :style="{
- width:
- Math.min(
- Math.round(
- (scope.row.installedQuantity /
- scope.row.totalQuantity) *
- 100
- ),
- 100
- ) + '%',
- backgroundColor: getProgressColor(
- scope.row.orderStatus
- ),
- }"
- >
- <span
- class="progress-text"
- v-if="scope.row.installedQuantity > 0"
- >
- {{ scope.row.installedQuantity }}
- </span>
- </div>
- <span class="total-text">
- {{ scope.row.totalQuantity }}
- </span>
- </div>
- </div>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="totalQuantity" label="计划量" align="center" />
- <el-table-column
- prop="installedQuantity"
- label="已完成量"
- align="center"
- />
- <el-table-column
- prop="uninstalledQuantity"
- label="未完成量"
- align="center"
- />
- <el-table-column
- prop="yesterdayQuantity"
- label="昨日完成量"
- align="center"
- />
- <el-table-column
- prop="todayQuantity"
- label="今日完成量"
- align="center"
- />
- <el-table-column prop="rate" label="完成率" align="center" sortable>
- <template slot-scope="scope">
- {{
- (
- (scope.row.installedQuantity / scope.row.totalQuantity) *
- 100
- ).toFixed(1) + "%"
- }}
- </template>
- </el-table-column>
- </el-table>
- </div>
- </el-card>
- </template>
- <script>
- import { GetDataByName, GetDataByNames } from "@/api/common";
- import * as XLSX from "xlsx";
- export default {
- name: "ProductProductionTable",
- data() {
- return {
- loading: false,
- localTableData: [],
- tooltip: null,
- tooltipTimeout: null,
- };
- },
- props: {
- updateTime: {
- type: String,
- required: true,
- },
- },
- created() {
- this.initData();
- },
- methods: {
- getStatusType(status) {
- switch (status) {
- case "生产完成":
- return "success";
- case "待生产":
- return "warning";
- case "未生产":
- return "info";
- case "生产中":
- return "primary";
- case "已关单":
- return "danger";
- case "未开始":
- return "info";
- default:
- return "info";
- }
- },
- getProgressColor(status) {
- switch (status) {
- case "生产完成":
- return "#67C23A";
- case "待生产":
- return "#E6A23C";
- case "生产中":
- return "#409EFF";
- case "已关单":
- return "#F56C6C";
- case "未生产":
- return "#909399";
- default:
- return "#909399";
- }
- },
- async initData() {
- try {
- await this.fetchTableData();
- } catch (error) {
- console.error("初始化数据失败:", error);
- }
- },
- async fetchTableData() {
- try {
- this.loading = true;
- await new Promise((resolve) => setTimeout(resolve, 1000));
- const send_data = {
- name: "getProductionSummaryByGoods",
- parammaps: {},
- };
- GetDataByName(send_data)
- .then((response) => {
- this.localTableData = response.data.list || [];
- })
- .catch((error) => {
- console.error("获取数据失败:", error);
- });
- this.$emit("data-loaded");
- } catch (error) {
- console.error("获取生产列表失败:", error);
- this.$message.error("获取生产列表失败");
- } finally {
- this.loading = false;
- }
- },
- showTooltip(event, percentage) {
- // 清除之前的tooltip和定时器
- this.hideTooltip();
- // 创建新的tooltip
- this.tooltip = document.createElement("div");
- this.tooltip.className = "progress-tooltip";
- this.tooltip.textContent = percentage + "%";
- document.body.appendChild(this.tooltip);
- const rect = event.target.getBoundingClientRect();
- const tooltipRect = this.tooltip.getBoundingClientRect();
- this.tooltip.style.left =
- rect.left + rect.width / 2 - tooltipRect.width / 2 + "px";
- this.tooltip.style.top = rect.top - tooltipRect.height - 8 + "px";
- // 使用 requestAnimationFrame 确保动画流畅
- requestAnimationFrame(() => {
- if (this.tooltip) {
- this.tooltip.classList.add("show");
- }
- });
- // 添加安全清理机制
- this.tooltipTimeout = setTimeout(() => {
- this.hideTooltip();
- }, 3000); // 3秒后自动清理
- },
- hideTooltip() {
- // 清除定时器
- if (this.tooltipTimeout) {
- clearTimeout(this.tooltipTimeout);
- this.tooltipTimeout = null;
- }
- // 清除tooltip
- if (this.tooltip) {
- this.tooltip.classList.remove("show");
- const tooltipToRemove = this.tooltip;
- setTimeout(() => {
- if (tooltipToRemove && tooltipToRemove.parentNode) {
- tooltipToRemove.parentNode.removeChild(tooltipToRemove);
- }
- }, 200);
- this.tooltip = null;
- }
- },
- // 获取格式化的日期时间
- getFormattedDateTime() {
- const now = new Date();
- const year = now.getFullYear();
- const month = String(now.getMonth() + 1).padStart(2, "0");
- const day = String(now.getDate()).padStart(2, "0");
- const hours = String(now.getHours()).padStart(2, "0");
- const minutes = String(now.getMinutes()).padStart(2, "0");
- const seconds = String(now.getSeconds()).padStart(2, "0");
- return `${year}${month}${day}${hours}${minutes}${seconds}`;
- },
- // 导出功能
- async handleExport() {
- try {
- if (this.localTableData.length === 0) {
- this.$message.warning("暂无数据可导出");
- return;
- }
- // 定义表头
- const headers = [
- "序号",
- "货品名称",
- "货品型号",
- "货品状态",
- "进度",
- "计划量",
- "已完成量",
- "未完成量",
- "昨日完成量",
- "今日完成量",
- "完成率",
- ];
- // 格式化数据
- const rows = this.localTableData.map((item, index) => [
- index + 1,
- item.goodsName || "",
- item.model || "",
- item.orderStatus || "",
- `${((item.installedQuantity / item.totalQuantity) * 100).toFixed(
- 2
- )}%`,
- item.totalQuantity || 0,
- item.installedQuantity || 0,
- item.uninstalledQuantity || 0,
- item.yesterdayQuantity || 0,
- item.todayQuantity || 0,
- `${((item.installedQuantity / item.totalQuantity) * 100).toFixed(
- 1
- )}%`,
- ]);
- // 创建工作簿
- const wb = XLSX.utils.book_new();
- // 组合表头和数据
- const wsData = [headers, ...rows];
- // 创建工作表
- const ws = XLSX.utils.aoa_to_sheet(wsData);
- // 设置列宽
- const colWidths = [
- { wch: 8 }, // 序号
- { wch: 30 }, // 货品名称
- { wch: 15 }, // 货品型号
- { wch: 12 }, // 货品状态
- { wch: 10 }, // 进度
- { wch: 10 }, // 计划量
- { wch: 12 }, // 已完成量
- { wch: 12 }, // 未完成量
- { wch: 12 }, // 昨日完成量
- { wch: 12 }, // 今日完成量
- { wch: 10 }, // 完成率
- ];
- ws["!cols"] = colWidths;
- // 添加工作表到工作簿
- XLSX.utils.book_append_sheet(wb, ws, "生产概况(按货品)");
- // 生成文件名
- const fileName = `生产概况(按货品)_${this.getFormattedDateTime()}.xlsx`;
- // 导出文件
- XLSX.writeFile(wb, fileName);
- this.$message.success("导出成功");
- } catch (error) {
- console.error("导出失败:", error);
- this.$message.error("导出失败");
- }
- },
- },
- beforeDestroy() {
- // 组件销毁时清理所有相关资源
- this.hideTooltip();
- if (this.tooltipTimeout) {
- clearTimeout(this.tooltipTimeout);
- }
- },
- };
- </script>
- <style lang="scss" scoped>
- .box-card {
- border-radius: 8px;
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
- transition: all 0.3s ease;
- min-height: 200px;
- height: auto;
- .header-container {
- margin-bottom: 15px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- .left {
- display: flex;
- align-items: center;
- min-width: 200px;
- .title {
- font-size: 13px;
- color: #333;
- }
- .update-time {
- margin-left: 8px;
- font-size: 13px;
- color: #666;
- }
- }
- .right {
- flex: 1;
- display: flex;
- justify-content: flex-start;
- padding-left: 40px;
- .filter-group {
- display: flex;
- align-items: center;
- }
- }
- }
- .el-card__header {
- padding: 16px 20px;
- border-bottom: 1px solid #ebeef5;
- }
- :deep(.el-card__body) {
- padding: 16px;
- transition: all 0.3s ease;
- }
- }
- .table-container {
- position: relative;
- padding-bottom: 0;
- }
- .progress-wrapper {
- padding: 6px 0;
- width: 100%;
- height: 100%;
- cursor: pointer;
- }
- .progress-container {
- display: flex;
- align-items: center;
- justify-content: center;
- .progress-bar {
- width: 100%;
- padding: 0 20px;
- .custom-progress {
- width: 100%;
- height: 12px;
- background-color: #ebeef5;
- border-radius: 100px;
- overflow: hidden;
- position: relative;
- .progress-inner {
- height: 100%;
- transition: all 0.3s ease;
- border-radius: 100px;
- position: relative;
- .progress-text {
- position: absolute;
- right: 6px;
- top: 50%;
- transform: translateY(-50%);
- color: #fff;
- font-size: 10px;
- line-height: 1;
- text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
- white-space: nowrap;
- }
- }
- .total-text {
- position: absolute;
- right: 6px;
- top: 50%;
- transform: translateY(-50%);
- color: #606266;
- font-size: 10px;
- line-height: 1;
- white-space: nowrap;
- }
- }
- }
- }
- :deep(.el-table) {
- font-size: 12px;
- .el-table__header-wrapper {
- position: sticky;
- top: 0;
- z-index: 1;
- }
- .el-table__header th {
- padding: 8px 0;
- background-color: #f5f7fa;
- }
- .el-table__body td {
- padding: 8px 0;
- }
- .el-table__body tr:hover > td {
- background-color: #f5f7fa !important;
- }
- }
- :deep(.el-table__body-wrapper) {
- overflow-y: auto;
- &::-webkit-scrollbar {
- width: 6px;
- height: 6px;
- }
- &::-webkit-scrollbar-thumb {
- border-radius: 3px;
- background: #c0c4cc;
- }
- &::-webkit-scrollbar-track {
- border-radius: 3px;
- background: #f5f7fa;
- }
- }
- </style>
- <style lang="scss">
- .progress-tooltip {
- position: fixed;
- background: rgba(0, 0, 0, 0.8);
- color: #fff;
- padding: 4px 8px;
- border-radius: 4px;
- font-size: 12px;
- pointer-events: none;
- z-index: 9999;
- opacity: 0;
- transform: translateY(5px);
- transition: all 0.2s ease;
- &::after {
- content: "";
- position: absolute;
- bottom: -4px;
- left: 50%;
- transform: translateX(-50%);
- border-left: 4px solid transparent;
- border-right: 4px solid transparent;
- border-top: 4px solid rgba(0, 0, 0, 0.8);
- }
- &.show {
- opacity: 1;
- transform: translateY(0);
- }
- }
- </style>
|