浏览代码

新增江南打码功能

695593266@qq.com 1 月之前
父节点
当前提交
ea947bbca9

+ 137 - 17
src/views/batchRecord/index.vue

@@ -300,29 +300,59 @@
         tableQuery: {
           productCode: '',
           batchNo: ''
-        }
+        },
+        // 扫码枪缓冲
+        scanBuffer: '',
+        scanLastTime: 0,
+        /** keep-alive 下避免与 created 重复请求;第二次 activated 再复位 */
+        _pageHasActivated: false
       };
     },
     computed: {},
+    mounted() {
+      window.addEventListener('keydown', this.handleScanKey, true);
+    },
+    beforeDestroy() {
+      window.removeEventListener('keydown', this.handleScanKey, true);
+    },
+    activated() {
+      if (this._pageHasActivated) {
+        this.initPage();
+      }
+      this._pageHasActivated = true;
+    },
     created() {
-      // 设置 productBody.createTimeStart 和 productBody.createTimeEnd 为前一个月时间包含今天
-      const endDate = new Date();
-      const startDate = new Date();
-      startDate.setMonth(startDate.getMonth() - 1);
-      // 设置时间为 00:00:00
-      startDate.setHours(0, 0, 0, 0);
-      this.productBody.createTimeStart = this.$util.toDateString(
-        startDate,
-        'yyyy-MM-dd HH:mm:ss'
-      );
-      this.productBody.createTimeEnd = this.$util.toDateString(
-        endDate,
-        'yyyy-MM-dd HH:mm:ss'
-      );
-
-      this.getAllProductInWorkOrder();
+      this.initPage();
     },
     methods: {
+      // 初始化页面:复位筛选条件并拉取产品列表
+      initPage() {
+        // 时间范围复位为最近一个月(含今天)
+        const endDate = new Date();
+        const startDate = new Date();
+        startDate.setMonth(startDate.getMonth() - 1);
+        startDate.setHours(0, 0, 0, 0);
+        this.productBody.param = '';
+        this.productBody.pageNum = 1;
+        this.productBody.size = 10;
+        this.productBody.createTimeStart = this.$util.toDateString(
+          startDate,
+          'yyyy-MM-dd HH:mm:ss'
+        );
+        this.productBody.createTimeEnd = this.$util.toDateString(
+          endDate,
+          'yyyy-MM-dd HH:mm:ss'
+        );
+
+        // 复位选中状态
+        this.tableQuery.productCode = '';
+        this.tableQuery.batchNo = '';
+        this.batchNos = [];
+        this.activeType = '生产工单';
+        this.activeComponentName = 'workOrderTable';
+
+        this.getAllProductInWorkOrder();
+      },
       // 刷新表格数据
       reload() {
         this.$refs.tableRef?.reload();
@@ -386,6 +416,96 @@
             'yyyy-MM-dd HH:mm:ss'
           );
         }
+      },
+      // 扫码枪监听:根据按键间隔判断是否为扫码输入
+      handleScanKey(e) {
+        const now = Date.now();
+        const interval = now - this.scanLastTime;
+        this.scanLastTime = now;
+
+        if (e.key === 'Enter') {
+          if (this.scanBuffer.length >= 3 && interval < 100) {
+            e.preventDefault();
+            e.stopPropagation();
+            const code = this.scanBuffer;
+            const target = e.target;
+            this.scanBuffer = '';
+            // 清掉首字符泄漏到输入框中的内容
+            if (
+              target &&
+              (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') &&
+              typeof target.value === 'string' &&
+              code.endsWith(target.value) === false &&
+              target.value.length > 0 &&
+              code.startsWith(target.value)
+            ) {
+              target.value = '';
+              target.dispatchEvent(new Event('input', { bubbles: true }));
+            }
+            this.processScanResult(code);
+          } else {
+            this.scanBuffer = '';
+          }
+          return;
+        }
+
+        if (e.key.length !== 1) return;
+
+        // 间隔过长视为人工输入,重置缓冲
+        if (interval > 50) {
+          this.scanBuffer = e.key;
+          return;
+        }
+
+        // 已检测到扫码连续输入,阻止字符进入输入框
+        e.preventDefault();
+        e.stopPropagation();
+        this.scanBuffer += e.key;
+      },
+      // 处理扫码结果:产品编码-批次号(按第一个 - 拆分,批次号可能包含 -)
+      async processScanResult(code) {
+        const idx = code.indexOf('-');
+        if (idx <= 0 || idx === code.length - 1) {
+          this.$message.warning(`扫码结果格式不正确:${code}`);
+          return;
+        }
+        const productCode = code.substring(0, idx);
+        const batchNo = code.substring(idx + 1);
+
+        let product = this.productList.find(
+          (p) => p.productCode === productCode
+        );
+        // 当前列表未找到则按产品编码搜索一次
+        if (!product) {
+          this.productBody.param = productCode;
+          this.productBody.pageNum = 1;
+          await this.getAllProductInWorkOrder();
+          product = this.productList.find(
+            (p) => p.productCode === productCode
+          );
+        }
+        if (!product) {
+          this.$message.warning(`未找到产品:${productCode}`);
+          return;
+        }
+
+        // 选中产品
+        this.tableQuery.productCode = product.productCode;
+
+        // 选中批次号:扫码命中时仅展示该批次号,未命中则展示该产品全部批次号
+        const allBatchNos = product.batchNos || [];
+        if (allBatchNos.includes(batchNo)) {
+          this.batchNos = [batchNo];
+          this.tableQuery.batchNo = batchNo;
+          this.$message.success(`扫码成功:${productCode} / ${batchNo}`);
+        } else {
+          this.batchNos = allBatchNos;
+          this.tableQuery.batchNo = '';
+          this.$message.warning(
+            `产品 ${productCode} 下未找到批次号:${batchNo}`
+          );
+        }
+        this.reload();
       }
     }
   };

+ 44 - 35
src/views/produce/components/encodingDialog/index.vue

@@ -3,7 +3,7 @@
     title="打码"
     :visible.sync="visible"
     v-if="visible"
-    width="28%"
+    width="40%"
     :maxable="true"
     append-to-body
   >
@@ -24,7 +24,9 @@
             <tr>
               <td class="label">产品编码</td>
               <td class="value">{{ item.productCode }}</td>
-              <td class="qrcode-cell" rowspan="4">
+              <td class="label">产品名称</td>
+              <td class="value">{{ item.productName }}</td>
+              <td class="qrcode-cell" rowspan="3">
                 <img
                   v-if="item.qrCodeUrl"
                   :src="item.qrCodeUrl"
@@ -34,19 +36,15 @@
               </td>
             </tr>
             <tr>
-              <td class="label">{{
-                sourceType === 'jobBom' ? '产品名称' : '工单号'
-              }}</td>
-              <td class="value">{{ item.productName }}</td>
+              <td class="label">零(部)件编码</td>
+              <td class="value">{{ item.partCode }}</td>
+              <td class="label">零(部)件名称</td>
+              <td class="value">{{ item.partName }}</td>
             </tr>
             <tr>
-              <td class="label">批号</td>
+              <td class="label">生产批号</td>
               <td class="value">{{ item.batchNo }}</td>
-            </tr>
-            <tr>
-              <td class="label">{{
-                sourceType === 'jobBom' ? '数量' : '本批数量'
-              }}</td>
+              <td class="label">本批数量</td>
               <td class="value">{{ item.feedQuantity }}</td>
             </tr>
           </table>
@@ -111,10 +109,14 @@
       buildBatchList() {
         const isJobBom = this.sourceType === 'jobBom';
         this.batchList = this.workOrderList.map((item) => ({
-          productCode: isJobBom
-            ? item.categoryCode || item.productCode || ''
+          productCode: item.topCategoryCode || '',
+          productName: item.topCategoryName || '',
+          partCode: isJobBom
+            ? item.categoryCode || ''
             : item.productCode || '',
-          productName: isJobBom ? item.name || '' : item.code || '',
+          partName: isJobBom
+            ? item.name || ''
+            : item.productName || '',
           batchNo: item.batchNo || '',
           feedQuantity: isJobBom
             ? (item.feedQuantity || item.quantity || item.planQuantity || '') +
@@ -128,7 +130,7 @@
               ]
                 .filter(Boolean)
                 .join('-')
-            : item.batchNo || item.categoryCode || item.code || '',
+            : [item.productCode, item.batchNo].filter(Boolean).join('-'),
           qrCodeUrl: '',
           model: isJobBom
             ? item.modelType || item.model || ''
@@ -148,7 +150,7 @@
       generateQRCodes() {
         this.batchList.forEach((item, index) => {
           const content = item.codeStr || item.productCode || String(index);
-          QRCode.toDataURL(content, { width: 160, margin: 1 })
+          QRCode.toDataURL(content, { width: 240, margin: 1 })
             .then((url) => {
               this.$set(this.batchList[index], 'qrCodeUrl', url);
             })
@@ -228,25 +230,25 @@
               <title>打码打印预览</title>
               <style>
                 @page {
-                  size: 60mm 50mm;
+                  size: 80mm 40mm;
                   margin: 0;
                 }
                 * { box-sizing: border-box; margin: 0; padding: 0; }
                 html, body {
                   font-family: 'Microsoft Yahei', Arial, sans-serif;
-                  width: 60mm;
+                  width: 80mm;
                   margin: 0;
                   padding: 0;
                 }
                 .encoding-content {
-                  width: 60mm;
+                  width: 80mm;
                 }
                 .batch-card {
-                  width: 60mm;
-                  height: 50mm;
+                  width: 80mm;
+                  height: 40mm;
                   padding: 2mm 3mm;
                   display: flex;
-                  align-items: center;
+                  align-items: stretch;
                   page-break-after: always;
                   overflow: hidden;
                 }
@@ -255,38 +257,45 @@
                 }
                 .card-table {
                   width: 100%;
+                  height: 100%;
                   border-collapse: collapse;
                   table-layout: fixed;
                 }
+                .card-table tr {
+                  height: 33.33%;
+                }
                 .card-table td {
                   border: 0.3mm solid #999;
-                  padding: 1mm 1.5mm;
-                  font-size: 8pt;
-                  line-height: 1.4;
+                  padding: 0.5mm 1mm;
+                  font-size: 6.5pt;
+                  line-height: 1.2;
                   word-break: break-all;
                 }
                 .card-table .label {
                   color: #333;
-                  width: 14mm;
-                  white-space: nowrap;
-                  font-size: 8pt;
+                  width: 13mm;
+                  font-size: 6pt;
+                  text-align: center;
+                  word-break: keep-all;
                 }
                 .card-table .value {
-                  font-size: 8pt;
+                  font-size: 6.5pt;
                   overflow: hidden;
                   text-overflow: ellipsis;
+                  text-align: center;
+                  word-break: break-all;
                 }
                 .card-table .qrcode-cell {
-                  width: 20mm;
+                  width: 24mm;
                   text-align: center;
                   vertical-align: middle;
-                  padding: 1mm;
+                  padding: 0.3mm;
                 }
-                .qr-img { width: 18mm; height: 18mm; }
+                .qr-img { width: 23mm; height: 23mm; }
                 .card-code { text-align: center; font-size: 7pt; color: #606266; margin-top: 0.5mm; }
                 .barcode-card {
-                  width: 60mm;
-                  height: 50mm;
+                  width: 80mm;
+                  height: 40mm;
                   padding: 2mm 3mm;
                   display: flex;
                   flex-direction: column;

+ 3 - 1
src/views/produce/components/jobBooking/components/batchSemiProductJobBom.vue

@@ -1388,7 +1388,9 @@
         }
       },
       handleBarcodePaste(e) {
-        const text = (e.clipboardData || window.clipboardData).getData('text').trim();
+        const text = (e.clipboardData || window.clipboardData)
+          .getData('text')
+          .trim();
         console.log('扫码粘贴事件:', text);
         if (text.length >= 3) {
           this.matchBarcode(text);

二进制
src/views/produceOrder/image.png


+ 48 - 3
src/views/produceOrder/index.vue

@@ -52,11 +52,33 @@
             >批量打印二维码</el-button
           >
 
-          <el-button type="success" @click="cardPrinting()"
+          <el-button
+            type="success"
+            @click="cardPrinting()"
+            v-if="clientEnvironmentId != 9"
             >工艺卡打印</el-button
           >
 
-          <el-button type="success" @click="originCode()">朔源码</el-button>
+          <el-button
+            type="success"
+            @click="JNPrinting()"
+            v-if="clientEnvironmentId == 9"
+            >工艺卡打印</el-button
+          >
+
+          <el-button
+            type="success"
+            @click="originCode()"
+            v-if="clientEnvironmentId != 9"
+            >朔源码</el-button
+          >
+
+          <el-button
+            type="success"
+            @click="batchCode()"
+            v-if="clientEnvironmentId == 9"
+            >朔源码</el-button
+          >
 
           <el-button v-if="tabValue == '6'" type="success" @click="batchRelease"
             >批量放行</el-button
@@ -277,6 +299,8 @@
     <printSr ref="printSrRef"></printSr>
     <printTg ref="printTgRef"></printTg>
     <printCard ref="printCardRef"></printCard>
+    <encodingDialog ref="encodingDialogRef" />
+    <printFlowCard ref="printFlowCardRef" />
 
     <createDialog ref="createRef" @success="createSuccess" />
     <unpackDialog ref="unpackRef" @success="createSuccess" />
@@ -349,6 +373,8 @@
   import { getTaskInstanceList } from '@/api/produce/job';
   import planDotLineReleaseDialog from './components/releaseDialog/planDotLineReleaseDialog.vue';
   import dotLinePopup from './components/details/dotLinePopup.vue';
+  import encodingDialog from '../produce/components/encodingDialog/index.vue';
+  import printFlowCard from './printFlowCard.vue';
 
   export default {
     mixins: [tableColumnsMixin],
@@ -370,7 +396,9 @@
       checkAdd,
       checkDetails,
       planDotLineReleaseDialog,
-      dotLinePopup
+      dotLinePopup,
+      encodingDialog,
+      printFlowCard
     },
 
     data() {
@@ -922,6 +950,7 @@
       },
       clientEnvironmentId() {
         return this.$store.state.user.info.clientEnvironmentId;
+        // return 9;
       }
     },
     watch: {
@@ -1213,6 +1242,22 @@
         }
       },
 
+      batchCode() {
+        if (!this.selection.length) {
+          return this.$message.warning('请至少选择一条工单!');
+        }
+
+        this.$refs.encodingDialogRef.open(this.selection);
+      },
+
+      JNPrinting() {
+        if (!this.selection.length) {
+          return this.$message.warning('请至少选择一条工单!');
+        }
+
+        this.$refs.printFlowCardRef.open(this.selection);
+      },
+
       getFieldModel() {
         fieldModel({ fieldModel: 't_main_category' }).then((res) => {
           const privateColumn = [

+ 381 - 0
src/views/produceOrder/printFlowCard.vue

@@ -0,0 +1,381 @@
+<template>
+  <ele-modal
+    title="工序流转卡"
+    :visible.sync="QRvisible"
+    v-if="QRvisible"
+    width="900px"
+    :maxable="true"
+  >
+    <div id="printSection" class="flow-card-print">
+      <div
+        class="flow-card-page"
+        v-for="(card, cIndex) in printList"
+        :key="cIndex"
+      >
+        <div class="flow-card-title">工&nbsp;&nbsp;序&nbsp;&nbsp;流&nbsp;&nbsp;转&nbsp;&nbsp;卡</div>
+
+        <table class="flow-card-header">
+          <tr>
+            <td class="label">产&nbsp;&nbsp;品&nbsp;&nbsp;代&nbsp;&nbsp;号</td>
+            <td class="value">{{ card.topCategoryCode }}</td>
+            <td class="label">产&nbsp;&nbsp;品&nbsp;&nbsp;名&nbsp;&nbsp;称</td>
+            <td class="value">{{ card.topCategoryName }}</td>
+          </tr>
+          <tr>
+            <td class="label">零(部)件代号</td>
+            <td class="value">{{ card.productCode }}</td>
+            <td class="label">零(部)件名称</td>
+            <td class="value">{{ card.productName }}</td>
+          </tr>
+          <tr>
+            <td class="label">产品批号(编号)</td>
+            <td class="value">{{ card.batchNo }}</td>
+            <td class="label">原材料本厂合格证号</td>
+            <td class="value">{{ card.materialFactoryCertNo }}</td>
+          </tr>
+          <tr>
+            <td class="label">原材料炉(批)号</td>
+            <td class="value">{{ card.materialFurnaceNo }}</td>
+            <td class="label">半成品合格证号</td>
+            <td class="value">{{ card.semiProductCertNo }}</td>
+          </tr>
+        </table>
+
+        <table class="flow-card-detail">
+          <thead>
+            <tr>
+              <th rowspan="2" class="col-no">工<br />序<br />号</th>
+              <th rowspan="2" class="col-name">工序名称</th>
+              <th rowspan="2" class="col-num">转<br />入<br />数</th>
+              <th rowspan="2" class="col-leader">工序负<br />责人</th>
+              <th rowspan="2" class="col-num">转<br />出<br />数</th>
+              <th colspan="2" class="col-bad">不&nbsp;&nbsp;合&nbsp;&nbsp;格&nbsp;&nbsp;数</th>
+              <th rowspan="2" class="col-date">日&nbsp;&nbsp;&nbsp;期</th>
+              <th rowspan="2" class="col-remark">备&nbsp;&nbsp;注</th>
+            </tr>
+            <tr>
+              <th class="col-bad-sub">返工<br />品数</th>
+              <th class="col-bad-sub">废品<br />数</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(row, idx) in getRows(card)" :key="idx">
+              <td>{{ row.taskNo }}</td>
+              <td>{{ row.taskName }}</td>
+              <td>{{ row.inNum }}</td>
+              <td>{{ row.leader }}</td>
+              <td>{{ row.outNum }}</td>
+              <td>{{ row.reworkNum }}</td>
+              <td>{{ row.scrapNum }}</td>
+              <td>{{ row.date }}</td>
+              <td>{{ row.remark }}</td>
+            </tr>
+          </tbody>
+        </table>
+
+        <div class="flow-card-footer">
+          <div class="footer-item">发出日期:{{ card.issueDate }}</div>
+          <div class="footer-item">收回日期:{{ card.recoverDate }}</div>
+        </div>
+      </div>
+    </div>
+
+    <div slot="footer">
+      <el-button @click="print">打印预览</el-button>
+      <el-button @click="close">关闭</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import { getTaskInstanceList } from '@/api/produce/job';
+
+  export default {
+    name: 'print-flow-card',
+    data() {
+      return {
+        QRvisible: false,
+        rowCount: 25,
+        printList: []
+      };
+    },
+    methods: {
+      async open(rows) {
+        const list = Array.isArray(rows) ? rows : rows ? [rows] : [];
+        if (!list.length) {
+          this.printList = [this.buildEmptyCard()];
+          this.QRvisible = true;
+          return;
+        }
+
+        const cards = await Promise.all(
+          list.map((row) =>
+            getTaskInstanceList(row.id)
+              .then((res) => this.buildCard(row, Array.isArray(res) ? res : []))
+              .catch(() => this.buildCard(row, []))
+          )
+        );
+
+        this.printList = cards;
+        this.QRvisible = true;
+      },
+      close() {
+        this.QRvisible = false;
+      },
+      buildEmptyCard() {
+        return {
+          topCategoryCode: '',
+          topCategoryName: '',
+          productCode: '',
+          productName: '',
+          batchNo: '',
+          materialFactoryCertNo: '',
+          materialFurnaceNo: '',
+          semiProductCertNo: '',
+          issueDate: '',
+          recoverDate: '',
+          taskList: []
+        };
+      },
+      buildCard(row, taskList) {
+        return {
+          topCategoryCode: row.topCategoryCode || '',
+          topCategoryName: row.topCategoryName || '',
+          productCode: row.productCode || '',
+          productName: row.productName || '',
+          batchNo: row.batchNo || '',
+          materialFactoryCertNo: row.materialFactoryCertNo || '',
+          materialFurnaceNo: row.materialFurnaceNo || '',
+          semiProductCertNo: row.semiProductCertNo || '',
+          issueDate: row.issueDate || '',
+          recoverDate: row.recoverDate || '',
+          taskList: taskList.map((item, idx) => ({
+            taskNo: idx + 1,
+            taskName: item.taskTypeName || item.taskName || ''
+          }))
+        };
+      },
+      getRows(card) {
+        const list = Array.isArray(card.taskList) ? card.taskList.slice() : [];
+        const rows = list.map((item, i) => ({
+          taskNo: item.taskNo || i + 1,
+          taskName: item.taskName || '',
+          inNum: item.inNum || '',
+          leader: item.leader || '',
+          outNum: item.outNum || '',
+          reworkNum: item.reworkNum || '',
+          scrapNum: item.scrapNum || '',
+          date: item.date || '',
+          remark: item.remark || ''
+        }));
+        while (rows.length < this.rowCount) {
+          rows.push({
+            taskNo: '',
+            taskName: '',
+            inNum: '',
+            leader: '',
+            outNum: '',
+            reworkNum: '',
+            scrapNum: '',
+            date: '',
+            remark: ''
+          });
+        }
+        return rows;
+      },
+      print() {
+        const printSection = document.getElementById('printSection');
+        const printWindow = window.open('', '_blank');
+        printWindow.document.open();
+        printWindow.document.write(`<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8" />
+<title>工序流转卡</title>
+<style>
+  @page { size: A4 portrait; margin: 12mm 10mm; }
+  html, body {
+    margin: 0;
+    padding: 0;
+    width: 190mm;
+    font-family: 'SimSun', '宋体', 'Microsoft YaHei', serif;
+    color: #000;
+    -webkit-print-color-adjust: exact;
+    print-color-adjust: exact;
+  }
+  .flow-card-page {
+    width: 190mm;
+    box-sizing: border-box;
+    page-break-after: always;
+  }
+  .flow-card-page:last-child { page-break-after: auto; }
+  .flow-card-title {
+    text-align: center;
+    font-size: 28px;
+    font-weight: bold;
+    letter-spacing: 16px;
+    text-decoration: underline;
+    margin: 0 0 8mm 0;
+    padding-top: 2mm;
+  }
+  table {
+    width: 190mm;
+    border-collapse: collapse;
+    table-layout: fixed;
+  }
+  .flow-card-header td {
+    border: 1px solid #000;
+    height: 11mm;
+    padding: 0 6px;
+    font-size: 16px;
+    vertical-align: middle;
+  }
+  .flow-card-header td.label {
+    width: 22%;
+    text-align: center;
+  }
+  .flow-card-header td.value { width: 28%; }
+
+  .flow-card-detail th,
+  .flow-card-detail td {
+    border: 1px solid #000;
+    text-align: center;
+    vertical-align: middle;
+    font-size: 14px;
+    padding: 1px 2px;
+    line-height: 1.3;
+    white-space: nowrap;
+  }
+  .flow-card-detail th { font-weight: bold; }
+  .flow-card-detail thead th { height: 7mm; }
+  .flow-card-detail tbody td { height: 9mm; }
+
+  .col-no { width: 7%; }
+  .col-name { width: 17%; }
+  .col-num { width: 7%; }
+  .col-leader { width: 10%; }
+  .col-bad { width: 14%; }
+  .col-bad-sub { width: 7%; }
+  .col-date { width: 13%; }
+  .col-remark { width: 11%; }
+
+  .flow-card-footer {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 6mm;
+    font-size: 16px;
+  }
+  .flow-card-footer .footer-item { width: 45%; }
+</style>
+</head>
+<body>
+${printSection.innerHTML}
+</body>
+</html>`);
+        printWindow.document.close();
+        printWindow.onload = function () {
+          printWindow.focus();
+          printWindow.print();
+        };
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .flow-card-print {
+    background: #fff;
+    color: #000;
+    font-family: 'SimSun', '宋体', 'Microsoft YaHei', serif;
+    padding: 6mm 4mm;
+
+    *,
+    *::before,
+    *::after {
+      box-sizing: border-box;
+    }
+  }
+
+  .flow-card-page {
+    width: 186mm;
+    margin: 0 auto 12mm auto;
+  }
+
+  .flow-card-title {
+    text-align: center;
+    font-size: 26pt;
+    font-weight: bold;
+    letter-spacing: 14px;
+    text-decoration: underline;
+    margin: 0 0 6mm 0;
+    padding-bottom: 2mm;
+  }
+
+  .flow-card-header,
+  .flow-card-detail {
+    width: 100%;
+    border-collapse: collapse;
+    table-layout: fixed;
+  }
+
+  .flow-card-header td {
+    border: 1px solid #000;
+    height: 11mm;
+    padding: 0 6px;
+    font-size: 13pt;
+    vertical-align: middle;
+
+    &.label {
+      width: 22%;
+      text-align: center;
+    }
+
+    &.value {
+      width: 28%;
+    }
+  }
+
+  .flow-card-detail {
+    th,
+    td {
+      border: 1px solid #000;
+      text-align: center;
+      vertical-align: middle;
+      font-size: 11pt;
+      padding: 1px 2px;
+      line-height: 1.25;
+    }
+
+    th {
+      font-weight: bold;
+    }
+
+    thead th {
+      height: 7mm;
+    }
+
+    tbody td {
+      height: 9mm;
+    }
+  }
+
+  .col-no { width: 7%; }
+  .col-name { width: 17%; }
+  .col-num { width: 7%; }
+  .col-leader { width: 10%; }
+  .col-bad { width: 14%; }
+  .col-bad-sub { width: 7%; }
+  .col-date { width: 13%; }
+  .col-remark { width: 11%; }
+
+  .flow-card-footer {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 6mm;
+    font-size: 13pt;
+
+    .footer-item {
+      width: 45%;
+    }
+  }
+</style>