liding
9 小时以前 49cdc0884a9a4381cd968cffff65e2a332bc675a
feat(cnas-search): 1.标准查新模块
2.印章使用记录
已修改15个文件
已添加2个文件
753 ■■■■■ 文件已修改
.env.development 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
outputs/standard-search-new/标准查新导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/标准查新导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
src/api/cnas/process/method/standardNoveltyRetrieval.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Onlyoffice/onlyoffice.vue 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ReportWordEditor/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/loadSuperDoc.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/reportDocxLayout.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/standardNoveltyRetrieval/component/EditFormDia.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/standardNoveltyRetrieval/component/FormDialog.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/standardNoveltyRetrieval/index.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/standardNoveltyRetrieval/options.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/reportPreparation/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/structural/sealManage/usageLedger/index.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/index.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -10,7 +10,7 @@
# æ–‡ä»¶è®¿é—®ä¸Žæ™®é€šæŽ¥å£ç»Ÿä¸€èµ°æœ¬åœ°å¼€å‘代理
VITE_APP_BASE_URL = '/dev-api'
# OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ˆé¢„è§ˆ pdf ç”¨ï¼Œæ²¡æœ‰ http:// å‰ç¼€ä¼šå½“成相对路径)
# OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ˆWord ç­‰æ–‡æ¡£çš„在线预览与编辑;PDF ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿé¢„è§ˆï¼‰
VITE_APP_ONLYOFFICE_API = 'http://192.168.21.53:9001'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
.env.production
@@ -10,7 +10,7 @@
# ç”Ÿäº§çŽ¯å¢ƒip+端口
VITE_APP_BASE_URL = 'http://192.168.21.53:8001/lims'
# OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ˆé¢„è§ˆ pdf ç”¨ï¼Œæ²¡æœ‰ http:// å‰ç¼€ä¼šå½“成相对路径)
# OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ˆWord ç­‰æ–‡æ¡£çš„在线预览与编辑;PDF ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿé¢„è§ˆï¼‰
VITE_APP_ONLYOFFICE_API = 'http://192.168.21.53:9001'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
.env.staging
@@ -10,7 +10,7 @@
# æµ‹è¯•环境ip+端口
VITE_APP_BASE_URL = '/stage-api'
# OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ˆé¢„è§ˆ pdf ç”¨ï¼Œæ²¡æœ‰ http:// å‰ç¼€ä¼šå½“成相对路径)
# OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ˆWord ç­‰æ–‡æ¡£çš„在线预览与编辑;PDF ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿé¢„è§ˆï¼‰
VITE_APP_ONLYOFFICE_API = 'http://192.168.21.53:9001'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
outputs/standard-search-new/±ê×¼²éе¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/±ê×¼²éе¼ÈëÄ£°å.xlsx
Binary files differ
src/api/cnas/process/method/standardNoveltyRetrieval.js
@@ -19,10 +19,19 @@
  });
}
// æŸ¥è¯¢ç”±æ ‡å‡†ç»´æŠ¤è‡ªåŠ¨å½’æ¡£çš„è®°å½•
export function pageAutoArchived(query) {
// æŸ¥è¯¢æŸä¸ªæ ‡å‡†çš„历次查新记录
export function listSearchNewHistory(query) {
  return request({
    url: "/processMethodSearchNew/pageAutoArchived",
    url: "/processMethodSearchNew/listSearchNewHistory",
    method: "get",
    params: query,
  });
}
// åˆ†é¡µæŸ¥è¯¢å…¨éƒ¨æŸ¥æ–°è®°å½•,包含已废止和被新版替代的标准
export function pageSearchNewHistory(query) {
  return request({
    url: "/processMethodSearchNew/pageSearchNewHistory",
    method: "get",
    params: query,
  });
src/components/Onlyoffice/onlyoffice.vue
@@ -8,13 +8,39 @@
      class="native-pdf-preview"
      title="PDF预览"
    ></iframe>
    <div v-else id="vabOnlyOffice"></div>
    <template v-else>
      <div :id="editorId"></div>
      <div v-if="editorWarning" class="editor-warning">{{ editorWarning }}</div>
      <div v-if="editorState !== 'ready'" class="editor-status">
        <template v-if="editorState === 'failed'">
          <p>{{ editorError }}</p>
          <el-button type="primary" @click="setEditor(option)">重新加载</el-button>
        </template>
        <span v-else>正在连接文档服务并加载报告...</span>
      </div>
    </template>
  </div>
</template>
<script>
// OnlyOffice æ–‡æ¡£æœåŠ¡åœ°å€ï¼ŒæŒ‰çŽ¯å¢ƒé…ç½®ï¼›è„šæœ¬åœ¨çœŸæ­£éœ€è¦é¢„è§ˆæ—¶æ‰åŠ è½½
const OFFICE_API_BASE = import.meta.env.VITE_APP_ONLYOFFICE_API || "http://192.168.21.53:9001";
function normalizeOptions(source = {}) {
  return {
    url: source.url || "",
    isEdit: source.isEdit === true || source.isEdit === "true",
    fileType: source.fileType || "",
    title: source.title || "",
    lang: source.lang || "zh-CN",
    isPrint: source.isPrint !== false,
    user: { id: source.user_id, name: source.user_name },
    editUrl: source.editUrl || "",
    key: source.key || "",
    model: source.model,
    token: source.token
  };
}
// å¤šä¸ªé¡µé¢å…±ç”¨ç¼–辑器,api.js åªåŠ è½½ä¸€æ¬¡
let docsApiPromise = null;
@@ -26,14 +52,23 @@
    docsApiPromise = new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.src = OFFICE_API_BASE.replace(/\/$/, "") + "/web-apps/apps/api/documents/api.js";
      const timeout = window.setTimeout(() => {
        script.remove();
        reject(new Error("OnlyOffice æ–‡æ¡£æœåŠ¡è¿žæŽ¥è¶…æ—¶"));
      }, 15000);
      script.onload = () => {
        window.clearTimeout(timeout);
        if (window.DocsAPI) {
          resolve(window.DocsAPI);
        } else {
          reject(new Error("api.js åŠ è½½å®Œæˆä½†æœªæ³¨å†Œ DocsAPI"));
        }
      };
      script.onerror = () => reject(new Error("OnlyOffice æ–‡æ¡£æœåŠ¡è„šæœ¬åŠ è½½å¤±è´¥ï¼š" + script.src));
      script.onerror = () => {
        window.clearTimeout(timeout);
        script.remove();
        reject(new Error("OnlyOffice æ–‡æ¡£æœåŠ¡è„šæœ¬åŠ è½½å¤±è´¥"));
      };
      document.head.appendChild(script);
    });
    // åŠ è½½å¤±è´¥åŽå…è®¸ä¸‹æ¬¡é‡è¯•
@@ -47,21 +82,18 @@
export default {
  name: "VabOnlyOffice",
  props: ['options'],
  emits: ['ready', 'error'],
  data() {
    return {
      editorId: `onlyoffice-${this._uid || Math.random().toString(36).slice(2)}`,
      doctype: "",
      docEditor: null,
      //参考vabOnlyOffice组件参数配置
      option: {
        url: "",
        isEdit: false,
        fileType: "",
        title: "",
        lang: "zh-CN",
        isPrint: true,
        user: {},
        editUrl: ""
      }
      editorGeneration: 0,
      editorState: 'loading',
      editorError: '',
      editorWarning: '',
      readyTimer: null,
      option: normalizeOptions(this.options || this.$route.query)
    };
  },
  computed: {
@@ -69,41 +101,24 @@
      return this.isPdfFile(this.option);
    }
  },
  created() {
    if(this.options){
      const option = this.options
      this.option.url = option.url
      this.option.isEdit = option.isEdit === "true" ? true : false
      this.option.fileType = option.fileType
      this.option.title = option.title
      this.option.lang = option.lang
      this.option.isPrint = option.isPrint
      this.option.user.id = option.user_id
      this.option.user.name = option.user_name
      this.option.editUrl = option.editUrl
    }else{
      const option = this.$route.query
      this.option.url = option.url
      this.option.isEdit = option.isEdit === "true" ? true : false
      this.option.fileType = option.fileType
      this.option.title = option.title
      this.option.lang = option.lang
      this.option.isPrint = option.isPrint
      this.option.user.id = option.user_id
      this.option.user.name = option.user_name
      this.option.editUrl = option.editUrl
    }
  },
  beforeUnmount() {
    this.editorGeneration++;
    window.clearTimeout(this.readyTimer);
    this.destroyEditor();
  },
  watch: {
    option: {
      handler: function(n) {
        this.setEditor(n);
        this.doctype = this.getFileType(n.fileType);
    options: {
      handler(newOptions) {
        this.option = normalizeOptions(newOptions || this.$route.query);
        this.setEditor(this.option);
      },
      deep: true
    },
    '$route.query'(query) {
      if (!this.options) {
        this.option = normalizeOptions(query);
        this.setEditor(this.option);
      }
    }
  },
  mounted() {
@@ -114,19 +129,32 @@
  methods: {
    destroyEditor() {
      if (this.docEditor !== null) {
        this.docEditor.destroyEditor();
        try {
          this.docEditor.destroyEditor();
        } catch (error) {
          console.error('销毁 OnlyOffice ç¼–辑器失败', error);
        }
        this.docEditor = null;
      }
    },
    async setEditor(option) {
      const generation = ++this.editorGeneration;
      window.clearTimeout(this.readyTimer);
      this.destroyEditor();
      // PDF ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿé¢„è§ˆï¼›å…¶ä»–æ–‡æ¡£æ‰åˆå§‹åŒ– OnlyOffice。
      if (!option || !option.url || this.isPdfFile(option)) {
        return;
      }
      this.editorState = 'loading';
      this.editorError = '';
      this.editorWarning = '';
      // ç­‰å¾… v-else ä¸­çš„ OnlyOffice å®¹å™¨å®Œæˆæ¸²æŸ“。
      await this.$nextTick();
      this.doctype = this.getFileType(option.fileType);
      if (!this.doctype) {
        this.failEditor('报告文件格式不受支持', generation);
        return;
      }
      let config = {
        document: {
          //后缀
@@ -149,6 +177,7 @@
          //定制
          customization: {
            autosave: true, //是否自动保存
            forcesave: true, //点击保存时立即回调业务服务
            chat: true,
            comments: false,
            help: false,
@@ -164,20 +193,54 @@
        },
        width: "100%",
        height: "100%",
        token: option.token || ""
        token: option.token || "",
        events: {
          onDocumentReady: () => {
            if (generation === this.editorGeneration) {
              window.clearTimeout(this.readyTimer);
              this.editorState = 'ready';
              this.$emit('ready');
            }
          },
          onError: (event) => {
            const detail = event && event.data && (event.data.errorDescription || event.data.errorCode);
            this.failEditor(detail ? `报告加载失败:${detail}` : '报告加载失败,请检查文档服务', generation);
          }
        }
      };
      try {
        await loadDocsApi();
      } catch (error) {
        console.error(error);
        this.$message.error("文档服务暂时不可用,无法预览");
        this.failEditor(error.message || '文档服务暂时不可用', generation);
        return;
      }
      if (this._isDestroyed) {
      if (generation !== this.editorGeneration || !this.$el?.isConnected) {
        return;
      }
      this.docEditor = new window.DocsAPI.DocEditor("vabOnlyOffice", config);
      try {
        this.readyTimer = window.setTimeout(() => {
          this.failEditor('报告加载超时,请检查文档服务与报告地址', generation);
        }, 30000);
        this.docEditor = new window.DocsAPI.DocEditor(this.editorId, config);
      } catch (error) {
        window.clearTimeout(this.readyTimer);
        console.error(error);
        this.failEditor('文档编辑器初始化失败,请重试', generation);
      }
    },
    failEditor(message, generation) {
      if (generation !== this.editorGeneration) return;
      window.clearTimeout(this.readyTimer);
      if (this.editorState === 'ready') {
        this.editorWarning = String(message);
        this.$emit('error', this.editorWarning);
        return;
      }
      this.editorError = String(message);
      this.editorState = 'failed';
      this.$emit('error', this.editorError);
    },
    isPdfFile(option) {
      if (!option) {
@@ -253,10 +316,36 @@
<style scoped>
.document-preview,
.native-pdf-preview,
#vabOnlyOffice {
.document-preview > div {
  width: 100%;
  height: 100%;
}
.document-preview {
  position: relative;
}
.editor-status {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 12px;
  background: #fff;
  color: #606266;
}
.editor-warning {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  height: auto !important;
  padding: 8px 12px;
  background: #fff3e0;
  color: #a35d00;
}
.native-pdf-preview {
  display: block;
  border: 0;
src/components/ReportWordEditor/index.vue
@@ -25,6 +25,7 @@
import { ElMessage } from 'element-plus'
import { normalizeReportDocxForEditor } from '@/utils/reportDocxLayout'
import { loadSuperDoc } from '@/utils/loadSuperDoc'
import documentWorkerUrl from 'virtual:superdoc-http-worker-url'
import { superdocToolbarZh } from '@/utils/superdocToolbarZh'
import 'superdoc/style.css'
@@ -114,6 +115,7 @@
      }),
      documentMode: 'editing',
      role: 'editor',
      workerUrls: { document: documentWorkerUrl },
      telemetry: { enabled: false },
      ui: { toolbar: { container: toolbar.value, strings: superdocToolbarZh }, ruler: true, comments: false },
      onReady: async ({ superdoc }) => {
src/utils/loadSuperDoc.js
@@ -1,8 +1,11 @@
import { installCryptoRandomUUID } from './cryptoRandomUUID'
let modulePromise
// å®¡æ ¸å¼€å§‹æ—¶é¢„加载;编辑器复用同一次加载,失败后仍允许重试。
export function loadSuperDoc() {
  if (!modulePromise) {
    installCryptoRandomUUID()
    modulePromise = import('superdoc').catch(error => {
      modulePromise = null
      throw error
src/utils/reportDocxLayout.js
@@ -4,6 +4,41 @@
const floatingPositionPattern = /<w:tblpPr\b[^>]*\/>/
const tableIndentPattern = /<w:tblInd\b[^>]*\/>/
const runFontsPattern = /<w:rFonts\b[^>]*\/>/g
const tableRowPattern = /<w:tr(?:\s[^>]*)?>[\s\S]*?<\/w:tr>/g
const tableCellPattern = /<w:tc(?:\s[^>]*)?>[\s\S]*?<\/w:tc>/g
function fitCoverCustomer(xml) {
  let changed = false
  const normalized = xml.replace(tableRowPattern, row => {
    if (changed) return row
    const cells = [...row.matchAll(tableCellPattern)]
    if (cells.length !== 2) return row
    const cellText = cell => [...cell.matchAll(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g)]
      .map(match => match[1]).join('')
    if (cellText(cells[0][0]).replace(/[\s::]/g, '') !== '委托单位') return row
    const valueCell = cells[1][0]
    const customer = cellText(valueCell)
    const widthTag = valueCell.match(/<w:tcW\b[^>]*>/)?.[0]
    const widthTwips = Number(widthTag?.match(/\bw:w="(\d+)"/)?.[1])
    const fontHalfPoints = Number(valueCell.match(/<w:sz\b[^>]*\bw:val="(\d+)"/)?.[1])
    if (!customer || customer.includes('{{') || !widthTwips || fontHalfPoints !== 32) return row
    // å°é¢ä¸‹åˆ’线是单元格底边框。16 ç£…的长单位名称刚好占满列宽时,
    // PDF æŽ’版器可能将段落结束符挤到第二行,导致表格行和下划线下移。
    const units = [...customer].reduce((sum, char) => sum +
      (/\s/.test(char) ? 0.35 : char.codePointAt(0) > 0x2e80 ? 1 : 0.6), 0)
    const availablePoints = widthTwips / 20 - 10
    if (units * fontHalfPoints / 2 <= availablePoints) return row
    const fittedHalfPoints = Math.max(24, Math.floor(availablePoints / units * 2))
    const fittedCell = valueCell.replace(/<w:sz(?:Cs)?\b[^>]*\/>/g, tag =>
      tag.replace(/\bw:val="32"/, `w:val="${fittedHalfPoints}"`))
    if (fittedCell === valueCell) return row
    changed = true
    return row.replace(valueCell, fittedCell)
  })
  return changed ? normalized : null
}
function alignReportHeader(xml, reportCode) {
  // æ—§æ£€éªŒæŠ¥å‘ŠæŠŠé¡µç æ”¾åœ¨æµ®åŠ¨æ–‡æœ¬æ¡†ä¸­ï¼›SuperDoc é‡æ–°åˆ†é¡µæ—¶ä¼šä¸¢å¤± Word çš„æ¨ªå‘锚点。
@@ -138,6 +173,7 @@
  const themeXml = themePart ? await themePart.async('string') : ''
  const themeFonts = { major: chineseThemeFont(themeXml, 'major'), minor: chineseThemeFont(themeXml, 'minor') }
  let normalizedXml = alignResultTable(documentXml) || documentXml
  normalizedXml = fitCoverCustomer(normalizedXml) || normalizedXml
  normalizedXml = materializeChineseThemeFonts(normalizedXml, themeFonts) || normalizedXml
  let changed = normalizedXml !== documentXml
  if (changed) zip.file('word/document.xml', normalizedXml)
src/views/CNAS/process/method/standardNoveltyRetrieval/component/EditFormDia.vue
@@ -1,75 +1,63 @@
<template>
  <div>
    <el-dialog v-model="formDia" title="编辑" width="80%" @close="closeDia">
    <el-dialog v-model="formDia" title="新增查新" width="820px" @close="closeDia">
      <el-form ref="form" :model="form" :rules="formRules" label-width="auto">
        <el-col :span="8">
          <el-form-item label="标准名称:" prop="methodName">
            <el-input v-model="form.methodName" size="small"></el-input>
            <el-input v-model="form.methodName" size="small" disabled></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="标准号:" prop="standardNo">
            <el-input v-model="form.standardNo" size="small"></el-input>
            <el-input v-model="form.standardNo" size="small" disabled></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="文件编号:" prop="fileNo">
            <el-input v-model="form.fileNo" size="small"></el-input>
          <el-form-item label="查新日期:" prop="searchNewTime">
            <el-date-picker v-model="form.searchNewTime" type="date" value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD" placeholder="请选择日期" size="small"
                            style="width: 100%;" @change="refreshDefaultNextSearchDate"></el-date-picker>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="查新时间:" prop="searchNewTime">
            <el-date-picker v-model="form.searchNewTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
                            format="YYYY-MM-DD HH:mm:ss" placeholder="请选择" size="small"
                            style="width: 100%;"></el-date-picker>
          <el-form-item label="下次查新日期:">
            <el-input v-if="isSearchRetired" model-value="不适用" size="small" disabled />
            <el-date-picker v-else v-model="form.nextSearchDate" type="date" value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD" placeholder="默认三个月后,可修改" size="small"
                            style="width: 100%;" />
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="查新人:">
            <el-input :model-value="form.searchNewUserName" size="small" disabled></el-input>
            <el-input :model-value="currentUserName" size="small" disabled></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="查新结果:" prop="searchNewResult">
            <el-input v-model="form.searchNewResult" size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="是否是新标准:">
            <el-tag type="success">是</el-tag>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="查新记录来源:" prop="searchNewSource">
            <el-radio-group v-model="form.searchNewSource">
              <el-radio :value="0">标准网</el-radio>
              <el-radio :value="1">委托情报</el-radio>
              <el-radio :value="2">标准数</el-radio>
              <el-radio :value="3">其他</el-radio>
            </el-radio-group>
          </el-form-item>
        </el-col>
        <el-col v-if="form.searchNewSource === 3" :span="8">
          <el-form-item label="来源网址/网站名称:" prop="searchNewWebsite"
                        :rules="[{required: true, message: '请填写具体网址或网站名称', trigger: 'blur'}]">
            <el-input v-model="form.searchNewWebsite" size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="备注:" prop="remark">
            <el-select v-model="form.remark"
                       clearable size="small">
              <el-option :value="0" label="作废"></el-option>
              <el-option :value="1" label="替换"></el-option>
            <el-select v-model="form.searchNewResult" placeholder="请选择" style="width: 100%"
                       @change="handleSearchResultChange">
              <el-option v-for="item in searchResultOptions" :key="item" :label="item" :value="item" />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col v-if="form.isNewStandard == 1" :span="8">
        <el-col :span="24">
          <el-form-item label="查新记录来源:" prop="searchNewSource">
            <el-radio-group v-model="form.searchNewSource">
              <el-radio v-for="item in searchSourceOptions" :key="item.value" :value="item.value">{{ item.label }}</el-radio>
            </el-radio-group>
          </el-form-item>
        </el-col>
        <el-col v-if="form.searchNewSource === 3" :span="24">
          <el-form-item label="其他来源名称:" prop="searchNewWebsite">
            <el-input v-model="form.searchNewWebsite" size="small" placeholder="选填,无需填写网址"></el-input>
          </el-form-item>
        </el-col>
        <el-col v-if="form.searchNewResult === '已修订/有新版本替代'" :span="12">
          <el-form-item label="新标准名称:" prop="newMethodName">
            <el-input v-model="form.newMethodName" size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col v-if="form.isNewStandard == 1" :span="8">
        <el-col v-if="form.searchNewResult === '已修订/有新版本替代'" :span="12">
          <el-form-item label="新标准号:" prop="newStandardNo">
            <el-input v-model="form.newStandardNo" size="small"></el-input>
          </el-form-item>
@@ -87,6 +75,8 @@
import {
  updateMethodSearchNew
} from '@/api/cnas/process/method/standardNoveltyRetrieval'
import useUserStore from '@/store/modules/user'
import { calculateNextSearchDate, isSearchRetired, searchResultOptions, searchSourceOptions } from '../options'
export default {
  name: 'FormDialog',
@@ -96,6 +86,8 @@
    // è¿™é‡Œå­˜æ”¾æ•°æ®
    return {
      formDia: false,
      searchResultOptions,
      searchSourceOptions,
      form: {
        methodName: '',
        standardNo: '',
@@ -103,18 +95,20 @@
        newMethodName: '',
        newStandardNo: '',
        searchNewTime: '',
        nextSearchDate: '',
        searchNewResult: '',
        searchNewUserName: '',
        searchNewWebsite: '',
        searchNewSource: '',
        remark: '',
        fileNo: '',
      },
      formRules: {
        methodName: [{required: true, message: '请填写标准名称',trigger: 'blur'}],
        standardNo: [{required: true, message: '请填写标准号',trigger: 'blur'}],
        newMethodName: [{required: false, message: '请填写新标准名称',trigger: 'blur'}],
        newStandardNo: [{required: false, message: '请填写新标准号',trigger: 'blur'}],
        newMethodName: [{required: true, message: '请填写新标准名称',trigger: 'blur'}],
        newStandardNo: [{required: true, message: '请填写新标准号',trigger: 'blur'}],
        searchNewTime: [{required: true, message: '请选择查新日期',trigger: 'change'}],
        searchNewResult: [{required: true, message: '请选择查新结果',trigger: 'change'}],
        searchNewSource: [{required: true, message: '请选择查新记录来源',trigger: 'change'}],
        fileNo: [{required: true, message: '请填写文件号',trigger: 'blur'}],
      },
@@ -122,11 +116,40 @@
      info: {},
    };
  },
  computed: {
    isSearchRetired() {
      return isSearchRetired(this.form.searchNewResult)
    },
    currentUserName() {
      const user = useUserStore()
      return user.nickName || user.name || user.userName
    }
  },
  // æ–¹æ³•集合
  methods: {
    openDia(row) {
      this.formDia = true
      this.form = {...row, isNewStandard: 1}
      this.form = {
        ...row,
        searchNewTime: '',
        nextSearchDate: '',
        searchNewResult: '',
        searchNewSource: null,
        searchNewWebsite: '',
        newMethodName: '',
        newStandardNo: ''
      }
    },
    refreshDefaultNextSearchDate() {
      this.form.nextSearchDate = this.isSearchRetired ? null
        : calculateNextSearchDate(this.form.searchNewTime)
    },
    handleSearchResultChange() {
      if (this.isSearchRetired) {
        this.form.nextSearchDate = null
      } else if (!this.form.nextSearchDate) {
        this.refreshDefaultNextSearchDate()
      }
    },
    handleEdit() {
      this.$refs['form'].validate((valid) => {
@@ -134,13 +157,14 @@
          this.editLoad = true
          const processMethodSearchNews = this.HaveJson(this.form)
          delete processMethodSearchNews.createTime
          processMethodSearchNews.searchNewTime = `${processMethodSearchNews.searchNewTime} 00:00:00`
          if (processMethodSearchNews.searchNewSource !== 3) {
            processMethodSearchNews.searchNewWebsite = null
          }
          updateMethodSearchNew(processMethodSearchNews).then(res => {
            this.editLoad = false
            if (res.code === 200){
              this.$message.success('操作成功')
              this.$message.success('查新记录已保存')
              this.closeDia()
            }
          }).catch(err => {
src/views/CNAS/process/method/standardNoveltyRetrieval/component/FormDialog.vue
@@ -14,20 +14,33 @@
            </div>
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item :prop="`taskRel.${index}.newMethodName`" :rules="{required: true, message: '请填写标准名称',trigger: 'blur',}" label="新标准名称:">
                  <el-input v-model="items.newMethodName" size="small"></el-input>
                <el-form-item :prop="`taskRel.${index}.methodName`" :rules="{required: true, message: '请填写标准名称',trigger: 'blur',}" label="标准名称:">
                  <el-input v-model="items.methodName" size="small"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item :prop="`taskRel.${index}.newStandardNo`" :rules="{required: true,message: '请填写标准号',trigger: 'blur',}" label="新标准号:">
                  <el-input v-model="items.newStandardNo" size="small"></el-input>
                <el-form-item :prop="`taskRel.${index}.standardNo`" :rules="{required: true,message: '请填写标准号',trigger: 'blur',}" label="标准号:">
                  <el-input v-model="items.standardNo" size="small"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item :prop="`taskRel.${index}.searchNewTime`" label="查新时间:">
                  <el-date-picker v-model="items.searchNewTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
                                  format="YYYY-MM-DD HH:mm:ss" placeholder="请选择" size="small"
                                  style="width: 100%;"></el-date-picker>
                <el-form-item :prop="`taskRel.${index}.fileNo`" :rules="{required: true,message: '请填写文件编号',trigger: 'blur',}" label="文件编号:">
                  <el-input v-model="items.fileNo" size="small"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item :prop="`taskRel.${index}.searchNewTime`" :rules="{required: true,message: '请选择查新日期',trigger: 'change',}" label="查新日期:">
                  <el-date-picker v-model="items.searchNewTime" type="date" value-format="YYYY-MM-DD"
                                  format="YYYY-MM-DD" placeholder="请选择日期" size="small"
                                  style="width: 100%;" @change="refreshDefaultNextSearchDate(items)"></el-date-picker>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="下次查新日期:">
                  <el-input v-if="isSearchRetired(items.searchNewResult)" model-value="不适用" size="small" disabled />
                  <el-date-picker v-else v-model="items.nextSearchDate" type="date" value-format="YYYY-MM-DD"
                                  format="YYYY-MM-DD" placeholder="默认三个月后,可修改" size="small"
                                  style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
@@ -35,34 +48,35 @@
                  <el-input :model-value="currentUserName" size="small" disabled></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item :prop="`taskRel.${index}.searchNewResult`" label="查新结果:">
                  <el-input v-model="items.searchNewResult" type="textarea" :rows="2" size="small"></el-input>
              <el-col :span="12">
                <el-form-item :prop="`taskRel.${index}.searchNewResult`" :rules="{required: true,message: '请选择查新结果',trigger: 'change',}" label="查新结果:">
                  <el-select v-model="items.searchNewResult" placeholder="请选择" style="width: 100%"
                             @change="handleSearchResultChange(items)">
                    <el-option v-for="result in searchResultOptions" :key="result" :label="result" :value="result" />
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item :prop="`taskRel.${index}.searchNewSource`" :rules="{required: true,message: '请选择查新记录来源',trigger: 'change',}" label="查新记录来源:">
                  <el-radio-group v-model="items.searchNewSource">
                    <el-radio :value="0">标准网</el-radio>
                    <el-radio :value="1">委托情报</el-radio>
                    <el-radio :value="2">标准数</el-radio>
                    <el-radio :value="3">其他</el-radio>
                    <el-radio v-for="source in searchSourceOptions" :key="source.value" :value="source.value">{{ source.label }}</el-radio>
                  </el-radio-group>
                </el-form-item>
              </el-col>
              <el-col v-if="items.searchNewSource === 3" :span="24">
                <el-form-item :prop="`taskRel.${index}.searchNewWebsite`"
                              :rules="{required: true, message: '请填写具体网址或网站名称', trigger: 'blur'}"
                              label="来源网址/网站名称:">
                  <el-input v-model="items.searchNewWebsite" size="small" placeholder="请输入具体网址或网站名称"></el-input>
                              label="其他来源名称:">
                  <el-input v-model="items.searchNewWebsite" size="small" placeholder="选填,无需填写网址"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item :prop="`taskRel.${index}.remark`" :rules="{required: false,message: '请选择备注',trigger: 'change',}" label="备注:">
                  <el-select v-model="items.remark" clearable size="small" style="width: 100%;">
                    <el-option :value="0" label="作废"></el-option>
                    <el-option :value="1" label="替换"></el-option>
                  </el-select>
              <el-col v-if="items.searchNewResult === '已修订/有新版本替代'" :span="12">
                <el-form-item :prop="`taskRel.${index}.newMethodName`" :rules="{required: true,message: '请填写新标准名称',trigger: 'blur',}" label="新标准名称:">
                  <el-input v-model="items.newMethodName" size="small"></el-input>
                </el-form-item>
              </el-col>
              <el-col v-if="items.searchNewResult === '已修订/有新版本替代'" :span="12">
                <el-form-item :prop="`taskRel.${index}.newStandardNo`" :rules="{required: true,message: '请填写新标准号',trigger: 'blur',}" label="新标准号:">
                  <el-input v-model="items.newStandardNo" size="small"></el-input>
                </el-form-item>
              </el-col>
            </el-row>
@@ -83,6 +97,7 @@
<script>
import { addMethodSearchNew } from '@/api/cnas/process/method/standardNoveltyRetrieval'
import useUserStore from '@/store/modules/user'
import { calculateNextSearchDate, isSearchRetired, searchResultOptions, searchSourceOptions } from '../options'
export default {
  name: 'FormDialog',
@@ -92,16 +107,21 @@
    // è¿™é‡Œå­˜æ”¾æ•°æ®
    return {
      formDia: false,
      searchResultOptions,
      searchSourceOptions,
      form: {
        taskRel: [
          {
            newMethodName: '',
            newStandardNo: '',
            methodName: '',
            standardNo: '',
            fileNo: '',
            searchNewTime: '',
            nextSearchDate: '',
            searchNewResult: '',
            searchNewWebsite: '',
            searchNewSource: '',
            remark: '',
            newMethodName: '',
            newStandardNo: '',
          }
        ]
      },
@@ -118,12 +138,26 @@
  },
  // æ–¹æ³•集合
  methods: {
    isSearchRetired,
    openDia (row) {
      this.formDia = true
    },
    // æ·»åŠ ä¸€æ¡
    addBtn() {
      this.form.taskRel.push({ newMethodName: '', newStandardNo:'', searchNewTime: '', searchNewResult: '', searchNewWebsite: '', searchNewSource: '', remark: ''});
      this.form.taskRel.push({ methodName: '', standardNo: '', fileNo: '', searchNewTime: '',
        nextSearchDate: '', searchNewResult: '', searchNewWebsite: '', searchNewSource: '',
        newMethodName: '', newStandardNo: '' });
    },
    refreshDefaultNextSearchDate(item) {
      item.nextSearchDate = isSearchRetired(item.searchNewResult) ? null
        : calculateNextSearchDate(item.searchNewTime)
    },
    handleSearchResultChange(item) {
      if (isSearchRetired(item.searchNewResult)) {
        item.nextSearchDate = null
      } else if (!item.nextSearchDate) {
        this.refreshDefaultNextSearchDate(item)
      }
    },
    // åˆ é™¤ä¸€æ¡
    deleteHeaderRow (index) {
@@ -135,7 +169,10 @@
          this.editLoad = true
          const records = this.form.taskRel.map(item => ({
            ...item,
            searchNewWebsite: item.searchNewSource === 3 ? item.searchNewWebsite : null
            searchNewTime: `${item.searchNewTime} 00:00:00`,
            searchNewWebsite: item.searchNewSource === 3 ? item.searchNewWebsite : null,
            newMethodName: item.searchNewResult === '已修订/有新版本替代' ? item.newMethodName : null,
            newStandardNo: item.searchNewResult === '已修订/有新版本替代' ? item.newStandardNo : null
          }))
          addMethodSearchNew(records).then(res => {
            this.editLoad = false
src/views/CNAS/process/method/standardNoveltyRetrieval/index.vue
@@ -13,6 +13,7 @@
        </div>
      </div>
      <div style="line-height: 30px;">
        <el-button size="small" @click="openAllHistory">查新记录</el-button>
        <el-button size="small" type="primary" style="margin-left: 10px;"
                   @click="$refs.importDialog.open()">导入</el-button>
        <el-button size="small" style="margin-left: 10px;" type="primary" @click="openFormDia">新 å¢ž</el-button>
@@ -26,6 +27,36 @@
      </div>
      <FormDialog v-if="formDialog" ref="formDialog" @closeDia="closeDia"></FormDialog>
      <EditFormDia v-if="editFormDialog" ref="editFormDialog" @closeEditDia="closeEditDia"></EditFormDia>
      <el-dialog v-model="historyDialog" :title="`${historyStandard} Â· æŸ¥æ–°è®°å½•`" width="960px">
        <div v-if="historyScopeId === null" style="display: flex; gap: 8px; margin-bottom: 12px;">
          <el-input v-model="historySearchNo" clearable placeholder="按标准号查询" style="width: 260px"
                    @keyup.enter="searchAllHistory" />
          <el-button type="primary" @click="searchAllHistory">查询</el-button>
        </div>
        <el-table v-loading="historyLoading" :data="historyRecords" border>
          <el-table-column prop="methodName" label="标准名称" min-width="160" />
          <el-table-column prop="standardNo" label="标准号" min-width="135" />
          <el-table-column prop="fileNo" label="文件编号" min-width="120" />
          <el-table-column prop="searchNewDate" label="查新日期" width="115" />
          <el-table-column prop="nextSearchDate" label="下次查新日期" width="125" />
          <el-table-column prop="searchNewResult" label="查新结果" width="190" />
          <el-table-column label="查新来源" min-width="210">
            <template #default="scope">
              {{ displaySearchSource(scope.row.searchNewSource) }}
              <span v-if="scope.row.searchNewWebsite">({{ scope.row.searchNewWebsite }})</span>
            </template>
          </el-table-column>
          <el-table-column prop="searchNewUserName" label="查新人" width="110" />
          <el-table-column prop="newMethodName" label="新版标准名称" min-width="150" />
          <el-table-column prop="newStandardNo" label="新版标准号" min-width="135" />
          <el-table-column prop="createTime" label="提交时间" width="165" />
        </el-table>
        <el-empty v-if="!historyLoading && historyRecords.length === 0" description="暂无查新记录" />
        <el-pagination v-if="historyScopeId === null" v-model:current-page="historyPage.current"
                       v-model:page-size="historyPage.size" :total="historyPage.total"
                       layout="total, prev, pager, next" style="margin-top: 12px; justify-content: flex-end;"
                       @current-change="loadHistory" />
      </el-dialog>
      <import-dialog
        ref="importDialog"
        title="导入标准查新"
@@ -35,9 +66,9 @@
        template-name="标准查新导入模板.xlsx"
        :tips="[
          '表头为两行,请从第 3 è¡Œå¼€å§‹å¡«å†™æ•°æ®',
          '标准名称、标准号请填写现行标准的名称和编号',
          '查新来源按“标准网 â†’ å§”托情报所 â†’ æ ‡å‡†ä¹¦åº— â†’ å…¶ä»–”的顺序取第一个非空项',
          '备注列只能填写“替换”或“作废”,留空表示无备注'
          '标准名称、标准号请填写本次查新的标准信息',
          '查新日期格式为 YYYY-MM-DD,查新结果填写现行有效、已修订/有新版本替代、废止(无替代)或即将实施',
          '六个查新来源列中只能填写一个,填写“√”表示选中'
        ]"
        @success="searchList"
      />
@@ -50,8 +81,10 @@
import ImportDialog from '@/components/ImportDialog/index.vue'
import FormDialog from './component/FormDialog.vue';
import EditFormDia from './component/EditFormDia.vue';
import { displayDate, displaySearchSource } from './options'
import {
  delMethodSearchNew,
  listSearchNewHistory,
  pageSearchNewHistory,
  pageMethodSearchNew
} from '@/api/cnas/process/method/standardNoveltyRetrieval'
@@ -67,6 +100,13 @@
  data() {
    // è¿™é‡Œå­˜æ”¾æ•°æ®
    return {
      historyDialog: false,
      historyLoading: false,
      historyRecords: [],
      historyStandard: '',
      historyScopeId: null,
      historySearchNo: '',
      historyPage: { current: 1, size: 10, total: 0 },
      searchForm: {
        standardNo: ''
      },
@@ -87,48 +127,15 @@
          minWidth: '100'
        },
        {
          dataType: 'tag',
          label: '是否是新标准',
          prop: 'isNewStandard',
          minWidth: '100',
          formatData: (params) => {
            if (params == 0) {
              return '否';
            } else if (params == 1) {
              return '是';
            } else {
              return null
            }
          },
          formatType: (params) => {
            if (params == 0) {
              return 'danger';
            } else if (params == 1) {
              return 'success';
            } else {
              return null
            }
          }
        },
        {
          label: '新标准名称',
          prop: 'newMethodName',
          minWidth: '100'
        },
        {
          label: '新标准号',
          prop: 'newStandardNo',
          minWidth: '100'
        },
        {
          label: '查新时间',
          label: '最近查新日期',
          prop: 'searchNewTime',
          minWidth: '150'
          minWidth: '125',
          formatData: displayDate
        },
        {
          label: '查新结果',
          prop: 'searchNewResult',
          minWidth: '140'
          minWidth: '190'
        },
        {
          label: '查新人',
@@ -136,65 +143,20 @@
          minWidth: '100'
        },
        {
          dataType: 'tag',
          label: '查新记录来源',
          prop: 'searchNewSource',
          minWidth: '100',
          formatData: (params) => {
            if (params == 0) {
              return '标准网';
            } else if (params == 1) {
              return '委托情报';
            } else if (params == 2) {
              return '标准数';
            } else if (params == 3) {
              return '其他';
            } else {
              return null
            }
          },
          formatType: (params) => {
            if (params == 0) {
              return 'success';
            } else if (params == 1) {
              return 'danger';
            } else if (params == 2) {
              return 'warning';
            } else if (params == 3) {
              return 'info';
            } else {
              return null
            }
          }
          minWidth: '180',
          formatData: displaySearchSource
        },
        {
          label: '来源网址/网站名称',
          label: '其他来源名称',
          prop: 'searchNewWebsite',
          minWidth: '150'
        },
        { label: '下次查新日期', prop: 'nextSearchDate', minWidth: '125', formatData: displayDate },
        {
          dataType: 'tag',
          label: '备注',
          prop: 'remark',
          minWidth: '100',
          formatData: (params) => {
            if (params == 0) {
              return '作废';
            } else if (params == 1) {
              return '替换';
            } else {
              return null
            }
          },
          formatType: (params) => {
            if (params == 0) {
              return 'danger';
            } else if (params == 1) {
              return 'success';
            } else {
              return null
            }
          }
          dataType: 'tag', label: '查新状态', prop: 'searchDueStatus', minWidth: '100',
          formatType: value => value === '已逾期' ? 'danger' : value === '今日到期' ? 'warning' : 'success'
        },
        {
          dataType: 'action',
@@ -202,17 +164,17 @@
          label: '操作',
          operation: [
            {
              name: '编辑',
              name: '查新',
              type: 'text',
              clickFun: (row) => {
                this.openEditFormDia(row);
              },
            },
            {
              name: '删除',
              name: '本标准记录',
              type: 'text',
              clickFun: (row) => {
                this.handleDelete(row);
                this.openHistory(row);
              },
            },
          ]
@@ -234,6 +196,43 @@
  },
  // æ–¹æ³•集合
  methods: {
    displaySearchSource,
    openAllHistory() {
      this.historyStandard = '全部标准'
      this.historyScopeId = null
      this.historySearchNo = ''
      this.historyPage.current = 1
      this.historyRecords = []
      this.historyDialog = true
      this.loadHistory()
    },
    searchAllHistory() {
      this.historyPage.current = 1
      this.loadHistory()
    },
    openHistory(row) {
      this.historyStandard = row.standardNo || row.methodName
      this.historyScopeId = row.methodSearchNewId
      this.historyRecords = []
      this.historyDialog = true
      this.loadHistory()
    },
    loadHistory() {
      this.historyLoading = true
      const request = this.historyScopeId === null
        ? pageSearchNewHistory({ standardNo: this.historySearchNo,
            current: this.historyPage.current, size: this.historyPage.size })
        : listSearchNewHistory({ methodSearchNewId: this.historyScopeId })
      request.then(res => {
        if (res.code !== 200) return
        if (this.historyScopeId === null) {
          this.historyRecords = res.data?.records || []
          this.historyPage.total = res.data?.total || 0
        } else {
          this.historyRecords = res.data || []
        }
      }).finally(() => { this.historyLoading = false })
    },
    // æŸ¥è¯¢åˆ—表
    searchList() {
      this.tableLoading = true
@@ -278,24 +277,10 @@
      this.editFormDialog = false
      this.searchList()
    },
    // åˆ é™¤æ ‡å‡†æŸ¥æ–°è®°å½•
    handleDelete(row) {
      this.$confirm('是否删除该条查新记录?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        delMethodSearchNew({ methodSearchNewId: row.methodSearchNewId }).then(res => {
          if (res.code !== 200) return
          this.$message.success('删除成功')
          this.searchList()
        })
      }).catch(() => {
      })
    },
    // åˆ†é¡µ
    pagination(page) {
      this.page.size = page.limit
      this.page.current = page.page
      this.searchList();
    },
  },
src/views/CNAS/process/method/standardNoveltyRetrieval/options.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
import dayjs from 'dayjs'
export const searchResultOptions = ['现行有效', '已修订/有新版本替代', '废止(无替代)', '即将实施']
export function isSearchRetired(searchNewResult) {
  return ['已修订/有新版本替代', '废止(无替代)'].includes(searchNewResult)
}
export function calculateNextSearchDate(searchNewTime) {
  if (!searchNewTime) return ''
  const date = dayjs(searchNewTime)
  return date.isValid() ? date.add(3, 'month').format('YYYY-MM-DD') : ''
}
export const searchSourceOptions = [
  { value: 4, label: '中国标准化委员会' },
  { value: 5, label: '国家市场监督管理总局' },
  { value: 6, label: '全国标准信息公共服务平台' },
  { value: 7, label: '国家标准全文公开系统' },
  { value: 8, label: '工标网' },
  { value: 3, label: '其他' }
]
const legacySearchSources = {
  0: '标准网(历史来源)',
  1: '委托情报所(历史来源)',
  2: '标准书店(历史来源)'
}
export function displaySearchSource(value) {
  return searchSourceOptions.find(item => item.value === value)?.label || legacySearchSources[value] || ''
}
export function displayDate(value) {
  return value ? String(value).slice(0, 10) : ''
}
src/views/business/reportPreparation/index.vue
@@ -165,7 +165,7 @@
      <div class="seal-select-block" v-loading="sealLoading">
        <div class="seal-select-title">
          <span>按位置选择印章</span>
          <span class="seal-select-tip">自动匹配 MAç«  / 1号章 / éª‘缝章,每项可单独调整或清空</span>
          <span class="seal-select-tip">自动匹配 MAç«  / 1号章 / æ£€éªŒæ£€æµ‹ç±»å°ç« ï¼Œæ¯é¡¹å¯å•独调整或清空</span>
        </div>
        <div v-if="sealOptions.length" class="seal-position-grid">
          <div v-for="position in sealPositions" :key="position.key" class="seal-position-card">
@@ -1290,7 +1290,8 @@
        const reportSeal = this.sealOptions.find(item => item.name === '1号章')
          || this.sealOptions.find(item => !String(item.name).toUpperCase().includes('MA') && !String(item.name).includes('骑缝'))
          || this.sealOptions[0]
        const seamSeal = this.sealOptions.find(item => String(item.name).includes('骑缝'))
        const seamSeal = this.sealOptions.find(item => /检验检测|检测检验/.test(String(item.name || '')))
          || this.sealOptions.find(item => String(item.name).includes('骑缝'))
        this.selectedSealPositions = {
          maSealId: availableIds.includes(Number(row && row.maSealId))
            ? Number(row.maSealId) : (maSeal ? Number(maSeal.id) : null),
src/views/structural/sealManage/usageLedger/index.vue
@@ -35,18 +35,10 @@
        v-hasPermi="['structural:sealUsageLedger:export']"
        type="primary"
        size="small"
        :disabled="selection.length === 0 || exportLoading || submitLoading"
        :loading="selectedExportLoading"
        @click="exportSelected"
      >勾选导出</el-button>
      <el-button
        v-hasPermi="['structural:sealUsageLedger:export']"
        type="primary"
        size="small"
        :disabled="exportLoading || submitLoading"
        :loading="filteredExportLoading"
        @click="exportFiltered"
      >按条件导出</el-button>
        :loading="exportLoading"
        @click="exportLedger"
      >导出</el-button>
    </div>
    <lims-table
@@ -125,8 +117,7 @@
      tableData: [],
      selection: [],
      tableLoading: false,
      selectedExportLoading: false,
      filteredExportLoading: false,
      exportLoading: false,
      copiesDialogVisible: false,
      submitLoading: false,
      copiesForm: { id: null, reportCode: '', copies: 1, version: 0 },
@@ -144,11 +135,6 @@
        { label: '份数', prop: 'copies', dataType: 'slot', slot: 'copies', width: '120px' },
        { label: '用印人签字', prop: 'useUserName', width: '140px' }
      ]
    }
  },
  computed: {
    exportLoading() {
      return this.selectedExportLoading || this.filteredExportLoading
    }
  },
  mounted() {
@@ -221,19 +207,15 @@
        })
      })
    },
    exportSelected() {
      if (!this.selection.length) {
        this.$message.warning('请选择要导出的记录')
        return
      }
      this.selectedExportLoading = true
      this.downloadWord({ mode: 'selected', ids: this.selection.map(item => item.id) })
        .finally(() => { this.selectedExportLoading = false })
    },
    exportFiltered() {
      this.filteredExportLoading = true
      this.downloadWord({ mode: 'filtered', query: this.buildQuery() })
        .finally(() => { this.filteredExportLoading = false })
    exportLedger() {
      if (this.exportLoading || this.submitLoading) return
      const payload = this.selection.length
        ? { mode: 'selected', ids: this.selection.map(item => item.id) }
        : { mode: 'filtered', query: this.buildQuery() }
      this.exportLoading = true
      this.downloadWord(payload)
        .catch(() => {})
        .finally(() => { this.exportLoading = false })
    },
    downloadWord(payload) {
      return exportSealUsageLedger(payload).then(async data => {
vite/plugins/index.js
@@ -4,9 +4,10 @@
import createSvgIcon from './svg-icon'
import createCompression from './compression'
import createSetupExtend from './setup-extend'
import superdocHttpWorker from './superdoc-http-worker.js'
export default function createVitePlugins(viteEnv, isBuild = false) {
  const vitePlugins = [vue()]
  const vitePlugins = [vue(), superdocHttpWorker()]
  vitePlugins.push(createAutoImport())
  vitePlugins.push(createSetupExtend())
  vitePlugins.push(createSvgIcon(isBuild))