银川
1.采购申请页面修改
2.添加在线查看附件预览功能
3.修改我的流程页面展示样式流程状态点击报错问题
已添加5个文件
已修改16个文件
533 ■■■■■ 文件已修改
package.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-workspace.yaml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/purchase/order/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/file-preview/file-preview-modal.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/file-preview/index.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/file-preview/typing.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/file-preview/use-file-preview.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/@core/base/shared/src/utils/upload.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes/modules/bpm.ts 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/vue-office.d.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/chat/index/modules/message/files.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/processInstance/detail/modules/operation-button.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/processInstance/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/data.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/modules/item-form.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/iqc/data.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/oqc/data.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/rqc/data.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/data.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/modules/line-list.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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:",
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:
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
src/api/erp/purchase/order/index.ts
@@ -50,6 +50,7 @@
    totalTaxPrice?: number; // å«ç¨Žæ€»ä»·ï¼Œå•位:元
    remark?: string; // å¤‡æ³¨
    stockCount?: number; // åº“存数量(显示字段)
    qcCheckFlag?: boolean; // æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒ
  }
}
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>
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';
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;
}
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>,
    },
  };
}
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';
}
/**
 * èŽ·å–æ–‡ä»¶å›¾æ ‡ï¼ˆLucide Icons)
 *
 * @param filename æ–‡ä»¶å
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',
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';
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>
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>
  <!-- æ–‡ä»¶é¢„览(PDF/Word/Excel/PPT) -->
  <FilePreviewModal
    v-model:visible="filePreviewState.visible.value"
    :file-url="filePreviewState.fileUrl.value"
    :file-name="filePreviewState.fileName.value"
  />
</template>
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 }">
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',
      },
    },
    {
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"
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: '请选择产品物料',
      },
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: '请选择产品物料',
      },
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: '请选择产品物料',
      },
src/views/wls/arrivalnotice/data.ts
@@ -315,6 +315,7 @@
      field: 'iqcCode',
      title: '检验单号',
      minWidth: 140,
      slots: { default: 'iqcCode' },
    },
    {
      field: 'remark',
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="[