已复制2个文件
已添加63个文件
已重命名1个文件
已修改16个文件
已删除13个文件
| | |
| | | "allow": [ |
| | | "WebFetch(domain:github.com)", |
| | | "Bash(gh repo *)", |
| | | "WebSearch" |
| | | "WebSearch", |
| | | "PowerShell(pnpm typecheck *)", |
| | | "PowerShell(pnpm dev *)", |
| | | "PowerShell(Get-ChildItem -Path \"D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\\\\src\\\\views\\\\mes\" -Directory -Recurse | Where-Object { $_.GetDirectories\\(\\).Count -gt 0 -or $_.Parent.Name -in @\\('mes','md','pro','process-design','cal','dv','qc','wm','tm','pd'\\) } | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Substring\\(66\\) })", |
| | | "PowerShell(cd \"D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\"; Get-ChildItem -Path \"src/views/mes\" -Directory -Recurse | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace\\('D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\\\\', ''\\) })", |
| | | "PowerShell(cd \"D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\"; Get-ChildItem -Path \"src/api/mes\" -Recurse -File | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace\\('D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\\\\', ''\\) })", |
| | | "PowerShell(cd \"D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\"; Get-ChildItem -Path \"src/router\" -Recurse -File | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace\\('D:\\\\å·¥ä½\\\\è¯å¯¼å
¬å¸\\\\项ç®\\\\è¿éå\\\\mom-pro2-before\\\\', ''\\) })" |
| | | ] |
| | | } |
| | | } |
| | |
| | | VITE_APP_DEFAULT_USERNAME=admin |
| | | # é»è®¤ç»å½å¯ç |
| | | VITE_APP_DEFAULT_PASSWORD=admin123 |
| | | |
| | | # 产åè®¾è®¡æ¨¡åæ¬å° Mockï¼æ åç«¯æ¶æ¼ç¤ºï¼ |
| | | VITE_PD_MOCK=true |
| | |
| | | 2. ä½¿ç¨ VeeValidate + Zod è¿è¡è¡¨åéªè¯ |
| | | 3. ä½¿ç¨ `v-dompurify-html` å¤çç¨æ·è¾å
¥ç HTML |
| | | 4. æææ°æ®ä½¿ç¨ `secure-ls` å å¯åå¨ |
| | | |
| | | ## MES 模åå¼å |
| | | |
| | | > **éè¦**ï¼å¼å MES 模ååï¼å¿
é¡»å
é
读 `MES_DEVELOPMENT.md`ï¼è¯¥æä»¶å
å«ï¼ |
| | | > - é¡µé¢æä»¶ç»æè§èï¼data.ts / index.vue / form.vueï¼ |
| | | > - API æ¥å£ç¼åæ¨¡æ¿ |
| | | > - 导å
¥è·¯å¾å±çº§è§åï¼index.vue ç¨ 4 å± `..`ï¼form.vue ç¨ 5 å± `..`ï¼ |
| | | > - åå
¸/æä¸¾æ³¨åä½ç½® |
| | | > - è·¯ç±è§åï¼å¨æè·¯ç±ï¼æ éæå¨é
ç½®ï¼ |
| | | > - Select/SelectDialog ç»ä»¶æ¨¡å¼ |
| | | > - å¼åæ£æ¥æ¸
å |
| | |
| | | |
| | | ```typescript |
| | | // â
æ£ç¡®ï¼ç»ä»¶æä»¶ä½¿ç¨ PascalCase |
| | | UserList.vue |
| | | DictTag.vue |
| | | TableAction.vue |
| | | UserList.vue; |
| | | DictTag.vue; |
| | | TableAction.vue; |
| | | |
| | | // â é误ï¼ä½¿ç¨ kebab-case æå
¶ä»å½å |
| | | user-list.vue |
| | | dict_tag.vue |
| | | user - list.vue; |
| | | dict_tag.vue; |
| | | ``` |
| | | |
| | | #### ç»ä»¶å®ä¹ |
| | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | // â
æ£ç¡®ï¼ç»ä¸ä½¿ç¨ <script setup lang="ts"> |
| | | defineOptions({ name: 'UserList' }); |
| | | defineOptions({ name: "UserList" }); |
| | | |
| | | // Props å®ä¹ |
| | | interface Props { |
| | |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | userName: '', |
| | | userName: "", |
| | | }); |
| | | |
| | | // Emits å®ä¹ |
| | | interface Emits { |
| | | (e: 'update', value: string): void; |
| | | (e: 'delete', id: number): void; |
| | | (e: "update", value: string): void; |
| | | (e: "delete", id: number): void; |
| | | } |
| | | |
| | | const emit = defineEmits<Emits>(); |
| | |
| | | } |
| | | |
| | | // â
æ£ç¡®ï¼ä½¿ç¨ import type 导å
¥ç±»å |
| | | import type { UserInfo } from '#/api/system/user'; |
| | | import type { UserInfo } from "#/api/system/user"; |
| | | |
| | | // â éè¯¯ï¼æ®é导å
¥ç±»å |
| | | import { UserInfo } from '#/api/system/user'; |
| | | import { UserInfo } from "#/api/system/user"; |
| | | ``` |
| | | |
| | | ### 1.2 ç®å½ç»æè§è |
| | |
| | | ```typescript |
| | | // src/api/system/user/index.ts |
| | | |
| | | import { request } from '#/utils/request'; |
| | | import { request } from "#/utils/request"; |
| | | |
| | | export namespace SystemUserApi { |
| | | /** ç¨æ·ä¿¡æ¯ */ |
| | |
| | | |
| | | /** è·åç¨æ·å页å表 */ |
| | | export function getUserPage(params: SystemUserApi.PageParams) { |
| | | return request.get<SystemUserApi.PageResult>('/system/user/page', { params }); |
| | | return request.get<SystemUserApi.PageResult>("/system/user/page", { params }); |
| | | } |
| | | |
| | | /** è·åç¨æ·è¯¦æ
*/ |
| | |
| | | |
| | | /** åå»ºç¨æ· */ |
| | | export function createUser(data: SystemUserApi.User) { |
| | | return request.post('/system/user/create', data); |
| | | return request.post("/system/user/create", data); |
| | | } |
| | | |
| | | /** æ´æ°ç¨æ· */ |
| | | export function updateUser(data: SystemUserApi.User) { |
| | | return request.put('/system/user/update', data); |
| | | return request.put("/system/user/update", data); |
| | | } |
| | | |
| | | /** å é¤ç¨æ· */ |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SystemUserApi } from '#/api/system/user'; |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { SystemUserApi } from "#/api/system/user"; |
| | | |
| | | import { useVbenVxeGrid, TableAction, ACTION_ICON } from '#/adapter/vxe-table'; |
| | | import { getUserPage } from '#/api/system/user'; |
| | | import { useVbenVxeGrid, TableAction, ACTION_ICON } from "#/adapter/vxe-table"; |
| | | import { getUserPage } from "#/api/system/user"; |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const columns: VxeTableGridOptions['columns'] = [ |
| | | { type: 'checkbox', width: 50 }, |
| | | { field: 'username', title: 'ç¨æ·å', width: 120 }, |
| | | { field: 'nickname', title: 'æµç§°', width: 120 }, |
| | | const columns: VxeTableGridOptions["columns"] = [ |
| | | { type: "checkbox", width: 50 }, |
| | | { field: "username", title: "ç¨æ·å", width: 120 }, |
| | | { field: "nickname", title: "æµç§°", width: 120 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | field: "status", |
| | | title: "ç¶æ", |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: 'common_status' }, |
| | | name: "CellDict", |
| | | props: { type: "common_status" }, |
| | | }, |
| | | }, |
| | | { field: 'createTime', title: 'å建æ¶é´', width: 180 }, |
| | | { field: 'action', title: 'æä½', width: 200, slots: { default: 'actions' } }, |
| | | { field: "createTime", title: "å建æ¶é´", width: 180 }, |
| | | { field: "action", title: "æä½", width: 200, slots: { default: "actions" } }, |
| | | ]; |
| | | |
| | | // è¡¨æ ¼å®ä¾ |
| | |
| | | formOptions: { |
| | | schema: [ |
| | | { |
| | | fieldName: 'username', |
| | | label: 'ç¨æ·å', |
| | | component: 'Input', |
| | | fieldName: "username", |
| | | label: "ç¨æ·å", |
| | | component: "Input", |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | fieldName: "status", |
| | | label: "ç¶æ", |
| | | component: "Select", |
| | | componentProps: { |
| | | options: getDictOptions('common_status'), |
| | | options: getDictOptions("common_status"), |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | gridOptions: { |
| | | columns, |
| | | height: 'auto', |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | keyField: "id", |
| | | isHover: true, |
| | | }, |
| | | } as VxeTableGridOptions<SystemUserApi.User>, |
| | |
| | | |
| | | #### è¡¨æ ¼åæ¸²æå¨ |
| | | |
| | | | 渲æå¨ | ç¨é | ç¤ºä¾ | |
| | | |--------|------|------| |
| | | | `CellImage` | åå¾å±ç¤º | `cellRender: { name: 'CellImage' }` | |
| | | | `CellImages` | å¤å¾å±ç¤º | `cellRender: { name: 'CellImages' }` | |
| | | | `CellLink` | 龿¥æé® | `cellRender: { name: 'CellLink', props: { text: 'æ¥ç' } }` | |
| | | | `CellTag` | æ ç¾å±ç¤º | `cellRender: { name: 'CellTag', props: { color: 'blue' } }` | |
| | | | `CellDict` | åå
¸æ ç¾ | `cellRender: { name: 'CellDict', props: { type: 'common_status' } }` | |
| | | | `CellSwitch` | å¼å
³åæ¢ | `cellRender: { name: 'CellSwitch', attrs: { beforeChange } }` | |
| | | | 渲æå¨ | ç¨é | ç¤ºä¾ | |
| | | | --------------- | -------- | -------------------------------------------------------------------- | |
| | | | `CellImage` | åå¾å±ç¤º | `cellRender: { name: 'CellImage' }` | |
| | | | `CellImages` | å¤å¾å±ç¤º | `cellRender: { name: 'CellImages' }` | |
| | | | `CellLink` | 龿¥æé® | `cellRender: { name: 'CellLink', props: { text: 'æ¥ç' } }` | |
| | | | `CellTag` | æ ç¾å±ç¤º | `cellRender: { name: 'CellTag', props: { color: 'blue' } }` | |
| | | | `CellDict` | åå
¸æ ç¾ | `cellRender: { name: 'CellDict', props: { type: 'common_status' } }` | |
| | | | `CellSwitch` | å¼å
³åæ¢ | `cellRender: { name: 'CellSwitch', attrs: { beforeChange } }` | |
| | | | `CellOperation` | æä½æé® | `cellRender: { name: 'CellOperation', options: ['edit', 'delete'] }` | |
| | | |
| | | ### 2.2 表åå¼¹çª (useVbenModal) |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import Form from './modules/form.vue'; |
| | | import { useVbenModal } from "#/packages/effects/common-ui/src"; |
| | | import Form from "./modules/form.vue"; |
| | | |
| | | // å¼¹çªå®ä¾ |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | |
| | | ```vue |
| | | <!-- modules/form.vue --> |
| | | <script lang="ts" setup> |
| | | import type { SystemUserApi } from '#/api/system/user'; |
| | | import type { SystemUserApi } from "#/api/system/user"; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createUser, updateUser } from '#/api/system/user'; |
| | | import { computed, ref } from "vue"; |
| | | import { useVbenModal } from "#/packages/effects/common-ui/src"; |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { createUser, updateUser } from "#/api/system/user"; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const emit = defineEmits(["success"]); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | |
| | | } else { |
| | | await createUser(values); |
| | | } |
| | | emit('success'); |
| | | emit("success"); |
| | | modalApi.close(); |
| | | }, |
| | | }); |
| | |
| | | const [Form, formApi] = useVbenForm({ |
| | | schema: [ |
| | | { |
| | | fieldName: 'username', |
| | | label: 'ç¨æ·å', |
| | | rules: 'required', |
| | | component: 'Input', |
| | | fieldName: "username", |
| | | label: "ç¨æ·å", |
| | | rules: "required", |
| | | component: "Input", |
| | | }, |
| | | { |
| | | fieldName: 'nickname', |
| | | label: 'æµç§°', |
| | | rules: 'required', |
| | | component: 'Input', |
| | | fieldName: "nickname", |
| | | label: "æµç§°", |
| | | rules: "required", |
| | | component: "Input", |
| | | }, |
| | | { |
| | | fieldName: 'email', |
| | | label: 'é®ç®±', |
| | | rules: 'email', |
| | | component: 'Input', |
| | | fieldName: "email", |
| | | label: "é®ç®±", |
| | | rules: "email", |
| | | component: "Input", |
| | | }, |
| | | { |
| | | fieldName: 'deptId', |
| | | label: 'é¨é¨', |
| | | component: 'TreeSelect', |
| | | fieldName: "deptId", |
| | | label: "é¨é¨", |
| | | component: "TreeSelect", |
| | | }, |
| | | ], |
| | | }); |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import DictTag from '#/components/dict-tag/dict-tag.vue'; |
| | | import DictTag from "#/components/dict-tag/dict-tag.vue"; |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import DictSelect from '#/components/form-create/components/dict-select.vue'; |
| | | import DictSelect from "#/components/form-create/components/dict-select.vue"; |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | #### åå
¸å·¥å
·å½æ° |
| | | |
| | | ```typescript |
| | | import { getDictOptions, getDictLabel, getDictObj } from '#/packages/effects/hooks/src'; |
| | | import { |
| | | getDictOptions, |
| | | getDictLabel, |
| | | getDictObj, |
| | | } from "#/packages/effects/hooks/src"; |
| | | |
| | | // è·ååå
¸é项å表ï¼ç¨äº Select/Radio çç»ä»¶ï¼ |
| | | const options = getDictOptions('common_status', 'number'); |
| | | const options = getDictOptions("common_status", "number"); |
| | | |
| | | // è·ååå
¸æ ç¾ææ¬ |
| | | const label = getDictLabel('common_status', 1); // "å¼å¯" |
| | | const label = getDictLabel("common_status", 1); // "å¼å¯" |
| | | |
| | | // è·ååå
¸å®æ´å¯¹è±¡ |
| | | const dictObj = getDictObj('common_status', 1); |
| | | const dictObj = getDictObj("common_status", 1); |
| | | // { label: 'å¼å¯', value: 1, colorType: 'success', cssClass: '' } |
| | | ``` |
| | | |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import { TableAction, ACTION_ICON } from '#/adapter/vxe-table'; |
| | | import { TableAction, ACTION_ICON } from "#/adapter/vxe-table"; |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import ImageUpload from '#/components/upload/image-upload.vue'; |
| | | import ImageUpload from "#/components/upload/image-upload.vue"; |
| | | |
| | | const imageUrl = ref(''); |
| | | const imageUrl = ref(""); |
| | | const imageList = ref<string[]>([]); |
| | | </script> |
| | | |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import FileUpload from '#/components/upload/file-upload.vue'; |
| | | import FileUpload from "#/components/upload/file-upload.vue"; |
| | | |
| | | const fileList = ref<string[]>([]); |
| | | </script> |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import Description from '#/components/description/description.vue'; |
| | | import Description from "#/components/description/description.vue"; |
| | | |
| | | const data = { |
| | | username: 'admin', |
| | | nickname: '管çå', |
| | | email: 'admin@example.com', |
| | | username: "admin", |
| | | nickname: "管çå", |
| | | email: "admin@example.com", |
| | | status: 0, |
| | | createTime: '2024-01-01 12:00:00', |
| | | createTime: "2024-01-01 12:00:00", |
| | | }; |
| | | |
| | | const schema = [ |
| | | { field: 'username', label: 'ç¨æ·å' }, |
| | | { field: 'nickname', label: 'æµç§°' }, |
| | | { field: 'email', label: 'é®ç®±' }, |
| | | { field: "username", label: "ç¨æ·å" }, |
| | | { field: "nickname", label: "æµç§°" }, |
| | | { field: "email", label: "é®ç®±" }, |
| | | { |
| | | field: 'status', |
| | | label: 'ç¶æ', |
| | | render: (value) => h(DictTag, { type: 'common_status', value }), |
| | | field: "status", |
| | | label: "ç¶æ", |
| | | render: (value) => h(DictTag, { type: "common_status", value }), |
| | | }, |
| | | { field: 'createTime', label: 'å建æ¶é´' }, |
| | | { field: "createTime", label: "å建æ¶é´" }, |
| | | ]; |
| | | </script> |
| | | |
| | |
| | | |
| | | #### å¸¸ç¨ Store |
| | | |
| | | | Store | ç¨é | 导å
¥è·¯å¾ | |
| | | |-------|------|----------| |
| | | | `useUserStore` | ç¨æ·ä¿¡æ¯ | `#/packages/stores/src` | |
| | | | Store | ç¨é | 导å
¥è·¯å¾ | |
| | | | ---------------- | ---------- | ----------------------- | |
| | | | `useUserStore` | ç¨æ·ä¿¡æ¯ | `#/packages/stores/src` | |
| | | | `useAccessStore` | æé/Token | `#/packages/stores/src` | |
| | | | `useDictStore` | æ°æ®åå
¸ | `#/packages/stores/src` | |
| | | | `useDictStore` | æ°æ®åå
¸ | `#/packages/stores/src` | |
| | | |
| | | #### ç¤ºä¾ |
| | | |
| | | ```typescript |
| | | import { useUserStore, useAccessStore } from '#/packages/stores/src'; |
| | | import { useUserStore, useAccessStore } from "#/packages/stores/src"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const accessStore = useAccessStore(); |
| | |
| | | |
| | | // æ£æ¥æé |
| | | const { hasAccessByCodes } = useAccess(); |
| | | const canEdit = hasAccessByCodes(['system:user:update']); |
| | | const canEdit = hasAccessByCodes(["system:user:update"]); |
| | | ``` |
| | | |
| | | --- |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import { useAccess } from '#/packages/effects/access/src'; |
| | | import { useAccess } from "#/packages/effects/access/src"; |
| | | |
| | | const { hasAccessByCodes } = useAccess(); |
| | | </script> |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import { $t } from '#/locales'; |
| | | import { $t } from "#/locales"; |
| | | </script> |
| | | |
| | | <template> |
| | | <Button>{{ $t('common.save') }}</Button> |
| | | <span>{{ $t('ui.actionMessage.deleteSuccess', ['ç¨æ·']) }}</span> |
| | | <Button>{{ $t("common.save") }}</Button> |
| | | <span>{{ $t("ui.actionMessage.deleteSuccess", ["ç¨æ·"]) }}</span> |
| | | </template> |
| | | ``` |
| | | |
| | |
| | | ### 6.1 usePagination - å页 |
| | | |
| | | ```typescript |
| | | import { usePagination } from '#/packages/effects/hooks/src'; |
| | | import { usePagination } from "#/packages/effects/hooks/src"; |
| | | |
| | | const { page, pageSize, total, setPage, setTotal } = usePagination(); |
| | | ``` |
| | |
| | | ### 6.2 useTabs - æ ç¾é¡µæä½ |
| | | |
| | | ```typescript |
| | | import { useTabs } from '#/packages/effects/hooks/src'; |
| | | import { useTabs } from "#/packages/effects/hooks/src"; |
| | | |
| | | const { closeOtherTabs, closeTab, refreshTab } = useTabs(); |
| | | |
| | |
| | | ### 6.3 useWatermark - æ°´å° |
| | | |
| | | ```typescript |
| | | import { useWatermark } from '#/packages/effects/hooks/src'; |
| | | import { useWatermark } from "#/packages/effects/hooks/src"; |
| | | |
| | | const { updateWatermark, destroyWatermark } = useWatermark(); |
| | | |
| | | // è®¾ç½®æ°´å° |
| | | await updateWatermark({ |
| | | content: 'ç¨æ·å', |
| | | content: "ç¨æ·å", |
| | | }); |
| | | |
| | | // éæ¯æ°´å° |
| | |
| | | |
| | | ```typescript |
| | | import { |
| | | formatDateTime, // æ¥ææ ¼å¼å |
| | | formatDate, // æ¥ææ ¼å¼åï¼ä¸å«æ¶é´ï¼ |
| | | formatDateTime, // æ¥ææ ¼å¼å |
| | | formatDate, // æ¥ææ ¼å¼åï¼ä¸å«æ¶é´ï¼ |
| | | downloadFileFromBlobPart, // æä»¶ä¸è½½ |
| | | isEmpty, // å¤ç©º |
| | | isFunction, // 夿彿° |
| | | isString, // 夿å符串 |
| | | } from '#/packages/utils/src'; |
| | | isEmpty, // å¤ç©º |
| | | isFunction, // 夿彿° |
| | | isString, // 夿å符串 |
| | | } from "#/packages/utils/src"; |
| | | |
| | | // æ¥ææ ¼å¼å |
| | | formatDateTime('2024-01-01T12:00:00'); // "2024-01-01 12:00:00" |
| | | formatDateTime("2024-01-01T12:00:00"); // "2024-01-01 12:00:00" |
| | | |
| | | // æä»¶ä¸è½½ |
| | | downloadFileFromBlobPart({ |
| | | fileName: 'ç¨æ·å表.xlsx', |
| | | fileName: "ç¨æ·å表.xlsx", |
| | | source: blobData, |
| | | }); |
| | | |
| | |
| | | |
| | | ```typescript |
| | | import { |
| | | erpNumberFormatter, // æ°åæ ¼å¼å |
| | | fenToYuan, // å转å
|
| | | formatFileSize, // æä»¶å¤§å°æ ¼å¼å |
| | | } from '#/packages/utils/src'; |
| | | erpNumberFormatter, // æ°åæ ¼å¼å |
| | | fenToYuan, // å转å
|
| | | formatFileSize, // æä»¶å¤§å°æ ¼å¼å |
| | | } from "#/packages/utils/src"; |
| | | |
| | | // ä¿ç两ä½å°æ° |
| | | erpNumberFormatter(1234.567, 2); // "1,234.57" |
| | |
| | | |
| | | ```vue |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SystemUserApi } from '#/api/system/user'; |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { SystemUserApi } from "#/api/system/user"; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { ref } from "vue"; |
| | | |
| | | import { confirm, Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '#/packages/utils/src'; |
| | | import { confirm, Page, useVbenModal } from "#/packages/effects/common-ui/src"; |
| | | import { getDictOptions } from "#/packages/effects/hooks/src"; |
| | | import { downloadFileFromBlobPart, isEmpty } from "#/packages/utils/src"; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message } from "ant-design-vue"; |
| | | |
| | | import { useVbenVxeGrid, TableAction, ACTION_ICON } from '#/adapter/vxe-table'; |
| | | import { useVbenVxeGrid, TableAction, ACTION_ICON } from "#/adapter/vxe-table"; |
| | | import { |
| | | getUserPage, |
| | | deleteUser, |
| | | createUser, |
| | | updateUser, |
| | | exportUser, |
| | | } from '#/api/system/user'; |
| | | import { $t } from '#/locales'; |
| | | } from "#/api/system/user"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import Form from './modules/form.vue'; |
| | | import Form from "./modules/form.vue"; |
| | | |
| | | // ========== å¼¹çªå®ä¹ ========== |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | |
| | | formOptions: { |
| | | schema: [ |
| | | { |
| | | fieldName: 'username', |
| | | label: 'ç¨æ·å', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç¨æ·å' }, |
| | | fieldName: "username", |
| | | label: "ç¨æ·å", |
| | | component: "Input", |
| | | componentProps: { placeholder: "请è¾å
¥ç¨æ·å" }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | fieldName: "status", |
| | | label: "ç¶æ", |
| | | component: "Select", |
| | | componentProps: { |
| | | options: getDictOptions('common_status', 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | options: getDictOptions("common_status", "number"), |
| | | placeholder: "è¯·éæ©ç¶æ", |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | gridOptions: { |
| | | columns: [ |
| | | { type: 'checkbox', width: 50 }, |
| | | { field: 'username', title: 'ç¨æ·å', width: 120 }, |
| | | { field: 'nickname', title: 'æµç§°', width: 120 }, |
| | | { type: "checkbox", width: 50 }, |
| | | { field: "username", title: "ç¨æ·å", width: 120 }, |
| | | { field: "nickname", title: "æµç§°", width: 120 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | field: "status", |
| | | title: "ç¶æ", |
| | | width: 100, |
| | | cellRender: { name: 'CellDict', props: { type: 'common_status' } }, |
| | | cellRender: { name: "CellDict", props: { type: "common_status" } }, |
| | | }, |
| | | { field: 'createTime', title: 'å建æ¶é´', width: 180 }, |
| | | { field: 'action', title: 'æä½', width: 180, slots: { default: 'actions' } }, |
| | | { field: "createTime", title: "å建æ¶é´", width: 180 }, |
| | | { |
| | | field: "action", |
| | | title: "æä½", |
| | | width: 180, |
| | | slots: { default: "actions" }, |
| | | }, |
| | | ], |
| | | height: 'auto', |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | rowConfig: { keyField: "id", isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<SystemUserApi.User>, |
| | | gridEvents: { |
| | |
| | | |
| | | async function handleExport() { |
| | | const data = await exportUser(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ç¨æ·.xls', source: data }); |
| | | downloadFileFromBlobPart({ fileName: "ç¨æ·.xls", source: data }); |
| | | } |
| | | |
| | | function handleCreate() { |
| | |
| | | |
| | | async function handleDelete(row: SystemUserApi.User) { |
| | | await deleteUser(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.username])); |
| | | message.success($t("ui.actionMessage.deleteSuccess", [row.username])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleDeleteBatch() { |
| | | await confirm($t('ui.actionMessage.deleteBatchConfirm')); |
| | | await confirm($t("ui.actionMessage.deleteBatchConfirm")); |
| | | // ... æ¹éå é¤é»è¾ |
| | | } |
| | | </script> |
| | |
| | | # ç©æä¸»æ°æ®æ¨¡å (MDM) åç«¯åæ´ææ¡£ |
| | | |
| | | > **åºåææ¡£**ï¼æ¬ææ¡£ä¸º MDM 模åç**ä¸å¡ä¸ API åæ´åºå**ã页é¢å®ç°æ¹å¼ï¼ç»ä»¶ãè¡¨æ ¼ã表åãç®å½ç»æï¼é¡»éµå¾ª [`FRONTEND_DEVELOPMENT.md`](./FRONTEND_DEVELOPMENT.md)ï¼æ¬ææ¡£ä¸ç Element Plus 示ä¾ä»
æè¿°ä¸å¡å段ä¸äº¤äºï¼ä¸å¯ç´æ¥ç
§æ¬ UI æ¡æ¶ã |
| | | |
| | | ## ä¸ãæ°å¢é¡µé¢ |
| | | |
| | | ### 1.1 ç©æä¸»æ°æ®ç®¡ç |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdArchiveApi { |
| | | /** æ°ååè®¾è®¡å½æ¡£å°è´¦ */ |
| | | export interface Archive { |
| | | id?: number; |
| | | code?: string; |
| | | projectId?: number; |
| | | projectCode?: string; |
| | | projectName?: string; |
| | | productCode?: string; |
| | | productName?: string; |
| | | version?: string; |
| | | archiveStatus?: number; |
| | | archiveTime?: number; |
| | | archiverId?: number; |
| | | archiverName?: string; |
| | | documentCount?: number; |
| | | bomCount?: number; |
| | | storagePath?: string; |
| | | remark?: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | projectCode?: string; |
| | | productCode?: string; |
| | | archiveStatus?: number; |
| | | } |
| | | } |
| | | |
| | | export function getPdArchivePage(params: MesPdArchiveApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getArchivePage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdArchiveApi.Archive>>( |
| | | '/mes/pd/archive/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function getPdArchive(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getArchive(id); |
| | | } |
| | | return requestClient.get<MesPdArchiveApi.Archive>( |
| | | `/mes/pd/archive/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | export function exportPdArchive(params: MesPdArchiveApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return Promise.resolve(new Blob()); |
| | | } |
| | | return requestClient.download('/mes/pd/archive/export-excel', { params }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdBomApi { |
| | | /** 设计 BOM è¡ */ |
| | | export interface BomLine { |
| | | id?: number; |
| | | projectId?: number; |
| | | itemId?: number; |
| | | itemCode?: string; |
| | | itemName?: string; |
| | | specification?: string; |
| | | unitMeasureName?: string; |
| | | quantity?: number; |
| | | remark?: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | projectId: number; |
| | | itemCode?: string; |
| | | itemName?: string; |
| | | } |
| | | } |
| | | |
| | | export function getPdBomPage(params: MesPdBomApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getBomPage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdBomApi.BomLine>>( |
| | | '/mes/pd/bom/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function createPdBomLine(data: MesPdBomApi.BomLine) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.createBomLine(data); |
| | | } |
| | | return requestClient.post<number>('/mes/pd/bom/create', data); |
| | | } |
| | | |
| | | export function updatePdBomLine(data: MesPdBomApi.BomLine) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.updateBomLine(data); |
| | | } |
| | | return requestClient.put('/mes/pd/bom/update', data); |
| | | } |
| | | |
| | | export function deletePdBomLine(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.deleteBomLine(id); |
| | | } |
| | | return requestClient.delete(`/mes/pd/bom/delete?id=${id}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdDocumentApi { |
| | | /** è®¾è®¡èµæ */ |
| | | export interface Document { |
| | | id?: number; |
| | | projectId?: number; |
| | | documentType?: number; |
| | | name?: string; |
| | | version?: string; |
| | | fileUrl?: string; |
| | | fileName?: string; |
| | | fileSize?: number; |
| | | uploaderId?: number; |
| | | uploaderName?: string; |
| | | remark?: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | projectId: number; |
| | | documentType?: number; |
| | | name?: string; |
| | | version?: string; |
| | | } |
| | | } |
| | | |
| | | export function getPdDocumentPage(params: MesPdDocumentApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getDocumentPage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdDocumentApi.Document>>( |
| | | '/mes/pd/document/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function getPdDocument(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getDocument(id); |
| | | } |
| | | return requestClient.get<MesPdDocumentApi.Document>( |
| | | `/mes/pd/document/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | export function createPdDocument(data: MesPdDocumentApi.Document) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.createDocument(data); |
| | | } |
| | | return requestClient.post<number>('/mes/pd/document/create', data); |
| | | } |
| | | |
| | | export function updatePdDocument(data: MesPdDocumentApi.Document) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.updateDocument(data); |
| | | } |
| | | return requestClient.put('/mes/pd/document/update', data); |
| | | } |
| | | |
| | | export function deletePdDocument(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.deleteDocument(id); |
| | | } |
| | | return requestClient.delete(`/mes/pd/document/delete?id=${id}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdDrawingReviewApi { |
| | | /** å¾çº¸å®¡æ ¸ */ |
| | | export interface DrawingReview { |
| | | id?: number; |
| | | code?: string; |
| | | projectId?: number; |
| | | projectCode?: string; |
| | | projectName?: string; |
| | | documentId?: number; |
| | | documentName?: string; |
| | | documentVersion?: string; |
| | | reviewerId?: number; |
| | | reviewerName?: string; |
| | | status?: number; |
| | | reviewComment?: string; |
| | | reviewTime?: number; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | projectId?: number; |
| | | status?: number; |
| | | reviewerId?: number; |
| | | } |
| | | |
| | | export interface ReviewParams { |
| | | id: number; |
| | | status: number; |
| | | reviewComment?: string; |
| | | } |
| | | } |
| | | |
| | | export function getPdDrawingReviewPage( |
| | | params: MesPdDrawingReviewApi.PageParams, |
| | | ) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getDrawingReviewPage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdDrawingReviewApi.DrawingReview>>( |
| | | '/mes/pd/drawing-review/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function getPdDrawingReview(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getDrawingReview(id); |
| | | } |
| | | return requestClient.get<MesPdDrawingReviewApi.DrawingReview>( |
| | | `/mes/pd/drawing-review/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | export function createPdDrawingReview(data: MesPdDrawingReviewApi.DrawingReview) { |
| | | if (isPdMockEnabled()) { |
| | | return Promise.resolve(1); |
| | | } |
| | | return requestClient.post<number>('/mes/pd/drawing-review/create', data); |
| | | } |
| | | |
| | | export function reviewPdDrawing(data: MesPdDrawingReviewApi.ReviewParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.reviewDrawing(data); |
| | | } |
| | | return requestClient.put('/mes/pd/drawing-review/review', data); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** æ¯å¦å¯ç¨äº§å设计ï¼PDï¼æ¨¡åæ¬å° Mock */ |
| | | export function isPdMockEnabled(): boolean { |
| | | return import.meta.env.VITE_PD_MOCK === 'true'; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { MesPdArchiveApi } from '#/api/mes/pd/archive'; |
| | | import type { MesPdBomApi } from '#/api/mes/pd/bom'; |
| | | import type { MesPdDocumentApi } from '#/api/mes/pd/document'; |
| | | import type { MesPdDrawingReviewApi } from '#/api/mes/pd/drawing-review'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | import type { MesPdVersionChangeApi } from '#/api/mes/pd/version-change'; |
| | | import type { MesPdWorkflowApi } from '#/api/mes/pd/workflow'; |
| | | |
| | | import { |
| | | MesPdDocumentTypeEnum, |
| | | MesPdDrawingReviewStatusEnum, |
| | | MesPdProjectStatusEnum, |
| | | MesPdVersionChangeTypeEnum, |
| | | } from '#/packages/constants/src'; |
| | | |
| | | const DAY = 86_400_000; |
| | | const now = Date.now(); |
| | | |
| | | function ts(daysAgo = 0): number { |
| | | return now - daysAgo * DAY; |
| | | } |
| | | |
| | | /** 设计任å¡ï¼åä¸äº§åå¯å¤æ¬¡åèµ·ï¼æ¯æ¬¡å¯¹åºä¸ä¸ªçæ¬ */ |
| | | export const pdMockProjects: MesPdProjectApi.Project[] = [ |
| | | { |
| | | id: 1, |
| | | code: 'DT-2026-0001', |
| | | name: 'æºè½æ¸©æ§æ¨¡ç» V1.0 设计', |
| | | productCode: 'PRD-TC-100', |
| | | productName: 'æºè½æ¸©æ§æ¨¡ç»', |
| | | version: 'V1.0', |
| | | status: MesPdProjectStatusEnum.DESIGNING, |
| | | designerName: 'å¼ å·¥', |
| | | reviewerName: 'æä¸»ç®¡', |
| | | planStartDate: ts(30), |
| | | planEndDate: ts(-15), |
| | | remark: '馿¬¡è®¾è®¡ï¼ä¸»æ§æ¿ + ä¼ æå¨ä¸ä½å', |
| | | createTime: ts(35), |
| | | }, |
| | | { |
| | | id: 2, |
| | | code: 'DT-2026-0002', |
| | | name: 'å·¥ä¸ç½å
³ V2.1 设计', |
| | | productCode: 'PRD-GW-200', |
| | | productName: 'å·¥ä¸ç©èç½ç½å
³', |
| | | version: 'V2.1', |
| | | status: MesPdProjectStatusEnum.REVIEWING, |
| | | designerName: 'çå·¥', |
| | | reviewerName: 'èµµç»ç', |
| | | planStartDate: ts(20), |
| | | planEndDate: ts(-5), |
| | | remark: 'éä¿¡åè®®å级ï¼å¤å£³ç»æè°æ´', |
| | | createTime: ts(25), |
| | | }, |
| | | { |
| | | id: 3, |
| | | code: 'DT-2025-0012', |
| | | name: 'å¤ééééæ¿ V1.2 设计', |
| | | productCode: 'PRD-SC-050', |
| | | productName: 'å¤ééééæ¿', |
| | | version: 'V1.2', |
| | | status: MesPdProjectStatusEnum.ARCHIVED, |
| | | designerName: 'éå·¥', |
| | | reviewerName: 'æä¸»ç®¡', |
| | | planStartDate: ts(90), |
| | | planEndDate: ts(45), |
| | | actualFinishDate: ts(40), |
| | | remark: 'å·²éäº§å½æ¡£', |
| | | createTime: ts(95), |
| | | }, |
| | | { |
| | | id: 4, |
| | | code: 'DT-2026-0004', |
| | | name: '伺æçµæºé©±å¨å¨ V0.9 设计', |
| | | productCode: 'PRD-MD-300', |
| | | productName: '伺æçµæºé©±å¨å¨', |
| | | version: 'V0.9', |
| | | status: MesPdProjectStatusEnum.DRAFT, |
| | | designerName: 'åå·¥', |
| | | reviewerName: 'èµµç»ç', |
| | | planStartDate: ts(5), |
| | | planEndDate: ts(-30), |
| | | createTime: ts(3), |
| | | }, |
| | | { |
| | | id: 5, |
| | | code: 'DT-2024-0008', |
| | | name: 'å¤ééééæ¿ V1.0 设计', |
| | | productCode: 'PRD-SC-050', |
| | | productName: 'å¤ééééæ¿', |
| | | version: 'V1.0', |
| | | status: MesPdProjectStatusEnum.ARCHIVED, |
| | | designerName: 'éå·¥', |
| | | reviewerName: 'æä¸»ç®¡', |
| | | planStartDate: ts(200), |
| | | planEndDate: ts(170), |
| | | actualFinishDate: ts(165), |
| | | remark: 'åä»£çæ¬å½æ¡£', |
| | | createTime: ts(205), |
| | | }, |
| | | { |
| | | id: 6, |
| | | code: 'DT-2026-0006', |
| | | name: 'æºè½æ¸©æ§æ¨¡ç» V1.1 设计', |
| | | productCode: 'PRD-TC-100', |
| | | productName: 'æºè½æ¸©æ§æ¨¡ç»', |
| | | version: 'V1.1', |
| | | status: MesPdProjectStatusEnum.APPROVED, |
| | | designerName: 'å¼ å·¥', |
| | | reviewerName: 'æä¸»ç®¡', |
| | | planStartDate: ts(10), |
| | | planEndDate: ts(-20), |
| | | remark: 'æ£çä¼åæ¹çï¼å¾
彿¡£', |
| | | createTime: ts(12), |
| | | }, |
| | | ]; |
| | | |
| | | export const pdMockArchives: MesPdArchiveApi.Archive[] = [ |
| | | { |
| | | id: 1, |
| | | code: 'AR-2025-0012', |
| | | projectId: 3, |
| | | projectCode: 'DT-2025-0012', |
| | | projectName: 'å¤ééééæ¿ V1.2 设计', |
| | | productCode: 'PRD-SC-050', |
| | | productName: 'å¤ééééæ¿', |
| | | version: 'V1.2', |
| | | archiveStatus: 1, |
| | | archiveTime: ts(38), |
| | | archiverName: 'éå·¥', |
| | | documentCount: 3, |
| | | bomCount: 1, |
| | | storagePath: '/archive/2026/PRD-SC-050/V1.2', |
| | | remark: 'V1.2 éäº§çæ¬å®æ´å½æ¡£', |
| | | createTime: ts(38), |
| | | }, |
| | | { |
| | | id: 2, |
| | | code: 'AR-2024-0008', |
| | | projectId: 5, |
| | | projectCode: 'DT-2024-0008', |
| | | projectName: 'å¤ééééæ¿ V1.0 设计', |
| | | productCode: 'PRD-SC-050', |
| | | productName: 'å¤ééééæ¿', |
| | | version: 'V1.0', |
| | | archiveStatus: 1, |
| | | archiveTime: ts(165), |
| | | archiverName: 'éå·¥', |
| | | documentCount: 4, |
| | | bomCount: 8, |
| | | storagePath: '/archive/2024/PRD-SC-050/V1.0', |
| | | remark: 'åä»£çæ¬å½æ¡£', |
| | | createTime: ts(165), |
| | | }, |
| | | ]; |
| | | |
| | | export const pdMockDocuments: MesPdDocumentApi.Document[] = [ |
| | | { |
| | | id: 1, |
| | | projectId: 1, |
| | | documentType: MesPdDocumentTypeEnum.SCHEME, |
| | | name: 'æ¸©æ§æ¹æ¡è¯´æ', |
| | | version: 'V1.0', |
| | | fileName: 'scheme-v1.pdf', |
| | | fileUrl: 'https://example.com/scheme-v1.pdf', |
| | | uploaderName: 'å¼ å·¥', |
| | | createTime: ts(25), |
| | | }, |
| | | { |
| | | id: 2, |
| | | projectId: 1, |
| | | documentType: MesPdDocumentTypeEnum.DRAWING, |
| | | name: 'ä¸»æ§æ¿åçå¾', |
| | | version: 'V1.0', |
| | | fileName: 'main-sch.pdf', |
| | | fileUrl: 'https://example.com/main-sch.pdf', |
| | | uploaderName: 'å¼ å·¥', |
| | | createTime: ts(22), |
| | | }, |
| | | { |
| | | id: 3, |
| | | projectId: 2, |
| | | documentType: MesPdDocumentTypeEnum.DRAWING, |
| | | name: 'ç½å
³å¤å£³å·¥ç¨å¾', |
| | | version: 'V2.1', |
| | | fileName: 'gw-enclosure.dwg', |
| | | fileUrl: 'https://example.com/gw-enclosure.dwg', |
| | | uploaderName: 'çå·¥', |
| | | createTime: ts(12), |
| | | }, |
| | | { |
| | | id: 4, |
| | | projectId: 3, |
| | | documentType: MesPdDocumentTypeEnum.OTHER, |
| | | name: '彿¡£æ£æ¥æ¸
å', |
| | | version: 'V1.2', |
| | | fileName: 'archive-checklist.xlsx', |
| | | uploaderName: 'éå·¥', |
| | | createTime: ts(39), |
| | | }, |
| | | { |
| | | id: 5, |
| | | projectId: 6, |
| | | documentType: MesPdDocumentTypeEnum.DRAWING, |
| | | name: 'æ£çä¼å PCB å¾', |
| | | version: 'V1.1', |
| | | fileName: 'thermal-pcb-v1.1.pdf', |
| | | fileUrl: 'https://example.com/thermal-pcb.pdf', |
| | | uploaderName: 'å¼ å·¥', |
| | | createTime: ts(8), |
| | | }, |
| | | { |
| | | id: 6, |
| | | projectId: 1, |
| | | documentType: MesPdDocumentTypeEnum.BOM, |
| | | name: 'BOM æ¸
å', |
| | | version: 'V1.0', |
| | | fileName: 'bom-v1.0.xlsx', |
| | | fileUrl: 'https://example.com/bom-v1.0.xlsx', |
| | | uploaderName: 'å¼ å·¥', |
| | | createTime: ts(20), |
| | | }, |
| | | { |
| | | id: 7, |
| | | projectId: 3, |
| | | documentType: MesPdDocumentTypeEnum.BOM, |
| | | name: 'BOM æ¸
å', |
| | | version: 'V1.2', |
| | | fileName: 'bom-v1.2.xlsx', |
| | | fileUrl: 'https://example.com/bom-v1.2.xlsx', |
| | | uploaderName: 'éå·¥', |
| | | createTime: ts(48), |
| | | }, |
| | | ]; |
| | | |
| | | export const pdMockBomLines: MesPdBomApi.BomLine[] = [ |
| | | { |
| | | id: 1, |
| | | projectId: 1, |
| | | itemCode: 'MAT-001', |
| | | itemName: 'STM32 主æ§è¯ç', |
| | | specification: 'LQFP-64', |
| | | unitMeasureName: 'PCS', |
| | | quantity: 1, |
| | | createTime: ts(20), |
| | | }, |
| | | { |
| | | id: 2, |
| | | projectId: 1, |
| | | itemCode: 'MAT-018', |
| | | itemName: 'NTC çæçµé»', |
| | | specification: '10K B3950', |
| | | unitMeasureName: 'PCS', |
| | | quantity: 2, |
| | | createTime: ts(20), |
| | | }, |
| | | { |
| | | id: 3, |
| | | projectId: 3, |
| | | itemCode: 'MAT-102', |
| | | itemName: 'è¿ç®æ¾å¤§å¨', |
| | | specification: 'SOP-8', |
| | | unitMeasureName: 'PCS', |
| | | quantity: 4, |
| | | createTime: ts(50), |
| | | }, |
| | | ]; |
| | | |
| | | export const pdMockDrawingReviews: MesPdDrawingReviewApi.DrawingReview[] = [ |
| | | { |
| | | id: 1, |
| | | code: 'DR-2026-0001', |
| | | projectId: 2, |
| | | projectCode: 'DT-2026-0002', |
| | | projectName: 'å·¥ä¸ç½å
³ V2.1 设计', |
| | | documentId: 3, |
| | | documentName: 'ç½å
³å¤å£³å·¥ç¨å¾', |
| | | documentVersion: 'V2.1', |
| | | reviewerName: 'èµµç»ç', |
| | | status: MesPdDrawingReviewStatusEnum.PENDING, |
| | | createTime: ts(10), |
| | | }, |
| | | { |
| | | id: 2, |
| | | code: 'DR-2026-0002', |
| | | projectId: 6, |
| | | projectCode: 'DT-2026-0006', |
| | | projectName: 'æºè½æ¸©æ§æ¨¡ç» V1.1 设计', |
| | | documentId: 5, |
| | | documentName: 'æ£çä¼å PCB å¾', |
| | | documentVersion: 'V1.1', |
| | | reviewerName: 'æä¸»ç®¡', |
| | | status: MesPdDrawingReviewStatusEnum.APPROVED, |
| | | reviewTime: ts(6), |
| | | createTime: ts(7), |
| | | }, |
| | | ]; |
| | | |
| | | export const pdMockVersionChanges: MesPdVersionChangeApi.VersionChange[] = [ |
| | | { |
| | | id: 1, |
| | | code: 'VC-2025-0001', |
| | | projectId: 3, |
| | | projectCode: 'DT-2025-0012', |
| | | projectName: 'å¤ééééæ¿ V1.2 设计', |
| | | changeType: MesPdVersionChangeTypeEnum.MINOR, |
| | | fromVersion: 'V1.0', |
| | | toVersion: 'V1.2', |
| | | applicantName: 'éå·¥', |
| | | changeReason: 'ééæ°æ©å±ä¸ç²¾åº¦æå', |
| | | changeContent: 'ç± 4 ééå级为 8 ééï¼æ´æ¢é«ç²¾åº¦ ADC', |
| | | createTime: ts(92), |
| | | }, |
| | | { |
| | | id: 2, |
| | | code: 'VC-2026-0001', |
| | | projectId: 6, |
| | | projectCode: 'DT-2026-0006', |
| | | projectName: 'æºè½æ¸©æ§æ¨¡ç» V1.1 设计', |
| | | changeType: MesPdVersionChangeTypeEnum.PATCH, |
| | | fromVersion: 'V1.0', |
| | | toVersion: 'V1.1', |
| | | applicantName: 'å¼ å·¥', |
| | | changeReason: 'ä¼åæ£çå¸å±', |
| | | changeContent: 'è°æ´ NTC ææ¾ä½ç½®ï¼æ´æ° PCB ä¸å°', |
| | | createTime: ts(8), |
| | | }, |
| | | ]; |
| | | |
| | | export const pdMockWorkflows: Record<number, MesPdWorkflowApi.WorkflowNode[]> = { |
| | | 1: [ |
| | | { id: 11, projectId: 1, nodeName: 'åèµ·ä»»å¡', operatorName: 'å¼ å·¥', status: 1, operateTime: ts(35) }, |
| | | { id: 12, projectId: 1, nodeName: 'åå¸ä»»å¡', operatorName: 'å¼ å·¥', status: 1, operateTime: ts(34) }, |
| | | { id: 13, projectId: 1, nodeName: 'ä¸ä¼ èµæ', operatorName: 'å¼ å·¥', status: 2, remark: 'è¿è¡ä¸' }, |
| | | { id: 14, projectId: 1, nodeName: 'èµæå®¡æ ¸', status: 0 }, |
| | | { id: 15, projectId: 1, nodeName: '彿¡£', status: 0 }, |
| | | ], |
| | | 2: [ |
| | | { id: 21, projectId: 2, nodeName: 'åèµ·ä»»å¡', operatorName: 'çå·¥', status: 1, operateTime: ts(25) }, |
| | | { id: 22, projectId: 2, nodeName: 'åå¸ä»»å¡', operatorName: 'çå·¥', status: 1, operateTime: ts(24) }, |
| | | { id: 23, projectId: 2, nodeName: 'ä¸ä¼ èµæ', operatorName: 'çå·¥', status: 1, operateTime: ts(12) }, |
| | | { id: 24, projectId: 2, nodeName: 'èµæå®¡æ ¸', operatorName: 'èµµç»ç', status: 2, remark: 'å¾
å®¡æ ¸' }, |
| | | { id: 25, projectId: 2, nodeName: '彿¡£', status: 0 }, |
| | | ], |
| | | 3: [ |
| | | { id: 31, projectId: 3, nodeName: 'åèµ·ä»»å¡', operatorName: 'éå·¥', status: 1, operateTime: ts(95) }, |
| | | { id: 32, projectId: 3, nodeName: 'åå¸ä»»å¡', operatorName: 'éå·¥', status: 1, operateTime: ts(94) }, |
| | | { id: 33, projectId: 3, nodeName: 'ä¸ä¼ èµæ', operatorName: 'éå·¥', status: 1, operateTime: ts(50) }, |
| | | { id: 34, projectId: 3, nodeName: 'èµæå®¡æ ¸', operatorName: 'æä¸»ç®¡', status: 1, operateTime: ts(42) }, |
| | | { id: 35, projectId: 3, nodeName: '彿¡£', operatorName: 'éå·¥', status: 1, operateTime: ts(38) }, |
| | | ], |
| | | 4: [ |
| | | { id: 41, projectId: 4, nodeName: 'åèµ·ä»»å¡', operatorName: 'åå·¥', status: 1, operateTime: ts(3) }, |
| | | { id: 42, projectId: 4, nodeName: 'åå¸ä»»å¡', status: 0 }, |
| | | { id: 43, projectId: 4, nodeName: 'ä¸ä¼ èµæ', status: 0 }, |
| | | { id: 44, projectId: 4, nodeName: 'èµæå®¡æ ¸', status: 0 }, |
| | | { id: 45, projectId: 4, nodeName: '彿¡£', status: 0 }, |
| | | ], |
| | | 5: [ |
| | | { id: 51, projectId: 5, nodeName: 'åèµ·ä»»å¡', operatorName: 'éå·¥', status: 1, operateTime: ts(205) }, |
| | | { id: 52, projectId: 5, nodeName: 'åå¸ä»»å¡', operatorName: 'éå·¥', status: 1, operateTime: ts(200) }, |
| | | { id: 53, projectId: 5, nodeName: 'ä¸ä¼ èµæ', operatorName: 'éå·¥', status: 1, operateTime: ts(170) }, |
| | | { id: 54, projectId: 5, nodeName: 'èµæå®¡æ ¸', operatorName: 'æä¸»ç®¡', status: 1, operateTime: ts(168) }, |
| | | { id: 55, projectId: 5, nodeName: '彿¡£', operatorName: 'éå·¥', status: 1, operateTime: ts(165) }, |
| | | ], |
| | | 6: [ |
| | | { id: 61, projectId: 6, nodeName: 'åèµ·ä»»å¡', operatorName: 'å¼ å·¥', status: 1, operateTime: ts(12) }, |
| | | { id: 62, projectId: 6, nodeName: 'åå¸ä»»å¡', operatorName: 'å¼ å·¥', status: 1, operateTime: ts(11) }, |
| | | { id: 63, projectId: 6, nodeName: 'ä¸ä¼ èµæ', operatorName: 'å¼ å·¥', status: 1, operateTime: ts(8) }, |
| | | { id: 64, projectId: 6, nodeName: 'èµæå®¡æ ¸', operatorName: 'æä¸»ç®¡', status: 1, operateTime: ts(6) }, |
| | | { id: 65, projectId: 6, nodeName: '彿¡£', status: 2, remark: 'å¾
彿¡£' }, |
| | | ], |
| | | }; |
| | | |
| | | export const pdMockSeq = { |
| | | project: 7, |
| | | archive: 3, |
| | | task: 4, |
| | | document: 8, |
| | | bom: 4, |
| | | drawingReview: 3, |
| | | versionChange: 3, |
| | | }; |
| | | |
| | | /** @deprecated 设计任å¡å³é¡¹ç®ï¼åä»»å¡ååå·²åºå¼ */ |
| | | export const pdMockTasks = []; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { MesPdArchiveApi } from '#/api/mes/pd/archive'; |
| | | import type { MesPdBomApi } from '#/api/mes/pd/bom'; |
| | | import type { MesPdDocumentApi } from '#/api/mes/pd/document'; |
| | | import type { MesPdDrawingReviewApi } from '#/api/mes/pd/drawing-review'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | import type { MesPdTaskApi } from '#/api/mes/pd/task'; |
| | | import type { MesPdVersionChangeApi } from '#/api/mes/pd/version-change'; |
| | | import type { MesPdWorkflowApi } from '#/api/mes/pd/workflow'; |
| | | |
| | | import { MesPdDrawingReviewStatusEnum, MesPdDocumentTypeEnum, MesPdProjectStatusEnum } from '#/packages/constants/src'; |
| | | |
| | | import { |
| | | pdMockArchives, |
| | | pdMockBomLines, |
| | | pdMockDocuments, |
| | | pdMockDrawingReviews, |
| | | pdMockProjects, |
| | | pdMockSeq, |
| | | pdMockTasks, |
| | | pdMockVersionChanges, |
| | | pdMockWorkflows, |
| | | } from './seed'; |
| | | import { mockDelay, mockNextCode, mockPage } from './utils'; |
| | | |
| | | function matchLike(value: unknown, keyword?: string): boolean { |
| | | if (!keyword) { |
| | | return true; |
| | | } |
| | | return String(value ?? '') |
| | | .toLowerCase() |
| | | .includes(String(keyword).toLowerCase()); |
| | | } |
| | | |
| | | function syncProjectRef(project: MesPdProjectApi.Project) { |
| | | const ref = { |
| | | projectCode: project.code, |
| | | projectName: project.name, |
| | | productCode: project.productCode, |
| | | productName: project.productName, |
| | | version: project.version, |
| | | }; |
| | | pdMockTasks.forEach((task) => { |
| | | if (task.projectId === project.id) { |
| | | Object.assign(task, ref); |
| | | } |
| | | }); |
| | | pdMockDrawingReviews.forEach((item) => { |
| | | if (item.projectId === project.id) { |
| | | Object.assign(item, ref); |
| | | } |
| | | }); |
| | | pdMockVersionChanges.forEach((item) => { |
| | | if (item.projectId === project.id) { |
| | | Object.assign(item, ref); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | const WORKFLOW_STEPS = [ |
| | | 'åèµ·ä»»å¡', |
| | | 'åå¸ä»»å¡', |
| | | 'ä¸ä¼ èµæ', |
| | | 'èµæå®¡æ ¸', |
| | | '彿¡£', |
| | | ] as const; |
| | | |
| | | function initWorkflow(projectId: number, designerName?: string) { |
| | | pdMockWorkflows[projectId] = WORKFLOW_STEPS.map((nodeName, index) => ({ |
| | | id: projectId * 10 + index, |
| | | projectId, |
| | | nodeName, |
| | | operatorName: index === 0 ? designerName : undefined, |
| | | status: index === 0 ? 1 : 0, |
| | | operateTime: index === 0 ? Date.now() : undefined, |
| | | })); |
| | | } |
| | | |
| | | function advanceWorkflow(projectId: number, completedStep: string, operatorName?: string) { |
| | | const nodes = pdMockWorkflows[projectId]; |
| | | if (!nodes) { |
| | | return; |
| | | } |
| | | const completedIndex = nodes.findIndex((n) => n.nodeName === completedStep); |
| | | if (completedIndex >= 0) { |
| | | nodes[completedIndex]!.status = 1; |
| | | nodes[completedIndex]!.operateTime = Date.now(); |
| | | if (operatorName) { |
| | | nodes[completedIndex]!.operatorName = operatorName; |
| | | } |
| | | } |
| | | const next = nodes[completedIndex + 1]; |
| | | if (next) { |
| | | next.status = 2; |
| | | if (operatorName) { |
| | | next.operatorName = operatorName; |
| | | } |
| | | } |
| | | } |
| | | |
| | | function createDocumentReviews(projectId: number, project: MesPdProjectApi.Project) { |
| | | const docs = pdMockDocuments.filter((d) => d.projectId === projectId); |
| | | for (const doc of docs) { |
| | | const exists = pdMockDrawingReviews.some((r) => r.documentId === doc.id); |
| | | if (exists) { |
| | | continue; |
| | | } |
| | | const reviewId = pdMockSeq.drawingReview++; |
| | | pdMockDrawingReviews.push({ |
| | | id: reviewId, |
| | | code: mockNextCode('DR-2026', reviewId), |
| | | projectId, |
| | | projectCode: project.code, |
| | | projectName: project.name, |
| | | documentId: doc.id, |
| | | documentName: doc.name, |
| | | documentVersion: doc.version, |
| | | reviewerName: project.reviewerName, |
| | | status: MesPdDrawingReviewStatusEnum.PENDING, |
| | | createTime: Date.now(), |
| | | }); |
| | | } |
| | | } |
| | | |
| | | export const pdMockStore = { |
| | | // ---------- è®¾è®¡é¡¹ç® ---------- |
| | | getProjectPage(params: MesPdProjectApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockProjects, params, (item) => { |
| | | return ( |
| | | matchLike(item.code, params.code) && |
| | | matchLike(item.name, params.name) && |
| | | matchLike(item.productCode, params.productCode) && |
| | | (params.status === undefined || item.status === params.status) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | getProject(id: number) { |
| | | const project = pdMockProjects.find((item) => item.id === id); |
| | | if (!project) { |
| | | return Promise.reject(new Error('设计任å¡ä¸åå¨')); |
| | | } |
| | | return mockDelay({ ...project }); |
| | | }, |
| | | |
| | | createProject(data: MesPdProjectApi.Project) { |
| | | const id = pdMockSeq.project++; |
| | | const project: MesPdProjectApi.Project = { |
| | | ...data, |
| | | id, |
| | | code: data.code || mockNextCode('DT-2026', id), |
| | | status: MesPdProjectStatusEnum.DRAFT, |
| | | createTime: Date.now(), |
| | | }; |
| | | pdMockProjects.unshift(project); |
| | | initWorkflow(id, project.designerName); |
| | | return mockDelay(id); |
| | | }, |
| | | |
| | | updateProject(data: MesPdProjectApi.Project) { |
| | | const index = pdMockProjects.findIndex((item) => item.id === data.id); |
| | | if (index < 0) { |
| | | return Promise.reject(new Error('设计任å¡ä¸åå¨')); |
| | | } |
| | | pdMockProjects[index] = { ...pdMockProjects[index], ...data }; |
| | | syncProjectRef(pdMockProjects[index]!); |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | deleteProject(id: number) { |
| | | const index = pdMockProjects.findIndex((item) => item.id === id); |
| | | if (index >= 0) { |
| | | pdMockProjects.splice(index, 1); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | publishProject(id: number) { |
| | | const project = pdMockProjects.find((item) => item.id === id); |
| | | if (!project || project.status !== MesPdProjectStatusEnum.DRAFT) { |
| | | return Promise.reject(new Error('ä»
èç¨¿ç¶æå¯åå¸')); |
| | | } |
| | | project.status = MesPdProjectStatusEnum.DESIGNING; |
| | | advanceWorkflow(id, 'åèµ·ä»»å¡', project.designerName); |
| | | advanceWorkflow(id, 'åå¸ä»»å¡', project.designerName); |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | submitProjectReview(id: number) { |
| | | const project = pdMockProjects.find((item) => item.id === id); |
| | | if (!project || project.status !== MesPdProjectStatusEnum.DESIGNING) { |
| | | return Promise.reject(new Error('请å
åå¸ä»»å¡å¹¶ä¸ä¼ èµæ')); |
| | | } |
| | | const docCount = pdMockDocuments.filter((d) => d.projectId === id).length; |
| | | if (docCount === 0) { |
| | | return Promise.reject(new Error('请å
ä¸ä¼ è®¾è®¡èµæ')); |
| | | } |
| | | project.status = MesPdProjectStatusEnum.REVIEWING; |
| | | createDocumentReviews(id, project); |
| | | advanceWorkflow(id, 'ä¸ä¼ èµæ', project.designerName); |
| | | advanceWorkflow(id, 'èµæå®¡æ ¸', project.reviewerName); |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | approveProject(id: number) { |
| | | const project = pdMockProjects.find((item) => item.id === id); |
| | | if (!project || project.status !== MesPdProjectStatusEnum.REVIEWING) { |
| | | return Promise.reject(new Error('å½åç¶æä¸å¯å®¡æ ¸éè¿')); |
| | | } |
| | | const pending = pdMockDrawingReviews.filter( |
| | | (r) => |
| | | r.projectId === id && |
| | | r.status === MesPdDrawingReviewStatusEnum.PENDING, |
| | | ); |
| | | if (pending.length > 0) { |
| | | return Promise.reject(new Error('å°æèµææªå®æå®¡æ ¸')); |
| | | } |
| | | project.status = MesPdProjectStatusEnum.APPROVED; |
| | | const nodes = pdMockWorkflows[id]; |
| | | const reviewNode = nodes?.find((n) => n.nodeName === 'èµæå®¡æ ¸'); |
| | | if (reviewNode) { |
| | | reviewNode.status = 1; |
| | | reviewNode.operateTime = Date.now(); |
| | | } |
| | | const archiveNode = nodes?.find((n) => n.nodeName === '彿¡£'); |
| | | if (archiveNode) { |
| | | archiveNode.status = 2; |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | archiveProject(id: number) { |
| | | const project = pdMockProjects.find((item) => item.id === id); |
| | | if (!project) { |
| | | return Promise.reject(new Error('设计任å¡ä¸åå¨')); |
| | | } |
| | | if (project.status !== MesPdProjectStatusEnum.APPROVED) { |
| | | return Promise.reject(new Error('ä»
å®¡æ ¸éè¿çä»»å¡å¯å½æ¡£')); |
| | | } |
| | | project.status = MesPdProjectStatusEnum.ARCHIVED; |
| | | project.actualFinishDate = Date.now(); |
| | | const archiveId = pdMockSeq.archive++; |
| | | pdMockArchives.unshift({ |
| | | id: archiveId, |
| | | code: mockNextCode('AR-2026', archiveId), |
| | | projectId: project.id, |
| | | projectCode: project.code, |
| | | projectName: project.name, |
| | | productCode: project.productCode, |
| | | productName: project.productName, |
| | | version: project.version, |
| | | archiveStatus: 1, |
| | | archiveTime: Date.now(), |
| | | archiverName: project.designerName, |
| | | documentCount: pdMockDocuments.filter((d) => d.projectId === id).length, |
| | | bomCount: pdMockDocuments.filter( |
| | | (d) => |
| | | d.projectId === id && |
| | | d.documentType === MesPdDocumentTypeEnum.BOM, |
| | | ).length, |
| | | storagePath: `/archive/2026/${project.productCode}/${project.version}`, |
| | | createTime: Date.now(), |
| | | }); |
| | | advanceWorkflow(id, '彿¡£', project.designerName); |
| | | const archiveNode = pdMockWorkflows[id]?.find((n) => n.nodeName === '彿¡£'); |
| | | if (archiveNode) { |
| | | archiveNode.status = 1; |
| | | archiveNode.operateTime = Date.now(); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | // ---------- 彿¡£å°è´¦ ---------- |
| | | getArchivePage(params: MesPdArchiveApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockArchives, params, (item) => { |
| | | return ( |
| | | matchLike(item.code, params.code) && |
| | | matchLike(item.projectCode, params.projectCode) && |
| | | matchLike(item.productCode, params.productCode) && |
| | | (params.archiveStatus === undefined || |
| | | item.archiveStatus === params.archiveStatus) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | getArchive(id: number) { |
| | | const archive = pdMockArchives.find((item) => item.id === id); |
| | | if (!archive) { |
| | | return Promise.reject(new Error('彿¡£è®°å½ä¸åå¨')); |
| | | } |
| | | return mockDelay({ ...archive }); |
| | | }, |
| | | |
| | | // ---------- ä»»å¡ ---------- |
| | | getTaskPage(params: MesPdTaskApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockTasks, params, (item) => { |
| | | return ( |
| | | item.projectId === params.projectId && |
| | | matchLike(item.code, params.code) && |
| | | matchLike(item.taskName, params.taskName) && |
| | | (params.status === undefined || item.status === params.status) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | getTask(id: number) { |
| | | const task = pdMockTasks.find((item) => item.id === id); |
| | | if (!task) { |
| | | return Promise.reject(new Error('设计任å¡ä¸åå¨')); |
| | | } |
| | | return mockDelay({ ...task }); |
| | | }, |
| | | |
| | | createTask(data: MesPdTaskApi.Task) { |
| | | const id = pdMockSeq.task++; |
| | | const project = pdMockProjects.find((p) => p.id === data.projectId); |
| | | const task: MesPdTaskApi.Task = { |
| | | ...data, |
| | | id, |
| | | code: data.code || mockNextCode('TK-2026', id), |
| | | projectCode: project?.code, |
| | | projectName: project?.name, |
| | | status: data.status ?? 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | pdMockTasks.unshift(task); |
| | | return mockDelay(id); |
| | | }, |
| | | |
| | | updateTask(data: MesPdTaskApi.Task) { |
| | | const index = pdMockTasks.findIndex((item) => item.id === data.id); |
| | | if (index < 0) { |
| | | return Promise.reject(new Error('设计任å¡ä¸åå¨')); |
| | | } |
| | | pdMockTasks[index] = { ...pdMockTasks[index], ...data }; |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | deleteTask(id: number) { |
| | | const index = pdMockTasks.findIndex((item) => item.id === id); |
| | | if (index >= 0) { |
| | | pdMockTasks.splice(index, 1); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | dispatchTask(data: MesPdTaskApi.DispatchParams) { |
| | | const task = pdMockTasks.find((item) => item.id === data.id); |
| | | if (task) { |
| | | task.status = 1; |
| | | task.planFinishDate = data.planFinishDate; |
| | | task.remark = data.remark; |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | completeTask(id: number) { |
| | | const task = pdMockTasks.find((item) => item.id === id); |
| | | if (task) { |
| | | task.status = 3; |
| | | task.actualFinishDate = Date.now(); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | // ---------- èµæ ---------- |
| | | getDocumentPage(params: MesPdDocumentApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockDocuments, params, (item) => { |
| | | return ( |
| | | item.projectId === params.projectId && |
| | | (params.documentType === undefined || |
| | | item.documentType === params.documentType) && |
| | | (params.version === undefined || item.version === params.version) && |
| | | matchLike(item.name, params.name) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | getDocument(id: number) { |
| | | const doc = pdMockDocuments.find((item) => item.id === id); |
| | | if (!doc) { |
| | | return Promise.reject(new Error('è®¾è®¡èµæä¸åå¨')); |
| | | } |
| | | return mockDelay({ ...doc }); |
| | | }, |
| | | |
| | | createDocument(data: MesPdDocumentApi.Document) { |
| | | const id = pdMockSeq.document++; |
| | | pdMockDocuments.unshift({ |
| | | ...data, |
| | | id, |
| | | uploaderName: data.uploaderName ?? 'å½åç¨æ·', |
| | | createTime: Date.now(), |
| | | }); |
| | | return mockDelay(id); |
| | | }, |
| | | |
| | | updateDocument(data: MesPdDocumentApi.Document) { |
| | | const index = pdMockDocuments.findIndex((item) => item.id === data.id); |
| | | if (index < 0) { |
| | | return Promise.reject(new Error('è®¾è®¡èµæä¸åå¨')); |
| | | } |
| | | pdMockDocuments[index] = { ...pdMockDocuments[index], ...data }; |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | deleteDocument(id: number) { |
| | | const index = pdMockDocuments.findIndex((item) => item.id === id); |
| | | if (index >= 0) { |
| | | pdMockDocuments.splice(index, 1); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | // ---------- BOM ---------- |
| | | getBomPage(params: MesPdBomApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockBomLines, params, (item) => { |
| | | return ( |
| | | item.projectId === params.projectId && |
| | | matchLike(item.itemCode, params.itemCode) && |
| | | matchLike(item.itemName, params.itemName) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | createBomLine(data: MesPdBomApi.BomLine) { |
| | | const id = pdMockSeq.bom++; |
| | | pdMockBomLines.unshift({ ...data, id, createTime: Date.now() }); |
| | | return mockDelay(id); |
| | | }, |
| | | |
| | | updateBomLine(data: MesPdBomApi.BomLine) { |
| | | const index = pdMockBomLines.findIndex((item) => item.id === data.id); |
| | | if (index < 0) { |
| | | return Promise.reject(new Error('BOM è¡ä¸åå¨')); |
| | | } |
| | | pdMockBomLines[index] = { ...pdMockBomLines[index], ...data }; |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | deleteBomLine(id: number) { |
| | | const index = pdMockBomLines.findIndex((item) => item.id === id); |
| | | if (index >= 0) { |
| | | pdMockBomLines.splice(index, 1); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | // ---------- å¾çº¸å®¡æ ¸ ---------- |
| | | getDrawingReviewPage(params: MesPdDrawingReviewApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockDrawingReviews, params, (item) => { |
| | | return ( |
| | | item.projectId === params.projectId && |
| | | matchLike(item.code, params.code) && |
| | | (params.status === undefined || item.status === params.status) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | getDrawingReview(id: number) { |
| | | const row = pdMockDrawingReviews.find((item) => item.id === id); |
| | | if (!row) { |
| | | return Promise.reject(new Error('å¾çº¸å®¡æ ¸åä¸åå¨')); |
| | | } |
| | | return mockDelay({ ...row }); |
| | | }, |
| | | |
| | | reviewDrawing(data: MesPdDrawingReviewApi.ReviewParams) { |
| | | const row = pdMockDrawingReviews.find((item) => item.id === data.id); |
| | | if (row) { |
| | | row.status = data.status; |
| | | row.reviewComment = data.reviewComment; |
| | | row.reviewTime = Date.now(); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | getVersionChangePage(params: MesPdVersionChangeApi.PageParams) { |
| | | return mockDelay( |
| | | mockPage(pdMockVersionChanges, params, (item) => { |
| | | return ( |
| | | item.projectId === params.projectId && |
| | | matchLike(item.code, params.code) && |
| | | (params.changeType === undefined || |
| | | item.changeType === params.changeType) |
| | | ); |
| | | }), |
| | | ); |
| | | }, |
| | | |
| | | getVersionChange(id: number) { |
| | | const row = pdMockVersionChanges.find((item) => item.id === id); |
| | | if (!row) { |
| | | return Promise.reject(new Error('çæ¬åæ´åä¸åå¨')); |
| | | } |
| | | return mockDelay({ ...row }); |
| | | }, |
| | | |
| | | createVersionChange(data: MesPdVersionChangeApi.VersionChange) { |
| | | const id = pdMockSeq.versionChange++; |
| | | const project = pdMockProjects.find((p) => p.id === data.projectId); |
| | | pdMockVersionChanges.unshift({ |
| | | ...data, |
| | | id, |
| | | code: data.code || mockNextCode('VC-2026', id), |
| | | projectCode: project?.code, |
| | | projectName: project?.name, |
| | | applicantName: data.applicantName ?? 'å½åç¨æ·', |
| | | createTime: Date.now(), |
| | | }); |
| | | return mockDelay(id); |
| | | }, |
| | | |
| | | updateVersionChange(data: MesPdVersionChangeApi.VersionChange) { |
| | | const index = pdMockVersionChanges.findIndex((item) => item.id === data.id); |
| | | if (index < 0) { |
| | | return Promise.reject(new Error('çæ¬åæ´åä¸åå¨')); |
| | | } |
| | | pdMockVersionChanges[index] = { ...pdMockVersionChanges[index], ...data }; |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | deleteVersionChange(id: number) { |
| | | const index = pdMockVersionChanges.findIndex((item) => item.id === id); |
| | | if (index >= 0) { |
| | | pdMockVersionChanges.splice(index, 1); |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | approveVersionChange(id: number) { |
| | | const row = pdMockVersionChanges.find((item) => item.id === id); |
| | | const project = pdMockProjects.find((p) => p.id === row?.projectId); |
| | | if (row && project && row.toVersion) { |
| | | project.version = row.toVersion; |
| | | } |
| | | return mockDelay(undefined); |
| | | }, |
| | | |
| | | // ---------- æµç¨ ---------- |
| | | getWorkflowList(projectId: number) { |
| | | return mockDelay(pdMockWorkflows[projectId] ?? []); |
| | | }, |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | /** æ¨¡ææ¥å£å»¶è¿ */ |
| | | export function mockDelay<T>(data: T, ms = 120): Promise<T> { |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => resolve(data), ms); |
| | | }); |
| | | } |
| | | |
| | | /** å页ä¸ç®ååæ®µè¿æ»¤ */ |
| | | export function mockPage<T extends Record<string, unknown>>( |
| | | list: T[], |
| | | params: PageParam & Record<string, unknown>, |
| | | filter?: (item: T) => boolean, |
| | | ): PageResult<T> { |
| | | const filtered = filter ? list.filter(filter) : [...list]; |
| | | const pageNo = params.pageNo ?? 1; |
| | | const pageSize = params.pageSize ?? 10; |
| | | const start = (pageNo - 1) * pageSize; |
| | | return { |
| | | list: filtered.slice(start, start + pageSize), |
| | | total: filtered.length, |
| | | }; |
| | | } |
| | | |
| | | export function mockNextCode(prefix: string, seq: number): string { |
| | | return `${prefix}-${String(seq).padStart(4, '0')}`; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdProjectApi { |
| | | /** 产åè®¾è®¡é¡¹ç® */ |
| | | export interface Project { |
| | | id?: number; |
| | | code?: string; |
| | | name?: string; |
| | | productCode?: string; |
| | | productName?: string; |
| | | version?: string; |
| | | status?: number; |
| | | designerId?: number; |
| | | designerName?: string; |
| | | reviewerId?: number; |
| | | reviewerName?: string; |
| | | planStartDate?: number; |
| | | planEndDate?: number; |
| | | actualFinishDate?: number; |
| | | remark?: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | name?: string; |
| | | productCode?: string; |
| | | status?: number; |
| | | designerId?: number; |
| | | } |
| | | } |
| | | |
| | | export function getPdProjectPage(params: MesPdProjectApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getProjectPage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdProjectApi.Project>>( |
| | | '/mes/pd/project/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function getPdProject(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getProject(id); |
| | | } |
| | | return requestClient.get<MesPdProjectApi.Project>( |
| | | `/mes/pd/project/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | export function createPdProject(data: MesPdProjectApi.Project) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.createProject(data); |
| | | } |
| | | return requestClient.post<number>('/mes/pd/project/create', data); |
| | | } |
| | | |
| | | export function updatePdProject(data: MesPdProjectApi.Project) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.updateProject(data); |
| | | } |
| | | return requestClient.put('/mes/pd/project/update', data); |
| | | } |
| | | |
| | | export function deletePdProject(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.deleteProject(id); |
| | | } |
| | | return requestClient.delete(`/mes/pd/project/delete?id=${id}`); |
| | | } |
| | | |
| | | export function exportPdProject(params: MesPdProjectApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return Promise.resolve(new Blob()); |
| | | } |
| | | return requestClient.download('/mes/pd/project/export-excel', { params }); |
| | | } |
| | | |
| | | export function submitPdProjectReview(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.submitProjectReview(id); |
| | | } |
| | | return requestClient.put('/mes/pd/project/submit-review', undefined, { |
| | | params: { id }, |
| | | }); |
| | | } |
| | | |
| | | export function publishPdProject(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.publishProject(id); |
| | | } |
| | | return requestClient.put('/mes/pd/project/publish', undefined, { |
| | | params: { id }, |
| | | }); |
| | | } |
| | | |
| | | export function approvePdProject(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.approveProject(id); |
| | | } |
| | | return requestClient.put('/mes/pd/project/approve', undefined, { |
| | | params: { id }, |
| | | }); |
| | | } |
| | | |
| | | export function archivePdProject(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.archiveProject(id); |
| | | } |
| | | return requestClient.put('/mes/pd/project/archive', undefined, { |
| | | params: { id }, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdTaskApi { |
| | | /** 产åè®¾è®¡ä»»å¡ */ |
| | | export interface Task { |
| | | id?: number; |
| | | code?: string; |
| | | projectId?: number; |
| | | projectCode?: string; |
| | | projectName?: string; |
| | | taskName?: string; |
| | | taskType?: number; |
| | | assigneeId?: number; |
| | | assigneeName?: string; |
| | | assignerId?: number; |
| | | assignerName?: string; |
| | | priority?: number; |
| | | status?: number; |
| | | planFinishDate?: number; |
| | | actualFinishDate?: number; |
| | | remark?: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | projectId?: number; |
| | | taskName?: string; |
| | | assigneeId?: number; |
| | | status?: number; |
| | | } |
| | | |
| | | export interface DispatchParams { |
| | | id: number; |
| | | assigneeId: number; |
| | | planFinishDate?: number; |
| | | remark?: string; |
| | | } |
| | | } |
| | | |
| | | export function getPdTaskPage(params: MesPdTaskApi.PageParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getTaskPage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdTaskApi.Task>>( |
| | | '/mes/pd/task/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function getPdTask(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getTask(id); |
| | | } |
| | | return requestClient.get<MesPdTaskApi.Task>(`/mes/pd/task/get?id=${id}`); |
| | | } |
| | | |
| | | export function createPdTask(data: MesPdTaskApi.Task) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.createTask(data); |
| | | } |
| | | return requestClient.post<number>('/mes/pd/task/create', data); |
| | | } |
| | | |
| | | export function updatePdTask(data: MesPdTaskApi.Task) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.updateTask(data); |
| | | } |
| | | return requestClient.put('/mes/pd/task/update', data); |
| | | } |
| | | |
| | | export function deletePdTask(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.deleteTask(id); |
| | | } |
| | | return requestClient.delete(`/mes/pd/task/delete?id=${id}`); |
| | | } |
| | | |
| | | export function dispatchPdTask(data: MesPdTaskApi.DispatchParams) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.dispatchTask(data); |
| | | } |
| | | return requestClient.put('/mes/pd/task/dispatch', data); |
| | | } |
| | | |
| | | export function completePdTask(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.completeTask(id); |
| | | } |
| | | return requestClient.put('/mes/pd/task/complete', undefined, { |
| | | params: { id }, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdVersionChangeApi { |
| | | /** çæ¬åæ´ */ |
| | | export interface VersionChange { |
| | | id?: number; |
| | | code?: string; |
| | | projectId?: number; |
| | | projectCode?: string; |
| | | projectName?: string; |
| | | changeType?: number; |
| | | fromVersion?: string; |
| | | toVersion?: string; |
| | | changeReason?: string; |
| | | changeContent?: string; |
| | | applicantId?: number; |
| | | applicantName?: string; |
| | | remark?: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | projectId?: number; |
| | | changeType?: number; |
| | | } |
| | | } |
| | | |
| | | export function getPdVersionChangePage( |
| | | params: MesPdVersionChangeApi.PageParams, |
| | | ) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getVersionChangePage(params); |
| | | } |
| | | return requestClient.get<PageResult<MesPdVersionChangeApi.VersionChange>>( |
| | | '/mes/pd/version-change/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | export function getPdVersionChange(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getVersionChange(id); |
| | | } |
| | | return requestClient.get<MesPdVersionChangeApi.VersionChange>( |
| | | `/mes/pd/version-change/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | export function createPdVersionChange(data: MesPdVersionChangeApi.VersionChange) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.createVersionChange(data); |
| | | } |
| | | return requestClient.post<number>('/mes/pd/version-change/create', data); |
| | | } |
| | | |
| | | export function updatePdVersionChange(data: MesPdVersionChangeApi.VersionChange) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.updateVersionChange(data); |
| | | } |
| | | return requestClient.put('/mes/pd/version-change/update', data); |
| | | } |
| | | |
| | | export function deletePdVersionChange(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.deleteVersionChange(id); |
| | | } |
| | | return requestClient.delete(`/mes/pd/version-change/delete?id=${id}`); |
| | | } |
| | | |
| | | export function approvePdVersionChange(id: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.approveVersionChange(id); |
| | | } |
| | | return requestClient.put('/mes/pd/version-change/approve', undefined, { |
| | | params: { id }, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | import { isPdMockEnabled } from '../mock/enabled'; |
| | | import { pdMockStore } from '../mock/store'; |
| | | |
| | | export namespace MesPdWorkflowApi { |
| | | /** 设计æµç¨èç¹ */ |
| | | export interface WorkflowNode { |
| | | id?: number; |
| | | projectId?: number; |
| | | nodeName?: string; |
| | | nodeType?: string; |
| | | operatorName?: string; |
| | | status?: number; |
| | | operateTime?: number; |
| | | remark?: string; |
| | | } |
| | | } |
| | | |
| | | export function getPdWorkflowList(projectId: number) { |
| | | if (isPdMockEnabled()) { |
| | | return pdMockStore.getWorkflowList(projectId); |
| | | } |
| | | return requestClient.get<MesPdWorkflowApi.WorkflowNode[]>( |
| | | '/mes/pd/workflow/list', |
| | | { params: { projectId } }, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '../../../../../packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesPdParameterApi { |
| | | /** å·¥èºåæ° */ |
| | | export interface Parameter { |
| | | id?: number; |
| | | code?: string; |
| | | name?: string; |
| | | type?: number; |
| | | unit?: string; |
| | | requiredFlag?: boolean; |
| | | dictType?: string; |
| | | apiUrl?: string; |
| | | remark?: string; |
| | | createTime?: Date; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢å·¥èºåæ°å页 */ |
| | | export function getParameterPage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesPdParameterApi.Parameter>>( |
| | | '/mes/pro/process-design/parameter/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢å·¥èºåæ°ç²¾ç®å表 */ |
| | | export function getParameterSimpleList() { |
| | | return requestClient.get<MesPdParameterApi.Parameter[]>( |
| | | '/mes/pro/process-design/parameter/simple-list', |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢å·¥èºåæ°è¯¦æ
*/ |
| | | export function getParameter(id: number) { |
| | | return requestClient.get<MesPdParameterApi.Parameter>( |
| | | `/mes/pro/process-design/parameter/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢å·¥èºåæ° */ |
| | | export function createParameter(data: MesPdParameterApi.Parameter) { |
| | | return requestClient.post( |
| | | '/mes/pro/process-design/parameter/create', |
| | | data, |
| | | ); |
| | | } |
| | | |
| | | /** ä¿®æ¹å·¥èºåæ° */ |
| | | export function updateParameter(data: MesPdParameterApi.Parameter) { |
| | | return requestClient.put( |
| | | '/mes/pro/process-design/parameter/update', |
| | | data, |
| | | ); |
| | | } |
| | | |
| | | /** å é¤å·¥èºåæ° */ |
| | | export function deleteParameter(id: number) { |
| | | return requestClient.delete( |
| | | `/mes/pro/process-design/parameter/delete?id=${id}`, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '../../../../../packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesPdTemplateApi { |
| | | /** å·¥èºåæ°æ¨¡æ¿ */ |
| | | export interface Template { |
| | | id?: number; |
| | | name?: string; |
| | | type?: number; |
| | | parameterIds?: number[]; |
| | | createUser?: string; |
| | | createTime?: Date; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢å·¥èºåæ°æ¨¡æ¿å页 */ |
| | | export function getTemplatePage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesPdTemplateApi.Template>>( |
| | | '/mes/pro/process-design/template/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢å·¥èºåæ°æ¨¡æ¿è¯¦æ
*/ |
| | | export function getTemplate(id: number) { |
| | | return requestClient.get<MesPdTemplateApi.Template>( |
| | | `/mes/pro/process-design/template/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢å·¥èºåæ°æ¨¡æ¿ */ |
| | | export function createTemplate(data: MesPdTemplateApi.Template) { |
| | | return requestClient.post( |
| | | '/mes/pro/process-design/template/create', |
| | | data, |
| | | ); |
| | | } |
| | | |
| | | /** ä¿®æ¹å·¥èºåæ°æ¨¡æ¿ */ |
| | | export function updateTemplate(data: MesPdTemplateApi.Template) { |
| | | return requestClient.put( |
| | | '/mes/pro/process-design/template/update', |
| | | data, |
| | | ); |
| | | } |
| | | |
| | | /** å é¤å·¥èºåæ°æ¨¡æ¿ */ |
| | | export function deleteTemplate(id: number) { |
| | | return requestClient.delete( |
| | | `/mes/pro/process-design/template/delete?id=${id}`, |
| | | ); |
| | | } |
| | |
| | | MD_VENDOR_CODE: 'MD_VENDOR_CODE', |
| | | MD_WORKSTATION_CODE: 'MD_WORKSTATION_CODE', |
| | | MD_WORKSHOP_CODE: 'MD_WORKSHOP_CODE', |
| | | PD_PROJECT_CODE: 'PD_PROJECT_CODE', |
| | | PD_TASK_CODE: 'PD_TASK_CODE', |
| | | PD_VERSION_CODE: 'PD_VERSION_CODE', |
| | | PRO_CARD_CODE: 'PRO_CARD_CODE', |
| | | PRO_FEEDBACK_CODE: 'PRO_FEEDBACK_CODE', |
| | | PRO_PROCESS_CODE: 'PRO_PROCESS_CODE', |
| | |
| | | RIGHT: 2, // å³è¡¥é½ |
| | | } as const; |
| | | |
| | | /** MES 产å设计项ç®ç¶ææä¸¾ */ |
| | | export const MesPdProjectStatusEnum = { |
| | | DRAFT: MesOrderStatusConstants.DRAFT, |
| | | DESIGNING: 10, |
| | | REVIEWING: MesOrderStatusConstants.APPROVING, |
| | | APPROVED: MesOrderStatusConstants.APPROVED, |
| | | ARCHIVED: 20, |
| | | CANCELLED: MesOrderStatusConstants.CANCELLED, |
| | | } as const; |
| | | |
| | | /** MES 产å设计任å¡ç¶ææä¸¾ */ |
| | | export const MesPdTaskStatusEnum = { |
| | | PENDING: 0, |
| | | ASSIGNED: 1, |
| | | IN_PROGRESS: 2, |
| | | COMPLETED: 3, |
| | | CANCELLED: 4, |
| | | } as const; |
| | | |
| | | /** MES 产å设计å¾çº¸å®¡æ ¸ç¶ææä¸¾ */ |
| | | export const MesPdDrawingReviewStatusEnum = { |
| | | PENDING: MesOrderStatusConstants.APPROVING, |
| | | APPROVED: MesOrderStatusConstants.APPROVED, |
| | | REJECTED: 5, |
| | | } as const; |
| | | |
| | | /** MES 产åè®¾è®¡çæ¬åæ´ç±»åæä¸¾ */ |
| | | export const MesPdVersionChangeTypeEnum = { |
| | | MAJOR: 1, |
| | | MINOR: 2, |
| | | PATCH: 3, |
| | | } as const; |
| | | |
| | | /** MES 产åè®¾è®¡èµæç±»åæä¸¾ */ |
| | | export const MesPdDocumentTypeEnum = { |
| | | SCHEME: 1, |
| | | DRAWING: 2, |
| | | MODEL: 3, |
| | | PRODUCT_DESIGN: 4, |
| | | OTHER: 5, |
| | | BOM: 6, |
| | | } as const; |
| | | |
| | | /** MES æ¡ç æ ¼å¼æä¸¾ */ |
| | | export enum BarcodeFormatEnum { |
| | | QR_CODE = 1, |
| | |
| | | [BarcodeFormatEnum.UPC_A]: 'UPC_A', |
| | | }; |
| | | |
| | | /** MES å·¥èºåæ°ç±»åæä¸¾ */ |
| | | export const MesPdParamTypeEnum = { |
| | | NUMERIC: 1, // æ°å¼æ ¼å¼ |
| | | TEXT: 2, // ææ¬æ ¼å¼ |
| | | SELECT: 3, // 䏿é项 |
| | | TIME: 4, // æ¶é´æ ¼å¼ |
| | | DATE: 5, // æ¥ææ ¼å¼ |
| | | } as const; |
| | | |
| | | /** MES å·¥èºæ¨¡æ¿ç±»åæä¸¾ */ |
| | | export const MesPdTemplateTypeEnum = { |
| | | PRODUCTION: 1, // çäº§è¦æ± |
| | | SELF_CHECK: 2, // èªæ£è¦æ± |
| | | PATROL_CHECK: 3, // å·¡æ£è¦æ± |
| | | } as const; |
| | | |
| | | /** MES æ¡ç ä¸å¡ç±»åæä¸¾ */ |
| | | export enum BarcodeBizTypeEnum { |
| | | WAREHOUSE = 102, |
| | |
| | | MES_PRO_TASK_STATUS: 'mes_pro_task_status', // MES ç产任å¡ç¶æ |
| | | MES_PRO_FEEDBACK_STATUS: 'mes_pro_feedback_status', // MES ç产æ¥å·¥ç¶æ |
| | | MES_PRO_FEEDBACK_TYPE: 'mes_pro_feedback_type', // MES ç产æ¥å·¥ç±»å |
| | | MES_PD_PROJECT_STATUS: 'mes_pd_project_status', // MES 产å设计项ç®ç¶æ |
| | | MES_PD_TASK_STATUS: 'mes_pd_task_status', // MES 产å设计任å¡ç¶æ |
| | | MES_PD_DRAWING_REVIEW_STATUS: 'mes_pd_drawing_review_status', // MES å¾çº¸å®¡æ ¸ç¶æ |
| | | MES_PD_VERSION_CHANGE_TYPE: 'mes_pd_version_change_type', // MES çæ¬åæ´ç±»å |
| | | MES_PD_DOCUMENT_TYPE: 'mes_pd_document_type', // MES è®¾è®¡èµæç±»å |
| | | MES_PD_ARCHIVE_STATUS: 'mes_pd_archive_status', // MES è®¾è®¡å½æ¡£ç¶æ |
| | | MES_PRO_FEEDBACK_CHANNEL: 'mes_pro_feedback_channel', // MES ç产æ¥å·¥éå¾ |
| | | MES_PRO_ANDON_STATUS: 'mes_pro_andon_status', // MES å®ç¯å¤ç½®ç¶æ |
| | | MES_PRO_ANDON_LEVEL: 'mes_pro_andon_level', // MES å®ç¯çº§å« |
| | |
| | | MES_WM_STOCK_TAKING_PLAN_PARAM_TYPE: 'mes_wm_stock_taking_plan_param_type', // MES çç¹æ¹æ¡åæ°ç±»å |
| | | MES_WM_TRANSFER_STATUS: 'mes_wm_transfer_status', // MES 转移åç¶æ |
| | | MES_WM_TRANSFER_TYPE: 'mes_wm_transfer_type', // MES 转移åç±»å |
| | | MES_PD_PARAM_TYPE: 'mes_pd_param_type', // MES å·¥èºåæ°ç±»å |
| | | MES_PD_TEMPLATE_TYPE: 'mes_pd_template_type', // MES å·¥èºæ¨¡æ¿ç±»å |
| | | } as const; |
| | | |
| | | /** ========== WMS - ä»å¨ç®¡ç模å ========== */ |
| | |
| | | }, |
| | | component: () => import('#/views/mes/pro/task/edit/index.vue'), |
| | | }, |
| | | { |
| | | path: 'pd/project/detail/:id', |
| | | name: 'MesPdProjectDetail', |
| | | meta: { |
| | | title: '设计任å¡è¯¦æ
', |
| | | activePath: '/mes/pd/project', |
| | | }, |
| | | component: () => import('#/views/mes/pd/project/detail/index.vue'), |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesMdClientApi } from '#/api/mes/md/client'; |
| | | |
| | | import { h } from 'vue'; |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | |
| | | import { |
| | | CommonStatusEnum, |
| | | DICT_TYPE, |
| | | MesAutoCodeRuleCode, |
| | | } from '../../../../packages/constants/src'; |
| | | import { getDictOptions } from '../../../../packages/effects/hooks/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹å®¢æ·ç表å */ |
| | | export function useFormSchema( |
| | | formType: FormType, |
| | | formApi?: VbenFormApi, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: '客æ·ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·ç¼ç ', |
| | | }, |
| | | rules: 'required', |
| | | suffix: |
| | | formType === 'detail' |
| | | ? undefined |
| | | : () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const code = await generateAutoCode( |
| | | MesAutoCodeRuleCode.MD_CLIENT_CODE, |
| | | ); |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'èªå¨çæ' }, |
| | | ), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '客æ·åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·åç§°', |
| | | }, |
| | | rules: z.string().min(1, '客æ·åç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'nickname', |
| | | label: '客æ·ç®ç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·ç®ç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'englishName', |
| | | label: '客æ·è±æåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·è±æåç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: '客æ·ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_CLIENT_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©å®¢æ·ç±»å', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'description', |
| | | label: '客æ·ç®ä»', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·ç®ä»', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'address', |
| | | label: '客æ·å°å', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·å°å', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'website', |
| | | label: '客æ·å®ç½å°å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·å®ç½å°å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'email', |
| | | label: '客æ·é®ç®±å°å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·é®ç®±å°å', |
| | | }, |
| | | rules: z.string().email('é®ç®±æ ¼å¼ä¸æ£ç¡®').or(z.literal('')).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'telephone', |
| | | label: '客æ·çµè¯', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ·çµè¯', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'logo', |
| | | label: 'å®¢æ· LOGO', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å®¢æ· LOGO å°å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact1Name', |
| | | label: 'è系人1', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人1', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact1Telephone', |
| | | label: 'è系人1çµè¯', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人1çµè¯', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact1Email', |
| | | label: 'è系人1é®ç®±', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人1é®ç®±', |
| | | }, |
| | | rules: z.string().email('é®ç®±æ ¼å¼ä¸æ£ç¡®').or(z.literal('')).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'contact2Name', |
| | | label: 'è系人2', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人2', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact2Telephone', |
| | | label: 'è系人2çµè¯', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人2çµè¯', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact2Email', |
| | | label: 'è系人2é®ç®±', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人2é®ç®±', |
| | | }, |
| | | rules: z.string().email('é®ç®±æ ¼å¼ä¸æ£ç¡®').or(z.literal('')).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'creditCode', |
| | | label: '社ä¼ä¿¡ç¨ä»£ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | }, |
| | | rules: z.number().default(CommonStatusEnum.ENABLE), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 导å
¥å®¢æ·ç表å */ |
| | | export function useImportFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'file', |
| | | label: 'å®¢æ·æ°æ®', |
| | | component: 'Upload', |
| | | rules: 'required', |
| | | help: 'ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶', |
| | | }, |
| | | { |
| | | fieldName: 'updateSupport', |
| | | label: 'æ¯å¦è¦ç', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | help: 'æ¯å¦æ´æ°å·²ç»åå¨çå®¢æ·æ°æ®', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: '客æ·ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å®¢æ·ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '客æ·åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å®¢æ·åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'nickname', |
| | | label: '客æ·ç®ç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å®¢æ·ç®ç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'englishName', |
| | | label: 'è±æåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å®¢æ·è±æåç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: '客æ·ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_CLIENT_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©å®¢æ·ç±»å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesMdClientApi.Client>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: '客æ·ç¼ç ', |
| | | minWidth: 150, |
| | | slots: { default: 'code' }, |
| | | }, |
| | | { |
| | | field: 'name', |
| | | title: '客æ·åç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'nickname', |
| | | title: '客æ·ç®ç§°', |
| | | minWidth: 130, |
| | | }, |
| | | { |
| | | field: 'type', |
| | | title: '客æ·ç±»å', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_CLIENT_TYPE }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'telephone', |
| | | title: '客æ·çµè¯', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'contact1Name', |
| | | title: 'è系人1', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'contact1Telephone', |
| | | title: 'è系人1çµè¯', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 160, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 客æ·éæ©å¼¹çªçæç´¢è¡¨å */ |
| | | /** 客æ·éæ©å¼¹çªæç´¢è¡¨å */ |
| | | export function useClientSelectGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** 客æ·éæ©å¼¹çªçåæ®µ */ |
| | | /** 客æ·éæ©å¼¹çªåè¡¨åæ®µ */ |
| | | export function useClientSelectGridColumns( |
| | | multiple = true, |
| | | ): VxeTableGridOptions<MesMdClientApi.Client>['columns'] { |
| | |
| | | label: 'ç©æåç±»', |
| | | component: markRaw(MdItemTypeSelect), |
| | | componentProps: { |
| | | onChange: async (itemType: any) => { |
| | | onChange: async (itemType: { itemOrProduct?: string }) => { |
| | | await formApi?.setFieldValue( |
| | | 'itemOrProduct', |
| | | itemType?.itemOrProduct, |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesMdVendorApi } from '#/api/mes/md/vendor'; |
| | | |
| | | import { h } from 'vue'; |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | |
| | | import { |
| | | CommonStatusEnum, |
| | | DICT_TYPE, |
| | | MesAutoCodeRuleCode, |
| | | } from '../../../../packages/constants/src'; |
| | | import { getDictOptions } from '../../../../packages/effects/hooks/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ä¾åºåç表å */ |
| | | export function useFormSchema( |
| | | formType: FormType, |
| | | formApi?: VbenFormApi, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä¾åºåç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåç¼ç ', |
| | | }, |
| | | rules: 'required', |
| | | suffix: |
| | | formType === 'detail' |
| | | ? undefined |
| | | : () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const code = await generateAutoCode( |
| | | MesAutoCodeRuleCode.MD_VENDOR_CODE, |
| | | ); |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'èªå¨çæ' }, |
| | | ), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºååç§°', |
| | | }, |
| | | rules: z.string().min(1, 'ä¾åºååç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'nickname', |
| | | label: 'ä¾åºåç®ç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåç®ç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'englishName', |
| | | label: 'è±æåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåè±æåç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'level', |
| | | label: 'ä¾åºåç级', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_VENDOR_LEVEL), |
| | | placeholder: 'è¯·éæ©ä¾åºåç级', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'description', |
| | | label: 'ä¾åºåç®ä»', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåç®ä»', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'address', |
| | | label: 'ä¾åºåå°å', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåå°å', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'website', |
| | | label: 'å®ç½å°å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåå®ç½å°å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'email', |
| | | label: 'é®ç®±å°å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåé®ç®±å°å', |
| | | }, |
| | | rules: z.string().email('é®ç®±æ ¼å¼ä¸æ£ç¡®').or(z.literal('')).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'telephone', |
| | | label: 'ä¾åºåçµè¯', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåçµè¯', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'score', |
| | | label: 'ä¾åºåè¯å', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | max: 100, |
| | | min: 0, |
| | | precision: 0, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact1Name', |
| | | label: 'è系人1', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人1', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact1Telephone', |
| | | label: 'è系人1çµè¯', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人1çµè¯', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact1Email', |
| | | label: 'è系人1é®ç®±', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人1é®ç®±', |
| | | }, |
| | | rules: z.string().email('é®ç®±æ ¼å¼ä¸æ£ç¡®').or(z.literal('')).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'contact2Name', |
| | | label: 'è系人2', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人2', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact2Telephone', |
| | | label: 'è系人2çµè¯', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人2çµè¯', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contact2Email', |
| | | label: 'è系人2é®ç®±', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人2é®ç®±', |
| | | }, |
| | | rules: z.string().email('é®ç®±æ ¼å¼ä¸æ£ç¡®').or(z.literal('')).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'creditCode', |
| | | label: '社ä¼ä¿¡ç¨ä»£ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | }, |
| | | rules: z.number().default(CommonStatusEnum.ENABLE), |
| | | }, |
| | | { |
| | | fieldName: 'logo', |
| | | label: 'ä¾åºå LOGO', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºå LOGO å°å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 导å
¥ä¾åºåç表å */ |
| | | export function useImportFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'file', |
| | | label: 'ä¾åºåæ°æ®', |
| | | component: 'Upload', |
| | | rules: 'required', |
| | | help: 'ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶', |
| | | }, |
| | | { |
| | | fieldName: 'updateSupport', |
| | | label: 'æ¯å¦è¦ç', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | help: 'æ¯å¦æ´æ°å·²ç»åå¨çä¾åºåæ°æ®', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä¾åºåç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ä¾åºåç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ä¾åºååç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'nickname', |
| | | label: 'ä¾åºåç®ç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ä¾åºåç®ç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'englishName', |
| | | label: 'è±æåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥è±æåç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesMdVendorApi.Vendor>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'ä¾åºåç¼ç ', |
| | | minWidth: 150, |
| | | slots: { default: 'code' }, |
| | | }, |
| | | { |
| | | field: 'name', |
| | | title: 'ä¾åºååç§°', |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | | field: 'nickname', |
| | | title: 'ä¾åºåç®ç§°', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'level', |
| | | title: 'ä¾åºåç级', |
| | | width: 130, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_VENDOR_LEVEL }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'score', |
| | | title: 'ä¾åºåè¯å', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'telephone', |
| | | title: 'ä¾åºåçµè¯', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 160, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ä¾åºå鿩弹çªçæç´¢è¡¨å */ |
| | | /** ä¾åºåéæ©å¼¹çªæç´¢è¡¨å */ |
| | | export function useVendorSelectGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** ä¾åºå鿩弹çªçåæ®µ */ |
| | | /** ä¾åºå鿩弹çªåè¡¨åæ®µ */ |
| | | export function useVendorSelectGridColumns( |
| | | multiple = true, |
| | | ): VxeTableGridOptions<MesMdVendorApi.Vendor>['columns'] { |
| | |
| | | import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse'; |
| | | import { getWarehouseAreaSimpleList } from '#/api/mes/wm/warehouse/area'; |
| | | import { getWarehouseLocationSimpleList } from '#/api/mes/wm/warehouse/location'; |
| | | import { ProProcessSelect } from '#/views/mes/pro/process/components'; |
| | | import { ProProcessSelect } from '#/views/mes/process-design/process/components'; |
| | | |
| | | import { MdWorkshopSelect } from './components'; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from "#/adapter/form"; |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | |
| | | import { DICT_TYPE } from "#/packages/constants/src"; |
| | | import { getDictOptions } from "#/packages/effects/hooks/src"; |
| | | |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: "code", |
| | | label: "彿¡£ç¼å·", |
| | | component: "Input", |
| | | componentProps: { placeholder: "请è¾å
¥å½æ¡£ç¼å·" }, |
| | | }, |
| | | { |
| | | fieldName: "projectCode", |
| | | label: "ä»»å¡ç¼ç ", |
| | | component: "Input", |
| | | componentProps: { placeholder: "请è¾å
¥ä»»å¡ç¼ç " }, |
| | | }, |
| | | { |
| | | fieldName: "productCode", |
| | | label: "产åç¼ç ", |
| | | component: "Input", |
| | | componentProps: { placeholder: "请è¾å
¥äº§åç¼ç " }, |
| | | }, |
| | | { |
| | | fieldName: "archiveStatus", |
| | | label: "彿¡£ç¶æ", |
| | | component: "Select", |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_ARCHIVE_STATUS, "number"), |
| | | placeholder: "è¯·éæ©å½æ¡£ç¶æ", |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useGridColumns(): VxeTableGridOptions["columns"] { |
| | | return [ |
| | | { |
| | | field: "code", |
| | | title: "彿¡£ç¼å·", |
| | | minWidth: 130, |
| | | slots: { default: "code" }, |
| | | }, |
| | | { field: "projectCode", title: "ä»»å¡ç¼ç ", minWidth: 120 }, |
| | | { field: "projectName", title: "ä»»å¡åç§°", minWidth: 140 }, |
| | | { field: "productCode", title: "产åç¼ç ", minWidth: 120 }, |
| | | { field: "productName", title: "产ååç§°", minWidth: 140 }, |
| | | { field: "version", title: "çæ¬å·", minWidth: 90 }, |
| | | { |
| | | field: "archiveStatus", |
| | | title: "彿¡£ç¶æ", |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: "CellDict", |
| | | props: { type: DICT_TYPE.MES_PD_ARCHIVE_STATUS }, |
| | | }, |
| | | }, |
| | | { field: "documentCount", title: "èµææ°", minWidth: 80 }, |
| | | { field: "archiverName", title: "彿¡£äºº", minWidth: 100 }, |
| | | { |
| | | field: "archiveTime", |
| | | title: "彿¡£æ¶é´", |
| | | minWidth: 160, |
| | | formatter: "formatDateTime", |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdArchiveApi } from '#/api/mes/pd/archive'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportPdArchive, getPdArchivePage } from '#/api/mes/pd/archive'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Detail from './modules/detail.vue'; |
| | | |
| | | const detailRef = ref<InstanceType<typeof Detail>>(); |
| | | |
| | | function handleDetail(row: MesPdArchiveApi.Archive) { |
| | | detailRef.value?.open(row.id!); |
| | | } |
| | | |
| | | async function handleExport() { |
| | | const data = await exportPdArchive(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'è®¾è®¡å½æ¡£å°è´¦.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useGridFormSchema() }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPdArchivePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesPdArchiveApi.Archive>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <Detail ref="detailRef" /> |
| | | |
| | | <Grid table-title="产åçæ¬å½æ¡£å°è´¦"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: '导åº', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pd-archive:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.code }}</Button> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesPdArchiveApi } from '#/api/mes/pd/archive'; |
| | | import type { MesPdVersionChangeApi } from '#/api/mes/pd/version-change'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { Alert, Descriptions, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { getPdArchive } from '#/api/mes/pd/archive'; |
| | | |
| | | import MaterialPanel from '#/views/mes/pd/project/modules/material-panel.vue'; |
| | | import VersionChangeList from '#/views/mes/pd/project/modules/version-change/version-change-list.vue'; |
| | | |
| | | const detail = ref<MesPdArchiveApi.Archive>(); |
| | | const activeTab = ref('version-change'); |
| | | const materialVersion = ref<string>(); |
| | | const materialVersionHint = ref<string>(); |
| | | |
| | | function handleViewMaterials(row: MesPdVersionChangeApi.VersionChange) { |
| | | materialVersion.value = row.toVersion; |
| | | materialVersionHint.value = `åæ´å ${row.code}ï¼${row.fromVersion} â ${row.toVersion}`; |
| | | activeTab.value = 'material'; |
| | | } |
| | | |
| | | function clearMaterialVersionFilter() { |
| | | materialVersion.value = detail.value?.version; |
| | | materialVersionHint.value = undefined; |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | detail.value = undefined; |
| | | activeTab.value = 'version-change'; |
| | | materialVersion.value = undefined; |
| | | materialVersionHint.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number }>(); |
| | | modalApi.lock(); |
| | | try { |
| | | detail.value = await getPdArchive(data.id); |
| | | materialVersion.value = detail.value.version; |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | defineExpose({ |
| | | open: (id: number) => modalApi.setData({ id }).open(), |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-4/5" title="彿¡£è¯¦æ
"> |
| | | <Descriptions v-if="detail" :column="3" bordered class="mx-4 mb-4" size="small"> |
| | | <Descriptions.Item label="彿¡£ç¼å·">{{ detail.code }}</Descriptions.Item> |
| | | <Descriptions.Item label="产åç¼ç ">{{ detail.productCode }}</Descriptions.Item> |
| | | <Descriptions.Item label="产ååç§°">{{ detail.productName }}</Descriptions.Item> |
| | | <Descriptions.Item label="è®¾è®¡çæ¬">{{ detail.version }}</Descriptions.Item> |
| | | <Descriptions.Item label="ä»»å¡ç¼ç ">{{ detail.projectCode }}</Descriptions.Item> |
| | | <Descriptions.Item label="ä»»å¡åç§°">{{ detail.projectName }}</Descriptions.Item> |
| | | <Descriptions.Item label="èµææ°">{{ detail.documentCount ?? '-' }}</Descriptions.Item> |
| | | <Descriptions.Item label="彿¡£äºº">{{ detail.archiverName || '-' }}</Descriptions.Item> |
| | | <Descriptions.Item label="彿¡£è·¯å¾" :span="3"> |
| | | {{ detail.storagePath || '-' }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="夿³¨" :span="3">{{ detail.remark || '-' }}</Descriptions.Item> |
| | | </Descriptions> |
| | | |
| | | <template v-if="detail?.projectId"> |
| | | <Tabs v-model:active-key="activeTab" class="mx-4"> |
| | | <Tabs.TabPane key="version-change" tab="çæ¬åæ´è®°å½"> |
| | | <VersionChangeList |
| | | form-type="detail" |
| | | :project-id="detail.projectId" |
| | | :project-version="detail.version" |
| | | @view-materials="handleViewMaterials" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="material" tab="çæ¬èµæ"> |
| | | <Alert |
| | | v-if="materialVersionHint" |
| | | class="mb-3" |
| | | closable |
| | | show-icon |
| | | type="info" |
| | | :message="`æ£å¨æ¥ç ${materialVersion} çæ¬èµæï¼${materialVersionHint}ï¼`" |
| | | @close="clearMaterialVersionFilter" |
| | | /> |
| | | <MaterialPanel |
| | | form-type="detail" |
| | | :project-id="detail.projectId" |
| | | table-title="çæ¬èµæ" |
| | | :version="materialVersion" |
| | | /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesMdItemApi } from '#/api/mes/md/item'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { h, markRaw } from 'vue'; |
| | | |
| | | import { |
| | | DICT_TYPE, |
| | | MesAutoCodeRuleCode, |
| | | MesPdDocumentTypeEnum, |
| | | } from '#/packages/constants/src'; |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { isPdMockEnabled } from '#/api/mes/pd/mock/enabled'; |
| | | import { mockNextCode } from '#/api/mes/pd/mock/utils'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | async function generateTaskCode(formApi?: VbenFormApi) { |
| | | const code = isPdMockEnabled() |
| | | ? mockNextCode('DT-2026', Math.floor(Date.now() % 10_000)) |
| | | : await generateAutoCode(MesAutoCodeRuleCode.PD_PROJECT_CODE); |
| | | await formApi?.setFieldValue('code', code); |
| | | } |
| | | |
| | | async function syncTaskNameFromProduct( |
| | | formApi: VbenFormApi | undefined, |
| | | productName?: string, |
| | | ) { |
| | | const version = (await formApi?.getFieldValue('version')) as |
| | | | string |
| | | | undefined; |
| | | const name = productName?.trim(); |
| | | if (name && version?.trim()) { |
| | | await formApi?.setFieldValue('name', `${name} ${version.trim()} 设计`); |
| | | } |
| | | } |
| | | |
| | | /** 产åéæ©ï¼éæ©åèªå¨å¸¦åºäº§åç¼ç */ |
| | | function useProductSelectSchema( |
| | | formApi: VbenFormApi | undefined, |
| | | readonly: boolean, |
| | | ): VbenFormSchema[] { |
| | | if (readonly) { |
| | | return [ |
| | | { |
| | | fieldName: 'productCode', |
| | | label: '产åç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { disabled: true }, |
| | | }, |
| | | { |
| | | fieldName: 'productName', |
| | | label: '产ååç§°', |
| | | component: 'Input', |
| | | componentProps: { disabled: true }, |
| | | }, |
| | | ]; |
| | | } |
| | | return [ |
| | | { |
| | | fieldName: 'productId', |
| | | label: '产å', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | onChange: async (item: MesMdItemApi.Item | undefined) => { |
| | | await formApi?.setFieldValue('productCode', item?.code ?? ''); |
| | | await formApi?.setFieldValue('productName', item?.name ?? ''); |
| | | await syncTaskNameFromProduct(formApi, item?.name); |
| | | }, |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'productCode', |
| | | label: '产åç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled: true, |
| | | placeholder: 'éæ©äº§ååèªå¨å¸¦åº', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'productName', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å起设计任å¡è¡¨å */ |
| | | export function useCreateTaskSchema(formApi?: VbenFormApi): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä»»å¡ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请ç¹å»çæä»»å¡ç¼ç ' }, |
| | | rules: 'required', |
| | | suffix: () => |
| | | h( |
| | | Button, |
| | | { type: 'default', onClick: () => generateTaskCode(formApi) }, |
| | | { default: () => 'çæ' }, |
| | | ), |
| | | }, |
| | | ...useProductSelectSchema(formApi, false), |
| | | { |
| | | fieldName: 'version', |
| | | label: 'è®¾è®¡çæ¬', |
| | | component: 'Input', |
| | | componentProps: { placeholder: 'å¦ V1.0ãV2.1' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä»»å¡åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: 'å°æ ¹æ®äº§åä¸çæ¬èªå¨çæï¼å¯ä¿®æ¹' }, |
| | | rules: 'required', |
| | | dependencies: { |
| | | triggerFields: ['productName', 'version'], |
| | | trigger(values: Record<string, string>, form) { |
| | | const productName = values.productName?.trim(); |
| | | const version = values.version?.trim(); |
| | | if (productName && version) { |
| | | form.setFieldValue('name', `${productName} ${version} 设计`); |
| | | } |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'designerName', |
| | | label: '主设计å¸', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä¸»è®¾è®¡å¸' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'reviewerName', |
| | | label: 'å®¡æ ¸äºº', |
| | | component: 'Input', |
| | | componentProps: { placeholder: 'èµæå®¡æ ¸è´è´£äºº' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'planDateRange', |
| | | label: '计å卿', |
| | | component: 'RangePicker', |
| | | componentProps: getRangePickerDefaultProps(), |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: 'ä»»å¡è¯´æ', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: 'ç®è¿°æ¬æ¬¡è®¾è®¡ç®æ ãåæ´èæ¯ç', |
| | | rows: 3, |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** åå¸ä»»å¡ç¡®è®¤è¡¨å */ |
| | | export function usePublishFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'publishRemark', |
| | | label: 'åå¸è¯´æ', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: 'å¯éï¼å¡«ååå¸è¦æ±ææ³¨æäºé¡¹', |
| | | rows: 3, |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export type WorkflowActionType = 'approve' | 'archive' | 'submit-review'; |
| | | |
| | | export interface WorkflowActionConfig { |
| | | title: string; |
| | | alertMessage: string; |
| | | alertDescription: string; |
| | | remarkLabel: string; |
| | | remarkTag: string; |
| | | remarkPlaceholder: string; |
| | | successMessage: string; |
| | | alertType: 'info' | 'success' | 'warning'; |
| | | } |
| | | |
| | | export const WORKFLOW_ACTION_CONFIG: Record< |
| | | WorkflowActionType, |
| | | WorkflowActionConfig |
| | | > = { |
| | | 'submit-review': { |
| | | title: 'æäº¤èµæå®¡æ ¸', |
| | | alertMessage: '确认æäº¤å®¡æ ¸', |
| | | alertDescription: |
| | | 'æäº¤åå°è¿å
¥èµæå®¡æ ¸ç¯èï¼è¯·ç¡®è®¤è®¾è®¡èµæå·²ä¸ä¼ 宿´ãæäº¤å设计å¸å°æ æ³ç»§ç»ä¿®æ¹èµæã', |
| | | remarkLabel: 'æäº¤è¯´æ', |
| | | remarkTag: 'ãæäº¤è¯´æã', |
| | | remarkPlaceholder: 'å¯éï¼å¡«åæäº¤å®¡æ ¸çè¡¥å
说æ', |
| | | successMessage: 'å·²æäº¤èµæå®¡æ ¸', |
| | | alertType: 'info', |
| | | }, |
| | | approve: { |
| | | title: 'å®¡æ ¸éè¿', |
| | | alertMessage: 'ç¡®è®¤å®¡æ ¸éè¿', |
| | | alertDescription: |
| | | 'å®¡æ ¸éè¿åä»»å¡è¿å
¥ãå·²éè¿ãç¶æï¼å¯è¿è¡å½æ¡£æä½ãè¯·ç¡®è®¤è®¾è®¡èµæç¬¦åè¦æ±ã', |
| | | remarkLabel: 'å®¡æ ¸æè§', |
| | | remarkTag: 'ãå®¡æ ¸æè§ã', |
| | | remarkPlaceholder: 'å¯éï¼å¡«åå®¡æ ¸éè¿æè§', |
| | | successMessage: 'èµæå®¡æ ¸éè¿', |
| | | alertType: 'success', |
| | | }, |
| | | archive: { |
| | | title: '彿¡£è®¾è®¡ä»»å¡', |
| | | alertMessage: 'ç¡®è®¤å½æ¡£', |
| | | alertDescription: |
| | | '彿¡£åå°çææ¬çæ¬å½æ¡£å°è´¦è®°å½ï¼ä»»å¡ç¶æåä¸ºå·²å½æ¡£ï¼è®¾è®¡èµæå°éå®ä¸å¯ä¿®æ¹ã', |
| | | remarkLabel: '彿¡£è¯´æ', |
| | | remarkTag: 'ã彿¡£è¯´æã', |
| | | remarkPlaceholder: 'å¯éï¼å¡«å彿¡£å¤æ³¨', |
| | | successMessage: 'ä»»å¡å·²å½æ¡£', |
| | | alertType: 'warning', |
| | | }, |
| | | }; |
| | | |
| | | /** æµç¨æä½ç¡®è®¤è¡¨å */ |
| | | export function useWorkflowActionFormSchema( |
| | | action: WorkflowActionType, |
| | | ): VbenFormSchema[] { |
| | | const config = WORKFLOW_ACTION_CONFIG[action]; |
| | | return [ |
| | | { |
| | | fieldName: 'actionRemark', |
| | | label: config.remarkLabel, |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: config.remarkPlaceholder, |
| | | rows: 3, |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä»»å¡ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä»»å¡ç¼ç ' }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä»»å¡åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä»»å¡åç§°' }, |
| | | }, |
| | | { |
| | | fieldName: 'productCode', |
| | | label: '产åç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥äº§åç¼ç ' }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ä»»å¡ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_PROJECT_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©é¡¹ç®ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'code', title: 'ä»»å¡ç¼ç ', minWidth: 140, slots: { default: 'code' } }, |
| | | { field: 'name', title: 'ä»»å¡åç§°', minWidth: 160 }, |
| | | { field: 'productCode', title: '产åç¼ç ', minWidth: 120 }, |
| | | { field: 'productName', title: '产ååç§°', minWidth: 140 }, |
| | | { field: 'version', title: 'çæ¬å·', minWidth: 90 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_PROJECT_STATUS }, |
| | | }, |
| | | }, |
| | | { field: 'designerName', title: '主设计å¸', minWidth: 100 }, |
| | | { field: 'reviewerName', title: 'å®¡æ ¸äºº', minWidth: 100 }, |
| | | { field: 'planEndDate', title: '计å宿', minWidth: 120, formatter: 'formatDate' }, |
| | | { field: 'createTime', title: 'å建æ¶é´', minWidth: 160, formatter: 'formatDateTime' }, |
| | | { field: 'action', title: 'æä½', width: 220, fixed: 'right', slots: { default: 'actions' } }, |
| | | ]; |
| | | } |
| | | |
| | | export function useFormSchema( |
| | | formType: FormType, |
| | | formApi?: VbenFormApi, |
| | | ): VbenFormSchema[] { |
| | | const readonly = formType === 'detail'; |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä»»å¡ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥ä»»å¡ç¼ç ' }, |
| | | rules: 'required', |
| | | suffix: |
| | | formType === 'detail' |
| | | ? undefined |
| | | : () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const code = isPdMockEnabled() |
| | | ? mockNextCode( |
| | | 'DT-2026', |
| | | Math.floor(Date.now() % 10_000), |
| | | ) |
| | | : await generateAutoCode( |
| | | MesAutoCodeRuleCode.PD_PROJECT_CODE, |
| | | ); |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'çæ' }, |
| | | ), |
| | | }, |
| | | ...useProductSelectSchema(formApi, readonly), |
| | | { |
| | | fieldName: 'version', |
| | | label: 'çæ¬å·', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: 'å¦ V1.0' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä»»å¡åç§°', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: 'å¦ï¼æºè½æ¸©æ§æ¨¡ç» V1.0 设计' }, |
| | | rules: 'required', |
| | | dependencies: { |
| | | triggerFields: ['productName', 'version'], |
| | | trigger(values: Record<string, string>, form) { |
| | | if (readonly) { |
| | | return; |
| | | } |
| | | const productName = values.productName?.trim(); |
| | | const version = values.version?.trim(); |
| | | if (productName && version) { |
| | | form.setFieldValue('name', `${productName} ${version} 设计`); |
| | | } |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ä»»å¡ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | disabled: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_PROJECT_STATUS, 'number'), |
| | | }, |
| | | dependencies: { triggerFields: [''], show: () => formType !== 'create' }, |
| | | }, |
| | | { |
| | | fieldName: 'designerName', |
| | | label: '主设计å¸', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥ä¸»è®¾è®¡å¸' }, |
| | | }, |
| | | { |
| | | fieldName: 'reviewerName', |
| | | label: 'å®¡æ ¸äºº', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥å®¡æ ¸äºº' }, |
| | | }, |
| | | { |
| | | fieldName: 'planDateRange', |
| | | label: '计å卿', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | disabled: readonly, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | formItemClass: 'col-span-3', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è®¾è®¡èµæç±»åé项ï¼ä¸ä¼ æ¶éæ©ï¼ */ |
| | | export function getPdDocumentTypeOptions() { |
| | | return [ |
| | | { label: 'è®¾è®¡æ¹æ¡', value: MesPdDocumentTypeEnum.SCHEME }, |
| | | { label: '设计å¾çº¸', value: MesPdDocumentTypeEnum.DRAWING }, |
| | | { label: '设计模å', value: MesPdDocumentTypeEnum.MODEL }, |
| | | { label: '产å设计', value: MesPdDocumentTypeEnum.PRODUCT_DESIGN }, |
| | | { label: 'BOM æ¸
å', value: MesPdDocumentTypeEnum.BOM }, |
| | | { label: 'å
¶ä»éä»¶', value: MesPdDocumentTypeEnum.OTHER }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç»ä¸è®¾è®¡èµæå表å */ |
| | | export function useMaterialListGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'name', |
| | | label: 'èµæåç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥èµæåç§°' }, |
| | | }, |
| | | { |
| | | fieldName: 'documentType', |
| | | label: 'èµæç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getPdDocumentTypeOptions(), |
| | | placeholder: 'è¯·éæ©èµæç±»å', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useMaterialListGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | | field: 'documentType', |
| | | title: 'èµæç±»å', |
| | | minWidth: 110, |
| | | formatter: ({ cellValue }: { cellValue: number }) => { |
| | | const option = getPdDocumentTypeOptions().find( |
| | | (item) => item.value === cellValue, |
| | | ); |
| | | return option?.label ?? String(cellValue ?? ''); |
| | | }, |
| | | }, |
| | | { field: 'name', title: 'èµæåç§°', minWidth: 180 }, |
| | | { field: 'version', title: 'çæ¬', minWidth: 90 }, |
| | | { field: 'fileName', title: 'æä»¶å', minWidth: 200 }, |
| | | { field: 'uploaderName', title: 'ä¸ä¼ 人', minWidth: 100 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'ä¸ä¼ æ¶é´', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'action', |
| | | title: 'æä½', |
| | | width: 140, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** @deprecated æç±»åå Tab æ¶ä½¿ç¨ï¼è¯·æ¹ç¨ useMaterialListGridColumns */ |
| | | export function useDocumentGridColumns(documentType: number): VxeTableGridOptions['columns'] { |
| | | const typeLabel = |
| | | documentType === MesPdDocumentTypeEnum.SCHEME |
| | | ? 'æ¹æ¡' |
| | | : documentType === MesPdDocumentTypeEnum.DRAWING |
| | | ? 'å¾çº¸' |
| | | : documentType === MesPdDocumentTypeEnum.MODEL |
| | | ? '模å' |
| | | : documentType === MesPdDocumentTypeEnum.PRODUCT_DESIGN |
| | | ? '产å设计' |
| | | : 'èµæ'; |
| | | |
| | | return [ |
| | | { field: 'name', title: `${typeLabel}åç§°`, minWidth: 180 }, |
| | | { field: 'version', title: 'çæ¬', minWidth: 90 }, |
| | | { field: 'fileName', title: 'æä»¶å', minWidth: 200 }, |
| | | { field: 'uploaderName', title: 'ä¸ä¼ 人', minWidth: 100 }, |
| | | { field: 'createTime', title: 'ä¸ä¼ æ¶é´', minWidth: 160, formatter: 'formatDateTime' }, |
| | | { field: 'action', title: 'æä½', width: 140, slots: { default: 'actions' } }, |
| | | ]; |
| | | } |
| | | |
| | | /** BOM è¡¨æ ¼å */ |
| | | export function useBomGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'itemCode', title: 'ç©æç¼ç ', minWidth: 120 }, |
| | | { field: 'itemName', title: 'ç©æåç§°', minWidth: 160 }, |
| | | { field: 'specification', title: 'è§æ ¼åå·', minWidth: 140 }, |
| | | { field: 'unitMeasureName', title: 'åä½', minWidth: 80 }, |
| | | { field: 'quantity', title: 'ç¨é', minWidth: 90 }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 140 }, |
| | | { field: 'action', title: 'æä½', width: 140, slots: { default: 'actions' } }, |
| | | ]; |
| | | } |
| | | |
| | | export type { MesPdProjectApi }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { DescriptionItemSchema } from '#/components/description'; |
| | | |
| | | import { h } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { formatDate, formatDateTime } from '#/packages/utils/src'; |
| | | |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | |
| | | /** 设计任å¡è¯¦æ
æè¦ */ |
| | | export function useDetailSchema(): DescriptionItemSchema[] { |
| | | return [ |
| | | { field: 'code', label: 'ä»»å¡ç¼ç ' }, |
| | | { field: 'name', label: 'ä»»å¡åç§°' }, |
| | | { field: 'productCode', label: '产åç¼ç ' }, |
| | | { field: 'productName', label: '产ååç§°' }, |
| | | { field: 'version', label: 'è®¾è®¡çæ¬' }, |
| | | { |
| | | field: 'status', |
| | | label: 'ä»»å¡ç¶æ', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.MES_PD_PROJECT_STATUS, value: val }), |
| | | }, |
| | | { field: 'designerName', label: '设计å¸' }, |
| | | { field: 'reviewerName', label: 'å®¡æ ¸äºº' }, |
| | | { |
| | | field: 'planStartDate', |
| | | label: '计åå¼å§', |
| | | render: (val) => (val ? formatDate(val) : '-'), |
| | | }, |
| | | { |
| | | field: 'planEndDate', |
| | | label: '计å宿', |
| | | render: (val) => (val ? formatDate(val) : '-'), |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | label: 'åèµ·æ¶é´', |
| | | render: (val) => (val ? formatDateTime(val) : '-'), |
| | | }, |
| | | { field: 'remark', label: '夿³¨' }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { useTabs } from '#/packages/effects/hooks/src'; |
| | | import { MesPdProjectStatusEnum } from '#/packages/constants/src'; |
| | | |
| | | import { Card, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | getPdProject, |
| | | } from '#/api/mes/pd/project'; |
| | | import { useDescription } from '#/components/description'; |
| | | import { ACTION_ICON, TableAction } from '#/adapter/vxe-table'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import Form from '../modules/form.vue'; |
| | | import PublishForm from '../modules/publish-form.vue'; |
| | | import WorkflowActionForm from '../modules/workflow-action-form.vue'; |
| | | import ReviewList from '../modules/drawing-review/review-list.vue'; |
| | | import MaterialPanel from '../modules/material-panel.vue'; |
| | | import TaskDraftBanner from '../modules/task-draft-banner.vue'; |
| | | import WorkflowTimeline from '../modules/workflow-timeline.vue'; |
| | | import { useDetailSchema } from './data'; |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const tabs = useTabs(); |
| | | |
| | | const loading = ref(false); |
| | | const projectId = ref(0); |
| | | const project = ref<MesPdProjectApi.Project>({}); |
| | | const activeTab = ref('workflow'); |
| | | const workflowKey = ref(0); |
| | | |
| | | const isDraft = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.DRAFT, |
| | | ); |
| | | const isDesigning = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.DESIGNING, |
| | | ); |
| | | const isReviewing = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.REVIEWING, |
| | | ); |
| | | const isApproved = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.APPROVED, |
| | | ); |
| | | const isArchived = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.ARCHIVED, |
| | | ); |
| | | |
| | | const isFormEditable = computed( |
| | | () => |
| | | !isArchived.value && |
| | | project.value.status !== MesPdProjectStatusEnum.CANCELLED && |
| | | (isDraft.value || isDesigning.value), |
| | | ); |
| | | |
| | | const materialFormType = computed<FormType>(() => |
| | | isDesigning.value ? 'update' : 'detail', |
| | | ); |
| | | |
| | | const showReviewTab = computed( |
| | | () => isReviewing.value || isApproved.value || isArchived.value, |
| | | ); |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: false, |
| | | column: 4, |
| | | schema: useDetailSchema(), |
| | | }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [PublishModal, publishModalApi] = useVbenModal({ |
| | | connectedComponent: PublishForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [WorkflowActionModal, workflowActionModalApi] = useVbenModal({ |
| | | connectedComponent: WorkflowActionForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | async function loadProjectDetail() { |
| | | loading.value = true; |
| | | try { |
| | | project.value = await getPdProject(projectId.value); |
| | | workflowKey.value += 1; |
| | | if (isDesigning.value) { |
| | | activeTab.value = 'material'; |
| | | } else if (isReviewing.value) { |
| | | activeTab.value = 'review'; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | function handleBack() { |
| | | tabs.closeCurrentTab(); |
| | | router.push({ path: '/mes/pd/project' }); |
| | | } |
| | | |
| | | function handleEdit() { |
| | | formModalApi.setData({ formType: 'update', id: projectId.value }).open(); |
| | | } |
| | | |
| | | function handlePublish() { |
| | | publishModalApi.setData({ id: projectId.value }).open(); |
| | | } |
| | | |
| | | function handleWorkflowAction(action: 'approve' | 'archive' | 'submit-review') { |
| | | workflowActionModalApi.setData({ id: projectId.value, action }).open(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | projectId.value = Number(route.params.id); |
| | | loadProjectDetail(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page |
| | | auto-content-height |
| | | :loading="loading" |
| | | :title="project.name || '设计任å¡è¯¦æ
'" |
| | | > |
| | | <FormModal @success="loadProjectDetail" /> |
| | | <PublishModal @success="loadProjectDetail" /> |
| | | <WorkflowActionModal @success="loadProjectDetail" /> |
| | | |
| | | <template #extra> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'è¿å', |
| | | type: 'default', |
| | | icon: 'lucide:arrow-left', |
| | | onClick: handleBack, |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: isFormEditable, |
| | | onClick: handleEdit, |
| | | }, |
| | | { |
| | | label: 'åå¸ä»»å¡', |
| | | type: 'primary', |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: isDraft, |
| | | onClick: handlePublish, |
| | | }, |
| | | { |
| | | label: 'æäº¤å®¡æ ¸', |
| | | type: 'default', |
| | | auth: ['mes:pd-project:submit-review'], |
| | | ifShow: isDesigning, |
| | | onClick: () => handleWorkflowAction('submit-review'), |
| | | }, |
| | | { |
| | | label: 'å®¡æ ¸éè¿', |
| | | type: 'primary', |
| | | auth: ['mes:pd-drawing-review:review'], |
| | | ifShow: isReviewing, |
| | | onClick: () => handleWorkflowAction('approve'), |
| | | }, |
| | | { |
| | | label: '彿¡£', |
| | | type: 'default', |
| | | auth: ['mes:pd-project:archive'], |
| | | ifShow: isApproved, |
| | | onClick: () => handleWorkflowAction('archive'), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | |
| | | <TaskDraftBanner |
| | | v-if="isDraft" |
| | | :designer-name="project.designerName" |
| | | :product-name="project.productName" |
| | | :version="project.version" |
| | | @edit="handleEdit" |
| | | @publish="handlePublish" |
| | | /> |
| | | |
| | | <Card class="min-h-[10%]"> |
| | | <Descriptions :data="project" /> |
| | | </Card> |
| | | |
| | | <Card class="mt-4 min-h-[60%]"> |
| | | <Tabs v-model:active-key="activeTab"> |
| | | <Tabs.TabPane key="workflow" tab="æµç¨è¿åº¦" :force-render="true"> |
| | | <WorkflowTimeline :key="workflowKey" :project-id="projectId" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="material" tab="è®¾è®¡èµæ" :force-render="true"> |
| | | <p v-if="isDraft" class="mb-3 text-sm text-amber-600"> |
| | | 请å
åå¸ä»»å¡ååä¸ä¼ è®¾è®¡èµæ |
| | | </p> |
| | | <MaterialPanel :form-type="materialFormType" :project-id="projectId" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane |
| | | v-if="showReviewTab" |
| | | key="review" |
| | | tab="èµæå®¡æ ¸" |
| | | :force-render="true" |
| | | > |
| | | <ReviewList form-type="detail" :project-id="projectId" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | </Card> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { MesPdProjectStatusEnum } from '#/packages/constants/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deletePdProject, |
| | | exportPdProject, |
| | | getPdProjectPage, |
| | | } from '#/api/mes/pd/project'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import CreateForm from './modules/create-form.vue'; |
| | | import Form from './modules/form.vue'; |
| | | import PublishForm from './modules/publish-form.vue'; |
| | | import WorkflowActionForm from './modules/workflow-action-form.vue'; |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const [CreateFormModal, createFormModalApi] = useVbenModal({ |
| | | connectedComponent: CreateForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [PublishModal, publishModalApi] = useVbenModal({ |
| | | connectedComponent: PublishForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [WorkflowActionModal, workflowActionModalApi] = useVbenModal({ |
| | | connectedComponent: WorkflowActionForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreateSuccess(projectId: number) { |
| | | handleRefresh(); |
| | | router.push({ name: 'MesPdProjectDetail', params: { id: projectId } }); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | createFormModalApi.open(); |
| | | } |
| | | |
| | | function handleDetail(row: MesPdProjectApi.Project) { |
| | | router.push({ name: 'MesPdProjectDetail', params: { id: row.id } }); |
| | | } |
| | | |
| | | function handleEdit(row: MesPdProjectApi.Project) { |
| | | formModalApi.setData({ formType: 'update', id: row.id }).open(); |
| | | } |
| | | |
| | | function handlePublish(row: MesPdProjectApi.Project) { |
| | | publishModalApi.setData({ id: row.id }).open(); |
| | | } |
| | | |
| | | function handleWorkflowAction( |
| | | row: MesPdProjectApi.Project, |
| | | action: 'approve' | 'archive' | 'submit-review', |
| | | ) { |
| | | workflowActionModalApi.setData({ id: row.id, action }).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: MesPdProjectApi.Project) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deletePdProject(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | async function handleExport() { |
| | | const data = await exportPdProject(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '设计任å¡.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useGridFormSchema() }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPdProjectPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesPdProjectApi.Project>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <CreateFormModal @success="handleCreateSuccess" /> |
| | | <FormModal @success="handleRefresh" /> |
| | | <PublishModal @success="handleRefresh" /> |
| | | <WorkflowActionModal @success="handleRefresh" /> |
| | | |
| | | <Grid table-title="设计任å¡"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'å起设计任å¡', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pd-project:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pd-project:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.code }}</Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'åå¸ä»»å¡', |
| | | type: 'link', |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.DRAFT, |
| | | onClick: handlePublish.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: |
| | | row.status === MesPdProjectStatusEnum.DRAFT || |
| | | row.status === MesPdProjectStatusEnum.DESIGNING, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æäº¤å®¡æ ¸', |
| | | type: 'link', |
| | | auth: ['mes:pd-project:submit-review'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.DESIGNING, |
| | | onClick: () => handleWorkflowAction(row, 'submit-review'), |
| | | }, |
| | | { |
| | | label: 'å®¡æ ¸éè¿', |
| | | type: 'link', |
| | | auth: ['mes:pd-drawing-review:review'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.REVIEWING, |
| | | onClick: () => handleWorkflowAction(row, 'approve'), |
| | | }, |
| | | { |
| | | label: '彿¡£', |
| | | type: 'link', |
| | | auth: ['mes:pd-project:archive'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.APPROVED, |
| | | onClick: () => handleWorkflowAction(row, 'archive'), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pd-project:delete'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.DRAFT, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesMdItemApi } from '#/api/mes/md/item'; |
| | | import type { MesPdBomApi } from '#/api/mes/pd/bom'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createPdBomLine, updatePdBomLine } from '#/api/mes/pd/bom'; |
| | | import { $t } from '#/locales'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const isUpdate = ref(false); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'projectId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'itemId', |
| | | label: 'ç©æ', |
| | | component: MdItemSelect, |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç©æ', |
| | | onChange: async (item: MesMdItemApi.Item | undefined) => { |
| | | await formApi.setValues({ |
| | | itemCode: item?.code, |
| | | itemName: item?.name, |
| | | specification: item?.specification, |
| | | unitMeasureName: item?.unitMeasureName, |
| | | }); |
| | | }, |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'itemCode', |
| | | label: 'ç©æç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { disabled: true }, |
| | | }, |
| | | { |
| | | fieldName: 'itemName', |
| | | label: 'ç©æåç§°', |
| | | component: 'Input', |
| | | componentProps: { disabled: true }, |
| | | }, |
| | | { |
| | | fieldName: 'quantity', |
| | | label: 'ç¨é', |
| | | component: 'InputNumber', |
| | | componentProps: { min: 0, precision: 4, class: 'w-full' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å¤æ³¨', rows: 2 }, |
| | | }, |
| | | ], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesPdBomApi.BomLine; |
| | | try { |
| | | if (isUpdate.value) { |
| | | await updatePdBomLine(data); |
| | | } else { |
| | | await createPdBomLine(data); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ projectId: number; row?: MesPdBomApi.BomLine }>(); |
| | | isUpdate.value = !!data.row?.id; |
| | | modalApi.setState({ title: isUpdate.value ? 'ç¼è¾ BOM è¡' : 'æ·»å BOM è¡' }); |
| | | if (data.row) { |
| | | await formApi.setValues(data.row); |
| | | return; |
| | | } |
| | | await formApi.setValues({ projectId: data.projectId, quantity: 1 }); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[560px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdBomApi } from '#/api/mes/pd/bom'; |
| | | |
| | | import { computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { deletePdBomLine, getPdBomPage } from '#/api/mes/pd/bom'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useBomGridColumns } from '../data'; |
| | | import BomForm from './bom-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | formType: FormType; |
| | | projectId: number; |
| | | }>(); |
| | | |
| | | const isEditable = computed(() => |
| | | ['create', 'update'].includes(props.formType), |
| | | ); |
| | | |
| | | const [BomFormModal, bomFormModalApi] = useVbenModal({ |
| | | connectedComponent: BomForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | bomFormModalApi.setData({ projectId: props.projectId }).open(); |
| | | } |
| | | |
| | | function handleEdit(row: MesPdBomApi.BomLine) { |
| | | bomFormModalApi.setData({ row, projectId: props.projectId }).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: MesPdBomApi.BomLine) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.itemName]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deletePdBomLine(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.itemName])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useBomGridColumns(), |
| | | height: 360, |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }) => { |
| | | return await getPdBomPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | projectId: props.projectId, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true }, |
| | | } as VxeTableGridOptions<MesPdBomApi.BomLine>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <BomFormModal @success="handleRefresh" /> |
| | | <Grid> |
| | | <template v-if="isEditable" #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'æ·»å BOM è¡', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | ifShow: isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | ifShow: isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.itemName]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { Alert, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createPdProject } from '#/api/mes/pd/project'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useCreateTaskSchema } from '../data'; |
| | | |
| | | const emit = defineEmits<{ |
| | | success: [projectId: number]; |
| | | }>(); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 110, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const raw = (await formApi.getValues()) as MesPdProjectApi.Project & { |
| | | planDateRange?: [string, string]; |
| | | productId?: number; |
| | | }; |
| | | const { productId: _productId, planDateRange, ...data } = raw; |
| | | if (planDateRange?.length === 2) { |
| | | data.planStartDate = new Date(planDateRange[0]).getTime(); |
| | | data.planEndDate = new Date(planDateRange[1]).getTime(); |
| | | } |
| | | try { |
| | | const id = await createPdProject(data); |
| | | emit('success', id); |
| | | message.success('设计任å¡å·²åèµ·ï¼è¯·åå¸ä»»å¡åå¼å§ä¸ä¼ èµæ'); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | await formApi.resetForm(); |
| | | return; |
| | | } |
| | | formApi.setState({ schema: useCreateTaskSchema(formApi) }); |
| | | modalApi.setState({ title: 'å起设计任å¡' }); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[720px]"> |
| | | <Alert |
| | | class="mx-4 mb-4" |
| | | message="åèµ·ä»»å¡" |
| | | description="为æå®äº§åçæä¸è®¾è®¡çæ¬å建设计任å¡ãä»»å¡ä¿å为è稿ï¼åå¸åæ¹å¯ä¸ä¼ è®¾è®¡èµæã" |
| | | show-icon |
| | | type="info" |
| | | /> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesPdDocumentApi } from '#/api/mes/pd/document'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { MesPdDocumentTypeEnum } from '#/packages/constants/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createPdDocument, |
| | | getPdDocument, |
| | | updatePdDocument, |
| | | } from '#/api/mes/pd/document'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { getPdDocumentTypeOptions } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const isUpdate = ref(false); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'projectId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'documentType', |
| | | label: 'èµæç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getPdDocumentTypeOptions(), |
| | | placeholder: 'è¯·éæ©èµæç±»å', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'èµæåç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥èµæåç§°' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'version', |
| | | label: 'çæ¬å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: 'å¦ V1.0' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'fileUrl', |
| | | label: 'æä»¶å°å', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥æä»¶ URL æä¸ä¼ åå¡«å
¥' }, |
| | | }, |
| | | { |
| | | fieldName: 'fileName', |
| | | label: 'æä»¶å', |
| | | component: 'Input', |
| | | componentProps: { placeholder: 'å¦ bom-list.xlsx' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | }, |
| | | ], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesPdDocumentApi.Document; |
| | | try { |
| | | if (isUpdate.value) { |
| | | await updatePdDocument(data); |
| | | } else { |
| | | await createPdDocument(data); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ |
| | | id?: number; |
| | | projectId: number; |
| | | documentType?: number; |
| | | }>(); |
| | | isUpdate.value = !!data.id; |
| | | modalApi.setState({ title: isUpdate.value ? 'ç¼è¾éä»¶' : 'ä¸ä¼ éä»¶' }); |
| | | if (data.id) { |
| | | modalApi.lock(); |
| | | try { |
| | | const detail = await getPdDocument(data.id); |
| | | await formApi.setValues(detail); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | return; |
| | | } |
| | | await formApi.setValues({ |
| | | projectId: data.projectId, |
| | | documentType: data.documentType ?? MesPdDocumentTypeEnum.SCHEME, |
| | | }); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[560px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdDocumentApi } from '#/api/mes/pd/document'; |
| | | |
| | | import { computed, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { MesPdDocumentTypeEnum } from '#/packages/constants/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deletePdDocument, |
| | | getPdDocumentPage, |
| | | } from '#/api/mes/pd/document'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { |
| | | useMaterialListGridColumns, |
| | | useMaterialListGridFormSchema, |
| | | } from '../data'; |
| | | import DocumentForm from './document-form.vue'; |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | formType: FormType; |
| | | gridHeight?: number | string; |
| | | projectId: number; |
| | | tableTitle?: string; |
| | | version?: string; |
| | | }>(), |
| | | { |
| | | gridHeight: 360, |
| | | tableTitle: 'è®¾è®¡èµæ', |
| | | }, |
| | | ); |
| | | |
| | | const isEditable = computed(() => |
| | | ['create', 'update'].includes(props.formType), |
| | | ); |
| | | |
| | | const [DocumentFormModal, documentFormModalApi] = useVbenModal({ |
| | | connectedComponent: DocumentForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | documentFormModalApi |
| | | .setData({ |
| | | projectId: props.projectId, |
| | | documentType: MesPdDocumentTypeEnum.SCHEME, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | function handleEdit(row: MesPdDocumentApi.Document) { |
| | | documentFormModalApi.setData({ id: row.id, projectId: props.projectId }).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: MesPdDocumentApi.Document) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deletePdDocument(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | function handlePreview(row: MesPdDocumentApi.Document) { |
| | | if (row.fileUrl) { |
| | | window.open(row.fileUrl, '_blank'); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useMaterialListGridFormSchema() }, |
| | | gridOptions: { |
| | | columns: useMaterialListGridColumns(), |
| | | height: props.gridHeight, |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPdDocumentPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | projectId: props.projectId, |
| | | version: props.version, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesPdDocumentApi.Document>, |
| | | }); |
| | | |
| | | watch( |
| | | () => props.version, |
| | | () => { |
| | | gridApi.query(); |
| | | }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <DocumentFormModal @success="handleRefresh" /> |
| | | <Grid :table-title="tableTitle"> |
| | | <template v-if="isEditable" #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'ä¸ä¼ éä»¶', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'é¢è§', |
| | | type: 'link', |
| | | ifShow: !!row.fileUrl, |
| | | onClick: handlePreview.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | ifShow: isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | ifShow: isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | |
| | | export function useDrawingReviewGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'å®¡æ ¸åå·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥å®¡æ ¸åå·' }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'å®¡æ ¸ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_DRAWING_REVIEW_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©å®¡æ ¸ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useDrawingReviewGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'code', title: 'å®¡æ ¸åå·', minWidth: 130 }, |
| | | { field: 'documentName', title: 'èµæåç§°', minWidth: 160 }, |
| | | { field: 'documentVersion', title: 'èµæçæ¬', minWidth: 90 }, |
| | | { field: 'reviewerName', title: 'å®¡æ ¸äºº', minWidth: 100 }, |
| | | { |
| | | field: 'status', |
| | | title: 'å®¡æ ¸ç¶æ', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_DRAWING_REVIEW_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'reviewTime', |
| | | title: 'å®¡æ ¸æ¶é´', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'action', |
| | | title: 'æä½', |
| | | width: 160, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useDrawingReviewFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'documentName', |
| | | label: 'èµæåç§°', |
| | | component: 'Input', |
| | | componentProps: { disabled: true }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'å®¡æ ¸ç»æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.MES_PD_DRAWING_REVIEW_STATUS, 'number'), |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'reviewComment', |
| | | label: 'å®¡æ ¸æè§', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å®¡æ ¸æè§', rows: 4 }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesPdDrawingReviewApi } from '#/api/mes/pd/drawing-review'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { getPdDrawingReview, reviewPdDrawing } from '#/api/mes/pd/drawing-review'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useDrawingReviewFormSchema } from './data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useDrawingReviewFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | const data = await formApi.getValues(); |
| | | await reviewPdDrawing(data as MesPdDrawingReviewApi.ReviewParams); |
| | | emit('success'); |
| | | message.success('å®¡æ ¸å®æ'); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number }>(); |
| | | modalApi.lock(); |
| | | try { |
| | | const detail = await getPdDrawingReview(data.id); |
| | | await formApi.setValues(detail); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[560px]" title="èµæå®¡æ ¸"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdDrawingReviewApi } from '#/api/mes/pd/drawing-review'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getPdDrawingReviewPage } from '#/api/mes/pd/drawing-review'; |
| | | |
| | | import { |
| | | useDrawingReviewGridColumns, |
| | | useDrawingReviewGridFormSchema, |
| | | } from './data'; |
| | | import ReviewForm from './review-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | formType: FormType; |
| | | projectId: number; |
| | | }>(); |
| | | |
| | | const [ReviewModal, reviewModalApi] = useVbenModal({ |
| | | connectedComponent: ReviewForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleReview(row: MesPdDrawingReviewApi.DrawingReview) { |
| | | reviewModalApi.setData({ id: row.id }).open(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useDrawingReviewGridFormSchema() }, |
| | | gridOptions: { |
| | | columns: useDrawingReviewGridColumns(), |
| | | height: 360, |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPdDrawingReviewPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | projectId: props.projectId, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesPdDrawingReviewApi.DrawingReview>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ReviewModal @success="handleRefresh" /> |
| | | <Grid table-title="èµæå®¡æ ¸"> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'å®¡æ ¸', |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-drawing-review:review'], |
| | | onClick: handleReview.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { getPdProject, updatePdProject } from '#/api/mes/pd/project'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const formType = ref<FormType>('update'); |
| | | const formData = ref<MesPdProjectApi.Project>(); |
| | | |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return 'æ¥ç设计任å¡'; |
| | | } |
| | | return 'ç¼è¾è®¾è®¡ä»»å¡'; |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 110, |
| | | }, |
| | | wrapperClass: 'grid-cols-3', |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const raw = (await formApi.getValues()) as MesPdProjectApi.Project & { |
| | | planDateRange?: [string, string]; |
| | | productId?: number; |
| | | }; |
| | | const { productId: _productId, planDateRange, ...data } = raw; |
| | | if (planDateRange?.length === 2) { |
| | | data.planStartDate = new Date(planDateRange[0]).getTime(); |
| | | data.planEndDate = new Date(planDateRange[1]).getTime(); |
| | | } |
| | | try { |
| | | await updatePdProject(data); |
| | | formData.value = { ...formData.value, ...data }; |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setState({ schema: useFormSchema(data.formType, formApi) }); |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getPdProject(data.id); |
| | | await formApi.setValues({ |
| | | ...formData.value, |
| | | planDateRange: |
| | | formData.value.planStartDate && formData.value.planEndDate |
| | | ? [formData.value.planStartDate, formData.value.planEndDate] |
| | | : undefined, |
| | | }); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import DocumentList from './document-list.vue'; |
| | | |
| | | withDefaults( |
| | | defineProps<{ |
| | | formType: FormType; |
| | | gridHeight?: number | string; |
| | | projectId: number; |
| | | tableTitle?: string; |
| | | version?: string; |
| | | }>(), |
| | | { |
| | | tableTitle: 'è®¾è®¡èµæ', |
| | | }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <DocumentList |
| | | :form-type="formType" |
| | | :grid-height="gridHeight" |
| | | :project-id="projectId" |
| | | :table-title="tableTitle" |
| | | :version="version" |
| | | /> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { formatDate } from '#/packages/utils/src'; |
| | | |
| | | import { Alert, Descriptions, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | getPdProject, |
| | | publishPdProject, |
| | | updatePdProject, |
| | | } from '#/api/mes/pd/project'; |
| | | |
| | | import { usePublishFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const project = ref<MesPdProjectApi.Project>(); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: usePublishFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!project.value?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | const { publishRemark } = await formApi.getValues(); |
| | | if (publishRemark) { |
| | | const mergedRemark = [project.value.remark, `ãåå¸è¯´æã${publishRemark}`] |
| | | .filter(Boolean) |
| | | .join('\n'); |
| | | await updatePdProject({ ...project.value, remark: mergedRemark }); |
| | | } |
| | | await publishPdProject(project.value.id); |
| | | emit('success'); |
| | | message.success('ä»»å¡å·²åå¸ï¼è¯·ä¸ä¼ è®¾è®¡èµæ'); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | project.value = undefined; |
| | | await formApi.resetForm(); |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number }>(); |
| | | modalApi.setState({ title: 'åå¸è®¾è®¡ä»»å¡' }); |
| | | modalApi.lock(); |
| | | try { |
| | | project.value = await getPdProject(data.id); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[640px]"> |
| | | <Alert |
| | | class="mx-4 mb-4" |
| | | message="确认åå¸" |
| | | description="åå¸åä»»å¡è¿å
¥ã设计ä¸ãç¶æï¼è®¾è®¡å¸å¯ä¸ä¼ è®¾è®¡èµæãåå¸å请确认产åãçæ¬å人å宿æ 误ã" |
| | | show-icon |
| | | type="warning" |
| | | /> |
| | | <Descriptions |
| | | v-if="project" |
| | | bordered |
| | | class="mx-4 mb-4" |
| | | :column="2" |
| | | size="small" |
| | | > |
| | | <Descriptions.Item label="ä»»å¡ç¼ç ">{{ project.code }}</Descriptions.Item> |
| | | <Descriptions.Item label="ä»»å¡åç§°">{{ project.name }}</Descriptions.Item> |
| | | <Descriptions.Item label="产åç¼ç ">{{ project.productCode }}</Descriptions.Item> |
| | | <Descriptions.Item label="产ååç§°">{{ project.productName }}</Descriptions.Item> |
| | | <Descriptions.Item label="è®¾è®¡çæ¬">{{ project.version }}</Descriptions.Item> |
| | | <Descriptions.Item label="主设计å¸">{{ project.designerName || '-' }}</Descriptions.Item> |
| | | <Descriptions.Item label="å®¡æ ¸äºº">{{ project.reviewerName || '-' }}</Descriptions.Item> |
| | | <Descriptions.Item label="计å宿"> |
| | | {{ project.planEndDate ? formatDate(project.planEndDate) : '-' }} |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | defineProps<{ |
| | | designerName?: string; |
| | | productName?: string; |
| | | version?: string; |
| | | }>(); |
| | | |
| | | const emit = defineEmits<{ |
| | | edit: []; |
| | | publish: []; |
| | | }>(); |
| | | </script> |
| | | |
| | | <template> |
| | | <div |
| | | class="mb-4 flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3" |
| | | > |
| | | <div class="text-sm text-amber-900"> |
| | | <span class="font-medium">ä»»å¡å·²åèµ·ï¼è稿ï¼</span> |
| | | <span class="ml-2 text-amber-700"> |
| | | {{ productName }} {{ version }} · è®¾è®¡å¸ {{ designerName || 'æªæå®' }} |
| | | </span> |
| | | <span class="ml-2 text-amber-600">â 请确认信æ¯ååå¸ä»»å¡ï¼åå¸åæ¹å¯ä¸ä¼ è®¾è®¡èµæ</span> |
| | | </div> |
| | | <div class="flex shrink-0 gap-2"> |
| | | <Button size="small" @click="emit('edit')">ä¿®æ¹ä»»å¡</Button> |
| | | <Button size="small" type="primary" @click="emit('publish')">åå¸ä»»å¡</Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | |
| | | export type TaskFormType = 'create' | 'detail' | 'dispatch' | 'update'; |
| | | |
| | | export function useTaskGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä»»å¡ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä»»å¡ç¼ç ' }, |
| | | }, |
| | | { |
| | | fieldName: 'taskName', |
| | | label: 'ä»»å¡åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä»»å¡åç§°' }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ä»»å¡ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_TASK_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ä»»å¡ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useTaskGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'code', title: 'ä»»å¡ç¼ç ', minWidth: 130 }, |
| | | { field: 'taskName', title: 'ä»»å¡åç§°', minWidth: 140 }, |
| | | { field: 'assigneeName', title: 'æ§è¡äºº', minWidth: 100 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_TASK_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'planFinishDate', |
| | | title: '计å宿', |
| | | minWidth: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'action', |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useTaskFormSchema(formType: TaskFormType): VbenFormSchema[] { |
| | | const readonly = formType === 'detail'; |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'projectId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä»»å¡ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥ä»»å¡ç¼ç ' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'taskName', |
| | | label: 'ä»»å¡åç§°', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥ä»»å¡åç§°' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'assigneeName', |
| | | label: 'æ§è¡äºº', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥æ§è¡äºº' }, |
| | | }, |
| | | { |
| | | fieldName: 'planFinishDate', |
| | | label: '计å宿', |
| | | component: 'DatePicker', |
| | | componentProps: { disabled: readonly, class: 'w-full', valueFormat: 'x' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useTaskDispatchSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'assigneeName', |
| | | label: 'æ§è¡äºº', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥æ§è¡äººå§å' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'planFinishDate', |
| | | label: '计å宿', |
| | | component: 'DatePicker', |
| | | componentProps: { class: 'w-full', valueFormat: 'x' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: 'åå说æ', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥åå说æ', rows: 3 }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { TaskFormType } from './data'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createPdTask, |
| | | dispatchPdTask, |
| | | getPdTask, |
| | | updatePdTask, |
| | | } from '#/api/mes/pd/task'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useTaskDispatchSchema, useTaskFormSchema } from './data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const mode = ref<TaskFormType>('create'); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | if (mode.value === 'dispatch') { |
| | | const data = await formApi.getValues(); |
| | | await dispatchPdTask({ |
| | | id: data.id, |
| | | assigneeId: 0, |
| | | planFinishDate: data.planFinishDate, |
| | | remark: data.remark, |
| | | }); |
| | | } else if (mode.value === 'create') { |
| | | await createPdTask(await formApi.getValues()); |
| | | } else if (mode.value === 'update') { |
| | | await updatePdTask(await formApi.getValues()); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ |
| | | formType: TaskFormType; |
| | | id?: number; |
| | | projectId?: number; |
| | | }>(); |
| | | mode.value = data.formType; |
| | | const readonly = data.formType === 'detail'; |
| | | formApi.setState({ |
| | | schema: |
| | | data.formType === 'dispatch' |
| | | ? useTaskDispatchSchema() |
| | | : useTaskFormSchema(data.formType), |
| | | }); |
| | | formApi.setDisabled(readonly); |
| | | modalApi.setState({ |
| | | showConfirmButton: data.formType !== 'detail', |
| | | title: |
| | | data.formType === 'dispatch' |
| | | ? 'ä»»å¡åå' |
| | | : data.formType === 'detail' |
| | | ? 'æ¥çä»»å¡' |
| | | : data.formType === 'update' |
| | | ? 'ç¼è¾ä»»å¡' |
| | | : 'æ°å¢ä»»å¡', |
| | | }); |
| | | if (data.formType === 'create' && data.projectId) { |
| | | await formApi.setValues({ projectId: data.projectId }); |
| | | return; |
| | | } |
| | | if (!data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | const detail = await getPdTask(data.id); |
| | | await formApi.setValues(detail); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[560px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdTaskApi } from '#/api/mes/pd/task'; |
| | | |
| | | import { computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | completePdTask, |
| | | deletePdTask, |
| | | getPdTaskPage, |
| | | } from '#/api/mes/pd/task'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useTaskGridColumns, useTaskGridFormSchema } from './data'; |
| | | import TaskForm from './task-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | formType: FormType; |
| | | projectId: number; |
| | | }>(); |
| | | |
| | | const isEditable = computed(() => |
| | | ['create', 'update'].includes(props.formType), |
| | | ); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: TaskForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi |
| | | .setData({ formType: 'create', projectId: props.projectId }) |
| | | .open(); |
| | | } |
| | | |
| | | function handleEdit(row: MesPdTaskApi.Task) { |
| | | formModalApi.setData({ formType: 'update', id: row.id }).open(); |
| | | } |
| | | |
| | | function handleDispatch(row: MesPdTaskApi.Task) { |
| | | formModalApi.setData({ formType: 'dispatch', id: row.id }).open(); |
| | | } |
| | | |
| | | async function handleComplete(row: MesPdTaskApi.Task) { |
| | | await completePdTask(row.id!); |
| | | message.success('ä»»å¡å·²å®æ'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleDelete(row: MesPdTaskApi.Task) { |
| | | await deletePdTask(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.taskName])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useTaskGridFormSchema() }, |
| | | gridOptions: { |
| | | columns: useTaskGridColumns(), |
| | | height: 360, |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPdTaskPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | projectId: props.projectId, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesPdTaskApi.Task>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid> |
| | | <template v-if="isEditable" #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['设计任å¡']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pd-task:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'åå', |
| | | type: 'link', |
| | | auth: ['mes:pd-task:dispatch'], |
| | | ifShow: isEditable, |
| | | onClick: handleDispatch.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-task:update'], |
| | | ifShow: isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: '宿', |
| | | type: 'link', |
| | | auth: ['mes:pd-task:complete'], |
| | | ifShow: isEditable, |
| | | onClick: handleComplete.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pd-task:delete'], |
| | | ifShow: isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.taskName]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { h } from 'vue'; |
| | | |
| | | import { DICT_TYPE, MesAutoCodeRuleCode } from '#/packages/constants/src'; |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { isPdMockEnabled } from '#/api/mes/pd/mock/enabled'; |
| | | import { mockNextCode } from '#/api/mes/pd/mock/utils'; |
| | | |
| | | export type VersionChangeFormType = 'create' | 'detail' | 'update'; |
| | | |
| | | export function useVersionChangeGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'åæ´åå·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥åæ´åå·' }, |
| | | }, |
| | | { |
| | | fieldName: 'changeType', |
| | | label: 'åæ´ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_VERSION_CHANGE_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©åæ´ç±»å', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useVersionChangeGridColumns( |
| | | showAction = true, |
| | | ): VxeTableGridOptions['columns'] { |
| | | const columns: VxeTableGridOptions['columns'] = [ |
| | | { field: 'code', title: 'åæ´åå·', minWidth: 130 }, |
| | | { |
| | | field: 'changeType', |
| | | title: 'åæ´ç±»å', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_VERSION_CHANGE_TYPE }, |
| | | }, |
| | | }, |
| | | { field: 'fromVersion', title: 'åçæ¬', minWidth: 90 }, |
| | | { field: 'toVersion', title: 'æ°çæ¬', minWidth: 90 }, |
| | | { field: 'applicantName', title: 'ç³è¯·äºº', minWidth: 100 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'ç³è¯·æ¶é´', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | ]; |
| | | |
| | | if (showAction) { |
| | | columns.push({ |
| | | field: 'action', |
| | | title: 'æä½', |
| | | width: 180, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }); |
| | | } |
| | | |
| | | return columns; |
| | | } |
| | | |
| | | export function useVersionChangeFormSchema( |
| | | formType: VersionChangeFormType, |
| | | formApi?: VbenFormApi, |
| | | ): VbenFormSchema[] { |
| | | const readonly = formType === 'detail'; |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'projectId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'åæ´åå·', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥åæ´åå·' }, |
| | | rules: 'required', |
| | | suffix: |
| | | formType === 'detail' |
| | | ? undefined |
| | | : () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const code = isPdMockEnabled() |
| | | ? mockNextCode( |
| | | 'VC-2026', |
| | | Math.floor(Date.now() % 10_000), |
| | | ) |
| | | : await generateAutoCode( |
| | | MesAutoCodeRuleCode.PD_VERSION_CODE, |
| | | ); |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'çæ' }, |
| | | ), |
| | | }, |
| | | { |
| | | fieldName: 'changeType', |
| | | label: 'åæ´ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | disabled: readonly, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_VERSION_CHANGE_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©åæ´ç±»å', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'fromVersion', |
| | | label: 'åçæ¬', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: 'å¦ V1.0' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'toVersion', |
| | | label: 'æ°çæ¬', |
| | | component: 'Input', |
| | | componentProps: { disabled: readonly, placeholder: 'å¦ V1.1' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'changeReason', |
| | | label: 'åæ´åå ', |
| | | component: 'Textarea', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥åæ´åå ', rows: 2 }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'changeContent', |
| | | label: 'åæ´å
容', |
| | | component: 'Textarea', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥åæ´å
容', rows: 4 }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { disabled: readonly, placeholder: '请è¾å
¥å¤æ³¨', rows: 2 }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VersionChangeFormType } from './data'; |
| | | |
| | | import type { MesPdVersionChangeApi } from '#/api/mes/pd/version-change'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createPdVersionChange, |
| | | getPdVersionChange, |
| | | updatePdVersionChange, |
| | | } from '#/api/mes/pd/version-change'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useVersionChangeFormSchema } from './data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const formType = ref<VersionChangeFormType>('create'); |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesPdVersionChangeApi.VersionChange; |
| | | try { |
| | | if (formType.value === 'create') { |
| | | await createPdVersionChange(data); |
| | | } else { |
| | | await updatePdVersionChange(data); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ |
| | | formType: VersionChangeFormType; |
| | | id?: number; |
| | | projectId?: number; |
| | | fromVersion?: string; |
| | | }>(); |
| | | formType.value = data.formType; |
| | | formApi.setState({ schema: useVersionChangeFormSchema(data.formType, formApi) }); |
| | | formApi.setDisabled(isDetail.value); |
| | | modalApi.setState({ |
| | | showConfirmButton: !isDetail.value, |
| | | title: |
| | | data.formType === 'detail' |
| | | ? 'æ¥ççæ¬åæ´' |
| | | : data.formType === 'update' |
| | | ? 'ç¼è¾çæ¬åæ´' |
| | | : 'æ°å¢çæ¬åæ´', |
| | | }); |
| | | if (data.formType === 'create' && data.projectId) { |
| | | await formApi.setValues({ |
| | | projectId: data.projectId, |
| | | fromVersion: data.fromVersion, |
| | | }); |
| | | return; |
| | | } |
| | | if (!data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | await formApi.setValues(await getPdVersionChange(data.id)); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[640px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdVersionChangeApi } from '#/api/mes/pd/version-change'; |
| | | |
| | | import { computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | approvePdVersionChange, |
| | | deletePdVersionChange, |
| | | getPdVersionChangePage, |
| | | } from '#/api/mes/pd/version-change'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { |
| | | useVersionChangeGridColumns, |
| | | useVersionChangeGridFormSchema, |
| | | } from './data'; |
| | | import VersionChangeForm from './version-change-form.vue'; |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | formType: FormType; |
| | | gridHeight?: number | string; |
| | | projectId: number; |
| | | projectVersion?: string; |
| | | selectable?: boolean; |
| | | }>(), |
| | | { |
| | | gridHeight: 360, |
| | | selectable: false, |
| | | }, |
| | | ); |
| | | |
| | | const emit = defineEmits<{ |
| | | viewMaterials: [row: MesPdVersionChangeApi.VersionChange]; |
| | | }>(); |
| | | |
| | | const isEditable = computed(() => |
| | | ['create', 'update'].includes(props.formType), |
| | | ); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: VersionChangeForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi |
| | | .setData({ |
| | | formType: 'create', |
| | | projectId: props.projectId, |
| | | fromVersion: props.projectVersion, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | function handleDetail(row: MesPdVersionChangeApi.VersionChange) { |
| | | if (!isEditable.value) { |
| | | emit('viewMaterials', row); |
| | | return; |
| | | } |
| | | formModalApi.setData({ formType: 'detail', id: row.id }).open(); |
| | | } |
| | | |
| | | function handleEdit(row: MesPdVersionChangeApi.VersionChange) { |
| | | formModalApi.setData({ formType: 'update', id: row.id }).open(); |
| | | } |
| | | |
| | | async function handleApprove(row: MesPdVersionChangeApi.VersionChange) { |
| | | await approvePdVersionChange(row.id!); |
| | | message.success('çæ¬åæ´å·²æ¹å'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleDelete(row: MesPdVersionChangeApi.VersionChange) { |
| | | await deletePdVersionChange(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.code])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | function handleSelectRow(row: MesPdVersionChangeApi.VersionChange) { |
| | | emit('viewMaterials', row); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useVersionChangeGridFormSchema() }, |
| | | gridOptions: { |
| | | columns: useVersionChangeGridColumns(!props.selectable), |
| | | height: props.gridHeight, |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPdVersionChangePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | projectId: props.projectId, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isCurrent: props.selectable, |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesPdVersionChangeApi.VersionChange>, |
| | | gridEvents: props.selectable |
| | | ? { |
| | | cellClick: ({ row }: { row: MesPdVersionChangeApi.VersionChange }) => { |
| | | handleSelectRow(row); |
| | | }, |
| | | } |
| | | : undefined, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid :table-title="selectable ? 'çæ¬åæ´è®°å½' : undefined"> |
| | | <template v-if="isEditable" #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['çæ¬åæ´']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pd-version-change:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: isEditable ? 'æ¥ç' : 'æ¥çèµæ', |
| | | type: 'link', |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-version-change:update'], |
| | | ifShow: isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æ¹å', |
| | | type: 'link', |
| | | auth: ['mes:pd-version-change:approve'], |
| | | ifShow: isEditable, |
| | | onClick: handleApprove.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pd-version-change:delete'], |
| | | ifShow: isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.code]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { WorkflowActionType } from '../data'; |
| | | import type { MesPdProjectApi } from '#/api/mes/pd/project'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { formatDate } from '#/packages/utils/src'; |
| | | |
| | | import { Alert, Descriptions, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | approvePdProject, |
| | | archivePdProject, |
| | | getPdProject, |
| | | submitPdProjectReview, |
| | | updatePdProject, |
| | | } from '#/api/mes/pd/project'; |
| | | |
| | | import { useWorkflowActionFormSchema, WORKFLOW_ACTION_CONFIG } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const project = ref<MesPdProjectApi.Project>(); |
| | | const actionType = ref<WorkflowActionType>('submit-review'); |
| | | |
| | | const actionConfig = computed(() => WORKFLOW_ACTION_CONFIG[actionType.value]); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!project.value?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | const { actionRemark } = await formApi.getValues(); |
| | | const config = actionConfig.value; |
| | | if (actionRemark) { |
| | | const mergedRemark = [project.value.remark, `${config.remarkTag}${actionRemark}`] |
| | | .filter(Boolean) |
| | | .join('\n'); |
| | | await updatePdProject({ ...project.value, remark: mergedRemark }); |
| | | } |
| | | const projectId = project.value.id; |
| | | switch (actionType.value) { |
| | | case 'approve': { |
| | | await approvePdProject(projectId); |
| | | break; |
| | | } |
| | | case 'archive': { |
| | | await archivePdProject(projectId); |
| | | break; |
| | | } |
| | | case 'submit-review': { |
| | | await submitPdProjectReview(projectId); |
| | | break; |
| | | } |
| | | } |
| | | emit('success'); |
| | | message.success(config.successMessage); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | project.value = undefined; |
| | | await formApi.resetForm(); |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ action: WorkflowActionType; id: number }>(); |
| | | actionType.value = data.action; |
| | | formApi.setState({ schema: useWorkflowActionFormSchema(data.action) }); |
| | | modalApi.setState({ title: WORKFLOW_ACTION_CONFIG[data.action].title }); |
| | | modalApi.lock(); |
| | | try { |
| | | project.value = await getPdProject(data.id); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[640px]"> |
| | | <Alert |
| | | class="mx-4 mb-4" |
| | | :message="actionConfig.alertMessage" |
| | | :description="actionConfig.alertDescription" |
| | | show-icon |
| | | :type="actionConfig.alertType" |
| | | /> |
| | | <Descriptions |
| | | v-if="project" |
| | | bordered |
| | | class="mx-4 mb-4" |
| | | :column="2" |
| | | size="small" |
| | | > |
| | | <Descriptions.Item label="ä»»å¡ç¼ç ">{{ project.code }}</Descriptions.Item> |
| | | <Descriptions.Item label="ä»»å¡åç§°">{{ project.name }}</Descriptions.Item> |
| | | <Descriptions.Item label="产åç¼ç ">{{ project.productCode }}</Descriptions.Item> |
| | | <Descriptions.Item label="产ååç§°">{{ project.productName }}</Descriptions.Item> |
| | | <Descriptions.Item label="è®¾è®¡çæ¬">{{ project.version }}</Descriptions.Item> |
| | | <Descriptions.Item label="主设计å¸">{{ project.designerName || '-' }}</Descriptions.Item> |
| | | <Descriptions.Item label="å®¡æ ¸äºº">{{ project.reviewerName || '-' }}</Descriptions.Item> |
| | | <Descriptions.Item label="计å宿"> |
| | | {{ project.planEndDate ? formatDate(project.planEndDate) : '-' }} |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesPdWorkflowApi } from '#/api/mes/pd/workflow'; |
| | | |
| | | import { onMounted, ref, watch } from 'vue'; |
| | | |
| | | import { Steps } from 'ant-design-vue'; |
| | | |
| | | import { getPdWorkflowList } from '#/api/mes/pd/workflow'; |
| | | |
| | | const props = defineProps<{ |
| | | projectId: number; |
| | | }>(); |
| | | |
| | | const loading = ref(false); |
| | | const nodes = ref<MesPdWorkflowApi.WorkflowNode[]>([]); |
| | | const current = ref(0); |
| | | |
| | | async function loadData() { |
| | | loading.value = true; |
| | | try { |
| | | nodes.value = await getPdWorkflowList(props.projectId); |
| | | const activeIndex = nodes.value.findIndex((item) => item.status !== 1); |
| | | current.value = activeIndex === -1 ? nodes.value.length : activeIndex; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.projectId, |
| | | () => loadData(), |
| | | { immediate: true }, |
| | | ); |
| | | |
| | | onMounted(loadData); |
| | | </script> |
| | | |
| | | <template> |
| | | <Steps :current="current" direction="vertical" size="small"> |
| | | <Steps.Step |
| | | v-for="node in nodes" |
| | | :key="node.id" |
| | | :description="node.remark" |
| | | :sub-title="node.operatorName" |
| | | :title="node.nodeName" |
| | | /> |
| | | <Steps.Step |
| | | v-if="!loading && nodes.length === 0" |
| | | description="ææ æµç¨è®°å½ï¼ä¿å项ç®åå°èªå¨çæ" |
| | | title="å¾
å¯å¨" |
| | | /> |
| | | </Steps> |
| | | </template> |
| | |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | import { MdWorkstationSelect } from '#/views/mes/md/workstation/components'; |
| | | import { ProProcessSelect } from '#/views/mes/pro/process/components'; |
| | | import { ProProcessSelect } from '#/views/mes/process-design/process/components'; |
| | | import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components'; |
| | | |
| | | import { AndonConfigSelect } from '../config/components'; |
| | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | import { MdWorkstationSelect } from '#/views/mes/md/workstation/components'; |
| | | import { ProProcessSelect } from '#/views/mes/pro/process/components'; |
| | | import { ProProcessSelect } from '#/views/mes/process-design/process/components'; |
| | | import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components'; |
| | | import { UserSelect } from '#/views/system/user/components'; |
| | | |
| | |
| | | import { MdClientSelect } from '#/views/mes/md/client/components'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | import { MdWorkstationSelect } from '#/views/mes/md/workstation/components'; |
| | | import { ProProcessSelect } from '#/views/mes/pro/process/components'; |
| | | import { RouteColorPicker } from '#/views/mes/pro/route/components'; |
| | | import { ProProcessSelect } from '#/views/mes/process-design/process/components'; |
| | | import { RouteColorPicker } from '#/views/mes/process-design/route/components'; |
| | | import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components'; |
| | | |
| | | /** å¾
æäº§å·¥åå表çæç´¢è¡¨å */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdParameterApi } from '#/api/mes/pro/processdesign/parameter'; |
| | | |
| | | import { DICT_TYPE } from '../../../../packages/constants/src'; |
| | | import { getDictOptions } from '../../../../packages/effects/hooks/src'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | |
| | | /** åæ°ç±»å - 䏿é项çå¼ */ |
| | | const PARAM_TYPE_SELECT = 3; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹å·¥èºåæ°ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'åæ°ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 64, |
| | | placeholder: '请è¾å
¥åæ°ç¼ç ', |
| | | }, |
| | | rules: z.string().min(1, 'åæ°ç¼ç ä¸è½ä¸ºç©º').max(64), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'åæ°åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥åæ°åç§°', |
| | | }, |
| | | rules: z.string().min(1, 'åæ°åç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: 'åæ°ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_PARAM_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©åæ°ç±»å', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'unit', |
| | | label: 'åä½', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 20, |
| | | placeholder: '请è¾å
¥åä½', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'requiredFlag', |
| | | label: 'æ¯å¦å¿
å¡«', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | }, |
| | | { |
| | | fieldName: 'dictType', |
| | | label: 'åå
¸ç±»å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 64, |
| | | placeholder: '请è¾å
¥åå
¸ç±»åï¼ä¸æé项æ¶é
ç½®ï¼', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | | show: (values) => values?.type === PARAM_TYPE_SELECT, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'apiUrl', |
| | | label: 'æ¥å£å°å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥æ¥å£å°åï¼ä¸æé项æ¶é
ç½®ï¼', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | | show: (values) => values?.type === PARAM_TYPE_SELECT, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'åæ°ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥åæ°ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'åæ°åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥åæ°åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: 'åæ°ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_PARAM_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©åæ°ç±»å', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesPdParameterApi.Parameter>['columns'] { |
| | | return [ |
| | | { |
| | | type: 'seq', |
| | | title: 'åºå·', |
| | | width: 60, |
| | | align: 'center', |
| | | }, |
| | | { field: 'code', title: 'åæ°ç¼ç ', minWidth: 150 }, |
| | | { field: 'name', title: 'åæ°åç§°', minWidth: 180 }, |
| | | { |
| | | field: 'type', |
| | | title: 'åæ°ç±»å', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_PARAM_TYPE }, |
| | | }, |
| | | }, |
| | | { field: 'unit', title: 'åä½', width: 80 }, |
| | | { |
| | | field: 'requiredFlag', |
| | | title: 'æ¯å¦å¿
å¡«', |
| | | width: 90, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, |
| | | }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 180 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 150, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'actions', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
copy from src/views/mes/md/unitmeasure/index.vue
copy to src/views/mes/process-design/parameter/index.vue
| Îļþ´Ó src/views/mes/md/unitmeasure/index.vue ¸´ÖÆ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesMdUnitMeasureApi } from '#/api/mes/md/unitmeasure'; |
| | | import type { MesPdParameterApi } from '#/api/mes/pro/processdesign/parameter'; |
| | | |
| | | import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '../../../../packages/utils/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteUnitMeasure, |
| | | exportUnitMeasure, |
| | | getUnitMeasurePage, |
| | | } from '#/api/mes/md/unitmeasure'; |
| | | deleteParameter, |
| | | getParameterPage, |
| | | } from '#/api/mes/pro/processdesign/parameter'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportUnitMeasure(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '计éåä½.xls', source: data }); |
| | | } |
| | | |
| | | /** å建计éåä½ */ |
| | | /** å建工èºåæ° */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®¡éåä½ */ |
| | | function handleEdit(row: MesMdUnitMeasureApi.UnitMeasure) { |
| | | formModalApi.setData(row).open(); |
| | | /** ç¼è¾å·¥èºåæ° */ |
| | | function handleEdit(row: MesPdParameterApi.Parameter) { |
| | | formModalApi.setData({ id: row.id, formType: 'update' }).open(); |
| | | } |
| | | |
| | | /** å é¤è®¡éåä½ */ |
| | | async function handleDelete(row: MesMdUnitMeasureApi.UnitMeasure) { |
| | | /** æ¥çå·¥èºåæ°è¯¦æ
*/ |
| | | function handleDetail(row: MesPdParameterApi.Parameter) { |
| | | formModalApi.setData({ id: row.id, formType: 'detail' }).open(); |
| | | } |
| | | |
| | | /** å é¤å·¥èºåæ° */ |
| | | async function handleDelete(row: MesPdParameterApi.Parameter) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteUnitMeasure(row.id!); |
| | | await deleteParameter(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getUnitMeasurePage({ |
| | | return await getParameterPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | keyField: 'id', |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesMdUnitMeasureApi.UnitMeasure>, |
| | | } as VxeTableGridOptions<MesPdParameterApi.Parameter>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="计éåä½å表"> |
| | | <Grid table-title="å·¥èºåæ°å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['计éåä½']), |
| | | label: $t('ui.actionTitle.create', ['åæ°']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:md-unit-measure:create'], |
| | | auth: ['mes:pd:parameter:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:md-unit-measure:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.code }}</Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:md-unit-measure:update'], |
| | | auth: ['mes:pd:parameter:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:md-unit-measure:delete'], |
| | | auth: ['mes:pd:parameter:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { MesPdParameterApi } from '#/api/mes/pro/processdesign/parameter'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createParameter, |
| | | getParameter, |
| | | updateParameter, |
| | | } from '#/api/mes/pro/processdesign/parameter'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesPdParameterApi.Parameter>(); |
| | | |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return $t('ui.actionTitle.detail', ['å·¥èºåæ°']); |
| | | } |
| | | return formType.value === 'update' |
| | | ? $t('ui.actionTitle.edit', ['å·¥èºåæ°']) |
| | | : $t('ui.actionTitle.create', ['å·¥èºåæ°']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-2', |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesPdParameterApi.Parameter; |
| | | try { |
| | | await (formData.value?.id ? updateParameter(data) : createParameter(data)); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | formApi.setState({ schema: useFormSchema() }); |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getParameter(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | export { default as ProProcessSelect } from './select.vue'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProProcessApi } from '#/api/mes/pro/process'; |
| | | |
| | | import { computed, onMounted, ref, watch } from 'vue'; |
| | | |
| | | import { Select, Tag, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { getProcessSimpleList } from '#/api/mes/pro/process'; |
| | | |
| | | defineOptions({ name: 'ProProcessSelect', inheritAttrs: false }); |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | allowClear?: boolean; |
| | | disabled?: boolean; |
| | | modelValue?: number; |
| | | placeholder?: string; |
| | | }>(), |
| | | { |
| | | allowClear: true, |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | placeholder: 'è¯·éæ©å·¥åº', |
| | | }, |
| | | ); |
| | | |
| | | const emit = defineEmits<{ |
| | | change: [item: MesProProcessApi.Process | undefined]; |
| | | 'update:modelValue': [value: number | undefined]; |
| | | }>(); |
| | | |
| | | const allList = ref<MesProProcessApi.Process[]>([]); |
| | | const selectedItem = ref<MesProProcessApi.Process>(); |
| | | |
| | | const selectValue = computed({ |
| | | get: () => props.modelValue, |
| | | set: (value: number | undefined) => { |
| | | emit('update:modelValue', value); |
| | | }, |
| | | }); |
| | | |
| | | /** åç«¯è¿æ»¤ï¼æå·¥åºåç§°æç¼ç 模ç³å¹é
*/ |
| | | function handleFilter(input: string, option: any) { |
| | | const keyword = input.toLowerCase(); |
| | | const item = option?.item as MesProProcessApi.Process | undefined; |
| | | return Boolean( |
| | | item?.name?.toLowerCase().includes(keyword) || |
| | | item?.code?.toLowerCase().includes(keyword), |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ®å½åå¼åæ¥ tooltip å±ç¤ºçå·¥åºè¯¦æ
*/ |
| | | function syncSelectedItem(value: number | undefined) { |
| | | selectedItem.value = |
| | | value === undefined |
| | | ? undefined |
| | | : allList.value.find((item) => item.id === value); |
| | | } |
| | | |
| | | /** é¤ v-model å¤ï¼é¢å¤æåºå®æ´å·¥åºå¯¹è±¡ç»ä¸å¡è¡¨åä½¿ç¨ */ |
| | | function handleChange(value: any) { |
| | | const nextValue = value === undefined ? undefined : Number(value); |
| | | syncSelectedItem(nextValue); |
| | | emit('change', selectedItem.value); |
| | | } |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (value) => { |
| | | syncSelectedItem(value); |
| | | }, |
| | | ); |
| | | |
| | | onMounted(async () => { |
| | | allList.value = await getProcessSimpleList(); |
| | | syncSelectedItem(props.modelValue); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false"> |
| | | <template #title> |
| | | <div v-if="selectedItem" class="leading-6"> |
| | | <div>ç¼ç ï¼{{ selectedItem.code || '-' }}</div> |
| | | <div>åç§°ï¼{{ selectedItem.name || '-' }}</div> |
| | | <div>å·¥èºè¦æ±ï¼{{ selectedItem.attention || '-' }}</div> |
| | | <div>夿³¨ï¼{{ selectedItem.remark || '-' }}</div> |
| | | </div> |
| | | </template> |
| | | <Select |
| | | v-bind="$attrs" |
| | | v-model:value="selectValue" |
| | | :allow-clear="allowClear" |
| | | :disabled="disabled" |
| | | :filter-option="handleFilter" |
| | | :placeholder="placeholder" |
| | | class="w-full" |
| | | show-search |
| | | @change="handleChange" |
| | | > |
| | | <Select.Option |
| | | v-for="item in allList" |
| | | :key="item.id" |
| | | :item="item" |
| | | :value="item.id" |
| | | > |
| | | <div class="flex items-center gap-2"> |
| | | <span>{{ item.name }}</span> |
| | | <Tag v-if="item.code" color="default">{{ item.code }}</Tag> |
| | | </div> |
| | | </Select.Option> |
| | | </Select> |
| | | </Tooltip> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProProcessApi } from '#/api/mes/pro/process'; |
| | | import type { MesProProcessContentApi } from '#/api/mes/pro/process/content'; |
| | | |
| | | import { h } from 'vue'; |
| | | |
| | | import { |
| | | CommonStatusEnum, |
| | | DICT_TYPE, |
| | | MesAutoCodeRuleCode, |
| | | } from '../../../../packages/constants/src'; |
| | | import { getDictOptions } from '../../../../packages/effects/hooks/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç产工åºç表å */ |
| | | export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'å·¥åºç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 64, |
| | | placeholder: '请è¾å
¥å·¥åºç¼ç ', |
| | | }, |
| | | rules: z.string().min(1, 'å·¥åºç¼ç ä¸è½ä¸ºç©º').max(64), |
| | | suffix: () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const code = await generateAutoCode( |
| | | MesAutoCodeRuleCode.PRO_PROCESS_CODE, |
| | | ); |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'çæ' }, |
| | | ), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'å·¥åºåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥å·¥åºåç§°', |
| | | }, |
| | | rules: z.string().min(1, 'å·¥åºåç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | }, |
| | | rules: z.number().default(CommonStatusEnum.ENABLE), |
| | | }, |
| | | { |
| | | fieldName: 'attention', |
| | | label: 'å·¥åºè¯´æ', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | maxLength: 500, |
| | | placeholder: '请è¾å
¥å·¥åºè¯´æ', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'å·¥åºç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å·¥åºç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'å·¥åºåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å·¥åºåç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesProProcessApi.Process>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'å·¥åºç¼ç ', |
| | | minWidth: 150, |
| | | slots: { |
| | | default: 'code', |
| | | }, |
| | | }, |
| | | { field: 'name', title: 'å·¥åºåç§°', minWidth: 180 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 180 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 150, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'actions', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥åºå
å®¹ï¼æä½æ¥éª¤ï¼è¡¨å */ |
| | | export function useContentFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'processId', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'sort', |
| | | label: 'åºå·', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | max: 999, |
| | | min: 1, |
| | | precision: 0, |
| | | }, |
| | | rules: z.number().default(1), |
| | | }, |
| | | { |
| | | fieldName: 'content', |
| | | label: 'æ¥éª¤è¯´æ', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | maxLength: 500, |
| | | placeholder: '请è¾å
¥æ¥éª¤è¯´æ', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'device', |
| | | label: 'è¾
å©è®¾å¤', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥è¾
å©è®¾å¤', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'material', |
| | | label: 'è¾
婿æ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥è¾
婿æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'docUrl', |
| | | label: 'ææææ¡£ URL', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥ææææ¡£ URL', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥åºå
容å表çåæ®µ */ |
| | | export function useContentGridColumns(): VxeTableGridOptions<MesProProcessContentApi.ProcessContent>['columns'] { |
| | | return [ |
| | | { field: 'sort', title: 'åºå·', width: 80, align: 'center' }, |
| | | { field: 'content', title: 'æ¥éª¤è¯´æ', minWidth: 220 }, |
| | | { field: 'device', title: 'è¾
å©è®¾å¤', width: 150 }, |
| | | { field: 'material', title: 'è¾
婿æ', width: 150 }, |
| | | { field: 'docUrl', title: 'ææææ¡£', minWidth: 180 }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ÎļþÃû´Ó src/views/mes/md/unitmeasure/index.vue ÐÞ¸Ä |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesMdUnitMeasureApi } from '#/api/mes/md/unitmeasure'; |
| | | import type { MesProProcessApi } from '#/api/mes/pro/process'; |
| | | |
| | | import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '../../../../packages/utils/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteUnitMeasure, |
| | | exportUnitMeasure, |
| | | getUnitMeasurePage, |
| | | } from '#/api/mes/md/unitmeasure'; |
| | | deleteProcess, |
| | | exportProcess, |
| | | getProcessPage, |
| | | } from '#/api/mes/pro/process'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportUnitMeasure(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '计éåä½.xls', source: data }); |
| | | } |
| | | |
| | | /** å建计éåä½ */ |
| | | /** å建çäº§å·¥åº */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®¡éåä½ */ |
| | | function handleEdit(row: MesMdUnitMeasureApi.UnitMeasure) { |
| | | formModalApi.setData(row).open(); |
| | | /** ç¼è¾çäº§å·¥åº */ |
| | | function handleEdit(row: MesProProcessApi.Process) { |
| | | formModalApi.setData({ id: row.id, formType: 'update' }).open(); |
| | | } |
| | | |
| | | /** å é¤è®¡éåä½ */ |
| | | async function handleDelete(row: MesMdUnitMeasureApi.UnitMeasure) { |
| | | /** æ¥çç产工åºè¯¦æ
*/ |
| | | function handleDetail(row: MesProProcessApi.Process) { |
| | | formModalApi.setData({ id: row.id, formType: 'detail' }).open(); |
| | | } |
| | | |
| | | /** å é¤çäº§å·¥åº */ |
| | | async function handleDelete(row: MesProProcessApi.Process) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteUnitMeasure(row.id!); |
| | | await deleteProcess(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** 导åºçäº§å·¥åº */ |
| | | async function handleExport() { |
| | | const data = await exportProcess(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ç产工åº.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getUnitMeasurePage({ |
| | | return await getProcessPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | keyField: 'id', |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesMdUnitMeasureApi.UnitMeasure>, |
| | | } as VxeTableGridOptions<MesProProcessApi.Process>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="计éåä½å表"> |
| | | <Grid table-title="ç产工åºå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['计éåä½']), |
| | | label: $t('ui.actionTitle.create', ['å·¥åº']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:md-unit-measure:create'], |
| | | auth: ['mes:pro-process:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:md-unit-measure:export'], |
| | | auth: ['mes:pro-process:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.code }}</Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:md-unit-measure:update'], |
| | | auth: ['mes:pro-process:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:md-unit-measure:delete'], |
| | | auth: ['mes:pro-process:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProProcessContentApi } from '#/api/mes/pro/process/content'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createProcessContent, |
| | | getProcessContent, |
| | | updateProcessContent, |
| | | } from '#/api/mes/pro/process/content'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useContentFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesProProcessContentApi.ProcessContent>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['å·¥åºæ¥éª¤']) |
| | | : $t('ui.actionTitle.create', ['å·¥åºæ¥éª¤']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 120, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useContentFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = |
| | | (await formApi.getValues()) as MesProProcessContentApi.ProcessContent; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateProcessContent(data) |
| | | : createProcessContent(data)); |
| | | // å
³éå¹¶æç¤º |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | id?: number; |
| | | maxSort?: number; |
| | | processId: number; |
| | | }>(); |
| | | if (!data?.id) { |
| | | // æ°å¢æ¶ï¼é»è®¤åºå· = maxSort + 1 |
| | | await formApi.setValues({ |
| | | processId: data?.processId, |
| | | sort: (data?.maxSort || 0) + 1, |
| | | }); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getProcessContent(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-2/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProProcessContentApi } from '#/api/mes/pro/process/content'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteProcessContent, |
| | | getProcessContentListByProcessId, |
| | | } from '#/api/mes/pro/process/content'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useContentGridColumns } from '../data'; |
| | | import ContentForm from './content-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | formType: FormType; |
| | | processId: number; |
| | | }>(); |
| | | |
| | | const isEditable = ref(props.formType !== 'detail'); // æ¯å¦å¯ç¼è¾ |
| | | const list = ref<MesProProcessContentApi.ProcessContent[]>([]); |
| | | |
| | | const [ContentFormModal, contentFormModalApi] = useVbenModal({ |
| | | connectedComponent: ContentForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | autoResize: true, |
| | | border: true, |
| | | columns: useContentGridColumns(), |
| | | data: list.value, |
| | | minHeight: 240, |
| | | pagerConfig: { enabled: false }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | keyField: 'id', |
| | | }, |
| | | showOverflow: true, |
| | | toolbarConfig: { enabled: false }, |
| | | } as VxeTableGridOptions<MesProProcessContentApi.ProcessContent>, |
| | | }); |
| | | |
| | | /** å 载工åºå
容å表 */ |
| | | async function getList() { |
| | | gridApi.setLoading(true); |
| | | try { |
| | | list.value = await getProcessContentListByProcessId(props.processId); |
| | | gridApi.setGridOptions({ data: list.value }); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | /** æ°å¢å·¥åºæ¥éª¤ */ |
| | | function handleCreate() { |
| | | const maxSort = Math.max(0, ...list.value.map((item) => item.sort || 0)); |
| | | contentFormModalApi.setData({ maxSort, processId: props.processId }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾å·¥åºæ¥éª¤ */ |
| | | function handleEdit(row: MesProProcessContentApi.ProcessContent) { |
| | | contentFormModalApi |
| | | .setData({ id: row.id, processId: props.processId }) |
| | | .open(); |
| | | } |
| | | |
| | | /** å é¤å·¥åºæ¥éª¤ */ |
| | | async function handleDelete(row: MesProProcessContentApi.ProcessContent) { |
| | | await deleteProcessContent(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', ['å·¥åºæ¥éª¤'])); |
| | | await getList(); |
| | | } |
| | | |
| | | watch( |
| | | () => props.processId, |
| | | (value) => { |
| | | if (value) { |
| | | getList(); |
| | | } |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentFormModal @success="getList" /> |
| | | <div v-if="isEditable" class="mb-3 flex items-center justify-start"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¥éª¤']), |
| | | type: 'primary', |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </div> |
| | | <Grid class="w-full" table-title="å·¥åºæä½æ¥éª¤"> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | ifShow: () => isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | ifShow: () => isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', ['å·¥åºæ¥éª¤']), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { MesProProcessApi } from '#/api/mes/pro/process'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { Divider, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createProcess, |
| | | getProcess, |
| | | updateProcess, |
| | | } from '#/api/mes/pro/process'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | import ContentList from './content-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); // è¡¨åæ¨¡å¼ |
| | | const formData = ref<MesProProcessApi.Process>(); |
| | | |
| | | const isDetail = computed(() => formType.value === 'detail'); // æ¯å¦æ¥çæ¨¡å¼ |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return $t('ui.actionTitle.detail', ['ç产工åº']); |
| | | } |
| | | return formType.value === 'update' |
| | | ? $t('ui.actionTitle.edit', ['ç产工åº']) |
| | | : $t('ui.actionTitle.create', ['ç产工åº']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-3', |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-3', |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = (await formApi.getValues()) as MesProProcessApi.Process; |
| | | try { |
| | | await (formData.value?.id ? updateProcess(data) : createProcess(data)); |
| | | // å
³éå¹¶æç¤º |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | formApi.setState({ schema: useFormSchema(formApi) }); |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getProcess(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | /** ç¨äºå·¥åºå
容å表çå·¥åºç¼å· */ |
| | | const processId = computed(() => formData.value?.id); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- ç¼è¾/详æ
模å¼ä¸å±ç¤ºå·¥åºæä½æ¥éª¤åè¡¨ï¼æ°å¢æ¨¡å¼ä¸éè --> |
| | | <template v-if="processId"> |
| | | <Divider class="!my-3" orientation="left">æä½æ¥éª¤</Divider> |
| | | <div class="mx-4"> |
| | | <ContentList :form-type="formType" :process-id="processId" /> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { computed } from 'vue'; |
| | | |
| | | /** çç¹å¾é¢è²éæ©å¨ï¼ant-design-vue 4.x ææªæä¾ ColorPickerï¼å°è£
åç input[type=color]ï¼ */ |
| | | defineOptions({ name: 'RouteColorPicker' }); |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | disabled?: boolean; |
| | | modelValue?: string; |
| | | }>(), |
| | | { |
| | | disabled: false, |
| | | modelValue: '', |
| | | }, |
| | | ); |
| | | |
| | | const emit = defineEmits<{ |
| | | change: [value: string]; |
| | | 'update:modelValue': [value: string]; |
| | | }>(); |
| | | |
| | | /** ç¨äº input[type=color] çå±ç¤ºå¼ï¼å¿
é¡»æ¯åæ³ hexï¼éæ³æ¶åéå° #000000 ä½ä¸ä¿®æ¹ modelValue */ |
| | | const swatchValue = computed(() => |
| | | /^#[0-9a-f]{6}$/i.test(props.modelValue ?? '') |
| | | ? (props.modelValue as string) |
| | | : '#000000', |
| | | ); |
| | | |
| | | /** é¢è²å忶忥 modelValue */ |
| | | function handleColorChange(event: Event) { |
| | | const value = (event.target as HTMLInputElement).value; |
| | | emit('update:modelValue', value); |
| | | emit('change', value); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="flex items-center gap-2"> |
| | | <input |
| | | class="route-color-picker__swatch" |
| | | :disabled="disabled" |
| | | type="color" |
| | | :value="swatchValue" |
| | | @change="handleColorChange" |
| | | @input="handleColorChange" |
| | | /> |
| | | <span v-if="modelValue">{{ modelValue }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .route-color-picker__swatch { |
| | | inline-size: 36px; |
| | | block-size: 28px; |
| | | padding: 2px; |
| | | cursor: pointer; |
| | | border: 1px solid var(--ant-color-border, #d9d9d9); |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .route-color-picker__swatch:disabled { |
| | | cursor: not-allowed; |
| | | opacity: 0.6; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | export { default as RouteColorPicker } from './color-picker.vue'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProRouteApi } from '#/api/mes/pro/route'; |
| | | import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process'; |
| | | import type { MesProRouteProductApi } from '#/api/mes/pro/route/product'; |
| | | import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom'; |
| | | |
| | | import { h, markRaw } from 'vue'; |
| | | |
| | | import { |
| | | CommonStatusEnum, |
| | | DICT_TYPE, |
| | | MesAutoCodeRuleCode, |
| | | } from '../../../../packages/constants/src'; |
| | | import { getDictOptions } from '../../../../packages/effects/hooks/src'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { |
| | | MdItemSelect, |
| | | MdProductBomSelect, |
| | | } from '#/views/mes/md/item/components'; |
| | | |
| | | import { RouteColorPicker } from './components'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** å·¥èºè·¯çº¿è¡¨å */ |
| | | export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: '路线ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 64, |
| | | placeholder: '请è¾å
¥å·¥èºè·¯çº¿ç¼ç ', |
| | | }, |
| | | rules: z.string().min(1, '路线ç¼ç ä¸è½ä¸ºç©º').max(64), |
| | | suffix: () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const code = await generateAutoCode( |
| | | MesAutoCodeRuleCode.PRO_ROUTE_CODE, |
| | | ); |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'çæ' }, |
| | | ), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '路线åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥å·¥èºè·¯çº¿åç§°', |
| | | }, |
| | | rules: z.string().min(1, '路线åç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'description', |
| | | label: '路线说æ', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | maxLength: 500, |
| | | placeholder: '请è¾å
¥å·¥èºè·¯çº¿è¯´æ', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表æç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: '路线ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥è·¯çº¿ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '路线åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥è·¯çº¿åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** åè¡¨åæ®µ */ |
| | | export function useGridColumns( |
| | | onStatusChange?: ( |
| | | newStatus: number, |
| | | row: MesProRouteApi.Route, |
| | | ) => PromiseLike<boolean | undefined>, |
| | | statusEditable = true, |
| | | ): VxeTableGridOptions<MesProRouteApi.Route>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: '路线ç¼ç ', |
| | | minWidth: 160, |
| | | slots: { default: 'code' }, |
| | | }, |
| | | { field: 'name', title: '路线åç§°', minWidth: 180 }, |
| | | { field: 'description', title: '路线说æ', minWidth: 200 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 110, |
| | | align: 'center', |
| | | cellRender: { |
| | | attrs: { beforeChange: onStatusChange }, |
| | | name: 'CellSwitch', |
| | | props: { |
| | | checkedValue: CommonStatusEnum.ENABLE, |
| | | disabled: !statusEditable, |
| | | unCheckedValue: CommonStatusEnum.DISABLE, |
| | | }, |
| | | }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 160 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 160, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥èºè·¯çº¿å·¥åºæç»è¡¨å */ |
| | | export function useRouteProcessFormSchema( |
| | | processOptions: Array<{ label: string; value: number }>, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'routeId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'sort', |
| | | label: 'åºå·', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 1, |
| | | precision: 0, |
| | | }, |
| | | rules: z.number().default(1), |
| | | }, |
| | | { |
| | | fieldName: 'processId', |
| | | label: 'å·¥åº', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: processOptions, |
| | | placeholder: 'è¯·éæ©å·¥åº', |
| | | showSearch: true, |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'linkType', |
| | | label: 'ä¸ä¸éå·¥åºå
³ç³»', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PRO_LINK_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©', |
| | | }, |
| | | rules: z.number().default(3), |
| | | }, |
| | | { |
| | | fieldName: 'colorCode', |
| | | label: 'çç¹å¾é¢è²', |
| | | component: RouteColorPicker, |
| | | }, |
| | | { |
| | | fieldName: 'keyFlag', |
| | | label: 'æ¯å¦å
³é®å·¥åº', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | }, |
| | | { |
| | | fieldName: 'checkFlag', |
| | | label: 'æ¯å¦è´¨æ£ç¡®è®¤', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | }, |
| | | { |
| | | fieldName: 'prepareTime', |
| | | label: 'å夿¶é´(å)', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 0, |
| | | }, |
| | | rules: z.number().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'waitTime', |
| | | label: 'çå¾
æ¶é´(å)', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 0, |
| | | }, |
| | | rules: z.number().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥èºè·¯çº¿å·¥åºåè¡¨åæ®µ */ |
| | | export function useRouteProcessGridColumns(): VxeTableGridOptions<MesProRouteProcessApi.RouteProcess>['columns'] { |
| | | return [ |
| | | { field: 'sort', title: 'åºå·', width: 70, align: 'center', fixed: 'left' }, |
| | | { field: 'processCode', title: 'å·¥åºç¼ç ', width: 140, fixed: 'left' }, |
| | | { field: 'processName', title: 'å·¥åºåç§°', width: 140, fixed: 'left' }, |
| | | { field: 'nextProcessName', title: 'ä¸ä¸éå·¥åº', width: 140 }, |
| | | { |
| | | field: 'linkType', |
| | | title: 'ä¸ä¸ä¸éå·¥åºå
³ç³»', |
| | | width: 160, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PRO_LINK_TYPE }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'keyFlag', |
| | | title: 'å
³é®å·¥åº', |
| | | width: 90, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'checkFlag', |
| | | title: 'è´¨æ£ç¡®è®¤', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, |
| | | }, |
| | | }, |
| | | { field: 'prepareTime', title: 'å夿¶é´(å)', width: 110 }, |
| | | { field: 'waitTime', title: 'çå¾
æ¶é´(å)', width: 110 }, |
| | | { |
| | | field: 'colorCode', |
| | | title: 'çç¹å¾é¢è²', |
| | | width: 130, |
| | | slots: { default: 'colorCode' }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥èºè·¯çº¿äº§ååè¡¨åæ®µ */ |
| | | export function useRouteProductGridColumns(): VxeTableGridOptions<MesProRouteProductApi.RouteProduct>['columns'] { |
| | | return [ |
| | | { field: 'itemCode', title: '产åç©æç¼ç ', width: 150 }, |
| | | { field: 'itemName', title: '产åç©æåç§°', width: 150 }, |
| | | { field: 'specification', title: 'è§æ ¼åå·', width: 150 }, |
| | | { field: 'unitName', title: 'åä½', width: 80 }, |
| | | { field: 'quantity', title: 'ç产æ°é', width: 100 }, |
| | | { |
| | | field: 'productionTime', |
| | | title: 'çäº§ç¨æ¶', |
| | | width: 130, |
| | | slots: { default: 'productionTime' }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥èºè·¯çº¿äº§å BOM åè¡¨åæ®µ */ |
| | | export function useRouteProductBomGridColumns(): VxeTableGridOptions<MesProRouteProductBomApi.RouteProductBom>['columns'] { |
| | | return [ |
| | | { field: 'itemCode', title: 'BOM ç©æç¼ç ', width: 150 }, |
| | | { field: 'itemName', title: 'BOM ç©æåç§°', width: 150 }, |
| | | { field: 'specification', title: 'è§æ ¼åå·', width: 150 }, |
| | | { field: 'unitName', title: 'åä½', width: 80 }, |
| | | { field: 'quantity', title: 'ç¨ææ¯ä¾', width: 100 }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥èºè·¯çº¿äº§å表å */ |
| | | export function useRouteProductFormSchema( |
| | | onItemChange?: (item: any) => void, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'routeId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | // 产åç©æï¼ä½¿ç¨ä¸å¡èªå®ä¹éæ©å¨ï¼change åå¡«ç¼ç /åç§°/è§æ ¼/åä½ |
| | | { |
| | | fieldName: 'itemId', |
| | | label: '产å', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | onChange: onItemChange, |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'quantity', |
| | | label: 'ç产æ°é', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 1, |
| | | precision: 0, |
| | | }, |
| | | rules: z.number().default(1), |
| | | }, |
| | | { |
| | | fieldName: 'productionTime', |
| | | label: 'çäº§ç¨æ¶', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | rules: z.number().default(1), |
| | | }, |
| | | { |
| | | fieldName: 'timeUnitType', |
| | | label: 'æ¶é´åä½', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_TIME_UNIT_TYPE), |
| | | placeholder: 'è¯·éæ©', |
| | | }, |
| | | rules: z.string().default('MINUTE'), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥èºè·¯çº¿äº§å BOM 表å */ |
| | | export function useRouteProductBomFormSchema( |
| | | itemId: () => number, |
| | | onBomChange?: (bom: any) => void, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'routeId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'processId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | { |
| | | fieldName: 'productId', |
| | | component: 'Input', |
| | | dependencies: { triggerFields: [''], show: () => false }, |
| | | }, |
| | | // BOM ç©æï¼ä¾èµäº§åç©æï¼ä½¿ç¨ä¸å¡èªå®ä¹éæ©å¨ |
| | | { |
| | | fieldName: 'itemId', |
| | | label: 'BOM ç©æ', |
| | | component: markRaw(MdProductBomSelect), |
| | | componentProps: () => ({ |
| | | itemId: itemId(), |
| | | onChange: onBomChange, |
| | | placeholder: 'è¯·éæ© BOM ç©æ', |
| | | }), |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'quantity', |
| | | label: 'ç¨ææ¯ä¾', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | rules: z.number().default(1), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProRouteApi } from '#/api/mes/pro/route'; |
| | | |
| | | import { useAccess } from '../../../../packages/effects/access/src'; |
| | | import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src'; |
| | | import { getDictLabel } from '../../../../packages/effects/hooks/src'; |
| | | import { downloadFileFromBlobPart } from '../../../../packages/utils/src'; |
| | | |
| | | import { Button, message, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteRoute, |
| | | exportRoute, |
| | | getRoutePage, |
| | | updateRouteStatus, |
| | | } from '#/api/mes/pro/route'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const { hasAccessByCodes } = useAccess(); |
| | | const statusEditable = hasAccessByCodes(['mes:pro-route:update']); // æ¯å¦å¯åæ¢ç¶æ |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** å建工èºè·¯çº¿ */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾å·¥èºè·¯çº¿ï¼ä»
åç¨ç¶æå¯ç¼è¾ï¼ */ |
| | | function handleEdit(row: MesProRouteApi.Route) { |
| | | formModalApi.setData({ id: row.id, formType: 'update' }).open(); |
| | | } |
| | | |
| | | /** 详æ
æ¥ç */ |
| | | function handleDetail(row: MesProRouteApi.Route) { |
| | | formModalApi.setData({ id: row.id, formType: 'detail' }).open(); |
| | | } |
| | | |
| | | /** åæ¢ç¶æ */ |
| | | async function handleStatusChange( |
| | | newStatus: number, |
| | | row: MesProRouteApi.Route, |
| | | ): Promise<boolean | undefined> { |
| | | try { |
| | | await confirm( |
| | | `确认è¦å°"${row.name}"å·¥èºè·¯çº¿åæ¢ä¸ºã${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}ãåï¼`, |
| | | ); |
| | | } catch { |
| | | return false; |
| | | } |
| | | await updateRouteStatus(row.id!, newStatus); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | return true; |
| | | } |
| | | |
| | | /** å é¤ï¼ä»
åç¨ç¶æå¯å é¤ï¼ */ |
| | | async function handleDelete(row: MesProRouteApi.Route) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteRoute(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** å¯¼åº */ |
| | | async function handleExport() { |
| | | const data = await exportRoute(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'å·¥èºè·¯çº¿.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(handleStatusChange, statusEditable), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getRoutePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | keyField: 'id', |
| | | }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<MesProRouteApi.Route>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="å·¥èºè·¯çº¿å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['å·¥èºè·¯çº¿']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-route:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-route:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.code }}</Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <Tooltip |
| | | :open="row.status === CommonStatusEnum.DISABLE ? false : undefined" |
| | | title="ä»
åç¨ç¶æï¼æå¯ä»¥æä½" |
| | | > |
| | | <span class="inline-block"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pro-route:update'], |
| | | disabled: row.status !== CommonStatusEnum.DISABLE, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pro-route:delete'], |
| | | disabled: row.status !== CommonStatusEnum.DISABLE, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </span> |
| | | </Tooltip> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createRouteProductBom, |
| | | getRouteProductBom, |
| | | updateRouteProductBom, |
| | | } from '#/api/mes/pro/route/productbom'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useRouteProductBomFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesProRouteProductBomApi.RouteProductBom>(); |
| | | const productId = ref<number>(0); // å½å产åç©æç¼å·ï¼ä¾ BOM éæ©å¨çéå¯ç¨ BOM |
| | | const getTitle = computed(() => |
| | | formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['BOM ç©æ']) |
| | | : $t('ui.actionTitle.create', ['BOM ç©æ']), |
| | | ); |
| | | |
| | | /** MdProductBomSelect change åè°ï¼æ BOM ç©æç¼ç /åç§°/è§æ ¼/åä½/é»è®¤ç¨éåå¡«å° form */ |
| | | async function handleBomChange(bom?: any) { |
| | | if (!bom) { |
| | | return; |
| | | } |
| | | await formApi.setValues({ |
| | | itemCode: bom.bomItemCode, |
| | | itemName: bom.bomItemName, |
| | | quantity: bom.quantity ?? 1, |
| | | specification: bom.specification, |
| | | unitName: bom.unitName, |
| | | }); |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 110, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useRouteProductBomFormSchema(() => productId.value, handleBomChange), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = |
| | | (await formApi.getValues()) as MesProRouteProductBomApi.RouteProductBom; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateRouteProductBom(data) |
| | | : createRouteProductBom(data)); |
| | | // å
³éå¹¶æç¤º |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | id?: number; |
| | | processId: number; |
| | | productId: number; |
| | | routeId: number; |
| | | }>(); |
| | | if (!data) { |
| | | return; |
| | | } |
| | | productId.value = data.productId; |
| | | if (!data.id) { |
| | | // æ°å¢æ¶ï¼ç» routeId/processId/productId å
åºé»è®¤å¼ |
| | | await formApi.setValues({ |
| | | processId: data.processId, |
| | | productId: data.productId, |
| | | quantity: 1, |
| | | routeId: data.routeId, |
| | | }); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getRouteProductBom(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/3"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { MesProRouteApi } from '#/api/mes/pro/route'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, TabPane, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createRoute, getRoute, updateRoute } from '#/api/mes/pro/route'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | import ProcessList from './process-list.vue'; |
| | | import ProductList from './product-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); // è¡¨åæ¨¡å¼ |
| | | const subTab = ref('process'); // å½åæ¿æ´»çå表 Tab |
| | | const formData = ref<MesProRouteApi.Route>(); |
| | | |
| | | const isDetail = computed(() => formType.value === 'detail'); // æ¯å¦æ¥çæ¨¡å¼ |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return $t('ui.actionTitle.detail', ['å·¥èºè·¯çº¿']); |
| | | } |
| | | return formType.value === 'update' |
| | | ? $t('ui.actionTitle.edit', ['å·¥èºè·¯çº¿']) |
| | | : $t('ui.actionTitle.create', ['å·¥èºè·¯çº¿']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-2', |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = (await formApi.getValues()) as MesProRouteApi.Route; |
| | | try { |
| | | if (formType.value === 'create') { |
| | | // æ°å¢æåååå°ç¼è¾æ¨¡å¼ï¼æ¹ä¾¿ç»§ç»ç»´æ¤ç»æå·¥åºãå
³è产å |
| | | const id = await createRoute(data); |
| | | formData.value = { ...data, id }; |
| | | await formApi.setFieldValue('id', id); |
| | | formType.value = 'update'; |
| | | } else { |
| | | await updateRoute(data); |
| | | formData.value = { ...formData.value, ...data }; |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | formApi.setState({ schema: useFormSchema(formApi) }); |
| | | subTab.value = 'process'; |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getRoute(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- ç¼è¾/详æ
模å¼ä¸å±ç¤ºå表 Tabï¼æ°å¢æ¨¡å¼ä¸éè --> |
| | | <Tabs |
| | | v-if="formType !== 'create' && formData?.id" |
| | | v-model:active-key="subTab" |
| | | class="mx-4 mt-4" |
| | | > |
| | | <TabPane key="process" tab="ç»æå·¥åº"> |
| | | <ProcessList :form-type="formType" :route-id="formData.id" /> |
| | | </TabPane> |
| | | <TabPane key="product" tab="å
³è产å"> |
| | | <ProductList :form-type="formType" :route-id="formData.id" /> |
| | | </TabPane> |
| | | </Tabs> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { getProcessSimpleList } from '#/api/mes/pro/process'; |
| | | import { |
| | | createRouteProcess, |
| | | getRouteProcess, |
| | | updateRouteProcess, |
| | | } from '#/api/mes/pro/route/process'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useRouteProcessFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesProRouteProcessApi.RouteProcess>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['å·¥èºè·¯çº¿å·¥åº']) |
| | | : $t('ui.actionTitle.create', ['å·¥èºè·¯çº¿å·¥åº']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 130, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-2', |
| | | }); |
| | | |
| | | /** å 载工åºé项ååçæ schemaï¼é¿å
ä¸æç©ºéé¡¹ï¼ */ |
| | | async function loadSchema(): Promise<VbenFormSchema[]> { |
| | | const list = await getProcessSimpleList(); |
| | | const options = (list || []).map((item) => ({ |
| | | label: item.name ?? '', |
| | | value: item.id!, |
| | | })); |
| | | return useRouteProcessFormSchema(options); |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = |
| | | (await formApi.getValues()) as MesProRouteProcessApi.RouteProcess; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateRouteProcess(data) |
| | | : createRouteProcess(data)); |
| | | // å
³éå¹¶æç¤º |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | // å·¥åºä¸æä¾èµè¿ç¨æ°æ®ï¼schema å¨å¼¹çªæå¼æ¶åçæ |
| | | const schema = await loadSchema(); |
| | | formApi.setState({ schema }); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | id?: number; |
| | | maxSort?: number; |
| | | routeId: number; |
| | | }>(); |
| | | if (!data) { |
| | | return; |
| | | } |
| | | if (!data.id) { |
| | | // æ°å¢æ¶ï¼é»è®¤åºå· = maxSort + 1ï¼å¹¶ç»ä¸ä¸ªé»è®¤çç¹å¾é¢è² |
| | | await formApi.setValues({ |
| | | colorCode: '#00AEF3', |
| | | routeId: data.routeId, |
| | | sort: (data.maxSort || 0) + 1, |
| | | }); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getRouteProcess(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteRouteProcess, |
| | | getRouteProcessListByRoute, |
| | | } from '#/api/mes/pro/route/process'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useRouteProcessGridColumns } from '../data'; |
| | | import ProcessForm from './process-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | formType: FormType; |
| | | routeId: number; |
| | | }>(); |
| | | |
| | | const isEditable = ref(props.formType !== 'detail'); // æ¯å¦å¯ç¼è¾ |
| | | const list = ref<MesProRouteProcessApi.RouteProcess[]>([]); // å·¥èºè·¯çº¿å·¥åºå表 |
| | | |
| | | const [ProcessFormModal, processFormModalApi] = useVbenModal({ |
| | | connectedComponent: ProcessForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | autoResize: true, |
| | | border: true, |
| | | columns: useRouteProcessGridColumns(), |
| | | data: list.value, |
| | | minHeight: 240, |
| | | pagerConfig: { enabled: false }, |
| | | rowConfig: { isHover: true, keyField: 'id' }, |
| | | showOverflow: true, |
| | | toolbarConfig: { enabled: false }, |
| | | } as VxeTableGridOptions<MesProRouteProcessApi.RouteProcess>, |
| | | }); |
| | | |
| | | /** å 载工èºè·¯çº¿å·¥åºå表 */ |
| | | async function getList() { |
| | | gridApi.setLoading(true); |
| | | try { |
| | | list.value = await getRouteProcessListByRoute(props.routeId); |
| | | gridApi.setGridOptions({ data: list.value }); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | /** æ°å¢å·¥èºè·¯çº¿å·¥åº */ |
| | | function handleCreate() { |
| | | const maxSort = Math.max(0, ...list.value.map((item) => item.sort || 0)); |
| | | processFormModalApi.setData({ maxSort, routeId: props.routeId }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾å·¥èºè·¯çº¿å·¥åº */ |
| | | function handleEdit(row: MesProRouteProcessApi.RouteProcess) { |
| | | processFormModalApi.setData({ id: row.id, routeId: props.routeId }).open(); |
| | | } |
| | | |
| | | /** å é¤å·¥èºè·¯çº¿å·¥åº */ |
| | | async function handleDelete(row: MesProRouteProcessApi.RouteProcess) { |
| | | await deleteRouteProcess(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', ['å·¥èºè·¯çº¿å·¥åº'])); |
| | | await getList(); |
| | | } |
| | | |
| | | watch( |
| | | () => props.routeId, |
| | | (value) => { |
| | | if (value) { |
| | | getList(); |
| | | } |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <ProcessFormModal @success="getList" /> |
| | | <div v-if="isEditable" class="mb-3 flex items-center justify-start"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['å·¥åº']), |
| | | type: 'primary', |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </div> |
| | | <Grid class="w-full" table-title="ç»æå·¥åº"> |
| | | <template #colorCode="{ row }"> |
| | | <div v-if="row.colorCode" class="flex items-center justify-center gap-1"> |
| | | <div |
| | | class="h-4 w-4 rounded" |
| | | :style="{ backgroundColor: row.colorCode }" |
| | | ></div> |
| | | <span>{{ row.colorCode }}</span> |
| | | </div> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | ifShow: () => isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | ifShow: () => isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', ['å·¥èºè·¯çº¿å·¥åº']), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process'; |
| | | import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, TabPane, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getRouteProcessListByRoute } from '#/api/mes/pro/route/process'; |
| | | import { |
| | | deleteRouteProductBom, |
| | | getRouteProductBomList, |
| | | } from '#/api/mes/pro/route/productbom'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useRouteProductBomGridColumns } from '../data'; |
| | | import BomForm from './bom-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | productId: number; |
| | | productName?: string; |
| | | routeId: number; |
| | | }>(); |
| | | |
| | | const processList = ref<MesProRouteProcessApi.RouteProcess[]>([]); // å·¥åºå表ï¼ç¨äº Tabï¼ |
| | | const activeProcessId = ref<number>(); // å½åéä¸çå·¥åº Tab |
| | | const list = ref<MesProRouteProductBomApi.RouteProductBom[]>([]); // å½åå·¥åºä¸ç BOM å表 |
| | | |
| | | const [BomFormModal, bomFormModalApi] = useVbenModal({ |
| | | connectedComponent: BomForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | autoResize: true, |
| | | border: true, |
| | | columns: useRouteProductBomGridColumns(), |
| | | data: list.value, |
| | | minHeight: 200, |
| | | pagerConfig: { enabled: false }, |
| | | rowConfig: { isHover: true, keyField: 'id' }, |
| | | showOverflow: true, |
| | | toolbarConfig: { enabled: false }, |
| | | } as VxeTableGridOptions<MesProRouteProductBomApi.RouteProductBom>, |
| | | }); |
| | | |
| | | /** å 载路线ä¸çå·¥åºå表ï¼ç¨äºå·¥åº Tab */ |
| | | async function loadProcessList() { |
| | | processList.value = (await getRouteProcessListByRoute(props.routeId)) || []; |
| | | if (processList.value.length > 0) { |
| | | activeProcessId.value = processList.value[0]!.processId; |
| | | await getList(); |
| | | } else { |
| | | activeProcessId.value = undefined; |
| | | list.value = []; |
| | | gridApi.setGridOptions({ data: list.value }); |
| | | } |
| | | } |
| | | |
| | | /** å è½½å½åå·¥åºä¸ç BOM å表 */ |
| | | async function getList() { |
| | | if (!activeProcessId.value) { |
| | | return; |
| | | } |
| | | gridApi.setLoading(true); |
| | | try { |
| | | list.value = await getRouteProductBomList({ |
| | | processId: activeProcessId.value, |
| | | productId: props.productId, |
| | | routeId: props.routeId, |
| | | }); |
| | | gridApi.setGridOptions({ data: list.value }); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | /** æ°å¢ BOM */ |
| | | function handleCreate() { |
| | | if (!activeProcessId.value) { |
| | | message.warning('请å
鿩工åº'); |
| | | return; |
| | | } |
| | | bomFormModalApi |
| | | .setData({ |
| | | processId: activeProcessId.value, |
| | | productId: props.productId, |
| | | routeId: props.routeId, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** ç¼è¾ BOM */ |
| | | function handleEdit(row: MesProRouteProductBomApi.RouteProductBom) { |
| | | bomFormModalApi |
| | | .setData({ |
| | | id: row.id, |
| | | processId: activeProcessId.value!, |
| | | productId: props.productId, |
| | | routeId: props.routeId, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** å é¤ BOM */ |
| | | async function handleDelete(row: MesProRouteProductBomApi.RouteProductBom) { |
| | | await deleteRouteProductBom(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', ['BOM ç©æ'])); |
| | | await getList(); |
| | | } |
| | | |
| | | watch( |
| | | () => [props.routeId, props.productId], |
| | | () => { |
| | | if (props.routeId && props.productId) { |
| | | loadProcessList(); |
| | | } |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <BomFormModal @success="getList" /> |
| | | <Tabs v-model:active-key="activeProcessId" @change="getList"> |
| | | <TabPane |
| | | v-for="item in processList" |
| | | :key="item.processId" |
| | | :tab="item.processName" |
| | | /> |
| | | </Tabs> |
| | | <div class="mb-3 flex items-center justify-start"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['BOM ç©æ']), |
| | | type: 'primary', |
| | | disabled: !activeProcessId, |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </div> |
| | | <Grid class="w-full" table-title="产å BOM"> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', ['BOM ç©æ']), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProRouteProductApi } from '#/api/mes/pro/route/product'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { Divider, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createRouteProduct, |
| | | getRouteProduct, |
| | | updateRouteProduct, |
| | | } from '#/api/mes/pro/route/product'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useRouteProductFormSchema } from '../data'; |
| | | import ProductBomList from './product-bom-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesProRouteProductApi.RouteProduct>(); |
| | | const getTitle = computed(() => |
| | | formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['å·¥èºè·¯çº¿äº§å']) |
| | | : $t('ui.actionTitle.create', ['å·¥èºè·¯çº¿äº§å']), |
| | | ); |
| | | |
| | | /** MdItemSelect change åè°ï¼æç©æç¼ç /åç§°/è§æ ¼/åä½åå¡«å° form */ |
| | | async function handleItemChange(item?: any) { |
| | | if (!item) { |
| | | return; |
| | | } |
| | | await formApi.setValues({ |
| | | itemCode: item.code, |
| | | itemName: item.name, |
| | | specification: item.specification, |
| | | unitName: item.unitMeasureName, |
| | | }); |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useRouteProductFormSchema(handleItemChange), |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-2', |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = |
| | | (await formApi.getValues()) as MesProRouteProductApi.RouteProduct; |
| | | try { |
| | | if (formData.value?.id) { |
| | | await updateRouteProduct(data); |
| | | // ç¨ææ°è¡¨åå¼åæ¥ formDataï¼ç¡®ä¿äº§å BOM å表ç»å®ç itemId ä¸è¡¨åä¸è´ |
| | | formData.value = { ...formData.value, ...data }; |
| | | } else { |
| | | // æ°å¢æåå忢å°ç¼è¾æ¨¡å¼ï¼æ¹ä¾¿ç»§ç»ç»´æ¤äº§å BOM |
| | | const id = await createRouteProduct(data); |
| | | formData.value = { ...data, id }; |
| | | await formApi.setFieldValue('id', id); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | id?: number; |
| | | routeId: number; |
| | | }>(); |
| | | if (!data) { |
| | | return; |
| | | } |
| | | if (!data.id) { |
| | | await formApi.setValues({ routeId: data.routeId }); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getRouteProduct(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- ç¼è¾æ¨¡å¼ä¸å±ç¤ºäº§å BOM åè¡¨ï¼æ°å¢æ¨¡å¼ä¸éè --> |
| | | <template v-if="formData?.id && formData?.itemId"> |
| | | <Divider class="!my-3" orientation="left">产å BOM é
ç½®</Divider> |
| | | <div class="mx-4"> |
| | | <ProductBomList |
| | | :route-id="formData.routeId!" |
| | | :product-id="formData.itemId" |
| | | :product-name="formData.itemName" |
| | | /> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProRouteProductApi } from '#/api/mes/pro/route/product'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | import { DICT_TYPE } from '../../../../../packages/constants/src'; |
| | | import { getDictLabel } from '../../../../../packages/effects/hooks/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteRouteProduct, |
| | | getRouteProductListByRoute, |
| | | } from '#/api/mes/pro/route/product'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useRouteProductGridColumns } from '../data'; |
| | | import ProductForm from './product-form.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | formType: FormType; |
| | | routeId: number; |
| | | }>(); |
| | | |
| | | const isEditable = ref(props.formType !== 'detail'); // æ¯å¦å¯ç¼è¾ |
| | | const list = ref<MesProRouteProductApi.RouteProduct[]>([]); // å·¥èºè·¯çº¿äº§åå表 |
| | | |
| | | const [ProductFormModal, productFormModalApi] = useVbenModal({ |
| | | connectedComponent: ProductForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | autoResize: true, |
| | | border: true, |
| | | columns: useRouteProductGridColumns(), |
| | | data: list.value, |
| | | minHeight: 240, |
| | | pagerConfig: { enabled: false }, |
| | | rowConfig: { isHover: true, keyField: 'id' }, |
| | | showOverflow: true, |
| | | toolbarConfig: { enabled: false }, |
| | | } as VxeTableGridOptions<MesProRouteProductApi.RouteProduct>, |
| | | }); |
| | | |
| | | /** å 载工èºè·¯çº¿äº§åå表 */ |
| | | async function getList() { |
| | | gridApi.setLoading(true); |
| | | try { |
| | | list.value = await getRouteProductListByRoute(props.routeId); |
| | | gridApi.setGridOptions({ data: list.value }); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | /** æ°å¢å·¥èºè·¯çº¿äº§å */ |
| | | function handleCreate() { |
| | | productFormModalApi.setData({ routeId: props.routeId }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾å·¥èºè·¯çº¿äº§å */ |
| | | function handleEdit(row: MesProRouteProductApi.RouteProduct) { |
| | | productFormModalApi.setData({ id: row.id, routeId: props.routeId }).open(); |
| | | } |
| | | |
| | | /** å é¤å·¥èºè·¯çº¿äº§å */ |
| | | async function handleDelete(row: MesProRouteProductApi.RouteProduct) { |
| | | await deleteRouteProduct(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', ['å·¥èºè·¯çº¿äº§å'])); |
| | | await getList(); |
| | | } |
| | | |
| | | watch( |
| | | () => props.routeId, |
| | | (value) => { |
| | | if (value) { |
| | | getList(); |
| | | } |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <ProductFormModal @success="getList" /> |
| | | <div v-if="isEditable" class="mb-3 flex items-center justify-start"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['å
³è产å']), |
| | | type: 'primary', |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </div> |
| | | <Grid class="w-full" table-title="å
³è产å"> |
| | | <template #productionTime="{ row }"> |
| | | <span v-if="row.productionTime"> |
| | | {{ row.productionTime }} |
| | | {{ getDictLabel(DICT_TYPE.MES_TIME_UNIT_TYPE, row.timeUnitType) }} |
| | | </span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | ifShow: () => isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | ifShow: () => isEditable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', ['å·¥èºè·¯çº¿äº§å']), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdTemplateApi } from '#/api/mes/pro/processdesign/template'; |
| | | |
| | | import { DICT_TYPE } from '../../../../packages/constants/src'; |
| | | import { getDictOptions } from '../../../../packages/effects/hooks/src'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹å·¥èºåæ°æ¨¡æ¿ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '模æ¿åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥æ¨¡æ¿åç§°', |
| | | }, |
| | | rules: z.string().min(1, '模æ¿åç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: '模æ¿ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_TEMPLATE_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©æ¨¡æ¿ç±»å', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'parameterIds', |
| | | label: 'å
³èåæ°', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'name', |
| | | label: '模æ¿åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥æ¨¡æ¿åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: '模æ¿ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PD_TEMPLATE_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©æ¨¡æ¿ç±»å', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesPdTemplateApi.Template>['columns'] { |
| | | return [ |
| | | { |
| | | type: 'seq', |
| | | title: 'åºå·', |
| | | width: 60, |
| | | align: 'center', |
| | | }, |
| | | { field: 'name', title: '模æ¿åç§°', minWidth: 180 }, |
| | | { |
| | | field: 'type', |
| | | title: '模æ¿ç±»å', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_TEMPLATE_TYPE }, |
| | | }, |
| | | }, |
| | | { field: 'createUser', title: 'å建人', width: 120 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 150, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'actions', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
copy from src/views/mes/md/unitmeasure/index.vue
copy to src/views/mes/process-design/template/index.vue
| Îļþ´Ó src/views/mes/md/unitmeasure/index.vue ¸´ÖÆ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesMdUnitMeasureApi } from '#/api/mes/md/unitmeasure'; |
| | | import type { MesPdTemplateApi } from '#/api/mes/pro/processdesign/template'; |
| | | |
| | | import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '../../../../packages/utils/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteUnitMeasure, |
| | | exportUnitMeasure, |
| | | getUnitMeasurePage, |
| | | } from '#/api/mes/md/unitmeasure'; |
| | | deleteTemplate, |
| | | getTemplatePage, |
| | | } from '#/api/mes/pro/processdesign/template'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportUnitMeasure(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '计éåä½.xls', source: data }); |
| | | } |
| | | |
| | | /** å建计éåä½ */ |
| | | /** å建工èºåæ°æ¨¡æ¿ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®¡éåä½ */ |
| | | function handleEdit(row: MesMdUnitMeasureApi.UnitMeasure) { |
| | | formModalApi.setData(row).open(); |
| | | /** ç¼è¾å·¥èºåæ°æ¨¡æ¿ */ |
| | | function handleEdit(row: MesPdTemplateApi.Template) { |
| | | formModalApi.setData({ id: row.id, formType: 'update' }).open(); |
| | | } |
| | | |
| | | /** å é¤è®¡éåä½ */ |
| | | async function handleDelete(row: MesMdUnitMeasureApi.UnitMeasure) { |
| | | /** æ¥çå·¥èºåæ°æ¨¡æ¿è¯¦æ
*/ |
| | | function handleDetail(row: MesPdTemplateApi.Template) { |
| | | formModalApi.setData({ id: row.id, formType: 'detail' }).open(); |
| | | } |
| | | |
| | | /** å é¤å·¥èºåæ°æ¨¡æ¿ */ |
| | | async function handleDelete(row: MesPdTemplateApi.Template) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteUnitMeasure(row.id!); |
| | | await deleteTemplate(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getUnitMeasurePage({ |
| | | return await getTemplatePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | keyField: 'id', |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesMdUnitMeasureApi.UnitMeasure>, |
| | | } as VxeTableGridOptions<MesPdTemplateApi.Template>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="计éåä½å表"> |
| | | <Grid table-title="å·¥èºåæ°æ¨¡æ¿å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['计éåä½']), |
| | | label: $t('ui.actionTitle.create', ['模æ¿']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:md-unit-measure:create'], |
| | | auth: ['mes:pd:template:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:md-unit-measure:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:md-unit-measure:update'], |
| | | auth: ['mes:pd:template:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:md-unit-measure:delete'], |
| | | auth: ['mes:pd:template:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { MesPdParameterApi } from '#/api/mes/pro/processdesign/parameter'; |
| | | import type { MesPdTemplateApi } from '#/api/mes/pro/processdesign/template'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { Button, message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createTemplate, |
| | | getTemplate, |
| | | updateTemplate, |
| | | } from '#/api/mes/pro/processdesign/template'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | import ParameterSelectDialog from './parameter-select-dialog.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesPdTemplateApi.Template>(); |
| | | |
| | | /** å·²éåæ°å表ï¼ç¨äºé¡µé¢å±ç¤ºï¼ */ |
| | | const selectedParameters = ref<MesPdParameterApi.Parameter[]>([]); |
| | | const parameterSelectDialogRef = ref<InstanceType<typeof ParameterSelectDialog>>(); |
| | | |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return $t('ui.actionTitle.detail', ['å·¥èºåæ°æ¨¡æ¿']); |
| | | } |
| | | return formType.value === 'update' |
| | | ? $t('ui.actionTitle.edit', ['å·¥èºåæ°æ¨¡æ¿']) |
| | | : $t('ui.actionTitle.create', ['å·¥èºåæ°æ¨¡æ¿']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-2', |
| | | }); |
| | | |
| | | /** æå¼å·¥èºåæ°éæ©å¼¹çª */ |
| | | function handleSelectParameters() { |
| | | parameterSelectDialogRef.value?.open( |
| | | selectedParameters.value |
| | | .map((p) => p.id) |
| | | .filter((id): id is number => id !== undefined), |
| | | ); |
| | | } |
| | | |
| | | /** æ¥æ¶éä¸çåæ° */ |
| | | function onParametersSelected(rows: MesPdParameterApi.Parameter[]) { |
| | | selectedParameters.value = rows; |
| | | } |
| | | |
| | | /** ç§»é¤å·²éåæ° */ |
| | | function removeParameter(param: MesPdParameterApi.Parameter) { |
| | | selectedParameters.value = selectedParameters.value.filter( |
| | | (p) => p.id !== param.id, |
| | | ); |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | if (selectedParameters.value.length === 0) { |
| | | message.warning('请è³å°éæ©ä¸ä¸ªå·¥èºåæ°'); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesPdTemplateApi.Template; |
| | | data.parameterIds = selectedParameters.value |
| | | .map((p) => p.id) |
| | | .filter((id): id is number => id !== undefined); |
| | | try { |
| | | await (formData.value?.id ? updateTemplate(data) : createTemplate(data)); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | selectedParameters.value = []; |
| | | return; |
| | | } |
| | | formApi.setState({ schema: useFormSchema() }); |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getTemplate(data.id); |
| | | await formApi.setValues(formData.value); |
| | | // 忾已éåæ°ï¼ä»è¯¦æ
æ¥å£è¿åçåæ°å表ä¸è·åï¼ |
| | | if (formData.value.parameterIds) { |
| | | // parameterIds å·²ç±å端è¿åï¼selectedParameters å¨è¯¦æ
模å¼ä¸å±ç¤º |
| | | } |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- å
³èåæ°éæ©åºå --> |
| | | <div class="mx-4 mt-4"> |
| | | <div class="mb-2 flex items-center justify-between"> |
| | | <span class="text-sm font-medium">å
³èå·¥èºåæ°</span> |
| | | <Button |
| | | v-if="!isDetail" |
| | | type="primary" |
| | | size="small" |
| | | @click="handleSelectParameters" |
| | | > |
| | | 鿩忰 |
| | | </Button> |
| | | </div> |
| | | <div |
| | | v-if="selectedParameters.length > 0" |
| | | class="min-h-10 rounded border border-gray-200 p-2" |
| | | > |
| | | <Tag |
| | | v-for="param in selectedParameters" |
| | | :key="param.id" |
| | | :closable="!isDetail" |
| | | class="mb-1 mr-1" |
| | | @close="removeParameter(param)" |
| | | > |
| | | {{ param.name }}({{ param.code }}) |
| | | </Tag> |
| | | </div> |
| | | <div v-else class="rounded border border-dashed border-gray-300 p-4 text-center text-gray-400"> |
| | | ææªéæ©åæ° |
| | | </div> |
| | | </div> |
| | | <ParameterSelectDialog |
| | | ref="parameterSelectDialogRef" |
| | | @selected="onParametersSelected" |
| | | /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesPdParameterApi } from '#/api/mes/pro/processdesign/parameter'; |
| | | |
| | | import { nextTick, ref } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '../../../../../packages/constants/src'; |
| | | |
| | | import { Button, message, Modal } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getParameterPage } from '#/api/mes/pro/processdesign/parameter'; |
| | | |
| | | defineOptions({ name: 'PdParameterSelectDialog' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | selected: [rows: MesPdParameterApi.Parameter[]]; |
| | | }>(); |
| | | |
| | | const open = ref(false); |
| | | const selectedRows = ref<MesPdParameterApi.Parameter[]>([]); |
| | | const preSelectedIds = ref<number[]>([]); |
| | | |
| | | /** è·åå¤éè®°å½ï¼å
å« VXE reserve è·¨é¡µè®°å½ */ |
| | | function getMultipleSelectedRows() { |
| | | const selectedMap = new Map<number, MesPdParameterApi.Parameter>(); |
| | | const records = [ |
| | | ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []), |
| | | ...(gridApi.grid.getCheckboxRecords?.() ?? []), |
| | | ] as MesPdParameterApi.Parameter[]; |
| | | records.forEach((row) => { |
| | | if (row.id) { |
| | | selectedMap.set(row.id, row); |
| | | } |
| | | }); |
| | | return [...selectedMap.values()]; |
| | | } |
| | | |
| | | /** å¤çå¾éåå */ |
| | | function handleCheckboxSelectChange() { |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | /** å¤é模å¼ä¸åæ¢è¡å¾é */ |
| | | async function toggleMultipleRow(row: MesPdParameterApi.Parameter) { |
| | | const selected = gridApi.grid.isCheckedByCheckboxRow(row); |
| | | await gridApi.grid.setCheckboxRow(row, !selected); |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | /** å¤çè¡åå» */ |
| | | async function handleCellDblclick({ row }: { row: MesPdParameterApi.Parameter }) { |
| | | await toggleMultipleRow(row); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'åæ°ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥åæ°ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'åæ°åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥åæ°åç§°', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | gridOptions: { |
| | | columns: [ |
| | | { type: 'checkbox', width: 50 }, |
| | | { field: 'code', title: 'åæ°ç¼ç ', minWidth: 150 }, |
| | | { field: 'name', title: 'åæ°åç§°', minWidth: 180 }, |
| | | { |
| | | field: 'type', |
| | | title: 'åæ°ç±»å', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PD_PARAM_TYPE }, |
| | | }, |
| | | }, |
| | | { field: 'unit', title: 'åä½', width: 80 }, |
| | | { |
| | | field: 'requiredFlag', |
| | | title: 'æ¯å¦å¿
å¡«', |
| | | width: 90, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, |
| | | }, |
| | | }, |
| | | ], |
| | | height: 520, |
| | | keepSource: true, |
| | | checkboxConfig: { |
| | | highlight: true, |
| | | range: true, |
| | | reserve: true, |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getParameterPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesPdParameterApi.Parameter>, |
| | | gridEvents: { |
| | | cellDblclick: handleCellDblclick, |
| | | checkboxAll: handleCheckboxSelectChange, |
| | | checkboxChange: handleCheckboxSelectChange, |
| | | }, |
| | | }); |
| | | |
| | | /** åæ¾é¢éåæ° */ |
| | | async function applyPreSelection() { |
| | | if (preSelectedIds.value.length === 0) { |
| | | return; |
| | | } |
| | | const rows = gridApi.grid.getTableData().fullData as MesPdParameterApi.Parameter[]; |
| | | for (const row of rows) { |
| | | if (row.id === undefined || row.id === null || !preSelectedIds.value.includes(row.id)) { |
| | | continue; |
| | | } |
| | | await gridApi.grid.setCheckboxRow(row, true); |
| | | } |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | /** éç½®æ¥è¯¢åéæ©ç¶æ */ |
| | | async function resetQueryState() { |
| | | selectedRows.value = []; |
| | | await gridApi.grid.clearCheckboxRow(); |
| | | await gridApi.grid.clearCheckboxReserve(); |
| | | await gridApi.formApi.resetForm(); |
| | | } |
| | | |
| | | /** æå¼åæ°éæ©å¼¹çª */ |
| | | async function openModal(selectedIds?: number[]) { |
| | | open.value = true; |
| | | preSelectedIds.value = selectedIds || []; |
| | | await nextTick(); |
| | | await resetQueryState(); |
| | | await gridApi.query(); |
| | | await nextTick(); |
| | | await applyPreSelection(); |
| | | } |
| | | |
| | | /** å
³éåæ°éæ©å¼¹çª */ |
| | | async function closeModal() { |
| | | open.value = false; |
| | | await resetQueryState(); |
| | | } |
| | | |
| | | /** ç¡®è®¤éæ©åæ° */ |
| | | function handleConfirm() { |
| | | const rows = getMultipleSelectedRows(); |
| | | if (rows.length === 0) { |
| | | message.warning('请è³å°éæ©ä¸æ¡æ°æ®'); |
| | | return; |
| | | } |
| | | emit('selected', rows); |
| | | open.value = false; |
| | | } |
| | | |
| | | defineExpose({ open: openModal }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | v-model:open="open" |
| | | title="å·¥èºåæ°éæ©" |
| | | width="70%" |
| | | :destroy-on-close="true" |
| | | @ok="handleConfirm" |
| | | @cancel="closeModal" |
| | | > |
| | | <Grid table-title="å·¥èºåæ°å表" /> |
| | | <template #footer> |
| | | <Button @click="closeModal"> åæ¶ </Button> |
| | | <Button type="primary" @click="handleConfirm"> ç¡®å® </Button> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { defineConfig } from './internal/vite-config/src'; |
| | | import { defineConfig } from "./internal/vite-config/src"; |
| | | |
| | | export default defineConfig(async () => { |
| | | return { |
| | |
| | | server: { |
| | | allowedHosts: true, |
| | | proxy: { |
| | | '/admin-api': { |
| | | "/admin-api": { |
| | | changeOrigin: true, |
| | | rewrite: (path) => path.replace(/^\/admin-api/, ''), |
| | | target: 'http://192.168.0.226:48080/admin-api', |
| | | rewrite: (path) => path.replace(/^\/admin-api/, ""), |
| | | target: "http://192.168.0.244:48080/admin-api", |
| | | ws: true, |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | | }; |
| | | }); |
| | | }); |