Преглед на файлове

feat(sb): 新增设备健康状态管理功能

- 在设备信息表单中添加健康状态下拉选择字段
- 在设备详情页展示健康状态标签,支持颜色标识
- 在设备列表表格中增加健康状态列展示
- 定义设备健康状态字典类型及对应颜色映射
- 优化设备二维码标签打印布局,支持网格排列
- 修复设备选择时cph字段取值逻辑,优先使用positionName
dazhaxie преди 2 седмици
родител
ревизия
15a31c2912

+ 33 - 0
src/api/check/checkjob.js

@@ -191,6 +191,39 @@ export function downloadCheckJobPdf(parameter) {
 }
 
 /**
+ * 批量下载保养记录 PDF(后端打包 zip 返回)
+ * parameter: 与列表查询条件一致(时间段/设备/设备类型/部门等)
+ * @param parameter :
+ * @returns {*}
+ */
+export function batchDownloadCheckJobPdf(parameter) {
+  return axios({
+    url: '/check/jobs/batch-download-pdf?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    },
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 统计批量下载范围内的已完成保养记录数量(下载前条数预览)
+ * parameter: 与批量下载一致(时间段/部门/类型/状态等)
+ * @param parameter :
+ * @returns {*}
+ */
+export function countCheckJobDownloadPdf(parameter) {
+  return axios({
+    url: '/check/jobs/count-download-pdf?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+/**
  * update func
  * parameter: { }
  * @param parameter

+ 15 - 0
src/api/upms/user.js

@@ -87,6 +87,21 @@ export function queryDeptChildrenRoleUser (parameter) {
   })
 }
 
+/**
+ * 报修通知班长候选人:全部班长 + 当前用户班组下的班长(默认勾选)
+ * 返回 { leaders: [], defaultIds: [] }
+ * @returns {AxiosPromise}
+ */
+export function queryRepairTeamLeaders () {
+  return axios({
+    url: '/upms/users/team-leaders',
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
 export function queryProjectRole (parameter) {
   return axios({
     url: `/upms/users/projectRole?${stringify(parameter)}`,

+ 1 - 0
src/utils/dict.js

@@ -85,6 +85,7 @@ DictCache.TYPE = {
   SB_COLOR: 'SB_COLOR', // 设备颜色
   SB_MEASURE_RESULT: 'SB_MEASURE_RESULT', // 检定结论
   SB_INFO_STATUS: 'SB_INFO_STATUS', // 设备状态
+  SB_HEALTH_STATUS: 'SB_HEALTH_STATUS', // 设备健康状态(0-5:0离线停用 1危急故障 2差 3一般 4良好 5优秀)
   SPARE_WARN_STATUS: 'SPARE_WARN_STATUS', // 备件库存不足预警状态
   SB_MEASURE_STATUS: 'SB_MEASURE_STATUS', // 检定设备状态
   SBTYPE_SOURCETYPE: 'SBTYPE_SOURCETYPE', // 设备来源方式

+ 12 - 1
src/views/check/checkjob/CheckJob.vue

@@ -91,6 +91,9 @@
         <a-button style="margin-left: 8px"
           v-if="($auth('check-spot-jobs-export') || $auth('check-polling-jobs-export'))" type="primary" icon="download"
           @click="doExport">导出</a-button>
+        <a-button style="margin-left: 8px"
+          v-if="status === 3 && ($auth('check-spot-jobs-export') || $auth('check-polling-jobs-export'))"
+          type="primary" icon="file-pdf" @click="doBatchDownloadPdf">批量下载PDF</a-button>
         <!-- 修改导入按钮:由父页面通过 hide-import-update 控制是否隐藏(仅保养任务/保养记录/延期任务三个页面传 true) -->
         <a-button v-if="!hideImportUpdate" style="margin-left:8px;" type="primary" @click="doImportForUpdate">
           <a-icon type="upload" />
@@ -184,6 +187,7 @@
     <import-form-update ref="importModalUpdate" @ok="handleOk" />
     <finish-form ref="finishModal" @ok="handleOk" />
     <annual-plan-modal ref="annualPlanModal" :check-type="checkType" />
+    <batch-download-modal ref="batchDownloadModal" :check-type="checkType" :filter="filter" :status="status" />
   </a-card>
 </template>
 
@@ -208,6 +212,7 @@ import DictCache from '@/utils/dict'
 import ImportFormUpdate from './modules/ImportFormUpdate'
 import FinishForm from './modules/FinishForm'
 import AnnualPlanModal from './modules/AnnualPlanModal'
+import BatchDownloadModal from './modules/BatchDownloadModal'
 import locale from 'ant-design-vue/es/date-picker/locale/zh_CN'
 export default {
   name: 'CheckJobList',
@@ -218,7 +223,8 @@ export default {
     ImportFormUpdate,
     Detail,
     FinishForm,
-    AnnualPlanModal
+    AnnualPlanModal,
+    BatchDownloadModal
   },
   props: {
     /**
@@ -767,6 +773,11 @@ export default {
       })
     },
 
+    doBatchDownloadPdf() {
+      // 打开批量下载弹窗:由用户选择时间段与部门,预览条数后再确认下载
+      this.$refs.batchDownloadModal.base()
+    },
+
     doImportForUpdate() {
       this.$refs.importModalUpdate.base(null, null)
     }

+ 2 - 1
src/views/check/checkjob/modules/BaseForm.vue

@@ -392,7 +392,8 @@ export default {
       fetchSbInfo({ id: this.sbId }).then(res => {
         this.visible = false
         const modal = this.$refs.baseModal
-        modal.base({ sbId: this.sbId, sbName: res.data.name, sbCph: res.data.cph })
+        // 使用位置绑定的是设备位置树 title(positionName),cph 仅在报修回写后才有值,这里优先取 positionName,兜底 cph 兼容老数据
+        modal.base({ sbId: this.sbId, sbName: res.data.name, sbCph: res.data.positionName || res.data.cph })
       })
     },
     handleOk () {

+ 2 - 1
src/views/check/checkjob/modules/FinishForm.vue

@@ -215,7 +215,8 @@ export default {
       fetchSbInfo({ id: this.sbId }).then((res) => {
         this.visible = false
         const modal = this.$refs.baseModal
-        modal.base({ sbId: this.sbId, sbName: res.data.name, sbCph: res.data.cph })
+        // 使用位置绑定的是设备位置树 title(positionName),cph 仅在报修回写后才有值,这里优先取 positionName,兜底 cph 兼容老数据
+        modal.base({ sbId: this.sbId, sbName: res.data.name, sbCph: res.data.positionName || res.data.cph })
       })
     },
     handleOk() {

+ 17 - 1
src/views/check/checkstandard/CheckStandard.vue

@@ -54,6 +54,11 @@
                 </a-select>
               </a-form-item>
             </a-col>
+            <a-col :md="6" :sm="24" v-if="checkType === 2">
+              <a-form-item label="执行人/班组">
+                <a-input v-model="queryParam.checkUserName" placeholder="请输入执行人/班组" />
+              </a-form-item>
+            </a-col>
             <a-col :md="6 || 24" :sm="24">
               <span class="table-page-search-submitButtons">
                 <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button>
@@ -113,7 +118,7 @@
       </a-space>
 
       <s-table ref="table" size="default" rowKey="id" :columns="columns"
-        :scroll="{ x: 1500, y: BaseTool.Constant.scrollY }" :data="loadData" :alert="options.alert"
+        :scroll="{ x: checkType === 2 ? 1650 : 1500, y: BaseTool.Constant.scrollY }" :data="loadData" :alert="options.alert"
         :rowSelection="options.rowSelection" showPagination="auto">
         <span slot="enable" slot-scope="text">
           <badge :status="DictCache.COLOR.YES_NO[text]" :text="enableMap[text]" />
@@ -214,6 +219,17 @@ export default {
           checked: true,
           width: '150px',
         },
+        ...(this.checkType === 2
+          ? [
+            {
+              title: '创建人',
+              dataIndex: 'createdUserName',
+              checked: true,
+              width: '120px',
+              customRender: (text) => text || '-',
+            },
+          ]
+          : []),
         ,
         /* {
           title: '标准编码',

+ 37 - 1
src/views/repair/application-form/modules/BaseForm.vue

@@ -109,6 +109,16 @@
           </a-form-item>
         </row-item>
         <row-item>
+          <a-form-item label="通知班长" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
+            <a-select mode="multiple" allowClear :maxTagCount="1" placeholder="请选择通知班长(可多选,非必填)"
+              v-decorator="['notifyTeamLeaders']">
+              <a-select-option v-for="leader in teamLeaderList" :key="leader.userId" :value="leader.userId"
+                :label="leader.realName">{{ leader.realName }}
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+        </row-item>
+        <row-item>
           <a-form-item label="报修图片" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
             <a-upload :action="uploadUrl" :multiple="true" list-type="picture"
               :file-list="this.defaultApplicationFileList" @change="handleApplicationFileChange" accept="image/*"
@@ -169,7 +179,7 @@ import BaseTool from '../../../../utils/tool'
 import { addRepairApplicationForm, updateRepairApplicationForm } from '@/api/repair/application-form'
 import PartInfoSelectModal from '@/views/part/info/modules/PartInfoSelectModal'
 import SbInfoSelectModal from '@/views/sb/info/modules/SbInfoSelectModal'
-import { queryUser } from '@/api/upms/user'
+import { queryUser, queryRepairTeamLeaders } from '@/api/upms/user'
 import { uploadUrl } from '@/api/upms/file'
 import Vue from 'vue'
 import { ACCESS_TOKEN } from '@/store/mutation-types'
@@ -196,6 +206,9 @@ export default {
       statusMap: {},
       userInfo: this.$store.getters.userInfo,
       userList: [],
+      // 通知班长下拉:全部班长候选人 + 当前用户班组下的班长(默认勾选)
+      teamLeaderList: [],
+      defaultLeaderIds: [],
       uploadUrl: uploadUrl,
       defaultRepairFileList: [],
       defaultApplicationFileList: [],
@@ -223,6 +236,7 @@ export default {
     this.needStopMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.YES_NO)
     this.categoryMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_APPLICATION_FORM_CATEGORY)
     this.getUsers()
+    this.getTeamLeaders()
   },
   methods: {
     base(record) {
@@ -233,6 +247,7 @@ export default {
       // 如果是空标识添加
       if (this.BaseTool.Object.isBlank(record)) {
         this.modalTitle = '添加'
+        this.setDefaultTeamLeaders()
         if (!this.BaseTool.Object.isBlank(record) && !this.BaseTool.Object.isBlank(record.sbId)) {
           const {
             form: { setFieldsValue },
@@ -245,6 +260,7 @@ export default {
       }
       if (this.BaseTool.Object.isBlank(record.id)) {
         this.modalTitle = '添加'
+        this.setDefaultTeamLeaders()
         if (!this.BaseTool.Object.isBlank(record.sbId)) {
           const {
             form: { setFieldsValue },
@@ -256,6 +272,10 @@ export default {
         return
       }
       this.modalTitle = '编辑'
+      // 通知班长回显:逗号分隔字符串转数组
+      record.notifyTeamLeaders = this.BaseTool.Object.isBlank(record.notifyTeamLeaders)
+        ? []
+        : record.notifyTeamLeaders.split(',')
       this.applicationFileList = record.applicationFileList
       this.repairFileList = record.repairFileList
       this.checkFileList = record.checkFileList
@@ -296,6 +316,7 @@ export default {
               'partName',
               'remark',
               'repairErrorTypeId',
+              'notifyTeamLeaders',
               'deptId'
             ])
           )
@@ -307,6 +328,19 @@ export default {
         this.userList = res.data
       })
     },
+    getTeamLeaders() {
+      queryRepairTeamLeaders().then((res) => {
+        const data = res.data || {}
+        this.teamLeaderList = data.leaders || []
+        this.defaultLeaderIds = data.defaultIds || []
+      })
+    },
+    // 默认带出当前用户班组下的班长,找不到则空着
+    setDefaultTeamLeaders() {
+      this.$nextTick(() => {
+        this.form.setFieldsValue({ notifyTeamLeaders: this.defaultLeaderIds })
+      })
+    },
     save() {
       const {
         form: { validateFieldsAndScroll },
@@ -327,6 +361,8 @@ export default {
         values.repairFileList = this.repairFileList
         values.applicationFileList = this.applicationFileList
         values.checkFileList = this.checkFileList
+        // 通知班长:多选数组转逗号分隔字符串
+        values.notifyTeamLeaders = (values.notifyTeamLeaders || []).join(',')
         if (this.BaseTool.String.isBlank(values.id)) {
           addRepairApplicationForm(values)
             .then(() => {

+ 14 - 0
src/views/sb/info/SbInfo.vue

@@ -192,6 +192,9 @@
             <span slot="status" slot-scope="text">
               <badge :status="DictCache.COLOR.SB_INFO_STATUS[text]" :text="statusMap[text]" />
             </span>
+            <span slot="healthStatus" slot-scope="text">
+              <a-tag v-if="text != null" :color="healthColorMap[text]">{{ healthStatusMap[text] }}</a-tag>
+            </span>
           </s-table>
         </div>
       </a-col>
@@ -353,6 +356,9 @@ export default {
       confirmLoading: false,
       useTypeMap: {},
       statusMap: {},
+      healthStatusMap: {},
+      // 设备健康状态标签颜色:0灰 1红 2橙 3黄 4浅绿 5绿
+      healthColorMap: { 0: '', 1: 'red', 2: 'orange', 3: 'yellow', 4: 'lime', 5: 'green' },
       expandedKeys: [],
       selectedKeys: [],
       sourceTypeMap: {},
@@ -549,6 +555,13 @@ export default {
           }
         }, */
         {
+          title: '设备健康状态',
+          checked: true,
+          dataIndex: 'healthStatus',
+          width: 110,
+          scopedSlots: { customRender: 'healthStatus' }
+        },
+        {
           title: '状态',
           checked: true,
           dataIndex: 'status',
@@ -599,6 +612,7 @@ export default {
     this.depreciationTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_DEPRECIATIONTYPE)
     this.levelMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_LEVEL)
     this.statusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_INFO_STATUS)
+    this.healthStatusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_HEALTH_STATUS)
     this.unitMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_UNIT)
     this.sourceTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBTYPE_SOURCETYPE)
     this.useTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_USE_TYPE)

+ 12 - 0
src/views/sb/info/modules/BaseForm.vue

@@ -102,6 +102,15 @@
             </a-select>
           </a-form-item>
         </a-col>
+        <a-col :lg="12" :md="24" :sm="24">
+          <a-form-item label="设备健康状态" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
+            <a-select v-decorator="['healthStatus']" placeholder="请选择" allowClear>
+              <a-select-option v-for="(label, value) in healthStatusMap" :key="value" :label="label"
+                :value="parseInt(value)">{{ label }}
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+        </a-col>
       </a-row>
       <a-row class="form-row" :gutter="BaseTool.Constant.row.gutter">
         <a-col :lg="12" :md="24" :sm="24">
@@ -642,6 +651,7 @@ export default {
       colorMap: {},
       useTypeMap: {},
       statusMap: {},
+      healthStatusMap: {},
       isChildMap: {},
       isShowMap: {},
       isFinancingMap: {},
@@ -680,6 +690,7 @@ export default {
     this.colorMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_COLOR)
     this.rlTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.RANLIAO_TYPE)
     this.statusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_INFO_STATUS)
+    this.healthStatusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_HEALTH_STATUS)
     this.unitMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_UNIT)
     this.sourceTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBTYPE_SOURCETYPE)
     this.useTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_USE_TYPE)
@@ -830,6 +841,7 @@ export default {
           // 'checkFile',
           // 'sbImage',
           'status',
+          'healthStatus',
           'remark',
           'zzh',
           'zz',

+ 8 - 0
src/views/sb/info/modules/Detail.vue

@@ -58,6 +58,10 @@
         <detail-list-item term="状态">
           <badge :status="DictCache.COLOR.SB_INFO_STATUS[model.status]" :text="statusMap[model.status]"></badge>
         </detail-list-item>
+        <detail-list-item term="设备健康状态">
+          <a-tag v-if="model.healthStatus != null" :color="healthColorMap[model.healthStatus]">{{
+            BaseTool.Object.getField(healthStatusMap, model.healthStatus) }}</a-tag>
+        </detail-list-item>
         <detail-list-item term="备注">{{ model.remark }}</detail-list-item>
       </detail-list>
       <title-divider width="120px" title="自定义参数"></title-divider>
@@ -323,6 +327,9 @@ export default {
       firmSupplier: {},
       firmProducer: {},
       statusMap: {},
+      healthStatusMap: {},
+      // 设备健康状态标签颜色:0灰 1红 2橙 3黄 4浅绿 5绿
+      healthColorMap: { 0: '', 1: 'red', 2: 'orange', 3: 'yellow', 4: 'lime', 5: 'green' },
       useTypeMap: {},
       levelMap: {},
       unitMap: {},
@@ -336,6 +343,7 @@ export default {
     this.levelMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_LEVEL)
     this.firmProducerLevelMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.FIRM_PRODUCER_LEVEL)
     this.statusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_INFO_STATUS)
+    this.healthStatusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_HEALTH_STATUS)
     this.unitMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_UNIT)
     this.colorMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SB_COLOR)
     this.rlTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.RANLIAO_TYPE)

+ 120 - 148
src/views/sb/info/modules/PrintInSbInfoBatch.vue

@@ -11,10 +11,21 @@
     </a-row>
     <a-row>
       <a-col :span="4">
-        标签纸宽度(黑色背景)
+        每行列数
       </a-col>
       <a-col :span="12">
-        <a-slider v-model="labelWidth" :min="1" :max="1000" />
+        <a-slider v-model="columnsPerRow" :min="1" :max="6" :marks="{1:'1',2:'2',3:'3',4:'4',5:'5',6:'6'}" />
+      </a-col>
+      <a-col :span="2">
+        <a-input-number v-model="columnsPerRow" :min="1" :max="6" style="marginLeft: 8px" />
+      </a-col>
+    </a-row>
+    <a-row>
+      <a-col :span="4">
+        页面总宽度
+      </a-col>
+      <a-col :span="12">
+        <a-slider v-model="labelWidth" :min="200" :max="1000" />
       </a-col>
       <a-col :span="2">
         <a-input-number v-model="labelWidth" style="marginLeft: 8px" />
@@ -25,7 +36,7 @@
         标签表格宽度
       </a-col>
       <a-col :span="12">
-        <a-slider v-model="labelContentWidth" :min="1" :max="1000" />
+        <a-slider v-model="labelContentWidth" :min="80" :max="500" />
       </a-col>
       <a-col :span="2">
         <a-input-number v-model="labelContentWidth" suffix="px" style="marginLeft: 8px" />
@@ -36,7 +47,7 @@
         标签表格高度
       </a-col>
       <a-col :span="12">
-        <a-slider v-model="labelContentHeight" :min="1" :max="1000" />
+        <a-slider v-model="labelContentHeight" :min="40" :max="300" />
       </a-col>
       <a-col :span="2">
         <a-input-number v-model="labelContentHeight" suffix="px" style="marginLeft: 8px" />
@@ -47,34 +58,36 @@
         二维码宽度
       </a-col>
       <a-col :span="12">
-        <a-slider v-model="imgWidth" :min="1" :max="1000" />
+        <a-slider v-model="imgWidth" :min="20" :max="200" />
       </a-col>
       <a-col :span="2">
         <a-input-number v-model="imgWidth" suffix="px" style="marginLeft: 8px" />
       </a-col>
     </a-row>
-    <div class="container" :style="labelWidthProgress" id="print-container-batch">
-      <table class="gridtable list" :style="labelContentWidthProgress" :id="'printDiv' + record.id" v-for="record in sbInfoList" :key="record.id">
-        <tbody>
-          <tr>
-            <td class="text-center">名称</td>
-            <td class="text-center">{{ record.name }}</td>
-            <td rowspan="4" style="padding: 0 !important;" class="text-center"><img :style="imageProgress" :src="record.qrCode"></img></td>
-          </tr>
-          <tr>
-            <td class="text-center">编号</td>
-            <td class="text-center">{{ record.no }}</td>
-          </tr>
-          <tr>
-            <td class="text-center">等级</td>
-            <td class="text-center">{{ BaseTool.Object.getField(levelMap,record.level) }}</td>
-          </tr>
-          <tr>
-            <td class="text-center" width='17%'>维护人</td>
-            <td class="text-center">{{ record.repairUserName }}</td>
-          </tr>
-        </tbody>
-      </table>
+    <div :style="labelWidthProgress" id="print-container-batch">
+      <div class="label-cell" v-for="record in sbInfoList" :key="record.id">
+        <table class="gridtable list" :style="labelContentWidthProgress" :id="'printDiv' + record.id">
+          <tbody>
+            <tr>
+              <td class="text-center">名称</td>
+              <td class="text-center">{{ record.name }}</td>
+              <td rowspan="4" style="padding: 0 !important;" class="text-center"><img :style="imageProgress" :src="record.qrCode"></img></td>
+            </tr>
+            <tr>
+              <td class="text-center">编号</td>
+              <td class="text-center">{{ record.no }}</td>
+            </tr>
+            <tr>
+              <td class="text-center">等级</td>
+              <td class="text-center">{{ BaseTool.Object.getField(levelMap,record.level) }}</td>
+            </tr>
+            <tr>
+              <td class="text-center" width='17%'>维护人</td>
+              <td class="text-center">{{ record.repairUserName }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
     </div>
   </div>
 </template>
@@ -94,35 +107,38 @@ export default {
       count: 0,
       levelMap: {},
       user: this.$store.getters.userInfo,
-      labelWidth: 200,
-      labelContentWidth: 100,
-      labelContentHeight: 100,
-      imgWidth: 50,
+      columnsPerRow: 2,
+      labelWidth: 750,
+      labelContentWidth: 340,
+      labelContentHeight: 120,
+      imgWidth: 80,
       configMap: {}
       // 下拉框map
     }
   },
   computed: {
     labelWidthProgress () {
-      const style = {}
-      style.width = this.labelWidth + 'px'
-      style.backgroundColor = 'black'
-      style.textAlign = 'center'
-      return style
+      return {
+        width: this.labelWidth + 'px',
+        display: 'grid',
+        gridTemplateColumns: `repeat(${this.columnsPerRow}, 1fr)`,
+        gap: '6px',
+        padding: '6px',
+        backgroundColor: '#f0f0f0'
+      }
     },
     labelContentWidthProgress () {
-      const style = {}
-      style.width = this.labelContentWidth + 'px'
-      style.height = this.labelContentHeight + 'px'
-      style.backgroundColor = 'white'
-      style.textAlign = 'center'
-      style.margin = '4px auto'
-      return style
+      return {
+        width: '100%',
+        height: this.labelContentHeight + 'px',
+        backgroundColor: 'white',
+        textAlign: 'center'
+      }
     },
     imageProgress () {
-      const style = {}
-      style.width = this.imgWidth + 'px'
-      return style
+      return {
+        width: this.imgWidth + 'px'
+      }
     }
   },
   props: {},
@@ -132,10 +148,18 @@ export default {
     this.levelMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.SBINFO_LEVEL)
     configData().then(res => {
       this.configMap = res.data['config:open:value']
-      this.labelWidth = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_WIDTH])
-      this.labelContentWidth = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_CONTENT_WIDTH])
-      this.labelContentHeight = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_CONTENT_HEIGHT])
-      this.imgWidth = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_IMG_WIDTH])
+      const savedWidth = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_WIDTH])
+      const savedContentWidth = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_CONTENT_WIDTH])
+      const savedContentHeight = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_CONTENT_HEIGHT])
+      const savedImgWidth = parseInt(this.configMap[this.DictCache.TYPE.SB_QR_CODE_LABEL_IMG_WIDTH])
+      if (savedWidth) this.labelWidth = savedWidth
+      if (savedContentWidth) this.labelContentWidth = savedContentWidth
+      if (savedContentHeight) this.labelContentHeight = savedContentHeight
+      if (savedImgWidth) this.imgWidth = savedImgWidth
+      // 尝试读取列数配置,如果没有则保持默认
+      if (this.configMap['SB_QR_CODE_LABEL_COLUMNS']) {
+        this.columnsPerRow = parseInt(this.configMap['SB_QR_CODE_LABEL_COLUMNS']) || 2
+      }
     })
   },
   methods: {
@@ -164,6 +188,7 @@ export default {
       parameter.push({ code: this.DictCache.TYPE.SB_QR_CODE_LABEL_CONTENT_WIDTH, content: this.labelContentWidth })
       parameter.push({ code: this.DictCache.TYPE.SB_QR_CODE_LABEL_CONTENT_HEIGHT, content: this.labelContentHeight })
       parameter.push({ code: this.DictCache.TYPE.SB_QR_CODE_LABEL_IMG_WIDTH, content: this.imgWidth })
+      parameter.push({ code: 'SB_QR_CODE_LABEL_COLUMNS', content: this.columnsPerRow })
       updateSysConfigBatch(parameter).then(res => {
         this.$message.info('更新成功, 退出,重新登录后全局生效')
       })
@@ -172,60 +197,26 @@ export default {
   }
 }
 </script>
-<style media=print>
-/* 应用这个样式的在打印时隐藏 */
-.noPrint {
-  display: none;
-}
-
-/* 应用这个样式的,从那个标签结束开始另算一页,之后在遇到再起一页,以此类推 */
-.page {
-  page-break-after: always;
-}
-</style>
-<style>
-.print-content{
+<style scoped>
+/* 页面正常显示时的样式 */
+.print-content {
   margin: 0 auto;
   width: 1000px;
   background-color: #fff;
 }
 
-.container {
-  width: 100%;
-  padding-right: 15px;
-  padding-left: 15px;
-  margin-right: auto;
-  margin-left: auto;
-}
-
-.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
-  position: relative;
-  min-height: 1px;
-  padding-right: 15px;
-  padding-left: 15px;
-}
-
-.text-left {
-  text-align: left;
-}
-
-.text-right {
-  text-align: right;
-}
-
-.text-center {
-  text-align: center;
+.label-cell {
+  break-inside: avoid;
+  page-break-inside: avoid;
 }
 
 table.gridtable {
-  width: 100%;
   font-family: verdana, arial, sans-serif;
   font-size: 11px;
   color: #333333;
   border-width: 1px;
   border-color: #666666;
   border-collapse: collapse;
-  page-break-after: always;
 }
 
 table.gridtable th {
@@ -238,7 +229,7 @@ table.gridtable th {
 
 table.gridtable td {
   border-width: 1px;
-  padding: 0;
+  padding: 2px 4px;
   border-style: solid;
   color: black;
   font-weight: bold;
@@ -246,78 +237,59 @@ table.gridtable td {
   background-color: #ffffff;
 }
 
-.row {
-  margin-right: -15px;
-  margin-left: -15px;
-}
-
-.container:before,
-.container:after,
-.row:before, .row:after {
-  display: table;
-  content: " ";
-}
-
-.container:after, .row:after {
-  clear: both;
-}
-
-.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
-  float: left;
-}
-
-.col-md-12 {
-  width: 100%;
-}
-
-.col-md-11 {
-  width: 91.66666667%;
-}
-
-.col-md-10 {
-  width: 83.33333333%;
-}
-
-.col-md-9 {
-  width: 75%;
+.text-center {
+  text-align: center;
 }
+</style>
 
-.col-md-8 {
-  width: 66.66666667%;
+<style media="print">
+/* 打印时隐藏非打印区域 */
+.noPrint {
+  display: none !important;
 }
 
-.col-md-7 {
-  width: 58.33333333%;
+@page {
+  size: A4 portrait;
+  margin: 5mm;
 }
 
-.col-md-6 {
-  width: 50%;
+.print-content {
+  width: auto !important;
+  margin: 0 !important;
 }
 
-.col-md-5 {
-  width: 41.66666667%;
+/* 打印时隐藏滑块配置区域 */
+.print-content .ant-row {
+  display: none !important;
 }
 
-.col-md-4 {
-  width: 33.33333333%;
+#print-container-batch {
+  background: white !important;
+  padding: 0 !important;
+  gap: 4px !important;
 }
 
-.col-md-3 {
-  width: 25%;
+.label-cell {
+  break-inside: avoid !important;
+  page-break-inside: avoid !important;
 }
 
-.col-md-2 {
-  width: 16.66666667%;
+table.gridtable {
+  font-family: verdana, arial, sans-serif;
+  font-size: 11px;
+  color: #333333;
+  border-width: 1px;
+  border-color: #666666;
+  border-collapse: collapse;
 }
 
-.col-md-1 {
-  width: 8.33333333%;
+table.gridtable td {
+  border-width: 1px;
+  padding: 2px 4px;
+  border-style: solid;
+  color: black;
+  font-weight: bold;
+  border-color: #666666;
+  background-color: #ffffff !important;
 }
-
- .node-line{
-   position: absolute;
-   width: 60px;
-   height: 10px;
-   left:20%;
- }
 </style>