Browse Source

技术人员花名册和技术人员月平均工资增加打印功能

ljb 11 months ago
parent
commit
d38848b578

+ 510 - 0
src/components/print-wide-table/index.vue

@@ -0,0 +1,510 @@
+<template>
+  <div class="wide-table-container">
+    <!-- 操作按钮区域 -->
+    <!-- <div class="controls" v-if="showControls">
+      <el-button size="small" type="primary" icon="el-icon-printer" @click="printTable">打印表格</el-button>
+      <button @click="togglePreview" class="preview-btn">{{ isPreviewing ? '关闭预览' : '打印预览' }}</button>
+      <label class="orientation-toggle">
+        <input type="checkbox" v-model="landscape"> 横向打印
+      </label>
+    </div> -->
+    
+    <!-- 打印预览区域 -->
+    <div class="preview-container" v-if="isPreviewing">
+      <div class="preview-content" :style="previewStyle">
+        <div class="print-header">
+          <h2>{{ printTitle }}</h2>
+          <div class="print-meta">打印时间: {{ new Date().toLocaleString() }}</div>
+        </div>
+        <div class="table-wrapper" ref="tableWrapper">
+          <table class="wide-table" ref="wideTable">
+            <thead>
+              <tr>
+                <th v-for="(col, index) in columns" :key="index" 
+                    :style="getColumnStyle(col)"
+                    :class="{ 'fixed-column': col.fixed }">
+                  {{ col.label }}
+                </th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(row, rowIndex) in paginatedData" :key="rowIndex">
+                <td v-for="(col, colIndex) in columns" :key="colIndex" 
+                    :style="getColumnStyle(col)"
+                    :class="{
+                      'fixed-column': col.fixed,
+                      'text-center': col.align === 'center',
+                      'text-right': col.align === 'right'
+                    }">
+                  <template v-if="col.formatter">
+                    {{ col.formatter(row[col.prop], row) }}
+                  </template>
+                  <template v-else>
+                    {{ row[col.prop] }}
+                  </template>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+        <div class="page-footer">
+          第 {{ currentPage }} 页,共 {{ totalPages }} 页
+        </div>
+      </div>
+      
+      <div class="preview-controls">
+        <button @click="prevPage" :disabled="currentPage === 1">上一页</button>
+        <span>第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span>
+        <button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
+      </div>
+    </div>
+    
+    <!-- 实际表格渲染区域(用于打印) -->
+    <div v-show="false" class="print-source">
+      <div v-for="page in totalPages" :key="page" class="print-page" :style="printPageStyle">
+        <div class="print-header">
+          <h2>{{ printTitle }}</h2>
+          <div class="print-meta">打印时间: {{ new Date().toLocaleString() }}</div>
+        </div>
+        <table class="wide-table">
+          <thead>
+            <tr>
+              <th v-for="(col, index) in columns" :key="index" 
+                  :style="getColumnStyle(col)"
+                  :class="{ 'fixed-column': col.fixed }">
+                {{ col.label }}
+              </th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(row, rowIndex) in getPageData(page)" :key="rowIndex">
+              <td v-for="(col, colIndex) in columns" :key="colIndex" 
+                  :style="getColumnStyle(col)"
+                  :class="{
+                    'fixed-column': col.fixed,
+                    'text-center': col.align === 'center',
+                    'text-right': col.align === 'right'
+                  }">
+                <template v-if="col.formatter">
+                  {{ col.formatter(row[col.prop], row) }}
+                </template>
+                <template v-else>
+                  {{ row[col.prop] }}
+                </template>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+        <div class="page-footer">
+          第 {{ page }} 页,共 {{ totalPages }} 页
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'WideTablePrinter',
+  props: {
+    columns: {
+      type: Array,
+      required: true,
+      validator: value => value.every(col => col.prop && col.label)
+    },
+    data: {
+      type: Array,
+      required: true
+    },
+    printTitle: {
+      type: String,
+      default: '宽形表格打印'
+    },
+    showControls: {
+      type: Boolean,
+      default: true
+    },
+    rowsPerPage: {
+      type: Number,
+      default: 30
+    },
+    defaultLandscape: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      landscape: this.defaultLandscape,
+      isPreviewing: false,
+      currentPage: 1,
+      columnWidths: {},
+      tableWidth: 0
+    }
+  },
+  computed: {
+    totalPages() {
+      return Math.ceil(this.data.length / this.rowsPerPage)
+    },
+    paginatedData() {
+      const start = (this.currentPage - 1) * this.rowsPerPage
+      const end = start + this.rowsPerPage
+      return this.data.slice(start, end)
+    },
+    previewStyle() {
+      return {
+        width: this.landscape ? 'calc(297mm - 40px)' : 'calc(210mm - 40px)',
+        height: this.landscape ? 'calc(210mm - 40px)' : 'calc(297mm - 40px)',
+        transform: `scale(${this.getPreviewScale()})`
+      }
+    },
+    printPageStyle() {
+      return {
+        size: this.landscape ? 'landscape' : 'portrait',
+        width: this.landscape ? '100%' : '100%',
+        height: this.landscape ? '210mm' : '297mm'
+      }
+    }
+  },
+  mounted() {
+    this.calculateColumnWidths()
+    window.addEventListener('resize', this.calculateColumnWidths)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.calculateColumnWidths)
+  },
+  methods: {
+    getColumnStyle(col) {
+      const style = {}
+      if (this.columnWidths[col.prop]) {
+        style.width = `${this.columnWidths[col.prop]}px`
+        style.minWidth = `${this.columnWidths[col.prop]}px`
+      } else if (col.width) {
+        style.width = typeof col.width === 'number' ? `${col.width}px` : col.width
+      }
+      if (col.minWidth) {
+        style.minWidth = typeof col.minWidth === 'number' ? `${col.minWidth}px` : col.minWidth
+      }
+      if (col.align === 'center') style.textAlign = 'center'
+      if (col.align === 'right') style.textAlign = 'right'
+      return style
+    },
+    calculateColumnWidths() {
+      if (!this.$refs.wideTable || !this.isPreviewing) return
+      
+      const table = this.$refs.wideTable
+      const wrapper = this.$refs.tableWrapper
+      const availableWidth = wrapper.clientWidth - 20 // 减去滚动条和边距
+      
+      // 计算内容宽度
+      let contentWidth = 0
+      const cols = table.querySelectorAll('th')
+      cols.forEach(th => {
+        contentWidth += th.scrollWidth
+      })
+      
+      // 如果内容宽度小于可用宽度,按比例扩展
+      if (contentWidth < availableWidth) {
+        const scale = availableWidth / contentWidth
+        cols.forEach(th => {
+          const colKey = this.columns[th.cellIndex].key
+          this.$set(this.columnWidths, colKey, th.scrollWidth * scale)
+        })
+      } else {
+        // 否则使用实际宽度
+        cols.forEach(th => {
+          const colKey = this.columns[th.cellIndex].key
+          this.$set(this.columnWidths, colKey, th.scrollWidth)
+        })
+      }
+      
+      this.tableWidth = contentWidth > availableWidth ? contentWidth : availableWidth
+    },
+    getPageData(page) {
+      const start = (page - 1) * this.rowsPerPage
+      const end = start + this.rowsPerPage
+      return this.data.slice(start, end)
+    },
+    getPreviewScale() {
+      if (!this.$refs.tableWrapper) return 1
+      
+      const containerWidth = this.$refs.tableWrapper.clientWidth - 20
+      const containerHeight = this.$refs.tableWrapper.clientHeight - 100
+      
+      const pageWidth = this.landscape ? 297 : 210 // mm
+      const pageHeight = this.landscape ? 210 : 297 // mm
+      
+      // 转换为像素 (1mm ≈ 3.78px)
+      const pageWidthPx = pageWidth * 3.78
+      const pageHeightPx = pageHeight * 3.78
+      
+      const widthScale = containerWidth / pageWidthPx
+      const heightScale = containerHeight / pageHeightPx
+      
+      return Math.min(widthScale, heightScale, 1)
+    },
+    printTable() {
+      const printWindow = window.open('', '_blank')
+      
+      printWindow.document.open()
+      printWindow.document.write(`
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <title>${this.printTitle}</title>
+            <meta charset="UTF-8">
+            <style>
+              body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
+              .print-page {
+                page-break-after: always;
+                width: ${this.landscape ? '297mm' : '210mm'};
+                height: ${this.landscape ? '210mm' : '297mm'};
+                padding: 10mm;
+                box-sizing: border-box;
+                margin: 0 auto;
+              }
+              .print-page:last-child {
+                page-break-after: auto;
+              }
+              .print-header {
+                text-align: center;
+                margin-bottom: 10px;
+              }
+              .print-header h2 {
+                margin: 0 0 5px 0;
+                font-size: 18pt;
+              }
+              .print-meta {
+                font-size: 10pt;
+                color: #666;
+                margin-bottom: 15px;
+              }
+              .wide-table {
+                width: 100%;
+                border-collapse: collapse;
+                font-size: 10pt;
+                table-layout: fixed;
+              }
+              .wide-table th, .wide-table td {
+                border: 1px solid #ddd;
+                padding: 5px 8px;
+                word-break: break-word;
+                overflow-wrap: break-word;
+              }
+              .wide-table th {
+                background-color: #f5f5f5;
+                font-weight: bold;
+                text-align: center;
+              }
+              .wide-table .fixed-column {
+                position: sticky;
+                left: 0;
+                background-color: white;
+                z-index: 1;
+              }
+              .text-center { text-align: center; }
+              .text-right { text-align: right; }
+              .page-footer {
+                margin-top: 10px;
+                font-size: 10pt;
+                text-align: center;
+                color: #666;
+              }
+              @page {
+                size: ${this.landscape ? 'A4 landscape' : 'A4 portrait'};
+                margin: 10mm;
+              }
+            </style>
+          </head>
+          <body>
+            ${document.querySelector('.print-source').innerHTML}
+          </body>
+        </html>
+      `)
+      printWindow.document.close()
+      
+      setTimeout(() => {
+        printWindow.print()
+        printWindow.close()
+      }, 300)
+    },
+    togglePreview() {
+      this.isPreviewing = !this.isPreviewing
+      if (this.isPreviewing) {
+        this.$nextTick(() => {
+          this.calculateColumnWidths()
+        })
+      }
+    },
+    prevPage() {
+      if (this.currentPage > 1) {
+        this.currentPage--
+      }
+    },
+    nextPage() {
+      if (this.currentPage < this.totalPages) {
+        this.currentPage++
+      }
+    }
+  },
+  watch: {
+    isPreviewing(val) {
+      if (val) {
+        this.$nextTick(() => {
+          this.calculateColumnWidths()
+        })
+      }
+    },
+    landscape() {
+      this.$nextTick(() => {
+        this.calculateColumnWidths()
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.wide-table-container {
+  width: 100%;
+  /* margin: 0 auto; */
+  /* padding-left: -260px; */
+}
+
+.controls {
+  margin-bottom: 15px;
+  display: flex;
+  gap: 10px;
+  align-items: center;
+  flex-wrap: wrap;
+}
+
+.print-btn, .preview-btn {
+  padding: 8px 16px;
+  background-color: #409eff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.print-btn:hover, .preview-btn:hover {
+  background-color: #66b1ff;
+}
+
+.orientation-toggle {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  font-size: 14px;
+}
+
+.preview-container {
+  width: 100%;
+  margin-top: 15px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.preview-content {
+  background-color: white;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+  margin-bottom: 15px;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20px;
+  box-sizing: border-box;
+}
+
+.print-header {
+  text-align: center;
+  margin-bottom: 15px;
+  width: 100%;
+}
+
+.print-header h2 {
+  margin: 0 0 5px 0;
+  font-size: 18px;
+}
+
+.print-meta {
+  font-size: 12px;
+  color: #666;
+}
+
+.table-wrapper {
+  width: 100%;
+  overflow-x: auto;
+  flex-grow: 1;
+  margin-bottom: 15px;
+}
+
+.wide-table {
+  width: 100%;
+  border-collapse: collapse;
+  font-size: 12px;
+}
+
+.wide-table th, .wide-table td {
+  border: 1px solid #ddd;
+  padding: 8px 12px;
+}
+
+.wide-table th {
+  background-color: #f5f5f5;
+  font-weight: bold;
+  text-align: left;
+}
+
+.wide-table .fixed-column {
+  position: sticky;
+  left: 0;
+  background-color: white;
+  z-index: 1;
+  box-shadow: 1px 0 5px rgba(0, 0, 0, 0.1);
+}
+
+.text-center {
+  text-align: center;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.page-footer {
+  font-size: 12px;
+  text-align: center;
+  color: #666;
+  width: 100%;
+}
+
+.preview-controls {
+  display: flex;
+  gap: 15px;
+  align-items: center;
+}
+
+.preview-controls button {
+  padding: 5px 10px;
+  background-color: #f5f5f5;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.preview-controls button:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.print-source {
+  position: absolute;
+  left: -9999px;
+  visibility: hidden;
+}
+</style>

+ 1 - 0
src/option/techPerson/cyPersonSumList.js

@@ -21,6 +21,7 @@ export default {
       span: 12,
       search: true,
       align: 'center',
+      minWidth: 160,
       showOverflowTooltip: true,
     },
     {

+ 33 - 0
src/views/basic-resource/comp-basic-info/technician-month-avg-sales.vue

@@ -1,6 +1,7 @@
 <template>
   <basic-container>
     <avue-crud
+      ref="crud"
       v-bind="bindVal"
       v-on="onEvent"
       v-model="form"
@@ -44,6 +45,15 @@
         >
           读取上年度工资明细
         </el-button>
+
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-printer"
+          @click="printTable"
+        >
+          打印
+        </el-button>
         <div>
           <year-month-select v-model="params.yearAndMonth" :showMonth="false"></year-month-select>
         </div>
@@ -57,6 +67,16 @@
     </avue-crud>
 
     <upload-excel-dialog ref="uploadExcelDialog" :uploadAfter="uploadAfter"/>
+
+
+    <WideTablePrinter
+      ref="printWideTable"
+      :columns="wideTableColumns"
+      :data="data"
+      :print-title="printTitle"
+      :rows-per-page="30"
+      :default-landscape="true"
+    />
   </basic-container>
 </template>
 
@@ -70,6 +90,8 @@ import moment from "moment";
 import {getToken} from "@/util/auth";
 import {downloadXls} from "@/util/util";
 import Decimal from "decimal.js";
+import WideTablePrinter from '@/components/print-wide-table'
+
 
 
 export default window.$crudCommon({
@@ -77,6 +99,7 @@ export default window.$crudCommon({
     YearMonthSelect,
     UpdatedText,
     UploadExcelDialog,
+    WideTablePrinter
   },
   data() {
     return {
@@ -88,6 +111,9 @@ export default window.$crudCommon({
       watchUpdateKey: ["averageMonthlySalary", "pensionInsurance", "medicalInsurance", "unemploymentInsurance", "injuryInsurance", "maternityInsurance", "providentFund"],
       beforeUpdatedData: {},
       updatedFormData: {},
+
+      wideTableColumns: [],
+      printTitle: "",
     };
   },
   watch: {
@@ -190,6 +216,13 @@ export default window.$crudCommon({
         });
       });
     },
+    printTable() {
+      this.wideTableColumns = this.$refs.crud.columnOption.filter(item => !item.hide);
+      this.printTitle = `技术人员花名册${this.params.yearAndMonth}`;
+      this.$nextTick(() => {
+        this.$refs.printWideTable.printTable();
+      })
+    },
   },
 }, {
   // 模块路径

+ 33 - 1
src/views/basic-resource/comp-basic-info/technician-roster.vue

@@ -47,7 +47,18 @@
         >
           读取上个月人员名单
         </el-button>
-        <div>
+
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-printer"
+          @click="printTable"
+        >
+          打印
+        </el-button>
+
+        
+        <div style="display: flex; align-items: center;">
           <year-month-select v-model="params.yearAndMonth"></year-month-select>
         </div>
       </template>
@@ -55,6 +66,15 @@
     </avue-crud>
 
     <upload-excel-dialog ref="uploadExcelDialog" :uploadAfter="uploadAfter"/>
+
+    <WideTablePrinter
+      ref="printWideTable"
+      :columns="wideTableColumns"
+      :data="data"
+      :print-title="printTitle"
+      :rows-per-page="30"
+      :default-landscape="true"
+    />
   </basic-container>
 </template>
 
@@ -66,12 +86,14 @@ import UploadExcelDialog from "@/components/upload-excel-dialog";
 import moment from "moment";
 import {getToken} from "@/util/auth";
 import {downloadXls} from "@/util/util";
+import WideTablePrinter from '@/components/print-wide-table'
 
 
 export default window.$crudCommon({
   components: {
     YearMonthSelect,
     UploadExcelDialog,
+    WideTablePrinter
   },
   data() {
     return {
@@ -79,6 +101,9 @@ export default window.$crudCommon({
         yearAndMonth: moment(new Date()).format('YYYYMM'),
       },
       isSelAnnual: false, // 是否选择全年
+
+      wideTableColumns: [],
+      printTitle: "",
     };
   },
   watch: {
@@ -169,6 +194,13 @@ export default window.$crudCommon({
         });
       });
     },
+    printTable() {
+      this.wideTableColumns = this.$refs.crud.columnOption.filter(item => !item.hide);
+      this.printTitle = `技术人员花名册${this.params.yearAndMonth}`;
+      this.$nextTick(() => {
+        this.$refs.printWideTable.printTable();
+      })
+    },
   },
 }, {
   // 模块路径