From 92f48fea04ed14d188652afa6bf11f3d6f940b5c Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 16 七月 2026 10:42:33 +0800
Subject: [PATCH] Merge branch 'dev_pro2.0' of http://114.132.189.42:9002/r/mom-pro2-before into dev_pro2.0

---
 pnpm-workspace.yaml                                               |    4 
 src/components/file-preview/file-preview-modal.vue                |  169 ++++++++++++++++++
 src/views/bpm/processInstance/index.vue                           |   23 +
 src/views/ai/chat/index/modules/message/files.vue                 |   17 +
 src/views/wls/arrivalnotice/modules/line-list.vue                 |   24 ++
 src/views/bpm/processInstance/detail/modules/operation-button.vue |   13 +
 src/views/mes/qc/iqc/data.ts                                      |    6 
 src/components/file-preview/typing.ts                             |   10 +
 src/views/wls/arrivalnotice/data.ts                               |    1 
 pnpm-lock.yaml                                                    |   81 ++++++++
 src/components/file-preview/index.ts                              |    3 
 src/views/mes/qc/oqc/data.ts                                      |    6 
 src/views/erp/purchase/order/data.ts                              |   18 +
 src/components/file-preview/use-file-preview.ts                   |   34 +++
 src/views/erp/purchase/order/modules/item-form.vue                |   13 +
 src/router/routes/modules/bpm.ts                                  |   39 ++--
 package.json                                                      |    4 
 src/packages/@core/base/shared/src/utils/upload.ts                |   28 +++
 src/views/mes/qc/rqc/data.ts                                      |    6 
 src/api/erp/purchase/order/index.ts                               |    1 
 src/types/vue-office.d.ts                                         |   33 +++
 21 files changed, 486 insertions(+), 47 deletions(-)

diff --git a/package.json b/package.json
index 1d29683..109e0d2 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,10 @@
     "@vben/types": "workspace:*",
     "@vben/utils": "workspace:*",
     "@videojs-player/vue": "catalog:",
+    "@vue-office/docx": "^1.6.3",
+    "@vue-office/excel": "^1.7.14",
+    "@vue-office/pdf": "^2.0.10",
+    "@vue-office/pptx": "^1.0.1",
     "@vueuse/core": "catalog:",
     "@vueuse/integrations": "catalog:",
     "ant-design-vue": "catalog:",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 97d3ddd..bc9f97f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -590,6 +590,18 @@
       '@videojs-player/vue':
         specifier: 'catalog:'
         version: 1.0.0(@types/video.js@7.3.58)(video.js@7.21.7)(vue@3.5.38(typescript@6.0.3))
+      '@vue-office/docx':
+        specifier: ^1.6.3
+        version: 1.6.3(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))
+      '@vue-office/excel':
+        specifier: ^1.7.14
+        version: 1.7.14(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))
+      '@vue-office/pdf':
+        specifier: ^2.0.10
+        version: 2.0.10(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))
+      '@vue-office/pptx':
+        specifier: ^1.0.1
+        version: 1.0.1(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))
       '@vueuse/core':
         specifier: 'catalog:'
         version: 14.3.0(vue@3.5.38(typescript@6.0.3))
@@ -4624,6 +4636,46 @@
       vue:
         optional: true
 
+  '@vue-office/docx@1.6.3':
+    resolution: {integrity: sha512-Cs+3CAaRBOWOiW4XAhTwwxJ0dy8cPIf6DqfNvYcD3YACiLwO4kuawLF2IAXxyijhbuOeoFsfvoVbOc16A/4bZA==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
+  '@vue-office/excel@1.7.14':
+    resolution: {integrity: sha512-pVUgt+emDQUnW7q22CfnQ+jl43mM/7IFwYzOg7lwOwPEbiVB4K4qEQf+y/bc4xGXz75w1/e3Kz3G6wAafmFBFg==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
+  '@vue-office/pdf@2.0.10':
+    resolution: {integrity: sha512-yHVLrMAKpMPBkhBwofFyGEtEeJF0Zd7oGmf56Pe5aj/xObdRq3E1CIZqTqhWJNgHV8oLQqaX0vs4p5T1zq+GIA==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
+  '@vue-office/pptx@1.0.1':
+    resolution: {integrity: sha512-+V7Kctzl6f6+Yk4NaD/wQGRIkqLWcowe0jEhPexWQb8Oilbzt1OyhWRWcMsxNDTdrgm6aMLP+0/tmw27cxddMg==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
   '@vue/babel-helper-vue-transform-on@1.1.5':
     resolution: {integrity: sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==}
 
@@ -6674,6 +6726,10 @@
 
   ignore@7.0.5:
     resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+    engines: {node: '>= 4'}
+
+  ignore@7.0.6:
+    resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
     engines: {node: '>= 4'}
 
   immutable@5.1.7:
@@ -11532,7 +11588,7 @@
       destr: 2.0.5
       errx: 0.1.0
       exsolve: 1.1.0
-      ignore: 7.0.5
+      ignore: 7.0.6
       jiti: 2.7.0
       klona: 2.0.6
       knitwork: 1.3.0
@@ -12689,6 +12745,26 @@
       unplugin-utils: 0.3.1
     optionalDependencies:
       vue: 3.5.38(typescript@6.0.3)
+
+  '@vue-office/docx@1.6.3(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))':
+    dependencies:
+      vue: 3.5.38(typescript@6.0.3)
+      vue-demi: 0.14.10(vue@3.5.38(typescript@6.0.3))
+
+  '@vue-office/excel@1.7.14(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))':
+    dependencies:
+      vue: 3.5.38(typescript@6.0.3)
+      vue-demi: 0.14.10(vue@3.5.38(typescript@6.0.3))
+
+  '@vue-office/pdf@2.0.10(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))':
+    dependencies:
+      vue: 3.5.38(typescript@6.0.3)
+      vue-demi: 0.14.10(vue@3.5.38(typescript@6.0.3))
+
+  '@vue-office/pptx@1.0.1(vue-demi@0.14.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))':
+    dependencies:
+      vue: 3.5.38(typescript@6.0.3)
+      vue-demi: 0.14.10(vue@3.5.38(typescript@6.0.3))
 
   '@vue/babel-helper-vue-transform-on@1.1.5': {}
 
@@ -15005,6 +15081,9 @@
 
   ignore@7.0.5: {}
 
+  ignore@7.0.6:
+    optional: true
+
   immutable@5.1.7: {}
 
   import-fresh@3.3.0:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index f01f48f..ad49499 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -210,6 +210,10 @@
 allowBuilds:
   '@carbon/icons': true
   '@parcel/watcher': true
+  '@vue-office/docx': set this to true or false
+  '@vue-office/excel': set this to true or false
+  '@vue-office/pdf': set this to true or false
+  '@vue-office/pptx': set this to true or false
   core-js: true
   core-js-pure: true
   esbuild: true
diff --git a/src/api/erp/purchase/order/index.ts b/src/api/erp/purchase/order/index.ts
index 02becec..1a75e5a 100644
--- a/src/api/erp/purchase/order/index.ts
+++ b/src/api/erp/purchase/order/index.ts
@@ -50,6 +50,7 @@
     totalTaxPrice?: number; // 鍚◣鎬讳环锛屽崟浣嶏細鍏�
     remark?: string; // 澶囨敞
     stockCount?: number; // 搴撳瓨鏁伴噺锛堟樉绀哄瓧娈碉級
+    qcCheckFlag?: boolean; // 鏄惁闇�瑕佹潵鏂欐楠�
   }
 }
 
diff --git a/src/components/file-preview/file-preview-modal.vue b/src/components/file-preview/file-preview-modal.vue
new file mode 100644
index 0000000..e6732e0
--- /dev/null
+++ b/src/components/file-preview/file-preview-modal.vue
@@ -0,0 +1,169 @@
+<script lang="ts" setup>
+import type { PreviewFileType } from '@vben/utils';
+
+import { computed, defineAsyncComponent, ref, watch } from 'vue';
+
+import { getFileIcon } from '@vben/utils';
+
+import { Button, Modal, Spin } from 'ant-design-vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+const VueOfficePdf = defineAsyncComponent(() => import('@vue-office/pdf'));
+const VueOfficeDocx = defineAsyncComponent(() => import('@vue-office/docx'));
+const VueOfficeExcel = defineAsyncComponent(() => import('@vue-office/excel'));
+const VueOfficePptx = defineAsyncComponent(() => import('@vue-office/pptx'));
+
+// @vue-office/excel 闇�瑕侀澶栧鍏� CSS
+// @vue-office/excel 闇�瑕侀澶栧鍏� CSS
+// @vue-office/excel 闇�瑕侀澶栧鍏� CSS
+import '@vue-office/excel-css';
+
+const props = withDefaults(
+  defineProps<{
+    fileUrl?: string;
+    fileName?: string;
+    visible?: boolean;
+    width?: number | string;
+  }>(),
+  {
+    fileUrl: '',
+    fileName: '',
+    visible: false,
+    width: '90vw',
+  },
+);
+
+const emit = defineEmits<{
+  'update:visible': [value: boolean];
+}>();
+
+const loading = ref(false);
+const hasError = ref(false);
+
+const open = computed({
+  get: () => props.visible,
+  set: (val) => emit('update:visible', val),
+});
+
+const displayName = computed(() => props.fileName || '鏂囦欢棰勮');
+
+// 鏍规嵁 fileName 鎴� fileUrl 鍒ゆ柇鏂囦欢绫诲瀷
+const fileType = computed<PreviewFileType>(() => {
+  const name = props.fileName || props.fileUrl;
+  if (!name) return 'unknown';
+  const ext = name.split('.').pop()?.toLowerCase() || '';
+  if (ext === 'pdf') return 'pdf';
+  if (['doc', 'docx'].includes(ext)) return 'docx';
+  if (['xls', 'xlsx'].includes(ext)) return 'excel';
+  if (['ppt', 'pptx'].includes(ext)) return 'pptx';
+  return 'unknown';
+});
+
+const canPreview = computed(
+  () => ['pdf', 'docx', 'excel', 'pptx'].includes(fileType.value),
+);
+
+watch(open, (val) => {
+  if (val) {
+    hasError.value = false;
+    loading.value = canPreview.value;
+  }
+});
+
+function onRendered() {
+  loading.value = false;
+}
+
+function onError() {
+  loading.value = false;
+  hasError.value = true;
+}
+
+function openInNewTab() {
+  if (props.fileUrl) {
+    window.open(props.fileUrl, '_blank');
+  }
+}
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    :title="displayName"
+    :width="width"
+    :footer="null"
+    destroy-on-close
+    centered
+    :style="{ top: '20px' }"
+    :body-style="{ height: '80vh', overflow: 'auto' }"
+  >
+    <!-- Loading -->
+    <div
+      v-if="loading"
+      class="flex h-full items-center justify-center"
+    >
+      <Spin size="large" tip="鍔犺浇涓�..." />
+    </div>
+
+    <!-- Error -->
+    <div
+      v-else-if="hasError"
+      class="flex h-full flex-col items-center justify-center"
+    >
+      <IconifyIcon icon="lucide:file-x" :size="64" class="text-red-400" />
+      <p class="mt-4 text-gray-500">鏂囦欢棰勮澶辫触</p>
+      <Button type="primary" class="mt-4" @click="openInNewTab">
+        鍦ㄦ柊鏍囩椤垫墦寮�
+      </Button>
+    </div>
+
+    <!-- Unsupported -->
+    <div
+      v-else-if="!canPreview"
+      class="flex h-full flex-col items-center justify-center"
+    >
+      <IconifyIcon
+        :icon="getFileIcon(fileName || fileUrl)"
+        :size="64"
+        class="text-gray-400"
+      />
+      <p class="mt-4 text-gray-500">涓嶆敮鎸侀瑙堟鏂囦欢绫诲瀷</p>
+      <Button type="primary" class="mt-4" @click="openInNewTab">
+        涓嬭浇鏂囦欢
+      </Button>
+    </div>
+
+    <!-- PDF -->
+    <VueOfficePdf
+      v-else-if="fileType === 'pdf'"
+      :src="fileUrl"
+      @rendered="onRendered"
+      @error="onError"
+    />
+
+    <!-- Word -->
+    <VueOfficeDocx
+      v-else-if="fileType === 'docx'"
+      :src="fileUrl"
+      @rendered="onRendered"
+      @error="onError"
+    />
+
+    <!-- Excel -->
+    <VueOfficeExcel
+      v-else-if="fileType === 'excel'"
+      :src="fileUrl"
+      @rendered="onRendered"
+      @error="onError"
+    />
+
+    <!-- PowerPoint -->
+    <VueOfficePptx
+      v-else-if="fileType === 'pptx'"
+      :src="fileUrl"
+      @rendered="onRendered"
+      @error="onError"
+    />
+  </Modal>
+</template>
diff --git a/src/components/file-preview/index.ts b/src/components/file-preview/index.ts
new file mode 100644
index 0000000..2b6ee86
--- /dev/null
+++ b/src/components/file-preview/index.ts
@@ -0,0 +1,3 @@
+export { default as FilePreviewModal } from './file-preview-modal.vue';
+export { useFilePreview } from './use-file-preview';
+export type { FilePreviewModalProps, PreviewFileType } from './typing';
diff --git a/src/components/file-preview/typing.ts b/src/components/file-preview/typing.ts
new file mode 100644
index 0000000..c9653cd
--- /dev/null
+++ b/src/components/file-preview/typing.ts
@@ -0,0 +1,10 @@
+import type { PreviewFileType } from '@vben/utils';
+
+export type { PreviewFileType };
+
+export interface FilePreviewModalProps {
+  fileUrl?: string;
+  fileName?: string;
+  visible?: boolean;
+  width?: number | string;
+}
diff --git a/src/components/file-preview/use-file-preview.ts b/src/components/file-preview/use-file-preview.ts
new file mode 100644
index 0000000..ca4fec0
--- /dev/null
+++ b/src/components/file-preview/use-file-preview.ts
@@ -0,0 +1,34 @@
+import type { Ref } from 'vue';
+
+import { computed, ref } from 'vue';
+
+import { getFileNameFromUrl, getFileType } from '@vben/utils';
+
+export function useFilePreview() {
+  const visible = ref(false);
+  const fileUrl = ref('');
+  const fileName = ref('');
+
+  const fileType = computed(() => getFileType(fileName.value || fileUrl.value));
+
+  function openPreview(url: string, name?: string) {
+    fileUrl.value = url;
+    fileName.value = name || getFileNameFromUrl(url);
+    visible.value = true;
+  }
+
+  function closePreview() {
+    visible.value = false;
+  }
+
+  return {
+    closePreview,
+    openPreview,
+    previewState: {
+      fileUrl: fileUrl as Ref<string>,
+      fileName: fileName as Ref<string>,
+      fileType,
+      visible: visible as Ref<boolean>,
+    },
+  };
+}
diff --git a/src/packages/@core/base/shared/src/utils/upload.ts b/src/packages/@core/base/shared/src/utils/upload.ts
index 57718b4..e25e83a 100644
--- a/src/packages/@core/base/shared/src/utils/upload.ts
+++ b/src/packages/@core/base/shared/src/utils/upload.ts
@@ -196,6 +196,34 @@
 }
 
 /**
+ * 棰勮鏂囦欢绫诲瀷
+ */
+export type PreviewFileType =
+  | 'docx'
+  | 'excel'
+  | 'image'
+  | 'pdf'
+  | 'pptx'
+  | 'unknown';
+
+/**
+ * 鏍规嵁鏂囦欢鍚嶅垽鏂瑙堟枃浠剁被鍨�
+ *
+ * @param filename 鏂囦欢鍚嶆垨 URL
+ * @returns 棰勮鏂囦欢绫诲瀷
+ */
+export function getFileType(filename: null | string | undefined): PreviewFileType {
+  if (!filename) return 'unknown';
+  const ext = filename.split('.').pop()?.toLowerCase() || '';
+  if (isImage(ext)) return 'image';
+  if (ext === 'pdf') return 'pdf';
+  if (['doc', 'docx'].includes(ext)) return 'docx';
+  if (['xls', 'xlsx'].includes(ext)) return 'excel';
+  if (['ppt', 'pptx'].includes(ext)) return 'pptx';
+  return 'unknown';
+}
+
+/**
  * 鑾峰彇鏂囦欢鍥炬爣锛圠ucide Icons锛�
  *
  * @param filename 鏂囦欢鍚�
diff --git a/src/router/routes/modules/bpm.ts b/src/router/routes/modules/bpm.ts
index bd4a7a2..c97f4d7 100644
--- a/src/router/routes/modules/bpm.ts
+++ b/src/router/routes/modules/bpm.ts
@@ -2,6 +2,26 @@
 
 const routes: RouteRecordRaw[] = [
   {
+    path: '/bpm/process-instance/detail',
+    component: () => import('#/views/bpm/processInstance/detail/index.vue'),
+    name: 'BpmProcessInstanceDetail',
+    meta: {
+      title: '娴佺▼璇︽儏',
+      activePath: '/bpm/task/my',
+      icon: 'ant-design:history-outlined',
+      keepAlive: false,
+      hideInMenu: true,
+      noBasicLayout: false,
+    },
+    props: (route) => {
+      return {
+        id: route.query.id,
+        taskId: route.query.taskId,
+        activityId: route.query.activityId,
+      };
+    },
+  },
+  {
     path: '/bpm',
     name: 'bpm',
     meta: {
@@ -9,25 +29,6 @@
       hideInMenu: true,
     },
     children: [
-      {
-        path: 'process-instance/detail',
-        component: () => import('#/views/bpm/processInstance/detail/index.vue'),
-        name: 'BpmProcessInstanceDetail',
-        meta: {
-          title: '娴佺▼璇︽儏',
-          activePath: '/bpm/task/my',
-          icon: 'ant-design:history-outlined',
-          keepAlive: false,
-          hideInMenu: true,
-        },
-        props: (route) => {
-          return {
-            id: route.query.id,
-            taskId: route.query.taskId,
-            activityId: route.query.activityId,
-          };
-        },
-      },
       {
         path: '/bpm/manager/form/edit',
         name: 'BpmFormEditor',
diff --git a/src/types/vue-office.d.ts b/src/types/vue-office.d.ts
new file mode 100644
index 0000000..a7a6426
--- /dev/null
+++ b/src/types/vue-office.d.ts
@@ -0,0 +1,33 @@
+declare module '@vue-office/pdf' {
+  import type { DefineComponent } from 'vue';
+  const VueOfficePdf: DefineComponent<{
+    src: string;
+  }>;
+  export default VueOfficePdf;
+}
+
+declare module '@vue-office/docx' {
+  import type { DefineComponent } from 'vue';
+  const VueOfficeDocx: DefineComponent<{
+    src: string;
+  }>;
+  export default VueOfficeDocx;
+}
+
+declare module '@vue-office/excel' {
+  import type { DefineComponent } from 'vue';
+  const VueOfficeExcel: DefineComponent<{
+    src: string;
+  }>;
+  export default VueOfficeExcel;
+}
+
+declare module '@vue-office/pptx' {
+  import type { DefineComponent } from 'vue';
+  const VueOfficePptx: DefineComponent<{
+    src: string;
+  }>;
+  export default VueOfficePptx;
+}
+
+declare module '@vue-office/excel/lib/index.css';
diff --git a/src/views/ai/chat/index/modules/message/files.vue b/src/views/ai/chat/index/modules/message/files.vue
index ed163a0..5cbc411 100644
--- a/src/views/ai/chat/index/modules/message/files.vue
+++ b/src/views/ai/chat/index/modules/message/files.vue
@@ -2,11 +2,15 @@
 import { computed } from 'vue';
 
 import { IconifyIcon } from '@vben/icons';
-import { getFileIcon, getFileNameFromUrl, getFileTypeClass } from '@vben/utils';
+import { getFileIcon, getFileNameFromUrl, getFileTypeClass, isImage } from '@vben/utils';
+
+import { FilePreviewModal, useFilePreview } from '#/components/file-preview';
 
 const props = defineProps<{
   attachmentUrls?: string[];
 }>();
+
+const { openPreview, previewState } = useFilePreview();
 
 /** 杩囨护鎺夌┖鍊肩殑闄勪欢鍒楄〃 */
 const validAttachmentUrls = computed(() => {
@@ -15,7 +19,11 @@
 
 /** 鐐瑰嚮鏂囦欢 */
 function handleFileClick(url: string) {
-  window.open(url, '_blank');
+  if (isImage(url)) {
+    window.open(url, '_blank');
+  } else {
+    openPreview(url);
+  }
 }
 </script>
 
@@ -50,4 +58,9 @@
       </div>
     </div>
   </div>
+  <FilePreviewModal
+    v-model:visible="previewState.visible.value"
+    :file-url="previewState.fileUrl.value"
+    :file-name="previewState.fileName.value"
+  />
 </template>
diff --git a/src/views/bpm/processInstance/detail/modules/operation-button.vue b/src/views/bpm/processInstance/detail/modules/operation-button.vue
index b955d7a..248bc1d 100644
--- a/src/views/bpm/processInstance/detail/modules/operation-button.vue
+++ b/src/views/bpm/processInstance/detail/modules/operation-button.vue
@@ -23,6 +23,8 @@
 import { useUserStore } from '@vben/stores';
 import { isEmpty } from '@vben/utils';
 
+import { FilePreviewModal, useFilePreview } from '#/components/file-preview';
+
 import FormCreate from '@form-create/ant-design-vue';
 import { until, useDebounceFn } from '@vueuse/core';
 import {
@@ -789,6 +791,8 @@
 const imagePreviewVisible = ref(false);
 const imagePreviewUrl = ref('');
 
+const { openPreview: openFilePreview, previewState: filePreviewState } = useFilePreview();
+
 /** 鍒ゆ柇鏂囦欢鏄惁涓哄浘鐗囩被鍨� */
 function isImageUrl(url: string) {
   return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(
@@ -811,7 +815,7 @@
     imagePreviewUrl.value = url;
     imagePreviewVisible.value = true;
   } else {
-    window.open(url, '_blank');
+    openFilePreview(url, file?.name);
   }
 }
 
@@ -1552,4 +1556,11 @@
       :src="imagePreviewUrl"
     />
   </div>
+
+  <!-- 鏂囦欢棰勮锛圥DF/Word/Excel/PPT锛� -->
+  <FilePreviewModal
+    v-model:visible="filePreviewState.visible.value"
+    :file-url="filePreviewState.fileUrl.value"
+    :file-name="filePreviewState.fileName.value"
+  />
 </template>
diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue
index 1f5612b..9f3ccff 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -11,7 +11,7 @@
   DICT_TYPE,
 } from '@vben/constants';
 
-import { Button, message, Textarea } from 'ant-design-vue';
+import { Button, message, Textarea, Tooltip } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import { getProcessDefinition } from '#/api/bpm/definition';
@@ -128,16 +128,27 @@
 <template>
   <Page auto-content-height><Grid table-title="娴佺▼鐘舵��">
       <template #slot-summary="{ row }">
-        <div
-          class="flex flex-col py-2"
+        <Tooltip
           v-if="row.summary && row.summary.length > 0"
+          placement="topLeft"
         >
-          <div v-for="(item, index) in row.summary" :key="index">
-            <span class="text-gray-500">
+          <template #title>
+            <div v-for="(item, index) in row.summary" :key="index">
               {{ item.key }} : {{ item.value }}
+            </div>
+          </template>
+          <div class="truncate max-w-[200px] cursor-pointer">
+            <span
+              v-for="(item, index) in row.summary"
+              :key="index"
+            >
+              <span class="text-gray-500">
+                {{ item.key }} : {{ item.value }}
+              </span>
+              <span v-if="index < row.summary.length - 1" class="mx-1 text-gray-300">|</span>
             </span>
           </div>
-        </div>
+        </Tooltip>
         <div v-else>-</div>
       </template>
       <template #slot-status="{ row }">
diff --git a/src/views/erp/purchase/order/data.ts b/src/views/erp/purchase/order/data.ts
index 361ba18..761cbad 100644
--- a/src/views/erp/purchase/order/data.ts
+++ b/src/views/erp/purchase/order/data.ts
@@ -1,15 +1,17 @@
 import type { VbenFormSchema } from '#/adapter/form';
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 
+import { markRaw } from 'vue';
+
 import { DICT_TYPE } from '@vben/constants';
 import { getDictOptions } from '@vben/hooks';
 import { erpPriceInputFormatter } from '@vben/utils';
 
 import { z } from '#/adapter/form';
 import { getAccountSimpleList } from '#/api/erp/finance/account';
-import { getProductSimpleList } from '#/api/erp/product/product';
 import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
 import { getSimpleUserList } from '#/api/system/user';
+import { MdmItemSelect } from '#/views/basicData/mdm/components';
 import { getRangePickerDefaultProps } from '#/utils';
 
 /** 琛ㄥ崟绫诲瀷 */
@@ -200,6 +202,13 @@
       slots: { default: 'remark' },
     },
     {
+      field: 'qcCheckFlag',
+      title: '鏄惁璐ㄦ',
+      minWidth: 80,
+      fixed: 'right',
+      slots: { default: 'qcCheckFlag' },
+    },
+    {
       field: 'count',
       title: '鏁伴噺',
       minWidth: 120,
@@ -266,14 +275,9 @@
     {
       fieldName: 'productId',
       label: '浜у搧',
-      component: 'ApiSelect',
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧',
-        allowClear: true,
-        showSearch: true,
-        api: getProductSimpleList,
-        labelField: 'name',
-        valueField: 'id',
       },
     },
     {
diff --git a/src/views/erp/purchase/order/modules/item-form.vue b/src/views/erp/purchase/order/modules/item-form.vue
index 37b3d5f..f4565b5 100644
--- a/src/views/erp/purchase/order/modules/item-form.vue
+++ b/src/views/erp/purchase/order/modules/item-form.vue
@@ -10,7 +10,7 @@
   erpPriceMultiply,
 } from '@vben/utils';
 
-import { Input, InputNumber, Select } from 'ant-design-vue';
+import { Input, InputNumber, Select, Switch } from 'ant-design-vue';
 
 import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import { getItemPage } from '#/api/mdm/item';
@@ -134,6 +134,7 @@
     taxPrice: undefined,
     totalPrice: undefined,
     remark: undefined,
+    qcCheckFlag: false,
   };
   tableData.value.push(newRow);
   // 閫氱煡鐖剁粍浠舵洿鏂�
@@ -261,6 +262,16 @@
       <Input v-if="!disabled" v-model:value="row.remark" class="w-full" />
       <span v-else>{{ row.remark || '-' }}</span>
     </template>
+    <template #qcCheckFlag="{ row }">
+      <Switch
+        v-if="!disabled"
+        v-model:checked="row.qcCheckFlag"
+        checked-children="鏄�"
+        un-checked-children="鍚�"
+        @change="handleRowChange(row)"
+      />
+      <span v-else>{{ row.qcCheckFlag ? '鏄�' : '鍚�' }}</span>
+    </template>
     <template #taxPercent="{ row }">
       <InputNumber
         v-if="!disabled"
diff --git a/src/views/mes/qc/iqc/data.ts b/src/views/mes/qc/iqc/data.ts
index 7b5b3ee..0a77b97 100644
--- a/src/views/mes/qc/iqc/data.ts
+++ b/src/views/mes/qc/iqc/data.ts
@@ -13,7 +13,7 @@
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
 import { getSimpleUserList } from '#/api/system/user';
 import { getRangePickerDefaultProps } from '#/utils';
-import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdmItemSelect } from '#/views/basicData/mdm/components';
 import { MdVendorSelect } from '#/views/mes/md/vendor/components';
 
 /** 琛ㄥ崟绫诲瀷 */
@@ -120,7 +120,7 @@
     {
       fieldName: 'itemId',
       label: '浜у搧鐗╂枡',
-      component: markRaw(MdItemSelect),
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
       },
@@ -291,7 +291,7 @@
     {
       fieldName: 'itemId',
       label: '浜у搧鐗╂枡',
-      component: markRaw(MdItemSelect),
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
       },
diff --git a/src/views/mes/qc/oqc/data.ts b/src/views/mes/qc/oqc/data.ts
index f0a8f81..08ddd90 100644
--- a/src/views/mes/qc/oqc/data.ts
+++ b/src/views/mes/qc/oqc/data.ts
@@ -13,7 +13,7 @@
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
 import { getSimpleUserList } from '#/api/system/user';
 import { MdClientSelect } from '#/views/mes/md/client/components';
-import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdmItemSelect } from '#/views/basicData/mdm/components';
 
 /** 琛ㄥ崟绫诲瀷 */
 export type FormType = 'create' | 'detail' | 'update';
@@ -119,7 +119,7 @@
     {
       fieldName: 'itemId',
       label: '浜у搧鐗╂枡',
-      component: markRaw(MdItemSelect),
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
       },
@@ -324,7 +324,7 @@
     {
       fieldName: 'itemId',
       label: '浜у搧鐗╂枡',
-      component: markRaw(MdItemSelect),
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
       },
diff --git a/src/views/mes/qc/rqc/data.ts b/src/views/mes/qc/rqc/data.ts
index dde09ff..4d08cbf 100644
--- a/src/views/mes/qc/rqc/data.ts
+++ b/src/views/mes/qc/rqc/data.ts
@@ -16,7 +16,7 @@
 
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
 import { getSimpleUserList } from '#/api/system/user';
-import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdmItemSelect } from '#/views/basicData/mdm/components';
 
 /** 琛ㄥ崟绫诲瀷 */
 export type FormType = 'create' | 'detail' | 'update';
@@ -144,7 +144,7 @@
     {
       fieldName: 'itemId',
       label: '浜у搧鐗╂枡',
-      component: markRaw(MdItemSelect),
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
       },
@@ -293,7 +293,7 @@
     {
       fieldName: 'itemId',
       label: '浜у搧鐗╂枡',
-      component: markRaw(MdItemSelect),
+      component: markRaw(MdmItemSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
       },
diff --git a/src/views/wls/arrivalnotice/data.ts b/src/views/wls/arrivalnotice/data.ts
index 238646d..136ed26 100644
--- a/src/views/wls/arrivalnotice/data.ts
+++ b/src/views/wls/arrivalnotice/data.ts
@@ -315,6 +315,7 @@
       field: 'iqcCode',
       title: '妫�楠屽崟鍙�',
       minWidth: 140,
+      slots: { default: 'iqcCode' },
     },
     {
       field: 'remark',
diff --git a/src/views/wls/arrivalnotice/modules/line-list.vue b/src/views/wls/arrivalnotice/modules/line-list.vue
index a48316f..29a152a 100644
--- a/src/views/wls/arrivalnotice/modules/line-list.vue
+++ b/src/views/wls/arrivalnotice/modules/line-list.vue
@@ -6,9 +6,11 @@
 
 import { computed } from 'vue';
 
+import { useRouter } from 'vue-router';
+
 import { useVbenModal } from '@vben/common-ui';
 
-import { message } from 'ant-design-vue';
+import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import {
@@ -24,6 +26,8 @@
   formType: FormType;
   noticeId: number;
 }>();
+
+const router = useRouter();
 
 const isEditable = computed(() =>
   // 鏄惁鍙紪杈戞槑缁嗚
@@ -62,6 +66,13 @@
     handleRefresh();
   } finally {
     hideLoading();
+  }
+}
+
+/** 璺宠浆 IQC 妫�楠屽崟璇︽儏 */
+function handleOpenIqc(row: MesWmArrivalNoticeLineApi.ArrivalNoticeLine) {
+  if (row.iqcId) {
+    router.push({ path: '/mes/qc/iqc', query: { id: row.iqcId } });
   }
 }
 
@@ -111,6 +122,17 @@
           ]"
         />
       </template>
+      <template #iqcCode="{ row }">
+        <Button
+          v-if="row.iqcCode"
+          size="small"
+          type="link"
+          @click="handleOpenIqc(row)"
+        >
+          {{ row.iqcCode }}
+        </Button>
+        <span v-else>-</span>
+      </template>
       <template #actions="{ row }">
         <TableAction
           :actions="[

--
Gitblit v1.9.3